《Django By Example》程序员GIT

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

2018-10-27  本文已影响36人  帅番茄

数据库表&模型

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 "文件上传表";
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

视图方法

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

 # 上传图片的接口
    path("upload/image", upload_views.uploadImage),

部署

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;
        }

}
[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
sudo service nginx restart
sudo service supervisor restart

测试

测试.jpg
上一篇 下一篇

猜你喜欢

热点阅读