จัดการดาวน์โหลดไฟล์จาก ajax post


392

ฉันมีแอพพลิเคชั่นจาวาสคริปต์ที่ส่ง ajax POST ไปยัง URL ที่กำหนด การตอบกลับอาจเป็นสตริง JSON หรืออาจเป็นไฟล์ (เป็นไฟล์แนบ) ฉันสามารถตรวจจับ Content-Type และ Content-Disposition ในการโทร ajax ของฉันได้อย่างง่ายดาย แต่เมื่อฉันตรวจพบว่าการตอบสนองมีไฟล์ฉันจะให้ลูกค้าดาวน์โหลดได้อย่างไร ฉันได้อ่านหัวข้อที่คล้ายกันจำนวนมากที่นี่ แต่ไม่มีกระทู้ใดที่ให้คำตอบที่ฉันต้องการ

ได้โปรดได้โปรดอย่าโพสต์คำตอบที่แนะนำว่าฉันไม่ควรใช้ ajax สำหรับสิ่งนี้หรือว่าฉันควรเปลี่ยนเส้นทางเบราว์เซอร์เพราะไม่มีตัวเลือกใด ๆ การใช้รูปแบบ HTML ธรรมดาไม่ได้เป็นตัวเลือก สิ่งที่ฉันต้องทำก็คือแสดงกล่องโต้ตอบดาวน์โหลดให้ลูกค้า สิ่งนี้สามารถทำได้และอย่างไร


สำหรับผู้ที่อ่านบทความนี้อ่านโพสต์นี้: stackoverflow.com/questions/20830309/…
sobhan

ฉันลบโซลูชันของคุณออกจากคำถามแล้ว คุณสามารถโพสต์เป็นคำตอบโพสต์ด้านล่าง แต่มันไม่ได้อยู่ในโพสต์คำถาม
Martijn Pieters

คำตอบ:


111

สร้างแบบฟอร์มใช้วิธี POST ส่งแบบฟอร์ม - ไม่จำเป็นต้องมี iframe เมื่อหน้าเซิร์ฟเวอร์ตอบสนองต่อคำขอให้เขียนหัวข้อการตอบกลับสำหรับประเภทไฟล์ mime และจะแสดงกล่องโต้ตอบดาวน์โหลด - ฉันทำมาหลายครั้งแล้ว

คุณต้องการประเภทของแอปพลิเคชั่น / ดาวน์โหลดเพียงค้นหาวิธีการจัดเตรียมการดาวน์โหลดสำหรับภาษาที่คุณใช้


35
ตามที่ระบุไว้ในคำถาม: "การใช้แบบฟอร์ม HTML ธรรมดาไม่ได้เป็นตัวเลือกด้วย"
Pavle Predic

13
ไม่เพราะการใช้ POST ปกติจะนำทางเบราว์เซอร์ไปยัง POST URL ฉันไม่ต้องการนำทางออกจากหน้า ฉันต้องการที่จะดำเนินการตามคำขอในพื้นหลังประมวลผลการตอบสนองและนำเสนอให้กับลูกค้า
Pavle Predic

6
หากเซิร์ฟเวอร์ส่งส่วนหัวกลับเหมือนคำตอบอื่น ๆ ก็จะเปิดในหน้าต่างใหม่ - ฉันเคยทำมาก่อน มันจะนำทางไปถ้าสคริปต์ฝั่งเซิร์ฟเวอร์ของคุณส่งคืนรหัส HTML

1
@PavlePredic คุณคิดวิธีจัดการกับสถานการณ์การตอบสนองทั้งสองอย่างเช่นการตอบกลับข้อความ JSON หรือการตอบสนองการดาวน์โหลดไฟล์หรือไม่
ผู้ใช้เว็บ

9
คำตอบไม่ชัดเจนและวิธีแก้ปัญหาที่เสนอไม่ทำงาน
stack247

530

อย่ายอมแพ้อย่างรวดเร็วเพราะสิ่งนี้สามารถทำได้ (ในเบราว์เซอร์สมัยใหม่) โดยใช้บางส่วนของ FileAPI:

var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
    if (this.status === 200) {
        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, '');
        }
        var type = xhr.getResponseHeader('Content-Type');

        var blob;
        if (typeof File === 'function') {
            try {
                blob = new File([this.response], filename, { type: type });
            } catch (e) { /* Edge */ }
        }
        if (typeof blob === 'undefined') {
            blob = new Blob([this.response], { type: type });
        }

        if (typeof window.navigator.msSaveBlob !== 'undefined') {
            // IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
            window.navigator.msSaveBlob(blob, filename);
        } else {
            var URL = window.URL || window.webkitURL;
            var downloadUrl = URL.createObjectURL(blob);

            if (filename) {
                // use HTML5 a[download] attribute to specify filename
                var a = document.createElement("a");
                // safari doesn't support this yet
                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;
            }

            setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
        }
    }
};
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send($.param(params));

นี่คือเวอร์ชั่นเก่าที่ใช้ jQuery.ajax มันอาจทำให้ข้อมูลไบนารียุ่งเหยิงเมื่อการตอบสนองถูกแปลงเป็นสตริงของชุดอักขระบางชุด

