
每年毕业季都会有不少同学来问我“锦宇气体城市货运系统”这类毕设项目该怎么写、怎么跑通、答辩时老师会问什么。标题里那串“10184”编号懂行的一看就知道是XX资源站上的项目编号但真正把它拿到手里能顺利改造成自己设计的人其实不多。这套以Spring Boot为核心的货运管理系统表面上是“订单配送后台管理”老三样但叠上“气体”这个细分场景后业务逻辑就有了不少讲究。这套系统是典型的Java后端毕业设计Spring Boot做服务端Vue或Thymeleaf做前端MySQL存业务数据再配上权限认证、Excel导入导出、MinIO文件存储这类加分项。它解决的痛点是气体这类特殊商品液化气、工业气瓶、医用氧气在城市配送中的流程管理——下单、气瓶出库、配送派单、客户签收、空瓶回收每一环都需要留痕。适合想做管理信息系统类毕设的计算机专业学生也适合刚入坑Java开发、想理解一个完整项目怎么串起来的初学者。下面我从业务设计、技术选型到前后端联调、答辩避坑完整过一遍。1. 内容整体设计与技术选型思路1.1 为什么选“气体城市货运”这个场景很多同学做毕设选题时第一反应是“网上购物系统”“图书馆管理系统”这种通用模板。但这类题目的问题是业务太泛没有场景壁垒答辩时很难讲出差异化。而“气体货运”这个切入点虽然是同一个骨架但因为业务对象特殊就有了值得展开讲的地方。气体的城市运输和普通快递不一样它有四个明显特征第一大部分气体属于危化品对车辆资质、驾驶员培训、运输时间有特殊要求第二交易单位是“瓶”而不是“件”气瓶有规格、有押金、有检测有效期要能追踪到具体瓶号第三存在空瓶回收这个逆向物流环节系统里必须设计“未归还瓶数”这种统计维度第四客户往往是企业或餐饮商户下单人和收货人未必是同一人所以要有联系人独立字段。这些点直接决定了数据模型的设计。普通快递订单可能只需要“收件人地址商品”但气体订单需要拆成主订单记录客户、配送地址、总金额、状态、订单明细每种气体品类、数量、气瓶规格、单价、配送任务由调度员把一批订单合并派给一个司机、签收记录客户签字、空瓶数、实收数。我在设计数据库时把这四张核心表拆得很干净哪怕源码里没有用到索引优化答辩时把这种“为什么拆表”的逻辑讲清楚老师也会觉得你有思考。1.2 技术栈选型Spring Boot为主体的合理性选Spring Boot作为毕设基座现在基本没有争议。原因有几个层面。第一是生态成熟无论你用的是2.7.x还是3.xMyBatis-Plus、Spring Security、Redis、MinIO都有现成的整合文档遇到问题搜一下就有解决方案对毕设这种“短周期、求稳”的项目来说效率最高。第二是Spring Boot的自动配置机制天然适合演示写一个Controller、配一个数据源项目就能跑起来不像SSH时代要写一堆XML。第三是就业市场上Spring Boot就是Java后端的事实标准写进简历里的说服力远远高于SSM裸写。不过版本选择有讲究。我见过不少同学一上来就选Spring Boot 3.2.x或3.3.x结果发现JDK必须用17MyBatis-Plus的旧版分页插件不兼容网上搜的教程很多还是2.x的写法被版本问题卡了一周。如果你是做毕设、追求平稳跑通我更推荐直接锁定Spring Boot 2.7.18这是2.x系列的最终版本坑最少教程最全。如果你确实想用3.x来体现自己追新那也要把JDK 17、jakarta命名空间变更、MyBatis-Plus 3.5.3的分页插件调整一并搞清楚。下面对比一下两条技术路线的取舍对比项Spring Boot 2.7.xSpring Boot 3.xJDK要求Java 8 / 11 / 17均可必须Java 17包名javax.*jakarta.*社区教程匹配度极高大部分资料都能直接用相对少部分旧插件不兼容适合场景稳妥跑通、快速出成果想体现技术敏感度、时间充裕整套系统的技术栈我建议采用前后端分离架构后端Spring Boot MyBatis-Plus MySQL 8 Redis可选用 Sa-Token或JWT鉴权前端Vue3 Element Plus Axios文件存储用MinIO部署用Nginx。这套组合的关键优势在于每一层都是主流方案答辩时老师问“你这个文件上传怎么做的”“权限拦截怎么设计的”你都有成熟的实战答案可讲。1.3 权限与数据隔离不止是做了登录气体货运系统的用户角色和普通商城不一样不是简单“用户/管理员”两级。在我设计的方案里至少要有五类角色普通客户下单查单、企业客户批量下单对账、司机接单、上报配送状态、调度员派单、系统管理员维护基础数据、审核资质。多角色带来的直接技术点是接口必须有权限控制数据必须做隔离。这里我推荐两种方案。一种是Spring Security JWT优点是简历分量高但学习曲线陡一点另一种是Sa-TokenAPI极简依赖少中文文档友好对学生来说特别好上手。我用得比较多的是Sa-Token因为它提供了“登录认证 权限认证 踢人下线”等开箱即用的功能做毕设完全可以满足。比如司机端的接口只要加一句SaCheckRole(driver)就能拦截非司机账号的访问演示效果非常好。2. 核心业务模块拆解与数据库设计2.1 功能清单从下单到气瓶回收的完整链路把整套系统拆成模块来看主干是“订单流转”围绕主干的辅助模块包括“客户管理、气体品类管理、车辆管理、司机管理、配送任务、气瓶回收、统计报表”。我见过很多毕设源码功能清单列得很长但真正把业务链走通的很少——要么没有签收环节要么订单状态和配送状态各管各的数据对不上。比较完整的模块设计方案是这样的客户下单端选择气体品类如液化气15kg、工业氧气40L、填写配送地址、选择期望送达时段、提交订单。调度派单端查看当日待派单列表按区域、载重、司机工作量把多个订单合并生成一个配送任务。司机端查看任务列表、查看路线、逐单上报“出发/到达/签收/拒收”状态签收时记录实收瓶数、空瓶回收数。管理后台用户管理、司机资质审核、车辆年检信息、气体品类价格管理、历史订单与财务报表。为什么要把“空瓶回收数”单独做成一个字段而不是简单在签收时填一个备注因为空瓶返还数量直接影响押金结算和后续气瓶库存。如果需求做得细致一点还可以设计“气瓶台账表”每只气瓶有唯一编号、检验周期、当前归属客户、所在位置。这个深度做完项目档案里可以直接写“实现了资产级气瓶的全程追踪”答辩亮点就有了。2.2 核心数据表结构设计详解数据库设计是毕设源码里最见功底的部分老师翻不到论文看代码时第一件事就是看你的ER图和建表SQL。我按“锦宇气体城市货运系统”的业务需求梳理出以下核心表和关键字段用户表 sys_user用户ID、用户名、密码BCrypt加密存储、姓名、手机号、角色customer/driver/dispatcher/admin、所属企业ID、状态、创建时间。这里要注意不要把角色直接做成一个字符串存死因为同一人在真实场景里可能既是客户也是司机虽然毕设可以简化但至少要预留一个user_role中间表的表达意识。气体品类表 gas_category品类ID、名称、规格15kg/40L/2L、单位、单价、押金、危险品等级、是否需要资质证明。这张表是订单明细的“价目表来源”建议把“押金”和“单价”分开存因为退款结算时逻辑会不一样。订单主表 gas_order订单ID、订单编号可用年月日流水号、客户ID、联系人、联系电话、配送地址、经度纬度可简单存文本、订单总金额、押金总额、订单状态0待支付/1已支付待派单/2配送中/3已完成/4已取消、期望配送时间、备注、创建时间、支付时间。订单明细表 gas_order_item明细ID、订单ID、气体品类ID、数量、单价、押金单价、气瓶规格。一张主表带多张明细是为了演示“一对多”的数据库建模能力也是MyBatis-Plus中TableId(type IdType.ASSIGN_ID)之类的应用场景。配送任务表 gas_dispatch_task任务ID、任务编号、调度员ID、司机ID、车辆ID、任务状态0待接单/1配送中/2已完成/3异常、任务备注、创建时间、完成时间。配送详情表 gas_dispatch_item详情ID、任务ID、订单ID、送达顺序用于规划路线、实际签收状态、拒收原因。气瓶回收记录表 gas_bottle_return记录ID、订单ID、气瓶编号、回收数量、回收时间、操作员ID。这套表设计的好处在于订单状态、配送状态、气瓶追踪是三个独立又互相引用的维度你可以分别查询“今日有多少订单未派单”“某司机手上有几个配送中任务”“某客户的未归还气瓶数”每一个都是毕业设计演示时可以现场跑给老师看的亮点。2.3 订单状态的流转设计与状态机思路订单状态是整个系统的灵魂不夸张地说把所有Controller都删了只要状态机设计得清楚系统依然能讲明白。气体订单一手写了很多种状态但我建议精简成六个状态创建支付 → 已支付待派单 → 已派单/配送中 → 已签收/已完成 → 已取消退款实际代码里我会用一个整数status字段加一组静态常量来管理而不是直接用字符串。为什么因为字符串“已完成”“PAID”“FINISHED”之间的映射容易写错而整数配合常量类在代码提示和后续扩展上友好得多。可以用一个OrderStatusEnum把“状态码、状态名、可流转到哪些状态”放在同一个枚举里状态流转的合法性判断集中管理。学习能力强的同学还可以再加一个order_status_log表记录每一次状态变更的操作人、操作时间、旧状态、新状态。这个表能给你带来一个非常好讲的答辩话题“这个系统如何实现对订单全生命周期的审计追踪”在运维实际业务里订单状态日志也是排查问题的重要依据。3. 实操环节环境准备与核心功能开发3.1 在IDEA里快速搭起Spring Boot项目骨架这个环节我直接给可照抄的步骤。首先打开IDEA用Spring Initializr创建项目Group填com.jinyuArtifact填gas-cloud你自己的项目名随意Java版本按你选定的Spring Boot版本来。如果你是Spring Boot 2.7.18Java选8或11如果是3.x选17。创建完成后把默认的application.properties改成application.yml因为YAML格式层级更清晰配置可读性更好。然后引入核心依赖。以下是pom.xml中最小可跑通的依赖组合dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.2/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdcn.dev33/groupId artifactIdsa-token-spring-boot-starter/artifactId version1.37.0/version /dependency dependency groupIdio.minio/groupId artifactIdminio/artifactId version8.5.7/version /dependency /dependencies这里多说一句MyBatis-Plus的好处它最核心的价值是BaseMapper里内置了selectById、selectList、insert、updateById单表CRUD完全不用手写XML。对毕设来说这意味着你把90%的业务逻辑都写在Service层而不是Mapper XML代码看起来清爽也好讲。比如用户管理、气体品类管理这种纯CRUD模块几乎就是定义一个Entity再定义一个Service继承ServiceImplGasCategoryMapper, GasCategory就完事了。3.2 配置文件的坑时区、文件上传、日志关于application.yml有四个地方我建议你一次配对否则后面跑着跑着就出幺蛾子。MySQL连接串必须加时区参数。我见过太多同学因为没加serverTimezoneAsia/Shanghai项目启动时数据源初始化报错或者查询出来的时间比实际少了8小时。在校验环境里这个错误非常低级但影响很大。配置如下spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/gas_city?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 123456文件上传大小限制要显式设置。Spring Boot 2.x默认单文件上传限制是1MB但你想给气体品类上传气瓶图片1MB是明显不够的。这里给出覆盖上传的配置spring: servlet: multipart: max-file-size: 20MB max-request-size: 50MBMinIO的endpoint、accessKey、secretKey建议单独放在一个minio配置项下用ConfigurationProperties读取。把连接参数集中在配置里的好处是部署到服务器或改成自己本地MinIO时不用动代码。最后是MyBatis-Plus的日志。开发阶段在application.yml里把日志级别调成Debug可以看到每一条执行的SQL语句排查“查不出数据”“更新没生效”这类问题会快很多。上线或演示环境再调成Info免得刷屏。3.3 用MinIO统一管理上传文件毕设里的“文件上传”通常有两种做法存本地磁盘路径或者存MinIO。我强烈建议用MinIO原因有三一是MinIO是S3协议兼容的对象存储简历里写上“对象存储”四个字面试官会继续追问你就有话可答二是演示时可以做到不落服务器随便传上传完直接拿到URL回显图片体验流畅三是MinIO和Spring Boot的整合热词里都有可见技术热度确实高。MinIO核心流程分三步启动MinIO服务可以本机装一个或者用Docker跑在控制台创建bucket并设置访问策略后端写一个配置类生成MinioClient实例再写一个Controller接收MultipartFile并调用putObject上传。给你一个核心代码示例import io.minio.*; import io.minio.http.Method; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class MinioConfig { Value(${minio.endpoint}) private String endpoint; Value(${minio.access-key}) private String accessKey; Value(${minio.secret-key}) private String secretKey; Bean public MinioClient minioClient() { return MinioClient.builder() .endpoint(endpoint) .credentials(accessKey, secretKey) .build(); } }上传接口就一行核心调用minioClient.putObject(PutObjectArgs.builder() .bucket(gas-bucket) .object(UUID.randomUUID() - file.getOriginalFilename()) .contentType(file.getContentType()) .stream(file.getInputStream(), file.getSize(), -1) .build());这里有一个关键体验问题上传文件后直接返回的URL如果不带签名默认是无法通过浏览器直接访问的因为MinIO的桶权限默认是private。两种解决办法一是把桶策略设为public适合演示环境简单粗暴但正规感差一点二是在上传后调用getPresignedObjectUrl生成一个临时签名URL前端拿着这个URL自然可访问图片安全性也好。我建议用第二种因为答辩时你能顺带讲清楚“私有读与临时授权”的区别这就是加分点。3.4 登录认证与权限拦截的实现要点气体货运系统里最简单的鉴权实现是拦截器Token。我以Sa-Token为例先说思路用户输入账号密码后端校验通过后调用StpUtil.login(userId)发放tokenSa-Token底层会把这个token和用户关联你可以在任意接口里通过StpUtil.getLoginIdAsLong()获得当前用户。拦截器只需要一个注册配置类对/api/**路径统一拦截放行登录接口、注册接口和MinIO临时地址Configuration public class SaTokenConfigure implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new SaInterceptor(handle - StpUtil.checkLogin())) .addPathPatterns(/**) .excludePathPatterns(/api/auth/login, /api/auth/register, /file/**); } }而角色级别的接口隔离在Controller方法上直接加注解即可例如SaCheckRole(dispatcher) PostMapping(/dispatch/create) public Result createDispatchTask(RequestBody DispatchCreateRequest request) { ... }这样一套下来你在答辩时被问到“用户权限是怎么控制的”就能从容地展开从登录发Token、到路由校验、再到方法级角色校验的三层链路。要记住的是前端按钮的隐藏只是体验层面的真正的安全必须是后端拦截这句表述用好能加分。3.5 订单与配送模块的核心流程代码订单创建接口的逻辑是事务性的先生成主订单再循环插入多条订单明细同时扣减对应气体品类的库存。必须加上Transactional否则明细插了一半失败会出现脏数据。这里给一个精简示例Transactional(rollbackFor Exception.class) public Long createOrder(OrderCreateDTO dto) { GasOrder order new GasOrder(); order.setCustomerId(dto.getCustomerId()); order.setStatus(OrderStatusEnum.PENDING_PAY.getCode()); // ...其余字段赋值 orderMapper.insert(order); for (OrderItemDTO item : dto.getItems()) { GasOrderItem oi new GasOrderItem(); oi.setOrderId(order.getId()); oi.setGasCategoryId(item.getGasCategoryId()); oi.setQuantity(item.getQuantity()); orderItemMapper.insert(oi); // 库存扣减用乐观锁版本号或库存字段直接减注意并发 } return order.getId(); }配送任务生成就更有业务味道了。调度员选一批订单然后点击“生成任务”后端做的事情本质上是创建gas_dispatch_task记录司机和车辆遍历选中的订单在gas_dispatch_item里写入订单关联并把这些订单状态从“已支付待派单”批量更新成“配送中”。这一步的代码不难但状态一致性要注意要么全成功要么回滚所以依然是要Transactional。批量更新订单状态可以用MyBatis-Plus的LambdaUpdateWrapperdispatchItemMapper.insert(new GasDispatchItem(...)); orderMapper.update(null, new LambdaUpdateWrapperGasOrder() .in(GasOrder::getId, orderIds) .eq(GasOrder::getStatus, OrderStatusEnum.PENDING_DISPATCH.getCode()) .set(GasOrder::getStatus, OrderStatusEnum.DISPATCHED.getCode()));注意我在更新条件里加了一个eq(status, PENDING_DISPATCH)这是乐观锁思想的变体保证只有处于待派单状态的订单才能被更新成已派单。这行代码能体现你对并发场景的考虑实战里非常实用。4. 实操经验部署、联调与常见问题排查4.1 本地如何让前后端快速联调跑通毕设项目最常见的加速方式就是前端用Vue3的devServer代理转发到后端。在vue.config.js里配置proxy把/api开头的请求转发到http://localhost:8080。这样前端调试时不需要考虑跨域问题。同时后端的WebMvcConfigurer里也得配置允许跨域否则你用Postman能通、用浏览器前端访问就报CORS错误。如果想把前端打成静态文件直接塞进Spring Boot里部署做法是将npm run build生成的dist目录复制到src/main/resources/static下再重打包。但这个方案只适合线下演示或临时查看不适合正规的前后端分离项目。我建议演示时开两个终端一个起后端一个起前端devServer网络里热词“vue打包放进springboot中”就是这类问题的搜索来源你按正规的分离结构才是最专业的。4.2 毕设源码常见缺失和启动步骤补全很多同学从网上下载到的毕设源码命名规则是“XX系统附源码编号”但拿到手经常缺三样东西数据库初始化脚本、配置文件里的真实参数、README启动说明。尤其是Spring Boot项目如果连数据库脚本都缺失基本跑不起来。我的建议是拿到源码后先按以下顺序检查看是否有src/main/resources/application.yml或properties没有就是半成品看是否有.sql脚本检查表结构是否包含用户、订单、明细、配送等核心表看pom.xml是否完整有没有缺失starter依赖确认JDK版本是否匹配Maven是否配置了国内镜像源。补全的通用做法是把SQL脚本放到src/main/resources/db/init.sql并在application.yml中配置spring.sql.init.modealways或只在第一次启动时需要。不过这里有个注意点线上环境千万不要设置always否则每次启动都会重新执行脚本数据会被覆盖。对这个毕设项目来说我建议手动手工执行一次SQL脚本后续关闭init模式自己控制数据初始化时机。4.3 排错实战我遇到过的那些诡异问题循环依赖启动报错。Spring Boot 2.6以后默认禁止循环依赖而毕设项目里最容易出现的情况是Service层互相注入比如订单Service引入了配送Service配送Service又引用了订单Service。解决办法优先是重构设计把公共逻辑抽出来紧急处理可以在配置里加spring.main.allow-circular-referencestrue。但答辩时不建议说这个开关最好说成“通过拆分职责消除了循环依赖”这才是真实项目里该有的态度。数据时间相差8小时。数据库时区、应用时区、Jackson序列化时区三者都要对齐。Jackson配置里我习惯设置spring.jackson.date-formatyyyy-MM-dd HH:mm:ss和spring.jackson.time-zoneGMT8。否则表里明明插入的是下午三点前端显示却是早上七点。MinIO上传成功但图片打不开。上面提过的签名URL问题或者bucket访问策略配错十有八九是这个原因。排查顺序先直接在浏览器访问返回的URL如果是403/XML错误就去MinIO控制台看bucket的Access Policy如果是图片能看但一会儿就失效那是你用的是临时下载链接而不是直接文件路径。MyBatis-Plus分页失效。这个坑几乎是新人标配。记住PaginationInnerInterceptor必须显式加入不是引入了MyBatis-Plus就能自动分页。如果你在selectPage得到的总数一直是0检查你是否漏加了MapperScan或者遗漏了拦截器配置。代码写法如下Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }4.4 排查技巧速查表异常现象大概率原因处理建议启动失败Error creating bean with name循环依赖重构Service依赖或临时开关allow-circular-references接口返回401/403Token未放行或角色不匹配检查登录后是否拿到token检查拦截器放行名单图片上传成功但访问失败MinIO桶策略或签名问题检查bucket访问策略改用预签名URL分页total0缺PaginationInnerInterceptor配置MybatisPlusInterceptor/api请求404前后端分离时路由未命中检查Controller的RequestMapping是否带/api前缀数据库中文乱码连接字符串未指定utf8在url后加characterEncodingutf85. 优先级排序毕设源码如何做出亮点5.1 答辩时老师最爱追问的七个问题我每年都有学生回来报告答辩现场被问的问题整理下来高度集中。第一个自然是“为什么用Spring Boot”你只要回答自动配置、起步依赖、生态成熟三点就够了不要展开讲原理除非老师追问自动配置源码。第二个是“订单状态是怎么管理的”这时候把上面的状态枚举、状态日志表讲出来很难被问倒。第三个是“如果两个人同时抢同一个配送任务怎么办”你可以用数据库乐观锁或Redis分布式锁回答毕设项目用乐观锁版本号已经足够。第四个是“你的数据库表为什么这么设计”用一对多、多对多、外键逻辑去串。第五个是“密码是怎么保存的”回答BCrypt加盐哈希并说明Spring Security提供了BCryptPasswordEncoder千万不要说“MD5两次加密”这种安全常识不过关的话。第六个是“文件上传是怎么做的”讲清楚MinIO的临时签名URL生成即可。最后一个高频追问是“项目里最难的模块是什么”这个我自己会引导到调度派单的状态一致性和订单状态流转因为这两块确实有业务宽度和代码深度。5.2 从合格到优秀的几点扩展建议如果时间充裕我建议在这五个方向上选一两个加深。第一给司机端做一个基于WebSocket的实时位置上报或通知用页面地图展示当前配送进度实现难度中等但演示效果很炸。第二引入Redis缓存气体品类和热点数据你可以在答辩时说“高并发读场景下Redis缓存能够显著降低数据库压力”再配合一个简单的缓存更新策略。第三为管理后台加一个ECharts统计大屏展示当日订单量、配送完成率、各气体品类销售占比用可视化讲业务能让老师眼前一亮。第四把发货单号、气瓶条码用QRCode生成二维码图片模拟气瓶扫码出库这个细节很小但很贴近真实场景。第五写单元测试或集成测试哪怕只有十几个用例都能在“工程素养”这个维度拉开和同龄人的差距。5.3 关于演示环境的备份和数据初始化答辩现场最怕的不是代码有bug而是出现演示环境数据不干净、登录不进去、网络连不上这种“技术之外的翻车”。建议提前准备三件事一是拍一段本地运行成功后的完整操作录屏万一现场环境故障你可以直接放录屏作为证据二是把数据库脚本和初始账号密码写进README老师如果让你现场打开数据库看数据你能快速找到admin账号密码三是在本地Docker里跑一套MySQL MinIO Redis的容器化环境保证换一台电脑也能快速恢复。Docker Compose的配置也就二十行左右写进项目里不仅方便自己也是简历里值得写的一句话。6. 落地部署从本地跑到服务器演示6.1 用Docker Compose一键启动中间件毕设演示最怕环境不一致。在自己电脑上能跑到答辩教室可能连不上MySQL。用Docker Compose把MySQL、MinIO、Redis三个中间件统一管起来会省很多事。给你一个最小可用的docker-compose.yml框架services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: 123456 MYSQL_DATABASE: gas_city ports: - 3306:3306 volumes: - ./mysql-data:/var/lib/mysql - ./init.sql:/docker-entrypoint-initdb.d/init.sql minio: image: minio/minio command: server /data --console-address :9001 environment: MINIO_ROOT_USER: minioadmin MINIO_ROOT_PASSWORD: minioadmin ports: - 9000:9000 - 9001:9001 volumes: - ./minio-data:/data redis: image: redis:7 ports: - 6379:6379注意第7行把初始化SQL脚本挂载到/docker-entrypoint-initdb.d/目录后MySQL容器在第一次启动时会自动执行脚本建库建表这是部署毕设项目最舒服的实践方式。不过要提醒一句这个机制只在数据目录为空时生效如果mysql-data里已经存在旧数据改动SQL脚本不会自动生效需要先docker compose down -v清掉卷再重来。6.2 Jar包启动遇到的内存和资源问题Spring Boot项目打成Jar后在服务器上用java -jar启动新人常踩的坑是默认堆内存太大云服务器2G内存直接被压死。推荐启动命令固定加JVM参数限制内存比如java -Xms256m -Xmx512m -jar gas-system.jar --spring.profiles.activeprod这样即使只有1G内存的小服务器也能稳定跑。另外如果需要用nohup后台运行我建议日志输出重定向到logs/app.log再用tail -f logs/app.log实时查看启动情况这样排查启动失败的原因会直观很多。--spring.profiles.activeprod配合多环境配置也是专业项目里必须养成的习惯。这一整套走下来“锦宇气体城市货运系统”的源码就已经从“跑通能用”变成“讲得清楚、改得动、有亮点”的完整作品了。真正拉开分数的往往是那些看起来简单却很容易被忽略的细节状态枚举是否清晰、事务边界是否正确、配置是否统一管理、时间时区是否对齐。把这些细节打磨好答辩时你会自然从容很多。