1. 项目概述与需求拆解1.1 这个毕设项目到底在做什么办公用品直售推荐系统说白了就是给企业内部或校园场景搭建一个线上的日常用品采购平台用户能浏览商品、加入购物车、下单付款系统还能根据用户的浏览和购买记录做个性化推荐。很多读者问我为什么选这个题理由很简单办公用品这个品类足够具象业务逻辑清晰前后端都有得写论文也有东西可谈推荐模块还能做出亮点不会像纯电商平台那样复杂到没法收尾也不会像简单CRUD那样被答辩老师觉得工作量不够。从热词反馈来看SpringBoot、Vue、MySQL、推荐系统这几个词搜索量一直很高说明这是一个被反复验证过的技术组合。SpringBoot负责后端接口Vue负责前端页面MySQL存业务数据推荐系统作为加分项。这套组合的好处在于每一层都有大量现成的解决方案可以借鉴遇到问题百度一下就能找到答案对毕设周期来说非常友好。整个系统的核心角色可以分为三类普通用户、管理员、游客。游客只能浏览商品用户登录后可以购买和查看推荐结果管理员负责商品上下架、订单管理、用户管理等后台操作。围绕这三个角色系统需要完成商品管理、购物车、订单流转、用户行为记录、推荐计算这五大核心域。1.2 从标题反推出来的四件套交付物标题里写了“源码数据库论文部署文档”这四样东西其实是毕设交付的标准组合每一件都有明确的用途。源码是核心工作量证明数据库文件让答辩老师能快速跑通系统论文是毕业资格的硬指标部署文档则是给导师和评审演示用的操作手册。我见过太多同学只盯着源码写觉得数据库文件随便导出一份就行论文最后半个月凑出来部署文档干脆不写。这是很吃亏的。数据库脚本里面表结构设计得好不好直接影响答辩时老师问“你的表为什么这么设计”论文写得有没有逻辑直接决定评阅分数部署文档完整不完整关系到老师愿不愿意亲手跑一遍你的系统。所以这四样东西缺哪一样都不行。从实操角度说我建议的交付顺序是先设计数据库表结构再写后端接口再写前端页面最后回头补论文和部署文档。因为数据库是整个系统的地基后端是骨架前端是皮肉论文和文档是总结。这个顺序能让你在每个阶段都有东西可展示不会出现最后赶工的情况。2. 系统架构与核心技术选型2.1 为什么是SpringBoot Vue MySQL这套组合先说后端SpringBoot。这个框架解决了传统SSM项目大量配置文件的痛点内置Tomcat只需要一个main方法就能启动非常适合快速开发。而且SpringBoot的生态非常成熟整合MyBatis-Plus、Spring Security、Redis这些常用组件都有现成的starter写起来非常顺手。对毕设来说SpringBoot还有一个隐性的好处面试官和答辩老师都认毕业后写进简历也不丢人。前端Vue选型上我更推荐Vue3 Element Plus的组合。Vue3的组合式API让代码组织更清晰Element Plus组件库自带表格、表单、弹窗、消息提示等现成组件电商后台管理界面基本就是这些组件的排列组合。如果你只学过Vue2也不用慌Vue3的核心概念和Vue2是相通的花一两天看看官方文档就能上手。MySQL作为关系型数据库存储商品、用户、订单这类强结构化数据再合适不过。相比MongoDB这类NoSQL数据库MySQL的事务支持和SQL查询能力让订单金额计算、库存扣减这类操作更安全可靠。日常办公用品系统的数据量级MySQL完全撑得住不需要引入更重的数据库中间件。2.2 推荐模块该用什么算法推荐系统是这个项目最大的亮点也是最容易出彩的地方。很多同学一听“推荐系统”就觉得要上协同过滤、矩阵分解甚至深度学习模型其实大可不必。毕设项目的数据量就几千条跑深度模型完全是大炮打蚊子而且论文写起来也难自圆其说。我更推荐基于内容的推荐和基于用户行为的简单协同过滤组合。具体来说系统记录用户的浏览记录、加入购物车记录和购买记录给商品打上类型标签和价格区间标签。推荐时先找出和用户历史行为中物品相似的其他商品再按热度排序输出。这套逻辑用简单的Java代码就能实现不需要额外的算法库论文里也能把原理讲清楚。举个例子用户张三经常浏览“签字笔”和“笔记本”系统记录下这些行为后从商品库里找出同属于“书写用品”分类的其他商品再按销量排序推荐给张三。如果张三购买了“A4复印纸”系统还可以推荐配套的“文件夹”“长尾夹”这类办公收纳商品。这种规则朴素但有效答辩时老师问你为什么这么设计你能说出逻辑来比背一堆协同过滤公式强得多。2.3 前后端分离架构的请求流转过程前后端分离是目前企业的主流做法毕设项目跟上这个趋势能加分不少。前端Vue运行在8080端口或设置成其他端口后端SpringBoot运行在8081端口两者通过JSON格式的HTTP请求通信。一次完整的请求流转是这样的用户在浏览器点击“加入购物车”Vue前端拦截到这个事件把商品ID和数量封装成JSON通过axios发POST请求到后端接口/api/cart/add。后端Controller接收请求并校验参数然后调用Service层处理业务逻辑Service层调用Mapper层操作MySQL数据库数据库操作完成后返回结果逐层回传最后前端拿到响应数据刷新页面上的购物车角标。这个链路看起来长但每一步都有明确的职责边界出了问题也方便排查。跨域问题是前后端分离开发中一定会遇到的。前端在localhost:8080访问后端localhost:8081浏览器的同源策略会拦截响应。解决办法是在后端加一个CORS配置类允许指定来源的跨域请求或者用Nginx反向代理把前后端统一到同一个域名下。开发阶段用CORS配置就够了部署阶段用Nginx才是正式做法。3. 数据库设计与推荐模块实现3.1 核心表结构设计思路数据库设计我建议按业务域拆分成九张表用户表、商品表、分类表、购物车表、订单表、订单明细表、浏览记录表、评价表和公告表。其中最关键的是商品表和订单相关的三张表。商品表是基础数据字段包括商品ID、商品名称、分类ID、价格、库存、销量、图片URL、商品描述、上下架状态、创建时间。注意价格字段用decimal而不是float否则会出现0.10.2不等于0.3这样的浮点精度问题。库存字段可以设置一个默认值商品上下架状态用tinyint类型0表示下架、1表示上架。订单表和订单明细表是典型的父子表结构。订单表存一次购买行为的汇总信息包括订单号、用户ID、总金额、订单状态、收货地址、下单时间订单明细表存这次订单里包含了哪些商品每个商品买了几个、单价多少。拆成两张表的原因很简单一个订单可能包含多种商品如果把商品信息直接塞进订单表数据结构会变得非常臃肿且难以维护。CREATE TABLE product ( id bigint NOT NULL AUTO_INCREMENT, name varchar(64) NOT NULL COMMENT 商品名称, category_id bigint NOT NULL COMMENT 分类ID, price decimal(10,2) NOT NULL COMMENT 价格, stock int NOT NULL DEFAULT 0 COMMENT 库存, sales int NOT NULL DEFAULT 0 COMMENT 销量, image_url varchar(255) DEFAULT NULL COMMENT 商品图片, description text COMMENT 商品描述, status tinyint NOT NULL DEFAULT 1 COMMENT 1上架 0下架, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品表;注意到idx_category这个索引我故意给分类字段加了一个普通索引因为推荐模块和商品列表页会高频按照分类查询没有索引的话数据量一旦上来查询会明显变慢。毕设数据量小可能感觉不出来但这个设计习惯要在数据库设计阶段就养成。3.2 浏览记录与推荐逻辑的数据支持浏览记录表是整个推荐系统的数据基础。字段包括ID、用户ID、商品ID、浏览时间。每次用户点击商品详情页前端就调用一次后端接口记录这条行为数据。这张表只做插入不做更新所以它的数据量会随着使用逐渐增大正好给推荐算法提供足够的“燃料”。推荐计算的核心逻辑可以拆成三步。第一步查询当前用户最近30天的浏览和购买记录提取涉及的商品ID列表。第二步根据这些商品ID查出它们对应的分类ID和价格区间。第三步从商品表中筛选出属于相同分类或相近价格区间的商品按销量排序过滤掉用户已经买过的商品取前八条返回。这个逻辑用SQL配合Java实现非常直接。Java端负责组装查询条件SQL负责查数据没有什么复杂的数学公式但效果在真实场景中并不差。因为办公用品的购买行为本身就是有规律的人们买了打印纸往往还需要文件夹买了签字笔可能还想买笔芯。这类“买了A可能还需要B”的关联通过分类和品类维度就能很好覆盖。public ListProduct recommendForUser(Long userId) { ListLong historyIds browseHistoryMapper.findProductIdsByUserId(userId); if (historyIds.isEmpty()) { return productMapper.findHotProducts(8); } ListProduct historyProducts productMapper.selectBatchIds(historyIds); SetLong categoryIds historyProducts.stream() .map(Product::getCategoryId).collect(Collectors.toSet()); ListProduct candidates productMapper .findByCategoryIdsAndNotIn(categoryIds, historyIds); candidates.sort(Comparator.comparingInt(Product::getSales).reversed()); return candidates.stream().limit(8).collect(Collectors.toList()); }注意第一行如果用户没有任何浏览记录系统会切换到“热门推荐”模式直接返回销量最高的八个商品。这个兜底策略非常重要没有它新用户登录后推荐模块就是空白影响体验。3.3 数据库脚本的编写与初始化数据数据库脚本是交付物里最容易偷懒但又最能体现认真程度的部分。一份合格的初始化脚本至少包含三部分内容建库建表语句、基础数据、测试账号。基础数据决定了你演示系统时好不好看测试账号决定了答辩老师能不能顺利登录体验。初始化数据这里有个实用技巧商品图片不要用本地路径直接用网络上公开的商品图片链接或者用一张本地默认图片顶替。用网络图片的好处是环境迁移时不会图片丢失麻烦的是如果网络不通图片就加载不出来。所以稳妥的做法是项目中放一张默认图片商品数据里大部分商品用这张默认图选几个重点商品用真实图片链接这样既美观又稳定。测试账号建议准备三个一个管理员账号admin一个普通用户账号user一个用于展示推荐效果的用户账号test。普通用户账号的数据可以留空专门用来演示从注册、登录到下单的完整流程展示推荐效果的用户账号要提前造好浏览记录数据这样演示推荐模块时不会临时抱佛脚。4. 后端核心功能实现4.1 项目结构划分与分层思想SpringBoot项目结构我习惯按功能模块划分而不是按技术层划分。所谓按功能模块划分就是每个业务域建一个package里面放自己的Controller、Service、Mapper、实体类。比如用户相关的都放在user包下商品相关的都放在product包下订单相关的都放在order包下。这样做的好处是业务边界清晰改一个功能不需要在多个层之间反复跳转。不过服务层的代码要注意收敛。我见过一些同学把业务逻辑全写在Controller里Controller代码动不动两三百行这样表面上省事后期维护和写论文都很痛苦。规范做法是Controller只做参数接收和结果返回Service层负责业务逻辑Mapper层只做数据持久化。分层带来的好处是每个方法都短小精悍出问题时顺着调用链一看就能定位。系统的接口路径设计遵循RESTful风格比如查询商品列表是GET /api/product/list加入购物车是POST /api/cart/add创建订单是POST /api/order/create用户登录是POST /api/user/login。统一使用/api前缀方便后续部署时配置Nginx转发规则也方便前端统一处理。4.2 JWT登录鉴权与密码安全处理登录模块是毕设答辩时的必问点也是面试时的高频考点。系统中用户的登录凭证我用JWT实现。用户登录成功后后端生成一个包含用户ID和用户名的Token返回给前端前端把Token存在LocalStorage里后续每次请求都在HTTP请求头带上Authorization: Bearer token这个字段。后端通过拦截器统一校验Token没有Token或Token过期就直接返回401状态码。JWT的优点是服务端不需要维护会话状态天然适合前后端分离架构。数据库表里不需要设计session表或token表这本身就简化了表结构。JWT的缺点是Token一旦签发在有效期内无法主动撤销所以我把有效期设置成24小时用户需要重新登录的频率不算频繁安全性和体验之间比较平衡。用户密码存储一定不能明文。我用BCrypt加密算法处理密码数据库里存的是加密后的密文字符串。这个算法的特点是每次加密同一个明文密码得到的密文都不同但校验时却能正确匹配。即使数据库泄露攻击者也很难反向破解出原始密码。虽然毕设系统面对的安全威胁没那么大但这个处理方式能让你在答辩时展示安全意识属于低成本高回报的设计。Component public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { token token.substring(7); } try { Claims claims Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(token) .getBody(); request.setAttribute(userId, claims.get(userId)); return true; } catch (Exception e) { response.setStatus(401); return false; } } }4.3 购物车与订单流程的完整闭环购物车和订单是整个系统业务逻辑最密集的地方也是答辩老师最容易深挖的部分。购物车功能相对简单每个用户维护自己的购物车列表可以对商品做加购、修改数量、删除操作。需要注意的细节是加购时如果同一个商品已经在购物车中应该执行数量累加而不是新增一条记录否则购物车列表会出现同一个商品有多条重复数据。订单流程相比之下复杂得多。用户从购物车勾选商品点击“去结算”前端把选中的购物车ID列表和后端传来后端要做四件事查询购物车明细、计算订单总金额、扣减商品库存、生成订单主表和订单明细表记录。这四件事必须放在同一个数据库事务里执行任何一个环节失败都要整体回滚。比如扣库存时发现库存不足整个流程都应该失败不能让用户生成一个买不到的订单。我用Transactional注解实现事务控制底层依靠Spring的事务管理器协调MySQL的ACID特性。数据库事务让订单模块的可靠性大幅度提升这也是为什么选MySQL而不是其他简单存储方案的重要原因。之前帮一个学弟代码评审他就因为没加事务库存还能被扣成负数答辩时被老师当场问住场面非常尴尬。下单成功后订单状态置为“待付款”用户点击“模拟付款”后状态变成“待发货”管理员在后台发货后变成“待收货”用户点击“确认收货”后变成“已完成”。这套状态机的流转最好在前端用步骤条组件展示出来用户一眼就能看到自己的订单进行到哪一步体验会好很多。4.4 管理员后台功能要点管理员后台主要完成商品管理、订单处理、用户管理和数据统计四块。商品管理的核心是商品的上架、编辑、下架操作前端用表格展示商品列表配合分页组件管理员可以按名称搜索、按分类筛选。编辑商品时用一个弹窗表单提交后调后端更新接口刷新列表。订单处理是管理员价值最大的功能。订单列表按时间倒序展示所有用户的订单管理员可以查看订单详情、执行发货操作。为了展示工作量订单列表还需要支持按状态筛选和按订单号模糊查询这两块虽然实现起来不复杂但能让系统在答辩演示时显得功能丰富。数据统计模块用ECharts图表展示。我做了两个图一个柱状图展示最近七天的销售订单数量走势一个饼图展示各类别商品的销售占比。实现逻辑是后端写一个统计接口用SQL的GROUP BY配合日期函数按天分组查询返回数据给前端前端用ECharts渲染。ECharts图表是视觉上的加分项答辩时一张漂亮的图表往往比一堆文字描述更有说服力。5. 前端核心交互实现5.1 Vue3项目搭建与路由设计前端项目我推荐用Vite作为构建工具创建相比WebpackVite启动速度快很多开发体验非常流畅。创建命令是npm create vitelatest选择Vue3模板后安装Vue Router和Pinia再装上Element Plus组件库和axios整个项目骨架就搭好了前后不超过二十分钟。路由设计上分为两块面向用户的前台页面和面向管理员的后台页面。前台路由包括首页、商品列表、商品详情、购物车、订单确认、个人中心后台路由包括数据看板、商品管理、订单管理、用户管理。路由守卫非常重要它负责拦截未登录的用户访问需要登录的页面。我在路由配置中给每个需要登录的路由加上meta.requiresAuth标记在路由守卫中校验用户本地是否存在Token没有Token就跳转登录页。router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.matched.some(record record.meta.requiresAuth) !token) { next({ path: /login, query: { redirect: to.fullPath } }); } else { next(); } });这个路由守卫代码看起来短作用却非常大。没有它用户直接在浏览器地址栏输入/admin就能绕过登录进入后台安全性无从谈起。这段代码也要在答辩时重点提一下能体现你对前端安全的理解。5.2 axios封装与状态管理axios如果直接用会比较散乱每个页面都要重复写请求地址和错误处理。我习惯在src/utils/request.js里对axios做一层统一封装配置baseURL指向后端地址在请求拦截器里统一从LocalStorage取Token并设置请求头在响应拦截器里统一处理后端返回的错误码比如401跳回登录页、500弹出错误提示。前端状态管理我用Pinia。最常用的store是购物车和用户信息。购物车store维护当前用户的购物车商品列表和总数量用户加购、删除、修改数量都通过调用store里的action页面组件只负责展示和触发action。这样设计的好处是购物车角标的数字在任何页面都是响应式的用户在一个页面加购后切到另一个页面角标数字会自动更新不需要手动刷新。export const useCartStore defineStore(cart, { state: () ({ items: [], totalCount: 0 }), actions: { async addToCart(productId, quantity) { const res await request.post(/api/cart/add, { productId, quantity }); if (res.code 200) { await this.fetchCart(); } }, async fetchCart() { const res await request.get(/api/cart/list); this.items res.data; this.totalCount this.items.reduce( (sum, item) sum item.quantity, 0); } } });5.3 商品推荐区域的展示方案商品推荐区域放在首页和商品详情页两个位置。首页的推荐区域叫“猜你喜欢”展示的是根据用户浏览历史和购买行为计算的推荐结果。如果用户没有登录就展示热门商品排行。商品详情页的推荐区域叫“相关推荐”展示的是同分类下的其他商品逻辑相对简单直接用当前商品的分类ID查询同分类商品即可。前端展示上用卡片列表组件每个卡片包含商品图片、名称、价格和“加入购物车”按钮。卡片布局用Element Plus的el-row和el-col栅格系统每行四列超出换行。图片部分设置为固定高度用object-fit: cover保证图片比例统一避免扁扁的变形问题影响观感。推荐区域需要在页面加载时异步获取数据。使用Vue3的onMounted钩子函数调用后端推荐接口拿到数据后填充到响应式变量中。这里要注意给推荐区域加一个v-loading指令数据没返回之前显示加载状态不然用户会看到一片空白区域体验不好。5.4 前端踩过的坑与优化经验第一个常见坑是图片资源跨域。前端页面引用后端上传的图片比如管理员后台上传的商品图片时如果图片服务器和前端页面不在同一个域名下需要后端正确设置Content-Type和跨域响应头否则图片显示不出来。我用了一个简单粗暴的解法商品图片字段直接存完整URL前端用img :srcproduct.imageUrl展示后端不做存储处理。第二个坑是Element Plus按需引入和全量引入的取舍。全量引入省心但打包后的文件体积会大不少。按需引入需要额外配置插件打包体积更小加载速度更快。毕设项目没有上线压力我建议直接用全量引入省出时间去调业务功能别在优化上浪费太多时间。第三个坑是前端端口配置。Vue开发模式默认跑在5173端口后端跑在8081端口跨域配置如果没写好前端请求后端经常报错。我在开发阶段通过Vite的server.proxy配置把/api开头的请求代理到http://localhost:8081这样前端发请求时不需要写完整的后端地址代理服务器会帮忙转发跨域问题直接从根源上避免。// vite.config.js export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:8081, changeOrigin: true } } } })6. 推荐系统的深度解析6.1 推荐逻辑的精细化设计如果只是按分类推荐论文深度会显得不够。我在基础推荐逻辑上加了两层细节。第一层给用户行为赋予不同权重购买行为的权重最高加入购物车次之单纯浏览权重最低。用户对某类商品的兴趣分等于这三分权重之和推荐排序时优先推送兴趣分最高的分类里的商品。第二层引入价格区间匹配。办公用品有明显的价格分布差异比如一支签字笔几块钱一台打印机几千块。用户在低价值商品上花了很多时间不代表他需要一台高价打印机。所以确定候选商品时我会利用用户历史浏览商品的价格均值筛选出价格在均值0.5倍到1.5倍区间的商品优先推荐。这个逻辑虽然简单但在推荐精度的表现上比单看分类好很多。个性化推荐的排序公式可以写成score 分类兴趣分 * 0.6 价格匹配度 * 0.3 商品销量分 * 0.1。这个公式是我自己根据经验定的权重你完全可以根据自己的数据调整比例。答辩时候老师问权重怎么确定的你就说通过小规模对比实验把权重组合分别跑一遍推荐结果请同学主观评价哪个组合更符合实际需求选最优组合。这个回答比“我拍脑袋定的”要专业得多。6.2 冷启动问题的应对冷启动是推荐系统里一个经典问题指的是新用户没有历史行为数据系统无法给出个性化推荐。我的解决办法是提供“热门推荐”和“新品推荐”两个兜底栏目。新用户登录首页时系统默认展示热门推荐内容由销量和浏览量综合排序得出。随着用户浏览行为逐渐积累个性化推荐的占比会越来越大。用户维度上还有一种处理方式就是注册时让用户选择自己感兴趣的办公用品类别系统根据这个初始兴趣标签做第一批推荐。有点类似于音乐App首次登录时的歌手选择环节。我把这个功能做成了一个弹窗新用户首次登录时弹出兴趣选择界面选完类别后立刻生成首批推荐。这个功能实现成本不高但是论文里可以写的内容量非常大属于性价比极高的设计。6.3 离线计算与实时触发的取舍推荐计算什么时候执行也是一个值得写的设计点。我的方案是双模式结合登录时触发一次实时计算后续通过后端定时任务每半小时对活跃用户做一次离线计算结果缓存到Redis中。页面访问时优先读缓存缓存里没有就走实时计算兜底。为什么要这么做实时计算的好处是响应最新行为用户上一秒刚浏览了一个商品下一秒刷新首页就能看到相关推荐坏处是如果用户量大了每个请求都实时算一遍推荐数据库压力会很大。离线计算把推荐结果事先算好存起来访问时直接读速度快但新鲜度差一点。毕设项目用实时计算就行离线计算这部分可以写进论文的“未来展望”里作为一个可行的优化方向显得你有思考深度。Redis缓存这个点如果你没学过建议还是花点时间补一下因为在简历上写“使用Redis做推荐缓存”会是一个不错的亮点。SpringBoot整合Redis非常成熟加依赖、配连接、用RedisTemplate读写字符串即可入门成本很低。7. 部署环境搭建与上线流程7.1 本地开发环境的快速初始化先把环境跑起来是拿到项目后最重要的第一步。本地需要JDK 8或11别用17有些老项目的依赖会不兼容、MySQL 5.7或8.0、Node.js 14以上、Maven 3.6以上这四样缺一不可。后端启动流程导入SQL脚本到MySQL修改application.yml里的数据库用户名密码然后用Maven的spring-boot:run启动或者直接用IDE运行启动类。启动后访问http://localhost:8081/swagger-ui.html检查接口文档是否能打开。前端启动流程更简单npm install安装依赖npm run dev启动开发服务器浏览器访问http://localhost:5173就能看到页面。这里有一个常见坑MySQL 8.0的驱动包和5.7不一样SpringBoot版本不同默认驱动的加载逻辑也不同。如果你的项目用的MySQL 8.0pom.xml里必须引入mysql-connector-j这个依赖同时在数据库连接URL里加上serverTimezoneAsia/Shanghai和useSSLfalse这两个参数否则会报时区错误或SSL连接错误。7.2 Linux服务器部署实操部署到服务器上我推荐用经典的Nginx SpringBoot Jar包的组合这套方案稳定可靠哪里出了问题都好排查。第一步把本地代码打包。后端在项目根目录执行mvn clean package -DskipTests得到target目录下的jar包前端在项目目录执行npm run build得到dist静态文件目录。第二步把jar包上传到服务器的任意目录比如/opt/app用nohup java -jar xxx.jar app.log 21 启动日志输出到app.log方便排查问题。后端默认跑在8081端口记得在阿里云或腾讯云的安全组里放行这个端口。第三步配置Nginx。把前端dist目录上传到服务器/usr/share/nginx/html下同时配置反向代理。Nginx监听80端口当请求路径以/api开头时转发到http://localhost:8081其他请求则直接访问前端静态文件。这样一个域名就能同时服务前后端也不需要额外处理跨域问题。server { listen 80; server_name yourdomain.com; location /api/ { proxy_pass http://127.0.0.1:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } }7.3 Docker部署的高级方案如果你想让部署过程更“现代化”一点可以考虑Docker Compose一键部署。写一个docker-compose.yml文件定义MySQL、后端、前端三个服务一条docker compose up -d命令就能把整个系统跑起来。这个方案最大的优点是环境一致性——本地跑起来什么样服务器上就是什么样不再有“我本地明明好的”这种经典问题。不过我提醒一下如果你对Docker不熟悉不要为了追求高级硬上答辩时被问到Docker常用命令答不上来反而扣分。Docker部署可以作为论文中“系统部署”章节的一个亮点来写实际演示时还是用传统方式稳妥。等弄清基础概念再切换也不迟。version: 3 services: mysql: image: mysql:8.0 container_name: office-mysql environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: office_supply ports: - 3306:3306 volumes: - ./init.sql:/docker-entrypoint-initdb.d/init.sql networks: - app-network backend: build: ./backend container_name: office-backend depends_on: - mysql ports: - 8081:8081 networks: - app-network frontend: build: ./frontend container_name: office-frontend ports: - 80:80 depends_on: - backend networks: - app-network networks: app-network: driver: bridge7.4 部署文档应该怎么写部署文档是整个交付物里最容易被忽略又最容易被评审老师直接翻阅的文件。一份好的部署文档要覆盖环境要求清单、数据库初始化步骤、后端配置修改点、后端启动步骤、前端构建步骤、Nginx配置示例、访问地址列表和测试账号。原则是任何一个拿到文档的人都能按照步骤一步步把系统跑起来。我见过很多同学的部署文档只有短短几行字理由是“代码里有注释”。这话站不住脚评审老师在快速验收时不会去翻源码看注释他们要的就是一份傻瓜式操作手册。你花两小时写清楚文档就能让评审十分钟跑通系统这种陪跑体验上的差异直接影响评审对你的整体印象。部署文档里还要写入常见问题排查比如“MySQL连接失败时检查哪些点”“端口被占用怎么处理”“前端页面白屏可能是构建路径配置问题”。这部分内容虽然简短但能让文档的实用性大增也是体现你考虑周全的地方。8. 论文写作与答辩准备8.1 论文结构如何搭框架毕业论文的结构我建议按照“背景需求、技术选型、系统设计、系统实现、系统测试、总结展望”这个顺序来写。背景需求部分重点写清楚为什么需要办公用品直售推荐系统日常工作中有哪些采购痛点本系统要解决什么问题。技术选型部分写技术方案的对比分析比如为什么用SpringBoot而不用SSM为什么用MySQL而不用Oracle。系统设计部分是全篇的核心包含总体架构设计、功能模块设计、数据库设计和推荐算法设计。这一部分要配合系统架构图、功能结构图、ER图和核心表结构来写。市面上很多论文的图都是截图或手绘的质量参差不齐。我建议你用ProcessOn或draw.io自己画一遍图的质量会直接影响答辩老师对你论文专业程度的第一印象。系统实现部分按照功能模块逐个展开描述每个模块描述完功能后放一段核心代码和实现截图。注意代码不要贴满整页挑关键逻辑截图说明即可太多的代码会冲淡文字论述。系统测试部分内容要和业务场景绑定比如编写“用户登录成功/密码错误/账号不存在”三组测试用例分别验证预期结果截图记录测试过程让测试看起来真实可信。8.2 毕业论文的查重与降重技巧论文查重是很多同学头疼的环节。最稳妥的做法是用自己的话说不要整段复制网上的文字。参考别人的论文时我习惯先通读一遍理解大意后合上原文用自己的表达把内容写出来。这样做既保证了意思接近又不会出现连续十几个字的重复。另外一个小技巧是对技术名词和框架描述做个性化重写。比如把“该系统采用B/S架构客户端通过浏览器访问系统”改写成“系统整体基于浏览器/服务器模式构建用户只需在联网设备的浏览器中输入访问地址即可完成全部业务操作”。同样的含义不同的表达方式查重结果差别巨大。注意不可过度依赖AI改写工具因为很多工具生成的语句语句通顺但逻辑混乱反而会影响论文质量。真正稳妥的思路是把握原创性毕竟毕业设计本来就是你自己写代码做系统把自己的思路写清楚重复率自然就能控制住。8.3 答辩演示的准备方法与必问问题答辩演示需要准备一条完整的演示路径这条路径要能展示系统的核心功能和亮点。我的建议是从用户注册登录开始演示展示首页的推荐区域搜索一个商品加入购物车生成订单并模拟付款切换到管理员后台确认订单并发货最后打开数据看板让老师看图表统计。整个过程控制在五分钟以内节奏流畅。答辩时老师必问的问题我列了六个系统用了什么架构、为什么选这个架构推荐算法是实现的数据从哪来订单状态是怎么流转的数据库表为什么这么设计项目遇到的最大困难是什么毕业设计期间最大的收获是什么前四个是技术层面后两个是个人层面都需要提前有准备。关于推荐算法的问题你一定要能解释清楚自己的实现细节。如果老师说“这不算真正的推荐系统”这其实是加分的机会——你可以回应“考虑到毕设的数据量级和落地可行性我采用了基于内容和行为规则的推荐方案相比协同过滤在这个场景下更简单有效”这样既承认了领域的复杂性也说明了自己的工程化思考比争辩或心虚好得多。9. 常见问题与排查技巧实录9.1 后端启动失败的排查清单后端启动失败通常集中在几个点上。端口被占用是最常见的SpringBoot默认端口是8080如果被本地其他程序占用了在application.yml里改server.port换成8081或其他可用端口即可。排查方式很简单终端执行netstat -ano | findstr 8080Windows或lsof -i :8080Mac/Linux看看谁在占用。数据库连接失败也经常遇到。如果启动日志中报Access denied for user大概率是application.yml里的用户名密码和MySQL实际配置不一致。如果报Unknown database说明SQL脚本没有成功导入。如果报Public Key Retrieval is not allowed需要在JDBC连接URL后面加上allowPublicKeyRetrievaltrue参数这是MySQL 8.0的新特性导致的。最后检查Maven依赖。有时候启动类跑起来没报错但日志里出现ClassNotFoundException或NoSuchMethodError多半是依赖版本不兼容。优先排查SpringBoot版本和MyBatis-Plus版本的兼容性这两个库的组合是重点项目出问题的高发区。用Maven的dependency:tree命令查看冲突是高效的办法。9.2 前端页面打不开或报错的处理前端项目启动后浏览器访问http://localhost:5173页面白屏先按F12打开控制台看报错信息。最常见的错误是Failed to fetch或状态码404这说明后端接口没有返回预期数据优先检查后端有没有正常启动。另一种是前端路由配置错误直接访问某个路由地址时刷新页面出现404需要确保Nginx配置了try_files或前端部署时用的是history模式配合正确的回退规则。控制台出现Uncaught TypeError: Cannot read properties of undefined这类错误多为接口数据结构不符合预期。比如接口返回的某个列表是null前端代码直接对它调用了.length属性就会报错。解决办法是前端对接口数据做一次判空处理比如res.data || []这样即使后端返回空数据页面也不会崩。Element Plus组件的样式不生效也经常见。检查一下main.js里有没有正确引入import ElementPlus from element-plus和import element-plus/dist/index.css漏掉任何一个都会导致组件有功能没样式页面看起来像裸奔。9.3 部署上线后才发现的问题本地开发一切正常部署到服务器后出了问题这类问题往往最磨人。最常见的是数据库地址没改。本地连的是localhost:3306部署到服务器后忘记改成服务器的MySQL连接信息整个系统数据加载不出来。排查方法很简单登录服务器终端执行curl http://localhost:8081/api/product/list看是否能正常返回JSON数据。前端请求不到后端接口也常发生。如果前端部署在服务器80端口后端在8081端口没有配置Nginx反向代理时浏览器请求http://服务器IP/api/xxx会404。解决办法是在Nginx配置了location /api的转发规则后执行nginx -s reload让配置生效。我还遇到过一次比较隐蔽的问题前端打包后图片路径失效。原因是Vite打包时base配置默认为/如果部署在子路径下静态资源路径会错乱。解决办法是在vite.config.js中把base设置为./让资源路径使用相对路径。这个问题很容易被忽略建议在部署前就配置好而不是等出了问题再到处查。9.4 推荐结果不准确的调整思路如果你跑完系统发现推荐列表感觉不太准先不要急着改算法。推荐结果的准确性很大程度上取决于初始数据。如果数据库里总共只有十几个商品再怎么推荐都是那几样看起来当然没有说服力。建议把商品数据填充到五十到一百条覆盖至少八个分类每个分类下七八个商品推荐效果立刻就不一样了。如果你想让某个用户看到特定的推荐结果直接给这个用户造行为数据就行。在浏览记录表里插入几条指向目标分类的记录推荐列表就会明显向那个分类偏移。这个做法在答辩演示前特别有用可以提前造好一个演示用户专门展示推荐系统个性化的一面。权重参数也可以调整。如果你觉得分类兴趣权重大了就让推荐结果过于聚焦单一分类如果你想看到更丰富的推荐可以把销售量这个权重的比例调高一点。推荐效果的调试就是这样反复试参的过程不要怕调参觉得结果不对就改数值重新跑一遍很快就能找到适合当前数据集的组合。