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">×</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