资讯动态

SpringBoot潮流时装商城系统实战:从商品展示到订单闭环的完整实现

发布时间:2026/10/6 9:46:59 来源:尧图企业网站定制
SpringBoot潮流时装在线商城系统是我这些年帮学生梳理毕业设计时碰到频率最高的一类选题。用一句话概括它就是基于SpringBoot搭起来的一个完整服饰电商展示与交易平台从首页商品浏览、购物车加购、订单结算到后台的商品管理和发货处理整条业务链路是闭环的。它在计算机毕设里的地位很特殊——功能覆盖够广难度又相对可控不像微服务、分布式那套东西让不少同学做到一半就卡死。如果你是准备拿Java后端方向找工作的人把这个项目的核心代码吃透面试聊项目经验时也比只会说“我做了个增删改查系统”的候选人有说服力得多。1. 项目概览SpringBoot时装商城到底在做什么先说清楚它解决什么问题。市面上很多服装电商系统要么只能看不能买要么只做后台管理没有用户端演示起来很干瘪。而这个项目把“逛店—看详情—加入购物车—下单—后台发货”串成了一整条线既能展示前端交互又能体现后端业务逻辑正好满足毕业设计“要有完整业务场景”的评审要求。我之前带过一个学生最初想做“服装展示网站”我跟他聊完直接劝他升级成商城。原因很简单纯展示网站只有商品查询和页面渲染技术点只到 MyBatis 的 select 和 Vue 的 v-for答辩老师一眼就看穿工作量。加上购物车和订单之后势必要处理事务、状态流转、库存扣减这些才是 Java 后端绕不开的核心能力。1.1 为什么SpringBoot是这类系统的首选很多同学会纠结既然学校教过 SSM为什么还要用 SpringBoot拿传统 SSM 搭项目你要手动配置 web.xml、Spring 容器、MyBatis 的 SqlSessionFactory光配置文件就有五六七八个其中任何一个路径写错启动就报 ClassNotFoundException排查半天结果只是少写了个扫描包。SpringBoot 把这些标配东西全部自动化了内嵌 Tomcat 让项目直接以 jar 包运行starter 依赖把常用组件的集成成本降到“加一行依赖”的程度。对于毕设项目时间成本是最现实的约束。用 SpringBoot 搭一个商城骨架我实测过在依赖下载好的情况下半小时内就能跑起来一个带数据库连接的 Web 服务。这套技术栈的学习资源也异常丰富遇到问题搜索“springboot mybatis 商品分页”答案一抓一大把不会像某些小众框架一样查了半天只有官方文档能看。它还有一个隐性优势SpringBoot 是目前国内中小型公司 Java 后端的事实标准。把这个毕设做完你简历上写的“基于 SpringBoot 的电商系统”面试官看到的是你掌握了自动装配、starter、AOP、事务管理等一整套工程化能力而不是单纯“会写 Java”。1.2 先把模块结构理清你其实在写四个子系统我拆这类项目时习惯把它分成四个逻辑块这样开发时可以并行推进答辩时也方便讲清楚。第一个是用户端商城。包括首页轮播图与分类导航、商品列表与关键词搜索、商品详情页多图展示、库存、尺码颜色、购物车、下单结算、收货地址管理、个人中心及订单列表。这部分的重点在“体验链路连贯”用户从随便逛逛到成功支付每一步都有对应的数据落库。第二个是后台管理端。管理员登录后可以做商品上下架、修改库存和价格、管理分类、查看用户订单并修改订单状态待支付、已支付、已发货、已完成。这里的核心是权限控制通常用拦截器加 JWT 实现只要没带合法 token 的请求一律拦截返回 401。第三个是公共服务层。文件上传商品图片、统一异常处理、统一返回格式、参数校验、定时任务比如自动取消超时未支付订单这些属于“基础设施”不直接面向用户但决定了项目能跑多稳。第四个是可选加分项。比如用 Redis 缓存热门商品减少数据库压力用消息队列异步处理下单通知或者用 HanLP 分词对商品名称做搜索优化。这些不用全上挑一个做透并在论文里写清楚就足够在评优环节拉开差距了。下面用一张表把这四块的职责和涉及技术列清楚模块核心职责主要技术点前台商城商品展示、搜索、购物车、订单SpringBoot、MyBatis、Vue、Redis可选后台管理商品、分类、订单、用户管理SpringBoot、拦截器、JWT公共服务文件上传、异常处理、定时任务MultipartFile、RestControllerAdvice、Scheduled扩展功能缓存、消息、搜索Spring Data Redis、ActiveMQ、HanLP很多同学一上来就写代码结果写到购物车发现商品表和订单表没关联回头再改表结构浪费大量时间。我建议不管你用不用 MyBatis-Plus先花两天时间把表设计和接口文档定下来后面开发会顺畅得多。2. 核心架构与数据库设计先把地基打稳2.1 分层架构Controller薄、Service厚SpringBoot 商城项目最经典的工程结构就是三层架构Controller 接收请求、校验参数Service 写业务逻辑Mapper 跟数据库打交道。很多人问为什么不能直接在 Controller 里操作数据库省点代码原因有两层。第一层是职责分离。下单这个动作涉及库存查询、价格计算、订单生成、购物车清空如果全塞在 Controller 里这个方法会有几百行任何改动都牵一发动全身。把业务逻辑下沉到 Service 后同样的业务可以在多个入口复用。举例来说后台管理员给用户补单和用户自己下单Controller 是两套入口但 Service 层可以共用同一个 createOrder 方法只是传入的用户 ID 不同。第二层是可测试性。Service 不依赖 Servlet 容器可以直接写单元测试调用。我在项目里给订单 Service 写过测试用例模拟各种库存和金额组合不需要启动整个 Web 服务就能验证逻辑对不对。下面是我常用的包结构你新建项目可以直接参考src/main/java/com/example/mall ├── MallApplication.java ├── config // 配置类WebMvc、Redis、Cors ├── controller // 控制层UserController、ProductController、OrderController ├── service // 业务层接口 impl ├── mapper // MyBatis Mapper接口 ├── entity // 数据库实体类 ├── dto // 入参对象、出参对象 ├── vo // 前端渲染用的视图对象 ├── common // 统一返回结果、异常类、常量 └── interceptor // 登录拦截器Controller 里不要写业务判断。比如“判断用户是否购买了该商品再允许评价”这种逻辑放 Service 里Controller 只负责拿到参数往里扔。统一返回体我习惯定义一个 Result 包含 code、msg、data 三个字段成功时 code200失败时抛出业务异常由 RestControllerAdvice 统一捕获转换成 JSON 响应。Data public class ResultT { private Integer code; private String msg; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.setCode(200); r.setMsg(success); r.setData(data); return r; } public static T ResultT error(String msg) { ResultT r new Result(); r.setCode(500); r.setMsg(msg); return r; } }这里有个容易被忽略的细节异常尽量不要在 Service 里 catch 住“吞掉”而是抛出自定义的 BizException让全局异常处理器去统一返回。否则你在 Service 里打印了 error 日志前端却收到一个 HTTP 200 的空数据排查线上问题会非常痛苦。2.2 数据库表设计那些答辩时容易被追问的细节数据库是整个商城的重中之重。我见过太多项目功能写了七七八八结果订单表里没有订单项表一个订单的商品只能存一个一看就是不懂“一对多”设计。下面这张表基本覆盖了潮流时装商城的所有核心数据表名说明关键字段user用户表id、username、password、nickname、phone、avatar、statuscategory商品分类id、name、parent_id、sortproduct商品表id、category_id、name、price、stock、main_image、detail、statusproduct_pic商品图片表id、product_id、url、sortcart购物车表id、user_id、product_id、quantity、checkedaddress收货地址表id、user_id、receiver、phone、province、city、detailorders订单表id、order_no、user_id、total_amount、status、create_time、pay_timeorder_item订单项表id、order_id、product_id、product_name、price、quantitybanner轮播图表id、image_url、link_url、sort、status几个设计要点值得展开说。第一订单表和订单项表一定要拆开。orders 表存每次下单的汇总信息比如总金额、收货地址快照、订单状态order_item 表存这个订单里买了哪些商品、每件多少钱、买了几个。为什么要存“商品快照”因为商品价格和名称会变如果直接关联 product 表用户三个月前下单的档案里商品名可能已经被管理员改掉了这在交易系统里是绝对不能接受的事情。第二金额字段一律用 decimal不要用 float 或 double。二进制浮点数表示小数是不精确的0.1 加 0.2 在 double 运算里可能等于 0.30000000000000004金额算错哪怕只差一分账都对不上。decimal 配合 Java 的 BigDecimal 才能保证货币精度。第三状态字段用 int 枚举不要直接用字符串。比如订单状态 0-待支付、1-已支付、2-已发货、3-已完成、4-已取消数据库里存数字Java 层用一个枚举类做映射。用字符串虽然看起来可读但一旦有拼写错误就会出现“查不到任何订单”的诡异问题而且索引也更大。第四给高频查询字段建索引。商品表的 category_id、status订单表的 user_id、order_no这些都是查询命中最频繁的字段。order_no 最好建唯一索引防止并发下生成重复订单号。索引不是越多越好但要建的这两个一定不能省。第五逻辑删除而不是物理删除。商品下架不一定要 delete 掉记录用一个 status 字段标记“已下架”即可。这样如果以后要恢复数据不需要从备份里找回而且商品和订单项的历史关联也不会断。3. 从零实现核心功能搭建工程到完成下单闭环3.1 工程搭建与基础配置先避开版本坑新建 SpringBoot 工程我建议直接用 IDEA 自带的 Spring Initializr或者去 start.spring.io 生成再导进来。需要特别注意版本匹配问题这里翻车的人非常多。SpringBoot 版本要求 JDK注意点2.7.xJava 8 及以上常用 8/11/17老项目兼容性好教材资料多毕业设计首选3.0.xJava 17命名空间从 javax 改为 jakarta3.3.x 及以上Java 17新特性多但部分老教程代码不兼容如果电脑装的是 JDK 8结果下了一个 Spring Boot 3.4 的工程启动会直接报 “UnsupportedClassVersionError”。解决方式就是把 Spring Boot 版本降到 2.7.x或者升级 JDK。对毕设而言Spring Boot 2.7.x 是资料最充足、踩坑成本最低的选择我带的项目基本都锁定这个版本。pom.xml 里核心依赖这样加parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies如果你的网络环境在 Maven 中央仓库下载很慢改用阿里云镜像能快不少这是老生常谈但每次都有人卡住。application.yml 是项目配置的中枢我的习惯是这样写server: port: 8080 servlet: context-path: /api spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mall?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 servlet: multipart: max-file-size: 10MB max-request-size: 20MB mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.mall.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl注意 url 里必须加serverTimezoneAsia/Shanghai否则高版本 MySQL 驱动会报时区错误。map-underscore-to-camel-case这个配置也建议打开这样数据库字段order_no能自动映射到实体类的orderNo少写一堆Results注解。还有一个小坑很多同学把静态资源直接放在src/main/webapp下用 SpringBoot 打包成 jar 时这个目录默认不会被处理。静态页面、图片、Vue 打包产物应该放src/main/resources/static或通过代码配置映射。3.2 商品展示一条典型的“查-传-显”链路商品展示是整个商城最表层的功能但它背后覆盖了一条完整的“请求到响应”链路很适合作为练手第一个功能。前端页面请求/api/product/list后端从 MySQL 查出商品经 Service 业务处理序列化成 JSON 返回Vue 渲染成商品卡片。Controller 层的写法尽量精简RestController RequestMapping(/product) public class ProductController { Autowired private ProductService productService; GetMapping(/list) public ResultPageResultProductVO list(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 12) Integer pageSize, RequestParam(required false) String keyword, RequestParam(required false) Integer categoryId) { return Result.success(productService.pageQuery(pageNum, pageSize, keyword, categoryId)); } }Service 里做分页和条件组装。这里我推荐直接用 PageHelper 插件三行代码解决分页不用手写 limit 和统计 countOverride public PageResultProductVO pageQuery(Integer pageNum, Integer pageSize, String keyword, Integer categoryId) { PageHelper.startPage(pageNum, pageSize); ListProduct list productMapper.selectByCondition(keyword, categoryId); PageInfoProduct pageInfo new PageInfo(list); // 转成 VO处理图片地址等字段 ListProductVO voList list.stream().map(this::convertToVO).collect(Collectors.toList()); return PageResult.build(voList, pageInfo.getTotal()); }对应 MyBatis 的 XML 里动态 SQL 这样写select idselectByCondition resultTypecom.example.mall.entity.Product select * from product where if testkeyword ! null and keyword ! and name like concat(%, #{keyword}, %) /if if testcategoryId ! null and category_id #{categoryId} /if and status 1 /where order by sort desc, create_time desc /select关于商品图片我见过不少项目把图片存到数据库的 BLOB 字段里这个方案千万慎重。数据库体积会迅速膨胀备份和查询都变慢而且浏览器加载图片还得走一遍后端接口性能很差。正经做法是图片上传到服务器本地目录、FastDFS 或对象存储 OSS数据库只存 URL。方案优点缺点适用场景本地磁盘存储实现简单毕设够用迁移不方便需配置虚拟路径本机演示、课程设计FastDFS性能较好支持分布式环境搭建稍繁琐有 Linux 服务器且想加分阿里云 OSS稳定有 CDN 能力要买服务可能要实名认证真实上线、简历项目七牛云有免费额度域名备案要求较多预算有限的个人项目毕设用本地磁盘存储时有个要注意的配置上传后的文件在磁盘上要设置静态资源映射才能访问。比如上传到D:/mall-images/需要在配置类里这样写Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceHandler(file:D:/mall-images/); } }前端页面里图片路径直接用/upload/xxx.jpg就能显示。注意别把图片路径写死成localhost:8080否则部署到服务器就全裂图了要写相对路径或通过配置取 Host。3.3 购物车与订单一个事务就该这么用购物车逻辑相对简单很多同学会直接insert一条记录。但实际应该先查购物车里有没有同一用户、同一商品的数据有则更新数量没有才新增。否则用户每点一次“加入购物车”就多一条记录购物车列表越看越乱。public void addToCart(Long userId, Long productId, Integer quantity) { Cart cart cartMapper.selectByUserAndProduct(userId, productId); if (cart null) { // 校验商品是否存在且处于上架状态 Cart newCart new Cart(); newCart.setUserId(userId); newCart.setProductId(productId); newCart.setQuantity(quantity); newCart.setChecked(true); cartMapper.insert(newCart); } else { cart.setQuantity(cart.getQuantity() quantity); cartMapper.updateById(cart); } }订单模块才是整个系统技术含量最高的地方。下单流程我总结成六步校验商品与库存、计算金额、扣减库存、生成订单主表、生成订单明细表、清空购物车。这六步要么全部成功要么全部失败绝不能出现“订单创建了但库存没扣”的状态。这正是Transactional的用武之地。在 Service 方法上加上事务注解默认任何 RuntimeException 都会触发回滚把中间已经执行的操作全部撤销Transactional(rollbackFor Exception.class) public OrderVO createOrder(Long userId, ListOrderItemParam items, Long addressId) { // 1. 遍历商品查询库存并计算总价 // 2. 扣减库存update product set stock stock - ? where id ? // 3. 生成订单号 // 4. 插入 orders // 5. 遍历插入 order_item // 6. 删除购物车中对应商品 }rollbackFor Exception.class这个参数千万别省。默认情况下Transactional只在遇到RuntimeException时回滚如果业务代码里抛的是受检异常比如 IOException事务不会回滚数据就走样了。写项目时一定要养成习惯统一写上rollbackFor Exception.class这点答辩论上也很加分。事务失效也是高频考点至少这几个场景你必须知道方法被private修饰时注解不生效同类内部调用this.createOrder()不走代理事务也不生效异常被 try-catch 捕获而不抛出事务无法感知也失效。最常见的锅就是在 Service 内部直接调用另一个带Transactional方法改成一个单独的类注入调用事务就正常了。订单号生成不要用自增 ID 当订单号往外给。第一自增 ID 会暴露系统日单量第二多表合并数据时容易撞。通用做法是yyyyMMddHHmmss 用户ID后四位 随机数大约 20 位唯一性基本可保证。如果想更专业可以了解下雪花算法它在分布式环境下也能生成趋势递增的唯一 ID毕设论文里写这个点能增加不少技术含量。订单状态机这块建议定义枚举类public enum OrderStatus { PENDING_PAYMENT(0, 待支付), PAID(1, 已支付), SHIPPED(2, 已发货), COMPLETED(3, 已完成), CANCELLED(4, 已取消); }用定时任务扫描超过 30 分钟仍未支付的订单自动把状态改成已取消同时恢复库存。实现方式很简单启动类或配置类上加上EnableScheduling然后在方法上写Scheduled(cron 0 0/5 * * * ?) public void autoCancelExpiredOrders() { // 查出待支付且创建时间早于当前时间30分钟的订单 // 把状态改为已取消 // 恢复商品库存 }这个点完全可以当成论文里的“系统优化设计”来写能体现你对真实业务场景的理解。3.4 热门款并发扣库存给毕设加分的硬核点商城项目最容易在答辩时被追问的就是如果热门商品同时被几百人下单怎么防止超卖如果你回答“库存先查再减不够就提示”老师会追问“你确定同一时间只有一个请求在操作数据库吗”这时候普通代码就露馅了。先看错误的实现先select stock在 Java 里判断 stock 0再update stock stock - 1。在高并发下多个请求都读到库存为 1都判断可以下单然后各自执行更新最终库存变成负数这就是超卖。解决办法是在 SQL 层面做原子判断也就是乐观锁update product set stock stock - #{quantity} where id #{productId} and stock #{quantity}这条 SQL 的意思是只有当库存扣减后仍然不小于 0 时更新才成功。数据库的行锁保证同一时刻只有一个请求能成功执行这条语句其他请求受stock #{quantity}条件影响受影响行数为 0随即可抛出“库存不足”的异常。Java 侧逻辑int rows productMapper.deductStock(productId, quantity); if (rows 0) { throw new BizException(手慢了商品已售罄); }这样处理的好处是不用引入额外中间件逻辑清晰答辩也好讲。更进一步的方案是引入 Redis 预扣库存先把库存预加载到 Redis用decr命令扣减扣减成功后再异步落库。这个方案能抗更大的并发但对毕设来说属于“加法”做完乐观锁并发扣减再在论文里提一句 Redis 方案的思路就已经超过 90% 的同类项目了。在这个模块我还想提醒一个事务细节如果你把deductStock和创建订单放在同一个Transactional方法里那超卖还是不可能发生的因为更新行锁会一直持有到事务结束。之所以很多同学压测还是有超卖多半是事务没生效或者update的 where 条件没带库存判断。4. 答辩与面试必考SpringBoot底层原理怎么讲很多同学功能写完了但答辩时一问 SpringBoot 原理就支支吾吾。这里我给你押几个必问题内容不深但讲出来已经很显功底。4.1 自动装配原理一道高频送分题面试官最常问的就是“SpringBoot 自动装配是怎么实现的”。你可以按这个思路回答。SpringBootApplication其实是个组合注解它包含了SpringBootConfiguration、ComponentScan和EnableAutoConfiguration。自动装配的核心是EnableAutoConfiguration它通过Import(AutoConfigurationImportSelector.class)引入了一个选择器这个选择器会去读取META-INF/spring.factories文件新版是META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports把里面列出的所有自动配置类全部加载进容器但最终是否生效要看每个配置类上的条件注解。以我们项目里用到的 Web 自动配置为例WebMvcAutoConfiguration上面会有类似这样的条件Configuration ConditionalOnWebApplication(type Type.SERVLET) ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class }) ConditionalOnMissingBean(WebMvcConfigurationSupport.class)简单说就是你的项目里引入了 spring-boot-starter-web类路径下存在 servlet 相关类SpringBoot 就帮你自动创建 DispatcherServlet、配置静态资源处理器、注册消息转换器。如果类路径下没有这些类条件不满足自动配置不会生效。这样回答已经把“自动配置”讲清楚了。再补充一句这也是为什么很多坑明明没写配置功能却能跑起来——SpringBoot 在背后按条件加载了默认配置但你要覆盖默认配置时定义自己的同名 Bean 即可。4.2 SpringBoot面试高频问题速答把商城项目里会碰到的几个高频面试题整理成速答版背熟再进面试间。SpringBoot 默认使用 CGLIB 代理还是 JDK 动态代理早期 Spring 默认使用 JDK 动态代理只有目标类没有接口时才用 CGLIB。但在 SpringBoot 2.x 以后官方默认设置spring.aop.proxy-target-classtrue也就是优先使用 CGLIB。原因是 CGLIB 通过生成目标类的子类来代理不要求目标类实现接口对大多数没提炼接口的 Service 类来说更省事。要注意如果你在项目里同时引用了spring-boot-starter-aop默认走的就是 CGLIB。starters 的工作原理简单说就是依赖管理和自动配置的结合体。你引入spring-boot-starter-data-redis它会自动把 Redis 客户端依赖带进来同时对应的自动配置类检测到类路径有 RedisTemplate 相关类就会自动创建连接工厂和模板 Bean。定时任务这块EnableScheduling开启调度Scheduled声明方法支持 cron 表达式和 fixedDelay。需要注意 cron 表达式是六位还是七位Spring 里通常是六位秒 分 时 日 月 周别把星期填到月份位置。如果论文里写了消息队列用 ActiveMQ 举例。引入spring-boot-starter-activemq配置spring.activemq.broker-url注入JmsTemplate发送消息用JmsListener(destination order.queue)监听队列即可。这个组合在毕设文档里写出来看起来比单纯用 Redis 更“中间件化”实际上手也不复杂。4.3 演示流程与答辩话术演示项目时我建议按“五步走”先展示首页介绍商品分类和搜索接着点进详情页说明多图和库存展示逻辑然后加入购物车走到结算页最后模拟下单演示订单生成和后台发货流程收尾前再打开数据库切换订单状态给老师看数据变化。整个流程控制在十分钟内别在页面样式上花太多时间重点让老师看到数据在流动。老师常追问的几个问题提前准备订单表和订单项表为什么拆开答一个订单有多个商品一对多关系拆开才能存数量和单项金额同时保留商品快照。库存扣减怎么保证原子性答使用update ... where stock ?数据库行锁保证并发下只有一个请求成功。事务在什么情况下会失效答private 方法、同类内部调用、异常被吞、事务方法在非 Spring 管理的对象上调用等。为什么用 Redis答把热点商品和首页轮播数据缓存起来减轻数据库查询压力。用户密码怎么存的答BCrypt 加密后存哈希值绝不存储明文。接口怎么保证安全答前端登录后拿 JWT后续请求带在 Header 中后端拦截器校验 token 并解析用户身份。这些问题答清楚答辩基本稳了。5. 部署上线与常见问题排查从本地跑到服务器5.1 Vue打包进SpringBoot的两种姿势毕业设计通常要现场演示最好能做到“双击启动、浏览器访问”不要每次演示都开三个终端。如果你用 Vue 写前端最省事的办法是把前端构建产物直接并入 SpringBoot jar 包整个项目变成一个可执行的 jar任何有 JDK 的机器都能跑。第一种做法Vue 项目执行npm run build生成dist目录把里面的index.html和static具体路径看你的项目可能是assets复制到 SpringBoot 的src/main/resources/static下。SpringBoot 默认把 static 目录映射为根路径/所以访问http://localhost:8080/index.html就能看到页面。但注意前端写的api请求地址要改成相对路径或者用 vite/webpack 代理处理跨域否则页面和接口域名不一致会出问题。第二种做法更灵活不复制文件而是在配置里指定静态资源路径spring: resources: static-locations: - file:./web/前端构建产物解压后放到 jar 同级的web目录这样前端资源可以独立更新不用重新打包整个后端 jar。这个方式部署在服务器上非常方便我实际项目里经常这么干。如果想更正规一点前端单独部署到 Nginx后端接口通过反向代理转发到 8080 端口前端和后端彻底分离。毕设演示前如果服务器环境允许这个方案体验最好但需要多配一份 Nginx 配置文件自己权衡成本。部署到服务器时很多同学会在宝塔面板里用 Docker 部署。核心步骤是写好 Dockerfile 把 SpringBoot jar 打成镜像再用docker run -p 8080:8080启动MySQL 可以用宝塔自带的数据库服务然后注意放行安全组端口。记住容器里的 MySQL 地址不能写成 localhost要写宿主机 IP 或 Docker 容器名这是一个常见的新手坑。5.2 新手最容易踩的八个坑我做毕设指导这些年把 SpringBoot 商城项目里学生踩得最多的坑整理成一张速查表遇到问题先对着排查现象根本原因解决方式启动报端口被占用8080 已被其他进程占用netstat -ano找进程杀掉或改 server.portJDK 版本不匹配用的是 SpringBoot 3.x 但 JDK 是 8降到 2.7.x 或升级 JDK 17中文乱码数据库连接缺少 utf8 配置url 加 useUnicodetruecharacterEncodingutf8Mapper 方法找不到Mapper 接口没被扫描启动类加 MapperScan或在接口上标 Mapper静态资源 404文件放错目录放 src/main/resources/static 下打包后无法启动缺主类配置pom 里配 spring-boot-maven-plugin保证 mainClass 正确访问 /api 404context-path 配置后路径变了请求地址要带上配置的 context-path 前缀本地正常部署后异常数据库地址写成了 localhost容器内要换成宿主机 IP 或容器网络别名还有一个很隐蔽的坑MyBatis 里实体类的Date字段JSON 序列化默认输出时间戳或 UTC 时间前端显示会差 8 个小时。在 application.yml 里配一个 Jackson 时间格式spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8这样接口返回的 create_time 就是正常可读的字符串也不用前端再做格式化。另外每次改完实体类字段如果发现 SQL 报“列不存在”先看表结构是不是真的加了字段再看 MyBatis 的 resultMap 和实体类字段能不能对上。这类问题 80% 不是代码逻辑错而是表结构没有同步。我在实际指导过程中最深的体会是SpringBoot 时代的商城项目功能能不能跑起来很简单但真正的分水岭在于你有没有把每个关键选择想明白。为什么订单拆两张表、为什么扣库存用一条 SQL、为什么事务要指定 rollbackFor这些才是老师愿意给高分的点也是你面试时能讲出深度的素材。做完这个项目如果你能顺手把这几个原理问题讲透它就不只是一份毕业设计而是你 Java 后端能力的一次系统验证。如果想继续往上走后面可以加 Spring Security 做细粒度权限、用 AOP 记录操作日志、或者给搜索模块接入 HanLP 做分词优化这些方向都足够做进阶版的论文产出。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