新闻详情

新闻详情

首页 / 资讯中心 / 详情

基于SpringBoot2+Vue3+MyBatis-Plus的校园闲置物品交易系统实战解析

发布时间:2026/10/2 10:20:46来源:尧图网络
基于SpringBoot2+Vue3+MyBatis-Plus的校园闲置物品交易系统实战解析
1. 项目概览校园闲置物品交易系统的价值与技术选型最近在帮学生调试一个基于 SpringBoot2 Vue3 MyBatis-Plus MySQL8.0 的校园闲置物品交易系统源码项目整体看下来这个组合在当下确实很有代表性。它不只是一个“毕业设计级别”的 CRUD 堆砌而是一个前后端分离、包含完整交易主流程、带文档、可跑通上线全过程的 Java Web 项目。如果你正在准备毕设、课设或者想找一套“能讲清楚技术点”的 Java Web 实战项目来提升自己这套系统的设计思路和实现细节都值得拆开看一遍。校园闲置物品交易这个场景核心价值在于解决“学生想转让旧书、小电器、自行车但缺少一个可信、封闭、围绕校园圈子来做的交易撮合平台”。相比闲鱼这类公网平台校园系统更强调用户验证、校园分区、线下自提业务边界更清晰非常适合用来做前后端分离项目的练手素材。从功能上看它通常涵盖用户注册登录、商品发布与管理、商品检索筛选、收藏留言、下单购买、订单状态流转、个人中心等主链路足以覆盖一个 Java Web 系统所需要的常规能力。这套系统适合谁参考第一种是准备 Java Web 方向毕业设计的学生可以用它讲清楚前后端分离、RESTful 风格接口、权限认证、数据库建模等高频答辩点第二种是刚学完 Spring Boot 和 Vue 基础、想找个完整项目把零散知识串起来的开发者它可以让你看到真实项目中代码是怎么分层、接口怎么约定、前端怎么联调第三种是有校园信息化需求的技术社团或实验室可以直接用它跑一套最小可用产品再根据学校情况做二次扩展。1.1 为什么这个技术栈组合是“黄金搭档”先说后端Spring Boot 2 是当前 Java 生态里最主流的基础框架相比 Spring MVC 时代它用自动装配把大量配置收敛了让开发者把精力放在业务代码上这对做课设、毕设以及中小企业内部系统非常友好。搭配 MyBatis-Plus相当于在 MyBatis 之上加了通用 CRUD、分页插件、逻辑删除、条件构造器这些能力写单表操作时基本不用再手写 XML代码量直接砍一半。这一点在实际开发中体验极好尤其是商品列表的多条件筛选用QueryWrapper就能优雅解决。再说前端Vue 3 配合 Vite 开发体验确实比 Vue 2 Webpack 顺滑很多启动热更新干净利落组合式 API 让复用逻辑更舒服。UI 层一般搭配 Element Plus做后台管理类、信息展示类页面非常快。之所以不选 React是因为国内 Java 后端开发者的普遍技能树里Vue 的学习曲线更平滑而且中后台生态成熟和 Spring Boot 组合的参考案例也更多。数据库选择 MySQL 8.0 则看重的是它的稳定性和新特性比如窗口函数、通用表表达式以及默认字符集 utf8mb4对商品标题里的 emoji 和生僻字支持更好。虽然 MySQL 5.7 也很成熟但 8.0 毕竟是长远趋势从学习角度直接用 8.0 可以避免以后换版本的麻烦。这套组合还有一个隐藏优势参考文档和问题排查资料非常多。无论你是卡在 JWT 鉴权还是 MyBatis-Plus 分页失效亦或是 Vue 3 的响应式丢失问题基本都能搜到现成的解决方案。对初学者来说“能搜到”就是最大的护城河。1.2 源码里的“含文档”有多重要很多人看到“含文档”会觉得只是附加说明实际拿到这套项目源码我反而建议先读文档再碰代码。一份好的项目文档应当包含环境部署手册、数据库初始化脚本说明、核心接口列表、功能模块说明、以及常见问题。这些内容能帮你在最短时间内把项目跑起来而不是一头扎进上万行的代码里瞎找。我见过不少同学拿到源码后第一步就打开 application.yml改完数据库密码就启动结果端口冲突、Redis 没装、前端 npm install 失败一堆问题然后开始怀疑代码有问题。其实大多数问题文档里都写了只是没人愿意先看。所以拿到含文档的源码项目建议按这个顺序来先读文档的“快速启动”部分把 MySQL 初始化脚本执行一遍再启动后端用 Swagger 或 Knife4j 看接口是否能通然后启动前端完成登录、发布商品、下单这条主流程。跑通主流程之后再去分析代码结构效率会高很多。2. 系统模块与数据库设计核心细节校园闲置物品交易系统的业务模块并不算复杂但要设计得清晰、可扩展还是需要提前规划好模块边界和数据库关系。这一节先把整体功能拆解清楚再讲数据库表设计最后说说用 MyBatis-Plus 时如何统一字段风格。2.1 核心业务模块怎么划分从用户视角看系统可以划分为四个大模块用户模块、商品模块、交易模块、互动模块。用户模块解决“我是谁、我能做什么”的问题。包括注册、登录、信息修改、密码找回以及管理员对用户的禁用、审核等操作。这里需要注意的用户角色一般分为普通学生用户和管理员如果考虑线下自提可能需要用户填写校区、宿舍楼等字段。商品模块是整个系统的流量中心。包括商品发布、商品编辑、上下架、删除、商品列表展示、详情查看、分类筛选、关键字搜索、热门推荐等。这个模块最考验数据库查询设计尤其是多条件组合搜索用 MyBatis-Plus 的条件构造器能省很多事。交易模块是业务核心。包括用户下单、卖家接单、订单状态变更、取消订单、确认收货、订单评价等。简单版本的交易可以不做支付接口只保留订单状态机重点在于状态流转的约束和幂等性。互动模块包括收藏、留言、举报等。收藏让用户能沉淀意向商品留言用于买卖双方在线沟通举报则是平台治理的必要保障。如果要做站内私信可以再单独拆一个会话表但课设阶段用留言即可。2.2 数据库表设计与关联关系我建议核心表至少做这几张用户表userid、username、password加密存储、nickname、avatar、phone、school、campus、role0普通/1管理员、status、create_time、update_time、deleted。商品表productid、user_id、title、description、cover_image、images多个图片可以存 JSON 字符串或关联图片表、category_id、price、original_price、quality成色、status0草稿/1上架/2下架/3已卖出、view_count、create_time、update_time、deleted。订单表orderid、order_no、product_id、buyer_id、seller_id、amount、status0待付款/1待发货/2待收货/3已完成/4已取消、create_time、update_time、deleted。收藏表favoriteid、user_id、product_id、create_time、deleted。留言表comment或 messageid、product_id、from_user_id、to_user_id、content、create_time、deleted。分类表categoryid、name、sort_order、create_time。这几张表之间的关系比较清晰用户与商品是一对多用户与订单买家和卖家都是多对多通过订单表关联商品后可以补齐买卖双方用户与商品通过收藏是多对多商品与留言是一对多。2.3 基于 MyBatis-Plus 的 BaseEntity 统一字段设计实际开发里我不会每张表都重复写 create_time、update_time、deleted 这三个字段而是定义一个BaseEntity让所有实体继承。这样做的好处有两个一是代码更简洁二是配合 MyBatis-Plus 的自动填充和逻辑删除可以统一控制。Data public class BaseEntity { TableId(type IdType.AUTO) private Long id; TableField(fill FieldFill.INSERT) private LocalDateTime createTime; TableField(fill FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; TableLogic TableField(fill FieldFill.INSERT) private Integer deleted; }注意这里有个细节逻辑删除字段deleted的默认值要在插入时自动填充为 0否则实体新增时如果没显式设置会插入 null导致后续查询WHERE deleted 0匹配不到数据。很多人踩过这个坑。解决办法是在MetaObjectHandler里统一处理插入时的默认值。Component public class MyMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, createTime, LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, deleted, Integer.class, 0); } Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } }这样所有表都统一了字段风格查数据、写数据、做逻辑删除都省心。另外建议所有表的主键都用Long自增不要用 UUID 字符串因为自增主键在 MySQL 里索引效率更好前端传输时也不容易出现精度丢失问题。3. 后端实现SpringBoot2 MyBatis-Plus 的关键代码后端是整个系统的中枢接口设计、鉴权方式、文件上传、订单状态机都是容易踩坑又值得展开讲的部分。这节挑四个关键点聊目录结构与统一返回体、JWT 登录鉴权、商品发布上传、订单状态流转。3.1 后端目录结构怎么组织才清晰一个结构清晰的后端项目别人接手时能省太多时间。我习惯按“模块分包 技术分层”结合的方式组织com.campus.trade ├── common // 统一返回体、全局异常、常量 ├── config // 配置类MybatisPlus、跨域、拦截器 ├── controller // 控制器 ├── service // 业务接口 │ └── impl // 业务实现 ├── mapper // MyBatis-Plus Mapper接口 ├── entity // 实体类 ├── dto // 入参出参对象 ├── vo // 视图对象有时和dto合并 └── utils // 工具类JWT、文件上传等这种结构的核心价值是controller 层只做参数接收和结果封装service 层写业务逻辑mapper 层只做数据访问。很多课设代码喜欢把业务逻辑全写在 controller 里但这样做后期扩展和答辩演示时都会很难受。评审老师如果看到 service 层是空的第一印象就会扣分。统一返回体设计也非常重要。我用一个ResultT泛型类包裹所有接口返回值Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.setCode(200); r.setMessage(操作成功); r.setData(data); return r; } public static T ResultT error(Integer code, String message) { ResultT r new Result(); r.setCode(code); r.setMessage(message); return r; } }前端拿到code 200就进入正常逻辑否则弹出 message。这样比直接返回裸 JSON 更规范也方便统一拦截异常。3.2 用户登录与 JWT Token 鉴权用户登录这块最常用的方案是 JWT。它的好处是服务端不需要存储 session适合前后端分离和分布式部署。我来说说落地时的几个关键点。首先用户密码不能明文存。用 Spring Security 的BCryptPasswordEncoder或者 Hutool 的BCrypt加密。注册时把明文密码加密后入库登录时再用matches校验。// 注册时 user.setPassword(new BCryptPasswordEncoder().encode(user.getPassword())); // 登录时 boolean matched new BCryptPasswordEncoder().matches(rawPassword, user.getPassword());登录成功后生成 JWT tokenpayload 里放用户 id、用户名、角色过期时间根据需求设为 1 到 7 天。后端用拦截器统一校验放行登录接口和相关公开接口其他接口都要从请求头Authorization里解析 token。Component public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (request.getMethod().equals(OPTIONS)) { return true; } String token request.getHeader(Authorization); if (StrUtil.isBlank(token) || !JwtUtil.validate(token)) { throw new CustomException(401, 未登录或登录已过期); } // 将用户信息放入request attribute后续业务可直接获取 Long userId JwtUtil.getUserId(token); request.setAttribute(userId, userId); return true; } }这里有一个很多新手会忽略的细节跨域请求默认会先发送一个OPTIONS预检请求这个请求不会带上自定义 header所以拦截器必须对OPTIONS放行否则前端会一直报跨域或 401。另外JwtUtil.validate里建议多校验一次 token 里的用户是否还存在、是否被禁用避免用户被删除后 token 仍然有效。3.3 商品发布与文件上传处理商品发布功能里图片上传是最典型的“看起来简单、做起来麻烦”的部分。校园闲置物品基本需要 1 到 5 张图片展示前端把图片传到后端后端存储后回传图片访问地址最后商品表单里只存地址字符串或 JSON 数组。图片存储方案我按项目规模分两种。第一种是本地磁盘存储适合课设、毕设、小型内部系统第二种是对象存储比如 MinIO、阿里云 OSS适合需要部署到公网、有海量图片的场景。如果只是本地演示用本地存储就够了。使用本地存储时Spring Boot 需要配置静态资源映射让上传目录可以被浏览器直接访问Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 将 /upload/** 映射到本地磁盘路径 String path System.getProperty(user.dir) File.separator upload File.separator; registry.addResourceHandler(/upload/**) .addResourceLocations(file: path); } }上传接口需要限制文件大小和类型。在application.yml中配置spring: servlet: multipart: max-file-size: 5MB max-request-size: 20MB如果不配置默认单文件只有 1MB传一张手机拍的高清图基本就会失败。接口里还要校验文件名后缀防止上传 JSP、JAR 等危险文件按白名单校验最简单。商品图片存储我建议在前端压缩后上传比如要求单张不超过 5MB后端再按日期分目录存储文件名用 UUID 拼接后缀避免中文名乱码。这里有个经验之谈数据库里不要直接存完整 URL而是存相对路径/upload/2025/06/xxx.jpg前端访问时拼上后端地址。这样以后换域名、迁移存储服务不需要改数据库。3.4 订单状态机与并发扣减简单版交易系统的核心在订单。校园闲置物品交易不需要像电商那样复杂的支付与库存体系但订单状态流转依然要设计清楚。我定义的状态集合是待付款、待发货、待收货、已完成、已取消。对应前端界面展示为不同标签。订单状态流转要遵循一张状态机表| 当前状态 | 可执行动作 | 目标状态 | 角色 | | 待付款 | 支付/取消 | 待发货/已取消 | 买家 | | 待发货 | 发货 | 待收货 | 卖家 | | 待收货 | 确认收货 | 已完成 | 买家 | | 已完成 | 无 | 无 | 无 | | 已取消 | 无 | 无 | 无 |在代码里我不会直接用 if-else 判断一堆状态而是写一个简单的状态流转校验方法public void changeOrderStatus(Long orderId, Integer fromStatus, Integer toStatus, Long userId) { Order order orderMapper.selectById(orderId); if (order null) { throw new CustomException(订单不存在); } if (!order.getStatus().equals(fromStatus)) { throw new CustomException(订单状态不匹配请刷新后重试); } order.setStatus(toStatus); orderMapper.updateById(order); }这个fromStatus由前端传入后端校验当前状态是否匹配。虽然理论上存在并发请求时状态不一致的情况但课设阶段用数据库的行锁或乐观锁也能解决。最简单的方式是在订单表加一个version字段更新时使用 MyBatis-Plus 的乐观锁插件Version private Integer version;配置乐观锁拦截器后updateById 时如果版本号不匹配更新影响行数为 0业务层捕获后提示“操作过于频繁请重试”。这样既简单又能应付并发场景。另一种容易踩坑的情况是商品被重复下单。同一件商品在还没卖出时如果两个买家同时下单会出现一货两卖。解决思路是在下单接口里使用“原子更新”// 把商品状态从1上架改为3已卖出只有当status仍为1时才能更新成功 int rows productMapper.update(null, new LambdaUpdateWrapperProduct() .eq(Product::getId, productId) .eq(Product::getStatus, 1) .set(Product::getStatus, 3)); if (rows 0) { throw new CustomException(商品已被抢走或已下架); }这种方式比先查后改更安全因为 update 的 where 条件限制了并发下只有一个请求能成功。4. 前端实现Vue3 Vite Element Plus 实战前端部分我以 Vue 3 Vite Element Plus 为例从工程搭建、请求封装、列表搜索、发布表单四个方面展开。这些内容也是面试官或答辩老师最喜欢深挖的点。4.1 前端工程基础搭建与目录组织Vue 3 项目建议直接用 Vite 创建命令就是常规的npm create vitelatest然后在交互选项里选择 Vue 框架。与 Vue 2 相比Vue 3 的组合式 API 让代码组织方式发生很大变化组件内不会再被data、methods、computed选项切分得支离破碎而是按功能点聚合。工程目录上我建议这样组织src ├── api // 接口定义模块 ├── assets // 静态资源 ├── components // 公共组件 ├── router // 路由配置 ├── stores // Pinia状态管理 ├── utils // 请求工具、通用方法 └── views // 页面组件页面组件再按业务模块分目录比如views/home、views/product、views/order、views/user。这样一个同学接手项目瞄一眼目录就能知道页面放在哪。Element Plus 的引入也可以用完整引入的方式课设项目这样做最简单不必做按需加载优化。如果对性能有要求再考虑自动导入插件。4.2 Axios 请求封装与登录态处理前端所有接口请求都走统一封装好的axios实例这一步非常重要。我在utils/request.js里做了三件事设置基础地址、携带 token、统一处理响应。import axios from axios import { ElMessage } from element-plus import router from /router const request axios.create({ baseURL: /api, timeout: 15000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization token } return config }) request.interceptors.response.use( response { const res response.data if (res.code 200) { return res.data } // 处理业务错误 ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) }, error { if (error.response error.response.status 401) { localStorage.removeItem(token) router.push(/login) } ElMessage.error(error.message || 网络异常) return Promise.reject(error) } )这里有一个关键点baseURL配置成/api然后通过 Vite 代理转发到后端localhost:8080这样可以绕开前端开发时的跨域问题。如果直接在baseURL写全地址http://localhost:8080就会触发跨域还要额外配置 CORS麻烦很多。Vite 代理配置在vite.config.js里server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, rewrite: path path.replace(/^\/api/, ) } } }需要说明的是这个代理只在开发环境生效。生产环境打包后如果后端和前端部署在不同域名还是需要后端配置 CORS 或通过 Nginx 反向代理统一入口。这里的选择取决于部署方案。4.3 商品列表页的搜索与分页商品列表页是流量最大的页面搜索区一般有商品标题关键字、分类、价格区间、成色等条件。后端接口接收这些条件后用 MyBatis-Plus 的LambdaQueryWrapper构造动态查询。前端对应的搜索逻辑我习惯封装成“筛选条件对象”再传给列表查询函数const queryParams reactive({ page: 1, pageSize: 12, keyword: , categoryId: undefined, minPrice: undefined, maxPrice: undefined }) async function loadData() { loading.value true try { const res await getProductList(queryParams) productList.value res.records total.value res.total } finally { loading.value false } }分页组件用 Element Plus 的el-pagination切换页码时重新调用loadData。这里容易犯的错是把page和pageSize混在 queryParams 对象里被双向绑定导致修改 pageSize 时 page 没有重置为 1用户从第 5 页切到每页 20 条后可能看到空白列表。建议在 pageSize 变化时主动重置 page。后端分页查询的写法也很固定public PageProductVO pageQuery(ProductQueryDTO dto) { LambdaQueryWrapperProduct wrapper new LambdaQueryWrapper(); wrapper.like(StrUtil.isNotBlank(dto.getKeyword()), Product::getTitle, dto.getKeyword()) .eq(dto.getCategoryId() ! null, Product::getCategoryId, dto.getCategoryId()) .ge(dto.getMinPrice() ! null, Product::getPrice, dto.getMinPrice()) .le(dto.getMaxPrice() ! null, Product::getPrice, dto.getMaxPrice()) .eq(Product::getStatus, 1) .orderByDesc(Product::getCreateTime); PageProduct page productMapper.selectPage(new Page(dto.getPage(), dto.getPageSize()), wrapper); // 转为VO补充用户昵称、封面图完整路径等 }这里用到了 MyBatis-Plus 条件构造器的condition重载当某个值为空时就不拼接该条件这是一种非常实用的写法。很多新手喜欢自己拼 SQL 字符串既不安全也不优雅。4.4 发布商品表单与图片上传组件发布商品页面是整个前端表单最复杂的部分。除了常见的基础字段还有图片上传、商品成色选择等。图片上传我推荐直接使用 Element Plus 的el-upload配合http-request自定义上传方法可以更好的控制上传逻辑。el-upload action# :show-file-listfalse :http-requesthandleUpload acceptimage/* img v-ifimageUrl :srcimageUrl classavatar / el-icon v-elsePlus //el-icon /el-uploadhandleUpload方法里通过 FormData 把文件传到后端接口async function handleUpload(options) { const formData new FormData() formData.append(file, options.file) const url await uploadFile(formData) imageUrl.value url }这里有一个实际体验问题上传接口如果返回的是完整 URL前面讲过后端建议返回相对路径那前端存储时也要存相对路径。表单提交时传给后端的是imageUrl.value相对路径商品详情页展示时用一个全局过滤器补全地址。表单校验也不能忽视。价格字段建议用数字类型并且校验必须大于 0描述字段限制最长 500 字分类下拉框不能留空。如果后端接口同时校验了这些条件前端校验的作用主要是提升用户体验不能替代后端校验。5. MySQL8.0 环境配置与项目部署运行很多项目最终没跑起来问题不在代码而在环境。这一节专门讲 MySQL8.0 的安装、初始化以及 SpringBoot 后端和 Vue 前端的部署衔接。5.1 MySQL8.0 安装和数据库初始化含时区问题MySQL8.0 的安装Windows 下推荐直接下载 MySQL Installer选择 Server only 版本。Linux 下可以用 apt 或 yum但更方便的方式是使用 Dockerdocker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDyourpassword \ -e TZAsia/Shanghai \ -v /mydata/mysql8:/var/lib/mysql \ mysql:8.0这里特意设置了TZAsia/Shanghai是因为 MySQL 8.0 连接时如果不指定时区会出现 CST 和 UTC 的 8 小时偏差更直接的是 JDBC 连接串里必须加serverTimezoneAsia/Shanghai否则启动报错。执行数据库初始化时把项目给的init.sql导入即可。如果手写建表要特别注意字符集和字段类型细节CREATE DATABASE IF NOT EXISTS campus_trade DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE campus_trade; CREATE TABLE user ( id BIGINT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(100) NOT NULL, nickname VARCHAR(50) DEFAULT NULL, avatar VARCHAR(255) DEFAULT NULL, phone VARCHAR(20) DEFAULT NULL, school VARCHAR(100) DEFAULT NULL, role TINYINT DEFAULT 0 COMMENT 0普通用户 1管理员, status TINYINT DEFAULT 1 COMMENT 1正常 0禁用, create_time DATETIME DEFAULT NULL, update_time DATETIME DEFAULT NULL, deleted TINYINT DEFAULT 0, PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4;注意user是 MySQL 的系统表名如果不加反引号很多数据库操作会报错。实际开发中我一般会把表名改成sys_user或者t_user避免不必要的麻烦。5.2 SpringBoot 多环境配置与参数说明Spring Boot 后端一般要区分开发环境、生产环境在resources下按application.yml拆成application-dev.yml和application-prod.yml主配置里用spring.profiles.active切换。spring: profiles: active: devapplication-dev.yml里配置本机数据库、本地文件上传路径application-prod.yml里配置服务器地址、对象存储等。这样做的好处是换环境部署时不用改代码只改启动参数或主配置即可。数据库连接配置里值得注意几点spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/campus_trade?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 123456driver-class-name8.0 必须用com.mysql.cj.jdbc.Driver而不是旧的com.mysql.jdbc.Driver。useSSLfalse可以避免本地连接时的 SSL 警告allowPublicKeyRetrievaltrue是 MySQL 8.0 在缓存 SHA-2 认证插件时可能出现的问题加上这个参数更稳。Spring Boot 2 的默认连接池是 HikariCP性能很好基本不用额外调参。如果项目里用了 Redis记得确认 Redis 服务也已启动否则应用启动可能失败。5.3 前端打包与 Nginx 部署前端开发完后需要npm run build产物在dist目录。部署方式有两种一是扔进 Nginx通过反向代理转发后端请求二是把dist放入后端的resources/static用 Spring Boot 直接托管静态资源。我推荐使用 Nginx 方式因为更贴近生产环境也很容易在后端接口迁移时保持前端不受影响。一个基础的 Nginx 配置如下server { listen 80; server_name yourdomain.com; root /opt/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /upload/ { proxy_pass http://127.0.0.1:8080/upload/; } location / { try_files $uri $uri/ /index.html; } }这里最大的坑是try_files $uri $uri/ /index.html。Vue 3 使用 history 路由时刷新/product/1这个地址Nginx 找不到对应文件会返回 404。加了这条配置后所有路径都会回退到index.html再由前端路由接管刷新问题就解决了。6. 实战中踩过的坑与排查技巧这个项目里我实际调试时遇到过不少问题有些是环境相关有些是代码细节整理出来给大家排雷。6.1 连接 MySQL 8.0 的时区与驱动报错报错最常见的有两类。一类是The server time zone value Öйú±ê׼ʱ¼ä is unrecognized这个就是时区问题把 URL 里的serverTimezone设为Asia/Shanghai即可。另一类是Public Key Retrieval is not allowed这个需要把allowPublicKeyRetrievaltrue加到连接串或者设置 MySQL 的 caching_sha2_password 插件为 mysql_native_password。个人推荐直接改连接串因为不需要动数据库配置。如果本地 MySQL 版本是 5.7而驱动是 8.0通常也能兼容反过来就不行。所以看到 ClassNotFoundException 时先确认driver-class-name是否写对、Jar 包依赖是否引入。6.2 MyBatis-Plus 查询条件构造器的空值陷阱使用LambdaQueryWrapper时很多人会写出这样的代码wrapper.eq(product.getCategoryId() ! null, Product::getCategoryId, product.getCategoryId());这个写法没有问题但问题是很多新手忘记加第一个布尔参数直接写成wrapper.eq(Product::getCategoryId, product.getCategoryId());如果categoryId为 nullSQL 会变成WHERE category_id null在 MySQL 里永远查不出数据。所以建议所有可能为空的条件都带上布尔参数。另外字符串条件如果要忽略空白字符不要用isNotBlank就只用like配合.like(StrUtil.isNotBlank(dto.getKeyword()), Product::getTitle, dto.getKeyword())。还有个小技巧MyBatis-Plus 的逻辑删除配置是全局的查询时自动追加deleted0。但如果某个统计 SQL 里用了selectCount且传入的包装类里没有排除deleted它也会自动带上。如果你想查包括已删除的数据需要特殊处理不过课设场景一般不需要。6.3 Vue 3 跨域代理与打包后 404 问题开发模式跨域问题前面讲 Vite 代理时提到过只要保证baseURL/api就能通过代理转发。但有一个细节如果后端接口路径本身已经带了/api前缀那 rewrite 去掉/api就会找不到接口如果后端接口没有/api前缀那必须像上面配置那样rewrite。配置前后最好用浏览器 Network 面板确认一下实际请求地址。生产环境打包后的 404 问题除了 Nginx 的try_files还有一种情况是后端把dist打进 jar 包后直接访问/可以但刷新子路由时 404。这是因为 Spring Boot 默认只映射了静态首页没有把非静态路径转发到index.html。解决方式是写一个转发控制器或者在配置类里加个ErrorPageRegistrar但最省事的依然是 Nginx 部署。6.4 文件上传失败与 token 过期后的体验优化文件上传失败最常见原因是超过大小限制。Spring Boot 报错信息是MaxUploadSizeExceededException但如果你没有写全局异常处理用户只会看到 500 或一个难看的错误页。建议在全局异常增强里捕获这个异常返回明确提示。另一个体验问题是 token 过期后用户正在发布长表单写了一半提交时突然被弹到登录页内容全丢。我建议前端在请求拦截器里对 401 做“静默刷新”或至少保存当前页面状态。课设阶段可以用一个简单的方式401 时弹出提示并记录当前路由登录成功后router.push回原来的页面。这样虽然表单内容可能还是丢但至少用户不用从头找入口。7. 对这个项目做二次扩展的个人建议如果这个项目你已经跑通想让它更有亮点或者作为毕设想拿更高分下面几个方向值得考虑。7.1 可以加哪些实用功能第一个建议是增加阿里云短信或邮件验证码注册。校园场景下可以用校园邮箱后缀校验学生身份这也是一个很有价值的业务亮点。第二个建议是增加“求购”功能用户发布求购信息卖家看到后主动联系。这个功能在闲置交易中非常高频实现也不复杂在现有商品表上增加一个type字段即可。第三个建议是增加 WebSocket 站内消息通知比如有人下单、卖家发货、买家确认收货时推送通知能明显提升系统完整感。如果对搜索体验有要求可以引入 Elasticsearch 或 MeiliSearch把商品标题、描述建立全文索引替代 MySQL 的like查询。但这不是必要项普通课设用like够用。7.2 从课设到生产还差哪几步要真正上线服务校园用户还需要做几件重要的事把图片存储切到对象存储减少服务器磁盘压力增加操作日志和敏感词过滤防止恶意内容接入真实支付渠道完成担保交易做好数据备份和容灾策略。如果只做课设展示这些都不需要完整实现但在论文和答辩时可以把“如何设计扩展点”讲清楚这比功能本身更能体现你的工程思维。我个人在实际操作中的体会是做一个技术栈覆盖到 SpringBoot2 Vue3 MyBatis-Plus MySQL8.0 的完整项目最大的价值不在于“代码量”或者“页面数量”而在于通过一条完整业务链路把前端、后端、数据库、部署这四块知识真正串起来。踩坑不可怕可怕的是只会复制粘贴而不理解为什么。希望你拿到这套项目后先跑通再读代码最后动手改一改把项目变成自己的作品。这样无论是答辩还是找实习你都能底气十足地讲清楚每个细节。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

