From 4ef3deb87ef27f61ea7a39c456dcbcec17a6b585 Mon Sep 17 00:00:00 2001 From: TtiPo Date: Fri, 11 Sep 2026 01:43:07 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20=EC=97=90=EB=9F=AC=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EB=AC=B8=EA=B5=AC=20=ED=95=B4=EC=9A=94=EC=B2=B4?= =?UTF-8?q?=EB=A1=9C=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 세 플랫폼이 공유하는 에러 코드 맵의 문장을 딱딱한 합니다체에서 해요체로 바꿨다. 화면 문구는 실제로 쓰는 말로만 쓴다는 기준에 맞춘 것이다. - SYS_001 "일시적인 시스템 오류가 발생했습니다." → "일시적인 오류가 생겼어요." - AUTH_003 "해당 메뉴나 기능에 접근할 수 있는 권한이 없습니다." → "이 메뉴나 기능을 이용할 권한이 없어요." - VAL_001 의 "(필수값 누락 또는 형식 오류)" 는 개발자 말이라 문장으로 풀었다. - main.ts 의 Rate Limit 응답도 같은 문장을 쓰고 있어 "요청이 너무 많아요" 로 함께 고쳤다. SSOT 3개 파일(backend 필터 · frontend ErrorCodeLexicon · flutter ErrorLexicon)과 규칙 문서, 그리고 문구를 인용하던 화면·테스트를 함께 갱신했다. frontend lint · type-check · 테스트 11개 통과. flutter analyze 에서 error_lexicon.dart 는 문제 없음. backend 는 node_modules 가 비어 있어 로컬에서 lint/test 를 돌리지 못했다(문자열 리터럴만 변경). Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0128kgT8ycCATfJVFULQMa9b --- .claude/rules/error-codes.md | 14 +++++++------- .../src/common/filters/http-exception.filter.ts | 12 ++++++------ backend/src/main.ts | 2 +- flutter/lib/core/api/error_lexicon.dart | 14 +++++++------- frontend/src/components/common/SectionBoundary.vue | 4 +++- frontend/src/composables/useApi.spec.ts | 10 +++++----- frontend/src/composables/useApi.ts | 14 +++++++------- .../src/composables/useSectionResource.spec.ts | 2 +- frontend/src/pages/NotFoundPage.vue | 2 +- 9 files changed, 38 insertions(+), 36 deletions(-) diff --git a/.claude/rules/error-codes.md b/.claude/rules/error-codes.md index d6ef5d0..7d4746c 100644 --- a/.claude/rules/error-codes.md +++ b/.claude/rules/error-codes.md @@ -13,13 +13,13 @@ description: 세 플랫폼이 공유하는 에러 코드 ↔ 한국어 메시지 | 코드 | HTTP / 상황 | 메시지 | | -------- | -------------------- | ------------------------------------------------------- | -| SYS_001 | 500 / 미정의 예외 | "일시적인 시스템 오류가 발생했습니다. 잠시 후 다시 시도해 주세요." | -| AUTH_001 | 401 | "로그인이 필요한 서비스입니다. 로그인 후 이용해 주세요." | -| AUTH_002 | 세션 만료 (클라이언트) | "안전을 위해 로그아웃 되었습니다. 다시 로그인해 주세요." | -| AUTH_003 | 403 | "해당 메뉴나 기능에 접근할 수 있는 권한이 없습니다." | -| VAL_001 | 400 / 422 (Validation) | "입력하신 정보를 다시 확인해 주세요. (필수값 누락 또는 형식 오류)" | -| RES_001 | 404 | "요청하신 정보나 페이지를 찾을 수 없습니다." | -| BIZ_001 | 200(비즈니스 실패) / 기타 | "요청하신 작업을 완료하지 못했습니다. 다시 시도해 주세요." | +| SYS_001 | 500 / 미정의 예외 | "일시적인 오류가 생겼어요. 잠시 후 다시 시도해 주세요." | +| AUTH_001 | 401 | "로그인이 필요한 기능이에요. 로그인 후 이용해 주세요." | +| AUTH_002 | 세션 만료 (클라이언트) | "안전을 위해 로그아웃했어요. 다시 로그인해 주세요." | +| AUTH_003 | 403 | "이 메뉴나 기능을 이용할 권한이 없어요." | +| VAL_001 | 400 / 422 (Validation) | "입력하신 내용을 다시 확인해 주세요. 빠진 항목이 있거나 형식이 맞지 않아요." | +| RES_001 | 404 | "찾으시는 정보나 페이지가 없어요." | +| BIZ_001 | 200(비즈니스 실패) / 기타 | "요청하신 작업을 끝내지 못했어요. 다시 시도해 주세요." | > 비즈니스 예외는 `HttpException` 응답의 `message` 로 사용자 노출 문구를 재정의할 수 있다. > 응답 래퍼 구조는 [[response-format]] 참조. diff --git a/backend/src/common/filters/http-exception.filter.ts b/backend/src/common/filters/http-exception.filter.ts index 224d5a5..e7c38fe 100644 --- a/backend/src/common/filters/http-exception.filter.ts +++ b/backend/src/common/filters/http-exception.filter.ts @@ -21,7 +21,7 @@ export class HttpExceptionFilter implements ExceptionFilter { let status: HttpStatus = HttpStatus.INTERNAL_SERVER_ERROR; let code = 'SYS_001'; let message = - '일시적인 시스템 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.'; + '일시적인 오류가 생겼어요. 잠시 후 다시 시도해 주세요.'; if (exception instanceof HttpException) { status = exception.getStatus(); @@ -29,23 +29,23 @@ export class HttpExceptionFilter implements ExceptionFilter { if (status === HttpStatus.UNAUTHORIZED) { code = 'AUTH_001'; - message = '로그인이 필요한 서비스입니다. 로그인 후 이용해 주세요.'; + message = '로그인이 필요한 기능이에요. 로그인 후 이용해 주세요.'; } else if (status === HttpStatus.FORBIDDEN) { code = 'AUTH_003'; - message = '해당 메뉴나 기능에 접근할 수 있는 권한이 없습니다.'; + message = '이 메뉴나 기능을 이용할 권한이 없어요.'; } else if (status === HttpStatus.NOT_FOUND) { code = 'RES_001'; - message = '요청하신 정보나 페이지를 찾을 수 없습니다.'; + message = '찾으시는 정보나 페이지가 없어요.'; } else if ( status === HttpStatus.BAD_REQUEST || status === HttpStatus.UNPROCESSABLE_ENTITY ) { code = 'VAL_001'; message = - '입력하신 정보를 다시 확인해 주세요. (필수값 누락 또는 형식 오류)'; + '입력하신 내용을 다시 확인해 주세요. 빠진 항목이 있거나 형식이 맞지 않아요.'; } else { code = 'BIZ_001'; - message = '요청하신 작업을 완료하지 못했습니다. 다시 시도해 주세요.'; + message = '요청하신 작업을 끝내지 못했어요. 다시 시도해 주세요.'; // 비즈니스 예외에서는 사용자에게 보일 메시지를 재정의 가능 if ( diff --git a/backend/src/main.ts b/backend/src/main.ts index 5531264..af9bcec 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -71,7 +71,7 @@ async function bootstrap() { error: { code: 'SYS_001', message: - '일시적인 시스템 오류가 발생했습니다. (요청 한도 초과) 잠시 후 다시 시도해 주세요.', // 글로벌 통합 에러 포맷 + '요청이 너무 많아요. 잠시 후 다시 시도해 주세요.', // 글로벌 통합 에러 포맷 }, }, }), diff --git a/flutter/lib/core/api/error_lexicon.dart b/flutter/lib/core/api/error_lexicon.dart index 423e18f..21ca2c1 100644 --- a/flutter/lib/core/api/error_lexicon.dart +++ b/flutter/lib/core/api/error_lexicon.dart @@ -1,12 +1,12 @@ class ErrorLexicon { static const Map codes = { - 'SYS_001': '일시적인 시스템 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.', - 'AUTH_001': '로그인이 필요한 서비스입니다. 로그인 후 이용해 주세요.', - 'AUTH_002': '안전을 위해 로그아웃 되었습니다. 다시 로그인해 주세요.', - 'AUTH_003': '해당 메뉴나 기능에 접근할 수 있는 권한이 없습니다.', - 'VAL_001': '입력하신 정보를 다시 확인해 주세요. (필수값 누락 또는 형식 오류)', - 'RES_001': '요청하신 정보나 페이지를 찾을 수 없습니다.', - 'BIZ_001': '요청하신 작업을 완료하지 못했습니다. 다시 시도해 주세요.', + 'SYS_001': '일시적인 오류가 생겼어요. 잠시 후 다시 시도해 주세요.', + 'AUTH_001': '로그인이 필요한 기능이에요. 로그인 후 이용해 주세요.', + 'AUTH_002': '안전을 위해 로그아웃했어요. 다시 로그인해 주세요.', + 'AUTH_003': '이 메뉴나 기능을 이용할 권한이 없어요.', + 'VAL_001': '입력하신 내용을 다시 확인해 주세요. 빠진 항목이 있거나 형식이 맞지 않아요.', + 'RES_001': '찾으시는 정보나 페이지가 없어요.', + 'BIZ_001': '요청하신 작업을 끝내지 못했어요. 다시 시도해 주세요.', }; static String getMessage(String code) { diff --git a/frontend/src/components/common/SectionBoundary.vue b/frontend/src/components/common/SectionBoundary.vue index 0827ff4..08c8bb8 100644 --- a/frontend/src/components/common/SectionBoundary.vue +++ b/frontend/src/components/common/SectionBoundary.vue @@ -1,5 +1,7 @@