docs: CLAUDE.md 상세화 — 전체 구현 가이드 추가
- 7단계 구현 로드맵 (엔티티→인증→관리자→활동→이슈→RBAC→시드) - DB 스키마 DDL (PostgreSQL + 인덱스) - 패키지 구조 목표 (entity/auth/service/controller/dto) - 인증/인가 흐름 상세 (Google OAuth→JWT→RBAC) - application.yml 설정 참조
This commit is contained in:
부모
d332283e86
커밋
7aa50dc6a2
324
CLAUDE.md
324
CLAUDE.md
@ -1,56 +1,308 @@
|
||||
# gc-guide-api — 가이드 사이트 백엔드 API
|
||||
|
||||
## 프로젝트 개요
|
||||
gc-guide 프론트엔드의 백엔드 API. Google OAuth2 인증, 사용자 관리, 이슈 트래커 제공.
|
||||
gc-guide 프론트엔드의 백엔드 API. Google OAuth2 인증, 사용자 관리(RBAC), 활동 기록, 이슈 트래커 제공.
|
||||
|
||||
## 기술 스택
|
||||
- Spring Boot 3.5 + JDK 17
|
||||
- Spring Security (JWT 기반 인증)
|
||||
- Spring Boot 3.5.2 + JDK 17
|
||||
- Spring Security (JWT 기반 Stateless 인증)
|
||||
- Spring Data JPA + PostgreSQL (운영) / H2 (로컬)
|
||||
- Google API Client (ID Token 검증)
|
||||
- Google API Client 2.7.2 (ID Token 검증)
|
||||
- JJWT 0.12.6 (JWT 생성/검증)
|
||||
- Lombok
|
||||
|
||||
## 빌드 & 실행
|
||||
|
||||
```bash
|
||||
source ~/.sdkman/bin/sdkman-init.sh && sdk use java 17.0.18-amzn # JDK 전환
|
||||
./mvnw -s .mvn/settings.xml clean compile # 컴파일
|
||||
./mvnw -s .mvn/settings.xml spring-boot:run # 로컬 실행 (H2)
|
||||
./mvnw -s .mvn/settings.xml spring-boot:run # 로컬 실행 (H2, port 8080)
|
||||
./mvnw -s .mvn/settings.xml package -DskipTests # JAR 패키징
|
||||
```
|
||||
|
||||
## 프로필
|
||||
- `local` (기본): H2 인메모리 DB, 디버그 로깅
|
||||
- `prod`: PostgreSQL (gc_guide DB), validate 모드
|
||||
|
||||
## API 엔드포인트
|
||||
|
||||
### 인증
|
||||
- `POST /api/auth/google` — Google ID Token 검증 → JWT 발급
|
||||
- `GET /api/auth/me` — 현재 사용자 정보
|
||||
- `POST /api/auth/logout` — 세션 무효화
|
||||
|
||||
### 활동 기록
|
||||
- `GET /api/activity/login-history` — 로그인 이력
|
||||
- `POST /api/activity/track` — 페이지 조회 기록
|
||||
|
||||
### 이슈 관리
|
||||
- `GET/POST /api/issues` — 이슈 목록/생성
|
||||
- `GET/PUT /api/issues/:id` — 이슈 상세/수정
|
||||
- `POST /api/issues/:id/comments` — 코멘트 추가
|
||||
|
||||
### 관리자
|
||||
- `GET /api/admin/users` — 사용자 목록
|
||||
- `GET /api/admin/stats` — 통계
|
||||
|
||||
## DB (운영)
|
||||
- Host: 211.208.115.83:5432
|
||||
- Database: gc_guide
|
||||
- Username: gcguide
|
||||
- `local` (기본): H2 인메모리 DB (`create-drop`), 디버그 로깅, H2 콘솔 `/h2-console`
|
||||
- `prod`: PostgreSQL (gc_guide DB), `validate` 모드
|
||||
|
||||
## 배포
|
||||
- Docker (eclipse-temurin:17-jre)
|
||||
- guide.gc-si.dev/api/* → Nginx 프록시
|
||||
- Docker (eclipse-temurin:17-jre + JAR)
|
||||
- guide.gc-si.dev/api/* → Nginx 프록시 → 컨테이너
|
||||
- CI/CD: Gitea Actions (main 머지 시 자동 빌드/배포)
|
||||
- Gitea: https://gitea.gc-si.dev/gc/gc-guide-api
|
||||
|
||||
## 의존성 레포지토리
|
||||
- Maven: Nexus 프록시 `.mvn/settings.xml` (admin/Gcsc!8932 인증 포함)
|
||||
|
||||
## 관련 프로젝트
|
||||
- gc-guide: 프론트엔드 (React + TypeScript)
|
||||
- Gitea: https://gitea.gc-si.dev/gc/gc-guide-api
|
||||
- gc-guide: 프론트엔드 (React + TypeScript + Vite)
|
||||
|
||||
---
|
||||
|
||||
## 현재 구현 상태
|
||||
|
||||
### 완료 (scaffold)
|
||||
- Spring Boot 프로젝트 초기화 (pom.xml + Maven Wrapper)
|
||||
- application.yml + local/prod 프로필 분리
|
||||
- SecurityConfig.java — CSRF 비활성화, Stateless 세션, 공개 엔드포인트 설정
|
||||
- HealthController.java — `GET /api/health` → `{status: "UP"}`
|
||||
- Nexus 프록시 인증 설정 (.mvn/settings.xml)
|
||||
- 빌드 검증: `mvn clean compile` 성공
|
||||
|
||||
### 미구현 (별도 세션에서 작업)
|
||||
아래 순서대로 구현 필요:
|
||||
|
||||
#### 1단계: DB 엔티티 + JPA
|
||||
패키지: `com.gcsc.guide.entity`
|
||||
|
||||
```java
|
||||
@Entity User — id, email, name, avatarUrl, status(PENDING/ACTIVE/REJECTED/DISABLED), isAdmin, createdAt, updatedAt, lastLoginAt
|
||||
@Entity Role — id, name, description, createdAt
|
||||
@Entity UserRole — userId, roleId (다대다, @IdClass 또는 @JoinTable)
|
||||
@Entity RoleUrlPattern — id, roleId, urlPattern, createdAt
|
||||
@Entity LoginHistory — id, userId, loginAt, ipAddress, userAgent
|
||||
@Entity PageView — id, userId, pagePath, viewedAt
|
||||
@Entity Issue — id, title, body, status, priority, authorId, assigneeId, createdAt, updatedAt
|
||||
@Entity IssueComment — id, issueId, authorId, body, createdAt
|
||||
```
|
||||
|
||||
패키지: `com.gcsc.guide.repository` — 각 엔티티별 JpaRepository
|
||||
|
||||
#### 2단계: 인증 API
|
||||
패키지: `com.gcsc.guide.auth`
|
||||
|
||||
- `JwtTokenProvider.java` — JWT 생성/검증 (JJWT 0.12.6)
|
||||
- secret: `app.jwt.secret` (256bit 이상)
|
||||
- expiration: `app.jwt.expiration-ms` (기본 24시간)
|
||||
- `JwtAuthenticationFilter.java` — OncePerRequestFilter, Authorization Bearer 토큰 파싱
|
||||
- `GoogleTokenVerifier.java` — Google API Client로 ID Token 검증
|
||||
- Client ID: `app.google.client-id`
|
||||
- 이메일 도메인 검증: `app.allowed-email-domain` (gcsc.co.kr)
|
||||
- `AuthController.java`:
|
||||
- `POST /api/auth/google` — { idToken } → GoogleTokenVerifier → User 조회/생성 → JWT 발급
|
||||
- 신규 사용자: status=PENDING, DB 저장
|
||||
- htlee@gcsc.co.kr: 자동 ACTIVE + isAdmin=true
|
||||
- 응답: { token, user }
|
||||
- `GET /api/auth/me` — JWT에서 userId 추출 → User + roles 반환
|
||||
- `POST /api/auth/logout` — (Stateless이므로 프론트에서 토큰 삭제, 서버는 204)
|
||||
|
||||
SecurityConfig 수정:
|
||||
- JwtAuthenticationFilter를 UsernamePasswordAuthenticationFilter 앞에 추가
|
||||
- 공개 엔드포인트: `/api/auth/**`, `/api/health`, `/actuator/health`, `/h2-console/**`
|
||||
|
||||
#### 3단계: 관리자 API
|
||||
패키지: `com.gcsc.guide.controller`
|
||||
|
||||
- `AdminUserController.java`:
|
||||
- `GET /api/admin/users` — 전체 목록 (status 필터 쿼리파라미터)
|
||||
- `PUT /api/admin/users/{id}/approve` — PENDING→ACTIVE
|
||||
- `PUT /api/admin/users/{id}/reject` — PENDING→REJECTED
|
||||
- `PUT /api/admin/users/{id}/disable` — ACTIVE→DISABLED
|
||||
- `PUT /api/admin/users/{id}/roles` — { roleIds: [1,2] } → user_roles 업데이트
|
||||
- `POST /api/admin/users/{id}/admin` — isAdmin=true
|
||||
- `DELETE /api/admin/users/{id}/admin` — isAdmin=false
|
||||
|
||||
- `AdminRoleController.java`:
|
||||
- `GET /api/admin/roles` — 전체 롤 목록
|
||||
- `POST /api/admin/roles` — { name, description } → Role 생성
|
||||
- `PUT /api/admin/roles/{id}` — 수정
|
||||
- `DELETE /api/admin/roles/{id}` — 삭제
|
||||
- `GET /api/admin/roles/{id}/permissions` — URL 패턴 목록
|
||||
- `POST /api/admin/roles/{id}/permissions` — { urlPattern } 추가
|
||||
- `DELETE /api/admin/permissions/{id}` — 삭제
|
||||
|
||||
- `AdminStatsController.java`:
|
||||
- `GET /api/admin/stats` — { totalUsers, activeUsers, pendingUsers, todayLogins, ... }
|
||||
|
||||
관리자 권한 체크: `@PreAuthorize("@authChecker.isAdmin()")` 또는 SecurityConfig에서 `/api/admin/**` 패턴에 커스텀 필터 적용
|
||||
|
||||
#### 4단계: 활동 기록 API
|
||||
- `ActivityController.java`:
|
||||
- `POST /api/activity/track` — { pagePath } → PageView 저장
|
||||
- `GET /api/activity/login-history` — 현재 사용자의 로그인 이력
|
||||
|
||||
#### 5단계: 이슈 관리 API
|
||||
- `IssueController.java`:
|
||||
- `GET /api/issues` — 이슈 목록 (status 필터, 페이징)
|
||||
- `POST /api/issues` — { title, body, priority } → Issue 생성
|
||||
- `GET /api/issues/{id}` — 이슈 상세 (코멘트 포함)
|
||||
- `PUT /api/issues/{id}` — 수정
|
||||
- `POST /api/issues/{id}/comments` — { body } → 코멘트 추가
|
||||
|
||||
#### 6단계: 롤 기반 URL 접근 제어 (선택)
|
||||
- `DynamicAuthorizationManager.java` — 커스텀 AuthorizationManager
|
||||
- 매 요청마다 DB에서 사용자 롤 → role_url_patterns 조회 → ant-style 매칭
|
||||
- SecurityConfig에서 `.anyRequest().access(dynamicAuthorizationManager)` 적용
|
||||
- 캐싱: 사용자별 URL 패턴을 JWT 클레임에 포함하거나 Redis/로컬캐시 활용
|
||||
|
||||
#### 7단계: 초기 데이터 시딩
|
||||
`data.sql` 또는 ApplicationRunner로:
|
||||
|
||||
```sql
|
||||
INSERT INTO roles (name, description) VALUES
|
||||
('ADMIN', '전체 접근 권한 (관리자 페이지 포함)'),
|
||||
('DEVELOPER', '전체 개발 가이드 접근'),
|
||||
('FRONT_DEV', '프론트엔드 개발 가이드만');
|
||||
|
||||
INSERT INTO role_url_patterns (role_id, url_pattern) VALUES
|
||||
(1, '/**'),
|
||||
(2, '/dev/**'),
|
||||
(3, '/dev/front/**');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## DB 스키마
|
||||
|
||||
### 로컬 (H2, create-drop)
|
||||
JPA가 엔티티 기반으로 자동 생성. `data.sql`로 초기 시드.
|
||||
|
||||
### 운영 (PostgreSQL)
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| Host | 211.208.115.83 (Docker: 172.17.0.1) |
|
||||
| Port | 5432 |
|
||||
| Database | gc_guide |
|
||||
| Username | gcguide |
|
||||
| Password | GcGuide!2026 |
|
||||
|
||||
**DB/사용자 아직 미생성** — 서버에서 아래 SQL 실행 필요:
|
||||
|
||||
```sql
|
||||
CREATE USER gcguide WITH PASSWORD 'GcGuide!2026';
|
||||
CREATE DATABASE gc_guide OWNER gcguide ENCODING 'UTF8';
|
||||
GRANT ALL PRIVILEGES ON DATABASE gc_guide TO gcguide;
|
||||
```
|
||||
|
||||
스키마 DDL (JPA가 로컬에서 자동 생성하되, 운영은 validate 모드이므로 수동 생성 필요):
|
||||
|
||||
```sql
|
||||
CREATE TABLE users (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
avatar_url VARCHAR(500),
|
||||
status VARCHAR(20) DEFAULT 'PENDING',
|
||||
is_admin BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW(),
|
||||
last_login_at TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE roles (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR(50) NOT NULL UNIQUE,
|
||||
description VARCHAR(255),
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE user_roles (
|
||||
user_id BIGINT REFERENCES users(id) ON DELETE CASCADE,
|
||||
role_id BIGINT REFERENCES roles(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (user_id, role_id)
|
||||
);
|
||||
|
||||
CREATE TABLE role_url_patterns (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
role_id BIGINT REFERENCES roles(id) ON DELETE CASCADE,
|
||||
url_pattern VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE login_history (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT REFERENCES users(id),
|
||||
login_at TIMESTAMP DEFAULT NOW(),
|
||||
ip_address VARCHAR(45),
|
||||
user_agent VARCHAR(500)
|
||||
);
|
||||
|
||||
CREATE TABLE page_views (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT REFERENCES users(id),
|
||||
page_path VARCHAR(255) NOT NULL,
|
||||
viewed_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE issues (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
body TEXT,
|
||||
status VARCHAR(20) DEFAULT 'OPEN',
|
||||
priority VARCHAR(20) DEFAULT 'NORMAL',
|
||||
author_id BIGINT REFERENCES users(id),
|
||||
assignee_id BIGINT REFERENCES users(id),
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE issue_comments (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
issue_id BIGINT REFERENCES issues(id) ON DELETE CASCADE,
|
||||
author_id BIGINT REFERENCES users(id),
|
||||
body TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_login_history_user ON login_history(user_id);
|
||||
CREATE INDEX idx_page_views_user ON page_views(user_id);
|
||||
CREATE INDEX idx_issues_status ON issues(status);
|
||||
CREATE INDEX idx_issue_comments_issue ON issue_comments(issue_id);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 패키지 구조 (목표)
|
||||
|
||||
```
|
||||
com.gcsc.guide/
|
||||
├── GcGuideApiApplication.java ✅
|
||||
├── config/
|
||||
│ └── SecurityConfig.java ✅ (JWT 필터 추가 필요)
|
||||
├── auth/
|
||||
│ ├── JwtTokenProvider.java ⬜
|
||||
│ ├── JwtAuthenticationFilter.java ⬜
|
||||
│ ├── GoogleTokenVerifier.java ⬜
|
||||
│ └── AuthController.java ⬜
|
||||
├── entity/
|
||||
│ ├── User.java ⬜
|
||||
│ ├── Role.java ⬜
|
||||
│ ├── RoleUrlPattern.java ⬜
|
||||
│ ├── LoginHistory.java ⬜
|
||||
│ ├── PageView.java ⬜
|
||||
│ ├── Issue.java ⬜
|
||||
│ └── IssueComment.java ⬜
|
||||
├── repository/ ⬜ (각 엔티티별 JpaRepository)
|
||||
├── service/
|
||||
│ ├── UserService.java ⬜
|
||||
│ ├── RoleService.java ⬜
|
||||
│ ├── ActivityService.java ⬜
|
||||
│ └── IssueService.java ⬜
|
||||
├── controller/
|
||||
│ ├── HealthController.java ✅
|
||||
│ ├── AdminUserController.java ⬜
|
||||
│ ├── AdminRoleController.java ⬜
|
||||
│ ├── AdminStatsController.java ⬜
|
||||
│ ├── ActivityController.java ⬜
|
||||
│ └── IssueController.java ⬜
|
||||
├── dto/ ⬜ (요청/응답 DTO)
|
||||
└── exception/ ⬜ (GlobalExceptionHandler)
|
||||
```
|
||||
|
||||
## application.yml 설정 참조
|
||||
|
||||
```yaml
|
||||
app:
|
||||
jwt:
|
||||
secret: ${JWT_SECRET:gc-guide-dev-jwt-secret-key-must-be-at-least-256-bits-long}
|
||||
expiration-ms: ${JWT_EXPIRATION:86400000}
|
||||
google:
|
||||
client-id: ${GOOGLE_CLIENT_ID:295080817934-1uqaqrkup9jnslajkl1ngpee7gm249fv.apps.googleusercontent.com}
|
||||
allowed-email-domain: gcsc.co.kr
|
||||
```
|
||||
|
||||
## 구현 시 주의사항
|
||||
- DTO와 Entity 분리 필수 (API 응답에 Entity 직접 노출 금지)
|
||||
- 비즈니스 로직은 Service 계층에 집중
|
||||
- @Transactional 범위 최소화
|
||||
- H2 로컬 모드에서 전체 기능 테스트 가능하도록 프로필 분리 유지
|
||||
- Google OAuth2 Client ID는 gc-guide (프론트엔드)와 동일한 것 사용
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user