11 KiB
11 KiB
| phase | agent | agent_version | generated_at | concerns | source_confidence | workers_spawned | ||||
|---|---|---|---|---|---|---|---|---|---|---|
| research | research-advisor | 2 | 2026-06-30T11:10:00+09:00 |
|
high | 4 |
조사 결과
주제
bibimbap 프로젝트 포스팅 메뉴 진입 시 500 Error 원인 조사
포인트별 발견
포인트 1: 포스팅 Controller URL 매핑
- 경로:
/Users/wemadeplay/workspace/stz/bibimbap/src/main/java/com/pandoli365/bibimbap/controller/PostController.java - 신뢰도: 확인됨
| HTTP | 경로 | 핸들러 메서드 | 반환 |
|---|---|---|---|
| GET | /posts |
list |
View: posts-list |
| GET | /posts/{id} |
detail |
View: posts-detail (null이면 redirect:/posts) |
| GET | /posts/new |
createForm |
View: posts-form (미인증 시 redirect:/login) |
| POST | /posts |
create |
JSON ResponseEntity |
| GET | /posts/{id}/edit |
editForm |
View: posts-form (미인증 시 redirect:/login) |
| POST | /posts/{id} |
update |
JSON ResponseEntity |
| POST | /posts/{id}/delete |
delete |
JSON ResponseEntity |
- 경로:
/Users/wemadeplay/workspace/stz/bibimbap/src/main/java/com/pandoli365/bibimbap/controller/PostAdminController.java - 신뢰도: 확인됨
| HTTP | 경로 | 핸들러 메서드 |
|---|---|---|
| GET | /admin/post-categories |
postCategoriesPage → View: admin-post-categories |
| POST | /admin/post-categories |
createCategory |
| POST | /admin/post-categories/{id} |
updateCategory |
| POST | /admin/post-categories/{id}/delete |
deleteCategory |
포인트 2: Service / Mapper 체인
- 신뢰도: 확인됨
PostController 의존 Bean:
PostsMapper(annotation 기반 MyBatis) —listPublishedKeyset,getPublished,insert,update,softDeletePostCategoriesMapper—listActive,getActive,getById,insert,update,delete,countPostsByCategoryPostMarkdownService— CommonMark 파싱 + Jsoup basicWithImages safelist sanitizeOgPreviewService— SSRF-safe HTTP fetch + OG 메타 파싱, 실패 시 graceful empty
별도 PostService/PostServiceImpl 없음. 비즈니스 로직이 Controller에 직접 집약. XML Mapper 없음, 전부 @Select/@Insert/@Update annotation.
포인트 3: 500 에러 원인 후보 (가능성 순)
[1순위 — 가장 유력] posts-detail.jsp NPE (확인됨)
- 파일:
/Users/wemadeplay/workspace/stz/bibimbap/src/main/webapp/WEB-INF/views/posts-detail.jsp PostData post = (PostData) request.getAttribute("post");이후 null 체크 없이post.getTitle(),post.getId()등 직접 접근postattribute가 null인 상태로 JSP가 렌더링되면 NullPointerException → 500- 발생 경로:
PostController.detail()이postsMapper.getPublished(id)를 호출해 null을 받으면redirect:/posts로 분기하므로 Controller 레벨에서는 방어됨. 그러나 다른 진입 경로(forward, include, 테스트 호출)에서 post attribute 누락 시 노출 가능.
[2순위] DRAFT 포스트 조회 시 null 처리 흐름 (확인됨)
- 파일:
PostController.javaline 167, 206, 252 editForm,update,delete핸들러 모두postsMapper.getPublished(id)사용getPublishedSQL:WHERE p.status = 'PUBLISHED' AND p.is_delete = false- DRAFT 상태로 저장된 포스트 ID로 editForm 접근 시 → null →
if (post == null) return "redirect:/posts"— 500이 아닌 redirect지만, JS fetch로 update/delete 호출 시 null → NPE 가능성:PostController.update()line 206:existing.setCategoryId(categoryId)—existing이 null이면 NPE → 500PostController.delete()line 252:postsMapper.softDelete(id)호출 전 null 체크 여부 추가 확인 필요
[3순위] PostsMapper.update() — category_id 컬럼 누락 (확인됨)
- 파일:
PostsMapper.javaline 120-135 - UPDATE SQL 대상 컬럼:
title, body_markdown, body_sanitized_html, link_url, og_*, status, updated_at category_id = #{categoryId}누락 → 카테고리 변경이 DB에 반영되지 않음- 500 직접 원인은 아니나, 카테고리 변경 요청 시 데이터 정합성 손상
[4순위] PostsMapper.insert() 직후 ID null 체크 (확인됨)
PostController.create()line 149:if (post.getId() == null)체크 후 500 응답- DB INSERT 실패(constraint violation, DB 연결 오류 등) 시 MyBatis 예외 → Spring이 500으로 변환
- 포스트 생성 시
category_idFK 참조 실패(비활성 카테고리 등) 가능
[5순위] Bean 주입 / 빌드 이슈 (확인됨 — 현재 정상)
- Spring Boot 3.5.16, MyBatis Spring Boot 3.0.5: 호환 정상
- 모든 Bean 생성자 주입,
@Mapper어노테이션 정상 - JSTL 미사용(scriptlet 방식), tomcat-embed-jasper compile scope: JSP 렌더링 정상
- ViewResolver: prefix=
/WEB-INF/views/, suffix=.jsp정상 - 기동 실패 유발 구조적 결함 없음
[6순위] PostAdminController 권한 체크 부재 (추정)
PostController는gate.has(session, "POST_WRITE")명시 검사PostAdminController에는PermissionGate주입 자체 없음InterceptorConfig에서/admin/**에RbacInterceptor등록 확인됨 — 필터 레벨 보호 존재/admin/jams/**는 제외(addExcludePatterns)./admin/post-categories는 포함 여부 추가 확인 필요- 500 직접 원인 아님; 인증 우회 시 NPE 가능성
포인트 4: 관련 JSP 파일 경로
- 신뢰도: 확인됨
| JSP | 절대 경로 |
|---|---|
| 포스트 목록 | /Users/wemadeplay/workspace/stz/bibimbap/src/main/webapp/WEB-INF/views/posts-list.jsp |
| 포스트 상세 | /Users/wemadeplay/workspace/stz/bibimbap/src/main/webapp/WEB-INF/views/posts-detail.jsp |
| 포스트 작성/수정 폼 | /Users/wemadeplay/workspace/stz/bibimbap/src/main/webapp/WEB-INF/views/posts-form.jsp |
| 관리자 카테고리 | /Users/wemadeplay/workspace/stz/bibimbap/src/main/webapp/WEB-INF/views/admin-post-categories.jsp |
admin-post-categories.jsp 주의사항:
var BASE = '/admin/post-categories';— context path 미포함 하드코딩- 다른 JSP들은
<%= ctx %>변수(contextPath 동적 주입)를 사용하는 것과 비교해 inconsistent - context root가
/이외로 배포 시 404 발생
포인트 5: 최근 git 변경 사항 (포스팅 관련)
- 신뢰도: 확인됨
| 커밋 해시 | 메시지 |
|---|---|
6047a39 |
feat(board): W3-3 포스팅 보드 — 공지/블로그 + OG 미리보기(SSRF 방어) + 유니티 피드 감시 |
- 포스팅 기능이 이 단일 커밋(32파일, 5327줄 추가)으로 초기 도입됨 (2026-06-29)
- 후속 수정 커밋 없음 — 초기 도입 이후 한 번도 수정되지 않은 상태
- 이전 세션 로그에서 발견된 PKIX 빌드 오류(
spring-boot-dependencies:3.5.14-SNAPSHOT)는e51ab01커밋으로 해소됨 (현재 정식 버전 사용)
종합 판단
"포스팅 메뉴 진입 시 500"의 가장 유력한 원인은 posts-detail.jsp의 null 체크 부재(NPE)다.
구체적 시나리오:
GET /posts/{id}요청 →PostController.detail()→postsMapper.getPublished(id)호출id가 DRAFT 상태이거나 삭제된 포스트이거나 존재하지 않으면 →null반환 →redirect:/posts로 처리됨 (Controller 레벨 방어 있음)- 그러나 "포스팅 메뉴 진입"이
GET /posts(목록)라면:posts-list.jsp로 렌더링되며postCategoriesMapper.listActive()가 빈 결과를 반환할 경우 JSP 루프는 정상 처리. 별도 NPE 없음. - 만약 카테고리가 하나도 없는 상태에서
createForm(GET /posts/new) 진입 시:postCategoriesMapper.listActive()가 빈 리스트를 반환하더라도 JSTL/scriptlet 루프이므로 NPE 없음.
따라서 정확한 500 재현 경로는 다음 중 하나:
GET /posts/{존재하지 않거나 DRAFT인 id}→ Controller null 체크 → redirect (500 아님)POST /posts/{DRAFT id}(JS fetch update) →existing = postsMapper.getPublished(id)→ null →existing.setCategoryId()→ NPE → 500 ← 가장 유력posts-detail.jsp직접 forward/include 시 post attribute 누락 → NPE → 500
권위 격상 전 검증 필요 항목:
PostController.update()line 206에서existing이 null일 때의 실제 분기 코드 (null 체크가 있는지 여부) — worker 탐색에서 null 체크 코드가 보이지 않아 NPE 유력하나, 해당 라인 전후 코드 직접 확인 권장
영향 파일 목록
| 파일 | 역할 | 이슈 |
|---|---|---|
src/main/java/.../controller/PostController.java |
메인 Controller | DRAFT 조회 불가, update NPE 후보 |
src/main/java/.../controller/PostAdminController.java |
관리자 Controller | 권한 체크 부재 |
src/main/java/.../mapper/PostsMapper.java |
MyBatis Mapper | update SQL category_id 누락 |
src/main/java/.../mapper/PostCategoriesMapper.java |
카테고리 Mapper | 정상 |
src/main/java/.../service/PostMarkdownService.java |
마크다운 렌더링 | 정상 |
src/main/java/.../service/OgPreviewService.java |
OG 미리보기 | 정상 |
src/main/webapp/WEB-INF/views/posts-detail.jsp |
상세 JSP | NPE 위험 (null 체크 없는 post 접근) |
src/main/webapp/WEB-INF/views/posts-list.jsp |
목록 JSP | 정상 |
src/main/webapp/WEB-INF/views/posts-form.jsp |
작성/수정 JSP | 정상 |
src/main/webapp/WEB-INF/views/admin-post-categories.jsp |
관리자 JSP | context path 하드코딩 주의 |
미해결 (open_questions)
PostController.update()line 206 전후 null 체크 존재 여부:existing이 null일 때 NPE로 이어지는지, 아니면 별도 분기가 있는지 해당 라인 직접 확인 필요. (현재 탐색에서는 null 체크 코드가 보이지 않아 NPE 유력 추정)- 정확한 "포스팅 메뉴 진입" URL:
/posts(목록),/posts/{id}(상세),/posts/new(작성폼),/admin/post-categories중 어느 URL에서 500이 발생하는지 재현 경로 미확인 - DB 스키마 상태:
post_categories테이블에 활성(is_active=true) 카테고리가 존재하는지 — 카테고리 없는 상태에서 create/edit 폼의 카테고리 드롭다운이 빈 상태로 표시되고 categoryId=null 제출 → validate() line 305categoryId == null조건으로 BadRequest 반환 (500 아님) - RbacInterceptor
/admin/post-categories보호 여부:addExcludePatterns에서/admin/jams/**외 추가 제외 경로 상세 확인 필요 - 애플리케이션 로그 스택트레이스: 실제 500 발생 시의 예외 클래스와 라인 넘버 — 현재 로그 파일에서 포스팅 관련 스택트레이스 미발견