☰
基于SpringBoot+Vue3的船舶监造系统设计与实现全解析
2026/10/10 8:01:43 网站建设 项目流程

船舶监造这种项目,看起来是个传统行业的管理系统,实际上涉及的流程管控、角色协同、文档流转一点都不比互联网项目简单。我做完这套基于 SpringBoot + Vue3 + MyBatis + MySQL 的船舶监造系统后,最大的感受就是:这种垂直领域的业务系统,技术栈反而是最不需要纠结的部分,真正花时间的是把监造业务里的报验流程、质量跟踪、问题闭环这些环节梳理清楚,再用代码优雅地落地。

这套系统采用前后端分离架构,前端 Vue3 + Element Plus,后端 SpringBoot 提供 RESTful API,MyBatis 做数据持久化,MySQL 存储业务数据。如果你正准备做类似的工业制造类管理系统,或者对船舶监造的业务数字化感兴趣,这篇文章会从业务拆解、表结构设计、核心功能实现到部署运维,把整个项目的关键节点都过一遍。

1. 船舶监造系统到底在管什么

1.1 监造业务的真实场景

船舶监造,通俗讲就是船东委托监造团队或者第三方监理机构,在船厂建造船舶的过程中,对施工质量、进度节点、材料使用等进行监督和验收。一条船从钢材进厂到交付,周期短则几个月,长则一两年,期间涉及的检验项目、报验申请、问题整改、图纸文档流转量非常大。

我刚接触这个项目需求时,客户给的需求文档有上百页,但总结下来,核心业务就几条线:监造计划管理、检验报验流程、质量问题的发现与闭环、图纸文档的版本控制。这四条线贯穿船舶建造全过程,对应到系统里,就是计划模块、报验模块、整改模块、文档模块。

这个场景和普通的企业管理系统有个显著区别:业务状态多、流程链条长、参与角色杂。一个检验项目可能要经过“报验提交 → 船厂自检 → 监造审核 → 现场检验 → 结果判定 → 问题整改 → 复验闭环”多个环节,每个环节都有对应的角色和操作。如果系统设计时没把状态流转理清楚,后期开发就会陷入不断打补丁的死循环。

1.2 前后端分离架构在这个场景下的优势

为啥选前后端分离?船舶监造系统有一个很实际的需求:监造人员需要频繁出差、驻场,甚至在不同的船坞、车间进行移动端操作。前后端分离后,后端 API 可以同时支撑 Web 管理端、移动端 H5,甚至将来扩展小程序、App,不需要重复开发业务逻辑。

另一个原因是团队协作效率。我接手这个项目时,前端和后端是并行开发的。前端同学根据接口文档用 Vue3 + Element Plus 搭页面,后端同学专注于 SpringBoot 业务接口开发,彼此之间通过接口约定解耦。对比传统的服务端渲染模式,前后端分离让两边的工作量都能被精确评估,进度管理也简单很多。

还有一点,就是技术栈本身的成熟度。SpringBoot 在 Java 生态里的地位不用多说,Vue3 的 Composition API 和响应式系统在处理复杂表单、状态联动时确实比 Vue2 写得舒服。MyBatis 灵活的手写 SQL 能力,在报表统计、多表关联查询这些场景下,比 JPA 那种全自动的 ORM 更容易优化和控制。

2. 技术选型背后的硬核理由

2.1 后端框架:SpringBoot 3.x 还是 2.x

这个项目我选择的是 SpringBoot 2.7.x,没有直接上 3.x。原因很实在:生态兼容性。项目里集成了一些第三方组件,比如工作流引擎、报表导出工具,这些组件对 JDK 8 和 SpringBoot 2.x 的适配是经过生产环境验证过的。上 3.x 就意味着 JDK 17 起跳,一些老牌库如果没跟上版本,排查兼容性问题的成本会很高。

当然,如果你是从零开始的新项目,技术选型比较自由,SpringBoot 3.x 完全可以用,毕竟性能和安全性确实有提升。但做工程项目的经验是:选技术栈不是选最新的,而是选最稳的。我见过太多项目因为框架升级,耗在依赖冲突上的时间比写业务代码还久。

