公司动态
Spring Boot实战:构建高并发二手车交易系统的架构设计与核心实现
简介这是一套基于SpringBoot开发的二手车交易系统完整源码面向计算机相关专业在校学生、教师及初级Java开发者用于课程设计、毕业设计参考或Web全栈开发实践。系统采用B/S架构涵盖用户管理、车辆发布、在线询价、订单处理等核心业务模块代码含详细中文注释经实测可正常运行。资源包共782个文件包含109个Java后端逻辑文件、54个Vue前端组件、157个JavaScript交互脚本、50个CSS样式文件及大量静态资源SVG图标、JPG/PNG图片、GIF动画等整体压缩包大小为31.75MB。已有128人学习下载适合具备Java基础与SpringBoot入门经验的学习者通过阅读清晰的目录结构、调试双bat启动脚本run.bat/install.bat及理解前后端分离实现逻辑快速掌握企业级二手交易平台的技术落地路径与工程组织方式。1. 项目概述一个实战派二手车交易系统的诞生最近几年身边不少朋友和同行都在琢磨着做点自己的项目要么是练手提升技术栈要么是想搞个能跑起来的商业原型。其中“二手车交易系统”绝对是个高频出现的选题。这玩意儿听起来接地气但真做起来从技术选型到业务逻辑坑一点都不少。今天我就结合自己最近用Spring Boot完整撸出来的一套带中文注释的系统跟大家从头到尾拆解一遍。这不仅仅是一个“能跑”的代码我更想分享的是在构建这样一个涉及多角色买家、卖家、平台管理员、复杂业务流程车辆上架、在线咨询、订单支付、物流跟踪的系统时那些藏在代码背后的设计思考、技术取舍和填坑实录。为什么是Spring Boot对于这样一个业务逻辑不算简单、需要快速迭代验证想法的项目来说Spring Boot的“约定大于配置”和强大的生态支撑能让我们把精力从繁琐的XML配置和依赖冲突中解放出来聚焦在核心业务实现上。这套系统涵盖了用户中心、车辆信息管理、在线交易、订单处理、后台数据统计等核心模块并且每一行关键代码我都加上了详细的中文注释目的就是让无论是刚接触Spring Boot的新手还是想参考业务实现的老鸟都能看得明白改得顺手。2. 系统整体架构与核心模块设计2.1 技术栈选型背后的逻辑在动手之前技术栈的选定决定了项目的开发效率和后期的维护成本。我的选型原则很明确主流、稳定、社区活跃、适合快速开发。后端框架Spring Boot 2.7.x。没有选择最新的3.x版本主要是考虑到2.7.x是长期支持版本生态极其成熟网上任何问题几乎都能找到解决方案避免在项目初期陷入新版本兼容性的泥潭。这比追求“最新”要务实得多。持久层MyBatis-Plus。相比原生的MyBatisMyBatis-Plus提供了强大的CRUD封装和条件构造器能极大减少单表操作的SQL编写量。对于车辆表、用户表这种基础表用起来非常爽快。但对于复杂的多表关联查询我们依然可以灵活使用自定义XML映射文件兼顾了效率和灵活性。数据库MySQL 8.0。关系型数据库依然是这类交易系统的核心事务特性对于保证交易一致性至关重要。MySQL 8.0在性能、窗口函数、JSON支持方面都有不错的表现。缓存Redis。用途广泛存储用户会话、缓存热门车辆列表、作为秒杀如果未来有的库存计数器、存储短信验证码等。选用Redis是提升系统响应速度和承载并发能力的标配。消息队列RabbitMQ。用于解耦耗时操作。例如用户成功下单后需要发短信通知、更新统计信息、生成电子合同。这些操作如果都在主线程同步执行会严重影响接口响应时间。通过消息队列异步处理系统整体更健壮。前端Vue.js Element UI。前后端分离架构前端负责页面渲染和用户交互通过RESTful API与后端通信。Element UI组件丰富能快速搭建出美观且功能完善的管理后台。其他Lombok简化Bean代码、Hutool工具类库、SwaggerAPI文档、Logback日志、Docker容器化部署。注意关于Spring Boot版本很多新手会纠结。如果你的项目没有必须使用Java 17的特性且需要引入大量旧的、未适配Spring Boot 3.x的第三方库那么选择2.7.x是更稳妥的方案。盲目追新可能会在依赖冲突上浪费大量时间。2.2 业务模块拆分与领域模型设计二手车交易的核心业务流是卖家发布车辆 - 买家浏览咨询 - 双方沟通线上/线下- 买家下单支付 - 平台确认/办理过户 - 交易完成。围绕这个流程我将系统拆分为以下核心模块用户中心模块处理用户注册、登录、认证、个人信息管理。这里区分了C端用户买家和卖家、B端用户车商、平台管理员三种角色通过角色权限控制RBAC来管理不同功能的访问权限。车辆信息模块这是系统的核心数据模块。包含车辆品牌型号、款式年代、里程数、牌照属地、车辆图片多图上传、检测报告可上传PDF、价格、发布时间、状态待审核、已上架、已售出、已下架等字段。设计上采用了“富模型”思想将车辆的核心业务逻辑如状态流转、价格计算逻辑封装在Vehicle实体或对应的Service中。在线交易模块收藏与对比用户可收藏心仪车辆或将多辆车加入对比列表对比项包括关键参数和价格。在线咨询买家与卖家或平台客服可围绕特定车辆进行实时聊天或留言。这里我实现了简单的WebSocket即时通讯也保留了历史消息的数据库存储。订单系统这是最复杂的部分。订单状态机包括待支付、已支付、待验车、验车通过/不通过、待过户、已完成、已取消。每个状态变更都关联着特定的业务规则和后续操作。支付与财务模块集成第三方支付平台如支付宝沙箱环境。处理支付、退款、平台佣金计算、资金流水记录。务必注意支付回调接口要做好签名验证和幂等性处理防止重复入账。内容与风控模块资讯与评测发布行业资讯、购车指南提升网站专业度和SEO。风控审核车辆上架前需要人工或AI辅助审核图片违规、价格异常、虚假信息。用户发布的评论、咨询内容也需要经过敏感词过滤。后台管理模块供平台运营人员使用包含数据看板交易量、用户增长、热门车型统计、所有业务数据的CRUD、审核流管理、系统配置如佣金比例、敏感词库等功能。在数据库设计时我遵循了以下几个原则表名、字段名使用下划线分隔的蛇形命名法为频繁查询的字段如车辆状态、价格区间、品牌建立索引将大字段如车辆长文本描述、检测报告拆分到单独的表中避免影响主表查询性能对于状态枚举使用tinyint类型并在代码中用常量类维护保证可读性。3. 核心功能实现细节与避坑指南3.1 车辆信息的多图上传与存储策略车辆展示图片是关键。我实现的是一个支持多图上传、可设定封面图、带进度显示的功能。后端实现要点接口设计使用MultipartFile[]数组接收前端上传的文件。一个/api/vehicle/{id}/images的POST接口。文件处理使用Apache CommonsFileUtils或Spring自带的工具进行文件大小、类型校验防止上传恶意文件。为避免文件名冲突和目录遍历攻击必须对上传的文件进行重命名。我采用UUID 时间戳 原始文件后缀的方式生成新文件名。文件存储路径不要写死在代码里而是通过application.yml配置。例如file.upload-dir: /data/upload/images/vehicle/。开发环境和生产环境可以配置不同的路径如开发环境存本地生产环境存OSS。存储策略对于中小型项目初期存储在服务器本地磁盘是可行的。但当图片量增大、需要分布式部署时就必须迁移到对象存储服务如阿里云OSS、腾讯云COS。我的做法是抽象一个FileStorageService接口有LocalFileStorageServiceImpl和OssStorageServiceImpl两个实现通过配置开关轻松切换。在数据库中只存储文件的相对路径或访问URL。数据库关联设计单独的vehicle_image表字段包括id,vehicle_id,image_url,is_cover是否封面图,sort_order排序。这样能灵活管理图片。避坑经验路径分隔符在Java代码中构造文件路径时使用File.separator或Paths.get()避免硬编码/或\保证跨平台兼容性。事务一致性车辆信息和图片信息的上传应该在一个事务内。如果图片保存成功但车辆信息插入数据库失败需要回滚并删除已上传的图片避免产生垃圾文件。我采用“先落库后删文件”的补偿机制或者使用Spring的Transactional注解管理。缩略图生成列表页展示不需要原图应在图片上传后立即生成指定尺寸的缩略图使用Thumbnailator库提升页面加载速度。3.2 基于WebSocket的实时在线咨询为了让买家和卖家能及时沟通我实现了基于WebSocket的简单聊天功能。技术实现引入依赖Spring Boot已经对WebSocket提供了很好的支持只需引入spring-boot-starter-websocket。配置类通过EnableWebSocketMessageBroker注解和继承AbstractWebSocketMessageBrokerConfigurer或实现WebSocketMessageBrokerConfigurer接口来配置端点前缀和消息代理。我使用简单的内存代理适合单机如果需要集群需配置RabbitMQ或Redis作为外部代理。Configuration EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { Override public void registerStompEndpoints(StompEndpointRegistry registry) { // 前端通过此端点建立连接允许跨域 registry.addEndpoint(/ws-chat).setAllowedOriginPatterns(*).withSockJS(); } Override public void configureMessageBroker(MessageBrokerRegistry registry) { // 客户端订阅消息的前缀 registry.enableSimpleBroker(/topic, /queue); // 客户端发送消息到服务器的前缀 registry.setApplicationDestinationPrefixes(/app); } }消息控制器使用MessageMapping注解处理客户端发来的消息使用SendTo或SimpMessagingTemplate向特定用户或频道广播消息。Controller public class ChatController { Autowired private SimpMessagingTemplate messagingTemplate; // 处理发送到/app/chat.send的消息 MessageMapping(/chat.send) public void sendMessage(Payload ChatMessage chatMessage) { // 1. 将消息保存到数据库sender, receiver, content, vehicleId, timestamp chatService.saveMessage(chatMessage); // 2. 通过消息模板发送给特定的接收者 messagingTemplate.convertAndSendToUser( chatMessage.getReceiverId().toString(), /queue/private, chatMessage ); } }前端连接使用SockJS和Stomp.js库建立连接订阅个人队列/user/queue/private接收消息。注意事项用户标识WebSocket连接建立时需要将连接会话Session与登录用户ID绑定。我通过在连接时传递JWT Token作为参数在HandshakeInterceptor中验证并绑定。离线消息用户离线时消息需要持久化到数据库。当用户下次上线并建立连接后主动拉取未读消息。心跳与断线重连网络不稳定时连接会断开前端需要实现心跳检测和自动重连逻辑。安全性务必验证每条消息的发送者身份防止用户冒充他人发送消息。在MessageMapping方法中可以从Principal参数获取当前已认证的用户信息。3.3 订单状态机与分布式事务考量订单流程是交易系统的生命线状态设计必须严谨。我定义的状态流转如下图所示在代码中用枚举OrderStatus定义待支付 --(支付成功)-- 已支付 --(客服确认)-- 待验车 | | (取消订单) (验车通过) | | V V 已取消 验车通过 --(办理过户)-- 待过户 --(过户完成)-- 已完成 | (验车不通过) | V 已取消退款流程实现方式在OrderService中任何改变订单状态的方法都必须先检查当前状态是否允许转移到目标状态。我设计了一个StateTransition工具类内部维护一个MapOrderStatus, SetOrderStatus定义所有合法的状态转移路径。public boolean transitionStatus(Long orderId, OrderStatus targetStatus, String remark) { Order order getById(orderId); if (!stateMachine.canTransition(order.getStatus(), targetStatus)) { throw new BusinessException(当前订单状态不允许执行此操作); } // 记录状态变更日志 OrderLog log new OrderLog(orderId, order.getStatus(), targetStatus, remark); orderLogService.save(log); // 更新订单状态 order.setStatus(targetStatus); updateById(order); // 触发状态变更后的后续操作如状态变为“已支付”则发送短信、生成合同 eventPublisher.publishEvent(new OrderStatusChangeEvent(this, order)); return true; }分布式事务问题在“支付成功回调”这个场景中涉及多个操作更新订单状态为“已支付”、生成交易流水、增加卖家待结算金额、可能还要调用第三方服务生成电子合同。这些操作可能跨多个数据库表甚至多个服务。为了保证一致性我采用了以下策略最终一致性主流选择支付回调接口只做最核心的操作——更新订单状态为“已支付”并记录一条“待处理任务”消息到数据库或Redis。然后立即返回成功给支付平台。后续的生成流水、结算等操作由一个独立的定时任务或监听消息队列的消费者来异步执行。即使后续步骤失败也可以通过任务重试机制来保证最终完成。本地消息表在同一个数据库事务中完成订单状态更新并插入一条“本地消息”记录。然后有一个后台线程扫描这张表将消息发送到MQ由其他服务消费。如果消息发送失败后台线程会不断重试。慎用分布式事务框架如Seata虽然能提供强一致性但会引入复杂度、降低性能。对于二手车交易这种对实时强一致性要求不是极端高的场景最终一致性是更务实的选择。心得订单状态的设计要预留扩展性。比如未来可能增加“退车中”、“争议处理中”等状态。我的做法是在状态枚举中留出一些间隔方便后续插入新状态。所有业务逻辑判断不要硬编码状态值而是通过状态枚举的方法如isPaid(),isCancelable()来判断。4. 关键业务逻辑与数据处理实战4.1 车辆搜索与筛选功能的Elasticsearch集成随着车辆数据增多单纯靠数据库的LIKE查询和多个WHERE条件筛选性能会急剧下降且无法支持复杂的相关性排序如根据车型、价格、里程、发布时间综合打分。我引入了Elasticsearch作为搜索引擎。集成步骤数据同步当车辆信息新增或更新时除了写入MySQL我还通过Spring Data Elasticsearch或手动调用Elasticsearch REST API将车辆数据索引到ES中。这里使用RabbitMQ解耦业务服务发送一条“车辆更新”消息由一个专门的“数据同步服务”消费消息并更新ES。防止主业务逻辑被ES的写入速度拖累。索引设计在ES中创建一个vehicle索引Mapping需要仔细设计。例如brand品牌、model型号使用keyword类型用于精确筛选同时使用text类型用于分词搜索。price价格、mileage里程integer或float类型用于范围查询和排序。title标题、description描述text类型配置合适的分词器如ik_smart。location所在地geo_point类型支持按距离排序。搜索服务实现构建一个VehicleSearchService接收复杂的搜索条件关键词、价格区间、品牌、车型、排序方式、分页动态构建Elasticsearch的BoolQueryBuilder并执行查询。public PageResultVehicleESDTO search(VehicleSearchParam param) { NativeSearchQueryBuilder queryBuilder new NativeSearchQueryBuilder(); // 1. 构建布尔查询 BoolQueryBuilder boolQuery QueryBuilders.boolQuery(); if (StringUtils.hasText(param.getKeyword())) { boolQuery.must(QueryBuilders.multiMatchQuery(param.getKeyword(), title, description)); } if (param.getMinPrice() ! null) { boolQuery.filter(QueryBuilders.rangeQuery(price).gte(param.getMinPrice())); } // ... 其他筛选条件 queryBuilder.withQuery(boolQuery); // 2. 排序 if (price_asc.equals(param.getSortBy())) { queryBuilder.withSort(SortBuilders.fieldSort(price).order(SortOrder.ASC)); } // 3. 分页 queryBuilder.withPageable(PageRequest.of(param.getPageNum()-1, param.getPageSize())); // 4. 执行查询 SearchHitsVehicleESDTO searchHits elasticsearchRestTemplate.search(queryBuilder.build(), VehicleESDTO.class); // 5. 封装结果 // ... }结果高亮对于关键词搜索可以使用withHighlightFields()来让匹配的标题或描述片段高亮显示提升用户体验。性能优化点索引分片与副本根据数据量预估设置合适的主分片数。副本分片提供高可用和读负载均衡。查询优化避免使用通配符查询*开头尽量使用过滤器filter上下文因为它可以利用查询缓存且不影响相关性评分。聚合查询用于实现“搜索页面侧边栏的筛选条件统计”例如根据当前搜索结果动态计算各个品牌下的车辆数量。这能极大提升筛选体验。4.2 定时任务与数据统计看板后台管理需要一个数据看板展示今日成交额、新增用户、热门车型等。这些数据不可能每次都实时从海量交易记录中聚合计算。解决方案定时任务预聚合。选择任务调度器Spring Boot内置了Scheduled注解简单易用适合单机部署。但对于集群部署多个节点同时执行同一个任务会导致数据重复计算。因此我选择了Quartz集群模式或者更轻量级的XXL-Job这类分布式任务调度框架。它们能保证同一任务在集群中只有一个实例执行。统计任务设计日度统计任务每天凌晨2点统计前一天的交易数据成交订单数、总金额、佣金收入、用户数据新增注册用户、车辆数据新上架车辆。将聚合结果写入statistics_daily表。看板上的“昨日数据”直接查这张表速度极快。热度更新任务每10分钟运行一次根据车辆近期的浏览量、收藏量、咨询量计算一个“热度分”更新到车辆表或ES索引中。用于首页和列表页的“热门推荐”排序。看板接口实现看板接口的数据来源分为两部分预聚合数据如昨日、前日、本月累计数据直接从statistics_daily表查询和汇总。实时数据如“今日实时成交额”则需要从当日的订单表中SUM计算。由于数据量小仅当天查询性能可以接受。为了进一步优化可以将实时数据也通过任务每分钟更新到缓存中。代码示例使用SpringScheduledComponent public class DailyStatisticJob { Autowired private OrderService orderService; Autowired private StatisticsDailyService statisticsDailyService; // 每天凌晨2点执行 Scheduled(cron 0 0 2 * * ?) public void calculateDailyStatistics() { LocalDate yesterday LocalDate.now().minusDays(1); // 1. 统计订单数据 OrderStatistic orderStat orderService.statisticByDate(yesterday); // 2. 统计用户数据 // ... // 3. 组装并保存到 statistics_daily 表 StatisticsDaily daily new StatisticsDaily(); daily.setStatDate(yesterday); daily.setOrderCount(orderStat.getCount()); daily.setTotalAmount(orderStat.getTotalAmount()); // ... statisticsDailyService.saveOrUpdate(daily); } }提示定时任务的cron表达式要写清楚注释。并且任务方法内部一定要做好异常捕获和日志记录避免因为单次任务失败导致后续任务不再触发。对于关键任务可以考虑增加失败告警如发送邮件或集成到监控平台。5. 安全、部署与性能优化实践5.1 系统安全防护要点一个交易系统安全是底线。我主要从以下几个层面做了防护认证与授权使用JWTJSON Web Token作为无状态认证方案。用户登录后服务器生成一个包含用户ID和角色的Token返回给前端。前端后续请求在Authorization头中携带此Token。后端通过拦截器HandlerInterceptor或过滤器Filter验证Token的签名和有效期并将用户信息存入SecurityContext。权限控制使用Spring Security的PreAuthorize注解例如PreAuthorize(hasRole(ADMIN) or #vehicle.sellerId authentication.principal.id)确保用户只能操作自己的车辆或管理员有权操作所有资源。数据安全SQL注入坚持使用MyBatis的#{}预编译占位符严禁在SQL中拼接用户输入。XSS攻击所有前端渲染的数据如车辆描述、用户评论在输出到HTML页面前必须进行转义。我使用Thymeleaf模板引擎它默认会对th:text输出的内容进行HTML转义。对于富文本内容如资讯详情则采用白名单过滤使用Jsoup库的方式只允许安全的HTML标签和属性。CSRF攻击在前后端不分离的传统项目中Spring Security默认提供CSRF防护。但在前后端分离如VueSpring Boot且使用JWT的场景下由于JWT通常存放在客户端如localStorage不依赖Cookie因此CSRF风险较低可以酌情禁用。但涉及敏感操作如支付的接口建议还是验证Referer头或使用自定义Token。敏感数据脱敏在日志、接口返回中对用户手机号、身份证号、银行卡号等敏感信息进行部分隐藏如138****1234。接口防刷与限流短信验证码接口必须图形验证码前置校验同一手机号在60秒内只能发送一次每天有发送次数上限。这些限制通过Redis的setex命令设置过期时间很容易实现。全局限流使用Guava的RateLimiter或集成Sentinel对核心接口如下单、支付进行QPS限制防止恶意爬虫或脚本攻击。5.2 使用Docker进行容器化部署为了让应用部署和环境一致我采用Docker进行容器化。编写Dockerfile# 使用官方Java运行环境作为基础镜像 FROM openjdk:8-jdk-alpine # 维护者信息 LABEL maintaineryour-emailexample.com # 设置时区 RUN apk add --no-cache tzdata \ cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ echo Asia/Shanghai /etc/timezone # 在容器中创建一个目录来存放应用 VOLUME /tmp # 将构建好的Spring Boot可执行jar包复制到容器中并重命名 ARG JAR_FILEtarget/*.jar COPY ${JAR_FILE} app.jar # 暴露端口与application.yml中server.port一致 EXPOSE 8080 # 指定容器启动时运行的程序 ENTRYPOINT [java,-jar,/app.jar]构建与运行# 在项目根目录包含Dockerfile和打包好的jar包执行构建 docker build -t used-car-system:latest . # 运行容器映射端口挂载配置文件目录和上传文件目录 docker run -d -p 8080:8080 \ --name used-car \ -v /path/to/your/application-prod.yml:/config/application.yml \ -v /data/upload:/data/upload \ used-car-system:latest这里通过-v将外部的配置文件挂载进容器这样修改配置无需重新构建镜像。上传目录也挂载出来避免容器重启后数据丢失。使用Docker Compose编排系统依赖MySQL、Redis、RabbitMQ、Elasticsearch。使用docker-compose.yml可以一键启动所有服务。version: 3.8 services: mysql: image: mysql:8.0 container_name: mysql-car environment: MYSQL_ROOT_PASSWORD: your_strong_password MYSQL_DATABASE: used_car_db ports: - 3306:3306 volumes: - mysql-data:/var/lib/mysql redis: image: redis:alpine container_name: redis-car ports: - 6379:6379 app: build: . container_name: springboot-app depends_on: - mysql - redis ports: - 8080:8080 environment: SPRING_PROFILES_ACTIVE: prod volumes: - ./config/application-prod.yml:/app/config/application.yml - upload-data:/data/upload volumes: mysql-data: upload-data:5.3 性能监控与优化建议系统上线后需要关注性能指标。监控集成Spring Boot Actuator启用后提供/actuator/health健康检查、/actuator/metrics指标如JVM内存、HTTP请求统计、/actuator/prometheus供Prometheus拉取数据等端点。Prometheus Grafana这是经典的监控组合。Prometheus定时抓取Actuator的指标数据Grafana用于配置炫酷的仪表盘监控QPS、响应时间、错误率、JVM状态、数据库连接池状态等。APM工具如SkyWalking、Pinpoint可以追踪分布式请求链路快速定位慢SQL或慢服务调用。数据库优化连接池使用HikariCP根据实际压力调整maximum-pool-size通常建议在10-20之间不是越大越好。索引优化使用EXPLAIN分析慢查询SQL为WHERE、ORDER BY、GROUP BY涉及的字段建立合适索引。注意避免索引失效的情况如对索引字段进行函数操作、使用!或NOT IN、模糊查询LIKE %xxx等。读写分离当读压力很大时可以考虑使用MySQL主从复制将读请求路由到从库。Spring Boot可以集成Sharding-JDBC或MyCat等中间件来实现。应用层优化缓存应用除了Redis还可以合理使用Spring Cache注解Cacheable,CacheEvict对不常变的热点数据进行本地缓存如Caffeine减少对数据库和Redis的访问。异步化将非核心、耗时的操作异步化如发送邮件/短信、记录操作日志、生成复杂报表。使用Spring的Async注解或消息队列。静态资源分离将图片、JS、CSS等静态资源放到Nginx或CDN上减轻应用服务器压力。6. 开发与调试中的常见问题排查在实际开发中总会遇到各种稀奇古怪的问题。这里记录几个我踩过的坑和解决方法。问题1Spring Boot应用启动后访问接口返回404。排查思路检查Controller路径确认RequestMapping或RestController注解的路径是否正确是否被SpringBootApplication主类所在的包或其子包扫描到。检查启动日志查看控制台启动日志是否有Mapped {[/api/xxx]}这样的信息。如果没有说明你的Controller没有被注册。包扫描问题如果你的Controller不在主类同级或子级目录需要在主类上使用ComponentScan手动指定扫描包路径。拦截器/过滤器拦截检查自定义的拦截器或过滤器是否错误地将请求拦截并返回了404。问题2MyBatis-Plus插入数据时字段为null的没有使用数据库默认值。原因与解决MyBatis-Plus的全局配置insertStrategy默认是NOT_NULL即当字段为null时插入语句会忽略该字段导致数据库默认值失效。解决方案在配置类中将insertStrategy和updateStrategy设置为IGNORED。Configuration public class MybatisPlusConfig { Bean public MybatisPlusPropertiesCustomizer plusPropertiesCustomizer() { return plusProperties - { GlobalConfig globalConfig plusProperties.getGlobalConfig(); GlobalConfig.DbConfig dbConfig globalConfig.getDbConfig(); dbConfig.setInsertStrategy(FieldStrategy.IGNORED); dbConfig.setUpdateStrategy(FieldStrategy.IGNORED); }; } }或者更精细地在实体类字段上使用TableField(insertStrategy FieldStrategy.IGNORED)注解。问题3使用Transactional注解的事务不回滚。常见原因异常类型不对默认只对RuntimeException和Error回滚。如果抛出的受检异常如Exception需要在注解中指定Transactional(rollbackFor Exception.class)。方法访问权限Transactional注解在代理模式下Spring AOP生效如果方法被定义为private、protected或是在同一个类内部调用this.method()事务会失效。确保方法是public的并且通过代理对象调用。数据库引擎不支持确认MySQL表使用的引擎是InnoDBMyISAM不支持事务。问题4前端Vue项目部署后刷新页面出现404。原因这是前端路由如Vue Router的history模式的典型问题。当你在/dashboard页面刷新时浏览器会向服务器请求/dashboard这个路径的资源而Spring Boot后端并没有这个接口。解决方案在后端Spring Boot中添加一个通用的错误页面控制器将所有未匹配到API路由的请求都转发到前端入口文件index.html。Controller public class FrontendController { RequestMapping(value {/, /{path:[^\\.]*}}) public String forward() { return forward:/index.html; } }同时确保你的静态资源index.html及打包后的JS/CSS位于Spring Boot默认的静态资源路径下如classpath:/static/。问题5Docker容器内应用时区不对。解决方案如前面Dockerfile所示在构建镜像时安装tzdata包并设置时区。这是最彻底的方法。也可以在运行容器时通过环境变量设置-e TZAsia/Shanghai但并非所有基础镜像都支持。这套二手车交易系统从零到一的搭建过程涉及了Spring Boot生态的方方面面。最大的体会是不要一开始就追求大而全的“完美”架构。先从核心业务流程跑通开始然后逐步迭代引入缓存、搜索、消息队列等中间件来解决具体的性能或架构问题。每一行代码、每一个设计选择最好都能说出“为什么”。这样构建出来的系统才是健壮、可维护、经得起推敲的。代码和详细注释我已经整理好希望能给正在类似项目中的你带来一些实实在在的参考和启发。本文还有配套的精品资源点击获取