feat: add project source code
This commit is contained in:
30
.gitignore
vendored
Normal file
30
.gitignore
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
|
||||
# Virtual environments
|
||||
env/
|
||||
venv/
|
||||
.venv/
|
||||
.env
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# Database
|
||||
*.sqlite3
|
||||
*.db
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
logs/
|
||||
|
||||
# Uploaded files
|
||||
files/
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
0
fileflow/__init__.py
Normal file
0
fileflow/__init__.py
Normal file
16
fileflow/asgi.py
Normal file
16
fileflow/asgi.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
ASGI config for fileflow project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "fileflow.settings")
|
||||
|
||||
application = get_asgi_application()
|
||||
25
fileflow/migrations/0001_initial.py
Normal file
25
fileflow/migrations/0001_initial.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 4.2.23 on 2025-10-21 08:12
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="FilesModel",
|
||||
fields=[
|
||||
("id", models.BigAutoField(primary_key=True, serialize=False)),
|
||||
("create_time", models.DateTimeField(auto_now_add=True)),
|
||||
("file_path", models.TextField()),
|
||||
("file_name", models.TextField()),
|
||||
],
|
||||
options={
|
||||
"db_table": "files",
|
||||
},
|
||||
),
|
||||
]
|
||||
0
fileflow/migrations/__init__.py
Normal file
0
fileflow/migrations/__init__.py
Normal file
14
fileflow/models.py
Normal file
14
fileflow/models.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
|
||||
class FilesModel(models.Model):
|
||||
objects = None
|
||||
id = models.BigAutoField(primary_key=True)
|
||||
create_time = models.DateTimeField(auto_now_add=True)
|
||||
file_path = models.TextField(null=False)
|
||||
file_name = models.TextField(null=False)
|
||||
class Meta:
|
||||
db_table = 'files'
|
||||
def __str__(self):
|
||||
return self.file_name
|
||||
68
fileflow/service/file_service.py
Normal file
68
fileflow/service/file_service.py
Normal file
@@ -0,0 +1,68 @@
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
|
||||
from decouple import config
|
||||
from django.forms import model_to_dict
|
||||
|
||||
from fileflow.models import FilesModel
|
||||
logger = logging.getLogger('django')
|
||||
|
||||
class FilesService:
|
||||
'''
|
||||
处理文件上传下载等业务
|
||||
'''
|
||||
|
||||
@staticmethod
|
||||
def get_files():
|
||||
logger.info("Start to get files service")
|
||||
try:
|
||||
dir_path = config('FILE_DIR')
|
||||
files = []
|
||||
files_objs = FilesModel.objects.all()
|
||||
for obj in files_objs:
|
||||
file_info = {"id": obj.id,"file_name":obj.file_name, "file_path": obj.file_path}
|
||||
if os.path.exists(obj.file_path):
|
||||
files.append(file_info)
|
||||
return files
|
||||
except Exception as e:
|
||||
logger.error("get vendor files service error={0}".format(e))
|
||||
raise e
|
||||
|
||||
|
||||
@staticmethod
|
||||
def upload_files(upload_files):
|
||||
logger.info("Start to upload files service")
|
||||
try:
|
||||
files_list = []
|
||||
for file in upload_files:
|
||||
dir_path = config('FILE_DIR')
|
||||
id = generate_timestamp_id()
|
||||
dir_path = os.path.join(dir_path,str(id))
|
||||
os.makedirs(dir_path, exist_ok=True)
|
||||
file_path = os.path.join(dir_path, file.name)
|
||||
filemd = FilesModel()
|
||||
filemd.id = id
|
||||
filemd.file_name = file.name
|
||||
filemd.file_path = file_path
|
||||
logger.info("absolute path is: %s", os.path.abspath(file_path))
|
||||
logger.info("file path is {0}".format(file_path))
|
||||
with open(file_path, 'wb+') as destination:
|
||||
for chunk in file.chunks():
|
||||
destination.write(chunk)
|
||||
filemd_dict = model_to_dict(filemd)
|
||||
logger.info("filemd_dict is {0}".format(filemd_dict))
|
||||
files_list.append(filemd_dict)
|
||||
filemd.save()
|
||||
return files_list
|
||||
except Exception as e:
|
||||
logger.error("upload files service error={0}".format(e))
|
||||
raise e
|
||||
|
||||
|
||||
def generate_timestamp_id():
|
||||
"""生成基于时间戳的ID"""
|
||||
return int(time.time() * 1000)
|
||||
|
||||
|
||||
|
||||
165
fileflow/settings.py
Normal file
165
fileflow/settings.py
Normal file
@@ -0,0 +1,165 @@
|
||||
"""
|
||||
Django settings for fileflow project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 4.2.23.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.2/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/4.2/ref/settings/
|
||||
"""
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from decouple import config
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = "django-insecure--m1a@uwg(0zyveppn9$#qw7ccg=o%z=-$a_li)dvhq(c1(e3or"
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
# ALLOWED_HOSTS = []
|
||||
ALLOWED_HOSTS = [i for i in config('DJANGO_ALLOWED_HOSTS', cast=str).split(',')]
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
"django.contrib.admin",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
"fileflow",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
]
|
||||
|
||||
ROOT_URLCONF = "fileflow.urls"
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [],
|
||||
"APP_DIRS": True,
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
"django.template.context_processors.debug",
|
||||
"django.template.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = "fileflow.wsgi.application"
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.sqlite3",
|
||||
"NAME": BASE_DIR / "db.sqlite3",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = "en-us"
|
||||
|
||||
TIME_ZONE = "UTC"
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/4.2/howto/static-files/
|
||||
|
||||
STATIC_URL = "static/"
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||
|
||||
|
||||
LOGGING = {
|
||||
'version': 1, # 配置版本,固定为 1
|
||||
'disable_existing_loggers': False, # 禁用默认日志器
|
||||
'formatters': {
|
||||
'verbose': {
|
||||
'format':'%(asctime)s [%(threadName)s] [%(module)s:%(lineno)d] [%(module)s:%(funcName)s] [%(levelname)s]- %(message)s'
|
||||
}
|
||||
},
|
||||
'handlers': {
|
||||
'console': { # 控制台日志处理器
|
||||
'level': config('DJANGO_LOG_LEVEL',default='INFO',cast=str), # 日志级别设置为 DEBUG
|
||||
'class': 'logging.StreamHandler', # 使用 StreamHandler 输出到控制台
|
||||
'formatter': 'verbose'
|
||||
},
|
||||
'file': { # 文件日志处理器
|
||||
'level': config('DJANGO_LOG_LEVEL',default='INFO',cast=str), # 日志级别设置为 DEBUG
|
||||
'class': 'logging.handlers.RotatingFileHandler', # 使用 FileHandler 输出到文件
|
||||
'filename': os.path.join(BASE_DIR,"logs/fileflow.log"), # 日志文件名
|
||||
'maxBytes': 1024 * 1024 * 50,
|
||||
'backupCount': 5, # 最多保留5个备份文件,
|
||||
'encoding': 'utf-8',
|
||||
'formatter': 'verbose'
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'django': { # 默认的 Django 日志器
|
||||
'handlers': ["console", "file"], # 输出到控制台
|
||||
'level': config('DJANGO_LOG_LEVEL',default='INFO',cast=str), # 日志级别设置为 DEBUG
|
||||
'propagate': True, # 传播日志给父日志记录器
|
||||
},
|
||||
'myapp': { # 自定义的应用日志器
|
||||
"handlers": ['console', 'file'], # 输出到文件
|
||||
'level': config('DJANGO_LOG_LEVEL',default='INFO',cast=str), # 日志级别设置为 DEBUG
|
||||
'propagate': True, # 传播日志给父日志记录器
|
||||
},
|
||||
},
|
||||
}
|
||||
26
fileflow/urls.py
Normal file
26
fileflow/urls.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
URL configuration for fileflow project.
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/4.2/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
from fileflow import views
|
||||
|
||||
urlpatterns = [
|
||||
path("admin/", admin.site.urls),
|
||||
path("fileflow/uploadFiles", views.upload_files),
|
||||
path("fileflow/getFiles", views.get_files),
|
||||
path("fileflow/viewFile", views.view_file),
|
||||
]
|
||||
95
fileflow/views.py
Normal file
95
fileflow/views.py
Normal file
@@ -0,0 +1,95 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import traceback, base64
|
||||
|
||||
from decouple import config
|
||||
from django.http import JsonResponse, HttpResponse, FileResponse
|
||||
from django.views.decorators.http import require_POST
|
||||
from rest_framework.decorators import api_view
|
||||
|
||||
from .service.file_service import FilesService
|
||||
|
||||
logger = logging.getLogger('django')
|
||||
|
||||
|
||||
|
||||
@api_view(['POST'])
|
||||
def upload_files(request):
|
||||
logger.info("Start to upload files in views")
|
||||
if 'files' not in request.FILES:
|
||||
return JsonResponse(data={'message': '没有文件上传'}, status=400)
|
||||
upload_files = request.FILES.getlist('files')
|
||||
|
||||
try:
|
||||
files = FilesService.upload_files(upload_files)
|
||||
return JsonResponse(data={'result': files}, status=200)
|
||||
except Exception as ex:
|
||||
logger.error(traceback.format_exc())
|
||||
return JsonResponse(data={'error': '文件上传失败'}, status=500)
|
||||
|
||||
|
||||
@api_view(['GET'])
|
||||
def get_files(request):
|
||||
logger.info("Start to get files in views")
|
||||
try:
|
||||
files = FilesService.get_files()
|
||||
return JsonResponse(data={'result': files}, status=200)
|
||||
except Exception as ex:
|
||||
logger.error(traceback.format_exc())
|
||||
return JsonResponse(data={'error': '获取文件列表失败'}, status=500)
|
||||
|
||||
|
||||
@api_view(['POST'])
|
||||
def download_file(request):
|
||||
'''
|
||||
body:
|
||||
filename
|
||||
vendor_id 供应商id,(仅管理员传,下载,普通用户调用该接口不用传,直接取上下文)
|
||||
return:
|
||||
|
||||
'''
|
||||
logger.info('Start to download file in views')
|
||||
data = json.loads(request.body)
|
||||
filename = data.get('filename', None)
|
||||
if not filename:
|
||||
return JsonResponse(data={'message': 'filename不能为空'}, status=400)
|
||||
try:
|
||||
|
||||
dir_path = config('FILE_DIR')
|
||||
file_path = os.path.join(dir_path, filename)
|
||||
if not os.path.exists(file_path) or not os.path.isfile(file_path):
|
||||
return JsonResponse(data={'error': '文件不存在'}, status=500)
|
||||
with open(file_path, 'rb') as f:
|
||||
response = HttpResponse(f.read(), content_type='application/octet-stream')
|
||||
# 告诉浏览器:这是要下载的文件
|
||||
response['Content-Disposition'] = f'attachment; filename="{filename}"'
|
||||
# 可选:添加文件大小
|
||||
response['Content-Length'] = os.path.getsize(file_path)
|
||||
return response
|
||||
except Exception as ex:
|
||||
logger.error(traceback.format_exc())
|
||||
return JsonResponse(data={'error': '下载文件失败'}, status=500)
|
||||
|
||||
|
||||
|
||||
@api_view(['GET'])
|
||||
def view_file(request):
|
||||
logger.info('Start to view file in views')
|
||||
filename = request.GET.get('filename', None)
|
||||
file_path = request.GET.get('file_path', None)
|
||||
if not file_path:
|
||||
return JsonResponse(data={'message': 'filepath cannot be empty'}, status=400)
|
||||
try:
|
||||
# 安全检查
|
||||
if not os.path.exists(file_path) or not os.path.isfile(file_path):
|
||||
return JsonResponse(data={'message': 'file is not exist'}, status=400)
|
||||
|
||||
response = FileResponse(open(file_path, 'rb'))
|
||||
response['Content-Disposition'] = f'inline; filename="{filename}"'
|
||||
|
||||
return response
|
||||
except Exception as ex:
|
||||
logger.error(traceback.format_exc())
|
||||
return JsonResponse(data={'error': '文件展示失败'}, status=500)
|
||||
|
||||
16
fileflow/wsgi.py
Normal file
16
fileflow/wsgi.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
WSGI config for fileflow project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "fileflow.settings")
|
||||
|
||||
application = get_wsgi_application()
|
||||
22
manage.py
Normal file
22
manage.py
Normal file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "fileflow.settings")
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
0
migrations/__init__.py
Normal file
0
migrations/__init__.py
Normal file
Reference in New Issue
Block a user