$.ajax({
    type: "POST",
    url: url,
    data: params,
    success: function(response, status, xhr) {
        // check for a filename
        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, '');
        }

        var type = xhr.getResponseHeader('Content-Type');
        var blob = new Blob([response], { type: type });

        if (typeof window.navigator.msSaveBlob !== 'undefined') {
            // IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
            window.navigator.msSaveBlob(blob, filename);
        } else {
            var URL = window.URL || window.webkitURL;
            var downloadUrl = URL.createObjectURL(blob);

            if (filename) {
                // use HTML5 a[download] attribute to specify filename
                var a = document.createElement("a");
                // safari doesn't support this yet
                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;
            }

            setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
        }
    }
});

1
ขอบคุณมาก ! ฉันต้องเพิ่ม 'การจัดการเนื้อหา' ลงในทั้ง 'การเข้าถึงการควบคุม - เปิดเผย - ส่วนหัว' และ 'การเข้าถึงการควบคุม - อนุญาต - ส่วนหัว' ในการตอบกลับ HTTP ของฉันเพื่อให้ทำงานได้
JulienD

1
มันไม่ทำงานเมื่อไฟล์มีขนาดใหญ่กว่า 500 mb บางทีเราควรใช้ API อื่น?
hirra

แล้วการลบองค์ประกอบออกจาก DOM ในส่วนของการล้างข้อมูล (ไม่ใช่แค่ URL) document.body.removeChild(a);
Scoregraphic

@hirra ใช้ responceType "หยด" แทน "arraybuffer" และเขียนฟังก์ชั่นการโทรกลับ onload ว่าวิธีการที่หยด var เป็น this.response (var หยด = this.response;) ดังนั้นvar blob =this.responce; /** if (typeof File === 'function') { try { blob = new File([this.response], filename, { type: type }); } catch (e) { /* Edge */ } } if (typeof blob === 'undefined') { blob = new Blob([this.response], { type: type }); } */
คริส Tobba

1
นี่คือทางออกที่สมบูรณ์แบบ เพียงแค่การเปลี่ยนแปลงเล็กน้อย ใน typescript ฉันต้องการwindow.location.href = downloadUrlแทนwindow.location = downloadUrl
michal.jakubeczy

39

ฉันประสบปัญหาเดียวกันและแก้ไขได้สำเร็จ กรณีการใช้งานของฉันคือสิ่งนี้

" โพสต์ข้อมูล JSON ไปยังเซิร์ฟเวอร์และรับไฟล์ excel ไฟล์ excel นั้นสร้างโดยเซิร์ฟเวอร์และส่งคืนเป็นการตอบกลับไปยังไคลเอนต์ดาวน์โหลดการตอบสนองนั้นเป็นไฟล์ที่มีชื่อที่กำหนดเองในเบราว์เซอร์ "

$("#my-button").on("click", function(){

// Data to post
data = {
    ids: [1, 2, 3, 4, 5]
};

// Use XMLHttpRequest instead of Jquery $ajax
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
    var a;
    if (xhttp.readyState === 4 && xhttp.status === 200) {
        // Trick for making downloadable link
        a = document.createElement('a');
        a.href = window.URL.createObjectURL(xhttp.response);
        // Give filename you wish to download
        a.download = "test-file.xls";
        a.style.display = 'none';
        document.body.appendChild(a);
        a.click();
    }
};
// Post data to URL which handles post request
xhttp.open("POST", excelDownloadUrl);
xhttp.setRequestHeader("Content-Type", "application/json");
// You should set responseType as blob for binary responses
xhttp.responseType = 'blob';
xhttp.send(JSON.stringify(data));
});

ตัวอย่างด้านบนกำลังทำตาม

  • การโพสต์อาร์เรย์เป็น JSON ไปยังเซิร์ฟเวอร์โดยใช้ XMLHttpRequest
  • หลังจากดึงเนื้อหาเป็นหยด (ไบนารี่) เรากำลังสร้าง URL ที่สามารถดาวน์โหลดได้และติดกับลิงค์ "a" ที่มองไม่เห็นจากนั้นคลิกมัน

ที่นี่เราจำเป็นต้องตั้งค่าบางอย่างที่ด้านเซิร์ฟเวอร์ ฉันตั้งค่าส่วนหัวเล็กน้อยใน Python Django HttpResponse คุณต้องตั้งค่าให้เหมาะสมหากคุณใช้ภาษาโปรแกรมอื่น

