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

资讯详情

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

SpringBoot+Vue构建二手交易系统:从订单状态机到JWT鉴权实战

SpringBoot+Vue构建二手交易系统:从订单状态机到JWT鉴权实战 1. 需求拆解与技术选型为什么二手交易系统是SpringBootVue的黄金练手项目做毕设或者想系统梳理全栈知识的时候二手物品交易系统几乎是绕不开的一个经典选题。原因很简单——它不是一个纯展示型的CRUD项目而是真正具备“用户与用户之间产生交易行为”的业务闭环。从注册登录、发布闲置、浏览检索到下单购买、确认收货、评价反馈这条链路上既有常规的增删改查又有状态流转、权限区分、文件上传这类非常实际的功能点恰好能把SpringBoot后端和Vue前端的核心能力全部覆盖到。我最早接触这个项目是在帮学弟做课程设计的时候那时我把这套系统定义为“三类用户、两个端、一条交易主线”。买家、卖家、管理员三种角色对应不同的操作权限前端用户端和管理后台分开设计交易主线则由“发布商品→浏览下单→卖家处理→确认收货→订单完成”这条状态链贯穿起来。先把这张业务全景图画清楚后端的表结构、接口设计、前端的页面路由才不会乱。技术选型上SpringBoot Vue这套组合放到今天依然是性价比最高的方案。SpringBoot解决的是后端“配置地狱”的问题内嵌Tomcat一个Application类就能跑起来配合MyBatis-Plus做数据访问开发效率非常可观。Vue这边用Vue 2 Element UI Vue Router Vuex组件化开发让页面结构变得非常清晰——商品卡片是一个组件订单列表是一个组件复用起来很方便前后端通过JSON格式的接口通信职责非常明确。1.1 核心角色与业务流程梳理这套系统的角色权限设计很关键我当时的划分方式是这样的买家浏览上架商品、搜索、收藏、下单购买、确认收货、发表评价。卖家即普通用户除了买家的能力外还可以发布闲置商品、编辑商品信息、上下架商品、处理收到的订单发货/拒绝/同意退款。管理员负责用户管理封禁/解封、类目管理、商品审核下架违规商品、数据统计等。因为二手交易有一个特殊性——每个人既是买家也是卖家所以我没有把“买家”和“卖家”设计成两种不同的注册角色而是给每个用户附加了一个is_seller标记任何用户都同时具备买卖两种身份。这一点在数据库设计上要提前想清楚否则后面做订单关联用户时会非常别扭。1.2 关键需求落地时的功能清单把主流程拆成功能点大概是下面这张清单模块功能点说明用户模块注册、登录、信息编辑、密码修改、头像上传登录采用JWT无状态认证商品模块发布闲置、图片上传、商品列表分页、按类目筛选、关键词搜索、商品详情、收藏/取消收藏支持多图展示与封面设置订单模块下单、取消订单、卖家发货、买家确认收货、退款申请与处理状态机流转见后文章节评论模块订单完成后买卖双方互评展示在商品详情页用于构建信任关系管理端用户管理、类目管理、商品管理、订单总览、数据统计基于Vue独立页面实现安全方面密码不能明文存储我用BCrypt加密接口权限通过SpringBoot拦截器校验JWT令牌实现管理端接口额外校验管理员角色。这些细节在文档和答辩时都是加分项。2. 数据库设计核心表结构与字段细节剖析数据库是整个系统最底层的骨架。二手交易系统虽然业务不算复杂但如果表设计得不好后面写SQL逻辑时会被各种边界情况折磨得痛不欲生。我最终的设计一共落了9张表其中7张核心业务表、2张辅助表。2.1 核心表结构一览user用户表id、username、passwordBCrypt哈希、nickname、avatar、phone、is_seller、status正常/封禁、create_time。category类目表id、name、parent_id支持二级类目、sort_order。goods商品表id、seller_id关联用户表、category_id、title、description、price、original_price、status0草稿/1上架/2下架/3已售出、cover_image、view_count、create_time、update_time。goods_image商品图片表id、goods_id、image_url、sort_order。favorite收藏表id、user_id、goods_id、create_time唯一索引处理重复收藏。orders订单表id、order_no唯一业务单号、goods_id、buyer_id、seller_id、price、status、create_time、pay_time、ship_time、confirm_time、close_time。comment评价表id、order_id、user_id、target_user_id、goods_id、content、rating、create_time。辅助表部分是轮播图表banner和系统反馈表feedback这两个属于锦上添花时间紧可以不做。2.2 几个容易翻车的字段设计细节订单编号。主键id自增没问题但对外展示的订单号一定要单独设计。我用的方式yyyyMMddHHmmss 4位随机数这样即使同一秒产生多笔订单也不会撞号而且客服沟通时报订单号也方便。商品状态与订单状态分离。商品状态和订单状态是两条独立的状态线千万别混在一起。商品从“上架”到“已售出”是在订单状态变为“待发货”即买家已经付款时联动的而订单状态的核心是“待付款→待发货→待收货→已完成”外加“已取消”和“退款中”两个分支。我用了一个Integer字段存储状态码在Java里定义常量类统一管理避免魔法值满天飞。2.3 SQL脚本的初始化策略数据库文档中我提供了初始化SQL脚本里面分为三类内容建库建表语句使用CREATE DATABASE IF NOT EXISTS second_hand DEFAULT CHARSET utf8mb4;编码必须指定utf8mb4否则商品标题里如果有特殊字符或者表情符号会报错。基础数据插入管理员账号、初始商品类目、几条示例商品数据。这样项目启动后前端页面至少有内容可展示不至于空荡荡的。索引设计goods表在status和category_id上建联合索引orders表在buyer_id和seller_id上分别建索引。数据量不大的时候索引效果不明显但这是数据库设计规范性问题文档里必须体现。提示如果你用的是MySQL 8.x数据库驱动记得用com.mysql.cj.jdbc.Driver同时连接串里加上serverTimezoneAsia/Shanghai否则会报时区错误。这个坑几乎每个跑SpringBootMySQL的人都会踩一次。3. 后端核心实现从JWT登录鉴权到交易订单状态机SpringBoot后端我按模块分包controller、service、mapper三层分离。下面挑几个最核心也最容易被问到的实现细节展开说。3.1 JWT登录与拦截器实现用户登录成功后后端生成一个JWT令牌返回给前端。令牌里只放用户id和角色信息不存敏感数据。生成方式用的是jjwt库String token Jwts.builder() .setSubject(String.valueOf(user.getId())) .claim(role, user.getRole()) .setExpiration(new Date(System.currentTimeMillis() 7 * 24 * 3600 * 1000L)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact();拦截器这边我写了一个JwtInterceptor实现HandlerInterceptor接口在preHandle里校验Token。需要放行的路径比如注册登录接口和商品列表接口通过WebMvcConfigurer的excludePathPatterns配置放行其余接口全部要求携带有效Token。这里有个当时踩过的坑前端如果直接调用接口请求头里需要手动加上Authorization: Bearer token但如果用的是Axios可以在请求拦截器里统一设置。前后端约定好请求头名称后后端从header里取出token时要做空值判断否则会抛出空指针异常。3.2 商品发布、分页检索与图片上传商品发布的后端逻辑相对直接接收前端传来的表单数据含多张图片URL列表先插入goods表拿到自增id再批量插入goods_image表。我用的是事务注解Transactional保证两步操作要么全成功、要么全失败。列表检索这里SpringBoot结合MyBatis-Plus的Page对象做分页查询条件用LambdaQueryWrapper拼接。关键词搜索我用了一个简单方案——对商品标题做LIKE %关键词%匹配。如果你希望搜索更智能可以引入Elasticsearch或者用MySQL全文索引但对毕设和中小型项目来说LIKE已经足够而且答辩时你可以把“如何优化搜索性能”作为后续优化点来谈。图片上传这块我用的方案是本地磁盘存储前端通过Element UI的el-upload组件选择图片上传到后端/api/upload接口后端把文件保存到指定目录然后返回可访问的URL。需要注意的有两点上传目录要配置成绝对路径不要用相对路径。项目如果打成jar包运行相对路径指向的目录有可能不可写。如果图片访问出现跨域问题要么配置WebMvcConfigurer的跨域映射要么用Nginx做静态资源代理。3.3 订单状态机二手交易最核心的业务逻辑这部分是我认为整个项目里最能体现工程能力的地方。传统电商的订单状态机比较简单二手交易要额外处理“线下交易”和“买家/卖家身份重叠”带来的边界情况。我把订单流转状态定义如下状态码状态名称触发操作后续动作0待付款买家下单锁定商品库存1待发货买家付款模拟支付商品标记为已售出2待收货卖家点击发货记录发货时间3已完成买家确认收货允许双方互评4已取消买家/卖家取消订单释放商品状态5退款中买家申请退款卖家可同意/拒绝这里面最容易遗漏的是商品状态必须与订单状态联动的。当买家下单待付款时商品不能马上标记已售出因为买家有可能不付款但当订单变为待发货时商品状态必须立即变为已售出否则其他用户还能下单购买同一件商品。另一个边界情况是买家主动取消订单的时机。我会在校验逻辑里判断当前订单状态是否等于待付款只有待付款状态才能执行取消操作。后端的每个状态迁移都要加校验不能只靠前端控制按钮的显示隐藏——前端改个接口照样能请求后端。4. 前端页面搭建Vue Element UI的路由设计与组件复用前端这块我用的技术栈是Vue 2.6 Element UI 2.15 Vue Router Vuex Axios。整体上分成用户端页面和管理后台两大部分通过路由进行区分。4.1 页面路由与布局规划用户端路由表设计如下/首页展示商品列表、轮播图、热门商品推荐按浏览量排序。/goods/detail/:id商品详情页包含商品图片轮播、价格信息、卖家信息、收藏按钮和立即购买按钮。/goods/publish发布闲置页表单包含标题、类目、价格、描述、多图上传。/goods/list商品广场页支持关键词搜索、类目筛选、价格排序。/user/login和/user/register登录注册页。/user/center个人中心分Tab展示我的商品、我的订单、我的收藏、我的评价。/admin管理后台包含Dashboard、用户管理、商品管理、类目管理、订单管理。路由守卫我加了全局前置守卫未登录用户访问个人中心和发布页面时自动跳转到登录页并携带redirect参数登录成功后回跳原来的页面。这个交互虽然简单但用户体验提升非常明显。4.2 Axios统一封装与Token携带前端每个页面都单独写fetch请求是非常痛苦的事情。我在utils/request.js里做了一层Axios封装const service axios.create({ baseURL: /api, timeout: 10000 }); service.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers[Authorization] Bearer token; } return config; }); service.interceptors.response.use( response response.data, error { if (error.response error.response.status 401) { localStorage.removeItem(token); router.push(/user/login); } return Promise.reject(error); } );这样每个页面只需要调用request({ url: /goods/list, method: get, params })即可完全不用关心Token怎么带、登录过期怎么处理。这个封装在代码审查时是典型的前端亮点。4.3 商品发布表单与图片上传组件Element UI的el-form配合el-upload实现商品发布功能非常顺手。图片上传我用的方案是el-upload的action属性指向后端上传接口上传成功后在回调里拿到返回的图片URL存入一个imageList数组提交表单时随商品数据一并发给后端。有一点要做特殊处理el-upload默认支持文件列表展示但如果想实现封面设置功能需要在文件列表中增加“设为封面”的按钮点击后把对应图片的URL赋值给coverImage字段。前端这层交互不复杂但很能体现细节完整度。5. 从源码到跑通本地部署时最容易踩的五个坑每次有同学拿到源码跑来问我部署问题我总结下来翻车点高度集中在下面五处。按顺序排好照着检查基本能一步到位。5.1 环境版本不匹配这是第一大坑。SpringBoot 2.x对应JDK 8或11SpringBoot 3.x要求JDK 17两者差异极大。如果代码里用的是javax.*包那必然是SpringBoot 2.x千万不要用JDK 17去跑SpringBoot 1.x或早期2.x的配置。Vue这边建议Node.js 14.x或16.xnpm install如果报错优先尝试删除node_modules后重新安装或者切换淘宝镜像源。5.2 数据库配置错误检查application.yml里的datasource配置包括URL、用户名、密码、驱动类。我见过最多的错误是密码包含特殊字符但yaml文件里没加引号导致解析失败。另外MySQL 8和MySQL 5的驱动类写法不同前面提过的serverTimezone配置也别忘了。5.3 前后端分离部署的跨域问题本地开发时前端默认跑在8080端口后端跑在8081端口跨域是必然的。我处理的方式是在后端配置跨域Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowCredentials(true) .maxAge(3600); } }注意allowCredentials(true)时allowedOrigins不能使用*必须用allowedOriginPatterns这是SpringBoot 2.4之后的行为变化。5.4 图片上传后访问404如果图片上传成功了但在页面上显示不出来九成是静态资源映射没配。需要配置虚拟路径映射将本地上传目录映射为/images/**访问前缀Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/images/**) .addResourceLocations(file: uploadPath /); }这行代码和Linux服务器上的软链接异曲同工配置完之后记得重启后端服务。5.5 端口占用问题SpringBoot默认8080端口经常被其他进程占用。解决方案要么启动时指定server.port参数要么用lsof -i:8080检查占用进程后杀掉。Vue开发服务器默认是8080的话也容易和后端冲突建议Vue这边改到8081或者后端改到8082两个服务不要打架。6. 答辩亮点与二次扩展方向让这个项目真正成为你的作品代码能跑只是开始。从“网上抄来的项目”到“我的毕设项目”中间只差一个东西——你对自己的代码能不能讲出所以然。答辩和展示环节我会强烈建议从下面几个角度去组织你的讲解逻辑。6.1 讲述项目时的三个“非技术加分点”第一个加分点是业务流程的完整闭环。不要只讲“我用了SpringBoot和Vue”而是讲清楚“一个买家从下单到收货经历了哪些状态流转每个状态对应哪个页面、哪个接口、哪个数据表字段”。这说明你真的理解了系统而不是背代码。第二个加分点是安全措施的主动意识。密码BCrypt加密、JWT过期机制、接口权限拦截、SQL参数预编译防注入、文件上传类型校验这些点只要你在代码里实现了答辩时主动提出来效果远好过被动回答技术问题。第三个加分点是边界情况的处理能力。比如买家拍下商品不付款库存会不会锁死卖家下架商品后其他人还能不能下单同一件商品能不能同时被两个用户购买这些我在设计时都做了约束每一处约束都可以作为一个“我如何思考和处理边界问题”的案例来讲。6.2 后续扩展方向系统已经能跑通完整交易流程但如果想进一步拔高有三个我比较推荐的扩展方向引入Redis做热点缓存和分布式Session存储。把首页商品列表、热门商品缓存到Redis降低MySQL压力。登录状态也可以从JWT扩展为Redis存储支持主动踢人下线。集成支付模拟功能。不用真接支付宝和微信支付可以做一个模拟支付页但要把支付回调、订单超时关闭、退款处理的流程设计出来这就是从学生作品往工程化迈进了。消息通知模块。买家下单后通知卖家、卖家发货后通知买家、商品被收藏后降价提醒用WebSocket或者Spring Boot自带的WebSocket实现都可以。这个模块能显著提升用户粘性也是加分项中非常“显眼”的一块。我在实际带这个项目时最大的体会是二手交易系统的难点从来不在某个单一技术而在于把用户流程完整地想清楚并且保证状态流转的一致性。框架和工具都是一学就会的真正拉开差距的是面对业务复杂度的建模能力和处理边界问题的工程意识。把数据库状态设计和订单状态机吃透这个项目就真正属于你了。
返回列表