Fastadmin云存储文件下载时自定义命名方案
·
云存储文件下载自定义命名方案
需求背景
项目中的文件(如送货照片、生产文件、设计源文件等)存储在云存储(阿里云OSS/腾讯云COS等),文件名使用MD5哈希值命名。用户下载时希望按照一定的命名规则生成有意义的文件名,例如:项目编号_生产文件.pdf。
解决方案
采用后端代理下载方案,通过创建下载代理接口,在响应头中设置 Content-Disposition 指定自定义文件名。
方案优势
- 通用性强 - 适用于所有云存储(OSS、COS、七牛等)
- 支持中文文件名 - 正确处理中文编码
- 灵活命名 - 支持模板变量和自定义函数
- 自动回退 - 可从Attachment表自动读取原始文件名
后端实现
下载代理接口
文件路径: application/admin/controller/Ajax.php
/**
* 代理下载文件(支持自定义文件名)
* @param string $url 文件URL路径
* @param string $name 自定义文件名(可选,不传则从Attachment表读取原始文件名)
* @param string $suffix 文件后缀(可选,用于指定后缀)
*/
public function download()
{
$url = $this->request->get('url');
$name = $this->request->get('name', '');
$suffix = $this->request->get('suffix', '');
if (empty($url)) {
$this->error('文件路径不能为空');
}
// 处理URL,获取完整URL
$fullUrl = cdnurl($url, true);
// 尝试从Attachment表获取原始文件名
$attachment = null;
if (empty($name)) {
$attachment = \app\common\model\Attachment::where('url', $url)->find();
if ($attachment) {
$name = $attachment->filename;
}
}
// 如果还是没有文件名,使用URL中的文件名
if (empty($name)) {
$name = basename(parse_url($fullUrl, PHP_URL_PATH));
}
// 获取文件后缀
if (empty($suffix)) {
if ($attachment && !empty($attachment->imagetype)) {
$suffix = $attachment->imagetype;
} else {
$suffix = pathinfo($name, PATHINFO_EXTENSION);
if (empty($suffix)) {
$suffix = pathinfo(parse_url($fullUrl, PHP_URL_PATH), PATHINFO_EXTENSION);
}
}
}
// 确保文件名有后缀
if ($suffix && !preg_match('/\.' . preg_quote($suffix, '/') . '$/i', $name)) {
$name .= '.' . $suffix;
}
try {
// 使用cURL获取文件内容
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $fullUrl,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => 300,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
]);
$content = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
$error = curl_error($ch);
curl_close($ch);
if ($httpCode !== 200 || $error) {
$this->error('文件获取失败: ' . ($error ?: 'HTTP ' . $httpCode));
}
$filesize = strlen($content);
$encodedName = rawurlencode($name);
// 设置响应头
header('Content-Type: ' . ($contentType ?: 'application/octet-stream'));
header('Content-Length: ' . $filesize);
header('Content-Disposition: attachment; filename="' . $encodedName . '"; filename*=UTF-8\'\'' . $encodedName);
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Expires: 0');
echo $content;
exit;
} catch (\Exception $e) {
$this->error('下载失败: ' . $e->getMessage());
}
}
API 接口说明
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
url | string | 是 | 文件URL路径(相对路径) |
name | string | 否 | 自定义文件名(不传则从Attachment表读取) |
suffix | string | 否 | 文件后缀(用于指定后缀) |
调用示例:
GET /admin/ajax/download?url=/uploads/xxx.pdf&name=自定义文件名.pdf
前端实现
格式化函数
文件路径: public/assets/js/require-table.js
/**
* 文件下载格式化(支持自定义文件名)
* 使用方式:
* 1. 基本用法:formatter: Table.api.formatter.download
* 会自动从Attachment表读取原始文件名
* 2. 自定义文件名模板:
* formatter: Table.api.formatter.download,
* nameTemplate: '{title}_{field}', // 支持变量:{title}, {field}, {id}, {date}, {time}, {row.xxx}
* 3. 完全自定义:
* formatter: Table.api.formatter.download,
* nameTemplate: function(value, row, index) { return '自定义文件名.pdf'; }
*/
download: function (value, row, index) {
return Table.api.formatter.downloads.call(this, value, row, index);
},
downloads: function (value, row, index) {
value = value == null || value.length === 0 ? '' : value.toString();
var classname = typeof this.classname !== 'undefined' ? this.classname : 'img-sm img-center';
var arr = value !== '' ? (value.indexOf('data:image/') === -1 ? value.split(',') : [value]) : [];
var html = [];
var column = this;
var nameTemplate = typeof this.nameTemplate !== 'undefined' ? this.nameTemplate : '';
$.each(arr, function (i, filePath) {
var suffix = /[\.]?([a-zA-Z0-9]+)$/.exec(filePath);
suffix = suffix ? suffix[1] : 'file';
var iconUrl = Fast.api.fixurl("ajax/icon?suffix=" + suffix);
var downloadUrl = Fast.api.fixurl("ajax/download") + "?url=" + encodeURIComponent(filePath);
// 处理自定义文件名
if (nameTemplate) {
var customName = '';
if (typeof nameTemplate === 'function') {
customName = nameTemplate.call(column, value, row, index);
} else {
customName = nameTemplate
.replace(/\{title\}/g, row.title || '')
.replace(/\{field\}/g, column.field || '')
.replace(/\{id\}/g, row.id || '')
.replace(/\{date\}/g, new Date().toISOString().slice(0, 10))
.replace(/\{time\}/g, new Date().toISOString().slice(11, 19).replace(/:/g, ''));
customName = customName.replace(/\{row\.([^}]+)\}/g, function(match, key) {
return row[key] || '';
});
}
if (customName) {
if (suffix && customName.indexOf('.' + suffix) === -1) {
customName += '.' + suffix;
}
downloadUrl += "&name=" + encodeURIComponent(customName);
}
}
html.push('<a href="' + downloadUrl + '" target="_blank" title="点击下载"><img src="' + iconUrl + '" class="' + classname + '" width="30" height="30"></a>');
});
return html.join(' ');
}
使用示例
方式一:基本用法(自动获取原始文件名)
{
field: 'file',
title: '文件',
formatter: Table.api.formatter.download
}
下载时会自动从 Attachment 表读取原始上传文件名。
方式二:模板变量方式
{
field: 'production_file',
title: '生产文件',
formatter: Table.api.formatter.download,
nameTemplate: '{title}_{id}_{date}'
}
支持的模板变量:
| 变量 | 说明 |
|---|---|
{title} | 行数据的 title 字段 |
{field} | 当前列的字段名 |
{id} | 行数据的 id |
{date} | 当前日期(YYYY-MM-DD) |
{time} | 当前时间(HHmmss) |
{row.xxx} | 行数据的任意字段,如 {row.project_no} |
方式三:函数方式(完全自定义)
{
field: 'design_source_file',
title: '设计源文件',
formatter: Table.api.formatter.download,
nameTemplate: function(value, row, index) {
// 生成文件名:项目编号_设计源文件.后缀
var suffix = /[\.]?([a-zA-Z0-9]+)$/.exec(value) || ['',''];
return (row.project_no || row.id || 'design') + '_设计源文件.' + (suffix[1] || 'pdf');
}
}
函数参数:
| 参数 | 说明 |
|---|---|
value | 当前字段值(文件URL) |
row | 当前行数据对象 |
index | 行索引 |
实际应用案例
文件路径: public/assets/js/backend/project.js
columns: [
// ... 其他字段
{
field: 'production_file',
title: __('Production_files'),
operate: false,
formatter: Table.api.formatter.download,
nameTemplate: function(value, row, index) {
var suffix = /[\.]?([a-zA-Z0-9]+)$/.exec(value) || ['',''];
return (row.project_no || row.id || 'production') + '_生产文件.' + (suffix[1] || 'pdf');
}
},
{
field: 'design_source_file',
title: __('Design_source_file'),
operate: false,
formatter: Table.api.formatter.download,
nameTemplate: function(value, row, index) {
var suffix = /[\.]?([a-zA-Z0-9]+)$/.exec(value) || ['',''];
return (row.project_no || row.id || 'design') + '_设计源文件.' + (suffix[1] || 'pdf');
}
}
]
下载效果:
- 原始文件:
/uploads/20240115/a1b2c3d4e5f6.pdf - 下载文件名:
P20240115001_生产文件.pdf
注意事项
- 性能考虑: 大文件下载会经过服务器中转,对于超大文件建议使用云存储的签名URL方式
- 安全限制: 确保下载接口有权限验证,防止未授权访问
- 编码问题: 使用
rawurlencode和filename*=UTF-8''双重保障中文文件名兼容性 - 缓存优化: 对于频繁下载的文件可考虑添加缓存机制
扩展:云存储签名URL方式(可选)
如果使用阿里云OSS,可以直接在生成签名URL时指定响应头:
$ossClient->signUrl($bucket, $object, $timeout, 'GET', [
'response-content-disposition' => 'attachment; filename="' . urlencode($filename) . '"'
]);
腾讯云COS类似:
$cosClient->getPresignedUrl($bucket, $object, '+30 minutes', [
'ResponseContentDisposition' => 'attachment; filename="' . urlencode($filename) . '"'
]);
这种方式无需经过服务器中转,下载速度更快,但需要根据具体云服务商API调整实现。
更多推荐
所有评论(0)