十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Django+MySQL实现校园闲置物品换购平台实战解析

Django+MySQL实现校园闲置物品换购平台实战解析 简介一份计算机类毕业论文docx文档适合正在设计基于PythonDjango的校园闲置物品换购平台、或需要快速梳理同类型Web系统论文结构的学生参考。文档针对传统校园闲置物品换购平台手工管理存在的人员成本高、随意管理、信息处理速度慢等问题提出了智能化管理方案并介绍了Python开发技术与MySQL数据库技术结合的优势。系统功能部分细化到主页、个人中心、用户管理、景点信息管理、系统管理等模块涵盖新用户注册、信息审核、闲置物品发布、权限分配等具体业务场景绪论中还包括研究意义、设计目的、设计思想及经济、技术、社会三方面可行性分析需求分析则明确了系统功能与操作流程。压缩包内共1个文件为docx格式完整论文文档大小约4.49MB包含中英文摘要、目录及各章节正文内容完整可直接用于论文写作参考。资源已有112人浏览学习可作为毕业设计选题论证、系统模块划分、论文框架搭建和答辩准备的一站式参考资料帮助快速理解项目脉络与实现要点。1. 一个校园个人闲置物品换购平台为什么值得用 Django 重写大学里最活跃的闲置交易发生在宿舍群和二手群但信息散落在聊天记录里人工登记商品、价格和交易状态又慢又容易漏。把校园个人闲置物品换购平台从“临时接需求”推进到可部署状态我用的技术栈是 Python Django MySQLDjango 自带 ORM、认证、Session 和 AdminMTV 模式让主页、个人中心、用户管理、景点信息管理这些模块边界清楚MySQL 负责把闲置物品、订单、置换账户和线下交易地点持久化。这套组合非常适合课程设计和毕业设计不依赖外部搜索或消息中间件也方便后续扩展通知和搜索。下面按数据模型、交易流程、MySQL 优化和上线部署的顺序拆开讲。2. MTV 拆分与数据模型先设计用户、闲置物品、线下交易点三张核心表2.1 Django 的 MTV 模式在这个场景里怎么拆Django 的 MTV 对应 Model、Template、View简单理解是 Model 负责 MySQL 表映射Template 负责 HTML 页面View 负责业务逻辑。和 MVC 习惯不一样的是URLconf 从 View 中独立出来路由、视图、模板三层可以各自替换所以在做管理员模块、前台用户模块、置换账户后台模块时URL 前缀可以分得很清楚例如admin/走 Django Adminprofile/走个人中心spot/走线下交易地点管理。这个换购平台真正需要反复改的是商品状态和订单状态。如果把这些状态逻辑混在模板里页面会散落大量{% if %}判断后面加一个“已锁定”状态就要改十几个模板。我用 View 层统一维护状态迁移模板只读状态字段。这样 MTV 各层的职责是Model 管字段约束View 管状态和权限Template 管展示。Django 的 Form 和 Admin 还能直接复用 Model 定义减少重复代码。2.2 用 django-admin 创建项目与 app并接入 MySQL我一般先把项目拆成 three 个 appaccounts 处理用户与置换账户goods 处理闲置物品和线下交易点trade 处理订单。新建命令如下django-admin startproject campus_exchange cd campus_exchange python manage.py startapp accounts python manage.py startapp goods python manage.py startapp trade执行完startapp后第一件事是把三个 app 写进INSTALLED_APPS否则后面makemigrations不会扫描这些目录。Django 创建 app 不等于自动注册这一步丢了会在迁移时报出No installed app with label新手常在这里卡住。数据库切换到 MySQL 的配置在settings.py里这样写DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: campus_exchange, USER: root, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { charset: utf8mb4, }, } }ENGINE指定 MySQL 驱动实际需要先装mysqlclient或用pymysql做兼容层否则启动时直接抛ModuleNotFoundError。OPTIONS里的utf8mb4一定要保留因为用户可能在商品标题里写 emoji普通utf8字符集存不下四个字节的字符写入会报Incorrect string value。建库语句建议显式指定排序规则CREATE DATABASE campus_exchange DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;HOST填写127.0.0.1表示连接本机 MySQL如果数据库和 Web 服务部署在不同机器改成内网 IP 而不是公网 IP避免数据库端口直接暴露。2.3 核心 models.py 怎么写换购平台的数据模型围绕“谁在卖、卖什么、在哪里交易、谁买了”展开。我自定义了User模型而不是直接使用默认的auth.User因为需要给用户追加学号、身份角色和置换账户的关联关系。示例代码如下from django.db import models from django.contrib.auth.models import AbstractUser class User(AbstractUser): identity models.SmallIntegerField( choices((0, 管理员), (1, 普通用户), (2, 置换账户)), default1 ) student_id models.CharField(max_length20, blankTrue) phone models.CharField(max_length11, blankTrue) class Meta: db_table userAbstractUser已经带了username、password、email、is_active等字段我只需要追加业务字段identity用来区分后台管理员、前台用户和置换账户比单独建三张用户表更容易维护。db_table user是给表名加一层控制避免 Django 默认按 app 前缀生成accounts_user多人协作时看 SQL 更直观。闲置物品和线下交易地点这样建模class CampusSpot(models.Model): name models.CharField(max_length50) location models.CharField(max_length100) is_active models.BooleanField(defaultTrue) class Meta: db_table campus_spot class IdleItem(models.Model): owner models.ForeignKey(User, on_deletemodels.CASCADE, related_nameitems) title models.CharField(max_length64) description models.TextField() price models.DecimalField(max_digits8, decimal_places2) original_price models.DecimalField(max_digits8, decimal_places2) condition models.CharField( max_length10, choices((10, 全新), (9, 99新), (8, 轻微使用痕迹)), default9 ) spot models.ForeignKey(CampusSpot, on_deletemodels.SET_NULL, nullTrue, blankTrue) status models.CharField( max_length10, choices((on_sale, 在售), (reserved, 已锁定), (sold, 已成交), (off, 下架)), defaulton_sale ) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table idle_itemon_deletemodels.CASCADE放在owner上用户注销时他的商品可以一并清掉spot用SET_NULL因为线下交易点可能被管理员删除商品不能跟着删除。状态字段status我用字符串而不是布尔值因为“在售、锁定、成交、下架”不是简单的上下架关系后续做列表过滤也方便。订单和置换账户模型如下class Order(models.Model): item models.OneToOneField(IdleItem, on_deletemodels.PROTECT) buyer models.ForeignKey(User, on_deletemodels.PROTECT, related_namebuy_orders) seller models.ForeignKey(User, on_deletemodels.PROTECT, related_namesell_orders) price models.DecimalField(max_digits8, decimal_places2) status models.CharField( max_length10, choices((pending, 待成交), (done, 已完成), (canceled, 已取消)), defaultpending ) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table order class ExchangeAccount(models.Model): user models.OneToOneField(User, on_deletemodels.CASCADE) balance models.DecimalField(max_digits10, decimal_places2, default0) locked models.DecimalField(max_digits10, decimal_places2, default0) class Meta: db_table exchange_account注意Order.item用了OneToOneField这是防止同一件商品被生成多条订单。PROTECT意味着有订单关联时商品不能物理删除只能通过状态改成off下架这是我在这个项目里特意保留的硬约束。模型写完后执行python manage.py makemigrations accounts goods trade python manage.py migrate2.4 数据表关系速查表名关键字段作用userusername, identity, student_id用户认证与角色区分campus_spotname, location, is_active线下交易地点/景点信息管理idle_itemowner, title, price, status, spot闲置物品发布与状态流转orderitem, buyer, seller, status交易订单记录成交价和买卖双方exchange_accountuser, balance, locked置换账户余额与冻结金额如果做的是简化版ExchangeAccount可以先不接入订单流程只作为个人中心的一个余额展示字段。但模型里保留它后面要加置换积分结算时就不用重新迁移用户表。原文中的“景点信息管理”在换购平台里通常落地为CampusSpot管理员维护校内广场、图书馆门口、宿舍楼下等定点交付位置发布商品时从下拉框里选一个线下交接时双方不用反复沟通见面地点。3. 登录、发布、下单把换购流程在 Django 视图中跑通3.1 URLconf 与视图的组织方式路由我放在 goods 和 trade 两个 app 里主路由用include挂载避免所有 URL 挤在项目根目录。关键路由如下from django.urls import path from goods import views urlpatterns [ path(, views.home, namehome), path(register/, views.register, nameregister), path(profile/, views.profile, nameprofile), path(item/create/, views.item_create, nameitem_create), path(item/int:pk/, views.item_detail, nameitem_detail), path(item/int:pk/delete/, views.item_delete, nameitem_delete), path(item/int:pk/order/, views.create_order, namecreate_order), ]int:pk是 Django 2.0 之后推荐的路由写法替代过去(?Ppk[0-9])正则。int转换器会把请求地址里的参数自动转成 Python 整数如果传字符串进视图再int()转换遇到非法参数只能靠异常兜底。主页和商品详情是公开页面发布、删除、下单都需要login_required这在视图函数上加装饰器即可。3.2 注册、登录与个人中心注册逻辑不复杂但要注意两点密码必须用内置create_user不能直接User.objects.create否则密码是明文注册成功后调用login(request, user)避免用户刚注册完还要再登录一次。示例from django.shortcuts import render, redirect from django.contrib.auth import login from django.contrib.auth.decorators import login_required def register(request): if request.method POST: username request.POST.get(username) password request.POST.get(password) if not username or not password: return render(request, accounts/register.html, {error: 用户名和密码不能为空}) user User.objects.create_user(usernameusername, passwordpassword) login(request, user) return redirect(home) return render(request, accounts/register.html)这里没有做验证码和手机号校验因为校园内网环境并发不大。如果对外部署需要加上 Django reCAPTCHA 或短信验证避免批量注册污染用户管理模块。个人中心展示当前用户发布的商品login_required def profile(request): items request.user.items.all().order_by(-created_at) return render(request, accounts/profile.html, {items: items})request.user.items能直接使用是因为IdleItem.owner的related_nameitems。没有这个反向关联名就要写成IdleItem.objects.filter(ownerrequest.user)代码可读性差很多。3.3 闲置物品发布、查询与下架发布商品时我建议直接接收表单字段并构造IdleItem实例而不是依赖ModelForm的自动校验。原因很简单spot_id可能需要从下拉框拿到一个整数ModelForm 在关联外键为空时会直接报Select a valid choice处理起来不够直观。发布视图如下login_required def item_create(request): if request.method POST: item IdleItem( ownerrequest.user, titlerequest.POST.get(title), descriptionrequest.POST.get(description), pricerequest.POST.get(price), original_pricerequest.POST.get(original_price), conditionrequest.POST.get(condition), spot_idrequest.POST.get(spot_id) or None, ) item.save() return redirect(profile) return render(request, goods/item_form.html, { spots: CampusSpot.objects.filter(is_activeTrue) })spot_idrequest.POST.get(spot_id) or None是一个常用技巧因为 HTML 下拉框可能没有选中任何地点提交的是空字符串外键字段不能存空字符串必须转成None。item.save()在 insert 阶段会再走一次字段校验比如 price 无法转换时抛出ValidationError所以生产环境还是建议配合 Form 校验。主页查询和删除对象是查询高频点def home(request): keyword request.GET.get(kw, ) items IdleItem.objects.filter(statuson_sale) if keyword: items items.filter(title__icontainskeyword) items items.select_related(owner, spot).order_by(-created_at) return render(request, goods/home.html, {items: items}) login_required def item_delete(request, pk): deleted_count, _ IdleItem.objects.filter(ownerrequest.user, pkpk).delete() if deleted_count 0: return redirect(home) return redirect(profile)title__icontainskeyword会生成WHERE title LIKE %keyword%适合关键词搜索但数据量大时性能一般校园场景完全够用。删除对象时Django 的 QuerySet 是惰性的filter(...).delete()这一步才真正执行 DELETE SQL。关键点是filter必须带ownerrequest.user否则用户只要知道商品 ID 就能删别人的商品这是越权漏洞。delete()返回的二元组第一个值是总共删除的记录数包括外键级联删除的关联记录我用它判断是否真的删除了内容。如果不希望订单关联被破坏物理删除不是首选。常见做法是把status改为off页面列表过滤掉下架商品后台仍可查看记录。3.4 下单的并发保护select_for_update 与事务同时有两个人看中同一件商品时普通逻辑会出现“下单成功但商品早就没了”的问题。我在下单视图里用事务加行锁from django.db import transaction login_required def create_order(request, pk): if request.method POST: with transaction.atomic(): item ( IdleItem.objects .select_for_update() .filter(pkpk, statuson_sale) .first() ) if item is None: return redirect(home) item.status reserved item.save(update_fields[status]) Order.objects.create( itemitem, buyerrequest.user, selleritem.owner, priceitem.price, ) return redirect(profile) return render(request, goods/order_confirm.html, { item: get_object_or_404(IdleItem, pkpk, statuson_sale) })transaction.atomic()确保锁住商品、改状态、建订单这三步要么全部成功要么全部回滚。select_for_update()在 MySQL InnoDB 下会对命中的行加写锁第二个请求进入事务后会等第一个事务提交然后重新读取此时status已经不是on_salefirst()返回None直接跳走。save(update_fields[status])只更新指定列避免把created_at等字段也写一遍减少 MySQL 的 binlog 体积。商品状态和订单状态汇总如下商品 status前端表现允许操作on_sale展示在主页可下单、可下架reserved展示为已锁定不可下单可取消锁定sold展示为已成交不可操作off前台不展示后台可恢复为 on_sale这里的reserved不是最终成交状态线下交付完成后管理员或卖家再把订单标记为完成商品同步成sold。这样做的好处是避免用户拍了不付钱商品直接进入成交状态后无法再卖。4. MySQL 下的存储、查询优化与管理后台4.1 连接 MySQL 与 DDL 迁移模型定义完成后迁移顺序很重要。如果先启动 Django 再makemigrations数据库里还没有对应表所有读数都会报Table idle_item doesnt exist。正确顺序是python manage.py makemigrations accounts goods trade python manage.py migrate python manage.py createsuperuser mysql -uroot -p campus_exchange -e SHOW TABLES;sqlmigrate可以查看 Django 生成的 DDL用来确认索引和外键有没有按预期创建python manage.py sqlmigrate goods 0001Django 的 ORM 不是把所有 SQL 隐藏掉而是把 DDL 版本化。团队合作时如果有人直接改数据库表结构migrate --fake不一定会对齐模型反而会留下脏数据所以我的习惯是任何表结构变更都走makemigrations。4.2 高频查询与索引优化详情页和首页列表最常查的是statuson_sale的在售商品并且需要展示卖家用户名、商品分类、线下交易点名称。最直观的 ORM 写法是模板里逐个访问item.owner.username这会产生 N1 查询。我一般用select_related一次 JOIN 查出外键对象items ( IdleItem.objects .filter(statuson_sale) .select_related(owner, spot) .only(id, title, price, condition, owner__username, spot__name) .order_by(-created_at)[:20] )select_related对ForeignKey和OneToOneField生效通过 LEFT JOIN 把关联表的数据一次取回。only限制查询字段减少 MySQL 回表但要小心如果后续模板里访问item.descriptionDjango 还会再执行一次补查这种补查发生在查询集算完缓存之后不容易发现。可以用print(items.query)查看最终 SQL确认 JOIN 有没有生效。索引设计上我通常直接在模型 Meta 里声明组合索引class Meta: db_table idle_item indexes [ models.Index(fields[status, -created_at], nameidx_status_time), models.Index(fields[owner, status], nameidx_owner_status), ]第一个索引status created_at覆盖首页“筛选在售商品并按发布时间排序”的场景因为等值条件status放前面排序字段created_at紧随其后MySQL 可以通过索引完成排序不需要 filesort。第二个索引覆盖个人中心“查某个用户发布的在售商品”。改了 Meta 后需要再次makemigrations生成索引迁移否则索引只在代码里而不在 MySQL 中。title__icontains这类模糊查询不会走索引但只要校园平台商品量在几万条以内完全可以接受。真到了十万条起步再考虑 MySQL FULLTEXT 或接入外部搜索现阶段不要为这种低频功能引入复杂组件。4.3 管理员后台Django Admin 与自定义管理系统管理模块不一定要重新写一套管理页面Django Admin 是现成的后台注册模型后就能完成用户管理、景点信息管理和订单管理。示例from django.contrib import admin from .models import User, CampusSpot, IdleItem, Order, ExchangeAccount admin.register(IdleItem) class IdleItemAdmin(admin.ModelAdmin): list_display (id, title, owner, price, status, created_at) search_fields (title, owner__username) list_filter (status, category) list_editable (status,) admin.register(Order) class OrderAdmin(admin.ModelAdmin): list_display (id, item, buyer, seller, price, status, created_at) actions [mark_done] admin.action(description标记为已完成) def mark_done(self, request, queryset): queryset.update(statusdone)list_editable可以在列表页直接改商品状态适合管理员快速下架违规商品。actions定义的是一个批量操作勾选多条订单后一键完成。注意list_editable的字段不能出现在list_display第一列因为第一列被当作链接列同时可编辑会导致 URL 解析冲突。系统管理还需要在settings.py里做几项收尾配置项推荐值原因DEBUGFalse关闭后不泄露异常堆栈ALLOWED_HOSTS域名或IP列表空列表会让 Django 拒绝所有请求TIME_ZONEAsia/Shanghai订单时间与校园本地时间一致LANGUAGE_CODEzh-hansAdmin 后台显示中文MEDIA_ROOT实际路径用户上传的闲置物品图片存储目录5. 上线前处理waitress nginx 部署与静态文件排错5.1 waitress 启动 Django开发时python manage.py runserver会在代码修改后自动重载但不能用于长期跑因为它是单线程开发服务器并发稍高就会排队。我偏好用 waitress 部署小项目它是纯 Python 的 WSGI 服务器Windows 和 Linux 都能直接运行不用编译 uWSGIpip install waitress waitress-serve --listen0.0.0.0:8000 campus_exchange.wsgi:applicationcampus_exchange.wsgi:application 是项目根目录里的 WSGI 入口--listen指定监听本机 8000 端口。如果局域网内多人同时访问可以加--threads8提高并发处理能力。waitress 不支持 HTTP/2但对校园系统来说已经足够。5.2 nginx 反向代理与静态文件waitress 只负责处理 Django 动态请求静态文件和图片上传应该交给 nginx。反向代理配置如下server { listen 80; server_name campus-exchange.example.com; client_max_body_size 10m; location /static/ { alias /var/www/campus_exchange/static/; } location /media/ { alias /var/www/campus_exchange/media/; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }location /static/直接读磁盘不进入 Django减少了 Python 进程压力。client_max_body_size 10m限制上传图片大小避免用户传大文件拖垮服务器。5.3 常见坑与验证技巧上线后最常见的问题是 Admin 样式丢失。原因是DEBUGFalse时 Django 不再自动服务静态文件需要先设置STATIC_ROOT然后执行python manage.py collectstatic如果使用 pymysql记得在项目__init__.py里加pymysql.install_as_MySQLdb()并且只能初始化一次。迁移失败时不要直接删django_migrations表先检查是否代码模型和数据库字段不一致再用python manage.py migrate --fake app名对齐版本。后台登录提示密码错误可以用python manage.py changepassword 用户名重置确认自定义 User 模型没有覆盖默认认证后端。验证查询次数时我常用的一个方法是CaptureQueriesContextfrom django.db import connection from django.test.utils import CaptureQueriesContext def check_queries(): with CaptureQueriesContext(connection) as ctx: items list(IdleItem.objects.select_related(owner, spot)) for item in items: _ item.owner.username print(fSQL条数: {len(ctx.captured_queries)})在python manage.py shell里执行这段代码如果 SQL 条数和商品数量接近说明select_related没有覆盖某个外键先检查item.spot是否为 NULL外键为空时 Django 依然会单独发一次查询这时通常在查询前加filter(spot__isnullFalse)再对比往往能发现被忽略的补查。本文还有配套的精品资源点击获取
返回列表