尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python+Django宠物诊所管理系统开发实战:数据库设计与权限控制全解析

Python+Django宠物诊所管理系统开发实战:数据库设计与权限控制全解析 刚写完一个面向仓鼠、兔子、龙猫、豚鼠这类小型哺乳类宠物的诊所管理系统这也是很多同学拿来当毕业设计或者课程设计的经典题目。项目标题里带Python说明技术栈很明确但选Python只是开始真正决定系统好不好用的是业务拆得清不清楚、表结构设计得合不合理、流程跑得顺不顺。这篇文章我就以这套宠物医院管理系统为例从需求拆解、技术选型、数据库设计到核心功能实现完整走一遍。我会把每个模块“为什么这么做”讲清楚也会把实际开发里容易踩的坑列出来。不管你是要交毕设还是单纯想练手一个全栈信息管理系统这篇应该都能帮上忙。1. 项目整体设计与模块划分1.1 需求分析与角色拆解开始写代码之前先想清楚一件事这个系统到底是给谁用的。小型哺乳类宠物诊所和大型连锁宠物医院不一样规模小人员结构简单但业务链条是完整的。我按实际使用场景把使用者拆成了三类角色前台/导诊负责宠物主人信息登记、宠物建档、预约排号、收费结算。兽医/医生负责接诊、写病历、开处方、安排用药和复诊提醒。管理员负责员工账号分配、药品库存调整、数据统计和系统参数配置。这个角色拆解直接决定了系统的权限控制怎么做。比如前台只能新增客户和预约不能改病历医生可以录入诊断内容但不能改药品价格管理员什么都能做但日常用得最少。很多初学朋友做管理系统上来就把所有功能堆在一个页面里谁登录都能干所有事这样演示是方便了但项目答辩或者实际落地的时候会显得非常不专业。我建议哪怕是小系统权限设计一开始就得有至少做到“登录后根据角色显示不同菜单”。1.2 功能模块划分结合宠物诊所的实际业务我把系统分成六个核心模块用户管理员工账号的增删改查、密码重置、角色分配。客户与宠物档案记录主人信息和宠物信息一个主人可以登记多只宠物。预约挂号前台登记预约医生查看当日接诊列表支持改期和取消。诊疗管理医生写病历记录症状、诊断结果、开具处方关联药品和收费项目。药品库存管理药品入库、出库、库存预警每次开药自动扣减库存。统计报表按时间维度统计接诊量、营业额、药品消耗量支持可视化展示。这六个模块基本覆盖了诊所日常运营的全部环节。实际开发的时候模块边界一定要清晰。我当时踩过的坑是把药品管理和诊疗管理耦合在一起——医生直接在病历表里写药品名而不是关联药品ID结果后面想统计哪种药消耗量最多根本查不出来因为病历表的文本字段没法聚合。所以模块划分不只是功能上的分类更是数据层面的解耦。1.3 核心业务流程梳理系统里最重要的流程是“预约 → 接诊 → 诊断开药 → 收费 → 出库”这条链路把前台的预约和医生的诊疗串在了一起。前端页面展示出来的操作顺序大概是这样的客户带宠物到店或者电话预约前台账登记预约信息。到店后前台确认签到宠物状态变为“待接诊”。医生选择该宠物录入问诊信息、检查结果和诊断结论。医生开处方选择药品和数量系统自动判断库存是否足够。前台根据处方生成收费单客户付费后订单完成。药房或前台确认发药系统扣减对应药品库存。这个流程设计里有一个关键点收费和出库最好绑在一起或者至少是“收费后自动触发扣库存”。如果分开操作很容易出现收了钱忘记扣库存的情况时间一长药品账面数和实际数就对不上了。提示库存扣减不要做成“录入处方时直接扣”而是“收费完成后扣”。原因是有些处方可能因为客户最终没付费而被作废如果开处方就扣库存作废后还得做库存回滚平白多出一堆逻辑。2. 技术选型与开发环境搭建2.1 为什么用Python做这套管理系统选Python做这类信息管理系统核心原因就三个字效率高。Python不用像Java那样写一堆样板代码一个基本的增删改查接口Java可能要写Controller、Service、Mapper三层Python一个文件就能搞定。对于课程设计和毕业设计这种有时间限制的项目Python能把开发周期缩短一半。再者Python的生态太丰富了。做Web有Django和Flask做桌面端有Tkinter和PyQt做数据分析有Pandas做图表有Matplotlib和Pyecharts。如果做的是普通管理系统基本上每个环节都有现成库可以用不用自己造轮子。但说句实在话Python的缺点也很明显就是不适合做超大型高并发的业务系统。不过宠物诊所这种规模并发量也就是同时几个医生几个前台在用Python完全扛得住。2.2 Web端还是桌面端这是一个关键决策我见过很多同学一上来就问“用Tkinter还是Django”其实这个问题问反了。正确的思考顺序应该是先确定系统需要给谁用、在什么场景下用再决定技术形态。如果你做的是毕业设计我强烈推荐Web端。原因不只是Web端更好看而是演示效果更合理。答辩的时候你不需要在一台电脑上装好环境、打开桌面窗口只需要打开浏览器输入地址就能登录操作而且Web端可以随时截页面流程图放进论文里。桌面端用Tkinter或PyQt的好处是部署简单不用起服务双击就能跑。但缺点也明显界面丑、跨平台不友好、代码结构容易烂。如果你非要走桌面端建议用PyQt5Tkinter做出来的界面真的会拉低整个项目的观感。如果是Web端Django和Flask之间怎么选对比项DjangoFlask上手难度稍高自带ORM和Admin低灵活自由自带功能用户认证、Admin后台需要自己安装扩展数据库操作自带ORMSQLAlchemy适合场景功能完整的管理系统轻量接口服务我做这套系统用的Django。原因很简单Django自带后台管理、用户认证、ORM这些在管理系统里全都是刚需。Flask虽然灵活但用户登录、权限控制这些都要自己搭工作量翻倍。2.3 数据库选型MySQL还是SQLite数据库的选择同样看场景。SQLite是一个文件型数据库不需要安装服务建库就是生成一个文件特别适合以“交作业”为导向的项目。你不用让评委老师额外安装MySQL而且SQLite最省心的一点是数据都在本地文件里不会出现连接不上的问题。MySQL则是正经的关系型数据库支持多用户并发访问、有完善的权限体系、数据容量大。如果你做的项目要真正跑起来用MySQL显然更靠谱。我的建议很简单如果项目要长期运行、多人并发操作选MySQL如果只是为了写代码演示选SQLite足够。标题里的管理系统我用的MySQL因为要模拟真实的诊所运营场景多客户端同时登录是很正常的。不过要注意用MySQL一定要处理好编码问题。建库的时候要用utf8mb4否则写着写着就会出现中文乱码这个后面专门说。2.4 Python环境准备与依赖安装开发环境的搭建其实不难但很多新手倒在第一步——Python版本乱装一通装到后面包冲突了也不知道是版本问题。我建议直接用Python 3.8以上版本Django 4.x。装的话可以用Anaconda或者装官方Python后自己配pip。这里有一点经验不要在系统里同时保留多个共存的Python解释器用了Anaconda就统一用conda管理环境。我常用的环境创建命令conda create -n pet_clinic python3.8 -y conda activate pet_clinic pip install django mysqlclient django-simpleui如果网速一般建议加国内源会快得多pip install django -i https://pypi.tuna.tsinghua.edu.cn/simple装好之后可以用python -c import django; print(django.get_version())验证Django版本能正常输出版本号就说明环境没问题了。注意mysqlclient在Windows上安装经常会报编译错误解决办法是直接下载对应Python版本的whl文件或者改用pymysql并在Django的__init__.py里执行pymysql.install_as_MySQLdb()。个人建议直接装pymysql更省事。3. 数据库设计与核心实现3.1 数据表结构设计思路表结构设计是整个管理系统最核心的部分没有之一。表设计得烂后面写功能的时候处处别扭表设计得好后端代码能少写一半。我设计这套数据库的时候核心原则只有一条让每个业务实体独立成表不让多个含义混在一张表里。全系统一共设计了11张表用户表user登录账号、角色、联系方式宠物主人表owner主人姓名、电话、地址宠物信息表pet主人ID、宠物名、品种、性别、出生日期、体重、绝育状态预约表appointment宠物ID、预约日期、时间段、状态病历表medical_record宠物ID、医生ID、主诉、诊断结果、处理意见处方明细表prescription病历ID、药品ID、单次用量、使用频率、天数药品表drug药品名称、规格、库存量、预警阈值、单价收费单表payment病历ID、收费项目、总金额、收费时间、收费人收费细目表payment_item收费单ID、收费项目名、金额疫苗记录表vaccine宠物ID、疫苗名称、接种日期、下次接种日期系统日志表log操作人、操作内容、操作时间3.2 核心表结构与字段说明拿宠物信息表举个例子这是最常见也是容易被做糊的一张表。CREATE TABLE pet ( id INT PRIMARY KEY AUTO_INCREMENT, owner_id INT NOT NULL, name VARCHAR(50) NOT NULL, species VARCHAR(20) NOT NULL COMMENT 物种兔/仓鼠/龙猫/豚鼠等, breed VARCHAR(50) COMMENT 品种细分, gender TINYINT COMMENT 0未知 1公 2母, birth_date DATE, weight DECIMAL(5,2) COMMENT 体重单位kg, is_neutered TINYINT DEFAULT 0 COMMENT 是否绝育, pet_photo VARCHAR(255) COMMENT 宠物照片路径, created_time DATETIME, updated_time DATETIME, FOREIGN KEY (owner_id) REFERENCES owner(id) );注意这里的两个细节。第一个是物种字段不能用VARCHAR(255)存一堆文字而是用简短的枚举字符串。因为后面做统计的时候很可能要按物种分组查“不同宠物的接诊量占比”物种字段越规范统计越好写。第二个是体重字段用了DECIMAL(5,2)。小型哺乳动物的体重是特别重要的生命体征——一只600g的兔子和一只1.5kg的兔子很多药物剂量完全不同。所以体重要单列后面开药时要参考这个字段。病历表和处方明细表是这个系统里最核心的业务表。一张病历对应多张处方明细一个药品可以出现在多条处方里这就是典型的一对多和多对多关系用中间关联表来处理。3.3 Django模型定义示例前面建的是SQL语句实际用Django开发时不需要手写SQL直接定义模型类就够了。以下是我用Django重写核心模型的关键代码from django.db import models class Owner(models.Model): name models.CharField(max_length50, verbose_name主人姓名) phone models.CharField(max_length20, verbose_name联系电话) address models.CharField(max_length200, blankTrue, verbose_name联系地址) class Meta: db_table owner class Pet(models.Model): owner models.ForeignKey(Owner, on_deletemodels.CASCADE, verbose_name主人) name models.CharField(max_length50, verbose_name宠物名) species models.CharField(max_length20, verbose_name物种) breed models.CharField(max_length50, blankTrue, verbose_name品种) gender models.SmallIntegerField(choices[(0, 未知), (1, 公), (2, 母)], default0) birth_date models.DateField(nullTrue, blankTrue, verbose_name出生日期) weight models.DecimalField(max_digits5, decimal_places2, verbose_name体重(kg)) class Meta: db_table pet class Appointment(models.Model): pet models.ForeignKey(Pet, on_deletemodels.CASCADE, verbose_name宠物) app_date models.DateField(verbose_name预约日期) time_slot models.CharField(max_length20, verbose_name时间段) status models.IntegerField(choices[(0, 待接诊), (1, 已接诊), (2, 已取消)], default0) class Meta: db_table appointment定义好模型之后执行python manage.py makemigrations和python manage.py migrateDjango会自动帮你建表。这也是我推荐Django的一个原因——模型即表结构改模型之后数据库结构跟着更新省去手动维护SQL脚本的痛苦。4. 核心功能流程实现4.1 登录鉴权与权限控制登录认证我直接用Django自带的django.contrib.auth这个模块已经实现了用户表、会话管理、密码加密等功能不用重复造轮子。但Django自带用户表的字段有限要加上角色字段可以通过扩展User模型来实现from django.contrib.auth.models import AbstractUser class User(AbstractUser): ROLE_CHOICES ( (admin, 管理员), (doctor, 医生), (front, 前台), ) role models.CharField(max_length10, choicesROLE_CHOICES, defaultfront) class Meta: db_table user权限控制方面我用了装饰器对视图函数做角色校验代码很简洁from django.http import JsonResponse from functools import wraps def role_required(allowed_roles): def decorator(view_func): wraps(view_func) def _wrapped_view(request, *args, **kwargs): if not request.user.is_authenticated: return JsonResponse({code: 401, msg: 未登录}) if request.user.role not in allowed_roles: return JsonResponse({code: 403, msg: 无权限访问}) return view_func(request, *args, **kwargs) return _wrapped_view return decorator然后在具体视图上加装饰器就可以了role_required([doctor, admin]) def add_medical_record(request): # 只有医生和管理员可以写病历 pass这里强调一下前端的“隐藏菜单”只是用户体验的一部分真正的权限控制必须做在后端视图层。前端隐藏了入口但别人完全可以绕过页面直接访问接口。所以每个视图都要做校验别偷懒。4.2 宠物档案管理和小哺乳类宠物的特殊字段宠物档案模块看起来只是简单的增删改查但针对“小型哺乳类宠物”这个场景有几个细节必须做好。第一个是体重变化趋势。对于仓鼠、龙猫这类小体型动物体重是健康的重要指标。每只宠物每次就诊时都应该记体重系统里最好能展示每次就诊的体重变化曲线。我在模型里加了一个weight_history字段用JSON格式存储历史体重和记录日期class Pet(models.Model): weight_history models.JSONField(defaultlist, blankTrue)每次测完体重后端把新数据追加进去def update_weight(pet, new_weight): history pet.weight_history history.append({ weight: float(new_weight), date: datetime.now().strftime(%Y-%m-%d) }) pet.weight_history history pet.weight new_weight pet.save()第二个是寿命和年龄的展示。仓鼠一岁就算高龄了兔子可以活十年以上不能像猫狗那样统一按“几年几月”来算。系统里我做了物种对应的寿命参考区间展示宠物体况时会把当前年龄放到参考区间里比较自动标记为“幼年/成年/老年”。4.3 预约挂号与时间冲突校验预约功能很容易做成一坨“只插入一条记录什么都不管”的代码。但真实场景里同一时间段不能让两个宠物同时预约同一个医生所以预约前必须做冲突校验。核心代码逻辑大概是这样的app_date request.POST.get(app_date) time_slot request.POST.get(time_slot) doctor_id request.POST.get(doctor_id) exists Appointment.objects.filter( app_dateapp_date, time_slottime_slot, doctor_iddoctor_id, status__in[0, 1] # 待接诊和已接诊的都不允许冲突预约 ).exists() if exists: return JsonResponse({code: 400, msg: 该时间段已被预约请更换时间})另外预约状态的流转需要注意预约创建时是“待接诊”宠物到店签到后可以改成“已接诊”如果客户爽约了需要提供一个“未到店”或者“已取消”的状态按钮千万不能删记录。因为后面统计“每天到诊率”的时候爽约数据也是重要的统计来源。4.4 诊疗与开药流程针对小宠物的剂量计算诊疗模块是这个系统的业务核心也是最出彩的地方。因为小型哺乳类动物的用药和猫狗不一样体重基数小、药物代谢差异大兽用药很多情况下需要按体重精确折算。所以在医嘱描述里除了常规的症状和治疗方案我专门加了一张处方明细表记录每种药的“单次用量/次数/天数”并把“按体重给药剂量提醒”做了进去比如某类药物按每公斤体重0.1ml计算系统自动算出该宠物的实际用量。def calculate_dose(pet_weight_kg, dose_per_kg, drug_conc_ml): # 宠物体重(kg) * 每kg剂量(mg/kg) / 药物浓度(mg/ml) 实际抽取体积(ml) dose_mg pet_weight_kg * dose_per_kg volume_ml dose_mg / drug_conc_ml return round(volume_ml, 2)这个功能看起来不大但在答辩的时候能体现你对业务的理解——不只是机械地做增删改查而是理解了宠物医疗场景下的实际需求。当时我把这个逻辑写进去之后评委老师明显对系统的完成度和专业性给分更高。开药时的库存判断逻辑也在这里drug Drug.objects.get(iddrug_id) if drug.stock target_num: return JsonResponse({code: 400, msg: f{drug.name}库存不足当前库存仅剩{drug.stock}})开药提交成功之后只是预占库存实际扣减放在收费完成时执行这个逻辑前面已经讲过了。4.5 收费与库存联动收费模块直接关系到整个系统的数据闭环。流程是这样的医生提交病历生成收费单草稿。前台核对收费单里的收费项目诊疗费、药费、检查费等。客户确认付款前台点击“确认收款”。系统标记收费单为已支付同时扣减库存。扣库存的代码我放在了收费确认的视图里role_required([front, admin]) def confirm_payment(request, payment_id): payment Payment.objects.select_related(prescription).get(idpayment_id) prescription_items PrescriptionItem.objects.filter(prescriptionpayment.prescription) for item in prescription_items: drug Drug.objects.get(iditem.drug_id) if drug.stock item.quantity: return JsonResponse({code: 400, msg: f{drug.name}库存不足}) drug.stock - item.quantity drug.save() payment.status 1 payment.save() return JsonResponse({code: 200, msg: 收费成功库存已更新})这里有一个很容易被忽略的问题如果同一个药品被两个收费单同时扣库存会出现并发超卖。虽然诊所场景并发量极低但课程设计里聊到这个问题能说清楚“用数据库事务行锁可以解决”就会是一个加分的亮点。from django.db import transaction with transaction.atomic(): drug Drug.objects.select_for_update().get(iditem.drug_id) if drug.stock item.quantity: raise Exception(库存不足) drug.stock - item.quantity drug.save()4.6 库存预警与统计报表当药品库存低于预警阈值时系统需要给管理员一个提醒。实现方案有两个层次登录时检查管理员登录系统后在首页展示“库存预警药品列表”。页面实时显示在药品管理列表页用颜色区分库存状态库存低于阈值的行标红。统计报表用Django ORM聚合配合ECharts展示我统计了这些维度每月接诊量趋势按月分组统计pet数量收入组成分布诊疗费 vs 药费 vs 检查费不同物种接诊占比仓鼠、兔、龙猫、豚鼠等常用药品Top10Django ORM聚合查询示例from django.db.models import Count from django.db.models.functions import TruncMonth monthly_stats MedicalRecord.objects.annotate( monthTruncMonth(create_time) ).values(month).annotate( countCount(id) ).order_by(month)把统计结果转成JSON传给前端模板用ECharts画折线图和饼图效果比表格直接展示好一个档次论文截图也好看。5. 常见问题与排查技巧实录5.1 中文乱码问题中文乱码是Python Web开发里最经典的老问题最常见的有两个场景。第一个是MySQL建库的时候字符集没设置成utf8mb4导致写入中文后查询结果显示问号或者乱码。解决办法很直接建库时指定CREATE DATABASE pet_clinic DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;第二个场景是Django的settings.py里没配置数据库连接为utf8mb4。在Django里连MySQL最好在OPTIONS里显式指定DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: pet_clinic, USER: root, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, OPTIONS: {charset: utf8mb4}, } }5.2 数据库连接报错Access denied与driver问题新手最常见的报错就是Access denied for user rootlocalhost通常是MySQL密码输错了或者用户权限没给够。解决办法是登录MySQL后给root用户授权GRANT ALL PRIVILEGES ON *.* TO rootlocalhost IDENTIFIED BY 你的密码; FLUSH PRIVILEGES;如果你本来就用root却还报权限错误检查一下是不是密码里有特殊字符被Python解析出错了。实在不行就换SQLite做演示至少不会因为数据库连不上影响答辩。5.3 日期时间格式问题DateField在HTML表单中默认显示格式是YYYY-MM-DD但很多浏览器上日期选择器的格式跟Django默认格式不同经常出现format problem之类的报错。解决办法有两个一是前端用typedate的input输入框浏览器会自动规范格式二是在Django的settings.py里设置DATE_INPUT_FORMATS [%Y-%m-%d, %m/%d/%Y, %d/%m/%Y]另外做时间范围统计时如果直接用create_time__date过滤在大数据量场景下会慢。建议在数据库表里额外存一个date类型的字段专门用于按天统计索引性能会好很多。5.4 页面长时间无操作后登录失效Django默认的会话过期时间是两周听起来很久但实际上很多人部署到服务器上就发现一个问题长时间不操作后第一次提交表单页面会提示CSRF校验失败。这是因为Django默认开启了CSRF保护表单里都有{% csrf_token %}。页面上CSRF token过期后提交就会失败。解决办法是多写一个全局异常处理遇到CSRFError时返回一个友好的提示让用户刷新页面重新登录。5.5 系统部署与演示的小技巧课程设计答辩的时候最尴尬的事情就是现场演示时环境出问题。我吃过一次亏写了好几千字经验核心就是提前准备好“零依赖演示模式”。具体做法是做一个演示配置只要在settings.py里改一行将数据库从MySQL切换到SQLite再执行一两行命令系统就能在没有任何数据库服务的机器上跑起来。这套演示模式配合pip freeze requirements.txt导出的依赖清单到哪台电脑都能五分钟内跑起来答辩再也不用看网速和数据库服务的脸色。另外如果你要部署到服务器上给别人访问记得关闭DEBUG False并且收集静态文件python manage.py collectstatic这几个命令虽然基础但很多人到部署的时候才发现忘记配置临时搜教程浪费时间。最后想说的几句实在话这套系统我从需求分析到编码完成前后大概用了两周时间。实际写下来最大的感受是管理系统的代码本身并不难难的是把业务逻辑理清楚、把表之间的关系设计好。如果你正在做类似的项目我的建议是先在纸上把业务流程画一遍把每个角色的操作场景走一遍再动手写代码。别一上来就建Django项目顺序反了会走很多弯路。另外一个实用的小技巧系统全部跑通之后记得往数据库里录几组真实的测试数据比如不同物种的宠物档案、连续三个月的接诊记录、带病历和处方明细的治疗记录。有真实数据做支撑页面上的表格、图表、统计报表才展示得出效果答辩和展示的说服力完全不一样。最后再送一个经验整个项目完成之后花一个晚上把代码里所有写死的菜单、按钮、标题检查一遍改成中英文统一、命名规范的状态。代码规范和界面细节看似不重要但就是这些细节决定了整个项目的质感。
返回列表