ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

应急物资管理系统实战:SpringBoot+Vue全栈开发避坑指南

应急物资管理系统实战:SpringBoot+Vue全栈开发避坑指南 刚接手一个应急物资管理系统项目时我第一反应是这东西看似简单——无非就是物资出入库、库存台账、预警提醒但真把需求聊细了才发现背后牵扯到批次管理、有效期校验、多角色审批、多仓库调拨还有应急场景下物资必须找得到、调得动、发得快的硬约束。当时团队定了SpringBootVueMySQLMyBatis这套技术栈既是为了快速交付也是为了方便后续维护。如果你正准备做一个类似的物资管理系统或者正在做Java方向的毕业设计这篇实操复盘应该能帮你避开不少坑。整套系统最终交付的是可运行的完整源码后端SpringBoot工程、前端Vue工程、MySQL初始化脚本、部署文档一应俱全。文章会从需求分析、数据库建模、后端实现、前端交互、权限控制到部署上线逐层拆解重点讲清楚每个关键模块的为什么这么做以及源码落地时那些文档里不会写的问题。1. 为什么用SpringBootVue这套组合需求分析与技术选型复盘1.1 应急物资管理的真实需求边界很多同学把应急物资系统理解成进销存这是最大的误区。常规应急物资系统面对的是帐篷、棉被、救生衣、照明设备、应急食品这类需要随时可查、随时可调的物资所以除了基础增删改查还必须满足几个硬性需求批次与有效期管理普通进销存只管数量应急物资必须精确到每一批的生产日期、失效日期。救灾时把过期物资发出去是要出人命的。库存动态变化物资既有常规入库也有紧急入库、领用出库、跨仓库调拨、定期盘点每一种操作都需要可追溯的流水记录。低库存与临期预警系统要能自动算出哪些物资低于安全库存线、哪些物资临期并推送给仓库管理员。多角色协同管理员、仓库员、部门申请人员、领导审批角色权限不同操作范围和可见数据也不同。操作留痕每次责任流转、数量变动都要有日志出了差错能回溯到具体操作人和时间。这套需求看起来多但用SpringBootVue做前后端分离刚好能把后端业务逻辑和前端交互界面解耦让仓库员在PC浏览器上操作管理员看统计大屏互不影响。1.2 技术选型对比为什么不考虑SSH或传统JSP早几年这种项目用SSHSpringStruts2Hibernate或者SpringMVCJSP也能做但实际开发体验很差。我做过一个老项目JSP里嵌了半屏Java代码改一个下拉框要到五个页面里找同一段逻辑改完还得重新打war包。前后端分离后前端只调接口后端只出数据哪怕后面要把PC端换成大屏展示接口可以原样复用。SpringBoot在这个场景里的优势是开箱即用内嵌Tomcat、自动配置、起步依赖一个main方法启动服务。比起传统SSH要手动配一堆XMLSpringBoot把配置收敛到application.yml对单人开发和毕业设计来说学习成本低得多。Vue这边组件化开发适合这种多个表单页长得像、逻辑相似的管理系统。物资列表、入库单、出库单、调拨单抽出公共的表格组件、弹窗组件、表单组件开发效率翻倍。配合Vue Router做菜单路由、Pinia/Vuex存登录态和权限信息整体开发体验比jQuery时代舒服太多。数据库用MySQL是因为这类系统本身是OLTP型事务处理数据量不大MySQL足够稳。MyBatis则是团队一致选择相比Hibernate的完全自动映射MyBatis把SQL写在自己手里遇到多条件动态查询、批量更新库存这种操作写起来更直接再一个MyBatis的学习曲线平缓新手能看懂老手也便于优化SQL。2. 数据库设计物资台账、出入库流水与库存快照的建模2.1 核心表结构与字段设计数据库是整个系统的地基。我设计这套库时核心原则是主数据流水库存分离不要图省事把所有字段塞进一张大表里。总共设计了这样几张核心表表名用途关键字段sys_user用户表id, username, password, salt, real_name, statussys_role角色表id, role_code, role_name, remarksys_user_role用户角色关联表user_id, role_idmaterial_category物资分类表id, parent_id, name, sortmaterial_info物资基础信息表id, category_id, material_code, material_name, spec, unit, low_stock, expire_daysmaterial_stock库存表id, material_id, warehouse_id, batch_no, production_date, expire_date, quantity, locked_quantity, versionstock_in_record入库流水表id, order_no, material_id, warehouse_id, batch_no, quantity, supplier, operator_id create_time, remarkstock_out_record出库流水表id, order_no, material_id, warehouse_id, target_unit, quantity, apply_user, approve_user, out_time, remarktransfer_record调拨记录表id, order_no, material_id, from_warehouse_id, to_warehouse_id, quantity, operator_id, create_timeinventory_record盘点记录表id, order_no, material_id, warehouse_id, book_quantity, actual_quantity, diff_quantity, check_user, check_timealarm_config预警配置表id, material_id, low_stock_value, before_expire_days这个设计有几个关键点值得展开说。第一个是库存表必须携带批次信息。很多人做库存直接写一张material_stock(material_id, warehouse_id, quantity)。这样设计会让先入库的批次先出库完全没法实现。我这里的库存表以material_id, warehouse_id, batch_no作为联合业务约束同一物资同一仓库有多条批次记录每次入库生成一个新批次出库时按照批次到期时间排序先出临期批次。虽然查询时稍微麻烦一点但对于应急物资来说这个代价必须付。第二个是区分总库存和锁定库存。我在库存表里增加了locked_quantity字段。什么意思呢比如一个部门提交了领用申请审批通过后但在仓库实际发货前这批物资应该被冻结不能又被其他人申领走。locked_quantity就扮演这个角色。可用库存 quantity - locked_quantity列表页展示的就是这个可用值。如果没有这个字段高并发下很容易出现超发两个人同时申请同一批物资都看到有100件最后出库200件库存直接变负数。第三个是流水表不轻易更新只追加。所有出入库、调拨、盘点都只向各自的记录表插入数据不要用UPDATE改历史记录。万一单据填错了要做红冲单或者反向单来冲正而不是直接改老记录。这样才能保证后续审计时流水是全链条的。实际开发中我们还在流水表上加了order_no唯一索引一个单号对应对一张单据方便后续对账和排查。2.2 字段级细节有效期、精度和逻辑删除有几个字段层面的细节是踩坑踩出来的有效期字段production_date和expire_date用date类型不要用datetime。紧急出库扫码填单的时候日期精度到天就够了用datetime反而可能因为时分秒不一致导致界面显示异常。数量字段所有数量用int或者decimal(12,2)。如果物资单位只有件、箱、个用int如果涉及吨、升这类计量用decimal千万别用double。数据库里double的浮点误差在累加后会被放大账面数量和实际数量对不上很尴尬。逻辑删除每个业务主表都加了deleted字段默认0。物理删除一张被外键引用的物资表会连带一堆流水变成孤儿数据。逻辑删除只把字段置1查询默认过滤。审计字段create_by,create_time,update_by,update_time是标配。在MyBatis里我在插入和更新SQL中手动维护这些字段没有引入太重的审计插件够用就好。2.3 初始化的基础数据要提前铺好数据库设计完建表之后不能一把梭。我会在初始化脚本里预置几类数据一个默认超管账号密码使用BCrypt加密后的字符串默认角色常用物资分类应急工具类、医疗救护类、生活保障类、照明通讯类等还有一到两种示例物资和仓库数据。这样前端第一次启动就能跑通登录、看列表、走一遍入库流程而不是入库单页面打开后物资下拉框是空的。对拿到源码想先看效果的人来说这点体验细节很关键。3. 后端落地SpringBoot与MyBatis的核心模块与事务细节3.1 工程结构与分层实践后端工程结构按业务分包而不是按技术层分包com.example.emergency ├── controller # 接口入口 ├── service # 业务逻辑 │ └── impl ├── mapper # MyBatis数据访问 ├── entity # 数据库实体 ├── dto # 接口出入参对象 ├── common # 统一返回、异常处理、工具类 └── config # JWT、拦截器、CORS、MyBatis配置这种结构的核心是controller很薄只负责参数接收和结果封装业务规则全部落在service里mapper只是SQL和对象映射的薄层。以前见过有人把几十行SQL拼在Controller里看着跑得通后期一改需求就想重写项目。接口返回统一用ResultT里面包含code、message、data三个字段。前端axios响应拦截器统一判断code而不是直接处理HTTP状态码这样业务异常比如库存不足返回50000跟系统错误HTTP 500可以区分开。3.2 核心入库出库流程与事务边界入库和出库是整个系统里最需要讲清楚为什么这么写的两个业务。入库流程的伪代码逻辑是这样的校验入库单中的物资、仓库、批次数据是否合法插入stock_in_record流水如果库存表中已存在相同material_id warehouse_id batch_no的记录则数量累加否则新建一条库存记录记录操作日志。这个流程必须被一个Transactional包起来。否则第2步插入流水成功第3步库存更新失败账面数据就和库存对不上了。SpringBoot的Transactional默认只在RuntimeException时回滚如果代码里手写了捕获异常且没有重新抛出事务就失效了——这个细节我特意在评审时说给团队听捕获异常处必须throw new RuntimeException(...)或者用Transactional(rollbackFor Exception.class)显式声明。出库流程比入库复杂因为涉及批次选择、锁库存、扣减三个环节。根据申请单按照expire_date ASC排序找出对应仓库的可用批次每次先扣减quantity再检查是否大于申请数量扣完一个批次不够继续扣下一个批次全部扣减成功后插入stock_out_record如果库存列表里某个批次的quantity变成0保留记录不物理删除。第2步的可用批次如果直接写SELECT quantity FROM material_stock WHERE id ?然后Java代码判断并发情况下会出事。两个请求同时读到100件都判断够扣最后库存变成负数。我这里的处理方式有两种二选一乐观锁库存表加version字段更新时执行UPDATE material_stock SET quantity quantity - #{num}, version version 1 WHERE id #{id} AND version #{version}影响行数为0就重试。悲观锁查询时加FOR UPDATE把要扣减的批次行锁住扣完再提交。应急物资系统里多人同时申请的场景确实存在我最终选择了悲观锁因为出库事务本身很短锁冲突概率不高实现起来直观。SQL写成了WHERE material_id #{materialId} AND warehouse_id #{warehouseId} AND quantity - locked_quantity #{num} FOR UPDATEMySQL在InnoDB引擎下会对命中的索引行加排他锁这样并发下就不会超扣。3.3 MyBatis动态SQL与批量操作技巧这个项目的查询条件很多物资列表按分类、名称、仓库、预警状态筛选出库单按时间段、申请部门、单据状态筛选。如果每个条件写一个查询方法那得写疯。MyBatis的where标签加if完美解决这个问题。比如库存列表的查询select idselectStockPage resultTypecom.example.emergency.dto.StockPageDTO SELECT ms.id, mi.material_name, mi.spec, ms.batch_no, ms.production_date, ms.expire_date, ms.quantity, ms.locked_quantity, (ms.quantity - ms.locked_quantity) AS available_quantity FROM material_stock ms LEFT JOIN material_info mi ON ms.material_id mi.id where if testmaterialName ! null and materialName ! AND mi.material_name LIKE CONCAT(%, #{materialName}, %) /if if testwarehouseId ! null AND ms.warehouse_id #{warehouseId} /if if testbatchNo ! null and batchNo ! AND ms.batch_no #{batchNo} /if if testalarmFlag ! null and alarmFlag true AND (ms.quantity - ms.locked_quantity) lt; mi.low_stock /if /where ORDER BY ms.expire_date ASC, ms.create_time DESC /select这里有个坑提醒一下XML里没法直接用比较符会被当成标签解析。必须用lt;转义或者用![CDATA[ (ms.quantity - ms.locked_quantity) mi.low_stock ]]包裹。我第一次写就漏了这个页面直接报XML解析异常排查了半天。批量插入入库流水时不要一条一条循环插入MyBatis支持foreach批量insertinsert idbatchInsert parameterTypelist INSERT INTO stock_in_record(order_no, material_id, warehouse_id, batch_no, quantity, supplier, operator_id, create_time) VALUES foreach collectionlist itemitem separator, (#{item.orderNo}, #{item.materialId}, #{item.warehouseId}, #{item.batchNo}, #{item.quantity}, #{item.supplier}, #{item.operatorId}, NOW()) /foreach /insert批量提交一定要控制单批大小我实测一次插入500条以上时MySQL执行时间和网络往返都明显增加。所以我在Service层做了分批处理每500条切一批批量插入后进入下一批。3.4 缓存与性能不盲目上Redis有热搜词提到mybatis缓存这里我多说一嘴。MyBatis自带一级缓存是SqlSession级别的二级缓存是namespace级别的但这个系统我没有开二级缓存。原因很简单物资库存数据对实时性要求极高一次出库操作后任何过期的缓存都可能让用户看到不准确的库存这在应急场景下是危险的事。如果以后要优化性能可以在material_info这类变化频率低的数据上做Redis缓存但库存和流水绝不要轻易加缓存记住这个原则。4. 前端Vue实战页面结构、动态路由与物资操作交互4.1 前端工程结构与技术要点前端用的是Vue框架同时用配套的路由和状态管理。工程结构按模块划分src ├── api # 接口请求封装 ├── assets # 静态资源 ├── components # 公共组件分页、导入导出、表单弹窗 ├── router # 路由配置 ├── store # 用户信息、权限等全局状态 ├── views # 页面 │ ├── login │ ├── dashboard │ ├── material # 物资管理 │ ├── stock # 库存管理 │ ├── inout # 出入库单 │ ├── transfer # 调拨 │ ├── inventory # 盘点 │ └── alarm # 预警中心 └── utils # 请求工具、日期工具、权限指令Vue后台管理系统最常用的两个库是Element UI和Element Plus。如果项目锁定的Vue是2.x版本选Element UI如果用Vue 3.x就选Element Plus。我这边源码用的是Vue3 Element Plus整体组件API更现代TypeScript支持也要好一点。如果你毕业设计想少踩坑直接照这个组合来网上资料也最多。Axios的封装是必做项目。负责的事情有请求头自动带入token、统一进度条、统一错误提示、401跳转登录页。响应拦截器里我会先看后端返回的Result.code等于200就把data直接返回不等于200就用Element Plus的ElMessage弹错误提示。这样页面里写接口调用一行代码就能拿到业务数据// api/material.js export function listMaterialPage(params) { return request({ url: /api/material/page, method: get, params }) } // views/material/index.vue const pageData ref({ records: [], total: 0 }) const queryParams reactive({ pageNum: 1, pageSize: 10, materialName: }) async function loadTable() { const res await listMaterialPage(queryParams) pageData.value res }4.2 动态路由与权限菜单前端导航栏的菜单不是写死的而是登录后从后端拿一份当前用户可见的菜单列表里面包含路由路径、组件名、图标、排序。前端拿到这个列表用router.addRoute动态注册路由。这样做的好处是不同角色的人进入系统看到的菜单项完全不同。仓库员看不到用户管理菜单领导审批角色看不到入库单编辑按钮。菜单表在后端由menu表维护用户→角色→菜单三级关联。Vue前端有一个全局的permission模块在路由守卫里判断是否已经加载过动态路由router.beforeEach(async (to, from, next) { const token localStorage.getItem(token) if (!token) { if (to.path /login) return next() return next(/login) } if (userStore.isLoaded) return next() try { const menuList await getMenuList() userStore.setMenus(menuList) menuList.forEach(menu { router.addRoute({ path: menu.path, name: menu.name, component: () import(../views/${menu.component}.vue) }) }) userStore.isLoaded true next({ ...to, replace: true }) } catch (e) { userStore.logout() next(/login) } })这里有一个大坑动态添加的路由在页面刷新后会全部消失。因为Vue Router的addRoute只存在于内存中浏览器一刷新整份前端代码重新执行之前动态加的路由没了。解决办法就是在路由守卫里做是否已加载判断没加载就重新拉取菜单再addRoute并用next({ ...to, replace: true })重新进入当前目标页。不加这行的话刷新后明明进过系统却直接掉回登录页或者白屏。4.3 物资出入库页面的交互设计出库操作页面是这个系统前端交互的核心。用户先选仓库再选物资或者物资列表直接带仓库条件输入数量、领用单位前端要做几个关键校验数量必须是正数且不能超过后端接口返回的可用库存批次优先选择临期批次前端用Radio组件展示批次列表默认选中最早到期的那条提交前弹确认框提示本次操作涉及的物资种类和总件数。有人会觉得校验在后端做就行了前端不用重复。但实际体验完全不一样后端校验失败是个红色错误条用户还要重新填一遍表单前端实时校验则能让用户在提交前就发现问题。边界值、必填项、非法输入前端拦一道后端守一道双保险。入库单设计更偏批量录入。一个入库单可能包含多种物资前端用表格维护明细行每行一个物资选择器、批次号、生产日期、失效日期、数量、供应商。明细行间可以新增、删除。提交时一次性把整个明细数组POST给后端后端一次性校验全部通过才提交事务。如果只有一个明细数据错了后端返回行号和原因前端把错误信息定位到对应行用红色边框标注。4.4 预警中心与库存看板预警中心是一个很能体现系统价值的模块。后端每天定时任务扫描所有库存批次统计两类预警quantity low_stock的低库存预警以及expire_date 今天 before_expire_days的临期预警。前端预警页面用两个Tab展示每个预警都关联到具体的物资批次点击可以直接跳转生成出库单或调拨单。库存看板则是仓库首页的汇总数据总物资种类数、总库存量、低库存预警数量、临期预警数量、今日出入库笔数。这组数字用ECharts画成卡片和柱状图。ECharts在Vue里用起来并不复杂组件卸载时记得chart.dispose()否则反复切换路由会报canvas already in use。5. 权限与数据安全角色分级、防越权与可追溯5.1 RBAC权限模型落地权限模块的设计直接决定系统能不能在企业里真正用起来而不是只在演示环境里跑。我采用的是经典RBAC模型用户关联角色角色关联权限。颗粒度上做了两层菜单权限控制用户能看到哪些菜单和路由对应前端的动态路由按钮权限控制用户能不能执行某个操作比如新增入库单删除盘点记录审批出库单对应Vue的自定义指令v-permission。后端每个接口在Controller层加自定义注解RequiresPermission(stock:out:add)配合Spring的HandlerInterceptor拦截器实现。拦截器里先解析出当前用户标识和权限集合再校验注解要求的权限是否包含在内。这样做的好处是前端就算绕过页面直接调用接口后端依旧会拦截从源头防越权。5.2 登录认证与密码安全登录流程我用的是JWTJSON Web Token方案。用户登录成功后后端把用户ID、用户名、角色编码封装进JWT并签名返回给前端。前端把token存到localStorage每次请求在axios请求拦截器里加上Authorization: Bearer token。后端拦截器验签、解析用户信息放到ThreadLocal上下文中Service层随时能取到当前操作人是谁。密码安全方面不要把明文密码直接存数据库。源码里我用了BCryptPasswordEncoder这是Spring Security自带的加密器它的一个显著优点是自动加盐每次加密同一个密码生成的hash都不相同而且算法自带校验强度爆破成本高。如果是旧项目里的MD5加盐方案建议也换掉MD5在算力发达的今天已经很弱了。这里有一个容易被忽略的点JWT不是加密只是签名。token里的payload只是Base64编码任何拿到token的人都能解码看到用户名等信息。所以绝对不要把密码、手机号这类敏感数据放进JWT。需要展示用户信息时从后端接口实时查而不是依赖token里存的数据。5.3 数据权限不只是能不能点按钮按钮权限解决的是能不能做数据权限解决的是能看哪些数据。在这个系统里不同层级的人对物资库存数据的可见范围不同。比如总仓库管理员可以看到所有仓库的数据而某个区域仓库的管理员只看自己仓库的出入库记录。数据权限的实现没有引入重框架简单做法是在业务查询的SQL里自动拼接一个warehouse_id条件。后端从用户上下文里取当前用户的仓库ID列表如果用户属于超管角色就不加条件否则强制追加AND warehouse_id IN (...)。拼接逻辑做在MyBatis拦截器里会更优雅但为了源码可读性我是把它放在Service层显式调用一个DataScopeHelper工具类这样每个查询一眼能看懂是否做了数据限制。5.4 审计日志与操作留痕系统里有一个独立操作日志表operation_log记录谁、什么时间、做了什么操作、请求参数、操作结果。实现不是在每个Service里手写日志而是用Spring AOP切面配合自定义注解OperationLog(入库操作)来完成。切面在方法执行前记录开始时间执行后记录返回值或异常、耗时异步写入日志表。这里异步写入用了简单的线程池避免写日志拖慢主业务。6. 部署上线与源码复用的避坑指南6.1 本地开发环境的配置细节环境配置这里最容易翻车的是MySQL版本和SpringBoot版本的兼容问题。MySQL从5.7到8.0有一个大坑8.0默认启用caching_sha2_password认证插件老版本的MySQL驱动5.x连接时直接报错。我在项目的application.yml里用的是com.mysql.cj.jdbc.Driver并且连接串加了几个关键参数spring: datasource: url: jdbc:mysql://localhost:3306/emergency?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.emergency.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpluseSSLfalse避免MySQL 8.0的SSL连接错误serverTimezoneAsia/Shanghai不然查询时间比正常时间早8小时map-underscore-to-camel-casetrue让数据库字段create_time自动映射成Java实体里的createTime省去一堆手动映射。如果遇到SpringBoot版本太高导致MyBatis依赖冲突先检查是不是用了Spring Boot 3.x。Spring Boot 3要求Java 17且不再兼容许多老版MyBatis starter。如果是毕业设计我建议直接用Spring Boot 2.7.x系列资料全、坑少、稳定。6.2 前后端联调与Nginx部署后端启动后默认端口8080。前端开发环境通过Vite代理解决跨域// vite.config.js server: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这个配置的意思是把所有/api开头的请求转发给后端。所以前端请求路径必须统一加/api前缀。联调通过后前端执行npm run build生成dist目录。后端执行mvn clean package打成emergency.jar。生产环境我用了Nginx做静态文件服务和反向代理配置要点有三个server { listen 80; server_name your-domain.com; root /opt/emergency/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }第一个要点是try_files如果前端路由用了history模式刷新一个子路由页面时Nginx不重写到index.html就返回404。这个配置是必须的。第二个要点是location /api/反代到后端端口前端所有跨域问题在Nginx层面解决生产环境不需要后端开CORS。第三个要点是不要忘了proxy_set_header Host否则后端如果校验了Host头会拿到错误的域名。6.3 完整源码落地后的常见问题清单我把源码交付后收到最多的提问和反馈集中在以下几类提前写在这里症状原因解决办法启动报Failed to configure a DataSource没建库或spring.datasource配置未生效先执行emergency.sql初始化脚本再检查yml缩进MyBatis的XML报Invalid bound statementmapper接口和XML命名空间不匹配或XML没扫到确认接口路径、XML的namespace、mapper-locations三方一致前端登录报跨域没走代理或后端没配CORS开发环境配Vite代理生产环境用Nginx反代表格时间显示为2023-01-01T00:00:00后端LocalDateTime序列化格式不对在application.yml中配置Jackson的日期格式出库提示库存不足但列表显示有货忘记考虑locked_quantity可出库数 quantity - locked_quantity前后端都统一用这个口径还有一个很实用的小技巧如果某个环境需要将SpringBoot打出的jar包内容翻出来排查jar本质上是个zip可以直接用压缩软件打开查看BOOT-INF/classes下的配置和class文件。需要将编译后的class还原成可阅读代码可以借助反编译工具但记住这只能作为排查手段源码工程里始终保留原始代码才是正路。我自己碰到过只有jar没有源码的维护需求当时反编译出来的代码没有注释、变量名混乱维护成本远高于重写所以看到项目源码时建议第一时间把完整工程按版本管理工具管理起来。6.4 上线前还要做的几件事最后提醒一下上线前容易被忽略的三件事定时任务预警扫描任务上线前先空跑一次确认没有把刚好等于低阈值的物资漏掉我的实现里quantity low_stock才会预警的情况测试环境发现经常被漏后来统一为小于等于。数据库备份MySQL库不大每天凌晨用mysqldump全量备份到另一台机器即可应急系统最怕数据丢失备份脚本要敢做恢复演练。默认密码强制修改初始化账号的密码在首次登录后必须强制改密否则安全测试第一关就过不去。个人在实际操作中的最深体会是像应急物资管理这种系统表面上是写代码本质上是梳理业务流。你能不能把一个库存为负批次乱掉谁动了库存说不清的脏账变成一个每一条流水都有来路、每一次操作都有留痕、每一个预警都指向具体批次的干净系统才是项目成败的标准。技术上SpringBootVue这套组合能保住下限但真正拉开差距的是数据库设计阶段对业务的理解以及出库锁库存、事务边界这些细节是否到位。如果这篇文章能帮你少踩我一两个坑那这个分享就值了。
返回列表