1111 lines
24 KiB
Markdown
1111 lines
24 KiB
Markdown
# 자주 쓰는 스니펫 (import 후보)
|
|
|
|
현재 `snippets.db` 구조 그대로. 한 스니펫 = `##` 섹션 하나.
|
|
|
|
| md | DB 컬럼 |
|
|
|---|---|
|
|
| `## 제목` | `name` (PK, 대문자·공백→`_`) |
|
|
| `- desc:` | `desc` |
|
|
| 코드펜스 안 | `body` (원문 그대로) |
|
|
| `- category:` | `category` |
|
|
|
|
기존 219개와 이름 안 겹치게 골랐음. 언어 태그(```ts 등)는 읽기 편하라고 붙인 거고 DB 컬럼 아님 — import 할 땐 버리면 됨.
|
|
|
|
---
|
|
|
|
## USEQUERY-REACT
|
|
- desc: react-query 조회 훅 기본형
|
|
- category: 코드
|
|
|
|
```ts
|
|
import { useQuery } from '@tanstack/react-query'
|
|
import { xxxxxApi } from '../api/xxxxx.api'
|
|
|
|
export const XXXXXS_KEY = ['xxxxxs'] as const
|
|
|
|
export function useXxxxxs() {
|
|
return useQuery({
|
|
queryKey: XXXXXS_KEY,
|
|
queryFn: xxxxxApi.list,
|
|
staleTime: 30_000,
|
|
})
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## USEMUTATION-REACT
|
|
- desc: react-query 변경 훅 — invalidate + toast + ApiError 분기
|
|
- category: 코드
|
|
|
|
```ts
|
|
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|
import { toast } from 'sonner'
|
|
import { ApiError } from '@/lib/api/errors'
|
|
import { xxxxxApi } from '../api/xxxxx.api'
|
|
import { XXXXXS_KEY } from './useXxxxxs'
|
|
|
|
export function useCreateXxxxx() {
|
|
const qc = useQueryClient()
|
|
return useMutation({
|
|
mutationFn: xxxxxApi.create,
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: XXXXXS_KEY })
|
|
toast.success('저장했어요')
|
|
},
|
|
onError: (e) => {
|
|
toast.error(e instanceof ApiError ? e.message : '저장 실패')
|
|
},
|
|
})
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## API_MODULE-REACT
|
|
- desc: feature api 객체 패턴 (apiGet/apiPost 래퍼)
|
|
- category: 코드
|
|
|
|
```ts
|
|
import { apiGet, apiPost, apiPatch, apiDelete } from '@/lib/api/client'
|
|
import type { Xxxxx, XxxxxCreate } from '@/types/api'
|
|
|
|
const BASE = '/xxxxxs'
|
|
|
|
export const xxxxxApi = {
|
|
list: () => apiGet<Xxxxx[]>(BASE),
|
|
get: (id: string) => apiGet<Xxxxx>(`${BASE}/${id}`),
|
|
create: (body: XxxxxCreate) => apiPost<Xxxxx>(BASE, body),
|
|
update: (id: string, body: Partial<XxxxxCreate>) => apiPatch<Xxxxx>(`${BASE}/${id}`, body),
|
|
remove: (id: string) => apiDelete<void>(`${BASE}/${id}`),
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## AXIOS_MOCK_TEST
|
|
- desc: axios-mock-adapter 로 api 모듈 테스트
|
|
- category: 코드
|
|
|
|
```ts
|
|
import MockAdapter from 'axios-mock-adapter'
|
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
import { http } from '@/lib/api/client'
|
|
import { xxxxxApi } from './xxxxx.api'
|
|
|
|
let mock: MockAdapter
|
|
|
|
beforeEach(() => { mock = new MockAdapter(http) })
|
|
afterEach(() => { mock.restore() })
|
|
|
|
describe('xxxxxApi', () => {
|
|
it('목록을 가져온다', async () => {
|
|
mock.onGet('/xxxxxs').reply(200, [{ id: '1' }])
|
|
await expect(xxxxxApi.list()).resolves.toHaveLength(1)
|
|
})
|
|
|
|
it('실패하면 ApiError 를 던진다', async () => {
|
|
mock.onGet('/xxxxxs').reply(500)
|
|
await expect(xxxxxApi.list()).rejects.toThrow()
|
|
})
|
|
})
|
|
```
|
|
|
|
---
|
|
|
|
## VITEST_RTL
|
|
- desc: vitest + testing-library 컴포넌트 테스트 뼈대
|
|
- category: 코드
|
|
|
|
```tsx
|
|
import { render, screen } from '@testing-library/react'
|
|
import userEvent from '@testing-library/user-event'
|
|
import { describe, it, expect, vi } from 'vitest'
|
|
import { Xxxxx } from './Xxxxx'
|
|
|
|
describe('Xxxxx', () => {
|
|
it('클릭하면 콜백이 불린다', async () => {
|
|
const onPick = vi.fn()
|
|
render(<Xxxxx onPick={onPick} />)
|
|
await userEvent.click(screen.getByRole('button', { name: '선택' }))
|
|
expect(onPick).toHaveBeenCalledTimes(1)
|
|
})
|
|
})
|
|
```
|
|
|
|
---
|
|
|
|
## ZUSTAND_STORE
|
|
- desc: zustand 스토어 기본형
|
|
- category: 코드
|
|
|
|
```ts
|
|
import { create } from 'zustand'
|
|
|
|
interface XxxxxState {
|
|
value: string
|
|
setValue: (v: string) => void
|
|
reset: () => void
|
|
}
|
|
|
|
export const useXxxxxStore = create<XxxxxState>((set) => ({
|
|
value: '',
|
|
setValue: (value) => set({ value }),
|
|
reset: () => set({ value: '' }),
|
|
}))
|
|
```
|
|
|
|
---
|
|
|
|
## USEDEBOUNCE
|
|
- desc: 입력 디바운스 훅
|
|
- category: 코드
|
|
|
|
```ts
|
|
import { useEffect, useState } from 'react'
|
|
|
|
export function useDebounce<T>(value: T, delay = 200): T {
|
|
const [debounced, setDebounced] = useState(value)
|
|
useEffect(() => {
|
|
const t = setTimeout(() => setDebounced(value), delay)
|
|
return () => clearTimeout(t)
|
|
}, [value, delay])
|
|
return debounced
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## USE_HOTKEY
|
|
- desc: 전역 키보드 단축키 훅 (팔레트용)
|
|
- category: 코드
|
|
|
|
```ts
|
|
import { useEffect } from 'react'
|
|
|
|
export function useHotkey(key: string, handler: () => void) {
|
|
useEffect(() => {
|
|
const onKeyDown = (e: KeyboardEvent) => {
|
|
if (e.isComposing) return // IME 조합 중이면 무시
|
|
if (e.key !== key) return
|
|
e.preventDefault()
|
|
handler()
|
|
}
|
|
window.addEventListener('keydown', onKeyDown)
|
|
return () => window.removeEventListener('keydown', onKeyDown)
|
|
}, [key, handler])
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## CN_UTIL
|
|
- desc: clsx + tailwind-merge 클래스 합치기
|
|
- category: 코드
|
|
|
|
```ts
|
|
import { type ClassValue, clsx } from 'clsx'
|
|
import { twMerge } from 'tailwind-merge'
|
|
|
|
export function cn(...inputs: ClassValue[]) {
|
|
return twMerge(clsx(inputs))
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## SHADCN_DIALOG
|
|
- desc: shadcn Dialog 열고 닫기
|
|
- category: 코드
|
|
|
|
```tsx
|
|
import {
|
|
Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter,
|
|
} from '@/components/ui/dialog'
|
|
import { Button } from '@/components/ui/button'
|
|
|
|
<Dialog open={open} onOpenChange={setOpen}>
|
|
<DialogContent className="sm:max-w-lg">
|
|
<DialogHeader>
|
|
<DialogTitle>XXXXX</DialogTitle>
|
|
</DialogHeader>
|
|
|
|
{/* 본문 */}
|
|
|
|
<DialogFooter>
|
|
<Button variant="ghost" onClick={() => setOpen(false)}>취소</Button>
|
|
<Button onClick={onSave}>저장</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
```
|
|
|
|
---
|
|
|
|
## SSE_STREAM
|
|
- desc: fetch 로 SSE 스트리밍 받아 한 줄씩 처리
|
|
- category: 코드
|
|
|
|
```ts
|
|
const res = await fetch(url, { method: 'POST', body: JSON.stringify(payload) })
|
|
const reader = res.body!.getReader()
|
|
const decoder = new TextDecoder()
|
|
let buf = ''
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read()
|
|
if (done) break
|
|
buf += decoder.decode(value, { stream: true })
|
|
|
|
const lines = buf.split('\n')
|
|
buf = lines.pop() ?? ''
|
|
for (const line of lines) {
|
|
if (!line.startsWith('data:')) continue
|
|
const data = line.slice(5).trim()
|
|
if (data === '[DONE]') return
|
|
onChunk(JSON.parse(data))
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## ENV_VITE
|
|
- desc: vite 환경변수 읽기 + .env 예시
|
|
- category: 코드
|
|
|
|
```ts
|
|
// .env.development
|
|
// VITE_API_BASE_URL=http://localhost:8000
|
|
|
|
const baseUrl = import.meta.env.VITE_API_BASE_URL as string
|
|
const isDev = import.meta.env.DEV
|
|
```
|
|
|
|
---
|
|
|
|
## FMAIN
|
|
- desc: FastAPI 앱 진입점 + CORS + 라우터 등록
|
|
- category: 코드
|
|
|
|
```python
|
|
from contextlib import asynccontextmanager
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
from routers import xxxxx
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
# 시작 시
|
|
yield
|
|
# 종료 시
|
|
|
|
|
|
app = FastAPI(title="XXXXX", lifespan=lifespan)
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["http://localhost:5173"],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
app.include_router(xxxxx.router)
|
|
```
|
|
|
|
---
|
|
|
|
## FROUTER
|
|
- desc: FastAPI APIRouter 뼈대 (CRUD 5종)
|
|
- category: 코드
|
|
|
|
```python
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from database import get_db
|
|
|
|
router = APIRouter(prefix="/xxxxxs", tags=["xxxxx"])
|
|
|
|
|
|
@router.get("")
|
|
async def list_xxxxx(db: AsyncSession = Depends(get_db)):
|
|
pass
|
|
|
|
|
|
@router.get("/{xxxxx_id}")
|
|
async def get_xxxxx(xxxxx_id: int, db: AsyncSession = Depends(get_db)):
|
|
pass
|
|
|
|
|
|
@router.post("", status_code=201)
|
|
async def create_xxxxx(request: XxxxxPIN, db: AsyncSession = Depends(get_db)):
|
|
pass
|
|
|
|
|
|
@router.patch("/{xxxxx_id}")
|
|
async def update_xxxxx(xxxxx_id: int, request: XxxxxPIN, db: AsyncSession = Depends(get_db)):
|
|
pass
|
|
|
|
|
|
@router.delete("/{xxxxx_id}", status_code=204)
|
|
async def delete_xxxxx(xxxxx_id: int, db: AsyncSession = Depends(get_db)):
|
|
pass
|
|
```
|
|
|
|
---
|
|
|
|
## FDB_SESSION
|
|
- desc: SQLAlchemy async 세션 + get_db 의존성
|
|
- category: 코드
|
|
|
|
```python
|
|
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
|
|
|
DATABASE_URL = "postgresql+asyncpg://user:pass@localhost:5432/dbname"
|
|
|
|
engine = create_async_engine(DATABASE_URL, echo=False, pool_pre_ping=True)
|
|
SessionLocal = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
|
|
|
|
|
async def get_db():
|
|
async with SessionLocal() as session:
|
|
yield session
|
|
```
|
|
|
|
---
|
|
|
|
## FSELECT-ONE
|
|
- desc: FastAPI 한 건 조회 (없으면 404)
|
|
- category: 코드
|
|
|
|
```python
|
|
from sqlalchemy import select
|
|
from fastapi import HTTPException
|
|
|
|
result = await db.execute(select(XXXXX).where(XXXXX.id == xxxxx_id))
|
|
ret = result.scalar_one_or_none()
|
|
|
|
if ret is None:
|
|
raise HTTPException(status_code=404, detail="XXXXX Not Found")
|
|
|
|
return ret
|
|
```
|
|
|
|
---
|
|
|
|
## FINSERT
|
|
- desc: FastAPI insert (add → commit → refresh)
|
|
- category: 코드
|
|
|
|
```python
|
|
from utils.error_handlers import handle_error
|
|
|
|
try:
|
|
row = XXXXX(**request.model_dump())
|
|
db.add(row)
|
|
await db.commit()
|
|
await db.refresh(row)
|
|
return row
|
|
|
|
except Exception as error:
|
|
await db.rollback()
|
|
handle_error(error, "XXXXX Create Fail")
|
|
```
|
|
|
|
---
|
|
|
|
## FUPDATE
|
|
- desc: FastAPI 부분 수정 (exclude_unset)
|
|
- category: 코드
|
|
|
|
```python
|
|
from sqlalchemy import select
|
|
|
|
result = await db.execute(select(XXXXX).where(XXXXX.id == xxxxx_id))
|
|
row = result.scalar_one_or_none()
|
|
if row is None:
|
|
raise HTTPException(status_code=404, detail="XXXXX Not Found")
|
|
|
|
for key, value in request.model_dump(exclude_unset=True).items():
|
|
setattr(row, key, value)
|
|
|
|
await db.commit()
|
|
await db.refresh(row)
|
|
return row
|
|
```
|
|
|
|
---
|
|
|
|
## FDELETE
|
|
- desc: FastAPI 소프트 삭제
|
|
- category: 코드
|
|
|
|
```python
|
|
from datetime import datetime
|
|
from sqlalchemy import update
|
|
|
|
await db.execute(
|
|
update(XXXXX)
|
|
.where(XXXXX.id == xxxxx_id)
|
|
.values(is_deleted=True, updated_at=datetime.now())
|
|
)
|
|
await db.commit()
|
|
```
|
|
|
|
---
|
|
|
|
## FPAGING
|
|
- desc: FastAPI 페이징 (count + limit/offset)
|
|
- category: 코드
|
|
|
|
```python
|
|
from sqlalchemy import func, select
|
|
|
|
base = select(XXXXX).where(XXXXX.is_deleted == False) # noqa: E712
|
|
|
|
total = await db.scalar(select(func.count()).select_from(base.subquery()))
|
|
result = await db.execute(base.order_by(XXXXX.created_at.desc()).limit(size).offset((page - 1) * size))
|
|
|
|
return {"total": total, "page": page, "size": size, "items": result.scalars().all()}
|
|
```
|
|
|
|
---
|
|
|
|
## FMODEL_PIN_POUT
|
|
- desc: pydantic 요청/응답 모델 (PIN/POUT 규칙)
|
|
- category: 코드
|
|
|
|
```python
|
|
from datetime import datetime
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
|
|
|
|
class XxxxxPIN(BaseModel):
|
|
name: str = Field(..., max_length=50)
|
|
description: str | None = None
|
|
|
|
|
|
class XxxxxPOUT(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
id: int
|
|
name: str
|
|
description: str | None
|
|
created_at: datetime
|
|
```
|
|
|
|
---
|
|
|
|
## FMODEL_ORM
|
|
- desc: SQLAlchemy 2.0 ORM 모델 (Mapped 스타일)
|
|
- category: 코드
|
|
|
|
```python
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import DateTime, String, func
|
|
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
|
|
|
|
|
class Base(DeclarativeBase):
|
|
pass
|
|
|
|
|
|
class Xxxxx(Base):
|
|
__tablename__ = "xxxxx"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
|
name: Mapped[str] = mapped_column(String(50), nullable=False)
|
|
is_deleted: Mapped[bool] = mapped_column(default=False)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
|
|
```
|
|
|
|
---
|
|
|
|
## ALEMBIC_CMD
|
|
- desc: alembic 마이그레이션 명령 모음
|
|
- category: 코드
|
|
|
|
```bash
|
|
alembic init -t async alembic
|
|
alembic revision --autogenerate -m "XXXXX"
|
|
alembic upgrade head
|
|
alembic downgrade -1
|
|
alembic current
|
|
alembic history --verbose
|
|
```
|
|
|
|
---
|
|
|
|
## PYTEST_ASYNC
|
|
- desc: pytest async 테스트 + httpx 클라이언트
|
|
- category: 코드
|
|
|
|
```python
|
|
import pytest
|
|
from httpx import ASGITransport, AsyncClient
|
|
|
|
from main import app
|
|
|
|
|
|
@pytest.fixture
|
|
async def client():
|
|
transport = ASGITransport(app=app)
|
|
async with AsyncClient(transport=transport, base_url="http://test") as ac:
|
|
yield ac
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_list_xxxxx(client):
|
|
res = await client.get("/xxxxxs")
|
|
assert res.status_code == 200
|
|
```
|
|
|
|
---
|
|
|
|
## LOGGING_PY
|
|
- desc: 파이썬 로깅 기본 설정
|
|
- category: 코드
|
|
|
|
```python
|
|
import logging
|
|
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
datefmt="%H:%M:%S",
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
logger.info("XXXXX")
|
|
```
|
|
|
|
---
|
|
|
|
## HTTPX_POST
|
|
- desc: httpx 비동기 POST 호출
|
|
- category: 코드
|
|
|
|
```python
|
|
import httpx
|
|
|
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
res = await client.post(url, json=payload, headers={"Authorization": f"Bearer {token}"})
|
|
res.raise_for_status()
|
|
data = res.json()
|
|
```
|
|
|
|
---
|
|
|
|
## GIT_UNDO
|
|
- desc: git 되돌리기 모음 (커밋·스테이징·파일)
|
|
- category: 코드
|
|
|
|
```bash
|
|
git reset --soft HEAD~1 # 마지막 커밋만 취소, 변경은 남김
|
|
git reset HEAD <file> # 스테이징만 취소
|
|
git restore <file> # 파일 변경 버리기
|
|
git restore --staged <file> # 스테이징 취소(신형)
|
|
git revert <commit> # 되돌리는 새 커밋 생성(푸시된 커밋용)
|
|
git reflog # 날린 커밋 찾기
|
|
```
|
|
|
|
---
|
|
|
|
## GIT_STASH
|
|
- desc: git stash 사용법
|
|
- category: 코드
|
|
|
|
```bash
|
|
git stash push -m "작업중"
|
|
git stash list
|
|
git stash show -p stash@{0}
|
|
git stash pop
|
|
git stash drop stash@{0}
|
|
```
|
|
|
|
---
|
|
|
|
## GIT_AMEND
|
|
- desc: 마지막 커밋 메시지·내용 고치기
|
|
- category: 코드
|
|
|
|
```bash
|
|
git commit --amend -m "새 메시지"
|
|
git commit --amend --no-edit
|
|
git push --force-with-lease
|
|
```
|
|
|
|
---
|
|
|
|
## GIT_LOG_PRETTY
|
|
- desc: 한 줄 그래프 로그
|
|
- category: 코드
|
|
|
|
```bash
|
|
git log --oneline --graph --decorate --all -20
|
|
git log --since="1 week ago" --author="XXXXX" --oneline
|
|
```
|
|
|
|
---
|
|
|
|
## DOCKER_EXEC
|
|
- desc: 컨테이너 접속·상태 확인
|
|
- category: 코드
|
|
|
|
```bash
|
|
docker exec -it <container> /bin/bash
|
|
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
|
docker stats --no-stream
|
|
docker inspect <container> | grep -i ipaddress
|
|
```
|
|
|
|
---
|
|
|
|
## PORT_KILL_WIN
|
|
- desc: 윈도우에서 포트 점유 프로세스 죽이기
|
|
- category: 코드
|
|
|
|
```powershell
|
|
netstat -ano | findstr :8000
|
|
taskkill /PID <pid> /F
|
|
|
|
# 한 줄
|
|
Get-NetTCPConnection -LocalPort 8000 | Select-Object -ExpandProperty OwningProcess | ForEach-Object { Stop-Process -Id $_ -Force }
|
|
```
|
|
|
|
---
|
|
|
|
## NPM_CLEAN_INSTALL
|
|
- desc: node_modules 완전 초기화 재설치
|
|
- category: 코드
|
|
|
|
```bash
|
|
rm -rf node_modules package-lock.json
|
|
npm cache clean --force
|
|
npm install
|
|
|
|
# 윈도우
|
|
Remove-Item -Recurse -Force node_modules, package-lock.json
|
|
```
|
|
|
|
---
|
|
|
|
## CURL_POST
|
|
- desc: curl 로 JSON POST + 토큰
|
|
- category: 코드
|
|
|
|
```bash
|
|
curl -X POST http://localhost:8000/xxxxxs \
|
|
-H "Content-Type: application/json" \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-d '{"name":"XXXXX"}' \
|
|
-w "\n%{http_code}\n"
|
|
```
|
|
|
|
---
|
|
|
|
## APPEND_ITAB-ABAP
|
|
- desc: 인터널 테이블에 행 추가
|
|
- category: 코드
|
|
|
|
```abap
|
|
"★★APPEND, 인터널 테이블에 한 줄 추가
|
|
CLEAR GS_LIST.
|
|
GS_LIST-MATNR = LS_DATA-MATNR.
|
|
GS_LIST-MAKTX = LS_DATA-MAKTX.
|
|
APPEND GS_LIST TO GT_LIST.
|
|
CLEAR GS_LIST.
|
|
|
|
"740 이상
|
|
APPEND VALUE #( MATNR = LS_DATA-MATNR
|
|
MAKTX = LS_DATA-MAKTX ) TO GT_LIST.
|
|
```
|
|
|
|
---
|
|
|
|
## TRY_CATCH-ABAP
|
|
- desc: ABAP 예외 처리 (CX_ROOT)
|
|
- category: 코드
|
|
|
|
```abap
|
|
DATA : LO_ERR TYPE REF TO CX_ROOT,
|
|
LV_MSG TYPE STRING.
|
|
|
|
TRY.
|
|
"★처리 구문
|
|
CATCH CX_SY_CONVERSION_NO_NUMBER INTO LO_ERR.
|
|
LV_MSG = LO_ERR->GET_TEXT( ).
|
|
MESSAGE LV_MSG TYPE 'S' DISPLAY LIKE 'E'.
|
|
CATCH CX_ROOT INTO LO_ERR.
|
|
LV_MSG = LO_ERR->GET_TEXT( ).
|
|
ENDTRY.
|
|
```
|
|
|
|
---
|
|
|
|
## SELECT_JOIN-ABAP
|
|
- desc: INNER / LEFT OUTER JOIN 조회
|
|
- category: 코드
|
|
|
|
```abap
|
|
SELECT A~MATNR
|
|
A~MTART
|
|
B~MAKTX
|
|
INTO CORRESPONDING FIELDS OF TABLE GT_LIST
|
|
FROM MARA AS A
|
|
INNER JOIN MAKT AS B
|
|
ON B~MATNR = A~MATNR
|
|
AND B~SPRAS = SY-LANGU
|
|
WHERE A~MATNR IN S_MATNR
|
|
AND A~LVORM EQ SPACE.
|
|
|
|
IF SY-SUBRC NE 0.
|
|
MESSAGE '조회된 데이터가 없습니다.' TYPE 'S' DISPLAY LIKE 'E'.
|
|
RETURN.
|
|
ENDIF.
|
|
```
|
|
|
|
---
|
|
|
|
## SELECT_UP_TO-ABAP
|
|
- desc: 건수 제한 조회 + 존재 여부 확인
|
|
- category: 코드
|
|
|
|
```abap
|
|
"★상위 N건만
|
|
SELECT MATNR MAKTX
|
|
INTO TABLE GT_LIST
|
|
FROM MAKT
|
|
UP TO 100 ROWS
|
|
WHERE SPRAS = SY-LANGU.
|
|
|
|
"★존재 여부만 확인 (성능)
|
|
SELECT SINGLE @ABAP_TRUE
|
|
FROM MARA
|
|
INTO @DATA(LV_EXISTS)
|
|
WHERE MATNR = @GS_LIST-MATNR.
|
|
```
|
|
|
|
---
|
|
|
|
## ALV_FIELDCAT-ABAP
|
|
- desc: ALV 필드카탈로그 수동 구성
|
|
- category: 코드
|
|
|
|
```abap
|
|
DATA : GT_FCAT TYPE LVC_T_FCAT,
|
|
GS_FCAT TYPE LVC_S_FCAT.
|
|
|
|
DEFINE _FCAT.
|
|
CLEAR GS_FCAT.
|
|
GS_FCAT-FIELDNAME = &1.
|
|
GS_FCAT-COLTEXT = &2.
|
|
GS_FCAT-OUTPUTLEN = &3.
|
|
GS_FCAT-KEY = &4.
|
|
APPEND GS_FCAT TO GT_FCAT.
|
|
END-OF-DEFINITION.
|
|
|
|
_FCAT : 'MATNR' '자재코드' 18 'X',
|
|
'MAKTX' '자재내역' 40 '',
|
|
'MENGE' '수량' 13 ''.
|
|
```
|
|
|
|
---
|
|
|
|
## ALV_LAYOUT-ABAP
|
|
- desc: ALV 레이아웃 기본 설정
|
|
- category: 코드
|
|
|
|
```abap
|
|
DATA : GS_LAYOUT TYPE LVC_S_LAYO.
|
|
|
|
CLEAR GS_LAYOUT.
|
|
GS_LAYOUT-ZEBRA = 'X'. "줄무늬
|
|
GS_LAYOUT-CWIDTH_OPT = 'X'. "컬럼폭 자동
|
|
GS_LAYOUT-SEL_MODE = 'A'. "선택 모드
|
|
GS_LAYOUT-NO_ROWMARK = SPACE.
|
|
GS_LAYOUT-STYLEFNAME = 'CELLSTYLE'.
|
|
GS_LAYOUT-CTAB_FNAME = 'CELLCOLOR'.
|
|
```
|
|
|
|
---
|
|
|
|
## ALV_HOTSPOT-ABAP
|
|
- desc: ALV 핫스팟 클릭 이벤트 핸들러
|
|
- category: 코드
|
|
|
|
```abap
|
|
CLASS LCL_EVENT DEFINITION.
|
|
PUBLIC SECTION.
|
|
METHODS HANDLE_HOTSPOT
|
|
FOR EVENT HOTSPOT_CLICK OF CL_GUI_ALV_GRID
|
|
IMPORTING E_ROW_ID E_COLUMN_ID.
|
|
ENDCLASS.
|
|
|
|
CLASS LCL_EVENT IMPLEMENTATION.
|
|
METHOD HANDLE_HOTSPOT.
|
|
READ TABLE GT_LIST INTO GS_LIST INDEX E_ROW_ID-INDEX.
|
|
CHECK SY-SUBRC EQ 0.
|
|
|
|
CASE E_COLUMN_ID-FIELDNAME.
|
|
WHEN 'MATNR'.
|
|
SET PARAMETER ID 'MAT' FIELD GS_LIST-MATNR.
|
|
CALL TRANSACTION 'MM03' AND SKIP FIRST SCREEN.
|
|
ENDCASE.
|
|
ENDMETHOD.
|
|
ENDCLASS.
|
|
|
|
"등록
|
|
SET HANDLER GO_EVENT->HANDLE_HOTSPOT FOR GO_GRID.
|
|
```
|
|
|
|
---
|
|
|
|
## CLASS_LOCAL-ABAP
|
|
- desc: 로컬 클래스 정의·구현 뼈대
|
|
- category: 코드
|
|
|
|
```abap
|
|
CLASS LCL_XXXXX DEFINITION.
|
|
PUBLIC SECTION.
|
|
METHODS : CONSTRUCTOR IMPORTING IV_WERKS TYPE WERKS_D,
|
|
GET_DATA RETURNING VALUE(RT_LIST) TYPE TT_LIST.
|
|
PRIVATE SECTION.
|
|
DATA : MV_WERKS TYPE WERKS_D,
|
|
MT_LIST TYPE TT_LIST.
|
|
ENDCLASS.
|
|
|
|
CLASS LCL_XXXXX IMPLEMENTATION.
|
|
METHOD CONSTRUCTOR.
|
|
MV_WERKS = IV_WERKS.
|
|
ENDMETHOD.
|
|
|
|
METHOD GET_DATA.
|
|
RT_LIST = MT_LIST.
|
|
ENDMETHOD.
|
|
ENDCLASS.
|
|
```
|
|
|
|
---
|
|
|
|
## SUBMIT_PROGRAM-ABAP
|
|
- desc: 다른 프로그램 실행 후 결과 받기
|
|
- category: 코드
|
|
|
|
```abap
|
|
DATA : LT_LIST TYPE TABLE OF ABAPLIST.
|
|
|
|
SUBMIT ZXXXXX
|
|
WITH P_BUKRS EQ GS_LIST-BUKRS
|
|
WITH S_MATNR IN S_MATNR
|
|
EXPORTING LIST TO MEMORY
|
|
AND RETURN.
|
|
|
|
CALL FUNCTION 'LIST_FROM_MEMORY'
|
|
TABLES
|
|
LISTOBJECT = LT_LIST
|
|
EXCEPTIONS
|
|
NOT_FOUND = 1
|
|
OTHERS = 2.
|
|
```
|
|
|
|
---
|
|
|
|
## RFC_CALL-ABAP
|
|
- desc: RFC 원격 호출 (DESTINATION)
|
|
- category: 코드
|
|
|
|
```abap
|
|
DATA : LV_MSG TYPE STRING.
|
|
|
|
CALL FUNCTION 'ZXXXXX'
|
|
DESTINATION 'XXXXXCLNT100'
|
|
EXPORTING
|
|
IV_WERKS = GS_LIST-WERKS
|
|
TABLES
|
|
ET_LIST = GT_LIST
|
|
EXCEPTIONS
|
|
COMMUNICATION_FAILURE = 1 MESSAGE LV_MSG
|
|
SYSTEM_FAILURE = 2 MESSAGE LV_MSG
|
|
OTHERS = 3.
|
|
|
|
IF SY-SUBRC NE 0.
|
|
MESSAGE LV_MSG TYPE 'S' DISPLAY LIKE 'E'.
|
|
RETURN.
|
|
ENDIF.
|
|
```
|
|
|
|
---
|
|
|
|
## JSON_CONVERT-ABAP
|
|
- desc: 인터널 테이블 ↔ JSON 변환
|
|
- category: 코드
|
|
|
|
```abap
|
|
DATA : LV_JSON TYPE STRING.
|
|
|
|
"★ITAB → JSON (카멜케이스)
|
|
LV_JSON = /UI2/CL_JSON=>SERIALIZE(
|
|
DATA = GT_LIST
|
|
COMPRESS = ABAP_TRUE
|
|
PRETTY_NAME = /UI2/CL_JSON=>PRETTY_MODE-CAMEL_CASE ).
|
|
|
|
"★JSON → ITAB
|
|
/UI2/CL_JSON=>DESERIALIZE(
|
|
EXPORTING JSON = LV_JSON
|
|
PRETTY_NAME = /UI2/CL_JSON=>PRETTY_MODE-CAMEL_CASE
|
|
CHANGING DATA = GT_LIST ).
|
|
```
|
|
|
|
---
|
|
|
|
## AUTHORITY_CHECK-ABAP
|
|
- desc: 권한 체크
|
|
- category: 코드
|
|
|
|
```abap
|
|
AUTHORITY-CHECK OBJECT 'M_MATE_WRK'
|
|
ID 'ACTVT' FIELD '03'
|
|
ID 'WERKS' FIELD GS_LIST-WERKS.
|
|
|
|
IF SY-SUBRC NE 0.
|
|
MESSAGE |플랜트 { GS_LIST-WERKS } 조회 권한이 없습니다.| TYPE 'S' DISPLAY LIKE 'E'.
|
|
RETURN.
|
|
ENDIF.
|
|
```
|
|
|
|
---
|
|
|
|
## COMMIT_ROLLBACK-ABAP
|
|
- desc: BAPI 호출 후 커밋/롤백 처리
|
|
- category: 코드
|
|
|
|
```abap
|
|
DATA : LT_RETURN TYPE TABLE OF BAPIRET2.
|
|
|
|
CALL FUNCTION 'BAPI_XXXXX_CREATE'
|
|
EXPORTING
|
|
XXXXX = GS_LIST-XXXXX
|
|
TABLES
|
|
RETURN = LT_RETURN.
|
|
|
|
READ TABLE LT_RETURN TRANSPORTING NO FIELDS WITH KEY TYPE = 'E'.
|
|
IF SY-SUBRC EQ 0.
|
|
CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
|
|
MESSAGE '처리 실패' TYPE 'S' DISPLAY LIKE 'E'.
|
|
ELSE.
|
|
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
|
|
EXPORTING WAIT = 'X'.
|
|
MESSAGE '처리 완료' TYPE 'S'.
|
|
ENDIF.
|
|
```
|
|
|
|
---
|
|
|
|
## PROMPT_CODE_REVIEW
|
|
- desc: 코드리뷰 요청 프롬프트
|
|
- category: 코드
|
|
|
|
```text
|
|
아래 코드를 리뷰해줘.
|
|
|
|
[중점]
|
|
1) 버그·엣지케이스 (null, 빈 배열, 동시 요청, 예외 경로)
|
|
2) 기존 코드 컨벤션과 어긋나는 부분
|
|
3) 지울 수 있는 것 (중복, 안 쓰는 추상화)
|
|
|
|
[규칙]
|
|
- 문제 있는 부분만 지적. 잘한 점 나열 금지.
|
|
- 각 지적은 "파일:줄 - 뭐가 문제 - 어떻게 고침" 세 줄로.
|
|
- 확실하지 않으면 추측이라고 명시.
|
|
|
|
[코드]
|
|
여기에 붙여넣기
|
|
```
|
|
|
|
---
|
|
|
|
## PROMPT_TDD
|
|
- desc: 실패하는 테스트부터 요청하는 프롬프트
|
|
- category: 코드
|
|
|
|
```text
|
|
TDD로 진행한다.
|
|
|
|
1) 먼저 실패하는 테스트만 작성해줘. 구현 코드는 아직 쓰지 마.
|
|
2) 테스트를 실행해서 실패하는 걸 확인한 뒤 알려줘.
|
|
3) 그 다음 테스트를 통과하는 최소 코드만 작성한다.
|
|
|
|
[요구사항]
|
|
여기에 기술
|
|
|
|
[기존 테스트 스타일]
|
|
여기에 붙여넣기
|
|
```
|
|
|
|
---
|
|
|
|
## PROMPT_ERROR_FIX
|
|
- desc: 에러 원인 분석 요청 프롬프트
|
|
- category: 코드
|
|
|
|
```text
|
|
에러가 났어. 추측으로 고치지 말고 순서대로 해줘.
|
|
|
|
1) 에러 메시지를 그대로 읽고 무슨 뜻인지 설명
|
|
2) 재현되는 최소 조건을 찾아 확인
|
|
3) 근본 원인 한 줄로 정리 (증상 말고 원인)
|
|
4) 같은 함수를 부르는 다른 곳도 같은 문제인지 확인
|
|
5) 그 다음에 수정
|
|
|
|
[에러]
|
|
여기에 붙여넣기
|
|
|
|
[관련 코드]
|
|
여기에 붙여넣기
|
|
```
|
|
|
|
---
|
|
|
|
## PROMPT_COMMIT
|
|
- desc: 커밋 메시지 작성 프롬프트
|
|
- category: 코드
|
|
|
|
```text
|
|
아래 변경사항으로 커밋 메시지를 써줘.
|
|
|
|
[규칙]
|
|
- 형식: <type>(<scope>): <한글 요약 50자 이내>
|
|
- type: feat / fix / docs / refactor / test / chore
|
|
- 본문은 "왜 고쳤는지"만. "무엇을 바꿨는지"는 diff 보면 아니까 생략.
|
|
- 여러 관심사가 섞였으면 커밋을 나누라고 알려줘.
|
|
|
|
[diff]
|
|
여기에 붙여넣기
|
|
```
|
|
|
|
---
|
|
|
|
## PROMPT_REFACTOR
|
|
- desc: 리팩터링 요청 프롬프트 (동작 불변)
|
|
- category: 코드
|
|
|
|
```text
|
|
아래 코드를 리팩터링해줘.
|
|
|
|
[철칙]
|
|
- 동작은 한 글자도 바뀌면 안 됨. 기능 추가 금지.
|
|
- 테스트가 있으면 먼저 돌려보고, 리팩터 후 다시 돌려서 같은 결과인지 확인.
|
|
- 추상화를 새로 만들기 전에 이미 있는 걸 먼저 찾아 쓸 것.
|
|
- 변경은 작게 나눠서, 각 단계마다 뭘 왜 바꿨는지 한 줄.
|
|
|
|
[코드]
|
|
여기에 붙여넣기
|
|
```
|