【问题标题】:Why does the below file generation method return FileNotFoundException on simultaneous request to this endpoint?为什么以下文件生成方法在同时向此端点请求时返回 FileNotFoundException?
【发布时间】:2021-09-03 02:43:53
【问题描述】:

我已经编写了一个端点来返回一个包含多个 qr 的 zip,其值基于我的数据库中的详细信息。当我通过向此端点发出多个请求进行负载测试时,它会抛出 FileNotFoundException。但如果请求是在特定时间间隔发出的,则不会发生这种情况。

@GetMapping(value = ["/{sysId}/code"], produces = ["application/zip"])
fun generateQrCode1(@PathVariable sysId: Int): ResponseEntity<InputStreamResource> {
    val sysDetails = sysService.getById(sysId)
    val productDetails = productService.getProductByProductId(sysDetails.productId)
    val zipName = sysDetails.productId.toString() + ".zip"
    FileOutputStream(zipName).use { fileOutputStream ->
        ZipOutputStream(fileOutputStream).use { zipOutputStream ->
            for (i in 1..sysDetails.length) {
                val u = "http://" + "$domain/" + "?sysId=${sysId}&pid=$i"
                val x = generateQRCodeWithText(u, 350, 300, arrayOf("QR CODE TEST"))
                val byteArrayOutputStream = ByteArrayOutputStream()
                byteArrayOutputStream.write(x!!)
                val zipEntry = ZipEntry(
                    "${
                        productDetails.name
                    }-${sysId}-$i.jpg"
                )
                zipOutputStream.putNextEntry(zipEntry)
                byteArrayOutputStream.writeTo(zipOutputStream)
            }
        }
    }
    try {
        return ResponseEntity
            .ok()
            .header("Content-Disposition", "attachment; filename=\"$zipName")
            .body(InputStreamResource(FileInputStream(zipName)))
    } finally {
        java.nio.file.Files.deleteIfExists(Paths.get(zipName))
    }
  
    fun generateQRCodeWithText(data: String?, width: Int?, height: Int, text: Array<String?>): ByteArray? {
    return try {
        val qrCodeWriter = QRCodeWriter()
        val bitMatrix = qrCodeWriter.encode(data, BarcodeFormat.QR_CODE, width!!, height)
        val pngOutputStream = ByteArrayOutputStream()
        MatrixToImageWriter.writeToStream(bitMatrix, "PNG", pngOutputStream)
        var pngData = pngOutputStream.toByteArray()

        if (text.size > 0) {
            val totalTextLineToadd = text.size
            val `in`: InputStream = ByteArrayInputStream(pngData)
            val image: BufferedImage = ImageIO.read(`in`)
            val outputImage =
                BufferedImage(image.width, image.height + 25 * totalTextLineToadd, BufferedImage.TYPE_INT_ARGB)
            val g: Graphics = outputImage.graphics
            val g2d = g as Graphics2D
            g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
            g.setColor(Color.WHITE)
            g.fillRect(0, 0, outputImage.width, outputImage.height)
            g.drawImage(image, 0, 0, image.width, image.height, null)
            g.setFont(Font("Arial Black", Font.BOLD, 20))
            val textColor: Color = Color.BLUE
            g.setColor(textColor)
            val fm: FontMetrics = g.getFontMetrics()
            var startingYposition = height + 5
            for (displayText in text) {
                g.drawString(
                    displayText,
                    outputImage.width / 2 - fm.stringWidth(displayText) / 2,
                    startingYposition
                )
                startingYposition += 20
            }
            val baos = ByteArrayOutputStream()
            ImageIO.write(outputImage, "PNG", baos)
            baos.flush()
            pngData = baos.toByteArray()
            baos.close()
        }
        pngData
    } catch (ex: WriterException) {
        throw ex
    } catch (ex: IOException) {
        throw ex
    }
}

多个请求同时出错:

java.io.FileNotFoundException: 2889.zip (No such file or directory)
at java.io.FileInputStream.open0(Native Method) ~[?:1.8.0_292]
 at java.io.FileInputStream.open(FileInputStream.java:195) ~[?:1.8.0_292]
at java.io.FileInputStream.<init>(FileInputStream.java:138) ~[?:1.8.0_292]
 at java.io.FileInputStream.<init>(FileInputStream.java:93) ~[?:1.8.0_292]
 at com.mangoChain.qrApi.QrApi.generateQrCode1(QrApi.kt:136) ~[classes!/:?]

【问题讨论】:

  • generateQrCode1 不是线程安全的。对同一个 sysId 的多次请求将导致多个线程创建和删除同一个 zip 文件。
  • @AndyWilkinson 我已经添加了! getById() ,getProductById() 是对 db 的调用,它不会导致问题,因为我已经单独检查过。
  • 抱歉,我编辑了我之前的评论,因为我意识到 generateQrCode1 不是线程安全的。
  • @VeerabalaJ 是否使用相同的sysId 发出了多个请求?如果不是,是否有可能sysService.getById(sysId) 与 2 个不同的sysId 返回相同的productId?正如安迪所说,如果多个线程处理同名文件,它们可以删除彼此的文件。
  • 另外,作为旁注,您在 Content-Disposition 标头中缺少文件名的结束双引号

标签: spring-boot kotlin file-io qr-code scalability


【解决方案1】:

您应该确保您的本地文件名 (zipName) 是唯一的,否则多个线程可能会删除彼此的文件。

您在 cmets 中提到 productId 可能会发生冲突,因此您可以根据 sysId 而不是(或除了)productId 来命名您的文件。例如:

val zipName = "$sysId-${sysDetails.productId}.zip"

请注意,您只需使本地文件名唯一 (zipName),但您仍然可以在 Content-Disposition 标头中保留您想要的用户可见文件名:

return ResponseEntity
    .ok()
    .header("Content-Disposition", "attachment; filename=\"${sysDetails.productId}.zip\"")
    .body(InputStreamResource(FileInputStream(zipName)))

附带说明一下,您可能应该使用临时文件夹而不是简单的文件名,因为目前这些文件是在当前工作目录中创建的。

【讨论】:

    猜你喜欢
    • 2020-06-02
    • 2019-09-10
    • 2020-01-27
    • 2018-08-12
    • 1970-01-01
    • 2014-11-26
    • 1970-01-01
    • 1970-01-01
    • 2017-04-04
    相关资源
    最近更新 更多