
PEANUT의 메인 기능 중 하나는 보호자-환자 관리 시스템입니다.
PEANUT 서비스의 보호자-환자 관리란? 환자의 당수치,복약,인슐린 투약 등 다양한 정보를 환자와 보호자가 서로 공유하며 환자의 상태를 모니터링하고, 관리하는 기능입니다.
'환자와 보호자를 연결하기위해 어떠한 방법이 있을까?'라는 의문점을 두고, 많은 방법을 생각했습니다.
다양한 방법을 생각했지만, 이메일 인증을 수행하여 환자와 보호자를 연결하는 방법을 선택했습니다.
간단하게 요약하자면,
1. 보편적 사용성: 대부분의 사용자들이 이미 익숙하게 사용하는 수단입니다.
2. 안전성: 암호화와 인증 절차로 개인정보 보호에 유리합니다.
3. 비용 절감: 문자 메시지 인증에 비해 경제적입니다.
4. 확장 가능성: 인증 외에 다양한 정보와 알림을 제공할 수 있는 장점이 있습니다.
5. 높은 성공률: 전송 실패율이 낮고 안정적입니다.
이와 같은 이유로 이메일 인증을 통한 보호자-환자 연결 방식은 PEANUT서비스에 매우 적합하다고 판단하였습니다.
<보호자>
1. 보호자가 환자의 이메일을 입력합니다.
2. 이메일에 관한 환자의 정보를 반환하고, 환자가 맞다면 환자에게 연결 요청을 보냅니다.
<환자>
3. 환자는 이메일로 온 인증 코드를 입력한다.
매우 간단한 흐름으로 보호자와 환자를 연결할 수 있습니다.
보호자가 환자에게 연결 요청을 보내면 연결 요청 대기 테이블에 연결 요청 정보가 저장이 됩니다.
환자가 연결 요청을 받기 전까진 '대기',요청 수락하면 '승인'으로 업데이트 됩니다.
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class ConnectionWaiting {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String patientName;
private String patientEmail;
private String inviteCode;
private String guardianEmail;
private String status;
}
보호자와 환자의 엔티티는 따로 관리하지 않습니다. 그 이유는 회원가입을 하는 순간 모두 동일한 환자의 역할이지만 유저가 다른 유저에게 환자 연결 요청을 하여 연결을 맺는 순간 그 유저(연결 요청 송신자)는 보호자 페이지도 접속할 수 있습니다. 즉 동일한 User엔티티에서 관리하지만 따로 연결 테이블을 만들어 연결된 보호자와 환자를 관리하도록 하였습니다.
public class PatientGuardian {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private boolean verified;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "patient_id")
private User patient;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "guardian_id")
private User guardian;
public PatientGuardian (User patient, User guardian,boolean verified) {
this.patient = patient;
this.guardian = guardian;
this.verified = verified;
}
}
POP3/IMAP 설정 -> 저번 포스팅에서 다룬 이메일인증을 위한 환경설정을 다뤘기 때문에 생략하도록 하겠습니다.
이메일 인증 환경 설정
EmailConfig
EmailConfig는 이메일을 전송하기 위한 JavaMailSender를 설정합니다. @Configuration을 통해 이메일 서버 설정을 로드하고, JavaMailSender를 생성해 이메일 전송에 필요한 속성들을 정의합니다.
@Configuration
public class EmailConfig {
@Value("${mail.host}")
private String host;
@Value("${mail.port}")
private int port;
@Value("${mail.username}")
private String username;
@Value("${mail.password}")
private String password;
@Value("${mail.auth}")
private boolean auth;
@Value("${mail.starttls.enable}")
private boolean starttlsEnable;
@Value("${mail.timeout}")
private int timeout;
@Bean
public JavaMailSender javaMailSender(){
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost(host);
mailSender.setPort(port);
mailSender.setUsername(username);
mailSender.setPassword(password);
mailSender.setDefaultEncoding("UTF-8");
mailSender.setJavaMailProperties(getMailProperties());
return mailSender;
}
private Properties getMailProperties(){
Properties properties = new Properties();
//"mail.smtp.auth": 이메일 서버가 사용자 인증을 필요로 하는지 여부를 설정 변수 auth의 값에 따라 true 또는 false가 될 수 있음.
properties.put("mail.smtp.auth", auth);
properties.put("mail.host",host);
properties.put("mail.username",username);
properties.put("mail.password",password);
//"mail.smtp.starttls.enable": SMTP 서버가 STARTTLS 명령을 사용하여 보안 연결을 시작할 수 있는지 여부를 설정.. 변수 starttlsEnable에 의해 결정.
properties.put("mail.enable", starttlsEnable);
//"mail.smtp.timeout": 메일 서버로부터 응답을 기다리는 타임아웃 시간(밀리초 단위)을 설정. 변수 timeout에 지정된 값으로 설정.
properties.put("mail.timeout", timeout);
return properties;
}
}
getPatientConnectingInfo 메서드는 보호자가 초대를 보내기 전에 환자의 이메일을 통해 환자 정보를 확인하는 기능을 제공합니다. 확인된 환자 정보는 세션에 저장됩니다.
@Override
public PatientConnectingResponse getPatientConnectingInfo(String email , HttpServletRequest request) {
User user = jwtAuthenticationService.authenticationToken(request).get();
log.info("[userEmail] : {}",user.getEmail());
//환자 이메일
Optional<User> patient = userDao.findUserByEmail(email);
String patientEmail = patient.get().getEmail();
PatientConnectingResponse response = userDao.findPatientConnecting(patientEmail);
request.getSession().setAttribute("email", email);
return response;
}
@Override
public Map<String, String> sendInviteCode(HttpServletRequest request) throws Exception {
Optional<User> user = jwtAuthenticationService.authenticationToken(request);
String email = (String)request.getSession().getAttribute("email");
Optional<User> patient = userDao.findUserByEmail(email);
String ePw = make_InviteCode();
MimeMessage message = createMessage(user.get().getUserName(),email,ePw);
try{
javaMailSender.send(message);
ConnectionWaiting saveConnectionWaiting = new ConnectionWaiting(
patient.get().getUserName(),
patient.get().getEmail(),
ePw,
"대기중",
user.get().getEmail()
);
connectionWaitngDao.save(saveConnectionWaiting);
}catch (MailException e){
e.printStackTrace();
throw new IllegalArgumentException();
}
Map<String,String>response = new HashMap<>();
response.put("GuardianName",user.get().getUserName());
response.put("Confirmation : ", ePw);
return response;
}
@Override
public ResultDto confirmGuardianRelation(String inviteCode, HttpServletRequest request) {
ResultDto resultDto = new ResultDto();
// ConnectionWaiting 엔티티에서 환자의 이메일과 초대 코드를 사용하여 정보 조회
Optional<ConnectionWaiting> connectionWaitingOpt = connectionWaitingRepository.findByInviteCode(inviteCode);
ConnectionWaiting connectionWaiting = connectionWaitingOpt.get();
String patientEmail = connectionWaitingOpt.get().getPatientEmail();
// 환자 정보 조회
Optional<User> patientOpt = userRepository.findByEmail(patientEmail);
User patient = patientOpt.get();
// 보호자 이메일을 통해 보호자 정보 조회
Optional<User> guardianOpt = userRepository.findByEmail(connectionWaiting.getGuardianEmail());
User guardian = guardianOpt.get();
if (!connectionWaitingOpt.isPresent()) {
return createFailureResult(resultDto, "인증 코드가 올바르지 않습니다.");
}
if (!patientOpt.isPresent()) {
return createFailureResult(resultDto, "환자를 찾을 수 없습니다.");
}
if (!guardianOpt.isPresent()) {
return createFailureResult(resultDto, "보호자를 찾을 수 없습니다.");
}
// PatientGuardian 엔티티에 보호자-환자 관계 저장
PatientGuardian patientGuardian = new PatientGuardian(
patient, guardian, true);
patientGuardianDao.save(patientGuardian);
connectionWaiting.setStatus("승인");
connectionWaitngDao.save(connectionWaiting);
resultDto.setDetailMessage("보호자와 환자의 관계가 성공적으로 연결되었습니다.");
resultDto.setSuccess(true);
return resultDto;
}
iywrD1R
1. 보호자 - 환자 정보 확인하기(유저 1)

2.보호자 - 이메일 전송하기
보호자가 이메일을 전송하면 테스트로 인증번호와 보호자이름을 반환하도록 하였습니다.

3. 환자 - 이메일 확인하기 (유저 2)
아래 사진과 같이 환자의 이메일로 인증코드가 온 것을 확인할 수 있습니다.

4. 인증번호 검증 및 연결하기
이렇게 연결이 된 것을 확인할 수 있습니다.

대기 데이터 저장 테이블과 보호자-환자 테이블에 값이 잘 저장되었습니다.


이메일 인증으로 쉽게 환자-보호자 연결 기능을 만들었지만, 굳이 이메일이 아니더라도 다른 방법을 사용하여 사용자 연결을 쉽게 할 수 있을 것 같습니다.
추후에 인증코드 관련 데이터 테이블은 레디스로 관리하도록 디벨롭 할 예정입니다.