oracle - 在 ColdFusion 中显示数据库中的 BLOB 文件
问题描述
我有一个表单,用户可以在其中上传 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>
最重要的代码部分是我包含的最后/上面的代码片段。即使我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]*=((['"]).*?\2|[^;\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 环境全局启用了 BLOB 检索,但在我的特定数据源上没有启用它。在全局设置中启用 BLOB 检索只是第一步;除非您明确启用它,否则它不会覆盖数据源。
我确实需要稍微调整一下我的代码,但不多。我将 更改cfcontent
为使用file
属性而不是variable
,我file
使用从<cffile action="write">
. 将文件写入服务器后,我通过 JavaScript 为文件创建一个锚链接并调用其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>
推荐阅读
- javascript - 如何使用带有多个参数的 findIndex
- vim - 在 neovim 中更改 HTML 属性
- c# - 如何更快地遍历数据并写入excel文件
- android-studio - Flutter UI Guides 未在 Android Studio 中显示
- javascript - 软件包安装失败:npm ERR!代码 ERESOLVEnpm 错误!无法解构“节点”的属性“名称”,因为它为空
- javascript - 如何对对象属性数组进行排序?或颠倒对象属性
- c# - 在 C# 中输入无效数据后,如何使程序请求用户输入?
- python - 如何增加 Dataframe 行名称值?
- android - Android : 安卓和远程服务之间的 VPN 隧道。将数据发送到服务
- java - 三个双引号 ''' 如何在 mybatis @Insert 注释块中工作?