增加定时清理回收站文件

This commit is contained in:
2026-04-03 22:27:43 +08:00
parent d47c60a5e0
commit 97f1482497
18 changed files with 220 additions and 59 deletions

2
.idea/compiler.xml generated
View File

@@ -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>

View 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
View File

@@ -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>

View File

@@ -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) {

View File

@@ -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()
// 静态资源 // 静态资源

View File

@@ -29,7 +29,7 @@ public class AuthController {
// 精确重算存储空间 // 精确重算存储空间
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());
@@ -91,19 +91,18 @@ public class AuthController {
// 精确重算存储空间 // 精确重算存储空间
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);

View File

@@ -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;

View File

@@ -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;
}
} }

View File

@@ -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);
} }

View 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 + " 个文件");
}
}

View File

@@ -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

View File

@@ -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",

View File

@@ -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(

View File

@@ -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')

View File

@@ -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))
} }

View File

@@ -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 }
}) })

View File

@@ -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) {

View File

@@ -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