资讯动态

Django校园租赁平台实战:订单状态机、并发控制与信用体系设计

发布时间:2026/10/17 14:55:28 来源:尧图企业网站定制
去年带一个学生团队做校园物品租赁共享平台代码量不算夸张但涉及的业务逻辑比想象中复杂得多。项目代号叫校园版咸鱼真正动手后才发现它和闲鱼完全是两个物种闲鱼解决的是闲置物品的所有权转移而校园租赁平台解决的是短期使用权流转。这个差异直接决定了数据建模、订单状态机、信任体系的设计方向也让我把Django的一些关键特性从头到尾重学了一遍。这篇博客把整个项目的设计与实现过程拆开来讲涵盖需求定位、技术选型、数据库设计、租赁订单状态机、信用体系和上线后的坑。适合正在做类似校园平台项目二手交易、租赁共享、预约借用的同学参考也适合想系统了解Django业务系统设计逻辑的开发者。1. 重新定义需求校园租赁和普通二手交易完全是两码事1.1 从谁会租、租什么反推系统核心功能做这类平台之前最容易犯的错是拿着闲鱼的页面结构直接抄。但校园租赁场景下的用户行为和交易逻辑完全不同。先说租客的典型需求。校内学生要租的东西主要集中在三类低频高价值的工具设备相机、投影仪、电钻、打印机、阶段性使用的物品考研资料、教材、演出服装、体育器材、短期体验型商品平衡车、滑板、游戏机。这些物品的特点是买新的贵使用频次低闲放着浪费空间而且校园内交易不需要物流见面在校内某个地点交接非常容易。再看出租者的需求。学生出租物品核心诉求不是赚钱而是盘活闲置资产、顺便赚点零花钱。但他们会担心东西被损坏、被弄丢、归还时间被无限拖延。所以平台的第一优先级不是撮合交易快而是建立可信任的保障机制。从这两个视角反推系统必须具备这样几个能力物品的发布、编辑、上下架管理支持多图展示。租赁订单的完整生命周期管理发起、确认、履约、归还、结算。押金托管与退款的资金流程。学生身份认证确保平台内的用户真实可信。信用记录与评价体系给用户积累可被看见的可靠性。举报与纠纷处理入口万一出事有正式的解决通道。这个范围定下来之后MVP的功能边界就清楚了不做即时聊天用站内短消息代替、不做交易担保之外的金融服务、不做复杂推荐算法先按发布时间和热度排序。先跑通业务闭环再谈体验优化。1.2 与闲鱼模式的三点关键差异为了不让团队在设计时跑偏我专门做了个对照表把校园租赁平台和闲鱼的核心差异摆到桌面上维度闲鱼模式校园租赁模式交易对象物品所有权的转让物品使用权的短期转移履约方式快递发货或线下当面交易校内当面交接需要约定借与还的时间点物品生命周期交易完成后流程即结束可循环发布一个物品能反复产生订单资金流买家付款、卖家收款涉及租金、押金、归还后退押金、逾期扣款信任基础平台信用分担保交易用户彼此陌生校内身份认证信任半径天然较小纠纷焦点货不对版、物流损坏逾期未还、物品损坏、押金扣款争议其中最关键的是物品生命周期。闲鱼上一个商品成交之后这笔交易就完结了商品可以下架。而租赁平台上一台相机可以被A租走三天归还后又被B租走一周每个物品始终处于可租、被预订、租出、待归还的循环状态。这意味着物品表必须维护实时状态而且状态切换受到订单状态的强约束。另外一个容易忽略的点是时间维度。租赁天然带起始时间和结束时间系统必须处理逾期这种特殊状态。闲鱼不存在逾期问题而校园租赁平台必须在订单状态机中预留逾期处理的分支还涉及逾期费和信用扣分。1.3 MVP功能边界怎么划一个学生团队做这种系统时间通常是8到12周。如果一开始就把聊天、消息推送、支付、积分商城全包进去大概率什么都做不好。当时我们的MVP保留了以下模块用户模块注册、登录、学生认证、个人资料、信用分展示。物品模块发布、编辑、图片上传、按分类筛选、关键词搜索。订单模块发起租赁、确认订单、履约中、申请归还、确认归还、评价。押金模块模拟押金支付与退还流程接微信支付需要商户资质校园项目先用模拟操作。评价与信用模块双方互评、信用分增减、信用分展示。后台管理物品审核、用户管理、举报处理、订单查看。至于站内聊天用Django自带的messages框架加一个简单的站内信模型完成消息推送这块直接留到二期因为校园用户都会看短信和微信消息站内信只需要满足在平台内能收到提醒即可。2. 技术选型复盘为什么Django比Flask、FastAPI更适配这类项目2.1 项目组的真实水平和迭代节奏技术选型这件事经常被搞成性能攀比。但说实话在一个课程项目或者为期一个学期的校园团队项目里框架的生态完整度和团队熟悉速度远比极限性能重要。团队里有几个成员之前只写过简单的Python脚本对Web框架的理解停留在路由模板的层面。如果选Flask自由度很大但很多功能需要自己拼——用户认证要装Flask-Login表单要装Flask-WTFORM要再选一个。面对这么多碎片化选择新手容易在集成阶段卡壳。选FastAPI则有另一个问题异步生态对新手的心智负担比较高而且模板、Admin后台、ORM迁移这些配套装备不如Django成熟。Django的优势恰好在于全家桶形式自带Admin后台、ORM、迁移工具、表单处理、认证系统、消息框架。项目组不用花时间做选择题只需要按Django已经约定好的方式写业务代码。对于校园平台这种以事务管理、状态流转、权限控制为核心的系统Django的约束反而是一种保护。2.2 Django自带的装备包到底省了多少事说几个实际节省了大量时间的内置能力Admin后台是最大的惊喜。上线初期管理员需要在后台审核物品、查看订单、处理举报。Django Admin只需要把模型注册进去就能获得可用的管理界面。我们当时只花了半天时间就把用户管理、物品审核、订单查看全部搭好了。如果用其他框架这个后台少说要额外写几千行代码。ORM迁移让数据库迭代变得无痛。项目开发过程中因为需求调整我们的订单表加了至少三次字段期望归还时间、实际归还时间、逾期费。Django的makemigrations和migrate两条命令就搞定了而且不会丢数据。这在快速迭代的项目里价值极大。认证系统是另一个省时间的点。Django内置User模型和session认证我们只需要扩展Profile表存学生信息。登录、登出、密码重置这些功能几乎是开箱即用。2.3 版本组合和基础工程结构我们用的技术栈是Python 3.10 Django 4.2。为什么选Django 4.x因为4.x支持Python 3.10及以上版本而且对异步视图、数据库路由支持更完善。如果是从零开始的新项目不建议再选Django 2.x有些老教程里的写法比如url()函数在4.x里已经废弃了。项目基础结构如下规划清楚了后续不会乱/rental_platform ├── manage.py ├── config/ # 项目配置目录 │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── apps/ │ ├── users/ # 用户模块 │ ├── items/ # 物品模块 │ ├── orders/ # 租赁订单模块 │ └── operations/ # 信用、评价、举报 ├── static ├── media └── templates把业务拆成多个app来组织而不是把所有代码塞进一个app里。这样每个模块的表结构、视图、URL和模板都能独立维护找问题的时候不用来回翻文件。3. 数据库建模整个租赁业务怎么翻译成表3.1 实体关系梳理用户、物品、订单、资金、信用开始建表之前先画逻辑模型。我们的核心实体有5个User用户、Item物品、LendOrder租赁订单、PaymentRecord资金记录、CreditRecord信用记录再加上评价和举报。关键关系是这样User与Item是1对N一个用户可以发布多个物品。Item与LendOrder是1对N一个物品可以产生多笔租赁订单但同一时间只能有一个有效订单。LendOrder与User是多对一关系一笔订单涉及出租者和借入者两个角色可以通过两个外键关联到User表。LendOrder与PaymentRecord是1对N一笔订单会先后产生押金支付、租金支付、逾期费、押金退回等多笔资金流水。User与CreditRecord是1对N信用分变化需要留痕。这个关系模型确定之后表结构基本就浮出来了。有一点要提醒用户角色不是靠专门的角色字段区分的因为在这个平台里任何用户既可以是出租者也可以是借入者所以订单表里设计lender和borrower两个外键字段就够了。3.2 租赁订单表设计的关键取舍订单表是整个平台的核心它的设计直接决定后续状态机和资金流的实现难度。当时我们踩过一个坑一开始把状态字段设计成CharField用字符串存待确认租赁中已完成结果代码里到处都要写字符串拼接和比较非常容易出错。后来改成IntegerField choices常量代码立刻清爽了很多。订单表最终长这样from django.db import models from django.contrib.auth.models import User class LendOrder(models.Model): STATUS_PENDING 0 # 待对方确认 STATUS_ACTIVE 1 # 租赁中 STATUS_RETURN_REQUEST 2 # 已申请归还 STATUS_COMPLETED 3 # 已完成 STATUS_CANCELLED 4 # 已取消 STATUS_OVERDUE 5 # 逾期未还 STATUS_DISPUTED 6 # 纠纷处理中 STATUS_CHOICES [ (STATUS_PENDING, 待对方确认), (STATUS_ACTIVE, 租赁中), (STATUS_RETURN_REQUEST, 已申请归还), (STATUS_COMPLETED, 已完成), (STATUS_CANCELLED, 已取消), (STATUS_OVERDUE, 逾期未还), (STATUS_DISPUTED, 纠纷处理中), ] order_no models.CharField(max_length32, uniqueTrue, verbose_name订单号) item models.ForeignKey(items.Item, on_deletemodels.CASCADE, verbose_name租赁物品) borrower models.ForeignKey(User, on_deletemodels.CASCADE, related_nameborrow_orders, verbose_name借入者) lender models.ForeignKey(User, on_deletemodels.CASCADE, related_namelend_orders, verbose_name出租者) status models.SmallIntegerField(choicesSTATUS_CHOICES, defaultSTATUS_PENDING, verbose_name订单状态) start_date models.DateTimeField(verbose_name借用开始时间) due_date models.DateTimeField(verbose_name应归还时间) actual_return_date models.DateTimeField(nullTrue, blankTrue, verbose_name实际归还时间) rent_amount models.DecimalField(max_digits8, decimal_places2, verbose_name租金) deposit_amount models.DecimalField(max_digits8, decimal_places2, verbose_name押金) overdue_fee models.DecimalField(max_digits8, decimal_places2, default0, verbose_name逾期费) created_at models.DateTimeField(auto_now_addTrue) updated_at models.DateTimeField(auto_nowTrue) class Meta: db_table lend_order indexes [ models.Index(fields[status, due_date]), models.Index(fields[borrower, status]), models.Index(fields[lender, status]), ]说几个字段设计的细节。order_no是订单号必须唯一。生成方式用的是时间戳用户ID随机数避免并发时重复。千万别直接用自增主键当订单号展示给用户很容易暴露业务规模也不够专业。borrower和lender两个外键必须用不同的related_name否则Django的反向查询会冲突。这一点初学者经常踩。due_date、actual_return_date这两个时间字段是租赁业务独有二手交易完全用不到。它们支撑了逾期计算逻辑。DecimalField而不是FloatField存金额正确做法。浮点数在租金和押金的计算上会有精度问题比如0.10.2的结果是0.30000000000000004这类显示在页面上非常不专业。3.3 用代码固化容易遗漏的约束数据建模不只是建表还包括约束。一些业务规则光靠模型字段不够需要在model层写clean方法或者使用数据库约束。当时主要做了这几个物品的状态和订单状态联动。比如物品处于已出租状态时不能再被创建新订单。订单的开始时间必须早于应归还时间。同一物品在同一时间段内不能重复出租。第三个约束最麻烦。数据库层面用重叠时间检查比较复杂我的做法是在创建订单的视图函数里做校验配合数据库的select_for_update锁来解决并发问题。这部分在第6章说。3.4 物品表设计时的细节物品表除了基本信息标题、描述、分类、图片、租金、押金还有一个容易忽略的字段物品状态。它不完全是上架/下架而是要和租赁订单联动class Item(models.Model): STATUS_AVAILABLE 0 # 可租 STATUS_RENTED 1 # 已出租 STATUS_OFFLINE 2 # 已下架 STATUS_PENDING_REVIEW 3 # 待审核这样一个相机在上架后可租被下单且确认后变成已出租订单完成后又恢复可租。物品状态的变化只能由订单状态迁移来驱动不能由用户直接修改否则会出现物品显示可租但实际已被借走的不一致情况。图片这块单表是处理不了的我们单独建了一张ItemImage表一个物品对应多张图class ItemImage(models.Model): item models.ForeignKey(Item, on_deletemodels.CASCADE, related_nameimages) image models.ImageField(upload_toitem_images/%Y/%m/) sort_order models.IntegerField(default0)图片存储用的是Django的media目录配合nginx托管。如果以后要上云对象存储把upload_to和默认存储后端换掉就行表结构不用动。4. 租赁订单状态机系统最核心的业务闭环4.1 六种状态和流转合法性规则订单状态是整个平台的血液。当时我把状态流转规则贴在小黑板上开会时反复对照编码阶段基本没出过大的逻辑错误。合法流转路径如下待对方确认0借入者发起租赁申请。此时可以流转到租赁中出租者确认、已取消任意一方取消。租赁中1确认后进入履约阶段。可以流转到已申请归还借入者发起归还、逾期未还超过应归还时间仍未归还。已申请归还2借入者点击归还申请。可以流转到已完成出租者确认收到物品且无损坏、纠纷处理中双方对损坏或扣款有争议。已完成3终态。已取消4终态。逾期未还5从租赁中自动转入。借入者归还并处理逾期费后流转到已申请归还或已完成。纠纷处理中6由已申请归还转入管理员介入处理完毕后到已完成。这里有个业务上的细节逾期费不会在状态变为逾期的那一刻就立刻结算而是记录一个逾期状态在真正归还时按实际超期天数计算费用。这样可以避免频繁计算导致的金额误差。4.2 每个状态切换背后的业务动作状态不能白切每个切换都要伴随业务动作。我把状态和动作的对应关系整理成一张表编码时照着实现状态变化触发动作数据变更0 - 1出租者点击确认物品种类变为已出租订单记录确认时间0 - 4任意一方取消订单关闭无资金变动1 - 2借入者点击申请归还记录申请归还时间1 - 5定时任务检测到超时物品种类不变生出逾期标记2 - 3出租者确认收货并验收计算实际费用释放押金信用评价互开2 - 6任一方发起纠纷订单冻结通知管理员介入5 - 2借入者归还申请计算逾期费进入验收流程注意到没有归还申请之后最终是出租者手动确认完成而不是系统自动完成。因为物品的损坏与否需要人来判断。这个设计虽然多了一步操作但避免了大量因自动完成导致的纠纷。4.3 状态机在Django中的落地方式实现时用了最直观的方式独立的transition_order函数所有状态变化都走这个函数不允许在视图里直接改status字段。from django.utils import timezone from django.db import transaction ALLOWED_TRANSITIONS { LendOrder.STATUS_PENDING: {LendOrder.STATUS_ACTIVE, LendOrder.STATUS_CANCELLED}, LendOrder.STATUS_ACTIVE: {LendOrder.STATUS_RETURN_REQUEST, LendOrder.STATUS_OVERDUE}, LendOrder.STATUS_RETURN_REQUEST: {LendOrder.STATUS_COMPLETED, LendOrder.STATUS_DISPUTED}, LendOrder.STATUS_OVERDUE: {LendOrder.STATUS_RETURN_REQUEST, LendOrder.STATUS_DISPUTED}, LendOrder.STATUS_DISPUTED: {LendOrder.STATUS_COMPLETED}, LendOrder.STATUS_COMPLETED: set(), LendOrder.STATUS_CANCELLED: set(), } def transition_order(order, target_status, operator, remark): 统一订单状态流转入口 if target_status not in ALLOWED_TRANSITIONS.get(order.status, set()): raise ValueError(f非法的状态流转: {order.get_status_display()} - {target_status}) with transaction.atomic(): order.status target_status order.updated_at timezone.now() if target_status LendOrder.STATUS_ACTIVE: order.item.status Item.STATUS_RENTED order.item.save() if target_status LendOrder.STATUS_COMPLETED: order.actual_return_date timezone.now() order.item.status Item.STATUS_AVAILABLE order.item.save() # 释放押金, 计算租金和逾期费 settle_order_payment(order) order.save() OrderLog.objects.create( orderorder, operatoroperator, from_statusorder.status, to_statustarget_status, remarkremark )之所以把所有流转逻辑收敛到一个函数里而不是直接在每个视图里给status赋值是为了审计。OrderLog表记录了每笔订单每一次状态变化的原因和操作人后期处理纠纷时能很快定位问题。4.4 逾期未还真正的压力测试点租赁平台和二手交易最大的不同就是有时间强制力。逾期处理方案我们分成了两层第一层是系统检测。Django没有内置定时任务我用一个简单的management command每分钟跑一次扫描过滤出状态为租赁中、due_date小于当前时间的订单把状态批量改成逾期from django.core.management.base import BaseCommand class Command(BaseCommand): help 检测逾期订单并更新状态 def handle(self, *args, **options): overdue_orders LendOrder.objects.filter( statusLendOrder.STATUS_ACTIVE, due_date__lttimezone.now() ) count 0 for order in overdue_orders: order.status LendOrder.STATUS_OVERDUE order.save() count 1 self.stdout.write(f共标记 {count} 笔逾期订单)第二层是用户通知。订单变成逾期后站内信系统给借入者和出租者各发一条提醒告知逾期费和后续处理办法。这一步不是可选的没有通知的逾期会直接被用户忽略导致物品迟迟收不回来。实际运营中逾期最多的情况是复习资料和考试书籍因为大家都在考试周归还时间撞车。后来我们给这类高热度物品增加了续租功能借入者可以在到期前申请延长租期出租者同意后更新due_date缓解了不少矛盾。5. 信任与信用体系校园版咸鱼能不能跑起来的底层逻辑5.1 学生身份认证第一道信任门槛闲鱼上的信任靠的是平台担保、实名认证和用户交易记录。但在校园场景里最有效的信任凭证是学生身份。一个经过学号认证的用户违约成本比随机注册用户高得多——因为可以追溯到具体的人。学生认证我们用了双通道学号姓名学院信息提交后与学校数据比对课程项目里用模拟数据。辅助邮箱认证要求学生使用学校的教育邮箱激活账号。在Django中我们扩展了User表增加一个Profile模型from django.contrib.auth.models import User from django.db import models class StudentProfile(models.Model): user models.OneToOneField(User, on_deletemodels.CASCADE, related_nameprofile) student_no models.CharField(max_length20, uniqueTrue, verbose_name学号) college models.CharField(max_length50, verbose_name学院) real_name models.CharField(max_length20, verbose_name真实姓名) is_verified models.BooleanField(defaultFalse, verbose_name是否认证) def __str__(self): return f{self.real_name}({self.student_no})平台规则是未认证用户只能浏览不能发布物品也不能发起租赁订单。这一步把绝大多数非学生用户挡在门外从源头降低风险。5.2 押金资金流设计和退款时序押金是整个租赁业务里最敏感的资金。押金问题的设计核心不是收多少而是什么条件下退、怎么退、扣多少。我们没有接真实的第三方支付校园项目没有商户资质押金流程用了一个PaymentRecord表模拟。以支付押金为例class PaymentRecord(models.Model): order models.ForeignKey(LendOrder, on_deletemodels.CASCADE, related_namepayments) user models.ForeignKey(User, on_deletemodels.CASCADE) amount models.DecimalField(max_digits8, decimal_places2) pay_type models.SmallIntegerField(choices[ (1, 押金支付), (2, 租金支付), (3, 逾期费), (4, 押金退还), (5, 纠纷扣款), ]) created_at models.DateTimeField(auto_now_addTrue)退款时序很关键押金不能在下单人发起归还申请时就退必须等出租者确认验收无误后才进入退款流程。如果是已申请归还状态就退款万一物品有损坏出租者再想扣款就非常被动了。我们当时的资金结算逻辑是订单创建时记录应缴押金和租金。借入者支付押金后订单才能流转到待确认状态模拟操作中简化了这一步。订单完成后settle_order_payment函数计算最终费用生成押金退还记录。实际运营时因为大部分物品金额不高押金通常等于物品原价的30%或者租金的两倍视物品类型来定。但编码上只需要把押金字段作为订单属性即可具体金额由发布者在发布时设定。5.3 信用分机制从评价抽象出的奖惩规则信用分是信任体系的第二条支柱。参考了常见平台的信用分逻辑我们设计了一个递增/递减规则表行为信用分变化完成一笔订单双方互评2获得借入者好评1按时归还1逾期归还-3逾期超过3天-8物品损坏未赔偿-15取消已确认订单-2恶意举报-10每次变化都记录一条CreditRecord保留原因和时间。用户的信用分在个人主页和物品列表上展示但初始分统一是100分。低于60分时限制发布物品和发起租赁低于40分的账号进入人工审核。这个机制不复杂但它能形成正向循环信用高的用户更容易被出租者接受出租者因为对方信用高而更愿意降低押金。平台可以后续扩展信用置换押金玩法但那是运营层面的后话技术上是预留了扩展位的。5.4 举报与纠纷处理流程纠纷处理做不了自动化平台必须有管理员介入的通道。当订单状态流转到STATUS_DISPUTED时系统给管理员发送一条待办通知管理员在Django Admin后台查看双方留言和相关订单人工判断后执行退款或扣款。这里的编码重点是纠纷中的订单不能被任何一方单方面完结只能通过管理员操作流转到终态。所以我们在transition_order函数的合法流转表里STATUS_DISPUTED只允许流向STATUS_COMPLETED且操作人必须是管理员。这样从技术上保证了纠纷处理闭环。另一个细节是评价的解锁。订单完成后双方才可互评如果订单处于评价之前的未完成状态评价按钮不显示。这个在模板层做判断就行不用太复杂的逻辑。6. 踩坑实录与上线优化这些坑比框架文档值钱6.1 并发超租一个订单两个人同时抢这是我们在联调阶段遇到的最大BUG。测试人员模拟两个用户同时点击发起租赁同一个物品结果两个订单都创建成功了物品被同时借给了两个人。问题的本质是竞态条件。在视图函数中先查物品状态是否可租再创建订单这两个操作之间没有加锁。两个请求都读到了可租状态然后各自创建订单就出现了超租。解决方案是用数据库的锁from django.db import transaction def create_lend_order(item_id, borrower, start_date, due_date): with transaction.atomic(): item Item.objects.select_for_update().get(iditem_id) if item.status ! Item.STATUS_AVAILABLE: raise ValueError(该物品已被租出或不可用) # 额外做时间段重叠校验 conflict LendOrder.objects.filter( itemitem, status__in[ LendOrder.STATUS_PENDING, LendOrder.STATUS_ACTIVE, LendOrder.STATUS_RETURN_REQUEST, LendOrder.STATUS_OVERDUE, ], start_date__ltdue_date, due_date__gtstart_date ).exists() if conflict: raise ValueError(该物品在所选时间段内已被预订) item.status Item.STATUS_RENTED item.save() order LendOrder.objects.create(...) return orderselect_for_update()在数据库层面给这行记录加了行锁。第二个事务执行到同一行时会阻塞等第一个事务提交后重新读到的是已更新的状态从而正确抛出不可租异常。这个坑告诉我们涉及资源抢占的业务判定和操作必须放在同一个数据库事务里任何先读取再判断再写入的代码都要警惕并发问题。6.2 N1查询把首页拖垮之后首页展示物品列表时每个物品需要显示发布者头像、信用分、图片列表。第一次写完列表页加载耗时在2秒以上。问题出在模板循环里顺手调用了太多关联查询# 错误写法每次循环都会额外查询一次发布者和图片 items Item.objects.filter(statusItem.STATUS_AVAILABLE) for item in items: print(item.owner.profile.is_verified)这是典型的N1问题。修复方式是在视图层的查询中一次性加载好关联数据items Item.objects.filter(statusItem.STATUS_AVAILABLE).select_related(owner__profile).prefetch_related(images)select_related适合ForeignKey和OneToOne关系prefetch_related适合ManyToMany和反向关联。组合使用后首页接口从几十条SQL降到3条加载时间降到300毫秒以内。Django的debug_toolbar是这个问题的照妖镜它会在页面侧边栏显示本次请求执行的所有SQL语句一眼就能看到重复查询。建议所有做Django项目的人第一时间装上它。6.3 图片上传与存储的细节物品图片上传踩了两个坑第一个是上传大小限制。默认情况下如果用户传一个20MB的照片服务器内存和请求时间都会飙高。我们在settings里做了限制DATA_UPLOAD_MAX_MEMORY_SIZE 5 * 1024 * 1024 # 5MB同时在表单层校验了图片的宽高和大小超过限制直接给出友好提示。第二个是文件命名碰撞。如果直接用用户原始文件名保存很可能会重名覆盖。我们改用了Django的upload_to自定义函数import os import uuid from django.utils import timezone def item_image_path(instance, filename): ext os.path.splitext(filename)[1] return fitem_images/{timezone.now():%Y%m}/{uuid.uuid4().hex}{ext}这样每个文件都有唯一名字同时按月份分目录后续维护也方便。6.4 后台站内信与权限控制消息通知这块最省事的做法是直接复用Django自带的消息框架但它默认只支持一次性闪现消息类似操作成功不适合做站内信列表。我们后来建了独立的站内信模型class Message(models.Model): sender models.ForeignKey(User, on_deletemodels.CASCADE, related_namesent_messages) receiver models.ForeignKey(User, on_deletemodels.CASCADE, related_namereceived_messages) content models.TextField() is_read models.BooleanField(defaultFalse) created_at models.DateTimeField(auto_now_addTrue)关键操作订单确认、归还申请、逾期提醒、纠纷通知发生时自动创建一条站内信。在导航栏显示未读数量用户点击进去标记已读。实现成本很低但很实用避免了用户遗漏关键操作。权限控制方面我在视图上用了Django的login_required装饰器同时把管理员操作限定在is_staff用户from django.contrib.admin.views.decorators import staff_member_required staff_member_required def dispute_resolve(request, order_id): ...实际跑下来这个系统的稳定性和完成度在校园项目里算是相当不错的。开发过程中最大的收获不是CRUD写得多快而是把状态流转、并发控制、资金顺序这几个抽象问题彻底想清楚了。如果有团队想复刻这个项目我的建议是先花两周时间把业务规则画成状态图和流程图再动手写代码写起来会顺畅得多。后续如果想继续扩展可以往预约功能、消息实时推送、基于历史交易的热度推荐方向深入那又是另一篇文章的话题了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