很多读者最近都在问我计算机毕业设计选旅游方向到底该怎么做。我前前后后帮人改过好几版基于SpringBoot的旅游信息交流网站印象最深的还是“行走圈”这个题目它把旅游分享和商品交易揉在一起前端用Vue做互动门户后端用SpringBoot管业务复杂度刚好撑得起一篇像样的毕业设计也不至于做到一半想换题。这篇文章就按我实际写过的方案从需求拆解、数据库设计、后端接口、前端页面到部署上线和答辩准备一步步还原整个落地的过程。适合正在纠结选题方向、或者已经选定了SpringBootVue但是还不知道怎么开工的同学来看。1. “行走圈”到底要做成一个什么系统毕业设计需求再拆解1.1 题目里隐藏的三层功能域先别看“全域旅游互动门户”这种词就觉得虚。拆开这个题目“旅游信息交流网站”是皮“旅游分享与商品交易”才是里子。一个完整的“行走圈”至少包含三层功能域信息交流层用户注册登录、发布旅游攻略/游记/动态浏览他人的分享评论、点赞、收藏。这一层解决的是“内容从哪来、用户怎么互动”的问题也是答辩时最容易讲清楚的部分。商城交易层商品景区门票、特色手信、旅行周边的展示、购物车、生成订单、模拟支付、订单状态管理。这一层是整个平台区别于普通论坛的关键也是把“交易系统”写进论文的重要素材。管理门户层后台管理包括用户管理、帖子审核/置顶、商品上下架、订单处理、统计看板。毕设有没有后台在答辩时是两种印象——只有前台叫“页面”不叫“系统”。如果把“全域旅游互动门户”理解为整个系统的对外门面信息交流层和商城交易层就是门面底下的承重结构一个负责沉淀内容一个负责交易转化再加上后台管理做运营支撑“行走圈”才算是一个能自圆其说的平台。我建议第一版就把这三层做齐哪怕某些功能做简单一点。因为毕业设计的评分逻辑通常是“功能广度 技术亮点 完成度”三者取平衡少一层都得在答辩时解释半天。1.2 功能边界与论文大纲如何互相倒推很多同学拿到题目第一步就急着写代码我反而不建议。先把论文大纲列出来再回来约束功能效率高得多。典型论文结构大概是六章绪论、相关技术介绍、需求分析、系统设计、系统实现、系统测试。其中“系统设计”这一章必然要有用例图、架构图、数据库E-R图“系统实现”这一章要把核心模块逐张截图配文字讲。这意味着你倒推回来代码里必须存在这些可画、可截图的实体有用户角色区分普通用户、管理员才有机会画角色用例图。有帖子、商品、订单、评论四类核心实体数据库E-R图才不会太空。有登录、发帖、下单三个主要业务流程流程图才有内容可画。有后台列表页、发布页、详情页、个人中心页截图素材才够撑满实现章节。按这个规则反推“行走圈”的功能清单其实可以收敛得非常明确我列一下第一版我的原型清单模块功能点说明用户模块注册、登录、个人资料编辑、头像上传密码加密JWT鉴权内容模块发布旅游攻略、浏览信息流、关键词搜索、分类筛选支持多图片互动模块点赞、收藏、评论前台做计数后台做记录商城模块商品列表、商品详情、购物车、下单、订单列表订单状态流转后台模块用户管理、内容审核、商品管理、订单管理独立管理路由1.3 技术栈的取舍原则技术栈这块题目清清楚楚写了SpringBootVue剩下的是你自己选。我给学弟的建议一般是这套后端SpringBoot 2.7.x MyBatis-Plus MySQL 5.7/8.0 Redis可选认证JWTjjwt库 Spring拦截器前端Vue3 Vite Vue Router Pinia Element Plus Axios接口文档SpringDoc/knife4jSwagger部署后端打成jar包前端打包后放入静态目录或独立Nginx为什么要强调版本“springboot版本太高”是这几年踩坑重灾区。SpringBoot 3.x要求JDK 17某些学校的机房装的是JDK 8而且很多教程里的写法在3.x里已经变了。除非你确定本机环境支持否则毕业设计老老实实选2.7.x它兼容JDK8生态环境也成熟。前端同理Vue3没问题但如果你只看过Vue2的课硬上Composition API反而会拖节奏这时选Vue2也不丢人。工具是给别人用的不是折磨自己的。另外一个构建层面的经验项目构建工具用Mavenparent就认准spring-boot-starter-parent版本号控制在2.7.x依赖的版本尽量交给parent统一管理别自己手动引入一堆不清楚的版本号否则依赖冲突会占用你大量时间。2. 整表建模旅游内容与商品交易的双域数据结构2.1 用户表设计与扩展字段用户表是所有模块的地基。设计成什么样直接决定后面功能好不好写。我用的基础结构如下CREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 登录名, password varchar(100) NOT NULL COMMENT BCrypt加密后的密码, nickname varchar(50) DEFAULT NULL COMMENT 昵称, avatar varchar(255) DEFAULT NULL COMMENT 头像URL, gender tinyint(1) DEFAULT 0 COMMENT 0未知 1男 2女, phone varchar(20) DEFAULT NULL, email varchar(100) DEFAULT NULL, intro varchar(500) DEFAULT NULL COMMENT 个人签名, role tinyint(1) NOT NULL DEFAULT 0 COMMENT 0普通用户 1管理员, status tinyint(1) NOT NULL DEFAULT 1 COMMENT 1正常 0禁用, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有几个值得注意的点username和nickname分开登录名和展示名解耦后面做“改昵称”功能不用动账号。password一定存哈希后的结果用Spring Security的BCryptPasswordEncoder或jBCrypt都行千万不要自己写MD5拼接盐。role字段放用户表里简单场景够用不需要单独拆RBAC表如果后面想加权限粒度再加角色表。2.2 旅游分享内容模型帖子表的设计我见过很多同学把内容全部塞进一个text字段其他什么信息都没有这会导致前端信息流非常难看。做旅游分享至少要知道这个分享发生在哪里、封面图是什么、属于哪个分类CREATE TABLE post ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, category_id bigint(20) DEFAULT NULL COMMENT 分类攻略/游记/问答, title varchar(100) NOT NULL, content text NOT NULL COMMENT 富文本或纯文本, location varchar(100) DEFAULT NULL COMMENT 地点/目的地, cover varchar(255) DEFAULT NULL COMMENT 封面图地址, status tinyint(1) NOT NULL DEFAULT 1 COMMENT 1正常 0待审 2隐藏, like_count int(11) NOT NULL DEFAULT 0, comment_count int(11) NOT NULL DEFAULT 0, view_count int(11) NOT NULL DEFAULT 0, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_category_id (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;关于帖子配图很多人纠结是存JSON还是拆表。我的答案是图片列表单独建一张表或者用逗号分隔都行但主帖表里一定要有cover封面字段因为信息流卡片必须靠封面来撑排面查封面比查全部图片效率高得多。评论区在互动模块一起讲。2.3 商品与订单表的设计取舍商城部分的核心是商品表、订单表、订单明细表。商品表相对常规id、name、cover、description、price、stock、sales、category_id、status。真正麻烦的是订单。先看订单表CREATE TABLE order ( id bigint(20) NOT NULL AUTO_INCREMENT, order_no varchar(64) NOT NULL COMMENT 业务订单号, user_id bigint(20) NOT NULL, total_amount decimal(10,2) NOT NULL, status tinyint(1) NOT NULL DEFAULT 0 COMMENT 0待支付 1已支付 2已发货 3已完成 4已取消, receiver_name varchar(50) DEFAULT NULL, receiver_phone varchar(20) DEFAULT NULL, receiver_address varchar(200) DEFAULT NULL, pay_time datetime DEFAULT NULL, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;订单明细表记录下单时商品维度的快照至少要包含商品ID、名称、单价、数量、小计金额。一个需要注意的设计问题不要在订单表里记一个“商品名称字符串”就完事明细表是必须的。原因是真实商城一笔订单可以包含多个商品而且毕业设计答辩时老师很可能问“订单怎么保证属于同一笔交易”“下单之后商品改价了怎么办”。有了明细表快照问题就解决了——下单时把当时的名称和单价写进明细商品后续改价不影响这笔订单。下单是一段典型的事务逻辑校验库存 - 冻结/扣减库存 - 生成主订单 - 生成明细。库存扣减放在哪里很关键最稳妥的做法是在下单SQL里加条件例如“update product set stock stock - 1 where id ? and stock 1”然后判断受影响行数而不是先查一遍再更新避免并发超卖。这种细节写进论文里是加分的。2.4 收藏、点赞、评论的表结构细节这三类互动虽然看起来简单但有一个容易出现的问题唯一约束和逻辑删除的冲突。点赞表CREATE TABLE like_record ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, post_id bigint(20) NOT NULL, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_user_post (user_id, post_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;收藏表结构类似。它的作用不是存“数据”而是存“关系”谁对什么内容点了赞。前端只显示数字后端靠这张表去重。唯一索引user_id, post_id是防止同一个用户重复点赞的数据库防线。但这里有个坑如果给like_record加deleted逻辑删除字段再保留唯一索引同一个用户取消点赞再点赞会插入新记录然后旧记录还占着唯一索引里的坑位直接导致第二次点赞失败。这就是“逻辑删除和唯一索引冲突”。解决方式有三种一是干脆物理删除毕业设计场景完全够用二是把唯一索引改成user_id, post_id, deleted并把deleted设计成tinyint区分三是改用状态字段0取消1点赞唯一索引覆盖user_id, post_id去更新状态。我一般推荐第一种或第三种别在这种地方给自己挖坑。另外帖子的点赞数不要每次回复都去count整张表可以像我的post表那样在帖子表里冗余一个like_count字段点赞时做1这样列表页拿数字非常快。删除帖子的时候顺手把这个帖子的关联互动记录清掉即可。3. SpringBoot后端实现从接口分层到业务闭环3.1 工程结构、统一返回体与全局异常处理先承认一个事实SpringBoot的好处就是约定大于配置一个空的工程跑起来只需要几分钟。但你一定不要把全部代码写在一个XXXApplication里面。给个实用的目录结构com.walkingcircle ├── controller │ ├── UserController.java │ ├── PostController.java │ ├── ProductController.java │ ├── OrderController.java │ └── AdminController.java ├── service │ ├── impl ├── mapper ├── entity ├── dto ├── vo ├── common │ ├── Result.java │ └── GlobalExceptionHandler.java ├── config │ ├── WebConfig.java │ └── MybatisPlusConfig.java └── WalkCircleApplication.java统一返回体这一条很多课上不讲但实际项目里没有它你会想哭。所有接口都返回一个Result对象public class ResultT { private Integer code; private String msg; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.code 200; r.msg success; r.data data; return r; } public static T ResultT error(Integer code, String msg) { ResultT r new Result(); r.code code; r.msg msg; return r; } }再加上全局异常处理器RestControllerAdvice把参数校验异常、业务异常、未知异常都收敛起来返回给前端。这样前端axios拦截器只需要判断code是不是200可以省掉大量重复的if/else。3.2 JWT登录与会话安全方案毕业设计最常见的登录方案是Session但我更推荐JWT。理由很简单Vue前端和后端分离之后跨域场景下Session要么配CORS开credentials要么cookie容易被浏览器拦而JWT不存在这个问题而且“无状态认证”写在论文里是一个现成的技术亮点。实现起来不复杂登录接口校验用户名密码成功后用jjwt生成token。token里放userId和role两个claim过期时间设为24小时。写一个LoginInterceptor从请求头Authorization里解析token解析成功放行。将拦截器注册到WebConfig里排除login、register、静态资源路径。具体拦截器大体逻辑Component public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { token token.substring(7); try { Claims claims JwtUtil.parseToken(token); request.setAttribute(userId, claims.get(userId)); return true; } catch (Exception e) { // 解析失败抛业务异常 } } throw new BusinessException(401, 请先登录); } }做完之后记得留一个获取当前登录用户的工具方法因为发帖、评论、下单都要拿到当前用户的ID。常用的做法是把用户ID放request attribute然后在Controller里用RequestAttribute取或者用ThreadLocal的UserContext工具类。两者都行我倾向于后者写的时候更干净不用每个接口方法都加一个额外参数。3.3 发帖与图片上传的完整链路发帖这个功能看起来简单真正做的时候涉及两个链路上传图片和提交内容。很多同学把这两个混成一个接口结果图片出问题的时候内容也存不进去。我建议拆成两个接口POST /api/upload/image上传图片返回图片URL。POST /api/post提交帖子内容内容是纯文本里面引用图片URL。图片上传落地实际注意点PostMapping(/upload/image) public ResultString upload(RequestParam(file) MultipartFile file) { // 1. 校验文件类型和大小 // 2. 生成唯一文件名UUID 后缀 // 3. 保存到本地磁盘 upload/ 目录 // 4. 返回可访问的URL }生成唯一文件名防止重名覆盖限制大小例如单张不得超过5MB注意把文件后缀转小写否则上传.jpg和.JPG会生成不同后缀。保存路径不要写死成绝对路径用配置项管理。还有一个容易忽略的点图片URL最终返回的时候如果开发环境用localhost生产环境要换成域名或者服务器IP。最好的办法是约定前端所有图片URL都存相对路径由前端拼接完整地址或者后端配置一个base-url参数拼接。否则你本地图片正常打包上线后全裂了。发帖service的核心我给一个要点清单写入post表时必须带上当前登录用户ID。content不要直接相信前端做一下长度和基础内容校验至少长度不能为空、不能超上限。事务注解Transactional要加在service方法上虽然单表插入看起来不需要但后续要同步更新帖子分类计数时事务能避免中间状态。3.4 商品下单与库存扣减的事务控制商城业务的后端核心难点在下单。我之前帮学弟改过的代码一半问题出在库存和订单状态。下单的service方法可以整理成四步参数校验检查收货地址、商品ID、购买数量。查询商品并校验在架状态。扣减库存使用库存字段加条件更新的写法。生成订单和明细返回orderNo。关键代码示意Transactional public OrderVO createOrder(OrderCreateDTO dto) { Product product productMapper.selectById(dto.getProductId()); if (product null || product.getStatus() ! 1) { throw new BusinessException(商品不存在或已下架); } // 条件更新扣库存返回受影响行数 int rows productMapper.deductStock(dto.getProductId(), dto.getQuantity()); if (rows 0) { throw new BusinessException(库存不足); } // 生成订单和明细... }失败立刻抛异常事务回滚库存不会变成负数。订单状态字段我用0/1/2/3/4五个值前端根据值渲染对应的按钮待支付显示“去支付”已支付显示“待发货”已发货显示“确认收货”已完成和已取消都只是展示状态。这里不要把状态文案直接写死在前端最好从后端字典里取或者至少在常量类里统一管理否则后面加一个状态你得改三个页面。4. Vue端页面落地信息流、商品详情与状态管理4.1 Vue工程初始化与前端目录结构前端这块我推荐Vite理由就一条启动快喝茶的时间都省了。创建工程一句话npm create vitelatest walk-front -- --template vue npm install npm install vue-router pinia axios element-plus目录结构可以这么拆src ├── api │ ├── post.js │ ├── product.js │ ├── order.js │ └── user.js ├── router │ └── index.js ├── stores │ └── user.js ├── views │ ├── Home.vue │ ├── PostDetail.vue │ ├── ProductList.vue │ ├── ProductDetail.vue │ ├── Cart.vue │ ├── OrderList.vue │ ├── UserCenter.vue │ └── admin │ ├── AdminUser.vue │ ├── AdminPost.vue │ └── AdminOrder.vue ├── components │ ├── PostCard.vue │ └── Pagination.vue └── utils └── request.js按api目录集中封装接口不要在每一个页面里直接写axios.get后面联调改baseURL你才知道什么叫痛苦。views目录按路由页面组织admin单独建子目录方便在路由做懒加载和守卫。4.2 路由守卫与axios拦截器路由守卫解决的是“未登录不能访问个人中心/商城下单”的问题。核心逻辑就一段router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.meta.requiresAuth !token) { next(/login); } else { next(); } });注意meta里给admin页面设置requiresAdmin管理员路由除了要有token还要从Pinia里取用户rolerole不是1就重定向到404或首页。这里最容易漏的是后端接口权限一定要做前端路由守卫只是改善体验不能作为安全手段——直接curl你的后端接口就能绕过前端。axios拦截器是每次请求的必经之路配置成复用逻辑service.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization Bearer ${token}; } return config; }); service.interceptors.response.use( res { if (res.data.code 200) return res.data.data; return Promise.reject(res.data.msg); }, err { if (err.response err.response.status 401) { localStorage.removeItem(token); router.push(/login); } return Promise.reject(err); } );这样所有业务页面拿到的就是接口真正的data不需要每次都写res.data.data。4.3 旅游动态信息流组件的实现信息流是“行走圈”的门面。我的做法是首页用PostCard组件渲染帖子列表每个PostCard包含封面图、标题、地点、作者头像、点赞数、评论数。拉到页面底部加载更多。列表接口建议做分页?pageNum1pageSize10返回total。前端拿到total之后决定是否显示“加载更多/没有更多了”。如果不做分页数据一多页面直接卡顿答辩老师一旦往上多滑几下观感差别很大。PostCard的骨架大概是这样template div classpost-card clickgoDetail el-image :srcpost.cover fitcover / div classpost-info h3{{ post.title }}/h3 span{{ post.location }}/span div classfooter span{{ post.author.nickname }}/span span赞 {{ post.likeCount }} 评论 {{ post.commentCount }}/span /div /div /div /template注意前端在v-for渲染时要加:key列表数据里最好有唯一id封面加载失败要兜底Element Plus的el-image自带error插槽随便放个占位图否则大面积红x很难看。4.4 商品交易与个人中心的关键联动商品详情页到订单页的链路往往需要跨路由传参数。常见做法有三种query传id、动态路由、本地变量。前端商品详情页点击“立即购买”时你要把商品id带到确认订单页同时把数量、总价在确认页展示。因为确认订单页需要根据商品id重新查商品再计算价格所以最稳的是把商品id放在路由query里确认页面onMounted时重新请求接口取最新价格。绝对不要只靠上一页传过来的price字符串以防用户在前端篡改支付金额。毕业设计也许没人黑你但答辩老师很可能问这个设计漏洞提前准备好答案会很加分。个人中心要展示的信息包括我的资料、我的帖子数、我的收藏数、我的订单列表。这些接口尽量按用户维度统一规划例如后端提供GET /api/user/center/{userId}一次性返回基础信息和统计值前端一个页面只调一次接口。否则个人中心打开要发五六个请求Loading转半天看着就很业余。5. 联调、跨域与部署的一揽子工程5.1 跨域配置开发代理与生产CORS前后端分离跨域问题是绕不开的。开发阶段最优雅的不是在后端开CORS而是用Vite的代理前端中间件把 /api 转给后端// vite.config.js server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这样前端请求 /api/xxxVite开发服务器会转发到后端浏览器眼里请求的是同一个origin不存在跨域。后端不需要对开发环境开放CORS。但生产环境要分情况。如果前端和后端不同域名部署就必须在后端启用CORS。SpringBoot里加一个配置类Configuration public class WebConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(false) .maxAge(3600); } }如果你用JWTCORS的allowCredentials设false问题不大token在header里不带cookie安全性反而更好控制。5.2 图片文件存储选型本地还是对象存储图片存储第一个选项是本地磁盘第二个是MinIO或云OSS。毕业设计预算有限我建议直接本地磁盘。做法不复杂后端把上传的图片写成UUID.png放到upload目录然后配置一个静态资源映射让SpringBoot能把 /upload/** 映射到物理目录。SpringBoot 2.7里这样配Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file:D:/walkingcircle/upload/); }如果你身边有对象存储环境比如学校给的服务器上有MinIO那就可以顺带做个练习把图片传到MinIO再返回访问地址。MinIO的好处是图床和后端分离将来部署到生产环境不会被本地磁盘空间限死。MinIO和SpringBoot整合也是一些人常问的点我的经验是前端配置不多安装MinIO客户端、配置endpoint/accessKey/secretKey/bucket然后在Spring Boot里集成一个StorageService封装putObject就行代码量很小但写在简历和论文里能多出一个“对象存储”的关键词。5.3 前后端打包上线的两种常规路线上线部署有两种常见方案我两种都跑通过方案一前端后端合体部署。把Vue打包出来的dist目录直接复制到SpringBoot的src/main/resources/static下面然后重新打包jar。这样访问同一个端口就能同时打开前端页面和后端接口连CORS都省了。缺点是不方便单独更新前端但毕业设计毫无压力。方案二Nginx反向代理。前端dist单独部署到Nginx后端jar单独启动Nginx配置把 /api 转发给后端端口。结构更正规但如果服务器上还没装Nginx搞起来多花半天。不管哪种方案有一个点特别容易翻车打包前端的时候baseURL。如果是方案一请求地址可以用相对路径’/api’由Nginx或SpringBoot转发如果是方案二你要把axios的baseURL写成一个完整的后端地址或者同样用Nginx代理。我见过太多同学本地好好的打包上线后所有接口401原因就是baseURL写死localhost:8080服务器上根本不存在。6. 从开发到答辩我的实战踩坑与讲解要点6.1 几个真正浪费过我时间的Bug第一个坑是Long类型主键传到前端精度丢失。前端的JS Number能安全表示的最大整数是2的53次方而MyBatis-Plus默认主键策略生成的是雪花ID那种19位Long传到前端后最后几位全变成了0详情页id对不上。解决方式很简单在SpringBoot里统一给Long字段配置ToStringSerializer或者给VO里的主键字段加JsonSerialize(using ToStringSerializer.class)。这个坑发生率很高早处理早舒服。第二个坑是MyBatis-Plus的分页插件没有配置导致Page对象返回的records总是空或者total永远是0。你需要注册一个MybatisPlusInterceptorBean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }同时注意Page的current从1开始前端分页组件当前页别从0传。另外时间字段序列化默认Jackson会把LocalDateTime输出成一大串数组前端根本看不懂。统一配置日期格式或者给字段加JsonFormat(pattern yyyy-MM-dd HH:mm:ss)两个都要会。第三个坑是上传图片返回的URL在本地能访问上线后图片全部404。原因一般就两个一是静态资源映射没配到生产环境目录二是URL里没带服务器实际端口或域名。排查的时候先curl一下图片URL看看响应码再检查映射路径别急着改代码。6.2 演示准备与基础性能优化演示是毕设答辩的命门。再好的系统演示卡顿都会扣印象分。这里给你几条实在建议准备一个“演示账号”里面预先发布10篇左右的攻略、上传好图片、下过几笔订单不要现场临时注册再发帖。把Redis加速这个功能想清楚再决定做不做。如果为了加分做了至少要在论文里讲清“热帖缓存、登录token缓存”两个应用场景否则老师说一句“你这个Redis就只是存了个token吧”会非常尴尬。基础性能优化只要做两件事就够首页帖子列表用分页后端SQL关键字字段加索引。这两条足够答辩时回答“系统有没有做性能优化”这种问题。6.3 答辩现场如何讲清楚项目亮点最后一个实际经验答辩时不要照念论文讲项目重点抓三条主线。第一条业务主线这是“旅游信息交流商品交易”双系统用户发布内容、互动、交易、后台管理全都有证明完整度。老师听完会知道这不是只写了几个页面。第二条技术主线说明前后端分离、JWT无状态认证、MyBatis-Plus的使用、事务实现下单、对象存储或者本地静态映射证明你的技术不是空吹。第三条成长主线讲一个你踩过的坑比如Long精度丢失或者分页插件然后讲你如何一步步排查解决。这个环节最有说服力比任何自夸都有效。我个人是常年帮人把关全栈毕业设计项目、改bug时积累的这些经验。做完“行走圈”这套最大的感受是毕设选型真的不用贪多求全把SpringBootVue这条线彻底走通数据库设计做到不冲突、事务不丢、鉴权不裸奔就已经超过大多数同学了。最后再分享一个小建议不要直接去网上找现成源码照着粘贴代码可以借鉴结构但每一段都要能自己讲明白。答辩老师并不怕你做得简单怕的是你在台上讲不清楚自己写了什么。希望这篇文章能帮你把“行走圈”从题目变成真正可以演示、可以答辩的系统。