如何在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序列化:
- 定义表结构:
object Users : Table() { // 其他字段 val avatarImage = blob("avatar_image") // 对应数据库BLOB类型 }
- 数据类直接用ByteArray:
@Serializable data class ExposedUser( // 其他字段 val avatarImage: ByteArray, // 其他字段 )
- 数据库操作:
- 查询:
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获取图片
两种方式:
返回Base64字符串(适合嵌入JSON响应):
直接用上面的ByteArray或修改后的序列化器,序列化时自动转成Base64。返回二进制流(更高效,适合单独的图片接口):
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
相关产品推荐
相关产品推荐

