Zip上传和解析的一些思考

本文探讨了如何处理批量导入商品图片的Zip文件。首先,通过代码展示了一个上传并解析Zip包的业务流程,包括解析Zip、内容校验、上传及数据库操作,以及临时文件的删除。为了解决并发问题,考虑使用`synchronized`关键字保证线程安全。最后,提出了对于优化现有流程,如采用多线程来提高效率的思考,邀请读者一起讨论。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

需求:
批量导入商品的图片;图片是用Zip包的形式;
在这里插入图片描述

如下代码是上传Zip的业务代码,我们在别处调用的话直接去调用productBatchUploadManager.productBatchUploadQuickly(MultipartFile multipartFile)即可;

productUploadQuickly(MultipartFile multipartFile)方法中分好几步去执行上传解析Zip中的图片:

  1. uZipUtils.readZipContentByFileMutiFile(multipartFile)去解析Zip包,并将该包解析完成的内容放在服务器的某一个地方;
  2. checkBatchUpload()对解压完成的内容进行校验,因为你在校验完毕之前是不能对该内容进行上传或者数据库操作的,所以这一步必须进行业务操作之前进行校验;
  3. productBatchUpload();对解压完成的内容进行上传以及数据库操作;
  4. deleteBatchUpload(UZipUtils.OUT_PUT_DIRECTORY);删除在解析完成后存放在服务器的临时文件

当然在有一定的并发的情况下,会存在线程1在执行checkBatchUpload,线程2在执行deleteBatchUpload,这样就会出现问题,我们可以在productUploadQuickly方法上加上synchronized,确保只有一个线程去执行该代码;

/**
 * @Author:朱国庆
 * @Date:2020/7/29 10:33
 * @Desription: qidian-server-distribution
 * @Version: 1.0
 */
@Service
public class ProductBatchUploadManagerImpl implements ProductBatchUploadManager {

    @Autowired
    private UZipUtils uZipUtils;

    @Autowired
    private UploadHandler uploadHandler;

    @Autowired
    private ProductDomainMapper productDomainMapper;

    @Autowired
    private GoodsApiMapper goodsApiMapper;

    @Override
    public void checkBatchUpload() {
        File file = new File(UZipUtils.OUT_PUT_DIRECTORY);
        File[] fileDirectories = file.listFiles();
        for (File fileDirectory : fileDirectories) {
            if (fileDirectory.isDirectory()) {
                String spuName = fileDirectory.getName();
                //对名称进行切割.将文件名做为spuId
                //spuName = spuName.substring(0, spuName.lastIndexOf("/"));
                Long spuNameId;
                try {
                    spuNameId = Long.parseLong(spuName);
                } catch (NumberFormatException e) {
                    throw new RuntimeException(spuName + "文件夹名称格式不合法,必须是spuId");
                }
                //去数据库中查询,如果spuId不存在,那么就抛出异常
                ProdSpuDO prodSpuDO = productDomainMapper.queryProdSpuDOById(spuNameId);
                if (Objects.isNull(prodSpuDO)) {
                    throw new RuntimeException(spuName + "文件夹找不到对应的商品spu");
                }
                File[] files = fileDirectory.listFiles();
                //该集合用来收集主图
                ArrayList<File> fileMain = new ArrayList<>();
                for (File fileToOSS : files) {
                    //过滤掉隐藏文件
                    if (fileToOSS.getName().startsWith(".")) {
                        continue;
                    }
                    if (fileToOSS.isFile()) {
                        String name = fileToOSS.getName();
                        //String spuNameTemp = name.substring(0, name.lastIndexOf("/"));
                        //对切割完的数据将后缀名去掉
                        String spuFileName = name.substring(0, name.lastIndexOf("."));
                        Long spuFileNameId;
                        String suffixName = name.substring(name.lastIndexOf("."));
                        if (!(suffixName.equalsIgnoreCase(".JPG")
                                || suffixName.equalsIgnoreCase(".JPEG")
                                || suffixName.equalsIgnoreCase(".PNG"))) {
                            throw new RuntimeException(name + "图片格式格式不和法,必须是是JPG,JPEG,PNG格式");
                        }
                        if (spuFileName.contains(spuName)) {
                            //主图
                            try {
                                try {
                                    spuFileName = spuFileName.substring(0, spuFileName.lastIndexOf("("));
                                } catch (Exception e) {
                                    spuFileNameId = Long.parseLong(spuFileName);
                                }
                            } catch (NumberFormatException e) {
                                throw new RuntimeException(spuFileName + "图片名称格式不合法,必须是spuId");
                            }
                            fileMain.add(fileToOSS);
                        } else {
                            //sku图片
                            try {
                                spuFileNameId = Long.parseLong(spuFileName);
                            } catch (NumberFormatException e) {
                                throw new RuntimeException(spuFileName + "图片名称格式不合法,必须是skuId");
                            }
                            ProdSkuDO prodSkuDO = goodsApiMapper.queryProdSkuDOBySkuId(spuFileNameId);
                            if (Objects.isNull(prodSkuDO)) {
                                throw new RuntimeException(spuFileName + "图片找不到对应的商品sku");
                            }
                        }
                    }
                }
                if (fileMain.size() > 8) {
                    throw new RuntimeException("商品主图最多为8张");
                }
            }
        }
    }

