بدرسة رح نبني نظام Onboarding للموظفين عن طريق إرسال إيميل تلقائي يحتوي على رابط إنشاء الحساب.
الفلو الكامل بيصير كالتالي:
الرابط يحتوي على Token خاص بالموظف يضمن إنه بس هو يقدر ينشئ الحساب.
بنضيف حقلين للـ Employee Entity:
@Column(name = "is_verified", columnDefinition = "boolean default false")
private Boolean isVerified = false;
@Column(name = "account_creation_token")
private String accountCreationToken;
isVerified: هل الموظف عمل Sign Up بعد؟ Default = falseaccountCreationToken: الـ Token اللي رح نرفقه بالرابطالـ columnDefinition = "boolean default false" بيضمن إنه حتى لو نسينا نحط قيمة، قاعدة البيانات بتحط false تلقائياً.
أولاً، نضيف الـ Dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
اشترك في النشرة البريدية
دروس جديدة، مقالات، وأدوات مباشرة لبريدك.
بالـ application.properties:
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.username=${GMAIL_APP_USERNAME}
spring.mail.password=${GMAIL_APP_PASSWORD}
backend.origin=http://localhost:8080الـ Username والـ Password محفوظين كـ Environment Variables لأنهم معلومات سرية. للـ Password: ما تستخدموا الباسورد الحقيقي لحساب Gmail، استخدموا App Password خاص تنشئوه من إعدادات Google (ابحثوا "Google App Password").
@Service
public class EmailService {
@Autowired
private JavaMailSender mailSender;
@Value("${spring.mail.username}")
private String fromEmail;
@Value("${backend.origin}")
private String origin;
public void sendAccountCreationEmail(String toEmail, String token) {
String link = origin + "/auth/sign-up?token=" + token;
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(fromEmail);
message.setTo(toEmail);
message.setSubject("Create Your Account");
message.setText("Hi, please create your account using the link below:\n" + link);
mailSender.send(message);
}
}@Override
public Employee create(EmployeeCreate dto) {
String token = UUID.randomUUID().toString();
Employee employee = new Employee();
employee.setFirstName(dto.firstName());
employee.setLastName(dto.lastName());
employee.setEmail(dto.email());
employee.setIsVerified(false);
employee.setAccountCreationToken(token);
employeeRepository.save(employee);
emailService.sendAccountCreationEmail(employee.getEmail(), token);
return employee;
}بنستخدم UUID.randomUUID().toString() للـ Token. ممكن تستخدموا JWT Token بديلاً لو بدكن تحكموا بوقت الانتهاء.
بدل ما الـ Client يبعث employeeId بالـ Request، صار يبعث الـ Token كـ Query String:
@PostMapping("/sign-up")
public ResponseEntity<GlobalResponse<String>> signUp(
@Valid @RequestBody SignUpRequest request,
@RequestParam String token) {
authService.signUp(request, token);
return new ResponseEntity<>(
new GlobalResponse<>("Signed up successfully"), HttpStatus.CREATED
);
}وبالـ Repository نضيف:
Optional<Employee> findOneByAccountCreationToken(String token);public void signUp(SignUpRequest request, String token) {
Employee employee = employeeRepository
.findOneByAccountCreationToken(token)
.orElseThrow(() -> CustomResponseException.badRequest("Invalid token"));
UserAccount account = new UserAccount();
account.setUsername(request.username());
account.setPassword(passwordEncoder.encode(request.password()));
account.setEmployee(employee);
userAccountRepository.save(account);
// نمسح التوكن بعد الاستخدام ونضع الموظف كـ verified
employee.setAccountCreationToken(null);
employee.setIsVerified(true);
employeeRepository.save(employee);
}لاحظوا إنه بهال Method عندنا أكثر من عملية على قاعدة البيانات وعملية إيميل خارجية. إذا فشل الإيميل بعد ما تم الحفظ، صار عندنا Inconsistency. الحل هو استخدام @Transactional - اللي بتحكي عنها بالدرس القادم بتفصيل.
الإيميل يوصل للموظف بالشكل التالي وبيحتوي على رابط Sign Up مع الـ Token بالـ Query String.