최초커밋
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# 아키텍처 가이드
|
||||
|
||||
## 전체 아키텍처
|
||||
|
||||
이 애플리케이션은 전형적인 Spring MVC 3-tier 아키텍처를 따릅니다:
|
||||
|
||||
- **Presentation Layer**: JSP + jQuery (Frontend)
|
||||
- **Business Layer**: Spring Controllers + Services (Backend Logic)
|
||||
- **Data Access Layer**: MyBatis + PostgreSQL (Database)
|
||||
|
||||
## 패키지 구조
|
||||
|
||||
```
|
||||
src/com/pms/
|
||||
├── controller/ # Spring MVC Controllers (@Controller)
|
||||
├── service/ # Business Logic (@Service)
|
||||
├── mapper/ # MyBatis XML Mappers
|
||||
├── common/ # 공통 유틸리티 및 설정
|
||||
├── salesmgmt/ # 영업관리 모듈
|
||||
└── ions/ # 특수 모듈
|
||||
```
|
||||
|
||||
## 주요 컴포넌트
|
||||
|
||||
### Controllers
|
||||
|
||||
모든 컨트롤러는 [BaseService](mdc:src/com/pms/common/service/BaseService.java)를 상속받습니다.
|
||||
|
||||
- URL 패턴: `*.do` (예: `/admin/menuMngList.do`)
|
||||
- 주요 컨트롤러: [AdminController](mdc:src/com/pms/controller/AdminController.java)
|
||||
|
||||
### Services
|
||||
|
||||
비즈니스 로직을 처리하는 서비스 계층입니다.
|
||||
|
||||
- 예시: [AdminService](mdc:src/com/pms/service/AdminService.java)
|
||||
- MyBatis SqlSession을 직접 사용하여 데이터베이스 접근
|
||||
|
||||
### MyBatis Mappers
|
||||
|
||||
SQL 쿼리를 XML로 정의합니다.
|
||||
|
||||
- 위치: `src/com/pms/mapper/`
|
||||
- 예시: [admin.xml](mdc:src/com/pms/mapper/admin.xml)
|
||||
|
||||
### JSP Views
|
||||
|
||||
JSP 뷰 파일들은 `WebContent/WEB-INF/view/` 디렉토리에 위치합니다.
|
||||
|
||||
- InternalResourceViewResolver 사용
|
||||
- prefix: `/WEB-INF/view`, suffix: `.jsp`
|
||||
|
||||
## 데이터베이스 설정
|
||||
|
||||
- JNDI DataSource 사용: `plm`
|
||||
- PostgreSQL 연결
|
||||
- 초기 데이터: [ilshin.pgsql](mdc:db/ilshin.pgsql)
|
||||
|
||||
## 설정 파일 위치
|
||||
|
||||
- Spring 설정: [dispatcher-servlet.xml](mdc:WebContent/WEB-INF/dispatcher-servlet.xml)
|
||||
- 로깅 설정: [log4j.xml](mdc:WebContent/WEB-INF/log4j.xml)
|
||||
- 웹 설정: [web.xml](mdc:WebContent/WEB-INF/web.xml)
|
||||
@@ -0,0 +1,207 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
# 데이터베이스 가이드
|
||||
|
||||
## 데이터베이스 설정
|
||||
|
||||
### PostgreSQL 연결
|
||||
- **JNDI 리소스명**: `plm`
|
||||
- **드라이버**: `org.postgresql.Driver`
|
||||
- **설정 파일**: [context.xml](mdc:tomcat-conf/context.xml)
|
||||
|
||||
### 초기 데이터
|
||||
- **스키마 파일**: [ilshin.pgsql](mdc:db/ilshin.pgsql)
|
||||
- **역할 설정**: [00-create-roles.sh](mdc:db/00-create-roles.sh)
|
||||
|
||||
## MyBatis 설정
|
||||
|
||||
### SqlSession 사용 패턴
|
||||
```java
|
||||
public List<Map<String, Object>> getData(Map<String, Object> paramMap) {
|
||||
SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession();
|
||||
try {
|
||||
return sqlSession.selectList("namespace.queryId", paramMap);
|
||||
} finally {
|
||||
sqlSession.close(); // 반드시 리소스 해제
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 트랜잭션 처리
|
||||
```java
|
||||
public void saveData(Map<String, Object> paramMap) {
|
||||
SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession(false); // autoCommit=false
|
||||
try {
|
||||
sqlSession.insert("namespace.insertQuery", paramMap);
|
||||
sqlSession.update("namespace.updateQuery", paramMap);
|
||||
sqlSession.commit(); // 명시적 커밋
|
||||
} catch (Exception e) {
|
||||
sqlSession.rollback(); // 오류 시 롤백
|
||||
throw e;
|
||||
} finally {
|
||||
sqlSession.close();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 매퍼 XML 작성 가이드
|
||||
|
||||
### 기본 구조
|
||||
```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="admin">
|
||||
<!-- 쿼리 정의 -->
|
||||
</mapper>
|
||||
```
|
||||
|
||||
### 파라미터 바인딩
|
||||
```xml
|
||||
<!-- 안전한 파라미터 바인딩 (권장) -->
|
||||
<select id="selectUser" parameterType="map" resultType="map">
|
||||
SELECT * FROM users
|
||||
WHERE user_id = #{userId}
|
||||
AND status = #{status}
|
||||
</select>
|
||||
|
||||
<!-- 동적 조건 처리 -->
|
||||
<select id="selectUserList" parameterType="map" resultType="map">
|
||||
SELECT * FROM users
|
||||
WHERE 1=1
|
||||
<if test="userName != null and userName != ''">
|
||||
AND user_name LIKE '%' || #{userName} || '%'
|
||||
</if>
|
||||
<if test="deptCode != null and deptCode != ''">
|
||||
AND dept_code = #{deptCode}
|
||||
</if>
|
||||
</select>
|
||||
```
|
||||
|
||||
### PostgreSQL 특화 문법
|
||||
```xml
|
||||
<!-- 시퀀스 사용 -->
|
||||
<insert id="insertData" parameterType="map">
|
||||
INSERT INTO table_name (id, name, reg_date)
|
||||
VALUES (nextval('seq_table'), #{name}, now())
|
||||
</insert>
|
||||
|
||||
<!-- 숫자 타입 캐스팅 -->
|
||||
<update id="updateData" parameterType="map">
|
||||
UPDATE table_name
|
||||
SET status = #{status}
|
||||
WHERE id = #{id}::numeric
|
||||
</update>
|
||||
|
||||
<!-- 재귀 쿼리 (메뉴 트리 구조) -->
|
||||
<select id="selectMenuTree" resultType="map">
|
||||
WITH RECURSIVE menu_tree AS (
|
||||
SELECT * FROM menu_info WHERE parent_id = 0
|
||||
UNION ALL
|
||||
SELECT m.* FROM menu_info m
|
||||
JOIN menu_tree mt ON m.parent_id = mt.id
|
||||
)
|
||||
SELECT * FROM menu_tree ORDER BY path
|
||||
</select>
|
||||
```
|
||||
|
||||
## 주요 테이블 구조
|
||||
|
||||
### 메뉴 관리
|
||||
- **MENU_INFO**: 메뉴 정보
|
||||
- **MENU_AUTH_GROUP**: 메뉴 권한 그룹
|
||||
- **AUTH_GROUP**: 권한 그룹 정보
|
||||
|
||||
### 사용자 관리
|
||||
- **USER_INFO**: 사용자 정보
|
||||
- **DEPT_INFO**: 부서 정보
|
||||
- **USER_AUTH**: 사용자 권한
|
||||
|
||||
### 코드 관리
|
||||
- **CODE_INFO**: 공통 코드
|
||||
- **CODE_CATEGORY**: 코드 카테고리
|
||||
|
||||
## 데이터베이스 개발 모범 사례
|
||||
|
||||
### 1. 파라미터 검증
|
||||
```xml
|
||||
<select id="selectData" parameterType="map" resultType="map">
|
||||
SELECT * FROM table_name
|
||||
WHERE 1=1
|
||||
<if test="id != null and id != ''">
|
||||
AND id = #{id}::numeric
|
||||
</if>
|
||||
</select>
|
||||
```
|
||||
|
||||
### 2. 페이징 처리
|
||||
```xml
|
||||
<select id="selectListWithPaging" parameterType="map" resultType="map">
|
||||
SELECT * FROM (
|
||||
SELECT *, ROW_NUMBER() OVER (ORDER BY reg_date DESC) as rnum
|
||||
FROM table_name
|
||||
WHERE 1=1
|
||||
<!-- 검색 조건 -->
|
||||
) t
|
||||
WHERE rnum BETWEEN #{startRow}::numeric AND #{endRow}::numeric
|
||||
</select>
|
||||
```
|
||||
|
||||
### 3. 대소문자 처리
|
||||
```xml
|
||||
<!-- PostgreSQL은 대소문자를 구분하므로 주의 -->
|
||||
<select id="selectData" resultType="map">
|
||||
SELECT
|
||||
user_id as "userId", -- 카멜케이스 변환
|
||||
user_name as "userName",
|
||||
UPPER(status) as "status"
|
||||
FROM user_info
|
||||
</select>
|
||||
```
|
||||
|
||||
### 4. NULL 처리
|
||||
```xml
|
||||
<select id="selectData" resultType="map">
|
||||
SELECT
|
||||
COALESCE(description, '') as description,
|
||||
CASE WHEN status = 'Y' THEN '활성' ELSE '비활성' END as statusName
|
||||
FROM table_name
|
||||
</select>
|
||||
```
|
||||
|
||||
## 성능 최적화
|
||||
|
||||
### 인덱스 활용
|
||||
```sql
|
||||
-- 자주 검색되는 컬럼에 인덱스 생성
|
||||
CREATE INDEX idx_user_dept ON user_info(dept_code);
|
||||
CREATE INDEX idx_menu_parent ON menu_info(parent_id);
|
||||
```
|
||||
|
||||
### 쿼리 최적화
|
||||
```xml
|
||||
<!-- EXISTS 사용으로 성능 개선 -->
|
||||
<select id="selectUserWithAuth" resultType="map">
|
||||
SELECT u.* FROM user_info u
|
||||
WHERE EXISTS (
|
||||
SELECT 1 FROM user_auth ua
|
||||
WHERE ua.user_id = u.user_id
|
||||
AND ua.auth_code = #{authCode}
|
||||
)
|
||||
</select>
|
||||
```
|
||||
|
||||
### 배치 처리
|
||||
```xml
|
||||
<!-- 대량 데이터 삽입 시 배치 사용 -->
|
||||
<insert id="insertBatch" parameterType="list">
|
||||
INSERT INTO table_name (col1, col2, col3)
|
||||
VALUES
|
||||
<foreach collection="list" item="item" separator=",">
|
||||
(#{item.col1}, #{item.col2}, #{item.col3})
|
||||
</foreach>
|
||||
</insert>
|
||||
```
|
||||
@@ -0,0 +1,118 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
# 개발 가이드
|
||||
|
||||
## 개발 환경 설정
|
||||
|
||||
### Docker 개발 환경
|
||||
```bash
|
||||
# 개발 환경 실행
|
||||
docker-compose -f docker-compose.dev.yml up --build -d
|
||||
|
||||
# 운영 환경 실행
|
||||
docker-compose -f docker-compose.prod.yml up --build -d
|
||||
```
|
||||
|
||||
### 로컬 개발 환경
|
||||
1. Java 7 JDK 설치
|
||||
2. Eclipse IDE 설정
|
||||
3. Tomcat 7.0 설정
|
||||
4. PostgreSQL 데이터베이스 설정
|
||||
|
||||
## 코딩 컨벤션
|
||||
|
||||
### Controller 개발
|
||||
```java
|
||||
@Controller
|
||||
public class ExampleController extends BaseService {
|
||||
|
||||
@Autowired
|
||||
ExampleService exampleService;
|
||||
|
||||
@RequestMapping("/example/list.do")
|
||||
public String getList(HttpServletRequest request,
|
||||
@RequestParam Map<String, Object> paramMap) {
|
||||
// 비즈니스 로직은 Service에서 처리
|
||||
List<Map<String, Object>> list = exampleService.getList(request, paramMap);
|
||||
request.setAttribute("list", list);
|
||||
return "/example/list";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Service 개발
|
||||
```java
|
||||
@Service
|
||||
public class ExampleService extends BaseService {
|
||||
|
||||
public List<Map<String, Object>> getList(HttpServletRequest request,
|
||||
Map<String, Object> paramMap) {
|
||||
SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession();
|
||||
try {
|
||||
return sqlSession.selectList("example.selectList", paramMap);
|
||||
} finally {
|
||||
sqlSession.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### MyBatis Mapper 개발
|
||||
```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="example">
|
||||
<select id="selectList" parameterType="map" resultType="map">
|
||||
SELECT * FROM example_table
|
||||
WHERE 1=1
|
||||
<if test="searchText != null and searchText != ''">
|
||||
AND name LIKE '%' || #{searchText} || '%'
|
||||
</if>
|
||||
</select>
|
||||
</mapper>
|
||||
```
|
||||
|
||||
## 주요 유틸리티
|
||||
|
||||
### 공통 유틸리티
|
||||
- [CommonUtils](mdc:src/com/pms/common/utils/CommonUtils.java) - 공통 유틸리티 메서드
|
||||
- [Constants](mdc:src/com/pms/common/utils/Constants.java) - 상수 정의
|
||||
- [Message](mdc:src/com/pms/common/Message.java) - 메시지 처리
|
||||
|
||||
### 파일 관련
|
||||
- [FileRenameClass](mdc:src/com/pms/common/FileRenameClass.java) - 파일명 변경
|
||||
- 파일 업로드/다운로드 처리
|
||||
|
||||
## 프론트엔드 개발
|
||||
|
||||
### JSP 개발
|
||||
- 위치: `WebContent/WEB-INF/view/`
|
||||
- 공통 초기화: [init_jqGrid.jsp](mdc:WebContent/init_jqGrid.jsp)
|
||||
- 스타일시트: [all.css](mdc:WebContent/css/all.css)
|
||||
|
||||
### JavaScript 라이브러리
|
||||
- jQuery 1.11.3/2.1.4
|
||||
- jqGrid 4.7.1 - 데이터 그리드
|
||||
- Tabulator - 테이블 컴포넌트
|
||||
- rMateChart - 차트 라이브러리
|
||||
|
||||
## 데이터베이스 개발
|
||||
|
||||
### 연결 설정
|
||||
- JNDI 리소스명: `plm`
|
||||
- 드라이버: PostgreSQL
|
||||
- 컨텍스트 설정: [context.xml](mdc:tomcat-conf/context.xml)
|
||||
|
||||
### 스키마 관리
|
||||
- 초기 스키마: [ilshin.pgsql](mdc:db/ilshin.pgsql)
|
||||
- 역할 설정: [00-create-roles.sh](mdc:db/00-create-roles.sh)
|
||||
|
||||
## 빌드 및 배포
|
||||
- Eclipse 기반 빌드 (Maven/Gradle 미사용)
|
||||
- 컴파일된 클래스: `WebContent/WEB-INF/classes/`
|
||||
- 라이브러리: `WebContent/WEB-INF/lib/`
|
||||
- WAR 파일로 Tomcat 배포
|
||||
@@ -0,0 +1,176 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
# Next.js 마이그레이션 가이드
|
||||
|
||||
## 마이그레이션 개요
|
||||
현재 JSP/jQuery 기반 프론트엔드를 Next.js로 전환하는 작업이 계획되어 있습니다.
|
||||
자세한 내용은 [TODO.md](mdc:TODO.md)를 참조하세요.
|
||||
|
||||
## 현재 프론트엔드 분석
|
||||
|
||||
### JSP 뷰 구조
|
||||
```
|
||||
WebContent/WEB-INF/view/
|
||||
├── admin/ # 관리자 화면
|
||||
├── approval/ # 승인 관리
|
||||
├── common/ # 공통 컴포넌트
|
||||
├── dashboard/ # 대시보드
|
||||
├── main/ # 메인 화면
|
||||
└── ... # 기타 모듈별 화면
|
||||
```
|
||||
|
||||
### 주요 JavaScript 라이브러리
|
||||
- **jQuery**: 1.11.3/2.1.4 - DOM 조작 및 AJAX
|
||||
- **jqGrid**: 4.7.1 - 데이터 그리드 (교체 필요)
|
||||
- **Tabulator**: 테이블 컴포넌트
|
||||
- **rMateChart**: 차트 라이브러리 (교체 필요)
|
||||
- **CKEditor**: 텍스트 에디터
|
||||
|
||||
### CSS 프레임워크
|
||||
- [all.css](mdc:WebContent/css/all.css) - 메인 스타일시트
|
||||
- jQuery UI 테마 적용
|
||||
- 반응형 디자인 미적용 (데스크톱 중심)
|
||||
|
||||
## API 설계 가이드
|
||||
|
||||
### RESTful API 변환
|
||||
현재 Spring MVC는 JSP 뷰를 반환하는 구조입니다:
|
||||
```java
|
||||
@RequestMapping("/admin/menuMngList.do")
|
||||
public String getMenuList(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
|
||||
// 데이터 조회
|
||||
List<Map<String, Object>> menuList = adminService.getMenuList(request, paramMap);
|
||||
request.setAttribute("menuList", menuList);
|
||||
return "/admin/menu/menuMngList"; // JSP 뷰 반환
|
||||
}
|
||||
```
|
||||
|
||||
Next.js 연동을 위해 JSON API로 변환 필요:
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class AdminApiController {
|
||||
|
||||
@GetMapping("/admin/menus")
|
||||
@ResponseBody
|
||||
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getMenuList(
|
||||
@RequestParam Map<String, Object> paramMap) {
|
||||
List<Map<String, Object>> menuList = adminService.getMenuList(null, paramMap);
|
||||
return ResponseEntity.ok(ApiResponse.success(menuList));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### API 응답 표준화
|
||||
```java
|
||||
public class ApiResponse<T> {
|
||||
private boolean success;
|
||||
private T data;
|
||||
private String message;
|
||||
private String errorCode;
|
||||
|
||||
public static <T> ApiResponse<T> success(T data) {
|
||||
return new ApiResponse<>(true, data, null, null);
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> error(String message, String errorCode) {
|
||||
return new ApiResponse<>(false, null, message, errorCode);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 컴포넌트 매핑 가이드
|
||||
|
||||
### 데이터 그리드 교체
|
||||
**현재**: jqGrid 4.7.1
|
||||
```javascript
|
||||
$("#grid").jqGrid({
|
||||
url: 'menuMngList.do',
|
||||
datatype: 'json',
|
||||
colModel: [
|
||||
{name: 'menuName', label: '메뉴명'},
|
||||
{name: 'url', label: 'URL'}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
**변환 후**: TanStack Table 또는 AG Grid
|
||||
```tsx
|
||||
import { useTable } from '@tanstack/react-table';
|
||||
|
||||
const MenuTable = () => {
|
||||
const columns = [
|
||||
{ accessorKey: 'menuName', header: '메뉴명' },
|
||||
{ accessorKey: 'url', header: 'URL' }
|
||||
];
|
||||
|
||||
const table = useTable({ data, columns });
|
||||
// 테이블 렌더링
|
||||
};
|
||||
```
|
||||
|
||||
### 차트 라이브러리 교체
|
||||
**현재**: rMateChart
|
||||
**변환 후**: Recharts 또는 Chart.js
|
||||
|
||||
### 폼 처리 교체
|
||||
**현재**: jQuery 기반 폼 처리
|
||||
**변환 후**: react-hook-form 사용
|
||||
|
||||
## 인증/인가 처리
|
||||
|
||||
### 현재 세션 기반 인증
|
||||
```java
|
||||
// 세션에서 사용자 정보 조회
|
||||
PersonBean person = (PersonBean)request.getSession().getAttribute(Constants.PERSON_BEAN);
|
||||
```
|
||||
|
||||
### Next.js 연동 방안
|
||||
1. **세션 유지**: 쿠키 기반 세션 ID 전달
|
||||
2. **JWT 토큰**: 새로운 토큰 기반 인증 도입
|
||||
3. **하이브리드**: 기존 세션 + API 토큰
|
||||
|
||||
## 개발 단계별 접근
|
||||
|
||||
### Phase 1: API 개발
|
||||
1. 기존 Controller 분석
|
||||
2. @RestController 신규 생성
|
||||
3. 기존 Service 재사용
|
||||
4. CORS 설정 추가
|
||||
|
||||
### Phase 2: Next.js 기본 구조
|
||||
1. Next.js 프로젝트 생성
|
||||
2. 기본 레이아웃 구현
|
||||
3. 라우팅 구조 설계
|
||||
4. 공통 컴포넌트 개발
|
||||
|
||||
### Phase 3: 화면별 마이그레이션
|
||||
1. 관리자 화면부터 시작
|
||||
2. 주요 업무 화면 순차 전환
|
||||
3. 대시보드 및 리포트 화면
|
||||
|
||||
### Phase 4: 테스트 및 최적화
|
||||
1. 기능 테스트
|
||||
2. 성능 최적화
|
||||
3. 사용자 테스트
|
||||
4. 점진적 배포
|
||||
|
||||
## 주의사항
|
||||
|
||||
### 데이터 호환성
|
||||
- 기존 데이터베이스 스키마 유지
|
||||
- API 응답 형식 표준화
|
||||
- 날짜/시간 형식 통일
|
||||
|
||||
### 사용자 경험
|
||||
- 기존 업무 프로세스 유지
|
||||
- 화면 전환 시 혼란 최소화
|
||||
- 점진적 마이그레이션 고려
|
||||
|
||||
### 성능 고려사항
|
||||
- API 응답 속도 최적화
|
||||
- 클라이언트 사이드 캐싱
|
||||
- 이미지 및 정적 자원 최적화
|
||||
@@ -0,0 +1,103 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
# 네비게이션 가이드
|
||||
|
||||
## 프로젝트 구조 이해
|
||||
|
||||
### 루트 디렉토리
|
||||
```
|
||||
plm-ilshin/
|
||||
├── src/ # Java 소스 코드
|
||||
├── WebContent/ # 웹 리소스 (JSP, CSS, JS, 이미지)
|
||||
├── db/ # 데이터베이스 스크립트
|
||||
├── tomcat-conf/ # Tomcat 설정
|
||||
├── docker-compose.*.yml # Docker 설정
|
||||
└── 문서/ # 프로젝트 문서
|
||||
```
|
||||
|
||||
### 주요 소스 디렉토리
|
||||
```
|
||||
src/com/pms/
|
||||
├── controller/ # 웹 컨트롤러 (@Controller)
|
||||
├── service/ # 비즈니스 로직 (@Service)
|
||||
├── mapper/ # MyBatis SQL 매퍼 (XML)
|
||||
├── common/ # 공통 컴포넌트
|
||||
├── salesmgmt/ # 영업관리 모듈
|
||||
└── ions/ # 특수 기능 모듈
|
||||
```
|
||||
|
||||
### 웹 리소스 구조
|
||||
```
|
||||
WebContent/
|
||||
├── WEB-INF/
|
||||
│ ├── view/ # JSP 뷰 파일
|
||||
│ ├── lib/ # JAR 라이브러리
|
||||
│ ├── classes/ # 컴파일된 클래스
|
||||
│ └── *.xml # 설정 파일
|
||||
├── css/ # 스타일시트
|
||||
├── js/ # JavaScript 파일
|
||||
├── images/ # 이미지 리소스
|
||||
└── template/ # 템플릿 파일
|
||||
```
|
||||
|
||||
## 주요 파일 찾기
|
||||
|
||||
### 컨트롤러 찾기
|
||||
특정 URL에 대한 컨트롤러를 찾을 때:
|
||||
1. URL 패턴 확인 (예: `/admin/menuMngList.do`)
|
||||
2. `src/com/pms/controller/` 에서 해당 `@RequestMapping` 검색
|
||||
3. 주요 컨트롤러들:
|
||||
- [AdminController.java](mdc:src/com/pms/controller/AdminController.java) - 관리자 기능
|
||||
- [ApprovalController.java](mdc:src/com/pms/controller/ApprovalController.java) - 승인 관리
|
||||
- [AsController.java](mdc:src/com/pms/controller/AsController.java) - AS 관리
|
||||
|
||||
### 서비스 찾기
|
||||
비즈니스 로직을 찾을 때:
|
||||
1. 컨트롤러에서 `@Autowired` 된 서비스 확인
|
||||
2. `src/com/pms/service/` 디렉토리에서 해당 서비스 파일 찾기
|
||||
3. 주요 서비스들:
|
||||
- [AdminService.java](mdc:src/com/pms/service/AdminService.java) - 관리자 서비스
|
||||
- [ApprovalService.java](mdc:src/com/pms/service/ApprovalService.java) - 승인 서비스
|
||||
|
||||
### SQL 쿼리 찾기
|
||||
데이터베이스 쿼리를 찾을 때:
|
||||
1. 서비스 코드에서 `sqlSession.selectList("namespace.queryId")` 확인
|
||||
2. `src/com/pms/mapper/` 에서 해당 namespace XML 파일 찾기
|
||||
3. XML 파일 내에서 queryId로 검색
|
||||
|
||||
### JSP 뷰 찾기
|
||||
화면을 찾을 때:
|
||||
1. 컨트롤러 메서드의 return 값 확인 (예: `"/admin/menu/menuMngList"`)
|
||||
2. `WebContent/WEB-INF/view/` + return 값 + `.jsp` 경로로 파일 찾기
|
||||
|
||||
## 모듈별 주요 기능
|
||||
|
||||
### 관리자 모듈 (`/admin/*`)
|
||||
- 메뉴 관리: [AdminController.java](mdc:src/com/pms/controller/AdminController.java)
|
||||
- 사용자 관리, 권한 관리
|
||||
- 코드 관리, 카테고리 관리
|
||||
- 시스템 로그 관리
|
||||
|
||||
### 영업 관리 (`/salesmgmt/*`)
|
||||
- 위치: `src/com/pms/salesmgmt/`
|
||||
- 영업 관련 컨트롤러, 서비스, 매퍼 분리
|
||||
|
||||
### 공통 기능 (`/common/*`)
|
||||
- 공통 유틸리티: [CommonUtils](mdc:src/com/pms/common/utils/CommonUtils.java)
|
||||
- 메시지 처리: [Message](mdc:src/com/pms/common/Message.java)
|
||||
- 파일 처리: [FileRenameClass](mdc:src/com/pms/common/FileRenameClass.java)
|
||||
|
||||
## 개발 시 주의사항
|
||||
|
||||
### 파일 수정 시
|
||||
1. Java 파일 수정 → Eclipse에서 자동 컴파일 → `WebContent/WEB-INF/classes/`에 반영
|
||||
2. JSP/CSS/JS 수정 → 바로 반영 (서버 재시작 불필요)
|
||||
3. XML 설정 파일 수정 → 서버 재시작 필요
|
||||
|
||||
### 데이터베이스 관련
|
||||
1. 스키마 변경 시 [ilshin.pgsql](mdc:db/ilshin.pgsql) 업데이트
|
||||
2. 새로운 쿼리 추가 시 해당 mapper XML 파일에 추가
|
||||
3. 트랜잭션 처리는 서비스 레벨에서 관리
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
# PLM 솔루션 (ILSHIN) - 프로젝트 개요
|
||||
|
||||
## 프로젝트 정보
|
||||
이 프로젝트는 제품 수명 주기 관리(PLM - Product Lifecycle Management) 솔루션입니다.
|
||||
Spring Framework 기반의 Java 웹 애플리케이션으로, 제품 개발부터 폐기까지의 전체 생명주기를 관리합니다.
|
||||
|
||||
## 기술 스택
|
||||
- **Backend**: Java 7, Spring Framework 3.2.4, MyBatis 3.2.3
|
||||
- **Frontend**: JSP, jQuery 1.11.3/2.1.4, jqGrid 4.7.1
|
||||
- **Database**: PostgreSQL
|
||||
- **WAS**: Apache Tomcat 7.0
|
||||
- **Build**: Eclipse IDE 기반 (Maven/Gradle 미사용)
|
||||
|
||||
## 주요 기능
|
||||
- 제품 정보 관리
|
||||
- BOM (Bill of Materials) 관리
|
||||
- 설계 변경 관리 (ECO/ECR)
|
||||
- 문서 관리 및 버전 제어
|
||||
- 프로젝트/일정 관리
|
||||
- 사용자 및 권한 관리
|
||||
- 워크플로우 관리
|
||||
|
||||
## 주요 설정 파일
|
||||
- [web.xml](mdc:WebContent/WEB-INF/web.xml) - 웹 애플리케이션 배포 설정
|
||||
- [dispatcher-servlet.xml](mdc:WebContent/WEB-INF/dispatcher-servlet.xml) - Spring MVC 설정
|
||||
- [docker-compose.dev.yml](mdc:docker-compose.dev.yml) - 개발환경 Docker 설정
|
||||
- [docker-compose.prod.yml](mdc:docker-compose.prod.yml) - 운영환경 Docker 설정
|
||||
@@ -0,0 +1,248 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
# 보안 가이드
|
||||
|
||||
## 인증 및 인가
|
||||
|
||||
### 세션 기반 인증
|
||||
현재 시스템은 세션 기반 인증을 사용합니다:
|
||||
|
||||
```java
|
||||
// 사용자 인증 정보 저장
|
||||
PersonBean person = new PersonBean();
|
||||
person.setUserId(userId);
|
||||
person.setUserName(userName);
|
||||
request.getSession().setAttribute(Constants.PERSON_BEAN, person);
|
||||
|
||||
// 인증 정보 조회
|
||||
PersonBean person = (PersonBean)request.getSession().getAttribute(Constants.PERSON_BEAN);
|
||||
if (person == null) {
|
||||
// 로그인 페이지로 리다이렉트
|
||||
}
|
||||
```
|
||||
|
||||
### 권한 관리 구조
|
||||
- **AUTH_GROUP**: 권한 그룹 정의
|
||||
- **MENU_AUTH_GROUP**: 메뉴별 권한 그룹 매핑
|
||||
- **USER_AUTH**: 사용자별 권한 할당
|
||||
|
||||
### 메뉴 접근 권한 체크
|
||||
```java
|
||||
public HashMap<String, Object> checkUserMenuAuth(HttpServletRequest request, Map<String, Object> paramMap) {
|
||||
PersonBean person = (PersonBean)request.getSession().getAttribute(Constants.PERSON_BEAN);
|
||||
String userId = person.getUserId();
|
||||
|
||||
paramMap.put("userId", userId);
|
||||
paramMap.put("menuUrl", request.getRequestURI());
|
||||
|
||||
// 권한 체크 쿼리 실행
|
||||
return sqlSession.selectOne("admin.checkUserMenuAuth", paramMap);
|
||||
}
|
||||
```
|
||||
|
||||
## 데이터 보안
|
||||
|
||||
### SQL 인젝션 방지
|
||||
**올바른 방법** - 파라미터 바인딩 사용:
|
||||
```xml
|
||||
<select id="selectUser" parameterType="map" resultType="map">
|
||||
SELECT * FROM user_info
|
||||
WHERE user_id = #{userId} <!-- 안전한 파라미터 바인딩 -->
|
||||
</select>
|
||||
```
|
||||
|
||||
**위험한 방법** - 직접 문자열 치환:
|
||||
```xml
|
||||
<select id="selectUser" parameterType="map" resultType="map">
|
||||
SELECT * FROM user_info
|
||||
WHERE user_id = '${userId}' <!-- SQL 인젝션 위험 -->
|
||||
</select>
|
||||
```
|
||||
|
||||
### 패스워드 보안
|
||||
```java
|
||||
// 패스워드 암호화 (EncryptUtil 사용)
|
||||
String encryptedPassword = EncryptUtil.encrypt(plainPassword);
|
||||
|
||||
// 패스워드 검증
|
||||
boolean isValid = EncryptUtil.matches(plainPassword, encryptedPassword);
|
||||
```
|
||||
|
||||
### 입력값 검증
|
||||
```java
|
||||
// CommonUtils를 사용한 입력값 정제
|
||||
String safeInput = CommonUtils.checkNull(request.getParameter("input"));
|
||||
if (StringUtils.isEmpty(safeInput)) {
|
||||
throw new IllegalArgumentException("필수 입력값이 누락되었습니다.");
|
||||
}
|
||||
```
|
||||
|
||||
## 세션 보안
|
||||
|
||||
### 세션 설정
|
||||
[web.xml](mdc:WebContent/WEB-INF/web.xml)에서 세션 타임아웃 설정:
|
||||
```xml
|
||||
<session-config>
|
||||
<session-timeout>1440</session-timeout> <!-- 24시간 -->
|
||||
</session-config>
|
||||
```
|
||||
|
||||
### 세션 무효화
|
||||
```java
|
||||
// 로그아웃 시 세션 무효화
|
||||
@RequestMapping("/logout.do")
|
||||
public String logout(HttpServletRequest request) {
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session != null) {
|
||||
session.invalidate();
|
||||
}
|
||||
return "redirect:/login.do";
|
||||
}
|
||||
```
|
||||
|
||||
## 파일 업로드 보안
|
||||
|
||||
### 파일 확장자 검증
|
||||
```java
|
||||
public boolean isAllowedFileType(String fileName) {
|
||||
String[] allowedExtensions = {".jpg", ".jpeg", ".png", ".gif", ".pdf", ".doc", ".docx", ".xls", ".xlsx"};
|
||||
String extension = fileName.toLowerCase().substring(fileName.lastIndexOf("."));
|
||||
return Arrays.asList(allowedExtensions).contains(extension);
|
||||
}
|
||||
```
|
||||
|
||||
### 파일 크기 제한
|
||||
```java
|
||||
// web.xml에서 파일 업로드 크기 제한
|
||||
<multipart-config>
|
||||
<max-file-size>10485760</max-file-size> <!-- 10MB -->
|
||||
<max-request-size>52428800</max-request-size> <!-- 50MB -->
|
||||
</multipart-config>
|
||||
```
|
||||
|
||||
### 안전한 파일명 생성
|
||||
```java
|
||||
// FileRenameClass 사용하여 안전한 파일명 생성
|
||||
String safeFileName = FileRenameClass.rename(originalFileName);
|
||||
```
|
||||
|
||||
## XSS 방지
|
||||
|
||||
### 출력값 이스케이프
|
||||
JSP에서 사용자 입력값 출력 시:
|
||||
```jsp
|
||||
<!-- 안전한 출력 -->
|
||||
<c:out value="${userInput}" escapeXml="true"/>
|
||||
|
||||
<!-- 위험한 출력 -->
|
||||
${userInput} <!-- XSS 공격 가능 -->
|
||||
```
|
||||
|
||||
### JavaScript에서 데이터 처리
|
||||
```javascript
|
||||
// 안전한 방법
|
||||
var safeData = $('<div>').text(userInput).html();
|
||||
|
||||
// 위험한 방법
|
||||
var dangerousData = userInput; // XSS 공격 가능
|
||||
```
|
||||
|
||||
## CSRF 방지
|
||||
|
||||
### 토큰 기반 CSRF 방지
|
||||
```jsp
|
||||
<!-- 폼에 CSRF 토큰 포함 -->
|
||||
<form method="post" action="save.do">
|
||||
<input type="hidden" name="csrfToken" value="${csrfToken}"/>
|
||||
<!-- 기타 입력 필드 -->
|
||||
</form>
|
||||
```
|
||||
|
||||
```java
|
||||
// 컨트롤러에서 CSRF 토큰 검증
|
||||
@RequestMapping("/save.do")
|
||||
public String save(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
|
||||
String sessionToken = (String)request.getSession().getAttribute("csrfToken");
|
||||
String requestToken = (String)paramMap.get("csrfToken");
|
||||
|
||||
if (!sessionToken.equals(requestToken)) {
|
||||
throw new SecurityException("CSRF 토큰이 일치하지 않습니다.");
|
||||
}
|
||||
|
||||
// 정상 처리
|
||||
}
|
||||
```
|
||||
|
||||
## 로깅 및 감사
|
||||
|
||||
### 보안 이벤트 로깅
|
||||
```java
|
||||
private static final Logger securityLogger = LoggerFactory.getLogger("SECURITY");
|
||||
|
||||
public void logSecurityEvent(String event, String userId, String details) {
|
||||
securityLogger.info("Security Event: {} | User: {} | Details: {}", event, userId, details);
|
||||
}
|
||||
|
||||
// 사용 예시
|
||||
logSecurityEvent("LOGIN_SUCCESS", userId, request.getRemoteAddr());
|
||||
logSecurityEvent("ACCESS_DENIED", userId, request.getRequestURI());
|
||||
```
|
||||
|
||||
### 민감 정보 마스킹
|
||||
```java
|
||||
public String maskSensitiveData(String data) {
|
||||
if (data == null || data.length() < 4) {
|
||||
return "****";
|
||||
}
|
||||
return data.substring(0, 2) + "****" + data.substring(data.length() - 2);
|
||||
}
|
||||
```
|
||||
|
||||
## 환경별 보안 설정
|
||||
|
||||
### 개발 환경
|
||||
- 상세한 오류 메시지 표시
|
||||
- 디버그 모드 활성화
|
||||
- 보안 제약 완화
|
||||
|
||||
### 운영 환경
|
||||
- 일반적인 오류 메시지만 표시
|
||||
- 디버그 모드 비활성화
|
||||
- 엄격한 보안 정책 적용
|
||||
|
||||
```java
|
||||
// 환경별 설정 예시
|
||||
if (Constants.IS_PRODUCTION) {
|
||||
// 운영 환경 설정
|
||||
response.sendError(HttpServletResponse.SC_FORBIDDEN, "접근이 거부되었습니다.");
|
||||
} else {
|
||||
// 개발 환경 설정
|
||||
response.sendError(HttpServletResponse.SC_FORBIDDEN, "권한 부족: " + detailedMessage);
|
||||
}
|
||||
```
|
||||
|
||||
## 보안 체크리스트
|
||||
|
||||
### 코드 레벨
|
||||
- [ ] SQL 인젝션 방지 (#{} 파라미터 바인딩 사용)
|
||||
- [ ] XSS 방지 (출력값 이스케이프)
|
||||
- [ ] CSRF 방지 (토큰 검증)
|
||||
- [ ] 입력값 검증 및 정제
|
||||
- [ ] 패스워드 암호화 저장
|
||||
|
||||
### 설정 레벨
|
||||
- [ ] 세션 타임아웃 설정
|
||||
- [ ] 파일 업로드 제한
|
||||
- [ ] 오류 페이지 설정
|
||||
- [ ] HTTPS 사용 (운영 환경)
|
||||
- [ ] 보안 헤더 설정
|
||||
|
||||
### 운영 레벨
|
||||
- [ ] 정기적인 보안 점검
|
||||
- [ ] 로그 모니터링
|
||||
- [ ] 권한 정기 검토
|
||||
- [ ] 패스워드 정책 적용
|
||||
- [ ] 백업 데이터 암호화
|
||||
@@ -0,0 +1,166 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
# 트러블슈팅 가이드
|
||||
|
||||
## 일반적인 문제 해결
|
||||
|
||||
### 애플리케이션 시작 오류
|
||||
|
||||
#### 데이터베이스 연결 실패
|
||||
```
|
||||
원인: JNDI DataSource 설정 문제
|
||||
해결:
|
||||
1. tomcat-conf/context.xml 확인
|
||||
2. PostgreSQL 서비스 상태 확인
|
||||
3. 데이터베이스 접속 정보 확인
|
||||
```
|
||||
|
||||
#### 클래스 로딩 오류
|
||||
```
|
||||
원인: 컴파일되지 않은 Java 파일
|
||||
해결:
|
||||
1. Eclipse에서 프로젝트 Clean & Build
|
||||
2. WebContent/WEB-INF/classes/ 디렉토리 확인
|
||||
3. 누락된 라이브러리 확인 (WebContent/WEB-INF/lib/)
|
||||
```
|
||||
|
||||
### 런타임 오류
|
||||
|
||||
#### 404 오류 (페이지를 찾을 수 없음)
|
||||
```
|
||||
원인: URL 매핑 문제
|
||||
해결:
|
||||
1. @RequestMapping 어노테이션 확인
|
||||
2. JSP 파일 경로 확인 (/WEB-INF/view/)
|
||||
3. web.xml의 servlet-mapping 확인 (*.do 패턴)
|
||||
```
|
||||
|
||||
#### 500 오류 (서버 내부 오류)
|
||||
```
|
||||
원인: Java 코드 실행 오류
|
||||
해결:
|
||||
1. 로그 파일 확인 (log4j 설정)
|
||||
2. MyBatis SQL 오류 확인
|
||||
3. NullPointerException 체크
|
||||
4. 데이터베이스 트랜잭션 오류 확인
|
||||
```
|
||||
|
||||
### 데이터베이스 관련 문제
|
||||
|
||||
#### SQL 실행 오류
|
||||
```
|
||||
원인: MyBatis 매퍼 설정 문제
|
||||
해결:
|
||||
1. mapper XML 파일의 SQL 문법 확인
|
||||
2. parameterType과 resultType 확인
|
||||
3. 테이블/컬럼명 대소문자 확인 (PostgreSQL)
|
||||
```
|
||||
|
||||
#### 트랜잭션 문제
|
||||
```
|
||||
원인: SqlSession 관리 문제
|
||||
해결:
|
||||
1. SqlSession.close() 호출 확인
|
||||
2. try-finally 블록에서 리소스 정리
|
||||
3. 트랜잭션 커밋/롤백 처리
|
||||
```
|
||||
|
||||
### 프론트엔드 문제
|
||||
|
||||
#### JavaScript 오류
|
||||
```
|
||||
원인: jQuery/jqGrid 라이브러리 문제
|
||||
해결:
|
||||
1. 브라우저 개발자 도구 콘솔 확인
|
||||
2. JavaScript 파일 로딩 순서 확인
|
||||
3. jQuery 버전 호환성 확인
|
||||
```
|
||||
|
||||
#### CSS 스타일 문제
|
||||
```
|
||||
원인: CSS 파일 로딩 또는 경로 문제
|
||||
해결:
|
||||
1. CSS 파일 경로 확인
|
||||
2. 브라우저 캐시 클리어
|
||||
3. all.css 파일 확인
|
||||
```
|
||||
|
||||
## 개발 환경 문제
|
||||
|
||||
### Docker 환경
|
||||
```bash
|
||||
# 컨테이너 로그 확인
|
||||
docker-compose logs app
|
||||
|
||||
# 컨테이너 내부 접속
|
||||
docker-compose exec app bash
|
||||
|
||||
# 데이터베이스 접속 확인
|
||||
docker-compose exec db psql -U postgres -d ilshin
|
||||
```
|
||||
|
||||
### Eclipse 환경
|
||||
```
|
||||
문제: 프로젝트 인식 오류
|
||||
해결:
|
||||
1. .project 파일 확인
|
||||
2. .classpath 파일 확인
|
||||
3. Project Properties > Java Build Path 확인
|
||||
4. Server Runtime 설정 확인 (Tomcat 7.0)
|
||||
```
|
||||
|
||||
## 로그 분석
|
||||
|
||||
### 주요 로그 위치
|
||||
- 애플리케이션 로그: log4j 설정에 따름
|
||||
- Tomcat 로그: `$CATALINA_HOME/logs/`
|
||||
- Docker 로그: `docker-compose logs`
|
||||
|
||||
### 로그 설정 파일
|
||||
- [log4j.xml](mdc:WebContent/WEB-INF/log4j.xml) - 로깅 설정
|
||||
- 로그 레벨 조정으로 상세 정보 확인 가능
|
||||
|
||||
## 성능 문제
|
||||
|
||||
### 데이터베이스 성능
|
||||
```sql
|
||||
-- 슬로우 쿼리 확인
|
||||
SELECT query, mean_time, calls
|
||||
FROM pg_stat_statements
|
||||
ORDER BY mean_time DESC;
|
||||
|
||||
-- 인덱스 사용률 확인
|
||||
SELECT schemaname, tablename, indexname, idx_scan, idx_tup_read, idx_tup_fetch
|
||||
FROM pg_stat_user_indexes;
|
||||
```
|
||||
|
||||
### 메모리 문제
|
||||
```
|
||||
원인: 메모리 누수 또는 부족
|
||||
해결:
|
||||
1. JVM 힙 메모리 설정 확인
|
||||
2. SqlSession 리소스 정리 확인
|
||||
3. 대량 데이터 처리 시 페이징 적용
|
||||
```
|
||||
|
||||
## 보안 관련
|
||||
|
||||
### 권한 문제
|
||||
```
|
||||
원인: 사용자 권한 설정 오류
|
||||
해결:
|
||||
1. MENU_AUTH_GROUP 테이블 확인
|
||||
2. 사용자 세션 정보 확인
|
||||
3. Spring Security 설정 확인 (있는 경우)
|
||||
```
|
||||
|
||||
### SQL 인젝션 방지
|
||||
```
|
||||
주의사항:
|
||||
1. MyBatis #{} 파라미터 바인딩 사용
|
||||
2. ${} 직접 문자열 치환 지양
|
||||
3. 사용자 입력값 검증
|
||||
```
|
||||
Reference in New Issue
Block a user