    @Override
    @Transactional
    public void productBatchUpload() {
        File file = new File(UZipUtils.OUT_PUT_DIRECTORY);
        File[] fileDirectories = file.listFiles();
        for (File fileDirectory : fileDirectories) {
            //如果判断该文件是文件夹,那么就表示
            if (fileDirectory.isDirectory()) {
                String spuName = fileDirectory.getName();
                //对名称进行切割.将文件名做为spu
                //spuName = spuName.substring(0, spuName.lastIndexOf("/"));
                File[] files = fileDirectory.listFiles();
                //首先删除所有的主图
                productDomainMapper.deleteProdMedia(Long.parseLong(spuName));
                for (File fileToOSS : files) {
                    //过滤掉隐藏文件
                    if (fileToOSS.getName().startsWith(".")) {
                        continue;
                    }
                    if (fileToOSS.isFile()) {
                        String spuNameTemp = fileToOSS.getName();

                        //String spuNameTemp = name.substring(0, name.lastIndexOf("/"));
                        //对切割完的数据将后缀名去掉
                        String spuFileName = spuNameTemp.substring(0, spuNameTemp.lastIndexOf("."));
                        UploadResult uploadResult = uploadHandler.upload("GOODS", fileToOSS.getName(), fileToOSS);
                        List<ItemResult> resultDetail = uploadResult.getResultDetail();
                        if (resultDetail.size() >= 1) {

                            //如果上传成功,会返回响应的数据
                            if ("success".equalsIgnoreCase(resultDetail.get(0).getResult())) {
                                String url = resultDetail.get(0).getUrl();
                                if (spuFileName.contains(spuName)) {
                                    //按照规则,如果说spuFileName包含了spuName,那么该文件一定是商品主图
                                    ProdMediaDO prodMediaDO = new ProdMediaDO();
                                    prodMediaDO.setSpuId(Long.parseLong(spuName));
                                    //资源类型为图片
                                    prodMediaDO.setType(0);
                                    prodMediaDO.setPictureUrl(url);
                                    //再新增主图
                                    productDomainMapper.saveProdMedia(prodMediaDO);
                                } else {
                                    //否则一定是sku图
                                    productDomainMapper.updatePicBySkuId(Long.parseLong(spuFileName), url);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    @Override
    public void deleteBatchUpload(String path) {
        File file = new File(path);
        if (file.isFile()) {
            file.delete();
        } else {
            File[] files = file.listFiles();
            if (files == null) {
                file.delete();
            } else {
                for (int i = 0; i < files.length; i++) {
                    deleteBatchUpload(files[i].getAbsolutePath());
                }
                file.delete();
            }
        }
    }


    @Override
    public synchronized void productBatchUploadQuickly(MultipartFile multipartFile) {
            try {
                try {
                    uZipUtils.readZipContentByFileMutiFile(multipartFile);
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new RuntimeException("ZIP解压失败", e);
                }
                checkBatchUpload();
                productBatchUpload();
            } catch (RuntimeException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            } finally {
                deleteBatchUpload(UZipUtils.OUT_PUT_DIRECTORY);
            }
        }
}

下面代码是Zip解析工具类

/**
 * @Author:朱国庆
 * @Date:2020/7/28 12:27
 * @Desription: qidian-server-distribution
 * @Version: 1.0
 */
@Component
public class UZipUtils {


    private static byte[] doc = null;

    //todo 测试之后需要修改
    public static final String OUT_PUT_DIRECTORY = "/Users/fechinchu/home/htd/qidian/tmp/";


    /**
     * 目前上传的图片只支持JPG,JPEG,PNG 大小不超过2M,图片建议800*800像素,商品主图最多为8张图片
     *
     * @param inputStream
     * @throws Exception
     */
    public static void readZipContentByInputStream(InputStream inputStream) throws IOException {

        try {
            //这里filename是文件名,如xxx.zip
            ZipInputStream zipis = new ZipInputStream(inputStream);
            ZipEntry fentry = null;
            while ((fentry = zipis.getNextEntry()) != null) {
                //fentry逐个读取zip中的条目,第一个读取的名称为文件夹。因此会创建一个File对象,File对象接收的参数为地址
                //然后就会用exists,判断该参数所指定的路径的文件或者目录是否存在
                //如果不存在,则构建一个文件夹;若存在,跳过
                //如果读到一个zip,也继续创建一个文件夹,然后继续读zip里面的文件,如txt
                if (fentry.isDirectory()) {
                    File dir = new File(OUT_PUT_DIRECTORY + fentry.getName());
                    if (!dir.exists()) {
                        dir.mkdirs();
                    }
                } else {
                    //fname是文件名,fileoutputstream与该文件名关联
                    String fname = new String(OUT_PUT_DIRECTORY + fentry.getName());
                    try {
                        //新建一个out,指向fname,fname是输出地址
                        FileOutputStream out = new FileOutputStream(fname);
                        doc = new byte[512];
                        int n;
                        //若没有读到,即读取到末尾,则返回-1
                        while ((n = zipis.read(doc, 0, 512)) != -1) {
                            //这就把读取到的n个字节全部都写入到指定路径了
                            out.write(doc, 0, n);
                        }
                        out.close();
                        out = null;
                        doc = null;
                    } catch (Exception ex) {
                        System.out.println("there is a problem");
                    }
                }
            }
            zipis.close();
        } catch (IOException ioex) {
            System.out.println("io错误:" + ioex);
        }
        System.out.println("finished!");

    }

    public void readZipContentByFileMutiFile(MultipartFile multipartFile) throws Exception {
        byte[] bytes = multipartFile.getBytes();
        ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
        readZipContentByInputStream(inputStream);
    }

}

如上就是所有关于图片Zip批量上传的所有内容,还有什么样的问题或者说还有什么需要我们去改进的,我们能不能做成多线程的方式,节省上传和处理的时间。欢迎大家一起讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值