尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SpringBoot校园二手交易平台设计:从表结构到订单状态机的毕设实战

SpringBoot校园二手交易平台设计:从表结构到订单状态机的毕设实战 又到了毕设选题的季节JavaWeb方向里二手交易平台几乎是出现频率最高的题目之一。但我发现很多同学拿到《基于SpringBoot的校园闲置物品流转平台设计与实现》这个题目后第一反应是去套电商商城那套东西——购物车、支付回调、运费模板、优惠券结果数据库设计复杂程度直接翻倍开发周期拖到答辩前还没跑通。实际上校园闲置流转和综合电商有本质区别交易半径小、买卖双方大多在同一个校园里、交付靠线下见面、支付甚至可以模拟。这篇文章基于我一对一带过的几个毕设项目把从需求拆分、技术选型、表结构设计、核心编码到排错的经验完整过一遍给正在做SpringBoot JavaWeb毕设、或者想拿完整项目做练手的同学一个可以直接复用的参考。1. 为什么校园闲置流转值得单独做一个平台很多同学看到校园二手交易第一反应是这不就是一个小型闲鱼吗有啥可做的这个理解恰恰是项目做不下去的根源。校园场景的核心特征是信任半径小、线下交付比重高所以平台不需要把精力花在复杂的支付结算上而应该花在让买卖双方高效撮合并管理交易状态上。我做需求分析的时候习惯先把用户和边界说清楚。这个平台涉及三类角色买家、卖家、系统管理员。买家要能浏览商品、搜索筛选、收藏、下单卖家在买家身份基础上多一个商品发布、上架下架、订单发货的能力管理员负责用户管理、商品审核、分类维护、订单兜底处理。注意这里还有个隐含约束平台上没有专职商家每个用户既可能是买家也可能是卖家所以不应该单独建一张商家表用户表加个角色字段足够了。功能边界划定上我比较推荐这种划分方式模块功能点说明用户注册、登录、个人资料、我的发布、我的订单不需要找回密码走管理员重置即可商品发布、编辑、上下架、图片上传、分类筛选、关键词搜索、浏览计数核心是发布和检索体验交易下单、模拟支付、发货、确认收货、取消订单用状态机管理流转不接真实支付管理后台用户禁用/启用、商品审核下架、分类增删改查毕设阶段做到简单的信息维护即可这么切分之后工作量是可控的——核心表大概四到五张主要接口二三十个按SpringBoot的标准分层结构按部就班写两周左右能完成绝大部分。还有一个我经常提醒学生的事不要一上来就打开IDE写代码。先花一晚上把需求文档、页面跳转关系、接口清单列出来。这个阶段多花的时间在后端编码阶段会成倍赚回来。比如商品状态到底有几种、谁有权限改、改了之后订单怎么联动这些问题不做梳理直接写代码后面一定会反复改表结构。2. SpringBoot版本选型与工程骨架搭建SpringBoot版本这块我踩过一个很典型的坑某年有个学生贪新鲜直接上了Spring Boot 3.2结果引入MyBatis-Plus、一些工具包时发现需要适配新版API而且网上能找到的教程案例大多基于2.x报错排起来特别费劲。做毕设稳定比版本新重要。我现在的建议是如果你用JDK 8就选Spring Boot 2.7.18如果你电脑上只有JDK 17那老老实实上Spring Boot 3.x但依赖版本要核对清楚。不要混搭。这里给出一个我常用的基础工程组合供你直接抄JDK 8 / Maven 3.8Spring Boot 2.7.18MyBatis-Plus 3.5.3这个版本对Spring Boot 2.x兼容好MySQL 8.05.7也行注意驱动依赖不同Hutool 5.8生成订单号、日期处理很省事LombokGetter/Setter/日志必装别排斥工程结构上推荐按功能包名分层而不是按controller/service这种技术层分包。也就是说不是建一堆controller包、service包、mapper包而是建user包、goods包、order包、admin包每个包内部放Controller、Service、Mapper。这种结构在项目变大之后可维护性好很多面试聊起来也能加分。骨架搭好后第一件事是配置统一返回体。我发现很多同学每个接口返回的类型都不一样有的返回Map有的直接返回实体前端对接时苦不堪言。我习惯定义一个ResultData public class ResultT { private Integer code; private String message; private T data; public static T ResultT ok(T data) { ResultT r new Result(); r.setCode(200); r.setMessage(success); r.setData(data); return r; } public static T ResultT fail(String message) { ResultT r new Result(); r.setCode(500); r.setMessage(message); return r; } }再配一个全局异常处理器把业务异常统一转成Result.fail返回这样Controller里就不用到处try-catch了。工程骨架搭到这一步项目已经能启动再开始写业务才有底气。3. 数据库建模从用户到订单的状态流转设计表结构设计是整个项目里最重要的部分没有之一。表设计不合理后面每写一个功能都在跟之前的错误设计作斗争。我见过太多学生把订单表设计成一张大宽表一个订单存好几件商品结果取消订单统计销量全都变得很别扭。校园二手平台的交易特点是一单一件——商品没库存概念被下单之后状态就是已售。这个特性让表结构可以做得非常简洁。我最终采用的表结构如下。用户表t_user字段类型说明idbigint主键usernamevarchar(40)登录名唯一passwordvarchar(100)BCrypt加密密文nicknamevarchar(40)昵称avatarvarchar(255)头像路径phonevarchar(20)联系电话user_typetinyint0普通 1管理员statustinyint0禁用 1正常deletedtinyint逻辑删除create_timedatetime创建时间商品表t_goods是关键字段也要仔细斟酌字段类型说明idbigint主键titlevarchar(80)标题descriptiontext详细描述pricedecimal(10,2)售价cover_imagevarchar(255)封面图imagesvarchar(1000)详情图逗号分隔category_idbigint分类IDseller_idbigint卖家用户IDstatustinyint0下架 1在售 2已售view_countint浏览量deletedtinyint逻辑删除create_timedatetime发布时间update_timedatetime更新时间订单表t_order我建议记录下单、支付、发货、完成四个时间字段而不是只存一个status。因为你做倒计时自动取消或者展示交易时间线时都需要这些时间点字段类型说明idbigint主键order_novarchar(32)订单号唯一goods_idbigint商品IDbuyer_idbigint买家IDseller_idbigint卖家IDamountdecimal(10,2)成交金额statustinyint0待付款 1待发货 2待收货 3已完成 4已取消create_timedatetime下单时间pay_timedatetime支付时间deliver_timedatetime发货时间confirm_timedatetime确认收货时间这里有几个设计决策值得说说。第一为什么商品图片用cover_image加images两个字段而不是建一张商品图片表因为校园二手场景一个商品配三五张图就到头了用逗号分隔字符串存储最省事查询时也不需要二次关联。第二为什么在订单表冗余一个seller_id因为查询我卖出的订单和我买到的订单如果只有商品ID就得先查商品拿到卖家ID多一次关联查询冗余字段让逻辑更直接。第三order_no用Hutool的IdUtil.createSnowflake生成雪花ID的格式也适合做字符串订单号。删除策略也要说下我的建议是一律用逻辑删除deleted字段不物理删除。原因很实际毕设答辩时老师可能会检查数据的完整性你删掉几条数据很难解释逻辑删除可以随时恢复演示也好看。4. 核心功能编码登录、商品发布与分页检索骨架和表都定了接下来是编码我按功能模块讲几个核心点。4.1 注册登录与权限拦截密码绝对不能明文存储。Spring Security对毕设来说偏重直接用Spring Security自带的BCryptPasswordEncoder做单向加密就够用了。注册的时候把用户输入的密码加密再存库登录时用matches比对。至于会话保持我不推荐JWT——毕设项目通常不需要那么复杂用Session 拦截器就足够稳定。你自己实现一个LoginInterceptor注入到Spring MVC的拦截器链里放行/login、/register和静态资源路径其余接口校验session里有没有登录用户没有就返回401。这里有个细节如果做前后端分离前端拿不到session里的登录态怎么办那就要考虑两种方案。方案一用拦截器校验前端传来的token自己生成的UUID或JWT方案二让前端打包后放到SpringBoot的static目录下热搜里也总有人搜vue打包放进springboot中这样Session天然可用。毕设答辩现场我更推荐方案二一个端口搞定前后端部署也省心。4.2 商品发布与图片上传商品发布看起来简单实际上图片上传是很多人的翻车点。我在做这个项目时图片保存策略选的是本地磁盘存储数据库存相对路径不折腾云存储。具体做法是上传目录固定为项目根目录外的upload文件夹然后通过实现WebMvcConfigurer的addResourceHandlers方法把/uploads/**映射到磁盘真实路径。Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 路径结尾必须有分隔符 String uploadDir file: System.getProperty(user.dir) File.separator upload File.separator; registry.addResourceHandler(/uploads/**).addResourceHandler(uploadDir); }这样做的好处是部署到服务器后图片目录和jar包分离重新打包不会丢图片开发时访问localhost:8080/uploads/xxx.jpg就和访问静态资源一样不用额外配Nginx。上传时记得校验文件类型和大小application.yml里配置spring: servlet: multipart: max-file-size: 5MB max-request-size: 20MB只限制大小还不够还要校验扩展名防止有人传个jsp或者exe上去。我用的是一个白名单方法只允许jpg、png、gif、webp四种后缀然后重命名文件为时间戳加随机数避免中文文件名乱码和路径注入。4.3 商品列表与分页检索列表页是流量最大的接口你会发现很多毕设项目就是从这里开始崩的——不用分页插件一次性查出几百条数据塞给前端页面卡成PPT。正确做法是用MyBatis-Plus的分页插件先配置拦截器Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }然后使用LambdaQueryWrapper做多条件查询LambdaQueryWrapperGoods wrapper new LambdaQueryWrapper(); wrapper.eq(Goods::getStatus, 1) .eq(categoryId ! null, Goods::getCategoryId, categoryId) .like(StringUtils.hasText(keyword), Goods::getTitle, keyword) .orderByDesc(sortField.equals(price) ? Goods::getPrice : Goods::getCreateTime);这里有个坑要提醒一下orderByDesc传入的字段如果是用户可控的字符串一定不要直接拼SQL要在后端用白名单映射校验。比如只允许price、time、view_count三个值对应到精准的字段引用其他一律按默认时间排序这样能防掉order by注入型问题。检索出来的是Goods实体但前端往往需要展示卖家昵称、图片完整URL等额外信息。我的做法是查list后手动组装VO不在SQL里做三表关联。原因很朴素单表查询加上内存组装逻辑清晰、调试方便数据量在校园场景下完全够用。5. 订单交易状态机从下单到确认收货订单部分是整个系统的业务核心也是答辩时最容易被打分老师追问的地方。我把订单的流转理解成一个状态机每一个动作只能从特定的状态跃迁到下一个状态不允许任意跳转。完整状态流转是这样的创建订单时订单状态为待付款同时商品状态从在售变为已售买家点击模拟支付后状态变为待发货记录支付时间卖家发货后状态变为待收货记录发货时间买家确认收货状态变为已完成记录完成时间如果买家下单后一直不支付超过30分钟可以由系统定时任务自动取消状态变为已取消。状态机约束直接体现在代码里不要在Service方法里随便set字段。以下单为例最核心的问题是如何防止两个人同时抢同一件商品。我用的是条件更新Transactional public Long createOrder(Long goodsId, Long buyerId) { // 1.查出商品判断基础合法性 Goods goods goodsMapper.selectById(goodsId); if (goods null || !goods.getStatus().equals(1)) { throw new BizException(商品不存在或已下架); } if (goods.getSellerId().equals(buyerId)) { throw new BizException(不能购买自己发布的商品); } // 2.条件更新where里带上status1只有状态没变才能更新成功 int updated goodsMapper.updateStatusWithCondition(goodsId, 1, 2); if (updated 0) { throw new BizException(手慢了商品已被购买); } // 3.创建订单记录 Order order new Order(); order.setOrderNo(IdUtil.createSnowflakeNextIdStr()); order.setGoodsId(goodsId); order.setBuyerId(buyerId); order.setSellerId(goods.getSellerId()); order.setAmount(goods.getPrice()); order.setStatus(0); orderMapper.insert(order); return order.getId(); }这个updateStatusWithCondition映射的就是UPDATE t_goods SET status 2 WHERE id ? AND status 1通过影响行数判断是否抢到了商品。这个方案比select再加update的写法稳得多也是能够向面试官讲清楚的并发处理思路。关于模拟支付不需要对接支付宝微信。我的做法是提供一个支付按钮点击后校验订单归属是否正确、状态是否为待付款然后一次性更新订单状态和支付时间。为了演示效果会故意让页面停留3秒钟再调接口展示支付中的状态实际代码就是一个事务方法。状态机里还有一个容易漏的点取消订单要把商品状态改回在售。如果这笔订单已经支付了逻辑上还要走退款流程。毕设阶段我建议简化只有未支付的订单能取消支付后的订单如果想退回走管理员介入将订单改为已取消、商品重新上架。这个规则在前后端都校验一遍防止用户通过接口直接改状态制造脏数据。6. 开发过程中的高频坑位与排查路线开发过程中踩的坑我觉得比业务本身更值得记录这里挑几个出现频率最高的每个都给排查链路。6.1 依赖包直接404pom飘红现象新建SpringBoot项目后拉取依赖极慢甚至报错。原因Maven默认中央仓库在国外网络波动大。解决修改settings.xml里的mirror为阿里云镜像然后强制更新依赖。这个步骤很多新手不知道导致一下午都耗在下载上。做完这一步几乎能把依赖下载时间缩短到原来的十分之一。6.2 MyBatis-Plus分页查询不生效现象调用selectPage返回的记录数和total都是全表数据等于没分页。这个坑的原因很明确——分页是需要依赖MybatisPlusInterceptor的而不是引入starter就自动配好。按照前面章节的配置类加上拦截器即可。排查思路是先检查项目里有没有这个Bean没有就补上有还是不行再检查有没有引入mybatis-plus-jsqlparser依赖因为新版MyBatis-Plus分页插件需要jsqlparser的支持。6.3 图片上传成功但浏览器访问404现象上传接口返回了文件路径但浏览器打开URL一直是404。排查链路分三步第一步看控制台有没有FileNotFoundException有就是磁盘路径不存在确认目录是否自动创建第二步看访问路径是否被拦截器拦了如果项目里自定义了拦截器放行名单记得加上/uploads/**第三步看资源映射是否生效注意addResourceHandler的pathPattern必须是斜杠开头而且磁盘路径结尾一定要带斜杠。6.4 数据库时间差8小时现象查出来的时间字段比数据库里时间正好慢8小时或快8小时。原因JDBC连接串里的serverTimezone配置缺失或设置错误。规范做法是在jdbc-url里加上serverTimezoneAsia/ShanghaiuseUnicodetruecharacterEncodingutf8同时建议数据库连接也显式设置useSSLfalse省得启动时一堆警告。6.5 前端拿到的Long型ID精度丢失现象前端控制台显示的商品ID最后几位变成0或者和后端返回值不一致。原因Java的Long最大长度超过JavaScript安全整数的范围2^53-1雪花ID大概率会超。解决方式给id字段加注解JsonSerialize(using ToStringSerializer.class) private Long id;或者直接配置全局Jackson把所有Long转成String返回后端接收时再反序列化回来。这个坑做前后端分离项目几乎必踩别等到答辩那天再发现。6.6 项目启动报8080端口被占用毕设开发期最容易出现的环境问题。排查思路netstat -ano | findstr 8080找到占用端口的PID然后去任务管理器结束对应进程。如果是因为本机有别的服务需要8080也可以在application.yml里改端口但要注意前端请求地址同步更新。6.7 Lombok突然失效现象实体类注解了Data但getter/setter找不到。原因经常是IDE的Annotation Processing没有开启或者Lombok版本和JDK版本不兼容。排查思路先确认pom里的Lombok版本然后检查IDE里Settings搜索Annotation Processor并勾选Enable再清缓存重启——这个流程解决掉我手上八九成的运行问题。7. 跑通项目之后我建议还可以补这几个方向到这里整个校园二手平台已经是一个能完整跑起来、能现场演示的毕设了。但我带学生做项目时通常还会在验收标准之外补几个点一个是给系统加缓存、一个是加定时任务、还有一个是提升部署体验。这些都是花一天时间就能见到效果的小优化却能让答辩时讲出的内容比别人丰富不少。缓存上用Spring Cache加Redis做一个简单的热门商品缓存首页推荐列表的接口能明显快一点。Redis在Windows上装个解压版就能开发用配置也不是很复杂。定时任务上用Spring自带的Scheduled每天凌晨扫一遍超时订单把待付款超过30分钟的订单自动取消、商品重新上架。更值得做的是一个提醒功能利用Spring Boot集成WebSocket当买家下单时给卖家推一条站内消息您有一个新订单当卖家发货时给买家推一条商品已发货。这部分代码量不大但演示效果非常直观能充分体现你对实时通信有一定掌握。部署方面最稳妥的做法是打包成jar放到一台云服务器上跑。注意三个点第一上传目录在服务器上要提前建好并且给足读写权限第二先用java -jar参数指定端口启动测试没问题再用systemd托管第三MySQL要允许远程连接或者跟jar装在同一台机器上。整个部署链路走通之后你对项目的掌控感和纯本地跑通完全是两个层次。我自己的习惯是每完成一个小功能就用maven打成jar在本地验证一遍启动流程而不是全部写完再部署这样能避免临近答辩时被环境问题突然袭击。
返回列表