多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

SpringBoot+微信小程序:生猪养殖信息化管理系统的设计与落地

SpringBoot+微信小程序:生猪养殖信息化管理系统的设计与落地 搞了三个多月总算是把这套“基于SpringBoot与微信小程序的生猪养殖信息化管理系统”从零到一完整落地了。一句话说清楚它是什么一套面向中小规模养殖场的数字化台账工具后端用SpringBoot提供接口服务前端用微信小程序做移动端采集和查看核心解决猪只档案、饲喂记录、疫病防控、饲料库存、存栏统计这些日常管理数据的线上化问题。老板不用再翻纸质本子问“现在存栏多少头”饲养员也不用每天下班补记饲喂表拿手机点几下就能完成当天全部记录。这篇文章适合正在做同类课题的同学也适合想用低成本方案给自家猪场做信息化的从业者参考我会把项目从设计思路到编码细节、再到联调排坑的全过程都写清楚。1. 项目整体设计与技术选型思路1.1 业务场景与核心痛点先聊业务背景。很多中小猪场的管理现状是猪舍里挂一块黑板写今天喂了几包料、有没有病猪月底再把黑板内容誊到Excel里。听起来原始但这确实是相当普遍的现状。问题出在几个地方猪只档案不连续。一头猪从进场到出栏中间经历了哪些疫苗、生过什么病、用过什么药几乎没有完整记录一旦出现批量症状很难追溯源头。库存靠拍脑袋。饲料还剩多少、兽药还剩多少基本靠仓库保管员“感觉”采购经常出现“急用料到了货才发现还有两吨”的情况。数据统计滞后。老板想知道这个月出栏多少头、均重多少、饲料成本涨没涨最快要等财务月底手工汇总根本没法做实时经营决策。所以这套系统的定位就不是做一个花哨的“数字孪生猪场”而是先解决最基础、最刚需的信息采集和汇总问题。微信小程序天然适合这类场景不用单独装App扫码即用猪舍环境复杂手上有饲料、有水渍不方便敲键盘小程序里的下拉选择、扫码录入、语音备注都比电脑端友好得多。SpringBoot则负责把采集上来的数据沉淀下来通过接口给不同角色呈现他们关心的内容。1.2 为什么选SpringBoot这条链路技术选型阶段我也纠结过一阵。当时摆在我面前的有三条路PHP、Node.js、SpringBoot。PHP上手快但项目结构混乱后期加权限、加定时任务、加消息推送会比较难受Node.js写接口效率确实高但团队里没人能长期维护也担心后续接物联网设备时生态不够稳。最后选的SpringBoot理由很实际生态成熟尤其是权限、事务、定时任务这些管理后台刚需能力几乎都是“开箱即用”的配置。和数据库打交道的方案成熟MyBatis-Plus这类工具能把单表CRUD的工作量砍掉一大半让我把精力放在业务逻辑上。养殖行业后续大概率要接传感器、自动料线、环境监控这些硬件Java在工业物联网侧的对接案例和数据吞吐能力都更靠谱。冷启动的时候最大的坑是版本匹配。当时图新鲜用了最新的Spring Boot 3.x结果JDK要求17起部分第三方依赖还没完全适配折腾了两天才退回到2.7系列。这里给个建议如果只是做业务系统而不是搞技术试验直接用SpringBoot 2.7 JDK8/11是最稳的组合不要因为版本号新就去踩坑。小程序端我选了原生开发而不是uni-app。原因很简单这套系统只需要微信一个端用uni-app多一层编译转换反而增加调试成本。原生小程序的组件和API最直接开发者工具也成熟遇到问题查资料命中率更高。1.3 模块划分与数据模型规划系统的功能模块基本是跟着猪场的日常流程走的我拆成七个部分用户认证、猪舍管理、猪只档案、饲喂管理、饲料兽药库存、疫病防控、统计报表。再加一个消息提醒模块用于疫苗到期和库存预警。数据模型是整个项目的底座我花的时间比写接口还多。核心表有user、pigpen、pig、feed_record、feed_stock、vaccine_record、disease_record。拿猪只表举例字段不能只设计成“品种、日龄、体重”还要考虑后续追溯create table pig ( id bigint primary key auto_increment, ear_tag varchar(32) not null comment 耳标编号, pen_id bigint not null comment 所在猪舍, breed varchar(32) comment 品种, birth_date date comment 出生日期, in_date date comment 进场日期, weight decimal(6,2) comment 当前体重kg, status tinyint comment 1哺乳 2保育 3育肥 4出栏 5死亡, source varchar(64) comment 来源自繁/外购, create_time datetime, update_time datetime, unique key uk_ear_tag (ear_tag) );这个表设计里有几个细节值得说。耳标必须全局唯一用唯一索引兜底不能只靠业务层判断status用数字字典而不是字符串查询和统计都方便所有表统一带create_time和update_time排查数据问题时你会感谢这两个字段。2. 功能模块拆解与数据库关键细节2.1 用户体系微信登录、手机号绑定与角色权限登录流程是微信小程序项目绕不开的第一关。标准做法是小程序端调wx.login拿到临时code把这个code传给后端后端再用code换openid和session_key。拿到openid后先查用户表没绑定手机号的返回一个标记让前端引导用户授权手机号已经绑定的直接生成自定义token返回。手机号获取这里有个容易踩的坑。2023年后微信调整了规则小程序端用open-typegetPhoneNumber按钮拿到的不是手机号本身而是动态code后端还要拿着这个code再调微信服务端接口才能换取真实手机号。流程虽然多了一步但安全性更高。处理用户拒绝授权的情况也要设计好不能一直弹授权框要给一个“下次再说”的出口否则用户直接卡死在登录页。角色权限我分了老板和饲养员两种。饲养员只能看到自己负责猪舍的数据和操作入口老板能看全场汇总。权限控制在后端做接口上通过拦截器判断角色不能只靠前端隐藏按钮否则随便改个参数就能越权查看数据。用户表里加一个role字段就够了这种规模的项目不需要引入复杂的权限框架。2.2 猪只档案与状态流转管理猪只档案的核心不只是“登记一头猪”而是把一头猪的完整生命周期管起来。我刚设计的时候就是用一堆if else判断状态流转后来发现逻辑越写越乱改成了状态机哺乳、保育、育肥、出栏、死亡每个状态定义清楚“从哪里来到哪里去”比如只有“保育”状态的猪才能转到“育肥”“死亡”状态只能从“哺乳/保育/育肥”转入。这样做的好处是数据不会被搞乱。比如一头猪已经出栏了结果还有人不小心给它录了一条饲喂记录这在状态机设计下是默认不允许的。实际操作中转群和出栏都要求操作人员二次确认弹窗。系统还要支持批量操作比如一批猪同时从保育转到育肥逐头操作不现实我用一个列表勾选加批量确认的方案解决。批量导入也是档案录入的重要入口。猪场一次性进几百头仔猪时让饲养员在小程序里一头头录入根本不现实。我提供了一个Excel模板导入的功能后台解析模板后校验耳标号格式和唯一性把错误行生成一个错误报告下载下来大大降低了初始建档成本。2.3 饲喂记录、饲料库存与并发扣减饲喂记录是使用频率最高的功能每天至少录入两次。它的核心联动逻辑是录入一条饲喂记录的同时自动扣减对应饲料的库存。我最初只是想当然地在插入记录后直接update库存后来想到一个场景——两个饲养员同一时间在各自猪舍喂料同时扣减同一种饲料的库存后提交的会把前一个的扣减覆盖掉。这就是典型的并发问题。解决办法是给feed_stock表加一个version字段做乐观锁。更新的SQL大致是这样update feed_stock set stock stock - #{quantity}, version version 1 where id #{stockId} and version #{version}如果更新影响行数为0说明版本不对需要提示操作人员“库存已变化请刷新后重试”。这个方案避免了悲观锁带来的性能损耗实现也简单。投喂量还要做合理性校验比如单次饲喂量超过该猪舍存栏头数乘以单头上限的120%就直接拦截这些数据校验规则是和养殖场技术员确认后写进代码的。2.4 疫病防控、疫苗提醒与养殖预警疫病防控模块对养猪场来说是刚需中的刚需。猪只的疫苗记录包括疫苗种类、生产厂家、批号、接种日期、下次接种日期。每当录入一条疫苗记录系统会自动生成一条“待办提醒”到期前三天推送到饲养员小程序的消息里。这个功能靠SpringBoot的Scheduled定时任务实现。每天凌晨扫描一次疫苗记录表把三天内需要接种的猪只按猪舍分组通过微信订阅消息推送给对应责任人。使用订阅消息有个前提用户必须在小程序里主动订阅过该消息模板否则推送不出去所以我在“设置”页面专门做了一个订阅引导。除了疫苗提醒我还加了一个异常预警逻辑如果连续三天某个猪舍的采食量环比下降超过20%小程序首页就会亮起预警卡片。这个规则不复杂但对早期发现疫情很有帮助。3. 后端服务核心实现SpringBoot实践3.1 工程搭建、分包结构与启动配置工程是用Spring Initializr生成的构建工具选的Maven。团队如果统一用Gradle用Gradle也行但我个人习惯Maven稳定而且文档多。分包结构虽然老套但确实好用com.pigfarm ├── controller // 接口层 ├── service // 业务层 ├── mapper // 数据访问层 ├── entity // 数据库实体 ├── dto // 接口出入参 ├── common // 统一响应、异常、常量 └── config // 配置类这个分层的好处是职责清晰出问题定位快。我见过把业务逻辑全写在controller里的项目维护起来真的痛苦。启动器上加了一个自定义Banner把项目名和版本号打印出来主要为了方便区分测试环境和生产环境不然同屏开好几个服务时根本分不清哪个是哪个。在线Banner生成器随便搜就有不用自己画。配置环境我用了一套规范application.yml放公共配置application-dev.yml和application-prod.yml分别放开发、生产配置。IDEA里启动时通过Program arguments指定--spring.profiles.activedev不要直接改公共配置文件里的端口和环境否则多人协作时经常互相覆盖。启动端口改成在application-dev.yml里用server.port配置比在IDEA的VM options里写-Dserver.port直观。3.2 统一响应、全局异常与分页接口规范小程序端最怕接口返回格式五花八门。我定义了一个统一响应体Result结构是code、message、datacode为0表示成功非0表示各种业务错误。所有接口都返回这个结构前端封装一个request工具类统一处理从此再也不用为某个接口单独写返回值解析。全局异常处理用RestControllerAdvice把参数校验异常、业务异常、未知异常分别处理返回对应的code和友好提示。这里有个细节业务异常不要让后端打印完整堆栈否则日志很快被无效信息刷满只需要记录业务异常码即可。未知异常才需要完整堆栈方便排查。分页接口统一接收pageNum和pageSize两个参数返回total和list。养了上万头猪之后一次性查所有猪只列表必然卡死分页是最基本的保护。MyBatis-Plus的分页插件做这个很方便小程序端滚动加载下一页体验也比较顺滑。3.3 登录鉴权、拦截器与事务自调用坑鉴权这层我一开始纠结要不要引入Spring Security。最后还是没引入理由很简单系统只有两个角色、接口数量不到一百个引入Spring Security反而要写一堆配置。我自己用token加拦截器解决用户登录成功后生成一个UUID作为token存进Redis并设置2小时过期后续请求在header里带token拦截器校验通过后把用户信息放到ThreadLocal里供业务代码取用。这里要提醒一个SpringBoot的经典坑就是CGLIB代理问题。SpringBoot在2.x之后默认使用CGLIB代理当你在一个类内部直接调用另一个加了Transactional的方法时会发现事务不生效。比如我写过一个Bug在某个Service内部调用自身的update方法update方法上有Transactional结果更新失败时数据只回滚了一半。原因就是内部调用没有走代理对象。解决办法是把需要事务的方法放到另一个Service里调用或者自己从容器里拿代理对象。这个坑很隐蔽没遇到过的同学很难一次定位。参数签名认证这件事我没有额外做。很多系统为了防止接口被篡改会加MD5签名但我觉得内部管理工具加上HTTPS和token已经够用再搞签名认证会白白增加前后端联调成本。如果后面系统对外开放接口再考虑也不迟。3.4 数据统计的实现思路与扩展边界统计报表是整个系统老板最关注的部分。存栏量、出栏量、饲料消耗趋势、各阶段死亡率、药费占比这些都要从业务表里聚合出来。简单统计直接用SQL查询加group byselect pen_id, count(*) as cnt, sum(weight) as total_weight from pig where status in (1,2,3) group by pen_id;复杂一点的比如按月统计出栏趋势、按日龄段统计存栏分布这些查询在数据量上来后会很慢。我的做法是写一个定时任务每天凌晨把核心指标预聚合到一张统计表里报表接口只查预聚合表。数据可能有最多一天的滞后但对于经营管理决策完全够用。搜索功能我也加了点“私货”猪只查询支持按耳标号、品种、状态模糊搜索。实际开发中我还尝试过引入HanLP分词库来做病猪症状描述的关键词提取比如操作员录入“咳嗽、喘气、食欲差”系统能自动提取“咳嗽”“喘气”作为标签方便后续统计症状分布。这个功能做出来效果还不错。关于实时计算这里多说一句。如果后续猪场要接入温湿度传感器、自动料线这类物联网设备数据量会大幅增长到时候可以考虑SpringBoot整合Flink做流式处理。但是现阶段MySQL加定时任务完全够用千万别为了技术热度去堆组件系统是给猪场用的稳定性比架构时髦重要得多。4. 微信小程序端实现与交互细节4.1 页面规划与业务操作流设计小程序端的页面规划紧密围绕使用场景。底部TabBar四个页签首页、猪舍、记录、我的。首页放存栏统计卡片和待办提醒猪舍页是猪舍列表和猪只列表记录页是饲喂、疫苗、病猪三个快捷录入入口我的页放个人信息和设置。录入流程的设计比页面数量更重要。在猪舍现场饲养员的手套上可能有饲料或消毒水所以表单要尽量少打字。猪舍选择用picker下拉饲料种类用选择器数量用步进器日期默认当天操作员直接取登录人。我把一次饲喂记录的录入控制在三次点击以内选猪舍、选饲料、填数量点保存。刚开始设计的表单有七八个必填项自己试用都觉得烦后来砍到只剩最核心的字段饲养员才愿意用。扫码功能也是现场场景的刚需。每头猪有耳标可以在小程序里直接调相机扫码绑定操作。这样在打疫苗、转群时扫一下耳标就能定位到猪只不需要手工输入编号效率提升特别明显。4.2 登录与手机号获取的完整链路登录链路我在后端设计时已经说了小程序端的处理逻辑是页面加载时先检查本地storage有没有token有就直接进首页没有就调wx.login拿code发到后端换取登录态。后端返回“需要绑定手机号”的标记时前端再弹出手机号授权按钮。手机号授权按钮的标准写法是button open-typegetPhoneNumber bindgetphonenumberonGetPhoneNumber绑定手机号/button注意处理回调时拿到的是动态code不是明文手机号不能直接显示需要把这个code传给后端换手机号。我在这个环节踩过一个坑用户点拒绝授权后再次点击按钮没有任何反应。原因是拒绝后该按钮的open-type被系统重置了需要把按钮状态重置或者引导用户重新触发。另外授权组件的位置要放在用户能理解的地方最好配上“绑定手机号后可使用全部功能”的说明文案减少用户疑虑。4.3 列表加载更多、下拉刷新与导航栏适配猪只列表是典型的长列表场景分页加载必不可少。实现上我用页面onReachBottom触发加载下一页同时用一个loading状态防止重复请求onReachBottom() { if (this.data.loading || this.data.noMore) return; this.setData({ loading: true }); const pageNum this.data.pageNum 1; fetchPigList({ pageNum, pageSize: this.data.pageSize }) .then(res { this.setData({ pigList: this.data.pigList.concat(res.list), pageNum, noMore: res.list.length this.data.pageSize }); }) .finally(() this.setData({ loading: false })); }列表底部还要区分“加载中”和“没有更多了”两种状态不然用户一直上滑还以为卡住了。第一页加载时要有骨架屏效果App级别的体验不一定能完全复刻但至少不要白屏干等。自定义导航栏高度适配也是一个绕不开的话题。因为小程序原生导航栏样式有限我想在首页做带搜索框的定制导航栏就得用自定义导航模式。这个高度计算比较经典状态栏高度可以通过wx.getSystemInfo拿到胶囊按钮的位置可以用wx.getMenuButtonBoundingClientRect拿到。导航栏高度公式是const statusBarHeight systemInfo.statusBarHeight; const menuButton wx.getMenuButtonBoundingClientRect(); const navBarHeight statusBarHeight (menuButton.top - statusBarHeight) * 2 menuButton.height;这个计算逻辑放到一个公共方法里所有自定义导航栏页面复用。直接写死一个数值的做法不建议不同机型差异很大写死高度在手机上要么重叠要么空一截。4.4 表单、校验与真机调试要点表单组件我用得比较多的是radio-group做单选状态、checkbox-group做多选症状、picker做下拉选择。这里有个体验细节多个表单元素在一个页面里时点击picker弹出选择器后底层页面最好不要滚动我给页面加了内容区固定的处理。表单校验要做两层小程序端做一层基础校验比如必填项不能为空后端在接口层再做完整校验防止绕过小程序直接调接口传脏数据。录音和拍照功能主要用于病猪症状记录。拍照可以直接用camera组件录音用wx.getRecorderManager。我踩过一个坑模拟器上录音生成的音频文件格式和真机不一致模拟器是mp3格式真机可能是aac格式后端解析时不能写死格式后缀。像这类功能尽量用真机调试模拟器只能验证逻辑不能验证真实体验。5. 实操过程与排坑实录5.1 上线前要走的流程与配置一个微信小程序项目从开发到能给别人用中间还有不少硬性流程。首先注册小程序账号时主体要选好做养殖场这类经营场景最好用企业主体个人主体很多接口受限。企业主体需要微信认证费用是300元一年这个钱省不了因为涉及后续开通订阅消息、支付等功能。开发阶段可以在开发者工具里勾选“不校验合法域名”但上线前必须在mp后台配置request合法域名而且域名必须备案且支持HTTPS。我一开始把接口服务部署在服务器上用IP加端口让小程序直连结果真机一测就失败小程序对HTTPS和域名校验很严格。后来老老实实申请域名、配SSL证书、加nginx反向代理这才跑通。要让别人试用小程序不用等审核发布。在mp后台加体验成员生成体验版二维码对方用微信扫码就能打开。体验版有个限制是只能体验成员使用但用来收集试用反馈足够了。如果想让所有人都能搜到才需要走提审发布流程。5.2 联调阶段典型问题速查联调阶段的问题主要集中在几个方面我列一下典型的开发者工具里报10002错误。这个错误刚出现时我一脸懵后来排查发现是入口文件配置问题。遇到10002时先看app.json是否存在、路径有没有写错、引用的页面文件有没有缺失九成是这类问题。后端日志里出现code2Session调用失败返回40029。这个通常是前端传上来的code无效最常见的原因是code被用了两次或者从wx.login获取到调用后端接口之间间隔太久code已过期。解决办法是每次登录重新调wx.login拿新code后端拿到code后立即调用微信接口不要做缓存。真机接口请求全部失败。检查顺序建议是域名是否在mp后台配置、是否启用HTTPS、TLS版本是否符合微信要求、服务器防火墙是否放行443端口。在电脑开发者工具里正常不代表真机也正常。联调接口时我强烈建议用抓包工具。PC端可以用Reqable或者Charles把手机代理到电脑上安装信任证书后就能看到小程序发出的完整请求和返回内容。排查“前端传的参数和后端接收到的不一致”这类问题抓包比两边对日志快十倍。5.3 IDEA与本地环境配置踩坑开发环境的坑不多但我都踩了一遍。IDEA里配置SpringBoot启动项时如果你需要临时指定端口更规范的做法是Program arguments里加--server.port8081或者直接改application-dev.yml。在VM options里写-Dserver.port虽然也能生效但容易跟后续引入的其它启动参数混淆不推荐。还有一个常见问题是代码改了没生效。我遇到过resources目录下的yml文件没有被编译到target里排查后是IDEA的文件类型设置把yml忽略了。类似这类问题优先看target目录里有没有最新文件。SpringBoot的热部署用devtools加IDEA的自动编译可以基本满足但要注意devtools只适合本地开发生产环境不要带这个依赖。本地联调时连接服务器数据库这个操作我也建议尽量避免除非你知道自己在做什么。我一哥们因为在本地连了生产库一条测试数据污染了真实的存栏统计严重影响当月报表。开发环境一定要连开发库数据源配置通过profile区分。5.4 性能与体验优化要点系统上线初期猪只数量少接口都是秒回。但随着档案越建越多列表接口慢慢变慢了。检查后发现最核心的问题是pig表的ear_tag字段虽然建了唯一索引但查询时用了LIKE模糊匹配索引失效。后来改成耳标前缀精确匹配加全文搜索辅助查询性能明显提升。常用字典数据比如品种列表、饲料类型列表加了一层Redis缓存小程序端每次启动时拉取一次减少对数据库的无效查询。如果是单机部署的小项目Redis不是必须的本地用一个Map缓存也行但既然为了后续扩展上Redis一步到位也合理。大列表展示方面上千条猪只记录在小程序中用scroll-view渲染时会明显卡顿。我做了两个优化一是分页限制每页20条二是非可视区域的数据用简易列表加延迟渲染。真要追求极致流畅可以上虚拟列表但对当前数据量来说分页已经够用。图片上传是另一个容易忽视的坑。我最初把猪舍照片、病猪照片直接上传到应用服务器本地目录上线不到两周磁盘就满了而且无法通过HTTPS直接访问。后来改成上传到对象存储后端只存访问URL问题彻底解决。6. 常见问题排查与项目心得6.1 问题排查速查表把整个项目周期里遇到的问题整理成一张表方便以后直接查现象可能原因处理建议小程序报10002app.json或页面路径问题检查入口文件是否存在、路径是否正确code2Session返回40029code失效或重复使用每次登录重新wx.login后端不缓存code真机请求失败域名未配置/未启用HTTPS/防火墙依次检查mp后台域名、SSL证书、服务器端口接口偶尔返回超时SQL缺索引或连接池耗尽查看慢SQL日志检查连接池配置定时任务不执行缺少EnableScheduling在启动类上开启调度事务回滚不完整Spring内部自调用把事务方法拆到其他Service或使用代理调用订阅消息发不出用户未订阅/模板ID不对引导用户重新订阅核对模板ID和关键词字段饲料库存对不上并发扣减使用version乐观锁增加操作日志这张表是我项目收尾时的真实复盘其中一半问题都是写完代码后靠日志和抓包定位的。写这类业务系统排查问题的耐心和技术方案本身一样重要。6.2 从开发到上线的几点真实体会整个项目从需求确认到上线试用我最大的体会是这类信息化系统的成败一半在代码一半在业务流程的梳理。你代码写得再漂亮如果饲养员觉得录入步骤太多、操作不顺手他宁可回到用纸笔记录的老路。我第一次做完让猪场师傅试用反馈是功能都有但太麻烦。后来我跟着走了一天的喂料流程回来看一遍自己设计的表单才发现很多字段在真实场景里压根没有时间填。从产品体验上我砍掉了将近三成的必填项把高频操作从五六步压缩到两三步第二次试用时师傅的评价变成了“这个能用”。我也意识到数据准确性靠教育是管不住的必须靠系统设计兜底。所有关键操作都加了二次确认和操作日志这样即使有人录错月底对账时能查到是谁在哪个环节录的。另外一点值得说的是别把技术方案做得比业务需要复杂太多。我在项目初始阶段列了一堆想上的能力比如地理围栏、温度监控、体重AI估算后来跟养殖场负责人一聊发现当前最有价值的还是最基础的档案数字化和预警提醒。做管理系统沉下去理解业务永远比多引入一个框架更重要。如果让我重新做一遍我会先把“跟着猪场师傅完整走一遍工作流程”放在编码之前而不是边开发边补业务认知。系统上线后真正的考验不是接口并发能不能抗住几千QPS而是用户愿不愿意每天打开这个小程序把每一条记录坚持下去。能把录入成本降到最低让用户觉得“顺手”这套系统的价值就已经兑现了大半。
返回列表