1. 从农场账本纷乱的现实切入我为什么要用Django做这套管理系统先交代一下项目背景。去年帮一个做蔬菜和水稻的农场搭管理系统时对方最初提的需求特别朴素想知道每块地今年种了什么、用了多少化肥农药、收了多少货、卖给了谁、年底到底赚没赚钱。就这么简单的一件事他们当时是靠Excel加微信聊天记录在管统计一次季度报表要三个人忙三天数字还经常对不上。我去地里转了一圈才发现农场的业务复杂度其实不低。地块不是整齐划一的三十多块地分散在不同区域种的东西年年换农工的工龄、工时、工种不一样化肥农药有自购和农资店代送的进出库没有统一登记到收获季采摘量、分级、损耗、冷藏库存、销售回款每一环都靠口头传递。用户真正需要的不是一个漂亮的信息系统而是一个把地块、批次、农事、库存、人工、销售串成一个闭环的可追溯工具。选型的时候我对比过几条路线。轻量一点的方案是用现成的低代码平台表单和数据表拖一拖就能用但农场管理涉及大量字段级校验、单位换算、状态流转和自定义报表低代码到后期基本要写插件兜底维护成本更不可控重一点的方案是上全套物联网平台加ERP对家庭农场和中小规模合作社来说复杂度明显超标光主数据规范化就能劝退使用者。最后敲定Django核心原因有三点一是后台管理能力成熟农场管理人员和农工的IT基础普遍一般Django自带的Admin和数据表单体系能快速搭建录入界面二是ORM对多表关联处理非常顺手农场这种强关系型业务用ORM建模比写原生SQL舒服得多三是Django的权限系统、事务管理和中间件生态都现成后面接移动端和传感器时不会掉链子。这套系统本质上不是什么高深技术它就是一个典型的业务管理系统Web端负责数据录入、计划编排和报表查询移动端做田间现场的拍照、扫码、农事打卡和采收登记数据层面挂接了温度湿度、土壤墒情一类的传感器上报通道。整篇文章我尽量按真实的开发顺序来写把数据库建模、权限设计、接口开发、部署运维和上线之后踩过的坑都整理出来能给你在开发同类系统时作为参考就行。2. 数据库设计地块、批次、农资、销售这些实体怎么建模2.1 地块档案和作物计划唯一约束是防止出错的第一道闸门农场系统的地基就是地块档案。我设计的第一张核心表是地块表字段包括地块名称、编码、面积、土壤类型、是否连栋大棚、当前状态闲置/种植中/休耕等。这里有个容易忽略的点地块的编码必须唯一而且最好让编码有业务含义比如东区-03号棚西区-12号大田。我见过不少系统把地块编码做成自增长数字结果打印二维码、盘点区域的时候对不上号农工报数经常报错。所以我在模型里直接加了uniqueTrue同时用代码校验编码规则防止录入阶段脏数据进库。# parcel/model.py 简化示例 from django.db import models class Parcel(models.Model): code models.CharField(地块编号, max_length32, uniqueTrue) name models.CharField(地块名称, max_length64) area models.DecimalField(面积亩, max_digits8, decimal_places2) soil_type models.CharField(土壤类型, max_length32, blankTrue) status models.CharField( 地块状态, max_length16, choices[(idle, 闲置), (planting, 种植中), (fallow, 休耕)], defaultidle ) greenhouse models.BooleanField(是否大棚, defaultFalse)真正的复杂度在种植计划和批次管理上。一块地在一年之内可能种两到三茬作物同一茬作物也可能跨多个地块。刚开始我把种植计划直接关联到地块后来发现计划—实际播种—采收入库三者的关系如果用单一表承载历史追溯会非常混乱。后来拆成了两层PlantPlan记录打算在哪个地块种什么、推荐播种时间”CropBatch记录实际已经种下去的批次关联到具体地块和种植计划每个批次有播种日期、预估采收日期、状态机育苗/生长/采收中/已结束。这样设计的好处是农事记录挂在批次下面哪怕明年换作物历史数据也不会被覆盖。2.2 农事记录时间线、操作人、投入品三件套不能少农事记录是这块地到底干了什么的原始凭证。施肥、打药、灌溉、除草、采收每次操作都要能追溯到谁干的、什么时候干的、用了多少东西。所以我建了AgriRecord表关联批次记录操作类型、操作时间、操作人、天气情况、备注和现场照片。农工在地里干活时没有心情填一堆字段所以移动端界面我只保留四个必填项批次通过扫码自动带出、操作类型、操作时间和备注投入品明细做成可选行勾选了哪种肥料或农药就自动减少对应库存。这样既保证记录完整又不会逼着农工在烈日下操作手机。2.3 农资库存单位换算和批次价格是农场的管钱手农资库存这块如果不做单位换算后面统计成本时一定会出问题。农场的人在买化肥时习惯说吨施用时习惯说斤或袋农药则按瓶/毫升计。数据库如果只存一个数字不同单位之间的记录根本无法汇总。我采用的方式是每种农资建立基本单位和换算系数例如复合肥的基本单位是千克袋装50kg的换算系数是50每次出入库记录都带着单位和数量但报表统计时统一折算成基本单位。库存模型还需要考虑价格和批次的变动。同一个肥料年初买和年末买价格可能完全不同销售成本核算要按批次均价计算。所以库存流水表要带出cost_price进货单价数据汇总时用加权移动平均算出当期出库成本。# inventory/models.py 关键字段 class StockMovement(models.Model): item models.ForeignKey(InputItem, on_deletemodels.PROTECT, verbose_name农资品项) direction models.CharField(出入库, choices[(in, 入库), (out, 出库)], max_length8) quantity models.DecimalField(本次数量, max_digits12, decimal_places3) unit models.CharField(单位, max_length16) unit_factor models.DecimalField(换算系数, max_digits10, decimal_places4, default1) unit_price models.DecimalField(成本单价, max_digits12, decimal_places2, nullTrue, blankTrue) created_at models.DateTimeField(auto_now_addTrue) ref_batch models.ForeignKey(CropBatch, on_deletemodels.SET_NULL, nullTrue, blankTrue)2.4 销售台账一单对应一批次走款和库存两个方向都要平销售模块业务逻辑不复杂但账款和出入库必须联动。我设计了销售订单和订单明细两层结构订单主表记录客户、签约日期、总额、回款状态明细行关联具体的作物批次、销售数量、单价、发货日期。销售明细在保存时自动触发两个动作扣减对应批次的可售库存同时生成一条销售出库流水。这里我用数据库事务包住整个流程避免订单保存成功但库存扣减失败的不一致状态。实际操作中还遇到一个常见需求同一批蔬菜卖给商超部分按斤称部分按件算。我给出的方案是明细行允许选择计费单位并记录与基本单位之间的换算系数报表层统一折算和农资处理思路保持一致。3. 业务开发阶段最值得抠的四个细节权限、缓存、单据、导出3.1 基于角色的权限模型三个角色刚好别太细权限设计我一开始想得很细打算按系统管理员、农场主、技术主管、农工、客户、财务拆六七个角色后来被真实使用节奏狠狠教育了。农场的日常分工没那么精细一个农技师可能同时干技术和管理的活一个农工也可能兼任司机。角色的分类越细管理成本越高用户反而觉得系统难用。最后收敛成三个角色加一个可选角色管理员能做系统配置、人员管理、数据导入导出、农场管理者看所有报表、审批采购和销售单、农技工只录入农事、采收、领用看不到成本和财务数据。采购、销售、财务相关权限全部挂在管理员角色下避免业务和财务数据被误改。权限控制我直接用Django自带的Group加自定义Permission没有另开权限表省去不少开发量。# 自定义权限示例 class Meta: permissions [ (manage_farm_data, 可以管理农事数据), (approve_sales_order, 可以审核销售订单), (view_cost_report, 可以查看成本报表), ]3.2 首页看板聚合查询加上缓存别让数据库扛不住农场管理系统的首页看板通常包含今日采收量、待处理任务、库存预警、最近农事动态这些卡片如果每个卡片都直接查数据库高峰期会非常吃力。我的做法是把看板数据拆成两类需要实时的今日采收量、今日领用量按每五分钟一次的频率做聚合结果写入Redis缓存允许延迟的年度产量趋势、成本分析图在夜间生成快照表白天查询直接读快照。看板缓存有个细节值得提醒缓存键不要设计成全局限定比如dashboard:overview:today这样第二天凌晨零点所有人看到的还是前一天的数据。更合理的做法是缓存键带日期参数dashboard:overview:{date}同时设置午夜过期时间让新一天的看板自动重建。3.3 农事任务工作流从派单到核销的闭环比想象中重要农场管理者派活给农工过去是口头转达干没干、干了多少全凭记忆。我在系统里做了任务流程管理员或技术主管在后台创建任务包括批次、作业类型、预计工时、要求完成时间推送到对应农工的移动端农工在田间执行后拍照、填写实际用时、勾选农资消耗并提交管理者在工作列表里看到待核销的任务核对照片和数量后完成核销。这里容易出问题的是任务状态和农事记录之间的同步。我的做法是任务核销时自动创建一条AgriRecord避免同一件事被录入两次如果管理者核销时修改了任务的实际工时则同时更新农事记录中的操作时长字段确保统计口径一致。3.4 报表导出除了Excel还得照顾打一张纸出来的习惯农场里很多数据最终是要纸质留档的比如农药使用记录监管和溯源都可能查。所以我在报表模块里同时提供Excel导出和PDF打印两个出口。Excel导出用流式写入几万行数据不会卡死服务器PDF打印则设计了一套固定模板把地块、批次、农资类型、用量、操作时间排版清楚一屏一表方便归档。导出功能有一个隐含的坑农资名称和历史记录里可能有特殊字符比如括号和分号导出到Excel时容易触发公式注入问题。我的规避方式是导出前统一对字段做转义处理除了文本内容外不让单元格内容以,,-,开头。4. 接移动端和传感器数据时我踩过的协议坑4.1 移动端选型为什么用H5加小程序的混合方式农场场景下农工的作业工具是手机而且以安卓机为主。给农工使用的录入端我选择了基于H5的移动端页面理由是部署零成本、兼容性好、后续在小程序内嵌也方便。农场管理者和客户则多使用微信小程序因为采购商、会计这类角色更习惯在小程序里查订单、看报表。两个端共享同一套后端API前端框架分开维护既降低开发成本又保证不同角色的使用体验。为了适配田间网络差的现实移动端所有表单都支持离线暂存。农工在地里可能有一小段路完全没有信号填写一半的农事记录先保存在本地缓存里等网络恢复后由客户端自动上传。后端的接口设计成幂等的同一份农事记录用前端生成的UUID作为业务主键重复提交不会产生重复数据。4.2 传感器接入不是所有设备都支持HTTPS要留一个上报代理层农场挂接了温湿度、土壤湿度等传感器设备这些设备大多只支持HTTP或MQTT上报联网能力和数据安全级别都很低。直接让设备调用Django的生产接口并不现实我在设备和后端之间加了一层轻量的上报代理服务专门负责接收传感器的HTTP POST报文做签名校验和数据格式校验后再转发给Django的接收接口。签名校验这里一定不能省。设备厂商给的默认方案往往只是传一个设备ID和一个固定密码这在公网环境非常不安全。我要求在每台设备配置时预置密钥上报报文里带上timestamp和基于密钥生成的signature服务端用同样的密钥计算并比对同时检查时间戳不能偏差超过五分钟防止重放攻击。# 传感器数据接收接口伪代码 def sensor_report(request, device_id): ts request.GET.get(ts) sign request.GET.get(sign) payload request.body if not check_signature(device_id, ts, payload, sign): return JsonResponse({code: 401, msg: signature error}, status401) data json.loads(payload) for item in data[samples]: save_sensor_reading(device_id, item) return JsonResponse({code: 0, msg: ok})4.3 传感器断线补偿历史补报和自动预警两种策略传感器设备并不能保证每次都在线尤其在大棚里断电、断网、设备老化都会造成数据缺口。我做了两套补偿机制。第一套是历史补报设备恢复联网后会把离线期间采集到的数据打包补发接收接口按采集时间去重后入库保证曲线完整。第二套是自动预警如果某台设备超过2小时没有上报数据系统生成一条运维工单推送给管理员如果温湿度数据超过预设阈值则触发异常提醒并关联到对应大棚的批次记录。这套机制上线后还是踩了坑。有段时间某个大棚的数据曲线每天深夜都缺一段排查下来发现是设备内置的定时任务在夜间进入了省电模式关机期间根本没有采集数据自然也就谈不上补报。后面我把所有设备的心跳包和数据采样计划都做进了设备台账通过对比计划上报时间和实际上报时间提前发现设备不在线或配置错误的问题。5. 部署上线与长期运维的经验清单5.1 用Docker Compose把开发和生产环境尽量拉齐农场系统规模不大但涉及的组件不少Django应用、PostgreSQL数据库、Redis缓存、对象存储、Nginx。如果开发环境裸跑生产环境手动部署环境差异迟早会出问题。我用Docker Compose把服务编排起来本地一条docker compose up就能起整套环境生产机器上同样用Compose管理降低运维成本。# docker-compose.yml 节选 services: db: image: postgres:15 volumes: - pgdata:/var/lib/postgresql/data redis: image: redis:7-alpine web: build: . depends_on: - db - redis volumes: - staticdata:/app/static - mediadata:/app/media nginx: image: nginx:1.24 ports: - 80:80 - 443:443 depends_on: - web5.2 文件存储本地目录只适合开发上线必须换对象存储农场系统的图片文件量不小农事照片、地块实拍、销售凭证每张图平均两到三兆一年累计下来就是几十个G。本地存储方案虽然在开发期方便但硬盘扩容和备份都是问题。我用了兼容标准的对象存储方案Django端通过统一的存储类适配代码里不直接写文件路径全部通过ORM的FileField配合自定义存储类处理。图片上传还有个体验细节田间网络慢农工拍照后上传经常失败。我在移动端做了压缩再上传的策略图片先压到最大边长1600像素再提交既满足溯源留档需要又能明显提升上传成功率。后端限制单图大小不能超过5MB超出则报错并提示重拍避免无效数据占用存储。5.3 备份策略要能恢复到一小时前农场数据虽然不像金融系统那么要求高实时性但农事记录、销售台账一旦丢失损失完全不可逆。我按每日全量每小时增量的方式做备份每天凌晨对PostgreSQL做一次全量物理备份保留七天每小时用WAL归档做增量备份保留24小时。文件存储部分则靠对象存储自带的版本管理误删或覆盖还能找回旧版本。备份恢复这件事我在项目里练过两次真实的恢复演练。第一次把全量备份恢复到一台临时机器上花了一个半小时第二次按照文档步骤操作从备份到接回流量花了不到二十分钟。扎扎实实走几遍恢复流程比写一打备份脚本有用得多。5.4 日志和监控农忙季节出问题等用户来报太被动农场业务有明显的季节性农忙时使用频率非常高。如果监控不到位页面响应变慢、接口超时这些问题都要等到农工抱怨才发现。我用了一套简单的监控组合Django日志接入集中日志服务按错误级别和模块分组关键接口登录、农事提交、销售单保存记录耗时指标平均耗时增加超过50%就告警Redis和数据库连接数也在监控范围内。监控重点不是面面俱到而是盯住在真实使用中影响最大的指标。我后来发现农忙时系统卡顿的第一大原因是农工在地里反复刷新页面导致大量重复查询请求打到数据库。解决思路是给农事录入接口加了个简单的防抖逻辑同一批次的同一操作五秒钟内重复提交直接返回上次结果数据库压力立减。6. 假如再来一次我会改掉哪些设计和流程6.1 品种和规格数据一定要尽早做规范化农场系统上线三个月后最让我头疼的其实是品种名称不统一这类小问题。同一种黄瓜有的批次录入成黄瓜-水果型有的录入成水果黄瓜销售报表统计时同一作物被拆成好几行数据看起来总是怪怪的。如果重新来我会在项目初期就建立作物品种的标准字典表所有录入界面的品种字段都改成下拉选择禁止自由输入后续的数据质量会好很多。这里要提醒一句标准字典表也要留一个申请新增的入口让农场管理者自己维护品种列表。系统正式运行后只有管理员权限才能往字典里加品种农工在录入时发现缺少某个品种提交申请后由管理者确认既保持数据规范又不会太僵化。6.2 活口状态的并发处理要提前想好方案农场业务里有一个非常特殊的并发场景同一批作物可以同时卖给两个客户也可能一部分库存留作自用还有一部分自然损耗。如果不做库存预占两个销售订单同时提交时都校验剩余库存充足最后实际发货时才发现库存超卖。我在销售确认环节引入了库存预占reserved机制创建销售订单时自动锁定对应批次的指定数量订单取消或发货完成时释放或扣减农技工在移动端盘点时看到的是可用库存而不是包含预占的总库存。6.3 时间和批次的对账逻辑比想象中复杂农业系统的时间处理有个特殊点农事记录往往以天为单位但大棚可以种两茬到三茬同一块地在同一天可能有不同批次的采收数据。比如某个大棚上午收第一茬的叶子菜下午收第二茬的番茄苗出库如果农事记录界面不带批次上下文很容易选错批次导致库存和平均产量统计全部失真。我的补救方案是在移动端做强制批次校验操作人必须通过扫描批次二维码或者下拉选择正在作业的批次才能提交记录同时根据地块档案里的当前批次信息做一个预填充如果在同一地块同一时段检测到两个批次的重复作业则弹出提示让农工确认是否真的改换更批次。这个改动上线后批次错录的问题基本绝迹了。6.4 给不上系统习惯的使用者留一条手工作业兼容路径最后聊一个理念层面的问题。农业从业者的工作节奏和写字楼完全不同农忙季节凌晨五点开工傍晚收工中间可能连续几个小时都没空碰手机。系统设计得再完善如果一味要求每个环节必须登系统大概率会被使用者在真实工作中绕过去。所以我的建议是关键业务节点上保留人工替代路径。比如销售订单必须先由管理员在系统里创建但收获单和农事记录允许农工先录入纸质记录再由管理员在当天统一扫描或拍照批量补录库存盘点允许离线录入本地草稿回办公室后再同步。这套系统优先、纸质兜底的策略在实际推广中起的作用比任何培训都大。我在这个项目里最大的体会是农场管理系统本质上是在跟农业生产的季节性和人员的操作习惯做斗争技术上能解决的问题其实都还好真正难的是让每个角色在正确的时间愿意把正确数据交到系统里。做这类系统与其追求功能大而全不如在关键数据链路少断几个地方让使用者发现系统确实能帮自己省时间数据质量自然就稳定了。
阅读完成 · 觉得有帮助?