<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>재우니 개발자 블로그</title>
    <link>https://aspdotnet.tistory.com/</link>
    <description>프로그램 언어 및  데이터베이스 또는 생활정보까지 다양한 정보 제공 합니다.</description>
    <language>ko</language>
    <pubDate>Mon, 29 Jun 2026 21:06:17 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>재우니</managingEditor>
    <image>
      <title>재우니 개발자 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/347293/attach/301e3e9d9f6f4aa48a9e87d00b8bd34d</url>
      <link>https://aspdotnet.tistory.com</link>
    </image>
    <item>
      <title>Antigravity Awesome Skills 실무 활용 가이드 (Top 20 + 예시)</title>
      <link>https://aspdotnet.tistory.com/3522</link>
      <description>&lt;h1&gt;Antigravity Awesome Skills 실무 활용 가이드 (Top 20 + 예시)&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스킬은 &lt;code&gt;GEMINI.md&lt;/code&gt;의 프로젝트 규칙과 자동 결합됩니다.&lt;br /&gt;아래 모든 예시는 &lt;b&gt;UnivEsGWSolution 기술 스택 기준&lt;/b&gt;으로 작성되었습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; ️ SECTION 1. 개발 및 설계&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. &lt;code&gt;@squirrel&lt;/code&gt; &amp;mdash; 풀사이클 자율 에이전트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계획 &amp;rarr; 구현 &amp;rarr; 테스트 &amp;rarr; 린트 &amp;rarr; 문서화까지 8단계를 자율 수행. 기능 전체를 통째로 위임할 때 사용.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  실무 예시:&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;@squirrel 을 실행해줘.
목표: '직원 연차 관리' 신규 피처를 처음부터 끝까지 만들어줘.
- 10단계 구현 프로세스를 준수할 것
- Domain Entity: VacationRequest (직원ID, 시작일, 종료일, 승인상태)
- Repository: IVacationRequestRepository (Dapper 기반, BaseRepository 상속)
- CQRS: CreateVacationRequestCommand &amp;rarr; ValueTask&amp;lt;Unit&amp;gt; 반환
- Controller: POST /api/vacation/request
- View: Vue 3 CDN IIFE 패턴 적용, v-text/v-on:click 사용 ({{ }} 금지)&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. &lt;code&gt;@api-endpoint-builder&lt;/code&gt; &amp;mdash; API 엔드포인트 자동화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 엔드포인트를 CQRS+Dapper+Mapperly 규격에 맞게 뼈대부터 완성까지 작성.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  실무 예시:&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@api-endpoint-builder 를 사용해서 부서 코드별 직원 목록 조회 엔드포인트를 만들어줘.
- CQRS Nested 구조 (실제 프로젝트 패턴):
    public static class GetEmployeeListByDept {
        public class Query : IQuery&amp;lt;IEnumerable&amp;lt;EmployeeDto&amp;gt;&amp;gt; { ... }
        public class Handler : IQueryHandler&amp;lt;Query, IEnumerable&amp;lt;EmployeeDto&amp;gt;&amp;gt; {
            public ValueTask&amp;lt;IEnumerable&amp;lt;EmployeeDto&amp;gt;&amp;gt; Handle(Query q, CancellationToken ct) { ... }
        }
    }
- Repository: FindByDeptCodeAsync(string deptCode, IDbTransaction? trans = null)
  (BaseRepository&amp;lt;Employee, int, IEmployeeRepository&amp;gt; 상속)
- SQL: GWProjects/DatabaseScripts/SqlQueries/Employee/GetEmployeeListByDept.sql 외부화
  &amp;rarr; ISqlQueryCacheService로 캐시 조회
- Mapperly: GW.Application/Features/HumanResource/{SubFeature}/EmployeeMappings.cs
  (파일명 패턴: {Domain}Mappings.cs, static partial class에 [Mapper] 어트리뷰트)
- Controller: GET /api/employees?deptCode=xxx (Presentation &amp;rarr; Application 계층만 참조)&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. &lt;code&gt;@tdd&lt;/code&gt; &amp;mdash; 테스트 주도 개발&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Red &amp;rarr; Green &amp;rarr; Refactor 사이클로 코드를 안전하게 개발.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  실무 예시:&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;@tdd 를 시작해줘.
기능: VacationRequestRepository의 FindPendingByEmployeeAsync 메서드
1. [Red] 아직 없는 메서드에 대해 실패하는 테스트 먼저 작성
   - Dapper QueryAsync&amp;lt;T&amp;gt;(CommandDefinition) 오버로딩 사용
   - IDbTransaction? trans 파라미터 포함 검증 (Pattern H)
   - 반환: ValueTask&amp;lt;IEnumerable&amp;lt;VacationRequest&amp;gt;&amp;gt;
2. [Green] 최소 코드로 테스트 통과
3. [Refactor] BaseRepository&amp;lt;VacationRequest, int, IVacationRequestRepository&amp;gt; 상속 패턴에 맞게 정리&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. &lt;code&gt;@prototype&lt;/code&gt; &amp;mdash; 빠른 프로토타이핑&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이디어를 검증하기 위한 UI/로직의 빠른 초안 생성.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  실무 예시:&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;@prototype 으로 '협업 문의 등록' 폼 UI 초안을 빠르게 만들어줘.
- Razor View(.cshtml) 기반
- Vue 3 IIFE 패턴, window.*Init으로 서버 데이터 수신
- 필드: 제목(text), 카테고리(Select2), 내용(TUI Editor), 첨부파일(FilePond)
- axios POST 요청으로 /api/coop/inquiry 호출
완성도보다 전체 흐름 파악에 집중해줘.&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. &lt;code&gt;@improve-codebase-architecture&lt;/code&gt; &amp;mdash; 아키텍처 점진적 개선&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레거시 코드를 최신 룰(ValueTask, Mapperly, Scrutor)에 맞게 안전하게 리팩토링.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  실무 예시:&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@improve-codebase-architecture 로 아래 파일을 리팩토링해줘.
파일: GWProjects/GW.Infrastructure/Repositories/Board/OldNoticeRepository.cs
현재 문제:
  - Task&amp;lt;T&amp;gt; 반환 &amp;rarr; ValueTask&amp;lt;T&amp;gt;로 전환
  - AutoMapper 사용 중 &amp;rarr; Application/Features/Board/ 하위 Mapperly static partial class로 교체
  - DI를 직접 new 키워드로 생성
    &amp;rarr; Scrutor 자동 스캔 대상으로 전환 (Name 접미사 &quot;Repository&quot; 조건 충족 시 자동 등록)
    &amp;rarr; 단, SqlQueryCacheService처럼 Singleton이 필요한 경우엔 DependencyInjection.cs 수동 등록
레이어 의존성 방향: Infrastructure &amp;rarr; Domain + Application 참조 (Application만 참조 아님)&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  SECTION 2. 디버깅 및 품질 관리&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. &lt;code&gt;@bug-hunter&lt;/code&gt; &amp;mdash; 체계적인 버그 추적&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;증상 &amp;rarr; 재현 &amp;rarr; 근본 원인 &amp;rarr; 최소 픽스 &amp;rarr; 재발 방지 테스트 순서로 접근.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  실무 예시:&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;@bug-hunter 를 실행해줘.
증상: 전자결재 문서번호 클릭 시 CommonDocumentDetailPopup이
      가끔 빈 화면으로 열림 (재현율 약 30%)
의심 구간:
  - gwApi.get('/api/document/{documentNumber}') 응답 타이밍
  - Vue의 v-if 조건과 데이터 로딩 순서 충돌
재현 환경: Chrome 최신, 세션 로그인 상태 유지 중&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. &lt;code&gt;@diagnosing-bugs&lt;/code&gt; &amp;mdash; 전 레이어 장애 진단&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 axios 호출부터 DB 프로시저까지 전체 스택을 흐름 추적.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  실무 예시:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@diagnosing-bugs 를 시작해줘.
증상: PMS 복합 입력 폼 저장 시 500 Internal Server Error 발생
흐름:
  1. Vue &amp;rarr; axios POST /api/pms/save (payload: 대형 XML 문자열)
  2. Controller &amp;rarr; CreatePmsCommand dispatch
  3. Handler &amp;rarr; IDistributedTransactionService 수동 트랜잭션 시작
  4. ERP Repository &amp;rarr; 프로시저 EXEC [__ERP_DATABASE__].dbo.SP_PMS_SAVE
각 단계별로 실패 가능 지점을 진단하고 확인 체크리스트를 만들어줘.&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8. &lt;code&gt;@performance-optimizer&lt;/code&gt; &amp;mdash; 성능 병목 최적화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성능을 측정하고 Before/After를 비교하며 코드 최적화.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  실무 예시:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@performance-optimizer 를 실행해줘.
문제: 매출 현황 화면에서 RealGrid 3개 인스턴스(연간/월간/일간)가
      동시에 초기화될 때 첫 렌더링이 3~4초 소요됨.
측정 대상:
  - 백엔드: 다중 DB 조인 쿼리 (ISqlQueryCacheService 캐시 적중률 확인)
  - 프론트엔드: Vue mounted() 훅에서 3개 gwApi 동시 호출 vs 순차 호출
  - RealGrid: setDataSource 호출 타이밍과 컬럼 렌더링 순서&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;9. &lt;code&gt;@logic-lens&lt;/code&gt; &amp;mdash; 논리 기반 딥 코드 리뷰&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아키텍처 결함, 상태 관리 오류, 보안 취약점을 논리적으로 심층 분석.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  실무 예시:&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;@logic-lens 로 아래 핸들러를 리뷰해줘.
파일: GWProjects/GW.Application/Features/Coop/Inquiry/CreateInquiry.cs
  (실제 패턴: static class CreateInquiry { public class Command; public class Handler })
집중 검토 항목:
  - Handler 생성자에서 인스턴스 필드로 상태를 저장하지 않는지
    (의존성 주입 필드는 허용, 업무 상태 저장은 금지)
  - GW.Domain.Entities.Coop 모델이 Controller/View로 직접 반환되지 않는지
    (InquiryMappings.cs를 통해 DTO 변환 후 반환해야 함)
  - IDistributedTransactionService trans 파라미터가 모든 Repository 호출에 전파되는지
  - ValueTask&amp;lt;long&amp;gt; 또는 ValueTask&amp;lt;Unit&amp;gt; 반환 시 올바른 반환값 사용 여부
  - Handler가 Scoped로 등록되어 Singleton 의존성을 캡처하지 않는지&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;10. &lt;code&gt;@brooks-lint&lt;/code&gt; &amp;mdash; 클래식 소프트웨어 공학 리뷰&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과도한 결합도, 설계 악취, 단일 책임 원칙 위반 등을 명저 원칙으로 진단.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  실무 예시:&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;@brooks-lint 로 아래 파일의 설계를 검토해줘.
파일: GWProjects/GW.Infrastructure/Repositories/Finance/ErpSalesRepository.cs
  (ErpBaseRepository&amp;lt;TEntity, TKey, TLogger&amp;gt; 상속 클래스)
이 파일이:
  - 단일 책임 원칙(SRP)을 위반하진 않는지
  - ERP 다중 DB 처리를 위해 [__ERP_DATABASE__] 플레이스홀더를
    .sql 파일 안에서만 사용하고 있는지 (인라인 SQL에서 직접 쓰는 것은 금지)
  - 너무 많은 메서드가 한 파일에 집중되어 God Class 문제가 있진 않은지&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;11. &lt;code&gt;@codebase-audit-pre-push&lt;/code&gt; &amp;mdash; 커밋 전 안전 검사&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경 파일을 줄 단위로 검사하여 잘못된 코드가 운영 환경에 배포되는 것을 방지.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  실무 예시:&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;@codebase-audit-pre-push 를 실행해줘.
오늘 변경된 파일 목록 대상으로:
  ✅ Razor .cshtml에서 {{ }}, @click 사용 금지 &amp;rarr; v-text, v-on:click 으로 교체 여부
  ✅ :key 사용 금지 &amp;rarr; v-bind:key 로 교체 여부 (Razor @ 충돌 방지)
  ✅ fetch()/XHR 사용 금지 &amp;rarr; axios/gwApi (timeout:30000, withCredentials:true) 사용 여부
  ✅ console.log, 디버그용 TODO, 임시 하드코딩된 접속 정보 검사
  ✅ new 키워드로 Repository/Handler 직접 생성하는 코드 (Scrutor 위반) 검사
  ✅ using EFCore / MediatR / AutoMapper namespace 사용 여부
  ✅ dayjs import 구문 사용 금지 &amp;rarr; dayjs 전역 변수로만 사용 여부&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⚡ SECTION 3. 생산성 및 컨텍스트 최적화&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;12. &lt;code&gt;@cavecrew&lt;/code&gt; &amp;mdash; 서브 에이전트 분업화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 1~2개 파일 작업이나 위치 탐색을 서브 에이전트에게 위임하여 메인 컨텍스트를 아낌.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  실무 예시:&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;cavecrew investigator에게 위임:
&quot;GWProjects/GW.Infrastructure/Repositories/ 하위에서
 IDistributedTransactionService를 주입받아 사용하는 Repository를
 전부 찾아서 파일명과 메서드명 목록만 반환해줘.&quot;

cavecrew builder에게 위임:
&quot;GWProjects/GW.Domain/Entities/Notice.cs 파일에
 IsDeleted(bool, 기본값 false) 속성 하나만 추가해줘.
 [Table] 어트리뷰트와 Korean XML doc 주석도 기존 스타일에 맞게 달아줘.&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;13. &lt;code&gt;@caveman-review&lt;/code&gt; &amp;mdash; 초압축 코드 리뷰&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PR 리뷰를 장황한 설명 없이 &lt;code&gt;[파일:줄] - [문제] - [해결]&lt;/code&gt; 한 줄로 압축 피드백.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  실무 예시:&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@caveman-review 로 아래 diff를 리뷰해줘.
[Git diff 또는 파일 경로 첨부]

기대 출력 예:
CreateCoopHandler.cs:42 - trans 미전파 &amp;rarr; FindAsync(id, trans) 로 수정
NoticeRepository.cs:87 - Task&amp;lt;T&amp;gt; 반환 &amp;rarr; ValueTask&amp;lt;T&amp;gt; 로 변경
Index.cshtml:34 - {{ title }} 사용 금지 &amp;rarr; v-text=&quot;title&quot; 로 교체&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;14. &lt;code&gt;@caveman-commit&lt;/code&gt; &amp;mdash; 초압축 커밋 메시지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Conventional Commits 준수, 잡음 제거, 50자 이내 핵심만 담은 커밋 메시지 생성.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  실무 예시:&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;@caveman-commit 으로 오늘 작업에 대한 커밋 메시지를 만들어줘.
작업 내용:
  - VacationRequestRepository 신규 추가 (Dapper, BaseRepository 상속)
  - CreateVacationRequestCommand 핸들러 구현 (ValueTask&amp;lt;Unit&amp;gt; 반환)
  - 연차 신청 View 추가 (Vue 3 CDN IIFE 패턴)

기대 출력 예:
feat(vacation): add vacation request CQRS and repository layer&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  SECTION 4. 추적, 관리 및 문서화&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;15. &lt;code&gt;@technical-change-tracker&lt;/code&gt; &amp;mdash; 작업 내역 JSON 트래킹&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세션 종료 전, 작업 이력을 JSON으로 기록하여 다음 세션이 끊김 없이 이어짐.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  실무 예시:&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@technical-change-tracker 를 호출해서 오늘 세션을 닫기 전에
변경 사항을 추적 레코드에 저장해줘.
요약:
  - 신규 파일:
      GWProjects/GW.Domain/Entities/VacationRequest.cs
      GWProjects/GW.Infrastructure/Repositories/Worklife/VacationRequestRepository.cs
      GWProjects/GW.Application/Features/Worklife/Commands/CreateVacationRequestCommandHandler.cs
  - 수정 파일:
      GWProjects/GW.Infrastructure/DependencyInjection.cs
      (Scrutor 자동 스캔으로 처리됨 / SqlQueryCacheService는 Singleton 수동 등록 유지)
  - 미완성: Vacation 승인 핸들러 (다음 세션에서 이어서 작업 예정)
  - 의존성 주의: IDistributedTransactionService trans 파라미터 Repository 전파 미완&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;16. &lt;code&gt;@triage&lt;/code&gt; &amp;mdash; 이슈 및 버그 우선순위 분류&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 오류나 작업 항목을 심각도별로 빠르게 분류하고 대응 순서를 정함.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  실무 예시:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@triage 를 실행해줘.
현재 미처리 이슈 목록:
  A. CommonDocumentDetailPopup 간헐적 빈 화면 (사용자 민원 접수)
  B. RealGrid 개인화 설정이 일부 사용자에서 저장 안 됨
  C. ERP 매출 쿼리 응답 시간 6초 초과
  D. VacationRequest 기능 개발 미완
  E. 코드 리뷰 미반영 피드백 3건
심각도, 영향 범위, 처리 순서 기준으로 분류해줘.&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;17. &lt;code&gt;@agenttrace-session-audit&lt;/code&gt; &amp;mdash; AI 세션 감사 및 비용 분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거 AI 코딩 세션의 토큰 비용 누수, 툴 실패, 비정상 지연을 분석.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  실무 예시:&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;@agenttrace-session-audit 을 실행해줘.
오늘 세션에서:
  - 토큰 사용량이 많았던 툴 호출이 무엇인지
  - 불필요하게 반복 실행된 파일 읽기가 있었는지
  - 실패하거나 타임아웃된 MCP 툴 호출이 있었는지
  - 전체 세션 비용 및 효율성 리포트를 만들어줘.&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;18. &lt;code&gt;@ax-extract-workflow&lt;/code&gt; &amp;mdash; 과거 워크플로우 역추적&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거에 AI가 특정 코드를 어떤 맥락과 단계로 작성했는지 역추적하여 문서로 복원.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  실무 예시:&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;@ax-extract-workflow 를 사용해줘.
지난 세션에서 구현된 gw-dalpha-integration 배치 서비스를
어떤 순서로 설계하고 코딩했는지 워크플로우를 역추적해서
팀 내 온보딩 문서로 재구성해줘.&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  SECTION 5. 확장 및 생태계&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;19. &lt;code&gt;@global-chat-agent-discovery&lt;/code&gt; &amp;mdash; 외부 MCP &amp;amp; 에이전트 검색&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;18,000개 이상의 외부 MCP 서버/AI 에이전트 레지스트리에서 필요한 도구를 검색.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  실무 예시:&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;@global-chat-agent-discovery 로 아래 기능을 해주는 MCP 서버를 찾아줘:
  - AWS SES 이메일 발송 상태를 실시간 모니터링하는 도구
  - SQL Server의 실행 계획(Execution Plan)을 AI가 해석해주는 도구
  - SFTP 파일 수집 배치의 헬스체크를 자동화하는 도구
각 도구의 신뢰도 점수와 설치 방법을 함께 알려줘.&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;20. &lt;code&gt;@skill-check&lt;/code&gt; &amp;mdash; 커스텀 스킬 무결성 검증&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 작성한 SKILL.md 파일이 agentskills 스펙을 올바르게 준수하는지 배포 전에 검증.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  실무 예시:&lt;/p&gt;
&lt;pre class=&quot;taggerscript&quot;&gt;&lt;code&gt;@skill-check 로 내가 새로 만든 스킬 파일을 검증해줘.
파일: d:\UnivEsGWSolution\.agents\skills\gw-new-feature\SKILL.md
확인 항목:
  - YAML frontmatter에 name, description 필드가 있는지
  - 트리거 키워드가 명확하게 정의되어 있는지
  - 지침이 500줄 이내인지 (초과 시 references/ 분리 권고)
  - 구조적 누락 항목이나 네이밍 문제가 없는지&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  빠른 참조 요약 (Quick Reference)&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;스킬&lt;/th&gt;
&lt;th&gt;언제 사용?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@squirrel&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;기능 전체를 처음부터 끝까지 위임&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@api-endpoint-builder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CQRS + Dapper 엔드포인트 신규 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@tdd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;테스트 먼저 쓰고 코드 개발&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@prototype&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;빠른 UI/로직 초안 검증&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@improve-codebase-architecture&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;레거시 코드 현대화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@bug-hunter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;체계적인 버그 추적 및 픽스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@diagnosing-bugs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;전 레이어 장애 흐름 진단&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@performance-optimizer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;쿼리/렌더링 병목 최적화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@logic-lens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;아키텍처 결함 딥 리뷰&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@brooks-lint&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;설계 악취 및 결합도 진단&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@codebase-audit-pre-push&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;커밋 전 안전성 검사&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@cavecrew&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;서브 에이전트에 작업 위임&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@caveman-review&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;초압축 PR 코드 리뷰&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@caveman-commit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Conventional Commits 자동 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@technical-change-tracker&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;세션 종료 전 작업 이력 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@triage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;이슈 심각도 분류 및 우선순위&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@agenttrace-session-audit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AI 세션 비용 및 효율 분석&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@ax-extract-workflow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;과거 워크플로우 역추적 및 문서화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@global-chat-agent-discovery&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;외부 MCP/에이전트 검색&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@skill-check&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;커스텀 스킬 파일 검증&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;a href=&quot;https://github.com/sickn33/antigravity-awesome-skills&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/sickn33/antigravity-awesome-skills&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1782323188585&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - sickn33/antigravity-awesome-skills: Installable GitHub library of 1,600+ agentic skills for Claude Code, Cursor, Codex &quot; data-og-description=&quot;Installable GitHub library of 1,600+ agentic skills for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and more. Includes specialized plugins, installer CLI, bundles, workflows, and offic...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/sickn33/antigravity-awesome-skills&quot; data-og-url=&quot;https://github.com/sickn33/antigravity-awesome-skills&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/EcTx8/dJMb8XSfT2O/3MNu7ONZghJ2Um4HZt1nEk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/kJSlo/dJMb8PGGeQv/7Dukr5MxjWpw82Bm3hMpek/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/X65NJ/dJMb8RR1Yu7/3tKsKIvUVBKhl7hbowE0Q1/img.jpg?width=1500&amp;amp;height=500&amp;amp;face=0_0_1500_500&quot;&gt;&lt;a href=&quot;https://github.com/sickn33/antigravity-awesome-skills&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/sickn33/antigravity-awesome-skills&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/EcTx8/dJMb8XSfT2O/3MNu7ONZghJ2Um4HZt1nEk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/kJSlo/dJMb8PGGeQv/7Dukr5MxjWpw82Bm3hMpek/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/X65NJ/dJMb8RR1Yu7/3tKsKIvUVBKhl7hbowE0Q1/img.jpg?width=1500&amp;amp;height=500&amp;amp;face=0_0_1500_500');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - sickn33/antigravity-awesome-skills: Installable GitHub library of 1,600+ agentic skills for Claude Code, Cursor, Codex&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Installable GitHub library of 1,600+ agentic skills for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and more. Includes specialized plugins, installer CLI, bundles, workflows, and offic...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI 관련/AI 기술</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3522</guid>
      <comments>https://aspdotnet.tistory.com/3522#entry3522comment</comments>
      <pubDate>Thu, 25 Jun 2026 02:32:31 +0900</pubDate>
    </item>
    <item>
      <title>Cursor에서 MSSQL을 AI와 연결하는 가장 쉬운 방법 (Extension + MCP 완벽 가이드)</title>
      <link>https://aspdotnet.tistory.com/3521</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;1. 왜 Cursor와 MSSQL을 연결해야 하는가?&lt;/h1&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;pre class=&quot;properties&quot;&gt;&lt;code&gt;SSMS 실행
 &amp;darr;
테이블 찾기
 &amp;darr;
컬럼 확인
 &amp;darr;
복사
 &amp;darr;
Cursor 붙여넣기
 &amp;darr;
AI 질문
 &amp;darr;
다시 SSMS&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;/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;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수백 개의 테이블&lt;/li&gt;
&lt;li&gt;수천 개의 컬럼&lt;/li&gt;
&lt;li&gt;수많은 FK 관계&lt;/li&gt;
&lt;/ul&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;를 AI에게 매번 설명해야 합니다.&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;반면 MCP를 연결하면 Cursor 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;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 실제 데이터베이스를 이해한 상태에서 코드를 생성하는 환경을 만드는 것이 이번 글의 목표입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;2. Extension과 MCP의 차이를 먼저 이해하자&lt;/h1&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;table data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;기능&lt;/th&gt;
&lt;th&gt;SQL Server Extension&lt;/th&gt;
&lt;th&gt;MCP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;사람이 사용&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI가 사용&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SELECT 실행&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;테이블 탐색&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI 자동 스키마 분석&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entity 생성&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repository 생성&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Extension은&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람이 보는 도구&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP는&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 사용하는 도구&lt;/p&gt;
&lt;/blockquote&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;둘은 경쟁 관계가 아니라 함께 사용하는 것이 가장 좋습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3. SQL Server Extension 설치&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 사람이 직접 DB를 확인할 수 있도록 Extension을 설치합니다.&lt;/p&gt;
&lt;pre class=&quot;dos&quot;&gt;&lt;code&gt;Ctrl + Shift + X&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;SQL Server (mssql)&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Microsoft에서 제공하는 공식 Extension을 설치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가 완료되면 좌측 Activity Bar에 Database 아이콘이 추가됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 새로운 Connection을 생성합니다.&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;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Server
localhost

Authentication
SQL Login

User
sa

Password
********

Database
SampleDB&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결이 완료되면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Tables&lt;/li&gt;
&lt;li&gt;Views&lt;/li&gt;
&lt;li&gt;Stored Procedures&lt;/li&gt;
&lt;li&gt;Functions&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등을 Cursor 내부에서 바로 탐색할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 SSMS를 열지 않아도 대부분의 조회 작업이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;4. 이제 AI가 DB를 읽을 차례이다&lt;/h1&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;여기서부터가 MCP입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Extension은 사람이 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 AI는 Extension을 읽지 못합니다.&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;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;AI
&amp;darr;

MCP

&amp;darr;

SQL Server&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;라는 통로를 만들어야 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;5. Python 설치&lt;/h1&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;먼저 Python이 설치되어 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PowerShell에서 확인합니다.&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;python --version&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;또는&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;py --version&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;버전이 출력되지 않는다면 Python을 먼저 설치해야 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;6. MSSQL MCP 설치&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PowerShell 실행&lt;/p&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;python -m pip install mssql_mcp_server&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;또는&lt;/p&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;py -m pip install mssql_mcp_server&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;설치 확인&lt;/p&gt;
&lt;pre class=&quot;mel&quot;&gt;&lt;code&gt;python -m mssql_mcp_server --help&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;정상이라면 도움말이 출력됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;7. Cursor에 MCP 등록&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cursor&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;Settings

&amp;darr;

MCP&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;.cursor/mcp.json&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;프로젝트에 등록합니다.&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;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;mcpServers&quot;: {
    &quot;mssql&quot;: {
      &quot;command&quot;: &quot;python&quot;,
      &quot;args&quot;: [
        &quot;-m&quot;,
        &quot;mssql_mcp_server&quot;
      ],
      &quot;env&quot;: {
        &quot;DB_SERVER&quot;: &quot;localhost&quot;,
        &quot;DB_NAME&quot;: &quot;SampleDB&quot;,
        &quot;DB_USER&quot;: &quot;sa&quot;,
        &quot;DB_PASSWORD&quot;: &quot;비밀번호&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재(2026년 기준)는 프로젝트별 &lt;code&gt;mcp.json&lt;/code&gt;으로 관리하는 방식이 가장 많이 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git으로 함께 관리할 수도 있어 팀 프로젝트에서도 활용하기 좋습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;8. AI가 실제로 무엇을 할 수 있을까?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP가 연결되면 Cursor는 실제 데이터베이스를 조회할 수 있습니다.&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;pre class=&quot;delphi&quot;&gt;&lt;code&gt;Customers 테이블 구조를 분석해줘.

PK와 FK를 설명해줘.

Entity를 만들어줘.

Dapper Repository를 작성해줘.

JOIN 최적화를 추천해줘.

인덱스 개선점을 알려줘.

사용하지 않는 컬럼을 찾아줘.

이 Stored Procedure를 리팩터링해줘.

ERD를 Mermaid로 그려줘.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 AI에게 스키마를 복사해서 붙여넣을 필요가 없어집니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;9. 실무에서 가장 많이 사용하는 프롬프트&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 아래와 같이 요청할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;현재 등록된 MSSQL MCP를 이용하여 Products, Sales, Customers 테이블 구조를 분석해 주세요.

관계를 먼저 설명한 후

ASP.NET Core 8

Dapper

MediatR

CQRS

기준으로

Entity

Repository

Query Handler

Response DTO

까지 생성해 주세요.

NULL 처리와 decimal 계산은 모두 고려하고

JOIN은 Index를 활용하도록 작성해 주세요.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI 관련/AI 기술</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3521</guid>
      <comments>https://aspdotnet.tistory.com/3521#entry3521comment</comments>
      <pubDate>Tue, 23 Jun 2026 03:12:46 +0900</pubDate>
    </item>
    <item>
      <title>SQL Server 2022 CLE (Column-Level Encryption) 완전 가이드, ENCRYPTBYKEY / DECRYPTBYKEY &amp;mdash; 설치부터 작동까지</title>
      <link>https://aspdotnet.tistory.com/3520</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;SQL Server 2022 CLE (Column-Level Encryption) 완전 가이드&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ENCRYPTBYKEY / DECRYPTBYKEY &amp;mdash; 설치부터 작동까지&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;환경&lt;/b&gt;: SQL Server 2022 (RTM-CU20) 16.0.4205.1 &amp;middot; Standard Edition &amp;middot; Windows Server 2022&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  암호화 계층 구조 (반드시 이 순서)&lt;/h2&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Service Master Key (SMK)       &amp;larr; SQL Server 설치 시 자동 생성 (건드리지 않음)
    └── Database Master Key (DMK)   &amp;larr; 우리가 생성
            └── Certificate          &amp;larr; 우리가 생성
                    └── Symmetric Key &amp;larr; 우리가 생성 (실제 데이터 암/복호화)
                            └── 암호화된 데이터 (VARBINARY)&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL Server 암호화 계층의 최상위는 Windows DPAPI로 보호되는 Service Master Key이며, 그 아래로 Database Master Key, 인증서/비대칭키, 대칭키 순으로 구성됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;STEP 0 &amp;mdash; 사전 확인&lt;/h2&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- 현재 버전 확인
SELECT @@VERSION;
-- 결과 예: Microsoft SQL Server 2022 (RTM-CU20) 16.0.4205.1

-- 현재 사용자 권한 확인 (db_owner 또는 sysadmin 필요)
SELECT IS_SRVROLEMEMBER('sysadmin') AS IsSysAdmin,
       IS_MEMBER('db_owner')        AS IsDbOwner;

-- 기존 DMK 존재 여부 확인
SELECT name, key_length, algorithm_desc, create_date
FROM   sys.symmetric_keys
WHERE  name = '##MS_DatabaseMasterKey##';

-- 기존 인증서 확인
SELECT name, subject, expiry_date, pvt_key_encryption_type_desc
FROM   sys.certificates;

-- 기존 대칭키 확인
SELECT name, key_algorithm, key_length, create_date
FROM   sys.symmetric_keys
WHERE  name != '##MS_DatabaseMasterKey##';&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 조회 결과가 모두 비어있으면 STEP 1부터 진행합니다. 이미 존재하면 해당 STEP을 건너뜁니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;STEP 1 &amp;mdash; 대상 데이터베이스 생성 및 테이블 준비&lt;/h2&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- ① 작업 DB 생성 (이미 있으면 생략)
USE master;
GO

IF DB_ID('EncryptDemo') IS NULL
    CREATE DATABASE EncryptDemo;
GO

USE EncryptDemo;
GO

-- ② 암호화 대상 테이블 생성
--    ENCRYPTBYKEY() 반환 타입은 varbinary(8000) 최대
--    계좌번호 정도면 varbinary(256)으로 충분
IF OBJECT_ID('dbo.CustBankAcc', 'U') IS NOT NULL
    DROP TABLE dbo.CustBankAcc;
GO

CREATE TABLE dbo.CustBankAcc
(
    CustSeq    INT           IDENTITY(1,1) NOT NULL PRIMARY KEY,
    CustName   NVARCHAR(200) NOT NULL,
    -- 암호화 저장 컬럼: AES-256 + 헤더 오버헤드 포함해도 256바이트 충분
    BackAccNo  VARBINARY(256) NULL
);
GO&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주의&lt;/b&gt;: &lt;code&gt;DECRYPTBYKEY&lt;/code&gt;의 반환 타입은 varbinary이며 최대 크기는 &lt;b&gt;8,000바이트&lt;/b&gt;입니다. 컬럼을 &lt;code&gt;VARBINARY(MAX)&lt;/code&gt;로 선언해도 함수 자체 한계는 8,000바이트입니다. 계좌번호처럼 짧은 데이터는 &lt;code&gt;VARBINARY(256)&lt;/code&gt;이 적절합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;STEP 2 &amp;mdash; Database Master Key (DMK) 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DMK는 인증서 및 비대칭키의 개인 키를 보호하는 대칭키입니다. 생성 시 AES_256 알고리즘과 사용자 지정 비밀번호로 암호화되며, 서비스 마스터 키(SMK)에 의해 자동 복호화될 수 있도록 복사본이 master 데이터베이스에도 저장됩니다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;USE EncryptDemo;
GO

-- DMK 생성 (한 DB에 하나만 존재 가능, 이미 있으면 이 단계 건너뜀)
CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'P@ssw0rd!DMK_2025#Str0ng';
GO

-- 생성 확인
SELECT name, algorithm_desc, key_length, create_date, modify_date
FROM   sys.symmetric_keys
WHERE  name = '##MS_DatabaseMasterKey##';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비밀번호 규칙&lt;/b&gt;: Windows 암호 정책 준수 필수 (대/소문자 + 숫자 + 특수문자, 8자 이상)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;STEP 3 &amp;mdash; 인증서 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비밀번호를 별도로 지정하지 않으면 인증서의 개인 키는 Database Master Key로 자동 암호화됩니다. DMK가 없는 상태에서 &lt;code&gt;ENCRYPTION BY PASSWORD&lt;/code&gt;도 생략하면 오류가 발생합니다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;USE EncryptDemo;
GO

-- 인증서 생성 (개인 키는 DMK로 자동 보호됨)
CREATE CERTIFICATE CertForSymKey
    WITH SUBJECT     = 'Certificate for CustBankAcc Column Encryption',
         EXPIRY_DATE = '2030-12-31';  -- 만료일 명시 권장
GO

-- 생성 확인
SELECT name, subject, expiry_date, pvt_key_encryption_type_desc, thumbprint
FROM   sys.certificates
WHERE  name = 'CertForSymKey';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;STEP 4 &amp;mdash; 대칭키 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RC4, RC4_128, DESX 알고리즘은 향후 버전에서 제거될 예정입니다. 신규 개발에서는 반드시 AES 계열 알고리즘을 사용하십시오.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;USE EncryptDemo;
GO

CREATE SYMMETRIC KEY SymKey_CustBankAcc
    WITH ALGORITHM = AES_256             -- 권장 알고리즘
    ENCRYPTION BY CERTIFICATE CertForSymKey;
GO

-- 생성 확인
SELECT name, key_algorithm, key_length, create_date, modify_date
FROM   sys.symmetric_keys
WHERE  name = 'SymKey_CustBankAcc';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;STEP 5 &amp;mdash; 키 오픈 헬퍼 Stored Procedure 생성&lt;/h2&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;USE EncryptDemo;
GO

IF OBJECT_ID('dbo.usp_SymKeyOpen', 'P') IS NOT NULL
    DROP PROCEDURE dbo.usp_SymKeyOpen;
GO

CREATE PROCEDURE dbo.usp_SymKeyOpen
AS
BEGIN
    SET NOCOUNT ON;

    -- 이미 열린 세션이면 OPEN을 재실행해도 무해하지만,
    -- 불필요한 중복 호출을 막기 위해 sys.openkeys 체크
    IF NOT EXISTS (
        SELECT 1
        FROM   sys.openkeys
        WHERE  key_name = 'SymKey_CustBankAcc'
    )
    BEGIN
        OPEN SYMMETRIC KEY SymKey_CustBankAcc
            DECRYPTION BY CERTIFICATE CertForSymKey;
    END
END;
GO&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;STEP 6 &amp;mdash; 암호화 데이터 INSERT&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;ENCRYPTBYKEY&lt;/code&gt;는 authenticator 파라미터를 지원합니다. Authenticator는 암호화된 필드의 값 치환(whole-value substitution) 공격을 방어합니다. 암호화 시 사용한 authenticator와 동일한 값을 복호화 시에도 전달해야 하며, 불일치하면 복호화 결과가 NULL을 반환합니다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;USE EncryptDemo;
GO

-- ① 키 오픈
EXEC dbo.usp_SymKeyOpen;

-- ② 암호화 INSERT
--    add_authenticator = 1, authenticator = CustSeq (행 고유값 바인딩)
--    &amp;rarr; 다른 행의 암호문으로 치환 공격 방어
INSERT INTO dbo.CustBankAcc (CustName, BackAccNo)
VALUES
(
    N'홍길동',
    ENCRYPTBYKEY(
        KEY_GUID('SymKey_CustBankAcc'),   -- 대칭키 GUID
        CONVERT(VARBINARY(256), N'123-456-7890'),  -- 암호화할 평문
        1,                                -- add_authenticator ON
        CONVERT(VARBINARY, NEWID())       -- 임시: 아래 주석 참고
    )
);
GO&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실무 패턴&lt;/b&gt;: INSERT 시에는 &lt;code&gt;CustSeq&lt;/code&gt;가 아직 확정되지 않으므로, authenticator 없이 먼저 INSERT 후 UPDATE로 재암호화하거나, authenticator로 &lt;code&gt;CustName&lt;/code&gt; 같은 불변 컬럼을 사용하는 것이 일반적입니다. 아래는 단순 예제이므로 authenticator 없이 진행합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;-- ① 키 오픈
EXEC dbo.usp_SymKeyOpen;

-- ② authenticator 없이 INSERT (실무에서 가장 많이 쓰는 단순 패턴)
INSERT INTO dbo.CustBankAcc (CustName, BackAccNo)
VALUES
(
    N'홍길동',
    ENCRYPTBYKEY(
        KEY_GUID('SymKey_CustBankAcc'),
        CONVERT(VARBINARY(256), N'123-456-7890')
    )
),
(
    N'김철수',
    ENCRYPTBYKEY(
        KEY_GUID('SymKey_CustBankAcc'),
        CONVERT(VARBINARY(256), N'987-654-3210')
    )
);

-- ③ 키 닫기
CLOSE SYMMETRIC KEY SymKey_CustBankAcc;
GO

-- ④ 암호화 확인 (평문 아닌 바이너리로 저장됨)
SELECT CustSeq, CustName, BackAccNo FROM dbo.CustBankAcc;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;STEP 7 &amp;mdash; 복호화 조회 Stored Procedure 생성&lt;/h2&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;USE EncryptDemo;
GO

IF OBJECT_ID('dbo.usp_GetCustBankAcc', 'P') IS NOT NULL
    DROP PROCEDURE dbo.usp_GetCustBankAcc;
GO

CREATE PROCEDURE dbo.usp_GetCustBankAcc
AS
BEGIN
    SET NOCOUNT ON;

    -- 1. 키 오픈 (같은 세션 내)
    EXEC dbo.usp_SymKeyOpen;

    -- 2. 복호화 조회
    --    DECRYPTBYKEY &amp;rarr; varbinary 반환, CONVERT로 문자열 변환
    SELECT
        CustSeq,
        CustName,
        CONVERT(NVARCHAR(200), DECRYPTBYKEY(BackAccNo)) AS BackAccNo_Plain
    FROM  dbo.CustBankAcc;

    -- 3. 키 닫기 (반드시)
    IF EXISTS (SELECT 1 FROM sys.openkeys WHERE key_name = 'SymKey_CustBankAcc')
        CLOSE SYMMETRIC KEY SymKey_CustBankAcc;
END;
GO

-- 실행 테스트
EXEC dbo.usp_GetCustBankAcc;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;DECRYPTBYKEY&lt;/code&gt;는 대칭키가 열려 있지 않거나 ciphertext가 NULL이면 NULL을 반환합니다. 결과가 NULL이면 키가 닫혀있거나 다른 세션 결과입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;STEP 8 &amp;mdash; UPDATE (암호화 값 수정)&lt;/h2&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;USE EncryptDemo;
GO

-- 키 오픈
EXEC dbo.usp_SymKeyOpen;

-- 계좌번호 업데이트 (재암호화)
UPDATE dbo.CustBankAcc
SET    BackAccNo = ENCRYPTBYKEY(
                      KEY_GUID('SymKey_CustBankAcc'),
                      CONVERT(VARBINARY(256), N'111-222-3333')
                  )
WHERE  CustSeq = 1;

-- 키 닫기
CLOSE SYMMETRIC KEY SymKey_CustBankAcc;
GO&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;STEP 9 &amp;mdash; 백업 (SQL Server 2022 신기능 포함)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL Server 2022에서는 대칭키 직접 백업(&lt;code&gt;BACKUP SYMMETRIC KEY&lt;/code&gt;)이 새로 추가되었습니다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;USE EncryptDemo;
GO

-- [1] DMK 백업 (SMK로 자동 복호화되는 경우 OPEN 생략 가능)
BACKUP MASTER KEY
    TO FILE = 'D:\Backup\EncryptDemo_DMK.bak'
    ENCRYPTION BY PASSWORD = 'P@ssw0rd!DMK_Backup2025#';
GO

-- [2] 인증서 백업 (SQL Server 2022: PFX 포맷 지원)
--     기존 방식 (PVK)
BACKUP CERTIFICATE CertForSymKey
    TO FILE = 'D:\Backup\CertForSymKey.cer'
    WITH PRIVATE KEY (
        FILE       = 'D:\Backup\CertForSymKey.pvk',
        ENCRYPTION BY PASSWORD = 'P@ssw0rd!Cert_Backup2025#'
    );
GO&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL Server 2022에서는 인증서와 개인 키를 PFX 포맷(&lt;code&gt;.pfx&lt;/code&gt;)으로 단일 파일에 백업하는 기능이 추가되었습니다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- SQL Server 2022 신기능: PFX 형식 백업 (인증서 + 개인키 한 파일)
BACKUP CERTIFICATE CertForSymKey
    TO FILE = 'D:\Backup\CertForSymKey.pfx'
    WITH FORMAT = 'PFX',
         PRIVATE KEY (
             FILE       = 'D:\Backup\CertForSymKey.pfx',
             ENCRYPTION BY PASSWORD = 'P@ssw0rd!Cert_PFX2025#'
         );
GO&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL Server 2022는 대칭키를 파일 또는 Azure Blob Storage로 직접 내보내고 가져오는 기능을 새로 지원합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;-- [3] 대칭키 직접 백업 (SQL Server 2022 신기능)
BACKUP SYMMETRIC KEY SymKey_CustBankAcc
    TO FILE = 'D:\Backup\SymKey_CustBankAcc.bak'
    ENCRYPTION BY PASSWORD = 'P@ssw0rd!SymKey_Backup2025#';
GO&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;STEP 10 &amp;mdash; C# / Dapper 연동 (.NET 8)&lt;/h2&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// ✅ .NET 8 기준: Microsoft.Data.SqlClient 사용
// NuGet: Install-Package Microsoft.Data.SqlClient
// NuGet: Install-Package Dapper

using Dapper;
using Microsoft.Data.SqlClient;  // &amp;larr; .NET 8에서는 반드시 이것
using System.Collections.Generic;
using System.Threading.Tasks;

public class CustBankAccDto
{
    public int    CustSeq         { get; set; }
    public string CustName        { get; set; } = string.Empty;
    public string BackAccNo_Plain { get; set; } = string.Empty;  // 복호화된 값
}

public class CustBankAccRepository
{
    private readonly string _connectionString;

    public CustBankAccRepository(string connectionString)
    {
        _connectionString = connectionString;
    }

    /// &amp;lt;summary&amp;gt;
    /// 암호화된 계좌번호 전체 조회 (Stored Procedure 방식 &amp;mdash; 권장)
    /// &amp;lt;/summary&amp;gt;
    public async Task&amp;lt;IEnumerable&amp;lt;CustBankAccDto&amp;gt;&amp;gt; GetAllAsync()
    {
        // using: 연결 종료 시 자동으로 커넥션 풀에 반환
        await using var conn = new SqlConnection(_connectionString);
        await conn.OpenAsync();

        // SP 내부에서 OPEN &amp;rarr; SELECT &amp;rarr; CLOSE 처리
        return await conn.QueryAsync&amp;lt;CustBankAccDto&amp;gt;(
            &quot;dbo.usp_GetCustBankAcc&quot;,
            commandType: System.Data.CommandType.StoredProcedure
        );
    }

    /// &amp;lt;summary&amp;gt;
    /// 인라인 SQL 방식 (같은 연결에서 키 오픈 &amp;rarr; 쿼리 &amp;rarr; 키 닫기)
    /// 커넥션 풀 재사용 시 키가 열린 채로 남지 않도록 finally에서 반드시 CLOSE
    /// &amp;lt;/summary&amp;gt;
    public async Task&amp;lt;IEnumerable&amp;lt;CustBankAccDto&amp;gt;&amp;gt; GetAllInlineAsync()
    {
        await using var conn = new SqlConnection(_connectionString);
        await conn.OpenAsync();

        try
        {
            // ① 같은 연결(세션)에서 키 오픈
            await conn.ExecuteAsync(&quot;EXEC dbo.usp_SymKeyOpen&quot;);

            // ② 복호화 조회
            const string sql = @&quot;
                SELECT
                    CustSeq,
                    CustName,
                    CONVERT(NVARCHAR(200), DECRYPTBYKEY(BackAccNo)) AS BackAccNo_Plain
                FROM dbo.CustBankAcc;
            &quot;;

            return await conn.QueryAsync&amp;lt;CustBankAccDto&amp;gt;(sql);
        }
        finally
        {
            // ③ 반드시 키 닫기 &amp;mdash; 예외 발생 시에도 보장
            //    sys.openkeys로 현재 세션 열린 키 체크 후 닫기
            try
            {
                await conn.ExecuteAsync(@&quot;
                    IF EXISTS (
                        SELECT 1 FROM sys.openkeys
                        WHERE key_name = 'SymKey_CustBankAcc'
                    )
                        CLOSE SYMMETRIC KEY SymKey_CustBankAcc;
                &quot;);
            }
            catch
            {
                // 닫기 실패는 로그만 남기고 무시
                // (연결 자체가 끊기면 세션 종료로 키도 자동 닫힘)
            }
        }
    }

    /// &amp;lt;summary&amp;gt;
    /// 신규 계좌번호 INSERT
    /// &amp;lt;/summary&amp;gt;
    public async Task InsertAsync(string custName, string backAccNo)
    {
        await using var conn = new SqlConnection(_connectionString);
        await conn.OpenAsync();

        try
        {
            await conn.ExecuteAsync(&quot;EXEC dbo.usp_SymKeyOpen&quot;);

            const string sql = @&quot;
                INSERT INTO dbo.CustBankAcc (CustName, BackAccNo)
                VALUES (
                    @CustName,
                    ENCRYPTBYKEY(
                        KEY_GUID('SymKey_CustBankAcc'),
                        CONVERT(VARBINARY(256), @BackAccNo)
                    )
                );
            &quot;;

            await conn.ExecuteAsync(sql, new { CustName = custName, BackAccNo = backAccNo });
        }
        finally
        {
            try
            {
                await conn.ExecuteAsync(@&quot;
                    IF EXISTS (SELECT 1 FROM sys.openkeys WHERE key_name = 'SymKey_CustBankAcc')
                        CLOSE SYMMETRIC KEY SymKey_CustBankAcc;
                &quot;);
            }
            catch { }
        }
    }
}&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;/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;DI 등록 (&lt;code&gt;Program.cs&lt;/code&gt;)&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;// appsettings.json의 연결 문자열
builder.Services.AddScoped&amp;lt;CustBankAccRepository&amp;gt;(sp =&amp;gt;
{
    var connStr = builder.Configuration.GetConnectionString(&quot;DefaultConnection&quot;)!;
    return new CustBankAccRepository(connStr);
});&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;appsettings.json&lt;/code&gt;&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;ConnectionStrings&quot;: {
    &quot;DefaultConnection&quot;: &quot;Server=YOUR_SERVER;Database=EncryptDemo;User Id=YOUR_ID;Password=YOUR_PW;TrustServerCertificate=True;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;STEP 11 &amp;mdash; 검증 쿼리 모음&lt;/h2&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;USE EncryptDemo;
GO

-- [1] 암호화 계층 전체 확인
SELECT name, key_algorithm, key_length, create_date
FROM   sys.symmetric_keys;

SELECT name, subject, expiry_date, pvt_key_encryption_type_desc
FROM   sys.certificates;

-- [2] 현재 세션에서 열린 키 확인
SELECT key_name, algorithm, key_length
FROM   sys.openkeys;

-- [3] 복호화 동작 단위 테스트
EXEC dbo.usp_SymKeyOpen;

SELECT
    CustSeq,
    CustName,
    BackAccNo                                                   AS 암호화값,
    CONVERT(NVARCHAR(200), DECRYPTBYKEY(BackAccNo))             AS 복호화값,
    DATALENGTH(BackAccNo)                                       AS 암호화바이트수
FROM dbo.CustBankAcc;

CLOSE SYMMETRIC KEY SymKey_CustBankAcc;

-- [4] 키 닫힌 상태에서 복호화 시도 &amp;rarr; NULL 반환 확인
SELECT
    CustSeq,
    CONVERT(NVARCHAR(200), DECRYPTBYKEY(BackAccNo)) AS 반드시NULL이어야함
FROM dbo.CustBankAcc;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;STEP 12 &amp;mdash; 전체 정리 및 실무 주의사항&lt;/h2&gt;
&lt;table style=&quot;width: 753px;&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;width: 160px;&quot;&gt;항목&lt;/th&gt;
&lt;th style=&quot;width: 593px;&quot;&gt;내용&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 160px;&quot;&gt;&lt;b&gt;컬럼 타입&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 593px;&quot;&gt;&lt;code&gt;VARBINARY(256)&lt;/code&gt; 권장 (함수 최대 반환값 8,000바이트)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 160px;&quot;&gt;&lt;b&gt;알고리즘&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 593px;&quot;&gt;&lt;code&gt;AES_256&lt;/code&gt; 사용 (RC4/RC4_128/DESX 사용 금지)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 160px;&quot;&gt;&lt;b&gt;키 오픈 범위&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 593px;&quot;&gt;같은 세션(연결) 내에서만 유효&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 160px;&quot;&gt;&lt;b&gt;커넥션 풀&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 593px;&quot;&gt;&lt;code&gt;finally&lt;/code&gt;에서 반드시 &lt;code&gt;CLOSE&lt;/code&gt; 보장 (&lt;code&gt;sys.openkeys&lt;/code&gt; 체크 후)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 160px;&quot;&gt;&lt;b&gt;NULL 반환&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 593px;&quot;&gt;키가 닫힌 상태에서 &lt;code&gt;DECRYPTBYKEY&lt;/code&gt;는 NULL 반환 (에러 아님)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 160px;&quot;&gt;&lt;b&gt;VIEW에서&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 593px;&quot;&gt;&lt;code&gt;OPEN SYMMETRIC KEY&lt;/code&gt; 불가, SP 또는 호출자에서 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 160px;&quot;&gt;&lt;b&gt;백업 순서&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 593px;&quot;&gt;DMK &amp;rarr; 인증서 &amp;rarr; 대칭키 순으로 백업, 별도 안전한 위치에 보관&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 160px;&quot;&gt;&lt;b&gt;SQL 2022 신기능&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 593px;&quot;&gt;&lt;code&gt;BACKUP SYMMETRIC KEY&lt;/code&gt; 직접 지원, 인증서 PFX 포맷 백업&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 160px;&quot;&gt;&lt;b&gt;.NET 8 패키지&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 593px;&quot;&gt;&lt;code&gt;Microsoft.Data.SqlClient&lt;/code&gt; (구 &lt;code&gt;System.Data.SqlClient&lt;/code&gt; 사용 금지)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 160px;&quot;&gt;&lt;b&gt;인증서 만료&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 593px;&quot;&gt;&lt;code&gt;EXPIRY_DATE&lt;/code&gt; 명시 권장, 만료돼도 암/복호화 동작은 유지됨&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>데이터베이스/MS-SQL  </category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3520</guid>
      <comments>https://aspdotnet.tistory.com/3520#entry3520comment</comments>
      <pubDate>Thu, 18 Jun 2026 20:14:41 +0900</pubDate>
    </item>
    <item>
      <title>Windows에서 Git 설치 및 GitHub 연동 가이드 (2026년 최신판)</title>
      <link>https://aspdotnet.tistory.com/3519</link>
      <description>&lt;h1&gt;Windows에서 Git 설치 및 GitHub 연동 가이드 (2026년 최신판)&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2026년 기준 실무 환경을 반영한 Windows 사용자 입문 가이드입니다.&lt;br /&gt;GitHub 인증(PAT), SSH, Git Credential Manager, ARM64 지원까지 포함합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;대상 독자&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Git을 처음 설치하는 개발자&lt;/li&gt;
&lt;li&gt;GitHub 연동이 처음인 사용자&lt;/li&gt;
&lt;li&gt;주니어 개발자 및 학생&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Git이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git은 소스 코드의 변경 이력을 관리하는 &lt;b&gt;분산 버전 관리 시스템(VCS, Version Control System)&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git을 사용하면 다음이 가능합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드 변경 이력 관리&lt;/li&gt;
&lt;li&gt;여러 개발자와 협업&lt;/li&gt;
&lt;li&gt;이전 버전 복구&lt;/li&gt;
&lt;li&gt;브랜치 기반 기능 개발&lt;/li&gt;
&lt;li&gt;GitHub, GitLab 등과 연동&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 설치 전 준비사항&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;권장 환경&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Windows 11&lt;/li&gt;
&lt;li&gt;Windows 10 (최신 업데이트 적용)&lt;/li&gt;
&lt;li&gt;인터넷 연결&lt;/li&gt;
&lt;li&gt;관리자 권한 계정&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Git 다운로드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;공식 사이트&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;https://git-scm.com&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;&lt;b&gt;2026년 6월 기준 최신 안정 버전&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Git for Windows 2.54.0 (2026-04-20 릴리즈)&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;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최신 버전은 공식 사이트에서 항상 확인하세요.&lt;/p&gt;
&lt;/blockquote&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;table style=&quot;height: 112px;&quot; width=&quot;783&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;환경&lt;/th&gt;
&lt;th&gt;파일명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;일반 PC (Intel/AMD)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Git-2.54.0-64-bit.exe&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ARM64 기기 (Surface Pro 11 등)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Git-2.54.0-arm64.exe&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Windows on ARM 기기(Surface Pro 11, Snapdragon 탑재 노트북 등)는 ARM64 빌드를 사용하면 성능이 향상됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Git 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다운로드한 설치 파일을 실행합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;권장 설치 옵션&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Select Components&lt;/h4&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;✅ Add a Git Bash Profile to Windows Terminal  &amp;larr; 2026년 권장 추가 옵션&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;Windows Terminal에서 탭 방식으로 Git Bash를 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Default Editor&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;권장&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;Visual Studio Code&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;&lt;b&gt;대안&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;brainfuck&quot;&gt;&lt;code&gt;Notepad++
Vim
Nano&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Initial Branch Name&lt;/h4&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Override the default branch name for new repositories&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력값&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;main&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;PATH 환경 변수&lt;/h4&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;Git from the command line and also from 3rd-party software&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;SSH Executable&lt;/h4&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;Use bundled OpenSSH&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;HTTPS Transport Backend&lt;/h4&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;Use the OpenSSL library&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Line Ending Conversion&lt;/h4&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;Checkout Windows-style, commit Unix-style line endings&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Terminal Emulator&lt;/h4&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;Use MinTTY&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;추가 옵션&lt;/h4&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;✅ Enable file system caching
✅ Enable Git Credential Manager&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 완료 후 &lt;b&gt;Install&lt;/b&gt; 클릭.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 설치 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git Bash 또는 PowerShell을 실행합니다.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;git --version&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력 예시&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;git version 2.54.0.windows.1&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 사용자 정보 설정&lt;/h2&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;git config --global user.name &quot;홍길동&quot;
git config --global user.email &quot;your_email@example.com&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 확인&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;git config --global --list&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. GitHub 계정 준비&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GitHub 계정이 없다면 생성합니다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;https://github.com&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. GitHub 인증 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2021년 이후 GitHub는 비밀번호 인증을 지원하지 않습니다.&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;table style=&quot;height: 158px;&quot; width=&quot;813&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;방식&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;th&gt;추천 대상&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HTTPS + PAT&lt;/td&gt;
&lt;td&gt;가장 쉬운 방법&lt;/td&gt;
&lt;td&gt;입문자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSH Key&lt;/td&gt;
&lt;td&gt;실무 표준&lt;/td&gt;
&lt;td&gt;개발자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Git Credential Manager&lt;/td&gt;
&lt;td&gt;GCM 자동 처리&lt;/td&gt;
&lt;td&gt;Windows 사용자 전반&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. Git Credential Manager (GCM)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git for Windows에는 &lt;b&gt;Git Credential Manager(GCM)&lt;/b&gt;가 기본 포함됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최초 Push 시 GitHub 로그인 창이 표시됩니다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Access Token 저장&lt;/li&gt;
&lt;li&gt;이후 자동 인증&lt;/li&gt;
&lt;li&gt;재로그인 최소화&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비밀번호 입력 불필요&lt;/li&gt;
&lt;li&gt;보안성 향상&lt;/li&gt;
&lt;li&gt;GitHub 공식 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. SSH Key 생성 (권장)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SSH Key 생성&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;ssh-keygen -t ed25519 -C &quot;your_email@example.com&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;&lt;b&gt;생성 위치&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;taggerscript&quot;&gt;&lt;code&gt;C:\Users\사용자명\.ssh&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;&lt;b&gt;공개키 확인&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;cat ~/.ssh/id_ed25519.pub&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;출력된 내용을 GitHub에 등록합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GitHub 등록 경로&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;Settings
 &amp;rarr; SSH and GPG Keys
 &amp;rarr; New SSH Key&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;11. 저장소 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;작업 폴더 생성&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;taggerscript&quot;&gt;&lt;code&gt;C:\Projects\MyApp&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;&lt;b&gt;Git 초기화&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;git init&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;출력&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;Initialized empty Git repository&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;12. 파일 추가 및 커밋&lt;/h2&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;# 파일 추가
git add .

# 상태 확인
git status

# 커밋 생성
git commit -m &quot;Initial Commit&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;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;13. GitHub 저장소 연결&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GitHub에서 새 저장소를 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HTTPS 방식&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;git remote add origin https://github.com/사용자명/저장소명.git&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;&lt;b&gt;SSH 방식&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;git remote add origin git@github.com:사용자명/저장소명.git&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;확인&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;git remote -v&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;14. 최초 업로드&lt;/h2&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;git push -u origin main&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;이후부터는&lt;/p&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;git push&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;15. 자주 사용하는 명령어&lt;/h2&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;# 현재 상태 확인
git status

# 커밋 이력 확인
git log --oneline

# 원격 저장소 다운로드
git clone 저장소주소

# 최신 변경 내용 가져오기
git pull

# 원격 저장소 업로드
git push&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;16. 브랜치 사용&lt;/h2&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;# 브랜치 생성
git branch feature/login

# 브랜치 이동
git switch feature/login

# 브랜치 생성과 이동 (한 번에)
git switch -c feature/login

# 브랜치 목록 확인
git branch&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;17. 권장 브랜치 전략&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개인 프로젝트&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;main&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;&lt;b&gt;팀 프로젝트&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;main
develop
feature/*
hotfix/*&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;&lt;b&gt;브랜치 이름 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;feature/login
feature/member-api
feature/order&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;18. GitHub Desktop vs Git Bash&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;Git Bash&lt;/th&gt;
&lt;th&gt;GitHub Desktop&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;난이도&lt;/td&gt;
&lt;td&gt;높음&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;기능&lt;/td&gt;
&lt;td&gt;전체&lt;/td&gt;
&lt;td&gt;대부분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;실무 활용&lt;/td&gt;
&lt;td&gt;매우 높음&lt;/td&gt;
&lt;td&gt;보통&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;학습 가치&lt;/td&gt;
&lt;td&gt;높음&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초보자 &amp;rarr; GitHub Desktop&lt;/li&gt;
&lt;li&gt;개발자 &amp;rarr; Git Bash&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;19. 자주 발생하는 문제&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용자 정보 누락&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;git config --global --list&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;확인 후 수정합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;줄바꿈 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 권장 옵션을 유지합니다.&lt;/p&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;Checkout Windows-style, commit Unix-style line endings&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인증 오류&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;원인&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PAT 만료&lt;/li&gt;
&lt;li&gt;GitHub 로그인 변경&lt;/li&gt;
&lt;li&gt;SSH Key 미등록&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;dos&quot;&gt;&lt;code&gt;git credential-manager erase&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;이후 재로그인 진행합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;20. Winget으로 설치 및 업데이트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;설치&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;brainfuck&quot;&gt;&lt;code&gt;winget install --id Git.Git -e --source winget&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;&lt;b&gt;업데이트&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;winget upgrade --id Git.Git -e --source winget&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;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--id 플래그와 -e(exact match), --source winget 옵션을 함께 사용하는 것이 공식 권장 방식입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git 설치 후 다음 순서로 연습해 보세요.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Git 설치 (x64 또는 ARM64 선택)&lt;/li&gt;
&lt;li&gt;사용자 정보 등록&lt;/li&gt;
&lt;li&gt;GitHub 계정 생성&lt;/li&gt;
&lt;li&gt;Git Credential Manager 로그인 또는 SSH Key 등록&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git init&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git add .&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git commit&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git push&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2026년 기준 실무 표준 정리&lt;/h2&gt;
&lt;table style=&quot;height: 266px;&quot; width=&quot;799&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;2026년 표준&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;인증 방식&lt;/td&gt;
&lt;td&gt;HTTPS + PAT 또는 SSH&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;자격 증명 관리&lt;/td&gt;
&lt;td&gt;Git Credential Manager&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;기본 브랜치명&lt;/td&gt;
&lt;td&gt;&lt;code&gt;main&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;아키텍처&lt;/td&gt;
&lt;td&gt;x64 또는 ARM64 네이티브&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;터미널&lt;/td&gt;
&lt;td&gt;Windows Terminal + Git Bash 프로필&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;설치 관리&lt;/td&gt;
&lt;td&gt;&lt;code&gt;winget&lt;/code&gt; 권장&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 가이드는 2026년 6월 기준으로 작성되었습니다.&lt;br /&gt;최신 Git 버전 및 GitHub 정책 변경 사항은 &lt;a href=&quot;https://git-scm.com&quot;&gt;git-scm.com&lt;/a&gt; 및 &lt;a href=&quot;https://docs.github.com&quot;&gt;docs.github.com&lt;/a&gt;에서 확인하세요.&lt;/p&gt;
&lt;/blockquote&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍/GIT</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3519</guid>
      <comments>https://aspdotnet.tistory.com/3519#entry3519comment</comments>
      <pubDate>Thu, 11 Jun 2026 19:44:34 +0900</pubDate>
    </item>
    <item>
      <title>CURSOR : chat 창 하단에 사용량 퍼센트 % 보여주기 (usage summary)</title>
      <link>https://aspdotnet.tistory.com/3518</link>
      <description>&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;Agents &amp;gt; usage summary 탭에서 &quot;always&quot; 로 변경하시면 agent 채팅창 하단에 사용량 % 보여줍니다.&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;839&quot; data-origin-height=&quot;651&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bg1tKK/dJMcagFShoO/xbY8xUUdlFXXDfOwxaCZ41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bg1tKK/dJMcagFShoO/xbY8xUUdlFXXDfOwxaCZ41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bg1tKK/dJMcagFShoO/xbY8xUUdlFXXDfOwxaCZ41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbg1tKK%2FdJMcagFShoO%2FxbY8xUUdlFXXDfOwxaCZ41%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;839&quot; height=&quot;651&quot; data-origin-width=&quot;839&quot; data-origin-height=&quot;651&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;&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;297&quot; data-origin-height=&quot;403&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JKuWj/dJMcafNJ1b9/orR5J5xEEJUXJEfCUosdi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JKuWj/dJMcafNJ1b9/orR5J5xEEJUXJEfCUosdi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JKuWj/dJMcafNJ1b9/orR5J5xEEJUXJEfCUosdi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJKuWj%2FdJMcafNJ1b9%2ForR5J5xEEJUXJEfCUosdi0%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;297&quot; height=&quot;403&quot; data-origin-width=&quot;297&quot; data-origin-height=&quot;403&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;&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;1140&quot; data-origin-height=&quot;669&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pzqC9/dJMcajvMnUA/HGzhIwzZKaeVq81JRkV7nK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pzqC9/dJMcajvMnUA/HGzhIwzZKaeVq81JRkV7nK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pzqC9/dJMcajvMnUA/HGzhIwzZKaeVq81JRkV7nK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpzqC9%2FdJMcajvMnUA%2FHGzhIwzZKaeVq81JRkV7nK%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;1140&quot; height=&quot;669&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;669&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>AI 관련/AI 기술</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3518</guid>
      <comments>https://aspdotnet.tistory.com/3518#entry3518comment</comments>
      <pubDate>Thu, 11 Jun 2026 11:01:24 +0900</pubDate>
    </item>
    <item>
      <title>[보안 경고] 우리가 매일 쓰는 웹앱이 위험하다? Axios 보안 취약점과 대처법 (CVE-2026-44495)</title>
      <link>https://aspdotnet.tistory.com/3517</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;[보안 경고] 우리가 매일 쓰는 웹앱이 위험하다? Axios 보안 취약점과 대처법 (CVE-2026-44495)&lt;/h1&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;lsquo;Axios(엑시오스)&amp;rsquo;인데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 이 Axios에서 로그인 정보나 비밀번호 같은 민감한 개인정보를 중간에서 가로챌 수 있는 심각한 보안 구멍(취약점)이 발견되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 관리하는 사이트나 서비스가 있다면, 혹은 IT 상식을 넓히고 싶다면 이번 글을 주목해 주세요. 최대한 이해하기 쉽게 원인과 해결책을 정리해 드립니다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 이번 보안 문제, 한눈에 보기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;문제의 이름&lt;/b&gt;: GHSA-3g43-6gmg-66jw / CVE-2026-44495&lt;/li&gt;
&lt;li&gt;&lt;b&gt;위험도&lt;/b&gt;: &lt;b&gt;매우 높음 (High)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;영향을 받는 대상&lt;/b&gt;: Axios 프로그램을 사용하는 모든 웹사이트, 앱, 서버 시스템&lt;/li&gt;
&lt;li&gt;&lt;b&gt;한 줄 요약&lt;/b&gt;: 해커가 가짜 '배달 규칙'을 심어두면, Axios가 그 규칙에 속아 &lt;b&gt;사용자의 로그인 토큰이나 비밀번호를 해커에게 배달&lt;/b&gt;할 수 있는 취약점입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 원인: 어떤 원리로 정보가 털릴까? (일상 비유)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터 프로그래밍에는 &amp;lsquo;프로토타입(Prototype)&amp;rsquo;이라는 개념이 있습니다. 쉽게 말해 &lt;b&gt;&quot;모든 물건이 기본적으로 가지고 태어나는 공통 규칙&quot;&lt;/b&gt; 같은 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, &quot;모든 택배 상자에는 주소가 적혀 있어야 하고, 뜯어서 내용물을 확인한다&quot;라는 공통 규칙이 전산망에 등록되어 있다고 해봅시다.&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;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;기본 규칙 조작 (오염)&lt;/b&gt;: 해커가 보안이 허술한 다른 틈새를 타서, 컴퓨터 시스템의 전역 기본 규칙을 슬쩍 바꿉니다. *&quot;앞으로 모든 택배 상자는 내용물을 확인하기 전에, 해커 방으로 복사본을 먼저 한 장 보낸다&quot;*라는 가짜 규칙을 심는 거죠.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Axios의 착각&lt;/b&gt;: 데이터를 배달하던 Axios는 배달을 마치고 돌아와 &quot;서버에서 받은 데이터를 어떻게 처리하지?&quot; 하고 기본 규칙을 조회합니다. 이때 해커가 조작해 둔 가짜 규칙을 그대로 믿고 실행해 버립니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;정보 유출&lt;/b&gt;: 결과적으로 사용자가 로그인할 때 입력한 비밀번호, 인증 토큰, 혹은 금융 데이터 같은 중요한 정보의 복사본이 해커의 서버로 고스란히 넘어가게 됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  쉽게 말해: Axios 자체가 나쁜 마음을 먹은 것은 아닙니다. 하지만 해커가 동네 전산망의 '기본 규칙'을 오염시켜 놓았을 때, 그것이 가짜인지 의심하지 않고 넙죽 믿어버리는 허점이 있었던 것입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 우리 서비스에는 어떤 피해가 올까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이 취약점을 방치하면 다음과 같은 심각한 문제가 생길 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;비밀번호 및 토큰 유출&lt;/b&gt;: 사용자가 로그인 상태를 유지하기 위해 쓰는 '인증 토큰'이나 API 키가 해커에게 털릴 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 가로채기&lt;/b&gt;: 서버가 회원에게 보내주는 정상적인 응답 데이터를 해커가 중간에서 훔쳐볼 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서비스 마비&lt;/b&gt;: 해커가 규칙을 아예 망가뜨려 놓으면, 웹사이트에서 데이터를 불러오지 못하고 계속 에러가 나면서 먹통이 될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 어떻게 해결해야 하나요? (조치 가이드)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히 이 문제를 발견한 보안 전문가들과 Axios 개발팀이 힘을 합쳐 &lt;b&gt;문제를 해결한 안전한 버전&lt;/b&gt;을 새로 만들어 배포했습니다. 개발자분들은 아래 방법으로 즉시 조치해야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;방법 1: 가장 확실한 방법, 'Axios 업그레이드'&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터나 스마트폰 앱을 업데이트하듯, 프로젝트에 사용된 Axios의 버전을 최신 버전(&lt;code&gt;1.15.2&lt;/code&gt; 이상)으로 업데이트하면 됩니다. 업데이트된 버전은 해커가 심어둔 가짜 기본 규칙을 알아서 무시하도록 똑똑해졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널(명령창)을 열고 사용 중인 도구에 맞춰 아래 명령어를 입력하세요.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;# npm을 쓰는 경우
npm install axios@latest

# yarn을 쓰는 경우
yarn add axios@latest
&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;방법 2: 강제로 버전 고정하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 내가 만든 코드가 아니라 다른 회사에서 만든 임의의 플러그인 내부에서 구버전 Axios를 끌어다 쓰고 있다면, &lt;code&gt;package.json&lt;/code&gt; 파일에 아래 내용을 추가해 시스템 전체가 안전한 버전을 강제로 쓰도록 고정해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;npm 환경일 때 예시:&lt;/b&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;overrides&quot;: {
&quot;axios&quot;: &quot;^1.15.2&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;http&quot;&gt;&lt;code&gt;

* **yarn 환경일 때 예시:**
```json
&quot;resolutions&quot;: {
  &quot;axios&quot;: &quot;^1.15.2&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 결론&lt;/h2&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 관리하고 계신 웹 서비스나 사내 시스템의 &lt;code&gt;Axios&lt;/code&gt; 버전을 꼭 확인해 보시고, &lt;b&gt;&lt;code&gt;1.15.2&lt;/code&gt; 이상&lt;/b&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;a href=&quot;https://github.com/advisories/GHSA-3g43-6gmg-66jw/dependabot&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/advisories/GHSA-3g43-6gmg-66jw/dependabot&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1781058349807&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;CVE-2026-44495 - GitHub Advisory Database&quot; data-og-description=&quot;axios Vulnerable to Credential Theft and Response Hijacking via Prototype Pollution Gadget in Config Merge&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/advisories/GHSA-3g43-6gmg-66jw/dependabot&quot; data-og-url=&quot;https://github.com/advisories/GHSA-3g43-6gmg-66jw/dependabot&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Am74g/dJMb9dHwhk3/WiXQJjb7PG89KxjWAVBbPk/img.png?width=240&amp;amp;height=240&amp;amp;face=0_0_240_240,https://scrap.kakaocdn.net/dn/SUUQK/dJMb9iIOZOr/DtKoOqFmLPKtSwnmdvjv61/img.png?width=240&amp;amp;height=240&amp;amp;face=0_0_240_240&quot;&gt;&lt;a href=&quot;https://github.com/advisories/GHSA-3g43-6gmg-66jw/dependabot&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/advisories/GHSA-3g43-6gmg-66jw/dependabot&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Am74g/dJMb9dHwhk3/WiXQJjb7PG89KxjWAVBbPk/img.png?width=240&amp;amp;height=240&amp;amp;face=0_0_240_240,https://scrap.kakaocdn.net/dn/SUUQK/dJMb9iIOZOr/DtKoOqFmLPKtSwnmdvjv61/img.png?width=240&amp;amp;height=240&amp;amp;face=0_0_240_240');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;CVE-2026-44495 - GitHub Advisory Database&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;axios Vulnerable to Credential Theft and Response Hijacking via Prototype Pollution Gadget in Config Merge&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>생활정보  </category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3517</guid>
      <comments>https://aspdotnet.tistory.com/3517#entry3517comment</comments>
      <pubDate>Wed, 10 Jun 2026 11:26:53 +0900</pubDate>
    </item>
    <item>
      <title>AI 에이전트를 시니어 개발자로 만드는 법: agent-skills + Cursor 세팅 가이드</title>
      <link>https://aspdotnet.tistory.com/3515</link>
      <description>&lt;h1&gt;AI 에이전트를 시니어 개발자로 만드는 법: agent-skills + Cursor 윈도우 완전 세팅 가이드&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 Google 엔지니어링 디렉터 Addy Osmani의 오픈소스 프로젝트 &lt;a href=&quot;https://github.com/addyosmani/agent-skills&quot;&gt;agent-skills&lt;/a&gt; (⭐ 43.1k, 2026.06 기준)의 공식 문서와 실제 소스코드를 직접 확인하여 작성했습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  왜 agent-skills인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cursor로 개발하다 보면 이런 상황이 자주 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;설계 논의도 없이 바로 코드를 뚝딱 만들어 버림&lt;/li&gt;
&lt;li&gt;테스트 코드는 &quot;나중에 쓸게요&quot; 하고 영원히 안 씀&lt;/li&gt;
&lt;li&gt;100줄 수정 요청에 1000줄짜리 괴물 PR을 만들어 옴&lt;/li&gt;
&lt;/ul&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;code&gt;agent-skills&lt;/code&gt;는 이 문제를 해결하기 위해 탄생했습니다. AI 에이전트가 Google 엔지니어링 문화에서 검증된 &lt;b&gt;시니어 개발자의 업무 프로토콜&lt;/b&gt;을 따르도록 강제하는 구조화된 워크플로우 모음입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*&quot;Skills encode the workflows, quality gates, and best practices that senior engineers use when building software.&quot;*&lt;br /&gt;&amp;mdash; agent-skills README&lt;/p&gt;
&lt;/blockquote&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;23개의 스킬&lt;/b&gt;이 개발 생명주기(Define &amp;rarr; Plan &amp;rarr; Build &amp;rarr; Verify &amp;rarr; Review &amp;rarr; Ship) 전 단계를 커버합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  저장소 구조 먼저 파악하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 저장소는 아래와 같이 구성되어 있습니다. 이 구조를 알아야 어디서 뭘 가져다 쓸지 알 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;agent-skills/
├── skills/          &amp;larr; 핵심! 23개의 SKILL.md 파일들
│   ├── incremental-implementation/SKILL.md
│   ├── test-driven-development/SKILL.md
│   ├── code-review-and-quality/SKILL.md
│   ├── spec-driven-development/SKILL.md
│   ├── planning-and-task-breakdown/SKILL.md
│   ├── security-and-hardening/SKILL.md
│   ├── performance-optimization/SKILL.md
│   └── ... (총 23개)
├── agents/          &amp;larr; 전문 페르소나 (code-reviewer, test-engineer, security-auditor)
├── references/      &amp;larr; 체크리스트 (testing, security, performance, accessibility)
├── .claude/commands/&amp;larr; Claude Code 전용 슬래시 커맨드 7개
├── hooks/           &amp;larr; 세션 라이프사이클 훅
└── docs/            &amp;larr; 도구별 세팅 가이드
    ├── cursor-setup.md      &amp;larr; 우리가 볼 파일
    ├── gemini-cli-setup.md
    ├── windsurf-setup.md
    └── ...&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;각 &lt;code&gt;SKILL.md&lt;/code&gt; 파일은 단순한 팁 모음이 아닙니다. &lt;b&gt;실행 가능한 단계별 워크플로우 + 안티 패턴 반박 테이블 + 검증 체크리스트&lt;/b&gt;로 구성된 정교한 프롬프트 문서입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; ️ Step-by-Step: 윈도우 세팅 가이드&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Step 1. 저장소 클론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PowerShell을 열고 원하는 경로에 저장소를 클론합니다.&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;# 예: C:\dev\agent-skills 에 클론
git clone https://github.com/addyosmani/agent-skills.git C:\dev\agent-skills&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;git이 없다면 GitHub에서 Code &amp;rarr; Download ZIP으로 다운로드 후 압축 해제해도 됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Step 2. Cursor 규칙 폴더 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업 중인 &lt;b&gt;프로젝트 루트&lt;/b&gt;로 이동해서 Cursor 전용 규칙 폴더를 만듭니다.&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;# 내 프로젝트 폴더로 이동
cd C:\Projects\MyProject

# .cursor/rules/ 폴더 생성 (-p 없이도 PowerShell mkdir은 중첩 폴더 자동 생성)
mkdir .cursor\rules&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Step 3. Essential Skills &amp;mdash; 핵심 3개 복사&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서(&lt;code&gt;docs/cursor-setup.md&lt;/code&gt;)가 &lt;b&gt;상시 로드(Always Load)&lt;/b&gt; 를 권장하는 핵심 스킬은 정확히 &lt;b&gt;3개&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;taggerscript&quot;&gt;&lt;code&gt;# 1. 점진적 구현 &amp;mdash; 한 번에 코드 뭉텅이 뱉는 것 방지
copy C:\dev\agent-skills\skills\incremental-implementation\SKILL.md `
     .cursor\rules\incremental-implementation.md

# 2. TDD &amp;mdash; 테스트 없이 PR 날리는 것 방지
copy C:\dev\agent-skills\skills\test-driven-development\SKILL.md `
     .cursor\rules\test-driven-development.md

# 3. 코드 리뷰 &amp;mdash; 머지 전 5축 품질 게이트 적용
copy C:\dev\agent-skills\skills\code-review-and-quality\SKILL.md `
     .cursor\rules\code-review-and-quality.md&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완료되면 프로젝트 구조가 이렇게 됩니다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;MyProject/
├── .cursor/
│   └── rules/
│       ├── incremental-implementation.md  &amp;larr; 상시 로드
│       ├── test-driven-development.md     &amp;larr; 상시 로드
│       └── code-review-and-quality.md     &amp;larr; 상시 로드
└── ... (기존 프로젝트 파일들)&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;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ 중요: .cursor/rules/ 폴더의 파일들은 Cursor가 자동으로 컨텍스트에 로드합니다. 파일명은 자유롭게 지어도 되지만, 내용은 반드시 원본 SKILL.md를 그대로 사용하세요. 내용을 직접 창작하면 agent-skills의 구체적인 워크플로우와 반박 테이블을 잃게 됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Step 4. Commands 세팅 &amp;mdash; 상황별 스킬 온디맨드 호출&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ 중요: Cursor의 Notepads 기능은 2025년 10월 말 Cursor 2.0 출시와 함께 완전히 폐기되었습니다. 현재 Cursor 최신 버전(3.x)에는 존재하지 않습니다.&lt;/p&gt;
&lt;/blockquote&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;code&gt;.cursor/rules/&lt;/code&gt;에 넣으면 &lt;b&gt;Cursor의 컨텍스트 한도 초과로 성능이 저하&lt;/b&gt;됩니다. 상황별로 필요한 스킬만 불러올 수 있는 현재 공식 대안은 &lt;b&gt;커스텀 슬래시 커맨드(&lt;code&gt;.cursor/commands/&lt;/code&gt;)&lt;/b&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;code&gt;.cursor/commands/&lt;/code&gt; 디렉토리에 Markdown 파일을 생성하면 파일명이 슬래시 커맨드 이름이 되고, 파일 내용이 프롬프트 템플릿으로 사용됩니다. 글로벌(전 프로젝트 공통)로 쓰려면 &lt;code&gt;~/.cursor/commands/&lt;/code&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;b&gt;Commands 폴더 생성:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;# 프로젝트 루트에 commands 폴더 생성 (-p 옵션 없이도 중첩 폴더 자동 생성됨)
mkdir .cursor\commands&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;&lt;b&gt;스킬을 커맨드 파일로 복사:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;taggerscript&quot;&gt;&lt;code&gt;# 보안 검토 커맨드
copy C:\dev\agent-skills\skills\security-and-hardening\SKILL.md `
     .cursor\commands\security.md

# 성능 최적화 커맨드
copy C:\dev\agent-skills\skills\performance-optimization\SKILL.md `
     .cursor\commands\performance.md

# 스펙 작성 커맨드
copy C:\dev\agent-skills\skills\spec-driven-development\SKILL.md `
     .cursor\commands\spec.md

# 프론트엔드 UI 커맨드
copy C:\dev\agent-skills\skills\frontend-ui-engineering\SKILL.md `
     .cursor\commands\frontend.md&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;&lt;b&gt;사용 방법:&lt;/b&gt; Cursor 채팅창에서 &lt;code&gt;/&lt;/code&gt;를 입력하면 등록된 커맨드 목록이 드롭다운으로 표시됩니다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;/security 이 로그인 API의 보안 취약점을 검토해줘.
/performance 이 목록 조회 쿼리의 성능 문제를 분석해줘.
/spec 결제 만료 알림 기능의 PRD를 작성해줘.&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;/p&gt;
&lt;table style=&quot;height: 108px;&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 24px;&quot;&gt;
&lt;th style=&quot;height: 24px;&quot;&gt;커맨드 파일명&lt;/th&gt;
&lt;th style=&quot;height: 24px;&quot;&gt;원본 SKILL.md 경로&lt;/th&gt;
&lt;th style=&quot;height: 24px;&quot;&gt;호출 방법&lt;/th&gt;
&lt;th style=&quot;height: 24px;&quot;&gt;사용 시점&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;code&gt;security.md&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;code&gt;skills/security-and-hardening/SKILL.md&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;code&gt;/security&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;인증/입력처리/배포 전&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;code&gt;performance.md&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;code&gt;skills/performance-optimization/SKILL.md&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;code&gt;/performance&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;성능 개선/리팩토링 시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;code&gt;spec.md&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;code&gt;skills/spec-driven-development/SKILL.md&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;code&gt;/spec&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;새 기능/프로젝트 설계 시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;code&gt;frontend.md&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;code&gt;skills/frontend-ui-engineering/SKILL.md&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;code&gt;/frontend&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;UI 컴포넌트 작업 시&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Step 5. &lt;code&gt;.cursorrules&lt;/code&gt; 방식 (선택 대안)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 루트에 &lt;code&gt;.cursorrules&lt;/code&gt; 단일 파일로 여러 스킬을 합치는 방법도 있습니다.&lt;br /&gt;팀 협업 시 &lt;code&gt;.cursor/rules/&lt;/code&gt; 폴더 구조 관리가 번거롭다면 이 방식을 고려하세요.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;# PowerShell에서 3개 스킬을 하나의 .cursorrules 파일로 합치기
Get-Content C:\dev\agent-skills\skills\test-driven-development\SKILL.md |
  Out-File .cursorrules -Encoding utf8

&quot;`n---`n&quot; | Out-File .cursorrules -Append -Encoding utf8

Get-Content C:\dev\agent-skills\skills\code-review-and-quality\SKILL.md |
  Out-File .cursorrules -Append -Encoding utf8

&quot;`n---`n&quot; | Out-File .cursorrules -Append -Encoding utf8

Get-Content C:\dev\agent-skills\skills\incremental-implementation\SKILL.md |
  Out-File .cursorrules -Append -Encoding utf8&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.cursorrules와 .cursor/rules/를 동시에 사용하면 충돌이 생길 수 있으니 둘 중 하나만 선택하세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  실전 활용: 스킬을 명시적으로 호출하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;규칙 파일이 자동 로드된다고 해서 AI가 항상 알아서 따르지는 않습니다. 공식 Usage Tips에 이렇게 명시되어 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*&quot;Reference skills explicitly &amp;mdash; Tell Cursor 'Follow the test-driven-development rules for this change' to ensure it reads the loaded rules.&quot;*&lt;br /&gt;&amp;mdash; docs/cursor-setup.md&lt;/p&gt;
&lt;/blockquote&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;새 기능 구현 시 (incremental-implementation)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;mercury&quot;&gt;&lt;code&gt;incremental-implementation 스킬을 따라서 결제 만료 알림 기능을 구현해줘.
한 번에 다 짜지 말고, 수직 슬라이스 단위로 쪼개서 각 단계마다
빌드와 테스트를 검증한 후 다음으로 넘어가.&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;&lt;b&gt;버그 수정 시 (test-driven-development)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;test-driven-development 스킬의 Prove-It Pattern을 따라서
이 버그를 수정해줘. 먼저 버그를 재현하는 실패 테스트를 작성하고,
그 테스트가 통과하도록 수정해.&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;&lt;b&gt;머지 전 리뷰 (code-review-and-quality)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;code-review-and-quality 스킬의 Five-Axis Review를 적용해서
이 변경사항을 검토해줘. 각 피드백에 Critical / Nit / Optional 레이블을 붙여줘.&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;&lt;b&gt;보안 검토가 필요할 때 (Commands)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;/security 이 로그인 API의 보안 취약점을 OWASP Top 10 기준으로 검토해줘.&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  전체 23개 스킬 한눈에 보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;agent-skills가 제공하는 전체 스킬 목록입니다. &lt;code&gt;.cursor/rules/&lt;/code&gt;에 &lt;b&gt;상시 로드&lt;/b&gt;할지, &lt;b&gt;&lt;code&gt;.cursor/commands/&lt;/code&gt;&lt;/b&gt; 로 필요할 때만 호출할지 구분해서 정리했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;단계&lt;/th&gt;
&lt;th&gt;스킬&lt;/th&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;th&gt;관리 방식&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Meta&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;using-agent-skills&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;어떤 스킬을 쓸지 매핑&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.cursor/commands/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Define&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;interview-me&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;요구사항 인터뷰&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.cursor/commands/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Define&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;idea-refine&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;아이디어 구체화&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.cursor/commands/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Define&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;spec-driven-development&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PRD 작성&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.cursor/commands/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Plan&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;planning-and-task-breakdown&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;태스크 분해&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.cursor/commands/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Build&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;incremental-implementation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;점진적 구현&lt;/td&gt;
&lt;td&gt;✅ &lt;b&gt;&lt;code&gt;.cursor/rules/&lt;/code&gt; 상시 로드&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Build&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;test-driven-development&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;TDD 사이클&lt;/td&gt;
&lt;td&gt;✅ &lt;b&gt;&lt;code&gt;.cursor/rules/&lt;/code&gt; 상시 로드&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Build&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;context-engineering&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;컨텍스트 최적화&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.cursor/commands/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Build&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;source-driven-development&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;공식 문서 기반 코딩&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.cursor/commands/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Build&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;doubt-driven-development&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;고위험 결정 재검토&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.cursor/commands/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Build&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;frontend-ui-engineering&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;UI 컴포넌트&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.cursor/commands/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Build&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;api-and-interface-design&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;API 설계&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.cursor/commands/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Verify&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;browser-testing-with-devtools&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;DevTools 디버깅&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.cursor/commands/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Verify&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;debugging-and-error-recovery&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;버그 트리아지&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.cursor/commands/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Review&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;code-review-and-quality&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;5축 코드 리뷰&lt;/td&gt;
&lt;td&gt;✅ &lt;b&gt;&lt;code&gt;.cursor/rules/&lt;/code&gt; 상시 로드&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Review&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;code-simplification&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;코드 단순화&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.cursor/commands/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Review&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;security-and-hardening&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OWASP 보안&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.cursor/commands/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Review&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;performance-optimization&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;성능 최적화&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.cursor/commands/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Ship&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git-workflow-and-versioning&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;트렁크 기반 Git&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.cursor/commands/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Ship&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ci-cd-and-automation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CI/CD 파이프라인&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.cursor/commands/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Ship&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;deprecation-and-migration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;레거시 제거&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.cursor/commands/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Ship&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;documentation-and-adrs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ADR 문서화&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.cursor/commands/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Ship&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;shipping-and-launch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;배포 체크리스트&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.cursor/commands/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  핵심 개념 3가지 &amp;mdash; 스킬이 왜 효과적인가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;agent-skills의 SKILL.md들이 단순한 &quot;지시문&quot;과 다른 이유가 있습니다. 실제 파일을 열어보면 공통된 설계 철학이 보입니다.&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;1. 안티 합리화 테이블 (Anti-rationalization)&lt;/b&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;table data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;AI의 변명&lt;/th&gt;
&lt;th&gt;실제 현실&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&quot;마지막에 한꺼번에 테스트할게요&quot;&lt;/td&gt;
&lt;td&gt;Slice 1의 버그가 Slice 2~5를 전부 오염시킵니다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&quot;한 번에 다 짜는 게 더 빠릅니다&quot;&lt;/td&gt;
&lt;td&gt;500줄 중 어디서 터진지 모를 때 체감됩니다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&quot;이 리팩토링은 같이 넣어도 작아요&quot;&lt;/td&gt;
&lt;td&gt;피처와 리팩토링이 섞이면 리뷰도, 롤백도 불가능합니다&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;2. 검증 게이트 (Verification Gates)&lt;/b&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;각 스킬의 마지막엔 반드시 &quot;증거 요구사항&quot;이 있습니다. &quot;잘 된 것 같아요&quot;는 완료가 아닙니다.&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;3. 레드 플래그 (Red Flags)&lt;/b&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;code&gt;incremental-implementation&lt;/code&gt;의 Red Flags:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트 실행 없이 100줄 이상 작성&lt;/li&gt;
&lt;li&gt;한 인크리먼트에 여러 관심사 혼재&lt;/li&gt;
&lt;li&gt;&quot;이것도 빠르게 추가할게요&quot; 스코프 크리프&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⚙️ 최종 세팅 요약&lt;/h2&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;MyProject/
├── .cursor/
│   ├── rules/                              &amp;larr; Cursor 자동 로드 (상시)
│   │   ├── incremental-implementation.md
│   │   ├── test-driven-development.md
│   │   └── code-review-and-quality.md
│   └── commands/                           &amp;larr; 슬래시 커맨드 온디맨드 호출
│       ├── security.md       &amp;rarr; /security
│       ├── performance.md    &amp;rarr; /performance
│       ├── spec.md           &amp;rarr; /spec
│       └── frontend.md       &amp;rarr; /frontend
└── ... (기존 프로젝트 파일들)&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;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;관리 방식&lt;/th&gt;
&lt;th&gt;대상 스킬&lt;/th&gt;
&lt;th&gt;활성화 방법&lt;/th&gt;
&lt;th&gt;사용 시점&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.cursor/rules/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;핵심 3개&lt;/td&gt;
&lt;td&gt;자동 로드&lt;/td&gt;
&lt;td&gt;항상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.cursor/commands/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;나머지 20개&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/커맨드명&lt;/code&gt; 입력&lt;/td&gt;
&lt;td&gt;해당 작업 시&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  참고 링크&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;GitHub 저장소&lt;/b&gt;: &lt;a href=&quot;https://github.com/addyosmani/agent-skills&quot;&gt;https://github.com/addyosmani/agent-skills&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Cursor 공식 세팅 문서&lt;/b&gt;: &lt;a href=&quot;https://github.com/addyosmani/agent-skills/blob/main/docs/cursor-setup.md&quot;&gt;https://github.com/addyosmani/agent-skills/blob/main/docs/cursor-setup.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스킬 전체 목록&lt;/b&gt;: &lt;a href=&quot;https://github.com/addyosmani/agent-skills/tree/main/skills&quot;&gt;https://github.com/addyosmani/agent-skills/tree/main/skills&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;참고 체크리스트&lt;/b&gt;: &lt;a href=&quot;https://github.com/addyosmani/agent-skills/tree/main/references&quot;&gt;https://github.com/addyosmani/agent-skills/tree/main/references&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>AI 관련/AI 기술</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3515</guid>
      <comments>https://aspdotnet.tistory.com/3515#entry3515comment</comments>
      <pubDate>Mon, 8 Jun 2026 00:56:09 +0900</pubDate>
    </item>
    <item>
      <title>MSSQL : 동적 데이터 복사 Stored Procedure 스크립트</title>
      <link>https://aspdotnet.tistory.com/3513</link>
      <description>&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;code&gt;INFORMATION_SCHEMA.COLUMNS&lt;/code&gt;와 &lt;code&gt;STRING_AGG&lt;/code&gt; 함수를 활용하면, 테이블의 컬럼이 추가되거나 변경될 때마다 일일이 쿼리를 수정할 필요 없이 동적으로 쿼리를 생성하고 실행하는 강력한 Stored Procedure(SP)를 만들 수 있습니다.&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;목적(문자열 추출 또는 실제 실행)에 맞게, 테이블명과 타겟/소스 데이터베이스명을 파라미터로 받아 유연하게 동작하는 SP 스크립트입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; ️ 동적 데이터 복사 Stored Procedure 스크립트&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;이 SP는 &lt;code&gt;@IsExecute&lt;/code&gt; 파라미터 값에 따라 단순히 쿼리를 텍스트로 출력(추출)할 수도 있고, 즉시 실행할 수도 있도록 설계되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;CREATE OR ALTER PROCEDURE usp_CopyTableData_Dynamic
    @SourceDB NVARCHAR(128),       -- 데이터를 가져올 Source DB명 (예: DB_INTRAWorkingHour)
    @TargetDB NVARCHAR(128),       -- 데이터를 넣을 Target DB명 (예: NpmUnivEsEW)
    @TableName NVARCHAR(128),      -- 대상 테이블명 (예: UNIV_WORK_ManagerConfig)
    @IsExecute BIT = 0             -- 0: 쿼리 문자열만 추출(PRINT 및 SELECT), 1: 쿼리 실제 실행
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @ColumnList NVARCHAR(MAX);
    DECLARE @SQL NVARCHAR(MAX) = N'';
    DECLARE @ColumnQuery NVARCHAR(MAX);
    DECLARE @IdentityCheckQuery NVARCHAR(MAX);
    DECLARE @HasIdentity BIT = 0;

    BEGIN TRY
        -- 1. 컬럼명 추출
        SET @ColumnQuery = N'
            SELECT @Cols = STRING_AGG(QUOTENAME(COLUMN_NAME), '', '') 
            FROM ' + QUOTENAME(@TargetDB) + N'.INFORMATION_SCHEMA.COLUMNS 
            WHERE TABLE_NAME = @Tbl';

        EXEC sp_executesql 
            @ColumnQuery, 
            N'@Tbl NVARCHAR(128), @Cols NVARCHAR(MAX) OUTPUT', 
            @TableName, 
            @ColumnList OUTPUT;

        IF @ColumnList IS NULL
        BEGIN
            PRINT 'Error: [' + @TargetDB + '] 데이터베이스에 [' + @TableName + '] 테이블이 존재하지 않거나 컬럼이 없습니다.';
            RETURN;
        END

        -- 2. 대상 테이블에 IDENTITY 컬럼이 존재하는지 확인
        SET @IdentityCheckQuery = N'
            SELECT @HasId = CASE WHEN EXISTS (
                SELECT 1 
                FROM ' + QUOTENAME(@TargetDB) + N'.sys.identity_columns 
                WHERE object_id = OBJECT_ID(''' + QUOTENAME(@TargetDB) + N'.dbo.' + QUOTENAME(@TableName) + N''')
            ) THEN 1 ELSE 0 END';
        
        EXEC sp_executesql
            @IdentityCheckQuery,
            N'@HasId BIT OUTPUT',
            @HasIdentity OUTPUT;

        -- 3. INSERT 쿼리 동적 생성 (IDENTITY 존재 여부에 따라 분기)
        IF @HasIdentity = 1
        BEGIN
            SET @SQL = @SQL + N'SET IDENTITY_INSERT ' + QUOTENAME(@TargetDB) + N'.dbo.' + QUOTENAME(@TableName) + N' ON;' + CHAR(13) + CHAR(10);
        END

        SET @SQL = @SQL + N'INSERT INTO ' + QUOTENAME(@TargetDB) + N'.dbo.' + QUOTENAME(@TableName) + N' (' + @ColumnList + N')' + CHAR(13) + CHAR(10) +
                   N'SELECT ' + @ColumnList + CHAR(13) + CHAR(10) +
                   N'FROM ' + QUOTENAME(@SourceDB) + N'.dbo.' + QUOTENAME(@TableName) + N';' + CHAR(13) + CHAR(10);

        IF @HasIdentity = 1
        BEGIN
            SET @SQL = @SQL + N'SET IDENTITY_INSERT ' + QUOTENAME(@TargetDB) + N'.dbo.' + QUOTENAME(@TableName) + N' OFF;';
        END

        -- 4. 실행 또는 문자열 추출
        IF @IsExecute = 1
        BEGIN
            PRINT 'Executing Script for Table: ' + @TableName;
            EXEC sp_executesql @SQL;
            PRINT 'Execute Completed Successfully.';
        END
        ELSE
        BEGIN
            PRINT '--- [Generated Script] ---';
            PRINT @SQL;
            SELECT @SQL AS GeneratedSQL;
        END
    END TRY
    BEGIN CATCH
        -- 에러 발생 시 처리
        PRINT 'Error occurred during execution.';
        PRINT 'Error Message: ' + ERROR_MESSAGE();
    END CATCH
END
GO&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  SP 실행 및 활용 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SP를 생성하신 후, 아래와 같이 상황에 맞게 파라미터를 변경하여 호출하시면 됩니다.&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;1. 쿼리 문자열만 추출 (테스트 및 확인용)&lt;/b&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;code&gt;@IsExecute = 0&lt;/code&gt;)&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;EXEC usp_CopyTableData_Dynamic 
    @SourceDB = 'DB_INTRAWorkingHour', 
    @TargetDB = 'NpmUnivEsEW', 
    @TableName = 'UNIV_WORK_ManagerConfig', 
    @IsExecute = 0;
&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;/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;2. 쿼리 즉시 실행 (실제 데이터 삽입)&lt;/b&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;code&gt;@IsExecute = 1&lt;/code&gt;)&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;EXEC usp_CopyTableData_Dynamic 
    @SourceDB = 'DB_INTRAWorkingHour', 
    @TargetDB = 'NpmUnivEsEW', 
    @TableName = 'UNIV_WORK_ManagerConfig', 
    @IsExecute = 1;
&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-path-to-node=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;참고로, 테이블 구조를 동적으로 다룰 때는 IDENTITY 속성 유무를 체크하는 것이 필수적입니다. 그렇지 않으면 자동 증가 컬럼이 없는 테이블에서 쿼리가 실패하게 됩니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;해당 테이블에 IDENTITY 컬럼이 존재하는지 시스템 테이블(sys.identity_columns)을 통해 검사하고, &lt;b data-index-in-node=&quot;67&quot; data-path-to-node=&quot;1&quot;&gt;결과에 따라 SET IDENTITY_INSERT ON/OFF 구문을 유동적으로 추가&lt;/b&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>데이터베이스/MS-SQL  </category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3513</guid>
      <comments>https://aspdotnet.tistory.com/3513#entry3513comment</comments>
      <pubDate>Thu, 28 May 2026 10:57:30 +0900</pubDate>
    </item>
    <item>
      <title>MSSQL : DATABASE 의 자동 증가(IDENTITY) 컬럼 전체 조회 쿼리</title>
      <link>https://aspdotnet.tistory.com/3512</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;테이블이&amp;nbsp;수십,&amp;nbsp;수백&amp;nbsp;개가&amp;nbsp;넘어갈&amp;nbsp;때&amp;nbsp;어떤&amp;nbsp;테이블의&amp;nbsp;어느&amp;nbsp;컬럼이&amp;nbsp;자동&amp;nbsp;증가(IDENTITY)로&amp;nbsp;설정되어&amp;nbsp;있는지&amp;nbsp;한눈에&amp;nbsp;파악하는&amp;nbsp;것은&amp;nbsp;데이터베이스&amp;nbsp;관리와&amp;nbsp;마이그레이션&amp;nbsp;작업에&amp;nbsp;아주&amp;nbsp;중요하죠!&lt;br /&gt;&lt;br /&gt;MSSQL에서&amp;nbsp;제공하는&amp;nbsp;시스템&amp;nbsp;뷰(System&amp;nbsp;Views)를&amp;nbsp;조인(JOIN)하여&amp;nbsp;활용하면&amp;nbsp;이&amp;nbsp;정보를&amp;nbsp;아주&amp;nbsp;쉽게&amp;nbsp;전체&amp;nbsp;목록으로&amp;nbsp;뽑아볼&amp;nbsp;수&amp;nbsp;있습니다.&lt;br /&gt;&lt;br /&gt; &amp;nbsp;자동&amp;nbsp;증가(IDENTITY)&amp;nbsp;컬럼&amp;nbsp;전체&amp;nbsp;조회&amp;nbsp;쿼리&lt;br /&gt;아래&amp;nbsp;쿼리를&amp;nbsp;실행하시면&amp;nbsp;현재&amp;nbsp;접속해&amp;nbsp;있는&amp;nbsp;데이터베이스&amp;nbsp;내의&amp;nbsp;모든&amp;nbsp;자동&amp;nbsp;증가&amp;nbsp;컬럼&amp;nbsp;목록을&amp;nbsp;확인할&amp;nbsp;수&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;pre id=&quot;code_1779930534316&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT 
    s.name AS [Schema_Name],              -- 스키마명 (예: dbo)
    t.name AS [Table_Name],               -- 테이블명
    c.name AS [Identity_Column_Name],     -- 자동 증가가 설정된 컬럼명
    c.seed_value AS [Seed_Value],         -- 시작값 (보통 1)
    c.increment_value AS [Increment]      -- 증가값 (보통 1)
FROM 
    sys.tables t
INNER JOIN 
    sys.schemas s ON t.schema_id = s.schema_id
INNER JOIN 
    sys.identity_columns c ON t.object_id = c.object_id
ORDER BY 
    t.name;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size23&quot;&gt;  쿼리 결과 설명&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,0,0&quot;&gt;sys.tables&lt;/b&gt;: 데이터베이스 내의 사용자 테이블 정보가 담겨 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,1,0&quot;&gt;sys.identity_columns&lt;/b&gt;: 데이터베이스 내에서 IDENTITY 속성이 부여된 모든 컬럼의 정보만 따로 모아둔 시스템 뷰입니다.&lt;/li&gt;
&lt;li&gt;위 쿼리를 실행하면 각 테이블마다 어떤 컬럼이 IDENTITY인지, 그리고 몇 번부터 시작해서(Seed_Value) 얼마씩 증가하는지(Increment) 한눈에 파악할 수 있어 관리하기 매우 편리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;이제 이 쿼리 결과를 바탕으로 앞서 하셨던 SET IDENTITY_INSERT 작업이 필요한 테이블들을 미리 파악하고 대비하실 수 있을 것입니다!&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;gtx-trans&quot; style=&quot;position: absolute; left: 22px; top: 145px;&quot;&gt;
&lt;div class=&quot;gtx-trans-icon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>데이터베이스/MS-SQL  </category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3512</guid>
      <comments>https://aspdotnet.tistory.com/3512#entry3512comment</comments>
      <pubDate>Thu, 28 May 2026 10:09:57 +0900</pubDate>
    </item>
    <item>
      <title>Git Worktree: 개발자가 몰라서 손해보는 브랜치 동시 작업의 기술</title>
      <link>https://aspdotnet.tistory.com/3510</link>
      <description>&lt;h1&gt;Git Worktree: 개발자가 몰라서 손해보는 브랜치 동시 작업의 기술&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;A 기능 개발 중인데, 갑자기 긴급 버그 수정 요청이 왔다.&quot;&lt;/b&gt;&lt;br /&gt;이 상황에서 당신은 어떻게 하시나요?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;들어가며: 당신은 어떤 방법을 쓰고 있나요?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무를 하다 보면 반드시 이런 순간이 찾아옵니다. &lt;code&gt;feature/payment&lt;/code&gt; 브랜치에서 결제 모듈을 한창 작업하고 있는데, 팀장님이 다가와 &quot;운영 서버에 장애 났어요, 지금 당장 핫픽스 올려야 합니다&quot;라고 합니다.&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;table data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;방법&lt;/th&gt;
&lt;th&gt;문제점&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git stash&lt;/code&gt; 후 브랜치 전환&lt;/td&gt;
&lt;td&gt;stash 충돌, 작업 컨텍스트 손실 위험&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;임시 커밋 후 브랜치 전환&lt;/td&gt;
&lt;td&gt;커밋 히스토리 오염 (&lt;code&gt;WIP&lt;/code&gt;, &lt;code&gt;임시 저장&lt;/code&gt; 커밋)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;프로젝트를 새 폴더에 &lt;code&gt;git clone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;저장소 용량 2배, 클론 시간 낭비&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;Git Worktree는 이 문제를 근본적으로 해결합니다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;h2 data-ke-size=&quot;size26&quot;&gt;Git Worktree란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git Worktree는 &lt;b&gt;하나의 &lt;code&gt;.git&lt;/code&gt; 저장소를 공유하면서, 여러 브랜치를 각각 다른 폴더에 동시에 체크아웃해서 작업할 수 있게 해주는 Git 내장 기능&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Git 2.5 버전부터 내장&lt;/b&gt; &amp;mdash; 별도 설치 없이 바로 사용 가능&lt;/li&gt;
&lt;li&gt;기존 작업 폴더와 내용은 &lt;b&gt;절대 건드리지 않음&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.git&lt;/code&gt; 폴더(커밋 히스토리, 설정 등)는 &lt;b&gt;하나만 존재&lt;/b&gt; &amp;mdash; 저장소 용량 이중 소모 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;my-api-project/          &amp;larr; 원래 작업 폴더 (feature/payment 브랜치)
  ├── .git/              &amp;larr; 이 하나의 .git을 두 폴더가 공유
  ├── src/
  └── ...

my-api-hotfix/           &amp;larr; worktree가 새로 만든 폴더 (hotfix/bug-01 브랜치)
  ├── src/               &amp;larr; .git 폴더 없음. .git 파일(참조)만 존재
  └── ...&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;h2 data-ke-size=&quot;size26&quot;&gt;  시작 전 반드시 알아야 할 핵심 제약&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동일한 브랜치를 두 개의 worktree에서 동시에 체크아웃할 수 없습니다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&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;이것이 Git Worktree의 유일하지만 가장 중요한 제약입니다.&lt;/p&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;# 현재 메인 폴더가 main 브랜치를 체크아웃한 상태라면
git worktree add ../my-api-copy main

# ❌ 아래 오류 발생
# fatal: 'main' is already checked out at 'C:/Dev/my-api-project'&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 worktree 폴더는 &lt;b&gt;반드시 서로 다른 브랜치&lt;/b&gt;를 바라보고 있어야 합니다.&lt;br /&gt;이 제약은 Git의 설계 원칙(하나의 브랜치는 하나의 HEAD만 가짐)에서 비롯된 것으로, 당연한 이유가 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;h2 data-ke-size=&quot;size26&quot;&gt;Step by Step 실전 가이드&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시나리오&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 위치: &lt;code&gt;C:\Dev\my-api-project&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;현재 브랜치: &lt;code&gt;feature/payment&lt;/code&gt; (한창 개발 중)&lt;/li&gt;
&lt;li&gt;긴급 요청: &lt;code&gt;main&lt;/code&gt; 기반으로 &lt;code&gt;hotfix/bug-01&lt;/code&gt; 작성 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Step 1. 새 worktree 추가하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상황에 맞는 명령어를 선택합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상황 A: &lt;code&gt;main&lt;/code&gt;을 베이스로 새 핫픽스 브랜치를 만들면서 작업 시작 (가장 흔한 케이스)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;git worktree add -b hotfix/bug-01 ../my-api-hotfix main&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;-b hotfix/bug-01&lt;/code&gt; : 새 브랜치를 생성&lt;/li&gt;
&lt;li&gt;&lt;code&gt;../my-api-hotfix&lt;/code&gt; : 생성할 폴더 경로 (현재 폴더 바깥에 생성)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;main&lt;/code&gt; : 어느 브랜치를 기준점으로 삼을지 (베이스 커밋)&lt;/li&gt;
&lt;/ul&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;상황 B: 이미 원격 저장소에 존재하는 브랜치를 로컬에 열어야 할 때 (코드 리뷰, 협업)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# fetch를 먼저 실행해서 원격 정보를 최신화하는 것을 권장
git fetch origin

git worktree add ../my-api-hotfix origin/hotfix/bug-01&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;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주의 (상황 B):&lt;/b&gt; &lt;code&gt;origin/&lt;/code&gt; 접두사 없이 브랜치명만 입력하면, 해당 이름의 로컬 브랜치가 없을 경우 오류가 발생합니다. &lt;code&gt;origin/&lt;/code&gt;을 붙이면 원격 브랜치를 직접 참조합니다. 이 방식은 detached HEAD 상태로 시작되므로, 커밋이 필요하다면 &lt;code&gt;git checkout -b hotfix/bug-01&lt;/code&gt;로 로컬 브랜치를 만들어야 합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;Step 2. 두 폴더를 동시에 IDE에서 열기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로 생긴 &lt;code&gt;my-api-hotfix&lt;/code&gt; 폴더를 VS Code나 Visual Studio에서 &lt;b&gt;새 창(New Window)&lt;/b&gt; 으로 엽니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 창 &amp;rarr; &lt;code&gt;feature/payment&lt;/code&gt; 결제 기능 개발 계속&lt;/li&gt;
&lt;li&gt;새 창 &amp;rarr; &lt;code&gt;hotfix/bug-01&lt;/code&gt; 긴급 버그 수정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;⚠️ ASP.NET Core 개발자 필독: 포트 충돌 방지&lt;/h4&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;code&gt;Properties/launchSettings.json&lt;/code&gt;에서 포트를 변경해 주세요.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;// my-api-hotfix/Properties/launchSettings.json
{
  &quot;profiles&quot;: {
    &quot;MyApiProject&quot;: {
      &quot;commandName&quot;: &quot;Project&quot;,
      &quot;applicationUrl&quot;: &quot;https://localhost:5003;http://localhost:5002&quot;,
      // &amp;uarr; 기존 5001/5000 대신 5003/5002로 변경
      &quot;environmentVariables&quot;: {
        &quot;ASPNETCORE_ENVIRONMENT&quot;: &quot;Development&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;⚠️ dotnet restore 필요 여부&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새 폴더는 물리적으로 새로 생성된 디렉토리이므로, &lt;b&gt;&lt;code&gt;bin/&lt;/code&gt;, &lt;code&gt;obj/&lt;/code&gt; 폴더가 존재하지 않습니다.&lt;/b&gt; 빌드 전에 한 번 실행해 주세요.&lt;/p&gt;
&lt;pre class=&quot;dos&quot;&gt;&lt;code&gt;cd ../my-api-hotfix
dotnet restore&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Step 3. 현재 worktree 상태 확인하기&lt;/h3&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;git worktree list&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;C:/Dev/my-api-project      1a2b3c4 [feature/payment]
C:/Dev/my-api-hotfix       5d6e7f8 [hotfix/bug-01]&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;커밋 해시와 브랜치명이 함께 표시되므로, 여러 worktree를 운영할 때 현재 상태를 한눈에 파악할 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Step 4. 작업 완료 후 worktree 삭제하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핫픽스 작업을 마치고 원격 저장소에 Push까지 완료했다면, 임시 폴더를 정리합니다. &lt;b&gt;원래 작업하던 폴더의 터미널&lt;/b&gt;에서 실행합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;git worktree remove ../my-api-hotfix&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;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  트러블슈팅: 삭제가 거부되는 경우&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드 산출물(&lt;code&gt;bin/&lt;/code&gt;, &lt;code&gt;obj/&lt;/code&gt;)이나 커밋되지 않은 파일(Untracked files)이 남아있으면 Git이 안전을 위해 삭제를 막습니다.&lt;/p&gt;
&lt;pre class=&quot;subunit&quot;&gt;&lt;code&gt;error: '../my-api-hotfix' contains modified or untracked files,
       use --force to delete it.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;--force&lt;/code&gt; 옵션으로 강제 삭제합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;git worktree remove --force ../my-api-hotfix&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;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고:&lt;/b&gt; &lt;code&gt;--force&lt;/code&gt;는 파일 시스템의 폴더 자체를 삭제하는 것으로, 로컬 브랜치(&lt;code&gt;hotfix/bug-01&lt;/code&gt;)는 영향을 받지 않습니다. 브랜치 히스토리는 그대로 보존됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Step 5. (번외) 폴더를 실수로 탐색기에서 직접 삭제했을 때&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;git worktree remove&lt;/code&gt; 대신 &lt;b&gt;윈도우 탐색기나 &lt;code&gt;rm -rf&lt;/code&gt; 명령으로 폴더를 직접 지운 경우&lt;/b&gt;, &lt;code&gt;.git&lt;/code&gt; 내부에 없어진 폴더를 참조하는 dead reference가 남습니다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;git worktree prune&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 명령어가 없어진 worktree 폴더의 참조를 자동으로 감지하고 정리합니다. 정기적으로 실행해도 안전합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;명령어 전체 요약&lt;/h2&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# 새 브랜치를 만들면서 worktree 추가 (가장 흔한 케이스)
git worktree add -b &amp;lt;새브랜치명&amp;gt; &amp;lt;폴더경로&amp;gt; &amp;lt;베이스브랜치&amp;gt;

# 기존 브랜치(원격)를 worktree로 추가
git worktree add &amp;lt;폴더경로&amp;gt; origin/&amp;lt;브랜치명&amp;gt;

# 현재 worktree 목록 확인
git worktree list

# worktree 삭제 (정상 삭제)
git worktree remove &amp;lt;폴더경로&amp;gt;

# worktree 강제 삭제 (untracked 파일 있을 때)
git worktree remove --force &amp;lt;폴더경로&amp;gt;

# 수동으로 지운 폴더의 dead reference 정리
git worktree prune&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실무에서 Worktree가 빛나는 3가지 상황&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 긴급 핫픽스 대응&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본문에서 다룬 시나리오입니다. 진행 중인 기능 개발을 stash 없이 그대로 유지하면서, 별도 폴더에서 핫픽스를 작업합니다.&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;2. 코드 리뷰 (PR Review)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀원의 PR을 리뷰할 때, 단순히 코드를 눈으로 보는 것을 넘어서 &lt;b&gt;실제로 실행해서 동작을 확인&lt;/b&gt;해야 할 경우가 있습니다.&lt;/p&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;git fetch origin
git worktree add ../review-pr-123 origin/feature/new-dashboard&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;자신의 작업 브랜치는 그대로 두고, 별도 폴더에서 PR 브랜치를 실행해볼 수 있습니다.&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;3. 릴리즈 브랜치와 개발 브랜치 동시 관리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포 후 릴리즈 브랜치(&lt;code&gt;release/v2.1&lt;/code&gt;)에서 소규모 패치를 진행하면서, 동시에 &lt;code&gt;develop&lt;/code&gt; 브랜치에서 다음 버전 개발을 이어가야 하는 경우에 이상적입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git Worktree는 화려한 기능이 아닙니다. 하지만 &lt;b&gt;정확히 필요한 순간에 정확히 문제를 해결해 주는&lt;/b&gt;, 실무형 기능입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;git stash&lt;/code&gt;로 컨텍스트를 날리고, &lt;code&gt;WIP&lt;/code&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;특히 ASP.NET Core나 Spring Boot 같은 &lt;b&gt;백엔드 프레임워크 환경&lt;/b&gt;에서는 포트 충돌과 &lt;code&gt;dotnet restore / mvn install&lt;/code&gt; 등의 초기 세팅만 신경 써주면, 이후에는 별다른 불편함 없이 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git 2.5 이상이라면 지금 당장 터미널을 열고 연습해 보시길 권합니다. 한번 손에 익히면 다시는 이전으로 돌아가기 어려운 기능입니다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&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;추후 긴급 패치 MAIN 원격지 PUSH 하고 나서, 추후에 이를 원격지에서 PULL 할 경우 충돌 발생이 있습니다. 이를 위한 가이드이며 순차 명령에 맞게 사용하시면 되겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1779033808726&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;순차 명령 (설명 + 실제 명령)

현재 상태 확인: 로컬 변경 및 브랜치 상태 확인
git status --porcelain --branch

원격 정보 가져오기: 원격 변경만 가져옴
git fetch origin

원격 병합 시도(초기): 바로 풀하면 로컬 변경으로 중단됨
git pull origin main

로컬 변경 임시저장: 추적/미추적 파일 포함해서 stash
git stash push -u -m &quot;auto-stash: before pull origin/main&quot;

저장된 stash 확인:
git stash list

원격에서 변경 가져와 병합: stash 후 다시 pull(성공, fast-forward)
git pull origin main

stash 다시 적용: 로컬 변경 복원 시도(여기서 충돌 발생)
git stash pop

충돌 수동 해결: 충돌 편집 후 저장 (Views/Home/Index.cshtml 수동 편집)

해결된 파일 스테이징:
git add Views/Home/Index.cshtml

병합 충돌 해결 커밋:
git commit -m &quot;Resolve merge conflict: Index.cshtml&quot;

남은 stash 확인 및 제거(선택):
git stash list
git stash drop stash@{0}

병합 결과 원격 푸시:
git push origin main

최종 상태 확인:
git status --short --branch&lt;/code&gt;&lt;/pre&gt;</description>
      <category>프로그래밍/GIT</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3510</guid>
      <comments>https://aspdotnet.tistory.com/3510#entry3510comment</comments>
      <pubDate>Mon, 18 May 2026 00:56:50 +0900</pubDate>
    </item>
    <item>
      <title>AI 기준 기술 스택 제안 (2026) - 신규 프로젝트라면 TypeScript + Next.js 생태계로 가는 것이 2026 기준 최선</title>
      <link>https://aspdotnet.tistory.com/3509</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;AI 기준 기술 스택 제안 (2026)&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;먼저 전제: &quot;AI 친화적&quot;이란 무엇인가&lt;/h2&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;table style=&quot;height: 167px;&quot; width=&quot;793&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;width: 203px;&quot;&gt;요소&lt;/th&gt;
&lt;th style=&quot;width: 356px;&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 203px;&quot;&gt;&lt;b&gt;훈련 데이터 볼륨&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 356px;&quot;&gt;GitHub 공개 레포, 공식 문서, Stack Overflow 누적량&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 203px;&quot;&gt;&lt;b&gt;패턴 일관성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 356px;&quot;&gt;같은 기능을 구현하는 방식이 생태계 내에서 얼마나 통일되어 있는가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 203px;&quot;&gt;&lt;b&gt;타입 명시성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 356px;&quot;&gt;AI가 컨텍스트 힌트를 얼마나 읽을 수 있는가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;h2 data-ke-size=&quot;size26&quot;&gt;솔직한 언어/프레임워크 AI 품질 평가&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Tier 1 &amp;mdash; AI가 가장 잘 생성하는 영역&lt;/h3&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;TypeScript (React/Next.js)
Python (FastAPI, Django)
SQL (표준 ANSI SQL)&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;이유: 훈련 데이터 압도적, 패턴 수렴도 높음, 오류 복구 쉬움&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Tier 2 &amp;mdash; AI가 충분히 잘 생성하나 보정 필요&lt;/h3&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;C# / ASP.NET Core 8
Java (Spring Boot)
PostgreSQL 고급 기능&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;C#에 대해 솔직히 말씀드리면:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Microsoft가 GitHub Copilot 훈련에 직접 투자했기 때문에 C# AI 품질은 생각보다 좋습니다&lt;/li&gt;
&lt;li&gt;CQRS + MediatR 패턴은 훈련 데이터에 상당히 존재합니다&lt;/li&gt;
&lt;li&gt;다만 &quot;최신 .NET 8 기능 + Dapper + 특수 패턴&quot; 조합에서 hallucination이 늘어납니다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;Tier 3 &amp;mdash; AI가 틀리기 쉬운 영역&lt;/h3&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;Vue.js 3 CDN (no build tools, Options API)
MSSQL 특수 문법 (일부)
최신 실험적 프레임워크
한국 로컬 라이브러리&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Vue.js 3 CDN에 대해 솔직히:&lt;/b&gt;&lt;br /&gt;AI는 기본적으로 Vue SFC + Vite 형태로 생성합니다. CDN + Options API 패턴을 강제하려면 반드시 &lt;code&gt;.cursorrules&lt;/code&gt; / &lt;code&gt;AGENTS.md&lt;/code&gt; 에 명시적 주입이 필요하며, 이 비용이 실제로 존재합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;도메인별 2026 최적 스택 제안&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Case 1: 비개발자 + 빠른 MVP&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;Frontend : Next.js 15 + TypeScript
UI       : Tailwind CSS + shadcn/ui
Backend  : Next.js API Routes (초기) &amp;rarr; FastAPI (확장)
DB       : PostgreSQL + Drizzle ORM
Auth     : Clerk 또는 Better Auth
Deploy   : Vercel + Supabase&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Drizzle 선택 이유 (Prisma 대비):&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL에 가까운 추상화 &amp;rarr; AI 추론 오류 적음&lt;/li&gt;
&lt;li&gt;Migration 파일이 plain SQL &amp;rarr; 검토 용이&lt;/li&gt;
&lt;li&gt;단, 이것은 커뮤니티 피드백 기반이며 제가 벤치마크를 직접 측정한 건 아닙니다 (불확실성 명시)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Case 2: 엔터프라이즈 ERP / 그룹웨어&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;&lt;b&gt;CQRS가 AI에게 왜 유리한가:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// AI는 이 구조를 매우 명확하게 이해합니다
// Command: 쓰기 의도가 명시적
public record CreateWorkOrderCommand(string Title, int AssigneeId) : IRequest&amp;lt;int&amp;gt;;

// Query: 읽기 의도가 명시적
public record GetWorkOrderListQuery(int DepartmentId) : IRequest&amp;lt;List&amp;lt;WorkOrderDto&amp;gt;&amp;gt;;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CQRS는 AI에게 &quot;이 코드가 무엇을 하는지&quot;를 명시적으로 알려주는 구조입니다. 오히려 AI 컨텍스트 전달 측면에서 장점입니다. 문서가 CQRS를 &quot;독&quot;이라 한 건 비개발자 MVP 맥락에서만 유효한 말입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Case 3: AI Agent / AI 기능 탑재 시스템&lt;/h3&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;AI Backend  : Python (FastAPI + LangGraph 또는 직접 Anthropic SDK)
Web Backend : ASP.NET Core 8 또는 Next.js API
통신        : REST 또는 gRPC&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;&lt;b&gt;솔직한 말:&lt;/b&gt; Python은 AI/ML 생태계에서 압도적입니다. AI 에이전트 백엔드를 C#으로 구축하는 것은 가능하지만, 훈련 데이터와 라이브러리 생태계 면에서 Python 대비 열세입니다. 이건 사실입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 제안&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&quot;스택 전환&quot;보다 &quot;하니스 강화&quot;가 먼저입니다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 ASP.NET Core 8 + CQRS 스택을 버리고 Next.js로 가는 것은 &lt;b&gt;잘못된 우선순위&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;도메인 지식 비용&lt;/b&gt;: 결재/근태/권한 도메인을 새 스택에서 다시 설계하는 비용이 AI 생산성 향상을 상쇄합니다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;C# AI 품질은 충분&lt;/b&gt;: Tier 2지만 CQRS + MediatR + Dapper 조합은 훈련 데이터에 존재합니다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;진짜 병목은 스택이 아닙니다&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;진짜 병목은 이것입니다&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[현재 AI 코딩의 실제 병목]

1. AI가 내 코드베이스를 얼마나 아는가  &amp;rarr; AGENTS.md / rules 품질
2. AI 생성 코드를 얼마나 빠르게 검증하는가  &amp;rarr; 테스트 전략
3. AI가 틀렸을 때 얼마나 빠르게 복구하는가  &amp;rarr; 오류 복구 패턴
4. AI에게 올바른 제약을 얼마나 잘 주입하는가 &amp;rarr; 하니스 엔지니어링&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;스택보다 이 네 가지가 더 중요합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;권장하는 실제 로드맵&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단기 (현재 프로젝트)&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;기존 스택 유지
+ AGENTS.md 고도화 (anti-pattern 명시, 도메인 규칙 주입)
+ Vue.js 3 &amp;rarr; Vite + SFC 전환 고려 (CDN 방식의 AI 비용 줄이기)
+ 단위 테스트 최소 Command/Query 핸들러에 추가&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;중기 (신규 프로젝트 착수 시)&lt;/h3&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;Frontend  : Next.js + TypeScript (기존 Vue CDN 탈피)
Backend   : ASP.NET Core 8 유지 (도메인 지식 활용)
DB        : PostgreSQL 검토 (MSSQL 유지도 나쁘지 않음)
AI 기능   : Python FastAPI 마이크로서비스 분리&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장기 (AI Agent 통합 시)&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Orchestrator  : Python (LangGraph 또는 직접 Anthropic API)
Web System    : ASP.NET Core 8 (안정적 운영)
Frontend      : Next.js
통신          : REST API 또는 MQ (RabbitMQ / Azure Service Bus)&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;불확실하다고 명시하는 부분&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;주장&lt;/th&gt;
&lt;th&gt;확실성&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TypeScript/Next.js AI 품질 최상위&lt;/td&gt;
&lt;td&gt;✅ 높음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C# AI 품질 Tier 2&lt;/td&gt;
&lt;td&gt;✅ 높음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CQRS가 AI 컨텍스트에 유리&lt;/td&gt;
&lt;td&gt;✅ 높음 (구조적 이유)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drizzle이 Prisma보다 AI hallucination 적음&lt;/td&gt;
&lt;td&gt;⚠️ 커뮤니티 경험 기반, 직접 측정 아님&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python FastAPI AI 품질 우위&lt;/td&gt;
&lt;td&gt;✅ 높음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vue CDN AI 비용 존재&lt;/td&gt;
&lt;td&gt;✅ 높음 (패턴 불일치 명확)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스택을 바꾸는 것보다, 현재 스택에서 AI가 정확하게 작동하도록 하니스를 고도화하는 것이 단기 ROI가 더 높습니다. 단, 신규 프로젝트라면 TypeScript + Next.js 생태계로 가는 것이 2026 기준 최선입니다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>AI 관련/AI 기술</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3509</guid>
      <comments>https://aspdotnet.tistory.com/3509#entry3509comment</comments>
      <pubDate>Fri, 15 May 2026 11:42:34 +0900</pubDate>
    </item>
    <item>
      <title>ASP.NET CORE 8 - 외부 요청 처리 흐름도 (In-Process 모델)</title>
      <link>https://aspdotnet.tistory.com/3506</link>
      <description>&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;pre id=&quot;code_1773984630485&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;configuration&amp;gt;
  &amp;lt;location path=&quot;.&quot; inheritInChildApplications=&quot;false&quot;&amp;gt;
    &amp;lt;system.webServer&amp;gt;
      &amp;lt;handlers&amp;gt;
        &amp;lt;add name=&quot;aspNetCore&quot; path=&quot;*&quot; verb=&quot;*&quot; modules=&quot;AspNetCoreModuleV2&quot; resourceType=&quot;Unspecified&quot; /&amp;gt;
      &amp;lt;/handlers&amp;gt;
      &amp;lt;aspNetCore processPath=&quot;dotnet&quot; arguments=&quot;.\GW.WebUI.dll&quot; stdoutLogEnabled=&quot;false&quot; stdoutLogFile=&quot;.\logs\stdout&quot; hostingModel=&quot;inprocess&quot; /&amp;gt;
      &amp;lt;!-- IIS 헤더 크기 제한 증설 (쿠키 크기 초과 방지) --&amp;gt;
      &amp;lt;security&amp;gt;
        &amp;lt;requestFiltering&amp;gt;
          &amp;lt;!-- 최대 URL 길이: 64KB, 쿼리 스트링: 32KB --&amp;gt;
          &amp;lt;requestLimits maxUrl=&quot;65536&quot; maxQueryString=&quot;32768&quot;&amp;gt;
            &amp;lt;!-- Cookie 헤더 최대 크기: 64KB --&amp;gt;
            &amp;lt;headerLimits&amp;gt;
              &amp;lt;add header=&quot;Cookie&quot; sizeLimit=&quot;65536&quot; /&amp;gt;
            &amp;lt;/headerLimits&amp;gt;
          &amp;lt;/requestLimits&amp;gt;
        &amp;lt;/requestFiltering&amp;gt;
      &amp;lt;/security&amp;gt;
    &amp;lt;/system.webServer&amp;gt;
  &amp;lt;/location&amp;gt;
&amp;lt;/configuration&amp;gt;
&amp;lt;!--ProjectGuid: 3DE55D15-CE32-4895-BD17-D74F07EBF2D0--&amp;gt;&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;/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;code&gt;web.config&lt;/code&gt; 설정(&lt;code&gt;hostingModel=&quot;inprocess&quot;&lt;/code&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;h4 data-ke-size=&quot;size20&quot;&gt;설명 1&lt;/h4&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;  외부 사용자 (웹 브라우저 / 클라이언트)
  │
  │ 1️⃣ HTTP 요청 (URL 주소, 파라미터, 쿠키 등)
  ▼
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃  ️ IIS 서버 프로세스 ( w3wp.exe )                                            ┃
┃   ※ 주의: 현재 설정에서는 dotnet.exe가 따로 뜨지 않고 이 안에서 다 해결됩니다!   ┃
┃                                                                            ┃
┃  [  ️ 2️⃣ 보안 검문소 (Request Filtering) ]                                  ┃
┃    ├─ URL 길이 64KB 이하인가? (통과)                                         ┃
┃    ├─ 쿼리 스트링 32KB 이하인가? (통과)                                      ┃
┃    └─ 쿠키(Cookie) 크기 64KB 이하인가? (통과)                                ┃
┃         │                                                                  ┃
┃         ▼ (검문 무사 통과!)                                                  ┃
┃                                                                            ┃
┃  [   3️⃣ 전담 매니저 (AspNetCoreModuleV2) ]                                 ┃
┃         │ 요청을 받아서 내부의 .NET 환경으로 토스!                           ┃
┃         │                                                                  ┃
┃         ▼                                                                  ┃
┃  ┌──────────────────────────────────────────────────────────────────────┐  ┃
┃  │   4️⃣ .NET 런타임 (In-Process)                                       │  ┃
┃  │   - w3wp.exe 뱃속(메모리)에 직접 올라가 있는 상태입니다.                   │  ┃
┃  │                                                                      │  ┃
┃  │     GW.WebUI.dll (우리가 만든 앱)                                     │  ┃
┃  │     - 여기서 C# 코드가 실행되고, DB(MSSQL 등)를 조회하고 HTML을 만듭니다. │  ┃
┃  └──────────────────────────────────────────────────────────────────────┘  ┃
┃         │                                                                  ┃
┃         ▼ (응답 데이터 생성 완료)                                            ┃
┃  [   전담 매니저 (AspNetCoreModuleV2) ]                                     ┃
┗━━━━━━━━━│━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
          │ 5️⃣ HTTP 응답 (최종 완성된 웹 페이지나 데이터)
          ▼
  외부 사용자 브라우저 화면에 출력!  &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;/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;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;큰 네모 박스 하나(&lt;code&gt;w3wp.exe&lt;/code&gt;) 안에서 모든 게 끝납니다.&lt;/b&gt; &lt;code&gt;In-Process&lt;/code&gt; 호스팅의 가장 큰 특징입니다.&lt;/li&gt;
&lt;li&gt;우리가 만든 &lt;code&gt;GW.WebUI.dll&lt;/code&gt; 파일은 별도의 독립된 프로그램으로 도는 게 아니라, &lt;b&gt;IIS(&lt;code&gt;w3wp.exe&lt;/code&gt;) 안에 세들어 살면서&lt;/b&gt; 요청을 처리합니다.&lt;/li&gt;
&lt;li&gt;따라서 이 웹사이트가 아무리 트래픽을 많이 받아도 작업 관리자에는 &lt;code&gt;w3wp.exe&lt;/code&gt;의 CPU/메모리만 올라갈 뿐, &lt;code&gt;.NET Host(dotnet.exe)&lt;/code&gt; 프로세스는 생기지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;설명 2&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;996&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Sp8vE/dJMcacWIB4A/8UnzMLGRqkLncLmu18dHgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Sp8vE/dJMcacWIB4A/8UnzMLGRqkLncLmu18dHgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Sp8vE/dJMcacWIB4A/8UnzMLGRqkLncLmu18dHgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSp8vE%2FdJMcacWIB4A%2F8UnzMLGRqkLncLmu18dHgK%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;1410&quot; height=&quot;996&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;996&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;&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;&lt;b&gt;스텝 위젯 &lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;(첫 번째)은 각 단계를 클릭하면서 설명과 web.config 코드를 함께 볼 수 있어서 발표나 스터디용으로 적합하고,&lt;/span&gt;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 web.config에서 특히 강조할 핵심 포인트 세 가지를 요약하면 이렇습니다.&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;첫째, hostingModel=&quot;inprocess&quot; 덕분에 작업 관리자에 dotnet.exe(.NET Host)가 &lt;b&gt;별도로 뜨지 않습니다&lt;/b&gt;. 모든 것이 w3wp.exe 안에서 돌아갑니다.&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;둘째, path=&quot;*&quot; verb=&quot;*&quot;는 .html, .css 같은 정적 파일 요청도 전부 ASP.NET Core로 넘긴다는 의미입니다. (정적 파일 미들웨어가 먼저 처리하도록 UseStaticFiles()가 등록되어 있어야 합니다.)&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;셋째, requestFiltering의 Cookie 64KB 제한은 JWT나 인증 쿠키가 기본 IIS 제한(4KB)을 넘을 때 추가하는 설정입니다. 이게 없으면 로그인은 되는데 두 번째 요청부터 400 에러가 납니다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;전담 매니저 `AspNetCoreModuleV2` 꼭 필요한가?&lt;/h4&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;&quot;그냥 넘겨주는 것뿐인데 왜 중요해?&quot; &amp;mdash; 사실 이게 핵심을 찌르는 질문이에요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 예전엔 없었나?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;**클래식 ASP.NET (.NET Framework 시절)**은 IIS와 완전히 결합되어 있었습니다.&lt;/p&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;IIS ──────────────── w3wp.exe
         System.Web (IIS 전용 API)
         ASP.NET 코드가 IIS 내부에서 직접 실행
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;System.Web이라는 라이브러리 자체가 IIS에 종속적으로 설계되어 있어서, IIS 파이프라인과 ASP.NET이 한 몸이었어요. 별도의 브릿지가 필요 없었습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ASP.NET Core가 이걸 일부러 끊었다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ASP.NET Core를 설계할 때 Microsoft가 내린 결정:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;IIS 없이도, Linux에서도, Docker에서도 돌아가게 만들자&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 System.Web을 완전히 버리고 Kestrel이라는 자체 웹서버를 만들었습니다. 덕분에 ASP.NET Core는 IIS를 전혀 모르는 상태가 되었어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;ASP.NET Core 앱  =  그냥 콘솔 프로그램
dotnet run 하면  =  Kestrel이 직접 포트를 열고 서비스
&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그런데&lt;/b&gt; 기존에 IIS 인프라를 쓰던 기업들은 ASP.NET Core를 IIS 위에서 돌리고 싶었습니다. 이 간극을 메우기 위해 ANCM이 태어난 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&quot;그냥 넘겨주는 것뿐&quot;이 아닌 이유&lt;/h2&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;ANCM이 실제로 하는 일은 꽤 많습니다.&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;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;프로세스 생명주기 관리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;앱이 크래시나면 자동 재시작, 앱 실행/종료 담당&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;CLR 로딩 (In-Process)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;w3wp 안에 .NET 런타임 자체를 주입&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;환경 변수 전달&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;ASPNETCORE_ENVIRONMENT 등 설정값 주입&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;stdout 로그 리디렉션&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;stdoutLogEnabled 설정이 여기서 처리됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;포트 동적 할당 (Out-of-Process)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;랜덤 포트를 Kestrel에 할당하고 프록시&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 &lt;b&gt;프로세스 재시작&lt;/b&gt;이 중요합니다. ANCM이 없으면 앱이 뻗었을 때 아무도 다시 살려주지 않아요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;한 줄 요약&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ANCM은 &quot;IIS를 모르는 ASP.NET Core&quot;와 &quot;ASP.NET Core를 모르는 IIS&quot;를 이어주기 위해 만들어진 브릿지입니다. 예전엔 System.Web이 그 역할을 대신했고, ASP.NET Core가 플랫폼 독립성을 얻는 대신 이 브릿지가 필요해진 거예요.&lt;/p&gt;
&lt;/blockquote&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>닷넷관련/ASP.NET CORE  </category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3506</guid>
      <comments>https://aspdotnet.tistory.com/3506#entry3506comment</comments>
      <pubDate>Fri, 20 Mar 2026 14:32:04 +0900</pubDate>
    </item>
    <item>
      <title>PYTHON 파이션 프로그램 - 윈도우 작업스케줄러 실행하기</title>
      <link>https://aspdotnet.tistory.com/3505</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이션 프로그램이 D: 드라이브에 있을 경우, 경로를 제대로 못 찾을 수 있으므로 아래처럼 작업 스케줄러에 일일이 매칭해 줘야 한다.&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;pre id=&quot;code_1773974804160&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1. 프로그램/스크립트

D:\Python\Python311\pythonw.exe

2. 인수 추가(옵션((A)

D:\GitHub\Private\git-action-python\fileresort.py

3. 시작 위치(옵션)(T)

D:\GitHub\Private\git-action-python\&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;875&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nkJsw/dJMcag5Uh1o/DodwjC4oKeBiGiQlNpZDZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nkJsw/dJMcag5Uh1o/DodwjC4oKeBiGiQlNpZDZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nkJsw/dJMcag5Uh1o/DodwjC4oKeBiGiQlNpZDZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnkJsw%2FdJMcag5Uh1o%2FDodwjC4oKeBiGiQlNpZDZ1%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;795&quot; height=&quot;875&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;875&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1773974790248&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;아하! 원인을 정확히 찾았습니다. 명령어를 직접 쳐서 확인해 주신 덕분에 문제가 아주 명확해졌네요. 훌륭합니다!

결과를 보면 두 가지 경로가 나왔는데, 이게 바로 에러의 원인입니다.

1. `C:\Users\luckshim\AppData\Local\Microsoft\WindowsApps\python.exe` 
   이 첫 번째 경로는 윈도우가 만들어놓은 일종의 '가짜(바로가기)' 파일이라서, 백그라운드로 도는 작업 스케줄러에서는 권한 문제나 경로 인식 오류(0x80070002)를 아주 자주 일으킵니다.
2. `D:\Python\Python311\python.exe`
   **이 두 번째 경로가 질문자님이 직접 설치하신 '진짜' 파이썬 경로입니다!**

따라서 작업 스케줄러가 엉뚱한 가짜 파일을 실행하지 않도록, 진짜 경로를 콕 집어서 알려주면 문제가 즉시 해결됩니다. 

작업 스케줄러의 해당 작업 [속성] -&amp;gt; [동작] 탭 -&amp;gt; [편집]으로 들어가셔서 **딱 이렇게 3칸을 수정해 주세요.**

---

###  ️ 작업 스케줄러 [동작] 탭 최종 설정

* **프로그램/스크립트(P):** `D:\Python\Python311\pythonw.exe`
    *(※ 주의: 끝에 `python.exe`가 아니라 반드시 `pythonw.exe`라고 적어주셔야 까만 도스 창이 안 뜹니다!)*
* **인수 추가(옵션)(A):** `&quot;D:\GitHub\Private\git-action-python\fileresort.py&quot;`
    *(※ 띄어쓰기 오류 방지를 위해 경로 양끝에 큰따옴표 `&quot;` 를 꼭 붙여주세요.)*
* **시작 위치(옵션)(T):** `D:\GitHub\Private\git-action-python\`
    *(※ 맨 끝에 파일 이름은 빼고 딱 폴더 경로까지만 적어주세요. 끝에 `\`가 있어도 되고 없어도 됩니다.)*

---

이렇게 세 칸을 모두 명확하게 채운 뒤 [확인]을 누르고 저장해 주세요. 

그리고 다시 해당 작업을 우클릭해서 **[실행]**을 눌러보시겠어요? 이번에는 에러 없이 조용히 실행되면서 `D:/Downloads_Sorted/sort_log.txt` 로그 파일도 예쁘게 생성될 겁니다. 결과가 어떤지 알려주세요!&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍/PYTHON</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3505</guid>
      <comments>https://aspdotnet.tistory.com/3505#entry3505comment</comments>
      <pubDate>Fri, 20 Mar 2026 11:47:17 +0900</pubDate>
    </item>
    <item>
      <title>엑셀에서 같은 파일을 2개 &amp;lsquo;새 창(별도 창)&amp;rsquo;으로 띄우는 방법</title>
      <link>https://aspdotnet.tistory.com/3504</link>
      <description>&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;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엑셀에서 같은 파일을 2개 &amp;lsquo;새 창(별도 창)&amp;rsquo;으로 띄우려면, 버전에 따라 아래처럼 하시면 됩니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;2013--365-2016-2019&quot; data-ke-size=&quot;size26&quot;&gt;엑셀 2013 이상 (365, 2016, 2019 등)&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;원하는 통합문서(파일)를 엽니다&lt;span data-state=&quot;closed&quot;&gt;​&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;상단 메뉴에서 &lt;b&gt;보기(View)&lt;/b&gt; 탭으로 이동합니다&lt;/li&gt;
&lt;li&gt;오른쪽에 있는 &lt;b&gt;새 창(New Window)&lt;/b&gt; 버튼을 클릭합니다.&lt;/li&gt;
&lt;li&gt;같은 파일이 두 개 창으로 열리며, 제목 표시줄에 파일명.xlsx:1, 파일명.xlsx:2처럼 번호가 붙습니다&lt;span data-state=&quot;closed&quot;&gt;​&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;필요하면 &lt;b&gt;보기 &amp;rarr; 모두 정렬(Arrange All)&lt;/b&gt; 을 눌러 화면을 좌우/상하로 나란히 배치할 수 있습니다.&lt;span data-state=&quot;closed&quot;&gt;​&lt;/span&gt;&lt;/li&gt;
&lt;/ol&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;이렇게 하면 한 창에서는 Sheet1, 다른 창에서는 Sheet2를 띄워서 동시에 비교&amp;middot;편집할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;143&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXdPsJ/dJMcacvGyCs/LeVN96IpBTkowTKkDcCub1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXdPsJ/dJMcacvGyCs/LeVN96IpBTkowTKkDcCub1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXdPsJ/dJMcacvGyCs/LeVN96IpBTkowTKkDcCub1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXdPsJ%2FdJMcacvGyCs%2FLeVN96IpBTkowTKkDcCub1%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;1208&quot; height=&quot;143&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;143&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;2010&quot; data-ke-size=&quot;size26&quot;&gt;엑셀 2010 이하&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;엑셀에서 파일을 한 번 연 상태에서,&lt;/li&gt;
&lt;li&gt;윈도우 키 + R &amp;rarr; 실행 창에 excel /x 입력 후 확인을 눌러 새 엑셀 인스턴스를 엽니다.&lt;span data-state=&quot;closed&quot;&gt;​&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;새로 열린 엑셀에서 &lt;b&gt;파일 &amp;rarr; 열기&lt;/b&gt;로 같은 파일을 다시 엽니다.&lt;span data-state=&quot;closed&quot;&gt;​&lt;/span&gt;&lt;/li&gt;
&lt;/ol&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;이 방식은 완전히 별도 인스턴스 2개에 같은 파일을 띄우는 방법이라, 서로 독립된 창으로 다룰 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원하시는 게 &amp;ldquo;같은 파일의 서로 다른 시트를 동시에 보고 싶다&amp;rdquo;라면, 위의 &lt;b&gt;보기 탭 &amp;rarr; 새 창&lt;/b&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;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>프로그래밍/기타(툴)</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3504</guid>
      <comments>https://aspdotnet.tistory.com/3504#entry3504comment</comments>
      <pubDate>Fri, 20 Mar 2026 10:03:45 +0900</pubDate>
    </item>
    <item>
      <title>NotebookLM을 내 AI 에이전트(Claude, Gemini)와 연결하는 방법 (Windows 완벽 가이드)</title>
      <link>https://aspdotnet.tistory.com/3498</link>
      <description>&lt;h1&gt;NotebookLM을 내 AI 에이전트(Claude, Gemini)와 연결하는 방법 (Windows 완벽 가이드)&lt;/h1&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;NotebookLM&lt;/b&gt;을 내 로컬 AI 에이전트(Claude Desktop, Cursor 등)에서 직접 사용할 수 있게 해주는 &lt;b&gt;NotebookLM MCP Server&lt;/b&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;Windows 환경&lt;/b&gt;에서 누구나 쉽게 설치하고 설정하실 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  준비물&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작하기 전에 &lt;b&gt;Python&lt;/b&gt;이 설치되어 있어야 합니다. 터미널(PowerShell 또는 CMD)에서 확인해 보세요.&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;python --version&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;설치가 안 되어 있다면 &lt;a href=&quot;https://www.python.org/downloads/&quot;&gt;Python 공식 홈페이지&lt;/a&gt;에서 다운로드하여 설치해 주세요.&lt;/i&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1단계: 패키지 설치하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 간편한 방법은 파이썬 패키지 관리자(&lt;code&gt;pip&lt;/code&gt;)를 사용하는 것입니다.&lt;/p&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;pip install notebooklm-mcp-cli&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;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  팁:&lt;/b&gt; 설치 후 노란색 경고 문구로 &lt;code&gt;PATH&lt;/code&gt; 관련 내용이 뜬다면, 윈도우 환경 변수에 파이썬 스크립트 경로를 추가해야 할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2단계: 구글 계정 인증 (로그인)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NotebookLM에 있는 내 자료들을 가져오려면 로그인이 필요합니다. 설치한 도구가 제공하는 인증 명령어를 사용합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;터미널에 아래 명령어를 입력하세요.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nlm login&lt;/code&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;i&gt;(만약 이 명령어가 작동하지 않는다면 &lt;span style=&quot;color: #ee2323;&quot;&gt;notebooklm-mcp-auth&lt;/span&gt;를 입력해 보세요.)&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Chrome 브라우저&lt;/b&gt;가 자동으로 열립니다.&lt;/li&gt;
&lt;li&gt;NotebookLM을 사용하는 &lt;b&gt;Google 계정으로 로그인&lt;/b&gt;해 주세요.&lt;/li&gt;
&lt;li&gt;로그인이 완료되고 터미널에 &lt;b&gt;SUCCESS&lt;/b&gt; 메시지가 뜨면 창을 닫아도 됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 내 컴퓨터에 안전하게 인증 정보가 저장되었습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3단계: AI 에이전트에 연결하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 설치된 MCP 서버를 내가 사용하는 AI 도구에 등록해 줄 차례입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Claude Desktop 사용자&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;설정 파일이 있는 폴더로 이동합니다. (탐색기 주소창에 입력)&lt;br /&gt;&lt;code&gt;%APPDATA%\Claude&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;claude_desktop_config.json&lt;/code&gt; 파일을 열고(없다면 생성) 아래 내용을 추가합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;mcpServers&quot;: {
    &quot;notebooklm-mcp&quot;: {
      &quot;command&quot;: &quot;notebooklm-mcp&quot;,
      &quot;args&quot;: [],
      &quot;env&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Gemini CLI (Antigravity) 사용자&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널에서 바로 추가할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;gemini mcp add notebooklm notebooklm-mcp&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Cursor / VS Code 사용자&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;mcp.json&lt;/code&gt; 파일에 위 Claude 설정과 동일한 &lt;code&gt;&quot;notebooklm-mcp&quot;&lt;/code&gt; 부분을 추가해 주시면 됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4단계: 연결 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정이 끝났습니다! 이제 AI 에이전트를 &lt;b&gt;재시작&lt;/b&gt;하고 잘 작동하는지 말을 걸어보세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;내 NotebookLM에 있는 노트북 목록을 보여줘&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  5. 실전 활용 시나리오 (Best Practice)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가 완료되었다면, 단순한 채팅을 넘어 강력한 기능을 200% 활용하는 다양한 시나리오를 경험해 보세요.&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;설치가 완료되었다면, 단순한 채팅을 넘어 강력한 기능을 200% 활용하는 다양한 시나리오를 경험해 보세요.

###   시나리오 A: 심층 리서치 및 분석 (Deep Research)
*주제만 던져주면 AI가 웹을 검색하여 자료를 수집하고 노트북을 생성합니다.*

1.  **리서치 시작**
    &amp;gt; &quot;최나 '생성형 AI의 최신 보안 동향'에 대해 **심층 리서치(deep research)**를 수행해주고, 결과를 'AI 보안 연구'라는 새 노트북에 저장해 줘.&quot;

2.  **소스 확인 및 분석**
    &amp;gt; &quot;방금 만든 노트북에 추가된 소스들이 뭐야? 각 소스의 핵심 내용을 요약해 줘.&quot;

3.  **질의응답**
    &amp;gt; &quot;이 노트북의 자료들을 바탕으로, 기업이 주의해야 할 3대 보안 위협이 뭔지 정리해 줘.&quot;

###  ️ 시나리오 B: 나만의 팟캐스트/비디오 제작 (Content Creator)
*읽기 귀찮은 자료를 팟캐스트나 해설 영상으로 변환하세요.*

1.  **자료 추가 (유튜브/URL)**
    &amp;gt; &quot;내 '기술 블로그' 노트북에 이 유튜브 영상 URL(https://...)을 소스로 추가해 줘.&quot;

2.  **오디오 오버뷰 생성 (딥 다이브)**
    &amp;gt; &quot;이 노트북의 내용을 바탕으로 두 사람이 토론하는 **심층(Deep Dive) 팟캐스트**를 만들어 줘.&quot;

3.  **비디오 브리핑 생성**
    &amp;gt; &quot;이 내용을 설명해주는 **짧은 비디오**를 만들어 줘. 시각 스타일은 '화이트보드' 스타일로 해 줘.&quot;
    *(생성이 완료되면 다운로드 링크를 제공합니다)*

###   시나리오 C: 학습용 도구 자동 생성 (Study Buddy)
*시험 공부나 자격증 준비를 위한 최고의 파트너입니다.*

1.  **구글 드라이브 자료 연동**
    &amp;gt; &quot;내 구글 드라이브에서 '2024 정보리기사 정리'라는 문서 파일들을 찾아서 '자격증 공부' 노트북에 넣어줘.&quot;

2.  **플래시카드(암기장) 생성**
    &amp;gt; &quot;이 노트북 내용으로 공부하기 좋게 **중급 난이도의 플래시카드** 10개를 만들어 줘.&quot;

3.  **퀴즈 생성**
    &amp;gt; &quot;내가 내용을 잘 이해했는지 테스트하게 **객관식 퀴즈 5문제**를 출제해 줘.&quot;

###   시나리오 D: 프로젝트 문서 동기화 (Smart Sync)
*계속 업데이트되는 회의록이나 기획서를 항상 최신 상태로 유지합니다.*

1.  **오래된 자료 확인**
    &amp;gt; &quot;내 '프로젝트 알파' 노트북에서 구글 드라이브 소스들의 **업데이트 상태(freshness)**를 확인해 줘.&quot;

2.  **자동 동기화**
    &amp;gt; &quot;업데이트가 필요한(Stale) 소스들을 전부 **최신 버전으로 동기화**해 줘.&quot;

3.  **마인드맵 정리**
    &amp;gt; &quot;업데이트된 내용을 한눈에 볼 수 있게 **마인드맵**을 그려서 시각화해 줘.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  유용한 프롬프트 모음 (Cheat Sheet)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복사해서 바로 사용하세요!&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;카테고리&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;프롬프트 예시&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;b&gt;기본 관리&lt;/b&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&quot;내 NotebookLM 노트북 목록 보여줘&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&quot;제목 없는 빈 노트북은 삭제해 줘&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;b&gt;자료 추가&lt;/b&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&quot;이 텍스트를 '회의록'이라는 제목으로 현재 노트북에 추가해 줘: [붙여넣기]&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&quot;이 웹페이지 URL을 소스로 추가해 줘: [URL]&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;b&gt;분석/요약&lt;/b&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&quot;이 노트북의 모든 소스를 종합해서 핵심 주장 3가지만 요약해 줘&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&quot;이 주제에 대해 찬성/반대 입장으로 나누어 정리해 줘&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;b&gt;문서 작성&lt;/b&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&quot;이 자료들을 바탕으로 블로그 포스트 초안을 작성해 줘&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&quot;경영진 보고용 브리핑 문서(Briefing Doc)를 만들어 줘&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;b&gt;내보내기&lt;/b&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&quot;작성된 보고서를 구글 닥스로 내보내기 해 줘&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;a style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; href=&quot;https://github.com/jacob-bd/notebooklm-mcp-cli&quot; data-turbo-frame=&quot;repo-content-turbo-frame&quot; data-pjax=&quot;#repo-content-pjax-container&quot;&gt;notebooklm-mcp-cli&lt;/a&gt;&lt;/h4&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://github.com/jacob-bd/notebooklm-mcp-cli&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/jacob-bd/notebooklm-mcp-cli&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1769957255837&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - jacob-bd/notebooklm-mcp-cli&quot; data-og-description=&quot;Contribute to jacob-bd/notebooklm-mcp-cli development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/jacob-bd/notebooklm-mcp-cli&quot; data-og-url=&quot;https://github.com/jacob-bd/notebooklm-mcp-cli&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c2A7ve/dJMb9dHi361/SV4PyAOMEhexl3IKArdH5k/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bHMBoN/dJMb9cBDaHb/y5YgpjF9uAOHeTb3mDVFb0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/jacob-bd/notebooklm-mcp-cli&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/jacob-bd/notebooklm-mcp-cli&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c2A7ve/dJMb9dHi361/SV4PyAOMEhexl3IKArdH5k/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bHMBoN/dJMb9cBDaHb/y5YgpjF9uAOHeTb3mDVFb0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - jacob-bd/notebooklm-mcp-cli&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to jacob-bd/notebooklm-mcp-cli development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI 관련/AI 기술</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3498</guid>
      <comments>https://aspdotnet.tistory.com/3498#entry3498comment</comments>
      <pubDate>Sun, 1 Feb 2026 23:48:04 +0900</pubDate>
    </item>
    <item>
      <title>GIT : Commit 없이 원격지 Pull &amp;amp; Merge 하는 방법</title>
      <link>https://aspdotnet.tistory.com/3497</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 변경사항을 커밋하지 않고 원격 변경사항과 병합하려면 Stash를 사용해야 합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;추천 방법: Stash 사용&lt;/h4&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;# 1. 현재 변경사항을 임시 저장 (커밋 없이)
git stash

# 2. Pull 실행
git pull origin develop

# 3. 임시 저장한 변경사항 다시 적용
git stash pop&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;VS CODE 에서 하는 방법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;... (More Actions) &amp;rarr; Stash &amp;rarr; Stash (Include Untracked)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;579&quot; data-origin-height=&quot;738&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Mhhb1/dJMcagdaQef/TpkwGOBiWZUVuVBCCJ4EHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Mhhb1/dJMcagdaQef/TpkwGOBiWZUVuVBCCJ4EHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Mhhb1/dJMcagdaQef/TpkwGOBiWZUVuVBCCJ4EHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMhhb1%2FdJMcagdaQef%2FTpkwGOBiWZUVuVBCCJ4EHK%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;738&quot; data-origin-width=&quot;579&quot; data-origin-height=&quot;738&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;... &amp;rarr; Pull&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;627&quot; data-origin-height=&quot;74&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eAQOhY/dJMcaacXvkO/cvCqZlD8XuJADR7HrIS6h0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eAQOhY/dJMcaacXvkO/cvCqZlD8XuJADR7HrIS6h0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eAQOhY/dJMcaacXvkO/cvCqZlD8XuJADR7HrIS6h0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeAQOhY%2FdJMcaacXvkO%2FcvCqZlD8XuJADR7HrIS6h0%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;627&quot; height=&quot;74&quot; data-origin-width=&quot;627&quot; data-origin-height=&quot;74&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;327&quot; data-origin-height=&quot;127&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xEi5B/dJMcaiPxYgX/EdXx76Tc1K3pIWEFGJWVok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xEi5B/dJMcaiPxYgX/EdXx76Tc1K3pIWEFGJWVok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xEi5B/dJMcaiPxYgX/EdXx76Tc1K3pIWEFGJWVok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxEi5B%2FdJMcaiPxYgX%2FEdXx76Tc1K3pIWEFGJWVok%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;327&quot; height=&quot;127&quot; data-origin-width=&quot;327&quot; data-origin-height=&quot;127&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.... &amp;rarr; Stash &amp;rarr; Apply Latest Stash 또는 Pop Latest Stash&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;540&quot; data-origin-height=&quot;748&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Rr9jH/dJMcadm9XqX/qUXAlHVKTMMs6q6kMbbO3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Rr9jH/dJMcadm9XqX/qUXAlHVKTMMs6q6kMbbO3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Rr9jH/dJMcadm9XqX/qUXAlHVKTMMs6q6kMbbO3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRr9jH%2FdJMcadm9XqX%2FqUXAlHVKTMMs6q6kMbbO3K%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;540&quot; height=&quot;748&quot; data-origin-width=&quot;540&quot; data-origin-height=&quot;748&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1019&quot; data-origin-height=&quot;348&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHjFEI/dJMcagRMjng/yeYZ0iktjkjQmdiVGHDl41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHjFEI/dJMcagRMjng/yeYZ0iktjkjQmdiVGHDl41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHjFEI/dJMcagRMjng/yeYZ0iktjkjQmdiVGHDl41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHjFEI%2FdJMcagRMjng%2FyeYZ0iktjkjQmdiVGHDl41%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;1019&quot; height=&quot;348&quot; data-origin-width=&quot;1019&quot; data-origin-height=&quot;348&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;h4 data-ke-size=&quot;size20&quot;&gt;보충 설명&lt;/h4&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;br /&gt;&lt;code&gt;git pull&lt;/code&gt; 명령 실행 시 아래와 같은 오류가 발생했습니다:&lt;/p&gt;
&lt;pre class=&quot;subunit&quot;&gt;&lt;code&gt;error: Your local changes to the following files would be overwritten by merge:
    .gitignore
Please commit your changes or stash them before you merge.
Aborting&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; &lt;b&gt;원인&lt;/b&gt;: 로컬에서 &lt;code&gt;.gitignore&lt;/code&gt; 파일을 수정했는데, 원격 &lt;code&gt;develop&lt;/code&gt; 브랜치에도 &lt;code&gt;.gitignore&lt;/code&gt; 파일이 변경되어 있어 병합(merge) 시 충돌이 예상되기 때문에 Git이 안전장치로 pull을 중단한 것입니다.&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;: 최대한 빠르고 안전하게 원격 변경사항을 가져오고 싶으며, merge를 진행하고 싶다.&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;/h3&gt;
&lt;table style=&quot;height: 324px;&quot; width=&quot;801&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 48px;&quot;&gt;
&lt;th style=&quot;height: 48px;&quot;&gt;순번&lt;/th&gt;
&lt;th style=&quot;height: 48px;&quot;&gt;방법&lt;/th&gt;
&lt;th style=&quot;height: 48px;&quot;&gt;명령어 예시&lt;/th&gt;
&lt;th style=&quot;height: 48px;&quot;&gt;언제 추천하는가?&lt;/th&gt;
&lt;th style=&quot;height: 48px;&quot;&gt;로컬 변경사항 운명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 72px;&quot;&gt;
&lt;td style=&quot;height: 72px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;height: 72px;&quot;&gt;&lt;b&gt;Stash&lt;/b&gt; (가장 추천)&lt;/td&gt;
&lt;td style=&quot;height: 72px;&quot;&gt;&lt;code&gt;git stash push -m &quot;WIP .gitignore&quot;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;git pull&lt;/code&gt;&lt;br /&gt;&lt;code&gt;git stash pop&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 72px;&quot;&gt;로컬 변경을 &lt;b&gt;잠시 보관&lt;/b&gt;하고 나중에 다시 적용하고 싶을 때&lt;/td&gt;
&lt;td style=&quot;height: 72px;&quot;&gt;나중에 다시 적용 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 72px;&quot;&gt;
&lt;td style=&quot;height: 72px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;height: 72px;&quot;&gt;&lt;b&gt;Commit&lt;/b&gt; 후 pull&lt;/td&gt;
&lt;td style=&quot;height: 72px;&quot;&gt;&lt;code&gt;git add .gitignore&lt;/code&gt;&lt;br /&gt;&lt;code&gt;git commit -m &quot;Update .gitignore&quot;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;git pull&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 72px;&quot;&gt;변경사항이 의미 있고 &lt;b&gt;커밋&lt;/b&gt;할 가치가 있을 때&lt;/td&gt;
&lt;td style=&quot;height: 72px;&quot;&gt;커밋으로 남음 (히스토리에 기록)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 54px;&quot;&gt;
&lt;td style=&quot;height: 54px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;height: 54px;&quot;&gt;&lt;b&gt;원격 변경 강제 덮어쓰기&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 54px;&quot;&gt;&lt;code&gt;git fetch&lt;/code&gt;&lt;br /&gt;&lt;code&gt;git reset --hard origin/develop&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 54px;&quot;&gt;&lt;b&gt;로컬 .gitignore 변경을 완전히 버리고 싶을 때&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 54px;&quot;&gt;&lt;b&gt;로컬 변경 완전 삭제&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&lt;b&gt;pull --rebase&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&lt;code&gt;git pull --rebase origin develop&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;깔끔한 히스토리를 원할 때 (merge 대신 rebase)&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;충돌 시 수동 해결 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1번 방법 - Stash 사용 (가장 안전하고 일반적인 선택)&lt;/p&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;# 1. 현재 변경사항을 임시 저장 (스태시)
git stash push -m &quot;임시 .gitignore 변경사항&quot;

# 2. 원격 브랜치 가져오기 (pull)
git pull origin develop

# 3. 필요하면 스태시 복원
git stash pop

# (선택) 복원 후 충돌이 발생하면 직접 수정 후 커밋
git add .gitignore
git commit -m &quot;Merge conflict resolved in .gitignore&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로컬 변경을 잃지 않음&lt;/li&gt;
&lt;li&gt;가장 안전&lt;/li&gt;
&lt;li&gt;나중에 변경사항을 다시 검토할 수 있음&lt;/li&gt;
&lt;/ul&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;2번 방법 - 바로 커밋하고 pull&lt;/h3&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;git add .gitignore
git commit -m &quot;로컬 .gitignore 업데이트&quot;
git pull origin develop&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 충돌이 발생하면 VS Code 등에서 충돌 해결 후 커밋&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;3번 방법 - 로컬 변경 완전히 포기하고 원격으로 덮어쓰기 (주의!)&lt;/h3&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;# 원격 최신 상태 가져오기
git fetch origin

# 현재 브랜치(develop)를 원격 develop으로 강제 이동
git reset --hard origin/develop&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;&lt;b&gt;⚠️ 경고&lt;/b&gt;: 이 방법은 &lt;b&gt;로컬에서 수정한 .gitignore를 완전히 삭제&lt;/b&gt;합니다.&lt;br /&gt;&amp;rarr; 정말 로컬 변경이 필요 없다고 판단될 때만 사용하세요.&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;추가 팁 (Visual Studio 환경)&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;VS Git GUI 사용&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Team Explorer &amp;rarr; Changes 탭&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.gitignore&lt;/code&gt; 파일 우클릭 &amp;rarr; &lt;b&gt;Stash&lt;/b&gt; 또는 &lt;b&gt;Commit&lt;/b&gt; 선택 가능&lt;/li&gt;
&lt;li&gt;Stash 후 Pull 버튼 클릭&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;충돌이 자주 발생한다면&lt;/b&gt;&lt;br /&gt;&lt;code&gt;.gitignore&lt;/code&gt; 파일을 프로젝트 초기 설정 단계에서 확정하고, 이후에는 거의 수정하지 않는 것이 좋습니다.&lt;br /&gt;(예: &lt;code&gt;.vscode/&lt;/code&gt; 폴더, 개인 설정 파일 등은 &lt;code&gt;.gitignore&lt;/code&gt;에 추가하고 추적하지 않도록)&lt;/li&gt;
&lt;/ol&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;/h3&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;git stash push -m &quot;develop pull 전 .gitignore 임시 저장&quot;
git pull origin develop
git stash pop
# 필요하면 충돌 해결 후
git add .gitignore
git commit -m &quot;충돌 해결: .gitignore&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍/GIT</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3497</guid>
      <comments>https://aspdotnet.tistory.com/3497#entry3497comment</comments>
      <pubDate>Wed, 14 Jan 2026 20:34:41 +0900</pubDate>
    </item>
    <item>
      <title>DATADOG : 서브 도메인 간 API 호출 시 발생하는 CORS(Cross-Origin Resource Sharing) 에러</title>
      <link>https://aspdotnet.tistory.com/3496</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DATADOG 을 함께 운영하는 사이트 경우, 서브 도메인 주소를 WEB API 로 호출할 경우, 아래와 같이 HEADER 에 정책을 추가해 줘야 합니다.&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;Datadog RUM의 allowedTracingUrls 설정으로 인해 자동으로 추가되는 분산 추적 헤더(traceparent, tracestate)가 CORS Preflight 요청에서 차단되고 있기 때문이죠.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Datadog RUM이 allowedTracingUrls에 포함된 도메인으로의 XHR/Fetch 요청에 traceparent 헤더를 자동 삽입&lt;/li&gt;
&lt;li&gt;서브 도메인 간 Cross-Origin 요청이므로 &lt;b&gt;Preflight 요청&lt;/b&gt;(OPTIONS) 발생&lt;/li&gt;
&lt;li&gt;서버의 Access-Control-Allow-Headers에 traceparent가 없으면 &lt;b&gt;CORS 차단&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1767924056093&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Access to XMLHttpRequest at 'https://XXXXX/api/...' 
from origin 'https://XXXXX.XXX.kr' has been blocked by CORS policy: 
Request header field traceparent is not allowed by Access-Control-Allow-Headers&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;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;서버&amp;nbsp;측&amp;nbsp;CORS&amp;nbsp;설정에&amp;nbsp;Datadog&amp;nbsp;헤더&amp;nbsp;추가&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;서브&amp;nbsp;도메인&amp;nbsp;간&amp;nbsp;분산&amp;nbsp;추적이&amp;nbsp;반드시&amp;nbsp;필요한&amp;nbsp;경우,&amp;nbsp;서버의&amp;nbsp;CORS&amp;nbsp;정책에&amp;nbsp;Datadog&amp;nbsp;헤더를&amp;nbsp;허용&amp;nbsp;목록에&amp;nbsp;추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&quot;traceparent&quot;,&amp;nbsp; &quot;tracestate&quot; 를 허용해주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1767924073733&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// =============================
// 1. CORS 정책 등록 (API 외부 호출 필요시)
// =============================
var corsOrigins = builder.Configuration.GetSection(&quot;SiteSettings:CorsOrigins&quot;).Get&amp;lt;string[]&amp;gt;()
    ?? throw new InvalidOperationException(&quot;SiteSettings:CorsOrigins 설정이 필요합니다.&quot;);

builder.Services.AddCors(options =&amp;gt;
{
    options.AddPolicy(&quot;AllowSubdomains&quot;, policy =&amp;gt;
    {
        policy.WithOrigins(corsOrigins)
            // ==========================================
            // [수정] W3C Trace Context 헤더 추가
            // ==========================================
            .WithHeaders(
                // 기본 헤더
                &quot;Content-Type&quot;,
                &quot;Authorization&quot;,
                &quot;X-Requested-With&quot;,
                &quot;requestverificationtoken&quot;,
                // [중요] W3C Trace Context 표준 헤더 (Datadog RUM 필수)
                &quot;traceparent&quot;,              // &amp;larr; 추가 필요
                &quot;tracestate&quot;,               // &amp;larr; 추가 필요
                // Datadog 전용 헤더 (기존 유지)
                &quot;x-datadog-trace-id&quot;,
                &quot;x-datadog-parent-id&quot;,
                &quot;x-datadog-origin&quot;,
                &quot;x-datadog-sampling-priority&quot;
            )
            .WithMethods(&quot;GET&quot;, &quot;POST&quot;, &quot;PUT&quot;, &quot;DELETE&quot;, &quot;OPTIONS&quot;)
            .AllowCredentials()
            .SetIsOriginAllowedToAllowWildcardSubdomains();
    });
});&lt;/code&gt;&lt;/pre&gt;</description>
      <category>닷넷관련/ASP.NET CORE  </category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3496</guid>
      <comments>https://aspdotnet.tistory.com/3496#entry3496comment</comments>
      <pubDate>Fri, 9 Jan 2026 11:05:40 +0900</pubDate>
    </item>
    <item>
      <title>ASP.NET CORE 8 - IIS : HTTP Error 500.19 - Internal Server Error - Module IIS Web Core</title>
      <link>https://aspdotnet.tistory.com/3493</link>
      <description>&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;HTTP Error 500.19 - Internal Server Error&lt;br /&gt;The requested page cannot be accessed because the related configuration data for the page is invalid.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Detailed Error Information:&lt;br /&gt;Module IIS Web Core&lt;br /&gt;Notification Unknown&lt;br /&gt;Handler Not yet determined&lt;br /&gt;Error Code 0x8007000d&lt;br /&gt;Config Error&lt;br /&gt;Config File \\?\D:\publish\xxxx\web.config&lt;br /&gt;Requested URL &lt;a href=&quot;https://devnpm.unives.kr:443/&quot;&gt;https://xxxx.kr:443/&lt;/a&gt;&lt;br /&gt;Physical Path&lt;br /&gt;Logon Method Not yet determined&lt;br /&gt;Logon User Not yet determined&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Config Source:&lt;br /&gt;-1:&lt;br /&gt;0:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;More Information:&lt;br /&gt;This error occurs when there is a problem reading the configuration file for the Web server or Web application. In some cases, the event logs may contain more information about what caused this error.&lt;br /&gt;View more information &amp;raquo;&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;896&quot; data-origin-height=&quot;670&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBQcE7/dJMcaaqoYj7/JqC89vhfw5vtledzS15FKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBQcE7/dJMcaaqoYj7/JqC89vhfw5vtledzS15FKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBQcE7/dJMcaaqoYj7/JqC89vhfw5vtledzS15FKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBQcE7%2FdJMcaaqoYj7%2FJqC89vhfw5vtledzS15FKK%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;896&quot; height=&quot;670&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;670&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IIS에서 ASP.NET Core 8 실행에 필요한 런타임/모듈만 설치하면 됩니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;.NET 8 Hosting Bundle&lt;/b&gt; (ASP.NET Core 런타임 + IIS 모듈 포함)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;IIS 역할/기능 활성화&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Web Server(IIS)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ASP.NET Core Module&lt;/b&gt;(Hosting Bundle에 포함되어 자동 설치됨)&lt;/li&gt;
&lt;li&gt;(권장) &lt;b&gt;HTTP Activation&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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;Windows Server / Windows 10/11 공통&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Hosting Bundle 설치 후 &amp;rarr; &lt;b&gt;서버 재부팅&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;IIS에 &lt;code&gt;AspNetCoreModuleV2&lt;/code&gt; 가 등록되어 있어야 정상(자동 등록됨)&lt;/li&gt;
&lt;/ul&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;dotnet --version&lt;/code&gt; &amp;rarr; 8.x 출력 확인&lt;/li&gt;
&lt;li&gt;IIS 모듈 목록에 &lt;b&gt;AspNetCoreModuleV2&lt;/b&gt; 존재 확인&lt;/li&gt;
&lt;li&gt;앱풀은 &lt;b&gt;No Managed Code&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;로그 경로 &lt;code&gt;.\logs\stdout&lt;/code&gt; 쓰기 권한 부여&lt;/li&gt;
&lt;/ul&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;현재 web.config는 구조적으로 유효합니다(inprocess + ModuleV2 사용).&lt;br /&gt;Hosting Bundle 설치가 누락되어 8 앱이 IIS에서 로드되지 않는 상태로 보입니다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;.NET 8 Hosting Bundle 설치 &amp;rarr; 재부팅 &amp;rarr; 권한/앱풀 설정 &amp;rarr; 배포&lt;/li&gt;
&lt;/ul&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;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;challenge&quot; data-emoticon-name=&quot;016&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/challenge/large/016.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/challenge/large/016.png&quot; width=&quot;150&quot; /&gt;&lt;/figure&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;**ASP.NET&amp;nbsp;Core&amp;nbsp;Module&amp;nbsp;V2&amp;nbsp;포함&amp;nbsp;설치&amp;nbsp;패키지(.NET&amp;nbsp;Hosting&amp;nbsp;Bundle)**&amp;nbsp;다운로드&amp;nbsp;링크:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt; &amp;nbsp;**.NET&amp;nbsp;8&amp;nbsp;Hosting&amp;nbsp;Bundle&amp;nbsp;다운로드&amp;nbsp;(공식)**&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;➡️&amp;nbsp;**[.NET&amp;nbsp;8.0&amp;nbsp;다운로드&amp;nbsp;페이지&amp;nbsp;(Windows&amp;nbsp;Hosting&amp;nbsp;Bundle&amp;nbsp;포함)]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;a href=&quot;https://dotnet.microsoft.com/ko-KR/download/dotnet/8.0?utm_source=chatgpt.com)**&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dotnet.microsoft.com/ko-KR/download/dotnet/8.0?utm_source=chatgpt.com)**&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;Windows용 .NET 8 다운로드 페이지에서 **&amp;ldquo;.NET 8 Hosting Bundle&amp;rdquo;** 설치 관리자를 선택하면 IIS용 ASP.NET Core Module V2가 포함된 패키지를 받을 수 있습니다.&lt;br /&gt;&lt;br /&gt;또는&amp;nbsp;직접&amp;nbsp;IIS&amp;nbsp;호스팅&amp;nbsp;번들&amp;nbsp;설명&amp;nbsp;페이지&amp;nbsp;참고:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;➡️&amp;nbsp;**[IIS에서&amp;nbsp;ASP.NET&amp;nbsp;Core&amp;nbsp;호스팅&amp;nbsp;번들&amp;nbsp;설치&amp;nbsp;안내(MS&amp;nbsp;Docs)]&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://learn.microsoft.com/ko-kr/aspnet/core/host-and-deploy/iis/?view=aspnetcore-10.0&amp;amp;utm_source=chatgpt.com)**&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://learn.microsoft.com/ko-kr/aspnet/core/host-and-deploy/iis/?view=aspnetcore-10.0&amp;amp;utm_source=chatgpt.com)**&lt;/a&gt;&lt;br /&gt;이 문서에서 최신 Hosting Bundle 인스톨러를 받을 수 있도록 링크가 제공됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;설치&amp;nbsp;후&amp;nbsp;재부팅,&amp;nbsp;앱풀&amp;nbsp;**No&amp;nbsp;Managed&amp;nbsp;Code**&amp;nbsp;설정,&amp;nbsp;로그&amp;nbsp;폴더&amp;nbsp;권한&amp;nbsp;확인을&amp;nbsp;함께&amp;nbsp;진행하세요. &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>닷넷관련/ASP.NET CORE  </category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3493</guid>
      <comments>https://aspdotnet.tistory.com/3493#entry3493comment</comments>
      <pubDate>Wed, 31 Dec 2025 12:03:22 +0900</pubDate>
    </item>
    <item>
      <title>Google Search Console SEO, Playwright MCP Extension 활용 가이드</title>
      <link>https://aspdotnet.tistory.com/3491</link>
      <description>&lt;h1&gt;Google&amp;nbsp;Search&amp;nbsp;Console&amp;nbsp;SEO,&amp;nbsp;Playwright&amp;nbsp;MCP&amp;nbsp;Extension&amp;nbsp;활용&amp;nbsp;가이드&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 가이드는 Google Search Console의 SEO 문제를 자동으로 확인하는 방법을 단계별로 설명합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  왜 필요한가요?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google Search Console에는 중요한 크롤링(Crawl) 데이터가 있는데, API로는 볼 수 없어서 매번 웹사이트에 접속해서 확인해야 합니다. 이 과정을 자동화하면 시간을 절약하고 정기적으로 모니터링할 수 있습니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;  준비물&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Google Chrome 브라우저&lt;/li&gt;
&lt;li&gt;Claude Code (터미널에서 Claude를 사용할 수 있는 도구)&lt;/li&gt;
&lt;li&gt;Google Search Console 계정 (이미 있다고 가정)&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1단계: Claude Code 설치하기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Windows/Mac 공통&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널(또는 명령 프롬프트)을 열고 아래 명령어를 입력합니다:&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;npm install -g @anthropic-ai/claude-code&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가 완료되면 다음 명령어로 실행합니다:&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;claude-code&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2단계: Playwright MCP Extension 설치하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Playwright는 브라우저를 자동으로 조작할 수 있게 해주는 도구입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-1. Extension 다운로드&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/microsoft/playwright-mcp/blob/main/extension/README.md&quot;&gt;이 링크&lt;/a&gt;로 이동&lt;/li&gt;
&lt;li&gt;페이지에서 extension 폴더를 다운로드 (ZIP 파일로 다운로드 후 압축 해제)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-2. Chrome에 Extension 추가&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Chrome 브라우저를 열고 주소창에 &lt;code&gt;chrome://extensions/&lt;/code&gt; 입력&lt;/li&gt;
&lt;li&gt;오른쪽 위의 &lt;b&gt;&quot;개발자 모드&quot;&lt;/b&gt; 토글을 켭니다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&quot;압축해제된 확장 프로그램을 로드합니다&quot;&lt;/b&gt; 버튼 클릭&lt;/li&gt;
&lt;li&gt;다운로드한 extension 폴더를 선택&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;![Chrome Extension 설정 화면 예시]&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-3. Extension이 제대로 설치되었는지 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Chrome 오른쪽 위 확장 프로그램 아이콘 영역에 Playwright MCP가 표시되면 성공입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3단계: Claude Code에 Playwright MCP 연결하기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-1. 설정 파일 찾기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제별로 설정 파일 위치가 다릅니다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Mac/Linux:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;~/.config/claude-code/mcp.json&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Windows:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;taggerscript&quot;&gt;&lt;code&gt;%APPDATA%\claude-code\mcp.json&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-2. 설정 파일 수정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일을 텍스트 에디터(VS Code, 메모장 등)로 열고 아래 내용을 추가합니다:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;mcpServers&quot;: {
    &quot;playwright&quot;: {
      &quot;command&quot;: &quot;npx&quot;,
      &quot;args&quot;: [&quot;@playwright/mcp@latest&quot;, &quot;--caps&quot;, &quot;vision&quot;, &quot;--extension&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주의:&lt;/b&gt; 이미 &lt;code&gt;mcpServers&lt;/code&gt; 항목이 있다면, 기존 내용에 &lt;code&gt;&quot;playwright&quot;: {...}&lt;/code&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;3-3. Claude Code 재시작&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널을 닫고 다시 열어서 &lt;code&gt;claude-code&lt;/code&gt; 명령어로 실행합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4단계: Google에 로그인하기 (중요!)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Playwright가 여러분의 Google 로그인 세션을 사용할 수 있도록 해야 합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Chrome 브라우저에서 Google Search Console에 로그인&lt;/li&gt;
&lt;li&gt;로그인 상태를 유지한 채로 브라우저를 열어둡니다&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜 이렇게 하나요?&lt;/b&gt;&lt;br /&gt;매번 자동 로그인을 구현하는 것보다, 이미 로그인된 브라우저 세션을 가져오는 게 훨씬 쉽고 안전합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5단계: SEO 문제 확인 루틴 정리하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동화하기 전에, 수동으로 어떻게 확인하는지 정리해야 합니다. 예를 들어:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;1. Google Search Console 접속
2. 왼쪽 메뉴에서 &quot;설정&quot; &amp;gt; &quot;크롤링 통계&quot; 클릭
3. 다음 항목들을 확인:
   - 일일 크롤링 요청 수가 평소보다 50% 이상 감소했는지
   - 크롤링 오류율이 5% 이상인지
   - 호스트 상태가 &quot;사용 불가&quot;인지
4. 문제가 있으면 스크린샷 저장 및 보고&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 단계별로 작성하세요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6단계: Claude Code로 자동화 실행하기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6-1. Claude Code 실행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널에서:&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;claude-code&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6-2. 명령어 입력&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude Code가 실행되면 다음과 같이 요청합니다:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Google Search Console에서 SEO 문제를 확인해줘.

확인할 URL: https://search.google.com/search-console?resource_id=YOUR_SITE_URL

다음 항목들을 체크해줘:
1. 크롤링 통계 페이지의 일일 요청 수
2. 크롤링 오류율
3. 인덱싱 상태

각 항목에서 문제가 발견되면 스크린샷과 함께 보고서를 작성해줘.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;YOUR_SITE_URL 부분을 여러분의 실제 사이트 URL로 바꾸세요.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6-3. 권한 허용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 실행하면 &quot;브라우저를 제어해도 되나요?&quot;라는 메시지가 나타납니다.&lt;/p&gt;
&lt;pre class=&quot;apache&quot;&gt;&lt;code&gt;Allow browser control? [y/N]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;y&lt;/code&gt; 를 입력하고 Enter를 누릅니다.&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;6-4. 결과 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude Code가:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Chrome 브라우저를 자동으로 열고&lt;/li&gt;
&lt;li&gt;Google Search Console 페이지를 방문하며&lt;/li&gt;
&lt;li&gt;스크린샷을 찍고 분석한 후&lt;/li&gt;
&lt;li&gt;문제점을 정리한 보고서를 작성해줍니다&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  실전 팁&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;&lt;b&gt;Mac/Linux (cron 사용):&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# crontab 편집
crontab -e

# 매일 오전 9시에 실행 (아래 줄 추가)
0 9 * * * cd /path/to/your/script &amp;amp;&amp;amp; claude-code run seo-check.txt&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Windows (작업 스케줄러 사용):&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&quot;작업 스케줄러&quot; 검색 후 실행&lt;/li&gt;
&lt;li&gt;&quot;기본 작업 만들기&quot; 클릭&lt;/li&gt;
&lt;li&gt;이름 입력 후 실행 주기 설정&lt;/li&gt;
&lt;li&gt;프로그램 시작에서 &lt;code&gt;claude-code&lt;/code&gt; 경로 지정&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 해결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;MCP 서버를 찾을 수 없습니다&quot; 오류:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;mcp.json&lt;/code&gt; 파일 경로가 맞는지 확인&lt;/li&gt;
&lt;li&gt;Claude Code를 재시작했는지 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;브라우저가 열리지 않습니다:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Chrome Extension이 제대로 설치되었는지 확인&lt;/li&gt;
&lt;li&gt;Chrome이 완전히 종료되어 있는지 확인 (작업 관리자에서 프로세스 확인)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;로그인이 안 됩니다:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Chrome에서 Google Search Console에 로그인되어 있는지 확인&lt;/li&gt;
&lt;li&gt;로그인 세션이 만료되지 않았는지 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  배운 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 자동화를 통해:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;반복적인 수동 작업을 줄일 수 있습니다&lt;/li&gt;
&lt;li&gt;API가 없는 기능도 브라우저 자동화로 해결할 수 있습니다&lt;/li&gt;
&lt;li&gt;업무 프로세스를 문서화하고 자동화하는 방법을 배웁니다&lt;/li&gt;
&lt;li&gt;팀의 다른 사람도 같은 작업을 쉽게 수행할 수 있게 됩니다&lt;/li&gt;
&lt;/ul&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.gpters.org/marketing/post/automating-google-search-console-fbU1mvsDkrejdgO?utm_source=newsletter&amp;amp;utm_medium=email&amp;amp;utm_campaign=case-newsletter&amp;amp;utm_term=251222&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.gpters.org/marketing/post/automating-google-search-console-fbU1mvsDkrejdgO?utm_source=newsletter&amp;amp;utm_medium=email&amp;amp;utm_campaign=case-newsletter&amp;amp;utm_term=251222&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1766727445447&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;ClaudeCode에서 구글 서치콘솔 SEO 확인 자동화하기&quot; data-og-description=&quot;이슈 구글 서치 콘솔에서는 서치 콘솔 API로는 볼 수 없는 데이터가 있습니다. 특히 SEO(검색 엔진 최적화)에서 Crawl stats와 관련된 데이터가 정말 중요한 데이터임에도 불구하고 서치 콘솔 API로 볼&quot; data-og-host=&quot;www.gpters.org&quot; data-og-source-url=&quot;https://www.gpters.org/marketing/post/automating-google-search-console-fbU1mvsDkrejdgO?utm_source=newsletter&amp;amp;utm_medium=email&amp;amp;utm_campaign=case-newsletter&amp;amp;utm_term=251222&quot; data-og-url=&quot;https://www.gpters.org/marketing/post/automating-google-search-console-fbU1mvsDkrejdgO?utm_campaign=case-newsletter&amp;amp;utm_medium=email&amp;amp;utm_source=newsletter&amp;amp;utm_term=251222&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bgFe6z/hyZPW5f6lN/qJbbqCZGzfIh8N7K8lTr2K/img.jpg?width=1000&amp;amp;height=711&amp;amp;face=0_0_1000_711,https://scrap.kakaocdn.net/dn/hff4i/hyZQAM45km/rYPUQPtUnxughXvUMkgVBK/img.jpg?width=1000&amp;amp;height=711&amp;amp;face=0_0_1000_711,https://scrap.kakaocdn.net/dn/pcibJ/hyZPECKhyb/3ll5Kd0YzVaW1LJZ4EOW41/img.jpg?width=2048&amp;amp;height=1161&amp;amp;face=0_0_2048_1161&quot;&gt;&lt;a href=&quot;https://www.gpters.org/marketing/post/automating-google-search-console-fbU1mvsDkrejdgO?utm_source=newsletter&amp;amp;utm_medium=email&amp;amp;utm_campaign=case-newsletter&amp;amp;utm_term=251222&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.gpters.org/marketing/post/automating-google-search-console-fbU1mvsDkrejdgO?utm_source=newsletter&amp;amp;utm_medium=email&amp;amp;utm_campaign=case-newsletter&amp;amp;utm_term=251222&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bgFe6z/hyZPW5f6lN/qJbbqCZGzfIh8N7K8lTr2K/img.jpg?width=1000&amp;amp;height=711&amp;amp;face=0_0_1000_711,https://scrap.kakaocdn.net/dn/hff4i/hyZQAM45km/rYPUQPtUnxughXvUMkgVBK/img.jpg?width=1000&amp;amp;height=711&amp;amp;face=0_0_1000_711,https://scrap.kakaocdn.net/dn/pcibJ/hyZPECKhyb/3ll5Kd0YzVaW1LJZ4EOW41/img.jpg?width=2048&amp;amp;height=1161&amp;amp;face=0_0_2048_1161');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;ClaudeCode에서 구글 서치콘솔 SEO 확인 자동화하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이슈 구글 서치 콘솔에서는 서치 콘솔 API로는 볼 수 없는 데이터가 있습니다. 특히 SEO(검색 엔진 최적화)에서 Crawl stats와 관련된 데이터가 정말 중요한 데이터임에도 불구하고 서치 콘솔 API로 볼&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.gpters.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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 관련/AI 기술</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3491</guid>
      <comments>https://aspdotnet.tistory.com/3491#entry3491comment</comments>
      <pubDate>Fri, 26 Dec 2025 14:36:34 +0900</pubDate>
    </item>
    <item>
      <title>Gemini CLI 설치 및 chat 하기</title>
      <link>https://aspdotnet.tistory.com/3489</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DUavK/dJMb99ZeUa2/yYpqadGWUorIQHxIiKdC10/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DUavK/dJMb99ZeUa2/yYpqadGWUorIQHxIiKdC10/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DUavK/dJMb99ZeUa2/yYpqadGWUorIQHxIiKdC10/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDUavK%2FdJMb99ZeUa2%2FyYpqadGWUorIQHxIiKdC10%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;1280&quot; height=&quot;720&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;  터미널에서 AI와 페어 프로그래밍하기: Gemini CLI 완벽 가이드&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;개발하다가 모르는 게 나오면 매번 브라우저를 켜고, 구글링하고, 다시 에디터로 돌아오시나요? 이제 그럴 필요가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 Google의 강력한 AI 모델인 &lt;b&gt;Gemini&lt;/b&gt;를 내 터미널(Terminal)에 심어두고, 마치 &lt;b&gt;사수와 대화하듯 코딩하는 방법&lt;/b&gt;을 소개합니다. 설치부터 자주 발생하는 에러 해결법까지 한 번에 정리했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1.  ️ 설치하기 (30초 컷)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node.js가 설치된 환경이라면 &lt;code&gt;npm&lt;/code&gt;으로 아주 간단하게 설치할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;npm i -g @google/gemini-cli
&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;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2.   API 키 설정 (가장 중요!)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gemini를 내 컴퓨터에서 부리려면(?) &lt;b&gt;API 키&lt;/b&gt;가 필요합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;a href=&quot;https://aistudio.google.com/app/apikey&quot;&gt;Google AI Studio&lt;/a&gt;&lt;/b&gt;에 접속해서 무료로 키를 발급받으세요.&lt;/li&gt;
&lt;li&gt;발급받은 키를 &lt;b&gt;환경 변수&lt;/b&gt;로 등록해야 합니다. (매번 입력하면 귀찮으니까요!)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;윈도우 PowerShell 기준:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;$env:GEMINI_API_KEY=&quot;여러분의_API_키_붙여넣기&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;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  꿀팁:&lt;/b&gt; 위 명령어는 현재 창을 닫으면 사라집니다. 계속 쓰려면 윈도우 검색창에 &lt;b&gt;'시스템 환경 변수 편집'&lt;/b&gt;을 검색해서 &lt;code&gt;GEMINI_API_KEY&lt;/code&gt;를 새로 만들어 등록해 두세요.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3.   실전 사용법: 딱 2개만 기억하세요&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① 궁금한 건 바로 물어보기: &lt;code&gt;gemini chat&lt;/code&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념이 헷갈리거나 간단한 코드가 필요할 때 사용합니다.&lt;/p&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;# 예시: JavaScript 배열 메서드 추천받기
gemini chat &quot;자바스크립트에서 배열 중복 제거하는 가장 효율적인 방법은?&quot;

# 예시: CSS 중앙 정렬 까먹었을 때
gemini chat &quot;Flexbox로 div 정가운데 정렬하는 코드 줘.&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;&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;② 내 코드 리뷰 &amp;amp; 리팩토링: &lt;code&gt;gemini generate&lt;/code&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기능이 진짜 &lt;b&gt;치트키&lt;/b&gt;입니다. 파일 경로를 같이 적어주면 AI가 내 코드를 읽고 답변해 줍니다.&lt;/p&gt;
&lt;pre class=&quot;verilog&quot;&gt;&lt;code&gt;# 예시: 레거시 코드 해석 요청
gemini generate &quot;이 코드가 무슨 역할을 하는지 주니어 눈높이로 설명해 줘.&quot; ./src/complex-utils.js

# 예시: 리팩토링 요청
gemini generate &quot;이 함수를 화살표 함수로 바꾸고 가독성을 높여줘.&quot; ./src/old-function.js
&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  트러블슈팅: 미리 알고 가면 당황하지 않는 에러들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치하다 보면 마주칠 수 있는 에러들과 해결법입니다. (이것만 알아도 삽질 시간 1시간 줄입니다!)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Q1. &lt;code&gt;You have exhausted your daily quota&lt;/code&gt; 에러가 떠요!&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;A. 모델을 바꿔보세요.&lt;/b&gt;&lt;br /&gt;기본 모델의 하루 사용량이 꽉 찼다는 뜻입니다. 더 가볍고 빠른 &lt;b&gt;Flash 모델&lt;/b&gt;을 명시적으로 지정하면 해결됩니다.&lt;/p&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;# --model 옵션 추가
gemini chat --model gemini-2.5-flash &quot;안녕?&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Q2. &lt;code&gt;Error executing tool write_file...&lt;/code&gt; 같은 로그가 떠요!&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;A. 무시하셔도 됩니다. (정상입니다)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Error executing tool write_file: Tool &quot;write_file&quot; not found in registry...
&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;이건 Gemini가 &quot;코드를 파일로 저장해 줄까?&quot; 하고 시도하다가, 보안상 막혀있어서 실패했다는 내부 로그입니다. &lt;b&gt;코드 생성과 답변에는 전혀 문제가 없으니 안심하고 무시하세요.&lt;/b&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;Q3. &lt;code&gt;Running in system directory&lt;/code&gt; 권한 경고가 떠요!&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;A. 사용자 폴더로 이동하세요.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;code&gt;C:\Windows\System32&lt;/code&gt; 같은 곳에서 실행하지 마시고, &lt;code&gt;cd C:\Users\내이름&lt;/code&gt; 명령어로 &lt;b&gt;내 사용자 폴더&lt;/b&gt;나 &lt;b&gt;프로젝트 폴더&lt;/b&gt;로 이동해서 실행하면 깔끔하게 해결됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gemini CLI는 단순한 도구가 아니라, 터미널 상주하는 &lt;b&gt;친절한 AI 사수&lt;/b&gt;입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;문법이 헷갈릴 때&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;에러 로그 해석이 안 될 때&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;변수명이 도저히 안 떠오를 때&lt;/b&gt;&lt;/li&gt;
&lt;/ul&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI 관련/AI 기술</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3489</guid>
      <comments>https://aspdotnet.tistory.com/3489#entry3489comment</comments>
      <pubDate>Wed, 17 Dec 2025 12:36:03 +0900</pubDate>
    </item>
    <item>
      <title>유투브 : Cursor 2.2 디버그 모드가 버그를 자동으로 수정해 줍니다 (다중 에이전트 기능)</title>
      <link>https://aspdotnet.tistory.com/3483</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Cursor 2.2 새 기능 완벽 가이드 (영상 대본 상세 설명)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 영상은 &lt;b&gt;Cursor 2.2 버전&lt;/b&gt;의 4가지 핵심 새 기능을 실제로 시연하면서 보여주는 내용이에요.&lt;br /&gt;개발자가 아니어도 이해할 수 있도록 아주 쉽게 설명해 드릴게요!  &lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;1. Debug Mode (디버그 모드) - 버그를 자동으로 잡아주는 AI 의사!&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[0:14 ~ 2:10]&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이게 뭔가요?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램에 문제(버그)가 있을 때, AI가 &lt;b&gt;의사처럼&lt;/b&gt; 진단하고 치료해주는 기능이에요.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;어떻게 작동하나요?&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;문제 진단&lt;/b&gt;  
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;발표자가 &quot;내 코드에 문제가 있는지 찾아줘&quot;라고 요청&lt;/li&gt;
&lt;li&gt;AI가 코드 전체를 스캔하고 &quot;아, 여기 문제가 있을 것 같은데?&quot;라고 가설을 세워요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;검사 도구 설치&lt;/b&gt;  ️
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AI가 자동으로 &quot;계측(instrumentation)&quot; 코드를 추가해요&lt;/li&gt;
&lt;li&gt;쉽게 말해, 병원에서 엑스레이 찍듯이 코드 곳곳에 &quot;센서&quot;를 달아서 문제를 찾아내는 거예요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;문제 발견 및 수리&lt;/b&gt; ✅
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제로 &quot;키 불일치&quot; 문제를 3곳에서 발견!&lt;/li&gt;
&lt;li&gt;AI가 자동으로 모든 오류를 수정&lt;/li&gt;
&lt;li&gt;다시 테스트해서 &quot;네, 이제 잘 작동해요!&quot;라고 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;왜 대단한가요?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에는 개발자가 몇 시간씩 걸려서 버그를 찾았는데, 이제 &lt;b&gt;1~2분 만에&lt;/b&gt; AI가 찾아서 고쳐줘요!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; ️ &lt;b&gt;2. Plan Mode with Images (계획 모드 + 그림) - 복잡한 계획을 그림으로!&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[2:12 ~ 3:13]&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이게 뭔가요?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 프로젝트 계획을 세울 때, 텍스트만 쓰는 게 아니라 &lt;b&gt;다이어그램(도표)&lt;/b&gt;도 그려주는 기능이에요.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실제 시연 내용:&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;발표자가 &quot;내 벤치마킹 도구에 추가할 새 기능 찾아줘&quot;라고 요청&lt;/li&gt;
&lt;li&gt;AI가 인터넷 검색하고 분석해서 계획 작성&lt;/li&gt;
&lt;li&gt;&quot;그림(Mermaid 다이어그램)도 추가해줘&quot;라고 하니 &lt;b&gt;순서도&lt;/b&gt;를 그려줌&lt;/li&gt;
&lt;li&gt;제안된 기능들:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;병렬 실행&lt;/b&gt; (여러 테스트 동시 진행) - 최우선!&lt;/li&gt;
&lt;li&gt;토큰 추적 기능&lt;/li&gt;
&lt;li&gt;HTML 대시보드&lt;/li&gt;
&lt;li&gt;CSV 파일 내보내기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;왜 좋은가요?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복잡한 계획도 &lt;b&gt;한눈에&lt;/b&gt; 이해할 수 있어요. 마치 지하철 노선도처럼요!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;3. Multi-Agent Judging (여러 AI가 경쟁하고 심사!) - AI 배틀 로얄!&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[3:16 ~ 6:20]&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이게 뭔가요?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;두 명의 AI&lt;/b&gt;가 동시에 같은 문제를 풀고, &lt;b&gt;또 다른 AI 심사위원&lt;/b&gt;이 누가 더 잘했는지 평가해주는 기능!&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실제 시연:&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;미션&lt;/b&gt;: &quot;병렬 실행 기능을 구현해라!&quot;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;참가자&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;  &lt;b&gt;Gemini 3 Pro&lt;/b&gt; (구글 AI)&lt;/li&gt;
&lt;li&gt;  &lt;b&gt;Claude Opus 3.5&lt;/b&gt; (Anthropic AI)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;동시 작업&lt;/b&gt;: 두 AI가 각자 방식으로 코드 작성&lt;/li&gt;
&lt;li&gt;&lt;b&gt;심사 결과&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Opus 3.5가 승리! ✨&lt;/li&gt;
&lt;li&gt;이유: 더 깔끔한 코드 구조, 더 좋은 에러 처리, 실시간 진행 상황 표시, 문서화도 더 잘함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성능 테스트&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 방식(순차 실행): 26초 ⏱️&lt;/li&gt;
&lt;li&gt;새 방식(병렬 실행): &lt;b&gt;7초!&lt;/b&gt;   (3.7배 빨라짐!)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;왜 혁신적인가요?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전엔 개발자가 &quot;어떤 코드가 더 나은지&quot; 고민했는데, 이제 AI가 알아서 &lt;b&gt;최고의 답&lt;/b&gt;을 골라줘요!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;4. Pin Chat (채팅 고정) - 중요한 대화 즐겨찾기!&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[6:22 ~ 6:31]&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이게 뭔가요?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카톡 공지사항처럼 &lt;b&gt;중요한 대화를 상단에 고정&lt;/b&gt;하는 기능이에요.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;사용법:&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;채팅 옆 점 3개(...) 클릭 &amp;rarr; &quot;Pin&quot; 선택&lt;/li&gt;
&lt;li&gt;나중에 쉽게 찾아볼 수 있음&lt;/li&gt;
&lt;li&gt;언제든 고정 해제 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;b&gt;&quot;AI가 개발자의 모든 귀찮은 일을 대신 해준다!&quot;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;  버그 찾기? &amp;rarr; AI가 자동으로!&lt;/li&gt;
&lt;li&gt;  복잡한 계획? &amp;rarr; 그림으로 쉽게!&lt;/li&gt;
&lt;li&gt;  어떤 코드가 나은지? &amp;rarr; AI들이 경쟁해서 최고만 선택!&lt;/li&gt;
&lt;li&gt;  중요한 건? &amp;rarr; 고정해두고 나중에 봐!&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  &lt;b&gt;누가 봐야 하나요?&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로그래머 (특히 AI 도구 관심자)&lt;/li&gt;
&lt;li&gt;스타트업 창업자&lt;/li&gt;
&lt;li&gt;코딩 배우는 학생&lt;/li&gt;
&lt;li&gt;&quot;AI가 얼마나 똑똑해졌나&quot; 궁금한 모든 분&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발표자는 실제로 자신의 프로젝트(AI 벤치마킹 도구)로 시연해서 더 실감나게 보여줬어요.&lt;br /&gt;특히 병렬 실행으로 &lt;b&gt;26초&amp;rarr;7초&lt;/b&gt;로 단축된 부분에서 정말 놀라워했답니다!  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Cursor 2.2로 코딩이 정말 게임처럼 쉬워졌네요!  &lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;challenge&quot; data-emoticon-name=&quot;020&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/challenge/large/020.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/challenge/large/020.png&quot; width=&quot;150&quot; /&gt;&lt;/figure&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;h3 data-ke-size=&quot;size23&quot;&gt;Cursor 2.2 주요 신기능 4가지&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Debug Mode (디버그 모드)&lt;/b&gt; &amp;ndash; 가장 강력한 신기능!
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;무엇을 해주나요?&lt;/b&gt;&lt;br /&gt;코드에 버그(오류)가 있을 때, AI가 자동으로&lt;br /&gt;① 코드를 분석해서 가능한 버그 원인을 추측하고&lt;br /&gt;② 코드에 &amp;ldquo;관찰 코드&amp;rdquo;(instrumentation)를 자동으로 삽입해서&lt;br /&gt;③ 프로그램을 실제로 돌려보며 로그를 수집하고&lt;br /&gt;④ 버그의 근본 원인을 찾아서 자동으로 고쳐줍니다.&lt;br /&gt;마지막으로 고친 후 다시 테스트까지 돌려서 제대로 됐는지 확인해 줘요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;시연 예시&lt;/b&gt;&lt;br /&gt;발표자가 자신의 LLM 벤치마크 앱을 열고&lt;br /&gt;&amp;ldquo;디버그 모드로 문제 찾아봐&amp;rdquo;라고만 입력 &amp;rarr;&lt;br /&gt;AI가 자동으로 키 불일치 버그를 발견 &amp;rarr;&lt;br /&gt;3곳을 자동 수정 &amp;rarr; 다시 테스트 돌려서 성공 확인.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;왜 좋은가?&lt;/b&gt;&lt;br /&gt;예전에 개발자가 직접 로그 찍고 하나하나 찾아야 했던 고통스러운 디버깅을&lt;br /&gt;AI가 거의 다 해줍니다. 특히 복잡한 버그일수록 빛을 발해요.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Plan Mode에서 이미지 지원 (Mermaid 다이어그램 추가)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;무엇을 해주나요?&lt;/b&gt;&lt;br /&gt;큰 기능을 추가하거나 프로젝트를 계획할 때&lt;br /&gt;AI가 작성하는 &amp;ldquo;계획서(Plan)&amp;rdquo;에 자동으로 &lt;b&gt;Mermaid 다이어그램&lt;/b&gt;(흐름도, 구조도 같은 그림)을 넣어줍니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;시연 예시&lt;/b&gt;&lt;br /&gt;&amp;ldquo;이 앱에 새 기능 추가할 만한 게 뭐가 있을까?&amp;rdquo;라고 물어보니&lt;br /&gt;AI가 웹 검색도 하고 여러 아이디어를 내준 뒤 계획서를 작성 &amp;rarr;&lt;br /&gt;&amp;ldquo;각 기능마다 Mermaid 이미지 넣어줘&amp;rdquo;라고 하니&lt;br /&gt;바로 흐름도와 구조도가 삽입된 보기 쉬운 계획서가 완성됐어요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;왜 좋은가?&lt;/b&gt;&lt;br /&gt;텍스트만 있는 계획서보다 훨씬 직관적으로 이해하고 소통하기 쉬워요.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Multi-Agent Judging (멀티 에이전트 자동 평가)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;무엇을 해주나요?&lt;/b&gt;&lt;br /&gt;하나의 작업을 &lt;b&gt;여러 AI 에이전트&lt;/b&gt;가 동시에 수행하게 한 뒤,&lt;br /&gt;각 에이전트가 만든 결과를 서로 비교&amp;middot;평가해서&lt;br /&gt;&amp;ldquo;이쪽이 더 좋은 코드야&amp;rdquo;라고 &lt;b&gt;가장 좋은 버전을 자동으로 추천&lt;/b&gt;해 줍니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;시연 예시&lt;/b&gt;&lt;br /&gt;&amp;ldquo;병렬 실행(parallel execution) 기능 만들어줘&amp;rdquo;라고 요청 &amp;rarr;&lt;br /&gt;동시에 두 에이전트(Gemini 3 Pro + Opus 4.5)를 투입 &amp;rarr;&lt;br /&gt;둘 다 구현 완료 후 Cursor가 자동으로 비교 &amp;rarr;&lt;br /&gt;&amp;ldquo;Opus 4.5 버전이 코드 구조, 에러 처리, 문서화 면에서 더 우수하다&amp;rdquo;고 판단 &amp;rarr;&lt;br /&gt;발표자가 한 번에 그 버전을 적용.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;왜 좋은가?&lt;/b&gt;&lt;br /&gt;예전에는 여러 버전 중 뭐가 제일 좋은지 개발자가 직접 비교해야 했는데,&lt;br /&gt;이제 AI가 객관적으로 판단해 주니 시간도 절약되고 품질도 올라가요.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Pin Chat (중요 대화 고정하기)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;간단하지만 편리한 기능&lt;br /&gt;채팅 창에서 특정 대화(예: 중요한 계획, 완성된 코드 등)가 마음에 들면&lt;br /&gt;오른쪽 위 ⋯ 버튼 &amp;rarr; &amp;ldquo;Pin&amp;rdquo; 클릭 &amp;rarr;&lt;br /&gt;대화 목록 상단에 고정돼서 나중에 쉽게 다시 찾을 수 있어요.&lt;br /&gt;필요 없으면 &amp;ldquo;Unpin&amp;rdquo;으로 해제 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실제 성능 시연 결과 (영상 속 예시)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발표자가 새로 만든 &amp;ldquo;병렬 실행&amp;rdquo; 기능을 테스트한 결과:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 순차 실행(sequential): 26초 소요&lt;/li&gt;
&lt;li&gt;새 병렬 실행(parallel): &lt;b&gt;7초&lt;/b&gt; 소요&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 약 &lt;b&gt;3.7배&lt;/b&gt; 빨라졌어요!&lt;br /&gt;이처럼 실제로 체감할 수 있는 속도 향상이 나왔습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이 영상을 보는 사람에게 추천하는 말&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;이미 Cursor를 쓰고 있다면&lt;/b&gt; &amp;rarr; 바로 2.2로 업데이트해서 Debug Mode와 Multi-Agent 기능을 써보세요. 생산성이 확 올라갈 거예요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;아직 Cursor를 안 써봤다면&lt;/b&gt; &amp;rarr; 이 영상만 봐도 &amp;ldquo;와, 이제 AI가 코딩을 진짜로 대신해 주네&amp;rdquo;라는 느낌이 확 올 거예요.&lt;/li&gt;
&lt;li&gt;특히 혼자서 앱&amp;middot;웹을 만드는 인디 개발자, 스타트업 개발자에게 강력 추천!&lt;/li&gt;
&lt;/ul&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;u&gt;&lt;b&gt;발췌 내용&lt;/b&gt;&lt;/u&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;a href=&quot;https://www.youtube.com/watch?v=ejRcMxeU0oc&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=ejRcMxeU0oc&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=ejRcMxeU0oc&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/i3KDj/hyZPk58WqP/AcG7zmFpfRsE9nEbLMrj7K/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=554_134_738_336,https://scrap.kakaocdn.net/dn/b8zTj4/hyZPxivpbc/G3WOlybLf8hxAcYPZTJjN1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=554_134_738_336&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;Cursor 2.2 Debug Mode Fixed My Bugs Automatically (Multi-Agent Features)&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/ejRcMxeU0oc&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI 관련/AI 기술</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3483</guid>
      <comments>https://aspdotnet.tistory.com/3483#entry3483comment</comments>
      <pubDate>Sun, 14 Dec 2025 04:32:16 +0900</pubDate>
    </item>
    <item>
      <title>Secure Coding 실무에서 바로 적용하는 코드 중심 예시</title>
      <link>https://aspdotnet.tistory.com/3482</link>
      <description>&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;Secure Coding 실무에서 바로 적용하는 코드 중심 예시&lt;/b&gt;들이다.&lt;br /&gt;언어 구분 없이 &lt;b&gt;웹, API, DB, 파일 처리, 인증&amp;middot;암호화&lt;/b&gt; 등 실제로 사고가 많이 나는 포인트만 골라서 정리했다.&lt;br /&gt;모두 &amp;ldquo;나쁜 코드 &amp;rarr; 좋은 코드&amp;rdquo; 형태로 설명한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;✅ 1. 입력 검증(Input Validation)&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;❌ 잘못된 예&lt;/h3&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;// 사용자가 입력한 값을 그대로 사용
const id = req.query.id;
const sql = `SELECT * FROM Users WHERE Id = ${id}`;
db.query(sql);
&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;/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;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 숫자 검증 + 파라미터 바인딩
const id = parseInt(req.query.id, 10);
if (Number.isNaN(id)) return res.status(400).send('Invalid');

const sql = &quot;SELECT * FROM Users WHERE Id = ?&quot;;
db.query(sql, [id]);
&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 실무 포인트&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;숫자는 parseInt, 문자열 길이/포맷 검증&lt;/li&gt;
&lt;li&gt;SQL은 무조건 파라미터 바인딩&lt;/li&gt;
&lt;li&gt;&amp;ldquo;검증 실패 = 요청 거절&amp;rdquo;이 기본 전략&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;✅ 2. SQL Injection 방지 (Prepared Statement 필수)&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;❌ 취약한 코드&lt;/h3&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;var sql = &quot;UPDATE Product SET Name = '&quot; + name + &quot;' WHERE Id=&quot; + id;
db.Execute(sql);
&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 안전한 코드 (Dapper)&lt;/h3&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;var sql = &quot;UPDATE Product SET Name = @Name WHERE Id = @Id&quot;;
db.Execute(sql, new { Name = name, Id = id });
&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;&lt;b&gt;실무 사투리 팁&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;ldquo;문자열 붙이면 끝장난다.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Dapper, EFCore, JDBC, PDO, 무엇이든 바인딩이 기본기&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;✅ 3. XSS 방지 (출력 시 인코딩)&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;❌ 나쁜 예&lt;/h3&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;div&amp;gt;사용자 입력: {{ userInput }}&amp;lt;/div&amp;gt;
&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 안전한 예 (Vue/React 기본 escape 기능 활용)&lt;/h3&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;div&amp;gt;사용자 입력: {{ safeText }}&amp;lt;/div&amp;gt;
&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;또는 서버에서 직접 이스케이프:&lt;/p&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;var safe = System.Net.WebUtility.HtmlEncode(input);
&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;&lt;b&gt;실무 팁&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;XSS는 입력이 아니라 &lt;b&gt;출력 시점에서 해결&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;리치 텍스트 필요하면 &amp;ldquo;화이트리스트 필터링&amp;rdquo; 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;✅ 4. 파일 업로드 (디렉토리 탈출 방지)&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;❌ 취약 코드&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;var path = &quot;uploads/&quot; + file.FileName;
await File.WriteAllBytesAsync(path, bytes);
&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;❗ 공격 예&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;../../../web.config &amp;rarr; 서버 파일 덮어쓰기&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 안전 코드&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;var safeName = Path.GetFileName(file.FileName); // 디렉토리 제거
var safePath = Path.Combine(&quot;uploads&quot;, safeName);
await File.WriteAllBytesAsync(safePath, bytes);
&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;추가 보호&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;확장자 화이트리스트 (.jpg, .png, .pdf)&lt;/li&gt;
&lt;li&gt;실행 가능한 파일 차단 (.exe, .aspx, .js)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;✅ 5. 인증 토큰 저장 시 Secure 옵션&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;❌ 취약 쿠키&lt;/h3&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Response.Cookies.Append(&quot;Token&quot;, token);
&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 보안 쿠키&lt;/h3&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;Response.Cookies.Append(&quot;Token&quot;, token, new CookieOptions {
    HttpOnly = true,
    Secure = true,
    SameSite = SameSiteMode.Strict
});
&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;&lt;b&gt;실무 팁&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HttpOnly: JS 탈취 방지&lt;/li&gt;
&lt;li&gt;Secure: HTTPS 전송 강제&lt;/li&gt;
&lt;li&gt;SameSite: CSRF 완화&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;✅ 6. 암호화 &amp;ndash; 절대 직접 구현하지 말 것&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;❌ 직접 만든 암호화(실무에서 절대 금지)&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;string Encrypt(string s) {
    return Convert.ToBase64String(Encoding.UTF8.GetBytes(s)); // 암호화 아님
}
&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 안전한 암호화 (AES-GCM 예시)&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;using var aes = Aes.Create();
aes.Mode = CipherMode.GCM; 
aes.GenerateKey();
aes.GenerateIV();

using var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
var cipherText = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실무 팁&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AES-GCM / RSA / PBKDF2 등 &lt;b&gt;검증된 알고리즘 사용&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;직접 암호화 로직 설계&amp;rdquo;는 금지&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;✅ 7. 비밀번호 저장 (Hash + Salt)&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;❌ 나쁜 예&lt;/h3&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;Password = SHA256('1234')
&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;❗ 해시만 쓰면 rainbow table에 뚫린다&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 안전 예&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PBKDF2&lt;/li&gt;
&lt;li&gt;bcrypt&lt;/li&gt;
&lt;li&gt;Argon2&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C# 예시(ASP.NET Core 기본)&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;var hashed = passwordHasher.HashPassword(user, plainPassword);
&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;✅ 8. 예외 메시지 노출 금지 (논리 정보 노출 방지)&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;❌ 위험한 예&lt;/h3&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;catch (SqlException ex) {
    return BadRequest(ex.ToString()); // 내부 DB 정보 노출
}
&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 안전한 예&lt;/h3&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;catch {
    _logger.LogError(&quot;DB 오류 발생&quot;);
    return StatusCode(500, &quot;Server Error&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;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;✅ 9. 서버 사이드 요청 위조(SSRF) 방지&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;❌ 취약 예&lt;/h3&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;var json = await http.GetStringAsync(userInputUrl);
&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✓ 위험: 내부망 접근, 메타데이터 API 접근 가능&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 안전 예&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;if (!Uri.TryCreate(url, UriKind.Absolute, out Uri? uri))
    return BadRequest();

var allowedSchemes = new[] { &quot;http&quot;, &quot;https&quot; };
if (!allowedSchemes.Contains(uri.Scheme))
    return BadRequest(&quot;Invalid Scheme&quot;);

// 도메인 화이트리스트
var allowedHosts = new[] { &quot;api.company.com&quot; };
if (!allowedHosts.Contains(uri.Host))
    return BadRequest(&quot;Blocked Host&quot;);

var json = await http.GetStringAsync(uri);
&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;  종합 실무 요약&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위험 방지 핵심&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SQL Injection&lt;/td&gt;
&lt;td&gt;파라미터 바인딩 필수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XSS&lt;/td&gt;
&lt;td&gt;출력 인코딩&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CSRF&lt;/td&gt;
&lt;td&gt;SameSite/CSRF 토큰&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;파일 업로드&lt;/td&gt;
&lt;td&gt;파일명 정규화 + 확장자 제한&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;인증&lt;/td&gt;
&lt;td&gt;HttpOnly + Secure 쿠키&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;비밀번호&lt;/td&gt;
&lt;td&gt;bcrypt/PBKDF2/Argon2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;예외 처리&lt;/td&gt;
&lt;td&gt;내부 정보 숨기기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;암호화&lt;/td&gt;
&lt;td&gt;표준 알고리즘 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>닷넷관련/CSharp  </category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3482</guid>
      <comments>https://aspdotnet.tistory.com/3482#entry3482comment</comments>
      <pubDate>Fri, 12 Dec 2025 16:05:57 +0900</pubDate>
    </item>
    <item>
      <title>IIS에서 ASP.NET Core 8 운영 시 Application Pool 설정 최적화 가이드</title>
      <link>https://aspdotnet.tistory.com/3481</link>
      <description>&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;IS + ASP.NET Core 8 환경에서의 Application Pool 튜닝 전략을 최신 기준으로 재정리한 기술 블로그 글&lt;/b&gt;입니다.&lt;br /&gt;불필요한 과장은 제거하고, 실무 중심&amp;middot;이론 근거&amp;middot;최신 운영 패턴이 일관되게 잡히도록 구성했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;IIS에서 ASP.NET Core 8 운영 시 Application Pool 설정 최적화 가이드&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ndash; AlwaysRunning, Idle Timeout, Preload, Recycle까지 완전 정리 &amp;ndash;&lt;/b&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;운영 환경에서 ASP.NET Core 기반 웹서비스를 IIS 위에서 구동할 때, 서비스 첫 요청 시 응답이 지연되거나 &lt;b&gt;아침 시간대 Gateway Timeout&lt;/b&gt; 문제가 발생하는 경우가 자주 보고된다. 이는 대부분 Application Pool의 기본 동작 모델과 서비스의 유휴 상태 전환(Idle Timeout) 때문에 발생한다.&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;이 글에서는 최신 기준(2025년)으로, IIS의 동작 메커니즘을 올바르게 이해하고 실무적으로 가장 안정적인 설정 전략을 정리했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 웹서비스가 첫 요청에서 느려지는 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IIS는 트래픽이 일정 시간 발생하지 않으면 &lt;b&gt;Application Pool을 Idle 상태로 전환&lt;/b&gt;한다.&lt;br /&gt;Idle 종료 또는 Suspend 상태에서는 서비스가 &lt;b&gt;Cold Start&lt;/b&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;Cold Start가 영향을 주는 요소:&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ASP.NET Core In-Process 초기화&lt;/li&gt;
&lt;li&gt;Kestrel 프로세스 생성(Out-of-Process)&lt;/li&gt;
&lt;li&gt;DI 초기화, Entity Framework 연결&lt;/li&gt;
&lt;li&gt;캐시 준비, Config 로딩&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS ALB&amp;middot;CloudFront&amp;middot;WAF 같은 네트워크 구성에서는 &lt;b&gt;백엔드에서 Cold Start가 1~5초 이상 걸리면 바로 504 Gateway Timeout&lt;/b&gt;이 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &amp;ldquo;첫 요청 딜레이&amp;rdquo;는 IIS 기본 설정이 만든 구조적 현상이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. AlwaysRunning vs Idle Timeout &amp;mdash; 가장 많이 오해한 지점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;❌ 잘못된 정보&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;AlwaysRunning을 설정하면 Idle Timeout은 자동으로 비활성화된다.&quot;&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AlwaysRunning과 Idle Timeout은 서로 독립적이다.&lt;/b&gt;&lt;br /&gt;AlwaysRunning을 켜도 Idle Timeout은 정상 동작하며 프로세스는 여전히 종료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Microsoft 공식 문서에서도 다음이 명시되어 있다:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AlwaysRunning을 사용하더라도 IdleTimeout이 0이 아니면 프로세스는 종료됩니다.&lt;br /&gt;장시간 유지하려면 idleTimeout=0을 반드시 설정해야 합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;즉,&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;AlwaysRunning만 설정 &amp;rarr; 여전히 종료됨&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;IdleTimeout=0만 설정 &amp;rarr; 지속 유지 가능&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;➜ &lt;b&gt;두 값을 함께 설정해야 진짜 Always-On 환경이 된다&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;h2 data-ke-size=&quot;size26&quot;&gt;3. PreloadEnabled가 Out-of-Process에서 동작하는지?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일부 오래된 문서에서는 Out-of-Process에서 PreloadEnabled가 효과가 제한적이라고 언급한다.&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;최신 기준 정리(2025)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호스팅 모델 PreloadEnabled 동작 특징&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;In-Process&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;✔ 정상 작동&lt;/td&gt;
&lt;td&gt;IIS 내부 프로세스(w3wp)에서 빠른 초기화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Out-of-Process&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;✔ 작동&lt;/td&gt;
&lt;td&gt;Kestrel 프로세스 생성 과정이 추가되어 상대적으로 느림&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Out-of-Process에서도 PreloadEnabled는 정상 동작한다.&lt;br /&gt;단지 초기화 과정이 복잡하여 시간이 더 소요될 뿐이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Suspend 모드(Idle Timeout Action)의 실제 의미&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IIS 8.5 이후 도입된 Suspend는 프로세스를 바로 종료하지 않고 메모리 상태를 보존한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;재활성 시 빠르게 복구됨&lt;/li&gt;
&lt;li&gt;CPU 사용량 감소 효과&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 누수 위험이 존재&lt;br /&gt;&amp;rarr; 장기간 Suspend된 프로세스는 메모리 증가 추세를 유지할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Suspend를 사용하는 경우에는 일정 주기로 메모리를 정리해야 하므로 &lt;b&gt;정기 재시작(Periodic Restart)&lt;/b&gt; 전략이 필요하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. Periodic Restart(정기 재시작)의 중요성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IIS는 기본적으로 29시간마다 자동 재시작을 수행한다.&lt;br /&gt;이는 예측 불가능한 시간에 재시작이 일어나 서비스 지연을 초래할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;운영 환경에서는 반드시 재시작 시간을 명시적으로 설정하는 것이 좋다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예) 매일 새벽 04:00 재시작&lt;br /&gt;&amp;rarr; 야간에 안정적으로 재시작을 수행&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 서비스 유형별 추천 설정값&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  고트래픽 / 핵심 서비스 (항상 Warm 상태)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 값&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Start Mode&lt;/td&gt;
&lt;td&gt;&lt;b&gt;AlwaysRunning&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Idle Timeout&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0 (필수)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Idle Timeout Action&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PreloadEnabled&lt;/td&gt;
&lt;td&gt;True&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Periodic Restart&lt;/td&gt;
&lt;td&gt;특정 시간(04:00 권장)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;Cold Start 완전 제거.&lt;br /&gt;항상 활성 상태로 유지되어 AWS 타임아웃 이슈 해결.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대부분 고트래픽 설정과 동일하게 IdleTimeout=0 권장&lt;/li&gt;
&lt;li&gt;메모리가 제한될 경우만 Suspend 고려&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  저트래픽 / 비용 최적화 서비스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 값&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Start Mode&lt;/td&gt;
&lt;td&gt;AlwaysRunning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Idle Timeout&lt;/td&gt;
&lt;td&gt;20분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Idle Timeout Action&lt;/td&gt;
&lt;td&gt;Suspend&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PreloadEnabled&lt;/td&gt;
&lt;td&gt;True&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Periodic Restart&lt;/td&gt;
&lt;td&gt;0 또는 특정 시간&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  개발/테스트 환경&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Start Mode = OnDemand&lt;/li&gt;
&lt;li&gt;Idle Timeout = 기본 20분&lt;/li&gt;
&lt;li&gt;Action = Terminate&lt;/li&gt;
&lt;li&gt;PreloadEnabled = Off&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. PowerShell 기반 실전 설정 코드&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;고트래픽 운영 서비스&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;Import-Module WebAdministration

$AppPool = &quot;DefaultAppPool&quot;
$Site = &quot;myhome.domain.kr&quot;

# AppPool 설정
$AppPoolInstance = Get-Item IIS:\AppPools\$AppPool
$AppPoolInstance.startMode = &quot;AlwaysRunning&quot;
$AppPoolInstance.processModel.idleTimeout = [TimeSpan]::FromMinutes(0)
$AppPoolInstance.Recycling.periodicRestart.time = [TimeSpan]::Parse(&quot;0&quot;)
$AppPoolInstance | Set-Item

# 기존 Recycle 스케줄 모두 삭제
Clear-ItemProperty &quot;IIS:\AppPools\$AppPool&quot; -Name recycling.periodicRestart.schedule

# 새로운 스케줄 추가 (첫 번째는 Set-ItemProperty 사용)
Set-ItemProperty &quot;IIS:\AppPools\$AppPool&quot; -Name recycling.periodicRestart.schedule -Value @{value=&quot;04:00:00&quot;}

# 추가 스케줄이 더 필요하면 New-ItemProperty 사용
# New-ItemProperty &quot;IIS:\AppPools\$AppPool&quot; -Name recycling.periodicRestart.schedule -Value @{value=&quot;12:00:00&quot;}

# Site의 Preload 설정
Set-ItemProperty &quot;IIS:\Sites\$Site&quot; -name applicationDefaults.preloadEnabled -value True&lt;/code&gt;&lt;/pre&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;410&quot; data-origin-height=&quot;361&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dNsQSc/dJMcaiuY3Cw/yb6Hz0itA3RzxsZYok7ckK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dNsQSc/dJMcaiuY3Cw/yb6Hz0itA3RzxsZYok7ckK/img.png&quot; data-alt=&quot;$AppPoolInstance.startMode = &amp;quot;AlwaysRunning&amp;quot;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dNsQSc/dJMcaiuY3Cw/yb6Hz0itA3RzxsZYok7ckK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdNsQSc%2FdJMcaiuY3Cw%2Fyb6Hz0itA3RzxsZYok7ckK%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;410&quot; height=&quot;361&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;361&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;$AppPoolInstance.startMode = &quot;AlwaysRunning&quot;&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;&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;410&quot; data-origin-height=&quot;361&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwT0BY/dJMcabQcygb/mxTdK6UhHkls87hTHNFXiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwT0BY/dJMcabQcygb/mxTdK6UhHkls87hTHNFXiK/img.png&quot; data-alt=&quot;$AppPoolInstance.Recycling.periodicRestart.time = [TimeSpan]::Parse(&amp;quot;0&amp;quot;)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwT0BY/dJMcabQcygb/mxTdK6UhHkls87hTHNFXiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwT0BY%2FdJMcabQcygb%2FmxTdK6UhHkls87hTHNFXiK%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;410&quot; height=&quot;361&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;361&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;$AppPoolInstance.Recycling.periodicRestart.time = [TimeSpan]::Parse(&quot;0&quot;)&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;&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;410&quot; data-origin-height=&quot;361&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rl2KT/dJMcai2OFGn/HGC8eFgZukKVu1nIsU5efk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rl2KT/dJMcai2OFGn/HGC8eFgZukKVu1nIsU5efk/img.png&quot; data-alt=&quot;$AppPoolInstance.processModel.idleTimeout = [TimeSpan]::FromMinutes( 0 )&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rl2KT/dJMcai2OFGn/HGC8eFgZukKVu1nIsU5efk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frl2KT%2FdJMcai2OFGn%2FHGC8eFgZukKVu1nIsU5efk%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;410&quot; height=&quot;361&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;361&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;$AppPoolInstance.processModel.idleTimeout = [TimeSpan]::FromMinutes( 0 )&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;673&quot; data-origin-height=&quot;514&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bopeR7/dJMcagqrlrJ/XK29vw39YUEIBewrvJYpUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bopeR7/dJMcagqrlrJ/XK29vw39YUEIBewrvJYpUK/img.png&quot; data-alt=&quot;Set -ItemProperty &amp;quot;IIS:\AppPools\$AppPool&amp;quot; -Name recycling.periodicRestart.schedule -Value @{value= &amp;quot;04:00:00&amp;quot; }&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bopeR7/dJMcagqrlrJ/XK29vw39YUEIBewrvJYpUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbopeR7%2FdJMcagqrlrJ%2FXK29vw39YUEIBewrvJYpUK%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;673&quot; height=&quot;514&quot; data-origin-width=&quot;673&quot; data-origin-height=&quot;514&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Set -ItemProperty &quot;IIS:\AppPools\$AppPool&quot; -Name recycling.periodicRestart.schedule -Value @{value= &quot;04:00:00&quot; }&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;&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;410&quot; data-origin-height=&quot;386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biQZJ6/dJMcaiuY3PS/cGl7IgKIXZVw0cKBlcz9NK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biQZJ6/dJMcaiuY3PS/cGl7IgKIXZVw0cKBlcz9NK/img.png&quot; data-alt=&quot;Set -ItemProperty &amp;quot;IIS:\Sites\$Site&amp;quot; -name applicationDefaults.preloadEnabled -value True&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biQZJ6/dJMcaiuY3PS/cGl7IgKIXZVw0cKBlcz9NK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiQZJ6%2FdJMcaiuY3PS%2FcGl7IgKIXZVw0cKBlcz9NK%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;410&quot; height=&quot;386&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;386&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Set -ItemProperty &quot;IIS:\Sites\$Site&quot; -name applicationDefaults.preloadEnabled -value True&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 최종 체크리스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항목 필요 여부&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Application Initialization 모듈 설치&lt;/td&gt;
&lt;td&gt;✔ 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hosting Model = InProcess&lt;/td&gt;
&lt;td&gt;✔ 권장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AlwaysRunning + IdleTimeout=0&lt;/td&gt;
&lt;td&gt;✔ 핵심 조합&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PreloadEnabled&lt;/td&gt;
&lt;td&gt;✔ Cold Start 방지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Periodic Restart 시간 지정&lt;/td&gt;
&lt;td&gt;✔ 안정성 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Suspend 모드 메모리 추적&lt;/td&gt;
&lt;td&gt;✔ 모니터링 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 최종 정리&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;AlwaysRunning만으로는 절대 Always-On이 되지 않는다&lt;/b&gt;&lt;br /&gt;Idle Timeout=0 설정이 반드시 필요하다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PreloadEnabled는 In/Out-of-Process 모두 작동한다&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Periodic Restart를 명시적으로 설정해야 운영 안정성이 확보된다&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Suspend는 메모리 누수가 없는 환경에서만 권장&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AWS 환경에서는 Cold Start가 바로 Timeout을 유발하므로 Always-On 구성이 사실상 필수&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ASP.NET Core 8을 IIS에서 운영할 때 가장 안정적인 설정은 &lt;b&gt;AlwaysRunning + IdleTimeout=0 + Preload + 정기 Restart 시간 설정&lt;/b&gt;이다.&lt;br /&gt;이 조합은 Cold Start 문제를 완전히 제거하고, AWS 환경에서 빈번한 504 Timeout 이슈를 구조적으로 차단한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;</description>
      <category>닷넷관련/ASP.NET CORE  </category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3481</guid>
      <comments>https://aspdotnet.tistory.com/3481#entry3481comment</comments>
      <pubDate>Tue, 2 Dec 2025 02:22:07 +0900</pubDate>
    </item>
    <item>
      <title>ASP.NET Core 10 클린 아키텍처 완전 가이드(youtube)</title>
      <link>https://aspdotnet.tistory.com/3480</link>
      <description>&lt;h1&gt;ASP.NET Core 10 클린 아키텍처 완전 가이드&lt;/h1&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  1단계: 설치 및 프로젝트 생성&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;템플릿 설치&lt;/h3&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;# Ardalis Clean Architecture 템플릿 설치
dotnet new install Ardalis.CleanArchitecture.Template

# 설치 확인
dotnet new list | grep clean&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로젝트 생성&lt;/h3&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;# 새 프로젝트 생성 (MyCleanApp이라는 폴더에 전체 솔루션 구조 생성)
dotnet new clean-arch -o MyCleanApp

# 생성된 폴더로 이동
cd MyCleanApp

# Visual Studio 또는 Rider로 솔루션 열기
start MyCleanApp.sln  # Windows
open MyCleanApp.sln   # macOS&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;생성된 프로젝트 구조&lt;/h3&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;MyCleanApp/
├── src/
│   ├── MyCleanApp.Core/           # 도메인 계층 (순수 비즈니스 로직)
│   ├── MyCleanApp.UseCases/       # 애플리케이션 계층 (유스케이스)
│   ├── MyCleanApp.Infrastructure/ # 인프라 계층 (DB, 외부 서비스)
│   └── MyCleanApp.Web/           # 프레젠테이션 계층 (API)
├── tests/
│   ├── MyCleanApp.UnitTests/
│   ├── MyCleanApp.IntegrationTests/
│   └── MyCleanApp.FunctionalTests/
└── MyCleanApp.AppHost/           # .NET Aspire 오케스트레이션&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; ️ 2단계: 아키텍처 계층별 상세 분석&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;의존성 규칙 (Dependency Rule)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 원칙: &lt;b&gt;의존성은 항상 안쪽(Core)을 향합니다&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Web &amp;rarr; Infrastructure &amp;rarr; UseCases &amp;rarr; Core
                          &amp;darr;
                        Core (의존성 없음)&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  3단계: 코드 레벨 구현 (실제 작동 원리)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;A. Core 계층: 도메인 엔티티 정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기존 방식의 문제점:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// ❌ 약한 타입 시스템 - 실수하기 쉬움
public void TransferMoney(int fromAccountId, int toAccountId, decimal amount)
{
    // fromAccountId와 toAccountId를 바꿔도 컴파일 에러 없음!
}&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;새로운 방식 (Strongly Typed ID with Vogen):&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// Core/ContributorAggregate/ContributorId.cs
using Vogen;

[ValueObject&amp;lt;int&amp;gt;]
public readonly partial struct ContributorId
{
    // Vogen이 자동으로 생성:
    // - 암묵적/명시적 변환
    // - Equals, GetHashCode
    // - JSON serialization
}&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;엔티티 정의:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// Core/ContributorAggregate/Contributor.cs
using Ardalis.GuardClauses;
using Ardalis.SharedKernel;

public class Contributor : EntityBase, IAggregateRoot
{
    // private setter로 캡슐화
    public string Name { get; private set; }
    public ContributorStatus Status { get; private set; }

    // 생성자에서 유효성 검사
    public Contributor(string name)
    {
        Name = Guard.Against.NullOrEmpty(name, nameof(name));
        Status = ContributorStatus.Active;
    }

    // 비즈니스 로직은 엔티티 내부에
    public void UpdateName(string newName)
    {
        Name = Guard.Against.NullOrEmpty(newName, nameof(newName));

        // 도메인 이벤트 발행 (새 기능!)
        RegisterDomainEvent(new ContributorNameChangedEvent(this));
    }

    public void Deactivate()
    {
        Status = ContributorStatus.Inactive;
        RegisterDomainEvent(new ContributorDeactivatedEvent(this));
    }
}

// 상태를 열거형으로 명확히
public enum ContributorStatus
{
    Active,
    Inactive,
    Suspended
}&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;B. Core 계층: Specification 패턴 (  핵심 기능)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기존 리포지토리 방식의 문제:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// ❌ 메서드가 무한정 증가
public interface IContributorRepository
{
    Task&amp;lt;Contributor&amp;gt; GetByIdAsync(int id);
    Task&amp;lt;Contributor&amp;gt; GetByNameAsync(string name);
    Task&amp;lt;List&amp;lt;Contributor&amp;gt;&amp;gt; GetActiveContributorsAsync();
    Task&amp;lt;List&amp;lt;Contributor&amp;gt;&amp;gt; GetContributorsByStatusAsync(Status status);
    // ... 계속 추가됨
}&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Specification 패턴으로 해결:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// Core/ContributorAggregate/Specifications/ContributorByIdSpec.cs
using Ardalis.Specification;

public class ContributorByIdSpec : Specification&amp;lt;Contributor&amp;gt;
{
    public ContributorByIdSpec(int contributorId)
    {
        Query
            .Where(c =&amp;gt; c.Id == contributorId)
            .AsNoTracking(); // 읽기 전용 최적화
    }
}

// Core/ContributorAggregate/Specifications/ActiveContributorsSpec.cs
public class ActiveContributorsSpec : Specification&amp;lt;Contributor&amp;gt;
{
    public ActiveContributorsSpec()
    {
        Query
            .Where(c =&amp;gt; c.Status == ContributorStatus.Active)
            .OrderBy(c =&amp;gt; c.Name);
    }
}

// 복잡한 쿼리도 조합 가능
public class ContributorWithProjectsSpec : Specification&amp;lt;Contributor&amp;gt;
{
    public ContributorWithProjectsSpec(int contributorId)
    {
        Query
            .Where(c =&amp;gt; c.Id == contributorId)
            .Include(c =&amp;gt; c.Projects)  // Eager loading
            .ThenInclude(p =&amp;gt; p.Tasks);
    }
}&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;C. Infrastructure 계층: 리포지토리 구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;놀라운 점: 제네릭 리포지토리 하나로 모든 엔티티 처리!&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// Infrastructure/Data/EfRepository.cs
using Ardalis.Specification.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

public class EfRepository&amp;lt;T&amp;gt; : RepositoryBase&amp;lt;T&amp;gt;, IReadRepository&amp;lt;T&amp;gt;, IRepository&amp;lt;T&amp;gt;
    where T : class, IAggregateRoot
{
    private readonly AppDbContext _dbContext;

    public EfRepository(AppDbContext dbContext) : base(dbContext)
    {
        _dbContext = dbContext;
    }

    // Specification 라이브러리가 자동으로 처리:
    // - FirstOrDefaultAsync(spec)
    // - ListAsync(spec)
    // - CountAsync(spec)
    // - 모든 LINQ 쿼리를 SQL로 변환
}&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;/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;DbContext 설정:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// Infrastructure/Data/AppDbContext.cs
public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions&amp;lt;AppDbContext&amp;gt; options) 
        : base(options)
    {
    }

    public DbSet&amp;lt;Contributor&amp;gt; Contributors =&amp;gt; Set&amp;lt;Contributor&amp;gt;();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        // Value Object 매핑 (Vogen 통합)
        modelBuilder.Entity&amp;lt;Contributor&amp;gt;()
            .Property(c =&amp;gt; c.Id)
            .HasConversion(
                id =&amp;gt; id.Value,  // DB에 저장할 때
                value =&amp;gt; ContributorId.From(value)  // 읽을 때
            );
    }
}&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;D. UseCases 계층: CQRS with MediatR&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Command (쓰기 작업):&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;// UseCases/Contributors/Create/CreateContributorCommand.cs
using MediatR;
using Ardalis.Result;

public record CreateContributorCommand(string Name) : IRequest&amp;lt;Result&amp;lt;int&amp;gt;&amp;gt;;

// UseCases/Contributors/Create/CreateContributorHandler.cs
public class CreateContributorHandler : IRequestHandler&amp;lt;CreateContributorCommand, Result&amp;lt;int&amp;gt;&amp;gt;
{
    private readonly IRepository&amp;lt;Contributor&amp;gt; _repository;

    public CreateContributorHandler(IRepository&amp;lt;Contributor&amp;gt; repository)
    {
        _repository = repository;
    }

    public async Task&amp;lt;Result&amp;lt;int&amp;gt;&amp;gt; Handle(
        CreateContributorCommand request, 
        CancellationToken cancellationToken)
    {
        // 1. 도메인 엔티티 생성
        var contributor = new Contributor(request.Name);

        // 2. 리포지토리에 저장
        var createdContributor = await _repository.AddAsync(contributor, cancellationToken);

        // 3. Result 패턴으로 반환
        return Result.Success(createdContributor.Id);
    }
}&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Query (읽기 작업):&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// UseCases/Contributors/Get/GetContributorQuery.cs
public record GetContributorQuery(int ContributorId) : IRequest&amp;lt;Result&amp;lt;ContributorDTO&amp;gt;&amp;gt;;

// DTO 정의
public record ContributorDTO(int Id, string Name, string Status);

// UseCases/Contributors/Get/GetContributorHandler.cs
public class GetContributorHandler 
    : IRequestHandler&amp;lt;GetContributorQuery, Result&amp;lt;ContributorDTO&amp;gt;&amp;gt;
{
    private readonly IReadRepository&amp;lt;Contributor&amp;gt; _repository;

    public GetContributorHandler(IReadRepository&amp;lt;Contributor&amp;gt; repository)
    {
        _repository = repository;
    }

    public async Task&amp;lt;Result&amp;lt;ContributorDTO&amp;gt;&amp;gt; Handle(
        GetContributorQuery request, 
        CancellationToken cancellationToken)
    {
        // Specification 사용
        var spec = new ContributorByIdSpec(request.ContributorId);
        var entity = await _repository.FirstOrDefaultAsync(spec, cancellationToken);

        if (entity == null) 
            return Result.NotFound($&quot;Contributor with ID {request.ContributorId} not found.&quot;);

        // 엔티티를 DTO로 변환
        var dto = new ContributorDTO(
            entity.Id, 
            entity.Name, 
            entity.Status.ToString()
        );

        return Result.Success(dto);
    }
}&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;E. Web 계층: API Endpoint (Minimal API 스타일)&lt;/h3&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;// Web/Contributors/ContributorEndpoints.cs
using FastEndpoints;

public class CreateContributorEndpoint : Endpoint&amp;lt;CreateContributorRequest, CreateContributorResponse&amp;gt;
{
    private readonly IMediator _mediator;

    public CreateContributorEndpoint(IMediator mediator)
    {
        _mediator = mediator;
    }

    public override void Configure()
    {
        Post(&quot;/api/contributors&quot;);
        AllowAnonymous(); // 또는 Roles(&quot;Admin&quot;)
    }

    public override async Task HandleAsync(
        CreateContributorRequest req, 
        CancellationToken ct)
    {
        // MediatR로 명령 전송
        var command = new CreateContributorCommand(req.Name);
        var result = await _mediator.Send(command, ct);

        // Result 패턴 처리
        if (result.IsSuccess)
        {
            await SendCreatedAtAsync&amp;lt;GetContributorEndpoint&amp;gt;(
                new { id = result.Value },
                new CreateContributorResponse(result.Value),
                cancellation: ct
            );
        }
        else
        {
            await SendNotFoundAsync(ct);
        }
    }
}

// Request/Response 모델
public record CreateContributorRequest(string Name);
public record CreateContributorResponse(int Id);&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  4단계: 새로운 핵심 기능들&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ &lt;b&gt;.NET Aspire 통합&lt;/b&gt; (클라우드 네이티브 오케스트레이션)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AppHost 프로젝트 설정:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// MyCleanApp.AppHost/Program.cs
var builder = DistributedApplication.CreateBuilder(args);

// PostgreSQL 컨테이너 추가
var postgres = builder.AddPostgres(&quot;postgres&quot;)
    .WithPgAdmin()  // pgAdmin 대시보드 자동 추가
    .AddDatabase(&quot;cleanarchdb&quot;);

// Redis 캐시
var redis = builder.AddRedis(&quot;cache&quot;);

// SMTP 서버 (이메일 테스트용 Papercut)
var smtp = builder.AddContainer(&quot;papercut&quot;, &quot;jijiechen/papercut&quot;)
    .WithHttpEndpoint(port: 37408, targetPort: 80, name: &quot;papercut-ui&quot;);

// Web API 프로젝트 추가
builder.AddProject&amp;lt;Projects.MyCleanApp_Web&amp;gt;(&quot;webapi&quot;)
    .WithReference(postgres)
    .WithReference(redis)
    .WithReference(smtp)
    .WithEnvironment(&quot;SMTP_HOST&quot;, smtp.GetEndpoint(&quot;papercut-ui&quot;));

builder.Build().Run();&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 방법:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;# AppHost를 시작 프로젝트로 설정하고 실행
dotnet run --project MyCleanApp.AppHost

# 브라우저에서 Aspire 대시보드 자동 오픈
# http://localhost:15000 (모든 서비스 상태 확인 가능)&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Aspire 대시보드에서 확인 가능한 것들:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 컨테이너의 로그 실시간 스트리밍&lt;/li&gt;
&lt;li&gt;데이터베이스 연결 상태&lt;/li&gt;
&lt;li&gt;API 엔드포인트 호출 트레이싱&lt;/li&gt;
&lt;li&gt;Papercut UI로 발송된 이메일 미리보기&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ &lt;b&gt;NsDepCop&lt;/b&gt; (네임스페이스 의존성 강제)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제 상황:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// UseCases/Contributors/CreateContributorHandler.cs
public class CreateContributorHandler
{
    public async Task Handle()
    {
        // ❌ UseCases에서 직접 DbContext 참조 - 아키텍처 위반!
        using var db = new AppDbContext();
        db.Contributors.Add(new Contributor());
    }
}&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;NsDepCop 설정:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!-- config.nsdepcop 파일 --&amp;gt;
&amp;lt;NsDepCopConfig&amp;gt;
  &amp;lt;Allowed From=&quot;MyCleanApp.UseCases.*&quot; To=&quot;MyCleanApp.Core.*&quot; /&amp;gt;
  &amp;lt;Allowed From=&quot;MyCleanApp.Infrastructure.*&quot; To=&quot;MyCleanApp.Core.*&quot; /&amp;gt;
  &amp;lt;Allowed From=&quot;MyCleanApp.Infrastructure.*&quot; To=&quot;MyCleanApp.UseCases.*&quot; /&amp;gt;
  &amp;lt;Allowed From=&quot;MyCleanApp.Web.*&quot; To=&quot;*&quot; /&amp;gt;

  &amp;lt;!-- Core는 어디에도 의존하면 안됨 --&amp;gt;
  &amp;lt;Disallowed From=&quot;MyCleanApp.Core.*&quot; To=&quot;MyCleanApp.Infrastructure.*&quot; /&amp;gt;
  &amp;lt;Disallowed From=&quot;MyCleanApp.Core.*&quot; To=&quot;MyCleanApp.UseCases.*&quot; /&amp;gt;

  &amp;lt;!-- UseCases는 Infrastructure에 의존하면 안됨 --&amp;gt;
  &amp;lt;Disallowed From=&quot;MyCleanApp.UseCases.*&quot; To=&quot;MyCleanApp.Infrastructure.*&quot; /&amp;gt;
&amp;lt;/NsDepCopConfig&amp;gt;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;Build Error: [NsDepCop] Namespace dependency violation detected!
MyCleanApp.UseCases.CreateContributorHandler cannot reference 
MyCleanApp.Infrastructure.AppDbContext&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣ &lt;b&gt;Vogen&lt;/b&gt; (Value Object Generator)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;설치:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;dotnet add package Vogen&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;&lt;b&gt;사용 예시:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// Core/ValueObjects/EmailAddress.cs
using Vogen;

[ValueObject&amp;lt;string&amp;gt;]
public readonly partial struct EmailAddress
{
    // 유효성 검사 로직 추가
    private static Validation Validate(string value)
    {
        if (string.IsNullOrWhiteSpace(value))
            return Validation.Invalid(&quot;Email cannot be empty&quot;);

        if (!value.Contains(&quot;@&quot;))
            return Validation.Invalid(&quot;Invalid email format&quot;);

        return Validation.Ok;
    }
}

// Core/ValueObjects/PhoneNumber.cs
[ValueObject&amp;lt;string&amp;gt;]
public readonly partial struct PhoneNumber
{
    private static Validation Validate(string value)
    {
        if (!System.Text.RegularExpressions.Regex.IsMatch(value, @&quot;^\d{3}-\d{4}-\d{4}$&quot;))
            return Validation.Invalid(&quot;Phone must be in format: 010-1234-5678&quot;);

        return Validation.Ok;
    }
}&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;타입 안전성 확보:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// ❌ 기존 방식 - 실수 가능
public void CreateUser(string email, string phone)
{
    // email과 phone을 바꿔도 컴파일 에러 없음!
    SendEmail(phone, email);
}

// ✅ Vogen 사용 - 컴파일 타임 에러
public void CreateUser(EmailAddress email, PhoneNumber phone)
{
    // 컴파일 에러: Cannot convert PhoneNumber to EmailAddress
    SendEmail(phone, email);
}&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  5단계: 실제 실행 및 테스트&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터베이스 마이그레이션&lt;/h3&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;# Infrastructure 프로젝트에서 마이그레이션 생성
dotnet ef migrations add InitialCreate --project src/MyCleanApp.Infrastructure --startup-project src/MyCleanApp.Web

# 데이터베이스 업데이트
dotnet ef database update --project src/MyCleanApp.Infrastructure --startup-project src/MyCleanApp.Web&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;/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;/h3&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;# AppHost로 전체 실행 (권장)
dotnet run --project MyCleanApp.AppHost

# 또는 Web만 개별 실행
dotnet run --project src/MyCleanApp.Web&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;/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;Swagger로 API 테스트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에서 &lt;code&gt;https://localhost:7001/swagger&lt;/code&gt; 접속&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트 시나리오:&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Contributor 생성:&lt;/b&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;POST /api/contributors
{
&quot;name&quot;: &quot;John Doe&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1764438366939&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Response: 201 Created
{
&quot;id&quot;: 1
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;
2. **Contributor 조회:**
```json
GET /api/contributors/1

Response: 200 OK
{
  &quot;id&quot;: 1,
  &quot;name&quot;: &quot;John Doe&quot;,
  &quot;status&quot;: &quot;Active&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;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;3&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;이름 업데이트:&lt;/b&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;PUT /api/contributors/1
{
&quot;name&quot;: &quot;Jane Smith&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Response: 204 No Content&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;
---

##   6단계: 테스트 작성

### 단위 테스트 (Core 계층)

```csharp
// tests/MyCleanApp.UnitTests/Core/ContributorTests.cs
using Xunit;

public class ContributorTests
{
    [Fact]
    public void UpdateName_WithValidName_ShouldUpdateSuccessfully()
    {
        // Arrange
        var contributor = new Contributor(&quot;Original Name&quot;);

        // Act
        contributor.UpdateName(&quot;New Name&quot;);

        // Assert
        Assert.Equal(&quot;New Name&quot;, contributor.Name);
    }

    [Fact]
    public void UpdateName_WithEmptyName_ShouldThrowException()
    {
        // Arrange
        var contributor = new Contributor(&quot;Original Name&quot;);

        // Act &amp;amp; Assert
        Assert.Throws&amp;lt;ArgumentException&amp;gt;(() =&amp;gt; 
            contributor.UpdateName(&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;통합 테스트 (Repository + Database)&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// tests/MyCleanApp.IntegrationTests/Data/EfRepositoryTests.cs
public class EfRepositoryTests : IClassFixture&amp;lt;DatabaseFixture&amp;gt;
{
    private readonly DatabaseFixture _fixture;

    public EfRepositoryTests(DatabaseFixture fixture)
    {
        _fixture = fixture;
    }

    [Fact]
    public async Task AddAsync_ShouldPersistToDatabase()
    {
        // Arrange
        var repository = _fixture.GetRepository&amp;lt;Contributor&amp;gt;();
        var contributor = new Contributor(&quot;Test User&quot;);

        // Act
        var saved = await repository.AddAsync(contributor);

        // Assert
        Assert.True(saved.Id &amp;gt; 0);

        // Verify persistence
        var spec = new ContributorByIdSpec(saved.Id);
        var retrieved = await repository.FirstOrDefaultAsync(spec);
        Assert.NotNull(retrieved);
        Assert.Equal(&quot;Test User&quot;, retrieved.Name);
    }
}&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  전체 흐름 다이어그램&lt;/h2&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;사용자 요청 (HTTP POST /api/contributors)
    &amp;darr;
Web Layer: ContributorEndpoint
    &amp;darr; (MediatR.Send)
UseCases Layer: CreateContributorHandler
    &amp;darr; (new Contributor)
Core Layer: Contributor Entity (비즈니스 로직)
    &amp;darr; (repository.AddAsync)
Infrastructure Layer: EfRepository&amp;lt;Contributor&amp;gt;
    &amp;darr; (DbContext.SaveChanges)
Database (PostgreSQL/SQL Server)
    &amp;darr; (Result 반환)
사용자에게 201 Created 응답&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  핵심 장점 요약&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;테스트 용이성&lt;/b&gt;: Core는 외부 의존성이 없어 순수 단위 테스트 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유지보수성&lt;/b&gt;: Specification 패턴으로 쿼리 로직 재사용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;타입 안전성&lt;/b&gt;: Vogen으로 컴파일 타임 에러 감지&lt;/li&gt;
&lt;li&gt;&lt;b&gt;아키텍처 강제&lt;/b&gt;: NsDepCop으로 의존성 규칙 위반 방지&lt;/li&gt;
&lt;li&gt;&lt;b&gt;개발 생산성&lt;/b&gt;: .NET Aspire로 로컬 환경 자동 구성&lt;/li&gt;
&lt;/ol&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&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;/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;&lt;a href=&quot;https://www.youtube.com/watch?v=rjefnUC9Z90&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=rjefnUC9Z90&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=rjefnUC9Z90&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/2fQmw/hyZOlqaSaG/ofhy15CQSWiN3kzGyu7h60/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/LbXKQ/hyZOtuZDoy/pWLksupFSYwW0jmDusKehK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;Clean Architecture with ASP.NET Core 10&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/rjefnUC9Z90&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>닷넷관련/ASP.NET CORE  </category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3480</guid>
      <comments>https://aspdotnet.tistory.com/3480#entry3480comment</comments>
      <pubDate>Sun, 30 Nov 2025 02:46:34 +0900</pubDate>
    </item>
    <item>
      <title>vanilla js 동적 form 구조 - example 2</title>
      <link>https://aspdotnet.tistory.com/3478</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gist.github.com/shimpark/f07523bdb0a459f21a2952da1cd41992&quot;&gt;https://gist.github.com/shimpark/f07523bdb0a459f21a2952da1cd41992&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1763914019144&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;vanilla js 동적 form 구조 - example 2&quot; data-og-description=&quot;vanilla js 동적 form 구조 - example 2. GitHub Gist: instantly share code, notes, and snippets.&quot; data-og-host=&quot;gist.github.com&quot; data-og-source-url=&quot;https://gist.github.com/shimpark/f07523bdb0a459f21a2952da1cd41992&quot; data-og-url=&quot;https://gist.github.com/shimpark/f07523bdb0a459f21a2952da1cd41992&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/7LwRA/hyZOhH2HWJ/SXOYXJuut2cHpmg3gZZBA0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/bQaAGo/hyZOhBgJR6/10i4R29OEu1PJd8AlI0qtK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://gist.github.com/shimpark/f07523bdb0a459f21a2952da1cd41992&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gist.github.com/shimpark/f07523bdb0a459f21a2952da1cd41992&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/7LwRA/hyZOhH2HWJ/SXOYXJuut2cHpmg3gZZBA0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/bQaAGo/hyZOhBgJR6/10i4R29OEu1PJd8AlI0qtK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;vanilla js 동적 form 구조 - example 2&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;vanilla js 동적 form 구조 - example 2. GitHub Gist: instantly share code, notes, and snippets.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gist.github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;h1&gt;ReactiveStore 활용법 &amp;mdash; 예제 모음&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문서는 &lt;code&gt;ReactiveStore&lt;/code&gt;와 스키마 기반 폼을 실제로 활용하는 방법을 코드 샘플 중심으로 정리한 문서입니다. 주니어 개발자가 바로 붙여넣고 실습할 수 있도록 간단한 설명과 함께 제공됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;목차&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;기본 사용: 스토어 생성 &amp;rarr; 스키마 정의 &amp;rarr; 폼 렌더링&lt;/li&gt;
&lt;li&gt;computed(파생값) 예제: 총합/요약 계산&lt;/li&gt;
&lt;li&gt;동적 필드 추가: 런타임에 필드 추가/삭제&lt;/li&gt;
&lt;li&gt;검증 사용 예: validateAll 호출과 UI 표시&lt;/li&gt;
&lt;li&gt;포맷터 적용 예: 금액/배달 정보 표시&lt;/li&gt;
&lt;li&gt;비동기 저장(옵티미스틱 업데이트) 예: 서버 저장 시 UX 처리&lt;/li&gt;
&lt;li&gt;디바운스 입력 처리: 타이핑 중 불필요한 업데이트 줄이기&lt;/li&gt;
&lt;li&gt;단위 테스트 샘플 (Jest + jsdom)&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1) 기본 사용: 스토어 생성 &amp;rarr; 스키마 정의 &amp;rarr; 폼 렌더링&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;목적: 최소한의 코드로 스토어를 초기화하고 폼을 화면에 렌더링합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;// 1) 스토어 생성
const store = new ReactiveStore();

// 2) 스키마 정의
store.defineSchema('profile', {
  name: { label: '이름', type: 'text', default: '' },
  age: { label: '나이', type: 'number', default: 20 },
  subscribe: { label: '뉴스레터', type: 'checkbox', default: false }
});

// 3) 렌더링 (index.html에 &amp;lt;div id=&quot;profileForm&quot;&amp;gt;&amp;lt;/div&amp;gt;가 있다고 가정)
store.generateForm('profileForm', 'profile');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예상 동작: &lt;code&gt;#profileForm&lt;/code&gt; 내부에 &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;age&lt;/code&gt;, &lt;code&gt;subscribe&lt;/code&gt; 필드가 생성되고, 입력은 &lt;code&gt;store.state&lt;/code&gt;로 반영됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2) computed(파생값) 예제: 총합/요약 계산&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;목적: 여러 필드로부터 계산된 값을 자동으로 갱신하여 표시합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// 스키마
store.defineSchema('cart', {
  itemPrice: { label: '단가', type: 'number', default: 1000 },
  qty: { label: '수량', type: 'number', default: 1 }
});

// computed 등록: total = itemPrice * qty
store.computed('total', ['itemPrice', 'qty'], (state) =&amp;gt; {
  const p = Number(state.itemPrice) || 0;
  const q = Number(state.qty) || 0;
  return p * q;
});

// 렌더링
store.generateForm('cartForm', 'cart');

// UI에 total을 실시간으로 표시하려면 구독
const totalEl = document.getElementById('cartTotal');
function renderTotal() {
  totalEl.textContent = store.state.total;
}
store._subscribe('total', renderTotal);
renderTotal();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;팁: computed 내부에서는 부작용(예: 네트워크 호출)을 하지 마세요. 단순 계산만 수행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3) 동적 필드 추가: 런타임에 필드 추가/삭제&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;목적: 사용자의 요청에 따라 폼 구조를 동적으로 변경합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;// 기존 스키마가 'recipient'라 가정
const recipientSchema = store._schemas['recipient']; // 내부 참조 &amp;mdash; 안전한 API를 만들면 더 좋음
const newKey = 'recipient_phone_' + Date.now();
recipientSchema[newKey] = { label: '전화번호 (추가)', type: 'text', default: '' };

// 상태 초기화
store.state[newKey] = '';

// 재렌더(간단 방식)
store.generateForm('recipientForm', 'recipient');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주의: 직접 &lt;code&gt;_schemas&lt;/code&gt;를 수정하는 것은 캡슐화 측면에서 권장되지 않습니다. 가능하면 &lt;code&gt;addField(schemaName, key, def)&lt;/code&gt; 같은 public API를 추가하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4) 검증 사용 예: validateAll 호출과 UI 표시&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;목적: 제출 전 클라이언트 측 유효성 검사 수행 및 에러 표시.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;document.getElementById('submitBtn').addEventListener('click', async () =&amp;gt; {
  const errors = store.validateAll('profile');
  if (errors &amp;amp;&amp;amp; errors.length &amp;gt; 0) {
    showValidationErrors(errors);
    return;
  }

  // validation 통과 시 처리
  await saveToServer(store.state);
  showToast('저장되었습니다.');
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;팁: 서버 측 검증과 클라이언트 검증을 분리하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5) 포맷터 적용 예: 금액/배달 정보 표시&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;목적: 중앙에서 포맷터를 관리하여 UI 표기 일관성 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;store.addFormatter('currency', (value) =&amp;gt; {
  if (value == null) return '';
  return Number(value).toLocaleString('ko-KR') + '원';
});

// 사용
const price = store.state.itemPrice;
document.getElementById('priceLabel').textContent = store.format('currency', price);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6) 비동기 저장(옵티미스틱 업데이트) 예: 서버 저장 시 UX 처리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;목적: 서버 응답 대기 중이라도 사용자에게 즉각적인 피드백 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;async function saveSettingsOptimistic(newData) {
  const backup = JSON.parse(JSON.stringify(store.state));
  Object.keys(newData).forEach(k =&amp;gt; store.state[k] = newData[k]);
  showToast('저장 시도 중...');

  try {
    await fetch('/api/saveSettings', {
      method: 'POST',
      headers: {'Content-Type':'application/json'},
      body: JSON.stringify(newData)
    });

    showToast('저장 성공');
  } catch (err) {
    Object.keys(backup).forEach(k =&amp;gt; store.state[k] = backup[k]);
    showToast('저장 실패 &amp;mdash; 변경 내용이 복원되었습니다.');
    console.error(err);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주의: 옵티미스틱 업데이트는 충돌 가능성 및 롤백 비용이 있으니 주의해서 사용하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7) 디바운스 입력 처리: 타이핑 중 불필요한 업데이트 줄이기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;목적: typing 이벤트로 지나치게 많은 상태 업데이트/리렌더를 막습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function debounce(fn, wait = 300) {
  let t;
  return (...args) =&amp;gt; {
    clearTimeout(t);
    t = setTimeout(() =&amp;gt; fn(...args), wait);
  };
}

const input = document.querySelector('input[data-key=&quot;search&quot;]');
const handler = debounce((e) =&amp;gt; {
  store.state.search = e.target.value;
}, 250);
input.addEventListener('input', handler);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8) 단위 테스트 샘플 (Jest + jsdom)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;목적: &lt;code&gt;computed&lt;/code&gt;, &lt;code&gt;defineSchema&lt;/code&gt; 초기화, 상태 변경시 파생값 재계산 등을 자동으로 검증합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;// store.test.js
const { JSDOM } = require('jsdom');
global.window = new JSDOM(`&amp;lt;!doctype html&amp;gt;&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;`).window;
global.document = window.document;

const { ReactiveStore } = require('./path/to/reactive-store');

test('defineSchema sets defaults and computed recalculates', () =&amp;gt; {
  const store = new ReactiveStore();
  store.defineSchema('t', {
    a: { type: 'number', default: 2 },
    b: { type: 'number', default: 3 }
  });
  store.computed('sum', ['a','b'], (s) =&amp;gt; Number(s.a) + Number(s.b));
  expect(store.state.sum).toBe(5);

  store.state.a = 10;
  expect(store.state.sum).toBe(13);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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>프로그래밍/자바스크립트</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3478</guid>
      <comments>https://aspdotnet.tistory.com/3478#entry3478comment</comments>
      <pubDate>Mon, 24 Nov 2025 02:18:19 +0900</pubDate>
    </item>
    <item>
      <title>vanilla js 동적 form 구조 - example 1</title>
      <link>https://aspdotnet.tistory.com/3477</link>
      <description>&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;a href=&quot;https://gist.github.com/shimpark/783013cceb50bb4a378077a1d2a21f01&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://gist.github.com/shimpark/783013cceb50bb4a378077a1d2a21f01&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1763907523306&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;vanilla js 동적 form 구조 확장 (proxy, computed, Subscribe/Notify 패턴)&quot; data-og-description=&quot;vanilla js 동적 form 구조 확장 (proxy, computed, Subscribe/Notify 패턴) - dynamic_form_ex1.html&quot; data-og-host=&quot;gist.github.com&quot; data-og-source-url=&quot;https://gist.github.com/shimpark/783013cceb50bb4a378077a1d2a21f01&quot; data-og-url=&quot;https://gist.github.com/shimpark/783013cceb50bb4a378077a1d2a21f01&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/NsMQ7/hyZNAgpkf1/l9W7PazD0Ht4GDoTOSspbK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/b4SRZ8/hyZNAgpkeK/vkzTbYShMQROIOXoLkGCNk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://gist.github.com/shimpark/783013cceb50bb4a378077a1d2a21f01&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gist.github.com/shimpark/783013cceb50bb4a378077a1d2a21f01&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/NsMQ7/hyZNAgpkf1/l9W7PazD0Ht4GDoTOSspbK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/b4SRZ8/hyZNAgpkeK/vkzTbYShMQROIOXoLkGCNk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;vanilla js 동적 form 구조 확장 (proxy, computed, Subscribe/Notify 패턴)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;vanilla js 동적 form 구조 확장 (proxy, computed, Subscribe/Notify 패턴) - dynamic_form_ex1.html&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gist.github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;h4 data-ke-size=&quot;size20&quot;&gt;✅&amp;nbsp;완성된&amp;nbsp;기능들&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;리액티브 스토어 시스템&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Proxy 기반 자동 반응성&lt;/li&gt;
&lt;li&gt;Computed 속성 (의존성 추적)&lt;/li&gt;
&lt;li&gt;Subscribe/Notify 패턴&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 주도 렌더링&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;renderList() - 배열 데이터를 HTML로 자동 변환&lt;/li&gt;
&lt;li&gt;조건부 렌더링 (data-show-if)&lt;/li&gt;
&lt;li&gt;양방향/단방향 바인딩&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유효성 검사 시스템&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;규칙 기반 검증&lt;/li&gt;
&lt;li&gt;실시간 필드 검증 (blur 이벤트)&lt;/li&gt;
&lt;li&gt;전체 폼 검증 (저장 시)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;포매터 시스템&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;화폐 형식 (currency)&lt;/li&gt;
&lt;li&gt;배열 표시 (array)&lt;/li&gt;
&lt;li&gt;커스텀 포매터 확장 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  테스트 시나리오&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;저장하기 버튼&lt;/b&gt; - 유효성 검사 후 API 전송&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프리셋 불러오기&lt;/b&gt; - 서버 데이터 일괄 로드&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서버 데이터 시뮬레이션&lt;/b&gt; - 동적 목록 업데이트&lt;/li&gt;
&lt;li&gt;&lt;b&gt;현재 State 출력&lt;/b&gt; - 디버깅용 상태 확인&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  핵심 설계 원칙&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;선언적 프로그래밍&lt;/b&gt;: HTML 속성만으로 바인딩&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단일 진실 공급원&lt;/b&gt;: State가 모든 UI의 출처&lt;/li&gt;
&lt;li&gt;&lt;b&gt;관심사 분리&lt;/b&gt;: 데이터/로직/뷰 완전 분리&lt;/li&gt;
&lt;li&gt;&lt;b&gt;확장성&lt;/b&gt;: 새로운 validator, formatter 쉽게 추가&lt;/li&gt;
&lt;/ul&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;현재 방식의 장단점&lt;/b&gt;&lt;/h4&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구현이 단순하고 이해하기 쉬움&lt;/li&gt;
&lt;li&gt;100개 이하 항목에서는 체감 불가능&lt;/li&gt;
&lt;li&gt;서버에서 완전히 새로운 데이터가 올 때 유리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;❌ &lt;b&gt;단점 (복잡한 경우):&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1000개+ 항목에서 느려짐&lt;/li&gt;
&lt;li&gt;애니메이션 적용 어려움&lt;/li&gt;
&lt;li&gt;스크롤 위치, 포커스 상태 등 소실&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결책: Virtual DOM 또는 Diffing 알고리즘&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍/자바스크립트</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3477</guid>
      <comments>https://aspdotnet.tistory.com/3477#entry3477comment</comments>
      <pubDate>Mon, 24 Nov 2025 00:19:43 +0900</pubDate>
    </item>
    <item>
      <title>방배역 인천공항 버스타기 6016 공항버스 타기(방배역 출발: 60~70분)</title>
      <link>https://aspdotnet.tistory.com/3471</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;527&quot; data-origin-height=&quot;401&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btG8IS/dJMb9PTWJJS/ki34FgarosoDOfftE1SFNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btG8IS/dJMb9PTWJJS/ki34FgarosoDOfftE1SFNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btG8IS/dJMb9PTWJJS/ki34FgarosoDOfftE1SFNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtG8IS%2FdJMb9PTWJJS%2Fki34FgarosoDOfftE1SFNk%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;527&quot; height=&quot;401&quot; data-origin-width=&quot;527&quot; data-origin-height=&quot;401&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방배역&amp;nbsp;인천공항&amp;nbsp;버스타기&amp;nbsp;6016&amp;nbsp;공항버스&amp;nbsp;타기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;challenge&quot; data-emoticon-name=&quot;002&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/challenge/large/002.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/challenge/large/002.png&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot; data-processed=&quot;true&quot; data-hveid=&quot;CAEQAA&quot; data-sfc-cp=&quot;&quot;&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot; data-complete=&quot;true&quot; data-sae=&quot;&quot; data-processed=&quot;true&quot; data-sfc-cp=&quot;&quot; data-animation-nesting=&quot;&quot;&gt;6016번 공항버스 노선 (2025년 기준)&lt;span data-complete=&quot;true&quot; data-wiz-uids=&quot;YdxCs_p,YdxCs_q&quot;&gt;&lt;span data-sae=&quot;&quot; data-animation-atomic=&quot;&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot; data-complete=&quot;true&quot; data-sae=&quot;&quot; data-processed=&quot;true&quot; data-sfc-cp=&quot;&quot; data-animation-nesting=&quot;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #0a0a0a; text-align: start;&quot; data-complete=&quot;true&quot; data-processed=&quot;true&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-sae=&quot;&quot; data-complete=&quot;true&quot; data-hveid=&quot;CAEIBRAA&quot;&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;&lt;b&gt;기점:&lt;/b&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;서울교육대학교&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(기업은행 교대사거리 지점 앞)&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-complete=&quot;true&quot; data-sae=&quot;&quot; data-hveid=&quot;CAEIBRAB&quot;&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;&lt;b&gt;주요 정류장:&lt;/b&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;서울고등학교&lt;/span&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;방배역&lt;/span&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;방배프라자&lt;/span&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;방배경찰서&lt;/span&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;(정금마을) &amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;총신대입구역&lt;/span&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;(남성시장) &amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;사당역&lt;/span&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;(&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;사당동우체국&lt;/span&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;) &amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;이수역&lt;/span&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;흑석역&lt;/span&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;효사정&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-sae=&quot;&quot; data-processed=&quot;true&quot; data-complete=&quot;true&quot; data-hveid=&quot;CAEIBRAC&quot;&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;&lt;b&gt;종점:&lt;/b&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;인천국제공항 제1여객터미널&lt;/span&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;제2여객터미널&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-complete=&quot;true&quot; data-wiz-uids=&quot;YdxCs_1r,YdxCs_1s&quot;&gt;&lt;span data-sae=&quot;&quot; data-animation-atomic=&quot;&quot;&gt;&amp;nbsp;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot; data-complete=&quot;true&quot; data-sae=&quot;&quot; data-processed=&quot;true&quot; data-sfc-cp=&quot;&quot; data-animation-nesting=&quot;&quot;&gt;요금&lt;span data-complete=&quot;true&quot; data-wiz-uids=&quot;YdxCs_4d,YdxCs_4e&quot;&gt;&lt;span data-sae=&quot;&quot; data-animation-atomic=&quot;&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot; data-complete=&quot;true&quot; data-sae=&quot;&quot; data-processed=&quot;true&quot; data-sfc-cp=&quot;&quot; data-animation-nesting=&quot;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #0a0a0a; text-align: start;&quot; data-processed=&quot;true&quot; data-complete=&quot;true&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-sae=&quot;&quot; data-complete=&quot;true&quot; data-hveid=&quot;CAEIDhAA&quot;&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;&lt;b&gt;성인:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;17,000원&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot; data-sae=&quot;&quot; data-complete=&quot;true&quot; data-hveid=&quot;CAEIDhAB&quot;&gt;&lt;span data-complete=&quot;true&quot; data-sfc-cp=&quot;&quot;&gt;&lt;b&gt;어린이:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;12,000원&lt;/span&gt;&lt;span data-complete=&quot;true&quot; data-wiz-uids=&quot;YdxCs_4m,YdxCs_4n&quot;&gt;&lt;span data-sae=&quot;&quot; data-animation-atomic=&quot;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;challenge&quot; data-emoticon-name=&quot;016&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/challenge/large/016.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/challenge/large/016.png&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1192&quot; data-origin-height=&quot;457&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsShy6/dJMb8Vs1ETq/khNQaa9H3MNVreL0pxAnKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsShy6/dJMb8Vs1ETq/khNQaa9H3MNVreL0pxAnKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsShy6/dJMb8Vs1ETq/khNQaa9H3MNVreL0pxAnKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsShy6%2FdJMb8Vs1ETq%2FkhNQaa9H3MNVreL0pxAnKK%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;1192&quot; height=&quot;457&quot; data-origin-width=&quot;1192&quot; data-origin-height=&quot;457&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;u&gt;&lt;b&gt;대우효령아파트 기준 시간대&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;table style=&quot;background-color: #000000; color: #1b1c1d; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background-color: #000000; color: #1b1c1d;&quot;&gt;
&lt;td style=&quot;background-color: #efefef; color: #1b1c1d; text-align: center;&quot;&gt;&lt;b&gt;시간대&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #efefef; color: #1b1c1d; text-align: center;&quot;&gt;&lt;b&gt;출발 시각 (시:분)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody style=&quot;background-color: #000000; color: #1b1c1d;&quot;&gt;
&lt;tr style=&quot;background-color: #000000; color: #1b1c1d;&quot;&gt;
&lt;td style=&quot;background-color: #000000; color: #1b1c1d;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;오전&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #000000; color: #1b1c1d;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;04:22, 04:44, 05:14, 05:44, 06:14, 06:49, 07:29, 08:19, 09:09, 09:49, 10:29, 11:09, 11:49&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #000000; color: #1b1c1d;&quot;&gt;
&lt;td style=&quot;background-color: #000000; color: #1b1c1d;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;오후&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #000000; color: #1b1c1d;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;12:29, 13:09, 13:49, 14:19, 14:49, 15:29, 16:09, 16:49, 17:39, 18:29, 19:19, &lt;b&gt;20:09 (막차)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;1232&quot; data-origin-height=&quot;134&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czTccQ/dJMb9PzEtrG/vUHgjbuVOjcTlyNr0kC7K0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czTccQ/dJMb9PzEtrG/vUHgjbuVOjcTlyNr0kC7K0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czTccQ/dJMb9PzEtrG/vUHgjbuVOjcTlyNr0kC7K0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FczTccQ%2FdJMb9PzEtrG%2FvUHgjbuVOjcTlyNr0kC7K0%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;1232&quot; height=&quot;134&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;134&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1217&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/01xjG/dJMb81UjrVi/rNH6eqMwlfhd3F1xG9uKyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/01xjG/dJMb81UjrVi/rNH6eqMwlfhd3F1xG9uKyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/01xjG/dJMb81UjrVi/rNH6eqMwlfhd3F1xG9uKyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F01xjG%2FdJMb81UjrVi%2FrNH6eqMwlfhd3F1xG9uKyk%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;1217&quot; height=&quot;462&quot; data-origin-width=&quot;1217&quot; data-origin-height=&quot;462&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;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;challenge&quot; data-emoticon-name=&quot;008&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/challenge/large/008.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/challenge/large/008.png&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1245&quot; data-origin-height=&quot;884&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4dKKx/dJMb9MpnMyX/CfM739Mwgb4EUzDt5sg0gK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4dKKx/dJMb9MpnMyX/CfM739Mwgb4EUzDt5sg0gK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4dKKx/dJMb9MpnMyX/CfM739Mwgb4EUzDt5sg0gK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4dKKx%2FdJMb9MpnMyX%2FCfM739Mwgb4EUzDt5sg0gK%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;1245&quot; height=&quot;884&quot; data-origin-width=&quot;1245&quot; data-origin-height=&quot;884&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;&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;h4 data-ke-size=&quot;size20&quot;&gt;인천공항 매표소 구매하고 버스타고 집에 가기&lt;/h4&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;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷_2025-07-28_오후_10.27.54.png&quot; data-origin-width=&quot;2576&quot; data-origin-height=&quot;1446&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/N24yD/dJMb9O1OkZf/YmoP1y1EXm901i9PrOCI40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/N24yD/dJMb9O1OkZf/YmoP1y1EXm901i9PrOCI40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/N24yD/dJMb9O1OkZf/YmoP1y1EXm901i9PrOCI40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FN24yD%2FdJMb9O1OkZf%2FYmoP1y1EXm901i9PrOCI40%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;2576&quot; height=&quot;1446&quot; data-filename=&quot;스크린샷_2025-07-28_오후_10.27.54.png&quot; data-origin-width=&quot;2576&quot; data-origin-height=&quot;1446&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;인천공항 2터미널(&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;T2)은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;u&gt;&lt;b&gt;지하1층 26번 승차홈에서 탑승&lt;/b&gt;&lt;/u&gt;가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;477&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Z5WET/dJMb85Ja11Y/Pgkk1BcwCiotRYvRJ1zdOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Z5WET/dJMb85Ja11Y/Pgkk1BcwCiotRYvRJ1zdOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Z5WET/dJMb85Ja11Y/Pgkk1BcwCiotRYvRJ1zdOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZ5WET%2FdJMb85Ja11Y%2FPgkk1BcwCiotRYvRJ1zdOK%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;649&quot; height=&quot;477&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;477&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;&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;size18&quot;&gt;6016 인천공항버스 노선도&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://airportlimousine.co.kr/sub/sub01.php?cat_no=22&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://airportlimousine.co.kr/sub/sub01.php?cat_no=22&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1761489116713&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;공항리무진&quot; data-og-description=&quot;공항리무진&quot; data-og-host=&quot;airportlimousine.co.kr&quot; data-og-source-url=&quot;https://airportlimousine.co.kr/sub/sub01.php?cat_no=22&quot; data-og-url=&quot;http://airportlimousine.co.kr&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cn0tDi/hyZMgocr7k/5MKPOD3KLoUmH0ioZFlgCK/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/btkcfW/hyZMqj2maC/pPMDMWKaYJ70tSkyqRTizk/img.jpg?width=1920&amp;amp;height=880&amp;amp;face=0_0_1920_880,https://scrap.kakaocdn.net/dn/O1VaK/hyZMifdMj0/TIbbXGXAeAZK5BseLUoTmk/img.jpg?width=1920&amp;amp;height=880&amp;amp;face=0_0_1920_880&quot;&gt;&lt;a href=&quot;https://airportlimousine.co.kr/sub/sub01.php?cat_no=22&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://airportlimousine.co.kr/sub/sub01.php?cat_no=22&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cn0tDi/hyZMgocr7k/5MKPOD3KLoUmH0ioZFlgCK/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/btkcfW/hyZMqj2maC/pPMDMWKaYJ70tSkyqRTizk/img.jpg?width=1920&amp;amp;height=880&amp;amp;face=0_0_1920_880,https://scrap.kakaocdn.net/dn/O1VaK/hyZMifdMj0/TIbbXGXAeAZK5BseLUoTmk/img.jpg?width=1920&amp;amp;height=880&amp;amp;face=0_0_1920_880');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;공항리무진&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;공항리무진&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;airportlimousine.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인천공항 2터미널 매표소&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;a href=&quot;https://blog.naver.com/staney/223842007986&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://blog.naver.com/staney/223842007986&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1761490586095&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;인천공항 2터미널에서 남부터미널 가는법 6016번 리무진 버스 시간표&quot; data-og-description=&quot;인천국제공항 2여객터미널에서 출발하는 6016번 리무진 버스 시간표 첫차 06:30 막차 22:50 타는곳 인천공...&quot; data-og-host=&quot;blog.naver.com&quot; data-og-source-url=&quot;https://blog.naver.com/staney/223842007986&quot; data-og-url=&quot;https://blog.naver.com/staney/223842007986&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bBsvHy/hyZMgBJ42C/tv9YcE9kP3ec66L5uX3J3k/img.jpg?width=743&amp;amp;height=558&amp;amp;face=0_0_743_558&quot;&gt;&lt;a href=&quot;https://blog.naver.com/staney/223842007986&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.naver.com/staney/223842007986&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bBsvHy/hyZMgBJ42C/tv9YcE9kP3ec66L5uX3J3k/img.jpg?width=743&amp;amp;height=558&amp;amp;face=0_0_743_558');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;인천공항 2터미널에서 남부터미널 가는법 6016번 리무진 버스 시간표&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;인천국제공항 2여객터미널에서 출발하는 6016번 리무진 버스 시간표 첫차 06:30 막차 22:50 타는곳 인천공...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.naver.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>생활정보  </category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3471</guid>
      <comments>https://aspdotnet.tistory.com/3471#entry3471comment</comments>
      <pubDate>Mon, 27 Oct 2025 00:05:21 +0900</pubDate>
    </item>
    <item>
      <title>SQL Server 2022 SQL 샘플과 암호화/복호화(ENCRYPTBYKEY / DECRYPTBYKEY)</title>
      <link>https://aspdotnet.tistory.com/3469</link>
      <description>&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;SQL 샘플과 &lt;b&gt;암호화/복호화(ENCRYPTBYKEY / DECRYPTBYKEY)&lt;/b&gt; 예제, 그리고 실무에서 흔히 쓰는 &lt;b&gt;Stored Procedure + Dapper 예제 &lt;/b&gt;까지 한 번에 알아보죠.. 실무 관점에서 권한&amp;middot;커넥션풀링&amp;middot;보안 주의사항도 같이 알아봅시다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;STEP 0 &amp;mdash; 전제 / 권한&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;작업은 DB 소유자(DBO) 또는 충분한 권한(데이터베이스 마스터키 생성, 인증서/대칭키 생성 권한)이 있는 계정에서 수행하세요.&lt;/li&gt;
&lt;li&gt;SQL Server 2022 환경에서 동작합니다.&lt;/li&gt;
&lt;li&gt;커넥션 풀 사용 시 OPEN SYMMETRIC KEY는 &lt;b&gt;세션(연결)&lt;/b&gt; 기준임을 유의하세요. (같은 연결에서 open &amp;rarr; select &amp;rarr; close 순으로 처리해야 합니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;STEP 1 &amp;mdash; 샘플 테이블 준비&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 예시 테이블을 만듭니다. 암호화 대상 컬럼은 varbinary로 저장합니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;USE YourDatabase;
GO

IF OBJECT_ID('dbo.Tables','U') IS NOT NULL DROP TABLE dbo.Tables;
CREATE TABLE dbo.Tables
(
    CustSeq INT IDENTITY(1,1) PRIMARY KEY,
    CustName NVARCHAR(200),
    BackAccNo VARBINARY(MAX) -- 암호화된 값 저장
);
GO&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;STEP 2 &amp;mdash; Database Master Key (DBMK) 생성 (없으면)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 마스터 키가 없으면 만듭니다. 이미 있으면 이 스텝은 건너뜁니다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- DB Master Key 생성 (한 번만)
CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'StrongDBMasterKey!2025';
GO
&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;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 환경에서는 강력한 비밀번호를 사용하고, 키 관리를 정책에 따라 하세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;STEP 3 &amp;mdash; 인증서 또는 비대칭키 생성&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대칭키를 보호(암호화)하기 위한 인증서를 만듭니다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- 인증서 생성 (대칭키를 보호하는데 사용)
CREATE CERTIFICATE MyCertificate
    WITH SUBJECT = 'Certificate for symmetric key encryption';
GO
&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;STEP 4 &amp;mdash; Symmetric Key 생성&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 암호화/복호화에 사용할 대칭키 생성:&lt;/p&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;CREATE SYMMETRIC KEY MySymmetricKey
    WITH ALGORITHM = AES_256
    ENCRYPTION BY CERTIFICATE MyCertificate;
GO
&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;STEP 5 &amp;mdash; _SYMMETRICKeyOpen 같은 프로시저(래퍼) 만들기&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 OPEN SYMMETRIC KEY ...를 래핑한 저장 프로시저를 만듭니다. 내부에서 OPEN SYMMETRIC KEY만 수행하도록 하세요(그리고 호출자는 닫기까지 책임지는 패턴).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;IF OBJECT_ID('dbo._SYMMETRICKeyOpen','P') IS NOT NULL
    DROP PROCEDURE dbo._SYMMETRICKeyOpen;
GO

CREATE PROCEDURE dbo._SYMMETRICKeyOpen
AS
BEGIN
    SET NOCOUNT ON;
    -- 인증서(또는 비밀번호)로 대칭키 열기
    OPEN SYMMETRIC KEY MySymmetricKey
    DECRYPTION BY CERTIFICATE MyCertificate;
END
GO
&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;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요하면 프로시저에 파라미터(예: certificate name)로 유연성을 둘 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;STEP 6 &amp;mdash; 암호화(데이터 저장) 예제&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대칭키를 열고 ENCRYPTBYKEY로 값을 암호화해서 저장합니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;-- 대칭키 오픈
EXEC dbo._SYMMETRICKeyOpen;

-- 암호화하여 삽입
INSERT INTO dbo.Tables (CustName, BackAccNo)
VALUES
('회사A', ENCRYPTBYKEY(KEY_GUID('MySymmetricKey'), CONVERT(VARBINARY(MAX), '123-456-7890'))),
('회사B', ENCRYPTBYKEY(KEY_GUID('MySymmetricKey'), CONVERT(VARBINARY(MAX), '987-654-3210')));

-- 키 닫기
CLOSE SYMMETRIC KEY MySymmetricKey;
GO&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;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ENCRYPTBYKEY 내부에서 두 번째 인자에 문자열을 바로 넣을 수 있지만, 안전하게 CONVERT(VARBINARY, '...')를 사용하는 것이 일반적입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;STEP 7 &amp;mdash; 복호화(조회) &amp;mdash; 순서 중요&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복호화는 &lt;b&gt;같은 세션에서&lt;/b&gt; OPEN SYMMETRIC KEY 후 DECRYPTBYKEY()를 호출해야 합니다.&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;방법 A: Stored Procedure로 묶기 (권장 &amp;mdash; 키 오픈/조회/닫기 일괄)&lt;/h3&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;IF OBJECT_ID('dbo.usp_GetCustBankAcc','P') IS NOT NULL
    DROP PROCEDURE dbo.usp_GetTables;
GO

CREATE PROCEDURE dbo.usp_GetTables
AS
BEGIN
    SET NOCOUNT ON;

    -- 1. 키 오픈 (다른 DB의 프로시저를 호출하는 경우 EXEC이 가능하도록 스키마/DB 지정)
    EXEC dbo._SYMMETRICKeyOpen;

    -- 2. 복호화 조회
    SELECT 
        CustSeq,
        CustName,
        CONVERT(NVARCHAR(1000), DECRYPTBYKEY(BackAccNo)) AS BackAccNo
    FROM dbo.Tables;

    -- 3. 키 닫기
    CLOSE SYMMETRIC KEY MySymmetricKey;
END
GO&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;사용:&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;EXEC dbo.usp_GetCustBankAcc;
&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;방법 B: 애플리케이션 레이어에서 제어 (Dapper)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱에서 &lt;b&gt;한 연결&lt;/b&gt;(same DB connection)을 열고 EXEC _SYMMETRICKeyOpen, SELECT, CLOSE SYMMETRIC KEY 순으로 실행합니다. (아래에 C# 샘플 포함)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;STEP 8 &amp;mdash; TVF/VIEW 관련 주의&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;VIEW 내부에는 EXEC나 OPEN SYMMETRIC KEY 등을 넣을 수 없으므로, 뷰 안에서 자동으로 복호화할 수 없습니다.&lt;/li&gt;
&lt;li&gt;인라인 TVF는 SELECT 문을 캡슐화하지만, 키 오픈은 TVF 밖(호출자)에서 처리해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;STEP 9 &amp;mdash; 실무 유의사항 (요약)&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;권한&lt;/b&gt;: 인증서 생성, 대칭키 생성은 권한이 필요합니다. 운영 DB에서는 DBA 정책에 따르세요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;커넥션풀링&lt;/b&gt;: OPEN SYMMETRIC KEY는 연결 세션에 종속됩니다. 커넥션 풀에서 재사용될 때 의도치 않게 키가 열린 채로 남아있을 수 있으니, 반드시 CLOSE SYMMETRIC KEY를 호출하세요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;예외 처리&lt;/b&gt;: 애플리케이션 코드에서 예외 발생 시 finally 블록에서 키 닫기 호출을 보장하세요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;로그/감사&lt;/b&gt;: 민감데이터 접근 로그를 남기도록 정책을 마련하세요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성능&lt;/b&gt;: DECRYPT 연산은 비용이 있어 대량 조회 시 성능 영향이 있음. 가능한 필터/페이징 적용.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;STEP 10 &amp;mdash; Dapper(C#) 실전 예제&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 ASP.NET Core(혹은 콘솔)에서 Dapper로 동일 연결 안에서 키 열기 &amp;rarr; 쿼리 &amp;rarr; 키 닫기 패턴입니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;using System.Data.SqlClient;
using Dapper;
using System.Threading.Tasks;
using System.Collections.Generic;

public class CustBankAccDto
{
    public int CustSeq { get; set; }
    public string CustName { get; set; }
    public string BackAccNo { get; set; }
}

public async Task&amp;lt;IEnumerable&amp;lt;CustBankAccDto&amp;gt;&amp;gt; GetCustBankAccsAsync(string connectionString)
{
    using var conn = new SqlConnection(connectionString);
    await conn.OpenAsync();

    try
    {
        // 1) 키 오픈 (동일 연결)
        await conn.ExecuteAsync(&quot;EXEC dbo._SYMMETRICKeyOpen&quot;);

        // 2) 복호화 조회
        var sql = @&quot;
            SELECT 
                CustSeq,
                CustName,
                CONVERT(NVARCHAR(1000), DECRYPTBYKEY(BackAccNo)) AS BackAccNo
            FROM dbo.Tables;
        &quot;;

        var result = await conn.QueryAsync&amp;lt;CustBankAccDto&amp;gt;(sql);
        return result;
    }
    finally
    {
        // 3) 키 닫기 (반드시)
        try
        {
            await conn.ExecuteAsync(&quot;IF EXISTS (SELECT 1 FROM sys.symmetric_keys WHERE name = 'MySymmetricKey') CLOSE SYMMETRIC KEY MySymmetricKey;&quot;);
        }
        catch
        {
            // 닫기 실패는 로그만 남기고 무시하거나 에러 처리
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;주의&lt;/b&gt;: 위 예제는 키 이름이 고정 MySymmetricKey인 경우입니다. 보안 정책에 따라 키 이름/관리 방법을 조정하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;STEP 11 &amp;mdash; 키 재생성/백업/복원 팁&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인증서/대칭키 삭제 전에 &lt;b&gt;백업&lt;/b&gt;하세요. 인증서를 삭제하면 대칭키를 암호 해독할 수 없게 됩니다.&lt;/li&gt;
&lt;li&gt;인증서 백업:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;BACKUP CERTIFICATE MyCertificate TO FILE = 'C:\backup\MyCert.cer' 
WITH PRIVATE KEY (
    FILE = 'C:\backup\MyCert.pvk',
    ENCRYPTION BY PASSWORD = 'StrongBackupPassword!2025'
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;STEP 12 &amp;mdash; 전체 흐름 요약 (한눈에)&lt;/h1&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;DBMK(없으면) 생성 &amp;rarr; 2. 인증서 생성 &amp;rarr; 3. 대칭키 생성(인증서로 보호) &amp;rarr; 4. _SYMMETRICKeyOpen 같은 프로시저 만들기 &amp;rarr; 5. 암호화: ENCRYPTBYKEY &amp;rarr; 6. 복호화: OPEN SYMMETRIC KEY &amp;rarr; DECRYPTBYKEY() &amp;rarr; CLOSE SYMMETRIC KEY &amp;rarr; 7. 앱(Dapper) 또는 Stored Proc에서 일괄 처리&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;challenge&quot; data-emoticon-name=&quot;017&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/challenge/large/017.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/challenge/large/017.png&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;개발자 입장에서는 &amp;ldquo;동작 원리 + 데이터 흐름&amp;rdquo;만 다시 한번 이해해 보죠.&lt;/h4&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  1. 기본 개념&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ &lt;b&gt;대칭키 암호화 (Symmetric Encryption)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;대칭&amp;rdquo;이라는 말 그대로 &lt;b&gt;암호화와 복호화에 같은 키를 사용&lt;/b&gt;합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ENCRYPTBYKEY() &amp;rarr; 데이터를 &lt;b&gt;암호화&lt;/b&gt;할 때 사용&lt;/li&gt;
&lt;li&gt;DECRYPTBYKEY() &amp;rarr; 암호화된 데이터를 &lt;b&gt;복호화&lt;/b&gt;할 때 사용&lt;/li&gt;
&lt;li&gt;둘 다 &lt;b&gt;&amp;ldquo;같은 대칭키(Symmetric Key)&amp;rdquo;&lt;/b&gt; 가 열려 있어야 작동합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;암호화: 평문 &amp;rarr; ENCRYPTBYKEY &amp;rarr; 암호문
복호화: 암호문 &amp;rarr; DECRYPTBYKEY &amp;rarr; 평문
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  2. 구조적 이해 (단계별 흐름)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① 대칭키 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 &lt;b&gt;대칭키(Symmetric Key)&lt;/b&gt; 는 실제 데이터를 암호화/복호화하는 실질적인 키입니다.&lt;/p&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;CREATE SYMMETRIC KEY MySymmetricKey
    WITH ALGORITHM = AES_256
    ENCRYPTION BY CERTIFICATE MyCertificate;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AES_256 : 매우 강력한 암호화 알고리즘 (Advanced Encryption Standard 256-bit)&lt;/li&gt;
&lt;li&gt;ENCRYPTION BY CERTIFICATE : 키를 보호하기 위해 &lt;b&gt;인증서&lt;/b&gt;로 한 번 더 암호화&lt;br /&gt;(즉, 키 자체도 평문으로 저장되지 않음)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;② 키 열기 (OPEN SYMMETRIC KEY)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL Server는 &amp;ldquo;세션 기반 암호화&amp;rdquo;를 사용합니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;OPEN SYMMETRIC KEY MySymmetricKey
DECRYPTION BY CERTIFICATE MyCertificate;
&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;이 명령은 SQL Server 내부적으로:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;인증서를 이용해 DB에 저장된 암호화된 대칭키를 복호화&lt;/li&gt;
&lt;li&gt;현재 세션(Session)의 메모리 영역에 해당 키를 &lt;b&gt;임시로 로드&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  즉, 키는 실제로 DB 테이블에 저장되어 있지만, OPEN SYMMETRIC KEY 실행 시 현재 세션에만 &amp;ldquo;활성화된 상태&amp;rdquo;로 올라옵니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;③ 데이터 암호화 (ENCRYPTBYKEY)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 키가 열린 상태에서 데이터를 암호화할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;INSERT INTO CustBankAcc (CustName, BackAccNo)
VALUES ('홍길동', ENCRYPTBYKEY(KEY_GUID('MySymmetricKey'), '123-456-789012'));
&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;&lt;b&gt;내부 동작 원리&lt;/b&gt;:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;KEY_GUID('MySymmetricKey') &amp;rarr; 대칭키의 GUID를 찾음&lt;/li&gt;
&lt;li&gt;SQL Server 내부 AES-256 엔진이 실행되어 평문 '123-456-789012'를 암호화&lt;/li&gt;
&lt;li&gt;결과는 varbinary 형태(이진 데이터)로 반환되어 DB에 저장됨&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시점에서 DB에는 &amp;ldquo;읽어도 알아볼 수 없는 암호화된 바이트 값&amp;rdquo;이 들어갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시 (실제 DB에 저장된 값 예):&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;0x00A7F9D4B6D23E117CF84A... (길고 복잡한 바이트)
&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;④ 데이터 복호화 (DECRYPTBYKEY)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 조회할 때는 대칭키가 열린 상태에서 복호화 함수를 사용합니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;OPEN SYMMETRIC KEY MySymmetricKey
DECRYPTION BY CERTIFICATE MyCertificate;

SELECT CONVERT(NVARCHAR(1000), DECRYPTBYKEY(BackAccNo))
FROM CustBankAcc;
&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;내부 동작 원리:&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;현재 세션에 열린 키를 사용해 BackAccNo 값을 복호화&lt;/li&gt;
&lt;li&gt;평문(예: '123-456-789012')을 반환&lt;/li&gt;
&lt;li&gt;CONVERT(NVARCHAR)로 문자열로 변환하여 읽을 수 있도록 함&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ 키가 닫혀 있으면 DECRYPTBYKEY() 결과는 NULL을 반환합니다.&lt;br /&gt;이는 보안적 설계로, &amp;ldquo;키가 없으면 평문을 얻을 수 없다&amp;rdquo;는 원칙을 강제합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⑤ 키 닫기 (CLOSE SYMMETRIC KEY)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복호화가 끝나면 키를 반드시 닫습니다.&lt;/p&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;CLOSE SYMMETRIC KEY MySymmetricKey;
&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로써 세션의 메모리에서 키가 제거되고, 더 이상 복호화가 불가능해집니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  3. 요약 그림 (개념적으로)&lt;/h2&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;                 ┌──────────────────────────┐
                 │ Database (암호화된 데이터) │
                 └───────────┬──────────────┘
                             │
            ┌───────────────▼───────────────┐
            │  OPEN SYMMETRIC KEY 실행        │
            │  인증서로 대칭키 복호화 (세션에 로드) │
            └───────────────┬───────────────┘
                             │
        ┌────────────────────▼──────────────────┐
        │ ENCRYPTBYKEY() &amp;rarr; 평문&amp;rarr;암호문 저장       │
        │ DECRYPTBYKEY() &amp;rarr; 암호문&amp;rarr;평문 복원       │
        └────────────────────┬──────────────────┘
                             │
                     ┌───────▼────────┐
                     │ CLOSE KEY (메모리 제거) │
                     └────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⚙️ 4. 실무에서 알아야 할 포인트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분 설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;  세션 기반&lt;/td&gt;
&lt;td&gt;OPEN SYMMETRIC KEY는 &amp;ldquo;현재 세션(커넥션)&amp;rdquo;에만 유효&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;  알고리즘&lt;/td&gt;
&lt;td&gt;AES_256, AES_192, AES_128, Triple_DES 등 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;  데이터형&lt;/td&gt;
&lt;td&gt;varbinary 로 저장, 복호화 시 nvarchar로 변환 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⚠️ NULL 반환&lt;/td&gt;
&lt;td&gt;키가 닫혀 있거나 없는 세션에서는 DECRYPTBYKEY() &amp;rarr; NULL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;  중첩 보호&lt;/td&gt;
&lt;td&gt;대칭키는 인증서로 보호, 인증서는 DB Master Key로 보호&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;  성능 주의&lt;/td&gt;
&lt;td&gt;대량 복호화 시 CPU 부하가 있음 (AES는 CPU 집약적)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  5. 간단한 실습 예제 (요약)&lt;/h2&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;-- 1. 대칭키 열기
OPEN SYMMETRIC KEY MySymmetricKey
DECRYPTION BY CERTIFICATE MyCertificate;

-- 2. 암호화 예제
DECLARE @Encrypted VARBINARY(MAX);
SET @Encrypted = ENCRYPTBYKEY(KEY_GUID('MySymmetricKey'), 'HelloWorld!');
SELECT @Encrypted AS EncryptedValue;

-- 3. 복호화 예제
SELECT CONVERT(NVARCHAR(100), DECRYPTBYKEY(@Encrypted)) AS DecryptedValue;

-- 4. 키 닫기
CLOSE SYMMETRIC KEY MySymmetricKey;
&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;&lt;b&gt;결과 예시:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;EncryptedValue: 0x007000A3F9D45B16...
DecryptedValue: HelloWorld!
&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단계 명령 설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;CREATE SYMMETRIC KEY&lt;/td&gt;
&lt;td&gt;암호화 키 생성 (AES 등 알고리즘 지정)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;OPEN SYMMETRIC KEY&lt;/td&gt;
&lt;td&gt;세션에서 키 활성화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;ENCRYPTBYKEY()&lt;/td&gt;
&lt;td&gt;평문 &amp;rarr; 암호문&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;DECRYPTBYKEY()&lt;/td&gt;
&lt;td&gt;암호문 &amp;rarr; 평문&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;CLOSE SYMMETRIC KEY&lt;/td&gt;
&lt;td&gt;세션에서 키 닫기&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;</description>
      <category>데이터베이스/MS-SQL  </category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3469</guid>
      <comments>https://aspdotnet.tistory.com/3469#entry3469comment</comments>
      <pubDate>Thu, 16 Oct 2025 18:05:07 +0900</pubDate>
    </item>
    <item>
      <title>GitHub SpecKit: AI 코딩, 이제 대충 시키지 마세요! 100배 똑똑하게 쓰는 4단계 비법</title>
      <link>https://aspdotnet.tistory.com/3454</link>
      <description>&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;h4 data-ke-size=&quot;size20&quot;&gt;AI 코딩, 왜 자꾸 에러가 날까요? '바이브 코딩'의 문제점은 무엇일까요?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깃허브가 개발자들을 위한 아주 멋진 새 오픈소스 도구를 내놓았어요. 이름은 바로 SpecKit(스펙킷)이죠 . 이 도구는 우리가 흔히 하던 AI 코딩 방식의 문제점을 해결하기 위해 만들어졌어요. 기존에는 AI 에이전트에게 &quot;이거 만들어줘&quot; 하고 프롬프트만 던지는 경우가 많았죠 .&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;이런 방식을 '바이브 코딩(Vive Coding)'이라고 부르기도 해요. 그냥 느낌대로 대충 시키고 코드가 잘 작동하기를 바라는 방식이죠 . 하지만 이렇게 하면 AI가 만든 코드에 오류가 많거나, 엉뚱한 결과(환각)를 내놓는 경우가 많았어요 . 결국 개발자가 다시 처음부터 고쳐야 하는 일이 생기곤 했죠.&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;SpecKit은 이런 비효율적인 코딩을 끝내기 위해 등장했어요 . SpecKit은 '설계도를 중심으로 하는 개발(Spec-driven development)'을 핵심 원칙으로 삼아요 . 즉, 개발의 처음부터 끝까지 '명세(Specification, 설계도)'를 가장 중요한 기준으로 두고 AI를 안내하는 방식이랍니다 . 이 설계도는 프로젝트와 함께 발전하며 모든 것을 일관성 있게 유지해주는 '살아있는 실행 가능한 문서'가 되는 것이죠 .&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;588&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/be79tW/btsQu2MpqSA/4KVHjiVgOkC4kXKDPJecH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/be79tW/btsQu2MpqSA/4KVHjiVgOkC4kXKDPJecH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/be79tW/btsQu2MpqSA/4KVHjiVgOkC4kXKDPJecH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbe79tW%2FbtsQu2MpqSA%2F4KVHjiVgOkC4kXKDPJecH1%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;1080&quot; height=&quot;588&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;588&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;SpecKit이 제시하는 '설계도 중심 개발' 4단계는 무엇인가요?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpecKit은 AI 코딩의 정확도와 신뢰성을 높이기 위해 개발 과정을 체계적인 4단계로 나누었어요 . 이 4단계는 마치 건물을 지을 때 설계도를 만들고, 계획을 세우고, 작은 작업으로 나누어 시공하는 과정과 비슷해요. 각 단계마다 명확한 목표와 점검 지점(체크포인트)이 있답니다 .&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;사양(Specification)&lt;/b&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;계획(Planning)&lt;/b&gt;이죠. 여기서는 어떤 기술 스택(Tech Stack)을 쓸지, 앱의 전체적인 구조(아키텍처)는 어떻게 할지, 그리고 지켜야 할 제약 조건들을 상세하게 매핑해요 . 예를 들어, &quot;최소한의 라이브러리만 사용해라&quot; 같은 제약 조건을 넣을 수 있어요 .&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;태스크(Task)&lt;/b&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;구현(Implementation)&lt;/b&gt;이에요. 이제 AI가 앞선 세 단계의 설계와 계획을 철저히 따르면서 단계별로 코드를 작성하고 검토를 받는 과정이랍니다 . 이 모든 과정을 거치면, 명세는 단순한 문서가 아니라 더 나은 정렬과 신뢰성을 보장하는 살아있는 결과물이 되는 것이죠 .&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;617&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pUqbR/btsQ7c3wIzP/5MLedURg4KSoLKkcKyAa0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pUqbR/btsQ7c3wIzP/5MLedURg4KSoLKkcKyAa0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pUqbR/btsQ7c3wIzP/5MLedURg4KSoLKkcKyAa0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpUqbR%2FbtsQ7c3wIzP%2F5MLedURg4KSoLKkcKyAa0k%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;617&quot; height=&quot;374&quot; data-origin-width=&quot;617&quot; data-origin-height=&quot;374&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;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Linux/macOS&lt;/b&gt; 또는 Windows의 경우 &lt;b&gt;WSL2&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AI 코딩 에이전트&lt;/b&gt;: &lt;a href=&quot;https://www.anthropic.com/claude-code&quot;&gt;Claude Code&lt;/a&gt;, &lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;GitHub Copilot&lt;/a&gt;, 또는 &lt;a href=&quot;https://github.com/google-gemini/gemini-cli&quot;&gt;Gemini CLI&lt;/a&gt; 중 하나&lt;/li&gt;
&lt;li&gt;패키지 관리 도구 &lt;b&gt;uv&lt;/b&gt; (uv는 Python 패키지 관리 도구입니다.)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Python 3.11+&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Git&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;STEP 1: 프로젝트 초기화&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 &lt;code&gt;specify&lt;/code&gt; CLI 도구를 사용하여 프로젝트를 초기화합니다. 이 명령어를 통해 &lt;code&gt;spec-kit&lt;/code&gt; 템플릿 파일과 필요한 스크립트가 포함된 프로젝트 구조가 생성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;uvx --from git+https://github.com/github/spec-kit.git specify init &amp;lt;PROJECT_NAME&amp;gt;&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;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;PROJECT_NAME&amp;gt;&lt;/code&gt; 부분에 원하는 프로젝트 이름을 입력합니다.&lt;/li&gt;
&lt;li&gt;현재 디렉터리에 초기화하려면 &lt;code&gt;specify init --here&lt;/code&gt; 명령어를 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;STEP 2: 프로젝트 명세(Spec) 작성&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;AI 에이전트를 실행한 후, &lt;code&gt;/specify&lt;/code&gt; 명령어를 사용하여 만들고자 하는 애플리케이션의 **&quot;무엇(what)&quot;**과 **&quot;왜(why)&quot;**에 초점을 맞춰 프로젝트의 명세를 상세하게 설명합니다. 이 단계에서는 기술 스택에 대해 언급하지 않는 것이 중요합니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;gemini cli 를 통해 실행해 봅니다.&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1757515191962&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;/specify 사진을 앨범으로 정리하는 애플리케이션을 만들어줘. 앨범은 날짜별로 그룹화되고, 메인 페이지에서 드래그 앤 드롭으로 재정렬할 수 있어야 해...&quot;&lt;/code&gt;&lt;/pre&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;1113&quot; data-origin-height=&quot;588&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beEv2i/btsQsiXBORW/LSbkFmkBNSl2i4wiHkXEv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beEv2i/btsQsiXBORW/LSbkFmkBNSl2i4wiHkXEv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beEv2i/btsQsiXBORW/LSbkFmkBNSl2i4wiHkXEv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeEv2i%2FbtsQsiXBORW%2FLSbkFmkBNSl2i4wiHkXEv1%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;1113&quot; height=&quot;588&quot; data-origin-width=&quot;1113&quot; data-origin-height=&quot;588&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프롬프트를 입력하면, AI 에이전트가 요구사항을 분석하여 &lt;code&gt;specs/&amp;lt;feature-name&amp;gt;/spec.md&lt;/code&gt; 파일에 &lt;b&gt;사용자 스토리&lt;/b&gt;와 &lt;b&gt;기능 요구사항&lt;/b&gt;이 포함된 명세 문서를 생성합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;STEP 3: 기술 구현 계획(Plan) 생성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명세가 완성되면, &lt;code&gt;/plan&lt;/code&gt; 명령어를 사용하여 구체적인 &lt;b&gt;기술 스택&lt;/b&gt;과 &lt;b&gt;아키텍처&lt;/b&gt;를 설명합니다. 이 단계에서는 &lt;code&gt;.NET&lt;/code&gt;, &lt;code&gt;Postgres&lt;/code&gt;, &lt;code&gt;Blazor&lt;/code&gt;, &lt;code&gt;REST API&lt;/code&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;b&gt;예시 프롬프트:&lt;/b&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;pre id=&quot;code_1757515198435&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;/plan 이 애플리케이션은 .NET Aspire와 Postgres 데이터베이스를 사용해. 프론트엔드는 Blazor Server를 활용하고, REST API를 만들어줘...&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 명령어를 실행하면 AI 에이전트가 기술 스택에 맞춰 &lt;b&gt;API 명세&lt;/b&gt;, &lt;b&gt;데이터 모델&lt;/b&gt;, &lt;b&gt;구현 계획&lt;/b&gt; 등을 담은 세부 문서를 생성합니다. 이 계획 문서를 통해 불필요한 재작업을 줄일 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;STEP 4: 구현 및 검증&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로, &lt;code&gt;/tasks&lt;/code&gt; 명령어를 사용하여 AI 에이전트에게 &lt;b&gt;세분화된 작업 목록&lt;/b&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;명령어 예시:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1757515203968&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; /tasks&lt;/code&gt;&lt;/pre&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;939&quot; data-origin-height=&quot;198&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbljJa/btsQsHQlFCS/DCJ6BcH41O7D1VGxeiUojk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbljJa/btsQsHQlFCS/DCJ6BcH41O7D1VGxeiUojk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbljJa/btsQsHQlFCS/DCJ6BcH41O7D1VGxeiUojk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbljJa%2FbtsQsHQlFCS%2FDCJ6BcH41O7D1VGxeiUojk%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;939&quot; height=&quot;198&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;198&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;&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;다시 정리하자면,&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 시작하려면 specify init 같은 명령어를 사용해요. 이때 내가 사용할 AI 코딩 에이전트를 선택해야 하죠 . 깃허브 코파일럿 코드(GitHub Copilot Code)나 제미니 CLI(Gemini CLI) 같은 옵션들이 있어요 . 깃허브 코파일럿이나 클라우드 코드가 specify와 아주 잘 작동한다고 추천하고 있어요 .&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;초기화가 끝나면, VS Code 같은 IDE(통합 개발 환경)에서 프로젝트 폴더를 열고 작업을 시작해요 . SpecKit은 슬래시 명령어(/)를 사용해서 AI에게 명령을 내리도록 설계되었어요 . 예를 들어, 사양을 만들 때는 /sp (specify) 명령어를, 구현 계획을 만들 때는 /pl (plan) 명령어를, 그리고 실제 작업 목록을 만들 때는 /task 명령어를 사용하죠 . 이 명령어들을 사용할 때마다 프로젝트의 핵심 원칙이 담긴 constitution.md 파일이 자동으로 업데이트된답니다 .&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;SpecKit 워크플로우의 첫걸음은 바로 사양(Specification)을 만드는 거예요 . IDE 터미널에서 /sp 명령어를 입력하고, 만들고 싶은 것에 대한 설명을 프롬프트로 넣어주면 되죠 . 여기서 중요한 점이 있어요. 단순히 &quot;이런 기술을 써서 만들어줘&quot;라고 말하는 것이 아니에요.&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;우리는 무엇을(what) 만들 것인지, 그리고 왜(why) 만들 것인지에 초점을 맞춰서 설명해야 해요 . 예를 들어, &quot;사진을 별도의 사진 앨범으로 구성하고 정리할 수 있는 애플리케이션을 만들고 싶다&quot;와 같은 구체적인 목표를 제시하는 것이죠 . AI는 이 설명을 바탕으로 개발자가 따라야 할 구체적인 사양 문서를 생성해 준답니다 .&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가 사양 생성을 완료하면, 우리는 이 문서를 확인해서 AI 에이전트가 어떤 지침을 따를지 정확히 파악할 수 있어요 . 이 사양 파일은 AI가 앞으로의 계획과 구현 작업을 진행하는 데 있어 가장 중요한 기준이 된답니다 . 이처럼 첫 단계부터 명확한 설계도를 만들면, 나중에 코딩 에이전트가 엉뚱한 방향으로 가는 것을 막을 수 있어요.&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;사양 생성이 끝나면 이제 /pl 명령어를 사용해서 2단계인 계획(Planning) 단계로 넘어가요 . 이 단계의 목표는 1단계에서 정한 사양을 실제로 구현하기 위한 상세한 로드맵을 만드는 것이죠 . AI는 이 로드맵에 기술 스택과 아키텍처 같은 기술적인 내용을 포함시켜요 .&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 코딩의 오류(환각)를 줄이는 데 결정적인 역할을 해요. 이 단계에서 개발자는 제약 조건(Constraints)을 명확히 설정할 수 있어요 . 예를 들어, &quot;바닐라 HTML/CSS만 사용하고 최소한의 라이브러리만 써라&quot;와 같은 규칙을 정하는 것이죠. 이렇게 하면 AI가 따를 규칙이 명확해져서 환각이 발생할 가능성이 훨씬 낮아진답니다 . 또한, 이 계획 단계를 통해 프로젝트 아이디어를 반복적으로 다듬을 수 있어서 토큰 사용량도 줄어드는 이점이 있어요 .&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;계획이 완성되면 /task 명령어를 실행해서 3단계인 태스크(Task) 생성으로 넘어가요 . AI는 앞선 계획에 따라 실행 가능한 작업 목록을 만들고, 이 목록을 작은 단위로 쪼개서 구현을 준비하죠 .&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;이제 4단계 구현(Implementation)에서는 AI가 사양과 계획을 철저히 따르면서 코드를 작성하게 된답니다 . 이 체계적인 워크플로우 덕분에, 단순히 프롬프트만 던져서 전체 앱을 개발하는 방식보다 훨씬 오류가 적고 신뢰성 높은 코드를 얻을 수 있어요 .&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;challenge&quot; data-emoticon-name=&quot;001&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/challenge/large/001.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/challenge/large/001.png&quot; width=&quot;150&quot; /&gt;&lt;/figure&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;다음은&lt;span&gt; 실습으로&amp;nbsp;&lt;/span&gt;&lt;/span&gt;spec-kit의 핵심 단계에 맞춰 ASP.NET Core 8 기반의 BOARD 웹사이트를 구현하는 실무적인 가이드입니다.&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;STEP 1: 프로젝트 명세(Spec) 작성  &lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 단계는 **&quot;무엇(What)&quot;**을 만들 것인지 정의하는 과정입니다. AI 에이전트에게 &lt;code&gt;/specify&lt;/code&gt; 명령어를 사용하여 요구사항을 상세하게 설명합니다. 이 단계에서는 &lt;b&gt;기술 스택에 대한 언급을 피하는 것이 중요&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;실행 가능한 프롬프트 예시:&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;/specify
사용자가 칸반 보드를 생성하고, 보드 내에 여러 개의 칼럼을 추가할 수 있는 웹 애플리케이션을 개발해줘.
각 칼럼에는 카드를 추가할 수 있어야 하고, 카드는 제목과 상세 설명을 포함해야 해.
사용자는 카드를 한 칼럼에서 다른 칼럼으로 드래그 앤 드롭하여 옮길 수 있어야 하고,
카드의 순서를 변경하는 것도 가능해야 해.
모든 데이터는 영구적으로 저장되어야 하고, 페이지를 새로고침해도 데이터가 유지되어야 해.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프롬프트를 입력하면, AI 에이전트가 이를 바탕으로 &lt;code&gt;spec.md&lt;/code&gt;와 같은 명세 문서를 자동으로 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;STEP 2: 기술 계획(Plan) 생성  ️&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;명세가 확정된 후, &lt;b&gt;&quot;어떻게(How)&quot;&lt;/b&gt; 구현할 것인지 구체적인 기술 계획을 &lt;code&gt;/plan&lt;/code&gt; 명령어를 통해 AI 에이전트에게 전달합니다. 이 단계에서 사용자께서 선호하는 &lt;b&gt;ASP.NET Core, Vue.js, CQRS, MSSQL&lt;/b&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;실행 가능한 프롬프트 예시:&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;/plan
이 애플리케이션은 ASP.NET Core 8 Web API를 백엔드로 사용하며,
CQRS 패턴을 적용하여 데이터 조회와 변경 로직을 분리해줘.
데이터베이스는 MSSQL을 사용하고 Entity Framework Core를 ORM으로 활용해줘.
프론트엔드는 Vue.js 3와 Axios를 사용해서 REST API를 호출하도록 구현해줘.
카드의 드래그 앤 드롭 기능은 Vue.js용 라이브러리를 활용하고,
실시간 업데이트를 위해 ASP.NET Core SignalR을 사용한 구현 계획을 포함해줘.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트는 이 계획을 바탕으로 &lt;b&gt;&lt;code&gt;plan.md&lt;/code&gt;&lt;/b&gt;, API 명세(예: &lt;code&gt;api-spec.json&lt;/code&gt;), 데이터 모델, 아키텍처 다이어그램 등 세부 문서를 생성합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;STEP 3: 코드 구현 및 검증  &lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로, AI 에이전트에게 구체적인 구현 작업을 요청합니다. 이 단계에서는 &lt;code&gt;/tasks&lt;/code&gt; 명령어를 통해 계획에 따라 코드를 작성하도록 지시합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;실행 가능한 프롬프트 예시:&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;/tasks
계획 문서에 따라 ASP.NET Core 백엔드와 Vue.js 프론트엔드 코드를 모두 구현해줘.
특히, CQRS 패턴을 적용한 Command와 Query 핸들러를 먼저 작성하고,
이를 사용하는 BoardsController와 CardsController의 API 엔드포인트를 구현해줘.
또한, Vue.js 프로젝트에 API 호출 로직과 UI 컴포넌트(보드, 칼럼, 카드)를 구현해줘.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 단계가 완료되면, AI 에이전트가 실제 코드를 생성합니다. 하지만 AI가 생성한 코드는 완벽하지 않을 수 있으므로, &lt;b&gt;개발자의 역할은 생성된 코드를 검토하고, 빌드 에러나 논리적 오류를 수정하며, 테스트를 통해 안정성을 확보하는 것&lt;/b&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;code&gt;spec-kit&lt;/code&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/O5Dpj/dJMb87tlR0m/6K8gELhMkOCDfvGHMPFtG0/001-kanban-board.zip?attach=1&amp;amp;knm=tfile.zip&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;001-kanban-board.zip&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.01MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;추천 유투브&lt;/h4&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://www.youtube.com/watch?v=_PeYeRWeQWw&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=_PeYeRWeQWw&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=_PeYeRWeQWw&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/trr8U/hyZLxbPe5r/2j53fOpUbwK5Y1nO0MO5P0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/bRfX5G/hyZKbnPUMt/fz6NWaOlKLo2c4UgA68x0K/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;Spec Kit: Github's NEW Tool Ends Vibe Coding! 100x Better Than Vibe Coding (Full Tutorial)&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/_PeYeRWeQWw&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI 관련/AI 기술</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3454</guid>
      <comments>https://aspdotnet.tistory.com/3454#entry3454comment</comments>
      <pubDate>Tue, 14 Oct 2025 01:05:01 +0900</pubDate>
    </item>
    <item>
      <title>Mapperly로 배우는 실전 .NET 객체 매핑</title>
      <link>https://aspdotnet.tistory.com/3468</link>
      <description>&lt;h1&gt;Mapperly로 배우는 실전 .NET 객체 매핑&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  서론: 왜 Mapperly인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.NET 개발자라면 &lt;b&gt;DTO 변환&lt;/b&gt;이나 &lt;b&gt;API 모델 매핑&lt;/b&gt; 때문에 반복적인 코드를 수도 없이 작성해 보셨을 겁니다.&lt;br /&gt;예를 들어, User 엔티티를 UserDto로 바꿔야 한다면 속성 하나하나를 직접 할당해야 하죠. 작은 프로젝트에서는 괜찮지만, 규모가 커지면 유지보수 지옥이 찾아옵니다.&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;Mapperly&lt;/b&gt;가 빛을 발합니다.&lt;br /&gt;Mapperly는 &lt;b&gt;Roslyn 소스 생성기&lt;/b&gt;를 활용해 &lt;b&gt;빌드 시점에 매핑 코드를 자동 생성&lt;/b&gt;합니다. 즉, 런타임에 리플렉션을 쓰지 않고 &lt;b&gt;순수 C# 코드처럼 빠르게 동작&lt;/b&gt;하죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 &lt;b&gt;실무에서 자주 마주치는 매핑 패턴&lt;/b&gt;을 중심으로 Mapperly 활용법을 알려드립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;h2 data-ke-size=&quot;size26&quot;&gt;  본론: 실무에서 자주 쓰는 Mapperly 패턴&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 가장 기본적인 DTO 변환&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 User &amp;rarr; UserDto 매핑이 필요하다고 합시다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class User
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
}

public class UserDto
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
}
&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mapperly 매퍼 선언은 이렇게 간단합니다  &lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;[Mapper]
public static partial class UserMapper
{
    public static partial UserDto ToDto(User user);
}
&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드 후 생성된 코드는 다음과 같아요:&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;public static partial UserDto ToDto(User user)
{
    var dto = new UserDto();
    dto.Id = user.Id;
    dto.Name = user.Name;
    return dto;
}
&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;핸드코딩과 똑같이 깔끔하게&lt;/b&gt; 생성됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 속성명이 다른 경우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 엔티티와 DTO 속성명이 다른 경우가 흔합니다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class Product
{
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;
}

public class ProductDto
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty; // Title &amp;rarr; Name
}
&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때는 MapProperty로 매핑을 지정합니다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;[Mapper]
public static partial class ProductMapper
{
    [MapProperty(nameof(Product.Title), nameof(ProductDto.Name))]
    public static partial ProductDto ToDto(Product product);
}
&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;➡️ 속성명이 달라도 문제없이 매핑됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 컬렉션 매핑&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Order 엔티티 안에 OrderItem 리스트가 있고, 이를 OrderItemDto 리스트로 매핑해야 한다고 해봅시다.&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;public class Order
{
    public List&amp;lt;OrderItem&amp;gt; Items { get; set; } = [];
}

public class OrderDto
{
    public List&amp;lt;OrderItemDto&amp;gt; Items { get; set; } = [];
}
&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mapperly는 컬렉션도 자동으로 처리합니다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;[Mapper]
public static partial class OrderMapper
{
    public static partial OrderDto ToDto(Order order);
}
&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡️ 빌드하면 내부적으로 foreach 루프를 생성해 줍니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 열거형 매핑 (Enum)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;열거형 값이 다를 때도 흔히 문제가 되는데요.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public enum Status { Pending = 1, Completed = 2 }
public enum StatusDto { Waiting = 1, Done = 2 }
&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;숫자 값이 다르면 Mapperly가 오류를 냅니다.&lt;br /&gt;이때 EnumMappingStrategy.ByName을 쓰면 이름 기준으로 매핑됩니다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;[Mapper(EnumMappingStrategy = EnumMappingStrategy.ByName)]
public static partial class StatusMapper
{
    public static partial StatusDto ToDto(Status status);
}
&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. EF Core 성능 최적화 (IQueryable Projection)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서 가장 많이 쓰이는 패턴 중 하나는 &lt;b&gt;DB에서 엔티티 &amp;rarr; DTO 변환&lt;/b&gt;입니다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;[Mapper]
public static partial class UserMapper
{
    public static partial UserDto ToDto(User user);

    // IQueryable projection 지원
    public static partial IQueryable&amp;lt;UserDto&amp;gt; ProjectToDto(IQueryable&amp;lt;User&amp;gt; query);
}
&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 EF Core에서 이렇게 사용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;var dtos = dbContext.Users.ProjectToDto().ToList();
&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;➡️ Mapperly가 &lt;b&gt;LINQ Expression으로 변환&lt;/b&gt;해주기 때문에 DB에서 필요한 컬럼만 SELECT 하게 됩니다. (성능 ⬆️)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;h2 data-ke-size=&quot;size26&quot;&gt;  결론: 실무에서 Mapperly가 주는 가치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제 경험상, AutoMapper 같은 리플렉션 기반 매퍼보다 Mapperly는 다음과 같은 장점이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;속도&lt;/b&gt;: 런타임 리플렉션 없음 &amp;rarr; DTO 변환 속도가 눈에 띄게 빨라집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;안정성&lt;/b&gt;: 빌드 시 매핑 검증 &amp;rarr; 런타임 버그 감소&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유지보수성&lt;/b&gt;: 생성된 코드가 사람이 작성한 것과 똑같아서 디버깅이 쉬움&lt;/li&gt;
&lt;li&gt;&lt;b&gt;현대적 개발 환경 대응&lt;/b&gt;: AOT, 트리밍, 서버리스 환경에서도 완벽 호환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Mapperly는 &lt;b&gt;실무에서 &quot;안전하고 빠른 매핑&quot;을 하고 싶을 때 선택해야 할 도구&lt;/b&gt;입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❓ Q&amp;amp;A&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Q1. AutoMapper에서 Mapperly로 옮길 수 있나요?&lt;/b&gt;&lt;br /&gt;A. 네, 기존 매핑 로직을 점진적으로 Mapperly로 교체할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Q2. 매핑이 잘못되면 어떻게 알 수 있나요?&lt;/b&gt;&lt;br /&gt;A. 빌드 시점에 오류나 경고 메시지를 통해 바로 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Q3. 성능 차이가 정말 크나요?&lt;/b&gt;&lt;br /&gt;A. 특히 EF Core와 같이 대량 데이터를 다루는 경우 성능 차이가 확실하게 납니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; ️ 관련 태그&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;#dotnet&lt;/li&gt;
&lt;li&gt;#mapperly&lt;/li&gt;
&lt;li&gt;#객체매핑&lt;/li&gt;
&lt;li&gt;#자동매핑&lt;/li&gt;
&lt;li&gt;#efcore&lt;/li&gt;
&lt;li&gt;#성능최적화&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  이미지 프롬프트 제안&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;서론&lt;/b&gt;: &amp;ldquo;개발자가 반복적인 매핑 코드를 지우고 자동화된 코드가 생성되는 장면&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;속성명이 다른 경우&lt;/b&gt;: &amp;ldquo;두 개의 다른 이름 속성이 연결되는 다이어그램&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;컬렉션 매핑&lt;/b&gt;: &amp;ldquo;리스트 아이콘이 DTO 아이콘으로 변환되는 과정&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Enum 매핑&lt;/b&gt;: &amp;ldquo;이름은 같지만 숫자가 다른 두 Enum이 일치하는 모습&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;EF Core Projection&lt;/b&gt;: &amp;ldquo;SQL SELECT가 DTO로 직접 변환되는 인포그래픽&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결론&lt;/b&gt;: &amp;ldquo;개발자가 성능과 생산성을 동시에 잡고 웃는 모습&amp;rdquo;&lt;/li&gt;
&lt;/ol&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>닷넷관련/CSharp  </category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3468</guid>
      <comments>https://aspdotnet.tistory.com/3468#entry3468comment</comments>
      <pubDate>Fri, 3 Oct 2025 02:26:08 +0900</pubDate>
    </item>
    <item>
      <title>ASP.NET CORE : Clean Architecture + Mediator 패턴 (MediatR ❌❌❌)</title>
      <link>https://aspdotnet.tistory.com/3467</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. Clean Architecture의 핵심 원칙 (Dependency Rule)&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;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;핵심 규칙: &quot;안쪽 계층은 바깥쪽 계층을 절대 알아서는 안 된다&quot;

Domain (가장 안쪽)
  &amp;uarr;
Application (Domain만 의존)
  &amp;uarr;
Infrastructure (Application + Domain 의존)
  &amp;uarr;
WebApi/Presentation (모든 계층 의존 가능)&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜 이렇게 해야 하나요?&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Domain 계층&lt;/b&gt;은 비즈니스 규칙만 담당하므로, 데이터베이스나 웹 프레임워크가 바뀌어도 영향을 받지 않습니다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;테스트 용이성&lt;/b&gt;: 각 계층을 독립적으로 테스트할 수 있습니다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유지보수성&lt;/b&gt;: 한 계층의 변경이 다른 계층에 영향을 최소화합니다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. Mediator 패턴 vs MediatR 라이브러리&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;첨부 문서에서 사용한 &lt;code&gt;martinothamar/Mediator&lt;/code&gt;는 &lt;b&gt;MediatR의 고성능 대안&lt;/b&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;b&gt;주요 차이점:&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;width: 781px;&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;width: 82px;&quot;&gt;특징&lt;/th&gt;
&lt;th style=&quot;width: 149px;&quot;&gt;MediatR&lt;/th&gt;
&lt;th style=&quot;width: 550px;&quot;&gt;martinothamar/Mediator&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 82px;&quot;&gt;구현 방식&lt;/td&gt;
&lt;td style=&quot;width: 149px;&quot;&gt;런타임 리플렉션&lt;/td&gt;
&lt;td style=&quot;width: 550px;&quot;&gt;Source Generators (컴파일 타임)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 82px;&quot;&gt;성능&lt;/td&gt;
&lt;td style=&quot;width: 149px;&quot;&gt;일반적인 성능&lt;/td&gt;
&lt;td style=&quot;width: 550px;&quot;&gt;&lt;b&gt;매우 빠름&lt;/b&gt; (리플렉션 오버헤드 없음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 82px;&quot;&gt;.NET 버전&lt;/td&gt;
&lt;td style=&quot;width: 149px;&quot;&gt;.NET Standard 2.0+&lt;/td&gt;
&lt;td style=&quot;width: 550px;&quot;&gt;&lt;b&gt;.NET 6+ (최신 기능 활용)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;Mediator 패턴의 장점:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// Mediator 없이 (결합도 높음)
public class TodoController
{
    private readonly TodoService _service;
    private readonly EmailService _emailService;
    private readonly LogService _logService;
    // 생성자가 비대해집니다 (Fat Constructor)

    public TodoController(TodoService service, EmailService email, LogService log)
    {
        _service = service;
        _emailService = email;
        _logService = log;
    }
}

// Mediator 사용 (결합도 낮음)
public class TodoController
{
    private readonly ISender _mediator; // 단 하나의 의존성!

    public TodoController(ISender mediator)
    {
        _mediator = mediator;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. CQRS 패턴 심화 이해&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;첨부 문서에서 설명한 CQRS를 더 구체적으로 보겠습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(1) Command (명령) - 상태 변경&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// Command: 데이터를 변경하고 결과(Id)를 반환
public sealed record CreateTodoItemCommand(string Title) : IRequest&amp;lt;Guid&amp;gt;;

// Command Handler: 실제 비즈니스 로직 실행
internal sealed class CreateTodoItemCommandHandler 
    : IRequestHandler&amp;lt;CreateTodoItemCommand, Guid&amp;gt;
{
    private readonly ITodoItemRepository _repository;

    public CreateTodoItemCommandHandler(ITodoItemRepository repository)
    {
        _repository = repository;
    }

    public async Task&amp;lt;Guid&amp;gt; Handle(
        CreateTodoItemCommand request, 
        CancellationToken cancellationToken)
    {
        // 1. 비즈니스 규칙 검증
        if (string.IsNullOrWhiteSpace(request.Title))
            throw new ArgumentException(&quot;제목은 필수입니다&quot;);

        // 2. Domain 엔티티 생성
        var todoItem = new TodoItem
        {
            Id = Guid.NewGuid(),
            Title = request.Title,
            IsDone = false
        };

        // 3. Repository를 통해 저장
        await _repository.AddAsync(todoItem, cancellationToken);

        // 4. 생성된 ID 반환
        return todoItem.Id;
    }
}&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;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(2) Query (조회) - 데이터 읽기&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// Query: 데이터를 조회하고 DTO 반환
public sealed record GetTodoItemQuery(Guid Id) : IRequest&amp;lt;TodoItemDto&amp;gt;;

// Query Handler: 데이터 조회 및 변환
internal sealed class GetTodoItemQueryHandler 
    : IRequestHandler&amp;lt;GetTodoItemQuery, TodoItemDto&amp;gt;
{
    private readonly ITodoItemRepository _repository;

    public GetTodoItemQueryHandler(ITodoItemRepository repository)
    {
        _repository = repository;
    }

    public async Task&amp;lt;TodoItemDto&amp;gt; Handle(
        GetTodoItemQuery request, 
        CancellationToken cancellationToken)
    {
        // 1. Repository에서 데이터 조회
        var todoItem = await _repository.GetByIdAsync(
            request.Id, 
            cancellationToken);

        if (todoItem is null)
            throw new NotFoundException(&quot;할 일을 찾을 수 없습니다&quot;);

        // 2. Domain 엔티티를 DTO로 변환
        return new TodoItemDto
        {
            Id = todoItem.Id,
            Title = todoItem.Title,
            IsDone = todoItem.IsDone
        };
    }
}&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CQRS의 장점:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;성능 최적화&lt;/b&gt;: 읽기와 쓰기를 독립적으로 최적화 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;확장성&lt;/b&gt;: 읽기 전용 DB를 별도로 구성 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;명확성&lt;/b&gt;: 코드의 의도가 명확하게 드러남&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;4. 실제 동작 흐름 (시퀀스 다이어그램)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첨부 문서의 흐름을 더 상세하게 표현하겠습니다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;사용자 &amp;rarr; [POST /api/todo-items { &quot;title&quot;: &quot;장보기&quot; }]
   &amp;darr;
[TodoItemsController]
   &amp;darr; (1) HTTP Request &amp;rarr; Command 변환
var command = new CreateTodoItemCommand(&quot;장보기&quot;);
   &amp;darr;
   &amp;darr; (2) Mediator에 전송
await mediator.Send(command);
   &amp;darr;
[Mediator 라이브러리]
   &amp;darr; (3) 적절한 Handler 자동 탐색 (Source Generator로 컴파일 타임에 생성됨)
   &amp;darr;
[CreateTodoItemCommandHandler]
   &amp;darr; (4) 비즈니스 로직 실행
var todoItem = new TodoItem { Id = Guid.NewGuid(), Title = &quot;장보기&quot; };
   &amp;darr;
   &amp;darr; (5) Repository 인터페이스 호출
await _repository.AddAsync(todoItem);
   &amp;darr;
[Infrastructure - TodoItemRepository]
   &amp;darr; (6) 실제 DB 저장 (Entity Framework Core)
await _context.TodoItems.AddAsync(todoItem);
await _context.SaveChangesAsync();
   &amp;darr;
   &amp;darr; (7) 생성된 ID 반환
return todoItem.Id;
   &amp;darr;
[TodoItemsController]
   &amp;darr; (8) HTTP 201 Created 응답
return CreatedAtAction(nameof(GetTodoItem), new { id = todoItemId });
   &amp;darr;
사용자 &amp;larr; [201 Created, Location: /api/todo-items/{guid}]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;5. 각 계층의 책임과 예제 코드&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(1) Domain 계층&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// Domain/Entities/TodoItem.cs
namespace Domain.Entities;

/// &amp;lt;summary&amp;gt;
/// 할 일 엔티티 (순수한 비즈니스 모델)
/// &amp;lt;/summary&amp;gt;
public sealed class TodoItem
{
    public Guid Id { get; set; }

    public required string Title { get; set; }

    public bool IsDone { get; set; }

    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;

    // 비즈니스 규칙 메서드
    public void MarkAsCompleted()
    {
        if (IsDone)
            throw new InvalidOperationException(&quot;이미 완료된 할 일입니다&quot;);

        IsDone = true;
    }
}&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다른 계층에 대한 의존성이 전혀 없음&lt;/li&gt;
&lt;li&gt;&lt;code&gt;using&lt;/code&gt; 문에 &lt;code&gt;Microsoft.EntityFrameworkCore&lt;/code&gt; 같은 기술적 라이브러리가 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(2) Application 계층&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// Application/Common/Interfaces/ITodoItemRepository.cs
namespace Application.Common.Interfaces;

/// &amp;lt;summary&amp;gt;
/// Repository 인터페이스 (구현은 Infrastructure에서)
/// &amp;lt;/summary&amp;gt;
public interface ITodoItemRepository
{
    Task&amp;lt;TodoItem?&amp;gt; GetByIdAsync(Guid id, CancellationToken cancellationToken);
    Task AddAsync(TodoItem todoItem, CancellationToken cancellationToken);
    Task UpdateAsync(TodoItem todoItem, CancellationToken cancellationToken);
    Task DeleteAsync(Guid id, CancellationToken cancellationToken);
}&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;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// Application/TodoItems/Commands/CreateTodoItem/CreateTodoItemCommand.cs
using Mediator;

namespace Application.TodoItems.Commands.CreateTodoItem;

/// &amp;lt;summary&amp;gt;
/// 할 일 생성 명령
/// &amp;lt;/summary&amp;gt;
public sealed record CreateTodoItemCommand(string Title) : IRequest&amp;lt;Guid&amp;gt;;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Application 계층의 DI 설정:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// Application/DependencyInjection.cs
using Microsoft.Extensions.DependencyInjection;
using Mediator;

namespace Application;

public static class DependencyInjection
{
    public static IServiceCollection AddApplication(
        this IServiceCollection services)
    {
        // Mediator 라이브러리 등록
        services.AddMediator(options =&amp;gt;
        {
            options.ServiceLifetime = ServiceLifetime.Scoped;
        });

        return services;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(3) Infrastructure 계층&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;// Infrastructure/Persistence/AppDbContext.cs
using Domain.Entities;
using Microsoft.EntityFrameworkCore;

namespace Infrastructure.Persistence;

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions&amp;lt;AppDbContext&amp;gt; options) 
        : base(options)
    {
    }

    public DbSet&amp;lt;TodoItem&amp;gt; TodoItems =&amp;gt; Set&amp;lt;TodoItem&amp;gt;();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Entity 설정
        modelBuilder.Entity&amp;lt;TodoItem&amp;gt;(entity =&amp;gt;
        {
            entity.HasKey(e =&amp;gt; e.Id);
            entity.Property(e =&amp;gt; e.Title)
                .IsRequired()
                .HasMaxLength(200);
        });
    }
}&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;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// Infrastructure/Persistence/TodoItemRepository.cs
using Application.Common.Interfaces;
using Domain.Entities;
using Microsoft.EntityFrameworkCore;

namespace Infrastructure.Persistence;

/// &amp;lt;summary&amp;gt;
/// ITodoItemRepository 인터페이스의 실제 구현
/// &amp;lt;/summary&amp;gt;
public class TodoItemRepository : ITodoItemRepository
{
    private readonly AppDbContext _context;

    public TodoItemRepository(AppDbContext context)
    {
        _context = context;
    }

    public async Task&amp;lt;TodoItem?&amp;gt; GetByIdAsync(
        Guid id, 
        CancellationToken cancellationToken)
    {
        return await _context.TodoItems
            .FirstOrDefaultAsync(x =&amp;gt; x.Id == id, cancellationToken);
    }

    public async Task AddAsync(
        TodoItem todoItem, 
        CancellationToken cancellationToken)
    {
        await _context.TodoItems.AddAsync(todoItem, cancellationToken);
        await _context.SaveChangesAsync(cancellationToken);
    }

    public async Task UpdateAsync(
        TodoItem todoItem, 
        CancellationToken cancellationToken)
    {
        _context.TodoItems.Update(todoItem);
        await _context.SaveChangesAsync(cancellationToken);
    }

    public async Task DeleteAsync(
        Guid id, 
        CancellationToken cancellationToken)
    {
        var todoItem = await GetByIdAsync(id, cancellationToken);
        if (todoItem is not null)
        {
            _context.TodoItems.Remove(todoItem);
            await _context.SaveChangesAsync(cancellationToken);
        }
    }
}&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Infrastructure DI 설정:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// Infrastructure/DependencyInjection.cs
using Application.Common.Interfaces;
using Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Infrastructure;

public static class DependencyInjection
{
    public static IServiceCollection AddInfrastructure(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        // DbContext 등록
        services.AddDbContext&amp;lt;AppDbContext&amp;gt;(options =&amp;gt;
            options.UseSqlServer(
                configuration.GetConnectionString(&quot;DefaultConnection&quot;)));

        // Repository 등록
        services.AddScoped&amp;lt;ITodoItemRepository, TodoItemRepository&amp;gt;();

        return services;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(4) WebApi 계층&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// WebApi/Controllers/TodoItemsController.cs
using Application.TodoItems.Commands.CreateTodoItem;
using Application.TodoItems.Queries.GetTodoItem;
using Mediator;
using Microsoft.AspNetCore.Mvc;

namespace WebApi.Controllers;

[ApiController]
[Route(&quot;api/todo-items&quot;)]
public class TodoItemsController : ControllerBase
{
    private readonly ISender _mediator;

    public TodoItemsController(ISender mediator)
    {
        _mediator = mediator;
    }

    /// &amp;lt;summary&amp;gt;
    /// 새로운 할 일 생성
    /// &amp;lt;/summary&amp;gt;
    /// &amp;lt;param name=&quot;request&quot;&amp;gt;생성할 할 일 정보&amp;lt;/param&amp;gt;
    /// &amp;lt;returns&amp;gt;생성된 할 일의 ID&amp;lt;/returns&amp;gt;
    [HttpPost]
    [ProducesResponseType(StatusCodes.Status201Created)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    public async Task&amp;lt;IActionResult&amp;gt; CreateTodoItem(
        [FromBody] CreateTodoItemRequest request)
    {
        // 1. API 요청을 Command로 변환
        var command = new CreateTodoItemCommand(request.Title);

        // 2. Mediator에 전달 (Handler가 자동 실행됨)
        var todoItemId = await _mediator.Send(command);

        // 3. 201 Created 응답 반환
        return CreatedAtAction(
            nameof(GetTodoItem), 
            new { id = todoItemId }, 
            new { id = todoItemId });
    }

    /// &amp;lt;summary&amp;gt;
    /// 할 일 상세 조회
    /// &amp;lt;/summary&amp;gt;
    /// &amp;lt;param name=&quot;id&quot;&amp;gt;할 일 ID&amp;lt;/param&amp;gt;
    /// &amp;lt;returns&amp;gt;할 일 상세 정보&amp;lt;/returns&amp;gt;
    [HttpGet(&quot;{id:guid}&quot;)]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task&amp;lt;IActionResult&amp;gt; GetTodoItem(Guid id)
    {
        // 1. Query 생성
        var query = new GetTodoItemQuery(id);

        // 2. Mediator에 전달
        var todoItem = await _mediator.Send(query);

        // 3. 200 OK 응답 반환
        return Ok(todoItem);
    }
}

// API Request DTO
public record CreateTodoItemRequest(string Title);&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;&lt;b&gt;Program.cs 설정:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// WebApi/Program.cs
using Application;
using Infrastructure;

var builder = WebApplication.CreateBuilder(args);

// 계층별 서비스 등록
builder.Services.AddControllers();
builder.Services.AddApplication();  // Application 계층
builder.Services.AddInfrastructure(builder.Configuration);  // Infrastructure 계층

// Swagger 설정
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Middleware 설정
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

app.Run();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  핵심 포인트 정리&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 왜 이렇게 복잡하게 나누나요?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단순한 방식 (레거시):&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// 모든 로직이 Controller에 섞여 있음
public class TodoController : ControllerBase
{
    private readonly SqlConnection _connection;

    [HttpPost]
    public IActionResult Create(string title)
    {
        // DB 연결, 비즈니스 로직, 예외 처리가 모두 섞여 있음
        var sql = $&quot;INSERT INTO Todos (Title) VALUES ('{title}')&quot;; // SQL Injection 위험!
        _connection.Execute(sql);
        return Ok();
    }
}&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Clean Architecture 방식:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;// Controller: 단순히 요청 전달만
// Handler: 비즈니스 로직만
// Repository: DB 접근만
// 각각의 책임이 명확하고 테스트하기 쉬움&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 실무 적용 팁&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(1) 작은 프로젝트에서는?&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Domain, Application을 합쳐도 됩니다 (Core 계층으로 통합)&lt;/li&gt;
&lt;li&gt;하지만 &lt;b&gt;의존성 방향 규칙&lt;/b&gt;은 반드시 지켜야 합니다&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(2) 성능 고려사항&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;// ❌ 나쁜 예: N+1 문제
public async Task&amp;lt;List&amp;lt;TodoItemDto&amp;gt;&amp;gt; GetAllTodos()
{
    var todos = await _repository.GetAllAsync();
    foreach (var todo in todos)
    {
        // 각 todo마다 별도 쿼리 실행 (느림!)
        todo.Category = await _categoryRepository.GetByIdAsync(todo.CategoryId);
    }
    return todos;
}

// ✅ 좋은 예: Include 사용
public async Task&amp;lt;List&amp;lt;TodoItemDto&amp;gt;&amp;gt; GetAllTodos()
{
    return await _context.TodoItems
        .Include(t =&amp;gt; t.Category)  // 한 번의 쿼리로 조회
        .Select(t =&amp;gt; new TodoItemDto
        {
            Id = t.Id,
            Title = t.Title,
            CategoryName = t.Category.Name
        })
        .ToListAsync();
}&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;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;(3) 예외 처리&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// Application/Common/Exceptions/NotFoundException.cs
public class NotFoundException : Exception
{
    public NotFoundException(string message) : base(message)
    {
    }
}

// WebApi에서 전역 예외 처리
public class GlobalExceptionHandler : IExceptionHandler
{
    public async ValueTask&amp;lt;bool&amp;gt; TryHandleAsync(
        HttpContext context,
        Exception exception,
        CancellationToken cancellationToken)
    {
        var response = exception switch
        {
            NotFoundException =&amp;gt; (StatusCodes.Status404NotFound, &quot;리소스를 찾을 수 없습니다&quot;),
            ValidationException =&amp;gt; (StatusCodes.Status400BadRequest, &quot;유효하지 않은 요청입니다&quot;),
            _ =&amp;gt; (StatusCodes.Status500InternalServerError, &quot;서버 오류가 발생했습니다&quot;)
        };

        context.Response.StatusCode = response.Item1;
        await context.Response.WriteAsJsonAsync(new { error = response.Item2 });
        return true;
    }
}&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;/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;h4 data-ke-size=&quot;size20&quot;&gt;출처&lt;/h4&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://github.com/martinothamar/Mediator/tree/main/samples/apps/ASPNET_Core_CleanArchitecture&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/martinothamar/Mediator/tree/main/samples/apps/ASPNET_Core_CleanArchitecture&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1759422708632&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Mediator/samples/apps/ASPNET_Core_CleanArchitecture at main &amp;middot; martinothamar/Mediator&quot; data-og-description=&quot;A high performance implementation of Mediator pattern in .NET using source generators. - martinothamar/Mediator&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/martinothamar/Mediator/tree/main/samples/apps/ASPNET_Core_CleanArchitecture&quot; data-og-url=&quot;https://github.com/martinothamar/Mediator/tree/main/samples/apps/ASPNET_Core_CleanArchitecture&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Zbi1J/hyZKjE2g3b/TxGeqXatg5pONWP1G2GZR0/img.png?width=1200&amp;amp;height=600&amp;amp;face=969_148_1049_236,https://scrap.kakaocdn.net/dn/cjRaLI/hyZKu7Ae9S/tclgiZENhDMKu859JPFISK/img.png?width=1200&amp;amp;height=600&amp;amp;face=969_148_1049_236&quot;&gt;&lt;a href=&quot;https://github.com/martinothamar/Mediator/tree/main/samples/apps/ASPNET_Core_CleanArchitecture&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/martinothamar/Mediator/tree/main/samples/apps/ASPNET_Core_CleanArchitecture&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Zbi1J/hyZKjE2g3b/TxGeqXatg5pONWP1G2GZR0/img.png?width=1200&amp;amp;height=600&amp;amp;face=969_148_1049_236,https://scrap.kakaocdn.net/dn/cjRaLI/hyZKu7Ae9S/tclgiZENhDMKu859JPFISK/img.png?width=1200&amp;amp;height=600&amp;amp;face=969_148_1049_236');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Mediator/samples/apps/ASPNET_Core_CleanArchitecture at main &amp;middot; martinothamar/Mediator&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A high performance implementation of Mediator pattern in .NET using source generators. - martinothamar/Mediator&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;&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;</description>
      <category>닷넷관련/ASP.NET CORE  </category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3467</guid>
      <comments>https://aspdotnet.tistory.com/3467#entry3467comment</comments>
      <pubDate>Fri, 3 Oct 2025 01:31:55 +0900</pubDate>
    </item>
    <item>
      <title>pdf.js : 5.4.54 버전 iframe file 의 URL 경로로 호출하기</title>
      <link>https://aspdotnet.tistory.com/3464</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 분석 및 원인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PDF.js는 버전이 업데이트되면서 보안 및 기능 개선이 이루어졌습니다. 특히 URL 파라미터 처리 방식에 변화가 있었습니다. 2.3.200 버전에서는 file 파라미터 뒤에 인코딩된 URL이 디코딩되어 백엔드 호출이 정상적으로 이루어졌지만, 5.4.149 버전에서는 보안 강화로 인해 URL에 특정 문자가 포함된 경우 요청이 차단되거나 정상적으로 파싱되지 않는 문제가 발생할 수 있습니다. 이는 URL의 유효성 검사 로직이 강화되었기 때문입니다.&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PDF.js 최신 버전에서 URL을 안전하게 전달하려면, URL 자체를 &lt;b&gt;URL-encoded string&lt;/b&gt;으로 만들고, 이를 다시 file 파라미터의 값으로 전달해야 합니다. 이는 URL 내의 특수문자(?, &amp;amp;, / 등)가 PDF.js 내부 파서에 의해 오인되지 않도록 하기 위함입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 최신 PDF.js 버전에서 URL을 전달하는 코드 예시입니다.&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;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// jQuery를 사용한 코드
// HTML에 &amp;lt;script src=&quot;https://code.jquery.com/jquery-3.6.0.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt; 가 포함되어 있어야 합니다.

// 문서 준비 완료 후 실행
$(document).ready(function() {

    // 백엔드 API URL !! 필히 도메인주소 넣으세요.~
    var backendUrl = 'https://your-backend-api.com/get-pdf-file';

    // 백엔드 API에 전달할 파라미터 객체
    var params = {
        id: 'document-12345',
        type: 'invoice'
    };

    // jQuery.param()을 사용하여 URL 파라미터 문자열 생성
    var queryString = $.param(params);

    // 완전한 URL 구성
    var fullBackendUrl = backendUrl + '?' + queryString;

    // URL 인코딩
    var encodedUrl = encodeURIComponent(fullBackendUrl);

    // PDF.js 뷰어 URL 구성
    var pdfViewerUrl = 'https://your-cdn-path.com/pdfjs-5.4.149/web/viewer.html?file=' + encodedUrl;

    // jQuery 셀렉터를 사용하여 iframe에 src 속성 설정
    $('#pdfViewer').attr('src', pdfViewerUrl);
});&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1759136186585&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;iframe id=&quot;ifrPDFViewer&quot; frameborder=&quot;0&quot; style=&quot;width:100%;height:auto; min-height:740px;&quot;&amp;gt;&amp;lt;/iframe&amp;gt;&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;/p&gt;
&lt;pre id=&quot;code_1759136122550&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// HTML에서 데이터 속성을 통해 값을 가져오는 것이 더 좋은 방법입니다.
// 이 예시에서는 기존 코드와 동일하게 변수를 사용합니다.
const backendUrl = '@domain/FileDownload/GetFileStream';
const pdfFilePath = '@pdfFilePath';

// URL 파라미터 구성
const params = {
    url: pdfFilePath,
};
const queryString = $.param(params);

// PDF Viewer URL 구성 및 iframe src 설정
const fullBackendUrl = `${backendUrl}?${queryString}`;
const encodedUrl = encodeURIComponent(fullBackendUrl);
const pdfViewerUrl = `/scripts/plugins/pdfJS-5.4.149/web/viewer.html?file=${encodedUrl}`;
$('#ifrPDFViewerNew').attr('src', pdfViewerUrl);

// iframe 로드 완료 시 이벤트 처리
$('#ifrPDFViewerNew').on('load', function () {
    try {
        const iframeContents = $(this).contents();
        
        // 숨기고자 하는 버튼 ID 목록을 배열로 관리
        const hiddenButtons = [
            '#printButton',
            '#downloadButton',
            '#editorStampButton',
            '#secondaryToolbarToggleButton'
        ];
        
        // jQuery의 .each() 메서드 또는 JavaScript의 forEach를 사용해 반복 처리
        $(hiddenButtons.join(', ')).each(function() {
             iframeContents.find($(this).selector).hide();
        });
        
    } catch (e) {
        // Cross-origin 이슈를 명확히 설명
        console.error(&quot;iframe 콘텐츠 접근 오류: 다른 도메인에서 로드된 콘텐츠일 수 있습니다.&quot;, e);
    }
});&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;file 파라미터에 도메인 주소 URL 꼭 기재하는것을 잊지 마세요.  &lt;/p&gt;
&lt;pre id=&quot;code_1759136359866&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;https://sample.univ.me/scripts/plugins/pdfJS-5.4.54/web/viewer.html?file=https%3A%2F%2Fsample.univ.me%2FFileDownload%2FGetFileStream%3Furl%3D%252FFileData%252F1%252FBoard%252F202509%252F11832%252FAttach%252Ff310bd9e-9b01-45b5-9e14-d103f88a0eff.pdf&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;file 파라미터에 실제 pdf 링크 주소나 backend url 주소로도 가능합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1759136610757&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public FileResult GetFileStream(string url)
{
    //url = &quot;/FileData/1/Board/202503/5347/Attach/compressed.tracemonkey-pldi-09.pdf&quot;;
    url = Server.UrlDecode(url);
    
    var fileStream = FileHelper.GetFileToStream(url);
    var fileExtension = Path.GetExtension(url);
    var mimeType = GetMimeType(fileExtension);
    return File(fileStream, mimeType);
}&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;/p&gt;</description>
      <category>프로그래밍/자바스크립트</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3464</guid>
      <comments>https://aspdotnet.tistory.com/3464#entry3464comment</comments>
      <pubDate>Mon, 29 Sep 2025 18:05:22 +0900</pubDate>
    </item>
    <item>
      <title>AutoMapper &amp;middot; MediatR &amp;mdash; 상용화(라이선스 변경) 이슈 정리</title>
      <link>https://aspdotnet.tistory.com/3463</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2025년부로 AutoMapper와 MediatR은 일부 최신 버전에서 듀얼 라이선스(RPL + Commercial)를 채택했습니다. 과거 MIT 시절의 버전(v14 이하 / v12.5 이하)은 상용(Production)에서도 자유롭게 사용 가능하지만, 최신 버전 사용 시에는 조직&amp;middot;운영 조건에 따라 라이선스 키 또는 유료 구매가 필요합니다.&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;1. 배경 &amp;mdash; 왜 확인해야 하나&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로젝트에 직접적인 비용&amp;middot;법적 책임이 생길 수 있습니다.&lt;/li&gt;
&lt;li&gt;최신 버전으로 업그레이드하면 기능&amp;middot;보안은 좋아지지만 라이선스 의무가 바뀔 수 있습니다.&lt;/li&gt;
&lt;li&gt;실무 의사결정(버전 고정 vs 유료 전환 vs 대체) 시 근거 자료로 쓰기 쉽도록 정리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;2. 핵심 사실 (빠르게)&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AutoMapper: &lt;b&gt;v15부터&lt;/b&gt; 듀얼 라이선스/라이선스 키 요구.&lt;/li&gt;
&lt;li&gt;MediatR: &lt;b&gt;v13부터&lt;/b&gt; 듀얼 라이선스/라이선스 키 요구.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #006dd7; color: #ffffff;&quot;&gt;&lt;u&gt;과거 버전(예: AutoMapper v14 이하, MediatR v12.5 이하)은 &lt;b&gt;MIT&lt;/b&gt; &amp;mdash; 상용 환경 사용에 제약 없음.&lt;/u&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Community(무료) 조건은 조직 연간 매출(또는 예산) 기준 등으로 제한되며, 일반적으로 &lt;b&gt;$5,000,000 USD&lt;/b&gt; 전후의 기준이 적용됨(공식 조건 확인 필요).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3. 현장 진단 &amp;mdash; 5분 체크리스트&lt;/h1&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;프로젝트에서 사용 중인 패키지 버전 확인: &lt;b&gt;dotnet list package &lt;/b&gt;&lt;/li&gt;
&lt;li&gt;해당 패키지의 사용 범위: 개발/테스트만 사용인가, 아니면 production에서 실제 동작 중인가?&lt;/li&gt;
&lt;li&gt;회사 연간 매출(또는 제품 관련 예산) &amp;mdash; Community 조건 충족 여부 판단 근거.&lt;/li&gt;
&lt;li&gt;법무/컴플라이언스와 공유(내부 기록 남기기).&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;4. 선택지(실무적 장단점)&lt;/h1&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;버전 고정(pinning)&lt;/b&gt; &amp;mdash; (예: AutoMapper 14.x, MediatR 12.5)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점: 즉시 리스크 회피, 라이선스 비용 없음.&lt;/li&gt;
&lt;li&gt;단점: 장기적으로 보안&amp;middot;기능 면에서 기술부채 발생 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;업그레이드 + Community 등록(또는 유료 구매)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점: 최신 기능, 공식 지원.&lt;/li&gt;
&lt;li&gt;단점: 조건 불충족시 비용 발생, 라이선스 관리 필요.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;대체 라이브러리 도입(예: Mapster, Mapperly 등)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점: 오픈 라이선스(MIT 등), 유지비 절감 가능.&lt;/li&gt;
&lt;li&gt;단점: 마이그레이션 비용&amp;middot;리스크 존재.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;수동 매핑 또는 소스제너레이터 활용&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점: 의존성 최소화, 성능&amp;middot;통제성 확보.&lt;/li&gt;
&lt;li&gt;단점: 초기 개발&amp;middot;유지 비용 증가.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;5. 실무 실행 가이드&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5-1. 버전 확인 / 고정 (예시)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 버전 확인:&lt;b&gt;dotnet list package &lt;/b&gt;&lt;/li&gt;
&lt;li&gt;v14 / v12.5로 고정(예시):&amp;nbsp;&lt;br /&gt;dotnet add package AutoMapper --version 14.* &lt;br /&gt;dotnet add package MediatR --version 12.5.*&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5-2. 최신 버전 사용 시 라이선스 키 등록(예시)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Program.cs(ASP.NET Core) 예시 &amp;mdash; AutoMapper 라이선스 키 환경변수에서 읽기:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAutoMapper(cfg =&amp;gt;
{
    cfg.LicenseKey = Environment.GetEnvironmentVariable(&quot;AUTOMAPPER_LICENSE_KEY&quot;);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5-3. Community 자격 검토 프로세스(실무 문서화)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서: 재무팀으로부터 &amp;lsquo;연간 매출/예산 증빙 자료&amp;rsquo; 수집 &amp;rarr; 법무에 Community 자격 여부 검토 요청 &amp;rarr; 결과 기록(프로젝트 리포지토리의 LICENSE_DECISION.md).&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;6. 권장 실행 플랜 (짧고 실무적)&lt;/h1&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;즉시(0~2일)&lt;/b&gt;: 패키지 버전&amp;middot;프로덕션 사용 여부&amp;middot;매출자료 수집(진단).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;의사결정(3일 이내)&lt;/b&gt;: 법무&amp;middot;재무와 함께 &amp;lsquo;Community 조건 충족 여부&amp;rsquo; 결정.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실행(1~2주)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Community 충족 &amp;rarr; 최신 버전 업그레이드 후 라이선스 키 등록 및 테스트.&lt;/li&gt;
&lt;li&gt;미충족 &amp;rarr; (a) v14/v12.5로 고정 후 중장기 마이그레이션 계획 수립 또는 (b) 유료 라이선스 구매.&lt;/li&gt;
&lt;li&gt;대체 검토 필요 &amp;rarr; Mapster/Mapperly POC(1주) 진행.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모니터링(계속)&lt;/b&gt;: 보안 공지&amp;middot;라이선스 업데이트 모니터링, 연 1회 재검토.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;7. 실무 팁(짧게)&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;로그 경고&lt;/b&gt;: 라이선스 키 미입력 시 런타임 경고(개발/테스트는 허용) &amp;mdash; production 배포 전 반드시 확인하세요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;법적 근거 문서화&lt;/b&gt;: 결정 사유(예: &amp;lsquo;v12.5 고정: 비용&amp;middot;호환성 문제로 2025-09 기준 고정&amp;rsquo;)를 리포지토리에 남기세요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;대체 라이브러리 POC&lt;/b&gt;: 핵심 매핑 5~10개 케이스로 성능&amp;middot;테스트 커버 검증 후 결정하세요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보안 패치 우선순위&lt;/b&gt;: 버전 고정 시에도 보안 이슈 발생하면 즉시 패치 계획을 수립해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;8. 결론(권장)&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단기: &lt;b&gt;리스크 최소화&lt;/b&gt;(v14 / v12.5 고정) &amp;rarr; 빠른 의사결정&amp;middot;문서화.&lt;/li&gt;
&lt;li&gt;중장기: &lt;b&gt;정책 선택&lt;/b&gt;(Community 등록 또는 유료 구매 또는 마이그레이션).&lt;/li&gt;
&lt;/ul&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;49&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eHtEUd/btsQPKF5J6X/q42evLM3okNGWeuJjpJ1S0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eHtEUd/btsQPKF5J6X/q42evLM3okNGWeuJjpJ1S0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eHtEUd/btsQPKF5J6X/q42evLM3okNGWeuJjpJ1S0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeHtEUd%2FbtsQPKF5J6X%2Fq42evLM3okNGWeuJjpJ1S0%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;480&quot; height=&quot;49&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;49&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;a href=&quot;https://github.com/LuckyPennySoftware/AutoMapper?utm_source=chatgpt.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/LuckyPennySoftware/AutoMapper?utm_source=chatgpt.com&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1758862636298&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - LuckyPennySoftware/AutoMapper: A convention-based object-object mapper in .NET.&quot; data-og-description=&quot;A convention-based object-object mapper in .NET. . Contribute to LuckyPennySoftware/AutoMapper development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/LuckyPennySoftware/AutoMapper?utm_source=chatgpt.com&quot; data-og-url=&quot;https://github.com/LuckyPennySoftware/AutoMapper&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/d1zOTe/hyZKbTQ3vw/OxUFDEkkljBeWoI9YEYOW1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/KJ2UZ/hyZJRV74uT/vi41Uslkz9IJCQtiGZ8KC1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/LuckyPennySoftware/AutoMapper?utm_source=chatgpt.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/LuckyPennySoftware/AutoMapper?utm_source=chatgpt.com&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/d1zOTe/hyZKbTQ3vw/OxUFDEkkljBeWoI9YEYOW1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/KJ2UZ/hyZJRV74uT/vi41Uslkz9IJCQtiGZ8KC1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - LuckyPennySoftware/AutoMapper: A convention-based object-object mapper in .NET.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A convention-based object-object mapper in .NET. . Contribute to LuckyPennySoftware/AutoMapper development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;AutoMapper and MediatR Commercial Editions Launch Today (Jul 2, 2025 &amp;bull; 3 min read)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.jimmybogard.com/automapper-and-mediatr-commercial-editions-launch-today/?utm_source=chatgpt.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.jimmybogard.com/automapper-and-mediatr-commercial-editions-launch-today/?utm_source=chatgpt.com&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1758862655667&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;AutoMapper and MediatR Commercial Editions Launch Today&quot; data-og-description=&quot;Today I'm excited to announce the official launch and release of the commercial editions of AutoMapper and MediatR. Both of these libraries have moved under their new corporate owner (me), Lucky Penny Software. I formed this company to house these projects&quot; data-og-host=&quot;www.jimmybogard.com&quot; data-og-source-url=&quot;https://www.jimmybogard.com/automapper-and-mediatr-commercial-editions-launch-today/?utm_source=chatgpt.com&quot; data-og-url=&quot;https://www.jimmybogard.com/automapper-and-mediatr-commercial-editions-launch-today/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/gvEwF/hyZJDj6skX/zkqUlKx9kzr5EdeJwl1i0k/img.png?width=1200&amp;amp;height=1200&amp;amp;face=0_0_1200_1200,https://scrap.kakaocdn.net/dn/9IOvg/hyZJXaWyGK/38kSYWKDFJNzNGsdOy7ol0/img.jpg?width=1015&amp;amp;height=968&amp;amp;face=0_0_1015_968,https://scrap.kakaocdn.net/dn/xu9IE/hyZKc6ic1k/ibWISVBjHWozbRsRKchrkk/img.png?width=250&amp;amp;height=250&amp;amp;face=60_77_154_180&quot;&gt;&lt;a href=&quot;https://www.jimmybogard.com/automapper-and-mediatr-commercial-editions-launch-today/?utm_source=chatgpt.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.jimmybogard.com/automapper-and-mediatr-commercial-editions-launch-today/?utm_source=chatgpt.com&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/gvEwF/hyZJDj6skX/zkqUlKx9kzr5EdeJwl1i0k/img.png?width=1200&amp;amp;height=1200&amp;amp;face=0_0_1200_1200,https://scrap.kakaocdn.net/dn/9IOvg/hyZJXaWyGK/38kSYWKDFJNzNGsdOy7ol0/img.jpg?width=1015&amp;amp;height=968&amp;amp;face=0_0_1015_968,https://scrap.kakaocdn.net/dn/xu9IE/hyZKc6ic1k/ibWISVBjHWozbRsRKchrkk/img.png?width=250&amp;amp;height=250&amp;amp;face=60_77_154_180');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;AutoMapper and MediatR Commercial Editions Launch Today&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Today I'm excited to announce the official launch and release of the commercial editions of AutoMapper and MediatR. Both of these libraries have moved under their new corporate owner (me), Lucky Penny Software. I formed this company to house these projects&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.jimmybogard.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;&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;</description>
      <category>닷넷관련/ASP.NET CORE  </category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3463</guid>
      <comments>https://aspdotnet.tistory.com/3463#entry3463comment</comments>
      <pubDate>Fri, 26 Sep 2025 13:56:33 +0900</pubDate>
    </item>
    <item>
      <title>웹 기반 테트리스 게임 JavaScript, HTML, CSS 조합</title>
      <link>https://aspdotnet.tistory.com/3462</link>
      <description>&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;626&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfEIKm/btsQMcC5a0l/Hy5ph0sye6Ba6vZFocVRP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfEIKm/btsQMcC5a0l/Hy5ph0sye6Ba6vZFocVRP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfEIKm/btsQMcC5a0l/Hy5ph0sye6Ba6vZFocVRP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfEIKm%2FbtsQMcC5a0l%2FHy5ph0sye6Ba6vZFocVRP1%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;626&quot; height=&quot;708&quot; data-origin-width=&quot;626&quot; data-origin-height=&quot;708&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;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1758735094275&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;웹 기반 테트리스 게임 제품 요구사항 문서 (PRD)
  1. 개요 (Introduction)
  2. 목표 (Goals)
  3. 타겟 사용자 (Target Audience)
  4.1. 게임 보드 (Game Board)
  *   게임은 10x20 크기의 그리드 형태의 보드에서 진행됩니다.
  *   보드는 항상 화면에 명확하게 표시되어야 합니다.
  4.2. 테트로미노 (Tetriminos - 블록)
  *   게임에는 7가지 종류의 테트로미노(I, O, T, L, J, S, Z)가 사용됩니다.
  *   각 테트로미노는 고유한 모양과 색상을 가집니다.
  *   블록은 보드 상단 중앙에서 무작위로 생성되어 아래로 떨어집니다.
  4.3. 블록 조작 (Player Controls)
     좌/우 이동:* 사용자는 키보드 방향키(&amp;larr;, &amp;rarr;)를 사용하여 떨어지는 블록을 좌우로 한 칸씩 이동시킬 수
  있습니다.
     회전 (Rotation):* 사용자는 키보드 위쪽 방향키(&amp;uarr;)를 사용하여 블록을 시계 방향으로 90도 회전시킬 수
  있습니다.
     소프트 드롭 (Soft Drop):* 사용자는 키보드 아래쪽 방향키(&amp;darr;)를 눌러 블록을 현재 속도보다 빠르게 아래로
  내릴 수 있습니다.
     하드 드롭 (Hard Drop):* 사용자는 스페이스바(Spacebar)를 눌러 블록을 즉시 바닥 또는 다른 블록 위로
  떨어뜨릴 수 있습니다.
     일시정지 (Pause):* 사용자는 'P' 키를 눌러 게임을 일시정지하거나 재개할 수 있습니다.
  4.4. 줄 완성 및 점수 (Line Clearing &amp;amp; Scoring)
  *   가로 한 줄이 빈칸 없이 블록으로 모두 채워지면 해당 줄은 사라집니다.
  *   점수는 한 번에 없앤 줄의 수에 따라 차등 지급됩니다. (예: 1줄: 100점, 4줄: 800점)
  4.5. 레벨 시스템 (Level System)
  *   10줄을 없앨 때마다 레벨이 1씩 증가하며, 레벨이 오르면 블록이 떨어지는 속도가 점차 빨라집니다.
  4.6. 게임 오버 (Game Over)
  *   블록이 보드 맨 위까지 쌓여 더 이상 새로운 블록이 나타날 공간이 없으면 게임이 종료됩니다.
  *   게임 오버 시, &quot;Game Over&quot; 메시지와 최종 점수를 표시하고, 게임을 다시 시작할 수 있는 옵션을 제공합니다.
  5. UI/UX 요구사항 (User Interface &amp;amp; Experience)
  6. 비기능적 요구사항 (Non-Functional Requirements)
  7. 기술 스택 (Technology Stack)
  8. 향후 개선 사항 (Future Enhancements - Out of Scope for V1)&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;/p&gt;
&lt;pre id=&quot;code_1758735228194&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;구현된 주요 기능들
1. 게임 보드 (10x20 그리드)

CSS Grid를 사용하여 정확한 10x20 크기의 게임 보드 구현
각 셀은 30x30px 크기로 명확하게 표시

2. 7가지 테트로미노 (I, O, T, L, J, S, Z)

각 블록마다 고유한 색상과 모양 정의
보드 상단 중앙에서 무작위 생성

3. 블록 조작 시스템

좌/우 이동: 방향키 &amp;larr; &amp;rarr;
회전: 위쪽 방향키 &amp;uarr; (시계방향 90도)
소프트 드롭: 아래쪽 방향키 &amp;darr;
하드 드롭: 스페이스바 (즉시 바닥으로)
일시정지: P키

4. 점수 및 레벨 시스템

1줄: 100점, 2줄: 300점, 3줄: 500점, 4줄: 800점
10줄 완성시마다 레벨 증가
레벨 상승 시 블록 낙하 속도 증가

5. 게임 오버 처리

블록이 보드 최상단에 도달하면 게임 종료
최종 점수, 레벨, 완성 줄 수 표시
재시작 버튼 제공

6. UI/UX 요소

현재 점수/레벨/줄 수 표시
다음 블록 미리보기
조작법 안내
일시정지 오버레이
반응형 디자인 (모바일 지원)

7. 추가 구현된 기능들

시각적 효과: 그라데이션 배경, 글래스모피즘 효과
충돌 감지 시스템: 정확한 블록 충돌 및 경계 검사
애니메이션: 부드러운 hover 효과
접근성: 키보드 전용 조작 가능

게임 조작법

&amp;larr;/&amp;rarr;: 블록 좌우 이동
&amp;uarr;: 블록 회전
&amp;darr;: 빠른 하강 (소프트 드롭)
Space: 즉시 하강 (하드 드롭)
P: 일시정지/재개&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/cH9B5i/dJMb8Y4hald/iHiVMLggkeC5X10WtuYF50/tetris_game.html?attach=1&amp;amp;knm=tfile.html&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;tetris_game.html&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.02MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://claude.ai/public/artifacts/16f77c57-4fe5-4b98-acfd-1492e4f28eee&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://claude.ai/public/artifacts/16f77c57-4fe5-4b98-acfd-1492e4f28eee&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;
&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>프로그래밍/HTML</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3462</guid>
      <comments>https://aspdotnet.tistory.com/3462#entry3462comment</comments>
      <pubDate>Thu, 25 Sep 2025 02:32:40 +0900</pubDate>
    </item>
    <item>
      <title>초등학교와 어린이집 스승의 날 선물</title>
      <link>https://aspdotnet.tistory.com/3461</link>
      <description>&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;스승의 날(5월 15일)과 관련된 선물 가능 여부와 김영란법(청탁금지법) 적용 기준을 중심으로, 어린이집&amp;middot;유치원&amp;middot;초중고 상황별 주요 내용을 요약하면 다음과 같습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어린이집&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반 보육교사에게는 선물이 허용됨(김영란법 적용 대상 아님).&lt;/li&gt;
&lt;li&gt;어린이집 원장은 공무를 수행하는 사인으로 간주되어 김영란법 적용 대상. 국공립 위탁, 대규모 직장어린이집의 원장도 포함됨.&lt;/li&gt;
&lt;li&gt;드물게 공무원 신분 보육교사는 선물 불가.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;유치원&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원장과 교사 모두 선물 불가(국공립&amp;middot;사립 모두 동일).&lt;/li&gt;
&lt;li&gt;학생 개별 카네이션 달아주기도 금지.&lt;/li&gt;
&lt;li&gt;직접 쓴 손편지 정도는 허용(과도하지 않을 경우).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;초&amp;middot;중&amp;middot;고등학교&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;교사에게 선물, 커피 등 음료 제공 모두 원칙적으로 금지(상시 평가&amp;middot;지도 관계 때문).&lt;/li&gt;
&lt;li&gt;학생 대표가 공개적으로 제공하는 카네이션 등 꽃은 허용 가능(사회상규, 과하지 않은 경우).&lt;/li&gt;
&lt;li&gt;학생이 직접 쓴 손편지&amp;middot;카드는 허용(과도하지 않으면).&lt;/li&gt;
&lt;li&gt;진급 및 졸업 후에는 금품&amp;middot;선물 가능(진급 시 5만원/졸업 시 100만원까지).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기타&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;학부모회&amp;middot;운영위가 교장&amp;middot;교감 등에게 선물하는 것 역시 허용되지 않음(직무관련성 인정).&lt;/li&gt;
&lt;li&gt;어린이집/유치원 및 학교마다 공식 공지 등이 있더라도 법률적 기준이 우선 적용됨.&lt;/li&gt;
&lt;/ul&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;middot;초중고는 교사 및 원장 모두 선물이 제한되며, 예외적으로 학생 대표의 카네이션과 손편지 정도만 허용된다는 점이 핵심입니다&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;/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;br /&gt;[1] 누구 선물은 되고 누군 안돼...스승의날 &amp;lsquo;김영란법&amp;rsquo; 혼란스럽네요 [초보엄마 잡학사전] - 매일경제 &lt;a href=&quot;https://m.mk.co.kr/news/society/10732474&quot;&gt;https://m.mk.co.kr/news/society/10732474&lt;/a&gt;&lt;/p&gt;</description>
      <category>생활정보  </category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3461</guid>
      <comments>https://aspdotnet.tistory.com/3461#entry3461comment</comments>
      <pubDate>Tue, 23 Sep 2025 09:25:00 +0900</pubDate>
    </item>
    <item>
      <title>ASP.NET Core 8 : AWS SSO 를 활용한 S3 연동 구현하기</title>
      <link>https://aspdotnet.tistory.com/3444</link>
      <description>&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;h1&gt;ASP.NET Core 8에서 AWS SSO를 활용한 S3 연동 구현하기&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS SSO(Single Sign-On)를 사용하는 환경에서 ASP.NET Core 8 애플리케이션을 개발할 때, S3와 연동하는 과정에서 자주 마주치는 문제들이 있습니다. 특히 자격 증명 처리와 토큰 만료 상황에 대한 대응이 까다로운데요. 이번 포스트에서는 실무에서 바로 적용할 수 있는 견고한 S3 클라이언트 구현 방법을 소개하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 상황 분석&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 환경에서 AWS CLI를 통해 SSO 로그인을 완료했다고 가정해봅시다:&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;aws sso login --profile UNIVESLUCKY&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 ASP.NET Core 애플리케이션에서 S3에 연결할 때 다음과 같은 문제들을 겪게 됩니다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;ProfilesLocation 경로 문제&lt;/b&gt;: 명시적인 자격 증명 파일 경로 지정의 어려움&lt;/li&gt;
&lt;li&gt;&lt;b&gt;토큰 만료&lt;/b&gt;: SSO 토큰의 주기적 만료로 인한 런타임 오류&lt;/li&gt;
&lt;li&gt;&lt;b&gt;의존성 문제&lt;/b&gt;: AWS SSO 관련 NuGet 패키지 누락으로 인한 런타임 예외&lt;/li&gt;
&lt;/ol&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;h2 data-ke-size=&quot;size26&quot;&gt;필수 NuGet 패키지 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS SSO를 사용하려면 다음 패키지들이 &lt;b&gt;반드시&lt;/b&gt; 필요합니다:&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;&amp;lt;ItemGroup&amp;gt;
  &amp;lt;PackageReference Include=&quot;AWSSDK.S3&quot; Version=&quot;4.0.6.4&quot; /&amp;gt;
  &amp;lt;PackageReference Include=&quot;AWSSDK.SSO&quot; Version=&quot;4.0.0.12&quot; /&amp;gt;
  &amp;lt;PackageReference Include=&quot;AWSSDK.SSOOIDC&quot; Version=&quot;4.0.0.11&quot; /&amp;gt;
&amp;lt;/ItemGroup&amp;gt;
&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 패키지들이 누락되면 다음과 같은 런타임 오류가 발생합니다:&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;System.InvalidOperationException: Assembly AWSSDK.SSOOIDC could not be found or loaded.
&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;완성된 S3StorageClient 구현&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인터페이스 정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 S3 작업을 위한 인터페이스를 정의합니다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;public interface IS3StorageClient
{
    Task&amp;lt;string&amp;gt; UploadFileAsync(string bucketName, string key, string filePath);
    Task&amp;lt;Stream&amp;gt; DownloadFileAsync(string bucketName, string key);
    Task&amp;lt;bool&amp;gt; DeleteFileAsync(string bucketName, string key);
    Task&amp;lt;bool&amp;gt; FileExistsAsync(string bucketName, string key);
    string GetFileUrl(string bucketName, string key);
}&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;/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;/h3&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;public class S3StorageClient : IS3StorageClient, IDisposable
{
    private readonly IAmazonS3 _s3Client;
    private readonly ILogger&amp;lt;S3StorageClient&amp;gt; _logger;
    private readonly string _profileName;
    private bool _disposed = false;

    public S3StorageClient(IConfiguration configuration, ILogger&amp;lt;S3StorageClient&amp;gt; logger)
    {
        _logger = logger;
        _profileName = configuration[&quot;AWS:ProfileName&quot;] ?? &quot;UNIVESLUCKY&quot;;
        
        var region = Amazon.RegionEndpoint.APNortheast2;
        var s3Config = new AmazonS3Config
        {
            RegionEndpoint = region,
            MaxErrorRetry = 3,
            RetryMode = Amazon.Runtime.RequestRetryMode.Standard,
            UseHttp = false,
            DisablePayloadSigning = false
        };

        _s3Client = CreateS3Client(s3Config);
    }
}&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AWS SSO 자격 증명 처리의 핵심&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 중요한 부분인 자격 증명 생성 로직입니다:&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;private IAmazonS3 CreateS3Client(AmazonS3Config s3Config)
{
    _logger.LogInformation(&quot;AWS S3 클라이언트 초기화 시작. 프로필: {ProfileName}&quot;, _profileName);

    try
    {
        // 1단계: AWS SSO 프로필 사용 시도
        if (TryCreateClientWithSSOProfile(s3Config, out var ssoClient))
        {
            _logger.LogInformation(&quot;AWS SSO 프로필을 사용하여 S3 클라이언트 초기화 성공&quot;);
            return ssoClient;
        }

        // 2단계: 기본 자격 증명 체인 사용
        _logger.LogInformation(&quot;기본 AWS 자격 증명 체인을 사용하여 S3 클라이언트 초기화&quot;);
        var defaultClient = new AmazonS3Client(s3Config);
        
        // 자격 증명 유효성 검증
        ValidateCredentialsAsync(defaultClient).GetAwaiter().GetResult();
        
        return defaultClient;
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, &quot;AWS S3 클라이언트 초기화 실패&quot;);
        throw new InvalidOperationException(&quot;AWS S3 클라이언트를 초기화할 수 없습니다. AWS 자격 증명을 확인해주세요.&quot;, ex);
    }
}&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;/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;SSO 프로필 처리의 핵심 포인트&lt;/h3&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;private bool TryCreateClientWithSSOProfile(AmazonS3Config s3Config, out IAmazonS3 client)
{
    client = null;
    
    try
    {
        // ProfilesLocation을 지정하지 않고 기본 경로 사용
        var credentialProfileStoreChain = new CredentialProfileStoreChain();
        
        if (credentialProfileStoreChain.TryGetProfile(_profileName, out var profile))
        {
            _logger.LogDebug(&quot;프로필 {ProfileName} 발견. 자격 증명 생성 시도&quot;, _profileName);
            
            // SSO 프로필 감지
            if (profile.Options.SsoSession != null || 
                !string.IsNullOrEmpty(profile.Options.SsoStartUrl))
            {
                _logger.LogDebug(&quot;SSO 프로필로 감지됨&quot;);
            }

            var credentials = profile.GetAWSCredentials(credentialProfileStoreChain);
            client = new AmazonS3Client(credentials, s3Config);
            
            // 즉시 유효성 검증
            ValidateCredentialsAsync(client).GetAwaiter().GetResult();
            
            return true;
        }
        else
        {
            _logger.LogWarning(&quot;프로필 {ProfileName}을 찾을 수 없습니다&quot;, _profileName);
        }
    }
    catch (AmazonServiceException ex) when (ex.ErrorCode == &quot;ExpiredToken&quot;)
    {
        _logger.LogWarning(&quot;AWS SSO 토큰이 만료되었습니다. 'aws sso login --profile {ProfileName}' 명령을 실행해주세요&quot;, _profileName);
    }
    catch (Exception ex)
    {
        _logger.LogWarning(ex, &quot;SSO 프로필 기반 자격 증명 초기화 실패&quot;);
    }

    return false;
}&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;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AWS SDK 4.0+ 버전 변경사항 대응&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWSSDK.S3 4.0+ 버전에서는 기존의 Timeout과 ReadWriteTimeout 속성이 제한적으로 작동합니다. 대신 개별 요청에서 CancellationToken을 사용하는 것이 권장됩니다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;public async Task&amp;lt;string&amp;gt; UploadFileWithTimeoutAsync(string bucketName, string key, 
    string filePath, TimeSpan timeout)
{
    using var cts = new CancellationTokenSource(timeout);
    
    try
    {
        var request = new PutObjectRequest
        {
            BucketName = bucketName,
            Key = key,
            FilePath = filePath,
            ContentType = GetContentType(filePath),
            ServerSideEncryptionMethod = ServerSideEncryptionMethod.AES256
        };

        var response = await _s3Client.PutObjectAsync(request, cts.Token);
        return GetFileUrl(bucketName, key);
    }
    catch (OperationCanceledException) when (cts.Token.IsCancellationRequested)
    {
        _logger.LogWarning(&quot;S3 업로드가 타임아웃되었습니다: {Timeout}초&quot;, timeout.TotalSeconds);
        throw new TimeoutException($&quot;S3 업로드가 {timeout.TotalSeconds}초 후 타임아웃되었습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;의존성 주입 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Program.cs에서 서비스를 등록합니다:&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;var builder = WebApplication.CreateBuilder(args);

// S3 클라이언트 등록
builder.Services.AddScoped&amp;lt;IS3StorageClient, S3StorageClient&amp;gt;();

var app = builder.Build();&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;appsettings.json 설정:&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;{
  &quot;AWS&quot;: {
    &quot;ProfileName&quot;: &quot;UNIVESLUCKY&quot;
  },
  &quot;Logging&quot;: {
    &quot;LogLevel&quot;: {
      &quot;Default&quot;: &quot;Information&quot;,
      &quot;S3StorageClient&quot;: &quot;Debug&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실제 사용 예제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러에서의 사용법:&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;[ApiController]
[Route(&quot;api/[controller]&quot;)]
public class FileController : ControllerBase
{
    private readonly IS3StorageClient _s3Client;
    private readonly ILogger&amp;lt;FileController&amp;gt; _logger;

    public FileController(IS3StorageClient s3Client, ILogger&amp;lt;FileController&amp;gt; logger)
    {
        _s3Client = s3Client;
        _logger = logger;
    }

    [HttpPost(&quot;upload&quot;)]
    public async Task&amp;lt;IActionResult&amp;gt; Upload(IFormFile file, [FromForm] string bucketName)
    {
        if (file == null || file.Length == 0)
            return BadRequest(&quot;파일이 선택되지 않았습니다.&quot;);

        try
        {
            // 임시 파일 생성
            var tempPath = Path.GetTempFileName();
            using (var stream = new FileStream(tempPath, FileMode.Create))
            {
                await file.CopyToAsync(stream);
            }

            // S3 업로드
            var key = $&quot;uploads/{DateTime.Now:yyyy/MM/dd}/{Guid.NewGuid()}-{file.FileName}&quot;;
            var fileUrl = await _s3Client.UploadFileAsync(bucketName, key, tempPath);

            // 임시 파일 삭제
            File.Delete(tempPath);

            return Ok(new { Url = fileUrl, Key = key });
        }
        catch (UnauthorizedAccessException ex)
        {
            _logger.LogError(ex, &quot;AWS 인증 오류&quot;);
            return Unauthorized(&quot;AWS SSO 토큰이 만료되었습니다. 재로그인이 필요합니다.&quot;);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, &quot;파일 업로드 실패&quot;);
            return StatusCode(500, &quot;파일 업로드 중 오류가 발생했습니다.&quot;);
        }
    }

    [HttpGet(&quot;download/{*key}&quot;)]
    public async Task&amp;lt;IActionResult&amp;gt; Download(string key, [FromQuery] string bucketName)
    {
        try
        {
            var stream = await _s3Client.DownloadFileAsync(bucketName, key);
            var fileName = Path.GetFileName(key);
            
            return File(stream, &quot;application/octet-stream&quot;, fileName);
        }
        catch (AmazonS3Exception ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
        {
            return NotFound(&quot;파일을 찾을 수 없습니다.&quot;);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, &quot;파일 다운로드 실패&quot;);
            return StatusCode(500, &quot;파일 다운로드 중 오류가 발생했습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주요 특징과 장점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. &lt;b&gt;자동 자격 증명 탐지&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ProfilesLocation 경로를 명시하지 않아도 AWS 기본 자격 증명 체인을 통해 자동 탐지&lt;/li&gt;
&lt;li&gt;SSO와 일반 자격 증명을 모두 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. &lt;b&gt;토큰 만료 상황 대응&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초기화 시점에 자격 증명 유효성 검증&lt;/li&gt;
&lt;li&gt;토큰 만료 시 명확한 에러 메시지와 해결 방법 제시&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. &lt;b&gt;견고한 오류 처리&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AWS 특화 예외 처리&lt;/li&gt;
&lt;li&gt;단계적 폴백 전략 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. &lt;b&gt;최신 AWS SDK 대응&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;4.0+ 버전의 변경사항 반영&lt;/li&gt;
&lt;li&gt;CancellationToken을 활용한 타임아웃 처리&lt;/li&gt;
&lt;/ul&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;h2 data-ke-size=&quot;size26&quot;&gt;주의사항 및 베스트 프랙티스&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. &lt;b&gt;배포 환경 고려사항&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로덕션 환경에서는 IAM Role 사용 권장&lt;/li&gt;
&lt;li&gt;SSO는 주로 개발/테스트 환경에서 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. &lt;b&gt;토큰 갱신 자동화&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 환경에서 토큰 만료를 방지하려면:&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# cron job이나 스케줄러에 등록
aws sso login --profile UNIVESLUCKY --no-browser&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. &lt;b&gt;로깅 전략&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;민감한 정보(토큰, 키 등)는 로깅하지 않기&lt;/li&gt;
&lt;li&gt;디버그 레벨에서만 상세 정보 출력&lt;/li&gt;
&lt;/ul&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;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 구현 방식은 실제 프로덕션 환경에서 검증된 패턴입니다. AWS SSO를 사용하는 개발 환경에서 발생할 수 있는 다양한 문제 상황에 대비한 견고한 구현을 통해, 안정적인 S3 연동을 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/sdk-for-net/&quot;&gt;AWS SDK for .NET Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html&quot;&gt;AWS SSO CLI Configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.nuget.org/packages/AWSSDK.S3/&quot;&gt;AWSSDK.S3 NuGet Package&lt;/a&gt;&lt;/li&gt;
&lt;/ul&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>닷넷관련/ASP.NET CORE  </category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3444</guid>
      <comments>https://aspdotnet.tistory.com/3444#entry3444comment</comments>
      <pubDate>Wed, 27 Aug 2025 14:49:41 +0900</pubDate>
    </item>
    <item>
      <title>ASP.NET Core MVC : [FromBody], [FromForm], [FromQuery] 사용해 보기</title>
      <link>https://aspdotnet.tistory.com/3443</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;ASP.NET Core MVC(Web API 포함)에서 [FromBody] 사용 여부에 따라 JavaScript(axios)에서 요청을 보내는 방식이 조금 달라집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;아래에서 &lt;b&gt;[FromBody]를 사용할 때&lt;/b&gt;와 &lt;b&gt;사용하지 않을 때&lt;/b&gt;를 나눠서 설명드리겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. [FromBody]를 사용하는 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[FromBody]는 **HTTP 요청의 Body(JSON, XML 등)**에서 데이터를 읽습니다.&lt;br /&gt;즉, &lt;b&gt;axios에서는 data 속성을 사용하여 JSON으로 전달&lt;/b&gt;해야 합니다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// C#
[HttpPost]
public async Task&amp;lt;IActionResult&amp;gt; UpdateUserDept([FromBody] UpdateUserDept.Command command)
{
    // command 객체는 JSON Body에서 바인딩됨
    ...
}
&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JavaScript (axios) 호출 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;axios.post('/User/UpdateUserDept', {
    userId: 123,
    deptId: 45
}, {
    headers: {
        'Content-Type': 'application/json'
    }
})
.then(res =&amp;gt; {
    console.log(res.data);
})
.catch(err =&amp;gt; {
    console.error(err);
});
&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;포인트&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;axios에서 &lt;b&gt;data 파라미터&lt;/b&gt;를 사용해야 함&lt;/li&gt;
&lt;li&gt;Content-Type: application/json 필수&lt;/li&gt;
&lt;li&gt;URL Query String은 필요 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. [FromBody]를 사용하지 않는 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 ASP.NET Core는 &lt;b&gt;[FromQuery]&lt;/b&gt; 또는 &lt;b&gt;[FromForm]&lt;/b&gt; 바인딩 규칙에 따라 처리합니다.&lt;br /&gt;즉, 데이터는 &lt;b&gt;QueryString&lt;/b&gt;이나 &lt;b&gt;Form-data&lt;/b&gt;로 전달해야 합니다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// C#
[HttpPost]
public async Task&amp;lt;IActionResult&amp;gt; UpdateUserDept(UpdateUserDept.Command command)
{
    // command는 QueryString, Form-data에서 바인딩됨
    ...
}
&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JavaScript (axios) - Form-data 방식&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const formData = new FormData();
formData.append('userId', 123);
formData.append('deptId', 45);

axios.post('/User/UpdateUserDept', formData)
    .then(res =&amp;gt; {
        console.log(res.data);
    })
    .catch(err =&amp;gt; {
        console.error(err);
    });
&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JavaScript (axios) - QueryString 방식&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;axios.post('/User/UpdateUserDept?userId=123&amp;amp;deptId=45')
    .then(res =&amp;gt; {
        console.log(res.data);
    })
    .catch(err =&amp;gt; {
        console.error(err);
    });&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;포인트&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[FromBody]가 없으면 JSON Body를 보내도 자동으로 바인딩되지 않음&lt;/li&gt;
&lt;li&gt;대신 QueryString이나 FormData를 사용해야 함&lt;/li&gt;
&lt;li&gt;파일 업로드나 단순 폼 전송이면 FormData가 적합&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 차이 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분 [FromBody] 있음 [FromBody] 없음&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;데이터 위치&lt;/td&gt;
&lt;td&gt;HTTP Body(JSON)&lt;/td&gt;
&lt;td&gt;QueryString / Form-data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;axios 전송 방식&lt;/td&gt;
&lt;td&gt;axios.post(url, jsonObject, { headers: { 'Content-Type': 'application/json' }})&lt;/td&gt;
&lt;td&gt;axios.post(url, formData) 또는 axios.post(url + '?key=value')&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content-Type&lt;/td&gt;
&lt;td&gt;application/json&lt;/td&gt;
&lt;td&gt;application/x-www-form-urlencoded 또는 multipart/form-data&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;개발 팁&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;API라면 [FromBody] + JSON 전송을 권장 (표준 REST 패턴)&lt;/li&gt;
&lt;li&gt;단순 페이지 폼 처리라면 [FromBody] 없이 FormData 전송이 편리&lt;/li&gt;
&lt;li&gt;파일 업로드 시에는 [FromForm]을 사용하면 FormData 처리와 궁합이 좋음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 [FromBody], [FromForm], [FromQuery]를 하나의 Controller 액션에서 비교하고,&lt;br /&gt;각각 axios로 호출하는 예제를 정리해 드리겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Controller 예제 (ASP.NET Core 8 MVC / API)&lt;/h2&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;using Microsoft.AspNetCore.Mvc;

namespace DemoApi.Controllers
{
    [ApiController]
    [Route(&quot;[controller]&quot;)]
    public class UserController : ControllerBase
    {
        public class UpdateUserDeptCommand
        {
            public int UserId { get; set; }
            public int DeptId { get; set; }
        }

        // 1. [FromBody] JSON
        [HttpPost(&quot;FromBody&quot;)]
        public IActionResult UpdateFromBody([FromBody] UpdateUserDeptCommand command)
        {
            return Ok(new { method = &quot;FromBody&quot;, data = command });
        }

        // 2. [FromForm] Form-data
        [HttpPost(&quot;FromForm&quot;)]
        public IActionResult UpdateFromForm([FromForm] UpdateUserDeptCommand command)
        {
            return Ok(new { method = &quot;FromForm&quot;, data = command });
        }

        // 3. [FromQuery] Query String
        [HttpGet(&quot;FromQuery&quot;)]
        public IActionResult UpdateFromQuery([FromQuery] UpdateUserDeptCommand command)
        {
            return Ok(new { method = &quot;FromQuery&quot;, data = command });
        }
    }
}&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. axios 호출 예제&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(1) [FromBody] - JSON Body 전송&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;axios.post('/User/FromBody', 
    {
        userId: 123,
        deptId: 45
    }, 
    {
        headers: { 'Content-Type': 'application/json' }
    }
).then(res =&amp;gt; {
    console.log(res.data);
});&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HTTP 요청 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;POST /User/FromBody
Content-Type: application/json

{
  &quot;userId&quot;: 123,
  &quot;deptId&quot;: 45
}&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(2) [FromForm] - Form-data 전송&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const formData = new FormData();
formData.append('userId', 123);
formData.append('deptId', 45);

axios.post('/User/FromForm', formData)
    .then(res =&amp;gt; {
        console.log(res.data);
    });
&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;&lt;b&gt;HTTP 요청 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;POST /User/FromForm
Content-Type: multipart/form-data

------WebKitFormBoundary...
Content-Disposition: form-data; name=&quot;userId&quot;

123
------WebKitFormBoundary...
Content-Disposition: form-data; name=&quot;deptId&quot;

45
&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(3) [FromQuery] - Query String 전송&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;axios.get('/User/FromQuery', {
    params: {
        userId: 123,
        deptId: 45
    }
})
.then(res =&amp;gt; {
    console.log(res.data);
});
&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HTTP 요청 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;GET /User/FromQuery?userId=123&amp;amp;deptId=45
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 차이점 요약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바인딩 속성 전송 방식 axios 데이터 설정 Content-Type&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;[FromBody]&lt;/td&gt;
&lt;td&gt;HTTP Body(JSON)&lt;/td&gt;
&lt;td&gt;axios.post(url, jsonObject, { headers: { 'Content-Type': 'application/json' }})&lt;/td&gt;
&lt;td&gt;application/json&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[FromForm]&lt;/td&gt;
&lt;td&gt;Form-data&lt;/td&gt;
&lt;td&gt;FormData() 객체로 생성 후 axios.post(url, formData)&lt;/td&gt;
&lt;td&gt;multipart/form-data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[FromQuery]&lt;/td&gt;
&lt;td&gt;URL Query&lt;/td&gt;
&lt;td&gt;axios.get(url, { params: { key: value }})&lt;/td&gt;
&lt;td&gt;- (기본 GET 요청)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  최신 트렌드로는 &lt;b&gt;REST API &amp;rarr; [FromBody] + JSON&lt;/b&gt;&lt;br /&gt;단, &lt;b&gt;파일 업로드&lt;/b&gt; &amp;rarr; [FromForm]&lt;br /&gt;&lt;b&gt;단순 필터/검색 파라미터&lt;/b&gt; &amp;rarr; [FromQuery] 사용이 일반적입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;h4 data-ke-size=&quot;size20&quot;&gt;Postman&amp;nbsp;테스트&amp;nbsp;스크린샷&amp;nbsp;+&amp;nbsp;axios&amp;nbsp;호출&amp;nbsp;실행&amp;nbsp;결과까지&amp;nbsp;한눈에&amp;nbsp;비교한&amp;nbsp;문서&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Controller 코드&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Postman 테스트 요청/응답&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;axios 호출 예시 및 브라우저 콘솔 결과&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;정리 표&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Controller 코드 (C# ASP.NET Core 8)&lt;/h2&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;using Microsoft.AspNetCore.Mvc;

namespace DemoApi.Controllers
{
    [ApiController]
    [Route(&quot;[controller]&quot;)]
    public class UserController : ControllerBase
    {
        public class UpdateUserDeptCommand
        {
            public int UserId { get; set; }
            public int DeptId { get; set; }
        }

        // 1. [FromBody] JSON
        [HttpPost(&quot;FromBody&quot;)]
        public IActionResult UpdateFromBody([FromBody] UpdateUserDeptCommand command)
        {
            return Ok(new { method = &quot;FromBody&quot;, data = command });
        }

        // 2. [FromForm] Form-data
        [HttpPost(&quot;FromForm&quot;)]
        public IActionResult UpdateFromForm([FromForm] UpdateUserDeptCommand command)
        {
            return Ok(new { method = &quot;FromForm&quot;, data = command });
        }

        // 3. [FromQuery] Query String
        [HttpGet(&quot;FromQuery&quot;)]
        public IActionResult UpdateFromQuery([FromQuery] UpdateUserDeptCommand command)
        {
            return Ok(new { method = &quot;FromQuery&quot;, data = command });
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Postman 테스트&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(1) [FromBody] 요청/응답&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;요청&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;POST /User/FromBody
Content-Type: application/json

{
    &quot;userId&quot;: 123,
    &quot;deptId&quot;: 45
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;응답&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
    &quot;method&quot;: &quot;FromBody&quot;,
    &quot;data&quot;: {
        &quot;userId&quot;: 123,
        &quot;deptId&quot;: 45
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(2) [FromForm] 요청/응답&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;요청&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;POST /User/FromForm
Content-Type: multipart/form-data

userId: 123
deptId: 45
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;응답&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
    &quot;method&quot;: &quot;FromForm&quot;,
    &quot;data&quot;: {
        &quot;userId&quot;: 123,
        &quot;deptId&quot;: 45
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(3) [FromQuery] 요청/응답&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;요청&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;GET /User/FromQuery?userId=123&amp;amp;deptId=45
&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;응답&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
    &quot;method&quot;: &quot;FromQuery&quot;,
    &quot;data&quot;: {
        &quot;userId&quot;: 123,
        &quot;deptId&quot;: 45
    }
}
&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. axios 호출 예시 + 콘솔 출력&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(1) [FromBody]&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;axios.post('/User/FromBody', 
    { userId: 123, deptId: 45 }, 
    { headers: { 'Content-Type': 'application/json' } }
).then(res =&amp;gt; console.log(res.data));
&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;콘솔 출력&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;{ method: &quot;FromBody&quot;, data: { userId: 123, deptId: 45 } }
&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(2) [FromForm]&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const formData = new FormData();
formData.append('userId', 123);
formData.append('deptId', 45);

axios.post('/User/FromForm', formData)
    .then(res =&amp;gt; console.log(res.data));
&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;&lt;b&gt;콘솔 출력&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;{ method: &quot;FromForm&quot;, data: { userId: 123, deptId: 45 } }
&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(3) [FromQuery]&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;axios.get('/User/FromQuery', {
    params: { userId: 123, deptId: 45 }
}).then(res =&amp;gt; console.log(res.data));
&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;&lt;b&gt;콘솔 출력&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;{ method: &quot;FromQuery&quot;, data: { userId: 123, deptId: 45 } }
&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 정리 표&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바인딩 방식 HTTP 메서드 axios 예시 Content-Type 데이터 위치&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;[FromBody]&lt;/td&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;axios.post(url, json, { headers: { 'Content-Type': 'application/json' }})&lt;/td&gt;
&lt;td&gt;application/json&lt;/td&gt;
&lt;td&gt;Request Body(JSON)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[FromForm]&lt;/td&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;axios.post(url, formData)&lt;/td&gt;
&lt;td&gt;multipart/form-data&lt;/td&gt;
&lt;td&gt;Request Body(Form-data)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[FromQuery]&lt;/td&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;axios.get(url, { params: {...} })&lt;/td&gt;
&lt;td&gt;기본 GET&lt;/td&gt;
&lt;td&gt;URL Query String&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>닷넷관련/ASP.NET CORE  </category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3443</guid>
      <comments>https://aspdotnet.tistory.com/3443#entry3443comment</comments>
      <pubDate>Tue, 26 Aug 2025 16:26:28 +0900</pubDate>
    </item>
    <item>
      <title>ASP.NET CORE : 웹 개발을 위해 꼭 알아야하는 보안 대응 (DOMPurify)</title>
      <link>https://aspdotnet.tistory.com/3095</link>
      <description>&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;1080&quot; data-origin-height=&quot;1434&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Qe4tU/btssUL85U5P/TwV45m3BkxAEs5iAUwpxo0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Qe4tU/btssUL85U5P/TwV45m3BkxAEs5iAUwpxo0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Qe4tU/btssUL85U5P/TwV45m3BkxAEs5iAUwpxo0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQe4tU%2FbtssUL85U5P%2FTwV45m3BkxAEs5iAUwpxo0%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;392&quot; height=&quot;520&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1434&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;span style=&quot;background-color: #f7f7f8; color: #374151; text-align: start;&quot;&gt;ASP.NET MVC 및 ASP.NET Core 애플리케이션을 보호하는 것은 다양한 보안 취약성으로부터 보호하는 데 중요합니다. 아래에서는 귀하가 언급한 보안 문제를 해결하기 위한 몇 가지 샘플 코드와 모범 사례를 제공하겠습니다.&lt;/span&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;1. SQL INJECTION&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;SQL 주입을 방지하려면 매개변수화된 쿼리나 Entity Framework와 같은 ORM을 사용하세요.&lt;/p&gt;
&lt;pre id=&quot;code_1693802499393&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var query = &quot;SELECT * FROM Users WHERE Username = @username AND Password = @password&quot;;
using (var connection = new SqlConnection(connectionString))
{
    connection.Open();
    var command = new SqlCommand(query, connection);
    command.Parameters.AddWithValue(&quot;@username&quot;, username);
    command.Parameters.AddWithValue(&quot;@password&quot;, password);
    // Execute the command
}&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;2. &lt;b&gt;XSS(교차 사이트 스크립팅):&lt;/b&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;XSS를 방지하려면 사용자 생성 콘텐츠를 렌더링하기 전에 HTML로 인코딩하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1693802529470&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;string userInput = &quot;&amp;lt;script&amp;gt;alert('XSS Attack');&amp;lt;/script&amp;gt;&quot;;
string encodedInput = HttpUtility.HtmlEncode(userInput);&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;&lt;b&gt;3. &lt;b&gt;CSRF(교차 사이트 요청 위조):&lt;/b&gt;&lt;/b&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;위조 방지 토큰을 사용하여 CSRF 보호를 구현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1693802567459&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- ASP.NET MVC
@Html.AntiForgeryToken()&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;p data-ke-size=&quot;size16&quot;&gt;4. &lt;b&gt;파일 업로드 공격:&lt;/b&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;span style=&quot;background-color: #f7f7f8; color: #374151; text-align: left;&quot;&gt;파일 업로드를 검증하고 정리합니다. 허용되는 파일 형식을 제한하고 고유한 파일 이름을 사용하십시오.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1693802590995&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[ValidateAntiForgeryToken]
[HttpPost]
public IActionResult Upload(IFormFile file)
{
    if (file != null &amp;amp;&amp;amp; file.Length &amp;gt; 0)
    {
        var allowedExtensions = new[] { &quot;.jpg&quot;, &quot;.jpeg&quot;, &quot;.png&quot;, &quot;.gif&quot; };
        var fileExtension = Path.GetExtension(file.FileName);

        if (allowedExtensions.Contains(fileExtension))
        {
            var uniqueFileName = Guid.NewGuid().ToString() + fileExtension;
            var filePath = Path.Combine(&quot;uploads&quot;, uniqueFileName);

            using (var stream = new FileStream(filePath, FileMode.Create))
            {
                file.CopyTo(stream);
            }
        }
        else
        {
            // Invalid file type
        }
    }
    else
    {
        // No file uploaded
    }
}&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. COMMAND INJECTION&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;background-color: #f7f7f8; color: #374151; text-align: left;&quot;&gt;쉘 명령을 구성하기 위해 사용자 입력을 사용하지 마십시오. &lt;/span&gt;System.Diagnostics.Process&lt;span style=&quot;background-color: #f7f7f8; color: #374151; text-align: left;&quot;&gt;와 같은 라이브러리를 안전하게 사용하세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1693802619547&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var process = new Process
{
    StartInfo = new ProcessStartInfo
    {
        FileName = &quot;cmd.exe&quot;,
        Arguments = &quot;/c &quot; + input, // Input should be sanitized
        RedirectStandardOutput = true,
        UseShellExecute = false,
        CreateNoWindow = true
    }
};
process.Start();
string result = process.StandardOutput.ReadToEnd();
process.WaitForExit();&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;&lt;span style=&quot;background-color: #f7f7f8; color: #374151; text-align: left;&quot;&gt;6. &lt;b&gt;비밀번호 보안:&lt;/b&gt;&lt;/span&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;span style=&quot;background-color: #f7f7f8; color: #374151; text-align: left;&quot;&gt;강력한 비밀번호 정책을 구현하고, 솔트를 사용하여 비밀번호를 해시하고, 키 스트레칭을 사용하세요.&lt;/span&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;ASP.NET Core ID를 사용하는 예:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1693802656823&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;services.Configure&amp;lt;IdentityOptions&amp;gt;(options =&amp;gt;
{
    options.Password.RequireDigit = true;
    options.Password.RequireLowercase = true;
    options.Password.RequireUppercase = true;
    options.Password.RequireNonAlphanumeric = true;
    options.Password.RequiredLength = 8;
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DDoS 보호: &lt;/b&gt;DDoS 공격을 완화하려면 호스팅 공급자의 DDoS 보호 서비스 또는 솔루션을 사용하세요.&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;span style=&quot;background-color: #f7f7f8; color: #374151; text-align: left;&quot;&gt;7.&amp;nbsp;&lt;b&gt;&lt;b&gt;무차별 대입 보호:&lt;/b&gt;&lt;/b&gt;&lt;/span&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;계정 잠금 정책 및 2단계 인증을 구현합니다.&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;ASP.NET Core ID는 기본적으로 계정 잠금을 지원합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1693802696205&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;services.Configure&amp;lt;IdentityOptions&amp;gt;(options =&amp;gt;
{
    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
    options.Lockout.MaxFailedAccessAttempts = 5;
});&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;&lt;span style=&quot;background-color: #f7f7f8; color: #374151; text-align: start;&quot;&gt;보안은 지속적인 프로세스이며 최신 보안 모범 사례 및 취약점에 대한 업데이트를 유지하는 것이 안전한 애플리케이션을 유지하는 데 필수적이라는 점을 기억하십시오. 보안 조치를 구현할 때는 항상 애플리케이션의 특정 요구 사항과 아키텍처를 고려하십시오.&lt;/span&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;span style=&quot;color: #343541; text-align: start;&quot;&gt;8. Inline Scripts 피하기&lt;/span&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;span style=&quot;color: #343541; text-align: start;&quot;&gt;&lt;b&gt;나쁜 습관(인라인 스크립트):&lt;/b&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1693803467244&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
    var userInput = '@Model.UserInput'; // Avoid this
&amp;lt;/script&amp;gt;&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;&lt;b&gt;선호 사례(외부 스크립트):&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1693803410159&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div id=&quot;userInput&quot; data-userinput=&quot;@Model.UserInput&quot;&amp;gt;&amp;lt;/div&amp;gt;&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_1693803427194&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Separate JavaScript file (external-script.js)

// Retrieve the user input from the HTML element
var userInput = document.getElementById('userInput').getAttribute('data-userinput');

// You can now safely use userInput in JavaScript logic
// Perform any necessary validation or processing&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_1693803442742&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script src=&quot;external-script.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&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;&lt;b&gt;9. &lt;b&gt;Input Validation Libraries&lt;/b&gt; 사용:&lt;/b&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;span style=&quot;background-color: #f7f7f8; color: #374151; text-align: left;&quot;&gt;DOMPurify 라이브러리 와 같은 JavaScript 라이브러리를 활용하여 사용자 생성 콘텐츠를 렌더링하기 전에 삭제하세요. 이 라이브러리는 안전하지 않은 HTML 및 JavaScript를 필터링하여 XSS 공격을 방지하는 데 도움이 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1693804099128&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//간단 샘플예제

&amp;lt;script type=&quot;text/javascript&quot; src=&quot;src/purify.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
const $element = document.getElementByClassName('conatiner'); 

let dirty = '&amp;lt;img src=&quot;x&quot; onerror=&quot;alert(document.cookie)&quot;&amp;gt;'
$element.innerHTML = DOMPurify.sanitize(dirty);&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;&lt;span style=&quot;background-color: #f7f7f8; color: #374151; text-align: left;&quot;&gt;&amp;gt;&amp;gt; asp.net mvc 사용시 적용법&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1693803560133&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- Include DOMPurify library --&amp;gt;
&amp;lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/dompurify/2.3.5/purify.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;!-- Sanitize user input before rendering --&amp;gt;
&amp;lt;div id=&quot;output&quot;&amp;gt;
    @Model.UserInput
&amp;lt;/div&amp;gt;

&amp;lt;script&amp;gt;
    // Sanitize user input
    var userInput = '@Model.UserInput';
    var sanitizedInput = DOMPurify.sanitize(userInput);
    document.getElementById('output').innerHTML = sanitizedInput;
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f7f7f8; color: #374151; text-align: start;&quot;&gt;이러한 사례를 따르고 입력 유효성 검사, 출력 인코딩 및 적절한 보안 헤더를 적용하면 ASP.NET MVC 또는 ASP.NET Core 애플리케이션에서 JavaScript 주입 위험을 크게 줄일 수 있습니다. 잠재적인 취약점을 완화하려면 애플리케이션과 라이브러리를 최신 상태로 유지해야 합니다.&lt;/span&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;span style=&quot;background-color: #f7f7f8; color: #374151; text-align: start;&quot;&gt;&amp;lt;참고자료&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;855&quot; data-origin-height=&quot;601&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bh1yf6/btssS3bDQ57/wSoZMJpPOQASR4hxX4cF71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bh1yf6/btssS3bDQ57/wSoZMJpPOQASR4hxX4cF71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bh1yf6/btssS3bDQ57/wSoZMJpPOQASR4hxX4cF71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbh1yf6%2FbtssS3bDQ57%2FwSoZMJpPOQASR4hxX4cF71%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;855&quot; height=&quot;601&quot; data-origin-width=&quot;855&quot; data-origin-height=&quot;601&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;&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;a href=&quot;https://www.isac.or.kr/upload/JavaScript_시큐어코딩_가이드2022년.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.isac.or.kr/upload/JavaScript_시큐어코딩_가이드2022년.pdf&lt;/a&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;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/bERUda/btssVJJW6M0/4HRbSrK90RQexmtiJZru40/JavaScript_%EC%8B%9C%ED%81%90%EC%96%B4%EC%BD%94%EB%94%A9_%EA%B0%80%EC%9D%B4%EB%93%9C2022%EB%85%84.pdf?attach=1&amp;amp;knm=tfile.pdf&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;JavaScript_시큐어코딩_가이드2022년.pdf&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;1.39MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>닷넷관련/ASP.NET CORE  </category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3095</guid>
      <comments>https://aspdotnet.tistory.com/3095#entry3095comment</comments>
      <pubDate>Tue, 19 Aug 2025 10:39:43 +0900</pubDate>
    </item>
    <item>
      <title>AI 기술 : gitmvp 활용해 보기</title>
      <link>https://aspdotnet.tistory.com/3440</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1509&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k4e6k/btsPOCCl9MH/uu3qAOb6W67oAkBTbsMRL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k4e6k/btsPOCCl9MH/uu3qAOb6W67oAkBTbsMRL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k4e6k/btsPOCCl9MH/uu3qAOb6W67oAkBTbsMRL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk4e6k%2FbtsPOCCl9MH%2Fuu3qAOb6W67oAkBTbsMRL0%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;1509&quot; height=&quot;634&quot; data-origin-width=&quot;1509&quot; data-origin-height=&quot;634&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;a href=&quot;https://www.gitmvp.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.gitmvp.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1754973220066&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Let Cursor cheat from GitHub - GitMVP&quot; data-og-description=&quot;&quot; data-og-host=&quot;www.gitmvp.com&quot; data-og-source-url=&quot;https://www.gitmvp.com/&quot; data-og-url=&quot;https://www.gitmvp.com/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.gitmvp.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.gitmvp.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Let Cursor cheat from GitHub - GitMVP&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.gitmvp.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;MCP 추가하기&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;gitmvp&quot;: {
  &quot;url&quot;: &quot;https://gitmvp.com/mcp&quot;,
  &quot;headers&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;명령 프롬프트&lt;/h4&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;give me a crash course on 
@https://github.com/phnx47/dapper-repositories 

use mermaid diagrams and markdown tables

use gitmvp mcp&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;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실행결과&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;925&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6CZ4P/btsPNIoZyFJ/heHWEZqn9C89willSLf06K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6CZ4P/btsPNIoZyFJ/heHWEZqn9C89willSLf06K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6CZ4P/btsPNIoZyFJ/heHWEZqn9C89willSLf06K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6CZ4P%2FbtsPNIoZyFJ%2FheHWEZqn9C89willSLf06K%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;925&quot; height=&quot;600&quot; data-origin-width=&quot;925&quot; data-origin-height=&quot;600&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실행 결과값&lt;/h4&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;현재 레포 내용을 빠르게 스캔해 정확한 사용법과 최신 구조를 정리하겠습니다. 다 읽은 뒤, 한국어로 짧고 실전 위주의 크래시 코스를 Mermaid 다이어그램과 표로 정리해 드립니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빠르게 레포 주요 파일을 더 읽어 실전 예제를 정확히 제시할 수 있게 하겠습니다. CRUD, 조인, 필터 체이닝 관련 메서드 시그니처를 동시에 확인합니다.&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;MicroOrm.Dapper.Repositories 크래시 코스&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;한눈에 보기&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: POCO + Attribute 메타데이터로 SQL을 자동 생성해 Dapper CRUD/조회/조인/페이지네이션을 간결화&lt;/li&gt;
&lt;li&gt;&lt;b&gt;핵심 구성&lt;/b&gt;: &lt;code&gt;DapperRepository&amp;lt;TEntity&amp;gt;&lt;/code&gt;, &lt;code&gt;ReadOnlyDapperRepository&amp;lt;TEntity&amp;gt;&lt;/code&gt;, &lt;code&gt;ISqlGenerator&amp;lt;TEntity&amp;gt;&lt;/code&gt;, &lt;code&gt;DapperDbContext&lt;/code&gt;, Attribute 세트(Join/Identity/Status/Deleted/UpdatedAt)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;graph LR
  A[POCO Entity&amp;lt;br/&amp;gt;DataAnnotations + MR Attributes] --&amp;gt; B[ISqlGenerator&amp;lt;TEntity&amp;gt;&amp;lt;br/&amp;gt;SQL 생성]
  B --&amp;gt; C[DapperRepository&amp;lt;TEntity&amp;gt;&amp;lt;br/&amp;gt;CRUD/조회 API]
  C --&amp;gt; D[Dapper(IDbConnection)]
  D --&amp;gt; E[(DB)]
  C -.필터 체이닝.-&amp;gt; F[FilterData&amp;lt;br/&amp;gt;Select/Order/Limit/Group]&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;설치&lt;/h3&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;dotnet add package MicroOrm.Dapper.Repositories&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;최소 설정&lt;/h3&gt;
&lt;pre class=&quot;protobuf&quot;&gt;&lt;code&gt;using MicroOrm.Dapper.Repositories.Config;
using MicroOrm.Dapper.Repositories.SqlGenerator;

MicroOrmConfig.SqlProvider = SqlProvider.MSSQL;  // MSSQL/MySQL/PostgreSQL/SQLite/Oracle
MicroOrmConfig.UseQuotationMarks = true;         // 필요시 식별자 quoting
MicroOrmConfig.TablePrefix = &quot;&quot;;                 // 선택: 스키마/접두어&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;엔티티 정의(필수 Attribute 핵심)&lt;/h3&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using MicroOrm.Dapper.Repositories.Attributes;
using MicroOrm.Dapper.Repositories.Attributes.LogicalDelete;
using MicroOrm.Dapper.Repositories.Attributes.Joins;

[Table(&quot;Orders&quot;)]
public class Order
{
    [Key] [Identity] public int Id { get; set; }   // 자동증가 키
    public int CustomerId { get; set; }

    [Status] public OrderStatus Status { get; set; }  // 논리삭제 상태 컬럼
    [UpdatedAt] public DateTime? UpdatedAt { get; set; } // Update/Insert 시 자동 갱신

    // 조인: 현재 테이블의 FK(CustomerId) &amp;rarr; 외부 PK(Id)
    [InnerJoin(&quot;Customers&quot;, &quot;CustomerId&quot;, &quot;Id&quot;)]
    public Customer Customer { get; set; } = default!;
}

public enum OrderStatus
{
    Active = 0,
    [Deleted] Deleted = 1  // 논리삭제 값 지정
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;리포지토리 사용(기본 CRUD)&lt;/h3&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;using (var repo = new DapperRepository&amp;lt;Order&amp;gt;(connection))
{
    // Insert (Identity 컬럼이면 반환된 키를 엔티티에 세팅)
    await repo.InsertAsync(new Order { CustomerId = 1, Status = OrderStatus.Active });

    // Update (명시 필드만 업데이트하려면 includes 사용)
    await repo.UpdateAsync(order, x =&amp;gt; x.Status);

    // Delete (논리삭제면 Status/UpdatedAt만 갱신)
    await repo.DeleteAsync(order);

    // 조회
    var one = await repo.FindAsync(x =&amp;gt; x.CustomerId == 1);
    var all = await repo.FindAllAsync(x =&amp;gt; x.Status == OrderStatus.Active);

    // 범위 조회(BETWEEN)
    var range = await repo.FindAllBetweenAsync(DateTime.UtcNow.AddDays(-7), DateTime.UtcNow, x =&amp;gt; x.UpdatedAt!);

    // 카운트/Distinct 카운트
    var total = await repo.CountAsync(x =&amp;gt; x.Status == OrderStatus.Active);
    var distinct = await repo.CountAsync(x =&amp;gt; x.CustomerId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;조인 포함 조회&lt;/h3&gt;
&lt;pre class=&quot;gml&quot;&gt;&lt;code&gt;// 단건 + 조인
var withJoin = repo.Find&amp;lt;Order&amp;gt;(
    x =&amp;gt; x.Id == someId,
    x =&amp;gt; x.Customer
);

// 다건 + 조인
var listWithJoin = repo.FindAll&amp;lt;Order&amp;gt;(
    x =&amp;gt; x.Status == OrderStatus.Active,
    x =&amp;gt; x.Customer
);&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;sequenceDiagram
  participant R as ReadOnlyDapperRepository
  participant G as SqlGenerator&amp;lt;TEntity&amp;gt;
  participant D as Dapper/IDbConnection
  R-&amp;gt;&amp;gt;G: GetSelect(..., includes: Customer)
  G--&amp;gt;&amp;gt;R: SELECT ... INNER JOIN Customers ...
  R-&amp;gt;&amp;gt;D: Query(sql, params)
  D--&amp;gt;&amp;gt;R: IEnumerable&amp;lt;TEntity&amp;gt; (멀티매핑)&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;페이징/정렬/프로젝션(필터 체이닝)&lt;/h3&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;using MicroOrm.Dapper.Repositories.SqlGenerator.Filters;

var paged = await repo
  .SetSelect&amp;lt;Order&amp;gt;(o =&amp;gt; new { o.Id, o.UpdatedAt })   // SELECT 컬럼 축소
  .SetOrderBy&amp;lt;Order&amp;gt;(OrderInfo.SortDirection.DESC, false, o =&amp;gt; o.UpdatedAt!) // ORDER BY
  .SetLimit(20, 0)                                    // LIMIT/OFFSET (MSSQL은 ORDER 필수)
  .FindAllAsync(o =&amp;gt; o.Status == OrderStatus.Active);&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;벌크 작업&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;await repo.BulkInsertAsync(orders);  // MSSQL: 2099 파라미터 제한 자동 분할
await repo.BulkUpdateAsync(orders);  // Oracle: MERGE INTO 생성&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;트랜잭션/컨텍스트&lt;/h3&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;using MicroOrm.Dapper.Repositories.DbContext;

public class AppDbContext : DapperDbContext
{
    public AppDbContext(IDbConnection conn) : base(conn) { }
}

using var ctx = new AppDbContext(connection);
using var tx = await ctx.BeginTransactionAsync();
var repoTx = new DapperRepository&amp;lt;Order&amp;gt;(ctx.Connection);
await repoTx.InsertAsync(order, tx);
tx.Commit();&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 타입 요약&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;타입/어트리뷰트&lt;/th&gt;
&lt;th&gt;용도&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DapperRepository&amp;lt;TEntity&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CRUD, Bulk, 조인 포함 조회 제공&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ReadOnlyDapperRepository&amp;lt;TEntity&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;읽기 전용 API(Find/FindAll/Count/Between 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ISqlGenerator&amp;lt;TEntity&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;속성/Attribute 메타데이터로 SQL 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DapperDbContext&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;연결 열기/트랜잭션 시작 유틸&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[Identity]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;자동 증가 키&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[UpdatedAt]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SQL 생성 시 시간 자동 세팅(UTC 기본, OffSet 지원)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[Status]&lt;/code&gt; + &lt;code&gt;[Deleted]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;논리삭제: bool/bool? 또는 Enum + Deleted 멤버&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[InnerJoin/LeftJoin/RightJoin]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;조인 메타데이터(테이블/키/스키마/별칭)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[IgnoreUpdate]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;UPDATE SET 제외&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;자주 쓰는 메서드&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;메서드&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Insert/InsertAsync&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Identity 반환/대입 포함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Update/UpdateAsync&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;includes로 부분 업데이트 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Delete/DeleteAsync&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;논리삭제면 Status/UpdatedAt만 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Find/FindAll/FindById&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;기본/조인 포함 오버로드 다양&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FindAllBetween&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;BETWEEN 조회(문자열 또는 DateTime 포맷)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Count&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;기본/Distinct 카운트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SetSelect/SetOrderBy/SetLimit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SELECT/ORDER BY/LIMIT 체이닝(한 번성/영구 플래그)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로바이더별 Identity 반환&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Insert 후 키 반환&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MSSQL&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SELECT SCOPE_IDENTITY()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MySQL&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SELECT CONVERT(LAST_INSERT_ID(), SIGNED INTEGER)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQLite&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SELECT LAST_INSERT_ROWID()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;&lt;code&gt;RETURNING Id&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oracle&lt;/td&gt;
&lt;td&gt;&lt;code&gt;RETURNING Id INTO :newId&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;베스트 프랙티스&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔티티 키/컬럼은 &lt;code&gt;Key&lt;/code&gt;, &lt;code&gt;Column&lt;/code&gt;, &lt;code&gt;Table&lt;/code&gt;(DataAnnotations)로 명시&lt;/li&gt;
&lt;li&gt;논리삭제는 &lt;code&gt;[Status]&lt;/code&gt; 컬럼 + &lt;code&gt;bool/bool?&lt;/code&gt; 또는 Enum의 &lt;code&gt;[Deleted]&lt;/code&gt; 멤버 사용&lt;/li&gt;
&lt;li&gt;MSSQL 페이징은 반드시 &lt;code&gt;SetOrderBy&lt;/code&gt; 후 &lt;code&gt;SetLimit&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;조인은 엔티티 내 내비게이션 속성에 Join Attribute 지정 후, &lt;code&gt;includes&lt;/code&gt; 전달&lt;/li&gt;
&lt;li&gt;대량 작업은 &lt;code&gt;BulkInsert/BulkUpdate&lt;/code&gt; 우선 고려&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Clean Architecture 연계(요약)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Domain: 엔티티 + DataAnnotations(외부 의존 최소), Join/Status/UpdatedAt Attribute는 규칙에 맞춰 도입 여부 결정&lt;/li&gt;
&lt;li&gt;Infrastructure: &lt;code&gt;DapperRepository&amp;lt;TEntity&amp;gt;&lt;/code&gt; 구현/주입, SQLProvider 전역 설정&lt;/li&gt;
&lt;li&gt;Application: CQRS 핸들러에서 리포지토리 인터페이스 사용&lt;/li&gt;
&lt;li&gt;Presentation: MediatR 통해 호출&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;빠른 스타터 코드 스니펫&lt;/h3&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;// 1) 설정
MicroOrmConfig.SqlProvider = SqlProvider.MSSQL;

// 2) 리포지토리
using var repo = new DapperRepository&amp;lt;Order&amp;gt;(connection);

// 3) CRUD
await repo.InsertAsync(new Order { CustomerId = 1, Status = OrderStatus.Active });
var page = await repo.SetOrderBy&amp;lt;Order&amp;gt;(OrderInfo.SortDirection.DESC, false, x =&amp;gt; x.UpdatedAt!).SetLimit(10).FindAllAsync(x =&amp;gt; x.Status == OrderStatus.Active);&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주요 파일 참고(최신): &lt;code&gt;IDapperRepository.cs&lt;/code&gt;, &lt;code&gt;DapperRepository.*.cs&lt;/code&gt;, &lt;code&gt;ReadOnlyDapperRepository.*.cs&lt;/code&gt;, &lt;code&gt;SqlGenerator/*&lt;/code&gt;, &lt;code&gt;Attributes/*&lt;/code&gt;, &lt;code&gt;DbContext/DapperDbContext.cs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;README: &lt;code&gt;https://github.com/phnx47/dapper-repositories&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;조인/논리삭제/업데이트 타임스탬프의 동작은 소스 확인 기준:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조인: &lt;code&gt;JoinAttributeBase&lt;/code&gt; + &lt;code&gt;ReadOnlyDapperRepository.*.Join.cs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;논리삭제: &lt;code&gt;SqlGenerator.InitLogicalDeleted.cs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;UpdatedAt: &lt;code&gt;UpdatedAtAttribute.cs&lt;/code&gt; + &lt;code&gt;GetInsert/GetUpdate/GetDelete&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;한계/주의
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복잡한 투영/멀티매핑은 필요한 컬럼만 &lt;code&gt;SetSelect&lt;/code&gt;로 투영 권장&lt;/li&gt;
&lt;li&gt;Oracle IN 절 빈 리스트 처리 특수 로직 포함(자동 null 치환)&lt;/li&gt;
&lt;li&gt;MSSQL 파라미터 2100 제한 &amp;rarr; Bulk 내부 분할 처리됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;더 깊이: Count Distinct, GroupBy/Select 커스텀, Quotation per Provider 등은 &lt;code&gt;SqlGenerator.GetSelect.cs&lt;/code&gt; 참고&lt;/li&gt;
&lt;li&gt;상태 업데이트: 깃허브 원본 저장소 구조/핵심 소스 파일을 스캔하여 최신 메서드 시그니처와 프로바이더별 동작을 반영했습니다.&lt;/li&gt;
&lt;li&gt;요약
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;POCO + Attribute로 SQL 생성 자동화&lt;/li&gt;
&lt;li&gt;CRUD/조회/조인/페이징 체이닝 간단&lt;/li&gt;
&lt;li&gt;논리삭제/UpdatedAt/벌크 작업 내장&lt;/li&gt;
&lt;li&gt;프로바이더별 Identity/페이징/인용부호 처리 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;</description>
      <category>AI 관련/AI 기술</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3440</guid>
      <comments>https://aspdotnet.tistory.com/3440#entry3440comment</comments>
      <pubDate>Tue, 12 Aug 2025 13:34:25 +0900</pubDate>
    </item>
    <item>
      <title>서울 사당역 &amp;rarr; 김포공항 &amp;rarr; 티웨이항공 카운터 알아보기 (8842번 리무진)</title>
      <link>https://aspdotnet.tistory.com/3434</link>
      <description>&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;348&quot; data-origin-height=&quot;145&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cq2Kaa/btsPC547d9E/7HzUo5rgDZFG6ToVIfvlf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cq2Kaa/btsPC547d9E/7HzUo5rgDZFG6ToVIfvlf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cq2Kaa/btsPC547d9E/7HzUo5rgDZFG6ToVIfvlf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcq2Kaa%2FbtsPC547d9E%2F7HzUo5rgDZFG6ToVIfvlf0%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;348&quot; height=&quot;145&quot; data-origin-width=&quot;348&quot; data-origin-height=&quot;145&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;h2 data-end=&quot;963&quot; data-start=&quot;926&quot; data-ke-size=&quot;size26&quot;&gt;서울 사당역 &amp;rarr; 김포공항 &amp;rarr; 티웨이항공 카운터&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;1218&quot; data-start=&quot;964&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;1005&quot; data-start=&quot;964&quot;&gt;사당역 또는 정부과천청사 등 서울에서 &lt;b&gt;8842번 버스 승차&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1067&quot; data-start=&quot;1006&quot;&gt;&lt;b&gt;김포공항 &lt;/b&gt;&lt;b&gt;국내선 10번홈 정류장&lt;/b&gt;(이후 정차)에서 하차&lt;/li&gt;
&lt;li data-end=&quot;1164&quot; data-start=&quot;1068&quot;&gt;&lt;b&gt;만약에 국제선 터미널 &amp;rarr; 국내선 터미널&lt;/b&gt; 이동이 필요한 경우:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1164&quot; data-start=&quot;1108&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1141&quot; data-start=&quot;1108&quot;&gt;국제선에서 셔틀버스 탑승 후 국내선 이동 (약 10 분)&lt;/li&gt;
&lt;li data-end=&quot;1164&quot; data-start=&quot;1145&quot;&gt;또는 도보로 터미널 간 이동&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1218&quot; data-start=&quot;1165&quot;&gt;국내선 터미널 &lt;b&gt;2층 또는 3층&lt;/b&gt;에 위치한 &lt;b&gt;티웨이항공 카운터&lt;/b&gt;에서 체크인 및 발권&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8842 버스&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;gt; 김포공항국내선(12번홈)&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;2083&quot; data-origin-height=&quot;1356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTXrI3/btsPCSZbo91/X6rhw8ddeZ5dL4n8U95qkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTXrI3/btsPCSZbo91/X6rhw8ddeZ5dL4n8U95qkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTXrI3/btsPCSZbo91/X6rhw8ddeZ5dL4n8U95qkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTXrI3%2FbtsPCSZbo91%2FX6rhw8ddeZ5dL4n8U95qkk%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;2083&quot; height=&quot;1356&quot; data-origin-width=&quot;2083&quot; data-origin-height=&quot;1356&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;&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;김포공항 국내선(11-2번홈)&amp;nbsp; -&amp;gt; 사당역&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2108&quot; data-origin-height=&quot;952&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RBhu7/btsPDlUaCiO/cVsnvhpki2YGMdO6KKsB9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RBhu7/btsPDlUaCiO/cVsnvhpki2YGMdO6KKsB9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RBhu7/btsPDlUaCiO/cVsnvhpki2YGMdO6KKsB9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRBhu7%2FbtsPDlUaCiO%2FcVsnvhpki2YGMdO6KKsB9K%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;2108&quot; height=&quot;952&quot; data-origin-width=&quot;2108&quot; data-origin-height=&quot;952&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;&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-origin-width=&quot;2867&quot; data-origin-height=&quot;1581&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pGrB1/btsPDI2Ax6j/38U16d0tFPb1Fy84DyUYN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pGrB1/btsPDI2Ax6j/38U16d0tFPb1Fy84DyUYN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pGrB1/btsPDI2Ax6j/38U16d0tFPb1Fy84DyUYN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpGrB1%2FbtsPDI2Ax6j%2F38U16d0tFPb1Fy84DyUYN1%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;2867&quot; height=&quot;1581&quot; data-origin-width=&quot;2867&quot; data-origin-height=&quot;1581&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;버스는 방배우성아파트 맞은편에 8842 공항버스를 타고 가면 됩니다.&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;1305&quot; data-origin-height=&quot;1108&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bp8hag/btsPC5cYA4U/qrng0NBYVcgqRmBkZMeje0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bp8hag/btsPC5cYA4U/qrng0NBYVcgqRmBkZMeje0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bp8hag/btsPC5cYA4U/qrng0NBYVcgqRmBkZMeje0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbp8hag%2FbtsPC5cYA4U%2Fqrng0NBYVcgqRmBkZMeje0%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;1305&quot; height=&quot;1108&quot; data-origin-width=&quot;1305&quot; data-origin-height=&quot;1108&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;사당역 -&amp;gt; 김포공항국내선(12번홈) 에서 내리면, 김포공항 국내선 티웨이항공 카운터는 7~12번이에요. 4번&amp;nbsp;출입구를&amp;nbsp;이용하면&amp;nbsp;가까워서&amp;nbsp;찾기&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;h4 data-ke-size=&quot;size20&quot;&gt;김포공항&amp;nbsp;국내선&amp;nbsp;제주도&amp;nbsp;항공권&amp;nbsp;티웨이항공&amp;nbsp;좌석&amp;nbsp;탑승&amp;nbsp;후기&lt;/h4&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://blog.naver.com/jammyrain/223866177567&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://blog.naver.com/jammyrain/223866177567&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1753840149210&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;김포공항 국내선 제주도 항공권 티웨이항공 좌석 탑승 후기&quot; data-og-description=&quot;김포공항 국내선 제주도 항공권 티웨이항공 좌석 후기 글, 사진 @단비 하이~~ 안녕하세요. 랑랑이 생일을 ...&quot; data-og-host=&quot;blog.naver.com&quot; data-og-source-url=&quot;https://blog.naver.com/jammyrain/223866177567&quot; data-og-url=&quot;https://blog.naver.com/jammyrain/223866177567&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b39V20/hyZqZ8dvz3/Vqo7nZ7U8CfGVfomRaPMY0/img.png?width=743&amp;amp;height=743&amp;amp;face=0_0_743_743&quot;&gt;&lt;a href=&quot;https://blog.naver.com/jammyrain/223866177567&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.naver.com/jammyrain/223866177567&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b39V20/hyZqZ8dvz3/Vqo7nZ7U8CfGVfomRaPMY0/img.png?width=743&amp;amp;height=743&amp;amp;face=0_0_743_743');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;김포공항 국내선 제주도 항공권 티웨이항공 좌석 탑승 후기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;김포공항 국내선 제주도 항공권 티웨이항공 좌석 후기 글, 사진 @단비 하이~~ 안녕하세요. 랑랑이 생일을 ...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.naver.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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://namu.wiki/w/%EA%B3%B5%ED%95%AD%EB%B2%84%EC%8A%A4%208842&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://namu.wiki/w/%EA%B3%B5%ED%95%AD%EB%B2%84%EC%8A%A4%208842&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1753840598236&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;공항버스 8842&quot; data-og-description=&quot;경기고속 에서 운행하는 공항버스 노선. 현 전체 정류장 목록 노선 정보 파일:경기도 휘장_White.svg 공&quot; data-og-host=&quot;namu.wiki&quot; data-og-source-url=&quot;https://namu.wiki/w/%EA%B3%B5%ED%95%AD%EB%B2%84%EC%8A%A4%208842&quot; data-og-url=&quot;https://namu.wiki/w/%EA%B3%B5%ED%95%AD%EB%B2%84%EC%8A%A4%208842&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cT74R2/hyZqQXKxce/vwKhTQT4KTSeNGwZASr0c1/img.jpg?width=200&amp;amp;height=133&amp;amp;face=0_0_200_133&quot;&gt;&lt;a href=&quot;https://namu.wiki/w/%EA%B3%B5%ED%95%AD%EB%B2%84%EC%8A%A4%208842&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://namu.wiki/w/%EA%B3%B5%ED%95%AD%EB%B2%84%EC%8A%A4%208842&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cT74R2/hyZqQXKxce/vwKhTQT4KTSeNGwZASr0c1/img.jpg?width=200&amp;amp;height=133&amp;amp;face=0_0_200_133');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;공항버스 8842&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;경기고속 에서 운행하는 공항버스 노선. 현 전체 정류장 목록 노선 정보 파일:경기도 휘장_White.svg 공&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;namu.wiki&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>생활정보  </category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3434</guid>
      <comments>https://aspdotnet.tistory.com/3434#entry3434comment</comments>
      <pubDate>Wed, 30 Jul 2025 11:58:01 +0900</pubDate>
    </item>
    <item>
      <title>MCP : Claude Task Master 설치 가이드 및 사용 가이드</title>
      <link>https://aspdotnet.tistory.com/3431</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;301&quot; data-origin-height=&quot;161&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HjS6k/btsPp6KbwOU/ppC577F0HfoglndTBARk5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HjS6k/btsPp6KbwOU/ppC577F0HfoglndTBARk5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HjS6k/btsPp6KbwOU/ppC577F0HfoglndTBARk5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHjS6k%2FbtsPp6KbwOU%2FppC577F0HfoglndTBARk5k%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;301&quot; height=&quot;161&quot; data-origin-width=&quot;301&quot; data-origin-height=&quot;161&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1 id=&quot;claude-task-master-installation-and-usage-guide&quot;&gt;Claude Task Master: 설치 가이드&lt;/h1&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 Task Master는 프로젝트 요구사항 문서(PRD)를 자동으로 구문 분석하고 구조화된 작업 목록을 생성하여 개발 워크플로를 간소화하도록 설계된 AI 기반 작업 관리 시스템입니다.&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;이 강력한 도구는 Cursor와 같은 편집기와 원활하게 통합되며 지능적인 작업 분류 및 관리 기능을 제공합니다.&lt;/p&gt;
&lt;h2 id=&quot;installation&quot; data-ke-size=&quot;size26&quot;&gt;설치&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Option 1: MCP Installation (추천)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP 접근 방식은 Cursor와 같은 AI 편집기와 가장 원활하게 통합됩니다.&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;Step 1: Install the Package&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1752934953475&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install -g task-master-ai&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div data-testid=&quot;code-language-indicator&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Step 2: Configure MCP Server&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;편집기의 MCP 설정 파일(예: .cursor/mcp.json)에 다음 구성을 추가합니다:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1752934968866&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;mcpServers&quot;: {
    &quot;taskmaster-ai&quot;: {
      &quot;command&quot;: &quot;npx&quot;,
      &quot;args&quot;: [&quot;-y&quot;, &quot;--package=task-master-ai&quot;, &quot;task-master-ai&quot;],
      &quot;env&quot;: {
        &quot;ANTHROPIC_API_KEY&quot;: &quot;YOUR_ANTHROPIC_API_KEY_HERE&quot;,
        &quot;PERPLEXITY_API_KEY&quot;: &quot;YOUR_PERPLEXITY_API_KEY_HERE&quot;,
        &quot;OPENAI_API_KEY&quot;: &quot;YOUR_OPENAI_KEY_HERE&quot;,
        &quot;GOOGLE_API_KEY&quot;: &quot;YOUR_GOOGLE_KEY_HERE&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;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Step 3: Initialize Project&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 편집기(커서)에서 간단히 프롬프트를 입력합니다:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1752934982125&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Can you please initialize taskmaster-ai into my project?&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;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&quot;project-structure&quot; data-ke-size=&quot;size26&quot;&gt;Project Structure (프로젝트 구조)&lt;/h2&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;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1752935120232&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;your-project/
├── .taskmaster/
│   ├── docs/           # Place your PRD files here
│   ├── config.json     # Configuration settings
│   └── tasks.json      # Generated tasks
├── tasks/              # Individual task files
└── .cursor/
    └── rules/
        └── dev_workflow.mdc  # Cursor AI integration rules&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;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&quot;core-usage-commands&quot; data-ke-size=&quot;size26&quot;&gt;Commands 사용법&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기본 기능 Task Management&lt;/h2&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;PRD&amp;nbsp;구문&amp;nbsp;분석&amp;nbsp;및&amp;nbsp;작업&amp;nbsp;생성:&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div data-testid=&quot;code-language-indicator&quot;&gt;
&lt;pre id=&quot;code_1752935173179&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;task-master parse-prd .taskmaster/docs/prd.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모든 Tasks 목록&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1752935191493&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;task-master list&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&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;다음 Task 보기:&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1752935204029&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;task-master next&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div data-testid=&quot;code-language-indicator&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&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;개별&amp;nbsp;작업&amp;nbsp;파일을&amp;nbsp;생성&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1752935222704&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;task-master generate&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&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;Task Status 변경하기:&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1752935273564&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;task-master set-status --id=3 --status=done&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;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;고급 기능&lt;/h2&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;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1752935301847&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;task-master expand --id=5 --num=3 task-master expand --id=5 --prompt=&quot;Focus on security aspects&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;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Update Multiple Tasks 변경하기&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div data-testid=&quot;code-language-indicator&quot;&gt;
&lt;pre id=&quot;code_1752935320292&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;task-master update --from=4 --prompt=&quot;Now we are using MongoDB instead of PostgreSQL&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Research Integration:&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1752935335538&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;task-master research &quot;Latest JWT security recommendations 2024&quot; --id=5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Multi-Context 개발을 위한 TAG 관리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Tagg Context 생성하기:&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div data-testid=&quot;code-language-indicator&quot;&gt;
&lt;pre id=&quot;code_1752935386360&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;task-master add-tag --from-branch task-master add-tag user-auth --description=&quot;User authentication feature tasks&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;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Contexts 간 전환하기:&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1752935415564&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;task-master use-tag user-auth&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&quot;real-todo-list-simulation-example&quot; data-ke-size=&quot;size26&quot;&gt;TODO List Simulation 재연하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task Master 실제&amp;nbsp;프로젝트&amp;nbsp;워크플로를&amp;nbsp;시뮬레이션해&amp;nbsp;보겠습니다:&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Create a Sample PRD&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 콘텐츠를 .taskmaster/docs/web-app-prd.txt에 배치합니다:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1752935476152&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Project: E-commerce Web Application

Requirements:
- User registration and authentication system
- Product catalog with search functionality  
- Shopping cart implementation
- Order processing and payment integration
- Admin dashboard for inventory management
- Email notifications for orders
- Mobile-responsive design
- Security implementation with JWT tokens&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Generate Tasks from PRD&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div data-testid=&quot;code-language-indicator&quot;&gt;
&lt;pre id=&quot;code_1752935492122&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;task-master parse-prd .taskmaster/docs/web-app-prd.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. View Generated Task List&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div data-testid=&quot;code-language-indicator&quot;&gt;
&lt;pre id=&quot;code_1752935500968&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;task-master list​&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Expected Output:&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1752935563339&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Tasks Overview:
ID | Title | Status | Priority | Dependencies
1  | User Authentication System | pending | high | []
2  | Product Catalog &amp;amp; Search | pending | high | [1]
3  | Shopping Cart Implementation | pending | medium | [1,2]
4  | Payment Integration | pending | high | [3]
5  | Admin Dashboard | pending | medium | [1]
6  | Email Notification System | pending | low | [4]
7  | Mobile Responsive Design | pending | medium | [2,3]
8  | Security &amp;amp; JWT Implementation | pending | high | [1]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Work on Next Priority Task&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div data-testid=&quot;code-language-indicator&quot;&gt;
&lt;pre id=&quot;code_1752935572191&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;task-master next&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. Break Down Complex Tasks&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1752935584972&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;task-master expand --id=1 --num=4&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;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;This generates subtasks like:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1.1: User registration form&lt;/li&gt;
&lt;li&gt;1.2: Login/logout functionality&lt;/li&gt;
&lt;li&gt;1.3: Password reset system&lt;/li&gt;
&lt;li&gt;1.4: User profile management&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&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;h2 id=&quot;aspnet-core-8-mvc-integration-example&quot; data-ke-size=&quot;size26&quot;&gt;ASP.NET Core 8 MVC Integration Example&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은&amp;nbsp;ASP.NET&amp;nbsp;Core&amp;nbsp;8&amp;nbsp;MVC&amp;nbsp;프로젝트에&amp;nbsp;클로드&amp;nbsp;태스크&amp;nbsp;마스터를&amp;nbsp;사용하는&amp;nbsp;포괄적인&amp;nbsp;예제입니다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ASP.NET Core Project&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. ASP.NET Core Specific PRD 작성하기&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;이&amp;nbsp;파일을&amp;nbsp;.taskmaster/docs/aspnet-mvc-prd.txt에&amp;nbsp;넣습니다:&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1752935625438&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ASP.NET Core 8 MVC E-Learning Platform

Technical Requirements:
- ASP.NET Core 8 MVC architecture
- Entity Framework Core 8 with SQL Server
- Identity Framework for authentication
- Razor Pages with ViewModels
- Repository pattern implementation
- Dependency injection container setup
- Areas for Admin/Student/Instructor modules
- SignalR for real-time notifications
- Background services for email processing
- API controllers for mobile app integration
- Unit testing with xUnit and Moq
- Docker containerization&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Research 기능으로 Tasts Generate 하기&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1752935634049&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;task-master parse-prd .taskmaster/docs/aspnet-mvc-prd.txt
task-master research &quot;ASP.NET Core 8 MVC best practices 2024&quot; --id=1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ASP.NET Core 개발을 위한 AI 질문 가이드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task Master&amp;nbsp; 를 설치한 후 ASP.NET Core 8 MVC 개발을 위해 Task Master 와 상호 작용하는 효과적인 방법은 다음과 같습니다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Architecture 와 셋팅 관련 질문&lt;/b&gt;&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1752935661436&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;What's the next task for setting up our ASP.NET Core 8 MVC project structure? 
Please consider dependency injection and the repository pattern.

I need to implement task 3 (Entity Framework setup). Can you research the latest 
EF Core 8 configuration patterns and update the task with current best practices?


Can you break down task 5 (Identity Framework integration) into subtasks that 
follow ASP.NET Core Identity conventions?&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개발 구현에 대한 질문&lt;/b&gt;&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div data-testid=&quot;code-language-indicator&quot;&gt;
&lt;pre id=&quot;code_1752935680395&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;I'm working on task 7 (Repository pattern). Research the latest ASP.NET Core 8 
repository pattern implementations and help me understand the current approach.

We've decided to use Minimal APIs alongside MVC controllers for our API endpoints. 
Can you update tasks 8-12 to reflect this architectural change?

Show me tasks 4, 6, and 8 so I can understand the database, authentication, 
and API dependencies before implementing the controller layer.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;테스트와 품질에 대한 내용&lt;/b&gt;&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1752935709776&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;I've completed the user controller implementation for task 6. All unit tests are 
passing. Please mark it as complete and tell me what MVC-related task I should work on next.

Can you expand task 11 (Unit Testing) with a focus on ASP.NET Core MVC controller 
testing patterns using xUnit and Moq?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;ASP.NET Core 고급 기능&lt;/b&gt;&lt;/h2&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1752935725835&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Research SignalR integration patterns for ASP.NET Core 8 MVC and update task 9 
with the latest real-time communication approaches.

I need to implement background services for task 10. Can you research .NET 8 
hosted services and update the task with current implementation patterns?&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>AI 관련/AI 기술</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3431</guid>
      <comments>https://aspdotnet.tistory.com/3431#entry3431comment</comments>
      <pubDate>Sat, 19 Jul 2025 23:37:14 +0900</pubDate>
    </item>
    <item>
      <title>python : Hacker News 프로젝트: uv로 가상환경 및 패키지 관리하기</title>
      <link>https://aspdotnet.tistory.com/3424</link>
      <description>&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;venv 로 가상화를 하여 구성할 수도 있지만,&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1751772711750&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Hacker News 프로젝트
가상환경 생성: python3.13 -m venv .hacker_news
가상환경 적용
source .hacker_news/bin/activate
.hacker_news\Scripts\activate
Django 설치: pip install django
루트 디렉토리 생성: mkdir hacker_news
Django 프로젝트 생성: django-admin startproject config hacker_news
프로젝트로 이동: cd hacker_news
Django 서버 실행: python manage.py runserver&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;아래 처럼 uv 를 사용해서도 가상화 환경을 구성할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;009&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/009.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/009.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1.&amp;nbsp;uv&amp;nbsp;설치&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;D:\Source\PythonProject\example&amp;gt;pip&amp;nbsp;install&amp;nbsp;uv&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2.&amp;nbsp;가상환경&amp;nbsp;생성&amp;nbsp;및&amp;nbsp;활성화&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;D:\Source\PythonProject\example&amp;gt;uv&amp;nbsp;venv&amp;nbsp;.hacker_news&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;D:\Source\PythonProject\example&amp;gt;.hacker_news\Scripts\activate&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;h4 data-ke-size=&quot;size20&quot;&gt;3.&amp;nbsp;Django&amp;nbsp;설치&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(.hacker_news) D:\Source\PythonProject\example&amp;gt;uv pip install django&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4.&amp;nbsp;프로젝트&amp;nbsp;디렉토리&amp;nbsp;생성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(.hacker_news)&amp;nbsp;D:\Source\PythonProject\example&amp;gt;mkdir&amp;nbsp;hacker_news&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5.&amp;nbsp;Django&amp;nbsp;프로젝트&amp;nbsp;생성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(.hacker_news)D:\Source\PythonProject\example&amp;gt;django-admin&amp;nbsp;startproject&amp;nbsp;config&amp;nbsp;hacker_news&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;6.&amp;nbsp;프로젝트&amp;nbsp;디렉토리로&amp;nbsp;이동&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(.hacker_news)&amp;nbsp;D:\Source\PythonProject\example&amp;gt;cd&amp;nbsp;hacker_news&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7.&amp;nbsp;Django&amp;nbsp;개발&amp;nbsp;서버&amp;nbsp;실행&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(.hacker_news) D:\Source\PythonProject\example\hacker_news&amp;gt;python manage.py runserver&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1751772652538&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
July 06, 2025 - 12:24:58
Django version 5.2.4, using settings 'config.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

WARNING: This is a development server. Do not use it in a production setting. Use a production WSGI or ASGI server instead.
For more information on production servers see: https://docs.djangoproject.com/en/5.2/howto/deployment/
[06/Jul/2025 12:25:30] &quot;GET / HTTP/1.1&quot; 200 12068
Not Found: /favicon.ico
[06/Jul/2025 12:25:30] &quot;GET /favicon.ico HTTP/1.1&quot; 404 2208&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;/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;&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;</description>
      <category>프로그래밍/PYTHON</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3424</guid>
      <comments>https://aspdotnet.tistory.com/3424#entry3424comment</comments>
      <pubDate>Sun, 6 Jul 2025 12:33:13 +0900</pubDate>
    </item>
    <item>
      <title>Meta Pixel &amp;amp; 전환 API(Conversions API, 이하 CAPI) : 개발자를 위한 가이드</title>
      <link>https://aspdotnet.tistory.com/3423</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Meta Pixel &amp;amp; 전환 API: 개발자를 위한 가이드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Meta 광고 생태계에서 데이터는 단순한 숫자가 아니라 비즈니스의 성패를 좌우하는 핵심 동력입니다. 특히 Meta Pixel(이하 픽셀)과 전환 API(Conversions API, 이하 CAPI)는 그 동력을 만들어내는 양대 엔진과도 같습니다.&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;왜 이렇게 해야 하는지(Why)&lt;/b&gt;, 그리고 **실제 업무에서 어떻게 적용하는지(How)**에 초점을 맞출 것입니다. 거짓 없이, 실제 저희 팀에서 사용하는 코드와 로직을 바탕으로 설명하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Step 0: 왜 픽셀과 CAPI를 함께 써야 하는가?&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 이해해야 할 것은 '픽셀 &lt;b&gt;또는&lt;/b&gt; CAPI'가 아니라 '&lt;b&gt;픽셀과 CAPI'&lt;/b&gt; 라는 점입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Meta Pixel (Client-Side):&lt;/b&gt; 사용자의 브라우저에서 직접 실행되는 JavaScript 코드입니다. 설치가 쉽고, 사용자의 다양한 웹 행동(페이지뷰, 버튼 클릭 등)을 즉각적으로 잡아낼 수 있다는 장점이 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;한계:&lt;/b&gt; iOS 14.5+ 업데이트, 광고 차단기(Ad Blocker), 브라우저의 추적 방지 기능(Safari ITP 등)으로 인해 데이터 수집이 누락될 확률이 매우 높습니다. 즉, &lt;b&gt;신뢰도가 떨어집니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;전환 API (Server-Side):&lt;/b&gt; 여러분의 서버가 Meta의 서버에게 직접 &quot;이런 이벤트가 발생했어&quot;라고 알려주는 방식입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;장점:&lt;/b&gt; 브라우저 환경에 영향을 받지 않으므로 데이터 수집이 매우 &lt;b&gt;안정적이고 신뢰도가 높습니다.&lt;/b&gt; 또한, 결제 완료 후의 CRM 데이터나 오프라인 전환 등 웹사이트를 벗어난 데이터까지 전송할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;한계:&lt;/b&gt; 구현이 픽셀보다 복잡하고, 서버 개발에 대한 이해가 필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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; 두 가지를 함께 사용하는 **'하이브리드 모델'**이 현재의 표준이자 필수입니다. 브라우저에서 놓친 데이터를 서버가 보완해줌으로써, 가장 완전하고 정확한 데이터를 Meta에 전송할 수 있습니다. Meta는 이 두 채널에서 들어온 데이터를 **'이벤트 중복 제거(Event Deduplication)'**를 통해 현명하게 하나로 합쳐줍니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Step 1: 사전 준비 및 아키텍처 설계&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Meta 비즈니스 계정:&lt;/b&gt; 모든 자산(광고 계정, 픽셀 등)을 관리하는 최상위 계정입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Meta 광고 계정:&lt;/b&gt; 광고를 집행할 계정입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Meta 픽셀:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비즈니스 설정 &amp;gt; 데이터 소스 &amp;gt; 픽셀에서 새로 만듭니다.&lt;/li&gt;
&lt;li&gt;만들어진 픽셀의 **'픽셀 ID'**는 반드시 기록해두세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;전환 API 액세스 토큰:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이벤트 관리자 &amp;gt; 해당 픽셀 선택 &amp;gt; 설정 탭으로 이동합니다.&lt;/li&gt;
&lt;li&gt;'전환 API' 섹션에서 &lt;b&gt;'직접 통합'&lt;/b&gt; 아래의 **'액세스 토큰 생성'**을 클릭하여 토큰을 발급받습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;경고:&lt;/b&gt; 이 토큰은 여러분 서버의 비밀 키입니다. 코드에 직접 하드코딩하거나 외부에 노출해서는 절대 안 됩니다. 반드시 환경 변수(Environment Variable) 등을 사용해 안전하게 관리해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;사용자&lt;/b&gt;가 웹사이트에서 특정 행동(예: 구매)을 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;브라우저 (Client-Side):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Meta Pixel이 이벤트를 즉시 감지하고 Meta 서버로 전송합니다.&lt;/li&gt;
&lt;li&gt;이때, &lt;b&gt;고유한 &lt;code&gt;event_id&lt;/code&gt;를 생성&lt;/b&gt;하여 함께 보냅니다.&lt;/li&gt;
&lt;li&gt;동시에, 이 &lt;code&gt;event_id&lt;/code&gt;를 우리 서버로도 전송합니다(예: 주문 정보와 함께).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;내 서버 (Server-Side):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주문 처리를 완료합니다.&lt;/li&gt;
&lt;li&gt;클라이언트로부터 전달받은 &lt;code&gt;event_id&lt;/code&gt;와 주문 정보를 바탕으로 CAPI 요청 데이터를 구성합니다.&lt;/li&gt;
&lt;li&gt;CAPI를 통해 Meta 서버로 동일한 이벤트를 전송합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Meta 서버:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;픽셀과 CAPI로부터 동일한 &lt;code&gt;event_id&lt;/code&gt;를 가진 두 개의 이벤트를 수신합니다.&lt;/li&gt;
&lt;li&gt;이를 중복으로 판단하고, 더 신뢰도 높은 CAPI 데이터를 우선적으로 처리하며 하나로 합칩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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;code&gt;event_id&lt;/code&gt;**를 일치시키는 것이 중복 제거의 핵심입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Step 2: 픽셀(Client-Side) 설치 및 이벤트 전송 코드&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 많이 실수하는 부분은 &lt;code&gt;event_id&lt;/code&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;1. 기본 픽셀 코드 설치 (&lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 태그 안에 삽입)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드는 Meta에서 제공하는 기본 코드입니다. &lt;code&gt;YOUR_PIXEL_ID&lt;/code&gt; 부분만 여러분의 것으로 교체하면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', 'YOUR_PIXEL_ID');
fbq('track', 'PageView');
&amp;lt;/script&amp;gt;
&amp;lt;noscript&amp;gt;&amp;lt;img height=&quot;1&quot; width=&quot;1&quot; style=&quot;display:none&quot;
src=&quot;https://www.facebook.com/tr?id=YOUR_PIXEL_ID&amp;amp;ev=PageView&amp;amp;noscript=1&quot;
/&amp;gt;&amp;lt;/noscript&amp;gt;&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;/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;2. 실무 이벤트 전송 코드 (예: 구매 완료)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Purchase&lt;/code&gt; 이벤트는 가장 중요한 전환 이벤트입니다. 실제 코드에서는 아래와 같이 &lt;code&gt;event_id&lt;/code&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;상황:&lt;/b&gt; 사용자가 '결제하기' 버튼을 눌러 주문이 최종 확정되는 순간.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// [CLIENT-SIDE] /views/purchase-complete.js

// 실무에서는 보통 UUID 라이브러리(e.g., uuid.v4())를 사용하거나, 
// 서버로부터 고유 ID를 받아오는 것이 더 안정적입니다.
// 간단한 예시를 위해 타임스탬프와 랜덤 숫자를 조합합니다.
function generateEventId() {
  return 'evt_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
}

// 결제가 성공적으로 완료되었을 때 호출되는 함수
function handleSuccessfulPurchase(orderData) {
  // 1. 중복 제거를 위한 고유 이벤트 ID 생성
  const eventId = generateEventId();

  // 2. Meta Pixel로 Purchase 이벤트 전송
  fbq('track', 'Purchase', {
    value: orderData.totalPrice, // 총 주문 금액
    currency: 'KRW',             // 통화
    content_ids: orderData.productIds, // 상품 ID 배열 (예: ['prod_123', 'prod_456'])
    content_type: 'product',
  }, {
    eventID: eventId // ★★★★★ 중복 제거를 위한 이벤트 ID 전달
  });

  // 3. 우리 서버로 CAPI 전송을 요청 (이때 eventId를 함께 넘김)
  // 실제로는 fetch, axios 등을 사용한 API 호출 형태가 됩니다.
  fetch('/api/track-conversion', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      orderData: orderData,
      eventId: eventId, // ★★★★★ 서버로 동일한 이벤트 ID 전달
      // 사용자를 특정할 수 있는 추가 정보 (예: 클릭 ID)
      fbc: getCookie('_fbc'), // Facebook Click ID 쿠키 값
      fbp: getCookie('_fbp')  // Facebook Browser ID 쿠키 값
    }),
  })
  .then(response =&amp;gt; response.json())
  .then(data =&amp;gt; console.log('Server-side tracking initiated:', data))
  .catch(error =&amp;gt; console.error('Error initiating server-side tracking:', error));
}

// 쿠키 값을 가져오는 헬퍼 함수 (실제 프로젝트에서는 쿠키 라이브러리 사용 권장)
function getCookie(name) {
  const value = `; ${document.cookie}`;
  const parts = value.split(`; ${name}=`);
  if (parts.length === 2) return parts.pop().split(';').shift();
}

// 예시: 결제 완료 후 이 함수가 호출된다고 가정
const sampleOrderData = {
  orderId: 'ORDER_98765',
  totalPrice: 50000.00,
  productIds: ['SKU_001', 'SKU_002'],
  userEmail: 'test@example.com', // 실제로는 로그인 정보나 입력 폼에서 가져옴
  userPhone: '01012345678'
};
// handleSuccessfulPurchase(sampleOrderData); // 실제로는 결제 완료 콜백에서 실행&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Step 3: 전환 API(Server-Side) 구현 코드 (Node.js 예시)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 클라이언트에서 받은 정보를 바탕으로 서버에서 Meta로 직접 데이터를 쏠 차례입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사전 필요 라이브러리 (Node.js 기준):&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;code&gt;npm install axios crypto&lt;/code&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;axios&lt;/code&gt;: HTTP 요청을 보내기 위한 라이브러리&lt;/li&gt;
&lt;li&gt;&lt;code&gt;crypto&lt;/code&gt;: 사용자 정보를 SHA-256으로 해싱하기 위한 내장 모듈&lt;/li&gt;
&lt;/ul&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;1. API 엔드포인트 구현 (&lt;code&gt;/api/track-conversion&lt;/code&gt;)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// [SERVER-SIDE] /controllers/trackingController.js

const axios = require('axios');
const crypto = require('crypto');

// ★★★★★ 절대로 코드에 직접 넣지 마세요! 환경 변수에서 불러옵니다.
const PIXEL_ID = process.env.META_PIXEL_ID;
const ACCESS_TOKEN = process.env.META_ACCESS_TOKEN;

// 사용자 데이터를 SHA-256으로 해싱하는 함수
function hash(data) {
  if (!data) return null;
  return crypto.createHash('sha256').update(data).digest('hex');
}

// 메인 CAPI 이벤트 전송 함수
async function trackConversionAPI(req, res) {
  try {
    const { orderData, eventId, fbc, fbp } = req.body;
    const clientIpAddress = req.ip; // 혹은 req.headers['x-forwarded-for'] 등 실제 IP
    const userAgent = req.headers['user-agent'];
    const eventTime = Math.floor(Date.now() / 1000); // Unix Timestamp

    // 1. CAPI 요청 페이로드(Payload) 구성
    const payload = {
      data: [
        {
          event_name: 'Purchase',
          event_time: eventTime,
          event_id: eventId, // ★★★★★ 클라이언트에서 받은 바로 그 eventId
          event_source_url: 'https://www.your-website.com/purchase-complete', // 이벤트 발생 URL
          action_source: 'website',
          user_data: {
            em: [hash(orderData.userEmail.toLowerCase())], // 이메일 (소문자로 변환 후 해싱)
            ph: [hash(orderData.userPhone.replace(/[^0-9]/g, ''))], // 전화번호 (숫자만 추출 후 해싱)
            client_ip_address: clientIpAddress,
            client_user_agent: userAgent,
            fbc: fbc || null, // 클릭 ID가 있으면 매칭률 상승
            fbp: fbp || null, // 브라우저 ID가 있으면 매칭률 상승
          },
          custom_data: {
            value: orderData.totalPrice,
            currency: 'KRW',
            content_ids: orderData.productIds,
            content_type: 'product',
            order_id: orderData.orderId // 내부 주문 ID (문제 해결 시 유용)
          },
        }
      ],
      // 테스트 시에만 사용. 실제 운영에서는 삭제하거나 Test Event Code를 환경변수로 관리
      // test_event_code: process.env.META_TEST_EVENT_CODE 
    };

    // 2. Meta Graph API로 POST 요청 전송
    const url = `https://graph.facebook.com/v19.0/${PIXEL_ID}/events?access_token=${ACCESS_TOKEN}`;

    console.log('Sending CAPI Payload:', JSON.stringify(payload, null, 2)); // 디버깅용 로그

    await axios.post(url, payload);

    console.log(`CAPI Event 'Purchase' (Event ID: ${eventId}) sent successfully.`);
    res.status(200).json({ status: 'success', message: 'Conversion tracked' });

  } catch (error) {
    // Axios 에러의 경우, 더 상세한 정보가 error.response.data에 담겨 있습니다.
    const errorMessage = error.response ? JSON.stringify(error.response.data) : error.message;
    console.error('Failed to send CAPI Event:', errorMessage);
    res.status(500).json({ status: 'error', message: 'Failed to track conversion' });
  }
}

module.exports = { trackConversionAPI };&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실무 적용 시 고려사항:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;해싱(Hashing):&lt;/b&gt; &lt;code&gt;user_data&lt;/code&gt;에 포함되는 모든 개인정보(이메일, 전화번호 등)는 반드시 &lt;b&gt;SHA-256&lt;/b&gt;으로 해싱하여 보내야 합니다. 이는 Meta의 정책이자 개인정보보호를 위한 필수 조치입니다. 보내기 전에 정규화(소문자 변환, 특수문자 제거)를 거치면 매칭률이 올라갑니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용자 파라미터 (&lt;code&gt;fbc&lt;/code&gt;, &lt;code&gt;fbp&lt;/code&gt;):&lt;/b&gt; 이 값들은 Meta가 사용자를 더 정확하게 식별하도록 돕는 중요한 쿠키 값입니다. 클라이언트 사이드에서 이 쿠키들을 읽어 서버로 전달해주면 광고 기여도 분석의 정확성이 크게 향상됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;에러 핸들링 및 로깅:&lt;/b&gt; CAPI 요청이 실패할 경우를 대비해, 실패한 이벤트를 잠시 큐(Queue)에 저장했다가 재시도하는 로직(Retry Logic)을 구현하는 것이 좋습니다. 또한 모든 요청과 응답을 상세히 로깅해야 문제 발생 시 원인을 빠르게 파악할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Step 4: 검증 및 디버깅 (시니어의 노하우)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 배포했다고 끝이 아닙니다. 데이터가 제대로 들어가고 있는지 반드시 확인해야 합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;이벤트 관리자 활용:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;테스트 이벤트 도구:&lt;/b&gt; 배포 전, '이벤트 관리자 &amp;gt; 테스트 이벤트' 탭을 활용하세요. 서버에서 &lt;code&gt;test_event_code&lt;/code&gt;를 페이로드에 포함시켜 보내면, 실시간으로 이벤트가 수신되는 것을 확인할 수 있습니다. 픽셀 이벤트와 CAPI 이벤트가 모두 들어오고, '중복 제거됨'이라고 표시되는지 확인해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;개요 및 진단:&lt;/b&gt; '개요' 탭에서 이벤트 수신 현황을 모니터링하고, '진단' 탭에서 중복 제거 비율, 파라미터 매칭 품질 등을 확인하며 지속적으로 최적화해야 합니다. &lt;code&gt;event_id&lt;/code&gt;가 일치하지 않으면 여기에 경고가 표시됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Meta Pixel Helper (Chrome 확장 프로그램):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트 사이드에서 픽셀 이벤트가 정상적으로 발생하는지, &lt;code&gt;eventID&lt;/code&gt;가 올바르게 포함되었는지 실시간으로 확인할 수 있는 필수 도구입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;마치며: 개발자에게 전하는 시니어의 조언&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;완벽보다 시작이 중요합니다.&lt;/b&gt; 처음부터 모든 이벤트를 CAPI로 구현하려 하지 마세요. 가장 중요한 전환 이벤트인 &lt;b&gt;&lt;code&gt;Purchase&lt;/code&gt;&lt;/b&gt; 부터 시작해서 점차 &lt;code&gt;AddToCart&lt;/code&gt;, &lt;code&gt;InitiateCheckout&lt;/code&gt;, &lt;code&gt;Lead&lt;/code&gt; 등으로 확장해나가세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;문서를 믿되, 항상 의심하고 검증하세요.&lt;/b&gt; Meta의 공식 문서는 훌륭한 출발점이지만, 실제 환경에서는 예상치 못한 변수들이 많습니다. 항상 테스트하고, 로깅하고, 실제 데이터를 눈으로 확인하는 습관을 들이세요.&lt;/li&gt;
&lt;/ul&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍/자바스크립트</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3423</guid>
      <comments>https://aspdotnet.tistory.com/3423#entry3423comment</comments>
      <pubDate>Fri, 27 Jun 2025 14:22:28 +0900</pubDate>
    </item>
    <item>
      <title>긴 WAV 음성과 짧은 영상을 자연스럽게 결합하는 방법 (FFmpeg)</title>
      <link>https://aspdotnet.tistory.com/3421</link>
      <description>&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;영상을&amp;nbsp;긴&amp;nbsp;WAV&amp;nbsp;음성&amp;nbsp;파일과&amp;nbsp;결합하여&amp;nbsp;영상이&amp;nbsp;음성&amp;nbsp;길이만큼&amp;nbsp;자연스럽게&amp;nbsp;반복(루프)되게&amp;nbsp;하려면&amp;nbsp;다음&amp;nbsp;방법들을&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있습니다.&amp;nbsp;각&amp;nbsp;방법은&amp;nbsp;영상&amp;nbsp;편집&amp;nbsp;도구의&amp;nbsp;특성에&amp;nbsp;따라&amp;nbsp;선택&amp;nbsp;가능하며,&amp;nbsp;FFmpeg&amp;nbsp;자동화가&amp;nbsp;가장&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-origin-width=&quot;1698&quot; data-origin-height=&quot;1156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbQMP0/btsOOCv9deg/TIPryahGA384zmEFkX8HLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbQMP0/btsOOCv9deg/TIPryahGA384zmEFkX8HLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbQMP0/btsOOCv9deg/TIPryahGA384zmEFkX8HLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbQMP0%2FbtsOOCv9deg%2FTIPryahGA384zmEFkX8HLk%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;1698&quot; height=&quot;1156&quot; data-origin-width=&quot;1698&quot; data-origin-height=&quot;1156&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;윈도우 환경이라면, 아래와 같이 설치 FFmpeg 진행에 맞게 설치 같이 해 보죠.&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://www.gyan.dev/ffmpeg/builds/ffmpeg-git-full.7z&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.gyan.dev/ffmpeg/builds/ffmpeg-git-full.7z&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;압축을 해제하고 ffmpeg 폴더 명칭으로 변경합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;157&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZRlx0/btsONMT45mU/8Rv7QXLu5IlEsNkymRXg8k/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZRlx0/btsONMT45mU/8Rv7QXLu5IlEsNkymRXg8k/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZRlx0/btsONMT45mU/8Rv7QXLu5IlEsNkymRXg8k/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZRlx0%2FbtsONMT45mU%2F8Rv7QXLu5IlEsNkymRXg8k%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;802&quot; height=&quot;157&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;157&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;&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-origin-width=&quot;409&quot; data-origin-height=&quot;464&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3DwUA/btsOPyUnA4o/o0Vk25kCqe3KlFdrIRzcm1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3DwUA/btsOPyUnA4o/o0Vk25kCqe3KlFdrIRzcm1/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3DwUA/btsOPyUnA4o/o0Vk25kCqe3KlFdrIRzcm1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3DwUA%2FbtsOPyUnA4o%2Fo0Vk25kCqe3KlFdrIRzcm1%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;409&quot; height=&quot;464&quot; data-origin-width=&quot;409&quot; data-origin-height=&quot;464&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 변수의 path 에 해당 경로를 추가합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;594&quot; data-origin-height=&quot;237&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dxknf9/btsOOevMqKx/7Y3dk58wXtDhLOsttUjdJk/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dxknf9/btsOOevMqKx/7Y3dk58wXtDhLOsttUjdJk/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dxknf9/btsOOevMqKx/7Y3dk58wXtDhLOsttUjdJk/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdxknf9%2FbtsOOevMqKx%2F7Y3dk58wXtDhLOsttUjdJk%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;594&quot; height=&quot;237&quot; data-origin-width=&quot;594&quot; data-origin-height=&quot;237&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ffmpeg 폴더 내부의 bin 폴더로 지정하여 저장합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;527&quot; data-origin-height=&quot;501&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pLws6/btsOPzMt9Mo/YayNjmrMKwwBQqPXw45zn1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pLws6/btsOPzMt9Mo/YayNjmrMKwwBQqPXw45zn1/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pLws6/btsOPzMt9Mo/YayNjmrMKwwBQqPXw45zn1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpLws6%2FbtsOPzMt9Mo%2FYayNjmrMKwwBQqPXw45zn1%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;527&quot; height=&quot;501&quot; data-origin-width=&quot;527&quot; data-origin-height=&quot;501&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;Command Prompt 나 PowerShell 을 새롭게 열어서 ffmpeg 명령어를 입력 후 실행해서 제대로 작동하는지 확인합니다&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;800&quot; data-origin-height=&quot;91&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MSCHu/btsONDCPuOi/NFWHetcUIzn4g6dN79akI1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MSCHu/btsONDCPuOi/NFWHetcUIzn4g6dN79akI1/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MSCHu/btsONDCPuOi/NFWHetcUIzn4g6dN79akI1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMSCHu%2FbtsONDCPuOi%2FNFWHetcUIzn4g6dN79akI1%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;800&quot; height=&quot;91&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;91&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&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;특정 폴더에 짧은 영상 &quot;short_video.mp4&quot; 파일을 준비하며, 매우 긴 음성파일인 &quot;long_audio.wav&quot; 을 동일한 폴더 내에 복사해 둡니다. 그리고 아래 명령어를 Command Prompt 나 PowerShell 에서 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1750743803806&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ffmpeg -stream_loop -1 -i short_video.mp4 -i long_audio.wav -shortest -c:v libx264 -c:a aac output.mp4&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵션 설명:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;-stream_loop -1: 영상을 무한 반복합니다.&lt;/li&gt;
&lt;li&gt;-shortest: 오디오 종료 시 영상도 자동 종료됩니다.&lt;/li&gt;
&lt;li&gt;-c:v&amp;nbsp;libx264:&amp;nbsp;영상을&amp;nbsp;H.264로&amp;nbsp;인코딩&amp;nbsp;(호환성&amp;nbsp;향상).&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음성 파일의 긴 시간에 맞게 만들어지고, 그 시간에 맞게 파일이 음성과 영상이 합쳐져 만들어 집니다. 최종 파일은 output.mp4 파일이 생성됩니다.&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;720&quot; data-origin-height=&quot;106&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dwiyBI/btsOPwWASIG/yrUgd06slReVrOkeq7T3l1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dwiyBI/btsOPwWASIG/yrUgd06slReVrOkeq7T3l1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dwiyBI/btsOPwWASIG/yrUgd06slReVrOkeq7T3l1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdwiyBI%2FbtsOPwWASIG%2FyrUgd06slReVrOkeq7T3l1%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;720&quot; height=&quot;106&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;106&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;&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;&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;&lt;a href=&quot;https://phoenixnap.com/kb/ffmpeg-windows&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://phoenixnap.com/kb/ffmpeg-windows&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.gyan.dev/ffmpeg/builds/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.gyan.dev/ffmpeg/builds/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍/기타(툴)</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3421</guid>
      <comments>https://aspdotnet.tistory.com/3421#entry3421comment</comments>
      <pubDate>Tue, 24 Jun 2025 20:52:11 +0900</pubDate>
    </item>
    <item>
      <title>AWS Summit Seoul 2025 Tech Notes - .NET을 위한 Amazon Q Developer Transformation 기능</title>
      <link>https://aspdotnet.tistory.com/3418</link>
      <description>&lt;p style=&quot;color: #000000; text-align: start;&quot; data-line=&quot;0&quot; 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;812&quot; data-origin-height=&quot;455&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/obvpe/btsOvgACzjp/gASvTo3awKSPSjNK5Kwkx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/obvpe/btsOvgACzjp/gASvTo3awKSPSjNK5Kwkx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/obvpe/btsOvgACzjp/gASvTo3awKSPSjNK5Kwkx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fobvpe%2FbtsOvgACzjp%2FgASvTo3awKSPSjNK5Kwkx0%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;812&quot; height=&quot;455&quot; data-origin-width=&quot;812&quot; data-origin-height=&quot;455&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-line=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-line=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-line=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&quot;Amazon Q Developer&quot;는 AWS에서 제공하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;애플리케이션 현대화를 위한 생성형 AI 기반 어시스턴트&lt;/b&gt;입니다. 특히 애플리케이션 업그레이드 및 변환 프로세스를 가속화하고 단순화하는 데 도움을 주기 위해 설계되었습니다.&lt;/p&gt;
&lt;h3 id=&quot;amazon-q-developer%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80%EC%9A%94&quot; style=&quot;color: #000000; text-align: start;&quot; data-line=&quot;2&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Amazon Q Developer는 무엇인가요?&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-line=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;Amazon Q Developer는 복잡한 레거시 애플리케이션을 현대화하는 과정을 지원하는 강력한 AI 도구입니다. 마치&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;개발팀의 한 일원&lt;/b&gt;처럼 작동하여 애플리케이션 코드를 이해하고, 현대화 계획을 세우며, 실제 변환 작업을 수행하는 데 기여합니다. 특히,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;개발자를 위한 IDE (통합 개발 환경) 경험&lt;/b&gt;을 제공하며, 웹 콘솔을 통해서도 사용할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-line=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-line=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;net%EC%9D%84-%EC%9C%84%ED%95%9C-amazon-q-developer-transformation-%EA%B8%B0%EB%8A%A5%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80%EC%9A%94&quot; style=&quot;color: #000000; text-align: start;&quot; data-line=&quot;2&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;.NET을 위한 Amazon Q Developer Transformation 기능이란 무엇인가요?&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-line=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;이 기능은 간단히 말해, 기존의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Windows 기반 .NET Framework 애플리케이션을 Linux 기반의 크로스 플랫폼 .NET 애플리케이션으로 전환(이식)하는 과정을 자동화하고 가속화&lt;/b&gt;해주는 Amazon Q Developer의 핵심 기능입니다. 특히&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;생성형 AI 기술&lt;/b&gt;을 활용하여 이 복잡한 변환 작업을 훨씬 빠르고 효율적으로 만들어 줍니다.&lt;/p&gt;
&lt;h3 id=&quot;%EC%99%9C-%EC%9D%B4-%EA%B8%B0%EB%8A%A5%EC%9D%B4-%ED%95%84%EC%9A%94%ED%95%9C%EA%B0%80%EC%9A%94-net-%ED%81%AC%EB%A1%9C%EC%8A%A4-%ED%94%8C%EB%9E%AB%ED%8F%BC-%EC%A0%84%ED%99%98%EC%9D%98-%EC%96%B4%EB%A0%A4%EC%9B%80&quot; style=&quot;color: #000000; text-align: start;&quot; data-line=&quot;6&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;왜 이 기능이 필요한가요? (.NET 크로스 플랫폼 전환의 어려움)&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-line=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;기존의 .NET 애플리케이션을 Windows 환경에서 Linux 환경으로 옮기는 작업은 생각보다 복잡하고 어려웠습니다. 개발자라면 특히 더 부담스러울 수 있는 이유들은 다음과 같습니다:&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-line=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-line=&quot;10&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;10&quot;&gt;&lt;b&gt;수동 작업의 복잡성&lt;/b&gt;: 소스 코드 분석, 호환성 체크, 코드 포팅, 검증, 배포 등 여러 단계에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;수동 코드 재작성, 오류 처리, 팀 간 조정&lt;/b&gt;이 많이 필요했습니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li data-line=&quot;11&quot;&gt;&lt;b&gt;높은 노동 집약도 및 오류 발생&lt;/b&gt;: 이 과정은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;노동 집약적&lt;/b&gt;이고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;에러가 발생하기 쉽고&lt;/b&gt;, 팀 협업에도 병목 현상을 유발할 수 있습니다. 전체 애플리케이션 포트폴리오에 대해 이 작업을 반복해야 하므로 시간 소모가 큽니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li data-line=&quot;12&quot;&gt;&lt;b&gt;비용 절감 및 혁신 가속화의 필요성&lt;/b&gt;: 기업들은 Windows 및 SQL Server 라이선스 비용 절감, 더 탄력적인 아키텍처 구축, ARM64 아키텍처를 통한 가격 대비 성능 향상 등을 통해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;컴퓨팅 비용을 절감&lt;/b&gt;하고 싶어 합니다. 또한, 모놀리식(Monolithic) 애플리케이션을 마이크로서비스로 분해하거나 서버리스 아키텍처를 채택하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;혁신 속도를 가속화&lt;/b&gt;하려는 요구도 큽니다. 이러한 목표를 달성하기 위해 .NET 애플리케이션의 크로스 플랫폼 전환은 필수적이지만, 기존 방식으로는 어렵습니다. 예를 들어, 동일한 Amazon EC2 인스턴스 유형/크기라도 Windows 대신 Linux를 사용하면 최대 50.8%의 비용을 절감할 수 있으며, Graviton 인스턴스로 이전하면 추가적인 비용 절감이 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;amazon-q-developer-transformation-%EA%B8%B0%EB%8A%A5%EC%9D%80-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%8F%95%EB%82%98%EC%9A%94&quot; style=&quot;color: #000000; text-align: start;&quot; data-line=&quot;14&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Amazon Q Developer Transformation 기능은 어떻게 돕나요?&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-line=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;Amazon Q Developer는 이러한 문제점들을 해결하기 위해 다음과 같은 기능을 제공합니다:&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-line=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-line=&quot;18&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;18&quot;&gt;&lt;b&gt;생성형 AI Transformation 에이전트 활용&lt;/b&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;생성형 AI 기반의 에이전트&lt;/b&gt;를 사용하여 수백 개의 .NET 애플리케이션을 Linux로 이식하는 과정을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;가속화&lt;/b&gt;합니다. 이 에이전트는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;IDE(통합 개발 환경)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;기반으로도 작동하여 개발자가 친숙한 환경에서 작업할 수 있도록 돕습니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li data-line=&quot;19&quot;&gt;&lt;b&gt;웹 콘솔을 통한 연결&lt;/b&gt;: 웹 경험을 통해 소스 코드 저장소에 쉽게 연결할 수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li data-line=&quot;20&quot;&gt;&lt;b&gt;자동화된 변환 과정&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;21&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;21&quot;&gt;&lt;b&gt;코드 분석&lt;/b&gt;: 애플리케이션 코드를 분석합니다.&lt;/li&gt;
&lt;li data-line=&quot;22&quot;&gt;&lt;b&gt;변환&lt;/b&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;자동으로 코드를 변환&lt;/b&gt;하여 Windows 종속성 문제를 해결하고 Linux 기반 크로스 플랫폼으로 포팅합니다.&lt;/li&gt;
&lt;li data-line=&quot;23&quot;&gt;&lt;b&gt;사람의 감독&lt;/b&gt;: 이 과정에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;사람의 감독&lt;/b&gt;을 통해 AI가 제안하는 변환 내용을 검토하고 필요 시 조정할 수 있습니다. (이는 Amazon Q Developer의 전반적인 &quot;휴먼 인 더 루프&quot; 프로세스의 일부입니다).&lt;/li&gt;
&lt;li data-line=&quot;24&quot;&gt;&lt;b&gt;Linux 지원 애플리케이션 생성&lt;/b&gt;: 변환된 애플리케이션은 Linux 환경에서 실행 가능하도록 지원됩니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;25&quot;&gt;&lt;b&gt;Visual Studio용 Q 포함된 AWS 툴킷&lt;/b&gt;: 개발자들이 많이 사용하는 Visual Studio IDE 안에서도 Amazon Q Developer 기능을 활용할 수 있는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;AWS 툴킷&lt;/b&gt;을 제공합니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;%EC%9D%B4-%EA%B8%B0%EB%8A%A5%EC%9D%98-%EC%A3%BC%EC%9A%94-%EC%9D%B4%EC%A0%90%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80%EC%9A%94&quot; style=&quot;color: #000000; text-align: start;&quot; data-line=&quot;27&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;이 기능의 주요 이점은 무엇인가요?&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-line=&quot;29&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;.NET을 위한 Amazon Q Developer Transformation 기능을 사용하면 다음과 같은 이점들을 얻을 수 있습니다:&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-line=&quot;31&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;31&quot;&gt;&lt;b&gt;.NET 애플리케이션 이식 속도 4배 향상&lt;/b&gt;: 수동으로 처리하던 작업을 AI가 가속화하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;이전보다 4배 빠르게&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;애플리케이션을 Linux로 이식할 수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li data-line=&quot;32&quot;&gt;&lt;b&gt;소프트웨어 품질/성능/보안 개선&lt;/b&gt;: AI의 도움으로 변환된 코드의 품질이 향상되고, 이는 전반적인 소프트웨어 성능과 보안 개선으로 이어질 수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li data-line=&quot;33&quot;&gt;&lt;b&gt;프로젝트 생산성 향상 및 40% 비용 효율화&lt;/b&gt;: 복잡성과 시간을 줄여 프로젝트의 생산성을 크게 향상시키며,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;최대 40%의 비용 효율화&lt;/b&gt;를 달성할 수 있습니다. 이는 주로 라이선스 비용 절감과 운영 효율성 증대에서 기인합니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li data-line=&quot;34&quot;&gt;&lt;b&gt;복잡성 해소 및 효율성 향상&lt;/b&gt;: .NET 애플리케이션을 Linux로 포팅하는 복잡한 과정을 자동화하여 개발자의 부담을 줄이고 작업 효율성을 높여줍니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-line=&quot;36&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-line=&quot;36&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-line=&quot;36&quot; data-ke-size=&quot;size16&quot;&gt;요약하자면, .NET을 위한 Amazon Q Developer Transformation 기능은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Windows 환경의 .NET 애플리케이션을 Linux 기반으로 현대화하려는 팀&lt;/b&gt;에게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;시간, 비용, 그리고 개발자의 수고를 획기적으로 줄여주는 강력한 생성형 AI 기반 도구&lt;/b&gt;입니다. 개발자도 이 도구의 도움을 받아 복잡한 현대화 프로젝트에 더 쉽게 참여하고 기여할 수 있게 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-line=&quot;60&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-line=&quot;60&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-line=&quot;60&quot; data-ke-size=&quot;size20&quot;&gt;영상 demo 화면 캡쳐&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1101&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2voDi/btsOv0quqkC/puvkERuzV4FlHT7XFv0bik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2voDi/btsOv0quqkC/puvkERuzV4FlHT7XFv0bik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2voDi/btsOv0quqkC/puvkERuzV4FlHT7XFv0bik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2voDi%2FbtsOv0quqkC%2FpuvkERuzV4FlHT7XFv0bik%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;1101&quot; height=&quot;622&quot; data-origin-width=&quot;1101&quot; data-origin-height=&quot;622&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;&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;1115&quot; data-origin-height=&quot;623&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1JnKR/btsOwuECjHD/VBuk3CnAOJR1bofKKjCGaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1JnKR/btsOwuECjHD/VBuk3CnAOJR1bofKKjCGaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1JnKR/btsOwuECjHD/VBuk3CnAOJR1bofKKjCGaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1JnKR%2FbtsOwuECjHD%2FVBuk3CnAOJR1bofKKjCGaK%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;1115&quot; height=&quot;623&quot; data-origin-width=&quot;1115&quot; data-origin-height=&quot;623&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;&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;1153&quot; data-origin-height=&quot;615&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGT5be/btsOwE1cKYw/ITAtDDMw8doqh6AwTuSvAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGT5be/btsOwE1cKYw/ITAtDDMw8doqh6AwTuSvAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGT5be/btsOwE1cKYw/ITAtDDMw8doqh6AwTuSvAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGT5be%2FbtsOwE1cKYw%2FITAtDDMw8doqh6AwTuSvAK%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;1153&quot; height=&quot;615&quot; data-origin-width=&quot;1153&quot; data-origin-height=&quot;615&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;&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;852&quot; data-origin-height=&quot;412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfMWIB/btsOuAM6yrJ/EzJtbkOH7odDsbZnZRyAN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfMWIB/btsOuAM6yrJ/EzJtbkOH7odDsbZnZRyAN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfMWIB/btsOuAM6yrJ/EzJtbkOH7odDsbZnZRyAN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfMWIB%2FbtsOuAM6yrJ%2FEzJtbkOH7odDsbZnZRyAN1%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;852&quot; height=&quot;412&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;412&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;639&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d2T4y0/btsOwRfjqSl/aPeKka41o3DhwPhbc0Bxk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d2T4y0/btsOwRfjqSl/aPeKka41o3DhwPhbc0Bxk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d2T4y0/btsOwRfjqSl/aPeKka41o3DhwPhbc0Bxk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd2T4y0%2FbtsOwRfjqSl%2FaPeKka41o3DhwPhbc0Bxk0%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;639&quot; height=&quot;628&quot; data-origin-width=&quot;639&quot; data-origin-height=&quot;628&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-origin-width=&quot;930&quot; data-origin-height=&quot;294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQxomV/btsOuCc5j5C/4UgV85vqw5SATavMKIgGBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQxomV/btsOuCc5j5C/4UgV85vqw5SATavMKIgGBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQxomV/btsOuCc5j5C/4UgV85vqw5SATavMKIgGBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQxomV%2FbtsOuCc5j5C%2F4UgV85vqw5SATavMKIgGBk%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;930&quot; height=&quot;294&quot; data-origin-width=&quot;930&quot; data-origin-height=&quot;294&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;&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;457&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzrKlY/btsOuCRKhvn/aJWmHWIcXRFV23E6dT8EpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzrKlY/btsOuCRKhvn/aJWmHWIcXRFV23E6dT8EpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzrKlY/btsOuCRKhvn/aJWmHWIcXRFV23E6dT8EpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzrKlY%2FbtsOuCRKhvn%2FaJWmHWIcXRFV23E6dT8EpK%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;850&quot; height=&quot;457&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;457&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-origin-width=&quot;854&quot; data-origin-height=&quot;553&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3NEyg/btsOwOJEcHI/pPXqOyb0ndDYsm0KfoxNmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3NEyg/btsOwOJEcHI/pPXqOyb0ndDYsm0KfoxNmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3NEyg/btsOwOJEcHI/pPXqOyb0ndDYsm0KfoxNmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3NEyg%2FbtsOwOJEcHI%2FpPXqOyb0ndDYsm0KfoxNmK%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;854&quot; height=&quot;553&quot; data-origin-width=&quot;854&quot; data-origin-height=&quot;553&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-origin-width=&quot;1096&quot; data-origin-height=&quot;396&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKPhLK/btsOvXAA9vh/vDtKT3jTlm4svKkizGmzE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKPhLK/btsOvXAA9vh/vDtKT3jTlm4svKkizGmzE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKPhLK/btsOvXAA9vh/vDtKT3jTlm4svKkizGmzE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKPhLK%2FbtsOvXAA9vh%2FvDtKT3jTlm4svKkizGmzE1%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;1096&quot; height=&quot;396&quot; data-origin-width=&quot;1096&quot; data-origin-height=&quot;396&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;참고사이트&lt;/h4&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://docs.aws.amazon.com/ko_kr/amazonq/latest/qdeveloper-ug/what-is.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.aws.amazon.com/ko_kr/amazonq/latest/qdeveloper-ug/what-is.html&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;&lt;a href=&quot;https://aws.amazon.com/ko/blogs/korea/aws-summit-seoul-2025-generative-ai-from-imagination-to-reality/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://aws.amazon.com/ko/blogs/korea/aws-summit-seoul-2025-generative-ai-from-imagination-to-reality/&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI 관련/AI 기술</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3418</guid>
      <comments>https://aspdotnet.tistory.com/3418#entry3418comment</comments>
      <pubDate>Wed, 11 Jun 2025 10:45:44 +0900</pubDate>
    </item>
    <item>
      <title>ASPNETCORE : .gitignore 파일</title>
      <link>https://aspdotnet.tistory.com/3416</link>
      <description>&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;a href=&quot;https://www.toptal.com/developers/gitignore/api/aspnetcore&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.toptal.com/developers/gitignore/api/aspnetcore&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1749543679752&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Created by https://www.toptal.com/developers/gitignore/api/aspnetcore
# Edit at https://www.toptal.com/developers/gitignore?templates=aspnetcore

### ASPNETCore ###
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.

# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates

# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs

# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/

# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/

# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*

# NUNIT
*.VisualState.xml
TestResult.xml

# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c

# DNX
project.lock.json
project.fragment.lock.json
artifacts/

*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc

# Chutzpah Test files
_Chutzpah*

# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb

# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap

# TFS 2012 Local Workspace
$tf/

# Guidance Automation Toolkit
*.gpState

# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user

# JustCode is a .NET coding add-in
.JustCode

# TeamCity is a build add-in
_TeamCity*

# DotCover is a Code Coverage Tool
*.dotCover

# Visual Studio code coverage results
*.coverage
*.coveragexml

# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*

# MightyMoose
*.mm.*
AutoTest.Net/

# Web workbench (sass)
.sass-cache/

# Installshield output folder
[Ee]xpress/

# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html

# Click-Once directory
publish/

# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj

# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/

# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets

# Microsoft Azure Build Output
csx/
*.build.csdef

# Microsoft Azure Emulator
ecf/
rcf/

# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt

# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/

# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs

# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/

# RIA/Silverlight projects
Generated_Code/

# Backup &amp;amp; report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm

# SQL Server files
*.mdf
*.ldf

# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings

# Microsoft Fakes
FakesAssemblies/

# GhostDoc plugin setting file
*.GhostDoc.xml

# Node.js Tools for Visual Studio
.ntvs_analysis.dat

# Visual Studio 6 build log
*.plg

# Visual Studio 6 workspace options file
*.opt

# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions

# Paket dependency manager
.paket/paket.exe
paket-files/

# FAKE - F# Make
.fake/

# JetBrains Rider
.idea/
*.sln.iml

# CodeRush
.cr/

# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc

# Cake - Uncomment if you are using it
# tools/

# End of https://www.toptal.com/developers/gitignore/api/aspnetcore&lt;/code&gt;&lt;/pre&gt;</description>
      <category>닷넷관련/ASP.NET CORE  </category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3416</guid>
      <comments>https://aspdotnet.tistory.com/3416#entry3416comment</comments>
      <pubDate>Tue, 10 Jun 2025 17:21:46 +0900</pubDate>
    </item>
    <item>
      <title>윈도우에서 Git 설치 가이드 (2025년 최신)</title>
      <link>https://aspdotnet.tistory.com/3415</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 가이드는 2025년 최신 자료를 바탕으로, 주니어 개발자도 쉽게 따라할 수 있도록 단계별로 설명합니다. 기존 가이드에서 개선할 점과 추가 정보를 반영했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. Git이란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git은 소스 코드의 변경 이력을 관리하고, 여러 명이 동시에 협업할 수 있게 도와주는 분산 버전 관리 시스템입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;주요 기능:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드 변경 이력 추적&lt;/li&gt;
&lt;li&gt;협업 환경 제공&lt;/li&gt;
&lt;li&gt;이전 상태로 손쉽게 복구&lt;/li&gt;
&lt;li&gt;오픈소스, 무료로 사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 설치 전 준비사항&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;윈도우 10 이상 권장&lt;/li&gt;
&lt;li&gt;인터넷 연결 필요&lt;/li&gt;
&lt;li&gt;관리자 권한이 있는 계정&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. Git 다운로드 및 설치&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;공식 사이트에서 설치 파일 받기&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;웹 브라우저를 열고 &lt;a href=&quot;https://git-scm.com/downloads/win&quot;&gt;git-scm 공식 사이트&lt;/a&gt;에 접속합니다.&lt;/li&gt;
&lt;li&gt;&quot;Download for Windows&quot; 버튼을 클릭해 최신 버전(예: 2.49.0, 2025년 3월 기준)을 다운로드합니다&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;설치 파일 실행 및 옵션 선택&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;다운로드한 설치 파일(&lt;code&gt;Git-2.xx.x-64-bit.exe&lt;/code&gt;)을 더블 클릭합니다.&lt;/li&gt;
&lt;li&gt;&quot;사용자 계정 컨트롤&quot; 창이 뜨면 &quot;예&quot;를 클릭합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;라이선스 동의&lt;/b&gt;: &quot;Next&quot; 클릭&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설치 경로 선택&lt;/b&gt;: 기본값 사용 권장, &quot;Next&quot; 클릭&lt;/li&gt;
&lt;li&gt;&lt;b&gt;컴포넌트 선택&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;Git Bash&quot;, &quot;Git GUI&quot; 체크 권장&lt;/li&gt;
&lt;li&gt;&quot;Windows Explorer integration&quot; 체크 시 폴더에서 바로 Git Bash 실행 가능&lt;/li&gt;
&lt;li&gt;&quot;Desktop Shortcut&quot; 체크하면 바탕화면에 바로가기 생성&lt;/li&gt;
&lt;li&gt;기타 옵션은 기본값 유지, &quot;Next&quot; 클릭[4][9]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;시작 메뉴 폴더&lt;/b&gt;: 기본값 또는 &quot;Don't create...&quot; 선택 가능, &quot;Next&quot;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기본 에디터 선택&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Visual Studio Code, Vim, Nano 등 선택 가능&lt;/li&gt;
&lt;li&gt;VS Code 설치되어 있다면 선택 추천&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기본 브랜치 이름&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;Override the default branch name for new repositories&quot; 선택 후 &lt;code&gt;main&lt;/code&gt; 입력 권장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;환경 변수 등록&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;Git from the command line and also from 3rd-party software&quot; 선택&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;OpenSSH 설정&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;Use bundled OpenSSH&quot; 선택&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;HTTPS 백엔드&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;Use the OpenSSL library&quot; 선택&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;줄바꿈 방식&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;Checkout Windows-style, commit Unix-style line endings&quot; 선택&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;터미널 에뮬레이터&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;Use MinTTY (the default terminal of MSYS2)&quot; 선택&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기타 옵션&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;Enable file system caching&quot; 체크&lt;/li&gt;
&lt;li&gt;&quot;Enable Git Credential Manager&quot; 체크&lt;/li&gt;
&lt;li&gt;&quot;Enable symbolic links&quot;은 필요시 체크&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&quot;Install&quot; 클릭하여 설치 진행&lt;/li&gt;
&lt;li&gt;설치 완료 후 &quot;Finish&quot; 클릭&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 설치 확인 및 초기 세팅&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;설치 확인&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Win + S&lt;/code&gt; &amp;rarr; &quot;Git Bash&quot; 또는 &quot;명령 프롬프트&quot; 실행&lt;/li&gt;
&lt;li&gt;아래 명령어로 버전 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;git --version&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예시 출력: &lt;code&gt;git version 2.49.0.windows.1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;기본 사용자 정보 등록&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git을 처음 사용할 때 한 번만 등록하면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;git config --global user.name &quot;홍길동&quot;
git config --global user.email &quot;your_email@example.com&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;도움말 확인&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;git help -a&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자주 쓰는 명령어 목록 확인 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. Git Bash &amp;amp; Git GUI 활용&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Git Bash&lt;/b&gt;: 리눅스 명령어와 Git 명령어를 함께 쓸 수 있는 터미널&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Git GUI&lt;/b&gt;: 마우스로 Git 기능을 사용할 수 있는 그래픽 툴 (주니어에게 추천)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Windows Explorer Integration&lt;/b&gt;: 폴더에서 마우스 우클릭 &amp;rarr; &quot;Git Bash Here&quot;로 바로 진입&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6. 새 폴더에서 Git 저장소 만들기&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;새 폴더 생성 (예: &lt;code&gt;C:\gitTest&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;폴더에서 마우스 우클릭 &amp;rarr; &quot;Git Bash Here&quot; 선택&lt;/li&gt;
&lt;li&gt;아래 명령어 입력&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;git init&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;.git&lt;/code&gt; 폴더가 생성되면 성공&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;7. 파일 추가 및 커밋&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;# 파일 추가
git add 파일명.txt

# 모든 파일 추가
git add .

# 상태 확인
git status

# 커밋
git commit -m &quot;첫 커밋 메시지&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;8. 원격 저장소(예: GitHub) 연결 (선택)&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;GitHub 등에서 새 저장소 생성&lt;/li&gt;
&lt;li&gt;아래 명령어로 원격 저장소 연결&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;git remote add origin https://github.com/사용자명/저장소명.git
git push -u origin main&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SSH 사용 시 별도 키 등록 필요[6]&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;9. 설치 및 사용 팁&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;winget으로 설치&lt;/b&gt;:&lt;br /&gt;최신 Windows에서는 아래 명령어로도 설치 가능:&lt;/li&gt;
&lt;li&gt;&lt;code&gt;winget install --id Git.Git -e --source winget&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;업데이트&lt;/b&gt;:&lt;br /&gt;새 버전이 나오면 공식 사이트에서 재설치하면 자동으로 업그레이드됨&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설치 후 환경 변수 자동 등록&lt;/b&gt;&lt;br /&gt;(별도 설정 필요 없음)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;10. 자주 하는 실수 &amp;amp; 주의사항&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;줄바꿈 문제&lt;/b&gt;:&lt;br /&gt;Windows와 Unix 줄바꿈 방식이 달라서, &quot;Checkout Windows-style, commit Unix-style line endings&quot; 옵션을 권장&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이름/이메일 오타&lt;/b&gt;:&lt;br /&gt;커밋 기록에 남으므로 정확히 입력&lt;/li&gt;
&lt;li&gt;&lt;b&gt;폴더 경로 한글/공백 주의&lt;/b&gt;:&lt;br /&gt;가급적 영문, 공백 없는 경로 사용 추천&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;11. 추가 참고자료&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/doc&quot;&gt;공식 Git 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/results?search_query=git+windows+2025&quot;&gt;초보자용 Git 튜토리얼&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://phoenixnap.com/kb/how-to-install-git-windows&quot;&gt;Git Bash 명령어 모음&lt;/a&gt;[6]&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Git for Windows 설치 옵션 상세 설명&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 Git for Windows 설치 시 자주 보게 되는 세 가지 옵션의 기능과 체크 여부를 판단할 수 있도록 자세히 설명한 내용입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. Enable file system caching&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;기능 설명:&lt;/b&gt;&lt;br /&gt;이 옵션을 체크하면 Git이 파일 시스템 정보를 메모리에 캐싱합니다. 즉, Git이 파일이나 폴더의 변경 여부를 확인할 때 매번 디스크를 직접 읽지 않고, 한 번 읽어온 정보를 일정 시간 메모리에 저장해두었다가 재사용합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;장점:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Git 명령어(&lt;code&gt;git status&lt;/code&gt;, &lt;code&gt;git add&lt;/code&gt;, &lt;code&gt;git commit&lt;/code&gt; 등) 실행 속도가 빨라집니다.&lt;/li&gt;
&lt;li&gt;대용량 프로젝트나 파일이 많은 저장소에서 더욱 효과적입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적인 사용 환경에서는 특별한 부작용이 없지만, 매우 드물게 파일 시스템의 변경을 실시간으로 반영하지 못하는 상황이 있을 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;체크 권장 여부:&lt;/b&gt;&lt;br /&gt;대부분의 경우 체크(활성화)하는 것이 좋습니다. 성능 향상 효과가 크고, 특별한 문제가 발생하는 경우는 드뭅니다[1][4][6].&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. Enable Git Credential Manager&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;기능 설명:&lt;/b&gt;&lt;br /&gt;Git Credential Manager(GCM)는 원격 저장소(예: GitHub, GitLab 등)에 접근할 때 사용하는 아이디/비밀번호, 토큰 등 인증 정보를 안전하게 저장하고 관리해주는 도구입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;주요 역할:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최초 인증 정보를 입력하면, 이후에는 자동으로 인증 정보를 불러와 입력 과정을 생략할 수 있습니다.&lt;/li&gt;
&lt;li&gt;인증 정보를 Windows 자격 증명 관리자(Windows Credential Store)에 암호화하여 저장하므로, 보안성이 높습니다.&lt;/li&gt;
&lt;li&gt;SSH, HTTPS 모두 지원하며, WSL(Windows Subsystem for Linux)에서도 연동 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;장점:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;매번 비밀번호를 입력하지 않아도 되어 편리합니다.&lt;/li&gt;
&lt;li&gt;인증 정보가 안전하게 관리됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;별도의 인증 도구 사용을 원하지 않는 경우(예: 직접 관리하고 싶을 때)에는 비활성화할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;체크 권장 여부:&lt;/b&gt;&lt;br /&gt;대부분의 사용자는 체크(활성화)하는 것이 편리하고 안전합니다. 기업/조직 정책에 따라 직접 관리가 필요한 경우에만 비활성화하면 됩니다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. Enable symbolic links&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;기능 설명:&lt;/b&gt;&lt;br /&gt;이 옵션을 체크하면 Git이 윈도우에서도 심볼릭 링크(바로가기와 유사한 특수 파일)를 제대로 생성하고 추적할 수 있도록 해줍니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;심볼릭 링크란?&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일 또는 폴더에 대한 &quot;참조&quot;를 만들어주는 일종의 링크입니다.&lt;/li&gt;
&lt;li&gt;예를 들어, &lt;code&gt;A.txt&lt;/code&gt;라는 파일의 심볼릭 링크를 &lt;code&gt;B.txt&lt;/code&gt;로 만들면, &lt;code&gt;B.txt&lt;/code&gt;를 열었을 때 실제로는 &lt;code&gt;A.txt&lt;/code&gt;의 내용을 보게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;주의사항:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;윈도우에서 심볼릭 링크를 사용하려면 &quot;SeCreateSymbolicLink&quot; 권한(일반적으로 관리자 권한)이 필요합니다.&lt;/li&gt;
&lt;li&gt;Windows 10 이상에서는 &quot;개발자 모드&quot;를 활성화하면 일반 사용자도 심볼릭 링크를 만들 수 있습니다.&lt;/li&gt;
&lt;li&gt;기존에 이미 클론한 저장소에는 이 설정이 바로 적용되지 않습니다. 새로 클론할 때만 적용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;장점:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리눅스/유닉스 환경과 동일하게 프로젝트를 관리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;오픈소스 프로젝트 등에서 심볼릭 링크를 사용하는 경우 필수입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;권한 문제가 발생할 수 있고, 심볼릭 링크를 잘 모르면 혼란을 줄 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;체크 권장 여부:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오픈소스 프로젝트 등에서 심볼릭 링크를 사용할 일이 있다면 체크&lt;/li&gt;
&lt;li&gt;개인 프로젝트나 심볼릭 링크가 필요 없는 경우에는 굳이 체크하지 않아도 무방합니다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;요약 표&lt;/b&gt;&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style7&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;옵션명&lt;/th&gt;
&lt;th&gt;기능 요약&lt;/th&gt;
&lt;th&gt;체크 권장 여부&lt;/th&gt;
&lt;th&gt;비고/주의사항&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Enable file system caching&lt;/td&gt;
&lt;td&gt;파일 시스템 정보 캐싱으로 속도 향상&lt;/td&gt;
&lt;td&gt;대부분 체크 권장&lt;/td&gt;
&lt;td&gt;성능 향상, 부작용 거의 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enable Git Credential Manager&lt;/td&gt;
&lt;td&gt;인증 정보 안전 저장 및 자동 입력&lt;/td&gt;
&lt;td&gt;대부분 체크 권장&lt;/td&gt;
&lt;td&gt;보안성&amp;uarr;, 입력 편의성&amp;uarr;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enable symbolic links&lt;/td&gt;
&lt;td&gt;윈도우에서 심볼릭 링크 지원&lt;/td&gt;
&lt;td&gt;필요 시에만 체크&lt;/td&gt;
&lt;td&gt;관리자 권한/개발자 모드 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최신 공식 사이트에서 설치 파일 다운로드&lt;/li&gt;
&lt;li&gt;설치 옵션은 대부분 기본값, 에디터와 브랜치 이름만 신경쓰기&lt;/li&gt;
&lt;li&gt;설치 후 Git Bash에서 버전 확인, 사용자 정보 등록&lt;/li&gt;
&lt;li&gt;폴더 생성 &amp;rarr; &lt;code&gt;git init&lt;/code&gt; &amp;rarr; 파일 추가 및 커밋&lt;/li&gt;
&lt;li&gt;필요시 Git GUI, winget 활용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 가이드를 따라하면 2025년 기준 윈도우에서 Git을 안전하게 설치하고, 바로 실무에 사용할 수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;참고사이트&lt;/h4&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://itconquest.tistory.com/entry/git-git-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0&quot;&gt;https://itconquest.tistory.com/entry/git-git-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0&lt;/a&gt;&lt;br /&gt;&lt;br /&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>프로그래밍/GIT</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3415</guid>
      <comments>https://aspdotnet.tistory.com/3415#entry3415comment</comments>
      <pubDate>Mon, 9 Jun 2025 15:32:42 +0900</pubDate>
    </item>
    <item>
      <title>Office Web Apps Viewer 온라인 office 파일 뷰어</title>
      <link>https://aspdotnet.tistory.com/3414</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Office Web Apps Viewer&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;('.ppt' '.pptx' '.doc', '.docx', '.xls', '.xlsx')&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;http://view.officeapps.live.com/op/view.aspx?src=[OFFICE_FILE_URL]

&amp;lt;iframe src='https://view.officeapps.live.com/op/embed.aspx?src=[OFFICE_FILE_URL]' width='px' height='px' frameborder='0'&amp;gt;
&amp;lt;/iframe&amp;gt;&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;OneDrive Embed Links&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Powerpoint&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;&amp;lt;iframe src=&quot;https://onedrive.live.com/embed?cid=CA582F2BC3AD1590&amp;amp;resid=CA582F2BC3AD1590%2133463&amp;amp;authkey=AFh1O3tkDLAQzzw&amp;amp;em=2&quot; width=&quot;402&quot; height=&quot;327&quot; frameborder=&quot;0&quot; scrolling=&quot;no&quot;&amp;gt;&amp;lt;/iframe&amp;gt;&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;&lt;b&gt;Excel&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;&amp;lt;iframe src=&quot;https://onedrive.live.com/embed?cid=CA582F2BC3AD1590&amp;amp;resid=CA582F2BC3AD1590%2136277&amp;amp;authkey=AHJ96YRJIbbJcec&amp;amp;em=2&quot; frameborder=&quot;0&quot; scrolling=&quot;no&quot; width=&quot;800&quot; height=&quot;346&quot;&amp;gt;&amp;lt;/iframe&amp;gt;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Google Docs Viewer&lt;/h2&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;Only files under 25 MB can be previewed with the Google Drive viewer.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google Drive viewer helps you preview over 16 different file types, listed below:&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Image files (.JPEG, .PNG, .GIF, .TIFF, .BMP)&lt;/li&gt;
&lt;li&gt;Video files (WebM, .MPEG4, .3GPP, .MOV, .AVI, .MPEGPS, .WMV, .FLV)&lt;/li&gt;
&lt;li&gt;Text files (.TXT)&lt;/li&gt;
&lt;li&gt;Markup/Code (.CSS, .HTML, .PHP, .C, .CPP, .H, .HPP, .JS)&lt;/li&gt;
&lt;li&gt;Microsoft Word (.DOC and .DOCX)&lt;/li&gt;
&lt;li&gt;Microsoft Excel (.XLS and .XLSX)&lt;/li&gt;
&lt;li&gt;Microsoft PowerPoint (.PPT and .PPTX)&lt;/li&gt;
&lt;li&gt;Adobe Portable Document Format (.PDF)&lt;/li&gt;
&lt;li&gt;Apple Pages (.PAGES)&lt;/li&gt;
&lt;li&gt;Adobe Illustrator (.AI)&lt;/li&gt;
&lt;li&gt;Adobe Photoshop (.PSD)&lt;/li&gt;
&lt;li&gt;Tagged Image File Format (.TIFF)&lt;/li&gt;
&lt;li&gt;Autodesk AutoCad (.DXF)&lt;/li&gt;
&lt;li&gt;Scalable Vector Graphics (.SVG)&lt;/li&gt;
&lt;li&gt;PostScript (.EPS, .PS)&lt;/li&gt;
&lt;li&gt;TrueType (.TTF)&lt;/li&gt;
&lt;li&gt;XML Paper Specification (.XPS)&lt;/li&gt;
&lt;li&gt;Archive file types (.ZIP and .RAR)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Google Docs Viewer (Apps)&lt;/h3&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;https://docs.google.com/a/[DOMINIO]/viewer?url=[FILE_URL]&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Google Docs Viewer&lt;/h3&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;https://docs.google.com/a/[DOMINIO]/viewer?url=[FILE_URL]&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;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Google Drive&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Sheets&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;&amp;lt;iframe src=&quot;https://docs.google.com/spreadsheets/d/13R8O15c_sZKZT2QRHom1z2SDA3E1O5chUvROnqHCkwE/pubhtml?widget=true&amp;amp;amp;headers=true&quot; style=&quot;width:100%;height:100%;&quot;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Embedded File Viewer&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google Docs offers an undocumented feature that lets you embed PDF files and PowerPoint presentations in a web page. The files don't have to be uploaded to Google Docs, but they need to be available online.&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;Google Drive Viewer: Explicit PDF files&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;&amp;lt;iframe src=&quot;https://docs.google.com/viewer?url=http://infolab.stanford.edu/pub/papers/google.pdf&amp;amp;embedded=true&quot; style=&quot;width:600px; height:500px;&quot; frameborder=&quot;0&quot;&amp;gt;&amp;lt;/iframe&amp;gt;&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;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;&amp;lt;iframe src=&quot;https://drive.google.com/viewerng/viewer?url=http://docs.google.com/fileview?id=0B5ImRpiNhCfGZDVhMGEyYmUtZTdmMy00YWEyLWEyMTQtN2E2YzM3MDg3MTZh&amp;amp;hl=en&amp;amp;pid=explorer&amp;amp;efh=false&amp;amp;a=v&amp;amp;chrome=false&amp;amp;embedded=true&quot; frameborder=&quot;0&quot;&amp;gt;&amp;lt;/iframe&amp;gt;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Google Drive Viewer: Non-PDF files (fileviewer URL)&lt;/b&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;To view Google Drive docs from fileviewer links, use the file ID as the &lt;code&gt;srcid&lt;/code&gt; attribute in the iframe.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The file ID for your PDF (one that is already in Google Drive) can be found in the PDFs web address. When you open a PDF, it&amp;rsquo;s the garbage-looking piece of the URL (it will be between forward-slashes, &amp;ldquo;/&amp;rdquo;).&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&gt;&lt;img src=&quot;http://www.benschersten.com/blog/wp-content/uploads/2014/04/Screen_Shot_2014-04-30_at_8_29_17_AM.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The file ID is highlighted in yellow.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;In this case it&amp;rsquo;s the 0B3xoQi_oa7_hU2J5S1RQbFdqS3c&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;Source: &lt;a href=&quot;http://docs.google.com/fileview?id=0B5ImRpiNhCfGZDVhMGEyYmUtZTdmMy00YWEyLWEyMTQtN2E2YzM3MDg3MTZh&amp;amp;hl=en&amp;amp;pid=explorer&amp;amp;efh=false&amp;amp;a=v&amp;amp;chrome=false&amp;amp;embedded=true&quot;&gt;http://docs.google.com/fileview?id=0B5ImRpiNhCfGZDVhMGEyYmUtZTdmMy00YWEyLWEyMTQtN2E2YzM3MDg3MTZh&amp;amp;hl=en&amp;amp;pid=explorer&amp;amp;efh=false&amp;amp;a=v&amp;amp;chrome=false&amp;amp;embedded=true&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;id: 0B5ImRpiNhCfGZDVhMGEyYmUtZTdmMy00YWEyLWEyMTQtN2E2YzM3MDg3MTZh&lt;/code&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;Or&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;Source:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://drive.google.com/file/d/0B5ImRpiNhCfGZDVhMGEyYmUtZTdmMy00YWEyLWEyMTQtN2E2YzM3MDg3MTZh/view?ddrp=1&amp;amp;hl=en#&quot;&gt;https://drive.google.com/file/d/0B5ImRpiNhCfGZDVhMGEyYmUtZTdmMy00YWEyLWEyMTQtN2E2YzM3MDg3MTZh/view?ddrp=1&amp;amp;hl=en#&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;id: 0B5ImRpiNhCfGZDVhMGEyYmUtZTdmMy00YWEyLWEyMTQtN2E2YzM3MDg3MTZh&lt;/code&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;Result:&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;&amp;lt;iframe src=&quot;https://docs.google.com/viewer?srcid=[put your file id here]&amp;amp;pid=explorer&amp;amp;efh=false&amp;amp;a=v&amp;amp;chrome=false&amp;amp;embedded=true&quot; width=&quot;580px&quot; height=&quot;480px&quot;&amp;gt;&amp;lt;/iframe&amp;gt;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Resources&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Info:&lt;br /&gt;&lt;a href=&quot;http://www.labnol.org/internet/google-docs-viewer-alternative/&quot;&gt;http://www.labnol.org/internet/google-docs-viewer-alternative/&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;참고사이트&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://gist.github.com/tzmartin/1cf85dc3d975f94cfddc04bc0dd399be&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://gist.github.com/tzmartin/1cf85dc3d975f94cfddc04bc0dd399be&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1748338401381&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Embedded File Viewer: Google Drive, OneDrive&quot; data-og-description=&quot;Embedded File Viewer: Google Drive, OneDrive. GitHub Gist: instantly share code, notes, and snippets.&quot; data-og-host=&quot;gist.github.com&quot; data-og-source-url=&quot;https://gist.github.com/tzmartin/1cf85dc3d975f94cfddc04bc0dd399be&quot; data-og-url=&quot;https://gist.github.com/tzmartin/1cf85dc3d975f94cfddc04bc0dd399be&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/GV1n1/hyY1aBtE0Z/Yd6UUrJiKCy4G1vi4fJov0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/dPJauw/hyY0xjoB2G/hD48KLHEGp5rwkUQO1dDgk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://gist.github.com/tzmartin/1cf85dc3d975f94cfddc04bc0dd399be&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gist.github.com/tzmartin/1cf85dc3d975f94cfddc04bc0dd399be&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/GV1n1/hyY1aBtE0Z/Yd6UUrJiKCy4G1vi4fJov0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/dPJauw/hyY0xjoB2G/hD48KLHEGp5rwkUQO1dDgk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Embedded File Viewer: Google Drive, OneDrive&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Embedded File Viewer: Google Drive, OneDrive. GitHub Gist: instantly share code, notes, and snippets.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gist.github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍/자바스크립트</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3414</guid>
      <comments>https://aspdotnet.tistory.com/3414#entry3414comment</comments>
      <pubDate>Tue, 27 May 2025 18:34:00 +0900</pubDate>
    </item>
    <item>
      <title>ASP.NET Core 8 애플리케이션 성능 최적화 방법</title>
      <link>https://aspdotnet.tistory.com/3413</link>
      <description>&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;1200&quot; data-origin-height=&quot;303&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pbFk2/btsOaFm9hgD/W6KCibRJHx8VHTrvOSdSaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pbFk2/btsOaFm9hgD/W6KCibRJHx8VHTrvOSdSaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pbFk2/btsOaFm9hgD/W6KCibRJHx8VHTrvOSdSaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpbFk2%2FbtsOaFm9hgD%2FW6KCibRJHx8VHTrvOSdSaK%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;1200&quot; height=&quot;303&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;303&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;아래는 최신 트렌드(.NET 8 기준)의 ASP.NET Core 애플리케이션 성능 최적화 방법을 단계별로 정리한 블로그 글 예시입니다. 각 단계별로 설명과 코드 샘플을 풍부히 담았으니, 필요에 따라 추가&amp;middot;변경하여 활용하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1단계: 불필요한 서비스 등록 줄이기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ASP.NET Core의 미들웨어&amp;middot;서비스는 필요할 때만 등록해야 애플리케이션이 가벼워집니다. &lt;code&gt;Program.cs&lt;/code&gt;에서 기본 템플릿이 등록해 주는 모든 것을 다 쓰는 것이 아니라, 실제 사용하는 것만 골라서 추가하세요.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;var builder = WebApplication.CreateBuilder(args);

// 예: Identity나 Razor Pages를 안 쓰면 제거
// builder.Services.AddRazorPages();
// builder.Services.AddDefaultIdentity&amp;lt;IdentityUser&amp;gt;().AddEntityFrameworkStores&amp;lt;ApplicationDbContext&amp;gt;();

// 필요한 서비스만 등록
builder.Services.AddControllersWithViews();
builder.Services.AddDbContext&amp;lt;MyDbContext&amp;gt;(options =&amp;gt;
    options.UseSqlServer(builder.Configuration.GetConnectionString(&quot;Default&quot;)));

// 캐싱, 압축, 인증&amp;middot;권한 등은 필요 시에만
builder.Services.AddMemoryCache();           // Tip 2에서 사용
builder.Services.AddResponseCompression();    // Tip 5에서 사용

var app = builder.Build();

// 미들웨어도 필요한 것만
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(&quot;/Home/Error&quot;);
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();

app.MapDefaultControllerRoute();
app.Run();&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 구성 요소를 슬림화하면 애플리케이션 시작 속도가 빨라지고 메모리 사용량도 줄어듭니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2단계: 메모리 캐싱으로 DB 호출 횟수 줄이기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 호출은 디스크 I/O로 느립니다. 자주 읽지만 자주 변경되지 않는 데이터는 &lt;code&gt;IMemoryCache&lt;/code&gt;(혹은 서버 팜 환경에는 Redis 등 분산 캐시)로 보관하세요.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;// 1) Repository 클래스
public class CustomerRepository
{
    private readonly IMemoryCache _cache;
    public CustomerRepository(IMemoryCache cache) =&amp;gt; _cache = cache;

    public async Task&amp;lt;Customer&amp;gt; GetByIdAsync(string id, bool forceReload = false)
    {
        if (forceReload)
            _cache.Remove(id);

        return await _cache.GetOrCreateAsync(id, async e =&amp;gt;
        {
            e.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30);
            return await LoadCustomerFromDbAsync(id);
        });
    }

    private Task&amp;lt;Customer&amp;gt; LoadCustomerFromDbAsync(string id)
        =&amp;gt; _dbContext.Customers.FirstOrDefaultAsync(c =&amp;gt; c.Id == id);
}

// 2) Controller에서 사용
public class HomeController : Controller
{
    private readonly CustomerRepository _repo;
    public HomeController(IMemoryCache cache)
        =&amp;gt; _repo = new CustomerRepository(cache);

    public async Task&amp;lt;IActionResult&amp;gt; Index(string id, bool force = false)
    {
        var customer = await _repo.GetByIdAsync(id, force);
        return View(customer);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;분산 캐시&lt;/b&gt;: 서버 팜을 쓰면 Redis, NCache, SQL Server 분산 캐시를 활용하세요&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3단계: 클라이언트로 전송하는 데이터 최소화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 페이지 로드 시 불필요한 데이터를 모두 내려주면 렌더링 지연이 발생합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;페이징(paging)&lt;/b&gt;: 첫 페이지 데이터만 내려주고, 사용자가 요청할 때 추가 로드&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Infinite Scroll&lt;/b&gt;: 화면에 보이는 영역 + &amp;alpha; 만큼만 로드&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!-- View: 최소 데이터만 렌더링 --&amp;gt;
&amp;lt;div id=&quot;grid&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;script&amp;gt;
  let page = 1, pageSize = 20;
  async function loadPage() {
    const res = await fetch(`/api/items?page=${page}&amp;amp;size=${pageSize}`);
    const data = await res.json();
    renderGrid(data);
  }
  loadPage();
  // 스크롤 이벤트로 추가 로드...
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// API: 페이징 지원
[HttpGet(&quot;api/items&quot;)]
public async Task&amp;lt;IActionResult&amp;gt; GetItems(int page = 1, int size = 20)
{
    var items = await _dbContext.Items
        .AsNoTracking()
        .Skip((page - 1) * size)
        .Take(size)
        .ToListAsync();
    return Ok(items);
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이징이나 무한 스크롤은 사용자 경험을 크게 개선합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4단계: CSS&amp;middot;JS는 CDN에서, 정적 파일은 최적화된 경로로&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Bootstrap, React, Vue 등 &lt;b&gt;산업 표준 라이브러리&lt;/b&gt;는 CDN 사용&lt;/li&gt;
&lt;li&gt;회사 로고&amp;middot;커스텀 스크립트 등 &lt;b&gt;자체 정적 파일&lt;/b&gt;도 Azure CDN 같은 서비스에 배포&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!-- _Layout.cshtml --&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;!-- Bootstrap CSS from CDN --&amp;gt;
  &amp;lt;link href=&quot;https://cdn.jsdelivr.net/npm/bootstrap@5.4.0/dist/css/bootstrap.min.css&quot; rel=&quot;stylesheet&quot; /&amp;gt;
  &amp;lt;!-- WebOptimizer로 번들된 커스텀 CSS --&amp;gt;
  &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;~/css/bundle.css&quot; /&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;script src=&quot;https://cdn.jsdelivr.net/npm/bootstrap@5.4.0/dist/js/bootstrap.bundle.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;script src=&quot;~/js/bundle.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CDN으로 파일을 제공하면 지리적으로 더 가까운 노드에서 빠르게 다운로드됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5단계: 파일 번들링&amp;middot;압축(WebOptimizer 연동)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;WebOptimizer&lt;/code&gt;를 사용하면 런타임에 자동으로 CSS/JS를 &lt;b&gt;번들링&amp;middot;최소화(minify)&lt;/b&gt; 해주고, GZIP 압축까지 처리합니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;// Program.cs
builder.Services.AddWebOptimizer(pipeline =&amp;gt;
{
    pipeline.AddCssBundle(&quot;/css/bundle.css&quot;, &quot;css/*.css&quot;);
    pipeline.AddJsBundle(&quot;/js/bundle.js&quot;, &quot;js/*.js&quot;);
});

var app = builder.Build();
app.UseWebOptimizer();&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!-- 번들 파일 참조 --&amp;gt;
&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;/css/bundle.css&quot; /&amp;gt;
&amp;lt;script src=&quot;/js/bundle.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청 수와 파일 크기를 동시에 줄여 초기 로드 속도를 높입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6단계: &lt;code&gt;ResponseCache&lt;/code&gt;로 서버 호출 횟수 줄이기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자주 요청되는 페이지에는 &lt;code&gt;[ResponseCache]&lt;/code&gt;를 걸어 브라우저&amp;middot;프록시 캐싱을 활용하세요.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;[ResponseCache(Duration = 600, VaryByQueryKeys = new[] { &quot;country&quot; })]
public IActionResult PriceList(string country)
{
    var list = _dbContext.Prices.Where(p =&amp;gt; p.Country == country).ToList();
    return View(list);
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 쿼리 문자열 요청 시 10분간 서버 호출 없이 캐시된 응답을 내려줍니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;보너스: 전체 비동기(&lt;code&gt;async/await&lt;/code&gt;) 활용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동기 메서드는 쓰레드를 블록해 요청 처리량을 제한합니다. 데이터베이스&amp;middot;HTTP 호출은 &lt;code&gt;async&lt;/code&gt; 패턴으로 작성해 쓰레드 풀 풀링 효율을 극대화하세요.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;public async Task&amp;lt;IActionResult&amp;gt; GetDataAsync()
{
    var data = await _httpClient.GetFromJsonAsync&amp;lt;List&amp;lt;MyDto&amp;gt;&amp;gt;(&quot;https://api.example.com/data&quot;);
    return View(data);
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기를 통해 높은 동시성(concurrency)을 확보할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;필요한 것만 등록&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DB 호출 줄이기 &amp;rarr; 캐싱&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클라이언트 전송 최소화&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CDN&amp;middot;번들링&amp;middot;압축&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;캐싱 및 비동기&lt;/b&gt;&lt;/li&gt;
&lt;/ul&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;br /&gt;여기에 &lt;b&gt;프로파일링&lt;/b&gt;(BenchmarkDotNet, MiniProfiler)이나 &lt;b&gt;GC 튜닝(Workstation vs Server mode)&lt;/b&gt;, &lt;b&gt;Kestrel 옵션 조정&lt;/b&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;b&gt;참고사이트&lt;/b&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;a href=&quot;https://www.telerik.com/blogs/make-run-faster-optimizing-aspnet-core-application&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.telerik.com/blogs/make-run-faster-optimizing-aspnet-core-application&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1748012587327&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Make It Run Faster: Optimizing Your ASP.NET Core Application&quot; data-og-description=&quot;Your users always like it when their applications run faster. Here are six things you can do right now to make that happen.&quot; data-og-host=&quot;www.telerik.com&quot; data-og-source-url=&quot;https://www.telerik.com/blogs/make-run-faster-optimizing-aspnet-core-application&quot; data-og-url=&quot;https://www.telerik.com/blogs/make-run-faster-optimizing-aspnet-core-application?ref=dailydev&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dAc6ck/hyYYywgrau/nfiqfI12IOk4TL0QCx0uZ0/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628,https://scrap.kakaocdn.net/dn/bTX4ZN/hyYYywgrlN/Dmq1qzJp8JOrTN4hrKnik0/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628,https://scrap.kakaocdn.net/dn/qe4Nw/hyYW7zg7ky/K56NyTjC10jBLhU8HyZze1/img.png?width=1200&amp;amp;height=303&amp;amp;face=0_0_1200_303&quot;&gt;&lt;a href=&quot;https://www.telerik.com/blogs/make-run-faster-optimizing-aspnet-core-application&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.telerik.com/blogs/make-run-faster-optimizing-aspnet-core-application&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dAc6ck/hyYYywgrau/nfiqfI12IOk4TL0QCx0uZ0/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628,https://scrap.kakaocdn.net/dn/bTX4ZN/hyYYywgrlN/Dmq1qzJp8JOrTN4hrKnik0/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628,https://scrap.kakaocdn.net/dn/qe4Nw/hyYW7zg7ky/K56NyTjC10jBLhU8HyZze1/img.png?width=1200&amp;amp;height=303&amp;amp;face=0_0_1200_303');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Make It Run Faster: Optimizing Your ASP.NET Core Application&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Your users always like it when their applications run faster. Here are six things you can do right now to make that happen.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.telerik.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>닷넷관련/ASP.NET CORE  </category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3413</guid>
      <comments>https://aspdotnet.tistory.com/3413#entry3413comment</comments>
      <pubDate>Sat, 24 May 2025 00:03:17 +0900</pubDate>
    </item>
    <item>
      <title>JAVASCRIPT : JSON 형태 작성 시 입력 값 유효성 검사 함수</title>
      <link>https://aspdotnet.tistory.com/3411</link>
      <description>&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;code&gt;bigo&lt;/code&gt; 값에 대해 전각 문자 처리, 잘못된 % 인코딩, XSS 보안 위험, URL 유효성 등 모든 요소를 종합적으로 검사하고 정리해주는&amp;nbsp; validation 함수를 만들어 보죠. 이는 어떤 입력이 들어와도 JSON 직렬화, 서버 전송, 화면 출력에 안전하도록 만들어 보죠.&lt;/p&gt;
&lt;h1&gt;비고 텍스트 필드 유효성 검사 함수&lt;/h1&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;code&gt;bigo&lt;/code&gt;) 필드를 처리할 때는 입력 문자열을 안전하게 정리하여 서버로 전송해야 합니다. 본 함수는 다음과 같은 과정을 거쳐 입력값을 검사&amp;middot;변환합니다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;전각(全角) 문자 &amp;rarr; 반각(半角) 변환&lt;/b&gt;: 전각 알파벳&amp;middot;숫자&amp;middot;특수문자를 대응하는 반각 문자로 변환합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;잘못된 URI 인코딩 방지&lt;/b&gt;: &lt;code&gt;%&lt;/code&gt; 기호로 시작하는 잘못된 인코딩(&lt;code&gt;%&lt;/code&gt; 단독, &lt;code&gt;%심&lt;/code&gt;, &lt;code&gt;%2&lt;/code&gt; 등)이 있을 경우, &lt;code&gt;decodeURIComponent&lt;/code&gt; 시 오류가 발생하지 않도록 처리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;XSS 위험 제거&lt;/b&gt;: 입력 문자열에 포함된 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 태그나 &lt;code&gt;javascript:&lt;/code&gt; 같은 XSS 스크립트 코드를 제거합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;URL 검증&lt;/b&gt;: 문자열에 URL이 포함되면 &lt;code&gt;new URL()&lt;/code&gt;로 검증하여 유효성을 확인하고, 허용된 프로토콜(&lt;code&gt;http&lt;/code&gt;, &lt;code&gt;https&lt;/code&gt;)이 아니면 제거합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;불필요 공백/특수문자 정리&lt;/b&gt;: 연속된 줄바꿈 또는 여러 공백을 하나로 줄이고, 과도한 특수문자 반복도 적절히 축소합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;최종 출력&lt;/b&gt;은 JSON 직렬화나 서버 전송 시 문제가 없는 &amp;ldquo;안전한 문자열&amp;rdquo;로 반환되며, 필요 시 경고를 &lt;code&gt;console.warn()&lt;/code&gt;으로 남깁니다.&lt;/li&gt;
&lt;/ul&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;전각 문자 변환&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한글 입력 환경에서 전각 문자(예: 전각 영문자, 전각 숫자, 전각 특수기호)가 입력될 수 있습니다. 이들을 모두 반각으로 변환해야 합니다. JavaScript에서는 정규표현식과 문자코드 조작으로 쉽게 처리할 수 있습니다. 예를 들어, 유니코드 U+FF01(&amp;lsquo;！&amp;rsquo;)부터 U+FF5E(&amp;lsquo;～&amp;rsquo;)까지의 전각 문자를 그 문자 코드에서 &lt;code&gt;0xFEE0&lt;/code&gt;(65248)만큼 빼면 대응하는 반각 문자 코드가 됩니다. 또한 전각 공백(&lt;code&gt;\u3000&lt;/code&gt;)은 반각 공백(&lt;code&gt;\u0020&lt;/code&gt;)으로 교체합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 예시: 전각문자를 반각으로 변환하는 함수
function toHalfWidth(str) {
  return str
    // U+FF01&amp;ndash;U+FF5E 범위의 전각 문자를 대응 반각으로 변환
    .replace(/[\uff01-\uff5e]/g, ch =&amp;gt; 
      String.fromCharCode(ch.charCodeAt(0) - 0xFEE0))
    // 전각 스페이스(\u3000)를 일반 스페이스(\u0020)로 변경
    .replace(/\u3000/g, '\u0020');
}

// 사용 예
console.log(toHalfWidth(&quot;ＡＢＣ１２３！＠＃&quot;)); // =&amp;gt; &quot;ABC123!@#&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;위 코드와 같이 &lt;code&gt;/[\uff01-\uff5e]/g&lt;/code&gt; 정규표현식과 &lt;code&gt;charCode&lt;/code&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;h2 data-ke-size=&quot;size26&quot;&gt;잘못된 URI 인코딩 방지&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 입력한 문자열에 &lt;code&gt;%&lt;/code&gt; 문자가 포함될 때, 뒤따르는 문자가 유효한 16진수 두 자리인지 검사해야 합니다. 잘못된 인코딩(예: &lt;code&gt;%&lt;/code&gt; 단독, &lt;code&gt;%2&lt;/code&gt;, &lt;code&gt;%심&lt;/code&gt; 등)이 들어오면 &lt;code&gt;decodeURIComponent&lt;/code&gt; 호출 시 &lt;b&gt;&lt;code&gt;URIError: malformed URI sequence&lt;/code&gt;&lt;/b&gt; 에러가 발생할 수 있습니다. 이를 막기 위해 정규표현식을 사용하여 &lt;code&gt;%&lt;/code&gt;로 시작하는 유효한 16진수 시퀀스(예: &lt;code&gt;%xx&lt;/code&gt;)만을 골라 &lt;code&gt;decodeURIComponent&lt;/code&gt;로 변환하고, 에러가 나는 경우 해당 시퀀스는 그대로 두도록 합니다. 예를 들어:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;// %xx 형태의 인코딩만 안전하게 디코딩하는 예시
input = input.replace(/(%[0-9A-Fa-f]{2})+/g, seq =&amp;gt; {
  try {
    return decodeURIComponent(seq);
  } catch (e) {
    console.warn(`Invalid escape sequence: ${seq}`);
    return seq;
  }
});&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;위 코드처럼 &lt;code&gt;try/catch&lt;/code&gt;로 감싸면 잘못된 &lt;code&gt;%&lt;/code&gt; 시퀀스는 건너뛰고 정상적인 시퀀스만 디코딩할 수 있습니다. (StackOverflow에서도 이와 같은 패턴을 제안합니다.) 필요시 더 간단히 모든 &lt;code&gt;%&lt;/code&gt;를 사전 처리(&lt;code&gt;%&lt;/code&gt; &amp;rarr; &lt;code&gt;%25&lt;/code&gt;)하는 방법도 있지만, 위 패턴이 개별 시퀀스를 안전하게 처리하는 일반적인 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;XSS 위험 요소 제거&lt;/h2&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;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 태그나 &lt;code&gt;javascript:&lt;/code&gt; URI 등 XSS 공격 벡터가 포함되지 않도록 필터링해야 합니다. 기본적으로 HTML로 취급되어 실행되는 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 태그는 제거하고, &lt;code&gt;javascript:&lt;/code&gt; 같은 스킴도 삭제합니다. 예를 들어, 정규표현식 &lt;code&gt;/\&amp;lt;script\b[^&amp;lt;]*(?:(?!&amp;lt;\/script&amp;gt;)&amp;lt;[^&amp;lt;]*)*&amp;lt;\/script&amp;gt;/gi&lt;/code&gt;를 사용하면 &lt;code&gt;&amp;lt;script&amp;gt; ... &amp;lt;/script&amp;gt;&lt;/code&gt; 블록 전체를 제거할 수 있습니다. 또한 &lt;code&gt;&quot;javascript:&quot;&lt;/code&gt; 패턴은 &lt;code&gt;/javascript:/gi&lt;/code&gt;로 찾아 빈 문자열로 대체합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MDN에서는 *&amp;ldquo;Sanitization is the process of removing unsafe features from a string of HTML: for example, &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags or inline event handlers&amp;rdquo;*라고 언급하며, &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 태그 등의 위험 요소 제거를 XSS 방지 조치로 권장하고 있습니다. 따라서 함수에서 입력값에 대해 이러한 치환/제거 작업을 수행해 주어야 합니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;URL 포함 시 검증&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력에 URL이 포함될 경우, 이를 검증하여 허용된 프로토콜인지 확인합니다. 예를 들어 &lt;code&gt;http://example.com&lt;/code&gt;이나 &lt;code&gt;https://example.com&lt;/code&gt;은 허용하되, &lt;code&gt;ftp:&lt;/code&gt;, &lt;code&gt;javascript:&lt;/code&gt; 등은 거릅니다. JavaScript의 &lt;code&gt;URL&lt;/code&gt; 생성자를 사용하면 문자열이 올바른 URL 형식인지 검사할 수 있습니다. 다음과 같은 함수가 자주 쓰입니다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;function isValidHttpUrl(string) {
  try {
    new URL(string);
  } catch (_) {
    return false;
  }
  return (url.protocol === &quot;http:&quot; || url.protocol === &quot;https:&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URL이 검증에 통과하지 못하면 해당 URL을 통째로 제거하거나 메시지를 경고하도록 처리합니다. 예를 들어 입력 텍스트에서 &lt;code&gt;http://&lt;/code&gt; 또는 &lt;code&gt;https://&lt;/code&gt;로 시작하는 패턴이 발견되면 &lt;code&gt;new URL()&lt;/code&gt;로 검사하고, 프로토콜이 &lt;code&gt;http:&lt;/code&gt; 또는 &lt;code&gt;https:&lt;/code&gt;인지 확인합니다. 그렇지 않으면 &lt;code&gt;console.warn&lt;/code&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;h2 data-ke-size=&quot;size26&quot;&gt;공백 줄&amp;middot;특수문자 정리&lt;/h2&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;pre class=&quot;arduino&quot;&gt;&lt;code&gt;// 연속된 공백(스페이스&amp;middot;탭)을 한 칸으로 축소
text = text.replace(/\s{2,}/g, ' ');
// 연속된 줄바꿈(빈 줄)이 두 줄 이상이면 한 줄로 축소
text = text.replace(/\n\s*\n/g, '\n');&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드처럼 &lt;code&gt;/\s{2,}/g&lt;/code&gt;를 사용하면 연속된 공백을 하나로 줄일 수 있고, &lt;code&gt;/\n\s*\n/g&lt;/code&gt;는 빈 줄이 여러 개 연속될 때 한 개로 줄여줍니다. 필요에 따라 전후 공백을 &lt;code&gt;.trim()&lt;/code&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;h2 data-ke-size=&quot;size26&quot;&gt;전체 구현 예제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 설계를 반영한 JavaScript 함수 예시는 다음과 같습니다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;function validateBigo(input) {
  // 1) 문자열 타입 확인
  if (typeof input !== 'string') {
    console.warn('Input is not a string.');
    return '';
  }

  let text = input;

  // 2) 전각 문자 &amp;rarr; 반각 변환
  text = text
    .replace(/[\uff01-\uff5e]/g, ch =&amp;gt;
      String.fromCharCode(ch.charCodeAt(0) - 0xFEE0))
    .replace(/\u3000/g, '\u0020');

  // 3) 잘못된 URI 인코딩 방지: %xx 패턴만 디코딩 시도
  text = text.replace(/(%[0-9A-Fa-f]{2})+/g, seq =&amp;gt; {
    try {
      return decodeURIComponent(seq);
    } catch (e) {
      console.warn(`Invalid escape sequence: ${seq}`);
      return seq;
    }
  });

  // 4) XSS 위험 요소 제거: &amp;lt;script&amp;gt; 태그 및 javascript: 스킴 삭제
  text = text.replace(/&amp;lt;script\b[^&amp;lt;]*(?:(?!&amp;lt;\/script&amp;gt;)&amp;lt;[^&amp;lt;]*)*&amp;lt;\/script&amp;gt;/gi, '');
  text = text.replace(/javascript:/gi, '');

  // 5) URL 포함 시 유효성 검사: http/https 외 제거
  text = text.replace(/https?:\/\/[^\s]+/g, url =&amp;gt; {
    try {
      let obj = new URL(url);
      if (obj.protocol === 'http:' || obj.protocol === 'https:') {
        return url;
      } else {
        console.warn(`Unsupported URL protocol: ${url}`);
        return '';
      }
    } catch {
      console.warn(`Malformed URL: ${url}`);
      return '';
    }
  });

  // 6) 공백 및 특수문자 정리: 연속 공백&amp;middot;줄바꿈 축소, trim
  text = text.replace(/\s{2,}/g, ' ');
  text = text.replace(/\n\s*\n/g, '\n');
  text = text.trim();

  return text;
}

// === 사용 예시 ===
const example = &quot;ＨＴＴＰ：／／예제.컴 \n&amp;lt;script&amp;gt;alert('XSS');&amp;lt;/script&amp;gt;\n\n  테스트  &quot;;
console.log(validateBigo(example));
// 출력 예: &quot;HTTP://예제.컴 테스트&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 함수는 &lt;b&gt;입력형 검사 &amp;rarr; 전각변환 &amp;rarr; URI디코딩 &amp;rarr; 스크립트 제거 &amp;rarr; URL검증 &amp;rarr; 공백정리&lt;/b&gt; 순서로 처리합니다. 코드 내 각 단계는 앞서 설명한 방식과 일치하며, 예제에서도 한글 도메인 등이 그대로 남고 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;와 과도한 공백이 제거된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트 케이스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 여러 입력에 대한 테스트 예시입니다:&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;console.log(validateBigo(&quot;ＡＢＣ　abc　！!  &quot;));
// 예상 출력: &quot;ABC abc !!&quot;  (전각 문자와 공백 정리)

console.log(validateBigo(&quot;Hello%20World%3&quot;)); 
// 경고: Invalid escape sequence: %3
// 예상 출력: &quot;Hello%20World%3&quot;  (% 인코딩 오류 부분 보존)

console.log(validateBigo(&quot;&amp;lt;script&amp;gt;evil()&amp;lt;/script&amp;gt;Safe&quot;));
// 예상 출력: &quot;Safe&quot;  (&amp;lt;script&amp;gt; 제거)

console.log(validateBigo(&quot;Click &amp;lt;a href=\&quot;javascript:alert(1)\&quot;&amp;gt;here&amp;lt;/a&amp;gt;&quot;));
// 예상 출력: &quot;Click &amp;lt;a href=\&quot;\&quot;&amp;gt;here&amp;lt;/a&amp;gt;&quot; 또는 &quot;Click here&quot;
                                     // (javascript: 제거)

console.log(validateBigo(&quot;Visit http://example.com and ftp://files&quot;));
// 경고: Unsupported URL protocol: ftp://files
// 예상 출력: &quot;Visit http://example.com and &quot;  (ftp URL 제거)

console.log(validateBigo(&quot;\n\nLine1\n\n\nLine2   \t\t&quot;));
// 예상 출력: &quot;Line1\nLine2&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 테스트에서 보듯, 유효성 검사 함수는 입력 문자열에서 문제되는 부분을 안전하게 제거하거나 경고합니다. 최종 반환값은 JSON으로 직렬화해도 오류가 없는 일반 텍스트 문자열입니다.&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; JavaScript를 이용한 전각-반각 변환 예제, &lt;code&gt;decodeURIComponent&lt;/code&gt; 예외 처리 방법, URL 검증 방법, 공백 정리 정규표현식 예, 그리고 XSS sanitization 설명 등을 참고하여 구현하였습니다.&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>프로그래밍/자바스크립트</category>
      <author>재우니</author>
      <guid isPermaLink="true">https://aspdotnet.tistory.com/3411</guid>
      <comments>https://aspdotnet.tistory.com/3411#entry3411comment</comments>
      <pubDate>Tue, 20 May 2025 01:42:48 +0900</pubDate>
    </item>
  </channel>
</rss>