PHP项目网盘容量如何统计限制用户配额

wen PHP项目 27

本文目录导读:

PHP项目网盘容量如何统计限制用户配额

  1. 数据库表设计
  2. 上传文件前的配额校验(核心逻辑)
  3. 上传成功后的空间更新
  4. 任务队列(T+1 修正)
  5. 多节点部署的并发安全
  6. 前端展示友好的容量信息
  7. 删除/回收站机制的特殊处理
  8. 性能优化建议
  9. 总结流程

在 PHP 项目中实现网盘容量统计与用户配额限制,通常需要从以下几个步骤入手:

  1. 数据库设计(存储配额与已用空间)
  2. 上传时实时统计(计算文件大小并累加)
  3. 配额校验逻辑(上传前检查是否超限)
  4. 删除时实时扣减(释放空间)
  5. 性能优化(避免每次查询都全表扫描)

下面是一套标准化的实现方案:


数据库表设计

需要在用户表中新增字段,或者创建独立的 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
    ]);
}

性能优化建议

  1. 缓存配额信息:将 quota_totalquota_used 缓存到 Redis,减少数据库查询。
  2. 批量操作场景:Zip 下载、批量移动等操作,建议在操作完成后统一更新配额。
  3. 大文件场景:超过 100MB 的文件,使用 分片上传 ,每次上传分片时检查配额,避免最后合并时发现空间不够。

总结流程

用户上传请求
    │
    ▼
检查配额 (数据库/缓存)
    │  └── 不足 → 返回错误提示
    ▼
执行文件上传 (本地/OSS)
    │  └── 失败 → 终止,不更新配额
    ▼
写入文件记录 (数据库)
    │
    ▼
原子更新 quota_used += 文件大小
    │  └── 更新失败(行锁) → 回滚文件,提示重试
    ▼
返回成功

这套方案在大多数 PHP 网盘项目(如 Nextcloud、自建企业网盘、SaaS 文件管理)中均适用。

抱歉,评论功能暂时关闭!