상황 Ubuntu 서버에서 ping 8.8.8.8 은 성공하지만, curl https://example.com 은 Could not resolve host 오류로 실패했다. 문제 가장 먼저 의심할 기능은 무엇일까? 아래 명령 중 도메인 이름이 IP 주소로 변환되는지 확인하는 데 가장 적합한 것은? A. ip route B. getent hosts example.com C. ss -tuln 정답과 풀이 DNS 이름 해석을 먼저 의심한다. IP 주소로는 통신되지만 도메인 이름을 IP 주소로 바꾸는 단계에서 오류가 났기 때문이다. 다만 ping 성공만으로 HTTPS 연결까지 정상이라고 단정할 수는 없다. B. gentent hosts example.com 주소가 출력되는지 확인한다. 출력되지 않으면 resolvectl status 로 서버가 사용하는 DNS 설정을 살펴볼 수 있다. 핵심 개념 DNS는 example.com 같은 이름을 접속에 필요한 IP 주소로 찾는 기능이다. ip route 는 패킷이 나갈 경로를, ss -tuln 은 현재 열려 있는 소켓을 확인할 때 쓴다. 실무에서 중요한 이유 서비스 접속 실패를 모두 방화벽 문제로 보면 원인을 찾는 데 시간이 걸린다. IP 통신 -> 이름 해석 -> 해당 서비스 연결 순서로 확인하면 장애 범위를 빠르게 좁힐 수 있다. DNS 설정을 바꾸기 전에는 현재 설정과 영향을 받는 서비스를 먼저 확인한다.
본 문서는 Claude를 사용해 정리했습니다. 『실무로 통하는 타입스크립트』 정확히 하나만, 전부, 또는 전무 — ExactOne과 AllOrNone 정확히 1개만 허용하거나, 모두 허용하거나, 아무것도 허용하지 않아야 할 때가 있다. 접근 : ExactOne<T> , AllOrNone<T, K> 를 만든다. 선택형 never 기법 을 사용한다. 허용하지 않는 나머지 프로퍼티는 모두 선택형으로 설정하고 값은 never 로 설정한다. never 와 호환되는 값은 없으므로 프로퍼티를 만들 수 없다. 선택형 never 기법의 원리 type Test1 = { a?: never }; const t1: Test1 = {}; // ○ — 프로퍼티가 없으면 통과 const t2: Test1 = { a: undefined }; // ○ — undefined는 넣을 수 있음 const t3: Test1 = { a: 1 }; // ✕ — 값이 있으면 막힘 ? 는 "프로퍼티가 아예 없어도 된다"는 뜻이라, 빈 객체는 통과한다. 다만 그 프로퍼티에 실제 값을 넣으려 하면, never 는 "존재할 수 없는 값의 타입"이라 어떤 값도 대입할 수 없어서 막힌다. ? 를 지우면 에러가 나는 이유 type Test2 = { a: never }; const t: Test2 = {}; // ✕ TS2741: Property 'a' is missing ? 가 없으면 그 프로퍼티는 필수 가 된다. "반드시 있어야 하는데, 넣을 수 있는 값이 never (즉 없음)"라는 모순이 생겨서, 빈 객체조차 이 타입을 만족시킬 방법이 없다. 그래서 아무것도 대입할 수 없는 타입이 되어버린다. ? 가 있어야 "이 프로퍼티는 아예 생략 가능하다"는 탈출구가 생겨서 실용적으로 쓸 수 있다. ExactOne — 프로퍼티 1개만 허용 type ExactOne<T> = { [K in keyof T]: { [P in K]: T[P]; } & { [P in Exclude<keyof T, K>]?: never; }; }[keyof T]; type VideoFormatURLs = { format360p: URL; format480p: URL; format720p: URL; format1080p: URL; }; function loadVideo(format: ExactOne<VideoFormatURLs>) {} loadVideo({ format360p: new URL("") }); // ○ loadVideo({ format360p: new URL(""), format480p: new URL(""), }); // ✕ 각 키마다 "그 키만 값을 가짐 + 나머지는 전부 선택형 never "인 타입을 만들고, [keyof T] 로 인덱싱해서 유니온으로 합친다. 어떤 멤버를 선택하든 그 멤버가 아닌 프로퍼티에는 값을 넣을 수 없으니, 정확히 하나만 허용된다. ExactOne과 exactOptionalPropertyTypes loadVideo({ format360p: new URL(""), format480p: undefined, }); exactOptionalPropertyTypes 설정에 따라 결과가 갈린다. 설정 결과 꺼짐 (기본값) 통과된다. ?: never 가 never | undefined (= undefined )를 허용하기 때문 켜짐 막힌다. ? 가 "키를 생략할 수 있다"는 뜻으로 엄격해져서, undefined 값 자체도 그 자리에 넣을 수 없어진다 직접 확인한 결과, --exactOptionalPropertyTypes 플래그를 켜면 정확히 TS2379: ... Type 'undefined' is not assignable to type 'never' 에러가 난다. 선택형 never 기법을 신뢰도 있게 쓰려면 exactOptionalPropertyTypes: true 를 함께 켜는 것이 사실상 필수다. 이 옵션 없이는 undefined 를 명시적으로 대입하는 경로로 "정확히 하나만"이라는 보장이 우회될 수 있다. Split으로도 만들 수 있다 type Split<T, OptionalNever extends boolean = false> = { [K in keyof T]: { [P in K]: T[P]; } & (OptionalNever extends false ? {} : { [P in Exclude<keyof T, K>]?: never; }); }[keyof T]; type ExactlyOne1<T> = Split<T, true>; OptionalNever 플래그로 "최소 1개"(기본값, {} )와 "정확히 1개"( true , 나머지를 never 로 막음) 두 가지를 하나의 헬퍼로 표현한다. AllOrNone — 지정한 키들을 전부 쓰거나 전부 안 쓰거나 type AllOrNone<T, Keys extends keyof T> = ( | { [K in Keys]-?: T[K] } // 모두 사용 가능 (필수로 강제) | { [K in Keys]?: never } // 모두 사용 불가능 ) & { [K in Exclude<keyof T, Keys>]: T[K]; // 나머지는 정의된 대로 }; -? 는 선택형 표시를 강제로 지워 필수로 만드는 매핑 타입 변경자다. 유니온의 두 멤버가 "지정한 키들이 전부 필수"이거나 "전부 never "인 두 극단만 표현해서, 일부만 쓰는 중간 상태를 막는다. -?의 실제 효과 — 있어도 없어도 같을 수 있다 type WithoutMinusQ = { [K in Keys]: T[K] }; // a 필수 type WithMinusQ = { [K in Keys]-?: T[K] }; // a 필수 (동일) Keys 가 이미 별도 제네릭 매개변수라서 비동형 매핑이고, 그래서 -? 없이도 이미 필수로 나온다. 직접 확인한 결과, 이 경우 -? 가 있든 없든 결과가 완전히 같았다. 기능적으로는 중복이다. 그런데도 -?를 쓰는 이유 — 의도를 명시하는 방어적 표기 type AllOrNone<T, Keys extends keyof T> = ( | { [K in Keys]-?: T[K] } // "이건 반드시 필수여야 한다"는 의도를 코드로 명시 | { [K in Keys]?: never } ) & { ... }; Keys 가 별도 매개변수를 거쳐서 "우연히" 필수가 된다는 사실은 코드를 처음 보는 사람 입장에서 당연하지 않다. -? 를 명시적으로 써두면 "어쩌다 필수인 게 아니라 의도적으로 필수로 만든 것"이라는 게 코드만 봐도 드러난다. 나중에 Keys 가 keyof T 로 직접 대체되는 리팩터링이 일어나도 안전하게 동작하도록 지켜주는 역할도 한다. -?가 실제로 필요해지는 경우 keyof T 를 직접 쓴 동형 매핑이라면 얘기가 달라진다. type ForceRequired<T> = { [K in keyof T]-?: T[K] }; 이 경우엔 -? 가 없으면 원본의 ? 가 자동으로 유지되어버리므로, -? 가 실제로 효과를 내는 유일한 경우 가 된다. Required<T> 의 실제 정의도 이 형태다. type Required<T> = { [P in keyof T]-?: T[P] }; 동형 vs 비동형 매핑 타입 동형(homomorphic) : [K in keyof T] 처럼 keyof T 를 직접 써서 순회하는 매핑. 원본 T의 ? (선택성), readonly 가 자동으로 그대로 복사된다. type T = { a?: string; readonly b: number }; type Copy = { [K in keyof T]: T[K] }; // { a?: string; readonly b: number } ← 원본 그대로 유지 비동형(non-homomorphic) : keyof T 가 아니라 별도 이름(제네릭 매개변수 등)을 거쳐 순회하는 매핑. 값이 keyof T 와 같아도 T와의 연결이 끊겨, ? 와 readonly 가 복사되지 않고 기본값(필수, 수정 가능)으로 초기화된다. type ViaKeys<Keys extends keyof T> = { [K in Keys]: T[K] }; type Result = ViaKeys<"a" | "b">; // { a: string; b: number } ← ?와 readonly 둘 다 사라짐 기준 : 매핑 타입이 순회하는 자리에 keyof T 가 직접 적혀 있는가(동형), 아니면 다른 이름을 거쳐 있는가(비동형). 정리 매핑 방식 원본의 ? 유지 여부 -? 의 효과 [K in keyof T] (동형) 유지됨 실제로 필요 — 강제로 필수화할 때 [K in Keys] (Keys는 별도 매개변수, 비동형) 유지 안 됨 (기본 필수) 중복 — 의도 명시용 방어적 표기 AllOrNone2 — 최소 1개 조건까지 추가 type AllOrNone2<T, Keys extends keyof T> = ( | { [K in Keys]-?: T[K] } | { [K in Keys]?: never } ) & Split<T>; // 1개 이상의 프로퍼티가 필수 function loadVideo2( format: AllOrNone2<VideoFormatURLs, "format360p" | "format480p">, ) {} loadVideo2({ format360p: new URL(""), format480p: new URL(""), }); // ○ — 지정한 키 둘 다 있음 loadVideo2({ format1080p: new URL("") }); // ○ — 지정한 키는 없고 다른 키가 있음 loadVideo2({ format480p: new URL(""), format1080p: new URL(""), }); // ✕ — 지정한 키 중 하나만 있음 직접 검증한 결과, 세 케이스 모두 의도대로 동작했다. format360p 와 format480p 는 "함께 있거나 둘 다 없거나"만 허용되고, 나머지 포맷( format1080p 등)은 Split<T> 덕분에 최소 1개는 있어야 한다는 조건이 함께 적용된다. AllOrNone3 — 내장 헬퍼 타입으로 구현 type AllOrNone3<T, Keys extends keyof T> = ( | Required<Pick<T, Keys>> | Partial<Record<Keys, never>> ) & Split<T>; Required<Pick<T, Keys>> 가 "지정한 키들을 전부 필수로", Partial<Record<Keys, never>> 가 "지정한 키들을 전부 선택형 never 로"에 대응한다. 직접 만든 매핑 타입과 같은 결과를 내장 유틸리티 조합으로 표현한 것이다. 유니온을 인터섹션으로 변환하기 — UnionToIntersection 다른 타입을 파생하려면 유니온 타입을 인터섹션 타입으로 먼저 변환해야 할 때가 있다. type BasicVideoData = { /* 구현 중 */ }; type Format320 = { urls: { format320p: URL } }; type Format480 = { urls: { format480p: URL } }; type Format720 = { urls: { format720p: URL } }; type Format1080 = { urls: { format1080p: URL } }; // 한 가지 이상의 포맷을 반드시 정의하도록 요구하는 타입 type Video = BasicVideoData & (Format320 | Format480 | Format720 | Format1080); 원문 노트의 Format320 ~ Format1080 정의가 전부 { urls: { format320p: URL } } 로 똑같이 적혀 있었다. 확인해보니 그래서 네 타입이 완전히 동일한 타입이 되어 유니온에서 하나로 합쳐졌고, keyof Video["urls"] 가 never 가 아니라 "format320p" 하나만 나온 것이었다. 각 타입의 프로퍼티 이름을 실제 포맷에 맞게( format480p , format720p , format1080p ) 고치면 이 문제가 해소된다. type FormatKeys = keyof Video["urls"]; // 원문 의도: "모든 포맷 키의 유니온"이어야 하는데, 위 복붙 실수 때문에 "format320p" 하나만 나왔었다. type Video2 = BasicVideoData & { urls: { format320p: URL; format480p: URL; format720p: URL; format1080p: URL; }; }; // 모든 키의 유니온 타입이 나온다 type FormatKeys2 = keyof Video2["urls"]; 하지만 Video (유니온 버전)처럼 인터섹션으로 묶인 여러 객체 타입에서, 각 타입 안의 프로퍼티 이름들을 유니온으로 뽑아내려면, 먼저 유니온을 인터섹션으로 바꿔야 한다. type UnionToIntersection<T> = (T extends any ? (x: T) => any : never) extends ( x: infer R, ) => any ? R : never; 작동 순서 UnionToIntersection<string | number> 를 예로 든다. 1단계 — 네이키드 T를 함수 매개변수로 감싸며 분배시킨다 T extends any ? (x: T) => any : never T 가 네이키드 타입 매개변수 (조건부 타입의 검사 대상에 아무것도 감싸지 않고 그대로 놓인 상태)라서, 유니온이 각 멤버로 쪼개져 개별적으로 처리된 뒤 다시 유니온으로 합쳐진다. 이를 분배 조건부 타입(distributive conditional type) 이라 한다. = (T extends any ? (x:T)=>any : never) 를 T=string, T=number 각각에 적용 = ((x: string) => any) | ((x: number) => any) T extends any 는 항상 참이라 : never ( false 분기)는 사실상 도달하지 않는 죽은 코드다. TypeScript의 조건부 타입 문법이 T extends U ? X : Y 삼항 형태만 존재하고 Y 없이 X 만 쓰는 문법이 없어서, 의미 없는 자리라도 채워야 한다. 그 자리에 "실행되지 않는다"는 의도를 가장 잘 드러내는 never 를 관례적으로 넣는다. 실제로 이 자리에 다른 타입을 넣어도 최종 결과는 달라지지 않는다. 2단계 — 함수 유니온을 다시 하나의 조건부 타입에 (이번엔 감싸지 않은 채로) 넣는다 (위에서 만든 함수 유니온) extends (x: infer R) => any ? R : never 이번에 검사 대상은 T 가 아니라 1단계에서 이미 만들어진 "함수 타입들의 유니온" 자체 다. 이건 네이키드 타입 매개변수가 아니라 이미 계산이 끝난 하나의 구체적인 타입이라서, 더 이상 분배되지 않고 통째로 검사된다. 3단계 — 함수 매개변수의 반변성이 유니온을 인터섹션으로 뒤집는다 함수 타입의 매개변수 자리는 반변(contravariant) 이다. 공변과 반변 — UnionToIntersection과의 연결 공변(covariant) "안의 타입이 좁으면(하위 타입이면), 바깥 타입도 좁다(하위 타입이다)"는 방향이 그대로 유지되는 것이다. string 은 string | number 의 하위 타입 (더 좁음) ↓ 배열로 감싸도 방향 유지됨(공변) string[] 은 (string|number)[] 의 하위 타입 (더 좁음) string 이 string | number 보다 좁은 것처럼, 배열로 감싸도 그 좁고 넓은 관계가 그대로 유지된다. 이게 "공변"이라는 말의 뜻이다 — "함께(co) 변한다(vary)", 즉 안쪽 타입의 방향과 바깥 타입의 방향이 같은 쪽으로 움직인다. 반변과 나란히 비교하면 안쪽 관계 바깥 타입에서의 관계 방향 배열 (공변) string ⊂ string|number string[] ⊂ (string|number)[] 같은 방향 유지 함수 매개변수 (반변) string ⊂ string|number (x: string|number)=>void ⊂ (x: string)=>void 방향이 뒤집힘 배열은 안쪽이 좁으면 바깥도 좁다. 하지만 함수 매개변수는 안쪽이 넓으면(더 많은 타입을 받으면) 바깥(그 함수 자체)이 오히려 더 좁은 타입(더 구체적인, 더 유능한 함수)이 된다. 이게 공변과 반변의 결정적 차이다. UnionToIntersection에서 쓰인 것과 연결하면 바로 이 "반변" 성질 때문에 함수 매개변수 자리에서는 유니온이 인터섹션으로 뒤집혔던 것이다. (x: string | number) => void ← 이게 (x: string) => void 의 하위 타입 (반변이라 뒤집힘) string | number (넓음)를 받는 함수가, string (좁음)만 받는 함수보다 더 좁은 (더 구체적인, 더 제약이 많은) 타입으로 취급된다. 배열이었다면 정반대였을 것이다. ((x: string) => any) | ((x: number) => any) 라는 함수 유니온을 만족하는 단일 함수 타입 을 찾으려면, 그 함수는 string 도 받을 수 있고 number 도 받을 수 있어야 한다 — 즉 매개변수 타입이 string 이면서 동시에 number 인 것 , 다시 말해 string & number 여야 한다. 반변 위치에서 여러 함수 타입을 하나로 합칠 때는 매개변수 자리의 유니온이 인터섹션으로 뒤집힌다. 4단계 — infer R이 그 인터섹션을 뽑아낸다 (x: infer R) => any R 은 정확히 3단계에서 계산된 "매개변수 자리의 인터섹션 타입"이 된다. UnionToIntersection<string | number> 의 결과는 string & number 가 된다. 객체 타입에 적용하면( { a: string } | { b: number } ) 결과는 { a: string } & { b: number } 가 되어, 양쪽 프로퍼티를 모두 가진 값만 통과하는 타입이 만들어진다. 정리하면 전체 트릭은 이렇다. 1. T(유니온)를 네이키드 자리에 놓아 분배시켜서, "함수들의 유니온"을 만든다 2. 그 함수 유니온을 다시 하나의 조건부 타입에 (감싸지 않은 채로) 넣는다 → 이번엔 이미 유니온이 아니라 "함수 타입 하나"로 취급되어 분배되지 않는다 3. 함수 매개변수는 반변이라, 여러 함수를 동시에 만족하는 단일 함수를 찾으면 매개변수 자리에서 유니온이 인터섹션으로 뒤집힌다 4. infer R로 그 인터섹션을 뽑아낸다 "유니온을 인터섹션으로 뒤집는" 효과를 내는 유일한 표준 메커니즘이 함수 매개변수의 반변성이기 때문에, 굳이 함수 타입으로 감싸는 이 우회로를 쓴다. 객체 프로퍼티나 배열 요소 위치는 공변이라, 그 자리에서는 유니온이 유니온인 채로 남는다.
Janda4d adalah platform taruhan online terpercaya yang menawarkan berbagai permainan, mulai dari taruhan olahraga hingga kasino live. Dengan layanan pelanggan 24/7 dan sistem keamanan canggih, Janda4D memastikan pengalaman bermain yang aman dan menyenangkan bagi semua pengguna. Bergabunglah sekarang dan menangkan hadiah besar!nonstop. Maklumat Perhubungan: Website: https://janda4d.biz/ Alamat: Jl. Banyumas No.15, RT.3/RW.4, Menteng, Kec. Menteng, Kota Jakarta Pusat, Daerah Khusus Ibukota Jakarta 10310, Indonesia Phone : +6282990058142 Email: janda4d.biz@gmail.com Hashtag: #janda4d #portalgamejanda4d #rumahjanda4d #masukjanda4d #daftarjanda4d #permainankartujanda4d https://x.com/janda4dbiz https://www.youtube.com/@janda4dbiz https://www.pinterest.com/janda4dbiz/ https://www.blogger.com/profile/05236811047088258154 https://www.reddit.com/user/janda4dbiz/ https://medium.com/@janda4dbiz https://janda4dbiz.wordpress.com/ https://vimeo.com/janda4dbiz
이 프로그램을 사용하면 보안 취약점을 검진해 준다 내용은 다음과 같다 심각도 CWE Flaw ID 취약점 내용 Medium CWE-113 49 Content-Disposition 헤더에 외부 파일명이 검증 없이 포함되어 CRLF 헤더 삽입 가능성이 있음 Medium CWE-113 46 ZIP 다운로드 파일명이 Content-Disposition 헤더에 직접 반영되어 응답 헤더 변조 가능성이 있음 Medium CWE-326 45 RSA 키 길이가 보안 권고 수준보다 낮아 암호화 강도가 충분하지 않음 Medium CWE-780 44 RSA 암복호화에 OAEP 패딩이 적용되지 않아 안전하지 않은 패딩 방식이 사용됨 Medium CWE-73 37 외부 path 값이 S3 업로드 object key로 사용되어 임의 경로 지정 가능성이 있음 Medium CWE-73 36 외부 path 값이 S3 조회 object key로 사용되어 임의 객체 접근 가능성이 있음 Medium CWE-73 38 원본 파일명이 실제 물리 저장 경로에 사용되어 경로 조작 가능성이 있음 Medium CWE-73 27 문서 key가 로컬 저장 경로 결합에 사용되어 기준 디렉터리 밖으로 벗어날 수 있음 Medium CWE-73 26 로컬 문서 쓰기 경로가 외부 key로 결정되어 임의 경로 쓰기 가능성이 있음 Medium CWE-73 25 문서 조회 시 검증되지 않은 key가 파일시스템 경로로 사용됨 Medium CWE-73 23 문서 저장 시 검증되지 않은 key가 파일시스템 경로로 사용됨 Medium CWE-73 20 문서 삭제 대상 경로가 외부 key로 결정되어 기준 경로 외 파일 삭제 가능성이 있음 Medium CWE-73 19 S3 문서 삭제 key에 대한 형식 제한이 없어 임의 객체 지정 가능성이 있음 Medium CWE-73 43 사용자 파일명이 로컬 파일 존재 확인 및 경로 처리에 사용되어 경로 조작 가능성이 있음 Medium CWE-73 42 첨부파일 저장 경로 생성 시 원본 파일명의 경로 문자가 반영될 수 있음 Medium CWE-73 41 정규화되지 않은 경로 결합으로 상위 디렉터리 접근 가능성이 있음 Medium CWE-73 40 최종 물리 경로가 첨부파일 기준 디렉터리 밖인지 확인하지 않음 Medium CWE-73 32 외부 fileName이 PDF 병합 출력 경로에 직접 결합됨 Medium CWE-73 31 PDF 출력 파일명에 경로 구분자나 상위 경로 문자가 포함될 수 있음 Medium CWE-73 30 PDF 출력 디렉터리와 파일명 결합 후 정규화 검사가 없음 Medium CWE-73 29 병합 파일 생성 경로가 허용된 저장 영역인지 보장되지 않음 Medium CWE-73 28 병합 결과 파일 경로를 외부 입력이 제어할 가능성이 있음 Medium CWE-73 48 DB의 PHYS_PATH와 ZIP entry 이름을 신뢰하여 임의 파일 접근 및 Zip Slip 형태의 entry 생성 가능성이 있음 Medium CWE-918 17 외부 report_id와 설정 URL을 이용한 요청 목적지가 충분히 제한되지 않아 SSRF 가능성이 있음 Medium CWE-918 33 OnlyOffice callback의 fileUrl/resultUrl을 그대로 다운로드하여 임의 URL 요청과 redirect 우회가 가능함 Low CWE-117 18 외부 입력값이 제어문자 제거 없이 로그에 기록되어 로그 위조 가능성이 있음 Low CWE-117 24 문서 key가 정제되지 않은 상태로 로그에 기록됨 Low CWE-117 22 문서 저장 관련 외부 값이 로그에 직접 기록됨 Low CWE-117 21 문서 처리 오류 로그에 외부 값이 직접 포함됨 Low CWE-117 39 요청 URI 등 사용자 제어값이 인터셉터 로그에 그대로 기록됨 Low CWE-117 35 SSO 사용자 ID 원문이 로그에 기록되어 로그 위조 및 개인정보 노출 우려가 있음 Low CWE-117 16 메일 발신자 값이 정제 없이 로그에 기록됨 Low CWE-117 15 메일 수신자 값이 정제 없이 로그에 기록됨 Low CWE-117 14 메일 제목이 정제 없이 로그에 기록됨 Low CWE-117 13 테스트 환경 수신자 변경 전 주소가 로그에 직접 기록됨 Low CWE-117 12 SES 발송 정보의 외부 값이 로그에 직접 포함됨 Low CWE-117 11 SES 발송 실패 로그에 수신자·예외 메시지가 직접 포함됨 Low CWE-117 10 SMTP 발송 모드 로그에 외부 값이 직접 포함됨 Low CWE-117 9 메일 발송 예외 로그에 외부 값이 직접 포함됨 Low CWE-117 8 SES 성공 로그의 수신자 값이 정제되지 않음 Low CWE-117 7 SES 실패 로그의 수신자 및 예외 값이 정제되지 않음 Low CWE-117 6 SMTP 발송 실패 로그에 수신자 값이 문자열 결합으로 직접 기록됨 Low CWE-117 5 단일 메일 발송 시 제목 등 외부 값이 정제 없이 로그에 기록됨 Low CWE-117 47 ZIP 처리 실패 로그에 외부 파일명이 직접 기록됨 Low CWE-117 34 OnlyOffice 처리 로그에 외부 입력이 직접 포함됨 Low CWE-597 3 문자열 docKey를 != 연산자로 비교하여 값이 같아도 참조에 따라 오동작할 수 있음 Low CWE-597 2 문자열 docKey 비교에 참조 비교 연산자를 사용함 Low CWE-597 1 문자열 docKey 비교에 != 연산자를 사용해 분기 결과가 불안정할 수 있음 Low CWE-209 4 예외의 상세 메시지가 사용자 응답 JavaScript에 포함되어 내부 정보가 노출될 수 있음 이 값들에 대한 수정은 반드시 최소한으로 바꿔야 한다 서비스는 이미 올라와 있고, 마지막으로 검토를 하기 직전에 소스코드를 수정하는 부분이기 때문이다
문제 짝지어 제거하기 문제 설명 짝지어 제거하기는, 알파벳 소문자로 이루어진 문자열을 가지고 시작합니다. 먼저 문자열에서 같은 알파벳이 2개 붙어 있는 짝을 찾습니다. 그다음, 그 둘을 제거한 뒤, 앞뒤로 문자열을 이어 붙입니다. 이 과정을 반복해서 문자열을 모두 제거한다면 짝지어 제거하기가 종료됩니다. 문자열 S 가 주어졌을 때, 짝지어 제거하기를 성공적으로 수행할 수 있는지 반환하는 삼수를 완성해 주세요. 성공적으로 수행할 수 있으면 1을, 아닐 경우 0을 리턴해주면 됩니다. 예를 들어, 문자열 S = baabaa 라면 b aa baa $\rightarrow$ bb aa $\rightarrow$ aa $\rightarrow$ 의 순서로 문자열을 모두 제거할 수 있으므로 1을 반환합니다. 제한사항 문자열의 길이: 1,000,000 이하의 자연수 문자열은 모두 소문자로 이루어져 있습니다. 입출력 예시 s result baabaa 1 cdcd 0 문제 풀이 맨 앞의 문자부터 하나씩 스택에 넣으면서 맨 위의 문자와 넣을 문자가 같으면 제거하는 방식으로 하면 해결할 수 있을 것 같다. baabaa 를 예시로 한 번 해보자. b 스택이 비어 있으니 b 를 append 한다. 스택 b a 스택이 비어 있지 않으나 스택의 제일 위 b 와 a 는 동일하지 않으므로 a 를 append 한다. 스택 a b a 스택이 비어있지 않고 스택의 제일 위 a 와 동일하므로 스택의 a 를 pop 하고 넘어간다. 스택 b b 스택이 비어있지 않고 스택의 제일 위 b 와 동일하므로 스택의 b 를 pop 하고 넘어간다. 스택 a 스택이 비어 있으니 a 를 append 한다. 스택 a a 스택이 비어있지 않고 스택의 제일 위 a 와 동일하므로 스택의 a 를 pop 하고 넘어간다. 스택 다 끝나고 스택의 길이가 0이면 1을, 그렇지 않으면 0을 반환하면 끝난다. def solution(s): stack = [] for ch in s: if not stack: stack.append(ch) continue if stack[-1] == ch: stack.pop() continue stack.append(ch) return 1 if len(stack) == 0 else 0
Một biển hiệu quảng cáo thu hút, bền bỉ và đúng chuẩn thẩm mỹ luôn là “vũ khí” quan trọng giúp các cửa hàng, quán cafe, showroom hay doanh nghiệp tạo ấn tượng đầu tiên với khách hàng. Tuy nhiên, khi chuẩn bị khai trương hoặc nâng cấp mặt bằng, nhu cầu tìm kiếm một đơn vị in biển quảng cáo gần đây nhanh chóng, chi phí tối ưu và đảm bảo chất lượng thi công luôn là bài toán khiến nhiều chủ đầu tư trăn trở. Hiểu được nỗi bận tâm đó, Quảng Cáo Tùng Huyền (với nền tảng trực tuyến chính thức tại bangquangcaoth.com) đã mang đến hệ sinh thái dịch vụ thiết kế, in ấn và thi công biển hiệu trọn gói, đáp ứng linh hoạt mọi yêu cầu quảng cáo từ đơn giản đến phức tạp. Vì Sao Nhu Cầu Tìm Đơn Vị In Biển Quảng Cáo Gần Đây Ngày Càng Tăng? Trong thời đại kinh doanh cạnh tranh gay gắt, thời gian chính là vàng bạc. Việc tìm kiếm cơ sở in biển quảng cáo gần đây đem lại nhiều lợi ích thiết thực: Khảo sát tận nơi cấp tốc: Giúp đội ngũ kỹ thuật dễ dàng di chuyển, đo đạc mặt tiền thực tế và tư vấn phương án thi công chuẩn xác nhất. Tiết kiệm chi phí và thời gian vận chuyển: Giảm thiểu rủi ro va đập hay hỏng hóc vật liệu trong quá trình di chuyển bảng hiệu khổ lớn. Xử lý sự cố và bảo hành nhanh chóng: Khi phát sinh các yêu cầu chỉnh sửa hoặc bảo trì hệ thống đèn, chữ nổi, đơn vị gần kề sẽ hỗ trợ xử lý tức thì. Các Hạng Mục Dịch Vụ Nổi Bật Tại Bangquangcaoth.com Truy cập danh mục dịch vụ in biển quảng cáo gần đây, khách hàng sẽ tìm thấy đa dạng các giải pháp truyền thông thương hiệu phù hợp với từng ngân sách: Thi Công Chữ Nổi Cao Cấp (Mica – Inox – Alu) Đây là giải pháp trang trí mặt tiền phổ biến cho các chuỗi spa, thẩm mỹ viện, quán cafe hay văn phòng làm việc. Chữ nổi mica bắt sáng tốt kết hợp chân viền inox hoặc inox mạ vàng mang lại vẻ đẹp sang trọng, đẳng cấp và hiện đại. In Bạt Hiflex Khổ Lớn & Biển Hiệu Ngoài Trời Trang bị công nghệ in phun kỹ thuật số tiên tiến, Quảng Cáo Tùng Huyền nhận in bạt Hiflex khổ lớn sắc nét, chống phai màu dưới điều kiện thời tiết khắc nghiệt. Hệ thống khung sắt chịu lực được gia công kiên cố, an toàn tuyệt đối. Biển Vẫy & Hộp Đèn Hút Nổi Cấp Tốc Dành cho những cửa hàng cần gấp mặt tiền hoặc tận dụng góc nhìn dọc đường phố. Hộp đèn mica hút nổi hai mặt mang lại hiệu ứng thu hút thị giác cao vào ban đêm. In Ấn Vật Phẩm Truyền Thông & Sự Kiện Bên cạnh bảng hiệu lớn, đơn vị còn cung cấp dịch vụ sản xuất standee chân X, standee cuốn, in poster, băng rôn, decal dán kính và photobooth phục vụ sự kiện khai trương, hội chợ. Điểm Khác Biệt Giúp Quảng Cáo Tùng Huyền Được Tin Tưởng Không chỉ đơn thuần là địa chỉ in biển quảng cáo đáng tin cậy, Quảng cáo Tùng Huyền chinh phục khách hàng nhờ quy trình làm việc chuyên nghiệp: Công nghệ hiện đại: Sử dụng máy cắt CNC, Laser và hệ thống máy in phun kỹ thuật số cao cấp, đảm bảo sản phẩm ra lò chuẩn từng milimet về màu sắc lẫn nét chữ. Đội ngũ giàu kinh nghiệm: Tăng tính thẩm mỹ nhờ những thiết kế độc đáo, bố cục chuẩn phong thủy và tư vấn chất liệu tối ưu chi phí cho từng ngành nghề. Báo giá minh bạch, minh bạch pháp lý: Cam kết không phát sinh chi phí phụ, cung cấp đầy đủ hóa đơn chứng từ cho doanh nghiệp. Thông tin liên hệ Quảng Cáo Tùng Huyền: Địa chỉ: 204 F325, Phường Đồng Thuận, Quảng Trị Hotline/Zalo: 0877 912 313 Email: inantunghuyen@gmail.com Website: https://bangquangcaoth.com/
4회차. PRD 스킬과 GitHub MCP로 TodoList 앱 만들기 이번 회차에서는 지금까지 준비한 도구(Claude Code, prd-feature 스킬, GitHub MCP)로 간단한 TodoList 웹앱을 만든다. 목표는 앱 자체보다 팀에서 코드를 관리하는 순서 를 한 번 끝까지 해 보는 것이다. 기획(PRD) → 작업 나누기(Issue) → 규칙 정하기 → [브랜치 → 구현 → 커밋 → PR → 리뷰 → 병합] 반복 모든 Claude Code 요청은 이 문서의 프롬프트를 그대로 복사해 붙여넣으면 된다. 저장소 이름이나 Issue 번호는 Claude가 직접 확인하도록 작성되어 있다. bash 블록은 Git Bash에서, text 블록은 Claude Code 대화창에 입력한다. 각 프롬프트는 "결과를 보여주고 멈춰줘"로 끝난다. 결과를 눈으로 확인한 뒤 다음 프롬프트로 넘어간다. 시간 배분 (30분) 단계 내용 시간 0 사전 준비 확인 3분 1 기획: PRD 작성 5분 2 작업 나누기: Issue 생성 3분 3 팀 규칙 정하기 3분 4 기능 개발 사이클 1회 (Issue 1개) 10분 5 동시 작업과 충돌 해결 4분 6 마무리 2분 시간이 부족하면 4단계까지 수업에서 진행하고, 남은 Issue와 5단계는 과제로 진행한다. 0. 사전 준비 (이전 회차 요약) 이전 회차에서 한 작업이다. 수업 전에 아래 표의 확인 명령이 모두 통과하는지 확인한다. 회차 한 일 확인 명령 1회차 Claude Code 설치, 계정 로그인 claude --version 2회차 prd-feature 스킬 생성 ls .claude/skills/prd-feature/SKILL.md 3회차 연습 저장소 claude-code-practice 생성·클론, GitHub MCP 등록 git remote -v , claude mcp list 이번 회차는 3회차의 claude-code-practice 저장소 폴더에서 이어서 진행한다. 0-1. PAT 권한 추가 3회차에서는 읽기 위주로 토큰을 만들었다. 이번에는 PR 생성·병합까지 하므로 권한을 올린다. GitHub 토큰 설정 → 3회차 토큰 선택 → Edit → Repository permissions를 아래처럼 바꾸고 저장한다. 토큰 값은 바뀌지 않는다. 권한 설정 Contents Read and write Issues Read and write Pull requests Read and write 0-2. 토큰 입력 후 Claude Code 실행 VS Code에서 claude-code-practice 폴더를 열고, Git Bash에서 한 줄씩 실행한다. 첫 줄에서 토큰을 붙여넣고 Enter를 누른다(화면에 표시되지 않음). read -r -s -p "GitHub PAT: " GITHUB_PAT export GITHUB_PAT claude mcp list 목록에 github 가 없으면 3회차 4단계의 claude mcp add ... 명령으로 다시 등록한다. prd-feature 스킬이 이 폴더에 없으면 2회차에서 만든 폴더에서 복사한다: mkdir -p .claude/skills && cp -r <2회차_폴더>/.claude/skills/prd-feature .claude/skills/ claude 0-3. 환경 점검 (Claude Code) 작업 시작 전에 환경을 점검해줘. 1. git remote -v 로 origin 저장소(OWNER/REPO)를 확인하고, 현재 브랜치와 변경 파일이 있는지 알려줘. 2. .claude/skills/prd-feature/SKILL.md 가 있는지 확인해줘. 3. GitHub MCP로 origin 저장소의 기본 브랜치와 열린 Issue, 열린 PR 개수를 조회해줘. 파일은 수정하지 말고, 결과를 표로 보여주고 멈춰줘. 3회차에서 만든 [실습] GitHub MCP 연결 확인 Issue가 열려 있으면 브라우저에서 닫는다. 세 항목이 모두 정상이면 시작한다. MCP 조회가 실패하면 7단계 문제 해결 표를 본다. 현재 브랜치가 main 이 아니거나 모르는 변경 파일이 있으면 먼저 정리한다. 1. 기획: PRD 작성 왜 하나? 무엇을 만들지, 무엇을 만들지 않을지, 어떻게 되면 "완료"인지를 먼저 글로 정한다. 팀원 모두가 같은 기준으로 일하기 위한 문서다. /prd-feature 간단한 TodoList 웹앱의 PRD를 작성해줘. - 기능: 할 일 추가, 할 일 목록 표시, 완료 체크/해제, 삭제, 새로고침해도 목록 유지 - 제외: 로그인, 서버·DB, 마감일, 카테고리, 디자인 프레임워크 - 기술: HTML, CSS, JavaScript만 사용. 빌드 도구 없이 index.html을 브라우저로 열면 실행. 저장은 localStorage - 빈 문자열이나 공백만 있는 할 일은 추가되지 않아야 해 - 완료조건은 "입력 → 기대 결과" 형태로 브라우저에서 직접 확인할 수 있게 써줘 질문이 있으면 한 번에 모아서 물어봐. PRD를 화면에 보여주고 멈춰줘. 파일 저장과 구현은 아직 하지 마. 추가 질문이 오면 직접 답하거나 "나머지는 네가 합리적으로 정해줘"라고 답한다. PRD에서 아래를 확인한다. 범위에 포함/제외 가 분명히 나뉘어 있는가 완료조건이 "잘 동작한다"가 아니라 확인 가능한 문장 인가 (예: "공백만 입력하고 추가 → 목록에 변화 없음") 확인이 끝나면 저장한다. 좋아. 이 PRD를 docs/PRD.md 로 저장해줘. 저장만 하고 멈춰줘. 커밋은 3단계에서 팀 규칙 파일과 함께 한다. 2. 작업 나누기: Issue 생성 왜 하나? 큰 기능을 한 번에 만들면 리뷰도 어렵고 여러 명이 나눠 일할 수 없다. PRD를 한 사람이 하루 안에 끝낼 수 있는 크기 의 Issue로 나눈다. Issue 하나 = 브랜치 하나 = PR 하나가 기본 단위다. docs/PRD.md 를 읽고 구현 작업을 GitHub Issue 3개로 나눠줘. 1) 기본 화면 + 할 일 추가 + 목록 표시 2) 완료 체크/해제 + 삭제 3) localStorage 저장 (새로고침 후 유지) 각 Issue 본문에는 "목적 / 작업 내용 / 완료조건(체크박스)" 을 넣고, 완료조건은 PRD에서 가져와줘. 라벨은 enhancement 를 붙여줘. origin 저장소에 같은 제목의 Issue가 있는지 먼저 확인하고, 생성할 3개의 제목과 본문 초안을 보여주고 멈춰줘. 아직 생성하지 마. 초안을 확인한 뒤 생성한다. 확인했어. GitHub MCP로 3개의 Issue를 생성하고 번호와 URL을 표로 보여줘. 브라우저에서 저장소의 Issues 탭을 열어 3개가 생성되었는지 확인한다. 3. 팀 규칙 정하기 왜 하나? 팀원마다 브랜치 이름, 커밋 메시지 형식이 다르면 기록을 읽을 수 없다. 규칙을 파일로 저장해 두면 사람도 Claude도 같은 규칙을 따른다. 파일 역할 CLAUDE.md Claude Code가 매번 자동으로 읽는 프로젝트 규칙 .github/pull_request_template.md PR을 만들 때 자동으로 채워지는 본문 양식 .gitignore 커밋하면 안 되는 개인 설정 파일 제외 팀 협업 규칙 파일을 만들어줘. 1. CLAUDE.md - 프로젝트: TodoList 웹앱 (HTML/CSS/JS, localStorage). 요구사항은 docs/PRD.md 를 따른다. - 브랜치: main 에 직접 커밋하지 않는다. 작업 브랜치는 최신 main 에서 만들고 이름은 <타입>/<이슈번호>-<짧은-영문-설명> (예: feat/1-add-todo) - 커밋 메시지: <타입>: <한글 요약> (#이슈번호) 예) feat: 할 일 추가 기능 구현 (#1) - 타입: feat(기능), fix(버그), docs(문서), refactor(구조 개선), chore(설정) - 하나의 브랜치에서는 하나의 Issue만 작업한다. Issue 범위 밖의 파일은 수정하지 않는다. - PR 본문에는 "Closes #이슈번호" 를 넣는다. 강제 푸시(force push)는 하지 않는다. 2. .github/pull_request_template.md : 관련 Issue(Closes #), 변경 내용, 확인 방법, 완료조건 체크리스트 섹션 3. .gitignore : .claude/settings.local.json 파일 내용을 보여주고 멈춰줘. 확인한 뒤 커밋·푸시한다. 초기 설정은 코드가 아니므로 이번 한 번만 main 에 직접 올린다. 좋아. docs/PRD.md, CLAUDE.md, .github/pull_request_template.md, .gitignore, .claude/skills/prd-feature 를 커밋해줘. 메시지는 "chore: 프로젝트 기획 문서와 협업 규칙 추가" 로 하고 origin main 에 푸시해줘. 결과를 보여주고 멈춰줘. 스킬 폴더( .claude/skills )를 함께 커밋하면 저장소를 클론한 팀원도 같은 /prd-feature 스킬을 쓸 수 있다. 처음 푸시할 때 브라우저 로그인 창이 뜨면 GitHub 계정으로 인증한다. git push 인증은 MCP 토큰과 별개다. 4. 기능 개발 사이클 (첫 번째 Issue) 이 단계가 이번 회차의 핵심이다. 모든 기능은 아래 6단계를 반복한다. 1 브랜치 생성 → 2 구현 → 3 직접 확인 → 4 커밋·푸시 → 5 PR 생성·리뷰 → 6 병합·정리 1 브랜치 생성 왜? main 은 항상 동작하는 상태로 유지한다. 새 작업은 별도 브랜치에서 하고, 검토가 끝난 뒤에만 main 에 합친다. enhancement 라벨이 붙은 열린 Issue 중 번호가 가장 작은 Issue를 작업할 거야. main 으로 전환해서 origin 의 최신 내용을 pull 받은 뒤, CLAUDE.md 규칙에 맞는 이름으로 작업 브랜치를 만들고 전환해줘. Issue 번호·제목과 만든 브랜치 이름을 보여주고 멈춰줘. Git Bash에서 직접 확인한다. git branch --show-current 2 구현 현재 브랜치의 Issue 내용과 docs/PRD.md 를 기준으로 구현해줘. 이 Issue 범위만 작업하고, 다른 Issue의 기능은 미리 만들지 마. 끝나면 변경한 파일 목록과, 브라우저에서 확인할 방법을 완료조건별로 알려주고 멈춰줘. 커밋은 아직 하지 마. 3 직접 확인 탐색기에서 index.html 을 더블클릭해 브라우저로 연다. Claude가 알려준 확인 방법대로 완료조건을 하나씩 직접 해 본다. 문제가 있으면 본 그대로 전달한다. 브라우저에서 확인해보니 [본 현상]이 발생해. 원인을 설명하고 수정해줘. 수정 후 다시 확인 방법을 알려주고 멈춰줘. 변경 내용도 확인한다. git status --short git diff 4 커밋·푸시 왜? 커밋은 "의미 있는 한 단위의 변경"을 기록하는 것이다. 메시지만 읽어도 무엇을 왜 바꿨는지 알 수 있어야 한다. 이번 Issue와 관련된 파일만 스테이징하고, CLAUDE.md 규칙에 맞는 커밋 메시지를 제안해줘. 커밋할 파일 목록과 메시지를 보여주고 멈춰줘. 좋아. 그 메시지로 커밋하고 현재 브랜치를 origin 에 푸시해줘. 결과를 보여주고 멈춰줘. 5 PR 생성과 리뷰 왜? PR(Pull Request)은 "내 브랜치를 main에 합쳐도 되는지 검토해 달라"는 요청이다. 팀에서는 다른 사람이 코드를 읽고 승인한 뒤에만 병합한다. GitHub MCP로 현재 브랜치 → main PR을 만들어줘. .github/pull_request_template.md 양식을 따르고, 본문에 "Closes #이슈번호", 변경 내용, 확인 방법, 완료조건 체크리스트(내가 3에서 확인한 항목은 체크)를 넣어줘. 같은 브랜치의 열린 PR이 이미 있으면 새로 만들지 말고 알려줘. PR 번호와 URL을 보여주고 멈춰줘. 리뷰어 역할로 Claude에게 코드 리뷰를 맡긴다. 팀에서는 이 역할을 동료가 한다. 방금 만든 PR을 리뷰어 입장에서 검토해줘. PRD 완료조건 충족 여부, 버그 가능성, 이 Issue 범위를 벗어난 변경이 있는지 확인하고, 결과를 GitHub MCP로 PR에 코멘트로 남겨줘. 코드는 수정하지 마. 브라우저에서 PR을 열어 Files changed 탭(변경 내용)과 Conversation 탭(리뷰 코멘트)을 확인한다. 리뷰에서 고칠 점이 나오면 2~4를 같은 브랜치에서 반복한다. 같은 브랜치에 푸시하면 PR에 자동으로 추가된다. 새 PR을 만들지 않는다. 6 병합과 정리 PR의 리뷰 코멘트 중 해결되지 않은 것이 있는지 확인해줘. 없으면 GitHub MCP로 PR을 merge 방식으로 병합해줘. 병합 후 관련 Issue가 자동으로 닫혔는지 확인하고, 로컬에서 main 으로 전환해 최신 내용을 pull 받은 다음, 병합된 작업 브랜치를 로컬과 origin 에서 삭제해줘. 결과를 보여주고 멈춰줘. 브라우저에서 확인한다. PR 상태가 Merged 인가 Issue가 Closed 이고, PR과 연결되어 있는가 main 의 Commits 기록에 방금 커밋이 있는가 첫 번째 Issue 완료. 남은 Issue도 1~6 프롬프트를 그대로 다시 붙여넣어 진행한다. 1의 프롬프트가 enhancement 라벨이 붙은 열린 Issue 중 가장 작은 번호를 자동으로 고른다. 5. 동시 작업과 충돌 해결 (팀 작업 연습) 실제 팀에서는 여러 명이 같은 시간에 서로 다른 브랜치에서 작업한다. 먼저 병합된 변경이 있으면 나중 사람은 최신 main을 자기 브랜치에 반영 해야 한다. 혼자서 두 명의 역할을 하며 연습한다. 첫 번째 Issue를 마치고 남은 Issue가 2개일 때 진행한다. 아래에서는 번호가 작은 Issue를 A (완료·삭제), 큰 Issue를 B (localStorage 저장)라고 부른다. 팀원 두 명이 동시에 작업하는 상황을 연습할 거야. enhancement 라벨이 붙은 열린 Issue 2개 중 번호가 작은 것을 A, 큰 것을 B라고 부를게. 1. 최신 main 에서 A 브랜치와 B 브랜치를 각각 만들어줘. 2. A 브랜치에서 A를 구현·커밋·푸시하고 PR을 만들어줘. 3. B 브랜치로 전환해서 B를 구현·커밋·푸시하고 PR을 만들어줘. B 브랜치에는 A의 변경이 없는 상태여야 해. 브랜치 이름과 커밋 메시지는 CLAUDE.md 규칙을 따르고, 각 단계의 결과와 A, B의 Issue 번호·PR URL을 보여주고 멈춰줘. 병합은 하지 마. 두 PR을 브라우저에서 확인한 뒤 A를 먼저 병합한다. A의 PR을 merge 방식으로 병합해줘. 그 다음 B의 PR이 main 과 충돌이 있는지 확인해서 알려주고 멈춰줘. B 브랜치에 최신 main을 반영한다. 팀에서는 B 담당자가 하는 일이다. B 브랜치로 전환해서 origin/main 의 최신 내용을 merge 해줘. 충돌이 나면 바로 해결하지 말고, 충돌한 파일과 양쪽 변경 내용을 비교해서 설명한 뒤, 두 기능이 모두 살아있는 해결안을 보여주고 멈춰줘. 해결안을 확인한 뒤 진행한다. 좋아. 그 해결안으로 충돌을 해결해서 커밋하고 B 브랜치를 푸시해줘. 강제 푸시는 하지 마. 브라우저에서 확인할 방법(A, B 기능 모두)을 알려주고 멈춰줘. 브라우저에서 추가·완료·삭제·새로고침 후 유지를 모두 확인한 뒤, 4단계 5의 리뷰 프롬프트와 6의 병합 프롬프트로 마무리한다. 기억할 것 충돌은 오류가 아니다. 같은 파일의 같은 부분을 두 사람이 고쳤다는 뜻이다. 양쪽 내용을 이해하고 합친다. 충돌을 줄이는 방법: 작업 시작 전에 main을 pull, 브랜치는 짧게 유지, PR은 작게 자주 올리기. 6. 마무리 결과 확인 docs/PRD.md , CLAUDE.md , .github/pull_request_template.md 가 main 에 있다. 이번 회차 Issue 3개가 모두 Closed이고 각각 PR과 연결되어 있다. 모든 PR이 Merged 상태이고, 본문에 Closes #번호 와 완료조건 체크리스트가 있다. main 의 커밋 메시지가 <타입>: <요약> (#번호) 형식이다. main 의 index.html 을 열면 추가·완료·삭제·새로고침 후 유지가 모두 동작한다. 이번 회차 핵심 정리 도구 역할 규칙 PRD 무엇을·어디까지·언제 완료인지 완료조건은 확인 가능한 문장으로 Issue 작업 단위 한 사람이 하루 안에 끝낼 크기 Branch 작업 공간 main에서 직접 작업하지 않는다, 최신 main에서 시작 Commit 변경 기록 한 커밋 = 한 가지 의미, 규칙에 맞는 메시지 PR 검토 요청 Closes #번호 , 확인 방법 기록, 리뷰 후 병합 Merge main에 반영 병합 후 main pull, 작업 브랜치 삭제 정리 unset GITHUB_PAT 실습이 끝난 토큰은 GitHub 토큰 설정 에서 삭제하거나 유효기간을 확인한다. 7. 문제 해결 증상 확인 순서 /prd-feature 가 안 보임 ls .claude/skills/prd-feature/SKILL.md → 저장소 최상위 폴더에서 claude 를 실행했는지 → Claude Code 재시작 MCP 조회·생성 실패 같은 Git Bash에서 토큰을 입력했는지 → claude mcp list → 토큰 만료 → 0-1의 권한 설정 → Repository access에 저장소 포함 git push 인증 실패 MCP 토큰과 별개다. Git Bash에서 git push 를 직접 실행해 브라우저 로그인 PR 생성 실패 브랜치를 푸시했는지 → main과 차이가 있는 커밋이 있는지 → Pull requests 권한 병합 후 Issue가 안 닫힘 PR 본문에 Closes #번호 가 있는지 → PR이 기본 브랜치(main)로 병합되었는지 실수로 main에서 작업함 커밋 전이라면 아래 프롬프트 사용 Claude가 멈추지 않고 다음 단계까지 진행함 Esc로 중단 → 프롬프트 끝에 "결과를 보여주고 멈춰줘"를 붙여 다시 요청 main에서 작업한 변경을 브랜치로 옮기기: main 브랜치에서 실수로 작업했어. 커밋하지 않은 변경을 그대로 유지한 채, enhancement 라벨이 붙은 열린 Issue 중 번호가 가장 작은 Issue의 작업 브랜치를 만들어 전환해줘. 변경 파일 목록을 보여주고 멈춰줘. 해결되지 않으면 실행한 명령과 오류 메시지를 정리해 질문한다. 토큰 값은 공유하지 않는다. 자주 쓰는 명령 명령 위치 용도 git status --short Git Bash 변경 파일 확인 git diff Git Bash 커밋 전 변경 내용 확인 git branch --show-current Git Bash 현재 브랜치 확인 git log --oneline -10 Git Bash 최근 커밋 기록 확인 /mcp Claude Code GitHub MCP 연결 상태 확인 /exit Claude Code 종료 공식 참고: Skills , Claude Code MCP , GitHub MCP 서버 , Issue와 PR 연결 , 병합 충돌 해결
논리볼륨 파티션을 다시 하기 위해 기존 디스크에 있던 파일들을 백업하고 디스크를 포맷하고 다시 설정해야함 파티션 확장이나 데이터 가용성 어려움 논리 볼륨의 크기를 동적으로 변경가능 논리 볼륨의 데이터를 유지한 상태에서 디스크를 추가하거나 제거가능 논리볼륨의 구성 디스크 장치(sdb,sdc) -> 물리 볼륨 -> 볼륨 그룹(물리 볼륨을 묶어서) -> 논리 볼륨(볼륨 그룹을 논리적으로 나눠서) 파티션부터 설정 sudo fdisk /dev/sdb type => n (new make) 주볼륨으로 설정할껀지? => p 시작크기 => enter (default) 마지막 크기 => enter => 전체 디스크 크기를 모두 하나의 파티션으로 설정됨 파티션 타입바꾸기 => t 파티션 종류보기 => L 8e = >Linux LVM 선택 저장 및 나가기 => w sudo fdisk -l /dev/sdb 파티션한 목록보기 기존 논리볼륨이 있는지 알아보기 dnf list installed | grep lvm2 lvm2 명령어가 리눅스에 설치되어있는지 확인 sudo partprobe 파티션 방금바꾼게 적용되게끔(필수는 아니나 반영안된 것 같을때) sudo lvmdiskscan lvm 관점 (논리볼륨 관점)으로 디스크 스캔 물리볼륨 만들기 sudo pvcreate /dev/sdb1 lsblk로 확인한 디스크 이름을 상대로 만든다 sudo lvmdiskscan lvm 관점 (논리볼륨 관점)으로 디스크 스캔 sudo pvdisplay 물리볼륨으로 만들어진 디스크가 있는지 체크 sudo pvdisplay /dev/sdb1 lsblk로 확인한 디스크가 물리볼륨으로 만들었었는지 체크 sudo pvs 간단한 버전으로 물리볼륨 체크 볼륨그룹 만들기 sudo vgcreate vg_data /dev/sdb1 사용자가 원하는 이름으로 볼륨그룹이름 지정 lsblk로 확인한 디스크 => 대상 sudo vgdisplay vg_data 사용자가 만든 볼륨그룹을 검색 sudo vgs 간단한 버전으로 볼륨그룹 체크 논리볼륨 만들기 sudo lvcreate -n lv_data -L 10G vg_data n: 사용자가 논리볼륨 이름 지정 L: 용량 지정 마지막 인자: 사용자가 만든 꺼내다 쓸 볼륨 그룹 sudo lvdisplay 논리볼륨 보기 결과: /dev/vg_data/lv_data sudo lvs 간단한 버전으로 논리볼륨 체크 이제 파일 시스템을 넣고 mount해야 사용가능 sudo mkfs.xfs /dev/vg_data/lv_data xfs 방식으로 만든 논리볼륨을 포맷하고 싶다 이때 논리볼륨이 있는 경로인 lvdisplay 결과를 지정해야함 수동으로 mount하는 방식 sudo mkdir /mnt/data mount 연결할 디렉토리를 생성 sudo mount /dev/vg_data/lv_data /mnt/data 논리볼륨 경로랑 mount 연결할 디렉토리를 연결 df -h /mnt/data 디스크 확인 재부팅을 해도 자동 mount가 되게끔 설정 sudo vi /etc/fstab /etc/fstab이 설정파일 /dev/vg_data/lv_data /mnt/data xfs defaults 0 0 논리볼륨 경로, 연결할 디렉토리, 파일 시스템 sudo umount /mnt/data 수동으로 mount한 것을 해제 sudo mount -a /etc/fstab에서 설정한 대로 mount 되도록 테스트 디스크 크기 늘리기 sudo lvextend -L 15G /dev/vg_data/lv_data lvextend : 논리볼륨 크기를 늘린다 L : 용량지정 lvdisplay로 볼 수 있는 논리볼륨이 있는 경로지정 sudo lvs 간단한 버전으로 논리볼륨 체크 df -h | grep vg 15G로 늘렸지만 현재 10G만 쓰는 중 sudo xfs_growfs /mnt/data 크기 늘린만큼 파일시스템 포맷 영역도 늘린다 df -h /mnt/data 늘린 15G로 인식 논리볼륨 완전 삭제 sudo umount /mnt/data mount부터 해제해야함 sudo vi /etc/fstab 자동 mount 설정 지우기 sudo lvremove /dev/vg_data/lv_data lvremove: 논리볼륨 폐기 lvdisplay로 볼 수 있는 논리볼륨이 있는 경로지정 sudo vgremove vg_data vgremove: 볼륨그룹 폐기 이름을 지었던 볼륨그룹 이름 sudo pvremove /dev/sdb1 pvremove: 물리볼륨 폐기 lsblk로 확인한 디스크 sudo pvs 물리볼륨 간단히 확인 => 없어야함 sudo vgs 볼륨그룹 간단히 확인 => 없어야함 sudo lvs 논리볼륨 간단히 확인 => 없어야함 리니어 물리 볼륨에 있는 물리 익스텐트를 순서대로 할당 남는 공간이 있어도 순차적으로 쓴다 RAID 여러 하드디스크를 하나의 논리단위로 본다 미러링: 동일한 데이터를 두 개 이상의 디스크에 복사하여 저장 스트라이프: 데이터를 여러 디스크에 나누어 동시에 읽고 쓰는 방식 패리티: 데이터 복구를 위한 오류 검출 및 정정 정보 => 내결합성 성능 향상, 데이터 보호 cluster 여러대의 서버를 하나의 시스템처럼 묶어서 가용성, 부하분산 하나의 서비스를 여러 서버에 운영 RAID 0 스트라이프: 데이터를 여러 개의 물리 볼륨에 나누어(쪼개서) 저장 RAID 1 미러: 동일한 데이터를 여러 개의 물리 볼륨에 복사하여 저장하는 방식 안전성은 올라가지만 공간 효율은 절반이상으로 떨어짐 RAID 5 최소 3개이상의 디스크 패리티 데이터를 저장하는 디스크를 별도로 두지 않고 여러 디스크에 분산하여 저장 systemd 시스템을 켤때 아무거나 뒤죽박죽 실행되면 안됨 네트워크가 된 후에야 웹서버를 연결 가능한 것처럼 리눅스의 관리자 또는 지휘자 systemctl 서비스를 직접 키고 끄는 명령어
게임 소개 게임 제목은 Last Cure로 좀비 바이러스로 감염된 도시에서 동료와 함께 싸우며, 백신 재료를 모으는 좀비 아포칼립스 싱글플레이 생존 FPS입니다. 다양한 총기와 스킬을 활용하면서, 감염 구역을 탐색하고 플레이어가 성장해나가면서 보스를 처치해 얻은 원종 혈청으로 백신을 완성하는 것이 클리어 목표입니다. 게임 개요 항목 내용 장르 싱글플레이 생존 FPS 플랫폼 Windows 개발 기간 2026.09.03 ~ 2026.09.29 팀 구성 6인 사용 엔진 Unreal Engin V5.6 사용 언어 C++, BluePrint Enemy 핵심 기능 1. 상태별 AI 구현 Idle, Alert, Chase, Attack, Return으로 상태 구분 상황에 따라 상태를 바꾸면서 행동하도록 구현 2. 플레이어 탐지 Detection Sphere를 이용해서 일정 범위 안의 플레이어 탐지 플레이어를 발견하면 Alert 애니메이션 이후 추격 시작 3. 추격 및 복귀 NavMesh를 이용해서 플레이어 추격 처음 위치에서 일정 거리 이상 벗어나면 추격 중단 원래 위치로 돌아온 뒤 다시 Idle 상태로 전환 4. 공격 및 피격 Anim Notify를 이용해서 공격 모션에 맞춰 데미지 적용 피격 시 짧은 경직 적용 데미지 숫자, 피 이펙트, 피격 사운드 추가 5. 사망 처리 체력이 0이 되면 AI 행동 정지 사망 애니메이션 재생 및 보상 지급 일정 시간이 지나면 시체 제거 6. 보스 AI 일반 Enemy AI를 기반으로 보스 AI 구현 나무 보스: 충격파 공격 방사능 보스: 지속 데미지 장판 마더 좀비: 체력에 따른 각성 및 좀비 소환 Enemy 핵심 기술 1. 상태 기반 AI 제어 Enum 을 이용해 Idle, Alert, Chase, Attack, Return 등의 상태 관리 현재 상태에 따라 AI 행동을 다르게 처리 2. NavMesh 기반 AI 이동 NavMesh를 이용한 플레이어 추적 및 원래 위치 복귀 이동 가능한 경로인지 확인한 뒤 추격하도록 처리 3. Collision 기반 범위 감지 Sphere Collision 을 이용해 플레이어 감지 범위 구현 Overlap 이벤트를 통해 플레이어 진입 감지 4. Anim Montage / Anim Notify 연동 AI 상태에 따라 필요한 애니메이션 Montage 재생 Anim Notify를 사용해서 공격 판정이나 상태 전환 시점을 애니메이션과 연결 5. Niagara VFX 연동 피격 시 Blood VFX 보스 공격의 충격파 같은 전투 이펙트 구현 6. Timer 기반 처리 피격 Stun 해제 보스 스킬 쿨타임 방사능 장판의 주기적인 데미지 사망 후 시체 제거 등에 Timer 사용 7. 상속을 이용한 Enemy / Boss 구조 Enemy → BossEnemy EnemyAIController → BossEnemyAIController 공통 기능은 부모 클래스에서 사용하고 보스별 기능은 확장해서 구현 기술적 의사결정 상황 기존 Enemy 적 감지 시스템이 플레이어가 시야에 들어왔을 때 공격하게 설정해놨었다가 뒤에 있는 적을 감지 못하는 상황이 발생하여 360도 전체적으로 Sphere처리하여 감지하게 수정요청하였음 선택지 시야에 따른 플레이어 감지 Sphere로 인한 360도 전방향 감지 선택한 방법과 이유 Sphere로 인한 360도 전 방향 감지로 선택하였음 적 시야에 따른 플레이어 감지는 플레이어를 감지 못하는 상황이 발생하여 수정하였음 결과 이제 플레이어가 좀비 뒤에서 가도 감지가 잘 되었음 팀 구성 이름 역할 담당 작업 이호준 팀장 캐릭터, 총기 및 전투시스템, 캐릭터 레벨 및 회복로직 구현 박성빈 깃마스터 레벨(맵) 제작, 스폰 배치, 사운드 및 이펙트 구현 손지협 팀원 게임 프레임워크(GameMode, GameInstance, GameState)구현 신찬호 팀원 적 AI 전체적인 부분(기능, 애니메이션, 사운드, VFX) 조민석 팀원 HUD, 각종 UI 제작 및 연출 구현 남윤민 팀원 동료 AI, 데이터·밸런스, 세계관·에셋
What is the main difference between blackout vs. dimout curtains? The main structural difference comes down to light blockage levels and fabric density. Blackout curtains block 99% to 100% of incoming light using specialized thermal backings or thick multi-layer weaves. Dimout curtains best curtains for Dubai villas block roughly 75% to 90% of natural light, diffusing harsh UV rays while allowing a soft ambient glow to pass into your living space. How do blackout curtains work in Dubai's intense desert climate? Blackout curtains utilize high-density woven fabrics lined with specialized acrylic layers that reflect solar rays before heat enters through your glass panes. In Dubai, where summer temperatures regularly exceed 40°C, these window treatments act as thermal barriers, reducing indoor temperatures, minimizing air conditioning load, and significantly lowering monthly electricity costs for homeowners and property managers. How do dimout curtains function during daytime hours? Dimout curtains rely on tightly woven dark threads sandwiched between decorative fabric layers to filter incoming sunlight without creating pitch-black conditions. During daytime hours in Dubai, they soften blinding sunlight, reduce television and monitor glare, protect furniture from ultraviolet damage, and maintain comfortable illumination levels across living rooms, home offices, and open-plan dining spaces. Which curtain style provides better sleep quality in bedrooms? Blackout curtains provide superior sleep quality because complete darkness stimulates natural melatonin production. For night-shift workers, young children, or sensitive sleepers living in bustling Dubai neighborhoods like Downtown or Marina, light pollution from streetlights, highway traffic, and commercial billboards can disrupt sleep cycles. Blackout fabrics eliminate external light, ensuring deep, uninterrupted rest at any hour. Are dimout curtains suitable for living rooms and dining areas? Yes, dimout curtains excel in social and daytime spaces like living rooms, family lounges, and dining areas. They filter out intense midday glare while preserving natural daylight, creating an inviting, warm atmosphere. They eliminate harsh shadows without requiring artificial indoor lighting during the day, making them a practical and aesthetically pleasing choice for everyday living areas. How do blackout vs dimout curtains compare in thermal insulation? When comparing blackout vs dimout curtains, blackout options offer superior thermal insulation due to their multi-layered backing and dense composite structure. While dimout curtains provide noticeable heat reduction compared to standard sheer fabrics, blackout materials create a strict thermal seal that traps air against the glass, keeping indoor living environments significantly cooler during extreme UAE summer months. Can blackout curtains completely block out streetlights and glare? Yes, high-quality blackout fabrics block 100% of direct light passing through the textile itself. However, to eliminate side light leakage best curtains for Dubai villasaround the edges, curtains must be custom-measured and fitted close to the wall or recessed ceiling track. Professional installation ensures floor-to-ceiling coverage that completely stops urban streetlights, vehicle headlights, and bright city neon signs from entering. Do dimout curtains offer adequate night privacy for Dubai apartments? Dimout curtains offer excellent daytime and nighttime privacy for Dubai apartments. From the outside, observers cannot see through the thick woven fabric, even when interior lights are switched on after dark. While silhouettes may occasionally show if standing directly against illuminated windows, they effectively shield your living space from neighboring towers and street-level views. Which option is better for media rooms and home theaters? Blackout curtains are the definitive choice for home theaters, media rooms, and gaming studios. Eliminating ambient light reflections on high-definition televisions and projection screens requires total darkroom conditions. Blackout linings prevent washed-out visuals, deepen color contrast, and provide an immersive cinematic experience during bright UAE afternoons without forcing you to wait until sunset. How do these curtain types impact air conditioning energy efficiency? Both options improve HVAC efficiency, but blackout linings provide maximum savings by reflecting infrared heat radiation back out through window glass. By preventing heat gain through large glass panels and sliding balcony doors, blackout curtains reduce indoor cooling demands by up to 30%, allowing air conditioning units to run less frequently and saving substantial energy costs. Are blackout curtains heavier than standard dimout curtains? Yes, blackout curtains are heavier because they incorporate additional chemical coatings, rubberized linings, or multiple woven layers. Dimout fabrics maintain a lighter, more fluid drape while achieving high opacity through dense internal weave techniques. If you select heavy blackout materials, ensure sturdy curtain rods or heavy-duty ceiling tracks are installed to support the additional weight properly. What fabric materials are used to make dimout curtains? Dimout curtains are typically crafted from dense polyester blends, velvet, jacquard, satin, or thick linen weaves integrated with a central black warp yarn. This triple-weave technology provides strength, drapeability, and soft texture without relying on stiff rubber backing, allowing the fabric to fall into elegant waves or neat double pinch pleats naturally. How do you clean and maintain custom blackout curtains? Blackout curtains with coated thermal backings require careful spot cleaning or dry cleaning to avoid cracking or damaging the protective lining. Avoid harsh machine washing, tumble drying, or direct ironing on the back surface. Regular light vacuuming with a soft brush attachment removes dust, while professional steam cleaning keeps custom fabrics fresh in dust-prone Dubai environments. What is the best way to wash and care for dimout curtains? Dimout fabrics are generally easier to clean than rubber-lined blackout options. Most high-quality polyester-blend dimout curtains can be gently hand-washed or professionally dry-cleaned depending on fiber content. Regular dusting with an upholstery attachment prevents desert dust buildup, while gentle steaming removes fabric creases, keeping your custom window draperies looking pristine for years. Can you combine blackout linings with sheer decorative fabrics? Yes, pairing blackout linings with sheer decorative fabrics is a popular design choice in luxury Dubai villas and apartments. A best curtains for Dubai villas double-track curtain system allows you to pull sheer panels across during daytime hours for soft light and privacy, then close thick blackout curtains at night or during hot afternoons for maximum shade, thermal insulation, and sleep comfort. Which curtain choice is best for high-rise Dubai glass apartments? High-rise apartments with floor-to-ceiling glass windows benefit immensely from combining both options or selecting heavy blackout fabrics. Panoramic glass surfaces absorb immense solar energy throughout the day. Installing custom blackout window treatments prevents hot house effects, protects valuable interior decor from fading, and maintains cool, comfortable indoor temperatures across high-rise floor plans. How do blackout vs dimout curtains differ in acoustic noise reduction? Evaluating blackout vs dimout curtains reveals that blackout options provide superior acoustic buffering due to their thicker composite layers and rubberized backings. While dimout fabrics absorb indoor echo and mild outdoor noise, heavy blackout drapery acts as a sound barrier, softening construction noise, high-rise wind hums, and traffic sounds from surrounding UAE expressways. Are dimout curtains a good choice for modern Dubai offices? Dimout curtains are ideal for modern commercial offices, conference rooms, and executive suites across Dubai. They filter direct glare on computer screens while preserving pleasant natural lighting, reducing eye strain for employees. Their clean aesthetic, lightweight operation, and flame-retardant properties make them a functional, stylish choice for professional workplace environments. How long do blackout curtains last in hot desert climates? High-quality blackout curtains crafted with premium UV-resistant linings can last 7 to 10 years in hot desert climates. Lower-quality rubber backings may crack or peel after prolonged exposure to intense UV rays. Choosing professional-grade fabrics designed specifically for Middle Eastern sun conditions ensures long-lasting flexibility, color retention, and structural integrity over time. Do dimout curtains fade faster when exposed to direct sunlight? Solution-dyed synthetic yarns in dimout fabrics resist UV fading remarkably well. However, continuous exposure to harsh Dubai sunlight can eventually affect any fabric over time. Using light-colored outer faces, adding protective sheer underlayers, or choosing high-grade woven textiles minimizes color loss, helping your decorative draperies stay vibrant through years of daily sun exposure. What curtain pleat styles work best with heavy blackout fabrics? Wave pleats and double pinch pleats work exceptionally well with heavy blackout fabrics. Wave fold tracks create clean, uniform S-curves that allow thick drapery to stack neatly away from window frames when drawn open. Pinch pleats offer structured elegance, holding heavy fabric weight securely while adding formal, sophisticated tailored detail to luxury interiors. Can motorized curtain tracks handle heavy blackout drapery smoothly? Yes, smart motorized curtain tracks are specifically engineered to carry heavy blackout fabrics effortlessly. best curtains for Dubai villas: Electric motors feature smooth start-and-stop mech