@startuml user-프로필수정 !theme mono title User Service - 프로필 수정 내부 시퀀스 (UFR-USER-030) actor Client participant "UserController" as Controller <> participant "UserService" as Service <> participant "UserRepository" as UserRepo <> participant "StoreRepository" as StoreRepo <> participant "PasswordEncoder" as PwdEncoder <> participant "Redis\nCache" as Redis <> participant "User DB\n(PostgreSQL)" as UserDB <> note over Controller, UserDB **UFR-USER-030: 프로필 수정** - 기본 정보: 이름, 전화번호, 이메일 - 매장 정보: 매장명, 업종, 주소, 영업시간 - 비밀번호 변경 (현재 비밀번호 확인 필수) - 전화번호 변경 시 재인증 필요 (향후 구현) end note Client -> Controller: PUT /api/users/profile\nAuthorization: Bearer {JWT}\n(UpdateProfileRequest DTO) activate Controller Controller -> Controller: @AuthenticationPrincipal\n(JWT에서 userId 추출) Controller -> Controller: @Valid 어노테이션 검증\n(이메일 형식, 필드 길이 등) Controller -> Service: updateProfile(userId, UpdateProfileRequest) activate Service == 1단계: 기존 사용자 정보 조회 == Service -> UserRepo: findById(userId) activate UserRepo UserRepo -> UserDB: SELECT * FROM users\nWHERE user_id = ? activate UserDB UserDB --> UserRepo: 사용자 정보 deactivate UserDB UserRepo --> Service: User 엔티티 deactivate UserRepo alt 사용자 없음 Service --> Controller: throw UserNotFoundException\n("사용자를 찾을 수 없습니다") Controller --> Client: 404 Not Found\n{"error": "사용자를 찾을 수 없습니다"} deactivate Service deactivate Controller else 사용자 존재 == 2단계: 비밀번호 변경 요청 처리 == alt 비밀번호 변경 요청 O Service -> Service: 현재 비밀번호 검증 필요 확인 Service -> PwdEncoder: matches(currentPassword,\nuser.getPasswordHash()) activate PwdEncoder PwdEncoder -> PwdEncoder: bcrypt compare PwdEncoder --> Service: boolean (일치 여부) deactivate PwdEncoder alt 현재 비밀번호 불일치 Service --> Controller: throw InvalidPasswordException\n("현재 비밀번호가 일치하지 않습니다") Controller --> Client: 400 Bad Request\n{"error": "현재 비밀번호가\n일치하지 않습니다"} deactivate Service deactivate Controller else 현재 비밀번호 일치 Service -> Service: 새 비밀번호 유효성 검증\n(8자 이상, 영문/숫자/특수문자) Service -> PwdEncoder: encode(newPassword) activate PwdEncoder PwdEncoder -> PwdEncoder: bcrypt 해싱\n(Cost Factor 10) PwdEncoder --> Service: newPasswordHash deactivate PwdEncoder Service -> Service: user.setPasswordHash(newPasswordHash) end end == 3단계: 기본 정보 업데이트 == alt 이름 변경 Service -> Service: user.setName(newName) end alt 전화번호 변경 Service -> Service: user.setPhoneNumber(newPhoneNumber) note right of Service **향후 구현: 재인증 필요** - SMS 인증 또는 이메일 인증 - 인증 완료 후에만 변경 반영 end note end alt 이메일 변경 Service -> Service: user.setEmail(newEmail) end == 4단계: 매장 정보 업데이트 == Service -> StoreRepo: findByUserId(userId) activate StoreRepo StoreRepo -> UserDB: SELECT * FROM stores\nWHERE user_id = ? activate UserDB UserDB --> StoreRepo: 매장 정보 deactivate UserDB StoreRepo --> Service: Store 엔티티 deactivate StoreRepo alt 매장명 변경 Service -> Service: store.setStoreName(newStoreName) end alt 업종 변경 Service -> Service: store.setIndustry(newIndustry) end alt 주소 변경 Service -> Service: store.setAddress(newAddress) end alt 영업시간 변경 Service -> Service: store.setBusinessHours(newBusinessHours) end == 5단계: 데이터베이스 트랜잭션 == Service -> UserDB: BEGIN TRANSACTION activate UserDB Service -> UserRepo: save(user) activate UserRepo UserRepo -> UserDB: UPDATE users\nSET name = ?, phone_number = ?,\nemail = ?, password_hash = ?,\nupdated_at = NOW()\nWHERE user_id = ? UserDB --> UserRepo: 업데이트 완료 UserRepo --> Service: User 엔티티 deactivate UserRepo Service -> StoreRepo: save(store) activate StoreRepo StoreRepo -> UserDB: UPDATE stores\nSET store_name = ?, industry = ?,\naddress = ?, business_hours = ?,\nupdated_at = NOW()\nWHERE store_id = ? UserDB --> StoreRepo: 업데이트 완료 StoreRepo --> Service: Store 엔티티 deactivate StoreRepo Service -> UserDB: COMMIT TRANSACTION UserDB --> Service: 트랜잭션 커밋 완료 deactivate UserDB == 6단계: 캐시 무효화 (선택적) == note right of Service **캐시 무효화 전략** - 세션 정보는 변경 없음 (JWT 유지) - 프로필 캐시가 있다면 무효화 end note alt 프로필 캐시 사용 중 Service -> Redis: DEL user:profile:{userId} activate Redis Redis --> Service: 캐시 삭제 완료 deactivate Redis end == 7단계: 응답 반환 == Service -> Service: 응답 DTO 생성\n(UpdateProfileResponse) Service --> Controller: UpdateProfileResponse\n(userId, userName, email,\nstoreId, storeName) deactivate Service Controller --> Client: 200 OK\n{"userId": 123,\n"userName": "홍길동",\n"email": "hong@example.com",\n"storeId": 456,\n"storeName": "맛있는집"} deactivate Controller end note over Controller, UserDB **Transaction Rollback 처리** - 트랜잭션 실패 시 자동 Rollback - User/Store UPDATE 중 하나라도 실패 시 전체 롤백 **보안 처리** - 비밀번호 변경: 현재 비밀번호 확인 필수 - JWT 인증: Controller에서 @AuthenticationPrincipal로 userId 추출 - 권한 검증: 본인만 수정 가능 **향후 개선사항** - 전화번호 변경: SMS/이메일 재인증 구현 - 이메일 변경: 이메일 인증 구현 end note @enduml