package com.emotion.service.impl; import com.emotion.dto.request.LoginRequest; import com.emotion.dto.request.RegisterRequest; import com.emotion.dto.response.AuthResponse; import com.emotion.dto.response.CaptchaResponse; import com.emotion.dto.response.UserInfoResponse; import com.emotion.entity.User; import com.emotion.exception.AuthException; import com.emotion.exception.BusinessException; import com.emotion.exception.CaptchaException; import com.emotion.exception.TokenException; import com.emotion.service.AuthService; import com.emotion.service.UserService; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Base64; import java.util.Random; import java.util.UUID; import java.util.concurrent.TimeUnit; /** * 认证服务实现类 * * @author emotion-museum * @date 2025-07-23 */ @Service public class AuthServiceImpl implements AuthService { @Autowired private UserService userService; @Autowired private RedisTemplate redisTemplate; private static final String CAPTCHA_PREFIX = "captcha:"; private static final String TOKEN_PREFIX = "token:"; private static final String REFRESH_TOKEN_PREFIX = "refresh_token:"; private static final int CAPTCHA_EXPIRE_MINUTES = 5; private static final int TOKEN_EXPIRE_HOURS = 24; private static final int REFRESH_TOKEN_EXPIRE_DAYS = 7; private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); @Override public AuthResponse login(LoginRequest request) { // 验证验证码 if (!validateCaptcha(request.getCaptchaKey(), request.getCaptcha())) { throw new CaptchaException("验证码错误或已过期"); } // 根据账号查询用户 User user = userService.getByAccount(request.getAccount()); if (user == null) { throw new AuthException("账号不存在"); } // 验证密码(这里应该使用加密后的密码比较) if (!verifyPassword(request.getPassword(), user.getPassword())) { throw new AuthException("密码错误"); } // 检查用户状态 if (user.getStatus() != 1) { throw new AuthException("账号已被禁用"); } // 生成令牌 String accessToken = generateAccessToken(user); String refreshToken = generateRefreshToken(user); // 更新用户最后活跃时间 userService.updateLastActiveTime(user.getId(), LocalDateTime.now()); // 构建响应 AuthResponse response = new AuthResponse(); response.setAccessToken(accessToken); response.setRefreshToken(refreshToken); response.setExpiresIn((long) TOKEN_EXPIRE_HOURS * 3600); response.setUserInfo(convertToUserInfoResponse(user)); response.setLoginTime(LocalDateTime.now().format(DATE_TIME_FORMATTER)); return response; } @Override public AuthResponse register(RegisterRequest request) { // 验证验证码 if (!validateCaptcha(request.getCaptchaKey(), request.getCaptcha())) { throw new CaptchaException("验证码错误或已过期"); } // 检查账号是否已存在 if (userService.getByAccount(request.getAccount()) != null) { throw new BusinessException("账号已存在"); } // 检查邮箱是否已存在 if (StringUtils.hasText(request.getEmail()) && userService.getByEmail(request.getEmail()) != null) { throw new BusinessException("邮箱已被使用"); } // 创建用户 User user = userService.createUser( request.getAccount(), StringUtils.hasText(request.getUsername()) ? request.getUsername() : request.getAccount(), encryptPassword(request.getPassword()), request.getEmail(), request.getPhone() ); // 设置昵称 if (StringUtils.hasText(request.getNickname())) { user.setNickname(request.getNickname()); userService.updateById(user); } // 生成令牌 String accessToken = generateAccessToken(user); String refreshToken = generateRefreshToken(user); // 构建响应 AuthResponse response = new AuthResponse(); response.setAccessToken(accessToken); response.setRefreshToken(refreshToken); response.setExpiresIn((long) TOKEN_EXPIRE_HOURS * 3600); response.setUserInfo(convertToUserInfoResponse(user)); response.setLoginTime(LocalDateTime.now().format(DATE_TIME_FORMATTER)); return response; } @Override public UserInfoResponse getCurrentUserInfo(String userId) { User user = userService.getById(userId); if (user == null) { throw new AuthException("用户不存在"); } return convertToUserInfoResponse(user); } @Override public CaptchaResponse generateCaptcha() { String captchaKey = UUID.randomUUID().toString(); String captchaCode = generateCaptchaCode(); // 生成验证码图片 String captchaImage = generateCaptchaImage(captchaCode); // 存储验证码到Redis redisTemplate.opsForValue().set( CAPTCHA_PREFIX + captchaKey, captchaCode.toLowerCase(), CAPTCHA_EXPIRE_MINUTES, TimeUnit.MINUTES ); CaptchaResponse response = new CaptchaResponse(); response.setCaptchaKey(captchaKey); response.setCaptchaImage(captchaImage); response.setExpiresIn((long) CAPTCHA_EXPIRE_MINUTES * 60); return response; } @Override public boolean validateCaptcha(String captchaKey, String captcha) { if (!StringUtils.hasText(captchaKey) || !StringUtils.hasText(captcha)) { return false; } String storedCaptcha = (String) redisTemplate.opsForValue().get(CAPTCHA_PREFIX + captchaKey); if (storedCaptcha == null) { return false; } // 验证成功后删除验证码 redisTemplate.delete(CAPTCHA_PREFIX + captchaKey); return storedCaptcha.equalsIgnoreCase(captcha.trim()); } @Override public boolean logout(String userId, String token) { // 删除访问令牌 redisTemplate.delete(TOKEN_PREFIX + token); // 删除刷新令牌(如果存在) String refreshTokenKey = REFRESH_TOKEN_PREFIX + userId; redisTemplate.delete(refreshTokenKey); return true; } @Override public boolean logoutByToken(String token) { String userId = validateTokenAndGetUserId(token); return logout(userId, token); } /** * 验证令牌并获取用户ID */ private String validateTokenAndGetUserId(String token) { if (!StringUtils.hasText(token)) { throw new TokenException("未提供访问令牌"); } if (!validateToken(token)) { throw new TokenException("访问令牌无效或已过期"); } String userId = getUserIdFromToken(token); if (userId == null) { throw new TokenException("访问令牌无效"); } return userId; } @Override public AuthResponse refreshToken(String refreshToken) { String userId = (String) redisTemplate.opsForValue().get(REFRESH_TOKEN_PREFIX + refreshToken); if (userId == null) { throw new TokenException("刷新令牌无效或已过期"); } User user = userService.getById(userId); if (user == null) { throw new AuthException("用户不存在"); } // 生成新的访问令牌 String newAccessToken = generateAccessToken(user); String newRefreshToken = generateRefreshToken(user); // 删除旧的刷新令牌 redisTemplate.delete(REFRESH_TOKEN_PREFIX + refreshToken); AuthResponse response = new AuthResponse(); response.setAccessToken(newAccessToken); response.setRefreshToken(newRefreshToken); response.setExpiresIn((long) TOKEN_EXPIRE_HOURS * 3600); response.setUserInfo(convertToUserInfoResponse(user)); response.setLoginTime(LocalDateTime.now().format(DATE_TIME_FORMATTER)); return response; } @Override public boolean validateToken(String token) { if (!StringUtils.hasText(token)) { return false; } return redisTemplate.hasKey(TOKEN_PREFIX + token); } @Override public String getUserIdFromToken(String token) { if (!StringUtils.hasText(token)) { return null; } return (String) redisTemplate.opsForValue().get(TOKEN_PREFIX + token); } @Override public String getUsernameFromToken(String token) { String userId = getUserIdFromToken(token); if (userId == null) { return null; } User user = userService.getById(userId); return user != null ? user.getUsername() : null; } /** * 生成访问令牌 */ private String generateAccessToken(User user) { String token = UUID.randomUUID().toString().replace("-", ""); redisTemplate.opsForValue().set( TOKEN_PREFIX + token, user.getId(), TOKEN_EXPIRE_HOURS, TimeUnit.HOURS ); return token; } /** * 生成刷新令牌 */ private String generateRefreshToken(User user) { String refreshToken = UUID.randomUUID().toString().replace("-", ""); redisTemplate.opsForValue().set( REFRESH_TOKEN_PREFIX + refreshToken, user.getId(), REFRESH_TOKEN_EXPIRE_DAYS, TimeUnit.DAYS ); return refreshToken; } /** * 生成验证码 */ private String generateCaptchaCode() { Random random = new Random(); StringBuilder code = new StringBuilder(); for (int i = 0; i < 4; i++) { code.append(random.nextInt(10)); } return code.toString(); } /** * 生成验证码图片 */ private String generateCaptchaImage(String code) { int width = 120; int height = 40; BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D g = image.createGraphics(); // 设置背景色 g.setColor(Color.WHITE); g.fillRect(0, 0, width, height); // 设置字体 g.setFont(new Font("Arial", Font.BOLD, 20)); g.setColor(Color.BLACK); // 绘制验证码 for (int i = 0; i < code.length(); i++) { g.drawString(String.valueOf(code.charAt(i)), 20 + i * 20, 25); } // 添加干扰线 Random random = new Random(); g.setColor(Color.GRAY); for (int i = 0; i < 5; i++) { int x1 = random.nextInt(width); int y1 = random.nextInt(height); int x2 = random.nextInt(width); int y2 = random.nextInt(height); g.drawLine(x1, y1, x2, y2); } g.dispose(); // 转换为Base64 try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageIO.write(image, "png", baos); byte[] imageBytes = baos.toByteArray(); return "data:image/png;base64," + Base64.getEncoder().encodeToString(imageBytes); } catch (IOException e) { throw new CaptchaException("生成验证码图片失败"); } } /** * 验证密码 */ private boolean verifyPassword(String rawPassword, String encodedPassword) { // 这里应该使用BCrypt等加密算法进行密码验证 // 简化实现,实际项目中应该使用加密后的密码 return rawPassword.equals(encodedPassword); } /** * 加密密码 */ private String encryptPassword(String rawPassword) { // 这里应该使用BCrypt等加密算法进行密码加密 // 简化实现,实际项目中应该使用加密算法 return rawPassword; } /** * 转换为用户信息响应 */ private UserInfoResponse convertToUserInfoResponse(User user) { UserInfoResponse response = new UserInfoResponse(); BeanUtils.copyProperties(user, response); if (user.getCreateTime() != null) { response.setCreateTime(user.getCreateTime().format(DATE_TIME_FORMATTER)); } if (user.getLastActiveTime() != null) { response.setLastActiveTime(user.getLastActiveTime().format(DATE_TIME_FORMATTER)); } return response; } }