本文目录导读:

在 PHP 项目中实现网盘容量统计与用户配额限制,通常需要从以下几个步骤入手:
- 数据库设计(存储配额与已用空间)
- 上传时实时统计(计算文件大小并累加)
- 配额校验逻辑(上传前检查是否超限)
- 删除时实时扣减(释放空间)
- 性能优化(避免每次查询都全表扫描)
下面是一套标准化的实现方案:
数据库表设计
需要在用户表中新增字段,或者创建独立的 user_quota 表。
方案 A:用户表直接扩展(推荐用户量 < 10万)
ALTER TABLE `users` ADD COLUMN `quota_total` BIGINT DEFAULT 1073741824 COMMENT '总配额,单位字节,默认1GB', ADD COLUMN `quota_used` BIGINT DEFAULT 0 COMMENT '已用空间,单位字节', ADD COLUMN `quota_files` INT DEFAULT 0 COMMENT '文件总数(可选限制)';
方案 B:独立配额表(适合 SaaS 或动态套餐)
CREATE TABLE `user_quotas` (
`user_id` INT PRIMARY KEY,
`total_bytes` BIGINT NOT NULL DEFAULT 1073741824,
`used_bytes` BIGINT NOT NULL DEFAULT 0,
`file_count` INT NOT NULL DEFAULT 0,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
上传文件前的配额校验(核心逻辑)
/**
* 检查用户配额是否充足
* @param int $userId
* @param int $fileSize 单位字节
* @param int $fileCount 本次上传文件数(通常为1)
* @return bool
*/
public function checkQuota(int $userId, int $fileSize, int $fileCount = 1): bool
{
// 从数据库或缓存获取用户配额信息
$user = $this->getUserQuota($userId);
// 检查总空间
if (($user['quota_used'] + $fileSize) > $user['quota_total']) {
throw new \Exception('存储空间不足');
}
// 检查文件数量(如果有限制)
if ($user['quota_files'] !== null) {
if (($user['file_count'] + $fileCount) > $user['quota_files']) {
throw new \Exception('文件数量已达上限');
}
}
return true;
}
何时调用:在 move_uploaded_file() 或写入云存储 SDK 之前。
上传成功后的空间更新
使用 悲观锁 或 原子操作 防止并发问题。
// 使用 MySQL UPDATE ... SET used_bytes = used_bytes + ? WHERE id = ? AND used_bytes + ? <= total_bytes
// 如果影响行数为0,说明配额超限,需要回滚已上传的文件
public function incrementQuota(int $userId, int $fileSize, int $fileCount = 1): bool
{
$sql = "UPDATE users
SET quota_used = quota_used + :size,
file_count = file_count + :count
WHERE id = :uid
AND (quota_used + :checkSize) <= quota_total";
// 如果使用了文件数量限制,还需要加 file_count 条件
$stmt = $this->db->prepare($sql);
$stmt->execute([
':size' => $fileSize,
':count' => $fileCount,
':uid' => $userId,
':checkSize' => $fileSize
]);
return $stmt->rowCount() > 0;
}
任务队列(T+1 修正)
由于网络波动或程序异常可能导致 quota_used 不准确,建议增加 定时校正任务:
// 每天凌晨执行
$sql = "UPDATE users u
LEFT JOIN (
SELECT user_id, COALESCE(SUM(file_size), 0) as real_used, COUNT(*) as real_count
FROM files
WHERE is_deleted = 0
GROUP BY user_id
) f ON u.id = f.user_id
SET u.quota_used = COALESCE(f.real_used, 0),
u.file_count = COALESCE(f.real_count, 0)";
多节点部署的并发安全
如果应用部署在多台服务器,推荐使用 Redis 分布式锁:
// 仅在配额增减时加锁
$lockKey = "quota_lock:{$userId}";
$lock = $redis->set($lockKey, 1, ['nx', 'ex' => 2]); // 2秒超时
if (!$lock) {
throw new \Exception('系统繁忙,请稍后重试');
}
try {
// 执行配额检查和更新
// 写入文件记录到数据库
} finally {
$redis->del($lockKey);
}
前端展示友好的容量信息
后端提供接口:
public function getQuotaInfo(int $userId): array
{
$user = $this->getUserQuota($userId);
return [
'total' => $user['quota_total'],
'used' => $user['quota_used'],
'percent' => $user['quota_total'] > 0
? round(($user['quota_used'] / $user['quota_total']) * 100, 2)
: 0,
// 格式化为人类可读
'total_formatted' => $this->formatBytes($user['quota_total']),
'used_formatted' => $this->formatBytes($user['quota_used']),
'free_formatted' => $this->formatBytes($user['quota_total'] - $user['quota_used']),
];
}
private function formatBytes(int $bytes, int $precision = 2): string
{
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= pow(1024, $pow);
return round($bytes, $precision) . ' ' . $units[$pow];
}
删除/回收站机制的特殊处理
如果支持回收站,建议:
- 移到回收站:不立即扣减配额,文件标记为
is_deleted = 1 - 永久删除/清空回收站:扣减配额
- 从回收站恢复:检查配额是否够用,如果不够提示用户清理
// 永久删除文件时
public function decrementQuota(int $userId, int $fileSize, int $fileCount = 1): void
{
$sql = "UPDATE users
SET quota_used = GREATEST(quota_used - :size, 0),
file_count = GREATEST(file_count - :count, 0)
WHERE id = :uid";
$stmt = $this->db->prepare($sql);
$stmt->execute([
':size' => $fileSize,
':count' => $fileCount,
':uid' => $userId
]);
}
性能优化建议
- 缓存配额信息:将
quota_total和quota_used缓存到 Redis,减少数据库查询。 - 批量操作场景:Zip 下载、批量移动等操作,建议在操作完成后统一更新配额。
- 大文件场景:超过 100MB 的文件,使用 分片上传 ,每次上传分片时检查配额,避免最后合并时发现空间不够。
总结流程
用户上传请求
│
▼
检查配额 (数据库/缓存)
│ └── 不足 → 返回错误提示
▼
执行文件上传 (本地/OSS)
│ └── 失败 → 终止,不更新配额
▼
写入文件记录 (数据库)
│
▼
原子更新 quota_used += 文件大小
│ └── 更新失败(行锁) → 回滚文件,提示重试
▼
返回成功
这套方案在大多数 PHP 网盘项目(如 Nextcloud、自建企业网盘、SaaS 文件管理)中均适用。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播