news 2026/9/11 17:22:14

Java Swing船舶管理系统:从JDBC到FTP上报的存量系统解析与升级实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java Swing船舶管理系统:从JDBC到FTP上报的存量系统解析与升级实践

简介:国内航运企业船舶管理系统是一套面向中小航运企业船舶管理场景的完整Java项目源码,适用于正在学习企业级管理系统开发的学生或初级程序员。资源包共69个文件,包含20个Java源文件、38个已编译的class文件、必要jar依赖及Excel数据文件,可直观对照源码与编译产物,理解项目结构。整包约19.46MB,下载部署便捷。目前已有305人学习,具有较高参考价值。系统覆盖船舶信息维护、航次调度与统计报表等典型业务模块,界面采用Swing构建,数据层整合MySQL,并涉及FTP工具类,能够帮助开发者快速搭建类似的航运信息管理平台,值得深入阅读与改造。

1. 航运企业的船舶管理系统,为什么十年后还在用 Swing 桌面端

多数人以为航运企业的信息系统早该全面 Web 化,但真实情况是,国内相当一部分航运公司的核心船舶管理系统,至今还是十年前用 Java Swing 写的桌面端。这轮渡口、船队调度、机务科的机器上,每天照常跑着靠离泊计划、船员名册、备件库存。这套系统不依赖浏览器,也不依赖外网,数据库直接落在公司局域网里的 MySQL 上;初始化船队数据靠的是一张data.xls,往总部传航次报表用的是edtftpj.jar这个老牌 FTP 库。它技术栈老,但胜在场景匹配。这篇文章就把这套资源的完整链路拆开讲,适合需要接手这类存量系统的 Java 工程师,也适合准备做信息化改造的航运企业技术人员。

2. BootMS1 启动链路与 Company.Boot.Ship 三层分包结构

拿到压缩包后先不要急着找数据库脚本,这个项目没有复杂的 Maven 依赖树,它是一个典型的 Eclipse 老工程。先把它的静态结构看清楚,再谈启动和改造。

2.1 从 .project、.classpath 到 bin 目录:一次典型的 Eclipse 老工程

解压后你会看到.project.settings/org.eclipse.jdt.core.prefsbinsrclib这些目录。.project是 Eclipse 的工程描述文件,.classpath记录了编译和运行时的类路径;src/Company/Boot/Ship是唯一源码根,bin/Company/Boot/Ship是对应的编译输出目录。也就是说,IDE 里直接以“Existing Projects into Workspace”方式导入,Eclipse 就能通过.project恢复工程配置,编译后自动输出到bin。这类工程在当年的标准做法是把第三方 jar 统一放在lib目录,再在.classpath里逐个引用,而不是交给 Maven 去中央仓库拉取。目录树大致是这样:

国内航运企业船舶管理系统.zip ├── BootMS1.java ├── .project ├── .classpath ├── .settings/org.eclipse.jdt.core.prefs ├── MainFrame.jpg ├── icon.png ├── data.xls ├── lib/ │ ├── mysql-connector-java-5.1.22-bin.jar │ ├── edtftpj.jar │ └── rt.jar ├── src/Company/Boot/Ship/ ├── bin/Company/Boot/Ship/ └── MainFrame.jpg

lib目录里的三个 jar 各有各的身份:mysql-connector-java-5.1.22-bin.jar负责连 MySQL 5.x 数据库;edtftpj.jar是 Enterprisedt 出品的 Java FTP 客户端,用来往总部服务器传航次报表;rt.jar则是老项目里非常典型的误配置。rt.jar是 JDK 自带的运行时类库,正常情况根本不需要手动加进工程,加了反而可能和当前 JRE 的类产生冲突。常见原因是当年开发机装的是自带 JRE 的 Eclipse,有人为了让编译期看到javax.swing包而把它引了进来,实际上正确做法是在 Eclipse 的 Installed JRE 里指定 JDK 版本,而不是引rt.jar。这块到第 5 章还会细讲怎么清理。