# In python django code
response = HttpResponse(file_content, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")

เนื่องจากฉันดาวน์โหลด xls (excel) ที่นี่ฉันจึงปรับ contentType ให้อยู่ด้านบน คุณต้องตั้งค่าตามประเภทไฟล์ของคุณ คุณสามารถใช้เทคนิคนี้เพื่อดาวน์โหลดไฟล์ประเภทใดก็ได้


33

คุณใช้ภาษาฝั่งเซิร์ฟเวอร์อะไร ในแอพของฉันฉันสามารถดาวน์โหลดไฟล์จากการโทร AJAX ได้อย่างง่ายดายโดยการตั้งค่าส่วนหัวที่ถูกต้องในการตอบสนองของ PHP:

การตั้งค่าส่วนหัวฝั่งเซิร์ฟเวอร์

header("HTTP/1.1 200 OK");
header("Pragma: public");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");

// The optional second 'replace' parameter indicates whether the header
// should replace a previous similar header, or add a second header of
// the same type. By default it will replace, but if you pass in FALSE
// as the second argument you can force multiple headers of the same type.
header("Cache-Control: private", false);

header("Content-type: " . $mimeType);

// $strFileName is, of course, the filename of the file being downloaded. 
// This won't have to be the same name as the actual file.
header("Content-Disposition: attachment; filename=\"{$strFileName}\""); 

header("Content-Transfer-Encoding: binary");
header("Content-Length: " . mb_strlen($strFile));

// $strFile is a binary representation of the file that is being downloaded.
echo $strFile;

ในความเป็นจริงนี้จะ 'เปลี่ยนเส้นทาง' เบราว์เซอร์ไปยังหน้าดาวน์โหลดนี้ แต่เมื่อ @ahren alread กล่าวในความคิดเห็นของเขามันจะไม่นำทางไปจากหน้าปัจจุบัน

ทุกอย่างเกี่ยวกับการตั้งค่าส่วนหัวที่ถูกต้องดังนั้นฉันมั่นใจว่าคุณจะพบโซลูชันที่เหมาะสมสำหรับภาษาฝั่งเซิร์ฟเวอร์ที่คุณใช้หากไม่ใช่ PHP

การจัดการฝั่งไคลเอ็นต์การตอบสนอง

สมมติว่าคุณทราบวิธีการโทร AJAX ในฝั่งไคลเอ็นต์คุณจะดำเนินการร้องขอ AJAX ไปยังเซิร์ฟเวอร์ เซิร์ฟเวอร์จะสร้างลิงค์จากที่ไฟล์นี้สามารถดาวน์โหลดได้เช่น URL 'ส่งต่อ' ที่คุณต้องการชี้ไปที่ ตัวอย่างเช่นเซิร์ฟเวอร์ตอบสนองด้วย:

{
    status: 1, // ok
    // unique one-time download token, not required of course
    message: 'http://yourwebsite.com/getdownload/ska08912dsa'
}

เมื่อประมวลผลการตอบสนองคุณฉีดเข้าไปiframeในร่างกายของคุณและตั้งค่าiframeSRC ของเป็น URL ที่คุณเพิ่งได้รับเช่นนี้ (ใช้ jQuery เพื่อความง่ายของตัวอย่างนี้):

$("body").append("<iframe src='" + data.message +
  "' style='display: none;' ></iframe>");

หากคุณตั้งค่าส่วนหัวที่ถูกต้องตามที่แสดงด้านบน iframe จะบังคับให้กล่องโต้ตอบดาวน์โหลดโดยไม่ต้องนำทางเบราว์เซอร์ออกจากหน้าปัจจุบัน

บันทึก

นอกจากนี้เกี่ยวกับคำถามของคุณ ฉันคิดว่าเป็นการดีที่สุดที่จะส่งคืน JSON ทุกครั้งเมื่อขอข้อมูลด้วยเทคโนโลยี AJAX หลังจากที่คุณได้รับการตอบกลับ JSON คุณสามารถตัดสินใจว่าจะทำอย่างไรกับไคลเอ็นต์ ตัวอย่างเช่นในภายหลังคุณต้องการให้ผู้ใช้คลิกลิงก์ดาวน์โหลดไปยัง URL แทนที่จะบังคับให้ดาวน์โหลดโดยตรงในการตั้งค่าปัจจุบันของคุณคุณจะต้องอัปเดตทั้งไคลเอนต์และฝั่งเซิร์ฟเวอร์ให้ทำ


24

สำหรับผู้ที่กำลังมองหาวิธีการแก้ปัญหาจากมุมมองเชิงมุมนี้เหมาะกับฉัน:

$http.post(
  'url',
  {},
  {responseType: 'arraybuffer'}
).then(function (response) {
  var headers = response.headers();
  var blob = new Blob([response.data],{type:headers['content-type']});
  var link = document.createElement('a');
  link.href = window.URL.createObjectURL(blob);
  link.download = "Filename";
  link.click();
});

สิ่งนี้ช่วยได้ แต่ฉันต้องเก็บชื่อไฟล์ดั้งเดิมไว้ ฉันเห็นชื่อไฟล์ในส่วนหัวการตอบสนองภายใต้ "เนื้อหา - การจัดการ" แต่ฉันไม่สามารถหาค่านั้นในวัตถุการตอบสนองในรหัส การตั้งค่าlink.download = ""ผลลัพธ์ในชื่อไฟล์ guid แบบสุ่มและlink.download = nullผลลัพธ์ในไฟล์ชื่อ "null"
มารี

@Marie คุณสามารถบันทึกชื่อไฟล์ในเวลาที่อัปโหลดโดยใช้คุณสมบัติINPUTขององค์ประกอบ HTMLInputElement.filesดูเอกสาร MDN ในอินพุตไฟล์สำหรับรายละเอียดเพิ่มเติม
Tim Hettler

ขนาดของ Blob มี จำกัด : stackoverflow.com/questions/28307789/…
user0800

22

นี่คือวิธีที่ฉันใช้งาน https://stackoverflow.com/a/27563953/2845977

$.ajax({
  url: '<URL_TO_FILE>',
  success: function(data) {
    var blob=new Blob([data]);
    var link=document.createElement('a');
    link.href=window.URL.createObjectURL(blob);
    link.download="<FILENAME_TO_SAVE_WITH_EXTENSION>";
    link.click();
  }
});

อัปเดตคำตอบโดยใช้download.js

$.ajax({
  url: '<URL_TO_FILE>',
  success: download.bind(true, "<FILENAME_TO_SAVE_WITH_EXTENSION>", "<FILE_MIME_TYPE>")
});


ขอบคุณสำหรับสิ่งนี้ฉันเพิ่งใช้มันวันนี้ ยอดเยี่ยม
Ryan Wilson

สวัสดีฉันต้องมี jQuery 3.0> เพื่อให้ใช้งานได้หรือไม่
gbade_

ฉันยังได้รับ PDF เปล่าพร้อมทั้งตัวอย่างที่คุณให้ ฉันพยายามดาวน์โหลดไฟล์ pdf
gbade_

@gbade_ ไม่คุณไม่ต้องการรุ่น jQuery ใด ๆ ควรใช้งานได้ดีกับทุกรุ่น คุณตรวจสอบว่า PDF ที่คุณกำลังดาวน์โหลดมีส่วนหัว CORS ที่ถูกต้องหรือไม่? ข้อผิดพลาดใด ๆ บนคอนโซลอาจช่วยในการแก้ไขข้อผิดพลาด
Mayur Padshala

สิ่งนี้ใช้ได้กับฉันโดยใช้ download.js:success: function (response, status, request) { download(response, "filename.txt", "application/text"); }
Sga

12

ฉันเห็นว่าคุณได้พบวิธีแก้ไขปัญหาแล้ว แต่ฉันต้องการเพิ่มข้อมูลบางอย่างซึ่งอาจช่วยให้ใครบางคนพยายามทำสิ่งเดียวกันให้สำเร็จด้วยคำขอ POST ขนาดใหญ่

ฉันมีปัญหาเดียวกันเมื่อสองสามสัปดาห์ที่ผ่านมาแน่นอนว่ามันเป็นไปไม่ได้ที่จะดาวน์โหลด "สะอาด" ผ่าน AJAX กลุ่ม Filament สร้างปลั๊กอิน jQuery ซึ่งทำงานอย่างที่คุณรู้แล้วมันเรียกว่าjQuery File ดาวน์โหลดแต่มีข้อเสียของเทคนิคนี้

หากคุณกำลังส่งคำขอใหญ่ผ่าน AJAX (พูดไฟล์ + 1MB) จะส่งผลเสียต่อการตอบสนอง ในการเชื่อมต่ออินเทอร์เน็ตที่ช้าคุณจะต้องรออย่างมากจนกว่าจะมีการส่งคำขอและรอให้ไฟล์ดาวน์โหลด ไม่เหมือนกับการคลิก "คลิก" => "ป๊อปอัป" => "เริ่มการดาวน์โหลด" มันเหมือน "คลิก" => "รอจนกว่าจะมีการส่งข้อมูล" => "รอการตอบกลับ" => "เริ่มการดาวน์โหลด" ซึ่งทำให้ไฟล์มีขนาดใหญ่เป็นสองเท่าเพราะคุณจะต้องรอคำขอที่จะส่ง ผ่าน AJAX และรับกลับเป็นไฟล์ที่ดาวน์โหลดได้

หากคุณทำงานกับไฟล์ขนาดเล็ก <1MB คุณจะไม่สังเกตเห็นสิ่งนี้ แต่เมื่อฉันค้นพบในแอพของฉันเองสำหรับไฟล์ขนาดใหญ่มันแทบจะทนไม่ไหว

แอพของฉันอนุญาตให้ผู้ใช้ส่งออกรูปภาพที่สร้างขึ้นแบบไดนามิกรูปภาพเหล่านี้ส่งผ่านคำขอ POST ในรูปแบบ base64 ไปยังเซิร์ฟเวอร์ (เป็นวิธีที่เป็นไปได้เท่านั้น) จากนั้นประมวลผลและส่งกลับไปยังผู้ใช้ในรูปแบบของไฟล์. png, .jpg, base64 สายอักขระสำหรับรูปภาพ + 1MB มีขนาดใหญ่บังคับให้ผู้ใช้รอมากกว่าที่จำเป็นสำหรับไฟล์เพื่อเริ่มดาวน์โหลด ในการเชื่อมต่ออินเทอร์เน็ตที่ช้าอาจเป็นเรื่องที่น่ารำคาญจริงๆ

วิธีแก้ปัญหาของฉันคือการเขียนไฟล์ไปยังเซิร์ฟเวอร์ชั่วคราวเมื่อพร้อมแล้วให้สร้างลิงค์ไปยังไฟล์ในรูปแบบของปุ่มที่เปลี่ยนแปลงระหว่าง "โปรดรอ ... " และ "ดาวน์โหลด" และแบบเดียวกัน เวลาพิมพ์ภาพ base64 ในหน้าต่างป๊อปอัพดูตัวอย่างเพื่อให้ผู้ใช้สามารถ "คลิกขวา" และบันทึก สิ่งนี้ทำให้ผู้ใช้ทุกคนรอคอยได้นานขึ้นและยังเพิ่มความเร็วให้อีกด้วย

อัปเดต 30 กันยายน 2557:

หลายเดือนผ่านไปตั้งแต่ฉันโพสต์สิ่งนี้ในที่สุดฉันก็พบวิธีที่ดีกว่าในการเพิ่มความเร็วในการทำงานกับสตริงเบส 64 ขนาดใหญ่ ตอนนี้ฉันเก็บสตริง base64 ลงในฐานข้อมูล (โดยใช้ longtext หรือ longblog field) จากนั้นฉันก็ส่ง ID บันทึกผ่านการดาวน์โหลดไฟล์ jQuery ในที่สุดเมื่อดาวน์โหลดสคริปต์ไฟล์ฉันค้นหาฐานข้อมูลโดยใช้ ID นี้เพื่อดึงสตริง base64 และส่งผ่าน ฟังก์ชั่นดาวน์โหลด

ดาวน์โหลดตัวอย่างสคริปต์:

<?php
// Record ID
$downloadID = (int)$_POST['id'];
// Query Data (this example uses CodeIgniter)
$data       = $CI->MyQueries->GetDownload( $downloadID );
// base64 tags are replaced by [removed], so we strip them out
$base64     = base64_decode( preg_replace('#\[removed\]#', '', $data[0]->image) );
// This example is for base64 images
$imgsize    = getimagesize( $base64 );
// Set content headers
header('Content-Disposition: attachment; filename="my-file.png"');
header('Content-type: '.$imgsize['mime']);
// Force download
echo $base64;
?>

ฉันรู้ว่านี่เป็นวิธีที่นอกเหนือจากที่ OP ถาม แต่ฉันรู้สึกว่าเป็นการดีที่จะปรับปรุงคำตอบด้วยการค้นพบของฉัน เมื่อฉันค้นหาวิธีแก้ไขปัญหาของฉันฉันอ่านเธรด"ดาวน์โหลดจากข้อมูล AJAX POST"จำนวนมากซึ่งไม่ได้ให้คำตอบที่ฉันกำลังมองหาฉันหวังว่าข้อมูลนี้จะช่วยให้บางคนมองหาสิ่งนี้


jQuery File Downloadเพียงเปลี่ยนเส้นทางฉันไปยัง URL jQuery.download("api/ide/download-this-file.php", {filePath: path2Down}, "POST");ผมเรียกมันเช่นนี้
Casper

5

ฉันต้องการชี้ให้เห็นปัญหาบางอย่างที่เกิดขึ้นเมื่อใช้เทคนิคในคำตอบที่ยอมรับคือใช้แบบฟอร์มโพสต์:

  1. คุณไม่สามารถตั้งค่าส่วนหัวตามคำขอ หากสคีมาตรวจสอบความถูกต้องของคุณเกี่ยวข้องกับส่วนหัว Json-Web-Token ถูกส่งผ่านในส่วนหัวการให้สิทธิ์คุณจะต้องค้นหาวิธีอื่นในการส่งตัวอย่างเช่นพารามิเตอร์การสืบค้น

  2. คุณไม่สามารถบอกได้ว่าเมื่อคำขอเสร็จสิ้น คุณสามารถใช้คุกกี้ที่ได้รับการตั้งค่าตามการตอบสนองโดยjquery.fileDownloadแต่มันห่างไกลจากความสมบูรณ์แบบ มันจะไม่ทำงานสำหรับคำขอที่เกิดขึ้นพร้อมกันและมันจะหยุดลงถ้าการตอบสนองไม่มา

  3. หากเซิร์ฟเวอร์ตอบสนองด้วยข้อผิดพลาดผู้ใช้จะถูกเปลี่ยนเส้นทางไปยังหน้าข้อผิดพลาด

  4. คุณสามารถใช้ประเภทเนื้อหาที่สนับสนุนโดยแบบฟอร์มเท่านั้น ซึ่งหมายความว่าคุณไม่สามารถใช้ JSON ได้

ฉันลงเอยด้วยการใช้วิธีการบันทึกไฟล์ใน S3 และส่ง URL ที่ลงชื่อล่วงหน้าเพื่อรับไฟล์


5

fetch APIสำหรับผู้ที่มองหาวิธีการที่ทันสมัยมากขึ้นคุณสามารถใช้ ตัวอย่างต่อไปนี้แสดงวิธีการดาวน์โหลดไฟล์สเปรดชีต ทำได้ง่ายด้วยรหัสต่อไปนี้

fetch(url, {
    body: JSON.stringify(data),
    method: 'POST',
    headers: {
        'Content-Type': 'application/json; charset=utf-8'
    },
})
.then(response => response.blob())
.then(response => {
    const blob = new Blob([response], {type: 'application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
    const downloadUrl = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = downloadUrl;
    a.download = "file.xlsx";
    document.body.appendChild(a);
    a.click();
})

ฉันเชื่อว่าวิธีการนี้จะเข้าใจได้ง่ายกว่าXMLHttpRequestวิธีอื่น ๆ นอกจากนี้ยังมีไวยากรณ์ที่คล้ายกันกับjQueryวิธีการโดยไม่จำเป็นต้องเพิ่มห้องสมุดเพิ่มเติมใด ๆ

แน่นอนฉันจะแนะนำให้ตรวจสอบเบราว์เซอร์ที่คุณกำลังพัฒนาเนื่องจากวิธีการใหม่นี้จะไม่ทำงานบน IE คุณสามารถค้นหารายการความเข้ากันได้ของเบราว์เซอร์เต็มรูปแบบได้ที่ [ลิงค์] [1]

สำคัญ : ในตัวอย่างนี้ผมส่งคำขอ JSON urlเพื่อฟังบนเซิร์ฟเวอร์ที่กำหนด นี้urlจะต้องมีการตั้งค่าในตัวอย่างของฉันฉันสมมติว่าคุณรู้ว่าส่วนนี้ นอกจากนี้ให้พิจารณาส่วนหัวที่จำเป็นสำหรับคำขอของคุณในการทำงาน เนื่องจากฉันกำลังส่ง JSON ฉันต้องเพิ่มContent-Typeส่วนหัวและตั้งค่าapplication/json; charset=utf-8เป็นเพื่อให้เซิร์ฟเวอร์ทราบประเภทของคำขอที่จะได้รับ


1
! น่ากลัว ในการเปิดในแท็บใหม่แทนที่จะเป็นป๊อปอัพดาวน์โหลดให้ใช้ `` `const window = open (downloadUrl," _blank "); if (window! == null) window.focus (); `` `
andy

มีวิธีให้ฉันทำเช่นนี้กับชุดข้อมูลหลายชุดหรือไม่? ตัวอย่างเช่นรับกลับ {fileOne: data, fileTwo: data, fileThree: data} จากการเรียก api และสร้างไฟล์ที่ดาวน์โหลดมาสามไฟล์พร้อมกันหรือไม่ ขอบคุณ!
il0v3d0g

อืมฉันไม่ได้ลองเลย แต่คุณสามารถบีบอัดรูปภาพในไฟล์ zip และดาวน์โหลดได้เสมอ ฉันจะตรวจสอบว่ามันเป็นไปได้
Alain Cruz

4

นี่คือวิธีการแก้ปัญหาของฉันโดยใช้แบบฟอร์มที่ซ่อนอยู่ชั่วคราว

//Create an hidden form
var form = $('<form>', {'method': 'POST', 'action': this.href}).hide();

//Add params
var params = { ...your params... };
$.each(params, function (k, v) {
    form.append($('<input>', {'type': 'hidden', 'name': k, 'value': v}));
});

//Make it part of the document and submit
$('body').append(form);
form.submit();

//Clean up
form.remove();

โปรดทราบว่าฉันใช้ JQuery อย่างหนาแน่น แต่คุณสามารถทำเช่นเดียวกันกับ JS พื้นเมือง


3

ดังที่คนอื่น ๆ ระบุไว้คุณสามารถสร้างและส่งแบบฟอร์มเพื่อดาวน์โหลดผ่านคำขอ POST อย่างไรก็ตามคุณไม่ต้องทำด้วยตนเอง

หนึ่งห้องสมุดง่ายจริงๆสำหรับการทำตรงนี้เป็นjquery.redirect มันให้ API คล้ายกับjQuery.postวิธีมาตรฐาน:

$.redirect(url, [values, [method, [target]]])

3

นี่เป็นคำถาม 3 ปี แต่ฉันมีปัญหาเดียวกันวันนี้ ฉันดูวิธีการแก้ไขของคุณ แต่ฉันคิดว่ามันสามารถเสียสละประสิทธิภาพได้เพราะต้องทำสองครั้ง ดังนั้นหากใครต้องการโซลูชันอื่นที่ไม่ได้หมายถึงการโทรติดต่อบริการสองครั้งนี่คือวิธีที่ฉันทำ:

<form id="export-csv-form" method="POST" action="/the/path/to/file">
    <input type="hidden" name="anyValueToPassTheServer" value="">
</form>

แบบฟอร์มนี้ใช้เพื่อเรียกใช้บริการและหลีกเลี่ยงการใช้ window.location () หลังจากนั้นคุณเพียงแค่ต้องส่งแบบฟอร์มจาก jquery เพื่อเรียกใช้บริการและรับไฟล์ มันง่ายสวย แต่วิธีนี้คุณสามารถทำให้การดาวน์โหลดใช้POST ตอนนี้ฉันสามารถทำได้ง่ายกว่านี้ถ้าบริการที่คุณโทรมาเป็นGETแต่นั่นไม่ใช่กรณีของฉัน


1
นี่ไม่ใช่
อาแจ็กซ์

มันเป็นเพียงวิธีการแก้ไขปัญหาข้างต้น แต่ไม่ใช่สำหรับการโทร ajax
Nomi Ali

1

ฉันใช้FileSaver.jsนี้ ในกรณีของฉันด้วยไฟล์ csv ฉันทำสิ่งนี้ (ใน coffescript):

  $.ajax
    url: "url-to-server"
    data: "data-to-send"
    success: (csvData)->
      blob = new Blob([csvData], { type: 'text/csv' })
      saveAs(blob, "filename.csv")

ฉันคิดว่าในกรณีที่ซับซ้อนที่สุดข้อมูลจะต้องถูกประมวลผลอย่างเหมาะสม ภายใต้ FileSaver.js ประทุนใช้วิธีการเดียวกันของคำตอบของโจนาธานแก้ไข


1
.. แต่คุณสามารถดาวน์โหลดไฟล์ใน iOS ได้หรือไม่
อเล็กซ์มาร์แชลล์

1

โปรดดู: http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/ มันจะส่งคืนการตอบสนองซึ่งหยดแล้วซึ่งสามารถใส่ลงใน filesaver


2
ในขณะที่สิ่งนี้อาจตอบคำถามในทางทฤษฎีมันก็ควรที่จะรวมส่วนสำคัญของคำตอบที่นี่และให้ลิงค์สำหรับการอ้างอิง
Bhargav Rao

1

ในการรับJonathan Amends คำตอบสำหรับการทำงานใน Edge ฉันได้ทำการเปลี่ยนแปลงต่อไปนี้:

var blob = typeof File === 'function'
    ? new File([this.response], filename, { type: type })
    : new Blob([this.response], { type: type });

สำหรับสิ่งนี้

var f = typeof File+"";
var blob = f === 'function' && Modernizr.fileapi
    ? new File([this.response], filename, { type: type })
    : new Blob([this.response], { type: type });

ฉันอยากจะโพสต์สิ่งนี้เป็นความคิดเห็น แต่ฉันไม่มีชื่อเสียงเพียงพอ


0

นี่คือโซลูชันของฉันรวบรวมจากแหล่งต่าง ๆ : การใช้งานฝั่งเซิร์ฟเวอร์:

    String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
    // Set headers
    response.setHeader("content-disposition", "attachment; filename =" + fileName);
    response.setContentType(contentType);
    // Copy file to output stream
    ServletOutputStream servletOutputStream = response.getOutputStream();
    try (InputStream inputStream = new FileInputStream(file)) {
        IOUtils.copy(inputStream, servletOutputStream);
    } finally {
        servletOutputStream.flush();
        Utils.closeQuitely(servletOutputStream);
        fileToDownload = null;
    }

การใช้งานฝั่งไคลเอ็นต์ (โดยใช้ jquery):

$.ajax({
type: 'POST',
contentType: 'application/json',
    url: <download file url>,
    data: JSON.stringify(postObject),
    error: function(XMLHttpRequest, textStatus, errorThrown) {
        alert(errorThrown);
    },
    success: function(message, textStatus, response) {
       var header = response.getResponseHeader('Content-Disposition');
       var fileName = header.split("=")[1];
       var blob = new Blob([message]);
       var link = document.createElement('a');
       link.href = window.URL.createObjectURL(blob);
       link.download = fileName;
       link.click();
    }
});   

0

มีวิธีการอื่นในการดาวน์โหลดเว็บเพจใน ajax แต่ฉันหมายถึงหน้าที่จะต้องดำเนินการก่อนแล้วจึงดาวน์โหลด

ก่อนอื่นคุณต้องแยกการประมวลผลหน้าออกจากการดาวน์โหลดผลลัพธ์

1) เฉพาะการคำนวณหน้าเท่านั้นในการโทร ajax

$ .post ("CalculusPage.php", {calculusFunction: true, ID: 29, data1: "a", data2: "b"},

       ฟังก์ชั่น (ข้อมูลสถานะ) 
       {
            ถ้า (status == "สำเร็จ") 
            {
                / * 2) ในคำตอบหน้าเว็บที่ใช้การคำนวณก่อนหน้านี้ถูกดาวน์โหลด ตัวอย่างเช่นนี่อาจเป็นหน้าที่พิมพ์ผลลัพธ์ของตารางที่คำนวณในการโทร ajax * /
                window.location.href = DownloadPage.php + "? ID =" + 29;
            }               
       }
);

// ตัวอย่างเช่น: ใน CalculusPage.php

    ถ้า (! ว่างเปล่า ($ _ POST ["calculusFunction"])) 
    {
        $ ID = $ _POST ["ID"];

        $ query = "INSERT INTO ExamplePage (data1, data2) ค่า ('". $ _ POST ["data1"]. "', '". $ _ POST ["data2"]. "ID WHERE ID =". $ ID;
        ...
    }

// ตัวอย่างเช่น: ใน DownloadPage.php

    $ ID = $ _GET ["ID"];

    $ sede = "SELECT * จากตัวอย่างหน้า WHERE id =". $ ID;
    ...

    $ ชื่อไฟล์ = "Export_Data.xls";
    header ("ประเภทเนื้อหา: application / vnd.ms-excel");
    header ("การจัดการเนื้อหา: inline; filename = $ filename");

    ...

ฉันหวังว่าโซลูชันนี้จะมีประโยชน์สำหรับหลาย ๆ คนเช่นเดียวกับฉัน


0

หากการตอบสนองเป็นArray Bufferให้ลองดำเนินการภายใต้เหตุการณ์ไม่สำเร็จใน Ajax:

 if (event.data instanceof ArrayBuffer) {
          var binary = '';
          var bytes = new Uint8Array(event.data);
          for (var i = 0; i < bytes.byteLength; i++) {
              binary += String.fromCharCode(bytes[i])
          }
          $("#some_id").append("<li><img src=\"data:image/png;base64," + window.btoa(binary) + "\"/></span></li>");
          return;
      }
  • โดยที่ event.data ได้รับการตอบรับในฟังก์ชั่นความสำเร็จของเหตุการณ์ xhr

0

ด้านล่างนี้เป็นวิธีการแก้ปัญหาของฉันสำหรับการดาวน์โหลดหลายไฟล์ขึ้นอยู่กับรายการซึ่งประกอบด้วยรหัสบางส่วนและค้นหาในฐานข้อมูลไฟล์จะถูกกำหนดและพร้อมสำหรับการดาวน์โหลด - ถ้ามีอยู่ ฉันกำลังเรียกการกระทำ C # MVC สำหรับแต่ละไฟล์โดยใช้ Ajax

และใช่เช่นเดียวกับคนอื่น ๆ กล่าวว่าเป็นไปได้ที่จะทำใน jQuery Ajax ฉันทำมันด้วยความสำเร็จของอาแจ็กซ์และฉันก็ส่งคำตอบ 200

ดังนั้นนี่คือกุญแจสำคัญ:

  success: function (data, textStatus, xhr) {

และนี่คือรหัสของฉัน:

var i = 0;
var max = 0;
function DownloadMultipleFiles() {
            if ($(".dataTables_scrollBody>tr.selected").length > 0) {
                var list = [];
                showPreloader();
                $(".dataTables_scrollBody>tr.selected").each(function (e) {
                    var element = $(this);
                    var orderid = element.data("orderid");
                    var iscustom = element.data("iscustom");
                    var orderlineid = element.data("orderlineid");
                    var folderPath = "";
                    var fileName = "";

                    list.push({ orderId: orderid, isCustomOrderLine: iscustom, orderLineId: orderlineid, folderPath: folderPath, fileName: fileName });
                });
                i = 0;
                max = list.length;
                DownloadFile(list);
            }
        }

จากนั้นโทร:

function DownloadFile(list) {
        $.ajax({
            url: '@Url.Action("OpenFile","OrderLines")',
            type: "post",
            data: list[i],
            xhrFields: {
                responseType: 'blob'
            },
            beforeSend: function (xhr) {
                xhr.setRequestHeader("RequestVerificationToken",
                    $('input:hidden[name="__RequestVerificationToken"]').val());

            },
            success: function (data, textStatus, xhr) {
                // check for a filename
                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, '');
                    var a = document.createElement('a');
                    var url = window.URL.createObjectURL(data);
                    a.href = url;
                    a.download = filename;
                    document.body.append(a);
                    a.click();
                    a.remove();
                    window.URL.revokeObjectURL(url);
                }
                else {
                    getErrorToastMessage("Production file for order line " + list[i].orderLineId + " does not exist");
                }
                i = i + 1;
                if (i < max) {
                    DownloadFile(list);
                }
            },
            error: function (XMLHttpRequest, textStatus, errorThrown) {

            },
            complete: function () {
                if(i===max)
                hidePreloader();
            }
        });
    }