SpringBoot 的核心价值在于它把大量的配置自动化了。我们只需要在pom.xml里引入依赖,再在application.yml里写上数据源、Redis、文件上传等基础配置,业务接口就能快速开发。项目的分包结构也比较常规:

com.shipmonitor ├── controller # 接口层 ├── service # 业务逻辑层 │ └── impl ├── mapper # MyBatis Mapper接口 ├── entity # 数据库实体 ├── dto # 数据传输对象 ├── common # 通用工具、返回值封装 └── config # 配置类

2.2 持久层:为什么 MyBatis 比 JPA 更顺手

船舶监造系统的查询场景非常复杂。举例来说,报验单列表页需要根据船号、报验类型、检验状态、报验人、时间范围做组合筛选,还要统计各状态的数量;检验记录要关联多个子表数据。这种场景用 MyBatis 的动态 SQL 特别好使。

<select id="selectInspectionReportPage" resultType="com.shipmonitor.dto.InspectionReportDTO"> SELECT ir.id, ir.report_no, ir.ship_id, ir.inspection_type, ir.status, ir.submit_user, ir.submit_time, su.user_name AS submit_user_name FROM inspection_report ir LEFT JOIN sys_user su ON ir.submit_user = su.id <where> <if test="shipId != null"> AND ir.ship_id = #{shipId} </if> <if test="inspectionType != null and inspectionType != ''"> AND ir.inspection_type = #{inspectionType} </if> <if test="status != null and status != ''"> AND ir.status = #{status} </if> <if test="startTime != null"> AND ir.submit_time &gt;= #{startTime} </if> </where> ORDER BY ir.submit_time DESC </select>

这种写法的好处是:条件不确定的时候,<where>标签自动处理多余的 AND 关键字。比 JPA 的 Specification 编程式查询读起来直观得多,也比 JPA 自动生成的 SQL 更容易做性能调优。尤其在做报表统计的时候,手写 SQL 能精确控制 JOIN 方式和聚合逻辑,避免 JPA 生成一堆效率堪忧的 SQL。

2.3 前端方案:Vue3 + Element Plus 的组合

Vue3 我用的是 Composition API +<script setup>语法。这个组合配合 Vite 构建工具,开发体验确实比 Vue2 时代的 Options API 舒服太多。组件逻辑可以按功能点组织,不用像以前那样所有代码都塞进data、methods、computed里。

船舶监造系统里有个典型场景:报验单填报页面,涉及船舶基本信息、检验项目多选、附件上传、备注说明等多个模块,且不同检验类型下,要填写的字段不同。用 Vue3 的响应式对象管理表单状态,再配合 watch 监听类型变化,动态渲染对应的字段区域,代码组织上非常清晰。

