1. 为什么选择JavaSpringBootVue技术栈在开始动手开发之前我们先聊聊为什么选择这个技术组合。我做过不少仓库管理系统从早期的JSPServlet到后来的SSM架构最后发现SpringBootVue的组合是最适合中小型仓库管理系统的。SpringBoot最大的优势就是开箱即用。记得我第一次用传统Spring配置数据源时光XML文件就写了200多行。而SpringBoot只需要在application.yml里写几行配置spring: datasource: url: jdbc:mysql://localhost:3306/warehouse username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.DriverVue.js作为前端框架学习曲线比React平缓很多。我们仓库系统的操作人员可能不是技术专家所以需要一个响应快、操作简单的界面。Vue的单文件组件开发模式让前端代码组织特别清晰。比如一个物资列表组件template div el-table :datamaterials el-table-column propname label物资名称/el-table-column el-table-column propstock label库存/el-table-column /el-table /div /template script export default { data() { return { materials: [] } }, mounted() { this.fetchMaterials() }, methods: { async fetchMaterials() { const res await this.$http.get(/api/materials) this.materials res.data } } } /script2. 系统架构设计要点2.1 前后端分离的实践前后端分离不是简单地把项目分成两个工程就完事了。在实际开发中我遇到过几个典型的坑首先是跨域问题。开发时前端跑在8080端口后端跑在9090端口浏览器会拦截跨域请求。SpringBoot中可以通过配置解决Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(*) .allowedMethods(GET, POST, PUT, DELETE) .allowCredentials(true) .maxAge(3600); } }其次是接口规范。建议从一开始就定义好统一的响应格式我习惯用这样的结构{ code: 200, message: success, data: { id: 1, name: 笔记本电脑 } }对应的Java封装类public class ResultT { private int code; private String message; private T data; // 成功静态方法 public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(success); result.setData(data); return result; } // getters setters }2.2 数据库设计经验仓库管理系统最核心的就是物资管理模块。经过多个项目的迭代我总结出一个比较合理的物资表设计CREATE TABLE material ( id bigint NOT NULL AUTO_INCREMENT, code varchar(64) NOT NULL COMMENT 物资编码, name varchar(100) NOT NULL COMMENT 物资名称, specification varchar(200) DEFAULT NULL COMMENT 规格型号, unit varchar(20) DEFAULT NULL COMMENT 单位, stock decimal(12,2) DEFAULT 0.00 COMMENT 当前库存, min_stock decimal(12,2) DEFAULT NULL COMMENT 最低库存预警, max_stock decimal(12,2) DEFAULT NULL COMMENT 最高库存预警, type_id bigint DEFAULT NULL COMMENT 物资类型, status tinyint DEFAULT 1 COMMENT 状态1-正常 0-停用, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_code (code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;特别注意的点物资编码要唯一建议使用规则编码如WL-2023-001库存字段用decimal而不是float避免精度问题记录创建和更新时间方便后期排查问题3. 核心功能实现细节3.1 物资入库的并发控制多个仓库管理员同时入库同一种物资时如果不做并发控制会导致库存数据不一致。我遇到过实际案例A和B同时入库查询时都看到库存是100A加了50B加了30最终库存应该是180但简单实现可能只有150。解决方案是用MySQL的乐观锁Transactional public Result stockIn(Long materialId, BigDecimal quantity) { // 先查询当前库存和版本号 Material material materialMapper.selectById(materialId); // 计算新库存 BigDecimal newStock material.getStock().add(quantity); // 更新库存带版本号校验 int rows materialMapper.updateStock( materialId, newStock, material.getVersion() ); if (rows 0) { throw new RuntimeException(库存更新冲突请重试); } // 记录入库流水 StockFlow flow new StockFlow(); flow.setMaterialId(materialId); flow.setType(1); // 1-入库 flow.setQuantity(quantity); flow.setBalance(newStock); stockFlowMapper.insert(flow); return Result.success(newStock); }对应的Mapper XMLupdate idupdateStock UPDATE material SET stock #{newStock}, version version 1 WHERE id #{id} AND version #{version} /update3.2 基于RBAC的权限控制仓库管理系统通常有三种角色超级管理员可以管理系统所有功能仓库管理员负责物资出入库管理普通用户只能申请物资和查看库存Spring Security的配置示例Configuration EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers(/api/admin/**).hasRole(ADMIN) .antMatchers(/api/warehouse/**).hasAnyRole(ADMIN, WAREHOUSE) .antMatchers(/api/**).authenticated() .anyRequest().permitAll() .and() .addFilter(new JwtAuthenticationFilter(authenticationManager())) .addFilter(new JwtAuthorizationFilter(authenticationManager())) .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); } }前端路由也要做相应控制Vue中可以这样实现const routes [ { path: /admin, component: AdminPanel, meta: { roles: [ADMIN] } }, { path: /warehouse, component: WarehousePanel, meta: { roles: [ADMIN, WAREHOUSE] } } ] router.beforeEach((to, from, next) { const userRoles store.getters.roles if (to.meta.roles !to.meta.roles.some(role userRoles.includes(role))) { next(/403) // 无权限页面 } else { next() } })4. 性能优化实践4.1 库存查询的缓存策略物资库存是高频查询但低频更新的数据非常适合用缓存。我的方案是使用Redis缓存热点物资数据采用Cache Aside Pattern模式设置合理的过期时间如5分钟Spring Boot中整合Redis很简单Cacheable(value material, key #id) public Material getMaterialById(Long id) { return materialMapper.selectById(id); } CacheEvict(value material, key #material.id) public void updateMaterial(Material material) { materialMapper.updateById(material); }对应的配置spring: cache: type: redis redis: host: localhost port: 63794.2 大数据量导出优化当需要导出全部物资数据时比如年底盘点直接查数据库可能会导致内存溢出。我的解决方案是使用MyBatis的游标查询分批次处理数据使用POI的SXSSFWorkbook处理Excel示例代码public void exportAllMaterials(HttpServletResponse response) { try (CursorMaterial cursor materialMapper.selectAllWithCursor()) { SXSSFWorkbook workbook new SXSSFWorkbook(100); // 内存中保留100行 Sheet sheet workbook.createSheet(物资清单); // 写表头 Row headerRow sheet.createRow(0); headerRow.createCell(0).setCellValue(物资编号); headerRow.createCell(1).setCellValue(物资名称); // 遍历游标 int rowNum 1; for (Material material : cursor) { Row row sheet.createRow(rowNum); row.createCell(0).setCellValue(material.getCode()); row.createCell(1).setCellValue(material.getName()); if (rowNum % 100 0) { sheet.flushRows(); } } response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); workbook.write(response.getOutputStream()); } }Mapper接口需要这样定义Select(SELECT * FROM material) CursorMaterial selectAllWithCursor();5. 项目部署与监控5.1 使用Docker容器化部署传统部署方式要装JDK、MySQL、Nginx等一堆环境用Docker可以简化很多。这是我的docker-compose.yml示例version: 3 services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: 123456 MYSQL_DATABASE: warehouse ports: - 3306:3306 volumes: - mysql_data:/var/lib/mysql redis: image: redis:6 ports: - 6379:6379 backend: build: ./backend ports: - 8080:8080 depends_on: - mysql - redis frontend: build: ./frontend ports: - 80:80 volumes: mysql_data:后端Dockerfile示例FROM openjdk:11-jdk COPY target/warehouse.jar app.jar ENTRYPOINT [java,-jar,/app.jar]5.2 系统监控方案线上系统必须要有监控我通常用Spring Boot Actuator Prometheus Grafana的方案首先添加依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-actuator/artifactId /dependency dependency groupIdio.micrometer/groupId artifactIdmicrometer-registry-prometheus/artifactId /dependency配置application.ymlmanagement: endpoints: web: exposure: include: health,info,prometheus metrics: tags: application: warehouse-systemPrometheus配置抓取scrape_configs: - job_name: warehouse metrics_path: /actuator/prometheus static_configs: - targets: [backend:8080]这样就能在Grafana中看到系统的各种指标包括API响应时间、JVM内存使用情况等。