針對 ThinkPHP / Laravel 等框架常見的資料表結構(id, filename, savename, url, downloads),提供一個精簡的 down() 動作:驗證記錄、累計下載數、輸出檔案。
CREATE TABLE `downloads` ( `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `filename` VARCHAR(255) NOT NULL COMMENT '原始檔名', `savename` VARCHAR(255) NOT NULL COMMENT '儲存檔名', `url` VARCHAR(500) DEFAULT NULL COMMENT '外部連結或相對路徑', `downloads` INT UNSIGNED DEFAULT 0, `status` TINYINT DEFAULT 1 );
public function down() {
$id = (int)input('id');
if (!$id) exit('參數錯誤');
$model = model('Downloads');
$data = $model->where('id', $id)->where('status', 1)->find();
if (!$data) exit('記錄不存在或已下架');
// 解決中文檔名編碼
$filename = iconv('UTF-8', 'GBK//IGNORE', $data['filename']);
$savePath = $data['url'] ?: 'Uploads/file/' . $data['savename'];
$absPath = realpath($savePath);
if (!$absPath || !is_file($absPath)) exit('檔案不存在');
// 累計下載數(原子操作)
$model->where('id', $id)->setInc('downloads');
// 輸出 Header
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . rawurlencode($filename) . '"');
header('Content-Length: ' . filesize($absPath));
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
// 分塊輸出
$fp = fopen($absPath, 'rb');
while (!feof($fp)) {
echo fread($fp, 8192);
@ob_flush(); flush();
}
fclose($fp);
exit;
}
filename* 模式。$fileSize = filesize($absPath);
$range = $_SERVER['HTTP_RANGE'] ?? '';
if ($range) {
list(, $range) = explode('=', $range);
list($start, $end) = explode('-', $range);
$start = (int)$start;
$end = $end ? (int)$end : $fileSize - 1;
$length = $end - $start + 1;
header('HTTP/1.1 206 Partial Content');
header('Content-Range: bytes ' . $start . '-' . $end . '/' . $fileSize);
header('Content-Length: ' . $length);
header('Accept-Ranges: bytes');
$fp = fopen($absPath, 'rb');
fseek($fp, $start);
$remaining = $length;
while (!feof($fp) && $remaining > 0) {
$chunk = min(8192, $remaining);
echo fread($fp, $chunk);
$remaining -= $chunk;
@ob_flush(); flush();
}
fclose($fp);
exit;
}
# nginx.conf
location /protected/ {
internal; # 僅內部重導向可存取
alias /var/www/files/; # 實體檔案根目錄
}
# PHP 只需驗證權限後:
header('X-Accel-Redirect: /protected/' . $relativePath);
header('Content-Disposition: attachment; filename="..."');
exit;
此精簡版下載動作可直接整合至任何 MVC 控制器,並可平滑升級為斷點續傳或 Web Server 卸載模式。