news 2026/10/12 6:26:11

SpringBoot+Vue航班进出港管理系统:数据库设计、后端链路到Nginx部署实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+Vue航班进出港管理系统:数据库设计、后端链路到Nginx部署实践

前阵子一个在机场信息中心做运维的朋友找我,说他们地服那边还在用Excel登记航班进出港计划,交接班时候电话打个不停,想要一套能管航班计划、能改动态、能查历史的系统。我把自己手头用SpringBoot+Vue+MyBatis+MySQL搭的航班进出港管理系统源码整理了一下发给他,顺便把部署文档也补齐了。这篇文章就是把这套系统的完整设计和落地过程分享出来,从数据库建模、后端接口、前端页面到最终部署,全程都是可复现的,适合想做前后端分离项目实战的Java后端、刚接触Vue的前端新人,以及需要做机场相关业务系统二次开发的同行参考。

1. 为什么航班进出港管理系统值得自己写一套

1.1 进出港业务到底在管什么

很多人一听"航班进出港"就觉得是不是要搞机场塔台那种高大上的东西,其实不是。我们说的进出港管理,指的是民航地面服务环节的数据管理,业务边界很清晰。

进港流程大概是这样的:航班从远方机场起飞、落地、靠桥或者停远机位、旅客下机、行李分拣。离港流程则包括旅客值机、托运行李、登机、关舱门、起飞。整个业务流程中最核心的东西就是"计划"和"动态"两个词。计划航班是预先排好的时刻表,比如每天有哪些航班几点进港几点离港;动态则是实际运行中不断变化的数据,比如航班延误了、换登机口了、实际落地时间提前了。

所以系统要解决的三个核心痛点很清楚:计划数据统一维护、动态数据快速更新、历史数据方便追溯。这三个痛点用Excel完全解决不了,尤其是航班状态一变,所有人都要知道,人工通知根本忙不过来。

1.2 功能边界与角色权限

我在这套系统里没有盲目去碰专业的FIDS(航班信息显示系统)大屏,因为那套东西涉及机场核心数据源和外部接口协议,个人项目做不了也做不深。作为一个可落地的管理系统,我保留了下面这些功能:

功能模块说明
航班计划管理录入、修改、删除航班进离港计划
航班动态更新修改实际起飞、实际落地时间,变更状态
状态流转控制计划、预计、到达、起飞、延误、取消
航班查询统计按航班号、起降时间、状态、航司多条件检索
基础数据维护航司、机场、机位、登机口数据
用户与权限登录认证、不同角色不同操作范围
操作日志记录谁在什么时间改了什么字段

角色权限这块我分了三类:系统管理员负责用户和数据字典维护;航调员负责航班计划和动态的操作;地服或查询人员主要就是查询和统计,没有改动权限。加上简单的菜单权限控制,不搞太复杂的RBAC,够用就好。

2. 技术选型复盘:SpringBoot、Vue、MyBatis、MySQL为什么这么搭

2.1 前后端分离的收益和代价

前后端分离概念现在听着很普通,但真上手过的人都知道,它不只是一个技术方案,更是一种协作方式的改变。在旧式开发里,一个项目使用Thymeleaf或者JSP,后端模板里混着大量HTML和JavaScript,改个页面经常要重启后端服务,前后端耦合严重。

前后端分离之后,后端只需要暴露JSON接口,前端独立开发和部署,Vue项目跑在Node服务里开发调试,最后构建成静态文件扔给Nginx。两边的开发节奏完全独立,前端可以Mock数据先做页面,后端也可以拿Postman直接调试接口,效率提升是非常明显的。

代价当然也有。跨域问题、接口文档维护、部署时反向代理配置,这些乱七八糟的事情只有在真正分离部署的时候才会遇到,但学会了就是自己的经验。这套系统里我用最标准的方案把这一套完整串了一遍,新手跑完能少走很多弯路。

2.2 后端选型:SpringBoot的简化能力和MyBatis的SQL可控性

后端我坚持用SpringBoot,理由很简单:生态成熟、配置极少、内嵌Tomcat,一个java -jar就能跑起来。这套系统里为了避免版本太新的坑,用的是SpringBoot 2.7.x,搭配JDK 8或者JDK 11都很稳。新学的人没必要一上来就追Spring Boot 3.x,很多老资料和插件版本兼容问题会把你劝退。

