增加定时清理回收站文件
This commit is contained in:
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@@ -7,6 +7,7 @@
|
|||||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||||
<outputRelativeToContentRoot value="true" />
|
<outputRelativeToContentRoot value="true" />
|
||||||
|
<module name="system-pro" />
|
||||||
<module name="file-system-backend" />
|
<module name="file-system-backend" />
|
||||||
</profile>
|
</profile>
|
||||||
</annotationProcessing>
|
</annotationProcessing>
|
||||||
@@ -14,6 +15,7 @@
|
|||||||
<component name="JavacSettings">
|
<component name="JavacSettings">
|
||||||
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
||||||
<module name="file-system-backend" options="-parameters" />
|
<module name="file-system-backend" options="-parameters" />
|
||||||
|
<module name="system-pro" options="-parameters" />
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
13
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
13
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<Languages>
|
||||||
|
<language minSize="91" name="Java" />
|
||||||
|
</Languages>
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="ExtractMethodRecommender" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="minLength" value="923" />
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -8,5 +8,5 @@
|
|||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="java-17" project-jdk-type="JavaSDK" />
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="JDK17" project-jdk-type="JavaSDK" />
|
||||||
</project>
|
</project>
|
||||||
@@ -3,9 +3,11 @@ package com.filesystem;
|
|||||||
import org.mybatis.spring.annotation.MapperScan;
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@MapperScan("com.filesystem.mapper")
|
@MapperScan("com.filesystem.mapper")
|
||||||
|
@EnableScheduling
|
||||||
public class FileSystemApplication {
|
public class FileSystemApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ public class SecurityConfig {
|
|||||||
// API 公开接口
|
// API 公开接口
|
||||||
.requestMatchers("/api/auth/login", "/api/auth/register", "/api/auth/logout").permitAll()
|
.requestMatchers("/api/auth/login", "/api/auth/register", "/api/auth/logout").permitAll()
|
||||||
.requestMatchers("/api/files/test").permitAll()
|
.requestMatchers("/api/files/test").permitAll()
|
||||||
|
.requestMatchers("/api/users/config").permitAll()
|
||||||
// WebSocket
|
// WebSocket
|
||||||
.requestMatchers("/ws/**").permitAll()
|
.requestMatchers("/ws/**").permitAll()
|
||||||
// 静态资源
|
// 静态资源
|
||||||
|
|||||||
@@ -14,23 +14,23 @@ import java.util.Map;
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/auth")
|
@RequestMapping("/api/auth")
|
||||||
public class AuthController {
|
public class AuthController {
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private UserService userService;
|
private UserService userService;
|
||||||
|
|
||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
public ResponseEntity<?> login(@RequestBody Map<String, String> request) {
|
public ResponseEntity<?> login(@RequestBody Map<String, String> request) {
|
||||||
String username = request.get("username");
|
String username = request.get("username");
|
||||||
String password = request.get("password");
|
String password = request.get("password");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String token = userService.login(username, password);
|
String token = userService.login(username, password);
|
||||||
User user = userService.findByUsername(username);
|
User user = userService.findByUsername(username);
|
||||||
|
|
||||||
// 精确重算存储空间
|
// 精确重算存储空间
|
||||||
long storageUsed = userService.recalculateStorage(user.getId());
|
long storageUsed = userService.recalculateStorage(user.getId());
|
||||||
long storageLimit = user.getStorageLimit() != null ? user.getStorageLimit() : 20L * 1024 * 1024 * 1024;
|
long storageLimit = user.getStorageLimit();
|
||||||
|
|
||||||
Map<String, Object> userData = new HashMap<>();
|
Map<String, Object> userData = new HashMap<>();
|
||||||
userData.put("id", user.getId());
|
userData.put("id", user.getId());
|
||||||
userData.put("username", user.getUsername());
|
userData.put("username", user.getUsername());
|
||||||
@@ -41,35 +41,35 @@ public class AuthController {
|
|||||||
userData.put("email", user.getEmail() != null ? user.getEmail() : "");
|
userData.put("email", user.getEmail() != null ? user.getEmail() : "");
|
||||||
userData.put("storageUsed", storageUsed);
|
userData.put("storageUsed", storageUsed);
|
||||||
userData.put("storageLimit", storageLimit);
|
userData.put("storageLimit", storageLimit);
|
||||||
|
|
||||||
Map<String, Object> result = new HashMap<>();
|
Map<String, Object> result = new HashMap<>();
|
||||||
result.put("token", token);
|
result.put("token", token);
|
||||||
result.put("user", userData);
|
result.put("user", userData);
|
||||||
|
|
||||||
Map<String, Object> body = new HashMap<>();
|
Map<String, Object> body = new HashMap<>();
|
||||||
body.put("data", result);
|
body.put("data", result);
|
||||||
body.put("message", "登录成功");
|
body.put("message", "登录成功");
|
||||||
|
|
||||||
return ResponseEntity.ok(body);
|
return ResponseEntity.ok(body);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return ResponseEntity.badRequest().body(Map.of("message", e.getMessage()));
|
return ResponseEntity.badRequest().body(Map.of("message", e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/register")
|
@PostMapping("/register")
|
||||||
public ResponseEntity<?> register(@RequestBody Map<String, String> request) {
|
public ResponseEntity<?> register(@RequestBody Map<String, String> request) {
|
||||||
String username = request.get("username");
|
String username = request.get("username");
|
||||||
String password = request.get("password");
|
String password = request.get("password");
|
||||||
String nickname = request.get("nickname");
|
String nickname = request.get("nickname");
|
||||||
|
|
||||||
if (userService.findByUsername(username) != null) {
|
if (userService.findByUsername(username) != null) {
|
||||||
return ResponseEntity.badRequest().body(Map.of("message", "用户名已存在"));
|
return ResponseEntity.badRequest().body(Map.of("message", "用户名已存在"));
|
||||||
}
|
}
|
||||||
|
|
||||||
User user = userService.createUser(username, password, nickname != null ? nickname : username);
|
User user = userService.createUser(username, password, nickname != null ? nickname : username);
|
||||||
return ResponseEntity.ok(Map.of("message", "注册成功", "data", Map.of("id", user.getId())));
|
return ResponseEntity.ok(Map.of("message", "注册成功", "data", Map.of("id", user.getId())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/logout")
|
@PostMapping("/logout")
|
||||||
public ResponseEntity<?> logout(@AuthenticationPrincipal UserPrincipal principal) {
|
public ResponseEntity<?> logout(@AuthenticationPrincipal UserPrincipal principal) {
|
||||||
if (principal != null) {
|
if (principal != null) {
|
||||||
@@ -77,36 +77,35 @@ public class AuthController {
|
|||||||
}
|
}
|
||||||
return ResponseEntity.ok(Map.of("message", "退出成功"));
|
return ResponseEntity.ok(Map.of("message", "退出成功"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/info")
|
@GetMapping("/info")
|
||||||
public ResponseEntity<?> getUserInfo(@AuthenticationPrincipal UserPrincipal principal) {
|
public ResponseEntity<?> getUserInfo(@AuthenticationPrincipal UserPrincipal principal) {
|
||||||
if (principal == null) {
|
if (principal == null) {
|
||||||
return ResponseEntity.status(401).body(Map.of("message", "未登录"));
|
return ResponseEntity.status(401).body(Map.of("message", "未登录"));
|
||||||
}
|
}
|
||||||
|
|
||||||
User user = userService.findById(principal.getUserId());
|
User user = userService.findById(principal.getUserId());
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
return ResponseEntity.status(401).body(Map.of("message", "用户不存在"));
|
return ResponseEntity.status(401).body(Map.of("message", "用户不存在"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 精确重算存储空间
|
// 精确重算存储空间
|
||||||
long storageUsed = userService.recalculateStorage(user.getId());
|
long storageUsed = userService.recalculateStorage(user.getId());
|
||||||
long storageLimit = user.getStorageLimit() != null ? user.getStorageLimit() : 20L * 1024 * 1024 * 1024;
|
long storageLimit = user.getStorageLimit();
|
||||||
|
|
||||||
Map<String, Object> userData = new HashMap<>();
|
Map<String, Object> userData = new HashMap<>();
|
||||||
userData.put("id", user.getId());
|
userData.put("id", user.getId());
|
||||||
userData.put("username", user.getUsername());
|
userData.put("username", user.getUsername());
|
||||||
userData.put("nickname", user.getNickname() != null ? user.getNickname() : "");
|
userData.put("nickname", user.getNickname());
|
||||||
userData.put("signature", user.getSignature() != null ? user.getSignature() : "");
|
userData.put("signature", user.getSignature());
|
||||||
userData.put("avatar", user.getAvatar() != null ? user.getAvatar() : "");
|
userData.put("avatar", user.getAvatar());
|
||||||
userData.put("email", user.getEmail() != null ? user.getEmail() : "");
|
userData.put("email", user.getEmail());
|
||||||
userData.put("phone", user.getPhone() != null ? user.getPhone() : "");
|
userData.put("phone", user.getPhone());
|
||||||
userData.put("storageUsed", storageUsed);
|
userData.put("storageUsed", storageUsed);
|
||||||
userData.put("storageLimit", storageLimit);
|
userData.put("storageLimit", storageLimit);
|
||||||
|
|
||||||
Map<String, Object> body = new HashMap<>();
|
Map<String, Object> body = new HashMap<>();
|
||||||
body.put("data", userData);
|
body.put("data", userData);
|
||||||
|
|
||||||
return ResponseEntity.ok(body);
|
return ResponseEntity.ok(body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,22 @@ public class UserController {
|
|||||||
@Value("${file.storage.path:./uploads}")
|
@Value("${file.storage.path:./uploads}")
|
||||||
private String storagePath;
|
private String storagePath;
|
||||||
|
|
||||||
|
@Value("${file.user.storage-limit-gb:50}")
|
||||||
|
private int storageLimitGb;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取系统配置(存储配额等)
|
||||||
|
*/
|
||||||
|
@GetMapping("/config")
|
||||||
|
public ResponseEntity<?> getSystemConfig() {
|
||||||
|
return ResponseEntity.ok(Map.of(
|
||||||
|
"data", Map.of(
|
||||||
|
"storageLimitGb", storageLimitGb,
|
||||||
|
"storageLimitBytes", (long) storageLimitGb * 1024 * 1024 * 1024
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有可用用户(用于文件共享等场景)
|
* 获取所有可用用户(用于文件共享等场景)
|
||||||
*/
|
*/
|
||||||
@@ -41,7 +57,7 @@ public class UserController {
|
|||||||
Map<String, Object> m = new java.util.HashMap<>();
|
Map<String, Object> m = new java.util.HashMap<>();
|
||||||
m.put("id", u.getId());
|
m.put("id", u.getId());
|
||||||
m.put("username", u.getUsername());
|
m.put("username", u.getUsername());
|
||||||
m.put("nickname", u.getNickname() != null ? u.getNickname() : u.getUsername());
|
m.put("nickname", u.getNickname());
|
||||||
m.put("avatar", u.getAvatar());
|
m.put("avatar", u.getAvatar());
|
||||||
m.put("signature", u.getSignature());
|
m.put("signature", u.getSignature());
|
||||||
return m;
|
return m;
|
||||||
|
|||||||
@@ -714,4 +714,49 @@ public class FileService {
|
|||||||
}
|
}
|
||||||
return tree;
|
return tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除回收站中超过指定天数的文件
|
||||||
|
* @param retentionDays 保留天数
|
||||||
|
* @return 删除的文件数量
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public int deleteExpiredTrashFiles(int retentionDays) {
|
||||||
|
LocalDateTime cutoffDate = LocalDateTime.now().minusDays(retentionDays);
|
||||||
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
|
// 查询所有在回收站且超过保留天数的文件
|
||||||
|
List<FileEntity> expiredFiles = fileMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<FileEntity>()
|
||||||
|
.eq(FileEntity::getIsDeleted, 1)
|
||||||
|
.isNotNull(FileEntity::getDeletedAt)
|
||||||
|
);
|
||||||
|
|
||||||
|
int deletedCount = 0;
|
||||||
|
for (FileEntity file : expiredFiles) {
|
||||||
|
try {
|
||||||
|
LocalDateTime deletedAt = LocalDateTime.parse(file.getDeletedAt(), formatter);
|
||||||
|
if (deletedAt.isBefore(cutoffDate)) {
|
||||||
|
// 彻底删除
|
||||||
|
if (file.getPath() != null && !file.getPath().isEmpty()) {
|
||||||
|
try {
|
||||||
|
Path filePath = Paths.get(storagePath).toAbsolutePath().resolve("files").resolve(file.getPath());
|
||||||
|
Files.deleteIfExists(filePath);
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
if (file.getSize() != null && file.getSize() > 0) {
|
||||||
|
userService.decreaseStorage(file.getUserId(), file.getSize());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fileMapper.deleteById(file.getId());
|
||||||
|
deletedCount++;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 日期解析失败,跳过
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return deletedCount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.filesystem.mapper.FileMapper;
|
|||||||
import com.filesystem.mapper.UserMapper;
|
import com.filesystem.mapper.UserMapper;
|
||||||
import com.filesystem.security.JwtUtil;
|
import com.filesystem.security.JwtUtil;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@@ -27,6 +28,9 @@ public class UserService {
|
|||||||
@Resource
|
@Resource
|
||||||
private BCryptPasswordEncoder passwordEncoder;
|
private BCryptPasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
@Value("${file.user.storage-limit-gb:50}")
|
||||||
|
private int storageLimitGb;
|
||||||
|
|
||||||
public User findByUsername(String username) {
|
public User findByUsername(String username) {
|
||||||
return userMapper.selectOne(
|
return userMapper.selectOne(
|
||||||
new LambdaQueryWrapper<User>().eq(User::getUsername, username)
|
new LambdaQueryWrapper<User>().eq(User::getUsername, username)
|
||||||
@@ -59,7 +63,7 @@ public class UserService {
|
|||||||
user.setNickname(nickname);
|
user.setNickname(nickname);
|
||||||
user.setStatus(1);
|
user.setStatus(1);
|
||||||
user.setStorageUsed(0L);
|
user.setStorageUsed(0L);
|
||||||
user.setStorageLimit(20L * 1024 * 1024 * 1024); // 20GB
|
user.setStorageLimit((long) storageLimitGb * 1024 * 1024 * 1024);
|
||||||
userMapper.insert(user);
|
userMapper.insert(user);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
@@ -84,9 +88,9 @@ public class UserService {
|
|||||||
User user = userMapper.selectById(userId);
|
User user = userMapper.selectById(userId);
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
user.setStorageUsed(total);
|
user.setStorageUsed(total);
|
||||||
// 同步存储上限为 20GB(兼容老用户)
|
// 同步存储上限(兼容老用户)
|
||||||
if (user.getStorageLimit() == null || user.getStorageLimit() < 20L * 1024 * 1024 * 1024) {
|
if (user.getStorageLimit() == null || user.getStorageLimit() < (long) storageLimitGb * 1024 * 1024 * 1024) {
|
||||||
user.setStorageLimit(20L * 1024 * 1024 * 1024);
|
user.setStorageLimit((long) storageLimitGb * 1024 * 1024 * 1024);
|
||||||
}
|
}
|
||||||
userMapper.updateById(user);
|
userMapper.updateById(user);
|
||||||
}
|
}
|
||||||
|
|||||||
27
src/main/java/com/filesystem/task/TrashCleanupTask.java
Normal file
27
src/main/java/com/filesystem/task/TrashCleanupTask.java
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package com.filesystem.task;
|
||||||
|
|
||||||
|
import com.filesystem.service.FileService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class TrashCleanupTask {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private FileService fileService;
|
||||||
|
|
||||||
|
@Value("${file.trash.retention-days:30}")
|
||||||
|
private int retentionDays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每天凌晨2点执行清理回收站任务
|
||||||
|
*/
|
||||||
|
@Scheduled(cron = "0 0 2 * * ?")
|
||||||
|
public void cleanupExpiredTrashFiles() {
|
||||||
|
System.out.println("[TrashCleanupTask] 开始清理回收站过期文件,保留天数: " + retentionDays);
|
||||||
|
int deletedCount = fileService.deleteExpiredTrashFiles(retentionDays);
|
||||||
|
System.out.println("[TrashCleanupTask] 清理完成,共删除 " + deletedCount + " 个文件");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,24 +3,24 @@ server:
|
|||||||
|
|
||||||
spring:
|
spring:
|
||||||
datasource:
|
datasource:
|
||||||
url: jdbc:mysql://127.0.0.1:13306/chat_app?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&createDatabaseIfNotExist=true
|
url: jdbc:mysql://192.168.31.182:13306/system?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&createDatabaseIfNotExist=true
|
||||||
username: root
|
username: dream
|
||||||
password: root_dream
|
password: info_dream
|
||||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
|
|
||||||
data:
|
data:
|
||||||
redis:
|
redis:
|
||||||
host: 127.0.0.1
|
host: 192.168.31.194
|
||||||
port: 6379
|
port: 16379
|
||||||
database: 0
|
database: 9
|
||||||
timeout: 10000ms
|
timeout: 10000ms
|
||||||
password: admin
|
password: admin
|
||||||
|
|
||||||
servlet:
|
servlet:
|
||||||
multipart:
|
multipart:
|
||||||
enabled: true
|
enabled: true
|
||||||
max-file-size: 1024MB
|
max-file-size: 512MB
|
||||||
max-request-size: 2048MB
|
max-request-size: 1024MB
|
||||||
file-size-threshold: 0
|
file-size-threshold: 0
|
||||||
|
|
||||||
mybatis-plus:
|
mybatis-plus:
|
||||||
@@ -36,6 +36,10 @@ mybatis-plus:
|
|||||||
file:
|
file:
|
||||||
storage:
|
storage:
|
||||||
path: /ogsapp/uploads
|
path: /ogsapp/uploads
|
||||||
|
user:
|
||||||
|
storage-limit-gb: 200
|
||||||
|
trash:
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
jwt:
|
jwt:
|
||||||
secret: mySecretKeyForJWTTokenGenerationThatIsLongEnough256BitsForHS256Algorithm
|
secret: mySecretKeyForJWTTokenGenerationThatIsLongEnough256BitsForHS256Algorithm
|
||||||
|
|||||||
9
web-vue/package-lock.json
generated
9
web-vue/package-lock.json
generated
@@ -886,7 +886,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
|
||||||
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
|
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/lodash": "*"
|
"@types/lodash": "*"
|
||||||
}
|
}
|
||||||
@@ -1422,15 +1421,13 @@
|
|||||||
"version": "4.17.23",
|
"version": "4.17.23",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||||
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/lodash-es": {
|
"node_modules/lodash-es": {
|
||||||
"version": "4.17.23",
|
"version": "4.17.23",
|
||||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz",
|
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz",
|
||||||
"integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==",
|
"integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/lodash-unified": {
|
"node_modules/lodash-unified": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
@@ -1637,7 +1634,6 @@
|
|||||||
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.21.3",
|
"esbuild": "^0.21.3",
|
||||||
"postcss": "^8.4.43",
|
"postcss": "^8.4.43",
|
||||||
@@ -1697,7 +1693,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.31.tgz",
|
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.31.tgz",
|
||||||
"integrity": "sha512-iV/sU9SzOlmA/0tygSmjkEN6Jbs3nPoIPFhCMLD2STrjgOU8DX7ZtzMhg4ahVwf5Rp9KoFzcXeB1ZrVbLBp5/Q==",
|
"integrity": "sha512-iV/sU9SzOlmA/0tygSmjkEN6Jbs3nPoIPFhCMLD2STrjgOU8DX7ZtzMhg4ahVwf5Rp9KoFzcXeB1ZrVbLBp5/Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-dom": "3.5.31",
|
"@vue/compiler-dom": "3.5.31",
|
||||||
"@vue/compiler-sfc": "3.5.31",
|
"@vue/compiler-sfc": "3.5.31",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
const request = axios.create({
|
const request = axios.create({
|
||||||
baseURL: '/api',
|
baseURL: '/api',
|
||||||
timeout: 300000
|
timeout: 0 // 不限制超时,大文件上传需要
|
||||||
})
|
})
|
||||||
|
|
||||||
request.interceptors.request.use(
|
request.interceptors.request.use(
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
import request from './request'
|
import request from './request'
|
||||||
|
|
||||||
export const getUsers = () => request.get('/users')
|
export const getUsers = () => request.get('/users')
|
||||||
|
|
||||||
|
export const getSystemConfig = () => request.get('/users/config')
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
<div class="file-list-footer">
|
<div class="file-list-footer">
|
||||||
<span>共 {{ fileList.length }} 个文件,总大小:{{ formatSize(totalSize) }}</span>
|
<span>共 {{ fileList.length }} 个文件,总大小:{{ formatSize(totalSize) }}</span>
|
||||||
<span v-if="isOverLimit" class="warning-text">(超出剩余空间)</span>
|
<span v-if="isOverLimit" class="warning-text">(超出剩余空间)</span>
|
||||||
|
<span v-if="isOverSizeLimit" class="warning-text">(单次上传不能超过500MB)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -52,7 +53,7 @@
|
|||||||
<el-icon><Close /></el-icon>
|
<el-icon><Close /></el-icon>
|
||||||
<span style="margin-left: 4px">取消</span>
|
<span style="margin-left: 4px">取消</span>
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="primary" @click="handleUpload" :loading="uploading" :disabled="isOverLimit">
|
<el-button type="primary" @click="handleUpload" :loading="uploading" :disabled="isOverLimit || isOverSizeLimit">
|
||||||
<el-icon><Upload /></el-icon>
|
<el-icon><Upload /></el-icon>
|
||||||
<span style="margin-left: 4px">开始上传</span>
|
<span style="margin-left: 4px">开始上传</span>
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -90,6 +91,12 @@ const isOverLimit = computed(() => {
|
|||||||
return totalSize.value > props.remainingStorage
|
return totalSize.value > props.remainingStorage
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 是否超过500MB限制
|
||||||
|
const MAX_UPLOAD_SIZE = 500 * 1024 * 1024 // 500MB
|
||||||
|
const isOverSizeLimit = computed(() => {
|
||||||
|
return totalSize.value > MAX_UPLOAD_SIZE
|
||||||
|
})
|
||||||
|
|
||||||
const handleChange = (file, list) => {
|
const handleChange = (file, list) => {
|
||||||
if (list.length > 10) {
|
if (list.length > 10) {
|
||||||
ElMessage.warning('最多一次上传10个文件')
|
ElMessage.warning('最多一次上传10个文件')
|
||||||
@@ -124,6 +131,11 @@ const handleUpload = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isOverSizeLimit.value) {
|
||||||
|
ElMessage.warning('单次上传总大小不能超过500MB')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
emit('upload', fileList.value.map(f => f.raw))
|
emit('upload', fileList.value.map(f => f.raw))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
|
import { getSystemConfig } from '@/api/user'
|
||||||
|
|
||||||
export const useUserStore = defineStore('user', () => {
|
export const useUserStore = defineStore('user', () => {
|
||||||
const token = ref(localStorage.getItem('token') || '')
|
const token = ref(localStorage.getItem('token') || '')
|
||||||
@@ -11,7 +12,7 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
const phone = ref(localStorage.getItem('phone') || '')
|
const phone = ref(localStorage.getItem('phone') || '')
|
||||||
const email = ref(localStorage.getItem('email') || '')
|
const email = ref(localStorage.getItem('email') || '')
|
||||||
const storageUsed = ref(Number(localStorage.getItem('storageUsed')) || 0)
|
const storageUsed = ref(Number(localStorage.getItem('storageUsed')) || 0)
|
||||||
const storageLimit = ref(Number(localStorage.getItem('storageLimit')) || 20 * 1024 * 1024 * 1024)
|
const storageLimit = ref(Number(localStorage.getItem('storageLimit')) || 0)
|
||||||
|
|
||||||
const isLoggedIn = computed(() => !!token.value)
|
const isLoggedIn = computed(() => !!token.value)
|
||||||
|
|
||||||
@@ -54,11 +55,23 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
localStorage.setItem('storageUsed', storageUsed.value)
|
localStorage.setItem('storageUsed', storageUsed.value)
|
||||||
}
|
}
|
||||||
if (user.storageLimit !== undefined) {
|
if (user.storageLimit !== undefined) {
|
||||||
storageLimit.value = Number(user.storageLimit) || 20 * 1024 * 1024 * 1024
|
storageLimit.value = Number(user.storageLimit) || 0
|
||||||
localStorage.setItem('storageLimit', storageLimit.value)
|
localStorage.setItem('storageLimit', storageLimit.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fetchStorageLimit = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getSystemConfig()
|
||||||
|
if (res.data?.storageLimitBytes) {
|
||||||
|
storageLimit.value = res.data.storageLimitBytes
|
||||||
|
localStorage.setItem('storageLimit', storageLimit.value)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('获取存储配额失败', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
token.value = ''
|
token.value = ''
|
||||||
username.value = ''
|
username.value = ''
|
||||||
@@ -69,7 +82,7 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
phone.value = ''
|
phone.value = ''
|
||||||
email.value = ''
|
email.value = ''
|
||||||
storageUsed.value = 0
|
storageUsed.value = 0
|
||||||
storageLimit.value = 20 * 1024 * 1024 * 1024
|
storageLimit.value = 0
|
||||||
localStorage.removeItem('token')
|
localStorage.removeItem('token')
|
||||||
localStorage.removeItem('username')
|
localStorage.removeItem('username')
|
||||||
localStorage.removeItem('userId')
|
localStorage.removeItem('userId')
|
||||||
@@ -82,5 +95,5 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
localStorage.removeItem('storageLimit')
|
localStorage.removeItem('storageLimit')
|
||||||
}
|
}
|
||||||
|
|
||||||
return { token, username, userId, nickname, signature, avatar, phone, email, storageUsed, storageLimit, isLoggedIn, setToken, setUser, logout }
|
return { token, username, userId, nickname, signature, avatar, phone, email, storageUsed, storageLimit, isLoggedIn, setToken, setUser, fetchStorageLimit, logout }
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ const movableFolders = ref([])
|
|||||||
|
|
||||||
// 存储 —— 真实数据
|
// 存储 —— 真实数据
|
||||||
const storagePercent = computed(() => {
|
const storagePercent = computed(() => {
|
||||||
const limit = userStore.storageLimit || 20 * 1024 * 1024 * 1024
|
const limit = userStore.storageLimit || 0
|
||||||
const used = userStore.storageUsed || 0
|
const used = userStore.storageUsed || 0
|
||||||
if (limit <= 0) return 0
|
if (limit <= 0) return 0
|
||||||
return Math.min(Math.round((used / limit) * 100), 100)
|
return Math.min(Math.round((used / limit) * 100), 100)
|
||||||
@@ -237,13 +237,14 @@ const usedStorage = computed(() => {
|
|||||||
: (used / (1024 * 1024)).toFixed(2) + ' MB'
|
: (used / (1024 * 1024)).toFixed(2) + ' MB'
|
||||||
})
|
})
|
||||||
const totalStorage = computed(() => {
|
const totalStorage = computed(() => {
|
||||||
const limit = userStore.storageLimit || 20 * 1024 * 1024 * 1024
|
const limit = userStore.storageLimit || 0
|
||||||
|
if (limit <= 0) return '—'
|
||||||
return (limit / (1024 * 1024 * 1024)).toFixed(0) + ' GB'
|
return (limit / (1024 * 1024 * 1024)).toFixed(0) + ' GB'
|
||||||
})
|
})
|
||||||
|
|
||||||
// 剩余存储空间(字节)
|
// 剩余存储空间(字节)
|
||||||
const remainingStorage = computed(() => {
|
const remainingStorage = computed(() => {
|
||||||
const limit = userStore.storageLimit || 20 * 1024 * 1024 * 1024
|
const limit = userStore.storageLimit || 0
|
||||||
const used = userStore.storageUsed || 0
|
const used = userStore.storageUsed || 0
|
||||||
return Math.max(0, limit - used)
|
return Math.max(0, limit - used)
|
||||||
})
|
})
|
||||||
@@ -251,12 +252,14 @@ const remainingStorage = computed(() => {
|
|||||||
// 刷新存储数据(从后端精确重算)
|
// 刷新存储数据(从后端精确重算)
|
||||||
const refreshStorage = async () => {
|
const refreshStorage = async () => {
|
||||||
try {
|
try {
|
||||||
|
// 先获取系统配置(存储配额)
|
||||||
|
await userStore.fetchStorageLimit()
|
||||||
|
// 再获取用户当前用量
|
||||||
const res = await getCurrentUser()
|
const res = await getCurrentUser()
|
||||||
const data = res.data
|
const data = res.data
|
||||||
if (data) {
|
if (data) {
|
||||||
userStore.setUser({
|
userStore.setUser({
|
||||||
storageUsed: data.storageUsed ?? 0,
|
storageUsed: data.storageUsed ?? 0
|
||||||
storageLimit: data.storageLimit ?? 20 * 1024 * 1024 * 1024
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<div class="feature-list">
|
<div class="feature-list">
|
||||||
<div class="feature-item">
|
<div class="feature-item">
|
||||||
<el-icon><Check /></el-icon>
|
<el-icon><Check /></el-icon>
|
||||||
<span>20GB 超大存储空间</span>
|
<span>{{ storageLimitText }} 存储空间</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-item">
|
<div class="feature-item">
|
||||||
<el-icon><Check /></el-icon>
|
<el-icon><Check /></el-icon>
|
||||||
@@ -58,17 +58,40 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { FolderOpened, Check } from '@element-plus/icons-vue'
|
import { FolderOpened, Check } from '@element-plus/icons-vue'
|
||||||
import LoginForm from './LoginForm.vue'
|
import LoginForm from './LoginForm.vue'
|
||||||
import RegisterForm from './RegisterForm.vue'
|
import RegisterForm from './RegisterForm.vue'
|
||||||
import { useUserStore } from '@/store/user'
|
import { useUserStore } from '@/store/user'
|
||||||
|
import { getSystemConfig } from '@/api/user'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
|
||||||
const isLogin = ref(true)
|
const isLogin = ref(true)
|
||||||
|
const storageLimitGb = ref(50)
|
||||||
|
|
||||||
|
const storageLimitText = computed(() => {
|
||||||
|
return storageLimitGb.value >= 1024
|
||||||
|
? (storageLimitGb.value / 1024).toFixed(1) + 'TB'
|
||||||
|
: storageLimitGb.value + 'GB'
|
||||||
|
})
|
||||||
|
|
||||||
|
const fetchConfig = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getSystemConfig()
|
||||||
|
if (res.data?.storageLimitGb) {
|
||||||
|
storageLimitGb.value = res.data.storageLimitGb
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 使用默认值
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchConfig()
|
||||||
|
})
|
||||||
|
|
||||||
const toggleMode = () => {
|
const toggleMode = () => {
|
||||||
isLogin.value = !isLogin.value
|
isLogin.value = !isLogin.value
|
||||||
|
|||||||
Reference in New Issue
Block a user