ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于SpringBoot+Vue的冷链物流管理系统设计与实现

基于SpringBoot+Vue的冷链物流管理系统设计与实现 做冷链物流管理系统的念头最早来自一个朋友的冷库。他在物流园里租了三个库主营冻品和生鲜配送旺季一天要跑十几车但仓库里的温度记录全靠纸质表格出问题只能靠客户投诉往回反查。我当时帮他梳理需求时发现冷链这个场景比普通物流麻烦得多——温度是必须盯死的红线、批次追溯是行业底线、稍有疏漏整批货就可能报废。所以后来这套基于SpringBootVue的BS模式冷链物流管理系统源码在我手里成型时我没有把它当成普通的增删改查练习而是把冷链业务里那些真实的逻辑做进去了。这篇文章就把这套系统的设计思路、技术选型、数据库建模、前后端实现和部署坑点完整拆开讲一遍给准备做类似管理系统、或正在找毕业设计/项目参考的朋友一些可以直接抄作业的干货。这套系统是什么、能做什么概括说它就是一个跑在浏览器里的冷链物流管理后台覆盖了订单管理、运输调度、温度监控、冷库库存、批次追溯、人员权限这几个核心模块。技术栈是SpringBoot Vue MyBatis MySQL前后端分离的BS架构。适合这几类人参考一是要交付软件工程类毕业设计或课程项目的学生二是初入行的Java开发想看看一个完整项目的分层写法三是小规模冷链物流公司想自建管理系统、又不想上来就上重型ERP的。1. 冷链场景的独特痛点这个系统不能照搬普通进销存1.1 温度红线、批次追溯、时效压力带来的硬需求普通物流管理系统关注的核心是订单和运输状态货丢了可以赔、送慢了可以催。但冷链物流多了一条命脉就是温度。冻品要求零下18度以下生鲜要求2到8度不同品类的温控区间完全不同。温度一旦掉出区间整批货的质检就过不去到客户端就是拒收和索赔这个经济损失不是几十块钱的事而是整车货全损。所以我在设计系统的时候把温度监控做成了独立模块而不是订单里的一个附属字段。温控设备车载温控探头、冷库传感器定时上报温度数据系统负责存储、展示曲线、超阈值报警。同时每批货从入库、出库、装车、运输到签收温控记录必须能按批次串起来。这就引出了第二个硬需求批次追溯。冷链行业规定每批货都要留全程温度轨迹出了问题能快速定位是仓库环节还是运输环节出了问题。还有时效压力。冷库周转快一天进出几十批次订单到车辆到任务的匹配如果全靠电话和Excel旺季必乱。业务上需要系统自动把待配送订单分配给车辆和司机并在仪表盘上直观展示当前所有在途任务的状态。1.2 系统角色与核心业务闭环我把使用这套系统的人分成四类角色管理员、调度员、司机端PC端填报、质检员。管理员管人员和基础数据调度员创建运输任务、派车司机在运输途中更新状态质检员审核温度记录和处理预警。这个角色的划分不是随便定的而是根据冷链公司真实岗位职责来的每个角色能看到的菜单和数据范围都不一样。系统的核心业务闭环是这样的客户下单产生销售订单订单里写明货品种类和温控要求调度员根据订单生成运输任务并指派冷链车车辆装货出库系统记录出库温度运输途中随车温控设备持续上报温度到达目的地后司机确认签收质检员核对该批次的全程温度记录最后归档到批次追溯档案。这样一条线走下来订单、车辆、库存、温度、批次全部联动而不是各模块各玩各的。1.3 为什么BS模式是冷链管理场景的正确选择技术选型时我明确排除了C/S桌面客户端。冷链公司的使用场景是多个冷库、多个办公点管理人员可能在外地也要查看温度数据如果每个点都要装客户端光升级维护就够喝一壶。BS模式的优势很明显浏览器直接访问手机上也能打开Vue做响应式适配服务端集中部署升级只改服务器各网点零安装成本。SpringBoot后端天然支持跨域和REST APIVue前端打包后扔到静态资源目录或Nginx里就能跑整个部署链路非常干脆。2. 技术选型的真实考量SpringBoot、Vue、MyBatis、MySQL各司其职2.1 SpringBoot内嵌容器与自动配置把开发效率拉满为什么选SpringBoot而不是SSH或者裸Servlet这个问题我思考过。做这类管理系统大部分工作集中在CRUD、事务控制、权限校验、定时任务这些通用能力上。SpringBoot的核心价值是两个内嵌Tomcat打一个jar包直接跑不用单独装Web服务器自动配置数据源、事务管理器、Web MVC这些基础设施零配置就能用。像温度预警这种场景SpringBoot的Scheduled定时任务一行注解就能启动不需要额外引入复杂框架。另外SpringBoot的Starter生态非常成熟我用到的组件基本都是引入一个依赖就接入spring-boot-starter-web提供Web能力mybatis-spring-boot-starter负责MyBatis整合mysql-connector-java负责数据库驱动。这种“依赖即服务”的方式特别适合快速交付项目从空目录到跑起来核心接口只需要半天。2.2 Vue组件化和数据驱动是后台界面的最优解前端这一块如果还用Thymeleaf配JQuery那套开发效率和后期维护都会很痛苦。冷链管理系统的界面有大量表格、表单、状态切换、实时数据展示Vue的组件化正好治这个。我把整个前端按功能拆成组件比如订单表格、温控曲线、车辆分配弹窗、预警消息列表每个组件只干自己那一摊事数据流通过Vuex或Pinia统一管理页面切来切去状态不丢。还有一点Vue配Element UI现在用Element Plus更多做后台系统几乎是标准答案表格、分页、表单校验、日期选择器、消息提示都是现成的不需要自己手搓UI库。对于冷链这种信息密度高的后台Element的表单校验和表格操作列能省掉大量开发时间。2.3 MyBatis复杂多表查询场景下半ORM的可控性选MyBatis而不选JPAHibernate是我刻意权衡的结果。这个系统的查询复杂度比普通CRUD高不少典型的如“查某辆车在某个时间段内执行过的所有运输任务并关联出每个任务的订单明细、货品信息和温控记录”这种多表关联、条件动态拼接的场景MyBatis的SQL可控性优势非常明显。我可以在XML里精确控制每一条SQL慢查询优化时直接改SQL就行不用去猜框架生成的语句是什么样子。同时MyBatis的动态SQLif、where、foreach标签在冷链场景很有用比如温度记录查询可能是按时间范围、按车辆、按设备、按是否超温的组合查询用动态SQL拼条件比JPA的Specification直观多了。至于缓存MyBatis一级缓存默认开启二级缓存在这个项目里我没开因为冷链数据实时性要求高缓存容易出现脏读。2.4 MySQL关系型数据库稳稳托住业务数据MySQL在冷链管理系统里的定位是业务主库承担订单、车辆、用户、库存、温控记录这些结构化数据的持久化。选择它的理由很朴素团队熟悉度高、部署运维简单、事务支持满足业务一致性要求。订单创建、分配车辆、扣减库存这些操作必须在一个事务里避免出现订单派了车但库存没扣、或者库存扣了订单却没创建这种错误。温控记录表是数据量最大的表一台车10分钟上报一次加仓库传感器一天的记录量也不小。MySQL这边我用的是按月分表策略或者至少做好索引设计查询时带上时间范围条件否则时间一长查询性能会明显下降。这部分在后面的建表设计里详细展开。2.5 2025年版本的合理搭配建议技术版本上我给一个比较稳妥的组合也是我实际用的后端SpringBoot 2.7.x不要盲目追3.x如果你的MyBatis整合经验和依赖习惯还停留在2.x2.7足够稳Java 8或11MyBatis 3.5.xMySQL 8.0。前端Vue 3.4 Vite 5 Element Plus Pinia。这个组合在2025年依然是成熟稳定、资料最多的搭配。热词里有人提到“SpringBoot版本太高”导致各种兼容问题我的经验是SpringBoot 3.x对JDK 17和Jakarta命名空间有硬性要求如果刚开始做项目2.7更省心。3. 核心业务模块与数据库建模温度、批次、订单怎么串成一条线3.1 模块拆解六条业务线构成系统骨架我把整个系统拆成六个功能模块这也是数据库设计的基础系统管理用户、角色、菜单权限RBAC模型用户角色多对多角色菜单多对多订单管理客户订单的创建、审核、状态流转待分配、已派车、运输中、已签收运输管理车辆信息、司机、运输任务、出车记录温度监控温控设备、温度上报记录、超温预警冷库管理冷库仓库、库位、库存批次入库、出库、盘点批次追溯以货品批次为主线串联订单、运输、温度、签收全链路模块之间不是孤立的订单表要关联运输任务表运输任务表要关联车辆表和司机表批次表要关联订单表和温控记录表。这条线理顺了后面所有查询都顺畅。3.2 核心表结构设计与建表SQL这里给几张关键表的建表SQL都是我在实际项目中调过的结构。首先是订单表CREATE TABLE order_info ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键ID, order_no varchar(32) NOT NULL COMMENT 订单编号, customer_name varchar(64) DEFAULT NULL COMMENT 客户名称, goods_name varchar(64) NOT NULL COMMENT 货品名称, goods_type tinyint(4) NOT NULL COMMENT 货品类型 1-冻品 2-生鲜 3-常温, temp_min decimal(4,1) DEFAULT NULL COMMENT 最低温控要求, temp_max decimal(4,1) DEFAULT NULL COMMENT 最高温控要求, quantity decimal(10,2) NOT NULL COMMENT 数量件/吨, start_address varchar(128) DEFAULT NULL COMMENT 起运地, end_address varchar(128) DEFAULT NULL COMMENT 目的地, status tinyint(4) NOT NULL COMMENT 状态 1-待分配 2-已派车 3-运输中 4-已签收 5-已取消, create_time datetime NOT NULL COMMENT 创建时间, update_time datetime NOT NULL COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_status (status), KEY idx_create_time (create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单信息表;订单表里我给order_no做了唯一索引给status和create_time各建了普通索引这是为了支撑“按状态列表查询”和“按时间范围查询订单”这两个高频场景。goods_type和温控区间字段是冷链特有的普通物流表不会设计这两个字段但它们直接决定了后续温度预警的判断逻辑。温控记录表是另一个关键表CREATE TABLE temp_record ( id bigint(20) NOT NULL AUTO_INCREMENT, device_no varchar(32) NOT NULL COMMENT 设备编号, batch_no varchar(32) NOT NULL COMMENT 批次号, temp_value decimal(5,2) NOT NULL COMMENT 温度值摄氏度, humidity decimal(5,2) DEFAULT NULL COMMENT 湿度值, record_time datetime NOT NULL COMMENT 上报时间, is_alert tinyint(1) NOT NULL DEFAULT 0 COMMENT 是否超温 0-否 1-是, PRIMARY KEY (id), KEY idx_batch_time (batch_no, record_time), KEY idx_device_time (device_no, record_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT温控记录表;这张表的索引设计我特意做成了联合索引因为查询模式很固定要么按批次查全程温度曲线要么按设备查最近记录。联合索引(batch_no, record_time)能直接覆盖“查某批次按时间排序的温度记录”这个最核心的查询不需要回表。等数据量上来再考虑按月分表表结构保持不变查询时路由到对应月份的表即可。批次表和库存表的设计逻辑我不展开全部SQL但说几个要点批次表goods_batch以batch_no为唯一键关联order_no和温控记录冷库库存表storage_stock同时存batch_no、入库温度、出库温度、剩余数量和库位编号。这样任何一个柜台问到“某批货现在在哪、温度记录是否完整”都能一条链路查到底。3.3 温控记录数据量增长后的查询优化思路温控记录是典型的时序数据如果不加规划半年后这张表就会变成几百万行甚至上千万行。我用的处理方式是“索引先行、分月分区兜底”。在数据量还没到百万级时联合索引足够扛住到了百万级以上按月份做表分区或者干脆建月度表查询语句里强制带上record_time范围。另外超温预警的is_alert字段不适合单独建索引因为它区分度太低绝大多数记录都是正常的真正的做法是每天凌晨跑一个统计任务把当天超温记录汇总到预警表里报表查询只查汇总表。4. 后端实现的关键环节从配置到Mapper再到服务层逻辑4.1 项目分层结构后端我按经典的四层结构组织包controller、service、mapper、entity。Entity里的对象和数据库表字段一一对应Mapper负责SQL交互Service负责业务逻辑和事务Controller只做参数接收和结果返回。强调一点不要把业务逻辑写在Controller里哪怕是很简单的校验。冷链系统后面要加权限、加日志、加消息推送如果业务逻辑都堆在Controller层改一处要动一片。4.2 application.yml配置里的几个细节SpringBoot的配置文件是项目的起点也是坑最多的位置之一。我给出一份核心配置片段server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/cold_chain?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: your_password jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.coldchain.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl这里三个细节值得划重点。第一个数据库连接URL里的serverTimezoneAsia/Shanghai必须加MySQL 8.x默认时区是UTC不加的话时间字段出来会差8小时。第二个mapUnderscoreToCamelCase这个配置非常关键数据库字段order_no才能自动映射到实体类里的orderNo否则每个字段都要写TableField注解。第三个log-impl配置成StdOutImpl开发阶段能看到每条SQL执行情况排查联查问题很有用上线前再换成别的日志策略。4.3 MyBatis Mapper层的XML写法多表联查与动态SQLMyBatis的核心在Mapper层尤其是XML的写法。我拿“查询运输任务详情”这个复杂查询举例它需要关联运输任务表、订单表、车辆表、司机表四张表select idselectTaskDetail resultTypecom.coldchain.entity.vo.TaskDetailVO SELECT t.id AS task_id, t.task_no, o.order_no, o.goods_name, o.goods_type, o.temp_min, o.temp_max, v.plate_no, v.driver_name, v.driver_phone, t.status, t.departure_time, t.arrive_time FROM transport_task t LEFT JOIN order_info o ON t.order_id o.id LEFT JOIN vehicle_info v ON t.vehicle_id v.id WHERE t.id #{id} /select注意我用的是LEFT JOIN而不是INNER JOIN为什么因为运输任务创建时可能订单信息还在完善中如果用内连接任务和订单任何一方缺失时查询结果就为空前端会出现数据闪烁。LEFT JOIN保证任务列表始终完整订单信息没有就返回空字段逻辑上更稳妥。动态SQL在温控记录查询里用得最多。比如超温汇总查询用户可能只选时间范围、不选设备也可能既选设备又选批次select idselectTempAlerts resultTypecom.coldchain.entity.vo.TempAlertVO SELECT device_no, batch_no, temp_value, record_time FROM temp_record where if testdeviceNo ! null and deviceNo ! AND device_no #{deviceNo} /if if testbatchNo ! null and batchNo ! AND batch_no #{batchNo} /if if teststartTime ! null AND record_time gt; #{startTime} /if if testendTime ! null AND record_time lt; #{endTime} /if AND is_alert 1 /where ORDER BY record_time DESC /select标签会自动处理AND前缀比手写WHERE 11优雅也不容易出错。这是我强烈推荐的一个用法既解决条件拼接问题又保留SQL的可读性。4.4 Service层的事务与业务规则派车和出库为什么必须在一个事务里Service层是业务规则的核心。我举一个代表性的场景订单派车出库。操作包括改订单状态、创建运输任务、标记车辆已出库、扣减库存。这四个操作必须保证要么全成功要么全失败否则就会出现“订单显示已派车库存还是满的”这种对不上的问题。实现方式就是Spring的Transactional注解Service public class TransportService { Transactional(rollbackFor Exception.class) public void dispatchVehicle(DispatchRequest request) { // 1. 校验订单存在且状态为待分配 OrderInfo order orderMapper.selectById(request.getOrderId()); if (order null || order.getStatus() ! 1) { throw new BusinessException(订单不存在或当前状态不可派车); } // 2. 更新订单状态为已派车 orderMapper.updateStatus(request.getOrderId(), 2); // 3. 创建运输任务 transportTaskMapper.insert(buildTask(request)); // 4. 扣减库存 stockMapper.deductStock(request.getOrderId(), request.getQuantity()); } }这段代码里有两个容易踩的坑。第一个Transactional注解的rollbackFor必须设置成Exception.class默认情况下它只回滚RuntimeException像BusinessException这种自定义异常如果不指定rollbackFor事务是不会回滚的。第二个事务方法里的查询和更新尽量不要跨Service互相调用避免出现“事务嵌套失效”的问题如果确实要调用外部注入的方式而不是this调用。关于订单状态流转我用了一个简单的状态机待分配(1) - 已派车(2) - 运输中(3) - 已签收(4)加上一个取消(5)的旁路。每个状态的迁移都在Service层做校验比如已签收的订单不允许再派车已取消的订单不允许再更新温度记录。状态机不用引框架一个if-else校验就够了但校验逻辑必须集中别散落在各个Controler里。4.5 Controller统一返回结构与分页查询Controller层我统一返回一个Result对象里面包含code、message、data三个字段code为200表示成功其他为业务错误码。前端Axios拦截器根据code统一处理错误提示而不是每个接口各写各的返回格式。分页查询我用PageHelper引入依赖后一行代码搞定PageHelper.startPage(pageNum, pageSize); ListOrderVO list orderMapper.selectOrderPage(query); PageInfoOrderVO pageInfo new PageInfo(list);PageHelper的原理是把分页参数绑定到当前线程的ThreadLocal然后通过MyBatis插件拦截下一条SQL自动拼接LIMIT。这里有个注意事项PageHelper.startPage之后必须紧跟着执行一条Mapper查询中间不能穿插其他查询否则分页参数会被错乱的SQL消费掉查出来的数据就对不上了。这个错误很隐蔽我调试过两次才定位到写下来提醒一下。4.6 温度预警定时任务Scheduled如何扫描超温记录冷链系统里最有价值的自动化功能就是超温预警。我在系统里写了一个定时任务每五分钟扫描一次最近十分钟内的温控记录发现超温就写入预警表并通知调度员Component public class TempAlertTask { Scheduled(cron 0 */5 * * * *) public void scanOverTemp() { // 查询最近10分钟温控记录中的超温数据 ListTempRecord records tempRecordMapper.selectRecentAlert(DateUtil.addMinutes(new Date(), -10)); for (TempRecord record : records) { // 已处理过的不重复写入 if (alertMapper.checkExists(record.getId())) { continue; } TempAlert alert new TempAlert(); alert.setBatchNo(record.getBatchNo()); alert.setDeviceNo(record.getDeviceNo()); alert.setTempValue(record.getTempValue()); alert.setAlertTime(record.getRecordTime()); alert.setStatus(0); alertMapper.insert(alert); } } }每个货品的温控区间是写在订单表里的定时任务判断超温时要先查一下这个批次对应的订单温控范围再和上报温度做比较。生成预警记录后前端仪表盘的预警列表会通过轮询接口拉取新数据页面顶部出现红点提醒。这套流程不需要引入消息队列简单够用等业务量大了再演进到RabbitMQ推送也不迟。5. Vue前端实现从路由守卫到温度曲线展示5.1 前端项目搭建与目录组织前端用的是Vue 3 Vite创建项目npm create vitelatest coldchain-web -- --template vue cd coldchain-web npm install element-plus element-plus/icons-vue axios vue-router4 pinia echarts目录结构我习惯这样组织views存放页面组件router存放路由配置store存放Pinia状态api存放所有后端接口调用按模块拆分components存放复用组件。一个容易被忽略的细节是api目录的每个接口方法都应该是封装好的函数页面里直接调用这些函数不要在页面组件里直接写axios请求字符串否则后端接口路径一改动前端要全局搜索替换。5.2 登录态与路由拦截的实现逻辑登录页走的是JWT方案后端登录接口返回token前端存在sessionStorage里。路由守卫的作用是没token时强制跳转登录页有token时如果访问登录页就重定向回首页router.beforeEach((to, from, next) { const token sessionStorage.getItem(token) if (to.path /login) { token ? next(/) : next() } else { token ? next() : next(/login) } })这里我补充一个后端配合的要点SpringBoot后端写了一个拦截器对非登录接口统一校验请求头里的Authorization字段校验失败返回401。前端Axios拦截器收到401后清除本地token并跳转登录页。这个配合逻辑前后端都要有缺一端就会出体验问题。菜单权限我用的是动态路由方案。用户登录后后端根据角色返回他有权访问的菜单列表前端动态添加路由。冷链系统里质检员看不到车辆调度菜单司机看不到系统管理菜单靠的就是这套角色路由控制。5.3 仪表盘与温度监控页面的数据展示仪表盘是整个系统的门面我放了四块内容顶部统计卡片今日订单数、在途车辆数、超温预警数、库存总量、温度曲线图最近24小时某批次的温度折线、运输任务进度列表、预警消息滚动条。ECharts的温度曲线是这里的核心const chart echarts.init(document.getElementById(tempChart)) const option { title: { text: 批次温度监控曲线 }, tooltip: { trigger: axis }, xAxis: { type: time }, yAxis: { type: value, name: 温度(℃) }, series: [{ type: line, data: tempDataList, markLine: { data: [ { yAxis: orderInfo.tempMin, lineStyle: { color: #f56c6c } }, { yAxis: orderInfo.tempMax, lineStyle: { color: #f56c6c } } ] } }] } chart.setOption(option)markLine在冷场场景里特别实用它可以在曲线上画出温控上下限的红线一旦温度曲线碰到红线视觉上立刻就能识别出异常点比看数字直观多了。前端每60秒调一次后端接口拉最新温度数据用setOption替换series.data实现曲线实时刷新不用整个图表重绘。5.4 订单管理页的状态流转交互订单管理页是典型的表格加状态操作页面。表格展示订单列表最后一列是操作按钮按钮的可点状态根据订单状态动态渲染。比如待分配的订单显示【派车】按钮已派车的显示【开始运输】运输中的显示【确认签收】已签收的不显示任何操作。点击【派车】会弹出一个Dialog里面是车辆选择下拉框只显示空闲车辆提交后调后端接口成功后刷新table。这里要注意一个前端交互细节table数据更新后要重新调用列表接口而不是本地手动改数据否则会出现接口更新成功但页面数据没刷新的问题。针对这个我封装了一个列表刷新方法所有操作完成后统一调用。5.5 前端打包与后端集成部署的两种方式前端最终要交付成可访问的页面有两种部署方式我都试过。方式一npm run build生成dist目录把dist里的静态文件直接复制到SpringBoot的src/main/resources/static目录下打包进jar这样一个jar就同时包含后端接口和前端页面部署最简单。方式二前端dist扔到NginxNginx配置反向代理/api路径到SpringBoot服务的8080端口适合前后端分离运维的场景。方式一有一个坑如果前端路由用的history模式SpringBoot访问非首页路径会404因为后端没有对应的Controller。解决方法是写一个WebMvcConfigurer把非静态文件路径全部转发到index.html或者干脆用hash模式。我实际项目中用的方式二配合Nginx的try_files配置体验更稳。6. 从源码跑到生产部署实测踩过的六类坑6.1 MySQL 8.x的连接时区与SSL问题第一个坑是启动项目时数据库连接报错“The server time zone value Öйú±ê׼ʱ¼ä is unrecognized”或SSL连接报错。原因就是MySQL 8.x默认使用UTC时区而JDBC驱动要求显式指定时区。解决方案就是我前面配置里写的URL加上serverTimezoneAsia/ShanghaiuseSSLfalse。allowPublicKeyRetrievaltrue也要加否则MySQL 8.x的caching_sha2_password认证方式会报Public Key Retrieval is not allowed。这三个参数组合起来基本能解决MySQL 8.x的初连接全部问题。6.2 MyBatis实体类字段驼峰映射不生效这个坑出现的场景是数据库字段order_no实体类字段orderNo查询结果里orderNo是null其他字段正常。原因多半是mybatis-configuration里的mapUnderscoreToCamelCase没开。注意这个配置项放在mybatis.configuration下面不是mybatis下面层级写错也不会生效。我见过不少同事把这段配置放在mybatis.mapper-locations的同级目录下结果怎么调都不生效。6.3 Vue路由history模式刷新404如果用的是history模式路由地址不带#号前端部署在Nginx后刷新页面会404。原因Nginx收到的是/user/list这个路径请求但实际静态文件只有index.htmlNginx找不到对应的物理文件就返回404。解决方案是Nginx配置try_fileslocation / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; }这段配置的意思是先按实际路径找文件找不到就回退到index.html由前端路由接管。如果你不想折腾Nginx直接改用hash模式最省事代价是URL里多一个#号。6.4 前后端分离联调时的跨域限制开发环境下前端跑在5173端口后端跑在8080端口浏览器会拦截跨域请求。解决方案是在后端加一个跨域配置类允许指定来源访问Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedMethod(*); config.addAllowedHeader(*); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }注意addAllowedOriginPattern()是SpringBoot 2.4的新写法老版本的addAllowedOrigin()在设置了allowCredentials(true)时会报错。生产环境如果前后端通过Nginx同源部署这层跨域配置其实用不上但开发环境没有它根本没法干活。6.5 温控记录批量插入的性能优化如果每台温控设备每分钟上报一次几十台设备同时在线用单条INSERT插入数据库压力不小。我的优化方案是MyBatis的批量插入用foreach标签一次插入多条记录insert idbatchInsert parameterTypelist INSERT INTO temp_record (device_no, batch_no, temp_value, humidity, record_time, is_alert) VALUES foreach collectionlist itemitem separator, (#{item.deviceNo}, #{item.batchNo}, #{item.tempValue}, #{item.humidity}, #{item.recordTime}, #{item.isAlert}) /foreach /insert实测下来一次批量插入100条比循环单条插入速度快5到10倍。需要注意MySQL对单条INSERT的最大包大小有限制max_allowed_packet默认4MB单次插入条数控制在200条以内是安全区间。6.6 版本兼容性SpringBoot 3.x和JDK版本不匹配问题我看到网上有人问“SpringBoot版本太高导致项目起不来”核心原因通常是SpringBoot 3.x强制要求JDK 17以上而且原来在2.x里导入的旧版MyBatis Starter可能不兼容Jakarta命名空间。我的建议很直接如果你的目标是把项目快点跑通用2.7.x配JDK 8或11最稳如果确实要体验SpringBoot 3.x保证JDK 17和配套的新版mybatis-spring-boot-starter3.x版本一起上不要混搭。这个项目的源码直接按2.7.x配置写老朋友代码不会闹脾气。我自己从零搭这套系统时最先做通的不是订单模块而是温度监控这条线。原因也简单温度是冷链的灵魂这个模块跑通了业务方就有信心把其他模块交给你搭。如果你也打算拿这套源码二次开发我建议你也按这个顺序来先跑通登录和权限然后做温度监控和批次追溯最后再补订单和库存的细节。上手之后你会慢慢体会到管理系统这东西门面看起来都是表格和表单真正的价值反而在那些看不见的地方状态怎么流转、数据怎么联动、异常怎么及时被发现。最后再分享一个小技巧。冷链系统的温控数据如果将来量大到MySQL撑不住可以考虑把温度记录迁到时序数据库或加一层Redis缓存热数据但业务主表订单、车辆、用户、库存留在MySQL不动。我的经验是这种混合架构在冷链这类场景里性价比最高既保住了事务一致性又解决了时序数据查询的性能瓶颈。这套源码本身已经留好了接口层迁移时只需要替换Mapper实现Controller和Service都不用大改这也是当初分层设计带来的最大好处。
返回列表