持久层我选了MyBatis而不是JPA或者Spring Data JPA,主要是因为航班管理这种业务里,查询条件非常灵活,航班号、起降时间段、状态、航司ID经常组合出现,SQL的动态拼接在XML里维护起来最直观。JPA虽然写简单CRUD很爽,但一旦接上复杂统计,为了调优想控制SQL就非常别扭。

MyBatis的XML映射能力强,SQL是DBA和开发都能看明白的东西,出了问题可以直接把日志里的SQL拿出去执行,排查效率很高。当然MyBatis-Plus也可以用,它是在MyBatis基础上做的增强,我有时候写单表CRUD也会用,但核心查询我更喜欢自己写XML,方便控制索引和where条件的顺序。

2.3 前端选型:Vue 2 + Element UI,务实比追新重要

前端我选的是Vue 2.7 + Element UI。我知道现在已经Vue 3了,Element Plus也出来了,但我还是要说Vue 2 + Element UI那套是真的稳定,中文文档全,表格组件开箱即用,教程和问题答案随便一搜就是一堆,对这套系统来说足够。

Vue的核心价值是组件化和数据驱动。一个航班列表页面,无非就是查询表单、表格、分页、状态Tag这几个组件拼起来,数据从axios拿过来,绑定到data上,页面自动刷新,代码量比用jQuery手写DOM操作少一大半。

如果非要问Vue 2和Vue 3怎么选,我的建议是:年纪大一点的项目看Vue 2省心,新项目从零开始而且团队熟悉的话可以直接上Vue 3,但对应要选Element Plus。本文所有前端代码我用的是Vue 2语法,因为这套源码本身也是基于Vue 2的,跑起来最省事。

2.4 为什么不直接套若依这类脚手架

若依框架在前后端分离的圈子里非常流行,功能齐全,有代码生成器,光看GitHub星星就知道很多人用它做快速开发。但我做这套系统时特意没有直接套若依,原因是:若依封装的底层太多了,数据权限、多租户、代码生成一套下来,初学者看到的是"怎么用这个框架",而不是"这个业务怎么设计"。

自己从零搭一遍,哪怕是一套简单的用户登录、航班CRUD、状态流转,你能把SpringBoot的Bean装载、MyBatis的Mapper代理、Vue的组件通信、路由拦截全都摸一遍。以后真去了用若依的公司,再上手反而很快。当然如果你是为了赶工期给企业交付,用若依没毛病,这是工具选型问题,不是对错问题。

3. 数据库建模:航班计划、航班动态、操作日志三张核心表

3.1 先梳理业务对象,再谈建表

动手建表之前,我习惯先把要管理的东西列一遍:航空公司、机场、航班、机位、登机口、用户、操作日志。航班不必单独拆成"计划表"和"动态表",因为大多数中小型机场地服系统,一个航班一条记录,计划和时间字段都放在同一行,更新动态时直接改对应的列就行,不需要做复杂的快照。

航司、机场、机位这类属于基础数据,单独建表维护。航班表用外键逻辑关联过去,但物理上不建强外键约束,因为这种业务系统要保证写入性能,关联关系通过SQL join维护,删除靠应用层控制,真的没必要让数据库去背这个约束。

3.2 核心表结构:flight_info

航班表是整套系统的核心,我实际使用的表结构大致如下:

