You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Ktor与Exposed中序列化/反序列化BLOB图片?

解决ExposedBlob序列化时mark/reset不支持的错误

直接修复现有序列化器

错误根源是ExposedBlob的getBytes()方法内部依赖InputStream的mark/reset操作,但数据库驱动返回的流大多不支持该操作。修改你的ExposedBlobSerializer的serialize方法,手动读取流内容替代调用value.bytes:

class ExposedBlobSerializer : KSerializer<ExposedBlob> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ExposedBlob", PrimitiveKind.STRING)

    override fun serialize(encoder: Encoder, value: ExposedBlob) {
        // 手动读取流内容,避免调用ExposedBlob.getBytes()
        val bytes = value.inputStream.use { it.readBytes() }
        encoder.encodeString(Base64.getEncoder().encodeToString(bytes))
    }

    override fun deserialize(decoder: Decoder): ExposedBlob {
        val base64String = decoder.decodeString()
        val bytes = Base64.getDecoder().decode(base64String)
        return ExposedBlob(bytes)
    }
}

use函数会自动关闭流,readBytes()直接读取所有字节,不需要依赖mark/reset操作,就能解决异常。

更简洁的替代方案:用ByteArray替代ExposedBlob

Exposed支持直接将数据库BLOB映射为ByteArray,无需自定义序列化器,Kotlinx Serialization默认支持ByteArray的Base64序列化:

  1. 定义表结构:
object Users : Table() {
    // 其他字段
    val avatarImage = blob("avatar_image") // 对应数据库BLOB类型
}
  1. 数据类直接用ByteArray:
@Serializable
data class ExposedUser(
    // 其他字段
    val avatarImage: ByteArray,
    // 其他字段
)
  1. 数据库操作:
  • 查询:user[Users.avatarImage]直接返回ByteArray
  • 插入:直接传入ByteArray即可

这种方式绕开了ExposedBlob的流操作问题,代码更简洁。

通用的图片网络传输方案

POST上传图片

推荐用Multipart表单接收图片,直接转成ByteArray存入数据库:

post("/users/avatar") {
    val multipart = call.receiveMultipart()
    var avatarBytes: ByteArray? = null
    multipart.forEachPart { part ->
        if (part is PartData.FileItem && part.name == "avatar") {
            avatarBytes = part.streamProvider().use { it.readBytes() }
        }
        part.dispose()
    }
    avatarBytes?.let {
        // 存入数据库
        Users.insert { this[avatarImage] = it }
    } ?: call.respond(HttpStatusCode.BadRequest)
}

GET获取图片

两种方式:

  1. 返回Base64字符串(适合嵌入JSON响应):
    直接用上面的ByteArray或修改后的序列化器,序列化时自动转成Base64。

  2. 返回二进制流(更高效,适合单独的图片接口):

get("/users/{id}/avatar") {
    val userId = call.parameters["id"]?.toInt() ?: run {
        call.respond(HttpStatusCode.BadRequest)
        return@get
    }
    val user = Users.select { Users.id eq userId }.singleOrNull() ?: run {
        call.respond(HttpStatusCode.NotFound)
        return@get
    }
    val avatarBytes = user[Users.avatarImage]
    // 根据图片实际类型设置Content-Type
    call.respondBytes(avatarBytes, ContentType.Image.Jpeg)
}

内容的提问来源于stack exchange,提问作者DroidCrafter

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 02:20:27