Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
264 views
in Technique[技术] by (71.8m points)

oracle - Display BLOB file from database in ColdFusion

I have a form where users can upload PDFs, which I store in the database as BLOBs. I'm showing a list of all the PDFs uploaded, all of which can be downloaded by a click. I've tried so many different workarounds to get the PDF to download properly but it will say "Failed to load PDF document" in the browser and "The file is damaged and could not be repaired" in Adobe Acrobat. Here is my code:

Instructors.cfc (form for uploading file)

<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 (inserts PDF blob to database)

<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 [again] (downloading PDF from database, where I'm having trouble)

<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>

The most important section of code is the last/above snippet I included. Even when I navigate to the downloadPDF function in the browser, it still won't download the PDF properly and gives the error messages. So cleaning that method up is step #1, and then I can actually retrieve the PDF on the user's page through an AJAX call, which I will also show in case it is helpful:

$(".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[^;=
]*=((['"]).*?2|[^;
]*)/;
                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;
                }
            }
        }
    });
});

Sorry for the overwhelming amount of code. But like I said, the most important part is in the downloadPDF function, where I utilize cfcontent and need to load the binary data properly. Any help would be greatly appreciated, as I've been stuck on this problem for a while and can't find much documentation.

UPDATE:

The PDF returned is of size 62.5 KB, and I've heard that output can be truncated to 64 KB due to a buffer in ColdFusion Admin if BLOB retrieval is disabled. I don't have access to ColdFusion Admin, yet one of my coworkers does, and perhaps he edited a wrong setting when attempting to enable BLOB retrieval globally. I'll check with him.

question from:https://stackoverflow.com/questions/65854046/display-blob-file-from-database-in-coldfusion

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

It turns out it was a setting in ColdFusion Administrator that was causing the problem. Although BLOB retrieval was enabled globally for the Dev environment, it wasn't enabled on my particular datasource. Enabling BLOB retrieval in the global settings is only step 1; it won't cover the datasource unless you enable it explicitly.

I did have to tweak my code a little, but not much. I changed the cfcontent to use the file attribute instead of variable, which I populated the file attribute with the path used from a <cffile action="write">. After having written the file to the server, I create an anchor link for the file via JavaScript and call its click() method so the PDF attachment will download. After that, I do another AJAX call in the done() method to delete the file, as I don't need to keep that file on the server; it's a temporary thing. Here is my final code:

Instructors.cfc (file upload form stays the same)

<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 (new file, takes over 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>

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...