浏览器上传文件到Tencent_cos+后端校验+后端临时凭借+删除文件+编辑文件+文件下载----组件封装

0.资源准备
cos-js-sdk-v5.min.js下载地址:
https://gitee.com/yqmc/cos-js-sdk-v5minjs-download.git
前端
    <script src="{% static 'js/cos-js-sdk-v5.min.js' %}"></script>
后端
    pip3 install -U qcloud-python-sts

 

# 1.url
urlpatterns
= [ url(r'^manage/(?P<project_id>\d+)/', include([ # 文件 url(r'^file.html/', file.file, name='file'), url(r'^file/delete.html/', file.file_delete, name='file_delete'), url(r'^cos/credential$', file.cos_credential, name='cos_credential'), url(r'^file/post/$', file.file_post, name='file_post'), ], None, None)), ]

 

<!--模板-->
<!DOCTYPE html>
{% load project %}

{% load static %}

<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="{% static 'plugin/bootstrap-3.4.1-dist/css/bootstrap.min.css' %}">
    <link rel="stylesheet" href="{% static 'plugin/font-awesome-4.7.0/css/font-awesome.min.css' %}">
    <title>
        {% block title %}
            项目模板
        {% endblock %}
    </title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        body {
            background-color: #f5f5f5;
        }

        .w {
            width: 1200px;
            margin: -9px auto;
        }

        .nav_menu {
            display: flex;
            justify-content: space-around;
            align-items: center;
            height: 70px;
            /* background-color: #4caf50; */
            /* border: 1px solid #ccc; */
            padding: 10px;
            color: #fff;

        }

        .menu_item {
            position: relative;
            flex: 1;
            height: 100%;
            line-height: 50px;
            text-align: center;
            transform-style: preserve-3d;
            transition: all 0.5s;
            /* margin: 0 5px; */
            font-size: 17px;

        }

        .menu_item:hover {
            cursor: pointer;
            transform: rotateX(90deg);
        }

        .home, .web_home {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            perspective: 300px;
        }

        .home {
            background-color: #4caf50;
            z-index: 11;
            transform: translateZ(25px);
        }

        .web_home {
            background-color: #009688;
            transform: translateY(25px) rotateX(-90deg);
        }

        .auth_css {
            font-size: 18px;
            color: #fff;
            text-decoration: none;
        }

        .error-msg {
            color: red;
            position: absolute;
            font-size: 5px;
        }

        .red_input {
            border: 1px solid red;
        }

        .green_input {
            border: 1px solid green;
        }

        #li_detailed {
            color: #255625;
            font-size: 18px;
            line-height: 15px;
        }

        .li_active {
            background: #FF5722;
        }
    </style>

    {% block css %}

    {% endblock %}


</head>
<body>

<!--导航栏start-->
<div class="nav_menu w">
    <div class="menu_item">
        <div class="home">网站首页</div>
        <div class="web_home">
            <a href="{% url 'home' %}" style="font-size: 17px;color: #fff;text-decoration: none;">
                Tracer
            </a>
        </div>
    </div>
    <div class="menu_item">
        <div class="home">项目</div>
        <div class="web_home">
            {% project_list request %}

        </div>
    </div>
    <div class="menu_item">
        <div class="home">项目详细</div>
        <div class="web_home">
            {% detailed_list request %}

        </div>
    </div>
    <div class="menu_item">
        <div class="home">工作台</div>
        <div class="web_home">工作台</div>
    </div>
    <div class="menu_item">
        <div class="home">日历</div>
        <div class="web_home">日历</div>
    </div>
    <div class="menu_item">
        <div class="home">个人中心</div>
        {% if request.tracer.user %}
            <div class="web_home">

                <!--折叠菜单-->
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                       aria-expanded="false">{{ request.tracer.user.username }} <span class="caret"></span></a>
                    <ul class="dropdown-menu">
                        <li><a href="{% url 'home' %}">官网</a></li>

                        <li role="separator" class="divider"></li>
                        <li><a href="{% url 'home' %}">{{ request.tracer.user.username }}的个人中心 </a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="{% url 'home' %}">待定 </a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="{% url 'home' %}">待定 </a></li>
                    </ul>
                </li>

            </div>
        {% else %}
            <div class="web_home">
                您目前非法登录
            </div>
        {% endif %}
    </div>
</div>
<!--导航栏end-->

{% block content %}

{% endblock %}

<script src="{% static 'plugin/jquery-3.4.1/jquery-3.4.1.min.js' %}"></script>
<script src="{% static 'plugin/bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></script>
<script>
    //鼠标点击出现爱心特效
    (function (window, document, undefined) {
        var hearts = [];
        window.requestAnimationFrame = (function () {
            return window.requestAnimationFrame ||
                window.webkitRequestAnimationFrame ||
                window.mozRequestAnimationFrame ||
                window.oRequestAnimationFrame ||
                window.msRequestAnimationFrame ||
                function (callback) {
                    setTimeout(callback, 1000 / 60);
                }
        })();
        init();

        function init() {
            css(".heart{width: 10px;height: 10px;position: fixed;background: #f00;transform: rotate(45deg);-webkit-transform: rotate(45deg);-moz-transform: rotate(45deg);}.heart:after,.heart:before{content: '';width: inherit;height: inherit;background: inherit;border-radius: 50%;-webkit-border-radius: 50%;-moz-border-radius: 50%;position: absolute;}.heart:after{top: -5px;}.heart:before{left: -5px;}");
            attachEvent();
            gameloop();
        }

        function gameloop() {
            for (var i = 0; i < hearts.length; i++) {
                if (hearts[i].alpha <= 0) {
                    document.body.removeChild(hearts[i].el);
                    hearts.splice(i, 1);
                    continue;
                }
                hearts[i].y--;
                hearts[i].scale += 0.004;
                hearts[i].alpha -= 0.013;
                hearts[i].el.style.cssText = "left:" + hearts[i].x + "px;top:" + hearts[i].y + "px;opacity:" + hearts[i].alpha + ";transform:scale(" + hearts[i].scale + "," + hearts[i].scale + ") rotate(45deg);background:" + hearts[i].color;
            }
            requestAnimationFrame(gameloop);
        }

        function attachEvent() {
            var old = typeof window.onclick === "function" && window.onclick;
            window.onclick = function (event) {
                old && old();
                createHeart(event);
            }
        }

        function createHeart(event) {
            var d = document.createElement("div");
            d.className = "heart";
            hearts.push({
                el: d,
                x: event.clientX - 5,
                y: event.clientY - 5,
                scale: 1,
                alpha: 1,
                color: randomColor()
            });
            document.body.appendChild(d);
        }

        function css(css) {
            var style = document.createElement("style");
            style.type = "text/css";
            try {
                style.appendChild(document.createTextNode(css));
            } catch (ex) {
                style.styleSheet.cssText = css;
            }
            document.getElementsByTagName('head')[0].appendChild(style);
        }

        function randomColor() {
            return "rgb(" + (~~(Math.random() * 255)) + "," + (~~(Math.random() * 255)) + "," + (~~(Math.random() * 255)) + ")";
        }
    })(window, document);

</script>
{% block js %}
{% endblock %}
</body>
</html>
<!--file.html-->

{% extends 'layout/manage.html' %}
{% load static %}
{% block title %}
    file
{% endblock %}

{% block css %}
    <style>
        .panel-default .panel-heading {
            display: flex;
            flex-direction: row;
            justify-content: space-between;
        }

        .panel-default > .panel-heading a {
            text-decoration: none;
        }

        .panel-default > .panel-heading span {
            padding: 0 5px;
        }

        .panel-default > .panel-heading .function .upload {
            overflow: hidden;
        }

        .panel-default > .panel-heading .function .upload input {
            opacity: 0;
            position: absolute;
            top: 0;
            bottom: 0;
            width: 76px;
            left: -2px;
            overflow: hidden;
        }

        .upload-progress {
            position: fixed;
            right: 2px;
            bottom: 2px;
            width: 400px;
        }

        .upload-progress .progress-error {
            color: red;
        }

    </style>

{% endblock %}

{% block content %}
    <div class="container-fluid">

        <div class="panel panel-default">
            <!-- Default panel contents -->
            <div class="panel-heading">
                <div>
                    <!--add-->
                    {% if parent_list %}
                        <a href="{% url 'file' project_id=request.tracer.project.id %}">
                            <i class="fa fa-home" aria-hidden="true"> 文件库</i>
                        </a>
                        {% for item in parent_list %}
                            <a href="{% url 'file' project_id=request.tracer.project.id %}?folder={{ item.id }}">
                                <i class="fa fa-hand-o-right" aria-hidden="true"> {{ item.name }}</i>
                            </a>
                        {% endfor %}
                    {% else %}
                        <a href="{% url 'file' project_id=request.tracer.project.id %}">
                            <span class="glyphicon glyphicon-home" aria-hidden="true"> 文件库</span>
                        </a>
                    {% endif %}
                </div>
                <div class="function">
                    <div class="btn btn-primary btn-xs upload" style="position: relative">
                        <div><i class="fa fa-upload" aria-hidden="true"></i> 上传文件</div>
                        <input type="file" multiple name="uploadFile" id="uploadFile">
                    </div>
                    <a class="btn btn-success btn-xs" data-toggle="modal" data-target="#myModal" data-whatever="新建文件夹">
                        <i class="fa fa-plus-circle" aria-hidden="true"></i> 新建文件夹
                    </a>
                </div>

            </div>
            <!--删除模态框-->
            <div class="modal fade" id="deleteModel" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
                <div class="modal-dialog" role="document">
                    <div class="alert alert-danger alert-dismissible fade in" role="alert">
                        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
                                aria-hidden="true">×</span></button>
                        <h4>是否要确定删除?</h4>
                        <p style="padding-top: 20px;padding-bottom: 20px;">
                            文件夹中包含的所有的文件都会被删除。
                        </p>
                        <p style="text-align: right;">
                            <a class="btn btn-default btn-sm" data-dismiss="modal" aria-label="Close">取 消</a>
                            <button id="btnDelete" type="button" class="btn btn-danger btn-sm">确 定</button>
                        </p>
                    </div>
                </div>
            </div>

            <!--文件夹添加/编辑模态框-->
            <div class="modal fade" tabindex="-1" role="dialog" id="myModal">
                <div class="modal-dialog" role="document">
                    <div class="modal-content">
                        <div class="modal-header">
                            <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
                                    aria-hidden="true">&times;</span></button>
                            <h4 class="modal-title">Modal title</h4>
                        </div>
                        <div class="modal-body">
                            <form id="myModelForm" novalidate>
                                {% csrf_token %}
                                <input type="text" class="hidden" name="fid" value="" id="fid">
                                {% for field in form %}
                                    <label for="{{ field.id_for_label }}">{{ field.label }}</label>
                                    {{ field }}
                                    <span class="error-msg"></span>
                                {% endfor %}

                            </form>
                        </div>
                        <div class="modal-footer">
                            <button type="button" class=
                                    "btn btn-default" data-dismiss="modal">关闭
                            </button>
                            <button type="button" class="btn btn-primary" id="myModelAdd">提交</button>
                        </div>
                    </div><!-- /.modal-content -->
                </div><!-- /.modal-dialog -->
            </div><!-- /.modal -->

            <!-- Table -->
            <table class="table">
                <thead>
                <tr>
                    <th>序列</th>
                    <th>名字</th>
                    <th>大小</th>
                    <th>更新者</th>
                    <th>时间</th>
                    <th>操作</th>
                </tr>
                </thead>
                <!--4-->
                <tbody id="rowList">
                {% for item in folder_file %}
                    <tr>
                        <th scope="row">{{ forloop.counter }}</th>
                        <td>
                            {% if item.file_type == 1 %}
                                <i class="fa fa-file" aria-hidden="true"></i>
                                {{ item.name }}

                            {% else %}
                                <!--有就一直渲染-->
                                <a href="{% url 'file' project_id=request.tracer.project.id %}?folder={{ item.id }}">
                                    <i class="fa fa-folder" aria-hidden="true">
                                    </i> {{ item.name }}
                                </a>
                            {% endif %}
                        </td>
                        <td>
                            {% if item.file_type == 1 %}
                                {{ item.file_size }}
                            {% else %}
                                --
                            {% endif %}
                        </td>
                        <td>{{ item.update_user.username }}</td>
                        <td>{{ item.update_datetime }}</td>
                        <td>
                            {% if item.file_type == 2 %}
                                <a class="btn btn-primary btn-xs"
                                   data-toggle="modal"
                                   data-target="#myModal"
                                   data-fid="{{ item.id }}"
                                   data-name="{{ item.name }}"
                                   data-whatever="编辑文件夹">
                                    <i class="fa fa-pencil-square-o" aria-hidden="true">编辑</i>
                                </a>
                            {% else %}
                                <a class="btn btn-default btn-xs download"
                                   href="">
                                    <i class="fa fa-cloud-download" aria-hidden="true">下载</i>
                                </a>
                            {% endif %}
                            <a class="btn btn-danger btn-xs"
                               data-toggle="modal"
                               data-fid="{{ item.id }}"
                               data-target="#deleteModel">
                                <i class="fa fa-trash" aria-hidden="true">删除</i>
                            </a>
                        </td>

                    </tr>
                {% endfor %}


                </tbody>


            </table>
        </div>
    </div>

    <!--上传-->
    <div id="uploadProgress" class="upload-progress hide">
        <div class="panel panel-primary">
            <div class="panel-heading">
                <i class="fa fa-cloud-upload" aria-hidden="true"></i> 上传进度
            </div>

            <table class="table">
                <tbody id="progressList">

                </tbody>
            </table>
        </div>
    </div>
    <!--上传动态展示-->
    <div class="hide">
        <table id="progressTemplate">
            <tr>
                <td>
                    <div class="name"></div>
                    <div class="progress">
                        <div class="progress-bar progress-bar-success progress-bar-striped" role="progressbar"
                             aria-valuenow="0"
                             aria-valuemin="0"
                             aria-valuemax="100" style="width: 0%;">
                            0%
                        </div>
                    </div>
                    <div class="progress-error"></div>
                </td>
            </tr>
        </table>
    </div>
    <!--上传成功以后展示数据-->
    <div class="hide">
        <table id="rowTpl">
            <tr>
                <td>
                    <i class="fa fa-file" aria-hidden="true"></i>
                    <span class="name"></span>
                </td>
                <td class="file_size"></td>
                <td class="username"></td>
                <td class="datetime"></td>
                <td>

                    <a class="btn btn-default btn-xs download">
                        <i class="fa fa-cloud-download" aria-hidden="true"></i>
                    </a>

                    <a class="btn btn-danger btn-xs delete" data-toggle="modal" data-target="#deleteModel">
                        <i class="fa fa-trash" aria-hidden="true"></i>
                    </a>
                </td>
            </tr>
        </table>
    </div>

{% endblock %}

{% block js %}
    <script src="{% static 'js/cos-js-sdk-v5.min.js' %}"></script>
    <script>
        var FILE_HREF = "{% url 'file' project_id=request.tracer.project.id %}"
        var DELETE_HREF = "{% url 'file_delete' project_id=request.tracer.project.id %}"

        var COS_CREDENTIAL = "{% url 'cos_credential' project_id=request.tracer.project.id %}";
        var FILE_POST = "{% url 'file_post' project_id=request.tracer.project.id %}";
        var CURRENT_FOLDER_ID = "{{ parent_obj.id }}";

        $(function () {
            initMyModel();
            initDeleteModel();
            addMyModel();
            deleteModel();

            bindUploadFile();

        })

        //初始化添加模态框
        function initMyModel() {
            $('#myModal').on('show.bs.modal', function (event) {
                var button = $(event.relatedTarget) // Button that triggered the modal
                var recipient = button.data('whatever') // Extract info from data-* attributes
                var dataName = button.data('name') // Extract info from data-* attributes
                var fid = button.data('fid') // Extract info from data-* attributes
                // If necessary, you could initiate an AJAX request here (and then do the updating in a callback).
                // Update the modal's content. We'll use jQuery here, but you could use a data binding library or other methods instead.
                var modal = $(this)
                modal.find('.modal-title').text(recipient)
                // 编辑
                if (fid) {
                    modal.find('#id_name').val(dataName)
                    // 给fid框添加一个value
                    modal.find('#fid').val(fid)

                } else {
                    // 添加
                    $('#myModelForm')[0].reset();
                }
            })
        }

        // 文件删除初始化模态框
        function initDeleteModel() {
            $('#deleteModel').on('show.bs.modal', function (event) {
                var button = $(event.relatedTarget) // Button that triggered the modal
                var fid = button.data('fid') // Extract info from data-* attributes
                var modal = $(this)

                // 给提交按钮绑定fid,用于后端进行判断
                modal.find('#btnDelete').attr('fid', fid)
            })
        }

        //文件夹添加提交ajax
        function addMyModel() {
            $("#myModelAdd").click(function () {
                $.ajax({
                    url: location.href,
                    type: "POST",
                    data: $("#myModelForm").serialize(),
                    dataType: "JSON",
                    success: function (ret) {
                        if (ret.status) {
                            location.href = location.href
                        } else {
                            $.each(ret.msg, function (key, value) {
                                $('#id_' + key).addClass('red_input').next().text(value[0])
                                // attr 只有添加的一个属性 其他的全部消失 addClass 在原有的基础上添加类
                            })
                        }
                    }
                })
                // 获取焦点 input红框消失 this: 当前操作的对象
                $('input').focus(function () {
                    $(this).removeClass('red_input').next().empty()
                })
            })
        }

        // 文件/文件夹删除提交Ajax
        function deleteModel() {
            $("#btnDelete").click(function () {
                $.ajax({
                    url: DELETE_HREF,
                    type: "POST",
                    data: {fid: $('#btnDelete').attr('fid'), csrfmiddlewaretoken: '{{ csrf_token }}'},
                    dataType: "JSON",
                    success: function (ret) {
                        if (ret.status) {
                            location.href = location.href
                        }
                    }
                })

            })
        }


        // 上传文件/获取凭证/写入数据库/展示进度
        function bindUploadFile() {
            $('#uploadFile').change(function () {
                $('#progressList').empty();

                var fileList = $(this)[0].files;
                // 获取本次要上传的每个文件 名称&大小
                var checkFileList = [];
                $.each(fileList, function (index, fileObject) {
                    checkFileList.push({'name': fileObject.name, 'size': fileObject.size})
                });

                // 把这些数据发送到django后台:Django后台进行容量的校验,如果么有问题则返回临时凭证;否则返回错误信息;
                var cos_credential = new COS({
                    getAuthorization: function (options, callback) {
                        $.post(COS_CREDENTIAL, JSON.stringify(checkFileList), function (res) {
                            if (res.status) {
                                var credentials = res.data && res.data.credentials;
                                // 凭证
                                callback({
                                    TmpSecretId: credentials.tmpSecretId,
                                    TmpSecretKey: credentials.tmpSecretKey,
                                    XCosSecurityToken: credentials.sessionToken,
                                    StartTime: res.data.startTime,
                                    ExpiredTime: res.data.expiredTime
                                });

                                $('#uploadProgress').removeClass('hide');
                            } else {
                                alert(res.error);
                            }
                        });
                    }
                });

                // 上传文件(上传之前先获取临时凭证)
                $.each(fileList, function (index, fileObject) {
                    var fileName = fileObject.name;
                    var fileSize = fileObject.size;
                    var key = (new Date()).getTime() + "_" + fileName;

                    var tr = $('#progressTemplate').find('tr').clone();
                    tr.find('.name').text(fileName);
                    $('#progressList').append(tr);

                    // 上传文件(异步)
                    cos_credential.putObject({
                        Bucket: '{{ request.tracer.project.bucket }}', /* 必须 */
                        Region: '{{ request.tracer.project.region }}', /* 存储桶所在地域,必须字段 */
                        Key: key, /* 必须 */
                        Body: fileObject, // 上传文件对象
                        onProgress: function (progressData) {
                            var percent = progressData.percent * 100 + '%';
                            tr.find('.progress-bar').text(percent);
                            tr.find('.progress-bar').css('width', percent);
                        }
                    }, function (err, data) {
                        if (data && data.statusCode === 200) {
                            // 上传成功,将本次上传的文件提交到后台并写入数据
                            // 当前文件上传成功
                            $.post(FILE_POST, {
                                name: fileName,
                                key: key,
                                file_size: fileSize,
                                parent: CURRENT_FOLDER_ID,
                                etag: data.ETag,
                                file_path: data.Location
                            }, function (res) {
                                // 在数据库中写入成功,将已添加的数据在页面上动态展示。
                                var newTr = $('#rowTpl').find('tr').clone();
                                newTr.find('.name').text(res.data.name);
                                newTr.find('.file_size').text(res.data.file_size);
                                newTr.find('.username').text(res.data.username);
                                newTr.find('.datetime').text(res.data.datetime);
                                newTr.find('.delete').attr('data-fid', res.data.id);
                                newTr.find('.download').attr('href', res.data.download_url);
                                $('#rowList').append(newTr);

                                // 自己的进度删除
                                tr.remove();
                                window.location.reload();

                            })

                        } else {
                            tr.find('.progress-error').text('上传失败');
                        }
                    });


                })
            });
        }

    </script>
{% endblock %}
# view
# fiel.py
from django.shortcuts import render, redirect, HttpResponse
from django.http.response import JsonResponse
from web import models
from django.urls import reverse
from web.Forms.file import FolderModelForm, FileModelForm
# 字典对象
from django.forms.models import model_to_dict
from utils.tencent.cos import delete_file, delete_file_obj, credential, check_file
from django.views.decorators.csrf import csrf_exempt
import json
import requests

'''
文件
'''


def file(request, project_id):
    # http://127.0.0.1:8000/manage/17/file/  根文件夹
    # http://127.0.0.1:8000/manage/17/file/?folder=7  子文件夹
    '''文件夹管理 文件夹添加'''
    parent_obj = None
    folder_id = request.GET.get('folder', '')
    # 查出当前对象 给前端渲染 循环
    if folder_id.isdecimal():
        parent_obj = models.FileRepository.objects.filter(id=int(folder_id), project=request.tracer.project,
                                                          file_type=2).first()
    if request.method == 'GET':
        # 文件导航导航
        breadcrumb_list = []
        parent = parent_obj
        while parent:
            breadcrumb_list.insert(0, model_to_dict(parent, ['id', 'name']))
            # breadcrumb_list.insert(0, {'id': parent.id, 'name': parent.name})
            parent = parent.parent

        # 文件展示
        queryset = models.FileRepository.objects.filter(project=request.tracer.project)
        if parent_obj:
            # 进入了目录 一直渲染 file_object_list 文件列表对象
            file_object_list = queryset.filter(parent=parent_obj).order_by('-file_type')
        else:
            # 根目录
            file_object_list = queryset.filter(parent__isnull=True)

        form = FolderModelForm(request, parent_obj)
        return render(request, 'file.html', locals())
    # post
    fid = request.POST.get('fid', '')
    edit_object = None
    if fid.isdecimal():
        edit_object = models.FileRepository.objects.filter(id=int(fid), project=request.tracer.project,
                                                           file_type=2
                                                           ).first()
    if edit_object:
        # 编辑
        form = FolderModelForm(request, parent_obj, data=request.POST, instance=edit_object)
        # form = FolderModelForm(request, parent_obj, data=request.POST)  这样的话一直走的就是form新增
    else:
        form = FolderModelForm(request, parent_obj, data=request.POST)
    if form.is_valid():
        form.instance.file_type = 2
        form.instance.update_user = request.tracer.user
        form.instance.project = request.tracer.project
        form.instance.parent = parent_obj
        form.save()
        return JsonResponse({'status': 1})
    return JsonResponse({'status': 0, 'msg': form.errors})


def file_delete(request, project_id):
    '''删除文件'''
    fid = request.GET.get('fid', '')
    delete_object = models.FileRepository.objects.filter(id=fid, project=request.tracer.project).first()
    if delete_object.file_type == 1:
        # 文件删除,项目使用空间返回 数据库删除  cos删除
        # 项目使用空间返回  # 默认字节
        request.tracer.project.user_space -= delete_object.file_size
        request.tracer.project.save()
        # 数据库删除
        delete_object.delete()
        # cos删除
        delete_file(Bucket=request.tracer.project.bucket, region=request.tracer.project.region,
                    Key=delete_object.key)
        return JsonResponse({'status': True})

    else:
        # 文件夹删除
        total_size = 0
        # folder_list 全是文件夹
        folder_list = [delete_object, ]
        # 文件对象
        key_list = []
        for folder in folder_list:
            # 从底层找父级 这样可以找到所有文件夹以及文件
            child_list = models.FileRepository.objects.filter(project=request.tracer.project, parent=folder).order_by(
                '-file_type')
            for child in child_list:
                if child.file_type == 2:
                    folder_list.append(child)
                else:
                    # 文件大小
                    total_size += child.file_size
                    # 当前文件对象
                    key_list.append({'Key': child.key})
        if total_size:
            # 归还空间
            request.tracer.project.user_space -= total_size
            request.tracer.project.save()
        if key_list:
            # cos批量删除
            delete_file_obj(Bucket=request.tracer.project.bucket, region=request.tracer.project.region,
                            Key=key_list)
        # 数据库删除
        delete_object.delete()

    return JsonResponse({'status': 1})


@csrf_exempt
def cos_credential(request, project_id):
    """ 获取cos上传临时凭证 """
    # 单文件限制
    per_file_limit = request.tracer.price_policy.project_size * 1024 * 1024
    total_file_limit = request.tracer.price_policy.project_space * 1024 * 1024 * 1024

    total_size = 0
    file_list = json.loads(request.body.decode('utf-8'))
    for item in file_list:
        # 文件的字节大小 item['size'] = B
        # 单文件限制的大小 M
        # 超出限制
        if item['size'] > per_file_limit:
            msg = "单文件超出限制(最大{}M),文件:{},请升级套餐。".format(request.tracer.price_policy.project_size, item['name'])
            return JsonResponse({'status': False, 'error': msg})
        total_size += item['size']

        # 做容量限制:单文件 & 总容量

    # 总容量进行限制
    # request.tracer.price_policy.project_space  # 项目的允许的空间
    # request.tracer.project.use_space # 项目已使用的空间
    # 项目的使用空间+上传文件空间
    if request.tracer.project.user_space + total_size > total_file_limit:
        return JsonResponse({'status': False, 'error': "容量超过限制,请升级套餐。"})
    # 获取cos上传临时凭证
    data_dict = credential(request.tracer.project.bucket, request.tracer.project.region)
    return JsonResponse({'status': True, 'data': data_dict})


@csrf_exempt
def file_post(request, project_id):
    """ 已上传成功的文件写入到数据 """
    """
    name: fileName,
    key: key,
    file_size: fileSize,
    parent: CURRENT_FOLDER_ID,
    # etag: data.ETag,
    file_path: data.Location
    """
    # 根据key再去cos获取文件Etag和"db7c0d83e50474f934fd4ddf059406e5"

    # print(request.POST)
    # 把获取到的数据写入数据库即可
    form = FileModelForm(request, data=request.POST)

    if form.is_valid():
        # 通过ModelForm.save存储到数据库中的数据返回的isntance对象,无法通过get_xx_display获取choice的中文
        # form.instance.file_type = 1
        # form.update_user = request.tracer.user
        # instance = form.save() # 添加成功之后,获取到新添加的那个对象(instance.id,instance.name,instance.file_type,instace.get_file_type_display()

        # 校验通过:数据写入到数据库
        data_dict = form.cleaned_data
        data_dict.pop('etag')
        data_dict.update({'project': request.tracer.project, 'file_type': 1,
                          'update_user': request.tracer.user})
        instance = models.FileRepository.objects.create(**data_dict)

        # 项目的已使用空间:更新 (data_dict['file_size'])
        request.tracer.project.user_space += data_dict['file_size']
        request.tracer.project.save()

        result = {
            'id': instance.id,
            'name': instance.name,
            'file_size': instance.file_size,
            'username': instance.update_user.username,
            'datetime': instance.update_datetime,
            # 'download_url': reverse('file_download', kwargs={"project_id": project_id, 'file_id': instance.id})
            # 'file_type': instance.get_file_type_display()
        }
        return JsonResponse({'status': True, 'data': result})

    return JsonResponse({'status': False, 'data': "文件错误"})


def file_download(request, project_id, file_id):
    '''下载文件'''
    file_obj = models.FileRepository.objects.filter(project_id=project_id, id=file_id).first()
    url = file_obj.file_path
    # 大文件的下载
    data = requests.get(url).iter_content()
    # 正常文件的下载
    # data = requests.get(url).content
    # 弹框提示框
    response = HttpResponse(data, content_type="application/octet-stream")
    # 中文转义
    from django.utils.encoding import escape_uri_path
    # 设置下载头
    response['Content-Disposition'] = "attachment; filename={};".format(escape_uri_path(file_obj.name))
    return response
form.py

from django import forms
from web import models
from utils.form_placeholder import form_placeholder
from django.core.exceptions import ValidationError


class file_add(form_placeholder, forms.ModelForm):
    class Meta:
        model = models.UploadFile
        fields = ['name', ]

    def __init__(self, request, parent_obj, *args, **kwargs):
        super(file_add, self).__init__(*args, **kwargs)
        self.request = request
        self.parent_obj = parent_obj

    def clean_name(self):
        name = self.cleaned_data.get('name')
        # 判断当前目录是否存在当前文件夹名字
        # 当前文件夹分为根目录/子目录
        # 1.根目录
        if not self.parent_obj:
            exist = models.UploadFile.objects.filter(project=self.request.tracer.project, file_type=2,
                                                     parent__isnull=True, name=name).exists()
        # 2. 子目录
        else:
            exist = models.UploadFile.objects.filter(project=self.request.tracer.project, file_type=2,
                                                     parent=self.parent_obj, name=name).exists()
        if exist:
            raise ValidationError('当前文件夹的名字已经存在')
        return name


class FileModelForm(forms.ModelForm):
    etag = forms.CharField(label='ETag')

    class Meta:
        model = models.UploadFile
        exclude = ['project', 'file_type', 'update_user', 'update_datetime']

    def __init__(self, request, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.request = request

    def clean_file_path(self):
        return "https://{}".format(self.cleaned_data['file_path'])

    """
    def clean(self):
        key = self.cleaned_data['key']
        etag = self.cleaned_data['etag']
        size = self.cleaned_data['size']

        if not key or not etag:
            return self.cleaned_data

        # 向COS校验文件是否合法
        # SDK的功能
        from qcloud_cos.cos_exception import CosServiceError
        try:
            result = check_file(self.request.tracer.project.bucket, self.request.tracer.project.region, key)
        except CosServiceError as e:
            self.add_error("key", '文件不存在')
            return self.cleaned_data

        cos_etag = result.get('ETag')
        if etag != cos_etag:
            self.add_error('etag', 'ETag错误')

        cos_length = result.get('Content-Length')
        if int(cos_length) != size:
            self.add_error('size', '文件大小错误')

        return self.cleaned_data
    """
# cos.py
# -*- coding:utf-8 -*-
# pip install -U cos-python-sdk-v5
# APPID
https://console.cloud.tencent.com/cam/overview

from qcloud_cos import CosConfig
from qcloud_cos import CosS3Client
from django.conf import settings
from qcloud_cos.cos_exception import CosServiceError


def create_bucket(Bucket, region='ap-chengdu'):
    """创建桶
    APPID
    在
    https://console.cloud.tencent.com/cam/capi
    获取
    bucket = 手机号-当前时间-APPID
    bucket = '{}-{}-1305448189'.format(request.tracer.user.mobile_phone, str(int(time.time())))
    """
    secret_id = settings.TENCENT_COS_ID  # 替换为用户的 secretId
    secret_key = settings.TENCENT_COS_KEY  # 替换为用户的 secretKey

    region = region  # 替换为用户的 Region

    config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key)

    client = CosS3Client(config)

    client.create_bucket(
        # Bucket='test-1305448189',
        Bucket=Bucket,

        ACL="public-read"  # private  /  public-read / public-read-write
    )
    # 跨域
    cors_config = {
        'CORSRule': [
            {
                'AllowedOrigin': '*',
                'AllowedMethod': ['GET', 'PUT', 'HEAD', 'POST', 'DELETE'],
                'AllowedHeader': "*",
                'ExposeHeader': "*",
                'MaxAgeSeconds': 500
            }
        ]
    }
    client.put_bucket_cors(
        Bucket=Bucket,
        CORSConfiguration=cors_config
    )


def upload_img(Bucket, region, Body, Key):
    """上传图片文件
    key 自己起的文件名称,一般拼凑出一个文件名称
    """

    secret_id = settings.TENCENT_COS_ID  # 替换为用户的 secretId
    secret_key = settings.TENCENT_COS_KEY  # 替换为用户的 secretKey

    region = region  # 替换为用户的 Region

    config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key)

    client = CosS3Client(config)

    response = client.upload_file_from_buffer(
        Bucket=Bucket,
        Body=Body,  # 文件对象
        Key=Key  # 上传到桶之后的文件名
    )
    # https://19803630852-1632301164-1305448189.cos.ap-chengdu.myqcloud.com/bd67b5c4b627b321372b483075a47992.png
    return 'https://{}.cos.{}.myqcloud.com/{}'.format(Bucket, region, Key)


def delete_file(Bucket, region, Key):
    """删除文件"""

    secret_id = settings.TENCENT_COS_ID  # 替换为用户的 secretId
    secret_key = settings.TENCENT_COS_KEY  # 替换为用户的 secretKey

    region = region  # 替换为用户的 Region

    config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key)

    client = CosS3Client(config)

    client.delete_object(
        Bucket=Bucket,
        Key=Key  # 上传到桶之后的文件名
    )


def delete_file_obj(Bucket, region, Key):
    """批量删除文件"""
    '''
         objects = {
                    "Quiet": "true",
                    "Object": [
                        {
                            "Key": "file_name1"
                        },
                        {
                            "Key": "file_name2"
                        }
                    ]
                }
        '''

    secret_id = settings.TENCENT_COS_ID  # 替换为用户的 secretId
    secret_key = settings.TENCENT_COS_KEY  # 替换为用户的 secretKey

    region = region  # 替换为用户的 Region

    config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key)

    client = CosS3Client(config)
    objects = {
        "Quiet": "true",
        "Object": Key
    }
    client.delete_objects(
        Bucket=Bucket,
        Delete=objects  # 上传到桶之后的文件名
    )


