美文网首页《Django By Example》程序员GIT
基于Django实现一个图片服务器(包括图片上传、存储和访问)

基于Django实现一个图片服务器(包括图片上传、存储和访问)

作者: 帅番茄 | 来源:发表于2018-10-27 22:43 被阅读36次
    • 背景:自己搭了一个技术博客,少不了图片展示,不想特地买个图片服务器用于图片存储和访问,于是自己实现了一个图片上传和访问的服务。
    • 你需要:
      1. 了解Python,Django [django 官方文档 : https://docs.djangoproject.com/zh-hans/2.0/]
      2. 有一台云服务器
    • 实现的内容:
      1. 图片上传
      2. 图片存储
      3. 图片访问

    数据库表&模型

    • 我们需要使用数据库(mysql)保存图片的上传记录,有两个目的,第一是保存图片的基本信息如图片名,图片大小,图片的md5值(用于区分图片,防止重复上传相同的图片),图片类型等等;第二是为以后做对已上传的图片进行管理做铺垫。
    create table upload_image(
        id int unsigned auto_increment,
        filename varchar(252) not null default "" comment "文件名",
        file_md5 varchar(128) not null comment "文件的MD5值",
        file_type varchar(32) not null default "" comment "文件类型",
        file_size int unsigned not null default 0 comment "文件大小",
        created_at datetime not null default current_timestamp, 
        updated_at datetime not null default current_timestamp on update current_timestamp,
        primary key(id),
        unique index(file_md5)
    )engine=innodb default charset=utf8 comment "文件上传表";
    
    • 我们使用django的orm(对象关系映射)做数据库访问的方式,模型定义如下:
    from django.db import models
    from datetime import datetime
    from django.conf import settings
    
    class UploadImage(models.Model):
        class Meta:
            db_table = "upload_image"
    
        id = models.IntegerField(primary_key=True)
        filename = models.CharField(max_length=252, default="")
        file_md5 = models.CharField(max_length=128)
        file_type = models.CharField(max_length=32)
        file_size = models.IntegerField()
        created_at = models.DateTimeField(default=datetime.now)
        updated_at = models.DateTimeField(default=datetime.now)
      
        # 我们还定义了通过文件md5值获取模型对象的类方法
        @classmethod
        def getImageByMd5(cls, md5):
            try:
                return UploadImage.objects.filter(file_md5=md5).first()
            except Exception as e:
                return None
    
        # 获取本图片的url,我们可以通过这个url在浏览器访问到这个图片
        # 其中settings.WEB_HOST_NAME 是常量配置,指你的服务器的域名
        # settings.WEB_IMAGE_SERVER_PATH 也是常量配置,指你的静态图片资源访问路径
        # 这些配置项我在Django项目的settings.py文件中进行配置
        def getImageUrl(self):
            filename = self.file_md5 + "." + self.file_type
            url = settings.WEB_HOST_NAME + settings.WEB_IMAGE_SERVER_PATH + filename
            return url
    
        # 获取本图片在本地的位置,即你的文件系统的路径,图片会保存在这个路径下
        def getImagePath(self):
            filename = self.file_md5 + "." + self.file_type
            path = settings.IMAGE_SAVING_PATH + filename
            return path
    
        def __str__(self):
            s = "filename:" + str(self.filename) + " - " + "filetype:" + str(self.file_type) \
            + " - " +  "filesize:" + str(self.file_size) + " - " + "filemd5:" + str(self.file_md5)
            return s
    
    

    视图方法

    • 视图方法即是你的服务接口的实现,几乎所有业务逻辑都放在视图方法里,目前我们只需要一个服务接口,用于上传图片,并返回访问该图片的url 供我们访问。
    • 在这个视图方法中,我们做的业务逻辑有:从请求中获取上传表单中的文件数据,检测文件的大小是否超出我们的限制,计算文件的md5值并判断该文件是否已经上传过(如果上传过我们直接返回该图片的url),判断该文件的类型是否为图片类型(我们只允许上传图片类型的文件),到目前为止通过所有的验证,插入一条数据库记录到数据库,保存图片到服务器磁盘,做一下日志记录,返回图片的url以供访问。
    • 代码有详细的注释
    rom django.views.decorators.http import require_http_methods
    import filetype, hashlib
    from app.views import returnOk, returnForbidden, returnBadRequest
    from app.models import UploadImage
    from django.conf import settings
    
    # 上传文件的视图
    @require_http_methods(["POST"])
    def uploadImage(request):
        # 从请求表单中获取文件对象
        file = request.FILES.get("img", None)
        if not file:  # 文件对象不存在, 返回400请求错误
            return returnBadRequest("need file.")
    
        # 图片大小限制
        if not pIsAllowedFileSize(file.size):
            return returnForbidden("文件太大")
    
        # 计算文件md5
        md5 = pCalculateMd5(file)
        uploadImg = UploadImage.getImageByMd5(md5)
        if uploadImg:   # 图片文件已存在, 直接返回
            return returnOk({'url': uploadImg.getImageUrl()})
    
        # 获取扩展类型 并 判断
        ext = pGetFileExtension(file)
        if not pIsAllowedImageType(ext):
            return returnForbidden("文件类型错误")
    
        # 检测通过 创建新的image对象
        # 文件对象即上一小节的UploadImage模型
        uploadImg = UploadImage()
        uploadImg.filename = file.name
        uploadImg.file_size = file.size
        uploadImg.file_md5 = md5
        uploadImg.file_type = ext
        uploadImg.save()  # 插入数据库
    
        # 保存 文件到磁盘
        with open(uploadImg.getImagePath(), "wb+") as f:
            # 分块写入
            for chunk in file.chunks():
                f.write(chunk)
    
        # 记录日志,这一步可有可无,可定制
        FileLogger.log_info("upload_image", uploadImg, FileLogger.IMAGE_HANDLER)
    
        # 返回图片的url以供访问
        return returnOk({"url": uploadImg.getImageUrl()})
    
    
    # 检测文件类型
    # 我们使用第三方的库filetype进行检测,而不是通过文件名进行判断
    # pip install filetype 即可安装该库
    def pGetFileExtension(file):
        rawData = bytearray()
        for c in file.chunks():
            rawData += c
        try:
            ext = filetype.guess_extension(rawData)
            return ext
        except Exception as e:
            # todo log
            return None
    
    # 计算文件的md5
    def pCalculateMd5(file):
        md5Obj = hashlib.md5()
        for chunk in file.chunks():
            md5Obj.update(chunk)
        return md5Obj.hexdigest()
    
    # 文件类型过滤 我们只允许上传常用的图片文件
    def pIsAllowedImageType(ext):
        if ext in ["png", "jpeg", "jpg"]:
            return True
        return False
    
    # 文件大小限制  
    # settings.IMAGE_SIZE_LIMIT是常量配置,我设置为10M
    def pIsAllowedFileSize(size):
        limit = settings.IMAGE_SIZE_LIMIT
        if size < limit:
            return True
        return False
    
    
    • 到目前为止我们实现了所有的业务逻辑,接下来将该视图方法配置到路由中即可(一般是urls.py文件)
     # 上传图片的接口
        path("upload/image", upload_views.uploadImage),
    

    部署

    • 我买了一台腾讯云的服务器,1核2GB内存,三年的使用时间,费用2000,这样的配置自己玩一玩一般是足够了的。
    • 我们使用ngnix来做代理服务器,使用supervisor作为服务的管理器,使用gunicorn作为http 服务器(即让django跑起来)
    • nginx和supervisor的安装有大把教程,gunicorn直接使用pip进行安装即可(pip install gunicorn)
    • nginx的配置可以参考:
    upstream image-server{
            server unix:/tmp/image-server.sock;
    }
    
    server {
            listen 80;
            # 你的服务器域名,对应代码配置中的settings.WEB_HOST_NAME
            server_name  your_host_name;
            charset utf-8;
    
            # 这是就是图片服务提供的静态资源访问的路径,对应代码配置中的settings.WEB_IMAGE_SERVER_PATH
            location /images/ {
                    # 这个路径就是你上传图片的保存的文件夹路径 对应代码配置中的settings.IMAGE_SAVING_PATH
                    alias /path/to/your/images/directory;
            }
    
            location / {
                    include proxy_params;
                    proxy_pass http://image-server;
            }
    
    }
    
    • supervisor 的配置可以参考:
    [program:image-server]
    command=/path/to/your/virtualenv/bin/gunicorn --bind unix:/tmp/image-server.sock project.wsgi
    directory=/path/to/your/project/directory
    user=ubuntu
    autostart=true
    autorestart=true
    stderr_logfile=/data/log/supervisor/image-server.err.log
    stdout_logfile=/data/log/supervisor/image-server.out.log
    
    • 重启nginx 和 supervisor 即可
    sudo service nginx restart
    sudo service supervisor restart
    

    测试

    • 我使用insomnia作为测试工具,它是类似postman的api测试工具,风格简约,功能强大;

    • 测试上传接口,选择一张图片并上传,如果成功,会返回该图片的url地址提供访问


      接口测试.png
    • 上传图片的url , 它现在就是一个网络资源了: http://littlewulu.cn/images/29d3472dc80a645f1f92646968c5afe4.jpg

    测试.jpg

    相关文章

      网友评论

        本文标题:基于Django实现一个图片服务器(包括图片上传、存储和访问)

        本文链接:https://www.haomeiwen.com/subject/mhzdtqtx.html