修改后台权限
This commit is contained in:
@@ -68,6 +68,18 @@ public class AdminController {
|
||||
return ApiResponse.success(adminService.updateUserPassword(userId, request.newPassword()));
|
||||
}
|
||||
|
||||
@PatchMapping("/users/{userId}/storage-quota")
|
||||
public ApiResponse<AdminUserResponse> updateUserStorageQuota(@PathVariable Long userId,
|
||||
@Valid @RequestBody AdminUserStorageQuotaUpdateRequest request) {
|
||||
return ApiResponse.success(adminService.updateUserStorageQuota(userId, request.storageQuotaBytes()));
|
||||
}
|
||||
|
||||
@PatchMapping("/users/{userId}/max-upload-size")
|
||||
public ApiResponse<AdminUserResponse> updateUserMaxUploadSize(@PathVariable Long userId,
|
||||
@Valid @RequestBody AdminUserMaxUploadSizeUpdateRequest request) {
|
||||
return ApiResponse.success(adminService.updateUserMaxUploadSize(userId, request.maxUploadSizeBytes()));
|
||||
}
|
||||
|
||||
@PostMapping("/users/{userId}/password/reset")
|
||||
public ApiResponse<AdminPasswordResetResponse> resetUserPassword(@PathVariable Long userId) {
|
||||
return ApiResponse.success(adminService.resetUserPassword(userId));
|
||||
|
||||
@@ -103,6 +103,20 @@ public class AdminService {
|
||||
return toUserResponse(userRepository.save(user));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public AdminUserResponse updateUserStorageQuota(Long userId, long storageQuotaBytes) {
|
||||
User user = getRequiredUser(userId);
|
||||
user.setStorageQuotaBytes(storageQuotaBytes);
|
||||
return toUserResponse(userRepository.save(user));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public AdminUserResponse updateUserMaxUploadSize(Long userId, long maxUploadSizeBytes) {
|
||||
User user = getRequiredUser(userId);
|
||||
user.setMaxUploadSizeBytes(maxUploadSizeBytes);
|
||||
return toUserResponse(userRepository.save(user));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public AdminPasswordResetResponse resetUserPassword(Long userId) {
|
||||
String temporaryPassword = generateTemporaryPassword();
|
||||
@@ -118,7 +132,9 @@ public class AdminService {
|
||||
user.getPhoneNumber(),
|
||||
user.getCreatedAt(),
|
||||
user.getRole(),
|
||||
user.isBanned()
|
||||
user.isBanned(),
|
||||
user.getStorageQuotaBytes(),
|
||||
user.getMaxUploadSizeBytes()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.yoyuzh.admin;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Positive;
|
||||
|
||||
public record AdminUserMaxUploadSizeUpdateRequest(
|
||||
@NotNull
|
||||
@Positive
|
||||
Long maxUploadSizeBytes
|
||||
) {
|
||||
}
|
||||
@@ -11,6 +11,8 @@ public record AdminUserResponse(
|
||||
String phoneNumber,
|
||||
LocalDateTime createdAt,
|
||||
UserRole role,
|
||||
boolean banned
|
||||
boolean banned,
|
||||
long storageQuotaBytes,
|
||||
long maxUploadSizeBytes
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.yoyuzh.admin;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Positive;
|
||||
|
||||
public record AdminUserStorageQuotaUpdateRequest(
|
||||
@NotNull
|
||||
@Positive
|
||||
Long storageQuotaBytes
|
||||
) {
|
||||
}
|
||||
@@ -261,7 +261,9 @@ public class AuthService {
|
||||
user.getPreferredLanguage(),
|
||||
buildAvatarUrl(user),
|
||||
user.getRole(),
|
||||
user.getCreatedAt()
|
||||
user.getCreatedAt(),
|
||||
user.getStorageQuotaBytes(),
|
||||
user.getMaxUploadSizeBytes()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ import java.time.LocalDateTime;
|
||||
@Index(name = "idx_user_created_at", columnList = "created_at")
|
||||
})
|
||||
public class User {
|
||||
public static final long DEFAULT_STORAGE_QUOTA_BYTES = 50L * 1024 * 1024 * 1024;
|
||||
public static final long DEFAULT_MAX_UPLOAD_SIZE_BYTES = 500L * 1024 * 1024;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@@ -68,6 +70,12 @@ public class User {
|
||||
@Column(nullable = false)
|
||||
private boolean banned;
|
||||
|
||||
@Column(name = "storage_quota_bytes")
|
||||
private Long storageQuotaBytes;
|
||||
|
||||
@Column(name = "max_upload_size_bytes")
|
||||
private Long maxUploadSizeBytes;
|
||||
|
||||
@PrePersist
|
||||
public void prePersist() {
|
||||
if (createdAt == null) {
|
||||
@@ -82,6 +90,12 @@ public class User {
|
||||
if (preferredLanguage == null || preferredLanguage.isBlank()) {
|
||||
preferredLanguage = "zh-CN";
|
||||
}
|
||||
if (storageQuotaBytes == null || storageQuotaBytes <= 0) {
|
||||
storageQuotaBytes = DEFAULT_STORAGE_QUOTA_BYTES;
|
||||
}
|
||||
if (maxUploadSizeBytes == null || maxUploadSizeBytes <= 0) {
|
||||
maxUploadSizeBytes = DEFAULT_MAX_UPLOAD_SIZE_BYTES;
|
||||
}
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
@@ -203,4 +217,26 @@ public class User {
|
||||
public void setBanned(boolean banned) {
|
||||
this.banned = banned;
|
||||
}
|
||||
|
||||
public long getStorageQuotaBytes() {
|
||||
if (storageQuotaBytes == null || storageQuotaBytes <= 0) {
|
||||
return DEFAULT_STORAGE_QUOTA_BYTES;
|
||||
}
|
||||
return storageQuotaBytes;
|
||||
}
|
||||
|
||||
public void setStorageQuotaBytes(Long storageQuotaBytes) {
|
||||
this.storageQuotaBytes = storageQuotaBytes;
|
||||
}
|
||||
|
||||
public long getMaxUploadSizeBytes() {
|
||||
if (maxUploadSizeBytes == null || maxUploadSizeBytes <= 0) {
|
||||
return DEFAULT_MAX_UPLOAD_SIZE_BYTES;
|
||||
}
|
||||
return maxUploadSizeBytes;
|
||||
}
|
||||
|
||||
public void setMaxUploadSizeBytes(Long maxUploadSizeBytes) {
|
||||
this.maxUploadSizeBytes = maxUploadSizeBytes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.yoyuzh.auth.dto;
|
||||
|
||||
import com.yoyuzh.auth.UserRole;
|
||||
import com.yoyuzh.auth.User;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@@ -14,9 +15,24 @@ public record UserProfileResponse(
|
||||
String preferredLanguage,
|
||||
String avatarUrl,
|
||||
UserRole role,
|
||||
LocalDateTime createdAt
|
||||
LocalDateTime createdAt,
|
||||
long storageQuotaBytes,
|
||||
long maxUploadSizeBytes
|
||||
) {
|
||||
public UserProfileResponse(Long id, String username, String email, LocalDateTime createdAt) {
|
||||
this(id, username, username, email, null, null, "zh-CN", null, UserRole.USER, createdAt);
|
||||
this(
|
||||
id,
|
||||
username,
|
||||
username,
|
||||
email,
|
||||
null,
|
||||
null,
|
||||
"zh-CN",
|
||||
null,
|
||||
UserRole.USER,
|
||||
createdAt,
|
||||
User.DEFAULT_STORAGE_QUOTA_BYTES,
|
||||
User.DEFAULT_MAX_UPLOAD_SIZE_BYTES
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ public class FileService {
|
||||
public FileMetadataResponse upload(User user, String path, MultipartFile multipartFile) {
|
||||
String normalizedPath = normalizeDirectoryPath(path);
|
||||
String filename = normalizeUploadFilename(multipartFile.getOriginalFilename());
|
||||
validateUpload(user.getId(), normalizedPath, filename, multipartFile.getSize());
|
||||
validateUpload(user, normalizedPath, filename, multipartFile.getSize());
|
||||
ensureDirectoryHierarchy(user, normalizedPath);
|
||||
|
||||
fileContentStorage.upload(user.getId(), normalizedPath, filename, multipartFile);
|
||||
@@ -64,7 +64,7 @@ public class FileService {
|
||||
public InitiateUploadResponse initiateUpload(User user, InitiateUploadRequest request) {
|
||||
String normalizedPath = normalizeDirectoryPath(request.path());
|
||||
String filename = normalizeLeafName(request.filename());
|
||||
validateUpload(user.getId(), normalizedPath, filename, request.size());
|
||||
validateUpload(user, normalizedPath, filename, request.size());
|
||||
|
||||
PreparedUpload preparedUpload = fileContentStorage.prepareUpload(
|
||||
user.getId(),
|
||||
@@ -88,7 +88,7 @@ public class FileService {
|
||||
String normalizedPath = normalizeDirectoryPath(request.path());
|
||||
String filename = normalizeLeafName(request.filename());
|
||||
String storageName = normalizeLeafName(request.storageName());
|
||||
validateUpload(user.getId(), normalizedPath, filename, request.size());
|
||||
validateUpload(user, normalizedPath, filename, request.size());
|
||||
ensureDirectoryHierarchy(user, normalizedPath);
|
||||
|
||||
fileContentStorage.completeUpload(user.getId(), normalizedPath, storageName, request.contentType(), request.size());
|
||||
@@ -265,6 +265,7 @@ public class FileService {
|
||||
}
|
||||
|
||||
if (!storedFile.isDirectory()) {
|
||||
ensureWithinStorageQuota(user, storedFile.getSize());
|
||||
fileContentStorage.copyFile(user.getId(), storedFile.getPath(), normalizedTargetPath, storedFile.getStorageName());
|
||||
return toResponse(storedFileRepository.save(copyStoredFile(storedFile, normalizedTargetPath)));
|
||||
}
|
||||
@@ -276,6 +277,11 @@ public class FileService {
|
||||
}
|
||||
|
||||
List<StoredFile> descendants = storedFileRepository.findByUserIdAndPathEqualsOrDescendant(user.getId(), oldLogicalPath);
|
||||
long additionalBytes = descendants.stream()
|
||||
.filter(descendant -> !descendant.isDirectory())
|
||||
.mapToLong(StoredFile::getSize)
|
||||
.sum();
|
||||
ensureWithinStorageQuota(user, additionalBytes);
|
||||
List<StoredFile> copiedEntries = new ArrayList<>();
|
||||
|
||||
fileContentStorage.ensureDirectory(user.getId(), newLogicalPath);
|
||||
@@ -421,7 +427,7 @@ public class FileService {
|
||||
byte[] content) {
|
||||
String normalizedPath = normalizeDirectoryPath(path);
|
||||
String normalizedFilename = normalizeLeafName(filename);
|
||||
validateUpload(recipient.getId(), normalizedPath, normalizedFilename, size);
|
||||
validateUpload(recipient, normalizedPath, normalizedFilename, size);
|
||||
ensureDirectoryHierarchy(recipient, normalizedPath);
|
||||
fileContentStorage.storeImportedFile(
|
||||
recipient.getId(),
|
||||
@@ -510,13 +516,27 @@ public class FileService {
|
||||
return storedFile;
|
||||
}
|
||||
|
||||
private void validateUpload(Long userId, String normalizedPath, String filename, long size) {
|
||||
if (size > maxFileSize) {
|
||||
private void validateUpload(User user, String normalizedPath, String filename, long size) {
|
||||
long effectiveMaxUploadSize = Math.min(maxFileSize, user.getMaxUploadSizeBytes());
|
||||
if (size > effectiveMaxUploadSize) {
|
||||
throw new BusinessException(ErrorCode.UNKNOWN, "文件大小超出限制");
|
||||
}
|
||||
if (storedFileRepository.existsByUserIdAndPathAndFilename(userId, normalizedPath, filename)) {
|
||||
if (storedFileRepository.existsByUserIdAndPathAndFilename(user.getId(), normalizedPath, filename)) {
|
||||
throw new BusinessException(ErrorCode.UNKNOWN, "同目录下文件已存在");
|
||||
}
|
||||
ensureWithinStorageQuota(user, size);
|
||||
}
|
||||
|
||||
private void ensureWithinStorageQuota(User user, long additionalBytes) {
|
||||
if (additionalBytes <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
long usedBytes = storedFileRepository.sumFileSizeByUserId(user.getId());
|
||||
long quotaBytes = user.getStorageQuotaBytes();
|
||||
if (usedBytes > Long.MAX_VALUE - additionalBytes || usedBytes + additionalBytes > quotaBytes) {
|
||||
throw new BusinessException(ErrorCode.UNKNOWN, "存储空间不足");
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureDirectoryHierarchy(User user, String normalizedPath) {
|
||||
|
||||
@@ -64,5 +64,12 @@ public interface StoredFileRepository extends JpaRepository<StoredFile, Long> {
|
||||
List<StoredFile> findByUserIdAndPathEqualsOrDescendant(@Param("userId") Long userId,
|
||||
@Param("path") String path);
|
||||
|
||||
@Query("""
|
||||
select coalesce(sum(f.size), 0)
|
||||
from StoredFile f
|
||||
where f.user.id = :userId and f.directory = false
|
||||
""")
|
||||
long sumFileSizeByUserId(@Param("userId") Long userId);
|
||||
|
||||
List<StoredFile> findTop12ByUserIdAndDirectoryFalseOrderByCreatedAtDesc(Long userId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user