49 KiB
CLAUDE.md
이 파일은 이 저장소에서 코드 작업을 할 때 Claude Code (claude.ai/code)에게 제공하는 가이드입니다.
⚠️ 최우선 규칙 - 반드시 준수 ⚠️
🚨 패턴 통일화 필수 프로세스 (MANDATORY)
모든 코드 작성 전 반드시 아래 프로세스를 따를 것. 이 프로세스를 건너뛰면 작업을 시작하지 말 것.
1단계: 패턴 분석 (ALWAYS FIRST)
새로운 코드를 작성하기 전, 반드시 유사한 기존 코드의 패턴을 먼저 분석 (참고대상 CRDN-단속 하위 디렉토리 or 패키지 or xml query):
-
Controller 작성 시:
- 동일 패키지 내 다른 Controller 파일을 최소 2개 이상 Read로 읽고 분석
- 메서드 구조, 어노테이션 패턴, 파라미터 처리 방식, 응답 구조 확인
-
JSP/JavaScript 작성 시:
- 동일 디렉토리 내 유사한 JSP 파일을 Read로 읽고 분석
- JavaScript 함수 네이밍, AJAX 호출 패턴, 에러 처리 방식 확인
- HTML 구조, CSS 클래스명, 폼 구조 확인
-
Service/Mapper 작성 시:
- 동일 패키지의 기존 Service/Mapper 파일 읽고 분석
- 메서드 네이밍, 트랜잭션 처리, 예외 처리 패턴 확인
2단계: 패턴 적용 검증 (BEFORE WRITING)
코드를 작성하기 전, 다음을 확인:
- ✅ 기존 파일을 Read 도구로 읽었는가?
- ✅ 해당 파일의 코딩 스타일(들여쓰기, 주석, 네이밍)을 파악했는가?
- ✅ 유사한 기능이 어떻게 구현되어 있는지 확인했는가?
- ✅ 내가 작성할 코드가 기존 패턴과 100% 일치하는가?
위 4가지 중 하나라도 NO면 코드를 작성하지 말고, 먼저 패턴 분석으로 돌아갈 것.
3단계: 작성 후 자체 검증 (AFTER WRITING)
코드 작성 후 제출 전, 다음을 점검:
- ✅ 들여쓰기(탭/스페이스)가 기존 파일과 동일한가?
- ✅ 변수/함수 네이밍 규칙이 기존과 동일한가?
- ✅ 주석 스타일(한글/영문, 위치)이 기존과 동일한가?
- ✅ 에러 처리 방식이 기존과 동일한가?
- ✅ API 응답 구조가 기존과 동일한가?
- ✅ [필수] VO 파일을 Read 도구로 다시 읽어서 필드가 정상 추가되었는지 확인했는가? (Edit 결과를 믿지 말고 반드시 Read로 재확인)
- ✅ [필수] XML 쿼리를 Read 도구로 다시 읽어서 수정이 정상 반영되었는지 확인했는가?
🔴 절대 금지 사항
- ❌ 기존 코드를 읽지 않고 "추측"으로 작성
- ❌ 기존 파일의 들여쓰기/포맷팅 변경
- ❌ 기존과 다른 스타일의 주석 추가
- ❌ 새로운 패턴이나 "더 나은 방법" 제안 (명시적 요청이 없는 한)
- ❌ 임의로 코드 리팩토링
📋 체크리스트 (모든 작업 전 필수 확인)
[ ] 1. 유사 기능의 기존 코드를 Read로 읽었는가?
[ ] 2. 기존 코드의 패턴(네이밍, 구조, 스타일)을 분석했는가?
[ ] 3. 내 코드가 기존 패턴과 100% 일치하는가?
[ ] 4. 들여쓰기, 주석, 변수명이 기존과 동일한가?
[ ] 5. [작업 후] VO 파일 변경 시 Read로 재확인했는가?
[ ] 6. [작업 후] XML 쿼리 변경 시 Read로 재확인했는가?
[ ] 7. [작업 후] Service/Mapper 변경 시 Read로 재확인했는가?
이 체크리스트를 모두 완료하지 않으면 코드를 작성하지 말 것. 특히 Edit 도구 사용 후에는 반드시 Read 도구로 재확인할 것. Edit 결과를 절대 믿지 말 것.
프로젝트 개요
일산 동구 건축물 위법행위 통합관리시스템 - 공무원이 불법 건축물 관련 위반사항을 관리하기 위한 Spring Boot 웹 애플리케이션입니다.
빌드 및 개발 명령어
빌드 명령어
# 프로젝트 빌드
./gradlew build
# 클린 빌드
./gradlew clean build
# 애플리케이션 실행 (기본 포트: 8080)
./gradlew bootRun
# 배포용 WAR 파일 빌드
./gradlew bootWar
테스트
이 프로젝트에서는 프로젝트 가이드라인에 따라 테스트가 명시적으로 비활성화되어 있습니다. 테스트 파일을 생성하지 마세요.
고수준 아키텍처
기술 스택
- JDK: JDK1.8만 사용
- 프레임워크: Spring Boot 2.7.18 with eGovFrame 4.3.0 (한국 정부 표준 프레임워크)
- 데이터베이스: MariaDB with MyBatis 2.3.1 for ORM
- 프론트엔드: JSP with Apache Tiles 3.0.8 (레이아웃), TOAST UI Grid 4.19.2 (데이터 그리드)
- 빌드 도구: Gradle
MVC 아키텍처 패턴
프로젝트는 엄격한 MVC 패턴을 따릅니다:
- Model:
model/패키지에PagingVO를 확장한 VO 클래스들 포함 - View: Apache Tiles 레이아웃을 사용하는
src/main/webapp/WEB-INF/views/의 JSP 파일들 - Controller: Swagger 어노테이션을 사용하는 Spring MVC 컨트롤러가 있는
controller/패키지들
비즈니스 도메인별 패키지 구조
go.kr.project/
├── system/ # 시스템 관리 (사용자, 역할, 메뉴, 코드)
├── login/ # 인증
├── common/ # 공통 컴포넌트 및 유틸리티
├── baseData/ # 마스터 데이터 관리 (건축물 지수, 위반 법령)
├── crdn/ # 핵심 비즈니스: 위반 관리
│ ├── crndRegistAndView/ # 위반 등록 및 조회
│ │ ├── main/ # 주요 위반 프로세스
│ │ ├── crdnActInfo/ # 위반 행위 정보
│ │ ├── crdnPstnInfo/ # 위치 정보
│ │ └── crdnOwnrInfo/ # 소유자 정보
│ └── ownac/ # 소유자 조치 관리
└── noti/ # 알림 시스템
데이터베이스 레이어 (MyBatis)
- 매퍼:
mapper/패키지의 인터페이스 클래스들 - SQL: 동일한 패키지 구조를 따르는
src/main/resources/mybatis/mapper/의 XML 파일들 - 시퀀스: 모든 기본 키는 10자리 LPAD 시퀀스 사용:
SELECT LPAD(NEXTVAL(seq_table_id), 10, '0') - 네이밍: camelCase 변환 자동 사용,
DB-DDL/maria/dictionary/의 컬럼 단어 사전 따름
서비스 레이어 패턴
각 비즈니스 도메인은 다음을 포함합니다:
- Service Interface: 비즈니스 오퍼레이션 정의
- ServiceImpl: 비즈니스 로직 구현,
@RequiredArgsConstructor사용하고 매퍼 호출 - 표준 메서드: 일관된 네이밍을 따르는
select*,insert*,update*,delete*
프론트엔드 아키텍처
레이아웃 (Apache Tiles)
- base: 헤더, 메뉴, 콘텐츠 영역을 가진 메인 관리자 레이아웃
- login: 로그인 페이지 레이아웃
- popup: 팝업 윈도우 레이아웃 (접미사
TilesConstants.POPUP)
UI 컴포넌트
- 모달: JavaScript로 제어되는
.modalzCSS 클래스를 가진 JSP에 임베드 - 팝업: 팝업 레이아웃을 사용하는 별도 JSP 페이지,
window.open()으로 열림 - 그리드: 구성을 위한
XitTuiGridConfig헬퍼 클래스를 가진 TOAST UI Grid
JavaScript 패턴
setTimeout사용 피하기 (문제 야기)- 모든 URL에
<c:url/>태그 사용 dataSource.api.readData를 통한 AJAX로 그리드 데이터 로딩
CSS, JavaScript 위치
src/main/webapp/resources/xit src/main/webapp/resources/css
TUI Grid 는 아래 패턴을 이용
/**
* 단속 목록 관리 네임스페이스
*/
var CrdnRegistAndViewList = {
/**
* 선택된 행 정보
*/
selectedRow: null,
/**
* 그리드 관련 객체
*/
grid: {
/**
* 그리드 인스턴스
*/
instance: null,
tui grid 선언 규칙 CrdnRegistAndViewList.grid.instance 해당페이지명명규칙.그리드객체.그리드인스턴스
중요한 개발 가이드라인
모드 표준화
모든 CRUD 작업은 일관된 모드 값을 사용합니다:
mode: C= Create (등록)mode: U= Update (수정)mode: V= View (보기)mode: D= Delete (삭제)
페이지네이션 패턴 (중요한 순서)
// 1. 먼저 총 개수 가져오기
int totalCount = service.selectTotalCount(paramVO);
// 2. 총 개수 설정
paramVO.setTotalCount(totalCount);
// 3. 페이징 활성화 (선택사항)
paramVO.setPagingYn("Y");
보안 및 세션
- 감사 필드에
SessionUtil.getUserId()사용 - 모든 폼에 CSRF 보호 필요
- 하드코딩된 자격증명이나 민감한 데이터 금지
코드 품질 요구사항
- 한글 주석: 모든 비즈니스 로직은 한글 주석 필수 (중요로직 주석)
- Swagger: 모든 컨트롤러는 Swagger 어노테이션 필수 (
@Tag,@Operation,@ApiResponses) - 포맷팅 변경 금지: 기존 코드 포맷팅이나 공백 수정 절대 금지
비즈니스 로직 패턴
위반 관리(crdn)와 마스터 데이터(baseData)의 경우, 다음의 기존 패턴을 따르세요:
- 뷰 패턴:
src/main/webapp/WEB-INF/views/baseData/와src/main/webapp/WEB-INF/views/crdn/ - Java 패턴:
src/main/java/go/kr/project/baseData/bldgNewPrcCrtrAmt/와src/main/java/go/kr/project/crdn/
최근 아키텍처 추가: 스마트 CRUD 패턴
CrdnLevyPrvntcController는 생성/업데이트 작업을 처리하는 고급 패턴을 보여줍니다:
- 통합 저장 API:
/saveLevyInfo.ajax가 insert vs update를 자동으로 결정 - 스냅샷 복원: 그리드 행 선택이 기존 데이터를 로드하여 이전 계산을 복원
- 스마트 데이터 로딩: 복잡한 조인을 사용하여 표시명과 계산 비율을 함께 가져옴
- 프론트엔드 상태 관리: JavaScript가 모드 감지와 폼 채우기를 처리
이 패턴은 데이터를 계산 스냅샷으로 저장하고 나중에 복원해야 하는 유사한 위반 처리 워크플로우에서 따라야 합니다.
📘 프로젝트 상세 문서
1. 전체 프로젝트 구조
1.1 Java 패키지 구조 상세
go.kr.project/
│
├── common/ # 공통 기능
│ ├── controller/ # 공통 컨트롤러
│ │ ├── CommonCodeController # 공통코드 조회
│ │ ├── HtmlEditorController # HTML 에디터 파일 관리
│ │ └── SearchAddressController # 주소 검색
│ ├── service/ # 공통 서비스
│ │ ├── CommonCodeService # 공통코드 서비스
│ │ ├── CommonHeaderService # 공통 헤더 서비스
│ │ └── HtmlEditorService # HTML 에디터 서비스
│ ├── mapper/ # 공통 매퍼
│ └── model/ # 공통 모델
│ ├── PagingVO # 페이징 기본 VO (모든 VO의 부모 클래스)
│ ├── FileVO # 파일 정보 VO
│ └── CmmnCodeSearchVO # 공통코드 검색 VO
│
├── login/ # 로그인 및 인증
│ ├── service/LoginService # 로그인 서비스
│ ├── mapper/LoginMapper # 로그인 매퍼
│ └── model/ # 로그인 모델
│ ├── SessionVO # 세션 정보 VO
│ ├── UserSessionVO # 사용자 세션 VO
│ └── LoginUserVO # 로그인 사용자 VO
│
├── mypage/ # 마이페이지
│ ├── controller/MypageController
│ ├── service/MypageService
│ └── service/impl/MypageServiceImpl
│
├── system/ # 시스템 관리
│ ├── user/ # 사용자 관리
│ │ ├── controller/UserController
│ │ ├── service/UserService
│ │ ├── service/impl/UserServiceImpl
│ │ ├── mapper/UserMapper
│ │ └── model/
│ │ ├── SystemUserVO # 시스템 사용자 VO
│ │ └── SystemUserSearchVO # 사용자 검색 VO
│ ├── group/ # 그룹 관리
│ │ ├── controller/GroupController
│ │ ├── service/GroupService
│ │ └── model/GroupVO
│ ├── role/ # 역할 관리
│ │ ├── controller/RoleController
│ │ ├── service/RoleService
│ │ └── model/RoleVO
│ ├── menu/ # 메뉴 관리
│ │ ├── controller/MenuController
│ │ ├── service/MenuService
│ │ └── mapper/MenuMapper
│ ├── code/ # 코드 관리
│ │ ├── controller/CodeController
│ │ ├── service/CodeService
│ │ ├── mapper/CodeMapper
│ │ └── model/
│ │ ├── CodeGroupVO # 코드 그룹 VO
│ │ └── CodeDetailVO # 코드 상세 VO
│ ├── auth/ # 권한 관리
│ │ ├── controller/AuthController
│ │ ├── service/AuthService
│ │ ├── mapper/AuthMapper
│ │ └── model/
│ │ ├── AuthVO
│ │ ├── GroupRoleVO
│ │ ├── RoleMenuVO
│ │ └── MenuProgramVO
│ ├── loginLog/ # 로그인 로그
│ │ ├── controller/LoginLogController
│ │ ├── service/LoginLogService
│ │ └── model/LoginLogVO
│ └── execquery/ # 쿼리 실행 (개발/디버깅용)
│ ├── controller/DbQueryController
│ ├── service/DbQueryService
│ └── config/IsolatedTransactionManager
│
├── baseData/ # 기초 데이터 관리
│ └── bldgNewPrcCrtrAmt/ # 건축물 신축가격 기준액 관리
│ ├── controller/BldgNewPrcCrtrAmtController (추정)
│ ├── service/BldgNewPrcCrtrAmtService
│ ├── service/impl/BldgNewPrcCrtrAmtServiceImpl
│ ├── mapper/BldgNewPrcCrtrAmtMapper
│ └── model/BldgNewPrcCrtrAmtVO
│
├── crdn/ # 단속 관리 (핵심 비즈니스 도메인)
│ ├── crndRegistAndView/ # 단속 등록 및 조회
│ │ ├── main/ # 메인 단속 프로세스
│ │ │ ├── controller/
│ │ │ │ ├── CrdnRegistAndViewController # 단속 등록/조회
│ │ │ │ ├── CrdnLevyPrvntcController # 부과예고 관리
│ │ │ │ ├── CrdnRelevyController # 재부과 관리
│ │ │ │ └── CrdnImpltTaskController # 이행 업무 관리
│ │ │ ├── service/
│ │ │ │ ├── CrdnRegistAndViewService
│ │ │ │ ├── CrdnLevyPrvntcService
│ │ │ │ ├── CrdnRelevyService
│ │ │ │ └── CrdnImpltTaskService
│ │ │ ├── service/impl/
│ │ │ │ ├── CrdnRegistAndViewServiceImpl
│ │ │ │ ├── CrdnLevyPrvntcServiceImpl
│ │ │ │ ├── CrdnRelevyServiceImpl
│ │ │ │ └── CrdnImpltTaskServiceImpl
│ │ │ └── model/
│ │ │ ├── CrdnRegistAndViewVO # 단속 기본 정보 VO
│ │ │ ├── CrdnLevyInfoVO # 부과 정보 VO (계산식 포함)
│ │ │ ├── CrdnRelevyVO # 재부과 정보 VO
│ │ │ ├── CrdnImpltTaskVO # 이행 업무 VO
│ │ │ ├── CrdnAdsbmtnRtVO # 가감산율 VO
│ │ │ ├── CrdnCmpttnRt2VO # 산정률2 VO
│ │ │ └── LevyPrvntcActInfoVO # 부과예고 행위정보 VO
│ │ ├── crdnActInfo/ # 행위 정보 관리
│ │ │ ├── controller/CrdnActInfoController
│ │ │ ├── service/
│ │ │ │ ├── CrdnActInfoService
│ │ │ │ └── CrdnPhotoService # 사진 관리
│ │ │ ├── service/impl/
│ │ │ └── model/
│ │ │ ├── CrdnActInfoVO # 행위 정보 VO
│ │ │ └── CrdnVltnLwrgVO # 위반 법령 VO
│ │ ├── crdnPstnInfo/ # 위치 정보 관리
│ │ │ ├── controller/CrdnPstnInfoController
│ │ │ ├── service/CrdnPstnInfoService
│ │ │ └── model/CrdnPstnInfoVO # 위치 정보 VO
│ │ ├── crdnOwnrInfo/ # 소유자 정보 관리
│ │ │ ├── controller/CrdnOwnrInfoController
│ │ │ ├── service/CrdnOwnrInfoService
│ │ │ └── model/CrdnOwnrInfoVO # 소유자 정보 VO
│ │ ├── crdnActrInfo/ # 행위자 정보 관리
│ │ │ ├── controller/CrdnActrInfoController
│ │ │ ├── service/CrdnActrInfoService
│ │ │ └── model/ (추정)
│ │ ├── crdnExmnr/ # 조사자 정보 관리
│ │ │ ├── controller/CrdnExmnrController
│ │ │ ├── service/CrdnExmnrService
│ │ │ ├── mapper/CrdnExmnrMapper
│ │ │ └── model/CrdnExmnrVO
│ │ └── crdnOwnrSelect/ # 소유자 선택
│ │ ├── controller/CrdnOwnrSelectController
│ │ ├── service/CrdnOwnrSelectService
│ │ └── service/impl/CrdnOwnrSelectSelectServiceImpl
│ ├── ownac/ # 소유자 조치 관리
│ │ ├── controller/OwnActRegistAndViewController
│ │ ├── service/OwnActRegistAndViewService
│ │ └── service/impl/OwnActRegistAndViewServiceImpl
│ └── exmnr/ # 조사자 관리
│ ├── controller/ExmnrController
│ ├── service/ExmnrService
│ └── service/impl/ExmnrServiceImpl
│
├── noti/ # 알림 시스템 (추정)
│
└── ma30/ # MA30 관련 (추정)
└── model/
├── Ma30FindListVO
└── Ma30FindRlistSearchVO
1.2 View (JSP) 디렉토리 구조
src/main/webapp/WEB-INF/views/
│
├── common/ # 공통 뷰
│ ├── header.jsp # 헤더
│ ├── footer.jsp # 푸터
│ └── menu.jsp # 메뉴
│
├── login/ # 로그인
│ └── login.jsp
│
├── system/ # 시스템 관리
│ ├── user/ # 사용자 관리
│ ├── group/ # 그룹 관리
│ ├── role/ # 역할 관리
│ ├── menu/ # 메뉴 관리
│ ├── code/ # 코드 관리
│ └── loginLog/ # 로그인 로그
│
├── baseData/ # 기초 데이터
│ └── bldgNewPrcCrtrAmt/ # 건축물 신축가격 기준액
│
└── crdn/ # 단속 관리
├── crndRegistAndView/
│ └── main/
│ ├── list.jsp # 단속 목록
│ ├── crdnRegistPopup.jsp # 단속 등록/수정 팝업
│ ├── detailView-main.jsp # 단속 상세보기 메인
│ └── crdnLevyPrvntc/
│ ├── levyPrvntcPopup.jsp # 부과예고 팝업
│ └── LevyAddMinusPopup.jsp # 가감산 팝업
└── ownac/ # 소유자 조치
1.3 MyBatis Mapper XML 구조
src/main/resources/mybatis/mapper/
│
├── common/
│ ├── CommonCodeMapper_maria.xml
│ └── CommonHeaderMapper_maria.xml
│
├── login/
│ └── LoginMapper_maria.xml
│
├── system/
│ ├── user/UserMapper_maria.xml
│ ├── group/GroupMapper_maria.xml
│ ├── role/RoleMapper_maria.xml
│ ├── menu/MenuMapper_maria.xml
│ ├── code/CodeMapper_maria.xml
│ ├── auth/AuthMapper_maria.xml
│ └── loginLog/LoginLogMapper_maria.xml
│
├── baseData/
│ └── bldgNewPrcCrtrAmt/BldgNewPrcCrtrAmtMapper_maria.xml
│
└── crdn/
├── crndRegistAndView/
│ ├── main/
│ │ ├── CrdnRegistAndViewMapper_maria.xml
│ │ ├── CrdnLevyPrvntcMapper_maria.xml
│ │ ├── CrdnRelevyMapper_maria.xml
│ │ └── CrdnImpltTaskMapper_maria.xml
│ ├── crdnActInfo/
│ │ ├── CrdnActInfoMapper_maria.xml
│ │ └── CrdnPhotoMapper_maria.xml
│ ├── crdnPstnInfo/
│ │ └── CrdnPstnInfoMapper_maria.xml
│ ├── crdnOwnrInfo/
│ │ └── CrdnOwnrInfoMapper_maria.xml
│ ├── crdnActrInfo/
│ │ └── CrdnActrInfoMapper_maria.xml
│ ├── crdnExmnr/
│ │ └── CrdnExmnrMapper_maria.xml
│ └── crdnOwnrSelect/
│ └── CrdnOwnrSelectMapper_maria.xml
├── ownact/
│ └── OwnActRegistAndViewMapper_maria.xml
└── exmnr/
└── ExmnrMapper_maria.xml
2. 주요 업무 로직 상세
2.1 단속 관리 프로세스 (crdn 도메인)
2.1.1 단속 등록 및 조회 (CrdnRegistAndViewController)
주요 API 엔드포인트:
GET /crdn/crndRegistAndView/list.do- 단속 목록 화면POST /crdn/crndRegistAndView/list.ajax- 단속 목록 조회 (페이징)GET /crdn/crndRegistAndView/crdnRegistPopup.do- 단속 등록/수정 팝업GET /crdn/crndRegistAndView/detailView.do- 단속 상세보기POST /crdn/crndRegistAndView/insert.ajax- 단속 등록POST /crdn/crndRegistAndView/update.ajax- 단속 수정POST /crdn/crndRegistAndView/delete.ajax- 단속 삭제 (논리삭제)GET /crdn/crndRegistAndView/selectOne.ajax- 단속 상세 조회POST /crdn/crndRegistAndView/updateStatus.ajax- 단속 상태 업데이트GET /crdn/crndRegistAndView/checkActCmpltCd.ajax- 조치처리상태 확인POST /crdn/crndRegistAndView/excel.do- 단속 목록 엑셀 다운로드
주요 필드 (CrdnRegistAndViewVO):
crdnYr(단속 연도) +crdnNo(단속 번호) = 복합키sggCd(시군구 코드)crdnPrcsSttsCd(단속 진행 상태 코드)dsclMthd(적발 방법)stdgEmdCd(법정동 읍면동 코드)rgnSeCd(지역 구분 코드)
비즈니스 규칙:
- 등록 시 세션의 조직코드(orgCd)를 sggCd로 자동 설정
- 삭제는 논리삭제 (DEL_YN='Y')
- 페이징 처리 시 반드시 순서 준수:
- 총 개수 조회 → totalCount 설정 → pagingYn='Y' 설정
2.1.2 부과예고 관리 (CrdnLevyPrvntcController)
주요 API 엔드포인트:
GET /crdn/crndRegistAndView/crdnLevyPrvntc/selectLevyInfoFirstCheck.ajax- 부과정보 존재 여부 확인GET /crdn/crndRegistAndView/crdnLevyPrvntc/levyPrvntcPopup.do- 부과예고 팝업GET /crdn/crndRegistAndView/crdnLevyPrvntc/selectIsAllLevyInfoCompleted.ajax- 부과정보 완료 여부POST /crdn/crndRegistAndView/crdnLevyPrvntc/actlist.ajax- 행위정보 목록 조회GET /crdn/crndRegistAndView/crdnLevyPrvntc/LevyAddMinusPopup.do- 가감산 팝업POST /crdn/crndRegistAndView/crdnLevyPrvntc/adsbmtnRtList.ajax- 가감산율 목록 조회GET /crdn/crndRegistAndView/crdnLevyPrvntc/selectLevyInfoByActInfoId.ajax- 기존 부과정보 조회POST /crdn/crndRegistAndView/crdnLevyPrvntc/saveLevyInfo.ajax- 부과정보 저장 (신규/수정 자동 판단)POST /crdn/crndRegistAndView/crdnLevyPrvntc/delLevyInfo.ajax- 부과정보 삭제POST /crdn/crndRegistAndView/crdnLevyPrvntc/calculateAll.ajax- 통합 계산 API (핵심 로직)
주요 필드 (CrdnLevyInfoVO):
- 기본키:
levyInfoId - 참조키:
crdnYr,crdnNo,impltTaskSeCd,actInfoId - 계산 관련 필드:
bldgCrtrMprcAmt(건물 기준 시가액)strctIdx(구조 지수)usgIdx(용도 지수)pstnIdx(위치 지수)elpsYrRdvlrt(경과 연도 잔가율)bscsCstrnRt(기초 공사율)vltnArea(위반 면적)adsbmtnEnfcRt(가감산 시행률)cmpttnRt(산정률)cmpttnRt2(산정률2)bdstTxtnMprc(건축물 과세 시가)mprcStdAmt(시가 표준액)cmpttnAmt(산정액)levyWholAmt(부과 총액)
핵심 비즈니스 규칙:
-
스마트 저장 패턴:
saveLevyInfo.ajax는 기존 데이터 존재 여부를 자동으로 확인- 존재하면 UPDATE, 없으면 INSERT 자동 실행
-
사전 검증:
- 부과예고 등록 전 반드시 위치정보, 행위정보가 먼저 등록되어야 함
selectLevyInfoFirstCheck.ajax로 사전 검증 수행
-
계산 스냅샷 패턴:
- 계산 시점의 모든 값(지수, 비율 등)을 DB에 저장
- 나중에 조회 시 저장된 값으로 복원하여 일관성 유지
2.1.3 행위 정보 관리 (CrdnActInfoController)
주요 데이터:
- 위반 행위의 상세 정보 (행위 유형, 위반 법령 등)
- 사진 정보 (CrdnPhotoService 별도 관리)
2.1.4 위치 정보 관리 (CrdnPstnInfoController)
주요 데이터:
- 단속 위치 정보
- 지번, 주소, 좌표 등
2.1.5 소유자 정보 관리 (CrdnOwnrInfoController)
주요 데이터:
- 건축물 소유자 정보
- 성명, 연락처, 주소 등
3. 데이터 흐름 상세
3.1 표준 CRUD 데이터 흐름
3.1.1 조회 (SELECT) 흐름
[사용자]
↓
[JSP - list.jsp]
↓ (페이지 로드 시 TUI Grid 초기화)
↓
[JavaScript - dataSource.api.readData]
↓ (AJAX POST 요청: /list.ajax)
↓
[Controller - listAjax(@ModelAttribute VO)]
↓ (1. totalCount 조회)
↓ (2. setTotalCount)
↓ (3. setPagingYn("Y"))
↓ (4. Service 호출)
↓
[Service - selectList(VO)]
↓ (비즈니스 로직, 검증)
↓
[Mapper - selectList(VO)]
↓ (MyBatis 호출)
↓
[MyBatis XML - <select id="selectList">]
↓ (SQL 실행)
↓
[MariaDB]
↓ (결과 반환)
↓
[Mapper → Service → Controller]
↓ (ApiResponseUtil.successWithGrid(list, vo))
↓
[ResponseEntity<?> 반환]
↓ (JSON 응답)
↓
[JavaScript - Grid 렌더링]
↓
[사용자에게 표시]
핵심 포인트:
- PagingVO의
page,perPageNum필드로 페이징 처리 totalCount설정 후pagingYn="Y"설정 (순서 중요!)- MyBatis에서
<if test="pagingYn == 'Y'">LIMIT #{startRow}, #{perPageNum}</if>처리
3.1.2 등록 (INSERT) 흐름
[사용자]
↓ (등록 버튼 클릭)
↓
[JSP - 팝업 열기]
↓ (window.open() or 모달)
↓
[사용자 입력]
↓ (폼 작성)
↓
[JavaScript - 저장 버튼 클릭]
↓ (유효성 검증)
↓ (AJAX POST: /insert.ajax)
↓
[Controller - insert(@ModelAttribute VO)]
↓ (세션 정보 설정: SessionUtil.getUserId())
↓ (sggCd = SessionUtil.getSessionVO().getUser().getOrgCd())
↓ (rgtr = SessionUtil.getUserId())
↓
[Service - insert(VO)]
↓ (비즈니스 검증)
↓ (트랜잭션 시작 - @Transactional)
↓
[Mapper - insert(VO)]
↓
[MyBatis XML - <insert id="insert">]
↓ (시퀀스 생성: LPAD(NEXTVAL(seq_*), 10, '0'))
↓ (INSERT 실행)
↓
[MariaDB]
↓ (결과 반환: affected rows)
↓
[Service - 트랜잭션 커밋]
↓
[Controller]
↓ (result > 0 확인)
↓ (ApiResponseUtil.success(MessageConstants.Common.SAVE_SUCCESS))
↓
[JavaScript - 성공 처리]
↓ (팝업 닫기, 목록 새로고침)
↓
[사용자에게 성공 메시지]
핵심 포인트:
- 등록자(rgtr) 필드에
SessionUtil.getUserId()자동 설정 - 시군구코드(sggCd)는 세션의 조직코드로 자동 설정
- 시퀀스는 10자리 LPAD 형식:
LPAD(NEXTVAL(seq_name), 10, '0')
3.1.3 수정 (UPDATE) 흐름
[사용자]
↓ (행 선택 + 수정 버튼)
↓
[JavaScript - 팝업 열기]
↓ (mode=U, PK 파라미터 전달)
↓
[Controller - popup(mode=U, PK)]
↓ (기존 데이터 조회)
↓
[Service - selectOne(PK)]
↓
[Mapper - selectOne]
↓
[DB 조회]
↓
[팝업에 데이터 표시]
↓
[사용자 수정]
↓ (저장 버튼)
↓
[JavaScript - AJAX POST: /update.ajax]
↓
[Controller - update(@ModelAttribute VO)]
↓ (mdfr = SessionUtil.getUserId() 설정)
↓
[Service - update(VO)]
↓ (낙관적 잠금 검증 - 선택적)
↓
[Mapper - update(VO)]
↓
[MyBatis XML - <update>]
↓ (UPDATE SET ... WHERE PK = ? AND DEL_YN = 'N')
↓
[DB 업데이트]
↓
[결과 반환]
↓
[ApiResponseUtil.success(MessageConstants.Common.UPDATE_SUCCESS)]
↓
[팝업 닫기, 목록 새로고침]
핵심 포인트:
- 수정자(mdfr) 필드에
SessionUtil.getUserId()설정 - WHERE 절에
DEL_YN = 'N'조건 필수 - 수정일시(mdfcnDt)는 DB에서 자동 설정 (DEFAULT CURRENT_TIMESTAMP)
3.1.4 삭제 (DELETE) 흐름 - 논리삭제
[사용자]
↓ (행 선택 + 삭제 버튼)
↓
[JavaScript - 확인 창]
↓ (confirm("정말 삭제하시겠습니까?"))
↓
[AJAX POST: /delete.ajax]
↓
[Controller - delete(PK)]
↓
[Service - delete(PK)]
↓
[Mapper - delete(PK)]
↓
[MyBatis XML - <update id="delete">]
↓ (UPDATE SET DEL_YN='Y', DLTR=?, DEL_DT=NOW())
↓ (WHERE PK = ? AND DEL_YN = 'N')
↓
[DB 논리삭제]
↓
[결과 반환]
↓
[ApiResponseUtil.success(MessageConstants.Common.DELETE_SUCCESS)]
↓
[목록 새로고침]
핵심 포인트:
- 물리삭제(DELETE)가 아닌 논리삭제(UPDATE) 사용
DEL_YN='Y',DLTR=사용자ID,DEL_DT=현재시간설정- 모든 조회 쿼리에는
WHERE DEL_YN='N'조건 필수
3.2 부과예고 특수 흐름 (스마트 CRUD 패턴)
3.2.1 부과정보 저장 흐름 (자동 INSERT/UPDATE 판단)
[사용자]
↓ (부과예고 팝업에서 행위정보 행 선택)
↓
[JavaScript - 행 클릭 이벤트]
↓ (actInfoId 획득)
↓
[AJAX GET: /selectLevyInfoByActInfoId.ajax]
↓ (기존 부과정보 존재 여부 확인)
↓
[Controller - selectLevyInfoByActInfoId]
↓
[Service - selectLevyInfoByActInfoId(actInfoId)]
↓
[Mapper - SELECT WHERE actInfoId = ?]
↓
[DB 조회]
↓
[결과 반환]
├── (있음) → 기존 데이터 복원
│ ↓ (JavaScript에 기존 계산값 모두 표시)
│ ↓ (모드 = 수정)
└── (없음) → 신규 입력 모드
↓ (빈 폼 표시)
↓ (모드 = 신규)
↓
[사용자가 값 입력/수정]
↓ (계산 버튼 클릭)
↓
[AJAX POST: /calculateAll.ajax]
↓ (모든 입력값 전송)
↓
[Controller - calculateAll]
↓ (BigDecimal 변환)
↓ (4단계 계산 수행 - 아래 섹션 참조)
↓
[계산 결과 반환]
↓
[JavaScript - 결과 필드에 표시]
↓
[사용자 - 저장 버튼 클릭]
↓
[AJAX POST: /saveLevyInfo.ajax]
↓
[Controller - saveLevyInfo]
↓ (행위정보 존재 확인)
↓ (sggCd 자동 설정)
↓
[Service - saveLevyInfo]
↓ (기존 데이터 재확인)
├── (있음) → updateLevyInfo 호출
│ ↓ (기존 levyInfoId 사용)
│ ↓ (UPDATE 실행)
└── (없음) → insertLevyInfo 호출
↓ (새 levyInfoId 생성)
↓ (INSERT 실행)
↓
[트랜잭션 커밋]
↓
[성공 메시지 반환]
↓
[JavaScript - 행위정보 그리드 새로고침]
↓ (저장된 데이터 표시 - 체크 아이콘 등)
핵심 포인트:
- 클라이언트는 INSERT/UPDATE 구분 불필요
- 서버에서 자동으로 존재 여부 확인 후 분기
- 계산 결과를 스냅샷으로 저장하여 데이터 일관성 보장
4. 계산식 및 비즈니스 규칙
4.1 부과금 계산 로직 (CrdnLevyPrvntcController.calculateAll)
위치: go.kr.project.crdn.crndRegistAndView.main.controller.CrdnLevyPrvntcController:473
4.1.1 단계별 계산 공식
입력 파라미터:
bldgNewPrcCrtrAmt(건물 기준 시가액) - BigDecimalstrctIdx(구조 지수) - BigDecimalusgIdx(용도 지수) - BigDecimalpstnIdx(위치 지수) - BigDecimalelpsYrRdvlrt(경과 년수별 잔가율) - BigDecimal (0 ~ 1 사이 값)bscsCstrnRt(기초 공사율) - BigDecimal (0 ~ 1 사이 값)vltnArea(위반 면적) - BigDecimal (㎡)adsbmtnEnfcRt(가감산 시행령률) - BigDecimal (%)cmpttnRtRate(산정률 비율값) - BigDecimal (0 ~ 1 사이 값)cmpttnRt2Rate(산정률2 비율값) - BigDecimal (0 ~ 1 사이 값)
계산 단계:
// ===== 1단계: 건축물과세시가 계산 =====
// 공식: 건물기준시가액 × 구조지수 × 용도지수 × 위치지수 × 경과년수별잔가율 × 기초공사율
BigDecimal bdstTxtnMprc = bldgNewPrcCrtrAmtDecimal
.multiply(strctIdxDecimal)
.multiply(usgIdxDecimal)
.multiply(pstnIdxDecimal)
.multiply(elpsYrRdvlrtDecimal)
.multiply(bscsCstrnRtDecimal)
.setScale(0, RoundingMode.HALF_UP); // 반올림
// ===== 2단계: 시가표준액 계산 (1,000원 미만 절사) =====
// 공식: 건축물과세시가 ÷ 1000 (소수점 버림) × 1000
BigDecimal mprcStdAmt = bdstTxtnMprc
.divide(new BigDecimal("1000"), 0, RoundingMode.DOWN) // 1000으로 나눈 후 버림
.multiply(new BigDecimal("1000")); // 다시 1000 곱하기
// ===== 3단계: 산정액 계산 =====
// 공식: 시가표준액 × 위반면적 × (가감산시행령률 ÷ 100) × 산정률 × 산정률2
BigDecimal cmpttnAmt = mprcStdAmt
.multiply(vltnAreaDecimal)
.multiply(adsbmtnEnfcRtDecimal.divide(new BigDecimal("100"), 10, RoundingMode.HALF_UP))
.multiply(cmpttnRtRateDecimal)
.multiply(cmpttnRt2RateDecimal)
.setScale(0, RoundingMode.DOWN); // 소수점 버림
// ===== 4단계: 부과총액 계산 (10원 단위 절사) =====
// 공식: 산정액 ÷ 10 (소수점 버림) × 10
BigDecimal levyWholAmt = cmpttnAmt
.divide(new BigDecimal("10"), 0, RoundingMode.DOWN) // 10으로 나눈 후 버림
.multiply(new BigDecimal("10")); // 다시 10 곱하기
반환 데이터:
Map<String, Object> data = new HashMap<>();
data.put("bdstTxtnMprc", bdstTxtnMprc); // 건축물과세시가 (숫자)
data.put("mprcStdAmt", mprcStdAmt); // 시가표준액 (숫자)
data.put("cmpttnAmt", cmpttnAmt); // 산정액 (숫자)
data.put("levyWholAmt", levyWholAmt); // 부과총액 (숫자)
data.put("bdstTxtnMprcDisplay", "1,234,567 원"); // 표시용 포맷 문자열
data.put("mprcStdAmtDisplay", "1,234,000 원");
data.put("cmpttnAmtDisplay", "987,654 원");
data.put("levyWholAmtDisplay", "987,650 원");
4.1.2 계산 예시
예시 입력값:
- 건물기준시가액: 1,000,000원
- 구조지수: 1.0
- 용도지수: 1.0
- 위치지수: 1.0
- 경과년수별잔가율: 0.8 (80%)
- 기초공사율: 0.7 (70%)
- 위반면적: 50㎡
- 가감산시행령률: 100%
- 산정률: 0.5 (50%)
- 산정률2: 1.0 (100%)
계산 과정:
1단계: 건축물과세시가
= 1,000,000 × 1.0 × 1.0 × 1.0 × 0.8 × 0.7
= 560,000원
2단계: 시가표준액 (1,000원 미만 절사)
= 560,000 ÷ 1,000 = 560 (버림)
= 560 × 1,000
= 560,000원
3단계: 산정액
= 560,000 × 50 × (100 ÷ 100) × 0.5 × 1.0
= 560,000 × 50 × 1.0 × 0.5 × 1.0
= 14,000,000원
4단계: 부과총액 (10원 단위 절사)
= 14,000,000 ÷ 10 = 1,400,000 (버림)
= 1,400,000 × 10
= 14,000,000원
4.2 가감산율 적용 규칙
가산율 (adtnRt):
- 위반 행위의 중대성에 따라 부과금 증가
- 예: 재범, 고의성, 피해 규모 등
감산율 (sbtrRt):
- 자진 시정, 경미한 위반 등의 사유로 부과금 감소
- 예: 자진 신고, 즉시 시정, 초범 등
가감산 시행령률 (adsbmtnEnfcRt):
- 가산율과 감산율을 합산한 최종 비율
- 공식:
adsbmtnEnfcRt = 100 + adtnRt - sbtrRt - 예: 가산 10%, 감산 5% → 105%
4.3 산정률 (cmpttnRt) 및 산정률2 (cmpttnRt2)
산정률 (cmpttnRt):
- 이행강제금의 1차 산정 비율
- 법령에 따라 정해진 비율표에서 선택
- 예: 건축법 위반 유형별로 0.1 ~ 1.0 범위
산정률2 (cmpttnRt2):
- 이행강제금의 2차 산정 비율
- 회차별 증가율 적용
- 예:
- 1회차: 1.0 (100%)
- 2회차: 1.5 (150%)
- 3회차: 2.0 (200%)
4.4 기초공사율 (bscsCstrnRt) 계산
기초공사 구분 코드 (bscsCstrnSeCd):
- 기초공사 있음 (Y): 일반 건축물
- 기초공사 없음 (N): 간이 건축물
- 복층증축 (D): 기존 건축물 위에 증축
계산 비율:
- 기초공사Y: 1.0 (100%)
- 기초공사N: 0.7 (70%) - 기본 비율의 70%
- 복층증축: 별도 산정 비율 적용
4.5 경과년수별 잔가율 (elpsYrRdvlrt)
계산 방법:
- 건축물의 경과 년수에 따라 감가상각
- 행위시작일자 기준으로 경과 년수 계산
- 잔가율표에서 해당 년도의 비율 조회
예시:
- 신축 (0년): 1.0 (100%)
- 5년 경과: 0.8 (80%)
- 10년 경과: 0.6 (60%)
- 20년 경과: 0.4 (40%)
5. 공통 패턴 및 규칙
5.1 VO (Value Object) 패턴
기본 구조:
@EqualsAndHashCode(callSuper=true) // PagingVO 상속 시
@Data // Getter, Setter, ToString
@Builder // 빌더 패턴
@AllArgsConstructor // 모든 필드 생성자
@NoArgsConstructor // 기본 생성자
@ToString // toString() 메서드
public class ExampleVO extends PagingVO {
// ===== 기본 필드 =====
private String exampleId; // 기본키
private String exampleNm; // 명칭
// ===== 감사 필드 (Audit Fields) =====
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime regDt; // 등록 일시
private String rgtr; // 등록자
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime mdfcnDt; // 수정 일시
private String mdfr; // 수정자
private String delYn; // 삭제 여부
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime delDt; // 삭제 일시
private String dltr; // 삭제자
// ===== 조회용 추가 필드 (조인 결과) =====
private String exampleCdNm; // 코드명 (표시용)
private String displayValue; // 표시용 값
}
5.2 Controller 패턴
표준 구조:
@Controller
@RequestMapping("/domain/subdomain")
@RequiredArgsConstructor // final 필드 생성자 자동 생성
@Slf4j // 로깅
@Tag(name = "한글 태그명", description = "설명")
public class ExampleController {
private final ExampleService service;
// ===== 화면 메서드 =====
@GetMapping("/list.do")
@Operation(summary = "목록 화면", description = "상세 설명")
public String list(Model model) {
// 공통코드 조회하여 model에 추가
return "경로" + TilesConstants.BASE;
}
// ===== AJAX 메서드 =====
@PostMapping("/list.ajax")
@Operation(summary = "목록 조회", description = "상세 설명")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "성공"),
@ApiResponse(responseCode = "400", description = "실패")
})
public ResponseEntity<?> listAjax(@ModelAttribute ExampleVO vo) {
int totalCount = service.selectListTotalCount(vo);
vo.setTotalCount(totalCount);
vo.setPagingYn("Y");
List<ExampleVO> list = service.selectList(vo);
return ApiResponseUtil.successWithGrid(list, vo);
}
@PostMapping("/insert.ajax")
public ResponseEntity<?> insert(@ModelAttribute ExampleVO vo) {
vo.setRgtr(SessionUtil.getUserId());
int result = service.insert(vo);
if (result > 0) {
return ApiResponseUtil.success(MessageConstants.Common.SAVE_SUCCESS);
} else {
return ApiResponseUtil.error(MessageConstants.Common.SAVE_ERROR);
}
}
}
5.3 Service 패턴
인터페이스:
public interface ExampleService {
List<ExampleVO> selectList(ExampleVO vo);
int selectListTotalCount(ExampleVO vo);
ExampleVO selectOne(ExampleVO vo);
int insert(ExampleVO vo);
int update(ExampleVO vo);
int delete(ExampleVO vo);
}
구현체:
@Service
@RequiredArgsConstructor
@Slf4j
public class ExampleServiceImpl implements ExampleService {
private final ExampleMapper mapper;
@Override
public List<ExampleVO> selectList(ExampleVO vo) {
return mapper.selectList(vo);
}
@Override
@Transactional // 쓰기 작업에만 적용
public int insert(ExampleVO vo) {
// 비즈니스 검증 로직
return mapper.insert(vo);
}
}
5.4 Mapper 인터페이스 패턴
@Mapper
public interface ExampleMapper {
List<ExampleVO> selectList(ExampleVO vo);
int selectListTotalCount(ExampleVO vo);
ExampleVO selectOne(ExampleVO vo);
int insert(ExampleVO vo);
int update(ExampleVO vo);
int delete(ExampleVO vo);
}
5.5 MyBatis XML 패턴
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="go.kr.project.domain.mapper.ExampleMapper">
<!-- 공통 컬럼 -->
<sql id="exampleColumns">
EXAMPLE_ID,
EXAMPLE_NM,
REG_DT,
RGTR,
MDFCN_DT,
MDFR,
DEL_YN,
DEL_DT,
DLTR
</sql>
<!-- 목록 조회 -->
<select id="selectList" parameterType="ExampleVO" resultType="ExampleVO">
SELECT
<include refid="exampleColumns"/>
FROM TB_EXAMPLE
WHERE DEL_YN = 'N'
<if test="searchKeyword != null and searchKeyword != ''">
AND EXAMPLE_NM LIKE CONCAT('%', #{searchKeyword}, '%')
</if>
ORDER BY REG_DT DESC
<if test="pagingYn == 'Y'">
LIMIT #{startRow}, #{perPageNum}
</if>
</select>
<!-- 총 개수 조회 -->
<select id="selectListTotalCount" parameterType="ExampleVO" resultType="int">
SELECT COUNT(*)
FROM TB_EXAMPLE
WHERE DEL_YN = 'N'
<if test="searchKeyword != null and searchKeyword != ''">
AND EXAMPLE_NM LIKE CONCAT('%', #{searchKeyword}, '%')
</if>
</select>
<!-- 단건 조회 -->
<select id="selectOne" parameterType="ExampleVO" resultType="ExampleVO">
SELECT
<include refid="exampleColumns"/>
FROM TB_EXAMPLE
WHERE EXAMPLE_ID = #{exampleId}
AND DEL_YN = 'N'
</select>
<!-- 등록 -->
<insert id="insert" parameterType="ExampleVO">
INSERT INTO TB_EXAMPLE (
EXAMPLE_ID,
EXAMPLE_NM,
RGTR
) VALUES (
LPAD(NEXTVAL(seq_example_id), 10, '0'),
#{exampleNm},
#{rgtr}
)
</insert>
<!-- 수정 -->
<update id="update" parameterType="ExampleVO">
UPDATE TB_EXAMPLE
SET EXAMPLE_NM = #{exampleNm},
MDFR = #{mdfr}
WHERE EXAMPLE_ID = #{exampleId}
AND DEL_YN = 'N'
</update>
<!-- 삭제 (논리삭제) -->
<update id="delete" parameterType="ExampleVO">
UPDATE TB_EXAMPLE
SET DEL_YN = 'Y',
DLTR = #{dltr},
DEL_DT = NOW()
WHERE EXAMPLE_ID = #{exampleId}
AND DEL_YN = 'N'
</update>
</mapper>
6. 주요 상수 및 설정
6.1 모드 상수
// 화면 모드
public static final String MODE_CREATE = "C"; // 등록
public static final String MODE_UPDATE = "U"; // 수정
public static final String MODE_VIEW = "V"; // 보기
public static final String MODE_DELETE = "D"; // 삭제
6.2 이행업무 구분 코드 (ImpltTaskSeConstants)
public static final String LEVY_PRVNTC = "1"; // 부과예고
public static final String LEVY = "2"; // 부과
public static final String RELEVY = "3"; // 재부과
public static final String IMPLT_TASK = "4"; // 이행강제금
6.3 삭제 여부
public static final String DEL_YN_N = "N"; // 미삭제
public static final String DEL_YN_Y = "Y"; // 삭제됨
6.4 페이징 설정
// PagingVO 기본값
private int page = 1; // 현재 페이지
private int perPageNum = 10; // 페이지당 행 수
private String pagingYn = "N"; // 페이징 사용 여부
7. 보안 및 권한
7.1 세션 정보 사용
// 사용자 ID 가져오기
String userId = SessionUtil.getUserId();
// 사용자 정보 가져오기
SessionVO sessionVO = SessionUtil.getSessionVO();
String orgCd = sessionVO.getUser().getOrgCd(); // 조직코드
String userNm = sessionVO.getUser().getUserNm(); // 사용자명
7.2 CSRF 보호
모든 JSP 폼에 CSRF 토큰 포함:
<form>
<sec:csrfInput/>
<!-- 폼 필드들 -->
</form>
8. 에러 처리
8.1 MessageException 사용
if (data == null) {
throw new MessageException("데이터를 찾을 수 없습니다.");
}
8.2 ApiResponseUtil 패턴
// 성공 응답
ApiResponseUtil.success(data, "성공 메시지");
ApiResponseUtil.successWithGrid(list, vo);
// 실패 응답
ApiResponseUtil.error("에러 메시지");
9. 엑셀 다운로드
9.1 엑셀 다운로드 패턴
@PostMapping("/excel.do")
public void downloadExcel(
@ModelAttribute ExampleVO paramVO,
HttpServletRequest request,
HttpServletResponse response) {
try {
// 페이징 없이 전체 조회
paramVO.setPagingYn("N");
List<ExampleExcelVO> excelList = service.selectListForExcel(paramVO);
// 파일명 생성
String filename = "예시목록_" +
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) +
".xlsx";
// 엑셀 파일 생성 및 다운로드
new SxssfExcelFile(
ExcelSheetData.of(excelList, ExampleExcelVO.class, "예시 목록 " + excelList.size() + "건"),
request,
response,
filename
);
} catch (Exception e) {
log.error("엑셀 다운로드 중 오류 발생", e);
}
}
10. 참고 파일 경로
10.1 핵심 비즈니스 로직 파일
단속 등록/조회:
- Controller:
src/main/java/go/kr/project/crdn/crndRegistAndView/main/controller/CrdnRegistAndViewController.java - Service:
src/main/java/go/kr/project/crdn/crndRegistAndView/main/service/impl/CrdnRegistAndViewServiceImpl.java - Mapper XML:
src/main/resources/mybatis/mapper/crdn/crndRegistAndView/main/CrdnRegistAndViewMapper_maria.xml - JSP:
src/main/webapp/WEB-INF/views/crdn/crndRegistAndView/main/list.jsp
부과예고 관리 (계산 로직 포함):
- Controller:
src/main/java/go/kr/project/crdn/crndRegistAndView/main/controller/CrdnLevyPrvntcController.java - Service:
src/main/java/go/kr/project/crdn/crndRegistAndView/main/service/impl/CrdnLevyPrvntcServiceImpl.java - Mapper XML:
src/main/resources/mybatis/mapper/crdn/crndRegistAndView/main/CrdnLevyPrvntcMapper_maria.xml - VO:
src/main/java/go/kr/project/crdn/crndRegistAndView/main/model/CrdnLevyInfoVO.java - JSP:
src/main/webapp/WEB-INF/views/crdn/crndRegistAndView/main/crdnLevyPrvntc/levyPrvntcPopup.jsp
10.2 공통 컴포넌트
페이징:
src/main/java/go/kr/project/common/model/PagingVO.java
공통코드:
src/main/java/go/kr/project/common/service/CommonCodeService.java
유틸리티:
egovframework.util.ApiResponseUtil(API 응답 생성)egovframework.util.SessionUtil(세션 정보 조회)egovframework.util.excel.SxssfExcelFile(엑셀 다운로드)
이 문서는 프로젝트의 전체 구조와 주요 업무 로직을 설명합니다. 새로운 기능 개발 시 이 문서를 참고하여 기존 패턴을 따라주세요.