1. 项目概述
福泰轴承股份有限公司是一家专注于轴承生产与销售的企业,随着业务规模的扩大,传统的手工记录和Excel管理方式已经无法满足企业日常运营需求。库存不准确、采购销售流程混乱、数据分析困难等问题日益突出。为此,我们开发了一套基于SpringBoot2+Vue3的现代化进销存管理系统。
这个系统采用前后端分离架构,后端使用SpringBoot2框架搭建RESTful API服务,前端采用Vue3构建响应式用户界面。数据库选用MySQL8.0存储业务数据,通过MyBatis-Plus简化数据访问层开发。系统实现了从采购入库、销售出库到库存管理的全流程数字化,帮助企业实现精细化管理。
提示:在实际开发中,我们特别注重系统的可扩展性和性能优化。例如,使用Redis缓存热点数据,采用RBAC模型进行权限控制,这些都是企业级应用必须考虑的关键点。
2. 技术选型与架构设计
2.1 后端技术栈
后端采用SpringBoot2作为基础框架,主要基于以下考虑:
- 自动配置特性大幅减少了XML配置
- 内嵌Tomcat服务器简化部署
- 丰富的Starter依赖可以快速集成常用组件
- 完善的生态和社区支持
数据访问层使用MyBatis-Plus而非原生MyBatis,主要优势在于:
- 内置通用CRUD方法,减少重复代码
- 强大的条件构造器简化复杂查询
- 支持Lambda表达式,类型安全
- 分页插件开箱即用
数据库选用MySQL8.0版本,主要利用了以下特性:
- 窗口函数支持复杂分析查询
- JSON字段类型存储半结构化数据
- 更好的索引性能和查询优化器
- 事务性能提升
2.2 前端技术栈
前端采用Vue3组合式API开发,相比Options API有以下优势:
- 更好的逻辑复用能力
- 更灵活的组合方式
- 更好的TypeScript支持
- 更小的打包体积
UI框架选用Element Plus,主要组件包括:
- 表单组件:用于数据录入和校验
- 表格组件:展示列表数据
- 弹窗组件:交互反馈
- 导航菜单:系统功能组织
2.3 系统架构设计
系统采用典型的前后端分离架构:
code复制客户端浏览器
↑↓ HTTP/HTTPS
前端服务(Vue3)
↑↓ RESTful API
后端服务(SpringBoot2)
↑↓ JDBC
MySQL数据库
↑↓
Redis缓存
这种架构的优势在于:
- 前后端可以独立开发和部署
- 前端可以使用更适合的渲染策略
- 后端专注于业务逻辑和数据处理
- 更易于实现负载均衡和扩展
3. 核心功能模块实现
3.1 采购管理模块
采购模块主要处理供应商管理和采购订单流程:
java复制// 采购订单创建示例
@PostMapping("/purchase/orders")
public Result createPurchaseOrder(@RequestBody PurchaseOrderDTO dto) {
// 参数校验
if (StringUtils.isEmpty(dto.getSupplierId())) {
return Result.fail("供应商不能为空");
}
// 生成订单编号
String orderNo = "PO" + System.currentTimeMillis();
// 保存订单主表
PurchaseOrder order = new PurchaseOrder();
BeanUtils.copyProperties(dto, order);
order.setOrderNo(orderNo);
order.setStatus(0); // 待审核
order.setCreateTime(LocalDateTime.now());
purchaseOrderService.save(order);
// 保存订单明细
List<PurchaseOrderDetail> details = dto.getDetails().stream()
.map(detailDto -> {
PurchaseOrderDetail detail = new PurchaseOrderDetail();
BeanUtils.copyProperties(detailDto, detail);
detail.setOrderId(order.getId());
return detail;
}).collect(Collectors.toList());
purchaseOrderDetailService.saveBatch(details);
return Result.success(orderNo);
}
关键业务逻辑:
- 订单编号采用"PO"+时间戳生成,确保唯一性
- 使用DTO接收前端数据,与实体类分离
- 主表和明细表分开保存,保证事务一致性
- 状态机管理订单生命周期
3.2 销售管理模块
销售模块处理客户订单的全流程:
vue复制<template>
<el-form :model="orderForm" :rules="rules" ref="formRef">
<el-form-item label="客户" prop="customerId">
<el-select v-model="orderForm.customerId" filterable>
<el-option
v-for="item in customers"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-table :data="orderForm.items">
<el-table-column prop="productName" label="产品">
<template #default="{row}">
<el-select
v-model="row.productId"
filterable
@change="handleProductChange(row)">
<!-- 产品选项 -->
</el-select>
</template>
</el-table-column>
<el-table-column prop="quantity" label="数量">
<template #default="{row}">
<el-input-number
v-model="row.quantity"
:min="1"
@change="calculateTotal"/>
</template>
</el-table-column>
</el-table>
</el-form>
</template>
<script setup>
// 计算总金额
const calculateTotal = () => {
let total = 0
orderForm.items.forEach(item => {
total += item.unitPrice * item.quantity
})
orderForm.totalAmount = total
}
</script>
前端实现要点:
- 使用Element Plus表单组件实现数据绑定
- 动态计算订单总金额
- 产品选择联动显示价格和库存
- 表单验证确保数据完整性
3.3 库存管理模块
库存管理核心是保证库存数据的准确性和实时性:
java复制// 库存扣减服务
@Service
@Transactional
public class InventoryServiceImpl implements InventoryService {
@Override
public boolean deductStock(String productId, int quantity) {
// 使用乐观锁防止超卖
int updated = inventoryMapper.deductStockWithLock(
productId,
quantity,
LocalDateTime.now()
);
if (updated == 0) {
throw new BusinessException("库存不足");
}
// 记录库存变更日志
InventoryLog log = new InventoryLog();
log.setProductId(productId);
log.setChangeAmount(-quantity);
log.setOperationType("SALE");
log.setCreateTime(LocalDateTime.now());
inventoryLogMapper.insert(log);
// 检查是否需要补货
checkReplenishment(productId);
return true;
}
private void checkReplenishment(String productId) {
Inventory inventory = inventoryMapper.selectById(productId);
if (inventory.getStockQuantity() < inventory.getMinStock()) {
// 触发补货预警
replenishmentAlertService.sendAlert(productId);
}
}
}
库存管理关键点:
- 使用乐观锁解决并发修改问题
- 记录完整的库存变更日志
- 实现库存预警机制
- 事务保证数据一致性
4. 数据库设计与优化
4.1 核心表结构
产品表优化设计
sql复制CREATE TABLE `product` (
`product_id` varchar(20) NOT NULL COMMENT '产品ID',
`product_name` varchar(50) NOT NULL COMMENT '产品名称',
`specification` varchar(100) NOT NULL COMMENT '规格',
`unit_price` decimal(10,2) NOT NULL COMMENT '单价',
`cost_price` decimal(10,2) DEFAULT NULL COMMENT '成本价',
`stock_quantity` int NOT NULL DEFAULT '0' COMMENT '库存数量',
`min_stock` int DEFAULT NULL COMMENT '最低库存',
`category_id` varchar(20) NOT NULL COMMENT '分类ID',
`status` tinyint DEFAULT '1' COMMENT '状态(1-正常,0-停用)',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`product_id`),
KEY `idx_category` (`category_id`),
KEY `idx_name` (`product_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='产品信息表';
设计考虑:
- 使用utf8mb4字符集支持emoji
- 为查询字段建立索引
- 添加成本价字段用于利润计算
- 状态字段支持产品上下架
订单表分表策略
对于可能产生海量数据的订单表,我们采用按年月分表策略:
java复制// 动态表名拦截器
@Component
public class DynamicTableInterceptor implements InnerInterceptor {
@Override
public void beforeQuery(Executor executor, MappedStatement ms,
Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql) {
if (ms.getId().contains("OrderMapper")) {
// 解析SQL替换表名
String sql = boundSql.getSql();
String newSql = sql.replace("order", getOrderTableName(parameter));
resetSql(ms, boundSql, newSql);
}
}
private String getOrderTableName(Object param) {
LocalDateTime createTime = getCreateTimeFromParam(param);
return "order_" + createTime.getYear() + "_" + createTime.getMonthValue();
}
}
分表优势:
- 单表数据量可控,查询性能好
- 可以按时间归档历史数据
- 减少索引大小,提高查询效率
- 便于备份和恢复特定时间段数据
4.2 查询优化实践
慢SQL优化案例
原始查询(执行时间1.2s):
sql复制SELECT * FROM order_detail
WHERE product_id IN (
SELECT product_id FROM product
WHERE category_id = 'C001'
)
优化后查询(执行时间0.1s):
sql复制SELECT d.* FROM order_detail d
JOIN product p ON d.product_id = p.product_id
WHERE p.category_id = 'C001'
优化手段:
- 将子查询改为JOIN
- 确保关联字段有索引
- 只查询必要字段
- 添加合适的WHERE条件
统计查询优化
使用物化视图提高统计报表性能:
sql复制CREATE TABLE inventory_stats_mv (
product_id VARCHAR(20) PRIMARY KEY,
total_in INT DEFAULT 0,
total_out INT DEFAULT 0,
last_7days_in INT DEFAULT 0,
last_7days_out INT DEFAULT 0,
update_time DATETIME
);
-- 定时任务更新物化视图
@Scheduled(cron = "0 0 1 * * ?")
public void refreshMaterializedView() {
inventoryStatsMapper.refreshDailyStats();
inventoryStatsMapper.refreshWeeklyStats();
}
5. 系统部署与运维
5.1 生产环境部署方案
推荐使用Docker Compose部署:
yaml复制version: '3'
services:
backend:
image: openjdk:11-jre
ports:
- "8080:8080"
volumes:
- ./app.jar:/app.jar
environment:
- SPRING_PROFILES_ACTIVE=prod
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/inventory?useSSL=false
- SPRING_REDIS_HOST=redis
depends_on:
- mysql
- redis
frontend:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./dist:/usr/share/nginx/html
depends_on:
- backend
mysql:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=123456
- MYSQL_DATABASE=inventory
volumes:
- ./mysql-data:/var/lib/mysql
redis:
image: redis:alpine
ports:
- "6379:6379"
部署要点:
- 使用官方镜像保证稳定性
- 数据卷持久化重要数据
- 环境变量配置敏感信息
- 服务依赖确保启动顺序
5.2 性能监控配置
SpringBoot Actuator + Prometheus + Grafana监控方案:
- 添加依赖:
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
- 配置application.yml:
yaml复制management:
endpoints:
web:
exposure:
include: health,info,prometheus
metrics:
tags:
application: inventory-system
- Prometheus配置:
yaml复制scrape_configs:
- job_name: 'inventory'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['backend:8080']
- Grafana仪表盘导入ID:4701
5.3 常见运维问题
数据库连接池耗尽
症状:系统变慢,出现"Timeout waiting for connection"错误
解决方案:
- 增加连接池大小
- 优化慢查询
- 添加连接泄漏检测
- 配置合理的超时时间
yaml复制spring:
datasource:
hikari:
maximum-pool-size: 20
leak-detection-threshold: 5000
connection-timeout: 30000
缓存雪崩预防
当大量缓存同时失效时,数据库可能承受不住突增的查询压力。
预防措施:
- 设置不同的过期时间
- 使用多级缓存
- 实现缓存预热
- 添加熔断机制
java复制// 缓存配置示例
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.disableCachingNullValues();
// 不同缓存设置不同TTL
Map<String, RedisCacheConfiguration> cacheConfigs = new HashMap<>();
cacheConfigs.put("products", config.entryTtl(Duration.ofHours(1)));
cacheConfigs.put("categories", config.entryTtl(Duration.ofDays(1)));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.withInitialCacheConfigurations(cacheConfigs)
.build();
}
}
6. 开发经验与最佳实践
6.1 前后端协作规范
- API设计原则:
- 使用RESTful风格
- 资源名使用复数形式
- 状态码正确反映结果
- 错误信息格式统一
json复制// 成功响应
{
"code": 200,
"message": "success",
"data": {
"id": "123",
"name": "轴承A"
}
}
// 错误响应
{
"code": 400,
"message": "参数校验失败",
"errors": [
{
"field": "productName",
"message": "产品名称不能为空"
}
]
}
- 接口文档管理:
- 使用Swagger或YAPI
- 及时更新文档
- 标注必填字段和枚举值
- 提供示例请求和响应
6.2 代码质量保障
- 单元测试规范:
- 测试覆盖率不低于70%
- 重点测试业务逻辑
- 使用Mock减少依赖
- 测试用例命名规范
java复制@Test
public void deductStock_shouldSuccess_whenInventoryEnough() {
// Given
Inventory inventory = new Inventory();
inventory.setProductId("P001");
inventory.setStockQuantity(100);
inventoryMapper.insert(inventory);
// When
boolean result = inventoryService.deductStock("P001", 10);
// Then
assertTrue(result);
Inventory updated = inventoryMapper.selectById("P001");
assertEquals(90, updated.getStockQuantity());
}
- 代码审查要点:
- 业务逻辑是否正确
- 是否有性能问题
- 异常处理是否完善
- 是否符合编码规范
- 是否有安全风险
6.3 性能优化技巧
- 批量操作代替循环:
java复制// 不推荐
for (OrderItem item : items) {
orderItemMapper.insert(item);
}
// 推荐
orderItemMapper.insertBatch(items);
- 延迟加载关联数据:
java复制@Mapper
public interface OrderMapper {
@Select("SELECT * FROM order WHERE id = #{id}")
@Results({
@Result(property = "items", column = "id",
many = @Many(select = "findItemsByOrderId",
fetchType = FetchType.LAZY))
})
Order findById(String id);
@Select("SELECT * FROM order_item WHERE order_id = #{orderId}")
List<OrderItem> findItemsByOrderId(String orderId);
}
- 缓存使用策略:
- 读多写少的数据适合缓存
- 设置合理的过期时间
- 考虑缓存穿透问题
- 重要数据要有降级方案
7. 项目总结与扩展方向
在开发福泰轴承进销存系统的过程中,我们遇到并解决了许多典型的企业级应用开发问题。系统目前已经实现了核心的进销存功能,但在以下方面还有优化空间:
- 移动端适配:开发微信小程序或APP版本,支持移动办公
- 智能分析:引入机器学习算法预测库存需求
- 供应链协同:与供应商系统对接实现自动补货
- 多仓库管理:支持分布式仓库的调拨和盘点
一个实际经验是:在开发初期就要考虑分库分表策略,当数据量达到百万级后再改造会非常困难。我们在项目中期不得不重构订单表的存储方案,这导致了额外的工作量。
另一个重要教训是关于缓存一致性的处理。我们曾经遇到过因为缓存更新不及时导致的库存显示不准确问题。最终的解决方案是采用"先更新数据库,再删除缓存"的策略,并在关键操作中添加缓存日志。