CREATE TABLE `flight_info` ( `flight_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '航班主键', `flight_no` VARCHAR(20) NOT NULL COMMENT '航班号', `airline_id` BIGINT DEFAULT NULL COMMENT '航司ID', `direction` TINYINT NOT NULL DEFAULT '1' COMMENT '方向 0离港 1进港', `from_airport` VARCHAR(20) DEFAULT NULL COMMENT '出发机场三字码', `to_airport` VARCHAR(20) DEFAULT NULL COMMENT '到达机场三字码', `plan_takeoff_time` DATETIME DEFAULT NULL COMMENT '计划起飞时间', `plan_landing_time` DATETIME DEFAULT NULL COMMENT '计划落地时间', `actual_takeoff_time` DATETIME DEFAULT NULL COMMENT '实际起飞时间', `actual_landing_time` DATETIME DEFAULT NULL COMMENT '实际落地时间', `status` TINYINT NOT NULL DEFAULT '0' COMMENT '状态 0计划 1预计 2到达/起飞 3延误 4取消', `gate_no` VARCHAR(10) DEFAULT NULL COMMENT '登机口', `stand_no` VARCHAR(10) DEFAULT NULL COMMENT '机位', `passenger_count` INT DEFAULT '0' COMMENT '旅客人数', `remark` VARCHAR(255) DEFAULT NULL COMMENT '备注', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`flight_id`), KEY `idx_flight_no` (`flight_no`), KEY `idx_status` (`status`), KEY `idx_plan_landing_time` (`plan_landing_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='航班进出港计划与动态表';

这里我给了三个索引:status单独建索引是因为查询列表时经常按状态筛选;plan_landing_time建索引是因为统计报表周按月按天查落地航班量;flight_no建索引是为了精确查询。索引不是越多越好,写频繁的字段加太多索引会影响插入更新性能,所以时间字段我只给计划落地时间加了索引,其他字段不加。

3.3 状态字段为什么用整数而不是直接存中文

我看到很多初学的项目里会直接搞一个status varchar字段,存"计划""到达""取消"这种中文,看起来直观,查起来也不难。但实际用一段时间就会发现问题:统计的时候要按照状态分组,中文值稍微写错一个字就算两组,而且业务上要加一个枚举值的时候,历史数据怎么迁移非常尴尬。

所以我在代码里定义了一个状态枚举,数据库只用tinyint存数字,显示的时候由前端字典翻译成中文标签。这样状态流转可以写在Java枚举里控制,数据库只是一块干净的存储层。

3.4 时间字段的坑:datetime、LocalDateTime、serverTimezone

航班系统里时间字段是最容易出问题的地方。我在第一版里直接用String接MySQL时间,结果排序、区间查询各种函数都要转来转去,后来痛定思痛,全部改成MySQL的datetime配合Java的LocalDateTime。

有一个之前折腾了很久的坑是时区问题。应用服务器和MySQL服务器如果不在一个时区,或者JDBC连接串上忘了写serverTimezone,查询出来的时间可能整体偏移8个小时。尤其是SpringBoot 2.x以后,强制要求连接串指定serverTimezone,我后来统一加上了:

spring: datasource: url: jdbc:mysql://localhost:3306/flight_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false

另外提醒一句,尽量用datetime而不要用timestamp。timestamp虽然会自动处理时区转换,但它的有效范围到2038年,而且会受到数据库全局时区设置影响,在分布式环境下更容易扯皮。datetime就是存储的字面时间,配合应用层统一设定为北京时间,简单直接。

4. 后端链路实现:查询、状态流转、鉴权与MyBatis实践

4.1 标准三层:Controller-Service-Mapper

后端整体上是标准的Controller-Service-Mapper三层。Controller只做参数接收和返回结果封装,Service放业务逻辑,Mapper只管SQL。以航班分页查询为例,Controller看起来是这样:

@RestController @RequestMapping("/api/flight") public class FlightController { @Resource private FlightService flightService; @GetMapping("/page") public Result<PageInfo<FlightVO>> page( @RequestParam(defaultValue = "1") int pageNum, @RequestParam(defaultValue = "10") int pageSize, FlightQuery query) { return Result.success(flightService.queryPage(pageNum, pageSize, query)); } }

Service里核心代码就三步:组装查询条件、开启分页、执行查询。

public PageInfo<FlightVO> queryPage(int pageNum, int pageSize, FlightQuery query) { PageHelper.startPage(pageNum, pageSize); List<FlightVO> list = flightMapper.selectFlightPage(query); return new PageInfo<>(list); }

Mapper接口只定义一个方法,XML里写动态SQL:

<select id="selectFlightPage" resultType="com.example.flight.vo.FlightVO"> select f.*, a.airline_name from flight_info f left join airline a on f.airline_id = a.airline_id <where> <if test="query.flightNo != null and query.flightNo != ''"> and f.flight_no like concat('%', #{query.flightNo}, '%') </if> <if test="query.status != null"> and f.status = #{query.status} </if> <if test="query.startTime != null"> and f.plan_landing_time >= #{query.startTime} </if> <if test="query.endTime != null"> and f.plan_landing_time &lt;= #{query.endTime} </if> </where> order by f.plan_landing_time desc </select>

动态SQL是MyBatis最强大的地方,if标签加上where标签可以自动处理多余的and,怎么拼都不会语法错。

4.2 PageHelper分页插件的配置和两个易踩的坑

分页功能直接引入PageHelper这个插件,GAV坐标不多说,SpringBoot场景下加一个starter依赖就行,然后配置里设两行:

pagehelper: helper-dialect: mysql reasonable: true support-methods-arguments: true

实际使用中我印象最深的坑有两个。

第一个坑是startPage之后必须紧跟一条Mapper查询,中间绝对不要再查任何东西。PageHelper的实现原理是把分页参数塞到了ThreadLocal里,下一次查询会清掉,如果你在startPage和业务查询之间执行了别的SQL,那个SQL就会被错误地分页,甚至报count拿不到的异常。

第二个坑是reasonable参数。加上它之后,pageNum小于1时自动改为1,超过最大页时就查最后一页的数据,这在真实系统里非常实用,不然前端传一个pageNum=999,SQL会查出空列表然后用户一脸懵。

4.3 航班状态流转:用简单状态机而不是到处if

航班状态有六种:计划、预计、到达、起飞、延误、取消。进港航班的状态链路和离港航班不完全一样:进港航班是计划到预计再到到达,离港航班是计划到预计再到起飞。如果每个接口里都写if判断,时间长了状态组合一多,代码会乱得没法维护。

我采用的方式是在Java枚举里定义一个规范的状态流转表:

public enum FlightStatus { PLAN(0, "计划", Arrays.asList(1, 3, 4)), ESTIMATE(1, "预计", Arrays.asList(2, 3, 4)), ARRIVED(2, "到达", Collections.emptyList()), DEPARTED(2, "起飞", Collections.emptyList()), DELAYED(3, "延误", Arrays.asList(1, 2, 4)), CANCELED(4, "取消", Collections.emptyList()); }

每次更新状态时,校验当前状态的允许去向里是否包含目标状态,不合法直接抛业务异常。比如到达状态不允许再改成预计,取消了也不能再回到计划。这套校验放在Service层,比在Controller里判断严谨得多。需要说明的是我这里为了表格简洁把到达和起飞的存储值都定义成了2,真正实现时进港和离港是分别用两个子枚举来处理的,避免语义混淆。

4.4 JWT登录鉴权与统一返回结构

系统里的登录认证我用了JWT方案,没引入Spring Security那套重框架,因为业务不复杂,一个拦截器完全能搞定。登录接口验证用户名密码后签发一个token,Redis存不存都行,我这里直接用了无状态的JWT,token里带用户的id和角色,过期时间设8小时。

拦截器需要放行/login接口,然后对/api下的其他接口统一校验token。校验通过后把userId放进ThreadLocal或者Request属性,方便后续写操作日志时取当前操作人。

所有接口返回我都统一包了一层Result类:

public class Result<T> { private int code; private String msg; private T data; }

code为200时表示成功,其他code是各种业务异常码。这样前端axios拦截器只需要判断code,不用每次处理HTTP状态码,整体交互很清爽。再加上全局异常处理器,把业务异常转成Result返回,未捕获的异常统一记日志,不会把异常堆栈直接抛给前端。

4.5 MyBatis二级缓存的适用边界

MyBatis的二级缓存是很多面试题里容易被吹上天的东西,但我在这个项目里默认是关闭的。原因很简单,航班动态数据是强实时数据,一条记录的变更频率非常快,二级缓存是namespace级别的,只要这个Mapper里发生更新操作,整个缓存就会失效,命中率非常不稳定。

更要注意的是多表join查询。如果两个表分属不同Mapper,其中一个表更新了,另一个Mapper的缓存并不会自动清掉,查出来的数据可能就是脏的。我在这个系统里唯一开了二级缓存的地方是字典表和航空公司表这种几乎不变的基础数据。如果你要开缓存,记住一个原则:只给低频更新、高频只读的单一表开,多表查询一律不走二级缓存。

5. 前端页面实现:从axios封装到航班动态列表

5.1 Vue项目初始化和目录结构

前端工程用Vue CLI创建,项目结构非常标准:

src ├── api # 接口定义 │ └── flight.js ├── assets ├── components # 公用组件 ├── router # 路由 │ └── index.js ├── store # vuex ├── utils │ └── request.js # axios封装 ├── views # 页面 │ ├── FlightList.vue │ ├── Login.vue │ └── Dashboard.vue ├── App.vue └── main.js

依赖安装就直接三条命令:vue add router、npm install element-ui、npm install axios。页面组件用Element UI的el-table、el-form、el-pagination拼起来,半小时就能出第一版。

5.2 axios请求封装与token注入

axios必须封装,不封装的话每个页面都要写错误处理,代码会重复到崩溃。我通常会在utils/request.js里创建一个axios实例:

import axios from 'axios' import { Message } from 'element-ui' const service = axios.create({ baseURL: process.env.VUE_APP_BASE_URL || '/api', timeout: 10000 }) service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config }) service.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { Message.error(res.msg || '请求失败') return Promise.reject(new Error(res.msg)) } return res }, error => { Message.error(error.message || '网络异常') return Promise.reject(error) } ) export default service

