ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SpringBoot水产养殖管理系统:从架构设计到项目落地

SpringBoot水产养殖管理系统:从架构设计到项目落地 1. 项目概述1.1 这个系统到底在解决什么先别急着看技术选型咱们把问题掰开揉碎看看这套系统真正要解决的问题是什么。水产养殖这个行当和普通的进销存系统有本质区别池塘里的事特别多而且环环相扣水质指标决定投料量投料量影响生长速度生长速度又关系着出售时机和利润。传统的人工记账方式靠Excel和微信群报数信息滞后严重一个池塘数据更新不及时后面一连串决策全得推倒重来。再加上近年养殖规模扩大多数从业者同时管着好几个塘苗种、饲料、药品、水电、人工的成本明细全靠嘴和脑子记账目混在一起年底一算能赚多少根本说不清。所以这套基于SpringBoot的水产养殖管理系统本质上就是要把这些散落在线下、分布在微信聊天记录和纸质本子上的数据统一收拢到一个地方形成一条完整的数字化链条水质监测 - 养殖工单 - 成本核算 - 库存管理 - 销售出库。后端用SpringBoot框架主要看中的是它启动快、生态成熟、部署方便一台普通服务器就能跑起来对中小养殖场非常友好。前端配合Vue做一个管理后台手机上装个微信小程序给现场工人用两头数据同源班组不用再拿着纸质表来回跑。适合谁来参考这套设计一类是正在做毕业设计、需要一个完整全栈项目骨架的同学另一类是真正在搞养殖数字化、想自建或选型系统的从业者。这两类人看完这篇文章都会有不小的收获——前者能照葫芦画瓢搭出完整项目后者能明白一套合格的管理系统应该覆盖哪些环节不至于被供应商忽悠。1.2 从选题到落地的整体思路这套系统的设计我从一开始就定了几个原则能用默认配置解决的不引入额外组件能用简单方案解决的绝不用复杂架构。市面上的SpringCloud微服务、消息队列、分布式事务在中小型水产养殖场景下完全是过度设计。一台服务器单机部署一个SpringBoot单体应用一个MySQL实例存数据足够应对养殖场内几十个终端同时操作的并发量。整个项目拆成三条线来走。第一条线是设备接入主要是水质监测仪通过Modbus协议上报温度、pH值、溶氧量这些数据系统定时去采集并存入时序表。第二条线是业务流转从投放苗种开始到养殖工单的生成再到饲料药品的领用最后到成鱼出塘销售所有环节在线上留痕。第三条线是追溯分析把池塘产量、饲料系数、用药成本这些数据汇总起来算一算哪个池子效益高、哪个饲料性价比好为下一季养殖做决策参考。这三条线不是互相独立的而是通过**批次Batch**这个概念串起来的一个批次对应一次放苗到出塘的完整周期水质数据挂在批次下工单挂在批次下成本也归集到批次下这样后续核算利润时按批次汇总就能算得清清楚楚。技术架构上后端分四层Controller只做参数接收和响应包装Service专注业务规则Mapper负责数据库交互Entity映射表结构。这样分工改起来边界特别清晰——想加一个报表接口不用动现有业务的代码想换掉底层数据库连接池也不影响上层业务逻辑。后面每个环节我都会具体讲实现时遇到的关键点和坑。2. 系统架构设计与数据建模2.1 后端分层架构的取舍架构设计的核心是让代码结构本身能跟着业务成长。我见过不少项目一开始图省事把SQL直接写在Controller里导致Service层形同虚设。后来业务一扩展改一个查询条件要翻遍几十个接口维护成本高得吓人。SpringBoot基于MVC的分层其实非常直白但很多人只是把这套结构当成惯例照抄没去深究背后的意义。这套系统我按五个包来组织代码controller接收HTTP请求做参数校验调用Service接口包装统一响应结果service业务逻辑核心比如判断“当前溶解氧过低时自动生成增氧机开启工单”的规则就放在这里mapper数据访问层Spring Boot集成MyBatis-Plus写SQL或者用LambdaQueryWrapper构造查询条件entity实体类字段对应数据库表config配置类比如跨域配置、拦截器注册、定时任务开关关于MyBatis-Plus多说一句。很多人纠结用JPA还是MyBatis我的经验是如果项目里涉及复杂的多表关联统计、动态报表SQLMyBatis-Plus的灵活度明显更高。比如“按月份、池塘、品种三个维度统计饲料总消耗量”用JPA写动态查询会比较绕而MyBatis-Plus里直接用注解SQL或XML写原生SQL逻辑清晰执行计划也好控制。配合它的分页插件列表查询这块基本不用自己操心。2.2 数据库表设计的核心思路表结构是整个系统的地基设计时吃亏最多。水产养殖领域的数据有一个明显特点实体少但实体之间的关联维度多。一个池塘有水质日数据、工单记录、成本流水、出塘记录如果不提前想清楚主表和子表的粒度后面写统计SQL会非常痛苦。我设计核心表时按这个思路来拆分表名用途关键字段说明pond_info池塘档案塘口编号、面积、水深、当前状态闲置、养殖中batch_info养殖批次关联塘口ID、放苗时间、苗种类型、预计上市时间water_quality水质数据关联批次ID、采集时间、温度、pH、溶氧、氨氮work_order工单表工单类型投料、增氧、换水、用药、处理状态、执行人feed_stock饲料库存饲料名称、规格、库存量、安全库存阈值feed_inout出入库明细单据类型入库/领用、领用量、关联批次sale_order出塘销售单关联批次、售出重量、单价、总金额、客户名称sys_user系统账号用户名、加密密码、角色标识这里有个关键点很多人容易忽略水质数据不要和业务数据混在一张表里。水质的采集频率高一天可能记录24次甚至更多一个月就是七百多条一年数据量会迅速膨胀。如果把它跟工单、出入库挤在同一张业务表里查询工单时会连带扫描大量用不到的采样记录明明是毫秒级的接口硬生生会拖到一两秒。把它单独拎出来并按时间做分区或者建索引统计和业务查询互不干扰。这是我做过好几个项目后总结出的最值得借鉴的分表原则。具体到SQL细节拿水质记录表举例我通常会加两个索引CREATE TABLE water_quality ( id BIGINT AUTO_INCREMENT PRIMARY KEY, batch_id BIGINT NOT NULL COMMENT 养殖批次ID, collect_time DATETIME NOT NULL COMMENT 采集时间, temperature DECIMAL(5,2), ph_value DECIMAL(4,2), dissolved_oxygen DECIMAL(4,2), ammonia_nitrogen DECIMAL(6,3), UNIQUE KEY uk_batch_time (batch_id, collect_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT水质监测数据表;联合唯一索引的作用是防止同一批次在同一时间点产生两条重复记录。之所以把batch_id放第一位是因为系统的查询模式几乎都是“先指定某个批次再看时间范围”联合索引能直接命中不需要回表找数据。3. 核心功能模块与业务闭环实现3.1 水质监测模块数据采集与预警联动水质监测是水产养殖系统里最有“行业属性”的功能也是最容易做砸的部分。市面上的水质监测仪接口参差不齐有的走RS485串口协议有的支持标准Modbus TCP还有的直接提供HTTP上报接口。如果系统把采集逻辑写死在模块内部后面换设备就得改代码非常僵化。所以我在设计时把采集端抽象成一个DeviceAdapter接口每种设备写一个实现类通过工厂模式按设备类型加载。核心的采集任务我用SpringBoot自带的Scheduled注解来驱动。之前有人建议引入Elastic-Job或者xxl-job做分布式调度考虑到单机部署完全用不上徒增运维复杂度。定时任务写法很简单Component public class WaterQualityCollectTask { private final WaterQualityService qualityService; public WaterQualityCollectTask(WaterQualityService qualityService) { this.qualityService qualityService; } Scheduled(fixedRate 3600000) public void collectAllPonds() { ListBatchInfo activeBatches batchService.listActiveByStatus(); for (BatchInfo batch : activeBatches) { DeviceData data deviceAdapterFactory.getAdapter(batch.getDeviceType()) .readData(batch.getDeviceAddress()); qualityService.saveBatchSample(batch.getId(), data); qualityService.checkAndAlert(batch, data); } } }fixedRate 3600000表示每小时采集一次这个频率是我和几位一线养殖户聊过之后定的。频率太低溶氧骤降发现不了频率太高比如十分钟一次数据量是大了但设备寿命消耗快而且对养殖决策并没有额外的帮助。养虾和养鱼对数据的敏感度不同配置里我把采集间隔做成可调的water.collect.interval3600000换品种时改配置即可。预警规则推荐条件组合的方式。单纯设置“溶氧低于3mg/L报警”太单一实际养殖场景中夜间和高温天溶氧下降快白天水草光合作用强溶氧往往偏高此时警报意义不大。我把规则设计成多维度的溶氧低于阈值且持续十五分钟以上才触发告警这样可以过滤掉传感器波动引起的误报。预警产生后系统自动生成一条待处理的工单推送给当班技术员并发送短信提醒。这套“采集-分析-预警-工单”的联动机制是整个水质模块最核心的价值。3.2 养殖批次与工单流转批次的概念前面提过是贯穿整个系统的业务主线。一个批次从投放苗种开始生成当时记录下品种、数量、规格、来源供应商养殖过程中所有饲料领用、用药记录、水质数据都归到这个批次下到最后出塘时销售订单关联批次系统自动汇总这个批次的收入和成本算出毛利。工单模块本质上是现场管理的抓手。养殖工人每天要做的事不是对着电脑看数据而是在手机小程序上接收任务、执行任务、填写结果。常见的工单类型有投料工单按饲料系数和当前存塘量计算每餐投喂量增氧工单水质预警触发或定时巡检需要对增氧机的运行时间做调整换水工单记录换水量、换水前后的水质对比用药工单涉及药品名称、用量、用药目的、休药期计算我把工单的执行流程做成“待处理 - 进行中 - 已完成 - 已确认”。前三个状态由现场人员操作最后一个“已确认”必须由管理人员在后台点击目的是形成一道复核关卡。实际跑下来这个双人复核机制对防止虚假工单非常有效成本却不增加多少——现场人员拍个完成照片上传管理员手机上看一眼就确认了。工单关联批次还有一个额外的好处月底统计饲料、药品的吨数时不用翻流水账直接按批次汇总工单就行。哪个批次饲料系数高、哪个批次的药费异常一眼就能看出来。3.3 进销存与成本核算库存模块看起来是标准的进销存但水产行业有它的特殊性不能照搬超市库存系统那套逻辑。饲料的库存单位是“袋”或者“吨”还好说但药品的效期管理苗种的批次追溯都是容易出问题的点。饲料入库数据来源是采购单供应商送货时仓库管理员在系统里录入数量、单价、生产日期。领料出库时必须选择对应的养殖批次这样一个“饲料去向”就明确了。系统里的库存报表不仅能看到当前结余还能追溯某一批次饲料是什么时候进来的、用到了哪个池塘。有一次仓库说饲料少了五十袋账面库存也对不上最后查出来是领料时批次选错了找到流水之后几分钟就修正了这在以前纸质账本时代想都不敢想。成本核算方面我设计了四张基础成本类型苗种成本、饲料成本、药品成本、人工水电。实际成本来自出入库单人工水电可以在月度结算单里统一录入。出塘销售之后系统按批次计算毛利毛利 该批次销售总金额 - 苗种成本 - 饲料成本 - 药品成本 - 分摊人工水电听上去简单但很多系统做不好根源在于成本归属不清晰。比如一种药同时给三个批次用过怎么分摊我采用“工单记录明细”的方式解决每次用药工单里都标明批次月底按各批次的用药工单汇总金额这样就无需事后人为估摊。分摊是估算工单是实际财务视角差别很大。3.4 销售出塘与数据统计报表销售模块的设计逻辑其实可以更简单直接创建销售单、关联批次、填入销售量与价格。真正复杂的是后续的凭证核对和统计报表。销售单据生成后系统自动更新批次状态。已出塘的批次不能再录入饲料领用和水质数据防止“塘都空了还在投料”的逻辑错误。回款记录放在销售订单的子表里记录现款和赊账按月汇总回款率。统计报表是管理层最看重的模块。我保留了三个固定报表日报表各塘口当日投喂量、用药量、水质最大值最小值月报表各批次饲料系数、存活率、累计成本出塘汇总表按时间段统计各品种销量、单价走势、总销售额报表的实现比起在Java代码里循环拼接Excel表格更推荐直接在数据库层把数据聚合好后端接口只返回结构化数据前端用工具渲染图表。聚合SQL提前用GROUP BY把粒度处理好接口的响应时间能控制在300毫秒以内。这一层做好了管理员在手机上查看月度简报完全不卡。4. 权限控制、登录与操作审计4.1 多角色权限设计水产养殖系统的用户角色不能照搬企业OA那套“管理员/普通用户”的简单二分法。实际场景里总经理关心销售利润报表技术员需要操作水质和工单仓库管理员只接触出入库页面工人连后台都不该进只在小程序端看到任务列表就行。角色多了权限划分就必须细。我用了RBAC模型角色-权限-用户但落地时做了简化不用动辄几万行的按钮级权限控制而是用Shiro框架按菜单-按钮两级控制。后端接口侧用Shiro的RequiresPermissions注解进行方法级拦截GetMapping(/batch/summary) RequiresPermissions(batch:summary:view) public ApiResponseListBatchSummaryVO batchSummary() { return ApiResponse.ok(batchService.getBatchSummary()); }这样设计的好处是前端不展示没有权限的菜单后端即使接口被猜到URL没有对应权限也进不来。Shiro在中小型系统里配置简单比Spring Security的入门门槛低很多这是实际过程中对比体验后的决策。工人端小程序不直接调用后台接口而是走一个独立的/mobile前缀接口内部统一识别当前登录用户的角色工人工号自动关联所属班组。这条路隔离了管理后台的复杂权限逻辑小程序端代码也更精简加载更快。4.2 登录安全与密码处理安全管理是最容易被人忽视的部分有些项目上线半年后密码还是明文存在库里这让我非常不理解。毕竟一套养殖管理系统里存着客户姓名、手机号、价格信息、报销流水一旦泄露信任就崩塌了。密码存储我坚持用加盐哈希方案具体选用的是BCrypt算法。BCrypt最突出的优势是内置盐值即使两个人设了相同密码哈希后的结果也完全不同而且计算强度可以调整能有效抵御彩虹表攻击和暴力破解。Spring Security里直接提供BCryptPasswordEncoder不过项目里我用的是Shiro同样集成了Sha256Hash加盐方案核心代码也很简单String hashedPwd new Sha256Hash(rawPassword, userSalt, 1024).toHex();这里多说一句salt一定要用SecureRandom生成随机数不要用用户的用户名做盐。用户名盐值等于“半公开信息”一旦数据库泄露攻击者挨个试就能猜到密码规律。生成盐值的代码public static String generateSalt() { SecureRandom random new SecureRandom(); byte[] bytes new byte[16]; random.nextBytes(bytes); return HexUtil.encodeHexStr(bytes); }登录接口做了防暴力破解限制同一IP五分钟内失败超过十次锁定十五分钟。这个策略上线后拦截了不知道多少扫描器和撞库脚本成本几乎为零效果却十分明显。4.3 操作留痕与审计日志审计日志的价值平时看不出来一旦出现争议它就是关键依据。水产养殖行业里工伤认定、饲料质量纠纷、销售价格争议都需要操作历史来还原现场。我用AOP切面统一记录关键操作日志。在某Service方法上加自定义AuditLog注解切面自动记录操作人、操作时间、IP、方法名、入参摘要。敏感字段在日志里做脱敏处理比如手机号只保留前三位和后四位中间用星号替代。这一步如果在切面里统一处理业务代码不用写任何日志逻辑后续维护也不用挨个方法去翻。运维侧我还开启MySQL的general_log用于更底层的审计追踪。生产上一般业务库不开这个日志因为数据量太大但部署在本地的小型系统开着完全没问题出现问题排查SQL时特别有用。为了控制日志文件膨胀用logrotate轮转保留最近十五天的日志。5. 部署上线与环境优化5.1 单机部署还是上云之前有人问我这套系统部署在云服务器还是本地机房我的回答是视体量而定。一个中等规模养殖场同时在线的操作人员不会超过五十人高峰期是早晚上报工单的时候单机部署一台2核4G的云主机完全扛得住。如果养殖场网络条件差内网部署方案更靠谱用一台普通办公电脑加上本地MySQL实例就能跑起来。单机部署时服务器上只需要装三样东西JDK、MySQL、Nginx。SpringBoot应用打成Fat Jar用systemd管理进程[Unit] DescriptionAquaculture Management System Afternetwork.target [Service] Userappuser ExecStart/usr/bin/java -Xms512m -Xmx1024m -jar /data/app/aquaculture-system.jar Restartalways RestartSec10 [Install] WantedBymulti-user.target-Xms和-Xmx设置成相同值这一步很关键能够避免JVM启动后由于动态调整堆大小导致的性能震荡。内存富余的机器可以适当调大比如2核4G的机器给-Xmx1536m就差不多了JVM之外的缓存、线程栈、线程池也要占内存。Nginx的角色是做反向代理和静态资源托管将/api/前缀的请求转发到后端服务其他路径直接返回Vue构建出的静态文件。这样用户访问一个域名就搞定前后端不用开两个端口也不用解决跨域问题。生产环境里跨域问题在本地开发频繁出现上线上改成同域部署后相关的过滤器直接给去掉减少一层的资源消耗和隐患。5.2 数据库备份与容灾策略数据是这套系统最值钱的部分水质历史记录、养殖批次成本、客户销售记录一旦丢失损失远超过服务器的采购成本。所以我将数据库备份策略视为硬性要求脚本化执行。每日凌晨两点通过mysqldump做全量备份#!/bin/bash BACKUP_DIR/data/backup/mysql DATE$(date %Y%m%d_%H%M%S) DB_USERbackup_user DB_PASS****** DB_NAMEaquaculture mysqldump -u${DB_USER} -p${DB_PASS} --single-transaction --quick \ ${DB_NAME} ${BACKUP_DIR}/${DB_NAME}_${DATE}.sql gzip ${BACKUP_DIR}/${DB_NAME}_${DATE}.sql find ${BACKUP_DIR} -name *.sql.gz -mtime 30 -delete--single-transaction参数在InnoDB引擎下依靠事务一致性读取不对业务表加锁备份过程中不影响线上写入。对于经常有水质数据入库的场景这个参数是必须的否则每次备份时前端写入都会短暂阻塞。备份文件通过任务每天同步到另一台服务器上防止服务器硬盘故障导致备份一并丢失。恢复流程最好也提前演练一次别等数据丢了才手忙脚乱。我做过一次完整的恢复演练把生产环境的备份mysql文件导入测试库核对记录数无误前后大概用了二十分钟。从此团队上下对备份这事心里都有底了。5.3 前后端部署细节与问题前端Vue项目部署注意一个问题打包后的index.html里资源路径必须是相对路径即在vue.config.js中配置module.exports { publicPath: ./ }如果直接使用默认的/部署到服务器子目录或通过Nginx非根路径转发时CSS和JS资源全部404页面一片空白。之前有位同事在本地开发一切正常一到服务器访问就白屏查了好久才定位到是路径问题。Nginx配置建议开启Gzip压缩Vue打包之后的JS和CSS文件体积不小gzip级别设5传输体积能减少70%左右。静态资源缓存时间根据是否带hash后缀决定带hash的终身缓存index.html不缓存或者缓存很短时间确保发布后用户能拿到最新版本。后端方面SpringBoot配置文件里建议显式设置server.tomcat.max-threads200和server.tomcat.accept-count200。默认值虽然也能跑但并发稍高一点时Tomcat的默认工作线程数在Windows和Linux上的表现差异很大设置好这两个参数能把资源利用控制得更稳定。6. 常见问题与性能排查实录6.1 接口慢、数据库连接池耗尽项目上线运行一个月后出现了一个典型的性能问题每天早上八点到九点各塘口集中上报水质数据和提交工单时部分接口响应时间飙升到好几秒甚至偶尔报“连接池超时”错误。排查过程比较有代表性。第一步看SpringBoot日志发现大量HikariCP的Connection is not available, request timed out异常说明数据库连接被占满。第二步查MySQL的processlist发现有几十个Sleep状态的连接而且长时间不释放。第三步看代码定位到问题出在一个查询接口上有人在Mapper里一个方法循环调用了五十多次数据库查询每次查询占用一个连接循环过程中事务一直不提交连接池就被这几十个连接全占住了。解决方案有两层。第一层是“删代码”把循环查询改成一条SQL批量查出数据在内存中做关联组装。第二层是“调参数”连接池大小从默认的10改成maximum-pool-size30connection-timeout30000留足余量给突发流量。另外在Service层接口增加了Transactional(readOnlytrue)标记只读事务不让它长时间持有连接。这三步做完高峰时段接口耗时从三秒降到两百毫秒以内之后再没有超时告警。6.2 水质数据重复与主键冲突水质采集模块偶尔在重启或网络重连后会采集到同一时刻的重复记录。原因很典型定时任务执行期间应用刚好被重启重启后补偿机制又重新执行了一次采集。虽然我对时间粒度建了唯一索引但出现的却是Duplicate entry异常。处理办法是放弃“插入即报错”的思路改用ON DUPLICATE KEY UPDATE的SQL写法。在MyBatis的XML中这样配置insert idinsertOrUpdate parameterTypemap INSERT INTO water_quality (batch_id, collect_time, temperature, ph_value, dissolved_oxygen, ammonia_nitrogen) VALUES (#{batchId}, #{collectTime}, #{temp}, #{ph}, #{do}, #{nh3}) ON DUPLICATE KEY UPDATE temperature VALUES(temperature), ph_value VALUES(ph_value), dissolved_oxygen VALUES(dissolved_oxygen), ammonia_nitrogen VALUES(ammonia_nitrogen) /insert这招是把“先查再插”这种两步操作改成了一步原子操作既避免了并发环境下的重复写入又不会因为日志里刷报错吓到运维人员。执行效率上也只多了一次索引判断开销可忽略。6.3 缓存穿透热门的“今日数据”查询“首页今日数据概览”这个接口入口是一个大屏展示页所有登录用户打开系统第一眼就会请求这个接口。正常情况下扛几十个并发没什么问题。但如果遇到有人写脚本自动轮询或者水质采集任务频繁刷新首页数据会反复击穿数据库。这里我引入了简单的Redis缓存方案。首页数据的缓存策略是设置五分钟过期每次用户请求时判断缓存是否存在不存在再查数据库并回填。这样同一时间段内无论多少个用户打开首页数据库只会被查询一次。public HomeOverviewVO getHomeOverview() { String cacheKey home:overview; String cached redisTemplate.opsForValue().get(cacheKey); if (StringUtils.hasText(cached)) { return JSONUtil.toBean(cached, HomeOverviewVO.class); } HomeOverviewVO overview buildFromDatabase(); redisTemplate.opsForValue().set(cacheKey, JSONUtil.toJsonStr(overview), 5, TimeUnit.MINUTES); return overview; }为什么实际不追求实时因为首页概览上的“今日投喂量”“当前预警数”等指标本身来自定时任务的缓存表数据已经是分钟级延迟页面再缓存五分钟完全不影响决策。相反把这个热点接口的数据库压力全部挡掉整个系统的稳定性提升非常明显。6.4 快速排查问题日志与监控小工具项目上线后我建议至少部署这几个基础监控手段不需要引入PrometheusGrafana那样的重型组件Spring Boot Actuator暴露/actuator/health和/actuator/metrics端点配合定时脚本检查服务存活状态与JVM内存自定义日志切面打印每个接口的耗时与状态码超过500毫秒的请求在日志中输出WARN级别MySQL慢查询日志slow_query_logONlong_query_time2定期扫描分析跑得慢的SQL有一次生产环境反馈“出塘报表页面卡死”我先看接口耗时日志发现慢查询集中在报表统计接口上SQL扫描了整张三年的销售流水表。调优思路不是去优化这条SQL而是加了一张月度汇总表平时每天由定时任务把前一天的数据汇总进去报表月结时只看汇总表秒开。这种“预聚合”思路在数据量增大的场景下比单纯加索引可靠得多。7. 项目复盘与后续演进路线7.1 哪些地方做了以后觉得特别值复盘这个项目有几个设计决策事后证明特别值得第一“批次”作为核心主线。从一开始就把所有业务数据都挂到批次维度下避免了后来“想统计却对不上账”的尴尬。市面上一堆进销存系统不适合水产养殖就是因为它们只做了“库存流水”没有“批次生命周期”的概念。第二设备适配层抽象。水质监测仪硬件品牌多、协议杂年初换了一种传感器时只加了一个适配器类和几行配置整个系统零改动就接入了新设备。这在业界算是少有的可扩展性。第三预警和工单联动。让系统不只是“看板”还能推动现场执行。管理人员不用整天盯着屏幕工单会自动推给当班技术员。这套机制上线之后水质异常的响应时间从以前平均四十分钟缩短到十分钟以内直接降低了泛塘死鱼的风险。7.2 未来可以往哪些方向扩展这套系统目前定位是“管理记录”但养殖行业设备化和智能化的速度特别快。往后面向的方向我梳理过几个一是物联网设备深化接入。现在只接了水质监测和增氧机控制未来可以接入智能投料机、自动换水阀门、气象站把“被动记录”升级为“主动控制”。基于溶氧数据和气象预测自动决定增氧机启停这部分完全是可行的。二是饲料系数与生长曲线的数据建模。当水质数据、投料数据积累了一整年可以建立品种间的生长模型。输入当前水温、存塘量、溶氧推荐当日最佳投喂量。算法不用多高级线性回归和查表就够用但给养殖户省下的饲料成本相当可观。三是农场多基地扩展。现在是一套系统管单个养殖场未来需要支持集团化养殖场景多个基地、统一账号、跨基地调拨苗种饲料。架构层面上要加一层“基地ID”的租户隔离这个改动贯穿大部分表结构越早规划越好。7.3 给准备做类似系统的人几点建议最后几句话说在前头都是实打实的经验先把业务流程图画出来再写代码。我见过太多项目数据库还没建就开始写Controller结果表结构改了一版又一版。水产养殖的业务闭环并不复杂但一定要把“出库单怎么关联批次”“水质异常怎么生成工单”这些规则在纸上先理清楚。不要过度追求技术时髦。这个系统用SpringBoot单体架构现在已经稳定运行两年功能迭代了十几个版本。为什么不用微服务因为没有多个独立部署的诉求也没有分布式事务的复杂性。用最合适的武器解决最实际的问题才是真正的工程能力。给用户设计界面时多一点同理心。养殖工人大多四十岁以上字体要大、按钮要少、操作路径要短。小程序端我只保留三个入口我的任务、扫码上报、历史记录。功能全不代表好用一线的工人觉得顺手这个系统才算真正落地成功。这套系统从零到上线对我个人而言最大的收获不是技术栈更熟练了而是真正理解了什么叫“技术要跟着业务走”。水产养殖看似传统但数字化改造的潜力巨大每一个环节都有优化空间。希望这篇文章的拆解能给正在做类似系统的你一些参考方向。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进