在 ColdFusion 中显示数据库中的 BLOB 文件
Display BLOB file from database in ColdFusion
我有一个用户可以上传 PDF 的表单,我将其作为 BLOB 存储在数据库中。我正在显示所有上传的 PDF 的列表,所有这些都可以通过单击下载。我已经尝试了很多不同的解决方法来让 PDF 正确下载,但它会在浏览器中显示“无法加载 PDF 文档”,在 Adobe Acrobat 中显示“文件已损坏且无法修复”。这是我的代码:
Instructors.cfc(上传文件格式)
<form method="post" enctype="multipart/form-data">
<input id="document_filename" name="document_filename" type="hidden">
<input id="document_title" name="document_title" type="hidden">
<input id="openFileBrowser" type="button" value="Import Data from Application PDF" onclick="document.getElementById('application_document').click();">
<input id="application_document" name="application_document" type="file" accept=".pdf" style="display:none">
<input id="upload_document" type="button" onclick="UploadDocument()" style="width:220px; display: none" value="Upload Instructor Application Form">
</form>
<script>
function UploadDocument() {
var fd = new FormData();
var theFile = document.getElementById("application_document").files[0];
fd.append('uploadedFile', theFile);
fd.append('file_name', document.getElementById("document_title").value);
$.ajax({
url: "InstructorForms.cfc?method=getApplicationPDFData",
type: "post",
data: fd,
processData: false,
contentType: false,
cache: false
});
</script>
InstructorForms.cfc(将 PDF blob 插入数据库)
<cffunction name="getApplicationPDFData" access="remote">
<cfset uploadDirectory = "#expandPath('../UPLOADS')#">
<cfif not directoryExists(uploadDirectory)>
<cfdirectory action="create" directory="#uploadDirectory#">
</cfif>
<cfif IsDefined("uploadedFile")>
<cffile action="upload" fileField="uploadedFile" destination="#uploadDirectory#" nameConflict="overwrite" accept="application/pdf">
</cfif>
<cfif IsDefined("file_name")>
<cfset filePath = uploadDirectory & "\" & file_name>
<cfpdfform action="read" source="#filePath#" result="documentStruct" />
<cfset nameArray = documentStruct.Name.split(",")>
<cffile action="readbinary" file="#filePath#" variable="binPDF">
<cfquery name="addPDFToDB" datasource="#request.dsn#">
INSERT INTO DDMS.UPLOADED_FILES (LAST_NAME, FIRST_NAME, DOCUMENT, DOCUMENT_TYPE)
VALUES(<cfqueryparam value="#nameArray[1]#" cfsqltype="cf_sql_varchar">,
<cfqueryparam value="#ltrim(rtrim(nameArray[2]))#" cfsqltype="cf_sql_varchar">,
<cfqueryparam value="#binPDF#" cfsqltype="cf_sql_blob">,
'Instructor Application')
</cfquery>
<cffile action="delete" file="#filePath#">
</cffunction>
Instructors.cfc [再次](从数据库下载 PDF,我遇到了问题)
<cffunction name="downloadPDF" access="remote" returntype="any">
<cfargument name="uploaded_file_id" required="yes" type="numeric">
<cfquery name="getInstructorApplication" datasource="#request.dsn#" result="output">
SELECT DOCUMENT, FIRST_NAME, LAST_NAME FROM DDMS.UPLOADED_FILES WHERE UPLOADED_FILE_ID = #arguments.uploaded_file_id#
</cfquery>
<cfset fileName = getInstructorApplication.LAST_NAME & "_" & getInstructorApplication.FIRST_NAME & "_application.pdf">
<cfset cfTags = "">
<cfsavecontent variable="cfTags">
<cfheader name="content-disposition" value="attachment; filename=#fileName#">
<cfcontent variable="#getInstructorApplication.DOCUMENT#" type="application/pdf" reset="yes">
</cfsavecontent>
<cfreturn cfTags>
</cffunction>
最重要的代码部分是我包含的 last/above 片段。即使我在浏览器中导航到 downloadPDF
功能,它仍然无法正确下载 PDF 并给出错误消息。因此,清理该方法是第 1 步,然后我实际上可以通过 AJAX 调用检索用户页面上的 PDF,如果有帮助,我也会展示它:
$(".pdfFile").on("click", function() {
var uploaded_file_id = $(this).data("id");
$.ajax({
url: "CFC/Instructors.cfc?method=downloadPDF",
data: { "uploaded_file_id": uploaded_file_id },
success: function(blob, status, xhr) {
var filename = "";
var disposition = xhr.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1) {
var filenameRegex = /filename[^;=\n]*=((['"]).*?|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
}
if (typeof window.navigator.msSaveBlob !== 'undefined') {
window.navigator.msSaveBlob(blob, filename);
} else {
var URL = window.URL || window.webkitURL;
var newBlob = new Blob([blob], {type: "application/pdf"});
var downloadUrl = URL.createObjectURL(newBlob);
if (filename) {
var a = document.createElement("a");
if (typeof a.download === 'undefined') {
window.location.href = downloadUrl;
} else {
a.href = downloadUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
}
} else {
window.location.href = downloadUrl;
}
}
}
});
});
对于大量的代码,我们深表歉意。但正如我所说,最重要的部分是 downloadPDF
函数,我在其中使用 cfcontent
并需要正确加载二进制数据。任何帮助将不胜感激,因为我已经在这个问题上停留了一段时间并且找不到太多文档。
更新:
返回的 PDF 大小为 62.5 KB,我听说如果禁用 BLOB 检索,由于 ColdFusion Admin 中的缓冲区,输出可能会被截断为 64 KB。我无权访问 ColdFusion Admin,但我的一位同事可以访问,也许他在尝试全局启用 BLOB 检索时编辑了错误的设置。我会和他核实一下。
原来是 ColdFusion Administrator 中的设置导致了问题。尽管在 Dev 环境中 globally 启用了 BLOB 检索,但在我的特定 datasource 上并未启用它。在全局设置中启用 BLOB 检索只是第一步;除非您明确启用它,否则它不会覆盖数据源。
我确实需要稍微调整一下我的代码,但幅度不大。我将 cfcontent
更改为使用 file
属性而不是 variable
,我使用 <cffile action="write">
中使用的路径填充了 file
属性。将文件写入服务器后,我通过 JavaScript 为文件创建锚点 link 并调用其 click()
方法,以便下载 PDF 附件。之后,我在 done()
方法中执行另一个 AJAX 调用来删除文件,因为我不需要将该文件保留在服务器上;这是暂时的。这是我的最终代码:
Instructors.cfc(文件上传形式保持不变)
<cfquery name="getInstructorApplications" datasource="#this.dsn#">
SELECT fm.FILELOB_ID, fm.FILEMETA_ID, fm.TITLE, fm.FILEEXT, fm.FILE_LEN, fm.CREATEDDATE
FROM DDMS.FILEMETA fm
JOIN DDMS.FILELOB fl
ON fm.FILELOB_ID = fl.FILELOB_ID
WHERE fl.DOCUMENT_TYPE = 'Instructor Application'
</cfquery>
<table class="DataTable">
<thead>
<tr>
<th style="width: 200px">File Name</th>
<th>File Extension</th>
<th>File Size</th>
<th>Date Uploaded</th>
</tr>
</thead>
<tbody>
<cfloop query="getInstructorApplications">
<cfoutput>
<tr>
<td style="width: 200px">
<a data-id="#FILELOB_ID#" data-meta-id="#FILEMETA_ID#" data-filename="#TITLE#" class="pdfFile" style="color: blue; cursor: pointer">#TITLE#</a>
</td>
<td>#FILEEXT#</td>
<td>#FILE_LEN#</td>
<td>#DATEFORMAT(LEFT(CREATEDDATE, 10))#</td>
</tr>
</cfoutput>
</cfloop>
</tbody>
</table>
<script>
$(".pdfFile").on("click", function() {
var file_id = $(this).data("id");
var file_meta_id = $(this).data("meta-id");
var file_name = $(this).data("filename");
var now = new Date();
var ticks = now.getTime();
<cfoutput>
var #ToScript(cfcPath, "cfcRoot")#;
</cfoutput>
$.ajax({
url: cfcRoot + "CFC/FileManager.cfc?method=ServeFileDownload&random=" + ticks,
type: "post",
data: { "FileID": file_id,
"FileMetaID": file_meta_id,
"fileName": file_name },
success: function() {
var a = document.createElement("a");
a.href = cfcRoot + "UPLOADS/" + file_name;
a.download = file_name;
document.body.appendChild(a);
a.click();
}
}).done(function() {
$.ajax({
url: cfcRoot + "CFC/FileManager.cfc?method=DeleteFile&random=" + ticks,
data: { "fileName": file_name }
});
});
});
</script>
FileManager.cfc(新建文件,接管InstructorForms.cfc)
<cffunction name="ServeFileDownload" access="remote" returntype="void">
<cfargument name="FileID" type="numeric" required="no" default=0>
<cfargument name="FileMetaID" type="numeric" required="no" default=0>
<cfargument name="fileName" type="string" required="no" default="">
<cfif ARGUMENTS.FileID NEQ 0>
<cfset local.FileMetaID = GetCurrentMetaIDByFileID(FileID=ARGUMENTS.FileID)>
<cfelse>
<cfset local.FileMetaID = ARGUMENTS.FileMetaID>
</cfif>
<cfif local.FileMetaID NEQ 0>
<cfset ServeFile( FileMetaID=ARGUMENTS.FileMetaID, ServeType="attachment", filename="#ARGUMENTS.fileName#")>
<cfelse>
<cfreturn "">
</cfif>
</cffunction>
<cffunction name="ServeFile" access="public" returntype="void">
<cfargument name="FileMetaID" type="numeric" required="yes">
<cfargument name="ServeType" type="string" required="yes">
<cfargument name="fileName" type="string" required="no" default="">
<cfquery name="GetFileMetaData" datasource="#application.DDMS.dsn#">
SELECT fm.FILEMETA_ID, fm.FILELOB_ID, fl.FILELOB, fm.FILE_ID, fm.TITLE, fm.FILEEXT, fm.MIMETYPE_ID, fm.FILE_LEN
FROM DDMS.FILEMETA fm
JOIN DDMS.FILELOB fl
ON fm.FILELOB_ID = fl.FILELOB_ID
WHERE fm.FILEMETA_ID = <cfqueryparam cfsqltype="CF_SQL_INTEGER" value="#ARGUMENTS.FileMetaID#">
AND fm.ISACTIVE = 1
</cfquery>
<cfset MIMETypeObj = CreateObject("component","#application.global.cfcpath#.filemanager.mimetype")>
<cfset local.filename = Len(ARGUMENTS.fileName) ? "#ARGUMENTS.fileName#" : "#GetFileMetaData.TITLE#">
<cfset local.MIMETYPE = MIMETypeObj.GetMIMETypeByID(MIMETYPEID=GetFileMetaData.MIMETYPE_ID)>
<cfif ARGUMENTS.ServeType EQ "attachment">
<cfset local.MIMETYPE = "application/octet-stream">
</cfif>
<cfset uploadDirectory = "#expandPath('../UPLOADS')#">
<cfset filePath = uploadDirectory & "\" & arguments.fileName>
<cfset RecordDownloadUsage(FILEMETAID=ARGUMENTS.FileMetaID,FILENAME="#local.filename#")>
<cffile action="write" file="#filePath#" output="#GetFileMetaData.FileLOB#" >
<cfheader name="Content-Disposition" value="#ARGUMENTS.ServeType#;filename=#local.filename#;" />
<cfcontent file="#filePath#" type="#local.MIMETYPE#" />
<cfreturn ToString(uploadDirectory & "/" & arguments.fileName)>
</cffunction>
<cffunction name="DeleteFile" access="remote" returntype="void">
<cfargument name="fileName" type="string" required="yes">
<cfset uploadDirectory = "#expandPath('../UPLOADS')#">
<cfset filePath = uploadDirectory & "\" & arguments.fileName>
<cffile action="delete" file="#filePath#">
</cffunction>
我有一个用户可以上传 PDF 的表单,我将其作为 BLOB 存储在数据库中。我正在显示所有上传的 PDF 的列表,所有这些都可以通过单击下载。我已经尝试了很多不同的解决方法来让 PDF 正确下载,但它会在浏览器中显示“无法加载 PDF 文档”,在 Adobe Acrobat 中显示“文件已损坏且无法修复”。这是我的代码:
Instructors.cfc(上传文件格式)
<form method="post" enctype="multipart/form-data">
<input id="document_filename" name="document_filename" type="hidden">
<input id="document_title" name="document_title" type="hidden">
<input id="openFileBrowser" type="button" value="Import Data from Application PDF" onclick="document.getElementById('application_document').click();">
<input id="application_document" name="application_document" type="file" accept=".pdf" style="display:none">
<input id="upload_document" type="button" onclick="UploadDocument()" style="width:220px; display: none" value="Upload Instructor Application Form">
</form>
<script>
function UploadDocument() {
var fd = new FormData();
var theFile = document.getElementById("application_document").files[0];
fd.append('uploadedFile', theFile);
fd.append('file_name', document.getElementById("document_title").value);
$.ajax({
url: "InstructorForms.cfc?method=getApplicationPDFData",
type: "post",
data: fd,
processData: false,
contentType: false,
cache: false
});
</script>
InstructorForms.cfc(将 PDF blob 插入数据库)
<cffunction name="getApplicationPDFData" access="remote">
<cfset uploadDirectory = "#expandPath('../UPLOADS')#">
<cfif not directoryExists(uploadDirectory)>
<cfdirectory action="create" directory="#uploadDirectory#">
</cfif>
<cfif IsDefined("uploadedFile")>
<cffile action="upload" fileField="uploadedFile" destination="#uploadDirectory#" nameConflict="overwrite" accept="application/pdf">
</cfif>
<cfif IsDefined("file_name")>
<cfset filePath = uploadDirectory & "\" & file_name>
<cfpdfform action="read" source="#filePath#" result="documentStruct" />
<cfset nameArray = documentStruct.Name.split(",")>
<cffile action="readbinary" file="#filePath#" variable="binPDF">
<cfquery name="addPDFToDB" datasource="#request.dsn#">
INSERT INTO DDMS.UPLOADED_FILES (LAST_NAME, FIRST_NAME, DOCUMENT, DOCUMENT_TYPE)
VALUES(<cfqueryparam value="#nameArray[1]#" cfsqltype="cf_sql_varchar">,
<cfqueryparam value="#ltrim(rtrim(nameArray[2]))#" cfsqltype="cf_sql_varchar">,
<cfqueryparam value="#binPDF#" cfsqltype="cf_sql_blob">,
'Instructor Application')
</cfquery>
<cffile action="delete" file="#filePath#">
</cffunction>
Instructors.cfc [再次](从数据库下载 PDF,我遇到了问题)
<cffunction name="downloadPDF" access="remote" returntype="any">
<cfargument name="uploaded_file_id" required="yes" type="numeric">
<cfquery name="getInstructorApplication" datasource="#request.dsn#" result="output">
SELECT DOCUMENT, FIRST_NAME, LAST_NAME FROM DDMS.UPLOADED_FILES WHERE UPLOADED_FILE_ID = #arguments.uploaded_file_id#
</cfquery>
<cfset fileName = getInstructorApplication.LAST_NAME & "_" & getInstructorApplication.FIRST_NAME & "_application.pdf">
<cfset cfTags = "">
<cfsavecontent variable="cfTags">
<cfheader name="content-disposition" value="attachment; filename=#fileName#">
<cfcontent variable="#getInstructorApplication.DOCUMENT#" type="application/pdf" reset="yes">
</cfsavecontent>
<cfreturn cfTags>
</cffunction>
最重要的代码部分是我包含的 last/above 片段。即使我在浏览器中导航到 downloadPDF
功能,它仍然无法正确下载 PDF 并给出错误消息。因此,清理该方法是第 1 步,然后我实际上可以通过 AJAX 调用检索用户页面上的 PDF,如果有帮助,我也会展示它:
$(".pdfFile").on("click", function() {
var uploaded_file_id = $(this).data("id");
$.ajax({
url: "CFC/Instructors.cfc?method=downloadPDF",
data: { "uploaded_file_id": uploaded_file_id },
success: function(blob, status, xhr) {
var filename = "";
var disposition = xhr.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1) {
var filenameRegex = /filename[^;=\n]*=((['"]).*?|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
}
if (typeof window.navigator.msSaveBlob !== 'undefined') {
window.navigator.msSaveBlob(blob, filename);
} else {
var URL = window.URL || window.webkitURL;
var newBlob = new Blob([blob], {type: "application/pdf"});
var downloadUrl = URL.createObjectURL(newBlob);
if (filename) {
var a = document.createElement("a");
if (typeof a.download === 'undefined') {
window.location.href = downloadUrl;
} else {
a.href = downloadUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
}
} else {
window.location.href = downloadUrl;
}
}
}
});
});
对于大量的代码,我们深表歉意。但正如我所说,最重要的部分是 downloadPDF
函数,我在其中使用 cfcontent
并需要正确加载二进制数据。任何帮助将不胜感激,因为我已经在这个问题上停留了一段时间并且找不到太多文档。
更新:
返回的 PDF 大小为 62.5 KB,我听说如果禁用 BLOB 检索,由于 ColdFusion Admin 中的缓冲区,输出可能会被截断为 64 KB。我无权访问 ColdFusion Admin,但我的一位同事可以访问,也许他在尝试全局启用 BLOB 检索时编辑了错误的设置。我会和他核实一下。
原来是 ColdFusion Administrator 中的设置导致了问题。尽管在 Dev 环境中 globally 启用了 BLOB 检索,但在我的特定 datasource 上并未启用它。在全局设置中启用 BLOB 检索只是第一步;除非您明确启用它,否则它不会覆盖数据源。
我确实需要稍微调整一下我的代码,但幅度不大。我将 cfcontent
更改为使用 file
属性而不是 variable
,我使用 <cffile action="write">
中使用的路径填充了 file
属性。将文件写入服务器后,我通过 JavaScript 为文件创建锚点 link 并调用其 click()
方法,以便下载 PDF 附件。之后,我在 done()
方法中执行另一个 AJAX 调用来删除文件,因为我不需要将该文件保留在服务器上;这是暂时的。这是我的最终代码:
Instructors.cfc(文件上传形式保持不变)
<cfquery name="getInstructorApplications" datasource="#this.dsn#">
SELECT fm.FILELOB_ID, fm.FILEMETA_ID, fm.TITLE, fm.FILEEXT, fm.FILE_LEN, fm.CREATEDDATE
FROM DDMS.FILEMETA fm
JOIN DDMS.FILELOB fl
ON fm.FILELOB_ID = fl.FILELOB_ID
WHERE fl.DOCUMENT_TYPE = 'Instructor Application'
</cfquery>
<table class="DataTable">
<thead>
<tr>
<th style="width: 200px">File Name</th>
<th>File Extension</th>
<th>File Size</th>
<th>Date Uploaded</th>
</tr>
</thead>
<tbody>
<cfloop query="getInstructorApplications">
<cfoutput>
<tr>
<td style="width: 200px">
<a data-id="#FILELOB_ID#" data-meta-id="#FILEMETA_ID#" data-filename="#TITLE#" class="pdfFile" style="color: blue; cursor: pointer">#TITLE#</a>
</td>
<td>#FILEEXT#</td>
<td>#FILE_LEN#</td>
<td>#DATEFORMAT(LEFT(CREATEDDATE, 10))#</td>
</tr>
</cfoutput>
</cfloop>
</tbody>
</table>
<script>
$(".pdfFile").on("click", function() {
var file_id = $(this).data("id");
var file_meta_id = $(this).data("meta-id");
var file_name = $(this).data("filename");
var now = new Date();
var ticks = now.getTime();
<cfoutput>
var #ToScript(cfcPath, "cfcRoot")#;
</cfoutput>
$.ajax({
url: cfcRoot + "CFC/FileManager.cfc?method=ServeFileDownload&random=" + ticks,
type: "post",
data: { "FileID": file_id,
"FileMetaID": file_meta_id,
"fileName": file_name },
success: function() {
var a = document.createElement("a");
a.href = cfcRoot + "UPLOADS/" + file_name;
a.download = file_name;
document.body.appendChild(a);
a.click();
}
}).done(function() {
$.ajax({
url: cfcRoot + "CFC/FileManager.cfc?method=DeleteFile&random=" + ticks,
data: { "fileName": file_name }
});
});
});
</script>
FileManager.cfc(新建文件,接管InstructorForms.cfc)
<cffunction name="ServeFileDownload" access="remote" returntype="void">
<cfargument name="FileID" type="numeric" required="no" default=0>
<cfargument name="FileMetaID" type="numeric" required="no" default=0>
<cfargument name="fileName" type="string" required="no" default="">
<cfif ARGUMENTS.FileID NEQ 0>
<cfset local.FileMetaID = GetCurrentMetaIDByFileID(FileID=ARGUMENTS.FileID)>
<cfelse>
<cfset local.FileMetaID = ARGUMENTS.FileMetaID>
</cfif>
<cfif local.FileMetaID NEQ 0>
<cfset ServeFile( FileMetaID=ARGUMENTS.FileMetaID, ServeType="attachment", filename="#ARGUMENTS.fileName#")>
<cfelse>
<cfreturn "">
</cfif>
</cffunction>
<cffunction name="ServeFile" access="public" returntype="void">
<cfargument name="FileMetaID" type="numeric" required="yes">
<cfargument name="ServeType" type="string" required="yes">
<cfargument name="fileName" type="string" required="no" default="">
<cfquery name="GetFileMetaData" datasource="#application.DDMS.dsn#">
SELECT fm.FILEMETA_ID, fm.FILELOB_ID, fl.FILELOB, fm.FILE_ID, fm.TITLE, fm.FILEEXT, fm.MIMETYPE_ID, fm.FILE_LEN
FROM DDMS.FILEMETA fm
JOIN DDMS.FILELOB fl
ON fm.FILELOB_ID = fl.FILELOB_ID
WHERE fm.FILEMETA_ID = <cfqueryparam cfsqltype="CF_SQL_INTEGER" value="#ARGUMENTS.FileMetaID#">
AND fm.ISACTIVE = 1
</cfquery>
<cfset MIMETypeObj = CreateObject("component","#application.global.cfcpath#.filemanager.mimetype")>
<cfset local.filename = Len(ARGUMENTS.fileName) ? "#ARGUMENTS.fileName#" : "#GetFileMetaData.TITLE#">
<cfset local.MIMETYPE = MIMETypeObj.GetMIMETypeByID(MIMETYPEID=GetFileMetaData.MIMETYPE_ID)>
<cfif ARGUMENTS.ServeType EQ "attachment">
<cfset local.MIMETYPE = "application/octet-stream">
</cfif>
<cfset uploadDirectory = "#expandPath('../UPLOADS')#">
<cfset filePath = uploadDirectory & "\" & arguments.fileName>
<cfset RecordDownloadUsage(FILEMETAID=ARGUMENTS.FileMetaID,FILENAME="#local.filename#")>
<cffile action="write" file="#filePath#" output="#GetFileMetaData.FileLOB#" >
<cfheader name="Content-Disposition" value="#ARGUMENTS.ServeType#;filename=#local.filename#;" />
<cfcontent file="#filePath#" type="#local.MIMETYPE#" />
<cfreturn ToString(uploadDirectory & "/" & arguments.fileName)>
</cffunction>
<cffunction name="DeleteFile" access="remote" returntype="void">
<cfargument name="fileName" type="string" required="yes">
<cfset uploadDirectory = "#expandPath('../UPLOADS')#">
<cfset filePath = uploadDirectory & "\" & arguments.fileName>
<cffile action="delete" file="#filePath#">
</cffunction>