def credential(bucket, region):
    '''
    获取cos上传临时凭证
    pip3 install -U qcloud-python-sts

    '''

    from sts.sts import Sts

    config = {
        # 临时密钥有效时长,单位是秒(30分钟=1800秒)
        'duration_seconds': 1800,
        # 固定密钥 id
        'secret_id': settings.TENCENT_COS_ID,
        # 固定密钥 key
        'secret_key': settings.TENCENT_COS_KEY,
        # 换成你的 bucket
        'bucket': bucket,
        # 换成 bucket 所在地区
        'region': region,
        # 这里改成允许的路径前缀,可以根据自己网站的用户登录态判断允许上传的具体路径
        # 例子: a.jpg 或者 a/* 或者 * (使用通配符*存在重大安全风险, 请谨慎评估使用)
        'allow_prefix': '*',
        # 密钥的权限列表。简单上传和分片需要以下的权限,其他权限列表请看 https://cloud.tencent.com/document/product/436/31923
        'allow_actions': [
            # "name/cos:PutObject",
            # 'name/cos:PostObject',
            # 'name/cos:DeleteObject',
            # "name/cos:UploadPart",
            # "name/cos:UploadPartCopy",
            # "name/cos:CompleteMultipartUpload",
            # "name/cos:AbortMultipartUpload",
            "*",
        ],

    }

    sts = Sts(config)
    result_dict = sts.get_credential()
    return result_dict


