ARTICLE DETAIL

资讯详情

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

Django实战:校园闲置物品换购平台设计与实现

Django实战:校园闲置物品换购平台设计与实现 每年到毕设季总有人拿着“校园个人闲置物品换购平台”这个题目来问我标题里还常挂着“Django毕设源码”“Python”这些关键词。这个题目确实很聪明技术难度不高不低业务贴近大学生活演示效果又直观答辩时老师一看就懂学生也讲得清楚。今天我就把这个项目从设计到实现再到文档、代码讲解、部署上线完整拆开聊一遍。看完这篇内容你能搞清楚这个项目本质上在考什么、换购和普通二手买卖有什么区别、表结构该怎么设计、WebSocket实时推送和Token登录态怎么实现、论文和答辩怎么准备以及怎么把一套代码真正跑起来而不是收藏一堆“毕设源码”最后却跑不起来。适合选了这道题、正在纠结怎么做或者想通过项目实战把Django学扎实的朋友。1. 项目整体拆解这个“换购平台”到底在考什么1.1 题目背后的真实考点很多人看到“闲置物品换购平台”第一反应是“这不就是个二手CRUD吗加个发布列表就行”。真这么做答辩多半会被问住。这道题看着简单但老师真正想看的是你能否把“换购”这个业务理解清楚并落实到数据库设计和代码实现里。先说业务层面的考点。换购和普通买卖有本质区别普通商城是单方向的卖家定价、买家付款、系统发货换购是双向的两个用户之间可能“物物交换”可能“物品补差价”还可能有协商过程。这意味着你的系统不能只做商品表和订单表还要处理“交换意向”“协商状态”“双方确认”这些环节。技术层面的考点更直白Django自带的User认证怎么扩展成校内用户体系ORM怎么检索物品、怎么删除对象怎么给符合条件的用户实时推送通知登录态怎么通过Cookie存Token来维持这些都会在答辩时被逐条问到。所以这道题的本质是“一个带协商机制的二手交易系统”它考察的是模型设计、状态管理和实时通信而不是简单的增删改查。1.2 换购业务的逻辑和普通二手商城差别在哪我建议你在写代码前先把业务流程画一遍。我们假设用户A看中了用户B的一本教材A愿意用自己的计算器再加20元去换这个流程怎么走先拆成几个环节B发布教材状态是“可换购”A发起交换请求附上“计算器20元”的意向B收到通知进入“待处理”B可以选择同意、拒绝或协商一旦同意教材变为“已锁定”系统生成一笔记录双方线下见面交付再在系统里确认完成、互评。你看这中间有“请求”“协商”“锁定”“完成”四个状态任何一个状态处理不好业务就会卡死。这也是为什么我总跟人说做这个题目先别急着敲代码。你把这条流程走一遍表格自然就出来了需要用户表、物品表、分类表、交换请求表、订单表、通知表可能还需要积分表和评价表。表之间的关系也清楚了一个用户有多件物品一条交换请求关联两件物品一笔订单对应一条被同意的请求一条通知指向某个用户和某个对象。1.3 技术选型为什么是 Python Django 而不是 Spring Boot选Django做这个题目的人很多但你要能说清楚“为什么”。不是因为它流行而是因为它真的匹配这道题的需求。对比一下主流方案Flask确实轻但用户认证、后台管理、ORM、CSRF防护都要自己搭毕设周期根本不够Spring Boot功能强但对Python基础的学生来说学习成本高写起来也重。Django是“全家桶”式的自带Admin后台、ORM、认证体系和表单校验天生适合这类信息系统题目。你用Django就能从注册登录做到后台管理代码量比Flask少一半比Spring Boot也少不少。另外Django生态里还有几个加分项django-rest-framework可以快速把接口做成API风格channels库能让项目支持WebSocket实时推送django-unfold等插件能美化自带的Admin后台演示的时候眼前一亮。这些都是答辩时能主动说“我还用了XX来增强”的素材。Python本身语法直观、调试方便对你快速理解系统运行逻辑很有帮助。2. 数据模型与核心功能设计表结构决定项目上限2.1 扩展用户体系从 Django User 到校内身份Django自带的User模型有用户名、密码、邮箱这些字段但大学场景里还需要学号、学院、宿舍楼、信用分这类信息。直接改User表不推荐更稳的做法是建一个Profile表和User做一对一关联。from django.db import models from django.contrib.auth.models import User class Profile(models.Model): user models.OneToOneField(User, on_deletemodels.CASCADE, related_nameprofile) student_id models.CharField(max_length20, uniqueTrue, verbose_name学号) college models.CharField(max_length50, verbose_name学院) dormitory models.CharField(max_length50, blankTrue, verbose_name宿舍楼) credit_score models.IntegerField(default100, verbose_name信用分) avatar models.ImageField(upload_toavatars/, blankTrue) created_at models.DateTimeField(auto_now_addTrue) def __str__(self): return f{self.user.username} - {self.student_id}注册的时候先创建User再创建Profile。这里有个实操细节注册表单里尽量用UserCreationForm做二次开发而不是自己写用户名查重能少踩不少坑。信用分是这个项目的加分项你可以规定成功完成一次换购加2分被对方投诉扣10分信用分低于60分时限制发布物品。这个逻辑一加系统的完整性马上就上来了。2.2 闲置物品模型状态机是换购系统的灵魂物品表是这个系统的核心。我的建议是除了常规的标题、描述、图片、分类外一定要设计清楚“期望换入”和“是否接受补差价”。因为换购场景里用户想知道的是“对方想换什么”而不是单纯看价格。class Item(models.Model): STATUS_CHOICES [ (draft, 草稿), (on_sale, 可换购), (locked, 已锁定), (exchanged, 已换出), (off_shelf, 已下架), ] owner models.ForeignKey(User, on_deletemodels.CASCADE, related_nameitems) title models.CharField(max_length100) description models.TextField() category models.ForeignKey(Category, on_deletemodels.SET_NULL, nullTrue) original_price models.DecimalField(max_digits10, decimal_places2) condition models.CharField(max_length20, choicesCONDITION_CHOICES, defaultgood) want_exchange models.TextField(blankTrue, verbose_name期望换入的物品) accept_diff models.BooleanField(defaultFalse, verbose_name是否接受补差价) status models.CharField(max_length20, choicesSTATUS_CHOICES, defaulton_sale) created_at models.DateTimeField(auto_now_addTrue) updated_at models.DateTimeField(auto_nowTrue) is_active models.BooleanField(defaultTrue, verbose_name逻辑删除标记) class Meta: ordering [-created_at]这里的status字段就是状态机。物品发布后默认是“可换购”被发起换购请求后可以进入“已锁定”防止多人重复操作同一件物品。is_active是逻辑删除标记后面我会细说为什么不能直接物理删除。再补充一个容易忽视的点图片不要只放一个ImageField。学生发闲置往往想传多张图你可以建一个ItemImage表ForeignKey指向Item一个物品对应多张图片展示页用第一张做封面详情页轮播所有图片。这样显得专业数据库设计也更合理。2.3 换购请求与订单双向买卖怎么落库换购请求表是“双向协商”的直接体现。我见过不少人用“订单表”硬套换购结果发起方、接收方都分不清状态也理不顺。正确的做法是用一张ExchangeRequest表专门管“意向”等双方确认后再生成一笔Order。class ExchangeRequest(models.Model): STATUS_CHOICES [ (pending, 待处理), (negotiating, 协商中), (accepted, 已同意), (rejected, 已拒绝), (cancelled, 已取消), (completed, 已完成), ] initiator models.ForeignKey(User, on_deletemodels.CASCADE, related_namesent_requests) receiver models.ForeignKey(User, on_deletemodels.CASCADE, related_namereceived_requests) from_item models.ForeignKey(Item, on_deletemodels.CASCADE, related_nameoutgoing_requests) to_item models.ForeignKey(Item, on_deletemodels.CASCADE, related_nameincoming_requests) message models.TextField(blankTrue, verbose_name留言) offer_diff models.DecimalField(max_digits10, decimal_places2, default0, verbose_name愿补差价) status models.CharField(max_length20, choicesSTATUS_CHOICES, defaultpending) created_at models.DateTimeField(auto_now_addTrue) updated_at models.DateTimeField(auto_nowTrue)为什么要把from_item和to_item分开因为一件物品可以同时收到多个换购请求每条请求都对应“发起方物品”和“被请求方物品”。如果只存一个物品ID整个协商链条就断了。这里有个并发问题要提前想用户B的教材同时被A和C发起请求B先同意了AC的请求应该自动变为“已拒绝”吗实际开发中我建议在“同意请求”的操作里用事务行锁处理防止两个请求同时通过from django.db import transaction from django.db.models import F with transaction.atomic(): request ExchangeRequest.objects.select_for_update().get(pkreq_id) if request.status ! pending: return {code: 0, msg: 该请求已处理} request.status accepted request.save() to_item Item.objects.select_for_update().get(pkrequest.to_item_id) to_item.status locked to_item.save()用select_for_update锁住行就能避免“一个物品被两个人同时换走”的尴尬。这个点在答辩时主动讲出来老师会认为你有并发意识。2.4 消息通知站内信 WebSocket 实时推送的模块设计换购平台里用户最关心的是“我发布的物品有没有人想换”“我的请求有没有被同意”。如果让对方自己去刷新页面体验会很差所以需要通知模块。最简单的方案是站内信每次产生关键操作时往Notification表插一条记录用户登录后拉取未读列表。再进一步用WebSocket做实时推送让用户在不刷新页面的情况下收到通知。项目里的推荐做法是两者结合站内信保证数据可回溯WebSocket保证提醒即时性。class Notification(models.Model): TYPE_CHOICES [ (request, 收到换购请求), (accept, 请求被同意), (reject, 请求被拒绝), (order, 订单状态变更), (system, 系统通知), ] receiver models.ForeignKey(User, on_deletemodels.CASCADE, related_namenotifications) sender models.ForeignKey(User, on_deletemodels.CASCADE, nullTrue, blankTrue) ntype models.CharField(max_length20, choicesTYPE_CHOICES) content models.CharField(max_length255) link models.CharField(max_length255, blankTrue) is_read models.BooleanField(defaultFalse) created_at models.DateTimeField(auto_now_addTrue)通知模块的设计原则是只在“状态发生变化”的地方插入通知不要在用户每次浏览时触发。把“业务操作”和“通知发送”解耦开代码会清爽很多。3. 核心功能实现ORM查询、软删除、WebSocket推送与登录态3.1 Django 执行查询ORM 与原生 SQL 的取舍Django查数据库大家最常遇到的问题是“查询很慢但不明白慢在哪”。热搜词里那个“django执行查询-删除对象”其实就是这类问题的核心。我建议你把ORM的查询分成三个层次来理解。第一层是基础查询比如Item.objects.filter(statuson_sale)、Item.objects.exclude(ownerrequest.user)这些是日常主力。第二层是条件组合用Q对象做或查询比如搜索时“标题包含教材或描述里包含计算器”from django.db.models import Q results Item.objects.filter( Q(title__icontainskeyword) | Q(description__icontainskeyword), statuson_sale, is_activeTrue, )第三层是性能优化。外键关联会引发N1查询问题比如列表页显示每件物品的封面图你不做优化的话查10件物品可能触发20多条SQL。这时候用select_related和prefetch_relateditems Item.objects.filter(statuson_sale).select_related(category).prefetch_related(images)select_related用于一对一、多对一等正向关系的JOIN查询prefetch_related用于反向关联和ManyToMany的额外查询。这两个方法答辩时一定要会讲。3.2 删除对象的设计别让“删库”变成事故很多学生拿到项目后第一反应就是把“删除”功能做成物理删除。管理员在后台点一下记录就没了关联的换购请求、图片路径、通知记录全变成悬空数据。这是新手最容易踩的坑。Django的delete()方法会触发关联删除比如你删除一个用户他发布的所有物品、收到的所有通知都会被级联删掉。用户只是想下架一件物品结果整个业务链断了。我的建议是在核心业务表上都加is_active逻辑删除标记把“删除对象”从物理删除改成状态置位。实际代码可以这样重写一个QuerySet让正常查询默认只拿未被删除的记录class ItemQuerySet(models.QuerySet): def alive(self): return self.filter(is_activeTrue) def delete(self): return self.update(is_activeFalse, statusoff_shelf) class Item(models.Model): ... objects ItemQuerySet.as_manager()这样调用Item.objects.filter(...).delete()时实际执行的是逻辑删除数据还在只是对外不可见。真正需要物理清除的场景只有一种用户发布后从未被别人请求过可以彻底删掉测试数据。其余情况一律优先逻辑删除。这个设计我强烈建议你保留在论文的“系统设计”章节里老师对这一条会非常认可。3.3 WebSocket 实时推送后台一有数据前端立刻弹出热搜词里“python django websocket实现后台有数据前端推送”基本说的就是这个需求。Django原生不支持WebSocket需要引入channels库把应用从同步模式切换到支持异步的ASGI模式。先说整体链路用户A给B的物品发起换购请求后台在保存ExchangeRequest的同时通过Redis频道往B所在的WebSocket连接推一条消息B的浏览器收到后弹出一个提示框并更新未读消息数。安装依赖我建议用这几个避免版本混乱pip install channels channels-redis daphnesettings.py里要配置ASGI应用和Redis频道层INSTALLED_APPS [ daphne, django.contrib.admin, ... channels, your_app, ] ASGI_APPLICATION config.asgi.application CHANNEL_LAYERS { default: { BACKEND: channels_redis.core.RedisChannelLayer, CONFIG: { hosts: [(127.0.0.1, 6379)], }, }, }注意daphne必须放在INSTALLED_APPS最前面否则runserver时Django会走WSGIWebSocket请求会直接400。接下来是WebSocket的消费端一般叫consumers.py。我做一个精简版按用户ID分组推送import json from channels.generic.websocket import AsyncWebsocketConsumer class NotificationConsumer(AsyncWebsocketConsumer): async def connect(self): self.user self.scope[user] if self.user.is_anonymous: await self.close() else: self.group_name fuser_{self.user.id} await self.channel_layer.group_add(self.group_name, self.channel_name) await self.accept() async def disconnect(self, close_code): await self.channel_layer.group_discard(self.group_name, self.channel_name) async def push_notification(self, event): await self.send(text_datajson.dumps({ type: event[ntype], content: event[content], link: event[link], }, ensure_asciiFalse))在业务视图里创建换购请求后调用推送from channels.layers import get_channel_layer from asgiref.sync import async_to_sync channel_layer get_channel_layer() async_to_sync(channel_layer.group_send)( fuser_{receiver.id}, { type: push_notification, ntype: request, content: f{sender.username} 想用 {from_item.title} 换你的 {to_item.title}, link: f/exchange/detail/{request_obj.id}/, } )前端的JavaScript大概是这样的我这里省略了装饰样式核心逻辑很简单const ws new WebSocket(ws:// window.location.host /ws/notify/); ws.onmessage function(e) { const data JSON.parse(e.data); showToast(data.content); fetch(/notification/unread-count/).then(r r.json()).then(d { document.getElementById(unread_badge).textContent d.count; }); };本地调试时有两个最常遇的坑一是没启动RedisChannels一接就报连接错误二是routing.py里没有配置websocket_urlpatterns导致在浏览器控制台看到WebSocket连接失败。这里补上routing.py的一个配置示例from django.urls import path from channels.auth import AuthMiddlewareStack from channels.routing import URLRouter from your_app.consumers import NotificationConsumer websocket_urlpatterns [ path(ws/notify/, NotificationConsumer.as_asgi()), ] application ProtocolTypeRouter({ websocket: AuthMiddlewareStack(URLRouter(websocket_urlpatterns)), })AuthMiddlewareStack的作用是让你在Consumer里能通过self.scope[user]拿到当前登录用户少了它连接时无法识别用户推送就不知道往哪个组发逻辑直接跑不起来。3.4 Cookie 设置 Token登录态与 CSRF 的安全边界热搜词里“django cookie 设置 token”是我今天要重点讲的一个点。很多人的做法是登录后把用户ID塞进Session然后用Django自带的Session中间件搞定一切。但如果你想做一个前后端分离一点、或者想让接口更像真实项目用Token会更好解释。简单方案是这样的用户登录成功后生成一个随机Token存到数据库用户表里或专门的Token表再把Token写进Cookie。前端每次发请求时Cookie会自动带上后端写个装饰器检查这个Token对应的用户是谁。生成Token可以用Python内置的secrets模块import secrets def login_view(request): if request.method POST: username request.POST.get(username) password request.POST.get(password) user authenticate(usernameusername, passwordpassword) if user: token secrets.token_hex(32) user.profile.token token user.profile.save() response redirect(/index/) response.set_cookie(token, token, max_age7 * 24 * 3600, httponlyTrue, samesiteLax) return response自定义装饰器检查登录状态from functools import wraps from django.shortcuts import redirect def login_required_token(view_func): wraps(view_func) def wrapper(request, *args, **kwargs): token request.COOKIES.get(token) if not token: return redirect(/login/) # 查询token对应的用户 profile Profile.objects.filter(tokentoken).first() if not profile: return redirect(/login/) request.user profile.user return view_func(request, *args, **kwargs) return wrapper这里有一个安全细节必须讲清楚Cookie要设置httponlyTrue这样JavaScript拿不到Token可以防XSS窃取再设置samesiteLax可以降低CSRF风险。Django自己的CSRF中间件会在POST请求时校验Cookie里的csrftoken如果你用了自定义Token方案前后端交互时要么保留Django的CSRF机制要么对纯API接口用csrf_exempt并做好Token校验。我建议你答辩时说项目保留了Django自带的CSRF防护同时加入自定义Token做接口认证属于“安全双保险”。3.5 搜索与简单推荐毕设够用的方案搜索功能别一上来就上Elasticsearch杀鸡用牛刀。Django的icontains配合Q对象已经能覆盖90%学生的需求。前面写过的那个搜索代码关键词能同时匹配标题和描述已经合格。想在搜索上加一点排序权重可以用Case和When来实现标题命中权重为3描述命中权重为1按权重倒序排。from django.db.models import Case, When, Value, IntegerField keyword request.GET.get(q, ) items Item.objects.filter(statuson_sale, is_activeTrue).annotate( relevanceCase( When(title__icontainskeyword, thenValue(3)), When(description__icontainskeyword, thenValue(1)), defaultValue(0), output_fieldIntegerField(), ) ).filter(relevance__gt0).order_by(-relevance, -created_at)首页的“猜你喜欢”也可以做个简单版。根据用户之前浏览或发布的物品分类取同一分类下其他用户的在售物品再排除自己发布的。这个逻辑用十几行代码就能实现但对演示效果提升巨大。协同过滤等推荐算法毕设阶段不推荐周期长、效果难讲清楚不如把基础搜索打磨好。4. 毕设交付物准备源码、文档、代码讲解与定制避坑4.1 源码整理一套别人能直接跑起来的项目不少学生的源码最后得分不高问题不在功能而在“跑不起来”。老师拿到项目连环境都没配好凭什么给高分所以交付物里最重要的不是代码多花哨而是可复现。根目录下必须有requirements.txt列出所有依赖和版本Django4.2,5.0 channels4.0.0 channels-redis4.2.0 daphne4.0.0 djangorestframework3.14.0 Pillow10.0.0README.md要写清楚三件事项目是什么、怎么安装、怎么运行。至少要包含这几条命令python -m venv venv source venv/bin/activate # Windows用 venv\Scripts\activate pip install -r requirements.txt python manage.py makemigrations python manage.py migrate python manage.py createsuperuser python manage.py runserver再用python manage.py seed_demo_data导入一份演示数据。这个命令怎么来写一个自定义management command把几个测试用户、十几件物品、几条换购请求一次性插入。答辩演示时不用从零开始录数据非常加分。另外项目里settings.py中不要硬编码数据库密码用环境变量或config文件区分开发环境和生产环境这也是老师眼中“工程化意识”的体现。4.2 毕业论文与演示文档五章结构逐段拆解Django毕设的论文一般按五章写每一章对应一个交付物。第一章绪论写背景、意义、国内外现状尽量结合“校园闲置资源利用率低”“绿色环保”这些立意来写。第二章相关技术介绍把Python、Django、Bootstrap、Redis、Channels介绍一遍但别当百科念要说明“这个技术在这个项目里用来做什么”。第三章需求分析与系统设计放用例图、业务流程图、E-R图、表结构说明这是论文的主体。第四章系统实现按功能模块走每个模块截两个页面图附一段核心代码讲清楚逻辑。第五章系统测试写测试环境、用例表格、测试结果分析。论文里必须画E-R图。我建议不要画那种一坨关联线绕成蜘蛛网的图就画核心的六张表User/Profile、Item、ItemImage、ExchangeRequest、Order、Notification标注清楚主外键和一对多关系。这个图答辩时老师看得最仔细。文档里还要补一个部分异常与边界情况设计。比如同一物品收到多个换购请求怎么处理用户信用分不足时能否发布物品锁定后忘记确认怎么办。这些问题写在论文里直接体现你的思考深度。4.3 代码讲解与答辩三个必讲点、五个必答问题代码讲解最重要的一点不要从models.py第一行开始念要按业务链路讲。我建议你准备十分钟的演示流程按“注册登录—发布物品—搜索物品—发起换购—WebSocket实时通知—后台管理”这个顺序来。必讲的三个点第一个是登录态怎么管理。从生成Token到写入Cookie再到装饰器校验这条路讲清楚老师基本就认可你的“认证”能力。第二个是换购请求的状态流转。从pending到accepted再到completed每步改状态时是否用事务、是否加锁以及为什么不能直接删除对象而用is_active。第三个是WebSocket推送的全链路讲了就知道你是真的做了实时通信而不是只搭了个壳。答辩时老师常问的问题我也列一下“换购平台和普通购物网站的区别是什么”答购物是单向的商品-订单模型换购是双向的请求-协商-确认模型核心差异在于交换请求表。“如果同一件物品被多个用户同时请求换购怎么办”答用select_for_update锁行事务里检查状态只允许第一个被接受的请求生效。“为什么要用Django不用Flask”答Django自带Admin、ORM、认证体系对这类业务系统开发效率高功能闭环完整。“如何保证用户发布的信息是安全的”答表单校验、XSS过滤、图片上传类型限制后台操作走Django Admin自带权限。“系统上线后最大的性能瓶颈可能在哪里”答WebSocket长连接和图片静态资源可以用Redis做缓存Nginx做静态资源分离。4.4 “一条龙定制”的现实情况与避坑提醒标题里写着“一条龙定制”现实里也确实有很多“卖家”在接手这类需求。我在这里不评价代做这件事只提醒两点第一拿到手的所谓“毕设源码”质量参差不齐很多是几年前的旧项目Django版本老、依赖冲突严重第二如果是为了学技术源码可以作为参考但一定要用自己的逻辑重写核心模块否则答辩时老师问出来的细节你根本答不上来。怎么验证一套源码靠不靠谱我会按这个清单过一遍requirements.txt是否完整版本是否兼容当前Python版本数据库迁移文件是否存在migrate能不能一次通过是否有README有没有写清楚启动步骤用createsuperuser创建账号后后台能不能正常登录关键流程是否可跑通发物品、搜索、换购请求、同意、通知。如果五条里有两条过不去那这套源码在你手里的价值就很低了。另外我强烈建议你把源码里的项目命名改成自己的比如把campus_exchange换成你学号或项目名相关的目录数据库中表名前缀也改一下。别问我为什么答辩时老师看到项目名都和你开题报告对不上那场面实在尴尬。5. 环境搭建与服务器部署从本机跑到公网5.1 本地环境Python 安装、虚拟环境与 VSCode 配置先解决环境问题。我建议用Python 3.10或3.11太老的3.7不支持新版Django太新的3.12在一些依赖库上可能还没跟上。Windows安装时一定要勾选“Add Python to PATH”否则命令行里敲python会提示找不到命令。Linux环境一般自带Python3但版本可能偏低建议用官方源或pyenv编译安装新版。项目依赖一定要装在虚拟环境里这是我在知乎上见过最多人踩的坑。不创建虚拟环境直接pip install django过半年你自己都不知道装到哪个Python里去了。创建虚拟环境并激活python -m venv venv # Windows venv\Scripts\activate # Linux / macOS source venv/bin/activateVSCode里打开项目后按CtrlShiftP选择Python解释器为./venv/bin/python再装Python和Django插件调试断点就很方便了。如果遇到pip下载慢的问题用国内镜像源pip install -i https://pypi.tuna.tsinghua.edu.cn/simple django channels channels-redis数据库方面本地起步用SQLite就行零配置写进settings.py就能跑。到了部署阶段再换成MySQL或PostgreSQL把DATABASES配置改一下然后重新执行一次migrate就好了。5.2 数据库初始化迁移、超级用户与演示数据项目代码克隆下来或写完以后按顺序执行几件事。第一步迁移python manage.py makemigrations python manage.py migrate第二步创建管理员账号python manage.py createsuperuser第三步导入演示数据。我建议写一个自定义的management command放在your_app/management/commands/seed_demo_data.py里内容大致是创建几个测试用户、发布十几件物品、插入几条交换请求和通知记录。写好以后执行python manage.py seed_demo_data整个后台立刻有东西看。这里有个细节很多人不知道默认的PhotoField之类都需要Pillow库管理后台里上传图片如果报ModuleNotFoundError: No module named PIL说明没装Pillow。这个依赖很基础但每年都有人被卡住。5.3 Linux 服务器部署Nginx Uvicorn Channels项目里有WebSocket部署就不能只用gunicorn一定要用ASGI服务器推荐uvicorn或daphne。我先给一个典型的部署链路Nginx负责静态资源和反向代理把HTTP请求转给Uvicorn把WebSocket的/ws/路径也代理给同一个ASGI服务Redis负责Channels的频道层。在服务器上装好Python、Redis、Nginx后把项目代码同步过去创建虚拟环境装依赖然后跑迁移和收集静态文件python manage.py collectstatic写一个systemd服务文件/etc/systemd/system/campus_exchange.service[Unit] DescriptionDjango Campus Exchange Service Afternetwork.target redis-server.service [Service] Userwww-data Groupwww-data WorkingDirectory/var/www/campus_exchange ExecStart/var/www/campus_exchange/venv/bin/uvicorn config.asgi:application --host 127.0.0.1 --port 8000 Restartalways [Install] WantedBymulti-user.target启动服务后配置Nginx的server段。静态、媒体、WS三个location分别处理server { listen 80; server_name your_domain.com; location /static/ { alias /var/www/campus_exchange/staticfiles/; } 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; } location /ws/ { proxy_pass http://127.0.0.1:8000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_set_header Host $host; } }/ws/路径必须显式设置Upgrade和Connection头否则浏览器连接WebSocket会一直握手失败。这个坑我在线下帮人调试时见过太多次了。5.4 部署阶段常见问题速查表现象主要原因解决办法静态文件404未执行collectstatic或Nginx静态路径配错执行python manage.py collectstatic核对STATIC_ROOTWebSocket连不上返回400未使用ASGI服务器或Nginx缺Upgrade头用uvicorn/daphne启动检查Nginx/ws/反代配置Redis连接错误本地没装Redis或端口不对redis-cli ping确认检查CHANNEL_LAYERS配置迁移时提示字段冲突改了模型但没生成新迁移或依赖冲突删掉旧的冲突迁移文件重新makemigrations上传图片不显示MEDIA_URL和Nginx/media/配置不一致核对Django配置和Nginx路径检查权限后台登录提示CSRF验证失败跨域或Cookie设置问题清理浏览器Cookie检查samesite设置确认CSRF_TRUSTED_ORIGINS服务器内存占用过高多开Uvicorn或数据库配置过大只保留一个ASGI进程调低MySQL buffer数据库中文乱码MySQL字符集未设为utf8mb4建库用CREATE DATABASE ... CHARACTER SET utf8mb4把上面这些坑提前踩一遍部署时的心理压力会小很多。我遇到过很多学生代码功能都做完了最后倒在了部署环节非常可惜。6. 写在最后给选这个题目的你几点建议做了这么多年项目我的体会是校园闲置物品换购平台这个题目功能链路完整、场景清晰、又有实时通信这种可展示的亮点特别适合作为Django学习路上的一个里程碑项目。如果你时间充裕我建议你在基础版本上再加三个小功能一是给物品加一个“浏览记录”在首页展示最近看过二是用Redis缓存首页热门的闲置物品减轻数据库压力三是做一个简单的用户互评页面和信用分联动起来。这三个功能代码量都不大但会让你的项目在答辩时明显比别人“厚”一圈。最后再分享一个小技巧演示前务必准备一份“干净的数据库”不要让答辩现场出现你测试时留下的乱码数据。用我前面写的seed_demo_data命令在演示前把数据库初始化到理想状态整个讲解过程就会非常有节奏。别小看这个细节很多人的项目本身能拿80分最后因为演示现场一团乱被压到70分。把流程顺一遍你这道题的高分基本就稳了。
返回列表