186 lines
7.0 KiB
Java
186 lines
7.0 KiB
Java
package com.webgame.webgamebackend.service.impl;
|
||
|
||
import com.webgame.webgamebackend.common.auth.AuthenticationProvider;
|
||
import com.webgame.webgamebackend.common.dto.user.UpdateStatusRequest;
|
||
import com.webgame.webgamebackend.common.dto.user.UpdateUserInfoRequest;
|
||
import com.webgame.webgamebackend.common.dto.user.UserInfoResponse;
|
||
import com.webgame.webgamebackend.common.exception.BusinessException;
|
||
import com.webgame.webgamebackend.common.exception.ErrorCode;
|
||
import com.webgame.webgamebackend.entities.UserEntity;
|
||
import com.webgame.webgamebackend.repository.UserRepository;
|
||
import com.webgame.webgamebackend.service.UserService;
|
||
import lombok.RequiredArgsConstructor;
|
||
import lombok.extern.slf4j.Slf4j;
|
||
import org.springframework.stereotype.Service;
|
||
import org.springframework.transaction.annotation.Transactional;
|
||
import org.springframework.web.multipart.MultipartFile;
|
||
|
||
import java.io.IOException;
|
||
import java.nio.file.Files;
|
||
import java.nio.file.Path;
|
||
import java.nio.file.Paths;
|
||
import java.util.Set;
|
||
|
||
/**
|
||
* 用户服务实现
|
||
*/
|
||
@Slf4j
|
||
@Service
|
||
@RequiredArgsConstructor
|
||
public class UserServiceImpl implements UserService {
|
||
|
||
/** 头像存储目录(相对于项目工作目录) */
|
||
private static final String AVATAR_DIR = "uploads/avatars";
|
||
|
||
/**
|
||
* 获取头像存储的绝对路径目录
|
||
*
|
||
* 使用 user.dir 构建绝对路径,避免 Tomcat 临时工作目录导致 FileNotFoundException。
|
||
*/
|
||
private Path getAvatarDir() {
|
||
return Paths.get(System.getProperty("user.dir"), AVATAR_DIR).toAbsolutePath();
|
||
}
|
||
/** 允许的头像文件类型 */
|
||
private static final Set<String> ALLOWED_CONTENT_TYPES = Set.of(
|
||
"image/jpeg", "image/png", "image/webp", "image/gif"
|
||
);
|
||
/** 头像文件大小上限(2MB) */
|
||
private static final long MAX_AVATAR_SIZE = 2 * 1024 * 1024;
|
||
|
||
private final UserRepository userRepository;
|
||
private final AuthenticationProvider authProvider;
|
||
|
||
@Override
|
||
public UserInfoResponse getCurrentUserInfo() {
|
||
String accountId = authProvider.getCurrentUserId();
|
||
log.info("[用户服务] 获取当前登录用户信息, accountId: {}", accountId);
|
||
UserEntity user = userRepository.findByAccountId(accountId)
|
||
.orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));
|
||
return UserInfoResponse.from(user);
|
||
}
|
||
|
||
@Override
|
||
@Transactional
|
||
public UserInfoResponse updateUserInfo(String accountId, UpdateUserInfoRequest request) {
|
||
UserEntity user = userRepository.findByAccountId(accountId)
|
||
.orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));
|
||
|
||
// 仅更新非 null 字段
|
||
if (request.nickName() != null) {
|
||
user.setNickName(request.nickName());
|
||
}
|
||
if (request.avatar() != null) {
|
||
user.setAvatar(request.avatar());
|
||
}
|
||
if (request.signature() != null) {
|
||
user.setSignature(request.signature());
|
||
}
|
||
if (request.age() != null) {
|
||
user.setAge(request.age());
|
||
}
|
||
if (request.sex() != null) {
|
||
user.setSex(request.sex());
|
||
}
|
||
if (request.birthday() != null) {
|
||
user.setBirthday(request.birthday());
|
||
}
|
||
|
||
userRepository.save(user);
|
||
log.info("[用户服务] 更新用户信息, accountId={}", accountId);
|
||
return UserInfoResponse.from(user);
|
||
}
|
||
|
||
@Override
|
||
@Transactional
|
||
public void updateStatus(String accountId, UpdateStatusRequest request) {
|
||
UserEntity user = userRepository.findByAccountId(accountId)
|
||
.orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));
|
||
|
||
user.setStatus(request.status());
|
||
userRepository.save(user);
|
||
log.info("[用户服务] 更新在线状态, accountId={}, status={}", accountId, request.status());
|
||
}
|
||
|
||
@Override
|
||
@Transactional
|
||
public UserInfoResponse uploadAvatar(String accountId, MultipartFile file) {
|
||
// 校验文件类型
|
||
String contentType = file.getContentType();
|
||
if (contentType == null || !ALLOWED_CONTENT_TYPES.contains(contentType)) {
|
||
throw new BusinessException(ErrorCode.AVATAR_INVALID_TYPE);
|
||
}
|
||
|
||
// 校验文件大小
|
||
if (file.getSize() > MAX_AVATAR_SIZE) {
|
||
throw new BusinessException(ErrorCode.AVATAR_TOO_LARGE);
|
||
}
|
||
|
||
UserEntity user = userRepository.findByAccountId(accountId)
|
||
.orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));
|
||
|
||
try {
|
||
// 确保目录存在(使用绝对路径,避免 Tomcat 临时目录问题)
|
||
Path avatarDir = getAvatarDir();
|
||
Files.createDirectories(avatarDir);
|
||
|
||
// 删除旧头像文件(如果存在且不是默认 SVG)
|
||
deleteOldAvatarFile(user.getAvatar());
|
||
|
||
// 保存新头像:uploads/avatars/{accountId}.jpg
|
||
String filename = accountId + ".jpg";
|
||
Path targetPath = avatarDir.resolve(filename);
|
||
file.transferTo(targetPath.toFile());
|
||
|
||
// 更新数据库
|
||
String avatarUrl = "/" + AVATAR_DIR + "/" + filename;
|
||
user.setAvatar(avatarUrl);
|
||
userRepository.save(user);
|
||
|
||
log.info("[用户服务] 头像上传成功, accountId={}, path={}", accountId, avatarUrl);
|
||
return UserInfoResponse.from(user);
|
||
} catch (IOException e) {
|
||
log.error("[用户服务] 头像上传失败, accountId={}", accountId, e);
|
||
throw new BusinessException(ErrorCode.AVATAR_UPLOAD_FAILED, "头像保存失败,请重试");
|
||
}
|
||
}
|
||
|
||
@Override
|
||
@Transactional
|
||
public UserInfoResponse deleteAvatar(String accountId) {
|
||
UserEntity user = userRepository.findByAccountId(accountId)
|
||
.orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));
|
||
|
||
// 删除旧头像文件
|
||
deleteOldAvatarFile(user.getAvatar());
|
||
|
||
// 数据库置空(前端会回退显示默认头像)
|
||
user.setAvatar(null);
|
||
userRepository.save(user);
|
||
|
||
log.info("[用户服务] 头像已删除, accountId={}", accountId);
|
||
return UserInfoResponse.from(user);
|
||
}
|
||
|
||
/**
|
||
* 删除旧的用户头像文件
|
||
*
|
||
* 仅删除 uploads/avatars/ 下的文件,跳过默认 SVG 和非本地路径。
|
||
*
|
||
* @param avatarPath 头像路径,可能为 null
|
||
*/
|
||
private void deleteOldAvatarFile(String avatarPath) {
|
||
if (avatarPath == null || !avatarPath.startsWith("/" + AVATAR_DIR + "/")) {
|
||
return;
|
||
}
|
||
try {
|
||
// 从 URL 路径中提取文件名,使用绝对路径删除
|
||
String filename = avatarPath.substring(avatarPath.lastIndexOf('/') + 1);
|
||
Path oldFile = getAvatarDir().resolve(filename);
|
||
Files.deleteIfExists(oldFile);
|
||
log.debug("[用户服务] 旧头像已删除: {}", oldFile);
|
||
} catch (IOException e) {
|
||
log.warn("[用户服务] 旧头像删除失败: {}", avatarPath, e);
|
||
}
|
||
}
|
||
}
|