ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

SpringBoot+Vue旧物置换系统实战:从数据库到部署全解析

SpringBoot+Vue旧物置换系统实战:从数据库到部署全解析 简介Java基于SpringBoot与Vue的旧物置换交易平台是一套完整的前后端分离毕业设计项目面向计算机相关专业学生及需要快速搭建二手交易场景的开发者。平台采用B/S结构与SpringBoot框架配合MySQL数据库和Vue.js实现涵盖管理员后台、卖家后台、用户前台三大模块支持旧物信息发布、分类管理、置换交易、公告与系统管理等完整业务流程。包内含634个文件压缩包29.25MB包含106个Java后端源码、75个Vue前端页面、151个JavaScript脚本、44个CSS样式以及HTML页面、SQL数据库脚本、XML配置和论文PPT等文档目录结构清晰便于按层查阅。目前已有113人学习浏览对于毕设选题或SpringBootVue实战练习具有较高参考价值。附带论文和PPT可帮助理解设计思路并快速完成答辩材料准备。1. 旧物置换平台选型SpringBootVue解决了这三件事旧物置换网站的业务模型不复杂普通用户挂出旧物、浏览、发起置换卖家管理旧物信息管理员做全局管控。真正的复杂度集中在权限隔离和置换状态流转上。选SpringBoot框架做后端、Vue.js做前端等于把这两个问题分开治理后端用starter机制快速装配Mysql、事务和接口前端用组件化把管理员后台、卖家后台、前台首页拆开维护。配合Tomcat中间件B/S结构链路完整可跑。这份源码包还带论文和答辩PPT适合做课程设计或毕设脚手架也适合想快速看SpringBootVue完整项目结构的从业者。下面按数据库到后端接口再到前端对接和部署的顺序拆。2. 数据模型Mysql表结构如何支撑三端角色旧物置换项目里最先要确定的是表结构。它直接决定后端接口写起来是顺手还是别扭也决定前端三个角色页面取数时要不要来回join。这一章从角色划分、建表SQL、实体类映射三个层面拆开讲。2.1 用户、卖家为什么要拆成两张表表单设计上最容易犯的错是把管理员、买家、卖家塞进一张表靠role字段区分。这套项目的设计是用户表和卖家表分开普通用户表存登录凭证和基本信息卖家表单独存店铺级信息与状态。原因在于卖家的操作域管理旧物类型、上下架旧物、处理置换交易和普通用户发起置换、查看记录完全不同。拆表之后后端做权限控制时直接用主键ID关联比一张表加role判断更直白。这里有个取舍要说清楚拆表不意味着普通用户永远不能成为卖家需要时由管理员在后台把用户升级成卖家记录即可。常见做法是用户表保留一个字段关联卖家主键为空就是普通用户。四张核心表的字段划分如下表名关键字段作用userid、username、password、phone、avatar普通用户登录认证与个人信息sellerid、user_id、shop_name、status卖家身份信息status控制是否允许发布goodsid、seller_id、type_id、name、photo、description、price、status、version旧物信息status区分上架/置换中/已成交swap_orderid、goods_id、from_user_id、to_user_id、status、create_time置换交易记录记录谁发起、谁接收从字段可以看出来goods表的version字段是给并发控制预留的第三章会展开。swap_order表没有放价格快照因为业务核心是以物易物金额只作参考展示不参与结算这样可以少维护一个价格变更后订单是否要联动的问题。2.2 建表SQL的细节字符集与状态字段默认值建表SQL的关键点落在字符集和状态默认值上。字符集建议统一为utf8mb4。旧物描述和置换申请备注里经常出现生僻字或特殊符号utf8mb4能保证不乱码在Mysql 5.7环境下utf8mb4还能规避某些索引长度超限问题。CREATE TABLE goods ( id BIGINT PRIMARY KEY AUTO_INCREMENT, seller_id BIGINT NOT NULL COMMENT 卖家id, type_id BIGINT NOT NULL COMMENT 旧物类型id, name VARCHAR(100) NOT NULL, photo VARCHAR(255) DEFAULT , description TEXT, price DECIMAL(10,2) DEFAULT 0.00, status TINYINT DEFAULT 0 COMMENT 0上架中 1置换中 2已成交, version INT DEFAULT 0 COMMENT 乐观锁版本号, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;状态字段统一用tinyint用注释写清每个值的含义。用数字而不是字符串的好处是Java代码里可以直接定义常量或枚举接口返回时再映射成中文避免数据库中散落一堆拼写不一致的状态值。CREATE_TIME加默认当前时间插入记录时少传一个参数也避免应用服务器时间和数据库时间不一致导致排序错乱。部署后若发现中文乱码先查两处application.yml里Mysql连接串有没有characterEncodingutf8建表语句的DEFAULT CHARSET是不是utf8mb4。这两处不一致是最常见的乱码来源改一处留一处问题照样复现。2.3 实体类映射MyBatis用注解还是XML后端持久层用MyBatis实现SpringBoot集成时有两种写法注解SQL和XML映射。一个干净的约定是单表增删改查用注解多表联查和动态SQL用XML。旧物信息列表需要join类型表取分类名称置换交易记录需要join用户表取昵称和头像这类查询放XML里维护更清晰SQL变更时不用重新编译Java代码。实体类对应goods表的写法如下public class Goods { private Long id; private Long sellerId; private Long typeId; private String name; private String photo; private String description; private BigDecimal price; private Integer status; private Integer version; // getter / setter 省略 }字段命名遵循驼峰Mysql字段用下划线MyBatis配置mapUnderscoreToCamelCasetrue后自动映射不需要手写大量resultMap。photo字段存的是图片URL而不是Base64字符串。图片上传后返回访问路径页面用浏览器缓存图片不会因为后端一次性返回大量图片数据导致接口超时。很多新手在这个字段上踩坑直接把图片转成Base64塞进数据库结果是列表页接口返回几十KB的字符串前端渲染卡顿换URL方案后问题立刻消失。3. 置换接口实现状态流转与防重复提交置换交易是旧物平台的核心链路也是最值得细看的部分。它不是一个简单的insert而是要处理状态迁移、事务一致性和并发下重复下单。这一章按状态机设计、接口链路、乐观锁三个层次展开。3.1 置换交易的四种状态置换不是一步完成的。用户看中一件旧物后发起申请卖家看到申请后同意或拒绝同意后进入完成状态整个链路在swap_order表中用一个状态字段驱动。status含义可流转到的状态0待卖家处理1、21卖家已同意32卖家已拒绝无3交易完成无状态机的价值在于无论前端怎么点击后端只接受合法的状态迁移。一笔已拒绝的交易不能再变成已完成一笔已完成的交易不能回退成待处理。前端按钮可以置灰但后端必须在Service层再校验一次否则绕过前端直接调接口就能破坏数据。这里也是Java面试八股文常考的切入点状态判断放Service层不放Controller层因为Controller只负责参数接收和响应包装业务规则必须沉淀在Service里。3.2 发起置换的接口链路前端发起置换时需要传goodsId、fromUserId、toUserId三个参数。Controller层只做参数接收和简单校验然后调用ServicePostMapping(/swap) public Result createSwap(RequestBody SwapRequest request) { if (request.getFromUserId() null || request.getToUserId() null || request.getGoodsId() null) { return Result.error(参数不完整); } return Result.success(swapService.createSwap(request)); }Service层做三件事第一查询goods信息校验status是否为上架中第二插入swap_order记录状态置为待卖家处理第三把goods.status更新为置换中防止该商品再被其他人发起置换。这三件事必须在同一个事务里完成方法上加Transactional注解。如果第三步更新失败整个事务回滚swap_order记录也不会残留不会出现订单存在但商品状态没变的脏数据。Orderinsert和update的执行顺序有讲究。先insert再update事务提交时两个操作是一个原子单位回滚都能回滚干净。如果先在Service里update商品状态再insert订单一旦insert抛异常商品状态已经被改了虽然事务回滚能恢复但排查问题时订单表的自增ID会出现空洞日志看起来更乱。3.3 用version字段解决并发下的重复下单上面的链路有个并发隐患两个用户几乎同时看到同一件旧物同时点击发起置换两边都通过了status校验。因为在第一步查询时两个请求读到的都是上架中状态。解决办法是乐观锁。goods表里的version字段参与UPDATE条件UPDATE goods SET status 1, version version 1 WHERE id #{goodsId} AND status 0 AND version #{oldVersion}执行后判断影响行数。影响行数为1说明更新成功继续提交订单影响行数为0说明商品状态已被其他请求改掉本次置换申请要失败int rows goodsMapper.compareAndSwapStatus(goodsId, oldVersion); if (rows 0) { throw new BusinessException(该旧物已被置换请选择其他物品); }注意oldVersion必须来自发起请求前查询出的版本号不能先查version再立刻用查和用之间夹了别的逻辑并发窗口就重新打开了。这个思路在Java面试题里经常被叫乐观锁实现但项目里用它不是为了应付面试而是旧物平台并发量远没到需要分布式锁的程度一行UPDATE加影响行数判断简单且可靠。3.4 卖家处理置换时的反向更新卖家同意置换时除了把swap_order.status从0改为1还需要把goods的status从置换中改为已成交。拒绝则把swap_order状态改为2并把goods状态回滚为上架中让其他用户还能继续发起申请。回滚必须做否则一件被拒绝的商品会永久卡在置换中列表页再也搜不到。反向更新同样要走乐观锁但此时条件不同WHERE id goodsId AND status 1置换中。如果影响行数为0说明这件商品状态已经被别的事务改掉了此时要返回一个明确提示让卖家刷新页面再看。这个场景在联调时经常出现卖家在A页面打开商品详情另一个人已经在B页面完成了置换A页面卖家点同意时就会拿到0行更新提示信息要做好不能抛一个500让前端干瞪眼。4. Vue前端路由划分与axios接口对接后端接口定义好了前端要解决的是三件事三套页面的路由怎么组织、接口请求怎么统一封装、登录态和角色权限怎么在前端控制。这一章的代码都是从项目里抽出来的实际结构直接对应SpringBoot后端接口。4.1 三套页面的路由结构前端按前台与后台拆成两套布局。前台给普通用户浏览旧物、查看公告、发起置换后台分管理端和卖家端。路由设计上把组件懒加载打开避免首次加载时把三个角色的代码全部下载影响首屏速度const routes [ { path: /, component: () import(/views/Home.vue) }, { path: /goods, component: () import(/views/goods/GoodsList.vue) }, { path: /goods/:id, component: () import(/views/goods/GoodsDetail.vue) }, { path: /admin, component: () import(/layout/AdminLayout.vue), children: [ { path: users, component: () import(/views/admin/UserManage.vue) }, { path: goods, component: () import(/views/admin/GoodsManage.vue) } ] } ]vue-router用history模式时部署到Tomcat后刷新非首页路由会404因为Tomcat没有把请求转发到index.html。常见做法是让后端增加一个转发规则把所有未匹配路径转发到index.html或者前端切回hash模式。拆这种带后台管理的项目时我一般建议先用hash模式省掉服务端配置把精力放在业务上上线后再根据运维能力决定要不要切history。如果遇到打包后布局异常的问题多半是静态资源路径不对。Vite或Webpack构建时publicPath配成相对路径部署到Tomcat的子目录下才能正常加载配成绝对路径/则只适合部署在ROOT根目录。这个细节很多人在本地开发时发现不了因为本地服务器默认就在根路径。4.2 axios实例的封装与外层拦截接口请求统一走axios封装不直接在组件里new axios。封装的核心是baseURL和响应拦截器import axios from axios const service axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器从localStorage取token并附加到请求头 service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] token } return config }) // 响应拦截器统一处理Result响应体的code service.interceptors.response.use( response { const res response.data if (res.code ! 200) { return Promise.reject(new Error(res.message)) } return res.data }, error Promise.reject(error) ) export default servicebaseURL写成/apiSpringBoot端在application.yml里配server.servlet.context-path/api前后端路径统一由一处管理。注意SpringBoot框架不同版本这个配置项的写法有差别较新版本是server.servlet.context-path老版本是server.context-path配置不生效时先检查版本差异这是springboot配置里最常见的坑之一。响应拦截器统一把res.code为200的数据解包返回组件里拿到的直接是业务数据不用每一次都写res.data.data。错误分支统一reject配合异常提示组件把错误信息弹出来。这样前端页面的代码会清爽很多不会每个接口都写一套错误处理。4.3 登录态与前端路由守卫前端路由守卫监听跳转判断目标页面是否需要登录、当前用户角色是否匹配。角色信息在登录接口返回后一并存到localStoragerouter.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { next(/login) return } if (to.meta.roles) { const role localStorage.getItem(role) if (!to.meta.roles.includes(role)) { next(/) return } } next() })前端路由守卫只是体验层拦截真正的权限控制必须靠后端接口校验。后端可以在SpringBoot拦截器里校验Authorization头也可以在每个Controller方法上声明角色注解。两者都做时前端控制的是能不能看到这个入口后端控制的是这个请求能不能执行。只做前端不做后端的项目用Postman改一下header就能越权这种低级漏洞在答辩和代码审查时都会被直接点出来。5. 部署脚本和打包后静态文件的几个坑源码包里有几个容易被忽略的文件1-install.bat、2-run.bat、mvnw.cmd、.classpath、app.a9c106a8.css、index.html.bak。它们分别对应依赖安装、启动脚本、Maven版本固定、Eclipse工程配置和前端构建产物。1-install.bat一般执行mvn install或npm install把依赖装齐2-run.bat负责启动SpringBoot应用。mvnw.cmd是Maven Wrapper作用是把Maven版本固定到项目需要的版本避免本机Maven版本过高或过低导致构建失败。SpringBoot版本太高报错时很多情况不是框架问题是Maven和JDK版本不匹配。用Wrapper启动的命令是mvnw.cmd spring-boot:run它读取.mvn/wrapper配置决定用哪个Maven版本绕开本机环境差异。.classpath是Eclipse导入时读取的编译路径配置。如果用IDEA这个文件可以忽略但不要删因为源码包的使用者可能还在用Eclipse。app.a9c106a8.css是前端构建后带内容hash的静态文件出现在源码包里说明dist目录被打进了部署包。部署时把它放到SpringBoot的static目录下或直接放到Tomcat的webapps对应目录。index.html.bak是原始index.html的备份一般不用管但要确认前端入口文件是index.html而不是index.html.bak否则启动后访问页面会白屏。部署时最常见的三个坑。第一端口被占用2-run.bat启动报错时把application.yml里的server.port换一个未占用端口。第二图片路径写死localhost部署到服务器后全部裂图改成相对路径或通过后端配置映射到上传目录。第三登录接口能通但页面请求404检查前端baseURL的/api和后端context-path是否完全一致差一个斜杠请求就会落到错误路径。验证部署是否成功有个顺序先直接访问后端接口文档地址确认SpringBoot应用本身正常再用浏览器访问前端页面用F12看Network面板里第一个接口请求是否返回200。这一步能在一分钟内区分问题出在前端还是后端比盲目改代码再反复重启快得多。本文还有配套的精品资源点击获取
返回列表