阿里云CPFS全栈自研高性能存储:AI存储成本降低69%的技术解析 2026/10/2 11:12:12

阿里云CPFS全栈自研高性能存储:AI存储成本降低69%的技术解析

1. 从一条发布消息说起:AI存储为什么突然成了焦点前阵子阿里云发布了新一代全栈自研的高性能存储CPFS,官方给出的数字是AI存储成本降低69%。这个数字一出来,做AI基础设施的圈子基本都炸了。我身边好几个做大模型训练和推理的朋友第一时间就在…

阅读更多 →
AI编程千人编队实战:开源模型+多Agent调度落地指南 2026/10/2 11:12:11

AI编程千人编队实战:开源模型+多Agent调度落地指南

1. 项目概述:这不是新闻简报,而是一份AI工程落地的路线图“今日AI大事件 | 2026.09.22:智谱豪掷50亿美元、中国开源模型连续20周霸榜、AI编程进入‘千人编队’时代”——这个标题乍看像科技媒体的头条快讯,但在我过去十年带团队做…

阅读更多 →
端侧LLM部署实战:模型选型、量化压缩与推理引擎优化指南 2026/10/2 11:12:11

端侧LLM部署实战:模型选型、量化压缩与推理引擎优化指南

端侧 Agent 这件事,真正落到工程上,第一个绕不开的坎就是:模型到底怎么塞进设备里。我见过太多团队在云端把 Agent 跑得风生水起,一到端侧就卡在部署环节——显存不够、量化掉点、推理延迟飙到没法用。这一篇就专门聊端侧 LLM 部署…

