ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

校园食堂点评系统开发全流程:基于Spring Boot+Vue+MySQL的详细实现

2026/9/5 18:21:08 拓冰建站 浏览量
校园食堂点评系统开发全流程:基于Spring Boot+Vue+MySQL的详细实现 写校园食堂点评系统这类毕业设计时最容易出现的问题不是某个功能不会写而是“数据库表之间是什么关系、前后端如何约定接口、评论状态该由谁控制”这些整体性问题没有梳理清楚。一个看起来只是“给食堂打分、留言”的项目实际上要同时处理用户登录、食堂和菜品展示、评价数据落库、管理端审核、统计分数等多条业务链路。这篇文章围绕一个可复现的 Spring Boot Vue MySQL 校园食堂点评系统按毕业设计最常见的路线来做先拆需求再设计表结构然后完成后端接口再写 Vue 页面最后做联调和验收。项目会覆盖学生端和管理员端包含 JWT 登录、点评提交、管理员审核、评分展示等核心模块代码示例可以直接作为你本地项目的起点。1. 先想清楚“校园食堂点评系统”要管什么、谁来用这一节不是写需求文档而是决定后面的数据库表和接口怎么拆。很多毕业设计做不下去是因为一上来就写代码做完之后发现功能之间互相矛盾例如学生端能看到未审核的脏评论或者管理员无法修改菜品价格。1.1 角色只有两类但权限要拆分清楚校园食堂点评系统可以设置两类角色一个是普通学生用户一个是管理员。如果论文需要再加一层“食堂窗口管理员”也可以承接这个模型只多维护一个role字段即可。学生注册、登录、查看食堂窗口、查看菜品、提交评分和文字点评、查看自己的历史点评。管理员维护食堂窗口信息、维护菜品、管理学生点评状态、查看整体评分统计。这两类角色会直接影响 API 设计。学生在调接口时只能操作canteen、dish、review中的部分资源管理员需要额外的管理接口。即使不做复杂权限框架也要在后端 Controller 上做基本的角色判断。1.2 点评系统的核心实体只有四个一个食堂点评系统的业务闭环可以拆成四张核心表用户表、食堂窗口表、菜品表、点评表。用户和点评是一对多关系一个学生可以发表多条点评。食堂窗口和菜品是一对多关系一个窗口有多个菜品。菜品表和点评表的关系需要注意如果点评必须绑定菜品会让“对某个窗口整体评价”变得很别扭如果点评只能绑定窗口又无法体现“哪个菜不好吃”。推荐做法是点评表同时允许窗口级和菜品级评价用dish_id可空来区分。1.3 功能清单可以先列成验收表在设计阶段把功能点做成表格后续开发完一条勾一条。功能模块角色操作说明是否必须注册登录学生用户名密码注册登录后获取 token是窗口列表学生分页查看食堂窗口及平均评分是菜品列表学生查看指定窗口下的菜品是提交点评学生选择窗口、菜名、评分、文字内容是我的点评学生查看自己提交的点评及审核状态是窗口维护管理员新增、编辑、下架食堂窗口是菜品维护管理员维护窗口下的菜品和价格是点评审核管理员通过或屏蔽学生点评是评分统计管理员查看窗口平均分和点评量可选从这张表可以看出来数据库字段必须包含“审核状态”和“平均分展示”这两个不容易一眼看到的需求。2. 数据库设计用 MySQL 把用户、窗口、菜品、点评串起来数据库是整个项目的基石。把表结构提前确定好后端 Mapper 层会省很多事Vue 页面需要的字段也能提前对齐。2.1 建库和表结构新建数据库campus_canteen字符集使用utf8mb4。不要在 Linux 或测试环境里把中文直接放进latin1库否则保存评论时会出现乱码。CREATE DATABASE campus_canteen DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE campus_canteen;用户表保存登录凭据和昵称。密码字段建议至少使用 BCrypt 加密如果是课程设计为了演示可以在项目里先存明文但论文和答辩要说明这是简化处理后续需要升级。CREATE TABLE t_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录账号, password VARCHAR(100) NOT NULL COMMENT 登录密码, nickname VARCHAR(50) DEFAULT COMMENT 昵称, role VARCHAR(20) DEFAULT STUDENT COMMENT 角色ADMIN/STUDENT, avatar VARCHAR(255) DEFAULT COMMENT 头像地址, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT用户表;窗口表保存食堂名称、位置、介绍和状态。avg_score字段是先冗余存储平均分还是每次都通过AVG函数现场计算这里可以保留冗余字段简化主列表查询。CREATE TABLE t_canteen ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, name VARCHAR(100) NOT NULL COMMENT 食堂窗口名称, location VARCHAR(100) DEFAULT COMMENT 所在校区/楼层, description VARCHAR(500) DEFAULT COMMENT 介绍, cover_url VARCHAR(255) DEFAULT COMMENT 图片地址, avg_score DECIMAL(3,2) DEFAULT 0 COMMENT 平均评分, status TINYINT DEFAULT 1 COMMENT 1启用 0停用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT食堂窗口表;菜品表需要关联canteen_id并保留价格、图片和简介。价格使用DECIMAL(10,2)不使用FLOAT避免精度问题。CREATE TABLE t_dish ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, canteen_id BIGINT NOT NULL COMMENT 所属食堂窗口, name VARCHAR(100) NOT NULL COMMENT 菜品名称, price DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT 价格, cover_url VARCHAR(255) DEFAULT COMMENT 图片地址, description VARCHAR(500) DEFAULT COMMENT 简介, status TINYINT DEFAULT 1 COMMENT 1在售 0下架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT菜品表;点评表是本系统的核心。user_id、canteen_id关联用户和窗口dish_id可空表示这条点评是对具体菜品的评价还是对窗口整体环境的评价。score取 1 到 5status控制审核流程0 表示待审核1 表示已通过2 表示已屏蔽。CREATE TABLE t_review ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, user_id BIGINT NOT NULL COMMENT 用户ID, canteen_id BIGINT NOT NULL COMMENT 窗口ID, dish_id BIGINT DEFAULT NULL COMMENT 菜品ID可空, score INT NOT NULL COMMENT 评分1-5, content VARCHAR(1000) DEFAULT COMMENT 点评内容, status TINYINT DEFAULT 0 COMMENT 0待审核 1已通过 2已屏蔽, reply_content VARCHAR(500) DEFAULT COMMENT 管理员回复, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT学生点评表;2.2 平均分字段如何维护如果窗口列表每次都去t_review表计算AVG(score)当表数据量上来后查询会变慢。因此t_canteen表里保留了avg_score字段。学生提交一条通过审核的点评后后端可以在一个事务内重新计算该窗口平均分并更新到窗口表UPDATE t_canteen AS c SET avg_score ( SELECT ROUND(AVG(r.score), 2) FROM t_review AS r WHERE r.canteen_id c.id AND r.status 1 ) WHERE c.id #{canteenId};这里有两个细节平均分只计算status 1的已审核点评更新平均分要放在事务里不能只修改t_review表否则学生端看到的分数和列表不一致。2.3 为什么不建外键毕业设计阶段不用在 MySQL 物理层加太多外键约束。把user_id、canteen_id、dish_id设计成普通索引字段通过应用层保证逻辑关系数据库压力小删除数据也更灵活。实际项目里可以考虑为三张关联表分别建立索引ALTER TABLE t_dish ADD INDEX idx_dish_canteen(canteen_id); ALTER TABLE t_review ADD INDEX idx_review_user(user_id); ALTER TABLE t_review ADD INDEX idx_review_canteen_status(canteen_id, status);如果t_review的查询条件固定在某个食堂窗口下按状态过滤联合索引(canteen_id, status)能明显减少回表。3. Spring Boot 后端搭建目录、依赖和配置文件后端项目用 Maven 管理最核心的依赖是 Spring Web、MySQL 驱动、MyBatis-Plus 和 JWT 工具类。很多人会在这一步踩到版本坑下面按一个可用组合来写。3.1 项目目录结构采用分层结构包名建议用com.campus.canteen。一个标准后端工程目录如下backend ├── src/main/java/com/campus/canteen │ ├── CampusCanteenApplication.java │ ├── common │ │ ├── RestResult.java │ │ └── BusinessException.java │ ├── config │ │ ├── CorsConfig.java │ │ └── WebInterceptorConfig.java │ ├── controller │ │ ├── AuthController.java │ │ ├── CanteenController.java │ │ ├── DishController.java │ │ └── ReviewController.java │ ├── interceptor │ │ └── TokenInterceptor.java │ ├── mapper │ │ ├── UserMapper.java │ │ ├── CanteenMapper.java │ │ ├── DishMapper.java │ │ └── ReviewMapper.java │ ├── entity │ │ ├── User.java │ │ ├── Canteen.java │ │ ├── Dish.java │ │ └── Review.java │ ├── dto │ │ ├── LoginDTO.java │ │ └── ReviewDTO.java │ ├── service │ │ ├── UserService.java │ │ └── ReviewService.java │ └── util │ └── JwtUtil.java └── src/main/resources ├── application.yml └── sql/campus_canteen.sql这个结构不复杂但是能保证后面写 Controller、Service、Mapper 时不会乱。3.2 pom.xml 依赖版本Spring Boot 2.7.x 和 Spring Boot 3.x 的兼容方式不同。如果本机 JDK 是 1.8建议使用 Spring Boot 2.7 系列如果本机已经安装 JDK 17 并希望使用较新的版本再选择 Spring Boot 3.2.x。下面的依赖以 Spring Boot 3.2.4 为例parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.4/version /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version3.5.7/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-impl/artifactId version0.11.5/version scoperuntime/scope /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-jackson/artifactId version0.11.5/version scoperuntime/scope /dependency /dependencies如果使用 Spring Boot 2.7.x则依赖需要把mybatis-plus-spring-boot3-starter改成mybatis-plus-boot-starter同时注意 JWT 库不需要变化。常见的一个坑是直接把 Spring Boot 3 的代码复制到 JDK 1.8 环境中编译结果出现类似org/apache/ibatis/binding/MapperRegistry或java: 不支持发行版本 17的错误。版本匹配问题要先在创建项目时检查清楚。3.3 application.yml 关键配置配置文件里最容易出问题的是 MySQL URL 参数。MySQL 8 默认时区是 UTC如果不在 URL 中指定serverTimezone后端驱动会报异常。server: port: 8080 spring: application: name: campus-canteen-backend datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/campus_canteen?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: 123456 servlet: multipart: max-file-size: 5MB max-request-size: 10MB mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: auto logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0map-underscore-to-camel-case必须开启这样数据库里的create_time才能自动映射到实体的createTime字段。log-impl用StdOutImpl是为了本地调试方便生产环境建议去掉或改成日志框架存储。3.4 统一返回结果和后端遇到的常见异常前后端沟通时不能一个接口返回Map另一个返回字符串要统一格式。一般约定如下public class RestResultT { private Integer code; private String message; private T data; public static T RestResultT success(T data) { RestResultT result new RestResult(); result.code 200; result.message success; result.data data; return result; } public static T RestResultT error(Integer code, String message) { RestResultT result new RestResult(); result.code code; result.message message; return result; } }代码中尽量不要直接把Exception.getMessage()返回给前端因为数据库驱动信息和内部堆栈会暴露太多实现细节。Controller 层做统一的业务异常处理即可。4. 后端核心模块JWT 登录、点评提交和审核流程毕业设计里用户登录最常见的实现是 JWT 方式。用户登录成功后后端签发一个 tokenVue 把它存在浏览器并在后续请求头中携带。下面是一段可用于教学的最小实现。4.1 用 JwtUtil 生成校验收到的 token登录鉴权先编写工具类。使用 JJWT 时需要准备密钥密钥长度要满足 HS256 算法要求。public class JwtUtil { private static final SecretKey SECRET_KEY Keys.hmacShaKeyFor(campus-canteen-secret-key-2024-change-me.getBytes(StandardCharsets.UTF_8)); public static String generateToken(Long userId, String role) { long expireMillis 7L * 24 * 3600 * 1000; return Jwts.builder() .setSubject(userId.toString()) .claim(role, role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() expireMillis)) .signWith(SECRET_KEY, SignatureAlgorithm.HS256) .compact(); } public static Claims parseToken(String token) { return Jwts.parserBuilder() .setSigningKey(SECRET_KEY) .build() .parseClaimsJws(token) .getBody(); } }这个类有两个注意点密钥不要硬编码写进代码里毕业设计本地跑没问题但论文里要说明正式环境密钥应放到环境变量或配置中心setClaims中的role只是给前端做展示真正的权限校验依然要以后端查到的数据库角色为准。4.2 登录接口用户通过username和password登录校验成功后返回用户基本信息和 token。密码校验这里直接使用数据库字段匹配生产项目需要改成 BCrypt 校验。RestController RequestMapping(/api/auth) public class AuthController { Resource private UserMapper userMapper; PostMapping(/login) public RestResultMapString, Object login(RequestBody LoginDTO dto) { User user userMapper.selectOne( new LambdaQueryWrapperUser() .eq(User::getUsername, dto.getUsername()) ); if (user null || !user.getPassword().equals(dto.getPassword())) { return RestResult.error(401, 用户名或密码错误); } String token JwtUtil.generateToken(user.getId(), user.getRole()); MapString, Object data new HashMap(); data.put(token, token); data.put(nickname, user.getNickname()); data.put(role, user.getRole()); return RestResult.success(data); } }为了管理员能登录表数据里至少要准备一个管理员账号INSERT INTO t_user (username, password, nickname, role) VALUES (admin, 123456, 系统管理员, ADMIN);这条 SQL 只是演示答辩时最好说明密码是 BCrypt 加密后的结果演示环境为了方便可以保留明文账号但生产系统万万不能这样处理。4.3 点评提交接口学生提交点评时要验证 token判断当前用户的身份同时校验分数范围。这个接口需要接收窗口、菜品、分数和文字内容。RestController RequestMapping(/api/review) public class ReviewController { Resource private ReviewMapper reviewMapper; PostMapping(/submit) public RestResultString submit(RequestBody ReviewDTO dto, HttpServletRequest request) { Long userId CurrentUserHolder.getUserId(request); if (userId null) { return RestResult.error(401, 请先登录); } if (dto.getScore() null || dto.getScore() 1 || dto.getScore() 5) { return RestResult.error(400, 评分必须在1到5之间); } Review review new Review(); review.setUserId(userId); review.setCanteenId(dto.getCanteenId()); review.setDishId(dto.getDishId()); review.setScore(dto.getScore()); review.setContent(dto.getContent()); review.setStatus(0); reviewMapper.insert(review); return RestResult.success(点评已提交等待管理员审核); } }注意CurrentUserHolder是拦截器往 ThreadLocal 里写入的当前用户信息这样才能从HttpServletRequest去验证已经解析出来的 userId。使用独立的拦截器而不是在业务代码里频繁调用parseToken可以避免大量重复代码。4.4 管理员审核接口管理员审核点评本质上是一个状态更新接口。需要区分角色否则学生也能把自己的点评直接改成通过状态。PutMapping(/admin/review/{id}) public RestResultString audit(HttpServletRequest request, PathVariable Long id, RequestParam Integer status) { User currentUser (User) request.getAttribute(currentUser); if (!ADMIN.equals(currentUser.getRole())) { return RestResult.error(403, 没有管理权限); } Review review reviewMapper.selectById(id); if (review null) { return RestResult.error(404, 点评不存在); } review.setStatus(status); reviewMapper.updateById(review); // 同步更新窗口平均分 updateCanteenAverageScore(review.getCanteenId()); return RestResult.success(操作成功); }审核动作只会用status 1或status 2。审核为通过后窗口平均分才被影响。这样设计的好处是未审核点评不会污染展示数据但坏处是最近提交的点评在管理员审核之前学生端看不到。4.5 拦截器和 CORS 配置后端项目如果直接让 Vue 页面请求http://localhost:8080/api/...而前端页面跑在http://localhost:5173会产生跨域问题。最稳妥的方法是后端配置允许跨域同时前端也做好开发代理双保险。Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }如果你使用了 token 拦截器还要留意OPTIONS请求。浏览器发来的预检请求没有实战的 Authorization 头因此拦截器对/api/auth/login和请求方法为OPTIONS的请求要直接放行。5. Vue 3 前端实现环境准备、路由、页面组件前端选择 Vue 3 Vite Element Plus。相比 Vue 2Vue 3 的组合式 API 更适合管理食堂点评这种包含表单提交、数据列表和权限判断的中小型管理页面。5.1 初始化项目并安装依赖本机建议先检查 Node.js 版本。Vite 5 要求 Node.js 18 或更高如果安装后出现报错第一步不是重装项目而是执行node -v确认版本。npm create vitelatest campus-canteen-web -- --template vue cd campus-canteen-web npm install npm install axios vue-router element-plus element-plus/icons-vue启动开发服务器npm run dev如果项目目录名里包含中文或特殊字符某些版本的老打包工具会出现路径异常建议所有前端项目目录都用英文字母。5.2 封装 axios 请求前后端联调时统一封装 axios 能减少重复代码。示例放到src/api/request.jsimport axios from axios import { ElMessage } from element-plus import router from ../router const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use((config) { const token localStorage.getItem(campus_token) if (token) { config.headers.Authorization Bearer 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(campus_token) router.push(/login) } ElMessage.error(网络异常) return Promise.reject(error) } ) export default request这里还有一个小细节baseURL写/api然后让 Vite 的本地代理转发到 Spring Boot。开发环境代理配置在vite.config.js中import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ plugins: [vue()], server: { host: 0.0.0.0, port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })使用代理而不是直接写http://localhost:8080能避免大量 CORS 报错也让前端代码更整洁。5.3 配置路由和登录守卫食堂点评系统至少需要登录页、首页、窗口详情、我的点评、管理员布局五个路由。Vue Router 配置示例import { createRouter, createWebHistory } from vue-router const routes [ { path: /login, component: () import(../views/Login.vue) }, { path: /, component: () import(../views/Home.vue) }, { path: /canteen/:id, component: () import(../views/CanteenDetail.vue) }, { path: /admin, component: () import(../views/admin/AdminLayout.vue), meta: { role: ADMIN }, children: [ { path: , component: () import(../views/admin/AdminCanteen.vue) }, { path: review, component: () import(../views/admin/AdminReview.vue) } ] } ] const router createRouter({ history: createWebHistory(), routes }) router.beforeEach((to, from, next) { const token localStorage.getItem(campus_token) const role localStorage.getItem(campus_role) if (to.path /login) { next() return } if (!token) { next(/login) return } if (to.meta.role to.meta.role ! role) { next(/) return } next() }) export default router路由守卫是解决“学生直接访问管理员页面”的关键地方。不要只在前端隐藏菜单路由守卫和后端接口角色校验要同时存在。5.4 点评表单页面食堂窗口详情页应同时展示窗口信息、菜品列表和点评提交区。点击某个菜品后可以在弹窗里填写评分和文字。下面是一个简化后的 Vue 组件关键片段template div el-form :modelform label-width80px el-form-item label评分 el-rate v-modelform.score / /el-form-item el-form-item label点评内容 el-input v-modelform.content typetextarea :rows3 maxlength500 show-word-limit placeholder这道菜分量如何口味怎样 / /el-form-item el-button typeprimary clicksubmitReview提交点评/el-button /el-form /div /template script setup import { reactive } from vue import { useRoute } from vue-router import { ElMessage } from element-plus import request from ../api/request const route useRoute() const form reactive({ score: 5, content: , dishId: null }) async function submitReview() { const data { canteenId: Number(route.params.id), dishId: form.dishId, score: form.score, content: form.content } await request.post(/review/submit, data) ElMessage.success(提交成功等待管理员审核) form.content } /script组件里没有直接调用 axios 原生对象而是用request实例管理这样 token 失效、错误提示等都集中在拦截器里不用每个页面都重复写。5.5 Element Plus 表格管理页面管理员页面中用el-table展示点评数据比较方便。审核按钮只需要传递状态值template el-table :datareviewList el-table-column propuser.nickname label用户 / el-table-column propcontent label内容 / el-table-column label状态 template #default{ row } el-tag :typestatusMap[row.status].type {{ statusMap[row.status].text }} /el-tag /template /el-table-column el-table-column label操作 template #default{ row } el-button typesuccess clickaudit(row, 1)通过/el-button el-button typedanger clickaudit(row, 2)屏蔽/el-button /template /el-table-column /el-table /templateElement Plus 里的el-table-column自带前端分页不代表后端就少做了分页。毕业设计如果想要展示“大数据量处理能力”可以在后端接口借助 MyBatis-Plus 的分页插件完成分页前端只接收records和total。6. 前后端联调启动顺序、验收用例和问题排查本地运行这个项目需要先启动 MySQL再启动 Spring Boot最后启动 Vue 开发服务器。启动顺序弄错时前端页面能打开但接口全部失败多数情况不是代码逻辑问题而是后端数据库连接失败导致。6.1 启动和初始化数据在 MySQL 执行第二章的建表 SQL 和管理员账号 SQL。启动 Spring Boot 主类观察控制台端口是不是8080以及 MyBatis-Plus 是否打印 SQL 日志。在campus-canteen-web目录执行npm run dev浏览器访问http://localhost:5173。如果启动后端时报Access denied for user rootlocalhost先检查application.yml里的用户名密码与本地 MySQL 是否一致。如果报Unknown database campus_canteen说明 SQL 脚本没有执行需要先用 MySQL 客户端连接并建库。6.2 推荐验收用例不要只看页面能打开就认为项目已经完成要逐条走一遍核心业务。下面这张表可以直接对应论文里的功能测试用例。编号操作入口操作内容预期结果1登录页用admin/123456登录跳转到管理页面本地存储 token2学生注册注册新账号并登录登录成功后能进入首页3首页列表查看食堂窗口列表窗口名称、位置、平均分均能看到4窗口详情点击窗口查看菜品显示该窗口下的菜品和价格5点评提交给窗口/菜品打分并写内容提示提交成功6管理员审核在评审列表看到待审核内容点击通过后点评状态变为“已通过”7学生首页刷新首页窗口平均分平均分已按新审核点评更新8未登录访问清理 localStorage 刷新页面路由跳转到登录页9学生访问管理页手动输入/admin被路由守卫拦截10后端点“通过”管理员角色调用审核接口返回成功数据库状态字段变化其中第 7 条最容易遗漏。很多项目做了审核但忘了在审核的同时更新窗口平均分。验收时一定要前后对比分数。6.3 联调阶段的排错清单最常见的报错如下。现象可能原因检查方法解决方案Vue 页面显示跨域错误没有配置 CORS 或代理查看浏览器 Network 中请求的 URL使用 Vite proxy 指向后端登录接口 500MySQL URL 没有设置时区查看后端异常日志在serverTimezoneAsia/Shanghai接口返回 401token 没有放入请求头查看 Network 请求头检查 axios 请求拦截器中文评论在数据库乱码数据库字符集不是 utf8mb4执行SHOW CREATE TABLE t_review建库时使用 utf8mb4连接参数加入编码启动报Invalid bound statementMapper XML 路径不一致查看 target/classes 是否存在 XML调整 MyBatis mapper-locations 配置Vue Router 匹配不到页面路由写错或缺少视图文件命令行或控制台警告提示确认动态路由导入路径正确后端返回的数据前端读不到data返回体包装层数不一致手动调用后端接口看 JSON统一使用 RestResult 并在 axios 拦截器处理Spring Boot 3 和 MyBatis-Plus 冲突引入了mybatis-plus-boot-starter查看 NoClassDefFoundErrorSpring Boot 3 使用mybatis-plus-spring-boot3-starter7. 写论文和答辩前还需要补充什么校园食堂点评系统作为毕业设计技术栈准确、功能闭环、能成功演示已经是比较合格的选题。但如果想让项目更有竞争力答辩时可以从这几个角度继续往下做。7.1 数据权限和接口安全当前系统使用 JWT 做登录只实现了最基础的身份验证。可以在此基础上增加密码 BCrypt 加密、登录失败次数限制、接口限流、管理员操作日志。答辩时被问到“系统安全怎么保证”不要只说有 token而要从数据加密、权限校验、审核机制三个层面展开。7.2 评论内容的敏感词处理校园场景中的评论内容需要做基础过滤。后端可以在点评提交接口里维护一个简单的敏感词列表包含词库后命中就把该条点评打上“待人工审核”标记。再加入一层基于正则的过滤或接入第三方云内容审核服务扩展性会更好。7.3 部署上线方式开发环境是npm run dev和 Spring Boot 的本地启动。毕业答辩前可以准备一套更接近生产环境的部署方案前端执行npm run build把dist放到 Nginx 目录后端打成 jar 包用java -jar启动Nginx 通过location /api把接口请求反向代理到 Spring Boot 应用MySQL 单独跑在服务器上。下面的本地部署可以先跑通npm run buildmvn clean package java -jar target/campus-canteen-0.0.1-SNAPSHOT.jar部署方案不需要太复杂但是要在论文答辩 PPT 里画清楚三个服务之间的请求链路。面试官或导师一旦看到“开发环境能跑且知道如何部署到服务器”会认为项目完整度较高。7.4 一个可以直接沿用的答题逻辑如果被问到“这个系统里最难的模块是什么”不用硬说登录或 CRUD 很难。你可以回答“核心难点是点评审核状态和窗口平均分之间的一致性。我通过在后端审核接口中同步更新平均分并让学生端只展示 status1 的评论让业务规则落到了数据库字段和代码逻辑上。这个模型可以回滚也能通过状态标记扩展为隐藏、举报等更多场景。”这个回答既说明你理解数据库一致性又能体现业务设计思维回答结构比“我完成了增删改查”更清晰。整个项目在动手前最值得花时间的不是装饰页面而是把“用户、食堂、菜品、点评”四张表和角色权限的关系画清楚。把本文前面几节作为基础版本跑通再按自己的学科方向加入图片上传、通知公告、Excel 导出或者订单预约等模块就能形成一个完整且独特的毕业设计项目。