新闻详情

新闻详情

首页 / 资讯中心 / 详情

SpringBoot+Vue+MyBatis应急物资管理系统开发实战全记录

发布时间:2026/9/30 7:36:06来源:尧图网络
SpringBoot+Vue+MyBatis应急物资管理系统开发实战全记录
从选题到上线我把这套应急物资管理系统完整做了一遍。技术栈锁定在SpringBoot Vue MySQL MyBatis这四个词放到今天依然是JavaWeb项目里最稳的组合前端用Vue做SPA单页应用后端用SpringBoot快速搭接口持久层交给MyBatis处理动态SQL数据落在MySQL里权限和流程自己用代码控制不依赖重型框架。这套系统面向的是社区、学校、园区这类场景的应急物资管理需求核心要解决的是三件事物资台账不清、出入库记录混乱、临期和库存预警滞后。我写这篇博文不是为了贴一套代码让你抄。我想把从建表、写接口、搭页面到最终打包部署的完整链路拆开讲包括我踩过的坑、改过的设计、以及为什么有些方案明明更“高级”我却没选。如果你正在做类似的毕设或者公司内部刚好需要一个轻量级的物资管理后台这篇文章可以帮你少走很多弯路。1. 整体设计与需求拆解1.1 应急物资管理要解决的业务痛点做系统第一步不是建表而是搞清楚业务里到底有哪些让人头疼的地方。我和几个做社区应急工作的朋友聊过他们之前用Excel表格管理物资问题非常典型物资种类多规格型号混乱同一个物品在Excel里能出现三种写法。出入库靠手写单据月底盘点时账实不符谁都说不清丢在哪一环。口罩、消毒液这类物资有保质期没有系统提醒过期了才发现。应急场景下需要快速查询“仓库里还有多少帐篷、多少手电筒”Excel按半天CtrlF才查完。这套系统就是围绕这些问题来设计的。业务核心围绕“物资台账 出入库流水 预警提醒 权限控制”四个模块展开角色分为管理员和普通操作员。管理员负责物资信息维护、供应商管理、用户管理和系统参数配置操作员负责日常的入库登记、出库登记和库存查询。一个容易被忽略的点是“应急”这个词带来的需求倾向。应急物资管理和平时的进销存系统有区别它的出库场景往往是批量性、突发性的比如某次演练或者实际应急响应一次要领取几百件物资系统必须支持快速录入不能每一步都弹窗确认。所以我在设计时特意简化了出库流程减少不必要的交互层级。1.2 技术选型为什么是这四件套选型这件事我见过太多人一上来就追新框架。其实对于这种管理系统核心诉求是稳定、够用、团队成员熟悉。SpringBoot Vue MySQL MyBatis这套组合有它的合理性SpringBoot简化了Spring的配置地狱。我只需要引入spring-boot-starter-web和spring-boot-starter-jdbc内嵌Tomcat打jar包就能跑不用管一堆XML配置。Vue的双向数据绑定让表单交互写起来非常舒服。配合Element UI组件库后台管理界面的表格、表单、弹窗、分页都能直接复用现成组件开发速度比jQuery写DOM不知道快到哪里去了。MySQL的InnoDB引擎支持事务MyBatis的XML里可以写动态SQL复杂查询时比JPA拼JPQL更直观。这套组合在人才市场上认知度高后续维护和扩展都能找到人。我在中间对比过要不要上SpringBoot JPA后来还是选了MyBatis。原因是这个系统的查询场景里物资列表往往要有组合条件筛选物资名称模糊 分类 库存状态这种动态SQL用MyBatis的where和if标签来写逻辑清晰拼接可控。用JPA写动态查询也行但要么用Specification写一堆回调要么用Query写JPQL可读性不如XML直观。1.3 数据库表结构设计思路整个系统围绕“物资”这个核心实体展开。我在数据库里一共设计了6张核心表说明下面表结构是简化后的核心字段实际建表SQL在完整源码里有详细版本。物资表t_supplies字段名类型说明idbigint主键自增material_namevarchar(100)物资名称categoryvarchar(50)分类如防护类、救援类specificationvarchar(100)规格型号unitvarchar(20)单位如只、箱、个stock_quantityint当前库存数量min_thresholdint库存预警下限max_thresholdint库存预警上限locationvarchar(100)存放位置expiration_datedate有效期可空supplier_idbigint关联供应商create_timedatetime创建时间update_timedatetime更新时间入库单表t_inbound字段名类型说明idbigint主键inbound_novarchar(50)入库单号格式如RKD20240511001supply_idbigint物资IDquantityint入库数量operatorvarchar(50)经办人inbound_timedatetime入库时间remarkvarchar(255)备注出库单表t_outbound结构和入库单对称多一个receiver字段记录领用人/领用单位多一个outbound_type字段区分普通领用还是应急调拨。用户表t_user和角色表t_role做简单的单用户单角色关联不引入复杂的RBAC中间表因为系统角色只有管理员和操作员两种没必要过度设计。供应商表t_supplier记录供应商名称、联系人、联系电话、地址。这里我想重点说一个设计教训最初我把物资的库存数量和出入库明细放在同一个聚合里想着简化查询。后来发现这样做会导致两个问题一是并发控制困难二是统计报表的时候要反复跨表查询。最终我把库存作为一个独立字段放在物资表上出入库单独记录流水通过事务保证一致性。这样查询当前库存只需要查一张表非常快要追溯历史就走流水表。2. 后端开发SpringBoot MyBatis 接口实现2.1 SpringBoot项目结构与统一响应封装项目用Maven管理依赖JDK用的1.8版本SpringBoot版本选的2.7.x。这里有个非常现实的考虑SpringBoot 3.x要求JDK 17起步如果你本机只有JDK 8那就老老实实用2.7.x。而且2.7.x的资料多、踩坑记录全遇到问题搜一下都有答案。基础包结构如下com.emergency.supplies ├── controller // Controller层接收前端请求 ├── service // Service层业务逻辑 │ └── impl ├── mapper // MyBatis Mapper接口 ├── entity // 实体类 ├── dto // 数据传输对象接收前端参数 ├── vo // 视图对象返回前端数据 ├── config // 配置类如拦截器、跨域配置 ├── common // 公共类统一返回结果、异常处理 └── utils // 工具类如JWT工具Controller层只做参数接收和结果返回业务逻辑全部下沉到Service层。管理系统的Controller大多是CRUD接口如果业务逻辑写在Controller里后期维护会很痛苦。统一返回结果是一个容易被忽视但非常重要的设计。我定义了一个ResultT类Data public class ResultT { private Integer code; // 200成功500失败 private String message; // 提示信息 private T data; // 数据体 }所有接口不管成功失败都返回这个结构前端Axios拦截器统一判断code成功就取data失败就弹message。这样做的好处是前端不用为每个接口单独处理错误逻辑后端加异常捕获也能统一转化格式。配合RestControllerAdvice写全局异常处理业务里抛出ServiceException(库存不足)全局处理器捕获后直接返回code500和对应信息代码干净很多。2.2 MyBatis Mapper层与动态SQL实践MyBatis的Mapper接口和XML文件是分离的XML文件的路径在application.yml里配置mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.emergency.supplies.entity configuration: map-underscore-to-camel-case: truemap-underscore-to-camel-case: true是个关键配置。数据库字段是material_name实体类是materialName开启这个配置后MyBatis会自动完成映射不用手动写resultMap前提是字段名和属性名驼峰对应。物资分页查询是动态SQL最典型的使用场景。前端传入当前页码、每页大小、物资名称、分类、库存状态等参数如果参数为空就不能拼进WHERE子句。MyBatis的写法select idselectPage resultTypecom.emergency.supplies.vo.SuppliesVO SELECT s.*, sup.supplier_name FROM t_supplies s LEFT JOIN t_supplier sup ON s.supplier_id sup.id where if testmaterialName ! null and materialName ! AND s.material_name LIKE CONCAT(%, #{materialName}, %) /if if testcategory ! null and category ! AND s.category #{category} /if if teststatus 1 AND s.stock_quantity lt; s.min_threshold /if if teststatus 2 AND s.stock_quantity gt; s.max_threshold /if /where ORDER BY s.update_time DESC /select注意点有两个一是if标签判断空值时用! null and ! 防止参数为空白字符串导致条件恒真二是XML里的和必须写成lt;和gt;否则XML解析直接报错。分页我用的是PageHelper插件在pom.xml引入pagehelper-spring-boot-starter后Service层只需要在查询前调用PageHelper.startPage(pageNum, pageSize)后面跟着的Mapper查询方法会自动拼上LIMIT并返回分页信息非常省心。2.3 出入库业务的事务与并发控制出入库是这个系统的核心操作做的不好就会造成库存不准确。我在Service里严格控制事务以出库为例Transactional(rollbackFor Exception.class) public void outbound(OutboundDTO dto) { // 1. 查询物资加行锁 SuppliesVO supplies suppliesMapper.selectByIdForUpdate(dto.getSupplyId()); if (supplies null) { throw new ServiceException(物资不存在); } // 2. 校验库存 if (supplies.getStockQuantity() dto.getQuantity()) { throw new ServiceException(库存不足当前库存 supplies.getStockQuantity()); } // 3. 扣减库存 int rows suppliesMapper.decreaseStock(dto.getSupplyId(), dto.getQuantity()); if (rows ! 1) { throw new ServiceException(扣减库存失败); } // 4. 插入出库流水 Outbound outbound new Outbound(); outbound.setOutboundNo(generateOutboundNo()); outbound.setSupplyId(dto.getSupplyId()); outbound.setQuantity(dto.getQuantity()); outbound.setReceiver(dto.getReceiver()); outbound.setOperator(SecurityUtils.getCurrentUsername()); outbound.setOutboundType(dto.getOutboundType()); outbound.setOutboundTime(new Date()); outbound.setRemark(dto.getRemark()); outboundMapper.insert(outbound); }这里有几个关键点第一selectByIdForUpdate的作用是给查询的这条物资记录加行锁。MySQL InnoDB引擎在事务里执行SELECT ... FOR UPDATE会对匹配的行加上排他锁其他事务想再更新这行会阻塞直到当前事务提交。这样一来两个操作员同时出库同一个物资时后一个请求会排队等待避免了超卖问题。第二Transactional加在方法上默认对RuntimeException和Error回滚。我们自定义的ServiceException继承了RuntimeException所以方法内任何一步抛出异常前面的数据库操作都会回滚。这里rollbackFor Exception.class是明确指定异常类型才能回滚因为Spring默认只回滚RuntimeException如果方法里有可能抛受检异常不设置会导致数据不一致。第三生成单据编号时我用的是“前缀 日期 当日流水号”的方式。RKD yyyyMMdd 三位序号每天的流水号从001开始。实现上可以查当天最大编号然后加一也可以直接用Redis的incr来做但为了不引入额外中间件我选择了查数据库的方式String todayPrefix RKD new SimpleDateFormat(yyyyMMdd).format(new Date()); String maxNo outboundMapper.selectMaxNoByPrefix(todayPrefix); int seq (maxNo null ? 0 : Integer.parseInt(maxNo.substring(maxNo.length() - 3))) 1; String newNo String.format(%s%03d, todayPrefix, seq);并发量不高的情况下这套逻辑够用同时这个字段本身有唯一索引兜底。3. 前端开发Vue 2 Element UI 页面实现3.1 Vue工程结构和路由设计前端我用的是Vue 2 Vue Router 3 Element UI Axios。为什么不直接用Vue 3因为Element UI对Vue 2的支持最成熟组件稳定、文档多、答疑贴一大堆。对于管理后台这种重表格、重表单的项目Vue 2配合Element UI开发效率非常高。工程结构src ├── main.js // Vue入口 ├── App.vue ├── router │ └── index.js // 路由配置 ├── store // Vuex管理登录态 ├── api // 接口请求模块 │ ├── login.js │ ├── supplies.js │ └── outbound.js ├── components // 公共组件 ├── views // 页面组件 │ ├── Login.vue │ ├── Dashboard.vue │ ├── supplies │ │ ├── List.vue │ │ └── Edit.vue │ └── outbound │ └── OutboundAdd.vue └── utils └── request.js // Axios封装路由配置里我做了路由守卫判断用户是否登录未登录强制跳转登录页router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.path ! /login !token) { next({ path: /login }); } else { next(); } });这个守卫看着简单但实际开发中很多人忘记在main.js里引入它。只要router.beforeEach这段代码写在路由配置文件里并且router在new Vue实例里挂载了守卫就会自动生效。3.2 Axios封装与登录态管理Axios如果不做封装每个组件里直接this.$http.get(/api/supplies)万一后端返回的是401令牌过期每个页面都要自己跳登录重复代码会非常多。我封装了一个request.js模块import axios from axios import { Message } from element-ui import router from /router const request axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器附加token request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] token } return config }) // 响应拦截器统一处理结果与错误 request.interceptors.response.use( response { const res response.data if (res.code 200) { return res } Message.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) }, error { if (error.response error.response.status 401) { localStorage.removeItem(token) router.push(/login) } Message.error(error.message || 网络异常) return Promise.reject(error) } ) export default request这里有个前后端联调的细节baseURL是/api不是完整的http://localhost:8080/api。这就需要在Vue的.env.development里配置代理让前端开发服务器把请求转发到后端VUE_APP_BASE_URL/api devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, pathRewrite: { ^/api: } } } }如果不配这个代理开发时前端直接请求8080端口会遇到跨域问题。后端开启了CrossOrigin其实也能解决但生产环境前后端同域部署时我更推荐用代理的方式后端不用单独处理跨域部署后同一个Nginx反代就搞定了。3.3 核心页面实现物资列表和出入库表单物资列表页是整个系统的门面。我用el-table展示数据配合分页组件、搜索表单和操作栏。这里分享一个经验直接把el-table的data绑定分页接口返回的records不要把分页后的数据再拼到总列表里否则翻页后数据会重复。搜索和分页查询的一个技巧搜索条件变化时页码重置为1不然在第三页搜了一个关键词接口返回第一页数据但分页组件还显示第三页体验很怪。出库表单页是我最用心设计的部分。用户输入物资名称或编号前端通过下拉选择组件远程搜索选中的物资自动带出当前库存、单位、规格数量输入框用el-input-number限制最大值为当前库存同时在输入框下方实时显示“当前库存128”避免用户提交超量。提交按钮做好loading状态防止用户双击重复提交。还有一个细节表单校验放在前端做后端也要做两者都在守卫着数据安全。前端校验失败直接提示不请求后端校验失败返回错误信息前端弹出来。没有后端的二次校验就等于把数据完整性交给了可以随意改写的浏览器。4. 核心功能与难点复盘4.1 库存预警机制的实现“应急物资”这个语义下预警功能是系统的灵魂。库存过低意味着应急时无物可用库存过高则说明备货冗余、占用资金。我在物资表里设计了min_threshold和max_threshold两个阈值字段然后在系统里提供了两种预警方式。第一种是页面直查。后端提供一个接口/supplies/warning/list查询条件是“库存量小于等于下限 或 库存量大于等于上限”返回结果在前端Dashboard页面用红色高亮卡片展示。实现就是一条普通查询SQL加两个条件就行。第二种是定时任务扫描。我在系统里加上了一个Scheduled任务每天上午9点扫描一次所有物资把预警信息写入一张专门的预警记录表关联用户后可以在系统内部消息栏看到通知。“为什么不直接在查询时实时算”因为有些场景比如领导要看昨天的预警汇总需要历史快照实时查询做不到。Component public class StockWarningTask { Scheduled(cron 0 0 9 * * ?) public void scanStockWarning() { ListSupplies list suppliesMapper.selectAll(); for (Supplies s : list) { if (s.getStockQuantity() s.getMinThreshold() || s.getStockQuantity() s.getMaxThreshold()) { warningMapper.insert(buildWarning(s)); } } } }Cron表达式0 0 9 * * ?表示每天9点执行问号表示星期不限制。SpringBoot的Scheduled默认单线程串行执行但我们的任务量很小一个不落的顺序执行完就能用。4.2 物资统计报表与ECharts可视化Dashboard页面如果只是一堆数字说服力不够。我引入了ECharts来做可视化展示四类图表物资分类占比饼图、近30天出入库趋势折线图、库存TOP10条形图、库龄分布图。这些图的数据来源是两个聚合查询接口。以分类占比为例select idselectCategoryStock resultTypejava.util.Map SELECT category AS name, SUM(stock_quantity) AS value FROM t_supplies GROUP BY category /select后端返回ListMapString, Object前端拿到数据后直接喂给ECharts的series.data。ECharts的饼图用name/value字段非常契合这个数据结构不用额外做映射。折线图的数据接口稍微复杂一些要按天聚合出入库数量。MySQL的日期函数在这里很有用SELECT DATE_FORMAT(inbound_time, %Y-%m-%d) AS day, SUM(quantity) AS total FROM t_inbound WHERE inbound_time DATE_SUB(CURDATE(), INTERVAL 30 DAY) GROUP BY day两个查询接口用UNION ALL合并查询出库和入库在Mapper XML里是两条select拼起来外层包一层临时表查询。4.3 权限控制与操作日志管理系统没有权限控制就是裸奔。我的方案是基于JWT的token认证 后端接口拦截器双重校验。用户表里加一个role字段0是管理员1是操作员。管理员可以访问用户管理、供应商管理、删除数据这些敏感接口操作员只能执行出入库、查询和维护库存。前端路由里做了菜单权限的v-if判断后端每个Controller在用PreAuthorize之前先通过拦截器校验token是否有效。具体实现上我写了一个AuthInterceptorpublic class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (StringUtils.isBlank(token)) { throw new ServiceException(未登录); } try { Claims claims JwtUtil.parseToken(token); request.setAttribute(userId, claims.get(userId)); request.setAttribute(role, claims.get(role)); } catch (Exception e) { throw new ServiceException(登录已过期); } return true; } }拦截器注册时排除登录接口和静态资源registry.addInterceptor(authInterceptor) .addPathPatterns(/**) .excludePathPatterns(/login, /**/*.html, /**/*.js, /**/*.css);每个需要权限的接口上用AOP切面检查当前用户的role字段。比Spring Security更轻量也能覆盖到后台管理系统的需求这是我的个人偏好写在这种规模的项目里正好。操作日志我用一个简单的LoginLog表和Aspect做切面记录谁在什么时候执行了什么操作。不用引入完整审计框架一个自定义注解OperationLog加一个切面就能完成代价是几十行代码但价值很直接运维排查和毕业答辩里能拿出真材实料。5. 完整部署与运行指南5.1 环境准备与版本匹配很多人在环境这一步就卡住了我这里列一个直接能跑通的环境版本组合来自我实际的运行验证软件推荐版本备注JDK1.88u202不要高于1.8别问为什么稳定第一Maven3.6.33.8以上对镜像仓库要求变多容易踩坑MySQL5.7 或 8.08.0需要调整驱动配置Node.js14.x 或 16.xVue 2项目构建工具兼容性最佳npm6.x 或 7.x配合Node版本即可MySQL 8.0和5.7有一个重大的配置差异驱动类名不同。5.7用com.mysql.jdbc.Driver8.0用com.mysql.cj.jdbc.Driver。如果你用的是8.0还需要在jdbc-url后面追加useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8allowPublicKeyRetrievaltrue否则会报时区和SSL相关错误。5.2 初始化数据库与导入数据第一步是建库。在MySQL里执行CREATE DATABASE IF NOT EXISTS emergency_supplies DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;注意用utf8mb4而不是utf8。utf8在MySQL里是utf8mb3的别名只能存基本多语言平面字符存储emoji或者部分生僻字会报错或者乱码。应急物资的名称里可能出现“医用N95口罩防护面罩头戴式”这类带括号的字符串括号虽然是正常字符但用utf8mb4更保险。导入项目自带的emergency_supplies.sql文件mysql -uroot -p emergency_supplies emergency_supplies.sql这个SQL文件里我预置了管理员账号admin/admin123和一些基础的物资分类数据保证项目导入后不用手动造数据就能看到效果。5.3 后端打包和前端构建后端的打包命令很常规mvn clean package -DskipTests打包完成后target目录下会生成一个emergency-supplies-0.0.1.jar。打出来大概50~70MB因为SpringBoot的内嵌Tomcat和依赖都打进去了。前端构建稍微注意一下npm install npm run build构建产物在dist目录。如果有人告诉你npm install装得很慢推荐设置一下npm镜像npm config set registry https://registry.npmmirror.comdist目录里是静态文件包括index.html、static文件夹等。部署时把dist的内容拷到服务器上后端jar包扔在任意目录用以下方式启动nohup java -jar emergency-supplies-0.0.1.jar --server.port8080 app.log 21 nohup和是Linux后台运行的基础操作 app.log把日志输出重定向到文件方便排查问题。如果服务器上同时跑了Nginx就把dist里的文件放到Nginx配置的root目录然后配置/api路径的反向代理到8080端口location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }走到这一步整个系统就通过http://服务器IP访问了前后端完全同域没有跨域问题。6. 常见问题与避坑记录6.1 启动时的“白屏”与端口占用很多人SpringBoot项目启动后浏览器访问localhost:8080出现白屏或者报404。最常见的原因是SpringBoot的静态资源路径和你前端构建产物目录对不上。SpringBoot默认会去classpath:/static/找静态文件。如果你想前后端一起打包部署把Vue构建出来的dist内容复制到src/main/resources/static/下再打包访问根路径就能看到页面。还有端口占用的问题如果8080被占用了修改application.ymlserver: port: 8081或者用命令行参数指定java -jar app.jar --server.port8081另外如果你打好了jar包运行时提示Unable to access jarfile大概率是当前终端所在目录和jar包路径不一致用绝对路径启动最稳妥。6.2 MyBatis XML文件常见的两个报错第一个是Invalid bound statement (not found)。这个报错意味着Mapper接口和XML里的namespace对不上或者XML文件没有被加载到。排查时看三点namespace写的是不是完整的接口类路径方法名在XML里的id是不是和接口方法名一致application.yml里的mapper-locations是不是指向了正确的目录。第二个是SQL条件拼接错误。我用where标签时踩过一次坑当所有if条件都不成立时where会自动去掉多余的WHERE关键字这没问题。但如果你在if里写了“OR xxx”开头的条件而前面的条件都不成立拼接出来的SQL可能是WHERE OR material_name x直接语法错误。写法上统一在if内部写完整的条件包括内部的AND/OR放在上一行条件末尾不让它成为SQL开头。6.3 前端联调时接口请求404这个问题几乎每个人都会遇到。前端开发服务器是8081Vue默认端口后端是8080接口请求发到8081自然找不到后端。检查顺序是这样的确认vue.config.js里的proxy配置是否生效。修改了vue.config.js要重启npm run dev热更新不会自动加载webpack配置。确认请求URL是不是以/api开头。如果没有按约定加前缀代理不会转发。在Network面板里看请求的路径如果是http://localhost:8081/api/supplies/list说明代理生效要去后端日志和接口地址里找原因如果还是http://localhost:8081/supplies/list说明proxy没匹配到路径。我自己被坑过的一次是后端的Controller类路径是/supplies方法路径是/list前端请求/api/supplies/list代理做了pathRewrite: {^/api: }转发到后端变成/supplies/list接口正常。后来改了代理配置忘写pathRewrite转发路径变成/api/supplies/list后端Controller没有这个路径报了404。排查半天才发现是代理配置的问题。6.4 MySQL连接时报错中文编码与时区连接MySQL 8.0时出现Public Key Retrieval is not allowed这是因为MySQL 8默认启用caching_sha2_password认证插件允许通过RSA公钥传输密码。JDBC驱动在第一次连接时不能直接获取公钥需要设置allowPublicKeyRetrievaltrue另一个常见报错是The server time zone value йʱ is unrecognized中文系统时区显示为乱码导致驱动无法解析。解决方式就是在jdbc-url里明确指定serverTimezoneAsia/Shanghai这里也提醒一下写代码时统一在Application.yml里配置数据源spring: datasource: url: jdbc:mysql://localhost:3306/emergency_supplies?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue如果不加useSSLfalse连接时会抛出SSL握手相关的警告甚至错误。本地开发完全不需要SSL加密直接用明文协议就行。6.5 用户登录后刷新页面登录态丢失这个问题出在很多人对Vuex的误解上Vuex的状态是存在内存里的刷新页面会全部清空。如果在state里只存了个token刷新后this.$store.state.token就变成undefined路由守卫看到没有token直接踢回登录页。解决方案是持久化存储。把token放到localStorage里页面加载时从localStorage读取并恢复Vuex状态。我用了一个通用的初始化方式const state () ({ token: localStorage.getItem(token) || , userInfo: JSON.parse(localStorage.getItem(userInfo) || null) })7. 项目扩展与二次开发方向7.1 接入MinIO实现物资图片与附件管理当前系统的物资信息只有文字字段没有图片。实际使用中物资的实物照片、出入库单据扫描件都有保存需求特别是应急物资比如消防器材、救援绳索实物照片对于识别物资状态和规格很有帮助。MinIO是一个开源的对象存储服务兼容S3协议部署起来就是一个二进制文件运行比FastDFS简单得多。我在另一个项目里用过FastDFS需要配置Tracker和Storage两个角色还要写Nginx插件部署复杂得多。MinIO只需要一个命令minio server /data/minio --address :9000SpringBoot集成MinIO的步骤也很直接。引入io.minio:minio依赖配置MinioClient上传文件时走putObject拿到URL后在物资列表里回显图片。这里我建议上传后只保存相对路径不要把完整URL写进数据库不然以后换域名或者迁移存储历史数据全部要改。7.2 引入Redis做热点数据缓存与验证码系统上线后会频繁浏览物资列表如果同时并发量稍微上来每次都查MySQL虽然不至于撑不住但响应时间会变慢。一个实用的优化是在Service层加一个Redis缓存查询数据库前优先从缓存取数据取不到再查库并回填。缓存设计有一个坑更新库存后必须同步删除或更新缓存。刚开始我只做了“查询时缓存更新时删除”但由于出入库频繁缓存删除后下次查询又回填为旧数据导致库存显示不准。后来我把更新逻辑改成“先更新数据库再删除缓存”让下一次查询重新从数据库加载最新数据这才正确。Redis还可以用来实现登录验证码。用户登录时后端生成随机四位数验证码存到Redis里设置5分钟过期验证时对比用户输入的验证码和Redis里存的是否一致一致才允许登录。比存Session更健康因为Redis可集群化Session在多节点环境里扩展性差。7.3 对接外部系统报表导出与消息通知管理系统的后续需求大概率会新增两个方向一是导出Excel报表二是消息通知。Excel导出这块我一开始用Apache POI硬写代码字段多、样式多时开发效率很低。后来换成Alibaba开源的EasyExcel基于注解的方式一行代码就能导出ExcelProperty(物资名称) private String materialName;出库单的导出、库存月报的导出都只要在接口里构建好数据列表EasyExcel自动处理表格样式和分sheet比POI省了非常多时间。消息通知方面如果预算有限可以用WebSocket做站内信给Web端推送“库存预警”“到货通知”之类的提醒。如果公司有企业微信或者钉钉也可以接入它们的机器人webhook仓库管理员在群里就能收到物资预警消息。这些扩展方向做下来系统就从“能用”升级成“好用”了。而我个人的经验是不要一开始就把所有扩展功能加进项目里。先把基础CRUD流程走顺系统的骨架立起来再针对使用中的痛点加功能代码质量和进度都能把控得更好。8. 写在最后的经验和建议做这个项目最长的时间里我都在和“出库改库存”这一个小环节较劲。为什么返回数据对不上、为什么同一种物资两条出库记录扣了双倍库存、为什么明明库存还有却提示不足。排来排去最后发现都是事务边界、锁范围、缓存一致性这类基础问题。这套系统做下来我对Spring的声明式事务、MySQL的锁机制、MyBatis的运行机制有了比看一百篇博客都深入的理解。如果对你有一点实际的启发我想说两件事一是遇到列表接口慢先看是不是循环调用数据库而不是先想着上分库分表二是前后端联调时遇到Bug抓包看数据不要凭感觉猜。这两条守则帮我避掉了太多浪费时间的坑。最后分享一个小技巧如果你也在做类似的毕设或者公司内系统可以试着在GitHub上搜一下“vue-admin-template”这类后台管理脚手架它是基于Vue Element UI封装好的登录、路由、权限、侧边栏框架能省掉搭基础壳子的两三天时间。把精力留给业务代码和那些真正有价值的功能系统质量反而更高。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

