ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Django实战:从零开发宠物领养管理系统的完整指南

2026/9/10 0:16:42 拓冰建站 浏览量
Django实战:从零开发宠物领养管理系统的完整指南 做救助站的朋友找我说他们现在的领养信息还靠微信群和Excel表格宠物档案、领养人申请、回访记录全是散着的容易漏也容易乱。我听完就决定用Django给他搭一个宠物领养系统从宠物信息发布到领养申请审核再到后台批量管理一条线走通。这个项目本身不算特别大但覆盖面很完整Django的ORM、认证、Admin后台、文件上传、分页筛选这些技能点全都会用到对于正在学Django、想拿一个完整项目练手的人或者想给自己救助站、宠物店做一套管理工具的人都非常有参考价值。这篇文章我就把整个项目的设计思路、核心模块实现、开发环境搭建、部署和排坑过程完整记录下来希望能帮你少走一些弯路。1. 项目背景与整体设计思路1.1 宠物领养场景里真正需要解决的问题宠物领养这件事看起来只是“把宠物送出去”但在实际运营中要处理的信息量远比想象中大。线下救助站通常需要记录每只宠物的来源、健康状况、疫苗和绝育状态、当前安置位置还要管理有意向领养人的联系方式、家庭环境、领养意向甚至后续的回访记录。如果只靠表格和聊天记录信息很快就会对不上同一只宠物被问了好几次、不同人提交了重复申请、领养人联系不上导致回访中断这些全是实际会发生的问题。这个项目要做的就是把“宠物档案”和“领养申请”两条核心数据线搬到线上。救助站工作人员可以在后台录入宠物信息并配图访客在前台浏览宠物列表、查看详情、提交领养申请管理员审核申请后更新宠物状态整个流程在系统里有迹可循。1.2 技术选型为什么用Django而不是Flask或Vue全家桶选Django的原因很直接这个系统有大量的数据管理和后台操作需求而Django自带Admin后台、ORM、认证系统、表单处理和迁移工具这些都是现成的能力。如果用Flask虽然轻量灵活但用户认证、后台管理、ORM都需要自己集成开发周期会被拉长不少。用Node.js或者纯前后端分离方案也能做但对一个需要快速落地、维护成本不高的内部管理项目来说Django“一套标准下来全都有”的开发体验是最省心的。Django的MVT模式也很适合这类业务系统。Model负责数据建模View处理请求逻辑Template负责页面渲染对于没有专职前端配合的团队来说直接使用Django模板渲染页面比额外搭一套Vue或React前端要方便得多。后期如果确实需要小程序或App入口也可以在此基础上用Django REST Framework提供API不会把路堵死。1.3 系统功能模块划分我把整个系统拆成了几个相对独立的模块这样开发的时候可以分步实现不会互相牵扯模块核心功能主要服务对象宠物信息管理宠物档案增删改查、图片上传、状态管理管理员、前台访客领养申请流程提交申请、审核、状态流转领养人、管理员用户系统注册、登录、个人信息维护全部用户前台浏览搜索宠物列表、关键字搜索、多条件筛选、分页访客后台管理数据管理、申请审核、统计查看管理员系统配置基础数据、站点信息、媒体文件管理管理员这种划分方式没有过度设计每块功能都是实际使用中确实需要的。比如初始版本没打算做消息通知但后来发现领养人提交申请后不知道审核进度所以加了状态查询入口这个在后续版本里可以根据需求再扩展。2. 数据模型设计把业务变成表结构2.1 宠物信息表核心字段与选择逻辑宠物信息是整个系统的数据核心设计这张表的时候要尽量把字段考虑全面因为中途加字段虽然不麻烦但如果数据已经录入很多了再补历史数据就很痛苦。所以我一开始就把救助场景里常见的属性都加上了。from django.db import models from django.contrib.auth.models import User class Pet(models.Model): GENRE_CHOICES [ (dog, 犬), (cat, 猫), (rabbit, 兔), (bird, 鸟), (other, 其他), ] SEX_CHOICES [ (male, 公), (female, 母), (unknown, 未知), ] STATUS_CHOICES [ (available, 待领养), (pending, 审核中), (adopted, 已领养), (offline, 已下架), ] name models.CharField(宠物名字, max_length50) genre models.CharField(宠物种类, max_length20, choicesGENRE_CHOICES) breed models.CharField(品种, max_length100, blankTrue) sex models.CharField(性别, max_length20, choicesSEX_CHOICES, defaultunknown) age models.IntegerField(年龄月, default1, help_text以月为单位) weight models.FloatField(体重kg, nullTrue, blankTrue) is_neutered models.BooleanField(是否绝育, defaultFalse) is_vaccinated models.BooleanField(是否接种疫苗, defaultFalse) status models.CharField(领养状态, max_length20, choicesSTATUS_CHOICES, defaultavailable) description models.TextField(宠物描述, blankTrue, help_text性格、生活习惯、救助经历等) city models.CharField(所在城市, max_length50, blankTrue) cover_image models.ImageField(封面图片, upload_topets/covers/, blankTrue, nullTrue) created_by models.ForeignKey(User, on_deletemodels.SET_NULL, nullTrue, related_namecreated_pets, verbose_name发布人) created_at models.DateTimeField(创建时间, auto_now_addTrue) updated_at models.DateTimeField(更新时间, auto_nowTrue) class Meta: db_table pet verbose_name 宠物信息 verbose_name_plural 宠物信息 ordering [-created_at] def __str__(self): return f{self.name}-{self.get_genre_display()}几个字段设计上比较关键的地方status字段是状态机的核心。待领养、审核中、已领养、已下架四个状态之间流转是有规则的不是随意改的。比如“已领养”的宠物不能再被别人提交申请这个逻辑在视图层和表单层都要控制。age用“月”作单位是因为幼猫幼犬的年龄在头一年变化很快按月记录比按年更精确展示的时候再转换成“X个月/ X岁”。cover_image用 ImageField 而不是普通的 URL 字段这样图片文件可以直接上传到服务器指定目录不依赖外部图床后期备份也方便。使用 ImageField 之前要先安装 Pillow 库。2.2 领养申请表申请人与宠物的关系建模领养申请表要解决的核心问题是“谁在什么时候申请了哪只宠物”并且要记录审核结果。这里用 ForeignKey 关联 Pet 和 User一条申请记录对应一个用户和一只宠物。class AdoptionApplication(models.Model): STATUS_CHOICES [ (pending, 待审核), (approved, 已通过), (rejected, 已拒绝), (cancelled, 已取消), ] pet models.ForeignKey(Pet, on_deletemodels.CASCADE, related_nameapplications, verbose_name申请宠物) applicant models.ForeignKey(User, on_deletemodels.CASCADE, related_nameadoption_applications, verbose_name申请人) applicant_name models.CharField(申请人姓名, max_length50) applicant_phone models.CharField(联系电话, max_length20) applicant_address models.CharField(居住地址, max_length200) has_experience models.BooleanField(是否有养宠经验, defaultFalse) reason models.TextField(申请理由, max_length500) status models.CharField(审核状态, max_length20, choicesSTATUS_CHOICES, defaultpending) admin_note models.TextField(审核备注, blankTrue, help_text管理员填写审核意见) created_at models.DateTimeField(申请时间, auto_now_addTrue) reviewed_at models.DateTimeField(审核时间, nullTrue, blankTrue) class Meta: db_table adoption_application verbose_name 领养申请 verbose_name_plural 领养申请 ordering [-created_at] def __str__(self): return f{self.applicant_name}申请领养{self.pet.name}有一点需要注意申请人姓名和电话是单独存在申请表里的而不是直接去关联的 User 表里取。这样设计是因为用户注册信息可能是昵称和邮箱但领养审核需要真实姓名和联系方式把这两份数据拆开更合理也方便统计。外键删除策略上宠物和领养申请之间用了 CASCADE因为申请对应的宠物如果被删掉了保留孤儿申请记录意义不大。但用户被删除时申请记录也会跟着删掉这在真实运营中可能不是最理想的最好是把申请人信息也独立保存一份。我在实际项目里建议改成 SET_NULL把 applicant 外键允许为空同时保留 applicant_name 等字段这样用户注销后申请记录还在。2.3 用户扩展信息扩展Django自带的User模型Django自带的User模型已经包含了用户名、密码、邮箱、姓名等基础字段但领养系统需要知道用户是普通访客还是管理员。虽然可以用 is_staff 和 is_superuser 来区分管理员但为了让权限模型更清晰我定义了一个UserProfile模型来补充信息class UserProfile(models.Model): user models.OneToOneField(User, on_deletemodels.CASCADE, related_nameprofile, verbose_name用户) phone models.CharField(手机号, max_length20, blankTrue) avatar models.ImageField(头像, upload_tousers/avatars/, blankTrue, nullTrue) city models.CharField(所在城市, max_length50, blankTrue) bio models.TextField(个人简介, blankTrue) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table user_profile verbose_name 用户资料 verbose_name_plural 用户资料这里用的是 OneToOneField 扩展而不是重写 User 模型。重写 User 模型在项目初期做起来方便但如果项目已经创建过数据库或者后续要接第三方应用迁移会很痛苦。用 UserProfile 做一对一扩展是最稳妥的方式不用改动认证流程Django Admin 也可以用 Inline 的方式把资料和用户放在同一页面管理。3. 核心功能模块实现从表单到视图再到模板3.1 用户注册登录用Django自带的认证系统改造用户认证不需要自己造轮子Django 的django.contrib.auth已经提供了完整的登录、登出、会话管理功能。注册部分要自定义一个视图因为默认的 UserCreationForm 只有用户名、密码没有邮箱和姓名。from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.models import User from django import forms class RegisterForm(UserCreationForm): email forms.EmailField(requiredTrue) first_name forms.CharField(max_length30, requiredFalse, label姓名) class Meta: model User fields [username, first_name, email, password1, password2]注册视图里要注意表单校验通过后要把用户数据保存同时顺手创建对应的 UserProfilefrom django.shortcuts import render, redirect from django.contrib.auth import login from django.contrib import messages def register_view(request): if request.method POST: form RegisterForm(request.POST) if form.is_valid(): user form.save() UserProfile.objects.create(useruser) login(request, user) messages.success(request, 注册成功欢迎加入) return redirect(pet_list) else: form RegisterForm() return render(request, accounts/register.html, {form: form})登录直接用 Django 内置的 LoginView在 urls.py 里配置一下就行不需要自己写视图。模板里用{{ form }}渲染表单加上 CSRF token 就可以了。这里有个我踩过的坑如果用UserProfile.objects.create(useruser)创建资料而后面代码里又用了user.profile.phone去获取手机号如果在某些视图里用户资料不存在会直接报错所以注册时创建资料这步不能省。3.2 图片上传不只是设置一个ImageField那么简单很多人以为模型里加了 ImageField 就能上传图片结果总是图片不显示、form 表单报错、或者上传的图片存不到指定目录。这里把整个链路完整的代码写出来照着配置就不会出问题。第一步在 settings.py 里配置媒体文件路径import os MEDIA_URL /media/ MEDIA_ROOT os.path.join(BASE_DIR, media)第二步在项目根 urls.py 里开发环境下的媒体文件访问路由from django.conf import settings from django.conf.urls.static import static urlpatterns [ # ... 其他路由 ] if settings.DEBUG: urlpatterns static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)如果不在 DEBUG 模式下加这个静态路由开发时上传的图片就会 404。因为 Django 开发服务器默认只处理 URL 配置里的路径不会自动服务媒体目录。生产环境则由 Nginx 直接映射 /media/ 目录。第三步表单要用 POST enctypemultipart/form-data在模板里写表单的时候必须加上enctype否则浏览器不会把文件内容发送到服务器form methodpost enctypemultipart/form-data {% csrf_token %} {{ form.as_p }} button typesubmit提交/button /form第四步视图里要从 request.FILES 里取文件并保存from django.shortcuts import get_object_or_404 def pet_create_view(request): if request.method POST: form PetForm(request.POST, request.FILES) if form.is_valid(): pet form.save(commitFalse) pet.created_by request.user pet.save() return redirect(pet_detail, pkpet.pk) else: form PetForm() return render(request, pets/pet_form.html, {form: form})注意PetForm(request.POST, request.FILES)这里FILES 必须作为第二个参数传进去表单类才能正确处理文件字段。一旦漏了form.is_valid()会返回 True但图片字段会是空值很多新手在这里卡住。3.3 领养申请流程有限状态机的转换控制领养申请不是一个简单的提交和保存而是要控制状态流转。我实际开发时的规则是这样的用户只能对“待领养”状态的宠物提交申请。同一只宠物同一用户不能重复提交未处理的申请。管理员审核通过后宠物状态变为“已领养”其他待审核申请自动变为“已拒绝”。管理员审核拒绝后宠物回到“待领养”状态。这套规则在视图层实现from django.core.exceptions import PermissionDenied from django.contrib import messages def apply_adoption_view(request, pet_id): pet get_object_or_404(Pet, pkpet_id) if pet.status ! available: messages.error(request, 这只宠物当前不可申请领养) return redirect(pet_detail, pkpet.id) existing AdoptionApplication.objects.filter( petpet, applicantrequest.user, statuspending ).exists() if existing: messages.warning(request, 你已提交过申请请耐心等待审核) return redirect(pet_detail, pkpet.id) if request.method POST: form AdoptionApplicationForm(request.POST) if form.is_valid(): application form.save(commitFalse) application.pet pet application.applicant request.user application.save() pet.status pending pet.save() messages.success(request, 申请提交成功等待管理员审核) return redirect(my_applications) else: form AdoptionApplicationForm() return render(request, applications/apply_form.html, {form: form, pet: pet})这里有一个很容易被忽略的细节提交申请后要把宠物状态改成“pending”否则会出现“同一只宠物多人同时申请然后管理员只能通过一个人其他人又被驳回”的并发问题。虽然数据库并发极端情况下还是会出问题但在应用层先挡住大部分情况已经能解决绝大多数运营问题。如果数据量大了可以考虑加事务锁但当前阶段不用过度设计。3.4 搜索筛选与分页Django ORM的链式查询写法前台首页不能只展示所有宠物需要支持按品种、城市、性别、状态等条件筛选。Django ORM 的链式过滤非常适合这种场景from django.core.paginator import Paginator def pet_list_view(request): pets Pet.objects.filter(statusavailable) genre request.GET.get(genre) if genre: pets pets.filter(genregenre) city request.GET.get(city) if city: pets pets.filter(city__icontainscity) keyword request.GET.get(q) if keyword: pets pets.filter( models.Q(name__icontainskeyword) | models.Q(breed__icontainskeyword) | models.Q(description__icontainskeyword) ) pets pets.order_by(-created_at) paginator Paginator(pets, 12) page_number request.GET.get(page) page_obj paginator.get_page(page_number) context { page_obj: page_obj, genre: genre, city: city, keyword: keyword, } return render(request, pets/pet_list.html, context)关键词搜索里用了Q对象实现多字段模糊匹配这样用户输入“金毛”就能同时在名字、品种、描述里匹配。分页用Paginator把每页 12 条限制住模板里渲染页码时保留当前筛选参数很关键不然翻页之后筛选条件就丢了a href?page{{ page_obj.next_page_number }}genre{{ genre }}city{{ city }}q{{ keyword }}下一页/a3.5 后台管理Django Admin让管理变得极其省力这个系统里最惊喜的部分是 Django Admin几乎不需要额外写任何管理页面把模型注册到 admin 就能直接用。from django.contrib import admin from .models import Pet, AdoptionApplication, UserProfile admin.register(Pet) class PetAdmin(admin.ModelAdmin): list_display [name, genre, breed, status, city, created_at] list_filter [status, genre, is_neutered, is_vaccinated] search_fields [name, breed, city] list_editable [status] readonly_fields [created_at, updated_at] actions [mark_as_adopted] admin.action(description将选中的宠物标记为已领养) def mark_as_adopted(self, request, queryset): queryset.update(statusadopted)这里用list_editable [status]就可以在列表页直接下拉修改宠物状态不需要进入详情页运营人员用起来效率极高。list_filter按状态、种类筛选search_fields支持跨字段搜索这两个配置加完管理体验完全不输定制开发的后台。4. 开发环境搭建与项目初始化4.1 创建虚拟环境并安装Django整个项目从零开始搭尽量模拟最典型的开发流程。第一步是创建虚拟环境让项目的依赖和系统全局Python环境隔离。mkdir django_pet_adoption cd django_pet_adoption python -m venv venv # Windows激活 venv\Scripts\activate # macOS/Linux激活 source venv/bin/activate pip install django pillow pip freeze requirements.txtDjango 和 Pillow 是必需的两个包Pillow 是处理图片时必须的库。如果还会用到 MySQL 或 PostgreSQL提前把对应的驱动库也装好比如mysqlclient或psycopg2-binary。4.2 创建项目和应用django-admin startproject config . python manage.py startapp pets python manage.py startapp users python manage.py startapp applications我习惯把项目名叫config然后放在当前目录注意后面有个点这样项目配置目录和业务应用目录平级看起来更整洁。应用划分按业务边界来pets 管宠物模型users 管用户资料applications 管领养申请。也可以只建一个 app 把所有模型放里面项目小的时候没问题但一旦模型多了回头再拆就很费劲所以一开始就按模块拆开。4.3 settings.py 的关键配置创建完项目后需要修改 settings.py 里的几个地方INSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, pets, users, applications, ] # 数据库默认使用SQLite如果需要切换MySQL # DATABASES { # default: { # ENGINE: django.db.backends.mysql, # NAME: pet_adoption, # USER: root, # PASSWORD: your_password, # HOST: 127.0.0.1, # PORT: 3306, # } # } # 语言与时区 LANGUAGE_CODE zh-hans TIME_ZONE Asia/Shanghai USE_TZ True MEDIA_URL /media/ MEDIA_ROOT os.path.join(BASE_DIR, media)LANGUAGE_CODE zh-hans和TIME_ZONE Asia/Shanghai这两个一定要改Django 默认是英文和 UTC 时区不改成中文的话 Admin 后台和模板里的时间显示都会有问题。数据库方面本地开发用 SQLite 完全够用但生产环境建议切换成 PostgreSQL 或 MySQLSQLite 在并发写入场景下会锁表。4.4 数据库迁移与后台注册模型定义好之后执行迁移命令python manage.py makemigrations python manage.py migrate python manage.py createsuperuser遇到模型字段改动比较频繁的时候makemigrations 可能会提示检测到非可空字段但没有默认值解决方法是要么给字段加default参数要么在命令行交互过程中选择提供默认值。我通常在模型里直接写好default避免在终端里手动填空。把所有模型都注册到 admin 之后登录http://127.0.0.1:8000/admin/就能在后台看到数据表直接增删改查了。这一步做完基本的数据管理能力就有了后续再慢慢做前台页面对接开发节奏很舒服。5. 开发中的关键细节与模板渲染技巧5.1 Django模板中的静态资源引用Django 模板里引用 CSS、JS 和图片不能直接写相对路径要先用{% load static %}加载静态文件模块{% load static %} !DOCTYPE html html langzh-hans head link relstylesheet href{% static css/bootstrap.min.css %} /head body img src{% static images/logo.png %} altlogo img src{{ pet.cover_image.url }} alt{{ pet.name }} /body /html宠物图片用{{ pet.cover_image.url }}来获取这个属性是 ImageField 根据 MEDIA_URL 自动生成的完整访问路径。如果图片没上传cover_image为 None直接调用.url会报错所以模板里最好加个判断{% if pet.cover_image %} img src{{ pet.cover_image.url }} alt{{ pet.name }} {% else %} img src{% static images/default_pet.jpg %} alt暂无图片 {% endif %}5.2 CSRF 防护与表单提交Django 默认开启了 CSRF 防护所有以 POST 方式提交的模板表单必须带上{% csrf_token %}否则会返回 403 错误。这个机制是安全设计不要为了省事关掉。如果做前后端分离用 DRF 写接口的时候则需要在 Django 设置里配置好 CSRF 的 API 策略或者使用 JWT 认证方案那是另一套玩法了。5.3 用消息框架给用户操作反馈django.contrib.messages框架可以在操作成功或失败后给用户显示一次性提示。在试图里调用messages.success或messages.error然后在模板底部循环渲染{% if messages %} div classmessages {% for message in messages %} div classalert alert-{{ message.tags }} {{ message }} /div {% endfor %} /div {% endif %}这个功能在用户提交申请、修改资料、管理员审核操作等场景下非常实用比写死一个成功页面要友好得多。6. 项目部署上线与常见问题排查6.1 部署到服务器参考方案与基本思路项目在本地开发完成后要部署到服务器让它真正可用。常见做法是把 Django 项目放到云服务器上用 Gunicorn 或 uWSGI 运行再用 Nginx 反代并托管静态文件和媒体文件。也可以使用宝塔面板的 Python 项目管理器来简化流程。核心步骤大致是把项目代码传到服务器可以用 git clone 或者 FTP。在服务器上创建虚拟环境并安装依赖。收集静态文件python manage.py collectstatic。使用 Gunicorn 启动服务gunicorn config.wsgi:application -b 0.0.0.0:8000。在 Nginx 中配置反向代理把域名或端口的请求转发到 8000 端口。配置 /static/ 和 /media/ 目录的访问路径。关闭 DEBUG配置 ALLOWED_HOSTS 和数据库连接。如果对服务器配置不熟用django-admin自带的runserver仅适合本地调试生产环境千万不要直接跑 runserver性能和安全性都不行。6.2 常见问题速查表我把自己在开发过程中遇到的高频问题整理成了表格方便你直接对号入座问题现象可能原因解决方案上传图片后前端页面图片不显示没配置 MEDIA_URL / MEDIA_ROOT或开发环境没加 static 路由按上文配置 media 路径urls.py 加 static()表单提交返回 403缺少 {% csrf_token %}模板中表单里加上 CSRF token403 且请求包含 CSRF token 但失败Cookie 和 session 配置异常检查 settings.py 中 SESSION_COOKIE_SECURE 设置图片字段提示 Pillow 未安装缺少图片处理库pip install pillowrunserver 启动报错端口被占用8000 端口已被其他进程占用换端口python manage.py runserver 8001数据库迁移提示 non-nullable field新增非空字段但没有默认值给字段加 default 或 nullTrue中文页面乱码编码配置问题设置 LANGUAGE_CODEzh-hans页面加 meta charsetutf-8Admin 后台没有样式静态文件没配置好检查 STATIC_URL 和 django.contrib.staticfilesORM 查询返回的是 QuerySet 而不是对象误用了 filter 而没有 get/firstfilter 后用 first() 获取单条记录分页翻页后筛选条件丢失分页链接没有带上 GET 参数生成分页链接时保留原 query 参数6.3 几个值得注意的踩坑经验第一个坑是删库重来的代价。开发早期总是改模型字段直接删掉数据库重新 migrate 是很爽但一旦有了真实数据就不能这么干了。我在项目第二次部署后就不敢随便删库了遇到字段改动用makemigrations生成迁移文件必要时写数据迁移脚本保证历史数据不丢。第二个坑是 Admin 后台上传图片后不显示。明明上传成功了图片文件也能在 /media/ 目录里看到但页面上一刷新就 404。排查了一圈发现是开发模式下没有在 urlpatterns 里加static(MEDIA_URL, document_rootMEDIA_ROOT)。这个问题太典型了值得单独提醒。第三个坑是权限问题。Django 自带的login_required装饰器只能保证用户已登录不能区分用户的角色。如果要做管理员和普通用户的权限隔离需要自定义装饰器或者用user.is_staff判断。我在写申请审核接口时一开始只加了 login_required结果所有登录用户都能访问审核页面后来加了 is_staff 判断并做了 403 处理才堵住。6.4 关于后续扩展方向这个系统做到这里已经可以跑起来了但从长期使用的角度看还有几个扩展点一个是加消息通知管理员审核通过或拒绝时给用户发送系统通知另一个是增加收藏功能用户可以收藏心仪的宠物方便后续跟踪再一个是接入地图在前台展示救助站的地理位置。如果想把宠物领养做成一个互联网产品可以考虑把宠物信息通过开放接口共享给其他平台。但这些都是锦上添花的事情核心的领养闭环已经完整了。整个项目做下来我最大的体会是 Django 特别适合这种“管理后台前台展示流程审核”的项目所有功能模块都能在框架里找到对应的解决方案不需要从零造轮子。对我来说最值回票价的部分是 Django Admin 在内部管理上的巨大效率提升运营人员几乎没有学习成本就能管理好所有宠物和申请。如果你也正在做一个类似的信息管理类项目直接照着这个思路去搭能少走很多弯路。