将字节数组输出转换为Blob会损坏文件

Converting byte array output into Blob corrupts file

本文关键字:Blob 损坏 文件 转换 字节 字节数 数组 输出      更新时间:2023-09-26

我正在使用Office Javascript API编写一个使用Angular的Word插件。

我想通过API检索Word文档,然后将其转换为文件并通过POST将其上传到服务器。

我使用的代码几乎与微软为这个用例提供的文档代码相同:https://dev.office.com/reference/add-ins/shared/document.getfileasync#example---get-a-document-in-office-open-xml-compressed-format

服务器端点要求上传通过多部分表单进行post,因此在创建$http调用时,我创建了一个FormData对象,在其上附加文件(blob)以及一些元数据。

文件正在传输到服务器,但是当我打开它时,它已经损坏,无法再用Word打开。

根据文档,Office.context.document.getFileAsync函数返回一个字节数组。但是,得到的fileContent变量是一个字符串。当我console.log这个字符串时,它似乎是压缩的数据,就像它应该的那样。

我的猜测是我需要做一些预处理之前把字符串变成Blob。但是哪种预处理呢?通过atob进行Base64编码似乎没有做任何事情。

                let sendFile = ( fileContent ) => {
                    let blob = new Blob([fileContent], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }),
                        fd = new FormData();
                    blob.lastModifiedDate = new Date();
                    fd.append('file', blob, 'uploaded_file_test403.docx');
                    fd.append('case_id', caseIdReducer.data());
                    $http.post('/file/create', fd, {
                        transformRequest: angular.identity,
                        headers: { 'Content-Type': undefined }
                    })
                    .success( ( ) => {
                        console.log('upload succeeded');
                    })
                    .error(( ) => {
                        console.log('upload failed');
                    });
                };

                function onGotAllSlices(docdataSlices) {
                    let docdata = [];
                    for (let i = 0; i < docdataSlices.length; i++) {
                        docdata = docdata.concat(docdataSlices[i]);
                    }
                    let fileContent = new String();
                    for (let j = 0; j < docdata.length; j++) {
                        fileContent += String.fromCharCode(docdata[j]);
                    }
                    // Now all the file content is stored in 'fileContent' variable,
                    // you can do something with it, such as print, fax...
                    sendFile(fileContent);
                }
                function getSliceAsync(file, nextSlice, sliceCount, gotAllSlices, docdataSlices, slicesReceived) {
                    file.getSliceAsync(nextSlice, (sliceResult) => {
                        if (sliceResult.status === 'succeeded') {
                            if (!gotAllSlices) { // Failed to get all slices, no need to continue.
                                return;
                            }
                            // Got one slice, store it in a temporary array.
                            // (Or you can do something else, such as
                            // send it to a third-party server.)
                            docdataSlices[sliceResult.value.index] = sliceResult.value.data;
                            if (++slicesReceived === sliceCount) {
                                // All slices have been received.
                                file.closeAsync();
                                onGotAllSlices(docdataSlices);
                            } else {
                                getSliceAsync(file, ++nextSlice, sliceCount, gotAllSlices, docdataSlices, slicesReceived);
                            }
                        } else {
                            gotAllSlices = false;
                            file.closeAsync();
                            console.log(`getSliceAsync Error: ${sliceResult.error.message}`);
                        }
                    });
                }
                // User clicks button to start document retrieval from Word and uploading to server process
                ctrl.handleClick = ( ) => {
                    Office.context.document.getFileAsync(Office.FileType.Compressed, { sliceSize: 65536 /*64 KB*/ }, 
                        (result) => {
                            if (result.status === 'succeeded') {
                                // If the getFileAsync call succeeded, then
                                // result.value will return a valid File Object.
                                let myFile = result.value,
                                    sliceCount = myFile.sliceCount,
                                    slicesReceived = 0, gotAllSlices = true, docdataSlices = [];
                                // Get the file slices.
                                getSliceAsync(myFile, 0, sliceCount, gotAllSlices, docdataSlices, slicesReceived);
                            } else {
                                console.log(`Error: ${result.error.message}`);
                            }
                        }
                    );
                };

我最终使用fileContent字符串:

let bytes = new Uint8Array(fileContent.length);
for (let i = 0; i < bytes.length; i++) {
    bytes[i] = fileContent.charCodeAt(i);
}

然后继续用这些字节构建Blob:

let blob = new Blob([bytes], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });

如果我然后通过POST请求发送这个,文件就不会被打乱,可以用Word正确打开。

我仍然觉得这可以通过更少的麻烦/更少的步骤来实现。如果有人有更好的解决方案,我很有兴趣学习。

谢谢你的回答,Uint8Array是解决方案。只是一个小改进,避免创建字符串:

let bytes = new Uint8Array(docdata.length);
for (var i = 0; i < docdata.length; i++) {
    bytes[i] = docdata[i];
}

讨论!什么是错误的获得一个文件的实例,而不使用FileReader api?微软来吧!

你应该把字节数组扔到blob构造函数中,在javascript中将二进制blob转换为字符串是一个坏主意,可能导致"超出范围"错误或不正确的编码

跟着这个做点什么

var byteArray = new Uint8Array(3)
byteArray[0] = 97
byteArray[1] = 98
byteArray[2] = 99
new Blob([byteArray])
如果数据块是类型化数组的实例或blob/file的实例,则

。在这种情况下,你可以输入:

blob = new Blob([blob, chunk])

,请……不要用base64编码(~大3倍+慢)