2.2 BootMS1 入口与 MainFrame 主窗体的启动时序

BootMS1.java放在工程根目录,从命名习惯看就是整个系统的启动类。Swing 应用有个硬性规则:界面组件必须在事件分发线程(EDT)上创建,否则界面会出现随机性闪烁、布局错乱甚至死锁。老项目里最常见的写法就是在main方法里通过SwingUtilities.invokeLater把主窗体的创建动作丢到 EDT 队列里,主窗体类名对应这里的MainFrame

import javax.swing.SwingUtilities; public class BootMS1 { public static void main(String[] args) { // Swing 组件必须在 EDT 上创建,invokeLater 负责把任务交给事件线程 SwingUtilities.invokeLater(new Runnable() { @Override public void run() { // MainFrame 构造函数里会加载 MainFrame.jpg 背景图 MainFrame frame = new MainFrame(); frame.setVisible(true); } }); } }

这段代码是典型的老 Java 6 / Java 7 风格,匿名内部类而不是 Lambda。逻辑上一共做了三件事:把任务包装成RunnableinvokeLater将它异步提交到 EDT;在 EDT 里实例化MainFrame并显示。frame.setVisible(true)放在最后,因为 Swing 窗体在可见状态下才触发绘制事件,在此之前构造函数里注册的监听器、加载的背景图、初始化的表格模型都已经准备完毕。如果你拿到源码后想改成 Java 8 的 Lambda 风格,把内部类换成() -> { ... }即可,行为完全一致,但要注意工程的 Java 编译级别要同步调高。

2.3 Company.Boot.Ship 分包:老项目的命名空间哲学

Company是公司域名的反写或公司名缩写,Boot对应启动引导层,Ship是核心业务包。这种包结构在当年的桌面项目里很流行:公司名保证唯一性,Boot 层放入口类,Ship 层放船舶档案、航次、报表等业务类。它没有像现在这样严格拆controller/service/dao,而是按业务对象分包。binsrc目录结构完全一致,说明构建方式就是 Eclipse 自动编译,没有经过 Ant 或 Maven。接手这类工程时不要一上来就想改成 Spring Boot,先顺着原有分包习惯跑通启动流程,再逐步抽取公共逻辑,否则一次改动过大容易把业务状态搞乱。

3. 基于 MySQL 5.1.22 驱动的船舶数据访问层实现要点

数据库访问层是这套系统的地基层。所有船舶档案、航次记录、备件台账都落在 MySQL 上,而工程里那个mysql-connector-java-5.1.22-bin.jar直接决定了 JDBC 代码的写法。

3.1 从驱动 jar 推断数据库访问风格

这个 jar 是 2012 年前后的 MySQL JDBC 驱动。这个版本的驱动同时支持 JDBC 3.0 的Class.forName显式加载和 JDBC 4.0 的 SPI 自动注册,但老项目普遍保留Class.forName,因为在当时的开发环境里显式加载更可控,也更容易排查“驱动缺失”的问题。连接工具类常见写法是这样:

package Company.Boot.Ship.db; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DbHelper { private static final String DRIVER = "com.mysql.jdbc.Driver"; private static final String URL = "jdbc:mysql://192.168.1.10:3306/shipping" + "?useUnicode=true&characterEncoding=utf8" + "&autoReconnect=true"; private static Connection conn; public static Connection getConnection() throws SQLException { try { // 老驱动需显式加载,新驱动即使省掉也会通过 SPI 自动注册 Class.forName(DRIVER); } catch (ClassNotFoundException e) { throw new SQLException("lib 目录缺少 mysql-connector-java-5.1.22-bin.jar", e); } if (conn == null || conn.isClosed()) { conn = DriverManager.getConnection(URL, "ship_user", "ship_pwd"); } return conn; } }

核心逻辑在getConnection方法里做了两层判断:conn == null处理首次调用;conn.isClosed()处理 MySQL 端在wait_timeout超时后主动断开连接的情况。这里用了单连接缓存而不是每次新建连接,是因为桌面客户端并发量低,一个客户端同时只有一个主界面在操作,单连接既省资源又能避免连接泄漏。192.168.1.10:3306是内网数据库地址,shipping是库名,ship_usership_pwd按实际环境替换。如果你接手后打算把它改成连接池,需要特别注意isClosed()在这种写法里只能判断“本地 socket 是否关闭”,遇到网络异常时它可能返回 false,真正的探活要靠conn.isValid(3)配合SELECT 1完成。

3.2 连接串里的关键参数与老驱动边界

驱动 jar 版本老,连接串反而简单,但没配置好一样会踩坑。把这几个参数记清楚,就够日常排查了。

参数作用老驱动下的行为与坑
useUnicode=true启用 Unicode 编码传输不加必乱码,是中文项目标配
characterEncoding=utf8指定客户端字符集必须与数据库端character_set_server一致
autoReconnect=true断线后自动重连只对“下次查询前的重连”有效,正在执行的事务不会自动恢复
useSSL=false关闭 SSL 加密通道5.1.22 默认不强制加密,但显式关闭后日志更干净,也避免和总部局域网内网设备的 SSL 中间层冲突

characterEncoding是这里面最容易出问题的。当年很多库在初始化时用utf8,但表的排序规则是gbk_chinese_ci,导致查询结果偶尔出现“问号”。排查时先用SHOW VARIABLES LIKE 'character%'看服务端设置,再和连接串对比,两端统一成utf8最省心。autoReconnect不是万能药,我一般会把它加上,但同时要求业务层在捕获SQLException后重试一次查询,而不是依赖驱动内部的自动重连逻辑。

3.3 没有连接池的年代:单连接的取舍

这套系统里没有连接池框架,这是当年的硬件和并发环境决定的。桌面端几十个用户同时在线,每个客户端一个长连接,对 MySQL 5.1 来说是完全可以承受的。但如果同一台机器上开了多个MainFrame实例,或者你加入了多线程批量上报,单连接就会成为瓶颈。遇到这种情况,建议先给DbHelper加一个synchronized关键字锁住连接创建过程,再用一个LinkedList<Connection>做简易空闲队列,而不是立刻引入几十 MB 的 HikariCP。老系统稳定优先,改动越少越容易回归验证。真正需要动连接层时,再做升级也不迟。

4. MainFrame 主窗体与船舶航次、FTP 上报模块的交互

主窗体是整个系统的门面。MainFrame.jpg是主界面背景图,icon.png是程序图标,这两个图片文件直接参与了 Swing 界面构建。

4.1 MainFrame 如何承载海事业务主界面

用 Swing 做航运管理系统界面,核心套路就是JFrameBorderLayout,中央放JTable,北边放功能按钮和查询条件,南边放状态栏。资源包里的MainFrame.jpg一般是整张界面背景图,常见做法是重写JPanel.paintComponent把图片铺满,再在上面叠放半透明的功能面板。icon.png则通过frame.setIconImage(Toolkit.getDefaultToolkit().getImage("icon.png"))设置,让 Windows 任务栏和标题栏显示企业标识。业务模块和 Swing 控件的对应关系大致如下:

业务模块主要控件数据表
船舶档案管理JTable+ 搜索框t_ship
航次记录JTable+ 选项卡t_voyage
船员名册JTable+JTextArea详情t_crew
FTP 航次上报JButton+ 状态列t_report_log

主窗体中央一般是一个JTabbedPane,各业务页签共享同一个DbHelper连接。这里有个细节:老系统界面上显示船舶状态(在航、锚泊、修理)往往是直接在表格单元格里写中文,没有单独的状态字典表,后期做统计报表时会很不方便。接手后如果要加“在航船舶数”这种统计,优先考虑加一张字典映射,而不是去解析中文字符串。

4.2 edtftpj.jar 在航次上报中的用法

edtftpj.jar是这套系统里负责“对外通信”的组件。航运企业的船队分布在沿海各港口,总部需要每天汇总各船的航次动态、装载量、到港时间。由于早期卫星网络带宽有限,用 FTP 传纯文本报表是最务实的选择。edtftpj 库的 API 很直接:

import com.enterprisedt.net.ftp.FTPClient; public class FtpUploader { public void uploadTripFile(String host, String user, String pwd, String localFile, String remoteFile) throws Exception { FTPClient ftp = new FTPClient(); try { ftp.connect(host, 21); // 连接 FTP 控制端口 ftp.login(user, pwd); // 认证 ftp.type(FTPClient.TYPE_BINARY); // 二进制模式,防止中文乱码 ftp.put(localFile, remoteFile); // 上传本地文件到远端目录 } finally { ftp.quit(); // 主动断开,避免服务端挂起会话 } } }

connect(host, 21)里的 21 是 FTP 默认控制端口;type(FTPClient.TYPE_BINARY)是关键,很多新手漏掉这行,用默认 ASCII 模式传中文文本文件,就会在服务器上看到“锟斤拷”。逻辑上这段代码做了五件事:建立控制连接、登录、切二进制模式、上传、断开。值得注意的坑是ftp.put(localFile, remoteFile)的第二个参数是服务器上的相对路径,它相对于用户登录后的根目录。如果总部服务器要求传到/report/2025/子目录,这里要写成/report/2025/xxx.csv,客户端本身没有目录概念。

4.3 从 JTable 到 FTP:航次数据上报链路

实际的航次上报不是单独点一个按钮,而是“选中航次 -> 生成文件 -> 上传 -> 回写状态”的闭环。界面逻辑常见是这样:

// 主窗体中“上报选中航次”按钮的动作监听 private void doReportTrip(java.awt.event.ActionEvent evt) { int row = tblTrip.getSelectedRow(); // 当前选中的表格行 if (row < 0) { return; // 未选中,直接返回 } String tripNo = (String) tblTrip.getValueAt(row, 0); // 取航次号 File csv = TripReportGenerator.exportByTripNo(tripNo); try { new FtpUploader().uploadTripFile( "202.98.12.34", "report", "******", csv.getAbsolutePath(), "/report/" + tripNo + ".csv"); markTripReported(tripNo); // 成功后把本地状态改为“已上报” } catch (Exception e) { showMessage("上报失败:" + e.getMessage()); } }

这段代码先取选中行的航次号,再调用TripReportGenerator把数据库里的航次记录导出成 CSV,然后走 4.2 的 FTP 上传,最后更新本地上报状态。第四个参数是本地临时文件路径,第五个参数是远端路径。如果上传失败,不能只弹窗提示,最好在markTripReported失败时用红色标注状态列,方便第二天跟踪。老系统一个常见的隐性问题是临时 CSV 文件没有清理机制,时间久了会占用磁盘,建议在finally块里检查文件是否存在并删除。

5. data.xls 初始数据准备与中文编码、时区、rt.jar 的坑

data.xls在资源包里容易被忽略,但它恰恰是部署这套系统的第一道门槛。它里面通常是船队基础资料,包括船名、IMO 号、总吨位、建造年份、船舶状态,格式是 Excel 表格。

5.1 data.xls 是给 DBA 还是给程序用

先看事实:工程lib目录里没有 Apache POI,这就基本排除了“程序启动时读取 xls 初始化数据库”这种方案。没有 POI,Java 侧很难直接解析 Excel 二进制格式。所以data.xls的真实定位是给 DBA 的“数据交付文档”,部署时由 DBA 在 MySQL 里手工执行导入或另存为 CSV 后用 SQL 导入。最省事的做法是让 Excel 另存为 CSV,再通过LOAD DATA LOCAL INFILE批量灌入:

-- 先将 data.xls 在 Excel 中另存为 data.csv,字符集选 UTF-8 LOAD DATA LOCAL INFILE 'C:/shipping/data.csv' INTO TABLE t_ship CHARACTER SET utf8 FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' IGNORE 1 LINES (imo_no, ship_name, owner_name, dwt, build_year, status);

LOCAL INFILE表示文件在客户端本地路径;FIELDS TERMINATED BY ','指定逗号作为字段分隔符;OPTIONALLY ENCLOSED BY '"'处理带逗号的船名字段;IGNORE 1 LINES跳过 CSV 第一行的列名。列清单最后的imo_nostatus必须和 CSV 列顺序一致,顺序反了会把船名灌进吨位字段。执行前先跑一条SELECT COUNT(*) FROM t_ship,导入后数字对上了再覆盖正式库。DATA.xls文件名大小写在 Linux 环境敏感,建议统一改成小写data.csv,避免部署脚本找不到文件。

5.2 中文乱码的三个检查点

data.xls是中国船东的船,船名里“长航”“中海”这类中文是常态,乱码一出现基本绕不开三个位置。

检查点排查手段期望结果
MySQL 服务端字符集SHOW VARIABLES LIKE 'character%'character_set_serverutf8
JDBC 连接串检查DbHelper.URLcharacterEncoding=utf8
导入文件编码Linux 执行file -bi data.csvcharset=utf-8,若是iso-8859-1gb2312需转换

有一个高频坑:Windows 上 Excel 另存为 CSV 时默认用 ANSI 编码,中文 Windows 就是 GBK。拿这份 GBK 的 CSV 直接执行上面的CHARACTER SET utf8导入,中文一定变乱码。我一般会先在 Linux 上跑file -bi data.csv确认编码,是 GBK 就转一道:

iconv -f GBK -t UTF-8 data.csv > data_utf8.csv mv data_utf8.csv data.csv

iconv-f指定源编码,-t指定目标编码。转换后打开文件看前几行,确认“大连”“上海”这类中文正常了再执行 SQL。顺带说一句,MySQL 5.1.22 时代的老库utf8并不是真正的四字节 UTF-8,如果你的船名里有生僻字和 emoji,老驱动是存不进去的,这是utf8mb4出现的原因,真遇到生僻字再考虑升级字符集,船名场景里基本碰不到。

5.3 lib 目录下 rt.jar 的隐患与正确清理方式

rt.jar出现在lib目录是老 Eclipse 工程的经典“污点”。当年的开发者在编译时遇到javax.swing找不到,直接把 JDK 里的rt.jar拷进了工程,这么做短期内编译确实通过了,但埋了雷:JDK 升级后,同一份类在扩展类加载器和应用类加载器各加载一次,会报java.lang.SecurityException: Prohibited package name,尤其在 Windows 多用户环境里切换 JDK 版本时特别容易出现。清理方式很简单:从lib目录删除rt.jar,打开 Eclipse 的Project Properties -> Java Build Path -> Libraries移除引用,然后在Java Compiler里把编译级别设置成你实际安装的 JDK 版本,比如 1.7 或 1.8。删除后重新编译,只要代码里没有直接依赖某个 JDK 内部类,立刻就能通过。

6. 老系统不重构也能做的自检与迁移验证

面对这套系统,最忌讳的就是上来就重构。正确的顺序是:先跑通最小闭环,再做依赖管理改造,最后才是驱动升级。

6.1 先跑通“最小闭环”再谈后续改造

最小闭环指不用完整界面,写一个几行代码的入口验证最核心的三件事:数据库连得上、主窗体打得开、FTP 能上传。FTP 这块可以用极简代码验证网络通不通:

import com.enterprisedt.net.ftp.FTPClient; public class FtpPing { public static void main(String[] args) throws Exception { FTPClient ftp = new FTPClient(); ftp.connect("192.168.1.20"); // 总部 FTP 内网地址 ftp.login("report", "password"); // 如果这里不抛异常,说明控制连接、认证都正常 ftp.quit(); } }

这段代码不做任何文件传输,只验证地址可达和账号有效。运行前先ping 192.168.1.20,再telnet 192.168.1.20 21确认端口通。如果 FTP 直连失败,检查是否开启了被动模式ftp.pasv(true),很多内网防火墙只放行被动模式的数据端口,这一步是排查重点。

6.2 从 lib 管理到 Maven 的关键一步

不重构,但可以把依赖管理理清。老工程直接在.classpath写死 jar 路径,换机器就报ClassNotFoundException。想逐步迁移到 Maven 但又不想改代码,可以使用mvn install-file命令把本地 jar 安装进私有仓库,保持现有import不变:

mvn install:install-file \ -Dfile=lib/mysql-connector-java-5.1.22-bin.jar \ -DgroupId=mysql -DartifactId=mysql-connector-java \ -Dversion=5.1.22 -Dpackaging=jar mvn install:install-file \ -Dfile=lib/edtftpj.jar \ -DgroupId=com.enterprisedt -DartifactId=edtftpj \ -Dversion=2.4.0 -Dpackaging=jar

-Dfile指向本地 jar 路径,-DgroupId-DartifactId是给依赖起一个坐标,-Dversion必须和 jar 实际版本一致。装好后的依赖坐标就变成mysql:mysql-connector-java:5.1.22。这样做的价值在于把隐藏的lib依赖显性化,后续升级驱动时只要改pom.xml里的版本号,不必再手工替换 jar 文件。

6.3 升级连接驱动的参数对比

如果决定从 5.1.22 升级到 8.x 驱动,连接方式和参数都变了,建议先在测试环境做一个对比验证:

项目5.1.228.0.33
Class.forName驱动类com.mysql.jdbc.Drivercom.mysql.cj.jdbc.Driver
时区配置不需要必须serverTimezone=Asia/Shanghai
SSL默认关闭默认开启,需显式useSSL=false
字符编码characterEncoding=utf8同样适用

新驱动的连接串要写成这样:

String url = "jdbc:mysql://192.168.1.10:3306/shipping" + "?useUnicode=true&characterEncoding=utf8" + "&useSSL=false&serverTimezone=Asia/Shanghai" + "&allowPublicKeyRetrieval=true";

serverTimezone是 8.x 的强制项,不配会直接抛Server time zone异常;allowPublicKeyRetrieval=true只有在useSSL=false且使用caching_sha2_password插件时才需要。按 6.1 的最小闭环把新旧两套连接串各跑一遍,对比同一张表的查询结果,确认中文、日期、浮点字段都一致后,再让主窗体切换新驱动。切换时保留旧 jar 不放行,观察一个完整航次的上报流程,出错就.classpath改回去,这是老系统迁移的安全兜底。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 17:21:30

G-Helper 快速上手指南:用一个 exe 接管你的华硕笔记本

G-Helper 快速上手指南&#xff1a;用一个 exe 接管你的华硕笔记本 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops with nearly the same functionality. Works with ROG Zephyrus, Flow, TUF, Strix, Scar, ProArt, Vivobook, Zenbook, E…

作者头像 李华
网站建设 2026/9/11 17:17:19

MidScene.js 自然语言驱动浏览器自动化:新手 30 分钟上手指南

MidScene.js 自然语言驱动浏览器自动化&#xff1a;新手 30 分钟上手指南 【免费下载链接】midscene GUI Agent for E2E Testing 项目地址: https://gitcode.com/GitHub_Trending/mid/midscene MidScene.js 是一个开源的 GUI 自动化 Agent&#xff1a;你用一句中文或英文…

作者头像 李华
网站建设 2026/9/11 17:09:51

系统变卡别瞎猜:ProcessHacker 快速盯住 CPU、内存与磁盘

系统变卡别瞎猜&#xff1a;ProcessHacker 快速盯住 CPU、内存与磁盘 【免费下载链接】systeminformer A free, powerful, multi-purpose tool that helps you monitor system resources, debug software and detect malware. Brought to you by Winsider Seminars & Solut…

作者头像 李华