
又到毕业设计季后台私信里问电子产品销售平台相关内容的最多。今年我不打算重复“跟着视频敲一遍”的老话而是站在一个过来人的角度把这类平台从0到1真正需要想清楚的几件事一次讲透技术栈怎么选、库表怎么设计、下单链路怎么保证不错账、答辩时老师最容易盯着哪里问。这篇文章是给打算用 Spring Boot Java 做电子产品销售平台的同学准备的前后端分离和服务端渲染两种路线我都会讲也会把我在实际带项目中踩过的坑拿出来分享。如果你正好准备做这个题目或者已经写了一半但心里没底建议完整看完尤其是第3和第4部分能帮你少走很多弯路。1. 项目整体设计与技术选型思路1.1 为什么是Spring Boot而不是SSH/SSM或者微服务电子产品销售平台算是一个“大而全”的经典题目有用户注册登录、商品浏览、购物车、下单、支付、后台管理还牵扯到库存和订单状态几乎把企业级Web系统的常见功能都覆盖了。这类题目能成为毕业设计热门说明它既不会简单到没东西可写又不会难到一个人根本做不完。技术选型上我看到很多同学纠结“要不要上微服务”。我的建议非常明确除非老师明确要求否则毕业设计不要主动上Spring Cloud。原因很简单微服务意味着要拆订单服务、商品服务、用户服务还要引入注册中心、网关、分布式事务任何一环出问题都会让演示现场失控。你花在环境搭建上的时间可能比写业务代码还多而答辩老师关心的是你业务逻辑到底理没理顺。Spring Boot的优势在于自动配置和内嵌Tomcat。自动配置帮你省掉一大堆XML配置业务代码写完一个jar就能跑。面对毕业设计这种一人开发、周期受限、以展示业务为主的场景Spring Boot几乎是最优解。方案上手难度开发效率答辩风险适用场景SSHStruts2SpringHibernate高配置繁琐低容易被问Hibernate缓存、Struts拦截器原理不推荐SSMSpringSpringMVCMyBatis中中能解释清楚Servlet流程加分但配置成本高时间充裕、想理解底层Spring Boot MyBatis-Plus低高主流、资料多、容易讲清楚毕业设计首选Spring Cloud 微服务高低分布式事务/服务调用容易翻车没有把握别碰1.2 前后端分离还是用Thymeleaf模板这是很多同学开场就卡住的问题。两种路线都能过但耗费的精力和答辩呈现效果完全不同。服务端渲染路线是后端直接用Thymeleaf渲染页面写起来像传统Web项目浏览器打开的每个页面都由后端拼接HTML返回。好处是整个项目只有一个Spring Boot进程部署简单、不用考虑跨域问题对前端基础薄弱的同学非常友好。坏处是后期改交互很痛苦而且简历里写“前后端未分离”在找工作时会被减分。前后端分离路线是Vue写前端Spring Boot只提供JSON接口最后把Vue打包后的dist目录放进Spring Boot的resources/static目录或者单独部署到Nginx然后接口走反向代理。很多同学搜“vue打包放进springboot”就是在处理这个部署细节。这条路线上手有一点门槛但好处非常明显功能模块边界清晰、接口风格贴近企业开发、答辨时有东西可讲。我个人更倾向推荐前后端分离。即便你是新手只要跟着流程走一遍Vue打包部署整个项目的工程化程度就会明显拉开和“纯CRUD管理系统”的差距。后面3.4章节我会专门讲图片上传4.1章节会讲跨域问题都是这条路线上必踩的坑。1.3 构建工具与版本组合这步选错能让你装环境装到自闭选了Spring Boot之后接下来就得确定JDK版本、Spring Boot版本、依赖管理方式。很多同学在这步栽跟头最常见的就是“springboot版本太高”导致JDK不兼容。Spring Boot 3.x从JDK 17起步如果你电脑或者学校机房还是JDK 8启动时直接报错查半天才发现是版本匹配问题。我建议的稳定组合如下组件推荐版本选型理由JDK1.8 或 11兼容性最好学校环境基本都是这个范围Spring Boot2.7.x资料最多、坑最少适合毕业设计MyBatis-Plus3.5.x内置分页插件、逻辑删除、代码生成器MySQL5.7 或 8.0建议8.0字符集用utf8mb4Maven3.6依赖管理利器配合阿里云镜像仓库能救命Vue2.7 或 3.x看你熟悉程度2.7也能打包出dist图片存储本地目录 或 MinIO本地最简单MinIO可作为论文创新点构建工具方面Maven是必须掌握的。IDEA新建Spring Boot项目时本身就是Maven工程pom.xml里管理依赖版本写清楚groupId、artifactId、versionMaven会自动把jar包拉下来。如果你发现依赖下载慢就在Maven的settings.xml里配置阿里云镜像仓库否则光等着下载依赖就能耗掉一小时。提示如果IDEA启动项目后报端口被占用先不要怀疑代码。在application.yml里修改server.port或者找到占用8080的进程结束掉。开发阶段改端口最省事。2. 数据库设计与核心业务模块拆解2.1 模块怎么划分表格怎么落先想清楚再写代码很多新手一上来就建表建完表直接写Controller最后代码堆成一团订单逻辑和购物车逻辑互相纠缠。电子产品销售平台虽然功能多但模块划分是有规律可循的。前台用户端的核心链路是注册登录 - 浏览商品 - 加入购物车 - 提交订单 - 模拟支付 - 查看订单。后台管理端的核心链路是管理员登录 - 商品上下架 - 订单发货 - 用户管理。另外还有公共能力文件上传、分页查询、统一异常处理、定时任务。对应到代码结构就是标准的Controller、Service、Mapper三层每个模块拆成独立的包比如controller/ProductController、service/OrderService、mapper/OrderItemMapper。模块之间不要互相调用Controller统一走Service。这个结构看起来基础但很多同学写着写着就把查询逻辑直接写在Controller里这是答辩时最容易被老师挑刺的地方。2.2 核心表设计从用户表、商品表到订单表表设计是数据库设计文档和答辩的重头戏。电子产品销售平台里“电子产品”往往意味着多规格比如手机有颜色、版本、内存差异所以我不建议把库存直接写在商品表里更合理的做法是建立商品表加商品规格SKU表。但考虑到毕业设计的体量很多同学只用一张商品表带stock字段也能说通。关键是主表关系要清晰。核心表我列一下sys_user用户表username、password、phone、avatar、role、statusproduct商品表title、sub_title、category_id、price、stock、sales、cover_image、detail、statuscategory商品分类表name、parent_id、sortcart_item购物车表user_id、product_id、quantity、checkedorders订单表order_no、user_id、total_amount、status、address_info、pay_timeorder_item订单明细表order_id、product_id、product_title、product_image、price、quantity、sub_totaladdress收货地址表user_id、receiver、phone、province、city、detailpayment_record支付流水表order_id、pay_amount、pay_type、pay_status、pay_time下面给几个关键表的建表SQL这是我在实测中验证过、字段不冗余也比较完整的版本CREATE TABLE sys_user ( id BIGINT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(100) NOT NULL, phone VARCHAR(20), avatar VARCHAR(255), role TINYINT DEFAULT 0 COMMENT 0普通用户 1管理员, status TINYINT DEFAULT 1 COMMENT 1启用 0禁用, create_time DATETIME, update_time DATETIME, deleted TINYINT DEFAULT 0, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE product ( id BIGINT NOT NULL AUTO_INCREMENT, title VARCHAR(200) NOT NULL, sub_title VARCHAR(300), category_id BIGINT, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL, sales INT DEFAULT 0, cover_image VARCHAR(500), detail TEXT, status TINYINT DEFAULT 1 COMMENT 1上架 0下架, create_time DATETIME, update_time DATETIME, deleted TINYINT DEFAULT 0, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE orders ( id BIGINT NOT NULL AUTO_INCREMENT, order_no VARCHAR(40) NOT NULL, user_id BIGINT NOT NULL, total_amount DECIMAL(10,2), status TINYINT DEFAULT 0 COMMENT 0待支付 1待发货 2已发货 3已完成 4已取消, address_info VARCHAR(500), pay_time DATETIME, create_time DATETIME, update_time DATETIME, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE order_item ( id BIGINT NOT NULL AUTO_INCREMENT, order_id BIGINT NOT NULL, product_id BIGINT NOT NULL, product_title VARCHAR(200), product_image VARCHAR(500), price DECIMAL(10,2), quantity INT, sub_total DECIMAL(10,2), PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;有几个设计细节很容易被忽视我重点说一下第一order_no订单号必须有唯一索引。如果用户连续点击两次提交订单没有唯一约束就很容易产生两条一模一样订单。第二order_item里一定要冗余商品标题和商品快照价格。因为商品信息可能会改、会删除但历史订单的明细必须保持原样不能关联查询时发现商品没了就什么都不显示。第三金额字段全用DECIMAL(10,2)不要用float或double。浮点数在Java和MySQL里做运算会有精度误差用户下单看到0.30000000000000004这种价格答辩时解释起来会非常尴尬。第四所有表都加上create_time和update_time这是我做项目的习惯。一方面方便排查数据问题另一方面论文里的E-R图和数据库设计表都需要这些字段后期补字段比一开始就加上更麻烦。2.3 订单状态流转用“状态机”思维而不是随手改字段订单状态是整个项目里最需要认真设计的地方之一。常见状态我定义为0待支付、1待发货、2已发货、3已完成、4已取消。这不是随便定义的每一个状态变化都有触发条件用户提交订单 - 状态0用户支付成功 - 状态1管理员后台发货 - 状态2用户确认收货或发货一段时间后自动确认 - 状态3待支付订单超时或用户主动取消 - 状态4为什么要强调状态机思维因为有同学写“订单取消”功能时不管当前状态是什么直接把状态改成4。比如订单已经发货了状态是2如果用户还能取消库存和物流就对不上了。所以每个状态变更前都要校验当前状态合法比如只有状态0才能取消已付款的订单要取消得走退款逻辑。这里有个生活化类比订单状态就像快递取件的流程——你必须先扫码取件才能出库不能人还没来快递就显示“已签收”。代码里提前把这些状态迁移规则固化下来能省掉后面大量判断。3. 核心功能实现与关键代码细节3.1 登录鉴权JWT还是Session选一个能讲清楚的就够了用户登录有两种主流方案Session和JWT。毕业设计里两者都行但如果你已经决定走前后端分离建议直接用JWT。原因在于Vue发请求时可以在Header里带Authorization后端拦截器校验token逻辑清晰答辩时也好讲。核心实现分三步登录成功后生成token前端每次请求带上token后端通过拦截器解析token把当前用户ID放进去。写一个简单的登录接口思路PostMapping(/api/login) public Result login(RequestBody LoginDTO dto) { // 1. 根据用户名查用户 // 2. BCrypt密码校验不能比对明文 // 3. 校验通过后生成token返回给前端 String token JwtUtil.createToken(user.getId(), user.getUsername()); return Result.success(token); }拦截器是鉴权的关键部分示例代码如下Component public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 跨域预检请求直接放行否则前端会莫名报跨域错误 if (OPTIONS.equalsIgnoreCase(request.getMethod())) { return true; } String token request.getHeader(Authorization); if (StringUtils.hasText(token) JwtUtil.checkToken(token)) { Long userId JwtUtil.getUserId(token); request.setAttribute(userId, userId); return true; } response.setStatus(401); response.setContentType(application/json;charsetutf-8); response.getWriter().write({\code\:401,\msg\:\未登录\}); return false; } }然后在配置类里注入拦截器并指定拦截路径registry.addInterceptor(loginInterceptor) .addPathPatterns(/api/**) .excludePathPatterns(/api/login, /api/register, /api/product/**);注意拦截器一定要放行登录、注册、商品列表这些不需要登录的接口。如果你把商品查询也拦了前端页面一打开就全是401排查起来会怀疑人生。密码存储方式也是答辩常问的点。明文存数据库绝对不行老师一眼就能看出来。MD5也不推荐因为没有加盐很容易被彩虹表破解。Spring Security自带的BCryptPasswordEncoder或者Spring Boot里的spring-security-crypto模块都能直接生成带随机盐的哈希值校验时matches(明文, 哈希)即可。我推荐在项目里直接用BCrypt理由说给老师听每次加密结果不同但又能校验通过这就是盐的作用。3.2 商品列表分页与多条件查询把细节做到位商品列表是前台最核心的入口。电子产品种类多用户会按分类、价格区间、关键字、上下架状态来筛选所以这里要处理分页和动态条件查询。MyBatis-Plus最方便的就是无需手写大量动态SQL。但要注意分页插件必须显式配置否则Page对象查出来没有数据或者数据全量返回这是新手高频问题。配置分页插件Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }查询商品列表的Service可以这样写public PageProductVO pageProducts(ProductQueryDTO query) { LambdaQueryWrapperProduct wrapper new LambdaQueryWrapper(); wrapper.eq(StringUtils.hasText(query.getKeyword()), Product::getTitle, query.getKeyword()) .eq(query.getCategoryId() ! null, Product::getCategoryId, query.getCategoryId()) .ge(query.getMinPrice() ! null, Product::getPrice, query.getMinPrice()) .le(query.getMaxPrice() ! null, Product::getPrice, query.getMaxPrice()) .eq(Product::getStatus, 1) .orderByDesc(Product::getSales); PageProduct page productMapper.selectPage( new Page(query.getPageNum(), query.getPageSize()), wrapper); // 这里要把Product实体转成ProductVO避免直接把库存返回给前端 }这个代码里有一个容易被忽略的点eq(Product::getStatus, 1)是用来过滤下架商品的。如果筛选条件漏了状态用户就能通过直接改接口参数看到下架商品这在答辩时是一个致命bug。另外返回给前端的VO不要直接包含stock库存、deleted逻辑删除字段。库存属于内部数据前台搜索接口把实时库存暴露出来可能出现和详情页不一致的情况。3.3 购物车到下单事务、库存扣减、价格重算一个都不能少购物车和订单是整个销售平台的心脏。很多同学以为下单就是把购物车数据原样搬进订单表这是大错特错的。下单过程有几个环节是必须严格把关的不能相信前端传过来的价格必须服务端从数据库重新查价扣库存时必须防止超卖即库存不够要直接失败下单失败时购物车不能留下脏数据所有数据库操作得在同一个事务里订单号必须唯一且要有一定规则方便后面查单先看核心方法骨架Transactional(rollbackFor Exception.class) public OrderVO createOrder(OrderCreateDTO dto) { // 1. 校验用户与收货地址 Address address addressService.getById(dto.getAddressId()); if (address null || !address.getUserId().equals(dto.getUserId())) { throw new BizException(收货地址不合法); } // 2. 查询要购买的购物车商品 ListCartItem cartList cartService.listByIds(dto.getCartIds()); if (CollectionUtils.isEmpty(cartList)) { throw new BizException(购物车为空); } // 3. 遍历商品服务端重新取价、扣减库存 BigDecimal total BigDecimal.ZERO; ListOrderItem orderItems new ArrayList(); for (CartItem cartItem : cartList) { Product product productService.getById(cartItem.getProductId()); if (product null || product.getStatus() ! 1) { throw new BizException(商品已下架); } // 乐观锁扣库存防止超卖 boolean deducted productService.deductStock(product.getId(), cartItem.getQuantity()); if (!deducted) { throw new BizException(product.getTitle() 库存不足); } BigDecimal itemTotal product.getPrice() .multiply(BigDecimal.valueOf(cartItem.getQuantity())); total total.add(itemTotal); // 构建订单明细快照 OrderItem item new OrderItem(); item.setProductId(product.getId()); item.setProductTitle(product.getTitle()); item.setProductImage(product.getCoverImage()); item.setPrice(product.getPrice()); item.setQuantity(cartItem.getQuantity()); item.setSubTotal(itemTotal); orderItems.add(item); } // 4. 生成订单主表订单号用时间戳随机数或雪花算法 Orders order new Orders(); order.setOrderNo(generateOrderNo()); order.setUserId(dto.getUserId()); order.setTotalAmount(total); order.setStatus(0); order.setAddressInfo(buildAddressText(address)); ordersService.save(order); // 5. 批量保存订单明细 orderItemService.saveBatch(orderItems); // 6. 删除购物车中已购买的商品 cartService.removeByIds(dto.getCartIds()); return OrderVO.from(order); }对应扣库存的Mapper语句我用的是乐观锁方式UPDATE product SET stock stock - #{quantity}, sales sales #{quantity} WHERE id #{productId} AND stock #{quantity}注意看WHERE stock #{quantity}这个条件。如果库存不足受影响行数为0那这个方法就返回false然后抛出异常让整个事务回滚。这种写法天然防住超卖而且不需要手动加悲观锁。你可以想象成抢演唱会门票系统只会在“剩余票数大于等于请求数量”时才会扣减否则直接拒绝。超时未支付订单也要处理。毕业设计可以做一个定时任务每5分钟扫描一次待支付订单判断创建时间是否超过30分钟超过就把订单改成取消状态并把库存加回商品表。这里务必注意先查订单状态再改状态再补库存避免用户刚好支付成功而定时任务又把订单取消的情况。3.4 图片上传本地目录、对象存储MinIO怎么选电子产品销售平台必然有商品图、轮播图、用户头像。图片上传如果处理不好会出现项目跑起来但图片加载不出来的尴尬情况。最简单的方案是把图片保存到本地磁盘比如项目根目录下的upload文件夹然后配置一个虚拟路径映射让/upload/**可以访问到磁盘上的文件。Spring Boot实现起来很简单Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { String uploadPath System.getProperty(user.dir) /upload/; registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadPath); } }上传接口不建议把文件存放在项目源码的resources目录。原因是项目打成jar包后resources目录在jar内部运行时没权限往里写文件。很多同学写完上传功能开发环境能跑打包部署后一上传就报错大多是这个原因。如果想让项目更有工程化味道可以把对象存储MinIO加进来。MinIO是一个开源的兼容S3协议的对象存储服务本地安装一个服务端然后Spring Boot里配置Endpoint、AccessKey、SecretKey、BucketName就能实现文件上传。对比一下方案优点缺点场景本地磁盘实现简单、不依赖额外组件打包部署需要注意路径基础演示MinIO架构合理、接口统一、可做创新点需要额外安装服务想体现工程化从实操角度说如果只想踏踏实实拿到毕业设计学分本地磁盘足够。如果你想让论文“基于Spring Boot的电子产品销售平台的设计与开发”里多一点技术亮点可以提MinIO但前提是演示环境必须能稳定启动MinIO服务否则答辩现场服务没启动就太尴尬了。3.5 后台管理的权限与统计做出来就是加分项后台管理模块经常被同学做成“能登录就行”但其实这正是最容易拉开分差的地方。权限控制上我建议用最简单的角色字段方案sys_user表里的role字段0代表普通用户1代表管理员。后台所有接口统一以/admin/**开头再配一个Admin拦截器检查当前登录用户role是否为1不是就返回403。这里不建议引入Spring Security的完整权限体系对毕业设计来说太重了而且配置出错后排查成本很高。统计功能是后台的亮点。首页可以展示总销售额、今日订单数、总用户数、商品总数下面配近7天订单趋势图和销量TOP5商品列表。这些数据用SQL聚合就能查出来-- 统计今日订单数 SELECT COUNT(*) FROM orders WHERE pay_time CURDATE() AND status ! 4; -- 统计总销售额只统计已支付和非取消订单 SELECT IFNULL(SUM(total_amount),0) FROM orders WHERE status IN (1,2,3); -- 近7天每天订单量 SELECT DATE(pay_time) AS d, COUNT(*) AS cnt FROM orders WHERE pay_time DATE_SUB(CURDATE(), INTERVAL 7 DAY) GROUP BY DATE(pay_time);这些查询本身难度不大但能在论文的“系统测试”或“数据库设计”章节里作为数据统计功能展示。前端再用ECharts画个柱状图视觉上很唬人老师看了通常会满意。4. 常见问题与踩坑实录4.1 前端报跨域POST请求变成了OPTIONS前后端分离开发时前端跑在8080端口后端跑在9090端口这时候浏览器会拦截跨域请求。现象是前端明明发起POST请求浏览器却先发一个OPTIONS预检请求如果后端没正确处理请求就失败了。解决办法是后端全局允许跨域Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }如果你用了拦截器还记得我在3.1节里写的“OPTIONS”判断放行吗这两个地方必须配合好否则预检请求会被拦截器拦下来前端一样会报跨域。4.2 Spring Boot版本太高导致JDK不兼容Spring Boot 3.x发布后很多同学在创建项目时习惯性选了最新版结果学校电脑JDK 8运行不了启动直接报UnsupportedClassVersionError。这个问题在热词里被称为“springboot版本太高”本质是Spring Boot 3.x要求JDK 17及以上。如果已经建了3.x的项目你有两条路把JDK升到17或者降级到Spring Boot 2.7.x并把pom里的依赖版本调整兼容。我的建议是直接降级到2.7.x因为网上资料、培训机构视频、论坛问答大部分都基于2.x遇到问题搜得到的概率大得多。毕业设计不追新稳定压倒一切。4.3 事务不生效的几种经典翻车Transactional注解是下单模块的核心但它不是万能的。我见过以下三种翻车情况而且都很隐蔽第一种同类中方法自调用。比如OrderServiceImpl里的createOrder调用了同类里的updateStock方法而updateStock上标了Transactional这不会生效。因为Spring的事务是通过代理对象实现的自调用相当于this.xxx()绕过了代理。解决方法是把库存扣减逻辑放到另一个Service类中或者用OrderServiceImpl代理对象调用自己。第二种异常被自己吃掉。Transactional默认只在RuntimeException和Error时回滚。如果方法里catch住了异常并返回了false事务就会以为一切正常造成部分SQL提交、部分SQL没提交。所以写下单逻辑时库存扣减失败要直接抛BizException让事务回滚。第三种数据库引擎是MyISAM。MySQL中MyISAM不支持事务InnoDB才支持。建表时如果没有指定ENGINEInnoDB事务注解就是摆设。默认字符集也建议用utf8mb4避免表情符号和生僻字乱码。提示Spring Boot默认使用CGLIB代理来创建代理对象所以加了Transactional的类不能是final类也不能用private方法作为事务边界。这一点答辩时会偶尔问到提前记住能加分。4.4 Long类型主键传到前端后精度丢失这是我非常想强调的一个坑。MySQL自增主键是BIGINT类型Java实体里对应Long数据库里可能存的是17823567192345678901这样的值。前端JavaScript的Number类型能精确表示的最大整数是2^53 - 1一旦超过这个范围后四位或后几位就会被舍入成0导致点击订单详情时拼接的id不对跳转失败。解决办法是在实体字段上添加序列化注解JsonSerialize(using ToStringSerializer.class) private Long id;或者配置Jackson全局处理让所有Long类型的字段都转成字符串序列化。开发时前端拿到的id是字符串就不会有精度丢失问题。这个坑在订单模块尤其常见因为订单号本身就是超长数字。4.5 常见问题速查表现象可能原因快速解法项目启动后访问页面404拦截器拦截了静态资源excludePathPatterns放行/static/**、/upload/**分页接口查不到数据MyBatis-Plus分页插件没配配置MybatisPlusInterceptor同一份数据重复提交多张订单前端重复点击或没有幂等处理用唯一订单号约束 前端按钮提交置灰上传图片后访问404本地存储路径映射没配置配置addResourceHandlers图片能传但文件名乱码未使用UUID或时间戳重命名保存时用UUID.randomUUID()长时间加载后页面登录失效token过期时间太短JWT有效期设置长一点或用refresh token思想前端数据更新后列表不刷新列表查询缓存或返回旧数据检查是否查了status0下架商品没过滤统计报表数据总不对没排除取消订单统计时加status IN (1,2,3)条件4.6 答辩前的功能演示清单项目做完不代表能顺利演示。我见过太多人开发环境跑得好好的一开答辩就翻车。这几个细节请务必在答辩前一晚过一遍第一准备一套漂亮的演示数据。商品图片不要用占位图至少上传6到8个真实感强的电子商品图片最好覆盖手机、耳机、键盘、显示器等品类价格有高有低才方便演示价格区间筛选。第二完整跑通一条下单链路。注册一个新用户添加购物车提交订单模拟支付然后在后台发货。这条链路必须顺滑中途不能有任何报错这比几十个冗余的CRUD页面都有说服力。第三不要把定时任务和超时逻辑放在答辩演示里展示。30分钟的等待时间演示不了如果你手动改数据库状态又容易被老师看出破绽。你只要在论文和口头上把“订单超时自动取消”的逻辑讲清楚即可。5. 论文与答辩相关的经验补充5.1 论文结构怎么搭才不会被骂“拼凑”毕业论文不是代码文档而是要求你从需求分析到设计再到实现形成一套完整逻辑。我建议按照这个骨架写需求分析章节写清楚系统的三种用户角色普通用户、管理员、游客每个角色能做什么配合用例图。重点体现“电子产品销售平台”的特殊性比如商品多规格、价格多变、库存敏感。总体设计章节写系统整体架构前后端交互方式功能模块图。如果用了Spring Boot自动装配、JWT、MyBatis-Plus都要在这里点到名字。数据库设计章节把E-R图画出来再把核心表的字段说明列表化。每个表都要解释为什么存在和哪张表关联比如order_item冗余商品快照的理由就是支持历史订单回溯。详细设计章节不要写流水账。挑3到4个核心技术点深度展开比如订单状态机设计、乐观锁防超卖、JWT拦截器鉴权、MinIO或本地图片上传方案。老师看论文的时候重点扫的就是这些“有技术含量”的地方。测试章节写功能测试用例表和部分核心接口测试结果。不用写一堆“点击按钮正常”的车轱辘话重点写异常场景比如库存不足下单、超时订单取消、拦截器拦截未登录请求。5.2 答辩高频问题与回答思路答辩不是考试老师不会故意为难你但基础问题躲不掉。我把高频问题整理成清单你闭着眼睛都能答上来基本就稳了。为什么选择Spring Boot做这个项目回答思路Spring Boot自动配置简化了传统SSM的XML配置内嵌Tomcat让项目一个jar就能跑同时生态完善社区资料多适合快速开发一个中小型Web系统。数据库表为什么这么设计回答思路围绕业务链路易联查的原则订单和订单明细拆分是为了支持一个订单多商品订单明细冗余商品快照是为了防止商品变更影响历史订单表都加逻辑删除和更新时间字段是为了维护和排查。订单超时未支付怎么处理回答思路定时任务扫描待支付订单超过30分钟改状态为已取消并恢复库存同时用状态机约束防止已支付订单被误取消。密码为什么要加密存储回答思路数据库泄露是常见风险明文密码直接暴露用户隐私BCrypt通过加盐哈希保证相同密码每次加密结果不同但校验依然可靠能有效抵御彩虹表攻击。怎么防止商品超卖回答思路扣库存SQL带stock quantity条件利用数据库行锁和受影响行数判断而不是Java代码先查库存再判断再更新避免并发场景下的竞态问题。项目的不足和改进方向回答思路可以承认当前用模拟支付后续可对接真实支付网关图片用本地存储可以迁移到MinIO或者云OSS单机部署可以改造成Nginx集群加Redis共享Session。5.3 时间不够怎么保节奏如果你现在已经完全没有时间了那我给你一个保底策略先把核心链路做通即注册、登录、商品列表、购物车、下单、模拟支付、后台发货。这几个功能跑通论文再按照五个章节套框架答辩基本能过。轮播图、优惠券、数据报表这些都是加分项有时间再补。最后分享一个我带人做项目时反复说的体会毕业设计的本质是证明你能独立完成一个完整的软件系统而不是做出一个商业平台。你不用把支付宝真实支付、真实短信验证码、高并发秒杀都堆进去把下单链路用事务和库存扣减做稳就是一份合格的设计。宁可核心功能打磨到挑不出毛病也不要堆一屏幕半成品功能。等你做完回头看最值钱的不是代码量而是你踩过这些坑之后真正理解了为什么订单系统要这么设计。