美文网首页
csv导出文件解决中文乱码和文件名空格问题

csv导出文件解决中文乱码和文件名空格问题

作者: Jafir | 来源:发表于2024-03-04 14:12 被阅读0次

    csv导出文件解决中文乱码和文件名空格问题

    开发环境

    前端:Vue

    后端:Java

    问题的出现:

    1、csv的文件中文内容 excel打开是乱码,wps没问题(wps会进行不同的编码转换,excel不会)

    2、其他未出现但潜在的问题(文件名中带空格,xxx xxx.csv最后变成的xxx+xxx.csv)

    3、文件名是中文,出现乱码

    要注意的几个问题:

    1、文件名为中文

    2、文件名中有空格

    3、文件内容有中文

    以上问题都需要处理

    处理方法

    前端:

    对于文件名的处理:

    把从content-disposition里面获取的fileName进行decodeURI处理

    对于中文内容乱码的处理:

    httprequest的responseType要添加为blob responseType: 'blob'

    核心代码
     _this.$axios({
            method: 'get',
            url: origin + url,
            params,
            headers,
            responseType: 'blob',    //重点代码
            timeout: 30000
        }).then(res => {
    
    const disposition = res.headers['content-disposition']
                let fName = decodeURI(disposition.substring(disposition.indexOf('filename=') + 9, disposition.length))
                fName = fName.replace(/"/g, '')
                link.download = fName
    
    参考代码
    import env from '../env'
    
    /**
     * 导出 CSV 文件下载
     * @param _this 上下文
     * @param url 请求地址
     * @param params 请求参数
     * @param fileName 文件名(需要带后缀, 如果传 false/null/undefined 可直接使用后台返回的文件名)
     * @param loadingName loading 挂载在 _this 上的名字
     */
    export const downloadCSV = (_this, url, params, fileName, loadingName) => {
        _this[loadingName] = true
        const origin = process.env.NODE_ENV === 'localDevelopment' ? process.env.BASE_URL : window.location.origin + process.env.BASE_URL
        const headers = {}
        let downloadUrl = ''
        if (_this.$store.state.user.token) {
            headers.userId = _this.$store.state.user.userId // 登录中,默认带用户ID
            headers.Token = _this.$store.state.user.token // 请求接口决定是否带Token
        }
        _this.$axios({
            method: 'get',
            url: origin + url,
            params,
            headers,
            responseType: 'blob',
            timeout: 30000
        }).then(res => {
            downloadUrl = window.URL.createObjectURL(new Blob([res.data]))
            const link = document.createElement('a')
            link.style.display = 'none'
            link.href = downloadUrl
            if (fileName) {
                link.download = fileName
            } else {
                const disposition = res.headers['content-disposition']
                let fName = decodeURI(disposition.substring(disposition.indexOf('filename=') + 9, disposition.length))
                fName = fName.replace(/"/g, '')
                link.download = fName
            }
            document.body.appendChild(link)
            link.click()
        }).finally(() => {
            _this[loadingName] = false
            window.URL.revokeObjectURL(downloadUrl)
        })
    }
    
    

    后端:

    对于文件名处理:

    里面fileName需要UrlEncoder进行url转义处理

    且对于文件名中有空格而言,会转为%x%x+%x%x.csv 这里会替换+为%20

    核心代码
     @Override
        public ResponseEntity<byte[]> toResponse() {
            this.close();
            try {
                FileInputStream fis = new FileInputStream(this.csvFile);
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                byte[] b = new byte[1024];
                int n;
                while ((n = fis.read(b)) != -1) {
                    bos.write(b, 0, n);
                }
                fis.close();
                bos.close();
                HttpHeaders httpHeaders = new HttpHeaders();
                String encodeName = URLEncoder.encode(this.name + SUFFIX, "UTF-8");
                String fileName = encodeName.replace("+", "%20");
                httpHeaders.setContentDispositionFormData("attachment", fileName);
                httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
                httpHeaders.setAccessControlExposeHeaders(Collections.singletonList("Content-Disposition"));
                return new ResponseEntity<>(bos.toByteArray(), httpHeaders, HttpStatus.CREATED);
            } catch (IOException e) {
                throw new BaseException(CSV_IO_EXCEPTION);
            }
        }
    
    参考代码
    import lombok.Getter;
    import org.apache.commons.csv.CSVFormat;
    import org.apache.commons.csv.CSVPrinter;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.MediaType;
    import org.springframework.http.ResponseEntity;
    
    import java.io.*;
    import java.net.URLEncoder;
    import java.util.Arrays;
    import java.util.Collection;
    import java.util.Collections;
    import java.util.stream.Stream;
    
    
    /**
     * Csv文件导出工具实现类
     *
     * @author hx
     * @version 1.0
     * @date 2021/5/25 16:45
     */
    
    
    @Getter
    public class CsvUtil implements FileUtil {
        private static final String PREFIX = "vehicle";
        private static final String SUFFIX = ".csv";
        private final String name;
        private String[] headers;
        private FileOutputStream fos;
        private File csvFile;
        private String inCharset;
        private String outCharset;
        private OutputStreamWriter osw;
        private CSVPrinter csvPrinter;
    
        public CsvUtil(String name) {
            this(name, "UTF-8", "iso-8859-1");
        }
    
        public CsvUtil(String name, String inCharset, String outCharset) {
            this.name = name;
            this.inCharset = inCharset;
            this.outCharset = outCharset;
        }
    
        @Override
        public void setHeader(String... headers) {
            this.setHeaders(headers);
        }
    
        @Override
        public void setHeaders(String[] headers) {
            this.headers = headers;
            this.initPrinter();
        }
    
        @Override
        public void setHeaders(Collection<String> row) {
            this.setHeaders(row.toArray(new String[0]));
        }
    
        @Override
        public void writeVaried(Iterable<?> row) {
            try {
                this.csvPrinter.printRecord(row);
            } catch (IOException e) {
                throw new BaseException(CSV_IO_EXCEPTION);
            } catch (NullPointerException e) {
                this.initPrinter();
                this.writeVaried(row);
            }
        }
    
    
        @Override
        public void writeRow(Object[] row) {
            try {
                this.csvPrinter.printRecord(row);
            } catch (IOException e) {
                throw new BaseException(CSV_IO_EXCEPTION);
            } catch (NullPointerException e) {
                this.initPrinter();
                this.writeRow(row);
            }
        }
    
    
        @Override
        public void writeFeed() {
            try {
                this.csvPrinter.println();
            } catch (IOException e) {
                throw new BaseException(CSV_IO_EXCEPTION);
            } catch (NullPointerException e) {
                this.initPrinter();
                this.writeFeed();
            }
        }
    
        @Override
        public void writeEmptyLine() {
            try {
                this.csvPrinter.println();
            } catch (IOException e) {
                this.writeLine("");
            } catch (NullPointerException e) {
                this.initPrinter();
                this.writeEmptyLine();
            }
        }
    
        @Override
        public void writeLine(Object... line) {
            this.writeRow(line);
        }
    
        @Override
        public void writeStrLine(String... line) {
            Stream<String> stream = Arrays.stream(line);
            this.writeVaried(FileUtil.filterStrStream(stream));
        }
    
        @Override
        public void writeStrRow(String[] row) {
            Stream<String> stream = Arrays.stream(row);
            this.writeVaried(FileUtil.filterStrStream(stream));
        }
    
        @Override
        public void writeList(Collection<?> row) {
            this.writeVaried(row);
        }
    
        @Override
        public void writeStrList(Collection<String> row) {
            this.writeVaried(FileUtil.filterStrStream(row.stream()));
        }
    
    
        @Override
        public ResponseEntity<byte[]> toResponse() {
            this.close();
            try {
                FileInputStream fis = new FileInputStream(this.csvFile);
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                byte[] b = new byte[1024];
                int n;
                while ((n = fis.read(b)) != -1) {
                    bos.write(b, 0, n);
                }
                fis.close();
                bos.close();
                HttpHeaders httpHeaders = new HttpHeaders();
                String encodeName = URLEncoder.encode(this.name + SUFFIX, "UTF-8");
                String fileName = encodeName.replace("+", "%20");
                httpHeaders.setContentDispositionFormData("attachment", fileName);
                httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
                httpHeaders.setAccessControlExposeHeaders(Collections.singletonList("Content-Disposition"));
                return new ResponseEntity<>(bos.toByteArray(), httpHeaders, HttpStatus.CREATED);
            } catch (IOException e) {
                throw new BaseException(CSV_IO_EXCEPTION);
            }
        }
    
        @Override
        public void close() {
            FileUtil.close(csvPrinter, osw, fos);
        }
    
        private void initPrinter() {
            CSVFormat csvFormat = CSVFormat.DEFAULT;
            if (this.headers != null) {
                csvFormat = csvFormat.withHeader(this.headers);
            }
            try {
                this.csvFile = File.createTempFile(PREFIX, SUFFIX);
                this.fos = new FileOutputStream(this.csvFile);
                this.osw = new OutputStreamWriter(fos);
                //防止excel中文乱码
                this.osw.write('\ufeff');
                this.osw.flush();
                this.csvPrinter = new CSVPrinter(this.osw, csvFormat);
            } catch (IOException e) {
                this.close();
                throw new BaseException(CSV_IO_EXCEPTION);
            }
        }
    
        public void setInCharset(String inCharset) {
            this.inCharset = inCharset;
        }
    
        public void setOutCharset(String outCharset) {
            this.outCharset = outCharset;
        }
    }
    
    

    最后

    以上问题,不仅针对于导出csv,一切导出下载文件,皆可适用,仅供参考。

    相关文章

      网友评论

          本文标题:csv导出文件解决中文乱码和文件名空格问题

          本文链接:https://www.haomeiwen.com/subject/vkhcwltx.html