基于Django的医院药物管理系统开发实战:从需求到部署 1. 项目背景与技术选型思路做医疗类Web项目尤其是药物管理系统第一反应往往是“安全”“合规”“稳定”这几个词。我这次接到的课题是用Django做一套新型医院药物管理系统标题里带着“基于Python”和“Django”两个明确的技术标签说明技术栈已经被圈定了。先说说为什么这类系统在2024年前后选择Django仍然是合理且稳妥的。医院药物管理系统本质上是一个典型的业务管理系统核心特征是多角色权限、高频读写、复杂的数据关联关系、严格的审计要求。Django自带Admin后台、ORM、权限框架、迁移机制这些能力恰好覆盖了医疗业务系统的大部分通用需求。我见过不少团队在这种项目里纠结要不要用FastAPI要不要前后端分离要不要上微服务我的观点很明确——如果目标是一个校级课题、毕业设计或者中小型医院的实用系统Django单体架构是最不容易翻车的方案。原因有三点第一Django的ORM对复杂关系建模的支持非常成熟。药物、库存、处方、供应商之间普遍存在多对多和一对多交织的关系Django的模型定义和查询API能把这些关联处理得很干净。第二内置Admin后台能极大降低管理端的开发成本。医院信息科的人需要维护药品目录、供应商资料、科室信息这类CRUD界面如果全部手写工作量会非常可观。用Django Admin定制一套管理界面几乎开箱即用。第三生态配套完善。认证、权限、日志、表单验证、缓存、任务队列CeleryDjango周边都有成熟的集成方案不需要从零造轮子。选型时还有一个容易被忽略的点团队的技术熟悉度。如果你手头的开发者对Django的MTV模式很熟开发效率会远高于现学现卖其他框架。这个项目我最终确认的技术栈是这样的技术项选型结果选择理由后端框架Django 4.2 LTS长期维护ORM稳定内置认证和Admin数据库MySQL 8.0医院场景普遍用MySQLInnoDB支持事务前端Bootstrap 5 jQuery后台管理系统追求实用无需上重型前端框架缓存Redis用于验证码、登录限流、药品热门缓存定时任务Celery Beat药品近效期、库存预警的每日巡检部署Nginx Gunicorn简单可靠单机部署即可覆盖中小医院场景技术方案确定后下一步就是梳理系统的核心需求。药物管理系统听起来简单真正铺开就会发现里面的业务分支非常多药品入库、出库、退药、盘点、近效期管理、处方开立与审核、库存上下限预警、供应商管理、用药统计报表……如果需求不预先收敛项目极容易失控。我当时做了一件事——把需求按优先级分成三个批次。第一批次是药品信息管理、库存管理、处方开立与审核这是系统的地基第二批次是供应商管理、药品预警、统计报表第三批次是操作日志审计、数据导入导出、多院区扩展。前两个批次做扎实这个系统已经能支撑日常业务运转后面的能力可以根据实际需要慢慢补齐。2. 系统核心模块与功能拆解需求梳理清楚之后我开始细化功能模块。这套药物管理系统最终落地的功能架构分为六大模块每个模块的职责边界都尽量划清楚避免功能交叉导致后期维护混乱。2.1 用户与权限模块用户角色分为四类系统管理员、药师、医生、护士。不同角色看到的操作界面和数据范围完全不同。Django自带的User模型能满足基础需求但为了扩展岗位、科室、工号等医院特有信息我采用了继承AbstractUser的方式新增了department科室、employee_id工号、phone联系电话等字段。权限控制没有完全依赖Django默认的Group和Permission而是结合了三层控制体系第一层登录校验。所有视图统一使用login_required装饰器未登录用户一律拦截。第二层角色校验。通过自定义装饰器或Mixin判断当前用户的角色类型例如只有药师角色能访问药物审核相关的视图。第三层数据权限。部分场景下需要细粒度过滤例如医生只能查看自己开立的处方护士只能操作本科室的库存。第三层看起来简单实际做起来非常容易出Bug。我踩过一次坑某个视图原本直接Medication.objects.filter(created_byrequest.user)后来需求变成“医生能看自己开的护士长能看全科室的”直接把过滤逻辑写散在多个视图里导致某次上线后普通护士看到了全院患者的用药记录。后来我统一封装了一个get_medication_queryset(request)的公共方法所有视图都从它获取初始QuerySet权限逻辑集中管理问题才算根治。2.2 药品信息管理模块药品信息是整个系统的数据基础。每个药品需要维护的属性非常多我拆成了“基础属性”和“扩展属性”两张表。基础属性表存放通用名、商品名、剂型、规格、生产厂家、批准文号、药品编码。扩展属性表存放适应症、用法用量、禁忌、不良反应描述、储存条件。为什么不一股脑塞进一张表因为药品的不良反应、适应症等长文本字段不是每个药品都填得满而且与其他表的关联模式不同。拆表后药品主表保持精简查询性能会好很多。药品编码我采用的是“拼音首字母分类编号”策略例如头孢呋辛酯片对应BGZXZP001。生成逻辑由系统自动完成避免人工录入重复。前期调研时我发现不少医院还在用完全无规则的流水号药师使用时只能靠重复搜索来定位药品体验非常差。自动编码配合搜索框的模糊查询支持编码、通用名、拼音首字母能让日常检索效率提升一大截。2.3 库存管理模块库存管理模块是这个系统里业务逻辑最复杂的一块因为每一条库存变动都关联着多张表的联动更新。入库操作涉及入库单创建、库存表的数量增加、库存流水表新增记录、供应商应付账款的记录。出库操作涉及出库单创建、对应批次的库存扣减、流水记录。退药操作则要处理原批次的库存回补和退款标记。这里的关键设计是批号和有效期必须贯穿始终。医院里同一药品可能有多批多次入库批次不同则有效期不同。如果库存只记录总量出库时很难做到“近效期先出”也就是医药供应链里的FEFO原则。所以我的库存表以药品 批号为最小粒度每次出库都要显式指定从哪个批次扣减。FEFO的逻辑我这样实现的定义每一笔出库请求先查询所有可用批次按有效期升序排列逐一尝试分配库存数量当订单数量大于当前批次可用量时继续找下一批。整个过程在事务中完成配合select_for_update()加行锁确保并发场景下不同窗口同时出库同一批次时不会把库存扣成负数。2.4 处方管理与发药模块医生开立处方之后处方进入待审核状态由药师审核通过后方可发药。这个流程模拟的是真实的门诊药房工作流。处方单的核心字段包括患者信息关联患者表、开立医生、审核药师、处方状态、处方明细列表。患者信息独立建表不直接绑在User上因为患者不登录系统只是被管理的对象。处方明细表与药品表关联记录药品、单价、数量、用法用量描述。处方状态的流转我用了一个状态字段取值包括待审核、审核通过、已发药、已退回。药师审核时可以填写退回原因医生端看到退回记录后可进行修改再次提交。这里要特别注意处方提交时间和审核时间都要记录后期做处方点评或用药分析时这些时间戳都是原始数据。发药操作与出库操作联动发药时系统自动创建一张出库单并锁定该处方对应的药品库存。如果一个处方里某种药库存不足系统会提示“库存不足请通知补货”此时药师可以部分发药或者拒绝发药。2.5 预警与统计报表模块药品预警模块的价值在真实场景中体现得非常明显——医院对“缺药”和“近效期”的零容忍度极高。我用Celery配置了三个每日任务库存下限预警检查各药品当前库存是否低于设定的安全库存线低于则生成预警记录并通知采购人员。近效期预警药品距有效期不足90天、60天、30天分别触发不同级别的预警。药品失效处理有效期已过的批次自动标记为不可用状态禁止出库。预警记录存放在单独的预警表中每条记录关联药品、批号、预警类型、预警级别、处理状态。采购人员处理完一笔预警后系统会记录处理人ID和处理时间形成管理闭环。统计报表这部分我开始时做得比较粗糙——直接写了几个SQL查询统计月度出入库总量。药品分类统计、科室用药排名、医师开药频次这些维度分散在多个视图里维护成本很高。后来我把报表逻辑统一收敛到一个report服务模块通过原生SQL Pandas做数据处理在前端用简单图表展示。报表的核心目标不是好看而是让管理者一屏看清“药品周转率、短缺药品清单、近效期药品分布”这三个核心指标。2.6 操作日志与审计模块医疗系统的审计要求远高于普通管理系统。《药品管理法》里的相关条款和医院的内部管理制度都要求关键操作可追溯。我实现了两类日志异常登录日志记录登录IP、时间、结果成功/失败。连续失败5次则锁定账号15分钟。关键业务日志记录入库、出库、审核、修改药品信息等重要操作的操作人、操作时间、操作前后数据的摘要。业务日志的实现没有直接在每个视图里写死日志代码而是通过装饰器统一处理。核心操作视图上挂一个operation_log装饰器装饰器内部捕获函数的入参和返回值生成结构化日志。这样业务代码保持干净审计信息也不会遗漏。3. 数据库设计与关键实现细节数据库设计是整个系统的蓝图表关系如果后期推倒重来代价是灾难级的。我最终的设计方案包含14张核心表这里挑最关键的几张表讲清楚。3.1 核心表结构说明药品表、库存表、处方表这三者之间的关联是整个数据库的心脏。下面是不需要完整建表语句、但能讲清楚字段含义的精简结构表名核心字段说明Medication药品id, code, name, generic_name, spec, manufacturer, approval_number, dosage_form, unit, created_at药品基础档案MedicationBatch药品批次id, medication_id(FK), batch_no, expire_date, quantity, entry_date每批药的独立库存追踪Supplier供应商id, name, contact, phone, address, status供应商档案StockEntry入库单id, supplier_id(FK), operator_id(FK), entry_date, remark入库头表StockEntryItem入库明细id, entry_id(FK), batch_id(FK), quantity, purchase_price入库明细明细层级才关联到批号Prescription处方id, patient_name, patient_age, patient_gender, doctor_id(FK), pharmacist_id(FK), status, total_amount, created_at处方主表PrescriptionItem处方明细id, prescription_id(FK), batch_id(FK), quantity, unit_price, usage明细带批号方便发药扣减StockOutRecord出库流水id, prescription_id(FK), batch_id(FK), quantity, operator_id(FK), out_time出库流水永久保留有一个字段的设计我反复权衡了很久——处方明细里直接关联batch_id。为什么不只关联药品ID让发药时临时找批次如果只是存储维度确实关联药品ID就够了。但从发药操作的准确性来看处方明细里记录落库时的批号才能保证“医生开的那批药就是后来发的那批药”。医院药房实际场景中不同批次价格可能不同药效完全一样但批号不同追溯时如果没有批号信息事后核实就会很麻烦。因此我坚持在处方明细中保存批号快照。3.2 Django模型设计要点Django的模型代码我直接贴几个关键点供参考。继承AbstractUser扩展用户模型from django.contrib.auth.models import AbstractUser from django.db import models class User(AbstractUser): 自定义用户模型扩展医院业务字段 ROLE_CHOICES ( (admin, 系统管理员), (pharmacist, 药师), (doctor, 医生), (nurse, 护士), ) role models.CharField(max_length20, choicesROLE_CHOICES, verbose_name角色) department models.CharField(max_length100, verbose_name科室, blankTrue) employee_id models.CharField(max_length30, uniqueTrue, verbose_name工号) phone models.CharField(max_length20, blankTrue, verbose_name联系电话) class Meta: db_table sys_user verbose_name 用户 verbose_name_plural 用户药品批次模型和库存模型合并设计class MedicationBatch(models.Model): medication models.ForeignKey(Medication, on_deletemodels.CASCADE, verbose_name药品) batch_no models.CharField(max_length100, verbose_name批号) expire_date models.DateField(verbose_name有效期) quantity models.PositiveIntegerField(default0, verbose_name当前可用数量) entry_date models.DateField(auto_now_addTrue, verbose_name入库日期) status models.CharField(max_length20, defaultactive, verbose_name状态) # active/disabled class Meta: db_table med_batch unique_together (medication, batch_no) ordering [expire_date] # 近效期批次排在前面 def __str__(self): return f{self.medication.name} - {self.batch_no} (有效期:{self.expire_date})这里有个设计细节值得注意ordering [expire_date]让默认查询顺序直接按效期升序取出批次列表时第一个就是最近期的批次天然适配“近效期先出”的业务要求。处方模型的关联字段设置class Prescription(models.Model): STATUS_CHOICES ( (pending, 待审核), (approved, 审核通过), (dispensed, 已发药), (rejected, 已退回), ) patient_name models.CharField(max_length50, verbose_name患者姓名) patient_age models.IntegerField(verbose_name患者年龄) patient_gender models.CharField(max_length10, verbose_name患者性别) doctor models.ForeignKey(User, on_deletemodels.PROTECT, related_nameprescriptions_doctor, verbose_name开立医生) pharmacist models.ForeignKey(User, on_deletemodels.PROTECT, nullTrue, blankTrue, related_nameprescriptions_pharmacist, verbose_name审核药师) status models.CharField(max_length20, choicesSTATUS_CHOICES, defaultpending, verbose_name处方状态) total_amount models.DecimalField(max_digits10, decimal_places2, default0, verbose_name总金额) reject_reason models.TextField(blankTrue, verbose_name退回原因) created_at models.DateTimeField(auto_now_addTrue, verbose_name开立时间) reviewed_at models.DateTimeField(nullTrue, blankTrue, verbose_name审核时间) class Meta: db_table med_prescriptionon_deletemodels.PROTECT是一个值得强调的细节。医生或药师用户的数据被处方引用后不能直接删除该用户记录否则历史处方会变成“无主数据”。PROTECT会让删除操作抛异常并提示先处理关联数据这对医疗系统的数据完整性是必要的保护。3.3 事务与并发扣库存的实战处理医院药房的高峰期并发出库多个窗口同时操作同一个药品批次库存扣成负数的事我听说过不少。这里把并发处理的关键代码写出来from django.db import transaction from django.db.models import F def deduct_stock_from_batch(batch_id, quantity, operator, prescription): 从指定批次扣减库存使用行锁保证并发安全 with transaction.atomic(): try: # select_for_update 锁定该批次行直到事务结束 batch MedicationBatch.objects.select_for_update().get(idbatch_id, statusactive) except MedicationBatch.DoesNotExist: raise ValueError(批次不存在或已停用) if batch.quantity quantity: raise ValueError(f库存不足当前可用 {batch.quantity}) # 使用 F 表达式做原子更新避免条件竞争 batch.quantity F(quantity) - quantity batch.save(update_fields[quantity]) # 刷新最新值 batch.refresh_from_db() # 写出发药出库记录 StockOutRecord.objects.create( prescriptionprescription, batchbatch, quantityquantity, operatoroperator ) return batch.quantity这段代码里有两个关键机制select_for_update()在事务内锁定被选中的行其他事务必须等当前事务提交或回滚后才能操作同一行F(quantity) - quantity将扣减操作转换为SQL层的原子操作而不是先从数据库读出数值在Python里加减再写回。两者叠加无论多少并发请求同时扣这一批次的库存都不会出错。实际压测过100个并发线程同时扣减同批次仅剩50个的库存最终结果是50个请求成功50个请求抛出库存不足异常库存不会变成负数。这个效果基本能达到药房高峰期的可靠性要求。4. 权限控制、缓存加速与安全防护实战医疗系统的数据安全要求极高用药记录一旦泄露就是严重事故。除了Django自带的认证机制我还补充了几项针对性的安全加固。4.1 RBAC权限控制方案落地Django默认的Group Permission机制能解决大部分权限问题但实际用起来还是有一点笨重——每一个视图函数都要逐个permission_required装饰权限列表极其冗长。我选择做了一个轻量级的RBAC封装在User模型上扩展role字段后直接配置了一组角色到URL切面的映射from django.contrib.auth.decorators import login_required from django.core.exceptions import PermissionDenied from functools import wraps def role_required(roles): 自定义角色校验装饰器 用法 role_required([pharmacist, admin]) def pharmacy_audit_view(request): ... def decorator(view_func): wraps(view_func) login_required def _wrapped_view(request, *args, **kwargs): if request.user.role not in roles: raise PermissionDenied(您没有权限执行此操作) return view_func(request, *args, **kwargs) return _wrapped_view return decorator角色校验统一走这个装饰器逻辑集中在一个地方比分散判断清爽得多。实际开发的时候遇到过一个坑部分视图同时需要“药师或管理员”角色而部分视图只要“管理员”如果装饰器不支持角色列表传入就无法复用。因此上面这段代码用了roles列表参数一个装饰器覆盖所有组合场景。4.2 缓存策略查多写少的加速方案药品目录和科室信息这类数据的读频率极高写频率几乎为零天然适合Redis缓存。我的缓存策略是这样的药品目录缓存缓存所有状态正常的药品基础信息列表Key为medication:all_active过期时间24小时。药品信息发生变更时主动清除缓存。热门药品索引按药品通用名字母序维护一个热门药品ID列表前台医师开药时下拉框直接读缓存响应时间从300ms降到5ms以下。科室列表前端科室下拉框几乎每页都要用到同样缓存处理。缓存清理的时机很重要。如果只是在视图里读到缓存、没有主动失效机制药品信息修改后医院药师看到的可能还是旧数据——这比没有缓存还要危险。我在药品信息编辑保存逻辑后面加了一行cache.delete(medication:all_active)确保变更即时生效。import redis r redis.Redis(host127.0.0.1, port6379, db1, decode_responsesTrue) def get_all_active_medications(): 优先读缓存miss后再查询数据库并回填缓存 cache_key medication:all_active cached r.get(cache_key) if cached: return json.loads(cached) meds list(Medication.objects.filter(statusactive).values(id, name, spec, code)) r.set(cache_key, json.dumps(meds), ex86400) return meds4.3 登录安全加固医疗系统的登录接口没人希望被暴力破解。我做了三层防护每一层都不复杂但组合效果立竿见影第一层Django内置的AuthenticationForm基础上增加验证码校验。验证码使用django-simple-captcha插件生成并存储到Redis刷新验证码时同步刷新缓存。第二层IP用户名维度限流。同一个IP在5分钟内尝试登录超过10次就锁定该IP同一个用户名在5分钟内失败超过5次则锁定该用户名一段时间。这里不靠自研算法直接使用django-ratelimit库实现简洁可靠。第三层登录成功后更新Session有效时间和CSRF令牌。用户角色变更、权限变化这类场景下旧的Session必须作废强制重新登录。# settings.py 中相关配置参考 LOGIN_URL /accounts/login/ LOGIN_REDIRECT_URL /dashboard/ LOGOUT_REDIRECT_URL /accounts/login/ # session过期时间秒设定为8小时 SESSION_COOKIE_AGE 8 * 60 * 60 # 但如果用户不活跃超过30分钟就失效 SESSION_SAVE_EVERY_REQUEST TrueSESSION_SAVE_EVERY_REQUEST True这个配置值得多说一句。它会让Django在每次请求时把Session的过期时间续期到8小时后用户只要保持活跃就不会无辜掉线但如果离开了30分钟以上再回来Session已经过期需要重新登录。这个策略既保证了空闲用户的便捷性又防止长期不活跃的会话常驻系统安全性和体验兼顾。4.4 数据脱敏与审计要求处方上的患者姓名、病案号属于敏感信息页面展示时应该按需脱敏。我在模板层做了一个过滤器姓名只显示第一个字和后两字中间用星号代替例如“张**”。但要注意一点药师在核对发药时必须能看到完整姓名否则无法确认患者身份。所以脱敏规则不是一刀切的而是根据当前用户角色决定。具体实现用Django自定义模板过滤器from django import template import re register template.Library() register.filter(name mask_name) def mask_name(value, role): 根据角色决定是否掩码角色非药师/管理员时脱敏 if not value: return value if role in (pharmacist, admin, doctor): return value return value[0] * * (len(value) - 1)关键业务日志的记录还有一个容易遗漏的细节操作日志的IP地址应该取经过反向代理后的真实IP而不是Nginx的127.0.0.1。在Django配置中加上USE_X_FORWARDED_HOST True并确保Nginx配置了X-Forwarded-For头否则后期审计时所有操作都显示来自本机排查价值会大打折扣。5. 前端页面交互与工作流设计后台管理系统的前端很难做得惊艳但至少在交互体验上要顺畅尤其是高频操作流程。这里我不详细列所有页面挑几个核心页面的交互设计思路。5.1 发药操作台一步走完的流程设计传统药房发药页面最常见的问题就是药师需要在多个页面跳转——先找出库单再找库存再判断是否可发。这种割裂的操作流程在高峰期会让药师非常烦躁。我设计的发药操作台是一个单页面顶部是搜索框支持扫描药品包装上的二维码或条形码快速定位患者处方。中间是当前选中处方的明细列表每种药后面直接显示可用批次和数量。药师点击“发药”按钮后弹确认框并提示“若库存不足请检查批次有效期”。整个操作链在3次点击内完成。发布之后我观察了一下使用反馈药师的学习成本很低基本不需要培训就能上手。5.2 入库单录入批量操作的简化方案入库单录入如果一次只录一条药品信息一车药品到货时药师要录几十次体验极差。我做了表格模式录入点“新增一行”就多出一行输入框支持复制粘贴Excel表格数据一次可以提交多行入库明细。提交前在前端做批量校验药品编码是否存在、批次是否重复、数量是否为正整数、有效期是否晚于当天。校验通过后才整体提交给后端。前端用了原生JavaScript没有依赖重型框架。因为后台系统的核心价值不在炫酷动效而在于信息密度和操作效率复杂的SPA改造反而会让页面变慢、维护成本翻倍。5.3 预警看板一眼看清全局预警看板放的是库存下限预警和近效期预警的实时概览。顶部放三张统计卡片——红色背景显示“严重预警数量”黄色背景显示“库存不足药品数量”绿色背景显示“临期30天药品数量”。下方是预警明细列表支持按预警级别筛选和按处理状态筛选。这个页面的数据请求频率是每分钟一次自动刷新用jQuery的$.getJSON拉取接口数据局部渲染。部署上线后药库管理员反馈最多的就是“这个看板太有用了以前要自己每天翻库存表现在开门先看看板”。5.4 响应式适配的取舍医院内部药房场景几乎都是台式机或笔记本操作极少有人用手机处理药品出入库。所以我没有做深度的移动端适配只保证页面在1366px以上分辨率下显示正常。否则为了兼容手机会牺牲PC端的信息密度本末倒置。6. 实测过程、生产部署与常见问题排查开发完成之后我从本地环境到正式环境做了完整的测试与部署整个过程遇到了不少问题挑几个有代表性的记录一下。6.1 测试环境准备与数据初始化本地开发用的SQLite测试通过后切换MySQL时遇到了第一个坑SQLite的日期时间字段与MySQL的行为不一致部分DateTimeField的查询在SQLite正常但MySQL报错。解决方案很粗暴从一开始就用MySQL Docker Compose来开发避免环境不一致带来的隐性Bug。测试数据方面我写了一个自定义的management commandDjango命令一键生成模拟数据20个用户、150种药品、40个批次、300条处方、500条出入库记录。每次测试前导入这些数据能极大提升效率不需要手工造数据。6.2 部署架构与关键配置部署架构采用的是最经典的单机方案Nginx(80端口) - Gunicorn(127.0.0.1:8000) - Django/MySQL/RedisGunicorn的worker配置gunicorn config.wsgi:application -w 3 -b 127.0.0.1:8000 --timeout 60-w 3代表3个worker进程Nginx的client_max_body_size设置成10M足够上传药品图片。静态文件通过collectstatic收集到独立目录由Nginx直接服务。Django的settings.py中有几个配置必须确认DEBUG False ALLOWED_HOSTS [your-domain.com, localhost, 127.0.0.1] SECURE_PROXY_SSL_HEADER (HTTP_X_FORWARDED_PROTO, https) CSRF_TRUSTED_ORIGINS [https://your-domain.com]SECURE_PROXY_SSL_HEADER这个配置容易漏。如果Nginx做了HTTPS代理转发而Django没有这个配置所有请求都会被当成HTTP导致部分浏览器安全策略拒绝请求。这种问题排查起来非常烧脑因为页面上不会报任何明显的错误。6.3 常见问题与排查技巧这里把实测过程中遇到的高频问题按“问题现象-根因-解决方案”格式整理成一张速查表问题现象根因分析解决方案发药时提示库存不足但查库显示有货并发扣减导致超卖使用select_for_update()加行锁药品近效期预警不触发Celery Beat定时任务未启动检查celery -A config beat进程并确认时区设为Asia/Shanghai登录后频繁掉线Session过期时间设置的过短SESSION_COOKIE_AGE延长到8小时配合SESSION_SAVE_EVERY_REQUEST前端下拉框加载慢每次请求都查数据库Redis缓存药品目录减少数据库压力静态文件404Nginx没有配置静态目录转发检查collectstatic输出并确认Nginx配置正确指向该目录上传图片失败显示413Nginx默认client_max_body_size过小设置client_max_body_size 10m管理员删用户时报错处方外键使用了PROTECT保护将用户状态停用而不是物理删除软删除更符合审计要求时间差了8小时MySQL时区与Django时区不一致TIME_ZONE Asia/ShanghaiMySQL连接参数OPTIONS中指定init_command设置会话时区另一个特别容易踩的坑是DateTimeField的自动现在时间。Django的auto_now_addTrue使用的是服务器本地时间如果服务器时区是UTC保存到MySQL后页面显示就会差8小时。务必在settings.py中将TIME_ZONE设置为Asia/Shanghai并且MySQL连接时加上init_command: SET time_zone 08:00才能从根本上保证时间一致。6.4 性能压测结果项目完成阶段用locust做了一轮简单的性能压测。测试场景设定为50个并发用户、持续5分钟模拟药师持续进行处方查询和发药操作。结果平均响应时间192msP95响应时间350ms数据库连接池正常未出现连接数打满的情况。对于一套医院内部系统来说这个性能已经足够。如果后续要支撑全院上千名医生同时在线开药可以加一层Redis缓存策略把药品查询接口的响应时间进一步压到10ms内。另外把数据库读写分离、加一台只读从库也可以解决报表查询拖垮主库的问题——但这些都是系统上线后按需演进的方向一上来就全部铺开反而增加复杂度。7. 一点个人体会整个项目从需求梳理到部署上线历时大约六周。我最大的感受是这套系统的难点不在写代码而在把医院药房的真实业务逻辑想透。举个例子发药与出库的联动看着简单但只有真正去过药房、见过药师拿着一张处方反复检查库存批次的操作才会理解为什么处方明细要关联批号为什么发药前必须再次校验库存。这不是技术问题而是业务理解问题。另外医疗项目的合规要求会比一般项目更严格数据备份、日志保留、权限细化都要提前考虑。上线前我对系统做了几轮数据备份测试确认Mysqldump备份和恢复流程在演练环境能跑通才敢真正在正式环境上线。做这类项目建议先花30%的时间跟目标用户聊需求再花20%的时间设计数据库最后才是写代码。顺序反了后面大概率返工。如果你正在做类似的医院管理系统课题希望这篇分享能帮你避开一些我踩过的坑。