程序员如何转Agent,抢占高薪风口? 2026/9/30 9:31:01

程序员如何转Agent,抢占高薪风口?

随着AI技术发展,传统后端、前端岗位需求下降,而AI相关岗位激增(如腾讯、字节、阿里等大厂招聘趋势)。程序员转行做Agent(AI数字员工开发)成为破局关键。Agent能自主执行任务,大厂疯抢这类人才以…

阅读更多 →
网上超市系统完整开发复盘:源码+数据库+文档全解析 2026/9/30 9:31:01

网上超市系统完整开发复盘:源码+数据库+文档全解析

每年都会有一批人拿到“网上超市系统”这个课题,无论是课程设计、毕业设计还是期末大作业,题目本身看起来平平无奇,但真正动手之后会发现:页面能打开只是一个开始,订单、库存、购物车、后台管理、数据库设计、文档编写…

阅读更多 →
DeepSeekCoder-V2实战:本地部署、提示词优化与自动化编程避坑指南 2026/9/30 9:30:54

DeepSeekCoder-V2实战:本地部署、提示词优化与自动化编程避坑指南

简介:这份PDF文档围绕DeepSeekCoder-V2在自动化编程中的应用展开,适合希望借助AI模型提升编码效率的开发者、数据工作者和学习者阅读。文档共24页,从模型原理、环境搭建到基本调用方法均有清晰讲解,并配有冒泡排序、斐波那契数列、…

