最近在给一个团队做Java Web项目评审,拿到了一套船舶监造系统源码,技术栈是SpringBoot2 + Vue3 + MyBatis-Plus + MySQL8.0,还附带了一份文档。我花了一晚上把前后端源码、数据库脚本、接口设计全部过了一遍,又把几个核心模块拆开做了验证。整理下来发现这套系统的业务建模思路、流程状态设计、权限边界划分都挺典型的,而且前后端分离的写法很适合用来做毕业设计、企业内训项目,或者是准备转行船舶信息化领域的参考原型。
不管你是刚入门看源码想学主流技术栈的Java开发,还是船舶行业信息部门想找一套监造管理系统的落地样板,这篇内容都值得从头到尾看一遍。我尽量把项目里那些“文档里一笔带过、实际跑起来才会遇到”的细节一并交代清楚。
1. 船舶监造系统到底在管什么事
我接触过不少制造行业的管理系统,船舶监造在制造业信息化里属于“项目型制造 + 强质量流程”的典型场景,比普通进销存复杂很多。想读懂这套源码,得先弄清楚监造这个业务是怎么运转的。
1.1 监造业务的背景和核心关系
船舶监造,简单说就是船东或者买方委托专业的监造代表,到船厂去监督船舶从开工到交付过程中的建造质量、进度和费用。船厂负责造,监造代表负责查、看、确认。一艘船从钢板切割到整船交付,期间有大量的工序报验、质量检验、问题整改、图纸送审、材料追踪这些流程。
这里面至少涉及几类角色:船东/委托方、监造组(驻厂代表)、船厂建造部门、质检部门、设计院或技术部门。监造代表不是船厂的人,但又天天待在船厂;船厂要赶进度,监造要保质量,两边天然存在博弈关系。所以监造系统的核心不是“记录”,而是“流程留痕 + 责任锁定”——谁报验的、谁检验的、谁放行的、谁整改的,全部要有据可查。
这套系统很明显是按照这个目标建模的。它把船舶监造的核心业务抽象成了几个主题:项目台账、报验单管理、检验计划、问题整改、文档送审、统计看板。没有做特别花哨的功能,但每个模块都贴合真实监造工作里的动作。
1.2 系统解决的问题和适用人群
我之前看过一些类似的源码,很多把船舶监造系统做成了“普通项目管理”,就是建个项目、派任务、查进度,完全没体现监造的特色。这套系统的定位不一样,它把报验单作为主线串起了所有环节,这是它最值得看的部分。
报验单是什么?就是船厂完成某个工序后,向监造代表提交的检验申请单。监造代表根据报验单去现场确认质量、核验数据、签署意见。合格就放行到下一道工序,不合格就打回去整改,整改后再次报验。这个“报验-检验-放行/退回-整改-复验”的闭环,是船舶监造日常最高频、最核心的动作。
如果你是下面这些人群,我建议重点看报验单模块的设计:
- 正在做毕业设计,想找一个“业务流程有深度、技术栈跟得上时代”的选题,这套系统的流程设计完全可以作为业务蓝本。
- 准备入行船舶或重型装备制造信息化,想提前理解监造业务场景,系统里的角色权限逻辑值得反复琢磨。
- 公司内部要做类似的质量监造平台,不想从零设计数据库和流程状态,这套源码里的表结构和工作流状态模型可以帮你少踩很多坑。
整体来看,这个项目不是那种“看起来很全、跑起来很虚”的空壳系统。它的业务边界清晰,技术方案务实,下面我按技术选型、模块拆解、数据库设计、前后端实现、部署运行、问题排查的顺序,逐一展开。
2. 技术选型:这套组合是不是“黄金搭配”
很多Java学习者一看到“SpringBoot2 + Vue3 + MyBatis-Plus + MySQL8.0”就觉得是老生常谈,但真正动手搭起来,每个版本细节都有讲究。
2.1 后端框架:SpringBoot2和MyBatis-Plus的取舍
项目使用SpringBoot2.x而不是SpringBoot3.x,这一点在2024年以后显得比较务实。SpringBoot3强制要求JDK17及以上,并且底层是Jakarta EE命名空间,很多老项目里的第三方依赖(尤其是工作流、权限框架)需要大改包名才能兼容。而SpringBoot2.x配合JDK8或JDK11,几乎是国内中小团队最成熟的组合,各类教程、封装、中间件兼容性都经过海量验证。
MyBatis-Plus在这里承担了数据访问层的核心工具角色。它有两点对这类业务系统特别友好:一是条件构造器(QueryWrapper/LambdaQueryWrapper)可以大幅减少写XML的重复劳动;二是内置的分页插件,对“列表+筛选+分页”这种管理端典型需求几乎是零成本接入。
我实际翻看后端代码,项目用MyBatis-Plus的地方很克制,没有为了秀技术去写一堆花哨的AR模式或链式查询,所有查询都集中在Service层里,Mapper层很干净,这点对维护者来说体验很好。
2.2 前端框架:Vue3 + Vite + Element Plus
前端选择Vue3组合是目前新项目的主流。Vite做开发服务器比Webpack快一个量级,启动项目基本是秒开,Element Plus则把后台管理需要的表格、表单、弹窗、树形控件都备齐了。
这套系统前端的目录结构是我比较欣赏的风格:api目录单独封装请求,views目录按业务模块划分,router目录用动态路由做权限控制,stores目录管理全局状态。没有把组件拆得七零八落,也没有把所有逻辑堆在一个巨型Vue文件里,属于可以拿来直接上手的工程。
2.3 数据库:MySQL8.0的使用细节
MySQL8.0相比5.7有几处质的提升:窗口函数、通用表表达式(CTE)、更好的JSON支持、默认字符集utf8mb4。这套系统的数据库脚本里用到了CTE和窗口函数处理统计报表,这在MySQL5.7上跑不通,必须8.0。
还有一个容易忽略的点:MySQL8.0的驱动类名和时区处理。8.0的驱动类名是com.mysql.cj.jdbc.Driver,连接串里必须带上serverTimezone=Asia/Shanghai,否则会报时区错。早期很多人从5.7迁移到8.0,第一道坎就死在这里。项目文档里特地注明了两条,说明作者是踩过坑的。
技术选型上,这套系统没有追求“最新最炫”,而是每个选型都贴着“快速交付、稳定运行、团队容易上手”的管理系统目标。这个思路,比单纯堆技术栈要成熟很多。
3. 功能全景:监造系统的业务模块是怎么划分的
一套源码拿到手,先别急着看代码,得先看功能菜单。我按项目里实际的菜单结构,把这套系统的功能板块拆给大家看。
3.1 项目台账与进度总览
系统的第一个核心板块是项目管理。每艘船作为一个独立项目,项目台账里记录船型、船号、建造船厂、开工日期、计划交船日期、监造组负责人、当前建造阶段等字段。
进度管理这里没有做成复杂的甘特图,而是用“项目阶段”加“节点计划”的组合来跟踪。比如一艘船会经历:开工/钢板切割、分段建造、船台合拢、下水、码头舾装、试航、交船这些阶段。系统允许在项目下维护多个节点,每个节点有计划时间和实际完成时间。
我比较喜欢这里的一个小设计:阶段进度用百分比自动计算,规则是“已完成节点数 / 总节点数”。这样做虽然有粗糙的地方,但胜在简单直观,领导看大屏汇报的时候一眼就能明白整体进度。如果要做精细化管理,这套模型还可以扩展为关键路径算法,但作为监造系统的早期版本,状态清晰比算法高级重要得多。
3.2 报验单管理:整个系统的流程心脏
报验单模块是这套系统里业务流程最重的部分。船厂质检员发起报验单,填写报验项目、报验区域、报验类型(如焊缝合格率、尺寸精度、密性试验)、关联图纸编号和施工单号。监造代表收到待办消息后,到现场完成检验,填写检验结论:合格/不合格/有条件合格。不合格的报验单自动回到待整改状态,并且关联整改记录。
这里有一个对业务理解很关键的细节:报验单的编号规则。系统里报验单号采用“项目编号 + 区域代码 + 日期 + 流水号”的组合方式。比如XS2025-HULL-0715-008,一眼就能看出是哪条船、哪个分段区域、哪一天报的、第几条。这个编号规则不是随性设计的,现场核对纸质单据时,靠这个编号能快速对应到系统记录。
报验单状态流转路径非常清晰:
- 待报验(草稿)——船厂填报人填写但未提交。
- 待检验——已提交,监造代表可处理。
- 已合格——监造检验通过,流程结束。
- 已退回——检验不通过,退回船厂整改。
- 待复验——整改完成后重新提交。
- 已关闭——复验通过或经协商终止报验。
这个状态机把报验全生命周期覆盖住了,源码里对每个状态迁移都做了合法性判断,比如已合格的报验单不能直接再提交检验,必须走“重新开启”流程。这种状态约束,是工作流系统“防呆”的关键。
3.3 质量问题的整改闭环
质量问题整改模块和报验单是强关联的。一条不合格的报验记录会自动生成或者手工发起一条质量问题单,内容包括问题描述、原因分析、整改措施、责任人。整改完成后提交复验申请,监造代表重新确认后才关闭。
问题台账里支持按项目、按船厂部门、按问题等级筛选,还有超时未整改的统计。这个“超时提醒”在管理上特别重要,造船现场最容易出现的问题就是整改拖着拖着没下文,最后全部堆在交船前爆发。系统里给每个问题设置整改期限,超过期限自动标红,监造组长能直接看到哪些问题卡在哪个环节。
这套机制如果只是写在需求文档里,那属于常规操作;但它能落地到源码里,说明作者对制造业质量管理场景确实门儿清。很多质量管理系统的通病就是“记录好、闭环难”,这个系统的设计明显是冲着闭环去的。
3.4 送审资料和图纸管理
监造过程会产生大量图纸、工艺文件、检验报告。系统里设置了资料管理模块,支持按项目归档资料,每个资料可以关联船型、阶段、专业类型。列表页预览、下载、版本号管理都有。
这里我要提醒一句:很多源码项目把“文件上传”做成一个单独的demo,放在系统里完全是为了凑功能。但这套系统的资料管理是承接业务需求的——船厂提交的图纸送审单关联到报验单,监造代表在审批报验单时可以直接看到相关图纸和附件,这种“业务对象 + 附件扩展”的建模思路很标准。
3.5 统计报表和监造看板
系统内置了一个数据看板模块,用于展示项目分布、报验合格率、问题整改及时率、进度完成比例等指标。图表用的是ECharts,后端对每个统计项写了单独SQL。
统计口径在源码里处理得比较清楚:比如报验一次性合格率 = 一次检验直接通过的报验单数 / 总报验单数。这个指标可不是前端拿数据简单算个除法,而是后端在SQL里通过状态字段和操作记录表配对算出来的。这样前端只需要负责渲染,不容易算错。
这部分使用的MySQL窗口函数(比如ROW_NUMBER、SUM OVER)在生产环境数据量大时也能撑住,不会出现前端卡死。就一个中小型监造团队的使用规模来说,这套统计方案绰绰有余。
4. 数据库设计:五张核心表是怎么串起整个业务流程的
读一套管理系统的源码,最有效的办法是先把数据库ER关系理清楚。我打开数据库脚本,把核心表的字段逐个过了一遍,这里挑重点讲。
4.1 核心数据表清单和关系
这套系统的数据表大约二十多张,分为几组:
- 权限组:用户表、角色表、用户角色关联表、菜单权限表。
- 业务组:项目表、报验单表、报验明细表、检验记录表、问题整改表、资料表。
- 流程组:报验单状态记录表、问题流转记录表。
- 基础组:船厂部门表、区域字典表、检验项目字典表。
其中最能体现业务设计功力的是报验单相关表组。报验单主表(inspection_order)负责记录报验单自身的属性,报验明细表(inspection_order_item)记录具体检验项目,检验记录表(inspection_record)记录每次检验的动作和结论,状态记录表(inspection_order_log)则记录报验单每一次状态变更的痕迹。
这里要强调一个设计原则:主表不存流程痕迹。很多新手设计状态表时,就在主表里加一个status字段,状态一变就直接UPDATE,完全查不到曾经是什么状态、谁改的。这套系统单独建了状态记录表,虽然多了一次写入,但换来了完整的历史链路。
4.2 报验单主表和明细表的字段设计
报验单主表典型字段包括:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| order_no | varchar(64) | 报验单编号 |
| project_id | bigint | 关联项目ID |
| block_name | varchar(64) | 分段/区域名称 |
| inspection_type | varchar(32) | 报验类型(焊接/尺寸/密性/涂装等) |
| apply_user | varchar(32) | 报验申请人 |
| apply_time | datetime | 报验申请时间 |
| inspect_result | varchar(16) | 检验结论(合格/退回/待复验等) |
| inspect_user | varchar(32) | 检验监造代表 |
| inspect_time | datetime | 检验时间 |
| remark | varchar(500) | 备注 |
报验明细表则记录一个报验单下包含的具体检验部位和检验要求,比如“分段A101左舷焊缝UT探伤”或“机舱区域管系密性试验”。这个设计让一张报验单可以批量检验多个点,又能在明细层面单独留痕。
4.3 状态记录表的巧妙之处
状态记录表(inspection_order_log)字段很简单:id、order_id、from_status、to_status、operator、create_time、remark。
但就是这张看似简单的表,让整个系统的报表能算准。比如前面提到的“一次性合格率”,SQL就可以这么写:
SELECT COUNT(*) AS total_orders, SUM(CASE WHEN first_inspect_result = 'PASS' THEN 1 ELSE 0 END) AS one_pass_orders FROM ( SELECT o.id, o.inspect_result AS first_inspect_result, ROW_NUMBER() OVER (PARTITION BY o.id ORDER BY l.create_time) AS rn FROM inspection_order o JOIN inspection_order_log l ON l.order_id = o.id WHERE l.to_status = '检验完成' ) t WHERE rn = 1;这个查询借助窗口函数,取每个报验单第一次检验记录的结论,统计第一次就合格的占比。这是业务上很常用的口径,但很多系统由于没有独立的日志表,根本算不出来。
4.4 权限设计:RBAC模型的经典落地
系统的用户权限模型是标准的RBAC,用户表、角色表、用户角色关系表、菜单表、角色菜单关系表。它允许一个用户拥有多个角色,一个角色拥有多个菜单权限。
菜单权限表的字段包括菜单编码、菜单名称、父级编码、路由路径、前端组件路径、类型(目录/菜单/按钮)。这就支持到按钮级别的权限控制。比如同一张报验单列表页,船厂质检员只看到“提交报验”按钮,监造代表才看到“开始检验”按钮。
这种按钮级权限在管理系统中非常重要。实际部署时,如果现场的监造代表误操作点了“确认合格”,轻则流程乱了,重则质量责任纠纷。RBAC加上后端方法级权限校验(@PreAuthorize注解),能在接口层二次拦截,做到前后端双保险。
5. 后端实现要点:认证、查询、状态流转怎么落代码
前面讲的都是“设计层面”,接下来看代码实现。这块我会直接展示核心写法,并补充一些我在实际跑源码时验证过的细节。
5.1 JWT认证和拦截器实现
系统使用JWT做无状态登录认证。用户登录成功后,后端签发一个Token,前端把Token存在本地存储中,每次请求在请求头里携带Authorization字段。
核心拦截器逻辑大致如下:
@Component public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token = request.getHeader("Authorization"); if (StringUtils.isBlank(token)) { throw new BusinessException(401, "未登录"); } // 解析token,校验签名和过期时间 Claims claims = JwtUtil.parseToken(token.replace("Bearer ", "")); // 把用户ID和角色信息放入ThreadLocal,供后续逻辑使用 UserContext.set(claims); return true; } }这里的UserContext使用ThreadLocal存储当前用户信息,避免每个Service方法都传一遍userId。要注意的是,接口处理完必须调用UserContext.clear(),否则Tomcat线程池复用会串数据。这是一个比较隐晦的坑。
5.2 MyBatis-Plus动态查询和分页
列表接口是管理系统的重头戏。系统里用LambdaQueryWrapper实现多条件筛选,代码很清爽:
public PageResult<InspectionOrderVO> pageOrders(OrderQueryDTO dto) { LambdaQueryWrapper<InspectionOrder> wrapper = Wrappers.lambdaQuery(); wrapper.eq(StringUtils.isNotBlank(dto.getProjectId()), InspectionOrder::getProjectId, dto.getProjectId()) .eq(StringUtils.isNotBlank(dto.getStatus()), InspectionOrder::getStatus, dto.getStatus()) .like(StringUtils.isNotBlank(dto.getOrderNo()), InspectionOrder::getOrderNo, dto.getOrderNo()) .orderByDesc(InspectionOrder::getCreateTime); Page<InspectionOrder> page = new Page<>(dto.getPageNum(), dto.getPageSize()); Page<InspectionOrder> result = inspectionOrderMapper.selectPage(page, wrapper); // 转换为VO并返回分页数据 return PageResult.of(result.getRecords(), result.getTotal()); }利用MyBatis-Plus的eq方法第二个参数判断条件是否为空,就不用写一堆if判断了。这里有个细节:like查询字段如果没限制长度,很容易被SQL注入,所以源码中对order_no做了白名单校验,只允许字母数字和短横线,这个细节建议保留。
5.3 状态流转的业务逻辑封装
状态流转是系统的核心,代码里没有用复杂状态机框架,而是用一个Service类集中处理。这种“轻量状态机”在中小项目里比引入Flowable要实用得多。
以“监造代表确认合格”为例:
@Transactional(rollbackFor = Exception.class) public void passOrder(Long orderId, Long operatorId, String remark) { InspectionOrder order = getOrderById(orderId); // 校验当前状态是否允许走向“已合格” if (!List.of("待检验", "待复验").contains(order.getStatus())) { throw new BusinessException("当前状态不允许确认合格"); } // 更新主表状态 order.setStatus("已合格"); order.setInspectResult("PASS"); order.setInspectUser(String.valueOf(operatorId)); order.setInspectTime(LocalDateTime.now()); updateById(order); // 写入状态记录表 saveOrderLog(orderId, order.getStatus(), "已合格", operatorId, remark); }@Transactional保证了主表更新和日志写入要么都成功、要么都失败,不会出现状态改了日志却丢了的情况。这个原子性设计值得抄。
5.4 文件上传和预览实现
文件上传使用SpringMVC标准MultipartFile,存储路径在配置文件里指定,文件名用UUID重新生成防止重名冲突。
public String uploadFile(MultipartFile file, Long projectId, String type) { if (file.isEmpty() || file.getSize() > MAX_FILE_SIZE) { throw new BusinessException("文件不能为空且大小不能超过50MB"); } String originalFilename = file.getOriginalFilename(); String ext = StringUtils.substringAfterLast(originalFilename, "."); String newFileName = UUID.randomUUID().toString().replace("-", "") + "." + ext; Path dir = Paths.get(uploadPath, String.valueOf(projectId), type); Files.createDirectories(dir); file.transferTo(dir.resolve(newFileName).toFile()); // 保存文件元数据到资料表 return "/api/file/" + newFileName; }文件类型这里建议加白名单限制,只允许pdf、jpg、png、doc、docx、zip,防止恶意上传脚本文件。源码里虽然加了大小限制,但类型校验可以再补强,部署时可以自己加上。
6. 前端实现要点:Vue3工程里有哪些值得搬的写法
前端部分用了Vue3的组合式API,整体代码风格干净。我挑几个对后台管理系统最有代表性的实现点。
6.1 路由动态加载和权限控制
前端路由不是一次性全部注册的,而是根据后端返回的菜单权限动态注册。这里用的是Vite的import.meta.glob机制,把匹配到权限的组件路径动态映射成路由。
const modules = import.meta.glob('../views/**/*.vue') function buildRoutes(menus: MenuItem[]): RouteRecordRaw[] { return menus.map(menu => ({ path: menu.path, name: menu.name, component: modules[`../views${menu.componentPath}.vue`] })) }这样做的最大好处是安全:没有权限的用户在前端连路由都看不到,更不会出现“输入URL直接跳过菜单访问”的低级漏洞。真正严格的数据隔离还得靠后端接口鉴权,但前端这层过滤可以大幅减少无谓的接口调用。
6.2 Pinia模块管理登录状态
系统用Pinia管理当前登录用户和Token。登录成功后,把用户信息、角色列表、按钮权限列表全部存入store,并提供hasPermission方法供按钮级判断。
export const useUserStore = defineStore('user', { state: () => ({ token: localStorage.getItem('token') || '', userInfo: {}, permissions: [] }), actions: { hasPermission(code: string) { return this.permissions.includes(code) || this.userInfo.role === 'admin' }, logout() { this.token = '' this.userInfo = {} localStorage.removeItem('token') router.push('/login') } } })按钮判断在组件里用得非常自然:
<el-button v-if="userStore.hasPermission('order:pass')" type="success">确认合格</el-button> <el-button v-if="userStore.hasPermission('order:reject')" type="danger">退回整改</el-button>6.3 流程表单的步骤条设计
报验单处理页面上,前端用Element Plus的el-steps组件展示当前流转位置。从待检验到合格,用户能直观地看到目前处于哪个节点。
对应的状态颜色也做了统一映射:待检验是蓝色、已合格是绿色、已退回是红色、待复验是橙色。这个状态配色虽然只是前端细节,但现场操作员长期使用下来,眼睛找状态的速度会快很多,属于低成本高感知度的体验优化。
6.4 图表看板的数据绑定
看板页面使用ECharts,所有统计项的数据来自后端统一接口。图表组件封装了一个公共的BaseChart.vue,传入option配置即渲染。这个封装把所有ECharts实例的初始化、resize监听、销毁逻辑收敛到一起,避免了每个图表组件重复写一套生命周期。
看板上的数据刷新用了定时器,每30秒轮询一次接口。这里有一个很容易被忽略的问题:页面销毁时必须清定时器,否则会一直请求后端。源码里在onUnmounted钩子中做了清除,这个习惯值得学习。
7. 部署运行:从零开始跑起来需要准备什么
这部分是实操内容。我按照项目的部署文档结合实际启动过程,把完整流程梳理成可以直接抄的清单。
7.1 环境准备和初始配置
本地运行需要准备的工具:
- JDK 1.8或11(对应SpringBoot2,JDK8即可)。
- MySQL 8.0以上版本。
- Node.js 16以上版本。
- Maven 3.6以上,或使用IDEA内置Maven。
- 前端包管理工具npm/yarn/pnpm均可。
先把项目源码解压,根目录下通常会有sql目录、backend目录、frontend目录和一份PDF文档,这是源码项目的标准布局。
在MySQL中新建数据库,将ship_supervision.sql导入。
mysql -u root -p -e "CREATE DATABASE ship_supervision DEFAULT CHARACTER SET utf8mb4;" mysql -u root -p ship_supervision < ship_supervision.sql导入后,重点检查sys_user表中初始账号,默认管理员账号密码一般是admin/123456,首次登录后建议立刻改密。
7.2 后端配置修改和启动
打开后端项目的application.yml,修改数据源配置:
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ship_supervision?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true username: root password: your_password这里几个参数很有必要说明:serverTimezone=Asia/Shanghai解决时区差8小时问题;allowPublicKeyRetrieval=true解决MySQL8.0加密插件引发的Public Key Retrieval is not allowed报错。
然后启动主启动类。看到“Tomcat started on port(s): 8080”的日志说明后端启动成功。
7.3 前端启动步骤
进入frontend目录,安装依赖:
npm install npm run dev如果npm install报错,可以换用淘宝镜像源:
npm config set registry https://registry.npmmirror.com npm install启动后,默认开发服务器地址是http://localhost:5173。后端接口地址一般配置在.env.development文件中,确认它指向http://localhost:8080/api即可。
用admin账号登录系统,进入项目台账新增一条船舶项目,再到报验单模块发起和检验一条报验单,跑通整个闭环就算部署成功。
7.4 部署后需要立刻改的几个配置项
源码默认配置适合本地跑,但真正进入生产前有几处必须改:
- JWT签名密钥。
application.yml里的jwt.secret不能是默认值,必须换成一个长随机字符串。 - 文件上传本地路径。源码默认写到系统临时目录或项目目录,建议改成独立数据盘路径,例如
/data/upload。 - MySQL密码和账号。源码里可能自带测试账号,生产环境必须关闭或在数据库层限制白名单。
这些如果官方网站或者论文里没提,也不要掉以轻心。我见过不止一个项目把默认密钥带进生产环境,结果Token被伪造,整库被拖。改密钥这种事,启动后第1分钟就该做。
8. 常见问题与排查技巧实录
最后这部分,是我把跑这套系统时可能遇到的典型报错和排查方法集中列出来。每个问题都对应真实场景,可以直接按图索骥。
8.1 MySQL8.0连接失败
报错信息一般是:
java.sql.SQLException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized...解决方案就是URL里明确指定时区:
url: jdbc:mysql://localhost:3306/ship_supervision?serverTimezone=Asia/Shanghai如果报错变成Public Key Retrieval is not allowed,在URL末尾加:
&allowPublicKeyRetrieval=true这两个问题属于MySQL8.0的经典入门坑,我在其他项目里几乎每次遇到都会看到。
8.2 前端请求后端跨域
开发阶段前后端端口不同(前端5173,后端8080),浏览器默认拦截跨域请求。解决方式有两种:
- 在后端CORS配置类里允许前端来源(源码已内置,检查路径或端口是否正确)。
- 在前端Vite配置里增加devServer代理,把
/api转发到http://localhost:8080,推荐这种方式。
Vite代理写法:
server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }注意:修改Vite配置后要重启前端服务才能生效,热更新不会自动加载配置文件。
8.3 页面刷新后登录状态丢失
这是管理系统最常见的问题。根源是Vuex/Pinia的state是内存态,刷新页面内存清空。解决办法是持久化Token到localStorage,并在页面加载时重新根据Token拉取用户信息。
这套系统已经在Pinia里做了localStorage同步,正常情况下不会丢。如果还是丢,检查刷新时是否触发了路由守卫跳转到登录页,可能是守卫里判断用户信息的逻辑在Token存在但用户信息为空时,直接走了登出分支。
8.4 文件上传页面提示路径不存在
文件上传如果存到绝对路径,比如/data/upload,但Linux系统下该目录没有创建权限,就会抛出FileNotFound或AccessDenied异常。
解决方案是启动前手动创建目录:
mkdir -p /data/upload chmod 755 /data/upload或者在代码里像前面展示的那样先用Files.createDirectories递归创建目录。这个逻辑源码里已经写了,但如果通过配置文件改成自定义路径,需要确保Java进程对路径有写权限。
8.5 前端刷新某个子页面返回404
Vue3是单页应用,如果前端路由用的是history模式,刷新时浏览器会真实请求/foo/bar路径,而服务器上没有这个路径,就会404。
生产环境需要在Nginx加如下配置:
location / { try_files $uri $uri/ /index.html; }开发环境下,Vite默认能正常回退,但如果部署到静态服务器,这个配置几乎是必改项。
最后分享一个我这次实操中的体会:读源码项目,尤其是这种带业务流程的管理系统,最忌讳上来就盯着Controller一个一个文件翻。正确顺序应该是先看文档和数据库脚本,弄清表之间怎么关联,再顺着核心业务功能(比如报验单)从前端页面一路追到后端SQL,最后才去看权限、工具类这些外围代码。这套系统本身结构规整,按照这个顺序读,一个周末基本就能摸透。接手的团队如果要做二次开发,我建议第一批改造放在两处:一是把报验单状态机从硬编码判断迁移成可配置的状态字典,二是把统计看板里的固定指标改为SQL模板可配置。这两处改完,系统的灵活性和可维护性会再上一个台阶。