outer 및 inner sequence 동기화 및 한글화

변경 사항:
- 이벤트생성플로우 outer: FE → Gateway → User Service 호출 패턴 추가
- user-로그인 inner: 전화번호 → 이메일 기반 인증으로 변경
- user-회원가입 inner: 국세청 API 제거, 이메일 중복검사 추가
- event-목적선택 inner: Gateway 경유, 요청/응답 한글화
- ai-트렌드분석및추천 inner: 과거 이벤트 데이터 제거, Timeout 5분으로 변경
- analytics-대시보드조회 inner: Redis TTL 5분 → 1시간으로 변경

모든 파일에 Repository CRUD 작업 한글 설명 적용 (SQL 제거)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
cherry2250
2025-10-23 15:36:45 +09:00
parent eea1fff98c
commit edb7045008
6 changed files with 234 additions and 207 deletions
@@ -5,112 +5,68 @@ title User Service - 회원가입 내부 시퀀스 (UFR-USER-010)
participant "UserController" as Controller <<API Layer>>
participant "UserService" as Service <<Business Layer>>
participant "BusinessValidator" as Validator <<Business Layer>>
participant "UserRepository" as UserRepo <<Data Layer>>
participant "StoreRepository" as StoreRepo <<Data Layer>>
participant "PasswordEncoder" as PwdEncoder <<Utility>>
participant "JwtTokenProvider" as JwtProvider <<Utility>>
participant "Redis\nCache" as Redis <<E>>
participant "User DB\n(PostgreSQL)" as UserDB <<E>>
participant "국세청 API" as NTSApi <<E>>
actor Client
note over Controller, NTSApi
note over Controller, UserDB
**UFR-USER-010: 회원가입**
- 기본 정보: 이름, 전화번호, 이메일, 비밀번호
- 매장 정보: 매장명, 업종, 주소, 영업시간, 사업자번호
- 사업자번호 검증 (국세청 API)
- 이메일/전화번호 중복 검사
- 트랜잭션 처리
- JWT 토큰 발급
end note
Client -> Controller: POST /api/users/register\n(RegisterRequest DTO)
Client -> Controller: POST /api/users/register\n{"name": "홍길동",\n"phoneNumber": "01012345678",\n"email": "hong@example.com",\n"password": "password123"}
activate Controller
Controller -> Controller: @Valid 어노테이션 검증\n(이메일 형식, 비밀번호 8자 이상 등)
Controller -> Controller: 입력값 검증\n(이메일 형식, 비밀번호 8자 이상 등)
Controller -> Service: register(RegisterRequest)
activate Service
== 1단계: 중복 사용자 확인 ==
== 1단계: 이메일 중복 확인 ==
Service -> UserRepo: findByPhoneNumber(phoneNumber)
Service -> UserRepo: findByEmail(email)
activate UserRepo
UserRepo -> UserDB: 전화번호로 사용자 조회\n(중복 가입 확인)
UserRepo -> UserDB: 이메일로 사용자 조회\n(중복 가입 확인)
activate UserDB
UserDB --> UserRepo: 조회 결과
UserDB --> UserRepo: 조회 결과 반환 또는 없음
deactivate UserDB
UserRepo --> Service: Optional<User>
deactivate UserRepo
alt 중복 사용자 존재
Service --> Controller: throw DuplicateUserException\n("이미 가입된 전화번호입니다")
Controller --> Client: 400 Bad Request\n{"code": "USER_001",\n"error": "이미 가입된 전화번호입니다"}
alt 이메일 중복 존재
Service --> Controller: throw DuplicateEmailException\n("이미 가입된 이메일입니다")
Controller --> Client: 400 Bad Request\n{"code": "USER_001",\n"message": "이미 가입된 이메일입니다"}
deactivate Service
deactivate Controller
else 신규 사용자
== 2단계: 사업자번호 검증 ==
else 이메일 신규
Service -> Validator: validateBusinessNumber(businessNumber)
activate Validator
== 2단계: 전화번호 중복 확인 ==
Validator -> Redis: GET user:business:{사업자번호}
activate Redis
Redis --> Validator: 캐시 확인 결과
deactivate Redis
Service -> UserRepo: findByPhoneNumber(phoneNumber)
activate UserRepo
UserRepo -> UserDB: 전화번호로 사용자 조회\n(중복 가입 확인)
activate UserDB
UserDB --> UserRepo: 조회 결과 반환 또는 없음
deactivate UserDB
UserRepo --> Service: Optional<User>
deactivate UserRepo
alt 캐시 HIT (검증 결과 있음)
Validator -> Validator: 캐시된 검증 결과 사용\n(응답 시간: 0.1초)
else 캐시 MISS (검증 필요)
note right of Validator
**Circuit Breaker 설정**
- 실패율: 50% 초과 시 Open
- 타임아웃: 5초
- Half-Open: 30초 후 전환
end note
Validator -> NTSApi: POST /사업자번호_검증\n(사업자번호)\n[Circuit Breaker, Timeout 5초]
activate NTSApi
alt 국세청 API 정상 응답
NTSApi --> Validator: 200 OK\n{"valid": true, "status": "영업중"}
deactivate NTSApi
Validator -> Redis: SET user:business:{사업자번호}\n검증 결과 (TTL 7일)
activate Redis
Redis --> Validator: 캐싱 완료
deactivate Redis
else 국세청 API 장애 (Circuit Breaker Open)
NTSApi --> Validator: 500 Internal Server Error\n또는 Timeout
deactivate NTSApi
note right of Validator
**Resilience 패턴 적용**
- Circuit Breaker: Open
- Retry: 최대 3회 (1초, 2초, 4초)
- Fallback: 검증 스킵 (수동 확인 안내)
end note
Validator -> Validator: Fallback 실행:\n사업자번호 검증 스킵\n(수동 확인 안내 플래그 설정)
end
end
alt 사업자번호 검증 실패 (휴폐업 등)
Validator --> Service: throw BusinessNumberInvalidException\n("유효하지 않은 사업자번호입니다")
deactivate Validator
Service --> Controller: BusinessNumberInvalidException
Controller --> Client: 400 Bad Request\n{"code": "USER_002",\n"error": "유효하지 않은 사업자번호입니다.\n휴폐업 여부를 확인해주세요."}
alt 전화번호 중복 존재
Service --> Controller: throw DuplicatePhoneException\n("이미 가입된 전화번호입니다")
Controller --> Client: 400 Bad Request\n{"code": "USER_002",\n"message": "이미 가입된 전화번호입니다"}
deactivate Service
deactivate Controller
else 사업자번호 검증 성공
Validator --> Service: ValidationResult\n(valid: true, needsManualCheck: false)
deactivate Validator
else 신규 사용자
== 3단계: 비밀번호 해싱 ==
@@ -120,40 +76,30 @@ else 신규 사용자
PwdEncoder --> Service: passwordHash
deactivate PwdEncoder
== 4단계: 사업자번호 암호화 ==
Service -> Service: EncryptionUtil 호출 준비
note right of Service
**암호화 처리**
- AES-256-GCM 모드 사용
- 환경변수에서 암호화 키 로드
end note
Service -> Service: encryptedBusinessNumber =\nEncryptionUtil.encrypt(businessNumber)
== 5단계: 데이터베이스 트랜잭션 ==
== 4단계: 데이터베이스 트랜잭션 ==
Service -> UserDB: 트랜잭션 시작
activate UserDB
Service -> UserRepo: save(User)\n(name, phoneNumber, email,\npasswordHash, createdAt)
Service -> UserRepo: save(User)
activate UserRepo
UserRepo -> UserDB: 사용자 정보 저장\n(이름, 전화번호, 이메일,\n비밀번호해시, 생성일시)\n사용자ID 반환
UserDB --> UserRepo: user_id
UserRepo -> UserDB: 사용자 정보 저장\n(이름, 전화번호, 이메일,\n비밀번호해시, 생성일시)\n저장 후 사용자ID 반환
UserDB --> UserRepo: 생성된 사용자ID
UserRepo --> Service: User 엔티티\n(userId 포함)
deactivate UserRepo
Service -> StoreRepo: save(Store)\n(userId, storeName, industry,\naddress, businessNumberEncrypted,\nbusinessHours)
Service -> StoreRepo: save(Store)
activate StoreRepo
StoreRepo -> UserDB: 매장 정보 저장\n(사용자ID, 매장명, 업종,\n주소, 암호화된사업자번호,\n영업시간)\n매장ID 반환
UserDB --> StoreRepo: store_id
StoreRepo -> UserDB: 매장 정보 저장\n(사용자ID, 매장명, 업종,\n주소, 사업자번호, 영업시간)\n저장 후 매장ID 반환
UserDB --> StoreRepo: 생성된 매장ID
StoreRepo --> Service: Store 엔티티\n(storeId 포함)
deactivate StoreRepo
Service -> UserDB: 트랜잭션 커밋
UserDB --> Service: 트랜잭션 커밋 완료
UserDB --> Service: 커밋 완료
deactivate UserDB
== 6단계: JWT 토큰 생성 ==
== 5단계: JWT 토큰 생성 ==
Service -> JwtProvider: generateToken(userId, role)
activate JwtProvider
@@ -161,48 +107,43 @@ else 신규 사용자
JwtProvider --> Service: JWT 토큰
deactivate JwtProvider
== 7단계: 세션 저장 ==
== 6단계: 세션 저장 ==
Service -> Redis: SET user:session:{token}\n(userId, role, TTL 7일)
Service -> Redis: 세션 정보 저장\nKey: user:session:{token}\nValue: {userId, role}\nTTL: 7일
activate Redis
Redis --> Service: 세션 저장 완료
Redis --> Service: 저장 완료
deactivate Redis
== 8단계: 응답 반환 ==
== 7단계: 응답 반환 ==
Service -> Service: 응답 DTO 생성\n(RegisterResponse)
Service --> Controller: RegisterResponse\n(token, userId, userName,\nstoreId, storeName)
Service -> Service: 회원가입 응답 DTO 생성
Service --> Controller: RegisterResponse\n{token, userId, userName, storeId, storeName}
deactivate Service
Controller --> Client: 201 Created\n{"token": "jwt_token",\n"userId": 123,\n"userName": "홍길동",\n"storeId": 456,\n"storeName": "맛있는집"}
Controller --> Client: 201 Created\n{"token": "eyJhbGc...",\n"userId": 123,\n"userName": "홍길동",\n"storeId": 456,\n"storeName": "맛있는집"}
deactivate Controller
end
end
end
note over Controller, NTSApi
note over Controller, UserDB
**Transaction Rollback 처리**
- 트랜잭션 실패 시 자동 Rollback
- User/Store INSERT 중 하나라도 실패 시 전체 롤백
- 예외: DataAccessException, ConstraintViolationException
**Resilience 패턴 요약**
- Circuit Breaker: 국세청 API (실패율 50% 초과 시 Open)
- Retry: 최대 3회 (지수 백오프: 1초, 2초, 4초)
- Timeout: 5초
- Fallback: 사업자번호 검증 스킵 (수동 확인 안내)
**보안 처리**
- 비밀번호: bcrypt 해싱 (Cost Factor 10)
- 사업자번호: AES-256-GCM 암호화 (EncryptionUtil)
- JWT 토큰: 7일 만료, 서버 세션과 동기화
**성능 목표**
- 평균 응답 시간: 2.0초 이내 (국세청 API 포함)
- P95 응답 시간: 3.0초 이내
- 캐시 HIT 시: 0.8초 이내
- 평균 응답 시간: 1.0초 이내
- P95 응답 시간: 1.5초 이내
- 트랜잭션 처리: 0.5초 이내
**에러 코드**
- USER_001: 중복 사용자
- USER_002: 사업자번호 검증 실패
- USER_001: 이메일 중복
- USER_002: 전화번호 중복
end note
@enduml