ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

流浪动物救助站管理系统开发:Django与Flask的选择及核心功能实现

流浪动物救助站管理系统开发:Django与Flask的选择及核心功能实现 1. 先弄清楚天使乐园到底要做什么业务流程与功能边界1.1 流浪动物救助站的真实运转流程我见过不少做这类选题的人上来就建表、写CRUD做到一半发现功能对不上需求回头改表结构改到怀疑人生。原因很简单没搞清楚救助站到底是怎么运转的。一个典型的流浪动物救助站日常流程大概是这样的志愿者在外面发现流浪动物或者接到市民求助电话先把动物收容进站然后做健康检查包括传染病筛查、体内外驱虫有条件的话安排疫苗和绝育手术接着给动物建档、拍照在线上线下发布领养信息有领养人表达意向后救助站要审核领养条件可能还要安排家访或者视频回访确认环境合适领养成功后通常还会约定回访跟踪一段时间看动物适应情况。这个链条里每个环节都产生数据动物档案、医疗记录、领养申请、审核意见、回访记录。如果靠Excel表格一旦数据量上了几百条查询、统计、关联记录就会变得一团糟。我见过有救助站用三个表格来回切换最后对不上账——哪只动物已经打了疫苗、哪个领养申请批到哪一步全靠人为记忆出了问题根本追溯不了。所以这个系统的核心价值不是做个网页展示动物照片那么浅而是要把整条救助闭环管理起来。每只动物从进站到被领养、再到回访结束它的状态、记录、关联操作都留痕这才是管理系统四个字的分量。1.2 角色权限三类用户看到的东西完全不同系统里到底有几种身份建议不要贪多。我见过有人一口气设计了五六个角色实际开发时光是权限判断就写了一堆if else还容易漏。按照救助站真实运作三类角色足够角色核心操作典型场景管理员站长/负责志愿者动物档案录入与修改、领养审核、回访管理、用户管理、数据统计一只猫做完绝育管理员更新健康状态收到领养申请决定是否安排家访注册用户潜在领养人浏览动物列表、查看详情、提交领养申请、查看审核进度、收养后提交反馈看到一只柴犬很投缘填写居住情况和工作信息提交领养申请游客未登录访客浏览已开放领养的动物信息先看看站里有什么动物决定要不要注册权限设计上遵循一个简单原则游客只能读公开数据注册用户能提交申请和管理自己的申请单管理员能操作全部业务数据。这个原则用Django自带的login_required装饰器加一个is_admin字段就能覆盖不需要引入复杂的权限框架。为什么强调这个因为我在不少毕设代码里看到过把管理功能直接暴露给所有登录用户的操作漏洞。演示的时候没人注意答辩老师一登录发现能删库这个印象分就没了。1.3 功能优先级排序先做骨架再做增值功能梳理需求的时候一定要给功能分优先级。这个项目的刚需只有四个动物档案管理录入、编辑、上下架、状态流转领养申请与审核提交申请、状态跟踪、审核操作用户认证注册、登录、登出、个人中心动物展示与筛选列表分页、按种类/状态筛选、详情页这四个模块是骨架必须先做扎实。至于志愿者排班、捐赠管理、站内信通知、数据可视化大屏都属于锦上添花时间充裕再补。很多人做毕业设计栽在想做的太多、做完的太少上——原型画了十个模块最后能跑的只有两个。我的建议是骨架功能跑通了再去碰那些亮点功能毕竟答辩时稳定运行的四个模块远比演示到一半报错的八个模块强。2. 框架选型Flask和Django不是二选一而是两道题2.1 Flask和Django各自擅长的场景这个项目标题写的是flask-django很容易让人误以为要同时用两个框架写一套系统。其实从实际项目角度看更合理的理解是你需要在两者之间做出选择或者用组合策略而不是在一个项目里乱炖。先看框架基因。Flask是微框架核心非常精简路由、视图、请求响应这些基础功能之外ORM、表单验证、Admin后台、用户认证全都要自己装第三方库或者手写。它的好处是灵活你可以完全掌控项目结构适合小项目、API服务、快速原型。Django是重框架自带的东西极多ORM、Admin后台、用户认证体系、表单处理、中间件、模板引擎几乎开箱即用。坏处是学习曲线陡而且Django的很多设计模式是约定优于配置比如你项目里必须存在models.py、views.py、urls.py这些文件目录结构也相对固定想按自己的想法改造会遇到阻力。给没概念的朋友打个比方Flask像一块乐高底板想拼成什么样全看你自己Django像一套精装修房水电煤气厨卫都给你装好了你只需要拎包入住、按自己的需要调整软装。做管理类系统这种业务逻辑重、CRUD占比高的项目精装房显然更省力气。2.2 一张表帮你做出选择对比维度FlaskDjango上手速度快十分钟能写出Hello World慢要理解项目结构、ORM、Admin等概念ORM需要安装SQLAlchemy或Peewee内置ORM迁移工具也算顺滑Admin后台需要安装Flask-Admin并自己配置内置Admin配置一下就能管理数据用户认证需要Flask-Login或手写session内置完整认证体系适合的项目API服务、中小型应用、学习练手数据密集型业务系统、内容管理平台代码量同样的功能要写更多代码很多功能几行配置搞定答辩讲解框架原理简单好讲模块多但每个都能讲出点东西就这个流浪动物管理系统的选题而言我的判断是如果你有三个月以上的开发周期选Django如果只有两三周赶工或者你想展示自己在框架底层上的理解选Flask。两个框架做出来的系统核心业务逻辑其实是相通的区别只在于组织代码的方式和内置功能的使用。2.3 标题里flask-django的组合策略怎么落地如果非要往标题的flask-django上靠有一种实务做法先用Flask快速搭建一个可点击的原型把页面跳转、业务流程、数据结构全部验证一遍然后用Django正式开发。这个流程对应真实的需求验证工程实现的工作方式你在论文里也能写出基于Flask进行快速原型验证基于Django实现完整系统这样的方法论反而比硬把两个框架混在一个工程里更有说服力。另外如果是希望系统里同时出现Flask和Django的元素可以这样设计主系统用Django面向救助站管理员和领养人独立模块用Flask写一个数据统计API比如按月度统计领养成功率、动物收容数量趋势给前端可视化页面供数据。两个服务各管一摊通过HTTP接口通信这在实际企业里是常见架构。不过要提醒一句别为了凑技术点强行加模块答辩老师的经典问题之一就是你这个Flask模块为什么不用Django实现你要能答出合理的工程理由。3. 数据模型设计给每只流浪动物建一份完整档案3.1 动物档案表状态字段比删除操作更靠谱数据模型是整个系统的地基。这个项目的核心表是动物信息表但很多第一次做的人会把这张表设计得过于简陋。我列一下实践下来需要的字段id主键name动物名字允许为空救助的流浪动物很多还没起名species种类猫/狗/其他建议用choices不要存自由文本gender性别age_month月龄比保存出生日期更实用流浪动物的出生日期基本是估算的breed品种可为空color毛色特征health_status健康状况描述文本is_vaccinated是否已接种疫苗布尔值is_neutered是否已绝育布尔值status当前状态在站待领养/已领养/暂时离站/休养中image照片路径description救助故事或性格描述文本adopted_at领养时间空表示未被领养created_at/updated_at创建与更新时间关键设计点是status字段。很多新手不知道状态该怎么管理往往直接加is_adopted这个布尔字段领养了就置True。问题来了体检中的动物怎么表示被人寄养中怎么表示被退回的又怎么表示一个布尔字段根本讲不清生命周期。正确做法是让status成为一个枚举完整覆盖动物的状态流转待领养→领养申请中→已领养→已退回。这样列表页、统计页、审核逻辑都能依赖这个字段做判断。在实际开发中我强烈建议不要用删除操作来处理动物下架而是增加一个下架/隐藏状态因为救助站的每一只动物都有档案留存的必要性物理删除了以后回访、统计、审计都无从谈起。3.2 领养人和申请记录一对多关系的正确建模领养申请是第二个核心模型。这里有一个初学者常犯的错误给动物表加一个applicant_id字段以为一只动物只能关联一个申请人。真实业务完全不是这样——同一只动物可能被好几个人同时递交申请管理员要从中选择合适的领养人。正确做法是建立一张申请表用外键同时关联动物和用户class AdoptionApplication(models.Model): APPLY_STATUS [ (pending, 待审核), (reviewing, 初审中), (approved, 已通过), (rejected, 已驳回), (completed, 已完成领养), (cancelled, 已取消), ] animal models.ForeignKey(Animal, on_deletemodels.CASCADE, related_nameapplications) applicant models.ForeignKey(User, on_deletemodels.CASCADE, related_nameadoption_applications) reason models.TextField(verbose_name领养理由) home_type models.CharField(max_length100, verbose_name居住类型) has_experience models.BooleanField(defaultFalse, verbose_name是否有养宠经验) status models.CharField(max_length20, choicesAPPLY_STATUS, defaultpending) created_at models.DateTimeField(auto_now_addTrue) updated_at models.DateTimeField(auto_nowTrue)这样设计的好处有两个。第一一只动物可以收到多个申请管理员可以在Admin后台按动物维度查看所有申请记录横向比较后选择最合适的第二一个用户可以提交多个不同的领养申请追踪他申请过哪些动物这在回访和管理场景下非常有用。3.3 为什么推荐在模型里使用choices而不是另建一张状态表有人可能会问状态这种东西为什么不建一张AnimalStatus表用外键关联呢放在动物状态这个场景里我的建议是用choices不要建表。原因是状态的扩展频率极低整个系统生命周期内也就那几个固定的值为它建一张表查询时要JOIN写代码时要先查状态表再赋值纯粹增加了复杂度。choices字段在数据库层面其实是一个CharFieldPython层面帮你做了取值校验ORM查询时还能直接按字符串过滤比如查所有待领养的动物就一行代码。这个原则可以外推到其他枚举型数据——性别、种类这些也一样。什么时候该建独立表答案是这个数据本身有额外属性需要维护的时候比如动物种类如果还要记录猫粮偏好常见疾病那建表才划算。4. 关键功能模块的编码实现从认证到领养审核4.1 认证模块Flask和Django的做法差别用户认证是每个Web系统都绕不开的模块这个项目里游客、注册用户、管理员三类身份都需要认证支持。Django的做法最省心自带auth应用和User模型。登录、登出、密码哈希、会话管理全是现成的你只需要写登录模板和调用authenticate()、login()这些API。如果要给用户加是否管理员字段用一个Profile模型关联User或者用Django 1.9之后支持的自定义User模型把is_admin字段直接加进去。我建议后者——因为一旦项目跑起来再想换User模型非常痛苦涉及数据库迁移和一堆代码改动。from django.contrib.auth.models import AbstractUser class User(AbstractUser): is_admin models.BooleanField(defaultFalse, verbose_name是否管理员)设置AUTH_USER_MODEL yourapp.User之后Django全局的认证都走这个自定义模型。注册、登录、鉴权这些写起来都顺了。Flask这边就要自己拼装了。基础做法是session加Flask-Login的UserMixin密码用werkzeug.security里的generate_password_hash做哈希登录后用login_user()把用户放进会话。Flask-Login还提供login_required装饰器来拦截未登录用户跟Django的装饰器用法非常像所以如果先学Flask再转Django这部分过渡很平缓。值得注意的是热门搜索词里有一条django cookie 设置 token这提醒我聊聊登录凭证的两种形态。Django默认用的是服务端session cookiesession数据存在服务端cookie里只存一个session_id。另一种做法是JWTJSON Web Token把用户信息加密后直接存在客户端。对这个项目来说Django默认的session方案已经够用也更容易理解。JWT更适合前后端分离、接口需要被多个端调用的场景如果系统里有移动端接口再考虑它不迟。4.2 动物列表页分页、筛选、排序一个都不能少列表页是访问量最高的页面游客一进来看到的就是它。这个页面要同时处理三件事分页、按条件筛选、列表排序。Django的Paginator类加上OR M的链式查询能高效处理。def animal_list(request): animals Animal.objects.filter(status待领养) species request.GET.get(species, ) if species: animals animals.filter(speciesspecies) paginator Paginator(animals, 12) # 每页12条 page_number request.GET.get(page) page_obj paginator.get_page(page_number) return render(request, animal_list.html, {page_obj: page_obj})这段代码里有个细节值得展开Animal.objects.filter(status待领养)先做一次过滤后面的if species再追加筛选条件。因为OR M的QuerySet是惰性的只有真正取值时才执行数据库查询所以哪怕加了一堆筛选条件最终只会有一次数据库查询不会造成性能问题。这是Django新手必须理解的一个特性。Flask SQLAlchemy的做法也差不多用query.filter_by()或者.filter()配合paginate()方法就能实现类似效果。框架不同思维模式是同一套。4.3 领养审核流程状态机的实现与权限控制领养审核是业务的核心环节也是答辩时可以重点讲的部分。我设计的是一个五状态流程用户提交申请 → 状态为待审核管理员看到申请做初审 → 状态变为初审中或直接驳回初审通过后安排家访/视频回访确认环境 → 状态变为已通过双方线下见面动物交付管理员更新动物状态为已领养申请状态为已完成领养任何环节不通过都可以驳回同时动物状态释放回待领养这里最容易出错的是更新申请状态的同时动物状态没有同步更新。比如领养人提交申请后这只动物应该从待领养变成领养申请中避免其他用户继续申请申请被驳回后动物要回到待领养。我建议把这些状态流转的逻辑封装在模型方法里而不是散落在视图函数中class Animal(models.Model): def mark_as_pending(self): self.status 待领养 self.save(update_fields[status]) def mark_as_applying(self): self.status 领养申请中 self.save(update_fields[status])视图层只负责调用业务方法这样逻辑清晰测试也好写。权限控制方面提交申请的接口只允许登录用户访问审核操作只允许管理员访问。在Django里可以用login_required加自定义的user_passes_test(lambda u: u.is_admin)组合。如果用Flask装饰器逻辑一样只是要自己写个admin_required。4.4 图片上传与展示三个必踩的坑动物档案必然要传照片这个功能看着简单实际上有六个字的口诀别存数据库存文件系统。照片文件存到服务器的media目录数据库只存文件路径/media/animals/20250201_cat_01.jpg。三个坑我挨个说第一中文文件名。用户上传的图片叫小橘猫.jpg直接存下去Linux服务器上没问题但Windows下可能乱码URL里还会出现编码问题。稳妥做法是重命名用uuid或时间戳随机数生成新文件名扩展名保留。这个细节处理不当上传功能在部署到Linux后就会出怪问题。第二静态文件和媒体文件的区分。Django里static目录放CSS、JS、logomedia目录放用户上传的文件这俩在settings.py里要分别配置部署时Nginx/Apache的重写规则也不同。新手最常见的错误是全部堆到static下结果部署后上传目录不可写图片显示不出来。第三图片尺寸校验。流浪动物照片经常是手机拍的动不动就5MB、10MB预览时浪费流量还拖慢页面。后端可以用Pillow做压缩和统一裁剪缩略图列表页只展示200x200的缩略图。这个需要给ImageField配置upload_to和validators并在视图里做二次处理。5. 全程复盘开发过程中踩过的坑和排错思路5.1 数据迁移引发的地狱改字段要趁早我做这个项目时前期动物表设计得不够细后面几乎每两天就要往模型里加字段今天加is_dewormed明天加medical_record后天又改adopted_at的类型。Django虽然提供了makemigrations和migrate来管理数据库变更但频繁迁移有个副作用迁移文件越来越多有时改动一个字段会影响到历史数据那就需要--fake或者手动写RunPython迁移非常折磨。复盘时我的体会是建模阶段宁可多花一天讨论不要开发阶段花三天改表。把1.1节里列的那些字段在纸上先过一遍再动手建模型。另一个相关的坑是外键的on_delete行为。Django里如果一只动物被删除了关联的领养申请怎么处理CASCADE会连带删除PROTECT会阻止删除SET_NULL会把申请记录的外键置空。我建议对外键关系使用PROTECT或SET_NULL因为客户数据被连带删光是生产事故级别的错误。现实中救助站要保留历史记录动物档案删除本来就不该被允许。5.2 N1查询问题列表页慢的元凶系统数据量几百条时可能感觉不到这个问题但一看性能问题就来了。列表页每显示一条动物记录在模板里如果访问了这个动物的关联对象——比如它的申请次数最简单的写法是animal.applications.countORM就会为每一只动物执行一条额外的查询。列表有20条动物就会多出20条查询再加上原本的列表查询就是21次这就是N1问题。解法是Django ORM的两个方法animals Animal.objects.filter(status待领养).select_related(xxxx).prefetch_related(applications)select_related用于外键或一对一关系的预加载prefetch_related用于多对多和反向查询的预加载。加了之后原来20次查询会合并成一次大的查询加一次预加载查询性能提升非常明显。这招答辩时说出来绝对是加分项因为很多应届生只知道CRUD不清楚ORM底层查询原理。5.3 部署上线那些事DEBUG、静态文件、数据库备份到了演示阶段有三件事最容易翻车第一忘记关闭DEBUG。Django的settings.py里DEBUG True会导致报错时把完整调用栈泄露到页面上跟安全相关。演示前必须改成DEBUG False同时配置ALLOWED_HOSTS否则直接报DisallowedHost。第二上传目录权限。Linux服务器上media目录需要给Web应用用户写入权限否则用户传图直接报错。排查方法很简单先手工在服务器上touch media/test.txt如果失败就是权限问题chmod一下就好。第三数据库备份。这个项目用的多半是SQLite或MySQL。演示前一定要把数据库备份好放在系统之外的位置。我见过有人演示中误操作把动物档案全部改成已领养如果没有备份只能现场丢人。用Django的话python manage.py dumpdata backup.json就能把全库导出演示前跑一遍成本极低收益极高。5.4 答辩验收前的测试面清单这是实践总结的检查清单照着走一遍能避免90%的演示事故游客访问受保护的页面如提交领养申请是否能跳转到登录页而不是直接报错新用户注册后是否默认不是管理员且普通用户能访问管理员页面应被拦截管理员添加动物档案时图片上传后页面能否显示、数据库是否存的是路径而非二进制一只动物被两个用户同时提交申请第二个申请是否正常领养申请被驳回后动物状态是否释放回待领养分页页面点击下一页时筛选条件是否丢失表单GET提交时很容易忘带筛选参数浏览器直接访问/admin未登录时是否跳转登录页数据库迁移文件与当前模型是否一致用makemigrations --check --dry-run验证这些点是我在真实开发中一个个试出来的尤其是第3条很多人把图片以Base64字符串存进数据库结果数据库体积暴涨列表页加载慢到让人崩溃。任何一步出了问题按上面讲的思路排查基本都能定位。最后再分享一点个人体会这类管理系统项目真正的技术难点从来不是某个框架的某个API而是把业务逻辑想清楚、把数据关系设计合理、把异常情况处理好。做完这个流浪动物收养管理系统之后我最大的收获不是学会了Django和Flask而是明白了数据库字段的一个选择会影响后续所有编码工作。建议你动手之前先找一家真实的救助站聊一聊或者在网上找找他们的业务流程记录把需求吃透再写代码——这个功夫花得值。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进