AngularJS $http-post - конвертировать двоичный файл в файл Excel и скачивать
Я создал приложение в Angular JS для загрузки книги Excel через пост $http.
В приведенном ниже коде я передаю информацию в виде JSON и отправляю ее на веб-службу REST сервера (java) через угловой пост $http. Веб-сервис использует информацию из JSON и создает книгу Excel. В ответе в теле сообщения об успехе $http я получаю двоичные данные в этой переменной данных, но не знаю, как их преобразовать и загрузить в виде файла Excel.
Может кто-нибудь, пожалуйста, скажите мне какое-нибудь решение для этого для преобразования двоичного файла в файл Excel и загрузки?
Мой код как указано ниже:
$http({
url: 'myweb.com/myrestService',
method: "POST",
data: json, //this is your json data string
headers: {
'Content-type': 'application/json'
}
}).success(function (data, status, headers, config) {
// Here i'm getting excel sheet binary datas in 'data'
}).error(function (data, status, headers, config) {
});
9 ответов
Просто заметил, что вы не можете использовать его из-за IE8/9, но я все равно отправлю подтверждение... может быть, кто-то найдет это полезным
Это можно сделать через браузер, используя blob
, Обратите внимание на responseType
и код в success
Обещаю.
$http({
url: 'your/webservice',
method: "POST",
data: json, //this is your json data string
headers: {
'Content-type': 'application/json'
},
responseType: 'arraybuffer'
}).success(function (data, status, headers, config) {
var blob = new Blob([data], {type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"});
var objectUrl = URL.createObjectURL(blob);
window.open(objectUrl);
}).error(function (data, status, headers, config) {
//upload failed
});
Есть некоторые проблемы с этим, хотя как:
- Он не поддерживает IE 8 и 9:
- Он открывает всплывающее окно, чтобы открыть
objectUrl
какие люди могли заблокировать - Создает странные имена файлов
Это сработало!
Серверный код в PHP, с которым я тестировал, выглядит следующим образом. Я уверен, что вы можете установить аналогичные заголовки в Java:
$file = "file.xlsx";
header('Content-disposition: attachment; filename='.$file);
header('Content-Length: ' . filesize($file));
header('Content-Transfer-Encoding: binary');
header('Cache-Control: must-revalidate');
header('Pragma: public');
echo json_encode(readfile($file));
Изменить 20.04.2016
Браузеры затрудняют сохранение данных таким способом. Один хороший вариант - использовать файлы.saver.js. Это обеспечивает кросс-браузерную реализацию для saveAs
и он должен заменить часть кода в success
обещание выше.
Вот как вы это делаете:
- Забудьте IE8/IE9, это не стоит усилий и не возвращает деньги.
- Вам нужно использовать правильный HTTP-заголовок, использовать Accept для "application / vnd.openxmlformats-officedocument.spreadsheetml.sheet", а также вы должны поместить responseType в "arraybuffer" (ArrayBuffer, но заданный строчными буквами).
- HTML5 saveAs используется для сохранения фактических данных в желаемом формате. Обратите внимание, что это все еще будет работать без добавления типа в этом случае.
$http({ url: 'your/webservice', method: 'POST', responseType: 'arraybuffer', data: json, //this is your json data string headers: { 'Content-type': 'application/json', 'Accept': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' } }).success(function(data){ var blob = new Blob([data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); saveAs(blob, 'File_Name_With_Some_Unique_Id_Time' + '.xlsx'); }).error(function(){ //Some error log });
Совет! Не смешивайте "и", придерживайтесь всегда использовать ", в профессиональной среде вам придется пройти проверку js, например, jshint, то же самое касается использования === и не ==, и так далее, но это другая тема:)
Я бы поставил save excel в другой сервис, чтобы у вас была чистая структура, и пост был в собственном сервисе. Я могу сделать для тебя скрипку JS, если ты не получишь мой пример. Тогда мне также понадобятся некоторые данные JSON от вас, которые вы используете для полного примера.
Удачного кодирования.. Эдуардо
Загрузите ответ сервера в виде буфера массива. Сохраните его как BLOB-объект, используя тип контента с сервера (который должен быть application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
):
var httpPromise = this.$http.post(server, postData, { responseType: 'arraybuffer' });
httpPromise.then(response => this.save(new Blob([response.data],
{ type: response.headers('Content-Type') }), fileName));
Сохраните BLOB-объект на устройстве пользователя:
save(blob, fileName) {
if (window.navigator.msSaveOrOpenBlob) { // For IE:
navigator.msSaveBlob(blob, fileName);
} else { // For other browsers:
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = fileName;
link.click();
window.URL.revokeObjectURL(link.href);
}
}
Работал для меня -
$scope.downloadFile = function () {
Resource.downloadFile().then(function (response) {
var blob = new Blob([response.data], { type: "application/pdf" });
var objectUrl = URL.createObjectURL(blob);
window.open(objectUrl);
},
function (error) {
debugger;
});
};
Который вызывает следующее из моей фабрики ресурсов
downloadFile: function () {
var downloadRequst = {
method: 'GET',
url: 'http://localhost/api/downloadFile?fileId=dfckn4niudsifdh.pdf',
headers: {
'Content-Type': "application/pdf",
'Accept': "application/pdf"
},
responseType: 'arraybuffer'
}
return $http(downloadRequst);
}
Убедитесь, что ваш API также устанавливает тип содержимого заголовка -
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/pdf");
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
Нет никакого способа (насколько мне известно) вызвать окно загрузки в вашем браузере из Javascript. Единственный способ сделать это - перенаправить браузер на URL, который передает файл в браузер.
Если вы сможете изменить службу REST, вы сможете решить ее, изменив таким образом, чтобы запрос POST отвечал не двоичным файлом, а URL-адресом этого файла. Это даст вам URL-адрес в Javascript вместо двоичных данных, и вы сможете перенаправить браузер на этот URL-адрес, что должно привести к загрузке, не покидая исходную страницу.
Ответ № 5 работал для меня, предложение для разработчиков, которые сталкиваются с аналогичной проблемой.
//////////////////////////////////////////////////////////
//Server side
//////////////////////////////////////////////////////////
imports ***
public class AgentExcelBuilder extends AbstractExcelView {
protected void buildExcelDocument(Map<String, Object> model,
HSSFWorkbook workbook, HttpServletRequest request,
HttpServletResponse response) throws Exception {
//poi code goes here ....
response.setHeader("Cache-Control","must-revalidate");
response.setHeader("Pragma", "public");
response.setHeader("Content-Transfer-Encoding","binary");
response.setHeader("Content-disposition", "attachment; filename=test.xls");
OutputStream output = response.getOutputStream();
workbook.write(output);
System.out.println(workbook.getActiveSheetIndex());
System.out.println(workbook.getNumberOfSheets());
System.out.println(workbook.getNumberOfNames());
output.flush();
output.close();
}//method buildExcelDocument ENDS
//service.js at angular JS code
function getAgentInfoExcel(workgroup,callback){
$http({
url: CONTEXT_PATH+'/rest/getADInfoExcel',
method: "POST",
data: workgroup, //this is your json data string
headers: {
'Content-type': 'application/json'
},
responseType: 'arraybuffer'
}).success(function (data, status, headers, config) {
var blob = new Blob([data], {type: "application/vnd.ms-excel"});
var objectUrl = URL.createObjectURL(blob);
window.open(objectUrl);
}).error(function (data, status, headers, config) {
console.log('Failed to download Excel')
});
}
////////////////////////////////in .html
<div class="form-group">`enter code here`
<a href="javascript:void(0)" class="fa fa-file-excel-o"
ng-click="exportToExcel();"> Agent Export</a>
</div>
Вы также можете использовать альтернативный подход - вам не нужно использовать $http, вам не нужны дополнительные библиотеки, и он должен работать в любом браузере.
Просто разместите невидимую форму на своей странице.
<form name="downloadForm" action="/MyApp/MyFiles/Download" method="post" target="_self">
<input type="hidden" name="value1" value="{{ctrl.value1}}" />
<input type="hidden" name="value2" value="{{ctrl.value2}}" />
</form>
И поместите этот код в свой угловой контроллер.
ctrl.value1 = 'some value 1';
ctrl.value2 = 'some value 2';
$timeout(function () {
$window.document.forms['downloadForm'].submit();
});
Этот код опубликует ваши данные в /MyApp/MyFiles/Download, и вы получите файл в папке "Загрузки".
Работает с Internet Explorer 10.
Если обычная форма HTML не позволяет опубликовать сложный объект, у вас есть два варианта:
1. Стригируйте свой объект и поместите его в одно из полей формы в виде строки.
<input type="hidden" name="myObjJson" value="{{ctrl.myObj | json:0}}" />
2. Рассмотрим формы JSON в формате HTML: https://www.w3.org/TR/html-json-forms/
Я создал сервис, который сделает это для вас.
Пройти в стандарт $http
объект, и добавьте некоторые дополнительные параметры.
1) Параметр типа. Указание типа файла, который вы получаете. По умолчанию: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
2) Параметр "fileName". Это необходимо и должно включать расширение.
Пример:
httpDownloader({
method : 'POST',
url : '--- enter the url that returns a file here ---',
data : ifYouHaveDataEnterItHere,
type : 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // this is the default
fileName : 'YourFileName.xlsx'
}).then(res => {}).catch(e => {});
Это все, что вам нужно. Файл будет загружен на устройство пользователя без всплывающего окна.
Вот репозиторий git: https://github.com/stephengardner/ngHttpDownloader
Я столкнулся с этой же проблемой. Позвольте мне рассказать вам, как я решил это и достиг всего, чего все вы, кажется, хотите.
Требования:
- Должна иметь кнопку (или ссылку) на файл - (или сгенерированный поток памяти)
- Нужно нажать кнопку и загрузить файл
В моем сервисе (я использую Asp.net Web API) у меня есть контроллер, возвращающий "HttpResponseMessage". Я добавляю "StreamContent" в поле response.Content, устанавливаю заголовки "application/octet-stream" и добавляю данные как вложение. Я даже даю ему имя "myAwesomeFile.xlsx"
response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StreamContent(memStream);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = "myAwesomeFile.xlsx" };
Теперь вот трюк;)
Я храню базовый URL в текстовом файле, который я прочитал в переменную в угловом значении, называемом "apiRoot". Я делаю это, объявляя об этом, а затем устанавливая его в функции "Выполнить" модуля, например так:
app.value('apiRoot', { url: '' });
app.run(function ($http, apiRoot) {
$http.get('/api.txt').success(function (data) {
apiRoot.url = data;
});
});
Таким образом, я могу установить URL-адрес в текстовом файле на сервере и не беспокоиться о том, чтобы "выбросить его" при загрузке. (Вы всегда можете изменить его позже по соображениям безопасности - но это устраняет разочарование в процессе разработки;))
И СЕЙЧАС волшебство:
Все, что я делаю, - это создаю ссылку с URL-адресом, который напрямую попадает в конечную точку моей службы, а цель - "_blank".
<a ng-href="{{vm.getFileHref(FileId)}}" target="_blank" class="btn btn-default"> Excel File</a>
секретный соус - это функция, которая устанавливает href. Вы готовы к этому?
vm.getFileHref = function (Id) {
return apiRoot.url + "/datafiles/excel/" + Id;
}
Да, вот и все.;)
Даже в ситуации, когда вы перебираете много записей, которые имеют файлы для загрузки, вы просто передаете Id функции, и функция генерирует URL-адрес для конечной точки службы, которая доставляет файл.
Надеюсь это поможет!