package com.openisle.service; import com.openisle.config.CachingConfig; import com.openisle.exception.FieldException; import com.openisle.model.Role; import com.openisle.model.User; import com.openisle.repository.UserRepository; import com.openisle.search.SearchIndexEventPublisher; import com.openisle.service.AvatarGenerator; import com.openisle.service.PasswordValidator; import com.openisle.service.UsernameValidator; import com.openisle.util.VerifyType; import java.util.Objects; import java.util.Optional; import java.util.Random; import java.util.concurrent.TimeUnit; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class UserService { private final UserRepository userRepository; private final PasswordValidator passwordValidator; private final UsernameValidator usernameValidator; private final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); private final ImageUploader imageUploader; private final AvatarGenerator avatarGenerator; private final RedisTemplate redisTemplate; private final EmailSender emailService; private final SearchIndexEventPublisher searchIndexEventPublisher; public User register( String username, String email, String password, String reason, com.openisle.model.RegisterMode mode ) { usernameValidator.validate(username); passwordValidator.validate(password); // ── 先按用户名查 ────────────────────────────────────────── Optional byUsername = userRepository.findByUsername(username); if (byUsername.isPresent()) { User u = byUsername.get(); if (u.isVerified()) { // 已验证 → 直接拒绝 throw new FieldException("username", "User name already exists"); } // 未验证 → 允许“重注册”:覆盖必要字段并重新发验证码 u.setEmail(email); // 若不允许改邮箱可去掉 u.setPassword(passwordEncoder.encode(password)); // u.setVerificationCode(genCode()); u.setRegisterReason(reason); u.setApproved(mode == com.openisle.model.RegisterMode.DIRECT); User saved = userRepository.save(u); searchIndexEventPublisher.publishUserSaved(saved); return saved; } // ── 再按邮箱查 ─────────────────────────────────────────── Optional byEmail = userRepository.findByEmail(email); if (byEmail.isPresent()) { User u = byEmail.get(); if (u.isVerified()) { // 已验证 → 直接拒绝 throw new FieldException("email", "User email already exists"); } // 未验证 → 允许“重注册” u.setUsername(username); // 若不允许改用户名可去掉 u.setPassword(passwordEncoder.encode(password)); // u.setVerificationCode(genCode()); u.setRegisterReason(reason); u.setApproved(mode == com.openisle.model.RegisterMode.DIRECT); User saved = userRepository.save(u); searchIndexEventPublisher.publishUserSaved(saved); return saved; } // ── 完全新用户 ─────────────────────────────────────────── User user = new User(); user.setUsername(username); user.setEmail(email); user.setPassword(passwordEncoder.encode(password)); user.setRole(Role.USER); user.setVerified(false); // user.setVerificationCode(genCode()); user.setAvatar(avatarGenerator.generate(username)); user.setRegisterReason(reason); user.setApproved(mode == com.openisle.model.RegisterMode.DIRECT); User saved = userRepository.save(user); searchIndexEventPublisher.publishUserSaved(saved); return saved; } public User registerWithInvite(String username, String email, String password) { User user = register(username, email, password, "", com.openisle.model.RegisterMode.DIRECT); user.setVerified(true); // user.setVerificationCode(genCode()); User saved = userRepository.save(user); searchIndexEventPublisher.publishUserSaved(saved); return saved; } private String genCode() { return String.format("%06d", new Random().nextInt(1000000)); } /** * 将验证码存入缓存,并发送邮件 * @param user */ public void sendVerifyMail(User user, VerifyType verifyType) { // 缓存验证码 String code = genCode(); String key; String subject; String content = "您的验证码是:" + code; // 注册类型 if (verifyType.equals(VerifyType.REGISTER)) { key = CachingConfig.VERIFY_CACHE_NAME + ":register:code:" + user.getUsername(); subject = "在网站填写验证码以验证(有效期为5分钟)"; } else { // 重置密码 key = CachingConfig.VERIFY_CACHE_NAME + ":reset_password:code:" + user.getUsername(); subject = "请填写验证码以重置密码(有效期为5分钟)"; } redisTemplate.opsForValue().set(key, code, 5, TimeUnit.MINUTES); // 五分钟后验证码过期 emailService.sendEmail(user.getEmail(), subject, content); } /** * 验证code是否正确 * @param user * @param code * @param verifyType * @return */ public boolean verifyCode(User user, String code, VerifyType verifyType) { // 生成key String key1 = VerifyType.REGISTER.equals(verifyType) ? ":register:code:" : ":reset_password:code:"; String key = CachingConfig.VERIFY_CACHE_NAME + key1 + user.getUsername(); // 这里不能使用getAndDelete,需要6.x版本 String cachedCode = (String) redisTemplate.opsForValue().get(key); // 如果校验code过期或者不存在 // 或者校验code不一致 if (Objects.isNull(cachedCode) || !cachedCode.equals(code)) { return false; } // 注册模式需要设置已经确认 if (VerifyType.REGISTER.equals(verifyType)) { user.setVerified(true); userRepository.save(user); } // 走到这里说明验证成功删除验证码 redisTemplate.delete(key); return true; } public Optional authenticate(String username, String password) { return userRepository .findByUsername(username) .filter(User::isVerified) .filter(User::isApproved) .filter(user -> passwordEncoder.matches(password, user.getPassword())); } public boolean matchesPassword(User user, String rawPassword) { return passwordEncoder.matches(rawPassword, user.getPassword()); } public Optional findByUsername(String username) { return userRepository.findByUsername(username); } public Optional findByEmail(String email) { return userRepository.findByEmail(email); } public Optional findById(Long id) { return userRepository.findById(id); } public Optional findByIdentifier(String identifier) { if (identifier.matches("\\d+")) { return userRepository.findById(Long.parseLong(identifier)); } return userRepository.findByUsername(identifier); } public User updateAvatar(String username, String avatarUrl) { User user = userRepository .findByUsername(username) .orElseThrow(() -> new com.openisle.exception.NotFoundException("User not found")); String old = user.getAvatar(); user.setAvatar(avatarUrl); User saved = userRepository.save(user); if (old != null && !old.equals(avatarUrl)) { imageUploader.removeReferences(java.util.Set.of(old)); } if (avatarUrl != null) { imageUploader.addReferences(java.util.Set.of(avatarUrl)); } return saved; } public User updateReason(String username, String reason) { User user = userRepository .findByUsername(username) .orElseThrow(() -> new com.openisle.exception.NotFoundException("User not found")); user.setRegisterReason(reason); User saved = userRepository.save(user); searchIndexEventPublisher.publishUserSaved(saved); return saved; } public User updateProfile(String currentUsername, String newUsername, String introduction) { User user = userRepository .findByUsername(currentUsername) .orElseThrow(() -> new com.openisle.exception.NotFoundException("User not found")); if (newUsername != null && !newUsername.equals(currentUsername)) { usernameValidator.validate(newUsername); userRepository .findByUsername(newUsername) .ifPresent(u -> { throw new FieldException("username", "User name already exists"); }); user.setUsername(newUsername); } if (introduction != null) { user.setIntroduction(introduction); } return userRepository.save(user); } public User updatePassword(String username, String newPassword) { passwordValidator.validate(newPassword); User user = userRepository .findByUsername(username) .orElseThrow(() -> new com.openisle.exception.NotFoundException("User not found")); user.setPassword(passwordEncoder.encode(newPassword)); return userRepository.save(user); } /** * Get all administrator accounts. */ public java.util.List getAdmins() { return userRepository.findByRole(Role.ADMIN); } }