baseURL用环境变量区分开发和生产,开发环境走vue.config.js里的代理,生产环境走Nginx反向代理,前端代码不用改一行。

5.3 航班列表页与路由参数

FlightList.vue的核心内容是:顶部一个查询表单,中间一张表格,底部一个分页器。查询表单包含航班号输入框、状态选择器、日期范围选择器,点查询时把参数对象传给后端接口。表格里的状态列用el-tag根据数字映射成不同颜色标签,这样扫一眼就知道哪些航班延误了。

路由参数方面我遇到过一个具体需求:航班列表页点详情进去,操作完返回列表时,查询条件要原样保留。实现方式很简单,跳详情页之前把当前查询参数通过query传过去,返回列表时在created里再读出来重新请求:

this.$router.push({ path: '/flight/detail', query: { id: row.flightId, pageNum: this.pageNum, status: this.form.status } })

返回时读取this.$route.query,把值回填进表单和分页参数。这个小细节很多项目里不做,用户用起来就非常烦,每次返回都要重新输入条件。

5.4 devtools调试和vue.config.js代理配置

开发环境调试时,vue-devtools插件是必须装的,浏览器里能直接看到Vue组件的data和vuex状态,排查数据绑定问题效率翻倍。我不会去手改代码里的状态,直接把这个插件打开就能看到是哪一层的属性没更新,对刚学Vue的人帮助最大。

