1. 项目背景与需求分析
实验室设备管理一直是高校和科研机构面临的痛点问题。传统的手工登记方式存在诸多弊端:借用记录容易丢失、设备状态难以实时追踪、审批流程效率低下。我曾参与过某高校物理实验室的调研,发现他们平均每周要处理超过200次设备借用申请,仅靠Excel表格和纸质登记本管理,经常出现设备超期未还、使用冲突等问题。
这个SpringBoot+Vue的实验设备借用管理系统正是为解决这类问题而设计。系统需要实现以下核心功能:
- 设备信息的数字化管理(包括分类、状态、位置等)
- 在线预约与审批流程
- 使用记录与归还提醒
- 数据统计与报表生成
从技术选型角度看,SpringBoot提供了稳定的后端服务能力,Vue则能构建响应式的前端界面,两者通过RESTful API进行数据交互,形成典型的前后端分离架构。这种组合既能保证系统性能,又能提供良好的用户体验。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统架构设计
2.1 技术栈选型
后端采用SpringBoot 2.7.x版本,主要基于以下考虑:
- 内嵌Tomcat服务器,简化部署
- 自动配置特性大幅减少XML配置
- 丰富的Starter依赖(如spring-boot-starter-data-jpa)
- 完善的监控机制(Actuator)
前端选择Vue 3 + Element Plus组合,原因包括:
- 响应式编程模型适合频繁交互的管理系统
- 组件化开发提高代码复用率
- TypeScript支持带来更好的类型安全
- Element Plus提供了丰富的UI组件
数据库选用MySQL 8.0,主要特性包括:
- JSON字段支持设备扩展属性存储
- 窗口函数便于生成统计报表
- 事务隔离级别满足并发控制需求
2.2 系统模块划分
系统采用分层架构设计:
code复制├── 表现层 (Vue前端)
│ ├── 用户认证模块
│ ├── 设备管理模块
│ ├── 预约审批模块
│ └── 统计报表模块
│
├── 应用层 (SpringBoot)
│ ├── API网关
│ ├── 业务逻辑服务
│ └── 定时任务
│
├── 数据层
│ ├── JPA Repository
│ └── 缓存服务(Redis)
│
└── 基础设施
├── 文件存储
└── 消息队列(可选)
3. 核心功能实现细节
3.1 设备管理模块
设备实体设计采用JPA注解:
java复制@Entity
@Table(name = "equipment")
public class Equipment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Enumerated(EnumType.STRING)
private EquipmentStatus status; // AVAILABLE, BORROWED, MAINTENANCE
@ManyToOne
@JoinColumn(name = "category_id")
private Category category;
@Column(columnDefinition = "JSON")
private String specifications; // 存储JSON格式的扩展属性
// 其他字段及getter/setter
}
前端采用Element Plus的表格组件展示设备列表,关键实现:
vue复制<template>
<el-table :data="equipmentList" style="width: 100%">
<el-table-column prop="name" label="设备名称" />
<el-table-column prop="status" label="状态">
<template #default="{row}">
<el-tag :type="statusTagType(row.status)">
{{ statusText(row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作">
<template #default="{row}">
<el-button @click="handleDetail(row)">详情</el-button>
</template>
</el-table-column>
</el-table>
</template>
3.2 预约审批流程
采用状态机模式管理借用流程:
java复制public enum BorrowStatus {
PENDING, // 待审批
APPROVED, // 已批准
REJECTED, // 已拒绝
IN_USE, // 使用中
RETURNED, // 已归还
OVERDUE // 超期未还
}
审批逻辑的关键代码:
java复制@Transactional
public void processApproval(Long borrowId, boolean approved, String comment) {
BorrowRecord record = borrowRepository.findById(borrowId)
.orElseThrow(() -> new ResourceNotFoundException("借用记录不存在"));
if (record.getStatus() != BorrowStatus.PENDING) {
throw new BusinessException("当前状态不允许审批");
}
record.setStatus(approved ? BorrowStatus.APPROVED : BorrowStatus.REJECTED);
record.setApprovalComment(comment);
record.setApprovalTime(LocalDateTime.now());
if (approved) {
Equipment equipment = record.getEquipment();
equipment.setStatus(EquipmentStatus.BORROWED);
equipmentRepository.save(equipment);
// 发送通知邮件
emailService.sendBorrowApproval(record);
}
}
3.3 定时任务设计
使用Spring Scheduler实现归还提醒:
java复制@Scheduled(cron = "0 0 9 * * ?") // 每天上午9点执行
public void checkOverdueEquipment() {
LocalDate today = LocalDate.now();
List<BorrowRecord> overdueRecords = borrowRepository
.findByStatusAndExpectedReturnDateBefore(
BorrowStatus.IN_USE,
today
);
overdueRecords.forEach(record -> {
record.setStatus(BorrowStatus.OVERDUE);
borrowRepository.save(record);
// 发送逾期提醒
notificationService.sendOverdueAlert(record);
});
}
4. 关键技术问题解决方案
4.1 并发控制问题
当多个用户同时预约同一设备时,可能出现超借情况。解决方案:
- 数据库层面添加乐观锁:
java复制@Entity
public class Equipment {
@Version
private Integer version;
// ...
}
- 业务层加锁策略:
java复制@Transactional
public BorrowRecord createBorrowRequest(BorrowRequest request) {
Equipment equipment = equipmentRepository.findById(request.getEquipmentId())
.orElseThrow(() -> new ResourceNotFoundException("设备不存在"));
// 使用SELECT FOR UPDATE加锁
Equipment lockedEquipment = equipmentRepository
.findByIdWithLock(request.getEquipmentId());
if (lockedEquipment.getStatus() != EquipmentStatus.AVAILABLE) {
throw new BusinessException("设备当前不可借用");
}
// 创建借用记录
BorrowRecord record = new BorrowRecord();
// 设置各字段...
// 更新设备状态
lockedEquipment.setStatus(EquipmentStatus.BORROWED);
return borrowRepository.save(record);
}
4.2 文件导出性能优化
报表导出可能涉及大量数据,采用以下优化方案:
- 使用Apache POI的SXSSFWorkbook实现流式导出:
java复制public void exportBorrowHistory(HttpServletResponse response) {
SXSSFWorkbook workbook = new SXSSFWorkbook(100); // 保持100行在内存中
// 创建工作表、表头等...
try (ScrollableResults scroll = borrowRepository
.findAllForExport()
.scroll(ScrollMode.FORWARD_ONLY)) {
int rowNum = 1;
while (scroll.next()) {
BorrowRecord record = (BorrowRecord) scroll.get(0);
// 填充行数据...
rowNum++;
if (rowNum % 100 == 0) {
workbook.getSheet().flushRows(100); // 刷新行到磁盘
}
}
}
// 设置响应头并写入输出流
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment; filename=borrow_history.xlsx");
workbook.write(response.getOutputStream());
workbook.dispose();
}
- 对于超大数据量,改用异步导出+邮件发送方案:
java复制@Async
public void asyncExportBorrowHistory(Long userId, DateRange range) {
String filePath = "/tmp/export_" + UUID.randomUUID() + ".xlsx";
// 执行导出...
// 完成后发送邮件
User user = userRepository.findById(userId).orElseThrow();
emailService.sendExportResult(user.getEmail(), filePath);
}
5. 部署与运维实践
5.1 多环境配置
使用SpringBoot的Profile特性管理环境配置:
yaml复制# application-dev.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/lab_dev
username: devuser
password: devpass
redis:
host: localhost
port: 6379
# application-prod.yml
spring:
datasource:
url: jdbc:mysql://prod-db:3306/lab_prod
username: ${DB_USER}
password: ${DB_PASSWORD}
redis:
host: redis-cluster
port: 6379
启动时指定Profile:
bash复制java -jar equipment-manager.jar --spring.profiles.active=prod
5.2 前端部署优化
Vue项目构建优化配置(vue.config.js):
javascript复制module.exports = {
productionSourceMap: false,
configureWebpack: {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
libs: {
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial'
},
elementPlus: {
name: 'chunk-elementPlus',
test: /[\\/]node_modules[\\/]_?element-plus(.*)/,
priority: 20
},
commons: {
name: 'chunk-commons',
test: /src[\\/]components/,
minChunks: 3,
priority: 5,
reuseExistingChunk: true
}
}
}
}
},
chainWebpack: config => {
config.plugin('preload').tap(() => [{
rel: 'preload',
fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
include: 'initial'
}])
}
}
5.3 监控与日志
集成SpringBoot Actuator进行健康监控:
yaml复制management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
prometheus:
enabled: true
日志收集采用ELK方案:
- 使用LogstashLogbackEncoder输出JSON格式日志
- Filebeat收集日志文件
- 发送到Elasticsearch集群
- 通过Kibana进行可视化
logback-spring.xml配置示例:
xml复制<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<springProperty scope="context" name="appName" source="spring.application.name"/>
<appender name="JSON" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/${appName}.log</file>
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/${appName}-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>5GB</totalSizeCap>
</rollingPolicy>
</appender>
<root level="INFO">
<appender-ref ref="JSON"/>
</root>
</configuration>
6. 项目演进与优化方向
在实际运行半年后,我们收集到以下改进需求:
-
移动端适配:
- 开发微信小程序版本
- 增加扫码借用功能
- 推送通知集成
-
智能调度:
- 基于历史数据预测设备需求
- 自动协调冲突预约
- 最优设备分配算法
-
物联网集成:
- 对接智能柜门禁系统
- 设备使用状态实时监测
- 自动记录实际使用时长
-
数据分析:
- 设备利用率热力图
- 用户行为分析
- 维护周期预测
技术债清理计划:
- 将单体应用拆分为微服务架构
- 引入GraphQL替代部分REST API
- 前端迁移到Vue 3的Composition API
- 增加E2E测试覆盖率
这个系统从最初仅满足基本借用需求,逐步发展为涵盖设备全生命周期管理的平台。在开发过程中,最大的体会是:业务逻辑的复杂性往往超过技术实现。特别是在处理各种边缘情况时(如部分归还、设备转移、连带责任等),需要与领域专家充分沟通,建立准确的状态机模型。
