十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Spring Boot图片销售系统毕设:从数据库设计到答辩全流程解析

Spring Boot图片销售系统毕设:从数据库设计到答辩全流程解析 从每年带毕设和帮人改项目的经验来看图片销售系统是个被低估的好选题。它没有智慧校园推荐系统这类题目听起来唬人但覆盖的业务链路非常完整用户认证、图片上传、商品展示、下单支付模拟、订单管理、权限控制每一环都是计算机专业的核心知识点而且全部可以在单机环境下跑通。更关键的是这个题目自带演示优势——答辩现场打开页面从上传一张图片到模拟购买完成十分钟就能展示完整个业务闭环评委想挑毛病都不好挑。这篇文章我把整个系统的技术选型逻辑、数据库设计思路、核心代码实现、答辩追问准备以及拿到源码后怎么改造吃透全部按我做项目时的真实顺序写一遍。适合正在做Spring Boot方向毕业设计、或者想快速上手一个JavaWeb完整项目的同学参考。所有代码都是我在实际项目中验证过的写法不是网上那种跑不通的碎片代码。1. 为什么说这个选题稳妥业务复杂度正好卡在及格线以上毕设选题的第一原则不是看上去厉害而是能在规定时间内完成并且能讲清楚。图片销售系统正好卡在这个位置——它比单纯的CRUD管理系统多了一层销售闭环但又没有复杂到需要分布式事务、消息队列、高并发缓存这些短期内啃不动的技术。1.1 业务天然覆盖核心知识点图片销售系统的本质是电商系统的简化版。电商的完整链路是用户浏览商品 → 加入购物车 → 下单 → 支付 → 收货 → 售后。图片销售系统把收货替换成下载原图把售后弱化剩下的是完整的交易主链路。这意味着你在项目里必须处理这几类硬核问题文件上传与存储上传图片、生成预览图、控制访问权限。权限认证区分普通用户、图片上传者卖家、管理员三种角色。订单状态流转待支付、已支付、已完成、已取消每个状态之间的转换规则。数据一致性下单时同时写入订单表和订单明细表一个失败则全部回滚。这些问题每一个都能在答辩时展开讲几分钟既不冷场也不超纲。评委问你的项目难点是什么你随便挑一个都能给出具体的解决方案而不是含糊地说实现了增删改查。1.2 演示效果直观不需要准备数据很多选题最大的痛点是演示时的数据荒。比如推荐系统你很难现场准备一份像样的用户行为数据再比如基于大数据的分析平台跑出来的图表可能又空又假。图片销售系统没这个问题——你现场上传两张图片设个价格模拟支付整个流程就出来了。图片本身就有视觉冲击力比纯文本的菜单管理、用户管理演示起来生动得多。这个选题适合谁我认为三类人最适合第一Java基础中等、想稳扎稳打过关的人第二项目经验不多、需要一个完整项目来建立信心的应届生第三运维和测试能力一般、不想在自己电脑上折腾分布式环境的人。如果你是这三类中的一类这个题目的难度曲线非常友好。2. 技术选型不是越新越好这套组合的每个决策理由选型是毕设的第一步也是我见人翻车最多的地方。很多人为了简历上好看非要在毕设里塞一堆微服务组件结果环境配置三天三夜都搞不定。我的建议很朴素满足需求、你能讲清楚、部署不折腾这三点比什么都重要。2.1 Spring Boot版本锁死2.7.x别碰3.xSpring Boot 2.7.x是2.x系列的最终版本成熟度极高社区资料最丰富。为什么不用3.x因为3.x强制JDK17及以上而很多学校的电脑、个人老笔记本装的是JDK8。Spring Boot 2.7.x在JDK8下运行毫无压力而且你遇到的大部分报错搜索引擎都能找到对应的解决方案。毕业设计的评价标准不是技术新而是完整可用。老老实实待在2.7.x可以帮你避开至少三天的环境折腾时间。2.2 MyBatis Plus单表CRUD的天花板图片销售系统大概有用户、图片、订单、订单明细这几张核心表其中90%的操作是单表CRUD。MyBatis Plus的BaseMapper直接内置了insert、selectById、updateById、deleteById这些常用方法连SQL都不用写。联表查询的场景不多——比如订单列表需要关联图片信息写个XML自定义SQL就能搞定。相比JPAMyBatis Plus的SQL可读性和可控性更好答辩被问到你的SQL是怎么优化的你可以直接打开XML文件指给老师看底气很足。顺便说一句MyBatis Plus的分页插件也是毕设里的高频考点。在配置类里加一个MybatisPlusInterceptor往里注册PaginationInnerInterceptor然后在代码里直接调Page对象就行。这个功能做图片列表的分页展示时一定会用到建议提前看一眼官方文档五分钟就能配置好。2.3 前端方案Vue 3 Element Plus是默认答案图片销售系统的前端分为买家端和管理员端。买家端需要图片瀑布流展示、详情弹窗、下单按钮管理员端需要图片上传表单、订单管理表格、数据统计卡片。我推荐Vue 3 Element Plus原因是Element Plus把表格、上传组件、弹窗、表单校验这些后台管理界面的高频组件都封装好了你不用从零写CSS。这里要强调一个经常被忽视的点前端工程化的构建流程对毕设来说是一个隐性成本。如果你对Node.js、npm、Vite这些工具链不熟悉光是把一个Vue项目从拉取依赖到成功打包就可能花掉一整天。我给你的建议是如果前端基础一般直接用Spring Boot的Thymeleaf模板引擎做页面后端一个项目全部搞定不用处理跨域、不用配置Node环境开发周期直接砍半。本文后面讲的接口逻辑无论你前端用Vue还是Thymeleaf都是通用的不用纠结。2.4 一张表理清技术栈对应的需求我做过一个对比方便你根据自己情况做决定技术点推荐方案备选方案我的建议后端框架Spring Boot 2.7.xSpring Boot 3.x锁死2.7兼容JDK8ORMMyBatis PlusSpring Data JPAMyBatis Plus更可控数据库MySQL 8.0MySQL 5.7MySQL8连接串记得加时区前端方案Vue 3 Element PlusThymeleaf模板按个人前端基础定鉴权方案JWT 拦截器SessionJWT更贴合主流图片存储本地磁盘阿里云OSS本地够用写配置别写死3. 数据库设计四张核心表的字段决策和关联关系数据库是答辩时评委必看的部分。图片销售系统不需要复杂的表结构但每一张表、每一个字段都要能说出为什么这么设计。下面我按核心表到辅助表的顺序拆解。3.1 用户表一个role字段就够了不要做角色表、用户角色关联表那种三表结构在毕业设计里这是过度设计。一张user表加一个role字段用数字区分角色0管理员、1卖家、2买家已经足够支撑系统的权限判断。答辩时如果老师问权限设计你可以这样回答系统采用轻量级RBAC设计以用户表的role字段区分角色在拦截器中进行角色校验。这种设计适合角色数量少、业务规模小的场景提高了权限判断的效率。这个回答既展示你懂RBAC的概念又解释了为什么做简化很加分。用户表必含字段id、username、password加盐哈希后的密文、nickname、role、status是否禁用、create_time。password字段用bcrypt加密不要用MD5——MD5已经被彩虹表破解得差不多了答辩时这个点也容易加分。3.2 图片信息表核心业务表字段设计有讲究CREATE TABLE image ( id INT(11) NOT NULL AUTO_INCREMENT COMMENT 图片ID, title VARCHAR(100) NOT NULL COMMENT 图片标题, description TEXT COMMENT 图片描述, category_id INT(11) DEFAULT NULL COMMENT 分类ID, price DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT 售价, file_path VARCHAR(255) NOT NULL COMMENT 原图存储路径, preview_path VARCHAR(255) DEFAULT NULL COMMENT 预览图路径, resolution VARCHAR(50) DEFAULT NULL COMMENT 分辨率如1920x1080, format VARCHAR(10) DEFAULT NULL COMMENT 格式如JPEG/PNG, uploader_id INT(11) NOT NULL COMMENT 上传者ID, status TINYINT(1) NOT NULL DEFAULT 1 COMMENT 状态1上架 0下架, is_delete TINYINT(1) NOT NULL DEFAULT 0 COMMENT 逻辑删除标记, download_count INT(11) NOT NULL DEFAULT 0 COMMENT 下载次数, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 上传时间, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT图片信息表;几个字段的设计逻辑你写论文和答辩时可以直接用price用DECIMAL(10,2)不用float/double这是老生常谈但依然高频出错的问题。浮点数在二进制中无法精确表示小数累加会出现0.10.2!0.3的尴尬。金额必须用定点数DECIMAL。status和is_delete分开status代表业务状态上架/下架is_delete代表逻辑删除。为什么不用物理删除因为图片可能被历史订单引用物理删了订单明细里的关联就断了。用逻辑删除带条件查询的时候过滤掉is_delete1的记录即可。preview_path单独存储销售场景下列表页和详情页展示的应该是压缩过的预览图而不是原图。原图留在file_path里只对已购买用户开放下载。这个设计和真实图片素材网站的做法一致毕业论文里的系统设计亮点可以直接写这一段。3.3 为什么购物车表可以选择不做购车车是电商的标准组件但图片销售系统有特殊性图片是虚拟商品用户在详情页看到预览图、确认价格、直接下单决策时长通常不超过一分钟。购物车用来暂存多个商品稍后一起结算的价值在虚拟商品场景下很弱。所以我建议直接砍掉购物车表走详情页直接下单的模式让核心链路更短。如果你为了让模块看起来丰富坚持要做购物车也很简单——建一张cart表存user_id和image_id两个字段就行不过做之前先想清楚答辩时怎么解释购物车对图片销售的必要性。3.4 订单表和订单明细表一对多拆分的理由一张订单可能包含多张图片现实中常见的是买家一次性购买同一个作者的多张图打包授权所以需要拆成两张表。order表存订单公共信息id、order_no订单号、user_id买家ID、total_amount总金额、status订单状态、create_time、pay_time。order_item表存每个条目id、order_id外键关联订单、image_id购买的图片、price下单时的快照价格。这里有一个容易被忽视的知识点——item表为什么要冗余一个price字段因为图片价格可能在上架后被修改而订单明细里的价格应该保留买家下单那个时刻的价格。这就是所谓的价格快照设计在真实电商系统里是标配。答辩时讲出这个理由评委就知道你是真做过功课的。4. 图片上传与访问安全这个模块做好了答辩直接上分图片上传是图片销售系统的技术门面。我见过太多毕设在上传这环翻车没做格式校验、文件重名互相覆盖、上传路径写死、原图裸奔可访问……这些问题其实都不难解决关键是你要把每一步都做到位。4.1 上传接口的完整实现格式、大小、路径三步校验RestController RequestMapping(/api/image) public class ImageController { Resource private ImageService imageService; PostMapping(/upload) public Result upload(RequestParam(file) MultipartFile file, RequestParam(title) String title, RequestParam(price) BigDecimal price, RequestParam(value categoryId, required false) Integer categoryId) { // 1. 文件非空校验 if (file.isEmpty()) { return Result.error(上传文件不能为空); } // 2. 文件大小校验限制10MB if (file.getSize() 10 * 1024 * 1024) { return Result.error(图片大小不能超过10MB); } // 3. 文件格式校验白名单方式 String originalFilename file.getOriginalFilename(); String suffix originalFilename.substring(originalFilename.lastIndexOf(.) 1).toLowerCase(); ListString allowedSuffix Arrays.asList(jpg, jpeg, png, gif, webp); if (!allowedSuffix.contains(suffix)) { return Result.error(不支持的图片格式仅支持 String.join(,, allowedSuffix)); } // 4. 组合业务保存文件 入库 ImageVO vo imageService.uploadImage(file, title, price, categoryId, getCurrentUserId()); return Result.success(vo); } }为什么要用白名单校验后缀而不是黑名单因为黑名单永远封不完攻击者可以不断换新后缀。白名单只允许列出的几种格式其他的一律拒绝这是文件上传的基本安全策略。另外注意一点MultipartFile的文件名校验不能只做一次因为用户上传的文件名可能带有路径信息比如C:\Users\xx\photo.jpg后端要用file.getOriginalFilename()仔细处理后再提取后缀防止路径穿越问题。4.2 文件存储路径相对路径 配置化换台电脑也能跑文件上传路径踩过的坑是最多的。很多人直接在代码里写死D:/upload/本地跑得欢拿到答辩现场换电脑以后项目直接崩。正确做法是把路径放到配置文件里file: upload-dir: ./upload preview-dir: ./upload/preview access-prefix: /files/**用相对路径./upload项目在哪个目录启动文件就存到项目目录下的upload文件夹天然具备可移植性。同时注册一个静态资源映射让/files/**这个URL能访问到上传目录Configuration public class WebMvcConfig implements WebMvcConfigurer { Value(${file.upload-dir}) private String uploadDir; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/files/**) .addResourceLocations(file: uploadDir /); } }注意addResourceLocations后面的路径必须以/结尾否则Spring会报错。这个细节卡过很多人。4.3 文件重名问题UUID改名是最好的解上传的文件如果直接用原始文件名保存一定会在某个时刻遇到重名覆盖的问题。所以我处理文件名的逻辑很简单用UUID生成新文件名忽略用户原始文件名String suffix originalFilename.substring(originalFilename.lastIndexOf(.)); String newFileName UUID.randomUUID().toString().replace(-, ) suffix;注意replace(-, )是为了去掉UUID中的连字符让文件名更清爽。保存图片信息时数据库中只存相对路径/files/2024/xx.jpg这种格式前端直接拼接访问即可。是否按日期分目录比如/files/2024/12/属于可选优化图片量不大的话不分目录也行。4.4 预览图与原图的隔离加个水印就能媲美真实商业系统图片销售系统如果不做预览图和原图的区分那销售就名存实亡了——用户直接右键另存为就拿到原图。所以正确的设计是图片列表和详情页展示的是加了水印的预览图原图只有下单支付后通过下载接口返回。用Thumbnator库几十行代码就能生成带水印的预览图dependency groupIdnet.coobird/groupId artifactIdthumbnailator/artifactId version0.4.20/version /dependency// 生成带水印的预览图 BufferedImage watermarkImage ImageIO.read(new File(watermark.png)); Thumbnails.of(new File(originalPath)) .size(800, 600) .watermark(Positions.BOTTOM_RIGHT, watermarkImage, 0.8f) .toFile(new File(previewPath));缩略到800x600外加一个半透明水印既让用户能看清图片内容又保证了原图不被白嫖。讲师一眼看到这个设计至少能判断你了解真实商业图片站点的运营逻辑。5. 销售闭环订单状态枚举、事务一致性、模拟支付与下载鉴权图片销售系统的核心是销售两个字。订单从创建到支付到完成每一步都必须走得通。很多半成品毕设只做了下单插入一条记录然后就没有然后了。这种完成度在答辩时非常容易被问倒我付了钱然后呢5.1 订单状态用枚举管理别在代码里散落魔法数字订单状态是业务的关键概念推荐直接用枚举类定义public enum OrderStatus { PENDING_PAYMENT(0, 待支付), PAID(1, 已支付), COMPLETED(2, 已完成), CANCELED(3, 已取消); private final int code; private final String desc; OrderStatus(int code, String desc) { this.code code; this.desc desc; } public int getCode() { return code; } public String getDesc() { return desc; } }为什么不用数字0、1、2直接写因为数字没有语义代码里到处都是if (status 1)会让阅读者满头问号。用OrderStatus.PAID.getCode()不仅可读性强还能在枚举里加状态流转的校验逻辑。在答辩时打开这段代码给老师看比你说一百句我的代码有规范都管用。5.2 下单接口事务是基本盘订单号生成是细节下单接口要做的事情比想象中多校验图片是否上架、计算总金额、生成订单号、插入订单表、插入订单明细表。这里有一个必须处理的点订单表和明细表是关联写入必须保证原子性所以方法上要加Transactional。如果不加事务订单表插入成功但明细表插入失败数据就散架了。Transactional(rollbackFor Exception.class) public Order createOrder(Long userId, ListLong imageIds) { if (imageIds null || imageIds.isEmpty()) { throw new BusinessException(请选择要购买的图片); } // 1. 批量查询图片同时校验状态 ListImage images imageMapper.selectBatchIds(imageIds); BigDecimal totalAmount BigDecimal.ZERO; for (Image image : images) { if (image null || image.getStatus() ! 1) { throw new BusinessException(图片不存在或已下架); } totalAmount totalAmount.add(image.getPrice()); } // 2. 生成订单号yyyyMMddHHmmss userId 4位随机数 String orderNo generateOrderNo(userId); // 3. 插入订单表 Order order new Order(); order.setOrderNo(orderNo); order.setUserId(userId); order.setTotalAmount(totalAmount); order.setStatus(OrderStatus.PENDING_PAYMENT.getCode()); orderMapper.insert(order); // 4. 插入订单明细表 for (Image image : images) { OrderItem item new OrderItem(); item.setOrderId(order.getId()); item.setImageId(image.getId()); item.setPrice(image.getPrice()); // 价格快照 orderItemMapper.insert(item); } return order; }订单号生成规则yyyyMMddHHmmss userId 4位随机数比如2024121814302610001283。这个规则在毕设场景下完全够用既包含时间信息又有用户维度还避免了并发场景下的重复问题。答辩老师问订单号怎么保证唯一你就说时间戳加用户ID加随机数三重保证他基本不会再追问。Transactional注解这里有个小坑默认只对RuntimeException回滚如果业务代码里抛的是Exception需要加上rollbackFor Exception.class才保险。这个细节也属于答辩加分项说明你真的理解事务的语义。5.3 模拟支付用一个接口模拟支付回调真实项目的支付非常复杂对接微信支付SDK、生成支付二维码、密文回调验签、处理掉单……这些在毕设里完全没有必要。我的做法是做一个模拟支付接口——用户点击页面上的模拟付款按钮后端直接把订单状态从待支付改成已支付同时更新支付时间。Transactional public void payOrder(Long userId, String orderNo) { Order order orderMapper.selectByOrderNo(orderNo); if (order null) { throw new BusinessException(订单不存在); } if (!order.getUserId().equals(userId)) { throw new BusinessException(无权操作该订单); } if (order.getStatus() ! OrderStatus.PENDING_PAYMENT.getCode()) { throw new BusinessException(订单状态不正确无法支付); } order.setStatus(OrderStatus.PAID.getCode()); order.setPayTime(new Date()); orderMapper.updateById(order); }要注意这里对订单状态做了前置校验只有待支付状态的订单才能支付。这样的状态机逻辑在订单流转中的每一处都要有否则会出现在已取消的订单上还能支付成功的诡异bug。支付完成后根据订单明细把对应图片的已购状态记录下来实现方式就是在下载接口里查询该用户是否有已支付的订单包含这张图片。如果以后要接真实支付只需要把payOrder方法内部换成调用微信支付SDK并在回调接口里做同样的状态更新接口的对外签名不用变。这就是一个典型的可扩展设计答辩时可以重点讲。5.4 下载接口文件不走静态路径走鉴权后转发下载接口是整个系统的安全底线。图片的原图路径如果对外暴露用户绕过前端直接访问URL就能免费下载那整个销售逻辑就崩塌了。所以设计成原图不放在静态资源映射目录下只通过下载接口鉴权后读取并返回。GetMapping(/download/{imageId}) public ResponseEntityResource download(PathVariable Integer imageId, HttpServletRequest request) { // 1. 获取当前登录用户 Integer userId getCurrentUserId(request); if (userId null) { return ResponseEntity.status(401).build(); } // 2. 查询该用户是否有该图片的已支付订单 Integer orderItemCount orderItemMapper.countPaidByUserIdAndImageId(userId, imageId); if (orderItemCount null || orderItemCount 0) { return ResponseEntity.status(403).build(); } // 3. 读取原图文件以流的方式返回 Image image imageMapper.selectById(imageId); File file new File(image.getFilePath()); if (!file.exists()) { return ResponseEntity.notFound().build(); } Resource resource new FileSystemResource(file); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, attachment; filename\ file.getName() \) .contentType(MediaType.IMAGE_JPEG) .body(resource); }这里的关键是下载接口返回的是文件流不是文件路径。前端调用这个接口时得到的是一份二进制数据无法从这个接口中窥探到服务器上的实际文件位置。这种设计比直接把filePath返回给前端让人家拼URL安全得多答辩讲到这一环评委不但不会刁难还会觉得你的安全意识很到位。6. 答辩现场高频追问十个问题提前备好答案项目做完了答辩是关键。我根据大量真实答辩反馈整理了十道高频问题每一道在图片销售系统里都有具体的回答方向。建议你不要背答案而是结合自己的代码理解后再用自己的话讲出来。为什么用JWT而不是SessionJWT无状态、服务端不需要存储会话、适合前后端分离场景。缺点是注销麻烦但毕设场景不涉及所以权衡后选JWT是合理决策。上传的图片重名怎么办UUID生成新文件名避免重名覆盖同时保留原文件后缀。订单状态怎么流转待支付→已支付→已完成待支付→已取消。用枚举管理状态状态机校验不允许跳跃。订单明细表为什么需要冗余price价格快照防止图片价格变动影响历史订单的数据准确性。超时未支付的订单怎么处理可以设计一个定时任务扫描超过30分钟未支付的待支付订单并自动取消。如果还没做承认这是扩展点说明你的改进思路。预览图和水印是怎么生成的用Thumbnator生成缩略图并叠加半透明水印原图不直接暴露。Controller层为什么不写业务逻辑分层架构Controller负责参数解析和响应封装Service层负责业务逻辑便于测试和复用。用户直接访问/files路径能绕过登录吗这正是要处理的点预览图可以匿名访问原图不放在静态映射目录下只能通过带鉴权的下载接口获取。换电脑部署需要改哪些配置数据库连接、文件上传路径全部集中在application.yml里不用改代码。密码是明文存储的吗不是用BCrypt算法加盐哈希。讲一下加盐的原理在密码末尾追加一个随机字符串后再哈希避免直接用彩虹表反查。这十个问题每个都能写一小段覆盖了数据表为什么会这么建安全上怎么防部署上怎么配这几个核心维度。你在答辩前把这些问题对应的代码位置都找到亲手在调试器里走一遍比背任何答案都稳。7. 从拿到源码到吃透项目三条路线的实操建议现在很多同学是附源码拿到的毕设项目但源码不等于你的。我的经验是拿到源码后按下面的路线走三遍才能真正把项目变成自己的。第一遍跑通项目在关键方法打断点。先按README把数据库导好、项目启动起来然后打开Idea在登录Service、图片上传Controller、下单Service、支付方法、下载接口这五个位置设置断点。每个断点触发后逐行跟踪一遍调用栈和变量变化。这一遍走完你对整个系统的数据流向会有一个整体认识。第二遍改三个小需求练手。我推荐三个低风险高收益的需求第一在图片详情页加一个同分类推荐列表练联表查询第二给订单列表加按时间范围筛选练条件查询第三在图片上传时增加原图/预览图分别上传的选项练分支逻辑。这三个需求分别涉及联表、筛选、字段扩展正好覆盖项目80%的代码结构。亲手改完之后你对项目的熟悉程度会有一个从看着懂到闭着眼也能定位问题的质变。第三遍把开发过程整理成论文材料。从现在开始每天用Markdown记录自己做了什么、遇到什么问题、怎么解决的。到写论文时这些记录就是需求分析、技术选型、系统设计、功能实现、系统测试五个章节的天然素材。我见过太多人最后赶论文时回忆不起具体细节从第一天就做记录能省下大量时间。8. 实操中踩过的坑四个比教程里更真实的bug最后分享几个我实际踩过的坑每一个都真实卡过我。第一个坑MySQL 8的驱动和时区问题。MySQL 8需要使用com.mysql.cj.jdbc.Driver并且在JDBC连接串里加上serverTimezoneAsia/Shanghai否则会报The server time zone value Öйú±ê׼ʱ¼ä is unrecognized这类乱码时区错误。如果你还在用MySQL 5.7驱动版本对应也要调整这个坑几乎人人都会踩写配置时记得顺手检查。第二个坑端口占用导致启动失败。Spring Boot默认8080端口如果之前跑过其他服务占用了端口项目启动就报Port 8080 was already in use。最快的排查方式是命令行执行netstat -ano | findstr 8080Windows或lsof -i:8080Mac/Linux找到占用进程结束掉或直接在启动配置里改一个端口。我建议开发阶段可以直接把端口改成8081省得老是冲突。第三个坑Vue上传文件请求头不对。用axios上传文件时必须确保请求头是Content-Type: multipart/form-data。很多人的axios实例里封装了全局拦截器默认给所有请求加了application/json结果上传接口永远收不到文件。排查时先看浏览器Network面板里发出的请求Content-Type是什么再检查你的拦截器有没有强制覆盖。另外上传FormData时字段名必须和后端RequestParam(file)里的名字一致差一个字母就是空指针。第四个坑逻辑删除和唯一索引的冲突。我给图片表加is_delete逻辑删除标记后又给file_path字段建了唯一索引结果第二次上传相同路径的图片时报Duplicate entry。因为逻辑删除的记录还占着唯一索引的位置物理删除又违背了不删历史的初衷。解决办法有两个方向去掉唯一索引或者把is_delete也加进联合唯一索引里。这个案例很经典如果你在答辩中讲出来会显得你对数据库设计有非常深入的思考。做一个毕业设计项目真正的分水岭不在于你选了多难的题目而在于你对自己做的系统能不能做到每一个设计决策都有解释。图片销售系统作为一个成熟的毕设选题它的每一处代码、每一条SQL、每一个安全设计都有非常清晰的学习路径和参考范例。把这套系统彻底吃透你收获的不仅是一个能通过答辩的项目更是对Spring Boot开发全流程的一次系统性训练。
返回列表