ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot+Vue全栈实战:航班进出港管理系统设计与部署详解

SpringBoot+Vue全栈实战:航班进出港管理系统设计与部署详解 好的我在整理手头这套源码的时候脑子里不断浮现出当年自己在航司项目中踩过的各种坑。这套基于SpringBootVue的航班进出港管理系统虽然是个典型的全栈教学项目但从数据库设计、接口规划到前端交互几乎把企业级开发中常用的那套东西都过了一遍。如果你正在找一套能真正跑起来、能写进简历、能应付答辩的系统源码这篇拆解应该能帮你省下大量摸索的时间。说实话“航班进出港管理”这个概念乍一听挺唬人但实际上拆开来看它就是一套带实时状态流转的CRUD系统只不过业务对象从普通的“商品”“订单”换成了“航班”并且多了一些统计看板和状态约束。做这种系统最忌讳一上来就写代码先花半天把业务梳理清楚后面几天会顺畅得多。我会顺着这个思路把项目从技术选型、数据库设计、后端实现到前端页面、部署排查完整讲一遍。1. 项目定位与技术选型1.1 为什么是SpringBootVue这套组合这套技术栈现在几乎成了中小型管理系统的事实标准原因很实际SpringBoot把SSM时代那些繁琐的XML配置全部干掉内嵌Tomcat一个main方法就能启动服务特别适合那种需要快速交付、快速迭代的管理后台。而Vue作为前端框架响应式数据绑定和组件化开发能把页面的交互逻辑理得很清晰尤其是航班列表这种需要频繁筛选、排序、状态切换的页面用Vue写起来比传统的jQuery省太多事。有人可能会问为什么不用更流行的SpringCloud微服务或者前端用React我的观点是这个项目的复杂度决定了它不需要微服务那一套注册中心、网关、配置中心的重量级架构单机单体应用加前后端分离已经足够。微服务是要解决“复杂系统拆分”的问题而不是为了炫技。同样Vue上手曲线比React平缓中文资料多对于学生或者刚转行的开发者来说踩坑成本低很多。这套组合最大的优势是“生态成熟、资料全、面试能聊的东西多”这才是选型的核心逻辑。1.2 核心需求拆解航班进出港到底管什么如果只做增删改查这个项目是没有灵魂的。一份合格的进出港管理系统至少要覆盖这几个业务场景航班基础信息的维护航班号、航空公司、机型、起降机场、计划起降时间、值机柜台、登机口等。进港与出港的分类管理进港航班重点看“到达时间、停靠廊桥、行李转盘”出港航班重点看“值机截止时间、登机状态、起飞状态”。航班动态状态流转从计划、值机、登机、起飞到落地状态的每一步变更都要有时间记录而且不是随便哪个状态都能乱跳的。旅客数据与统计看板当天进出港航班总数、旅客吞吐量、准点率、延误航班列表这些数据要能在首页直观展示。多条件检索按航班号、起降日期、机场、状态等条件组合查询这种查询在航班量大的时候对SQL优化有一定要求。把这些需求列清楚你会发现这个系统本质上是一个“状态机CRUD统计报表”的组合体。后面所有的表结构设计、接口划分、页面组织都是围绕着这几个核心需求展开的。很多同学做项目失败不是代码写不出来而是需求没想明白就开始建表结果做到一半发现表结构不对推倒重来特别浪费时间。1.3 系统整体功能模块规划基于上面的需求系统可以拆成几个大模块每个模块对应一组页面和接口功能模块核心功能点前端页面用户认证登录、登出、密码加密登录页航班管理航班的增删改查、逻辑删除航班列表页进出港管理按进港/出港分类查询、状态流转进出港看板页动态监控当日航班状态实时更新、时间轴航班动态页统计报表航班量趋势、准点率、吞吐量首页数据看板系统管理用户管理、基础数据字典用户管理页这里不得不提一个经验模块划分不要贪多。很多同学喜欢把“角色权限”“操作日志”“数据字典”这些都塞进去结果每个模块都做得半吊子答辩的时候一问细节就露馅。这个系统把核心放在航班管理、状态流转和统计看板上把这些做深做透远比堆一堆凑数的功能要好。2. 数据库设计与接口规划整个项目最容易翻车的环节2.1 航班信息表到底怎么设计数据库设计这个环节直接决定了后面开发是“顺畅”还是“到处打补丁”。我见过太多人把航班表设计成一个大宽表所有字段全塞进去结果查询的时候各种OR条件索引完全失效性能惨不忍睹。这套系统里我用了“主表状态扩展”的思路核心表叫flight_info承载的是航班的所有静态和动态信息。CREATE TABLE flight_info ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键ID, flight_no VARCHAR(20) NOT NULL COMMENT 航班号如CA1831, flight_type TINYINT NOT NULL COMMENT 航班类型1-出港2-进港, airline VARCHAR(50) DEFAULT NULL COMMENT 航空公司, aircraft_type VARCHAR(20) DEFAULT NULL COMMENT 机型如A320/B738, origin_airport VARCHAR(50) DEFAULT NULL COMMENT 起飞机场, arrival_airport VARCHAR(50) DEFAULT NULL COMMENT 到达机场, scheduled_departure DATETIME DEFAULT NULL COMMENT 计划起飞时间, scheduled_arrival DATETIME DEFAULT NULL COMMENT 计划到达时间, actual_departure DATETIME DEFAULT NULL COMMENT 实际起飞时间, actual_arrival DATETIME DEFAULT NULL COMMENT 实际到达时间, gate_no VARCHAR(20) DEFAULT NULL COMMENT 登机口, checkin_counter VARCHAR(20) DEFAULT NULL COMMENT 值机柜台, flight_status TINYINT NOT NULL DEFAULT 0 COMMENT 航班状态0-计划1-值机2-登机3-起飞4-落地5-取消, passenger_count INT DEFAULT 0 COMMENT 旅客人数, remark VARCHAR(255) DEFAULT NULL COMMENT 备注延误原因等, deleted TINYINT NOT NULL DEFAULT 0 COMMENT 逻辑删除0-正常1-删除, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), KEY idx_flight_no (flight_no), KEY idx_flight_type (flight_type), KEY idx_flight_status (flight_status), KEY idx_scheduled_departure (scheduled_departure), KEY idx_scheduled_arrival (scheduled_arrival) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT航班信息表;几个容易忽略的设计细节deleted字段做逻辑删除而不是物理DELETE。这个在管理系统里几乎是标配因为航班数据有审计价值而且物理删除会破坏关联统计。四个时间字段分开存计划起飞、计划到达、实际起飞、实际到达。很多人图省事只存“起飞时间”和“到达时间”但一旦做准点率统计就会发现完全没法算因为准时率实际时间与计划时间的差值没有计划时间就没有统计依据。索引不是越多越好但要覆盖高频查询路径。航班号、类型、状态、计划时间是查询和统计最常见的选择条件建上这4个索引基本够了。注意idx_scheduled_departure和idx_scheduled_arrival是给范围查询用的如果你们项目里主要按日起飞查询前者尤其关键。2.2 状态流转如何用代码约束“航班不能乱跳状态”航班的生命周期是有序的计划-值机-登机-起飞-落地中间可以插入“取消”作为终态。如果用户在前端把一个已经落地的航班改成“计划”这就是数据事故。所以后端必须要有一层状态机校验逻辑我写了一个非常轻量的校验器private static final MapInteger, SetInteger STATUS_TRANSITIONS new HashMap(); static { STATUS_TRANSITIONS.put(0, new HashSet(Arrays.asList(1, 5))); // 计划可转为值机或取消 STATUS_TRANSITIONS.put(1, new HashSet(Arrays.asList(2, 5))); // 值机可转为登机或取消 STATUS_TRANSITIONS.put(2, new HashSet(Arrays.asList(3, 5))); // 登机可转为起飞或取消 STATUS_TRANSITIONS.put(3, new HashSet(Arrays.asList(4))); // 起飞只能落地 STATUS_TRANSITIONS.put(4, Collections.emptySet()); // 落地是终态 STATUS_TRANSITIONS.put(5, Collections.emptySet()); // 取消是终态 } public void validateTransition(Integer currentStatus, Integer targetStatus) { SetInteger allowed STATUS_TRANSITIONS.get(currentStatus); if (allowed null || !allowed.contains(targetStatus)) { throw new BusinessException(非法的状态流转从 currentStatus 到 targetStatus); } }这个代码的作用不仅仅是“校验”更重要的是把业务规则显式地写出来而不是散落在各个Service方法里。如果后续业务扩展了“返航”“备降”等状态只需要改这一个Map所有调用方自动生效。这种“规则集中管理”的思路在真实项目中非常吃香面试聊到这个点也会加分。2.3 接口清单给前端提供稳定的“服务契约”接口设计的原则是“按前端页面的数据需求来设计而不是按表结构来设计”。很多新手直接把表字段原封不动返回给前端导致前端拿到一堆用不上的字段还容易把数据库结构暴露出去。我在这套系统里做了明确的VO层隔离核心接口如下接口方法路径说明POST/api/auth/login用户登录返回JWT TokenGET/api/flight/page分页多条件查询航班列表GET/api/flight/{id}获取航班详情POST/api/flight新增航班PUT/api/flight修改航班信息DELETE/api/flight/{id}逻辑删除航班PUT/api/flight/status变更航班状态GET/api/flight/statistics/overview首页统计总览GET/api/flight/statistics/trend近7日航班量趋势GET/api/flight/statistics/punctuality准点率统计看到没有查询条件是通过GET参数拼的比如/api/flight/page?flightType1status2startDate2024-01-01endDate2024-01-31pageNum1pageSize10。这里有个经验日期范围查询一定要用 startDate AND endDate1的方式来过滤而不是用BETWEEN因为DATETIME字段在用BETWEEN的时候容易把最后一天的边界数据漏掉或者包含到第二天。3. 后端落地SpringBoot里那些真正写代码的细节3.1 项目分层与统一返回体后端工程我用了最经典的四层结构Controller接收参数和返回结果、Service业务逻辑、Mapper数据访问、Entity/VO数据模型。这种分层在菜鸟眼里可能觉得“多此一举”但在多人协作或长期维护的场景下它能有效地把职责隔离——Controller不写SQLMapper不写业务判断Service不直接操作HttpServletRequest。统一返回体的设计也是一个容易被忽略但极其重要的点。我写了一个R类所有接口都返回这个格式public class RT { private Integer code; // 200成功500业务异常 private String message; // 提示信息 private T data; // 业务数据 public static T RT ok(T data) { RT r new R(); r.setCode(200); r.setMessage(success); r.setData(data); return r; } public static T RT fail(String message) { RT r new R(); r.setCode(500); r.setMessage(message); return r; } }有了这个统一返回体前端axios拦截器就可以全局判断code字段不用每个接口单独写错误处理。同时配合RestControllerAdvice做全局异常捕获所有未处理的异常都会转成R.fail(系统异常请稍后重试)避免把堆栈信息直接泄露给前端这个安全细节在答辩或者面试的时候都可以提。3.2 MyBatis的核心XML里写动态SQL要注意什么MyBatis在这套系统里的定位是“半自动ORM”SQL由你掌控映射交给框架。很多人纠结用注解还是XML我的建议是多条件查询和复杂SQL一律用XML因为动态SQL标签if、where、foreach在XML里写才最顺手注解里拼字符串写动态SQL简直是灾难。拿航班多条件分页查询举例子这是全项目最核心的一段SQLselect idselectFlightPage resultTypecom.example.flight.entity.FlightInfo SELECT * FROM flight_info where AND deleted 0 if testquery.flightNo ! null and query.flightNo ! AND flight_no LIKE CONCAT(%, #{query.flightNo}, %) /if if testquery.flightType ! null AND flight_type #{query.flightType} /if if testquery.flightStatus ! null AND flight_status #{query.flightStatus} /if if testquery.startDate ! null AND scheduled_departure gt; #{query.startDate} /if if testquery.endDate ! null AND scheduled_departure lt; DATE_ADD(#{query.endDate}, INTERVAL 1 DAY) /if if testquery.originAirport ! null and query.originAirport ! AND origin_airport #{query.originAirport} /if if testquery.arrivalAirport ! null and query.arrivalAirport ! AND arrival_airport #{query.arrivalAirport} /if /where ORDER BY scheduled_departure DESC /select这里有两个细节很多人会栽跟头模糊查询一定要用CONCAT(%, #{value}, %)而不是直接在Java代码里拼好%值%再传进去。虽然结果一样但后者容易在传参时忘记处理特殊字符。更重要的是#{}是预编译占位符能防SQL注入而如果用${}直接拼接那等于把数据库敞开了让别人打。航班号这种场景虽然不容易被攻击但习惯一定要养成。动态where标签会自动处理第一个条件前面的AND你不需要写WHERE 11这种丑陋的兼容写法。if标签里每一个条件都要判断“不为空”尤其是字符串类型没判空的话MyBatis会拼出AND flight_no 这种残缺SQL直接报语法错误。分页用的是PageHelper用法极其简单在查询前调用一行代码后面的查询自动带上LIMITpublic PageResultFlightVO pageFlights(FlightQuery query) { PageHelper.startPage(query.getPageNum(), query.getPageSize()); ListFlightInfo list flightMapper.selectFlightPage(query); PageInfoFlightInfo pageInfo new PageInfo(list); // 把Entity转成VO再组装PageResult返回 return PageResult.build(pageInfo.getTotal(), voList); }注意PageHelper.startPage必须紧挨着下一次Mapper查询中间不能穿插其他查询否则分页会错乱。这个坑我在生产环境见过不止一次——有人在startPage和selectList之间加了一个日志查询或者字典查询结果分页被“截胡”数据全乱套。3.3 时间字段、VO转换和JSON序列化的坑航班系统的核心数据几乎全是时间所以时间处理是整个后端最容易出幺蛾子的地方。我的建议有三条数据库用DATETIMEJava实体用LocalDateTime两者配合是最好的既没有java.util.Date的时区噩梦也没有Timestamp的精度问题。JDBC连接串必须加serverTimezoneAsia/Shanghai否则MySQL驱动会拿默认时区通常是UTC来解析时间导致存进去和查出来的时间差了8个小时。这种问题极其隐蔽前端显示永远比实际时间少8小时但数据看着又是“合理”的。返回给前端的JSON时间格式要统一。我习惯在application.yml里配置spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai前端拿到的就是2024-06-01 08:30:00这种字符串直接展示即可。如果配置漏了默认序列化出来是一串时间戳数字前端还得自己格式化两边标准不统一就会出现“详情页显示正常、列表页显示NAN”这种莫名其妙的问题。VO转换我用的是BeanUtils.copyProperties虽然效率不是最优但代码极其简洁。在航班量不大的场景下这点性能损耗完全可接受。如果你追求极致性能或者字段特别多可以引入MapStruct编译期生成转换代码效率高且类型安全但需要多写一点接口定义。这个度大家自己把握。3.4 登录鉴权JWT该怎么用才不“裸奔”虽然毕设或者课设对权限要求不高但一个没有登录环节的管理系统总感觉差点意思。这套系统用的是JWTJSON Web Token做无状态认证用户登录成功后后端签发一个有效期2小时的Token返回给前端前端每次请求在Header里带上Authorization: Bearer token后端通过一个过滤器解析Token顺便把用户信息放进ThreadLocal里供后续业务使用。JWT的密钥一定要放在配置文件中别硬编码在代码里。我还要加一层验证用户状态被禁用时即使Token没过期也要拒绝访问。实现方式是每次请求除了验签之外再去数据库查一下用户状态虽然多了一次查询但安全性提升一个档次。对于真实系统来说“Token过期但用户已被删号”还能继续访问这是不可接受的漏洞。密码存储用的是BCryptPasswordEncoder不是MD5。原因很简单MD5是摘要算法撞库和彩虹表都能破解而BCrypt内置随机盐同一个密码每次加密结果都不同暴力破解成本指数级上升。这个点其实一行代码就能体现专业度String encodedPassword new BCryptPasswordEncoder().encode(rawPassword); boolean matches new BCryptPasswordEncoder().matches(rawPassword, encodedPassword);4. 前端页面与交互Vue怎么把这套系统做得“像那么回事”4.1 路由、Axios封装与全局拦截前端用Vue3 Element Plus Vue Router Pinia这套组合工程初始化用Vite。启动项目前先做两件基础工作路由配置和axios封装。路由设计围绕顶部导航展开分为登录页、首页看板、航班管理、航班动态、统计报表几个模块。使用懒加载模式const routes [ { path: /login, component: () import(/views/Login.vue) }, { path: /, component: () import(/layout/index.vue), redirect: /dashboard, children: [ { path: dashboard, component: () import(/views/Dashboard.vue) }, { path: flight/list, component: () import(/views/flight/FlightList.vue) }, { path: flight/dynamic, component: () import(/views/flight/FlightDynamic.vue) }, { path: statistics/trend, component: () import(/views/statistics/TrendReport.vue) } ]} ];懒加载的意义在于首屏只加载当前需要的页面不会一上来就把所有组件全塞进浏览器首屏渲染速度会快不少。axios封装的核心是拦截器。请求拦截器负责自动带上Token响应拦截器统一处理code ! 200的情况service.interceptors.request.use(config { const token localStorage.getItem(token); if (token) config.headers[Authorization] Bearer ${token}; return config; }); service.interceptors.response.use(res { const { code, message } res.data; if (code 200) return res.data.data; if (code 401) { localStorage.removeItem(token); router.push(/login); } ElMessage.error(message || 请求失败); return Promise.reject(new Error(message)); });这段代码的精髓是所有接口请求都走同一个出口登录过期时全局跳转登录页并提示不需要每个页面单独处理“Token失效”这种特殊情况。这也是前后端分离项目的基本盘。4.2 航班列表页表格、分页与多条件搜索的组合拳航班列表页是系统使用频率最高的页面交互设计要围绕“快速筛选、快速编辑”来展开。页面顶部是一排筛选条件航班类型下拉、航班状态下拉、起飞机场输入、到达机场输入、日期范围日期选择器、航班号输入下面才是表格主体。表格用Element Plus的el-table列字段和接口返回的VO保持一一对应。这里有一个特别容易出问题的点时间列的格式化。因为后端已经按yyyy-MM-dd HH:mm:ss格式返回了前端直接渲染即可但如果你后端返回的是时间戳前端就需要用dayjs格式化而且格式化函数要加容错判断否则空值会产生“Invalid date”白字特别难看。分页交互我用的是el-pagination关键点是页码和每页条数的变化都会触发数据重新加载但不会重置筛选条件。也就是说用户在筛选条件面板填了一堆条件翻到第5页后刷新筛选条件还在。这个很基础但很多初学者会把分页状态和筛选状态混在一起导致一翻页条件全没了。状态变更操作建议做成下拉选择框放在表格行内而不是弹窗修改。这样用户点一下就能切换状态配合状态机校验交互效率很高el-table-column label航班状态 width130 template #default{ row } el-select :model-valuerow.flightStatus changehandleStatusChange(row, $event) el-option v-foritem in statusOptions :keyitem.value :labelitem.label :valueitem.value / /el-select /template /el-table-columnhandleStatusChange里调用后端状态变更接口成功之后用ElMessage.success提示然后刷新当前页数据。注意不要像有些人那样整个页面重新查询第一页那样会把用户正在看的页码跳回去体验很差。正确做法是重新请求当前页码。4.3 首页看板与航班动态数据可视化怎么做才好看首页看板放的是“数字总览图表”数字卡片展示今天的进出港航班总数、出港数、进港数、取消数、旅客总人数图表部分用ECharts画近7日航班量趋势和准点率环图。这里要提醒ECharts初始化之后组件销毁时一定要dispose否则内存泄漏。如果在Vue里频繁切换菜单泄漏积累多了页面会越来越卡。正确范例onMounted(() { chart echarts.init(domRef.value); chart.setOption(option); }); onBeforeUnmount(() { chart.dispose(); });航班动态页是我个人比较喜欢的一块设计按时间轴展示今日航班每条数据是一张卡片卡片上显示航班号、起降机场、计划时间、实际时间、当前状态状态用不同颜色标签标注。这块页面用el-timeline组件实现很合适数据量控制在当天的前50条避免拉取全量数据拖慢页面。高亮当前正在登机或起飞的航班就能做出一块类似机场大屏的效果。5. 部署上线与常见问题排查这是我踩坑最多的地方5.1 本地开发环境搭建要点搭环境的顺序别乱先装JDK要求8以上我建议17、Maven3.6再装MySQL8.0最后装Node.js16。每一步都要验证成功再进行下一步别一口气全装完才发现版本不兼容。MySQL初始化时最需要留意的就是字符集和时区。字符集一定要是utf8mb4不然存中文或者生僻字会变成问号时区设置Asia/Shanghai。初始化数据库后执行建表SQL或者导入flight_management.sql再在application.yml里改数据库账号密码。后端启动前我习惯用Postman先测试/api/auth/login和/api/flight/page两个接口确认后端没问题之后再启动前端。前端启动比较麻烦的就是安装依赖npm install经常会慢可以用npm config set registry https://registry.npmmirror.com切换镜像源。如果遇到node-sass编译报错优先检查Node版本——node-sass和Node版本是强绑定关系版本对不上怎么装都报错现在Vue3项目一般都用dart-sass替代了会省心很多。启动完成后浏览器访问http://localhost:5173这是Vite默认端口后端接口是http://localhost:8080。两个端口不同就会触发跨域问题所以必须配置前端代理// vite.config.js server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }5.2 生产部署jar包和Nginx的组合拳开发环境跑通后部署生产环境就是另一个世界。前端执行npm run build产物在dist目录包含静态的HTML/CSS/JS后端执行mvn clean package生成可执行jar包。后端部署java -jar flight-management.jar --spring.profiles.activeprod用nohup挂后台运行。如果是服务器资源紧张可以加-Xms256m -Xmx512m限制内存SpringBootMyBatis这种轻量应用512M内存足够跑得很欢。前端部署把dist目录复制到Nginx的html目录下配置一个server块监听80端口root指向该目录。这里最容易踩的坑是路由刷新404问题。因为Vue是SPA单页应用所有路由都是前端路由直接刷新/flight/list时Nginx会去磁盘找/flight/list这个文件找不到就报404。解决方案是配置try_files回退到index.htmllocation / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; }还有一个常见坑是后端接口地址写死。前端在生产环境请求/api时Nginx要把/api反向代理到后端服务的8080端口location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }这条配好前后端才真正打通。5.3 高频报错速查表我把开发过程中最常遇到的十几个问题整理成一张速查表这些问题每一条都是我一边开发一边记录的真的非常实用错误现象根本原因解决方案数据库连接失败Access denied账号密码错误或远程权限未授权核对密码给用户授权grant all on *.* to root%中文乱码成???建库时字符集不是utf8mb4ALTER DATABASE xxx CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci页面时间差8小时JDBC连接串缺serverTimezone连接串添加?serverTimezoneAsia/ShanghaiXML里符号报错XML规范不能直接解析小于号用lt;或者![CDATA[]]Mapper接口找不到接口和XML路径不一致检查MapperScan路径和XML的namespace分页数据不对PageHelper被其他查询截胡确保startPage后紧接目标Mapper调用前端跨域报错端口不一致没有代理配置Vite proxy或Nginx反向代理刷新页面404SPA路由没有try_files回退Nginx增加try_files $uri $uri/ /index.htmlMyBatis查询返回null表字段下划线和Java驼峰没有映射map-underscore-to-camel-case: true表格时间显示Invalid Date后端返回时间戳或null前端直接格式化后端统一格式前端格式化前判空新增航班后列表不刷新前端没有重新加载数据新增成功后重新调用查询接口打包后XML没打进jarMaven默认不包含mapper目录pom中配置resources包含**/*.xml5.4 查询性能优化航班量大了怎么办虽然毕设数据量不会大但既然做的是航班系统以后面试官问到“百万数据怎么优化”你得能说出点东西。三个方向索引优化上面那张表已经建了四个索引但要注意“复合索引”的使用。如果高频查询是“按日期范围航班类型”那建一个(scheduled_departure, flight_type)的复合索引远比两个独立索引效果好因为数据库可以走索引的最左前缀匹配。规避LIKE %xxx%模糊查询如果通配符在开头索引会失效全表扫描。对于航班号这种前缀固定的字段LIKE CA%就能走索引。这是面试的高频考点。分页深翻页LIMIT 100000, 10这种分页越翻越慢因为MySQL要先丢弃前10万行。优化方式是记住上一页的最大ID用WHERE id lastMaxId ORDER BY id LIMIT 10来翻页这是游标分页的思路。在航班动态这种按时间倒序的场景下尤其好用。6. 代码质量、安全性与后续扩展方向最后再聊聊代码层面的几个加分项。虽然管理系统“能用就行”但作为简历项目多几个亮点能让面试官在10分钟内记住你。在安全性上除了MyBatis的#{}防SQL注入之外还要注意XSS攻击。这个系统里航班备注、机场名都是用户可输入字段如果原样存原样回显就能在页面上注入恶意脚本。我加了一个全局过滤器对请求参数中的script、onerror等关键字做转义处理。如果你的项目里涉及文件上传更应该做文件类型白名单校验别让用户传个.jsp上去直接执行。密码加密、JWT鉴权、逻辑删除、全局异常处理、统一返回体、乐观锁版本号字段——这些点每一个都是企业级开发的基本功。我把flight_info表加了一个version字段更新时用UPDATE ... SET ... WHERE id? AND version?如果更新行数为0就说明数据被其他人改过抛出并发冲突提示。这种乐观锁机制在业务系统里几乎是标配。项目后续要扩展的话优先级从高到低排列增加角色权限管理员/操作员/只读用户基于Spring Security JWT做RBAC。接入消息队列模拟航班信息推送比如Kafka或者RabbitMQ让前端通过WebSocket实时收到状态变更。把统计报表做成多维分析按航空公司、航线、小时维度下钻。引入Redis缓存机场基础数据、航班字典缓解数据库压力。我个人在实际开发这个系统过程中最大的体会是这个项目帮我重新串了一遍“为什么这么设计”的逻辑。比如状态机为什么用Map存规则而不是if-else统一返回体为什么能减少前端大量重复代码逻辑删除为什么比物理删除更安全——这些单个看起来都很小的知识点连在一起就是一个完整的工程思维。如果你准备拿这个项目去面试不要只背名词要把每个设计决策背后的“为什么”想清楚面试官往往就爱追着这个点一直问到底。最后再分享一个小技巧多条件查询的DTO不要命名成FlightQueryDTO这么泛直接在类上注明用途加个ApiModelProperty注解描述每个字段的语义等过几个月你自己回来看代码时会感谢当初那个写清楚注释的自己。工程能力往往就是在这些琐碎的细节里慢慢积累起来的。
返回列表