C # MVC:

 [HttpPost]
 [ValidateAntiForgeryToken]
public IActionResult OpenFile(OrderLineSimpleModel model)
        {
            byte[] file = null;

            try
            {
                if (model != null)
                {
                    //code for getting file from api - part is missing here as not important for this example
                    file = apiHandler.Get<byte[]>(downloadApiUrl, token);

                    var contentDispositionHeader = new System.Net.Mime.ContentDisposition
                    {
                        Inline = true,
                        FileName = fileName
                    };
                    //    Response.Headers.Add("Content-Disposition", contentDispositionHeader.ToString() + "; attachment");
                    Response.Headers.Add("Content-Type", "application/pdf");
                    Response.Headers.Add("Content-Disposition", "attachment; filename=" + fileName);
                    Response.Headers.Add("Content-Transfer-Encoding", "binary");
                    Response.Headers.Add("Content-Length", file.Length.ToString());

                }
            }
            catch (Exception ex)
            {
                this.logger.LogError(ex, "Error getting pdf", null);
                return Ok();
            }

            return File(file, System.Net.Mime.MediaTypeNames.Application.Pdf);
        }

ตราบใดที่คุณคืนการตอบสนอง 200 ความสำเร็จใน Ajax สามารถทำงานกับมันได้คุณสามารถตรวจสอบว่าไฟล์มีอยู่จริงหรือไม่เพราะบรรทัดด้านล่างในกรณีนี้จะเป็นเท็จและคุณสามารถแจ้งให้ผู้ใช้ทราบเกี่ยวกับสิ่งนั้น:

 if (disposition && disposition.indexOf('attachment') !== -1) {

0

ฉันต้องการโซลูชันที่คล้ายกับ @ alain-cruz แต่ใน nuxt / vue พร้อมการดาวน์โหลดหลายครั้ง ฉันรู้ว่าเบราว์เซอร์บล็อกการดาวน์โหลดไฟล์หลายครั้งและฉันยังมี API ซึ่งส่งคืนชุดข้อมูลที่จัดรูปแบบ csv ฉันจะใช้ JSZip ในตอนแรก แต่ฉันต้องการการสนับสนุน IE ดังนั้นนี่คือโซลูชันของฉัน หากใครสามารถช่วยฉันปรับปรุงสิ่งนี้ให้ดีขึ้นได้ แต่มันก็ใช้ได้ผลสำหรับฉันแล้ว

API ส่งคืน:

data : {
  body: {
    fileOne: ""col1", "col2", "datarow1.1", "datarow1.2"...so on",
    fileTwo: ""col1", "col2"..."
  }
}

page.vue:

<template>
  <b-link @click.prevent="handleFileExport">Export<b-link>
</template>

export default = {
   data() {
     return {
       fileNames: ['fileOne', 'fileTwo'],
     }
   },
  computed: {
    ...mapState({
       fileOne: (state) => state.exportFile.fileOne,
       fileTwo: (state) => state.exportFile.fileTwo,
    }),
  },
  method: {
    handleExport() {
      //exportFileAction in store/exportFile needs to return promise
      this.$store.dispatch('exportFile/exportFileAction', paramsToSend)
        .then(async (response) => {
           const downloadPrep = this.fileNames.map(async (fileName) => {
           // using lodash to get computed data by the file name
           const currentData = await _.get(this, `${fileName}`);
           const currentFileName = fileName;
           return { currentData, currentFileName };
         });
         const response = await Promise.all(downloadPrep);
         return response;
       })
       .then(async (data) => {
         data.forEach(({ currentData, currentFileName }) => {
           this.forceFileDownload(currentData, currentFileName);
         });
       })
       .catch(console.error);
    },
    forceFileDownload(data, fileName) {
     const url = window.URL
         .createObjectURL(new Blob([data], { type: 'text/csv;charset=utf-8;' }));
     const link = document.createElement('a');
     link.href = url;
     link.setAttribute('download', `${fileName}.csv`);
     document.body.appendChild(link);
     link.click();
   },
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.