법인 회원 초대 대응

This commit is contained in:
Rinjae
2025-11-24 22:23:04 +09:00
parent a7d94c4d92
commit d7a32e1dcf
16 changed files with 2293 additions and 20 deletions
@@ -0,0 +1,99 @@
flowchart TD
Start([법인 관리자가 사용자 초대]) --> GenerateToken[8글자 토큰 생성<br/>예: K7MNPQ2R<br/>UserInvitation 저장]
GenerateToken --> SendEmail[이메일 발송<br/>- 링크 포함<br/>- 8글자 토큰 표시]
SendEmail --> UserType{사용자 유형}
%% 신규 회원 플로우
UserType -->|신규 회원<br/>미가입자| NewUserPath{초대 확인 방법}
NewUserPath -->|폐쇄망 환경| LoginPage1[로그인 페이지 접근]
LoginPage1 --> InviteLink1["초대를 받으셨나요?" 링크 클릭]
InviteLink1 --> InputCodePage1[초대코드 입력 페이지]
InputCodePage1 --> InputCode1[8자리 코드 입력<br/>예: K7MNPQ2R]
InputCode1 --> ValidateCode1{코드 유효성 검증}
ValidateCode1 -->|유효하지 않음| Error1[에러: 유효하지 않은 초대코드]
ValidateCode1 -->|유효| SignupPage1[회원가입 페이지<br/>/signup/portalUser?invitation=CODE]
NewUserPath -->|이메일 링크| EmailLink1[이메일 링크 클릭<br/>/signup/portalUser?invitation=CODE]
EmailLink1 --> SignupPage1
SignupPage1 --> RegisterForm[가입 정보 입력<br/>- 이메일 자동 입력됨<br/>- 법인명 표시<br/>- 약관 동의]
RegisterForm --> RegisterComplete[회원가입 완료<br/>ROLE_CORP_USER로 생성]
RegisterComplete --> NewUserEnd([가입 완료 안내])
%% 기존 회원 플로우
UserType -->|기존 회원<br/>ROLE_USER| ExistingUserPath{초대 확인 방법}
ExistingUserPath -->|폐쇄망 환경| LoginPage2[로그인 페이지 접근]
LoginPage2 --> InviteLink2["초대를 받으셨나요?" 링크 클릭]
InviteLink2 --> InputCodePage2[초대코드 입력 페이지]
InputCodePage2 --> CheckLogin1{로그인 여부}
CheckLogin1 -->|미로그인| RedirectLogin1[로그인 페이지로 리다이렉트<br/>코드 저장]
RedirectLogin1 --> Login1[로그인]
Login1 --> AfterLogin1[로그인 성공]
CheckLogin1 -->|로그인됨| InputCode2[8자리 코드 입력]
InputCode2 --> ValidateCode2{코드 유효성 검증}
ValidateCode2 -->|유효하지 않음| Error2[에러: 유효하지 않은 초대코드]
ValidateCode2 -->|유효| DecisionPage1[초대 수락 페이지<br/>/signup/decision_process]
ExistingUserPath -->|이메일 링크| EmailLink2[이메일 링크 클릭<br/>/signup/decision?invitation=CODE]
EmailLink2 --> DecisionPagePublic[초대 수락 페이지<br/>비로그인 상태]
DecisionPagePublic --> RedirectLogin2[로그인 유도]
RedirectLogin2 --> Login2[로그인]
Login2 --> AfterLogin2[로그인 성공]
ExistingUserPath -->|직접 로그인| DirectLogin[로그인 페이지에서 로그인]
DirectLogin --> Login3[로그인]
Login3 --> AfterLogin3[로그인 성공]
%% 로그인 후 자동 초대 확인
AfterLogin1 --> CheckInvitation[초대 코드 자동 확인<br/>findFirstByInvitationEmailAndStatus]
AfterLogin2 --> CheckInvitation
AfterLogin3 --> CheckInvitation
CheckInvitation --> HasInvitation{초대 코드 존재?}
HasInvitation -->|없음| NormalHome[일반 홈페이지]
HasInvitation -->|있음| AutoRedirect[자동으로 초대 수락 페이지로 이동<br/>/signup/decision_process]
AutoRedirect --> DecisionPage1
DecisionPage1 --> UserDecision{사용자 선택}
UserDecision -->|수락| AcceptProcess[초대 수락 처리<br/>1. PortalOrg 변경<br/>2. ROLE_CORP_USER 전환<br/>3. 약관 재동의]
UserDecision -->|거절| RejectProcess[초대 거절 처리<br/>Invitation.status = REJECTED]
AcceptProcess --> CompleteInvitation[Invitation.status = COMPLETED]
CompleteInvitation --> LogoutPrompt[로그아웃 후 재로그인 안내]
LogoutPrompt --> ExistingUserEnd([초대 수락 완료])
RejectProcess --> RejectEnd([초대 거절 완료])
%% 에러 처리
Error1 --> ErrorEnd([처리 종료])
Error2 --> ErrorEnd
%% 스타일링
style Start fill:#e1f5e1
style NewUserEnd fill:#c8e6c9
style ExistingUserEnd fill:#c8e6c9
style RejectEnd fill:#fff9c4
style ErrorEnd fill:#ffcdd2
style Error1 fill:#ffcdd2
style Error2 fill:#ffcdd2
style GenerateToken fill:#bbdefb
style AcceptProcess fill:#c5cae9
style CheckInvitation fill:#ffe0b2
style AutoRedirect fill:#ffe0b2
style LoginPage1 fill:#e1bee7
style LoginPage2 fill:#e1bee7
style InviteLink1 fill:#f8bbd0
style InviteLink2 fill:#f8bbd0
style InputCodePage1 fill:#f8bbd0
style InputCodePage2 fill:#f8bbd0
+44
View File
@@ -0,0 +1,44 @@
flowchart TD
Start([법인 관리자가 사용자 초대 시작]) --> CheckUser{가입된 사용자인가?}
CheckUser -->|아니오| Error1[에러: 가입된 사용자만 초대 가능]
CheckUser -->|예| CheckRole{이미 법인 사용자인가?}
CheckRole -->|예| Error2[에러: 이미 기관에 등록된 사용자]
CheckRole -->|아니오 ROLE_USER| CreateInvitation[8글자 토큰 생성<br/>UserInvitation 저장<br/>상태: PENDING]
CreateInvitation --> SendEmail[이메일 발송<br/>UserInvitationEvent]
SendEmail --> UserReceive{사용자가 토큰 받음}
UserReceive -->|폐쇄망| ManualInput[8글자 토큰 직접 입력<br/>예: K7MNPQ2R]
UserReceive -->|이메일 링크| EmailLink[Base64 인코딩된<br/>링크 클릭]
ManualInput --> DecodeToken[토큰 디코딩<br/>decodeInvitationToken]
EmailLink --> DecodeToken
DecodeToken --> ValidateToken{토큰 유효성 검증}
ValidateToken -->|무효| Error3[에러: 유효하지 않은 초대]
ValidateToken -->|유효 & PENDING| ShowDecision[약관 동의 페이지<br/>/signup/decision]
ShowDecision --> UserDecision{사용자 선택}
UserDecision -->|수락| ProcessAccept[processInvitation: accept]
UserDecision -->|거절| ProcessReject[processInvitation: reject]
ProcessAccept --> UpdateUser[1. PortalOrg 변경<br/>2. ROLE_CORP_USER로 전환<br/>3. 약관 재동의 PRIVACY_COLLECT_ORG]
UpdateUser --> CompleteInvitation[Invitation 상태:<br/>COMPLETED + completeDate]
CompleteInvitation --> Success([초대 수락 완료<br/>로그아웃 후 재로그인])
ProcessReject --> RejectInvitation[Invitation 상태:<br/>REJECTED + completeDate]
RejectInvitation --> End([초대 거절 완료])
style Start fill:#e1f5e1
style Success fill:#c8e6c9
style End fill:#fff9c4
style Error1 fill:#ffcdd2
style Error2 fill:#ffcdd2
style Error3 fill:#ffcdd2
style CreateInvitation fill:#bbdefb
style UpdateUser fill:#c5cae9
+64
View File
@@ -0,0 +1,64 @@
sequenceDiagram
actor 법인관리자
participant Portal as Portal UI
participant UserManFacade
participant UserInvitationRepo as UserInvitation Repository
participant MessageService as Message Handler
participant Email as Email System
actor 초대받은사용자
participant UserRegisterController
participant UserRegisterFacade
participant PortalUserRepo as PortalUser Repository
법인관리자->>Portal: 사용자 이메일 입력<br/>초대 요청
Portal->>UserManFacade: sendInvitation(sender, emailAddr)
UserManFacade->>PortalUserRepo: findByEmailAddr(emailAddr)
alt 사용자가 없거나 이미 법인 사용자
UserManFacade-->>Portal: 에러 반환
Portal-->>법인관리자: 초대 불가 알림
else 일반 사용자 ROLE_USER
UserManFacade->>UserManFacade: generateEightCharToken()<br/>예: K7MNPQ2R
UserManFacade->>UserInvitationRepo: save(UserInvitation)<br/>status: PENDING<br/>expiresOn: 7일 후
UserManFacade->>MessageService: publishEvent(UserInvitationEvent)
MessageService->>Email: 초대 이메일 발송<br/>(법인명, 관리자명, 토큰)
UserManFacade-->>Portal: 초대 완료
Portal-->>법인관리자: 초대 성공 알림
end
Email->>초대받은사용자: 초대 이메일 수신
alt 폐쇄망 환경
초대받은사용자->>Portal: 8글자 토큰 직접 입력<br/>/signup/decision?invitation=K7MNPQ2R
else 이메일 링크
초대받은사용자->>Portal: 링크 클릭<br/>/signup/decision?invitation=BASE64TOKEN
end
Portal->>UserRegisterController: showDecisionPage(invitationToken)
UserRegisterController->>UserRegisterController: decodeInvitationToken()<br/>(Base64 or 직접입력 처리)
UserRegisterController->>UserInvitationRepo: findByToken(decodedToken)
alt 토큰 무효 or 상태가 PENDING 아님
UserRegisterController-->>Portal: 에러: 유효하지 않은 초대
Portal-->>초대받은사용자: 에러 페이지
else 토큰 유효
UserRegisterController->>PortalUserRepo: findByEmailAddr()
UserRegisterController-->>Portal: 약관 동의 페이지<br/>(법인명, 사용자명 표시)
Portal-->>초대받은사용자: 수락/거절 선택 화면
end
초대받은사용자->>Portal: 수락 or 거절 선택
Portal->>UserRegisterController: processInvitation(action)
UserRegisterController->>UserRegisterFacade: processInvitation(action, invitation)
alt 수락
UserRegisterFacade->>PortalUserRepo: findByEmailAddr()
UserRegisterFacade->>PortalUserRepo: updateUserToCorpUser()<br/>(PortalOrg 변경<br/>ROLE_CORP_USER 전환)
UserRegisterFacade->>UserInvitationRepo: save(invitation)<br/>status: COMPLETED<br/>completeDate: now()
UserRegisterFacade-->>Portal: 수락 성공
Portal-->>초대받은사용자: 성공 메시지<br/>로그아웃 후 재로그인 안내
else 거절
UserRegisterFacade->>UserInvitationRepo: save(invitation)<br/>status: REJECTED<br/>completeDate: now()
UserRegisterFacade-->>Portal: 거절 처리 완료
Portal-->>초대받은사용자: 거절 완료 메시지
end
+20
View File
@@ -0,0 +1,20 @@
stateDiagram-v2
[*] --> 초대생성: 법인관리자가 초대 시작
초대생성 --> PENDING: 8글자 토큰 생성<br/>UserInvitation 저장
PENDING --> 토큰전달: 이메일 발송
토큰전달 --> 사용자접근: 폐쇄망 직접입력 or<br/>이메일 링크 클릭
사용자접근 --> 약관동의: 토큰 유효성 검증 성공
사용자접근 --> 에러: 토큰 무효
약관동의 --> COMPLETED: 사용자 수락<br/>ROLE_CORP_USER 전환<br/>PortalOrg 변경
약관동의 --> REJECTED: 사용자 거절
COMPLETED --> [*]: 초대 완료
REJECTED --> [*]: 초대 거절
PENDING --> CANCELED: 관리자가 초대 취소
CANCELED --> [*]: 초대 취소
에러 --> [*]: 처리 종료