简介:这是一套面向计算机相关专业本科生的高分毕业设计级项目资源,基于Python与Django框架实现完整的垃圾分类管理系统,适用于软件工程、人工智能、电子信息等方向的毕设、课程设计或项目实践。资源包共2000个文件,主体为1457个Python源码(含Django核心逻辑、模型定义与视图处理)、139个HTML模板(构建前端页面结构)、96个JavaScript脚本(实现交互功能)及20个CSS样式文件(含select2、widgets等UI组件),整体压缩包仅17.12MB,轻量易部署。已有139人下载学习,适合具备基础Web开发能力的学生在真实业务场景中理解MVC架构落地、数据库设计(如垃圾类别分级、用户权限管理)、前后端协同与系统部署全流程。资源包含全部可运行代码、详细设计文档、数据库初始化脚本及完整项目目录结构说明,所有模块均经测试验证,支持直接部署或二次开发拓展识别接口、管理后台等功能。
1. 垃圾分类系统不是演示Demo:它得在社区物业后台真跑起来,还要扛住阿姨扫码录入高峰
这不是一个“用Django搭个CRUD页面+几个按钮弹窗”的毕业设计套壳项目。真正能拿去交差、甚至被社区临时上线试用的垃圾分类系统,核心矛盾从来不在“能不能显示四类垃圾桶图标”,而在于:如何让保洁员在凌晨五点寒风里,用一台电量只剩20%的安卓旧机,3秒内完成一次湿垃圾拍照→自动识别→归属楼栋→生成积分→同步到物业小程序。它要处理真实场景里的模糊图、反光塑料袋、半遮挡厨余、手写纸条混投;要应对物业管理员批量导入327户居民数据时的Excel乱码和空行;更要保证每天凌晨三点定时任务清理过期未审核记录时不卡死整个后台。本篇不讲“Django有多优雅”,只拆解我去年帮三个学院指导毕设时,学生从“本地runserver能跳转”到“部署到阿里云轻量服务器后连续72小时无报错”的完整链路——包括模型轻量化怎么选TensorFlow Lite还是ONNX Runtime、Django Admin怎么魔改才能让60岁物业主任看懂审核界面、以及为什么你写的python manage.py migrate在老师电脑上总报no module named 'PIL'这种玄学错误。
2. 从零初始化:用Django 4.2 + Python 3.10 搭建可交付的最小生产骨架
2.1 创建带生产就绪配置的Django项目(不是django-admin startproject)
很多同学直接django-admin startproject waste,结果后期加Redis缓存、日志分级、静态文件CDN时,配置全塞进settings.py,最后变成2000行难以维护的巨兽。我推荐用django-project-template社区模板起步,它强制分离开发/测试/生产配置:
# 安装cookiecutter(比官方startproject更可控) pip install cookiecutter # 拉取经实战验证的模板(已剔除所有敏感依赖) cookiecutter https://github.com/pydanny/cookiecutter-django.git # 交互式填空(关键选项如下): # project_name: waste_system # version: 0.1.0 # timezone: Asia/Shanghai # use_docker: n # 毕设不用Docker,避免答辩时解释容器概念 # use_async: n # 同步IO足够,异步反而增加调试复杂度 # use_sentry: n # 毕设不用错误监控生成结构后,删掉requirements/base.txt里psycopg2-binary(我们用SQLite起步,避免学生装PostgreSQL失败),保留pillow==9.5.0(图像处理刚需)、django-crispy-forms==2.0(Admin表单美化)。重点检查config/settings/production.py是否已预置DEBUG = False、ALLOWED_HOSTS = ['*'](答辩演示用)、SECURE_SSL_REDIRECT = False(本地没HTTPS证书)。
提示:
cookiecutter-django模板默认禁用django.contrib.sites,但我们的积分模块需关联用户所属小区,必须手动在INSTALLED_APPS中补上'django.contrib.sites',并在production.py末尾加SITE_ID = 1,否则迁移时会报relation "django_site" does not exist。
2.2 设计贴合物业实际的数据库模型(不是教科书式ER图)
毕业设计常犯的错:把“可回收物”“有害垃圾”做成独立Model,结果录入一条记录要跨5张表JOIN。真实物业系统需要的是高写入、低查询延迟、强业务语义。我们采用“单表多字段+状态机”设计:
# waste/models.py from django.db import models from django.contrib.auth.models import User class WasteRecord(models.Model): # 【核心字段】不拆分类型表,用choices提升写入速度 CATEGORY_CHOICES = ( ('kitchen', '厨余垃圾'), ('recyclable', '可回收物'), ('hazardous', '有害垃圾'), ('other', '其他垃圾'), ) category = models.CharField(max_length=20, choices=CATEGORY_CHOICES) # 【业务字段】直接存楼栋号,避免关联Building表查ID building_code = models.CharField(max_length=10, help_text="如:A-03-1201") # 【防作弊字段】GPS坐标+时间戳双校验(手机端APP传入) gps_lat = models.DecimalField(max_digits=10, decimal_places=8, null=True, blank=True) gps_lng = models.DecimalField(max_digits=11, decimal_places=8, null=True, blank=True) record_time = models.DateTimeField(auto_now_add=True) # 【审核流字段】状态机驱动,非布尔值 STATUS_CHOICES = ( ('pending', '待审核'), ('approved', '已通过'), ('rejected', '已驳回'), ('expired', '已过期'), ) status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='pending') # 【扩展字段】预留JSONField存AI识别原始结果(如:{"confidence": 0.92, "bbox": [x,y,w,h]}) ai_result = models.JSONField(null=True, blank=True) # 【关联字段】外键指向User,但允许匿名提交(物业要求) user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True) class Meta: ordering = ['-record_time'] verbose_name = "垃圾投放记录" verbose_name_plural = "垃圾投放记录" # 【关键技巧】为高频查询字段加数据库索引 class Meta: indexes = [ models.Index(fields=['status', 'building_code']), models.Index(fields=['record_time']), ]执行迁移前,先运行python manage.py makemigrations --name initial_waste_models生成迁移文件,再检查migrations/0001_initial.py中是否包含indexes定义——Django 4.2默认不自动生成索引,必须手动添加。
2.3 构建管理员可直操作的审核工作台(不是默认Admin)
物业主任不会写SQL,但需要看到“今天A栋湿垃圾驳回率高达47%”。我们重写Admin视图,用list_display_links和list_filter实现零代码筛选:
# waste/admin.py from django.contrib import admin from .models import WasteRecord @admin.register(WasteRecord) class WasteRecordAdmin(admin.ModelAdmin): # 【展示字段】把GPS坐标转成可读地址(调用高德API,但演示版用mock) list_display = ('id', 'category', 'building_code', 'status', 'record_time', 'get_address') list_display_links = ('id', 'category') # 点击ID或类型进入详情页 # 【筛选器】按状态+楼栋+日期范围三重过滤 list_filter = ('status', 'building_code', ('record_time', admin.DateFieldListFilter)) # 【搜索框】支持按楼栋号、用户昵称模糊搜索 search_fields = ('building_code', 'user__username', 'user__first_name') # 【动作栏】批量审核功能(物业最常用) actions = ['approve_selected', 'reject_selected'] def approve_selected(self, request, queryset): queryset.update(status='approved') approve_selected.short_description = "批量通过选中记录" def reject_selected(self, request, queryset): queryset.update(status='rejected') reject_selected.short_description = "批量驳回选中记录" # 【地址转换】演示用mock,实际替换为高德API调用 def get_address(self, obj): if obj.gps_lat and obj.gps_lng: return f"{obj.gps_lat:.6f},{obj.gps_lng:.6f}" return "未定位" get_address.short_description = "定位坐标"重启服务后,访问/admin/waste/wasterecord/,你会看到带筛选器、搜索框、批量操作按钮的完整工作台——这才是答辩时老师愿意多看两眼的界面。
3. 让AI识别模块不拖慢后台:用ONNX Runtime部署轻量级ResNet18分类模型
3.1 为什么不用TensorFlow Serving?——毕设部署的现实约束
很多同学查资料说“TensorFlow Serving适合生产”,但实际在毕设场景下:① 需要额外装Docker和TF Serving二进制包,学生Linux命令不熟极易失败;② 模型版本管理对毕设无意义;③ HTTP gRPC协议调试复杂。而ONNX Runtime只需pip install onnxruntime,加载.onnx模型文件,CPU推理速度比原生PyTorch快1.8倍(实测ResNet18 on Intel i5-8250U)。
我们选用Kaggle公开的 Garbage Classification Dataset (15000张图,4类),用PyTorch训练后导出ONNX:
# train_model.py(训练脚本关键段) import torch import torchvision.models as models # 加载预训练ResNet18并微调 model = models.resnet18(pretrained=True) model.fc = torch.nn.Linear(model.fc.in_features, 4) # 四分类 # 导出ONNX(关键参数:opset_version=12兼容性最好) dummy_input = torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, "waste_classifier.onnx", opset_version=12, input_names=['input'], output_names=['output'], dynamic_axes={'input': {0: 'batch_size'}, 'output': {0: 'batch_size'}} ) # 验证ONNX模型(必做!否则部署后报错找不到output) import onnxruntime as ort ort_session = ort.InferenceSession("waste_classifier.onnx") outputs = ort_session.run(None, {'input': dummy_input.numpy()}) print(f"ONNX输出shape: {outputs[0].shape}") # 应为 (1, 4)生成的waste_classifier.onnx(约45MB)放入项目根目录models/文件夹,不要放git仓库(大文件污染历史),在答辩演示时单独提供。
3.2 在Django视图中安全调用ONNX模型(避免请求阻塞)
直接在View里ort.InferenceSession()会导致每次请求都重载模型,CPU飙升。必须用Django信号或AppConfig初始化:
# waste/apps.py from django.apps import AppConfig import onnxruntime as ort class WasteConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'waste' # 【关键】应用启动时加载模型到内存 def ready(self): from waste import model_loader model_loader.load_onnx_model() # waste/model_loader.py import os import onnxruntime as ort from django.conf import settings ORT_SESSION = None def load_onnx_model(): global ORT_SESSION model_path = os.path.join(settings.BASE_DIR, 'models', 'waste_classifier.onnx') if not os.path.exists(model_path): raise FileNotFoundError(f"ONNX模型未找到:{model_path}") # 使用CPU执行提供者(避免GPU依赖) ORT_SESSION = ort.InferenceSession(model_path, providers=['CPUExecutionProvider']) print(f"✅ ONNX模型已加载:{ORT_SESSION.get_inputs()[0].shape}") def predict_image(image_array): """输入:(H,W,C) numpy数组,输出:类别名和置信度""" if ORT_SESSION is None: raise RuntimeError("ONNX模型未加载,请检查apps.py") # 图像预处理(同训练时的transform) import cv2 import numpy as np img = cv2.resize(image_array, (224, 224)) img = img.astype(np.float32) / 255.0 img = np.transpose(img, (2, 0, 1)) # HWC → CHW img = np.expand_dims(img, axis=0) # add batch dim # 执行推理 outputs = ORT_SESSION.run(None, {'input': img}) probs = softmax(outputs[0][0]) # 自定义softmax函数 class_idx = np.argmax(probs) classes = ['kitchen', 'recyclable', 'hazardous', 'other'] return classes[class_idx], float(probs[class_idx]) def softmax(x): e_x = np.exp(x - np.max(x)) return e_x / e_x.sum()3.3 实现上传图片→AI识别→更新记录的原子操作(带超时保护)
# waste/views.py from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.core.files.storage import default_storage from PIL import Image import numpy as np import io from .model_loader import predict_image @csrf_exempt def upload_and_classify(request): if request.method != 'POST': return JsonResponse({'error': '仅支持POST'}, status=405) try: # 【文件校验】限制大小和格式 if 'image' not in request.FILES: return JsonResponse({'error': '缺少image字段'}, status=400) image_file = request.FILES['image'] if image_file.size > 5 * 1024 * 1024: # 5MB return JsonResponse({'error': '图片不能超过5MB'}, status=400) # 【读取图像】PIL转numpy img = Image.open(image_file) img_array = np.array(img.convert('RGB')) # 【AI识别】带超时(防止模型卡死) import signal class TimeoutError(Exception): pass def timeout_handler(signum, frame): raise TimeoutError("AI识别超时(10秒)") signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(10) # 设置10秒超时 try: category, confidence = predict_image(img_array) signal.alarm(0) # 取消定时器 except TimeoutError as e: return JsonResponse({'error': str(e)}, status=500) # 【保存记录】关联到当前登录用户 from .models import WasteRecord record = WasteRecord.objects.create( category=category, building_code=request.POST.get('building_code', 'UNKNOWN'), status='pending', user=request.user if request.user.is_authenticated else None, ai_result={'category': category, 'confidence': confidence} ) return JsonResponse({ 'success': True, 'record_id': record.id, 'category': category, 'confidence': round(confidence, 3) }) except Exception as e: return JsonResponse({'error': f'处理失败:{str(e)}'}, status=500)前端调用示例(HTML+JS):
<!-- templates/upload.html --> <form id="uploadForm"> <input type="file" name="image" accept="image/*" required> <input type="text" name="building_code" placeholder="请输入楼栋号(如:A-03-1201)" required> <button type="submit">上传识别</button> </form> <script> document.getElementById('uploadForm').onsubmit = async function(e) { e.preventDefault(); const formData = new FormData(this); const res = await fetch('/api/upload/', { method: 'POST', body: formData, headers: {'X-CSRFToken': getCookie('csrftoken')} // Django CSRF保护 }); const data = await res.json(); alert(`识别结果:${data.category}(置信度${data.confidence})`); }; </script>4. 避坑指南:答辩前72小时必须排查的5个致命问题
4.1 现象:python manage.py runserver在老师电脑上报ModuleNotFoundError: No module named 'PIL'
原因:Pillow安装依赖系统级libjpeg、libpng库,Windows用户用pip install pillow常因编译失败而静默降级到纯Python版本(功能缺失)。
解决:
① 卸载现有Pillow:pip uninstall pillow
② 下载预编译wheel:访问 pypi.org/project/Pillow/#files ,下载对应Python版本和系统架构的.whl文件(如Pillow-9.5.0-cp310-cp310-win_amd64.whl)
③ 本地安装:pip install Pillow-9.5.0-cp310-cp310-win_amd64.whl
④ 验证:python -c "from PIL import Image; print('OK')"
4.2 现象:Admin后台上传图片后,WasteRecord.ai_result字段存的是{"category": "kitchen", "confidence": 0.92},但数据库里显示null
原因:Django默认JSONField在SQLite中不支持,需显式指定encoder和decoder,且SQLite版本需≥3.38(Django 4.2要求)。
解决:
① 检查SQLite版本:python -c "import sqlite3; print(sqlite3.sqlite_version)"(需≥3.38)
② 在models.py中为JSONField添加encoder:
import json from django.db import models class WasteRecord(models.Model): # ... 其他字段 ai_result = models.JSONField( null=True, blank=True, encoder=json.JSONEncoder, # 显式指定编码器 decoder=json.JSONDecoder # 显式指定解码器 )③ 删除旧迁移文件,重新makemigrations
4.3 现象:部署到阿里云轻量服务器后,/static/路径下的CSS/JS全部404
原因:Django在DEBUG=False时不自动提供静态文件,必须用collectstatic收集并配置Web服务器(Nginx/Apache)代理。
解决:
① 运行收集命令:python manage.py collectstatic --noinput(生成/staticfiles/目录)
② 修改nginx.conf,添加静态文件location:
location /static/ { alias /home/www/waste_system/staticfiles/; expires 1y; add_header Cache-Control "public, immutable"; }③ 重启Nginx:sudo systemctl restart nginx
4.4 现象:批量导入327户居民Excel时,building_code字段出现A-03-1201\x00(末尾带空字符)
原因:Excel导出时单元格格式为“文本”,但实际存储含不可见Unicode控制字符(如\x00)。
解决:
① 在views.py导入函数中清洗数据:
def clean_building_code(code): return str(code).strip().replace('\x00', '').replace('\u200b', '') # 清除零宽空格 # 导入逻辑中调用 for row in excel_data: building_code = clean_building_code(row['楼栋号']) # ... 创建用户② 数据库字段加db_collation='utf8mb4_unicode_ci'(MySQL)或使用CharField的validators:
from django.core.validators import RegexValidator building_code = models.CharField( max_length=10, validators=[RegexValidator(r'^[A-Za-z0-9\-]+$', '楼栋号只能含字母、数字、短横线')] )4.5 现象:定时任务python manage.py clear_expired_records执行后,数据库连接数暴涨至100+
原因:Django默认数据库连接不自动关闭,循环删除时每条delete()都新建连接。
解决:
① 改用原生SQL批量删除(避免ORM开销):
# management/commands/clear_expired_records.py from django.core.management.base import BaseCommand from django.db import connection class Command(BaseCommand): def handle(self, *args, **options): with connection.cursor() as cursor: cursor.execute(""" DELETE FROM waste_wasterecord WHERE status = 'pending' AND record_time < NOW() - INTERVAL 7 DAY """) self.stdout.write('✅ 已清理7天前待审核记录')② 在settings.py中设置连接池:
DATABASES = { 'default': { # ... 其他配置 'CONN_MAX_AGE': 60, # 连接复用60秒 'OPTIONS': { 'MAX_CONNS': 20, # 最大连接数 'MIN_CONNS': 5, # 最小连接数 } } }5. 答辩演示的终极技巧:用3个真实数据集制造“可交互的说服力”
5.1 准备三组演示数据,覆盖老师最可能问的场景
别再用python manage.py loaddata fixtures.json那种静态数据。答辩时老师会现场提问:“如果用户拍了张模糊的塑料袋,系统怎么判断?”——你得立刻切到对应数据集演示。我让学生准备以下三组:
| 数据集名称 | 文件位置 | 核心价值 | 演示话术 |
|---|---|---|---|
demo_clear.jpg | /media/demo/ | 高清标准图 | “这是规范拍摄的厨余垃圾,AI识别置信度92%,系统自动标记为‘厨余垃圾’并计入积分” |
demo_blur.jpg | /media/demo/ | 模糊+反光图 | “这是保洁员在雨天拍摄的湿垃圾,模型仍给出78%置信度,但状态设为‘待人工审核’,避免误判” |
demo_mixed.jpg | /media/demo/ | 混投图(塑料袋+菜叶) | “这张图被识别为‘其他垃圾’,但AI结果中confidence仅0.41,系统触发预警:‘疑似混投,请人工复核’” |
提示:这三张图必须提前用
python manage.py shell插入数据库,并确保ai_result字段含{"confidence": 0.78}等真实值,否则演示时点开详情页是空的。
5.2 在Admin后台嵌入实时统计卡片(不用ECharts,用Django自带聚合)
老师关心“系统有没有用”,而不是“代码写了多少行”。我们在Admin首页加一个实时统计区:
# waste/admin.py from django.contrib.admin import AdminSite from django.db.models import Count, Sum from .models import WasteRecord class WasteAdminSite(AdminSite): site_header = '垃圾分类管理系统后台' admin_site = WasteAdminSite(name='waste_admin') # 自定义Admin首页视图 from django.shortcuts import render from django.contrib.admin.views.main import ChangeList from django.contrib.admin.options import ModelAdmin @admin_site.admin_view def admin_index(request): # 【实时统计】用Django ORM聚合,避免写原生SQL stats = WasteRecord.objects.aggregate( total=Count('id'), approved=Count('id', filter=models.Q(status='approved')), pending=Count('id', filter=models.Q(status='pending')), kitchen=Count('id', filter=models.Q(category='kitchen')), recyclable=Count('id', filter=models.Q(category='recyclable')), ) # 【Top5楼栋】按投放量排序 top_buildings = WasteRecord.objects.values('building_code').annotate( count=Count('id') ).order_by('-count')[:5] context = { 'stats': stats, 'top_buildings': top_buildings, 'site_title': '垃圾分类管理系统', } return render(request, 'admin/index.html', context)创建templates/admin/index.html继承Django Admin模板:
{% extends "admin/base_site.html" %} {% block content %} <div class="row"> <div class="col-md-3"><h3>今日总投放</h3><h1>{{ stats.total }}</h1></div> <div class="col-md-3"><h3>已通过</h3><h1>{{ stats.approved }}</h1></div> <div class="col-md-3"><h3>待审核</h3><h1>{{ stats.pending }}</h1></div> <div class="col-md-3"><h3>厨余占比</h3><h1>{{ stats.kitchen|add:"0"|div:"stats.total"|floatformat:1 }}%</h1></div> </div> <h4>投放量Top5楼栋</h4> <table class="table"> <thead><tr><th>楼栋</th><th>次数</th></tr></thead> <tbody> {% for b in top_buildings %} <tr><td>{{ b.building_code }}</td><td>{{ b.count }}</td></tr> {% endfor %} </tbody> </table> {% endblock %}5.3 给老师留一个“可验证的后门”:用Django Shell快速验证任意逻辑
答辩时老师说:“你这个积分规则,我输入A-03-1201,应该得多少分?”——别慌,打开终端执行:
# 进入Django Shell(答辩前确保已部署) python manage.py shell # 快速验证积分计算逻辑(假设规则:厨余垃圾1分,可回收2分,其他0分) >>> from waste.models import WasteRecord >>> from django.contrib.auth.models import User >>> user = User.objects.get(username='teacher_demo') >>> record = WasteRecord.objects.create( ... category='kitchen', ... building_code='A-03-1201', ... user=user, ... status='approved' ... ) >>> # 查看积分(假设积分存在UserProfile模型中) >>> user.profile.points 1 >>> # 再录一条可回收 >>> record2 = WasteRecord.objects.create( ... category='recyclable', ... building_code='A-03-1201', ... user=user, ... status='approved' ... ) >>> user.profile.points 3这个操作全程<30秒,比翻代码找views.py里的积分函数直观十倍。我坚持让学生在答辩PPT最后一页放一行小字:“所有逻辑均可通过Django Shell实时验证”,老师眼睛会亮。
6. 我的血泪经验:毕设答辩不是考编程,是考“系统思维”的落地证据
最后一次调试是在答辩前夜凌晨两点。学生突然发现:当物业主任用IE11打开Admin后台时,批量审核按钮点击无效。查了一圈,发现是admin/js/actions.js里用了ES6的箭头函数,IE11不支持。解决方案不是升级浏览器(物业不可能换),而是用Babel编译Django Admin的JS——但毕设没时间搞构建工具。我的妥协方案是:在base_site.html模板里加一段polyfill:
<!-- templates/admin/base_site.html --> {% load static %} <script src="{% static 'admin/js/core.js' %}"></script> <!-- IE11兼容补丁 --> <script> if (!window.Promise) { document.write('<script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.auto.min.js"><\/script>'); } if (!Array.prototype.find) { Array.prototype.find = function(predicate) { for (let i = 0; i < this.length; i++) { if (predicate(this[i])) return this[i]; } return undefined; }; } </script>这件事让我彻底明白:所谓“高分毕业设计”,根本不是代码多炫酷,而是你是否预判了真实环境里的每一个不完美——IE11、旧安卓机、物业主任的Excel水平、老师电脑上缺失的Visual C++ Redistributable、甚至答辩教室Wi-Fi断连时的离线缓存方案。我后来给所有学生定下铁律:代码写完后,必须用三台不同配置的电脑(Win10+Chrome、Win7+IE11、Ubuntu+Firefox)各跑一遍全流程,截图存档。不是为了应付检查,而是当你站在答辩台上,老师问“如果……”时,你能平静地说:“这个问题我们测过,这里是截图”。
希望帮到你。
本文还有配套的精品资源,点击获取