def check_file(bucket, region, key):
    '''校验文件'''
    config = CosConfig(Region=region, SecretId=settings.TENCENT_COS_ID, SecretKey=settings.TENCENT_COS_KEY)
    client = CosS3Client(config)

    data = client.head_object(
        Bucket=bucket,
        Key=key
    )

    return data


def delete_bucket(bucket, region):
    """ 删除桶 """
    # 删除桶中所有文件
    # 删除桶中所有碎片
    # 删除桶
    config = CosConfig(Region=region, SecretId=settings.TENCENT_COS_ID, SecretKey=settings.TENCENT_COS_KEY)
    client = CosS3Client(config)

    try:
        # 找到文件 & 删除
        while True:
            part_objects = client.list_objects(bucket)

            # 已经删除完毕,获取不到值
            contents = part_objects.get('Contents')
            if not contents:
                break

            # 批量删除
            objects = {
                "Quiet": "true",
                "Object": [{'Key': item["Key"]} for item in contents]
            }
            client.delete_objects(bucket, objects)

            if part_objects['IsTruncated'] == "false":
                break

        # 找到碎片 & 删除
        while True:
            part_uploads = client.list_multipart_uploads(bucket)
            uploads = part_uploads.get('Upload')
            if not uploads:
                break
            for item in uploads:
                client.abort_multipart_upload(bucket, item['Key'], item['UploadId'])
            if part_uploads['IsTruncated'] == "false":
                break

        client.delete_bucket(bucket)
    except CosServiceError as e:
        pass

 

posted @ 2021-11-05 17:17  mofr  阅读(185)  评论(0)    收藏  举报