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

如何使用ContentFile将解码后的图片上传至Django的ImageField?

问题:Django存储Base64图片后文件损坏无法打开

背景

我用Django开发聊天应用,用户通过JavaScript的btoa()函数把图片转成Base64格式发送到服务器,服务器需要将图片存入数据库。

模型代码

class Message(models.Model):
    sender_user = models.ForeignKey(User, on_delete=models.CASCADE)
    text = models.TextField(blank=False, null=False)
    image = models.ImageField(blank=True, null=True, upload_to='messages/')
    date = models.DateTimeField(default=timezone.now)
    chat = models.ForeignKey(Chat, on_delete=models.CASCADE)

    def __str__(self) -> str:
        return self.text

问题现象

解码后的Base64图片用ContentFile存储后,数据库中的图片文件损坏,无法被任何程序打开;用imghdr.what()检测图片类型时总是返回None,但把原始Base64字符串粘贴到网页工具中能正常显示图片,且工具能正确识别图片类型。

现有服务器代码

def create_message(
    self,
    sender_user_id: Union[str, int],
    text: str,
    image: Optional[str],
    chat_id: Union[str, int],
) -> None:
    """Creates and stores a new message object in the database.

    Args:
        sender_user_id (Union[str, int]): The id (numeric value) of the user that sent the message.
        text (str): What the message says.
        image (str): The encoded image data in base64 encoding.
        chat_id (Union[str, int]): The id (numeric value) of the chat that the sender sent this message on.
    """
    sender_user_instance = User.objects.get(id=sender_user_id)
    chat_instance = Chat.objects.get(id=chat_id)
    new_message = Message.objects.create(
        sender_user=sender_user_instance,
        text=text,
        date=timezone.now(),
        chat=chat_instance,
    )
    if image != "":
        image_bytes_data = base64.b64decode(image)
        # removes the prefix data:image/jpeg;base64 from the decoded bytes
        image_prefix, image_bytes = image_bytes_data.split(b",", 1)

        # print(image == base64.b64encode(image_bytes_data)) prints False
        # print(image_bytes_data == base64.b64decode(base64.b64encode(image_bytes_data))) prints True

        # Check image type
        image_type = imghdr.what(BytesIO(image_bytes_data))

        if image_type not in ["jpeg", "png", "gif"]:
            # Handle invalid image type
            print(f"Invalid image type: {image_type}")

        image_file = ContentFile(image_bytes, f"user_message.{image_type}")

        new_message.image = image_file
        new_message.save()

问题原因及修复方案

  • Base64解码逻辑错误:你先对包含data:image/xxx;base64,前缀的完整字符串做Base64解码,再拆分前缀和图片字节,这完全搞反了。前缀是Base64字符串的一部分,不是解码后的字节内容,正确流程是先拆分字符串前缀,再对剩余的纯图片编码内容解码。
  • 图片类型检测错误:用带前缀的解码内容检测类型,自然识别不出,因为解码后的内容开头是文本字节,不是图片文件头。

修复后的代码:

import base64
from io import BytesIO
import imghdr
import uuid
from django.core.files.base import ContentFile

def create_message(
    self,
    sender_user_id: Union[str, int],
    text: str,
    image: Optional[str],
    chat_id: Union[str, int],
) -> None:
    sender_user_instance = User.objects.get(id=sender_user_id)
    chat_instance = Chat.objects.get(id=chat_id)
    new_message = Message.objects.create(
        sender_user=sender_user_instance,
        text=text,
        date=timezone.now(),
        chat=chat_instance,
    )
    if image and image.strip() != "":
        # 先拆分Base64字符串的前缀和实际编码内容
        if "," in image:
            _, base64_data = image.split(",", 1)
        else:
            # 兼容不带前缀的Base64数据
            base64_data = image
        
        # 对纯图片编码内容解码
        image_bytes = base64.b64decode(base64_data)
        
        # 检测图片类型
        image_type = imghdr.what(BytesIO(image_bytes))
        if not image_type or image_type not in ["jpeg", "png", "gif"]:
            print(f"Invalid image type: {image_type}")
            # 可添加错误处理逻辑,比如抛出异常或终止保存
            return
        
        # 用UUID生成唯一文件名,避免同名覆盖
        unique_filename = f"{uuid.uuid4().hex}.{image_type}"
        image_file = ContentFile(image_bytes, unique_filename)
        new_message.image = image_file
        new_message.save()

额外提示

  • 前端建议用FileReader.readAsDataURL()直接获取带前缀的Base64字符串,比手动用btoa()编码更可靠,避免二进制数据编码时出现字符集问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 05:55:39