基于Vue3的校园二手交易系统设计与实现:从前后端分离到JWT权限认证

发布时间:2026/10/3 9:55:54
基于Vue3的校园二手交易系统设计与实现:从前后端分离到JWT权限认证 毕业设计年年有人做二手交易平台但绝大多数人要么只搭了个静态页面糊弄答辩要么功能堆了一堆却根本跑不通真实场景。我这次用Vue3从零实现了一个可演示、可部署、可扩展的校园二手商品交易系统前后端分离覆盖学生用户端和管理员端的核心闭环。做完之后最大的感受是这题目看着简单但真正要落地坑全在细节里。这篇东西写给正在做类似题目的同学——不管你是纯前端背景还是刚接触Vue3都能从中找到直接能抄的设计思路、关键代码片段和踩坑记录。我会尽量少说废话多讲实际做法和为什么这么做。1. 系统整体设计思路先弄清楚做给谁用再谈页面1.1 核心用户与核心流程拆解校园二手交易和闲鱼、转转这类平台在场景上有本质区别。用户身份高度统一都是在校学生交易范围限定在校内商品类型以教材、数码配件、生活用品、自行车这类高频刚需为主而且交易时间集中在开学、毕业季两个节点。所以系统不应该去追求全品类、全流程的电商复杂度而要把精力放在“学生愿意用”这个核心上。我最终圈定的核心角色只有三个前台的学生用户、后台的管理员以及未登录的游客。学生用户最关心的链路是三条浏览商品、发布商品、处理订单。游客可以逛但不可以买这个限定的目的在于逼出注册登录模块的存在感否则答辩时评委问你“为什么需要登录”你就得现编。管理员的核心链路则是商品审核、用户管理、分类管理和数据总览。1.2 技术栈选型的逻辑与取舍技术选型上我直接锁定了Vue3 Element Plus Pinia Vue Router Axios这一套组合后端用Spring Boot MyBatis Plus MySQL。这里有几个关键决策我解释一下第一为什么要用Vue3的Composition API而不是Options API。这个题目下标签里带了vue3说明选题人是有意识去体现新技术点的。Composition API最大的优势是可以按功能逻辑组织代码而不是按选项类型分散——比如一个商品发布表单它的响应式数据、校验规则、提交方法可以写在一起后期维护和论文里的功能模块对应关系会更直观。而且答辩时考官大概率会问Vue3和Vue2的区别你用Composition API本身就是最好的回答。第二状态管理用Pinia而不是Vuex。Pinia是Vue3官方推荐的状态管理库API更简洁没有mutations的概念直接改state。对学生项目来说它最大价值是让token存储、用户信息、购物车或订单状态这类跨组件共享数据有了统一出口。我实测下来同样的功能用Vuex写需要多写三成样板代码完全没有必要。第三UI框架选Element Plus没什么好纠结的它对Vue3的支持最成熟表格、表单、弹窗、分页组件一应俱全能极大缩短后台管理页面的开发时间。唯一要留意的是它和Vite的按需自动导入配合时偶尔会出现组件样式没加载的情况后面我会专门讲。1.3 前后端分离接口约定的重要性我第一次做这种项目的时候走了弯路——前端自己造mock数据后端的接口返回格式跟前端对不上联调阶段改得想骂人。这次我一开始就定死了统一的响应格式所有接口都返回下面这个结构{ code: 200, message: success, data: {} }code为200表示成功401表示未登录或token过期403表示无权限500表示服务器异常。前端在Axios的响应拦截器里统一处理这些状态码页面里只需要关心data部分。这个约定直接决定了后面所有联调工作的效率强烈建议任何做前后端分离毕设的人先做这一步。2. 数据库设计五张核心表撑起整个系统2.1 表结构与字段设计详解校园二手交易系统的数据模型没有想象中复杂核心是五张表用户表、商品表、分类表、订单表、收藏表。另外我加了一张留言表用来支撑商品详情页的互动。下面把每张表的关键字段和设计意图说清楚。用户表重点是角色区分。我用一个role字段来区分学生和管理员0表示普通用户、1表示管理员而不是单独建一张角色表。对小体量系统来说冗余设计反而是负担。用户表的密码字段存的是BCrypt加密后的密文绝对不允许明文存库这一条在答辩时是加分项。商品表是整个系统的枢纽。核心字段包括标题、描述、价格用Decimal类型而不是Float避免金额精度问题、图片URL存字符串路径而不是二进制、所属分类ID、发布者ID、成色描述全新/几乎全新/轻微使用痕迹/明显磨损等、交易状态0在售、1已被下单、2已售出、3已下架、浏览量。这里有一个关键设计商品状态和订单状态分开管理商品被下单时锁定为“已被下单”但交易未完成前商品不能被删除防止并发场景下订单和商品状态错乱。订单表记录交易关系。字段包括订单编号用时间戳随机数生成可读性好且不会重复、商品ID、买家ID、卖家ID、订单状态0待付款、1待发货/待面交、2已完成、3已取消、下单时间、完成时间和交易备注。注意这里买家ID和卖家ID都来自用户表所以在SQL查询时要别名区分。分类表很简单ID、分类名称、排序号、状态。用固定数据初始化教材书籍、数码产品、生活用品、运动器材、自行车、其他。分类数量控制在六个左右最好太多会让发布表单变得累赘。收藏表解决“想要”的需求。字段是ID、用户ID、商品ID、创建时间联合唯一索引user_id, product_id防止重复收藏。2.2 为什么不用外键和复杂关联查询很多教材喜欢讲外键约束但真实开发中尤其这种毕业设计级别的前后端分离项目我建议减少物理外键的使用改用逻辑关联。原因很简单物理外键会让数据的插入和删除变得极其麻烦比如删除一个用户时如果他有在售商品外键约束会直接报错。而实际业务中用户“注销”更合理的做法是软删除——用一个deleted字段标记而不是真正从库里删掉。我在商品表和用户表都加了deleted字段做软删除查询时统一通过MyBatis Plus的逻辑删除注解自动追加条件。这样做的好处是数据始终可追溯又不影响查询效率而且不用写一堆级联删除的SQL。用逻辑外键也就是普通的索引字段进行关联查询时配合MyBatis Plus的分页插件性能完全够用。2.3 索引设计别让查询烂在数据量上虽然毕设级系统的数据量通常不大但索引设计仍然是加分项。我在三处加了索引商品表的status和create_time联合索引用于首页按状态过滤并按时间排序、订单表的buyer_id和seller_id索引用于“我买到的”和“我卖出的”列表、收藏表的联合唯一索引。用EXPLAIN验证过这些索引在几千条数据量下就已经能看到效果。3. 前端核心页面与关键实现3.1 首页商品流条件查询与排序的实现方案首页是一个交易系统的门面。我的首页布局是顶部导航栏包含搜索框、发布按钮、用户下拉菜单、左侧是分类筛选栏、中间是商品卡片瀑布流、右侧是热门推荐位。首页的商品列表接口设计得非常简单直接传入keyword、categoryId、sortType、pageNum、pageSize五个参数。sortType支持三种默认按最新发布排序、按价格从低到高、按价格从高到低。前端用Vue3的ref维护筛选状态任何一项变化就重新请求数据。这里有一个可以拿出来讲实现难点的点图片懒加载。Element Plus的el-image组件自带懒加载属性lazy但需要注意它在切换筛选条件时图片闪烁的问题。解决方案是给el-image加上key值强制组件重新渲染。价格区间的筛选我放到了前端组件里做成两个输入框加一个确认按钮传给后端的参数是minPrice和maxPrice。后端用MyBatis Plus的QueryWrapper做动态拼接代码简洁且参数为空时自动忽略条件。3.2 发布商品模块前端校验与后端兜底商品发布是整个系统里逻辑最完整的一个模块也是答辩时最能展示水平的部分。前端表单元件包括标题输入框限制1-30字、描述多行文本框、价格输入框限制两位小数、分类下拉选择、成色单选、图片上传、联系方式输入框。图片上传是一个值得展开讲的技术点。我前端使用Element Plus的el-upload组件action指向后端的文件上传接口。上传成功后后端返回文件访问的相对路径前端把这个路径收集到一个图片列表中提交整个商品表单时再把图片路径数组一并提交。前端校验我用的是Element Plus表单的rules机制。需要自定义校验的地方是“价格必须大于0且最多两位小数”写一个validator函数即可。但注意前端的校验只是用户体验层面的真正安全的校验必须落在后端。后端用了spring-boot-starter-validation注解在实体类的价格字段上加DecimalMin(0.01)和Digits(integer6, fraction2)这样即使有人绕过前端直接调接口也造不了乱。3.3 商品详情页与轮播图实现商品详情页是整个系统中访问频次最高的页面。左半部分是图片轮播Element Plus的el-carousel右半部分从上到下排列标题、价格用醒目颜色展示、成色、分类、浏览量、卖家信息卡片头像、昵称、学校学院、发布时间、操作按钮区。按钮区需要根据当前用户身份和商品状态做差异化渲染如果是卖家本人显示“编辑”和“下架”如果是其他登录用户且商品在售显示“立即购买”和“收藏”未登录用户一律显示“登录后购买”已售出的商品则显示交易完成的状态标签。这个逻辑用一个computed函数就能算清楚但要注意computed内部依赖的响应式数据必须包含当前用户信息和商品状态的深度监听。详情页还有一个信息展示模块商品浏览量的实时更新。我用了最简单的方式请求详情接口时后端直接返回浏览量字段前端展示。同时后端在详情接口里做了浏览量自增。虽然严格来说这不是实时统计但应付演示和论文完全够了也不要给自己挖“高并发下计数不准”这种坑。3.4 订单处理流程状态机的设计与页面联动订单模块的难点不在增删改查而在状态流转。我给订单定义了四个状态0待付款、1待收货/面交、2已完成、3已取消。操作按钮跟随状态变化当订单状态为0时买家可执行“取消订单”此时商品恢复为在售状态库存如果有回补。 当订单状态为1时买家可执行“确认收货”或“申请取消”卖家可执行“确认发货”。确认收货后订单变更为2商品状态变更为已售出。 订单状态为3的订单不参与任何后续操作仅作为历史记录展示。这个状态流转我用一个订单状态常量文件管理避免魔法数字散落在代码各处。前端用一个订单状态标签组件统一渲染不同状态的颜色避免每个页面写死if else。状态变更时前端发请求给后端后端校验状态转化的合法性再更新数据库。比如已取消的订单不能直接跳转到已完成否则会破坏数据一致性。支付环节是这个模块最尴尬的地方——真的接支付宝或者微信支付需要企业资质学生个人没法申请。所以我的处理策略是在订单创建后展示一个“在线支付模拟”的按钮点击后弹出确认框模拟等待两秒后直接置为待发货/面交状态。这个设计在论文里要诚实描述为“支付模拟模块”答辩时可以说这是预留接口后续可以替换为真实支付SDK。千万不要造假说自己接入了真实支付这是答辩被问倒的高发区。4. 管理后台用Vue3实现高效的数据看板4.1 布局与路由权限控制管理后台和前台是两个独立的页面应用还是一个应用分角色渲染我的做法是做成同一个Vue应用的二套布局通过路由meta信息来区分前台页面和后台页面然后通过前置守卫判断用户角色来拦截访问。后台布局用的是Element Plus的Container布局左侧菜单栏el-menu顶部是面包屑导航和退出按钮中间是主内容区router-view。侧边栏菜单配置和路由表联动菜单项点击后跳转对应路由路由变化时菜单高亮项同步更新。这里用了一个技巧把后台所有页面的路由配置整理成一个数组用v-for生成el-menu-item菜单的index直接绑定路由路径而不是在el-menu里写死每个item。路由权限控制是必答的考点。我用的是Vue Router的beforeEach守卫在跳转前从Pinia的store里读取当前用户信息判断目标路由的meta.requiresAdmin是否为true如果是且当前用户不是管理员就跳转到登录页并带上redirect参数——这样管理员登录过后会被直接送回原本想访问的页面体验更好。4.2 商品审核与用户管理的CRUD细节运营后台最常用的功能就是商品列表的用户管理本质无外乎是个表格页但有几个细节值得重点处理商品审核列表的筛选条件放在表格上方状态筛选待审核、已通过、已拒绝、分类筛选、关键词搜索。表格列包括商品ID、缩略图el-image带预览功能、标题、分类、价格、发布者、发布时间、状态标签、操作按钮。对于待审核的商品操作按钮是“通过”和“拒绝”对于已通过的商品则是“下架”。用户管理表格相对简单但要注意密码字段绝对不能返回到前端。后端的用户实体类在序列化时用JsonIgnore注解把password字段排除掉这是安全性底线。用户列表支持关键词搜索、状态筛选正常、封禁、重置密码功能后端把密码重置为初始化密码待办事项里提示用户下次登录后修改以及封禁/解封操作。封禁用户时前端弹确认框后端做逻辑更新同时把该用户所有在售商品强制下架并给用户发送站内信通知。所有列表页的删除操作都不做物理删除而是软删除。这样即便误删也可以通过改数据库字段恢复数据答辩演示时如果误操作了也不会当场翻车。4.3 数据看板的设计从Excel思维转到可视化思维数据看板这个页面是提升系统档次的利器。我用ECharts的折线图和柱状图展示近7天/30天的商品发布量趋势用一个环形图展示商品分类占比用几张卡片展示核心指标总用户数、总商品数、待审核商品数、今日订单数。这里需要注意ECharts的按需引入。全量引入会把整个charts包打进bundle里体积非常大。我改成手动按需引入只引入LineChart、BarChart、PieChart和需要的组件。Vue3下用vue-echarts这个库接入更平滑它会自动处理响应式问题——window尺寸变化时图表跟随缩放。数据看板的数据接口是一个聚合查询。我直接在mapper里写了自定义SQL用DATE_FORMAT按天分组统计。后端返回一个Map列表前端按ECharts的要求转换成series格式即可。5. 权限认证与安全防护登录、令牌与常见漏洞封堵5.1 基于JWT的登录认证设计前后端分离项目最常用的认证方案是JWTJSON Web Token我也用的这个方案。登录流程是前端把用户名我这里用的是学号和密码通过HTTPS POST到登录接口后端验证通过后生成一个包含用户ID和角色的JWT令牌返回给前端。前端把这个令牌存到localStorage里之后每次请求都在请求头里带上Authorization: Bearer token。前端请求拦截器统一配置携带token的逻辑响应拦截器统一处理token过期。最容易踩坑的地方在于401处理当token过期时后端返回401前端响应拦截器应该清除本地的用户信息并跳转到登录页。这里注意不能只跳转要把当前访问的路径记录下来否则用户重新登录后会被甩回首页而不是他原本想看的那页。笔者这里遇到的一个实际问题是有些用户清除了浏览器缓存后localStorage里的token和Pinia里的用户信息不一致了一刷新界面就变得很怪异。解决的稳妥方案是做一个“当前用户信息拉取”接口页面加载时如果检测到有token就调用这个接口拿最新用户信息拿不到就清理token。5.2 密码存储与XSS、CSRF的防御实践密码安全是最基础的安全要求。后端使用BCryptPasswordEncoder对密码进行加密存储登录验证时用matches方法比对明文密码和密文。BCrypt算法的好处是每次哈希结果不同但能验证通过而且本身内置盐值不需要额外维护盐字段。XSS攻击的防御点主要在前端所有用户输入的内容在渲染时都不使用v-html而是使用插值表达式——Vue默认的插值会转义HTML标签所以只要你不手贱用v-html大部分XSS场景都翻不了车。后台富文本编辑的需求如果实在有建议引入成熟的富文本组件并且在后端对内容进行HTML标签白名单过滤。CSRF跨站请求伪造在前后端分离JWT的模式下风险相对较低因为JWT不依赖Cookie自动携带的机制攻击者没法在第三方网站上构造一个带着用户token的请求。如果之后改成基于Cookie的会话方式才需要专门配置CSRF防护。这块你理解到这种程度答辩时已经够应付了。5.3 文件上传的安全限制文件上传是二手交易系统里最容易出现安全隐患的模块。上传接口我做了四个限制文件类型白名单.jpg、.jpeg、.png、.gif、.webp只允许图片、文件大小上限单张不超5MB、文件内容头校验不能只看扩展名还要读取文件头判断真实类型、存储文件名重命名用UUID生成新文件名保持原扩展名。后端的实现方式是Spring Boot的MultipartFile加上自定义的拦截判断存储到服务端的指定目录并给这个目录配置虚拟路径映射让图片可以通过URL直接访问。商品图片列表页不建议把多个图片塞进一个字段用逗号分隔因为后端的“主图”逻辑会很绕。我的做法是商品图片表和商品主表分开图片表里有商品ID和排序字段查询时按排序返回图片数组。如果嫌表太多折中方案是商品表加一个cover字段存主图详情页轮播只展示一张主图。但说真的正规做法还是分表代码写起来清爽很多。6. 前后端联调我踩过的那些想不到的坑6.1 跨域问题的终极解决方案开发环境下的跨域问题基本每个项目都会遇到。前端跑在Vite的5173端口后端跑在Spring Boot的8080端口浏览器直接发请求必然产生跨域阻塞。Vite的解决方案是在配置文件中加代理// vite.config.js export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, ) } } } })这样前端所有请求都以/api开头代理转发时去掉前缀就不存在跨域问题了。后端不需要配置CORS也能工作。注意生产环境部署时没有Vite代理这回事了就要保证前后端部署在同一个域名下或者后端单独配置CORS允许指定域名。我这里在上线阶段用Nginx做了反向代理统一入口。6.2 图片上传后的路径问题图片上传后返回的是“相对路径”比如/uploads/2024/10/xxx.jpg前端把完整的图片URL拼成http://localhost:8080/uploads/2024/10/xxx.jpg来访问。这个拼接方式在开发环境没问题但部署到服务器后后端服务地址变了前端写死的localhost就歇菜。好的做法是前端统一用一个图片访问基础地址的常量文件管理或者后端在上传接口直接返回完整可访问的URL。考虑到部署后域名统一我更倾向于后端只返回相对路径、前端在请求时统一拼接这样环境切换时只需要改一个配置文件。6.3 商品状态和订单状态不一致的坑我遇到的最恼人的逻辑漏洞出现在一个边界场景里买家下单后并没有立即支付而是取消了订单与此同时卖家在后台把商品下架了。这时候出现了一个“已取消”的订单但它关联的商品状态是“已下架”从用户视角看这个商品在记录里无法再回到在售状态卖家也再找不到地方把这个商品重新上架。解决办法是在取消订单的后端接口里增加一道守卫当订单状态从0变更为3时检查关联商品的当前状态如果商品处于正常状态0在售则恢复在售如果商品已经被下架或删除则保持原样。用事务包裹这几个更新操作避免中间状态出现。这种问题说明了一个真理状态机设计里所有状态流转的出口都要考虑被关联对象的状态不能只盯着自己这一张表。写代码之前花半小时把状态流转图画清楚比改bug省太多时间。6.4 Element Plus按需引入后样式丢失按需引入Element Plus组件时我用的是官方推荐的unplugin-auto-import和unplugin-vue-components方案但表格的某几个样式比如分页器在暗色背景下特别丑或者个别组件的弹层样式偶尔会不生效。排查后发现是组件被自动导入但对应的样式文件没有正确引入。解决方法是在入口文件里手动补充引入基础样式import element-plus/theme-chalk/el-message.css import element-plus/theme-chalk/el-message-box.css import element-plus/theme-chalk/el-loading.css这几个弹层组件的样式因为是用命令式方式调用的不会被按需导入插件自动识别。遇到样式对不上的组件就照这个思路手动引入对应CSS这是已知的插件局限不用反复排查浪费一晚上。7. 项目工程化与可维护性的一些实用建议7.1 目录结构与命名规范不要小看目录结构它决定了你后期写论文和答辩准备素材的效率。我的Vue3项目目录是这样组织的src/ ├── api/ // 接口请求封装按模块拆文件 │ ├── product.js │ ├── order.js │ └── user.js ├── assets/ // 静态资源 ├── components/ // 全局公共组件 ├── layout/ // 前台和后台布局组件 ├── router/ // 路由配置 ├── stores/ // Pinia状态 ├── utils/ // 工具函数请求封装、格式化等 └── views/ // 页面组件 ├── home/ ├── product/ ├── order/ ├── user/ └── admin/接口请求独立成一个模块放在api目录好处是页面组件里不直接写Axios逻辑全部通过调用api函数来请求。后端接口路径如果变了只需要改api文件裡的url不需要动页面组件。这让重构和维护的代价大幅降低也便于答辩时展示你的工程化意识。命名规范这一块我的约定是组件文件用大驼峰ProductCard.vue路由路径和接口路径用小写连字符/product-detail。变量名里面所有响应式数据的ref名字以Ref结尾区分productListRefcomputed以Computed结尾。这不是强制标准但保持统一后代码可读性显著提升。7.2 环境变量配置多套环境我在项目里配了三个环境文件.env.development开发环境、.env.production生产环境、.env.test测试环境。每个环境文件里只放几个关键变量VITE_API_BASE_URL接口基础地址、VITE_UPLOAD_URL上传地址、VITE_APP_TITLE页面标题。前端代码里所有接口请求和资源地址拼接都用import.meta.env来读取。这样在开发机上跑和后端联调在服务器上部署上线只需要切环境变量不用改动任何业务代码。更重要的是答辩演示如果在教室里的电脑上进行网络环境不稳定你可以提前准备一个本地mock模式把VITE_API_BASE_URL指向本地启动的mock服务确保现场演示的稳定性。这个小细节能帮你避免当天网络状况不佳导致的尴尬场面。8. 常见问题速查表开发周期里被问爆的问题合集问题现象可能原因解决手段登录后刷新页面路由守卫误判跳回登录页Pinia中的用户信息是内存数据刷新丢失在App.vue的onMounted里拉取用户信息或使用Vuex persist插件做持久化上传图片后表格里不显示图片路径拼接错误或后端未配置虚拟映射检查后端资源映射配置确认返回路径是否带/开头商品列表分页点击后数据不变分页组件page-size绑定值未转数字给el-pagination的v-model:current-page绑定数字类型必要时Number()强转修改商品状态后旧数据还在列表里后端未重新查询或者缓存未清理确认接口返回的是新查询结果不要复用之前的内存对象后台请求返回403进不去路由守卫拦截了非管理员访问在守卫里打console.log输出现有角色判断依据重点检查store和token解析结果发布商品时图片一直转圈上传失败后端文件上传接口报错或跨域直接浏览器访问后台上传URL看是否通检查代理配置是否覆盖该接口Redis缓存了旧分类数据如果用了缓存需要清理后台分类管理的修改操作里主动删除缓存Element Plus表格排序箭头无反应未设置sortable与后端排序参数关联自定义表头的排序事件监听排序变化重新请求数据9. 我的一点实际操作心得最后说几个不带代码的实在体会。第一做这种系统最重要的不是炫技而是先把主干业务跑通。我见过有人花了三周时间做精美的后台UI结果前台连下单流程都没走通。答辩时评委关注的是功能的完整性和技术难点的说明不是视觉的华丽程度。第二文档和代码的一致性非常重要。开发时顺手把接口文档、数据库表结构说明、功能清单维护好到最后写论文的时候会特别省力。我的做法是每个后端接口都用Swagger注解标注好自动生成接口文档论文里把Swagger页面截图放上去比纯文字描述直观得多。第三给自己多留一个“亮点功能”。我的系统里加了Excel导出——平台运营人员可以把商品、订单数据汇总导出成Excel报表。这个功能花了一天时间但答辩时很能撑气场而且技术含量足够讲清楚。类似的亮点还可以是商品浏览量的Redis缓存、登录验证码、用户操作日志AOP实现、热门商品排行榜等。第四因为项目代号是4259233我把这个编号作为一个版本号标记在了README和系统版本公告里。这个小细节不体现技术但和项目名里的编号呼应得上答辩材料整理起来也方便追踪版本迭代。二次开发的方向其实有很多接入校园统一身份认证、增加在线聊天功能、把推荐算法引入首页商品流、或者把移动端适配做好。这些你可以在论文的展望章节里提一嘴既诚实又显得你在思考系统的纵深。先把这个版本的每个页面和接口吃透后续你加任何功能都会有很清晰的地基。