阅读更多 →
Spring Boot垃圾分类管理系统:从数据库设计到前后端分离实战 2026/9/30 9:30:54

Spring Boot垃圾分类管理系统:从数据库设计到前后端分离实战

1. 接到这个题目后,我第一件事是划清“管理”和“识别”的边界如果你打开搜索引擎查“基于Spring Boot的环保垃圾分类管理系统”,会看到大量论文和开源项目。但说实话,很多项目一看就是从“CRUD模板”里复制出来的,功能表写得天花…

阅读更多 →
跑腿系统源码选购避坑指南:从源码完整度到二次开发能力评估 2026/9/30 9:30:54

跑腿系统源码选购避坑指南:从源码完整度到二次开发能力评估

1. 源码完整度与加密陷阱:目录结构暴露真实底细1.1 一套合格源码应该包含哪些模块跑腿系统不是"一个后台加一个用户端小程序"这么简单。我见过的商业跑腿源码,哪怕是最精简版本,也至少要包含平台管理后台、用户下单端、骑手接单端、…

阅读更多 →
“匠承新裘”2026南京禄口皮草非遗产业发展大会 2026/9/30 9:30:54

“匠承新裘”2026南京禄口皮草非遗产业发展大会

金秋九月,“匠承新裘”2026南京禄口皮草非遗产业发展大会在南京禄口隆重举办。本次大会立足禄口深厚的皮草非遗历史积淀与空港临空产业区位优势,汇聚中国畜产品流通协会、中国皮革协会、中国土畜进出口商会、哥本哈根皮草、江苏省服装协会、江苏省设计师…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