const formData = reactive({ shipId: null, inspectionType: '', inspectionItems: [], checkResult: '', attachmentList: [], remark: '' }) watch(() => formData.inspectionType, (newType) => { if (newType === 'WELDING') { // 焊接检验需要填充焊工证号、焊缝编号等字段 formData.specialFields = { welderNo: '', weldSeamNo: '' } } else { formData.specialFields = null } })

Element Plus 的表格、表单、树形控件、Upload 组件都比较完善,配合分页组件能快速搭建管理后台风格界面。项目里的进度展示、质量统计图表,用了 ECharts 的 Vue3 封装库,比如按船型分布、检验合格率趋势图,效果直观,实现成本也不高。

3. 系统核心模块拆解与业务设计

3.1 监造计划与船舶建造节点管理

船舶建造是有明确节点划分的:材料进场 → 下料切割 → 分段制造 → 船台合拢 → 下水 → 舾装 → 系泊试验 → 试航 → 交付。每个阶段都有对应的监造计划和检验任务。

系统里我把监造计划设计成两级结构:节点计划 + 检验任务计划。节点计划绑定到具体船舶,指定计划名称、计划开始时间、计划结束时间、责任人;检验任务计划挂在节点计划下面,指定检验项目、检验类型、检验标准、计划完成时间。

这个设计源于一个实际业务痛点:监造人员经常反馈“计划赶不上变化”。船舶建造总会出现各种延期,船厂、设备到货情况都会影响进度。所以计划模块里,我专门做了一个“计划调整记录”子模块,每次调整都留下痕迹,方便后续追溯到底为什么延期、谁批准的调整。

还有一个细节需要注意:系统里所有计划状态和实际完成状态是分开的。计划状态是“未开始、进行中、已完成、已延期”,实际生产状态是船厂报上来的。两者对比才能反映项目真实健康度,这也是监造管理报表的核心数据来源。

3.2 报验流程:船舶监造的质量核心

报验是船舶监造里最高频的操作。船厂完成一个工序节点后,需要向监造方提交报验申请,监造人员到现场检验或审核提交的材料,给出合格或不合格结论,不合格就需要整改后重新报验。

我把报验流程抽象成了一张状态机流转图,核心状态包括:

状态含义可执行操作
待提交船厂已创建但未正式提交编辑、删除、提交
待审核已提交,等待监造审核审核通过进入待检验、审核退回
待检验审核通过,安排检验录入检验结果
检验中正在现场检验登记检验记录、挂起
检验完成检验结果已录入判定合格或不合格
合格检验通过归档
不合格检验不通过发起整改
整改中问题整改进行中提交整改完成申请
待复验整改后申请复验复验通过后合格,不通过继续整改

这个流程设计最关键的一点,是所有状态流转都必须有操作记录。我在报验单表旁边设计了一张inspection_flow_log表,记录每一步的操作人、操作时间、操作动作、意见内容。这既满足监造行业的合规审计要求,也为后续做质量分析提供了数据基础。

报验单的数据结构也比较有代表性,主表存报验单的公共信息,子表存报验的项目明细。类似订单和订单明细的关系。一个报验单可能包含多个检验项目,每个项目的检验结果独立记录,这样统计“某条船报验合格率”时,就能按明细口径精确计算。

3.3 问题整改与闭环管理

船舶监造过程中,问题整改是个高频且需要强跟踪的事情。检验发现焊缝不合格、尺寸超差、材料证书缺失等问题,都要下发整改通知,并要求船厂限期整改,整改完成后申请复验,复验不合格继续整改。

问题整改模块我单独建了两张表:quality_issue(质量问题记录)和quality_issue_reply(整改回复记录)。质量问题记录关联到报验单或者检验项,包含问题描述、严重程度(轻微/一般/严重/重大)、发生区域、责任人;整改回复记录记录每次整改的内容、整改人、整改时间、附件材料。

这个模块有一个业务需求特别值得注意:超期自动提醒。监造规范要求一般问题整改必须在规定期限内完成,超过期限系统要自动提醒监造负责人。我这里用 SpringBoot 的@Scheduled定时任务,每天扫描未闭环且超期的整改单,生成待办提醒,并通过消息中心推送给相关角色。这个功能上线后,监造人员反馈“终于不用自己翻台账盯期效了”。

3.4 文档管理:图纸、证书、检验报告的统一归档

船舶建造涉及大量的文档:设计图纸、材料证书、焊接工艺规程、检验报告、船级社证书。传统方式是纸质存档,找一份资料翻箱倒柜,效率极低,还容易丢失版本。

系统里的文档模块,我把文档分为两类:报验关联文档和独立归档文档。报验关联文档挂在报验单下,如检验报告扫描件、现场照片、探伤报告;独立归档文档则按照船号 + 大类 + 小类的目录树进行管理。

文件存储方案上,项目初期用的是本地磁盘存储,配置一个统一的磁盘路径,按业务类型和日期建目录。这种方式简单,单机部署时完全够用。如果后续要做集群部署,再切换成 MinIO 或者阿里云 OSS 也不难,因为文件访问路径都是通过后端接口中转的,存储迁移不影响前端逻辑。

4. 数据库设计的关键细节

4.1 船舶与项目的主数据设计

数据库设计的核心原则,先搭好主数据的骨架,再延伸业务数据。船舶监造系统里,主数据包括:船型字典、船舶信息、船东信息、船厂信息、监造单位信息、用户信息。

船舶信息表是最核心的主数据表,除了船名、船型、船体编号等基础字段,还需要记录当前建造阶段(对应前面说的节点),这个字段会被大范围使用,所以加了普通索引。另外还有一个construction_progress字段,存放当前计划进度的百分比,方便首页进度看板直接展示。

主数据表之间还有一个容易被忽视的细节:归属关系的建模。一条船归属于某个船东,在某家船厂建造,由某个监造单位负责监造。这些归属关系如果直接写成外键,后续查询要 JOIN 好几张表。我的做法是:主数据表之间保持合理的冗余,比如船舶信息表里直接存shipyard_id和supervisor_org_id,查询时只关联一张单位表就行,避免多层 JOIN。

4.2 业务表的核心索引设计

报验单表inspection_report是业务量最大的表,整表字段包含报验单号、船号、报验类型、检验状态、提交人、提交时间、监造负责人、审核意见等。组合筛选查询特别频繁,索引设计成下面这样:

ALTER TABLE inspection_report ADD INDEX idx_ship_type_status (ship_id, inspection_type, status); ALTER TABLE inspection_report ADD INDEX idx_submit_time (submit_time);

分别是:按船舶 + 类型 + 状态查,按时间范围查。这两个索引覆盖了列表页 90% 以上的查询场景。ship_id的区分度高,放在最左边可以快速缩小扫描范围。

质量整改表quality_issue的索引设计则围绕“超期提醒”这个定时任务做。定时任务每天执行的 SQL 是:

SELECT * FROM quality_issue WHERE status = '整改中' AND deadline < NOW() AND remind_flag = 0;

所以建了idx_status_deadline (status, deadline)联合索引。这个查询频率不高,但每次要扫描整张表,没有索引的话,数据量上来后会拖慢定时任务执行。

4.3 MyBatis 代码生成器与手写 SQL 的平衡

项目里我用了 MyBatis Generator 生成基础的实体类和 Mapper 接口,单表增删改查直接复用生成的代码。但复杂的多表关联查询、动态条件查询,我都手写 SQL 或者在生成的Example类基础上改造。

经验是:基础 CRUD 用生成器,业务查询手写。因为生成的代码虽然省事,但一旦业务复杂起来,那种大而全的 Example 对象反而让你的代码变得难以维护。手写 SQL 看着多费几行代码,但可读性和可控性都强得多。特别是报表统计类的需求,手写 SQL 能明确控制聚合粒度,出问题也好定位。

5. 核心功能实现的全流程拆解

5.1 后端接口的 RESTful 设计与权限控制

后端接口设计遵循 RESTful 风格,比如报验单模块的核心接口:

方法路径功能
GET/api/inspection-report/page分页查询报验单
GET/api/inspection-report/{id}获取报验单详情
POST/api/inspection-report新建报验单
PUT/api/inspection-report/{id}更新报验单
DELETE/api/inspection-report/{id}删除报验单
POST/api/inspection-report/{id}/submit提交审核
POST/api/inspection-report/{id}/audit审核报验单
POST/api/inspection-report/{id}/inspect录入检验结果

权限控制用的是 JWT + 拦截器的方式。登录成功后签发 JWT,每次请求携带 Token,后端拦截器校验身份,然后根据角色判断是否有接口访问权限。系统角色分为:系统管理员、监造负责人、监造工程师、船厂报验员、船级社验船师。角色不同,能访问的接口和数据范围就不同。

实现简单的权限控制,用拦截器 + 注解就能解决,不需要引入 Spring Security 或 Shiro 这种重量级框架。我在common包下定义了一个@RequireRole注解,标注在 Controller 方法上,拦截器读取注解进行权限校验。

@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RequireRole { String[] value(); }

Controller 里的使用方式:

@PostMapping("/{id}/audit") @RequireRole({"SUPERVISOR_LEADER", "SUPERVISOR_ENGINEER"}) public Result<?> audit(@PathVariable Integer id, @RequestBody AuditDTO dto) { inspectionReportService.audit(id, dto); return Result.success(); }

5.2 前端用户认证与动态菜单

前端认证流程很简单:登录页提交账号密码,后端验证通过返回 JWT Token 和用户信息,前端把 Token 存到localStorage,然后在 Axios 请求拦截器里统一加上 Token 头。

// axios 请求拦截器 service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = `Bearer ${token}` } return config }) // 响应拦截器处理 401 service.interceptors.response.use( response => response.data, error => { if (error.response.status === 401) { localStorage.removeItem('token') router.push('/login') } return Promise.reject(error) } )

动态菜单的实现逻辑是:登录成功后,通过/api/user/menus接口获取当前角色能访问的菜单列表,前端用 Vue Router 的动态路由 API 按需注册。这样不同角色登录系统,看到的左侧菜单是完全不同的。船厂报验员看不到质量统计分析菜单,监造工程师看不到用户管理菜单,这在管理类系统里是刚需。

5.3 状态流转控制的前后端联动

报验流程的状态流转,前端和后端都要做控制。后端在 Service 层做状态校验,避免跳过中间状态直接流转到终态;前端根据当前状态动态渲染可操作按钮,避免用户误操作。

以报验单为例,前端表格操作列根据row.status动态判断:

function getActionButtons(row) { const btns = [] if (row.status === '待提交') { btns.push({ label: '提交审核', action: 'submit' }) btns.push({ label: '编辑', action: 'edit' }) } if (row.status === '待审核') { btns.push({ label: '审核', action: 'audit' }) } if (row.status === '待检验') { btns.push({ label: '录入结果', action: 'inspect' }) } return btns }

后端 Service 的对应逻辑,核心是校验状态更新的合法性:

public void submitReview(Integer id, LoginUser user) { InspectionReport report = this.getById(id); if (!report.getStatus().equals("待提交")) { throw new BusinessException("当前状态不允许提交审核"); } // 更新状态并插入流程日志 report.setStatus("待审核"); this.updateById(report); flowLogService.log(report.getId(), "提交审核", user.getId(), "船厂提交报验申请"); }

状态机设计好不好,直接决定业务后续扩展顺不顺利。在设计阶段,我是把状态流转梳理清楚后才动手建表,不然到后期开发会不断加字段、加 if else 判断,代码烂到不行。

5.4 文件上传与预览的实现细节

文件上传后端用 SpringBoot 的MultipartFile接收,存储后用 UUID 重命名,保存文件名和存储路径到数据库。上传接口的核心逻辑:

@PostMapping("/upload") public Result<String> upload(@RequestParam("file") MultipartFile file) { String originalFilename = file.getOriginalFilename(); // 生成新的文件名,防止重名覆盖 String newFileName = UUID.randomUUID().toString() + "." + FilenameUtils.getExtension(originalFilename); String datePath = new SimpleDateFormat("yyyyMMdd").format(new Date()); String savePath = fileBasePath + "/" + datePath + "/" + newFileName; File dest = new File(savePath); if (!dest.getParentFile().exists()) { dest.getParentFile().mkdirs(); } file.transferTo(dest); // 入库保存原始文件名和存储路径 return Result.success("/api/file/download?fileId=" + fileId); }

文件下载时,通过接口读取文件流,设置Content-Disposition头来支持预览和下载。注意一个坑:文件下载接口要在拦截器里放行,或者对登录用户可以访问,不然前端<img>标签直接访问文件地址时会因为没带 Token 而 401。我这里的做法是路径上带有动态 Token 参数,或者用 Blob 方式加载文件。

前端用 Element Plus 的el-upload组件,配好自定义上传方法,把文件上传和表单数据提交分离,提升用户体验。项目里检验报告、现场照片、整改附件都用了这套统一机制。

6. 环境搭建与项目部署实操

6.1 本地开发环境初始化

开发环境准备,几件套:JDK 1.8 或以上、Maven 3.6+、Node 16+、MySQL 5.7 或 8.0、Redis(可选)。后端项目启动前,先创建好数据库:

CREATE DATABASE ship_supervision DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

然后修改application.yml里的数据源配置:

spring: datasource: url: jdbc:mysql://localhost:3306/ship_supervision?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver

建议不用密码明文写在配置文件里,可以通过环境变量的方式注入:

username: ${DB_USERNAME:root} password: ${DB_PASSWORD:root}

前端项目把.env.development里配置的 API 地址指向后端:

VITE_API_BASE_URL=http://localhost:8080/api

然后依次启动后端(Maven 打包后java -jar或 IDE 启动)和前端(npm run dev),就能跑通全部功能了。

6.2 生产环境部署要点

生产环境我用的是经典部署方案:前端 Nginx 托管静态文件 + 后端 SpringBoot Jar 包 + MySQL 独立部署。

前端项目打包:

npm run build

打包产物在dist目录,把整个目录上传到服务器的 Nginx 静态目录下,配置反向代理:

server { listen 80; server_name your-domain.com; root /opt/ship-supervision/dist; index index.html; # 前端路由 history 模式配置 location / { try_files $uri $uri/ /index.html; } # API 反向代理 location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 文件下载接口 location /file/ { proxy_pass http://127.0.0.1:8080/file/; proxy_set_header Host $host; } }

这个配置解决了一个常见问题:Vue Router 用 history 模式时,刷新页面容易 404,try_files指令把所有路径都 fallback 到index.html,前端路由接管后就能正常渲染页面。如果你不想处理这个问题,也可以把路由改成 hash 模式,但我个人更推荐 history + Nginx 配置,地址栏干净不少。

后端启动建议用脚本管理,比如写一个start.sh:

#!/bin/bash nohup java -Xms512m -Xmx1024m \ -jar ship-supervision.jar \ --spring.profiles.active=prod \ > logs/app.log 2>&1 & echo $!

生产环境的 MySQL 建议独立部署,数据目录定期备份,用系统自带的 mysqldump 或者项目管理软件做定时备份策略。我这边是每天凌晨 2 点自动全量备份,保留最近 30 天的备份文件,上线至今还没出现过数据事故。

7. 常见问题与排查技巧实录

7.1 前端打包后刷新页面 404

这个问题出现频率极高。Vue Router 的 history 模式,本地开发跑得好好的,打包部署到 Nginx 后,进入页面点刷新直接 404。原因就是 Nginx 没有配置try_files指令,请求/inspection时 Nginx 去找物理路径inspection,找不到就 404 了。

解决方法是配置里加try_files $uri $uri/ /index.html;,让所有路径都回到index.html,交给前端路由去处理。如果你用的不是 Nginx,而是 Tomcat 或 SpringBoot 内嵌容器部署,那要配置 forward 到 index.html,或者干脆用 hash 模式省心。

7.2 多表关联查询慢

报验单列表要关联船舶表、用户表、报验类型字典表,数据量到几万条时查询明显变慢。排查方法是先用EXPLAIN看执行计划,发现关联查询里驱动表没走索引,或者使用了非索引字段做关联。

我当时的优化方案:

  • 在关联字段(如ship_id、submit_user)上建索引
  • 控制分页查询的深度,不能用LIMIT 10000, 10这种深分页,改用基于游标的分页方式,比如WHERE id < ? ORDER BY id DESC LIMIT 10,或者记录上一页最后一条记录的时间戳。
  • 对于统计报表类查询,创建独立汇总表,用定时任务同步数据,避免实时聚合。

深分页的问题在监造系统里尤其容易踩到。报验记录、流程日志这种只增不减的表,页面越往后点越慢。换成游标分页后,即使百万级数据也能秒级响应。

7.3 MyBatis 的多级嵌套查询与 N+1 问题

报验单详情页,需要把报验单、检验项目列表、检验结果、附件列表一次性查出来。最初我用 MyBatis 的嵌套查询,结果组装时对每条报验单都执行一次子查询,造成了 N+1 问题,详情页接口偶尔会超时。

解决方式是改为连表查询,使用collection标签一次性封装结果:

<resultMap id="ReportDetailMap" type="com.shipmonitor.dto.InspectionReportDetailDTO"> <id column="id" property="id"/> <result column="report_no" property="reportNo"/> ... <collection property="inspectionItems" ofType="com.shipmonitor.dto.InspectionItemDTO"> <id column="item_id" property="itemId"/> <result column="item_name" property="itemName"/> <result column="result" property="result"/> </collection> </resultMap>

注意collection一对多映射时,如果子表数据量很大,内存占用会明显上升。要对查询结果做评估,必要时拆分成多个接口分别加载,前端用并发请求拉取,减少用户等待时间。

7.4 JWT Token 过期与用户状态冲突

项目上线后遇到一个线上问题:用户一直使用系统,但到了 Token 过期时间就被强制下线,体验很差。原因是 JWT 实现里没有做”续签”机制。

我后来优化成滑动过期策略:在 JWT 里指定过期时间(如 2 小时),每次请求时解析出剩余有效时间,如果剩余时间少于 30 分钟,就签发一个新的 Token 返回给前端,前端拦截响应后更新本地存储。这样用户只要在活跃使用,Token 就永远不会真正过期。

不过注意,如果你是单机部署,这个方案没问题;如果做了多节点集群,用户请求落到不同节点,每次刷新签发新 Token 需要保证所有节点后台统一时间,或者把 Token 状态存到 Redis 里做集中管理。这个升级方案我后面也会在项目里落实。

7.5 文件存储路径不一致问题

开发环境、测试环境、生产环境部署在不同的服务器,文件上传保存的路径如果写死到配置文件里,换环境后旧数据就会失效。这里我把文件存储路径专门做成了配置项,而且建议用相对路径或环境变量。

数据库里存的是相对路径,比如20250601/xxx.pdf,访问时通过下载接口拼接基础路径。这样换环境部署时,只需要修改全局配置就能访问到原有文件,不用改数据库数据。前期没有考虑到这点,开发机上跑的好好的数据,测试环境调试时全找不到文件,折腾了小半天。后来果断改成现在的方案,一劳永逸。

8. 项目落地过程中的一些反思

整套船舶监造系统从需求分析到上线,花了三个多月时间,核心功能都跑通了,监造计划、报验流程、质量整改、文档管理这些主链路在项目上稳定运行。

我技术上的总体感受是:SpringBoot + Vue3 + MyBatis 这套组合,做中小型企业管理类系统非常成熟稳定,不需要过多的框架定制,把精力投入到业务和数据结构的设计上,收益率最高。而在业务层面,船舶监造这类垂直行业的系统,最大的坑是对行业流程理解不到位。比如报验状态的流转顺序、问题整改的闭环要求、监造文档的版本管理规则,这些如果不和客户反复确认,做出来的系统就是“技术正确但业务不可用”。

我踩过的另一个坑是需求变更频繁。船舶监造的业务规范有国标、行标、船级社规范等多种来源,客户在执行时又有自己的特殊流程。一开始我把状态流转设计得比较僵化,结果客户提出某个场景要支持紧急跳过检验直接判定合格时,代码改动挺大。后来我调整了设计思路:状态机是核心框架,但特殊操作可以通过审批流程来动态执行,这让系统的适应能力提高了不少。

如果后续继续扩展这个项目,我觉得有两个方向:一个是移动端应用,监造人员在船坞、车间现场需要随时随地拍照取证、登记检验结果,移动端的价值很明显;另一个是数据分析看板,积累足够数据后,用大屏展示各船型的建造进度、质量趋势、供应商绩效,对管理层决策会很有帮助。做这类行业系统,只要把业务流程真正吃透了,后续的增值功能会源源不断。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询