init: SR System — Helpdesk & ITAM Portal

This commit is contained in:
pray4skylark
2026-06-02 13:50:39 +09:00
commit 832c9f096f
262 changed files with 30470 additions and 0 deletions
+321
View File
@@ -0,0 +1,321 @@
# 개발 가이드 및 컨벤션 (Development Guide)
## 1. 프로젝트 디렉토리 구조
### Backend (Spring Boot)
```
src/main/java/com/hq/support/portal/
├── global/
│ ├── common/
│ │ └── ApiResponse.java // 공통 응답 래퍼
│ ├── config/
│ │ ├── AsyncConfig.java // @Async 스레드 풀
│ │ ├── JpaAuditConfig.java // @EnableJpaAuditing
│ │ ├── BatchConfig.java // Spring Batch 메타 설정
│ │ └── SecurityConfig.java // Security Filter Chain
│ ├── error/
│ │ ├── ErrorCode.java // 에러코드 Enum 매트릭스
│ │ ├── GlobalExceptionHandler.java // @RestControllerAdvice
│ │ └── exception/
│ │ └── BusinessException.java // 최상위 예외 클래스
│ └── security/
│ ├── JwtTokenProvider.java // JWT 생성/검증
│ └── JwtAuthenticationFilter.java // 인증 필터
│
├── domain/
│ ├── base/
│ │ └── BaseEntity.java // createdAt, updatedAt, isDeleted
│ ├── employee/
│ │ ├── entity/TbEmployee.java
│ │ ├── repository/EmployeeRepository.java
│ │ └── exception/InvalidEmployeeException.java
│ ├── project/
│ │ ├── entity/TbProject.java
│ │ └── repository/ProjectRepository.java
│ ├── dispatch/
│ │ ├── entity/TbDispatchHistory.java
│ │ └── repository/DispatchRepository.java
│ ├── ticket/
│ │ ├── entity/TbIssueTicket.java
│ │ ├── entity/TbTicketComment.java
│ │ ├── repository/TicketRepository.java
│ │ ├── controller/IssueTicketController.java
│ │ ├── dto/TicketDto.java
│ │ ├── service/IssueTicketService.java
│ │ └── event/TicketCreatedEvent.java
│ ├── report/
│ │ ├── entity/TbWorkReport.java
│ │ └── repository/ReportRepository.java
│ ├── equipment/
│ │ ├── entity/TbEquipment.java
│ │ ├── entity/TbEquipmentAssignment.java
│ │ ├── entity/TbEquipmentMaintenance.java
│ │ └── repository/EquipmentRepository.java
│ └── training/
│ ├── entity/TbTrainingProgress.java
│ └── repository/TrainingRepository.java
│
└── infra/
└── notification/
├── listener/NotificationEventListener.java
└── scheduler/SlaJobScheduler.java
src/main/resources/
├── application.yml
└── ddl/oracle_11g_schema.sql
```
### Frontend (Next.js 14+)
```
src/
├── app/
│ ├── (auth)/login/page.tsx
│ ├── tickets/
│ │ ├── page.tsx // 칸반 보드
│ │ └── [ticketId]/page.tsx // 상세/타임라인
│ ├── equipment/page.tsx // 장비 인벤토리
│ ├── reports/page.tsx // 업무 보고서
│ ├── training/page.tsx // 교육 진도
│ ├── employees/page.tsx // 직원 관리 (ADMIN)
│ └── layout.tsx // GNB + LNB 레이아웃
├── components/
│ ├── layout/
│ │ ├── GNB.tsx
│ │ └── LNB.tsx
│ ├── ticket/
│ │ ├── KanbanBoard.tsx
│ │ ├── TicketCard.tsx
│ │ └── TimelineComment.tsx
│ ├── equipment/
│ │ ├── EquipmentTable.tsx
│ │ └── AssignModal.tsx
│ └── common/
│ ├── ApiResponse.ts
│ └── Pagination.tsx
├── stores/
│ ├── authStore.ts
│ └── notificationStore.ts
├── services/
│ ├── apiClient.ts // Axios 인스턴스 + 인터셉터
│ ├── ticketService.ts
│ ├── equipmentService.ts
│ └── reportService.ts
└── types/
└── index.ts // 전역 TypeScript 타입
```
---
## 2. Backend 코딩 컨벤션
### 2.1 엔티티 네이밍
- **테이블명:** `TB_XXX` (Oracle 11g 컨벤션)
- **엔티티 클래스명:** `TbXxx.java` (PascalCase, 테이블명 매핑)
- **PK 컬럼:** `xxx_id` (NUMBER, Sequence 할당)
### 2.2 ID 생성 전략 (Oracle 11g 최적화)
```java
@Entity
@SequenceGenerator(name = "SEQ_TB_TICKET", sequenceName = "SEQ_TB_TICKET", allocationSize = 50)
public class TbIssueTicket {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_TB_TICKET")
private Long ticketId;
}
```
- Oracle DDL 시퀀스: `CREATE SEQUENCE SEQ_TB_TICKET INCREMENT BY 50;`
- `allocationSize = 50` + `INCREMENT BY 50` 반드시 정합
### 2.3 Soft Delete 패턴
```java
@SQLDelete(sql = "UPDATE tb_issue_ticket SET is_deleted = 'Y' WHERE ticket_id = ?")
@Where(clause = "is_deleted = 'N'")
public class TbIssueTicket { ... }
```
- 모든 엔티티에 `@SQLDelete` + `@Where` 선언
- `BaseEntity`에 `isDeleted` 컬럼 공통 상속
### 2.4 레이어드 아키텍처 규칙
| 레이어 | 책임 | 규칙 |
|--------|------|------|
| Controller | HTTP 진입점 | DTO 반환, `@Valid` 검증, 서비스 호출만 |
| Service | 비즈니스 로직 | `@Transactional`, 예외 throw, 이벤트 발행 |
| Repository | DB 접근 | Spring Data JPA, `Specification` 동적 쿼리 |
| DTO | 데이터 전송 | Request/Response 분리, 엔티티 직접 노출 금지 |
### 2.5 예외 처리 계층
```java
// 최상위 예외
public class BusinessException extends RuntimeException {
private final ErrorCode errorCode;
}
// 도메인별 구체 예외
public class TicketClosedException extends BusinessException {
public TicketClosedException() {
super(ErrorCode.CLOSED_TICKET_MOD);
}
}
// ControllerAdvice에서 ErrorCode → ApiResponse 자동 변환
```
### 2.6 EDA (이벤트 기반 알림)
```java
// 발행
@Service
public class IssueTicketService {
@Autowired
private ApplicationEventPublisher publisher;
public TicketDto createTicket(TicketCreateReq req) {
TbIssueTicket ticket = repository.save(req.toEntity());
publisher.publishEvent(new TicketCreatedEvent(ticket));
return TicketDto.from(ticket);
}
}
// 구독 (트랜잭션 완료 후 비동기 실행)
@Component
public class NotificationEventListener {
@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleTicketCreated(TicketCreatedEvent event) {
// Slack/Email 발송 (메인 트랜잭션과 완전 격리)
}
}
```
---
## 3. Frontend 코딩 컨벤션
### 3.1 API 통신 (Axios)
```typescript
// apiClient.ts - 공통 인터셉터
const apiClient = axios.create({
baseURL: '/api/v1',
timeout: 10000,
});
apiClient.interceptors.response.use(
(res) => res.data, // ApiResponse.data 자동 언래핑
(err) => {
const { errorCode, message } = err.response.data;
// errorCode 기반 전역 토스트/알림
return Promise.reject({ errorCode, message });
}
);
```
### 3.2 Pinia Store 패턴
```typescript
// stores/authStore.ts
export const useAuthStore = defineStore('auth', () => {
const user = ref<Employee | null>(null);
const roleCode = computed(() => user.value?.roleCode);
async function fetchMe() {
const res = await apiClient.get('/employees/me');
user.value = res.data;
}
return { user, roleCode, fetchMe };
});
```
### 3.3 컴포넌트 네이밍
- **페이지 컴포넌트:** `page.tsx` (App Router), 도메인 폴더 하위
- **UI 컴포넌트:** `PascalCase.tsx` (단일 책임)
- **Props:** Interface 선언 후 `defineProps<Props>()`
- **Event Emit:** `defineEmits<{ eventName: [payload: type] }>()`
---
## 4. 개발 환경 설정
### Backend (application.yml)
```yaml
spring:
datasource:
url: jdbc:oracle:thin:@localhost:1521:XE
username: portal
password: ${DB_PASSWORD}
jpa:
hibernate:
ddl-auto: none # 운영 DDL은 별도 스크립트 관리
properties:
hibernate:
dialect: org.hibernate.dialect.Oracle10gDialect
batch:
jdbc:
initialize-schema: always # 배치 메타 테이블 자동 생성
jwt:
secret: ${JWT_SECRET}
access-token-validity: 3600000 # 1시간
refresh-token-validity: 604800000 # 7일
```
### Frontend (.env.local)
```
NEXT_PUBLIC_API_BASE_URL=http://localhost:8080/api/v1
NEXT_PUBLIC_WS_URL=ws://localhost:8080/ws/notifications
```
---
## 5. 빌드 및 배포
### CI/CD (GitHub Actions)
- **트리거:** `release` 생성 시에만 운영 빌드
- **Push 이벤트:** Lint + Test만 실행 (배포 없음)
- **파이프라인:** Build → Test → Docker Image → Deploy (Nginx Proxy Manager)
### 로컬 빌드 검증
```bash
# Backend
./gradlew clean build
# Frontend
npm run build
npm run lint
```
### Nginx Proxy Manager
- SSL 인증서 관리
- `/api/*` → Backend Container (Reverse Proxy)
- `/*` → Frontend Static/Node Server
- Rate Limit 설정 (API abuse 방어)
---
## 6. DB DDL 관리 규칙
- DDL 파일: `src/main/resources/ddl/oracle_11g_schema.sql`
- 모든 시퀀스: `CREATE SEQUENCE SEQ_TB_XXX INCREMENT BY 50 START WITH 1`
- `ddl-auto: none` — DDL은 스크립트로만 관리 (Hibernate 자동 생성 금지)
- 스키마 변경 시 DDL 파일 버전 관리 (Git)
- 인덱스: 자주 조회하는 컬럼(상태, 날짜, 외래키)에 인덱스 별도 추가
- 공통 코드 데이터: `INSERT` 문으로 시드 데이터 관리
---
## 7. 커밋 컨벤션
한국어로 명확하게 작성 (팀 협업 가독성 보장)
```
feat: SR 티켓 칸반 보드 API 구현
fix: 장비 지급 시 낙관적 락 충돌 해결
refactor: BaseEntity 공통 필드 추출
docs: DB 설계서 최신화
```
### 브랜치 전략
```
main ← develop ← feature/{domain}-{description}
- feature/issue-ticket-kanban
- feature/equipment-assign-optimistic-lock
- fix/report-status-transition-bug
```