1. 船舶维保管理系统架构解析
船舶维保管理系统采用前后端分离架构,后端基于SpringBoot框架构建,前端使用Vue3实现动态交互,数据持久层采用MyBatis框架,MySQL作为核心数据库存储业务数据。这种架构选择在当前的Web应用开发中已经成为主流方案,特别适合中大型企业级应用。
1.1 技术栈选型考量
后端技术栈:
- SpringBoot 2.7.x:简化了Spring应用的初始搭建和开发过程,内置Tomcat服务器,提供自动配置和起步依赖
- MyBatis-Plus 3.5.x:增强版ORM框架,提供通用Mapper和Service,减少重复CRUD代码
- Spring Security:处理认证授权,实现基于角色的访问控制(RBAC)
- Lombok:通过注解简化POJO类的编写
- Hutool:Java工具类库,提供各种实用工具方法
提示:SpringBoot版本选择2.7.x而非最新的3.x系列,主要考虑企业环境的稳定性和兼容性需求。
前端技术栈:
- Vue3 + Composition API:提供更好的TypeScript支持和代码组织方式
- Element Plus:基于Vue3的UI组件库,提供丰富的现成组件
- Axios:处理HTTP请求,与后端API交互
- Vue Router:实现前端路由管理
- Pinia:Vue3的状态管理库,替代Vuex
数据库:
- MySQL 8.0:关系型数据库,支持事务和复杂查询
- Redis:缓存高频访问数据,如权限信息和系统配置
1.2 系统架构设计
系统采用经典的三层架构,但根据船舶维保业务特点做了针对性优化:
code复制客户端层(Web前端)
↑↓ HTTP/HTTPS
应用服务层(SpringBoot)
↑↓ JDBC/MyBatis
数据存储层(MySQL+Redis)
关键设计特点:
- 前后端完全分离:前端通过RESTful API与后端交互,便于独立开发和部署
- 模块化设计:按功能划分为船舶管理、维保管理、故障管理等模块
- 权限控制:基于角色的访问控制,细粒度到按钮级别
- 审计日志:记录关键操作,满足航运业合规要求
2. 数据库设计与实现
2.1 核心表结构解析
船舶基础信息表(ship_info)
sql复制CREATE TABLE `ship_info` (
`ship_id` varchar(20) NOT NULL COMMENT '船舶编号(主键)',
`ship_name` varchar(50) NOT NULL COMMENT '船舶名称',
`ship_type` varchar(30) DEFAULT NULL COMMENT '船舶类型',
`tonnage` double DEFAULT NULL COMMENT '船舶吨位(吨)',
`build_date` date DEFAULT NULL COMMENT '建造日期',
`status` tinyint DEFAULT '1' COMMENT '状态(0:停用,1:启用)',
`last_maintenance` datetime DEFAULT NULL COMMENT '上次维保时间',
`next_maintenance` datetime DEFAULT NULL COMMENT '下次计划维保时间',
`insurance_expire` date DEFAULT NULL COMMENT '保险到期日',
PRIMARY KEY (`ship_id`),
KEY `idx_status` (`status`),
KEY `idx_next_maintenance` (`next_maintenance`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
设计要点:
- 主键使用业务意义的
ship_id而非自增ID,便于业务关联 - 添加了
last_maintenance和next_maintenance字段,实现维保计划自动提醒 - 建立状态和维保时间的索引,提高查询效率
维保任务记录表(maintenance_task)
sql复制CREATE TABLE `maintenance_task` (
`task_id` varchar(20) NOT NULL COMMENT '任务编号(主键)',
`ship_id` varchar(20) NOT NULL COMMENT '船舶编号(外键)',
`task_type` varchar(30) NOT NULL COMMENT '任务类型',
`executor` varchar(50) DEFAULT NULL COMMENT '执行人员',
`start_time` datetime DEFAULT NULL COMMENT '开始时间',
`end_time` datetime DEFAULT NULL COMMENT '结束时间',
`task_status` tinyint DEFAULT '0' COMMENT '状态(0:未开始,1:进行中,2:已完成)',
`cost` decimal(10,2) DEFAULT NULL COMMENT '维保费用',
`parts_used` text COMMENT '使用零件清单',
`remark` text COMMENT '备注',
PRIMARY KEY (`task_id`),
KEY `idx_ship_id` (`ship_id`),
KEY `idx_task_status` (`task_status`),
CONSTRAINT `fk_ship` FOREIGN KEY (`ship_id`) REFERENCES `ship_info` (`ship_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
优化点:
- 添加了成本统计字段,便于财务分析
- 使用外键约束确保数据完整性
- 文本字段使用
text类型,避免内容截断
2.2 数据库性能优化实践
索引策略:
- 为所有外键字段创建索引
- 为高频查询条件创建组合索引
- 避免过度索引,定期使用
EXPLAIN分析查询性能
分表考虑:
- 故障记录表按年度分表,命名规则
fault_record_2023 - 使用Spring动态数据源路由实现透明访问
SQL优化示例:
java复制// 不好的写法
@Select("SELECT * FROM maintenance_task WHERE ship_id = #{shipId}")
List<MaintenanceTask> findByShipId(String shipId);
// 优化后的写法
@Select("SELECT task_id, task_type, start_time, end_time FROM maintenance_task WHERE ship_id = #{shipId} ORDER BY start_time DESC LIMIT 100")
List<MaintenanceTask> findRecentByShipId(String shipId);
3. 后端核心功能实现
3.1 船舶管理模块
RESTful API设计:
java复制@RestController
@RequestMapping("/api/ships")
public class ShipController {
@Autowired
private ShipService shipService;
@GetMapping
public PageResult<ShipVO> listShips(
@RequestParam(required = false) String keyword,
@RequestParam(required = false) String shipType,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size) {
return shipService.queryShips(keyword, shipType, page, size);
}
@PostMapping
public Result addShip(@Valid @RequestBody ShipDTO dto) {
return shipService.addShip(dto);
}
@PutMapping("/{shipId}")
public Result updateShip(@PathVariable String shipId, @Valid @RequestBody ShipDTO dto) {
return shipService.updateShip(shipId, dto);
}
@GetMapping("/{shipId}/maintenance-records")
public List<MaintenanceTask> getMaintenanceRecords(@PathVariable String shipId) {
return shipService.getMaintenanceRecords(shipId);
}
}
业务逻辑实现要点:
- 使用DTO模式实现层间数据传输
- 参数校验使用JSR-303注解
- 分页查询统一返回格式
- 异常处理使用全局异常处理器
3.2 维保任务调度算法
系统实现了智能维保任务调度,核心算法如下:
java复制public class MaintenanceScheduler {
public List<MaintenanceTask> scheduleTasks(List<Ship> ships, List<MaintenancePlan> plans) {
// 1. 按船舶优先级和维保紧急度排序
ships.sort(Comparator.comparingInt(Ship::getPriority)
.thenComparing(s -> s.getNextMaintenance()));
// 2. 匹配维保资源(人员、设备)
Map<String, Technician> availableTechs = techService.getAvailableTechnicians();
Map<String, Equipment> availableEquipments = equipmentService.getAvailableEquipments();
// 3. 贪心算法分配任务
List<MaintenanceTask> tasks = new ArrayList<>();
for (Ship ship : ships) {
MaintenancePlan plan = findMatchingPlan(plans, ship);
if (plan != null) {
Technician tech = findAvailableTechnician(availableTechs, plan.getRequiredSkills());
Equipment equipment = findAvailableEquipment(availableEquipments, plan.getRequiredEquipment());
if (tech != null && equipment != null) {
MaintenanceTask task = createTask(ship, plan, tech, equipment);
tasks.add(task);
// 更新资源可用状态
availableTechs.remove(tech.getId());
availableEquipments.remove(equipment.getId());
}
}
}
return tasks;
}
// 其他辅助方法...
}
4. 前端关键实现技术
4.1 Vue3组合式API实践
船舶列表组件实现:
vue复制<script setup>
import { ref, onMounted } from 'vue'
import { useShipStore } from '@/stores/ship'
import ShipTable from '@/components/ShipTable.vue'
const shipStore = useShipStore()
const loading = ref(false)
const pagination = ref({
page: 1,
pageSize: 10,
total: 0
})
const fetchShips = async () => {
loading.value = true
try {
await shipStore.fetchShips({
page: pagination.value.page,
size: pagination.value.pageSize
})
pagination.value.total = shipStore.totalCount
} finally {
loading.value = false
}
}
onMounted(fetchShips)
</script>
<template>
<div class="ship-container">
<el-card>
<div class="filter-container">
<!-- 筛选条件 -->
</div>
<ship-table
:data="shipStore.ships"
:loading="loading"
@refresh="fetchShips"
/>
<el-pagination
v-model:current-page="pagination.page"
v-model:page-size="pagination.pageSize"
:total="pagination.total"
@current-change="fetchShips"
/>
</el-card>
</div>
</template>
4.2 维保任务可视化看板
使用ECharts实现维保任务状态分布图:
vue复制<script setup>
import * as echarts from 'echarts'
import { onMounted, ref, onUnmounted } from 'vue'
import { useTaskStore } from '@/stores/task'
const taskStore = useTaskStore()
const chartRef = ref(null)
let chartInstance = null
onMounted(async () => {
await taskStore.fetchTaskStats()
initChart()
})
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose()
}
})
const initChart = () => {
chartInstance = echarts.init(chartRef.value)
const option = {
tooltip: {
trigger: 'item'
},
legend: {
top: '5%',
left: 'center'
},
series: [
{
name: '任务状态分布',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '18',
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{ value: taskStore.stats.pending, name: '待处理' },
{ value: taskStore.stats.inProgress, name: '进行中' },
{ value: taskStore.stats.completed, name: '已完成' }
]
}
]
}
chartInstance.setOption(option)
window.addEventListener('resize', () => chartInstance.resize())
}
</script>
<template>
<div ref="chartRef" style="width: 100%; height: 400px;"></div>
</template>
5. 系统部署与运维
5.1 生产环境部署方案
服务器配置建议:
- 应用服务器:4核8G内存,建议2台做负载均衡
- 数据库服务器:8核16G内存,SSD存储
- Redis服务器:2核4G内存
Docker Compose部署示例:
yaml复制version: '3.8'
services:
backend:
image: ship-maintenance-backend:1.0.0
container_name: backend
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- DB_URL=jdbc:mysql://mysql:3306/ship_db
- DB_USER=root
- DB_PASSWORD=yourpassword
depends_on:
- mysql
- redis
frontend:
image: ship-maintenance-frontend:1.0.0
container_name: frontend
ports:
- "80:80"
depends_on:
- backend
mysql:
image: mysql:8.0
container_name: mysql
environment:
- MYSQL_ROOT_PASSWORD=yourpassword
- MYSQL_DATABASE=ship_db
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
redis:
image: redis:6.2
container_name: redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
mysql_data:
redis_data:
5.2 性能监控与调优
SpringBoot监控配置:
properties复制# application-prod.properties
management.endpoints.web.exposure.include=health,info,metrics,prometheus
management.metrics.export.prometheus.enabled=true
management.metrics.tags.application=ship-maintenance
关键监控指标:
- API响应时间(P99 < 500ms)
- 数据库查询耗时(< 100ms)
- JVM内存使用率(< 70%)
- 活跃数据库连接数(< 80%连接池上限)
常见性能问题排查:
- 慢查询:开启MySQL慢查询日志,定期分析优化
- 内存泄漏:使用VisualVM或Arthas分析堆内存
- 线程阻塞:通过线程转储(thread dump)分析阻塞原因
6. 项目开发经验总结
6.1 技术决策反思
- MyBatis vs JPA选择:对于船舶维保这类业务模型相对固定的系统,JPA可能更合适,可以减少大量XML配置
- Vuex到Pinia的迁移:项目中期从Vuex切换到Pinia是正确的,简化了状态管理代码
- 日期处理:早期使用java.util.Date导致时区问题,后统一改为java.time.LocalDateTime
6.2 典型问题解决方案
问题1:船舶维保计划冲突
- 现象:同一船舶在同一时间段被分配多个维保任务
- 解决方案:在任务创建时增加冲突检测逻辑
java复制public boolean checkScheduleConflict(String shipId, LocalDateTime start, LocalDateTime end) {
return maintenanceTaskMapper.exists(
new QueryWrapper<MaintenanceTask>()
.eq("ship_id", shipId)
.eq("task_status", 1) // 只检查进行中的任务
.and(wrapper -> wrapper
.between("start_time", start, end)
.or()
.between("end_time", start, end)
)
);
}
问题2:大文件上传失败
- 现象:前端上传超过10MB的船舶图片时失败
- 解决方案:
- 调整Nginx配置:
client_max_body_size 20M - SpringBoot配置:
spring.servlet.multipart.max-file-size=20MB - 前端实现分片上传
- 调整Nginx配置:
6.3 项目扩展方向
- 移动端适配:开发微信小程序或React Native应用,支持现场维保记录
- 物联网集成:对接船舶传感器数据,实现预测性维护
- 数据分析:使用Apache Spark进行大规模维保数据分析
- 工作流引擎:集成Camunda等引擎实现复杂审批流程