开发跨域的解决方法是在项目根目录的vue.config.js里配devServer代理:

module.exports = { devServer: { port: 3000, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } }

开发环境前端跑3000端口,后端跑8080端口,代理解决跨域,后端不需要任何CORS配置。我在本地调试、前后端联调时都是用这种方案。

6. 部署教程:从源码到可运行系统的完整步骤

6.1 第一步:MySQL准备和初始化数据

部署的第一步是准备好MySQL。Windows环境下最简单的办法是去官网下载MySQL Community Server的ZIP包,解压后以管理员身份打开命令行:

mysqld --initialize-insecure mysqld --install net start mysql

--initialize-insecure是初始化数据目录,不设置root初始密码,方便第一步本地登录。登录后立刻执行:

ALTER USER 'root'@'localhost' IDENTIFIED BY '你的密码'; CREATE DATABASE flight_db DEFAULT CHARACTER SET utf8mb4;

然后在系统里面执行我提供的init.sql,它会自动建表并插入航司、用户等基础数据。如果使用Navicat这类图形化客户端,直接新建数据库后运行SQL脚本就行。注意脚本里的utf8mb4不能换成utf8,因为航班备注里可能有生僻字或者emoji,utf8在MySQL里实际上是utf8mb3,存在字符装不下的风险。

6.2 第二步:后端打包,Jar直跑还是外置Tomcat

SpringBoot后端默认打成可执行Jar包,因为内嵌了Tomcat,所以部署非常省事。在项目根目录执行:

mvn clean package -DskipTests

然后在target目录下拿到jar包,直接启动:

java -jar flight-system.jar --server.port=8080

数据库连接配置我建议通过环境变量传,而不是直接把明文密码写在application.yml里,至少做到:

export DB_PASSWORD=你的数据库密码 java -jar flight-system.jar --spring.datasource.password=$DB_PASSWORD

如果你想部署到外置Tomcat,也就是网上常说的tomcat部署前后端分离项目的方式,那需要改动三点:pom.xml里打包方式改成war,spring-boot-starter-tomcat依赖的scope改成provided,启动类继承SpringBootServletInitializer并重写configure方法。打包后把war丢到Tomcat的webapps目录,启动Tomcat即可。但说实话,如果不是公司运维流程硬性要求,我更推荐Jar直跑,原因就一个:内嵌Tomcat版本的升级完全由应用自己控制,不会出现外置Tomcat版本跟项目不兼容的破事。

6.3 第三步:前端打包与Nginx部署

前端部署过程我梳理成三步:安装依赖、构建产物、扔给Nginx。

先在前端工程目录执行:

npm install npm run build

构建完成后会生成dist目录,里面是纯静态文件。把整个dist目录复制到服务器,比如放在/opt/flight-web目录。然后Nginx配置里指定静态根目录,同时把/api的请求反向代理到后端服务。

这里要解释一个关键逻辑:为什么生产环境不直接用CORS而要用Nginx反向代理。CORS虽然能解决跨域,但它让浏览器直接暴露到后端服务,后端的内网IP和端口很容易被前端源码或开发者工具抓包看到。Nginx作为反向代理,对外只有一个80端口,后端的真实地址完全隐藏,同时还能做静态资源缓存和后续的负载均衡,一举多得。

6.4 一份可以直接用的nginx.conf示例

这是我部署这套系统时用的精简配置,去掉注释可以直接抄:

server { listen 80; server_name your-domain.com; root /opt/flight-web; index index.html; # 前端路由history模式,找不到文件时回退到index.html location / { try_files $uri $uri/ /index.html; } # 后端接口反向代理 location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 静态资源缓存 location /static/ { expires 7d; } }

配完后执行nginx -t检查语法,再用nginx -s reload热加载配置。整个系统就可以通过域名访问了。

7. 实际踩坑记录与后续扩展建议

7.1 跨域问题的三种解法到底怎么选

这一路下来我前前后后用三种方式解决过跨域问题。本地开发用vue.config.js代理最方便;后端接口直接调用场景可以用@CrossOrigin注解或者全局CorsFilter;生产环境用Nginx反向代理。

有一次我在本地调试时图省事,后端加了CorsFilter,结果前端请求带上了自定义Header Authorization,触发了一次OPTIONS预检请求。虽然也能跑,但每次请求多一次往返,而且接口调试日志里一堆OPTIONS记录,看着很烦。后来我把后端CorsFilter全部去掉,本地靠webpack代理,生产靠Nginx,逻辑就清爽了。这个经验写在这里,希望能帮后来人少走弯路,不要图省事在SpringBoot代码里随手写跨域放行,尤其是带token的请求,生产环境还开放跨域就等于让人扫你接口。

7.2 到港时间差八个小时的排查过程

上线后遇到过一个特别诡异的问题:航班数据从管理后台看全部正常,但某个查询接口返回到前端的时间总是慢了8小时。我一开始怀疑是前端时区转换问题,用devtools看了network面板,发现接口返回的JSON本身就是慢8小时,顿时放心了一大半。

接着查后端日志,发现SQL查出来之后在实体里的时间就已经偏了。再查MySQL连接串,发现其中一台测试环境的配置里漏了serverTimezone=Asia/Shanghai。MySQL 5.7默认时区是系统时区,服务器是UTC,所以从datetime取出来的时间传到Java里就被当成了本地时间,整体偏移8小时。处理办法就是在连接串里显式指定serverTimezone,然后所有环境统一。这个坑我现在每建一个新环境都会先检查,成了肌肉记忆。

7.3 MySQL部署时常见的连接问题

部署过程中最容易卡住的其实不是代码,而是MySQL本身的启动和连接问题。我在Windows上遇到过mysqld服务启动不了,一查日志是data目录权限不对,用管理员身份重新初始化解决。

还有一个网上搜烂了的报错:ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock'。这个在Linux下出现通常是MySQL服务还没启动,或者my.cnf里socket路径配置和客户端不一致。先检查service mysqld status,没启动先启动;启动了的就去看/etc/my.cnf里的socket路径,对不上就改。不要一上来就重装MySQL,那是最粗暴也是最浪费时间的方案。

7.4 这套代码可以往哪些方向继续扩展

如果你拿到源码想继续深入,我建议按需求优先级做几件事:接入消息队列让航班动态通过MQ推送,甚至对接机场其他系统;做一套航班实时状态看板,滚动展示进离港动态和延误统计;文件上传这块目前是简单实现,可以引入MinIO做附件存储,比如放舱单、放客货单的扫描件;安全方面给上传接口加一个全局过滤器,做一遍富文本和PDF上传时的XSS攻击防护。

Jenkins自动化部署也是一个很值得探索的方向,尤其你要在Windows服务器上频繁发布前后端分离项目时,写一套流水线自动编译后端、构建前端、再推送Nginx目录,能大大减少手工操作。不过这些都是后话,先把这套基础系统跑通,再看你所在团队最痛的点在哪。

最后说点实际体会

这套航班进出港管理系统我从数据库设计到前端页面再到部署脚本,前后花了两周业余时间。坦白讲,系统本身不大,但把一个业务闭环完整地落到前后端分离架构里,过程中遇到的问题和积累的经验才是最有价值的。你在跑这套项目时如果遇到什么奇怪问题,大概率就是时区、端口、连接串、Nginx代理这四个方向里的某一个,按我这个排查顺序一步步来,基本都能解决。最后再分享一个小技巧:启动SpringBoot时用banner生成器给自己的项目搞个自定义启动图案,虽然没什么实际功能,但对维护开源项目的人来说,仪式感真的能提升心情,坚持维护的劲头也更足。

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

QLVideo:为 macOS Finder 补上视频缩略图与 QuickLook 预览

简介&#xff1a;QLVideo 是一款面向 macOS 用户的 QuickLook 增强插件&#xff0c;采用 Objective-C 编写&#xff0c;主要解决系统 Finder 与 Spotlight 对非原生视频格式支持不足的问题。macOS 10.9 及以上版本仅能识别有限的 MPEG 容器与编解码器&#xff0c;而该插件补充了…

作者头像 李华
网站建设 2026/10/12 6:25:20

Python 代码加密防逆向加固:从混淆到编译的完整实践

1. 引言 Python 因其简洁易读的语法而广受欢迎&#xff0c;但这份「易读性」在商业软件分发时却成了痛点&#xff1a;源码以 .py 明文形式交付&#xff0c;竞争对手拿到后几乎可以零成本阅读、复制甚至篡改。无论是保护核心算法、商业逻辑&#xff0c;还是防止脚本被恶意篡改&a…

作者头像 李华
网站建设 2026/10/12 6:25:01

对着屏幕骂脏话竟被直接拉黑,这家科技巨头把防虐待写进了新规

对着屏幕骂脏话竟被直接拉黑&#xff0c;这家科技巨头把防虐待写进了新规 如果你在和人工智能聊天时&#xff0c;因为得到一个愚蠢的答案而大发雷霆&#xff0c;甚至在对话框里连发十句恶毒的咒骂&#xff0c;会发生什么&#xff1f;过去&#xff0c;屏幕对面的程序只会机械地回…

作者头像 李华
网站建设 2026/10/12 6:24:30

TortoiseSVN实战指南:从安装避坑到分支合并与钩子配置

简介&#xff1a;面向 Windows 开发者的 SVN 客户端工具资料包&#xff0c;围绕小乌龟 TortoiseSVN 的实际使用场景展开&#xff0c;适合刚接触版本控制的新手&#xff0c;也适合需要快速配置仓库和规范提交流程的团队开发人员。资料从安装与认证配置讲起&#xff0c;先后梳理检…

作者头像 李华
网站建设 2026/10/12 6:23:31

虚拟知识图谱(Virtual KG):架构、原理与企业落地实践

一、什么是虚拟知识图谱 虚拟知识图谱&#xff08;Virtual Knowledge Graph&#xff0c;简称 VKG&#xff09;是不迁移、不复制原始数据&#xff0c;通过语义映射、本体定义与查询重写技术&#xff0c;将分散在异构数据源的结构化、半结构化数据&#xff0c;实时虚拟统一为标准…

作者头像 李华