阅读更多 →
Unity特效系统工具链实战:对象池与事件驱动的表现层管理 2026/10/2 11:12:11

Unity特效系统工具链实战:对象池与事件驱动的表现层管理

版本提测前一天,策划在群里反馈:二段技能的脚下光圈又没出现。我打开 Hierarchy,找到那个特效节点,它还在场景里,Particle System 停在最后一帧,明明播放完毕却没有被回收。这不是第一次了。再往深里查&…

阅读更多 →
算法设计与分析核心主线:复杂度、动态规划、贪心与图算法 2026/10/2 11:12:10

算法设计与分析核心主线:复杂度、动态规划、贪心与图算法

如果你正在准备算法设计与分析这门课,或者刷题时总觉得“看答案懂、自己写就废”,那大概率不是编程语言的问题,而是算法设计与分析的主线没有串起来。算法设计负责把一个模糊问题变成可执行的求解过程,算法分析负责判断这个过程正…

阅读更多 →
全国产化电子架构与具身智能机器人操作系统底层技术突破 2026/10/2 11:11:57

全国产化电子架构与具身智能机器人操作系统底层技术突破

1. 从"全国产化电子架构"这个词说起:它到底意味着什么"全球首台搭载全国产化电子架构的具身智能机器人正式亮相"——这条消息里最容易被忽略、但含金量最高的词,其实是"电子架构"四个字,而不是"具身智能&…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