这类后台管理系统项目对很多同学来说核心痛点不是“能不能找到源码”而是“拿到源码后能不能在自己电脑上跑起来并且理解每一行代码是干什么的”。很多教程只给代码不讲环境、不讲配置、不讲为什么这么写结果就是跟着敲完换个环境就报错根本没法写到简历里。我花点时间把从零搭建一个 SpringBoot Vue3 前后端分离后台管理系统的完整流程以及每个环节最容易卡住的地方拆解一遍。目标很明确让你在3小时内得到一个能稳定运行、包含增删改查、可以放进简历的完整项目。整个过程我会重点讲“为什么这么做”而不是只给命令和代码。1. 先别急着敲代码把环境和思路理清楚很多人一上来就打开 IDE 开始创建项目结果卡在 Maven 依赖下载、Node.js 版本或者数据库连接上。我建议先把“战场”打扫干净准备好再动手。1.1 环境清单与版本选择避坑第一步你的电脑上需要准备好这几样东西版本很关键不匹配会导致各种奇怪错误Java JDK推荐 JDK 8 或 JDK 11。这是 SpringBoot 2.x 最兼容的版本。如果你用 SpringBoot 3.x则需要 JDK 17。为了稳定我们选JDK 8或11。检查命令java -version。Maven用于管理 Java 项目的依赖。去官网下载配置好环境变量MAVEN_HOME和Path。检查命令mvn -v。Node.jsVue3 的运行环境。不要用太老的版本推荐Node.js 16.x 或 18.x LTS。太新的版本如20可能和某些老版本脚手架工具不兼容。检查命令node -v和npm -v。IDE后端用IntelliJ IDEA Community 或 Ultimate 版前端用VS Code。IDEA 对 SpringBoot 支持最好。数据库MySQL 5.7 或 8.0。这是最常用的组合。确保 MySQL 服务已启动并记住你的 root 密码或新建一个专门用于项目的用户。注意环境变量配置是新手第一道坎。如果java或mvn命令提示“不是内部或外部命令”就是环境变量没配好。网上搜“Win/Mac 配置 Java/Maven 环境变量”跟着做这一步必须通过。1.2 项目结构与技术栈明确我们要做一个标准的前后端分离项目这意味着两个独立的工程通过 HTTP API通常是 JSON 格式通信。your-project/ ├── backend/ # SpringBoot 后端项目 │ ├── src │ ├── pom.xml │ └── ... └── frontend/ # Vue3 前端项目 ├── src ├── package.json └── ...后端技术栈SpringBoot 2.7.x快速构建后端服务的框架。MyBatis-Plus极大简化对数据库的增删改查操作不用写大量重复 SQL。MySQL关系型数据库。Spring Security 或 JWT处理用户认证和授权登录、权限。为了简化我们先做登录功能用 JWTJSON Web Token管理会话。Lombok用注解自动生成 Getter/Setter 等方法让代码更简洁。前端技术栈Vue 3使用 Composition API 写法。Vite新一代前端构建工具速度比老旧的 Vue CLI 快很多。Element Plus基于 Vue 3 的 UI 组件库做后台管理系统界面最快。Axios用于发送 HTTP 请求到后端 API。Vue Router 4管理前端页面路由。PiniaVue 3 官方推荐的状态管理库替代 Vuex。理解这个结构你就知道接下来要创建两个项目并且清楚每个项目负责什么。2. 后端 SpringBoot 项目搭建与核心配置后端是提供数据和 API 的地方。我们从创建项目到写出第一个 API。2.1 使用 Spring Initializr 快速创建项目不要手动创建 Maven 项目用官方工具最省事。打开 IDEA选择File - New - Project...。左侧选择Spring Initializr。关键配置Project SDK选择你安装的 JDK 8 或 11。Namedemo-backend(项目名)Location选择一个空文件夹比如D:\Projects\。TypeMavenLanguageJavaPackagingJarJava Version8 或 11Version选择2.7.x版本稳定点击Next进入依赖选择页面。勾选Spring Web提供 Web MVC 功能用于写 Controller。MyBatis Framework数据库 ORM 框架。MySQL DriverMySQL 数据库连接驱动。Lombok简化实体类代码。 先不选 Security我们初期简化处理点击FinishIDEA 会自动生成项目并下载依赖。第一次创建会下载很久取决于你的网速请耐心等待 Maven 下载完毕。2.2 连接数据库与配置 MyBatis-Plus项目创建好后需要配置才能连上数据库。创建数据库打开 MySQL 命令行或工具如 Navicat, MySQL Workbench执行CREATE DATABASE demo_db CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE demo_db;utf8mb4编码支持存储 Emoji 等特殊字符。配置application.ymlSpringBoot 的配置文件。在src/main/resources/下将application.properties重命名为application.ymlYAML 格式更清晰并写入server: port: 8080 # 后端服务启动端口 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/demo_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root # 改成你的数据库用户名 password: yourpassword # 改成你的数据库密码 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 在控制台打印 SQL 语句调试用 global-config: db-config: id-type: auto # 主键 ID 自增注意useSSLfalse在本地开发环境常用。如果密码有特殊字符可能需要用 URL 编码。serverTimezone设置时区避免时间错误。引入 MyBatis-Plus 依赖打开pom.xml在dependencies部分添加dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3/version !-- 使用一个稳定的版本 -- /dependency保存后IDEA 通常会提示 Maven 导入变更点击刷新图标。如果没有自动刷新在右侧 Maven 工具栏点刷新按钮。2.3 实现第一个增删改查以用户管理为例这是后端核心我们遵循Controller - Service - Mapper - Entity的分层架构。创建实体类User.java对应数据库表。package com.example.demo.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.time.LocalDateTime; Data // Lombok 注解自动生成 getter/setter/toString 等 TableName(sys_user) // 指定对应的数据库表名 public class User { TableId(type IdType.AUTO) // 主键自增 private Long id; private String username; private String password; private String email; private String phone; private LocalDateTime createTime; private LocalDateTime updateTime; }创建 Mapper 接口UserMapper.java负责数据库操作。package com.example.demo.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.demo.entity.User; import org.apache.ibatis.annotations.Mapper; Mapper // 告诉 Spring 这是一个 MyBatis Mapper public interface UserMapper extends BaseMapperUser { // 继承了 BaseMapper就已经拥有了基本的增删改查方法无需写 XML }创建 Service 层UserService.java处理业务逻辑。package com.example.demo.service; import com.baomidou.mybatisplus.extension.service.IService; import com.example.demo.entity.User; public interface UserService extends IServiceUser { // 可以在这里定义复杂的业务方法简单的增删改查直接用 IService 提供的 }实现类UserServiceImpl.javapackage com.example.demo.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.demo.entity.User; import com.example.demo.mapper.UserMapper; import com.example.demo.service.UserService; import org.springframework.stereotype.Service; Service // 声明为 Spring 的 Service 组件 public class UserServiceImpl extends ServiceImplUserMapper, User implements UserService { // 继承了 ServiceImpl也拥有了所有基础方法 }创建 ControllerUserController.java提供 RESTful API 给前端调用。package com.example.demo.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.example.demo.entity.User; import com.example.demo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; RestController // 表示这是 REST API 控制器返回 JSON 数据 RequestMapping(/user) // 所有接口路径前缀为 /user public class UserController { Autowired private UserService userService; // 1. 新增用户 PostMapping public boolean save(RequestBody User user) { // RequestBody 接收 JSON 格式的请求体 return userService.save(user); } // 2. 修改用户 PutMapping public boolean update(RequestBody User user) { return userService.updateById(user); } // 3. 根据ID删除用户 DeleteMapping(/{id}) public boolean delete(PathVariable Long id) { // PathVariable 获取路径中的 id return userService.removeById(id); } // 4. 根据ID查询单个用户 GetMapping(/{id}) public User getById(PathVariable Long id) { return userService.getById(id); } // 5. 查询所有用户简单示例实际需要分页 GetMapping(/list) public ListUser list() { return userService.list(); } // 6. 分页查询用户更实用 GetMapping(/page) public PageUser page(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize) { PageUser page new Page(pageNum, pageSize); return userService.page(page); } }创建数据库表运行项目前需要在 MySQL 中创建对应的表。可以用 SQL 文件也可以用 MyBatis-Plus 的自动建表工具需要额外依赖。这里我们手动执行 SQLUSE demo_db; CREATE TABLE sys_user ( id bigint NOT NULL AUTO_INCREMENT, username varchar(50) DEFAULT NULL COMMENT 用户名, password varchar(100) DEFAULT NULL COMMENT 密码, email varchar(100) DEFAULT NULL COMMENT 邮箱, phone varchar(20) DEFAULT NULL COMMENT 手机号, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;2.4 启动后端并测试 API找到主启动类DemoBackendApplication.java通常位于src/main/java/com/example/demo/下里面应该有一个main方法。点击旁边的绿色三角形运行。控制台看到Tomcat started on port(s): 8080和Started DemoBackendApplication就成功了。使用Postman或Apifox等 API 测试工具测试我们写的接口POSThttp://localhost:8080/userBody 选 raw/JSON输入{ username: test1, password: 123456, email: test1example.com, phone: 13800138000 }应返回true。GEThttp://localhost:8080/user/list应返回包含刚才新增用户的列表。GEThttp://localhost:8080/user/page?pageNum1pageSize10应返回分页数据。PUT,DELETE也按类似方法测试。如果测试通过恭喜你后端基础增删改查 API 已经完成了。控制台会打印出 MyBatis-Plus 执行的 SQL 语句方便调试。3. 前端 Vue3 项目搭建与界面开发后端 API 准备好了现在需要一个界面来操作它们。我们用 Vue3 Element Plus 快速搭建。3.1 使用 Vite 创建 Vue3 项目Vite 比 Vue CLI 快得多是现在的主流选择。打开终端CMD、PowerShell 或 VS Code 终端进入你准备放前端项目的目录比如D:\Projects\执行npm create vuelatest demo-frontend这是 Vue 官方的创建命令。命令行会交互式询问你配置按空格键选择/取消选择回车确认✔ Project name: … demo-frontend ✔ Add TypeScript? … No / Yes (根据喜好初学者选 No) ✔ Add JSX Support? … No / Yes (选 No) ✔ Add Vue Router for Single Page Application development? … Yes (选 Yes) ✔ Add Pinia for state management? … Yes (选 Yes) ✔ Add Vitest for Unit Testing? … No (选 No) ✔ Add an End-to-End Testing Solution? › No (选 No) ✔ Add ESLint for code quality? … Yes (选 Yes)创建完成后进入项目并安装依赖cd demo-frontend npm install安装 Element Plus 和 Axiosnpm install element-plus axios安装图标库Element Plus 组件可能需要npm install element-plus/icons-vue3.2 配置 Element Plus 和 Axios全局引入 Element Plus修改src/main.js(或main.ts) 文件import { createApp } from vue import App from ./App.vue import router from ./router import { createPinia } from pinia // 引入 Element Plus 及其样式 import ElementPlus from element-plus import element-plus/dist/index.css // 引入图标组件按需 import * as ElementPlusIconsVue from element-plus/icons-vue const app createApp(App) // 注册所有图标组件 for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component) } app.use(createPinia()) app.use(router) app.use(ElementPlus) // 使用 Element Plus app.mount(#app)配置 Axios 全局实例在src目录下新建utils/request.js文件import axios from axios // 创建一个 axios 实例配置基础 URL 和超时时间 const service axios.create({ baseURL: http://localhost:8080, // 后端 API 地址 timeout: 10000 // 请求超时时间 10秒 }) // 请求拦截器可以在发送请求前做点什么比如加 token service.interceptors.request.use( config { // 假设 token 存在 localStorage 里 const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config }, error { return Promise.reject(error) } ) // 响应拦截器可以对后端返回的数据做统一处理 service.interceptors.response.use( response { // 如果后端返回的数据结构是 { code: 200, data: ..., message: 成功 } const res response.data if (res.code 200) { return res.data // 直接返回 data 部分给业务代码 } else { // 非 200 状态码提示错误信息 ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message || Error)) } }, error { // HTTP 状态码非 2xx 的情况比如 401, 404, 500 console.log(err error) ElMessage.error(error.message || 网络错误) return Promise.reject(error) } ) export default service这个配置将后端 API 地址统一管理并处理了请求和响应的通用逻辑。3.3 开发用户管理页面对接后端 API我们将创建一个用户列表页包含查询、新增、编辑、删除功能。创建页面组件在src/views/目录下新建UserView.vue。编写模板和逻辑以下是该文件的完整内容我加了详细注释template div classuser-container !-- 搜索和操作栏 -- el-card shadownever classsearch-box el-form :inlinetrue :modelsearchForm el-form-item label用户名 el-input v-modelsearchForm.username placeholder请输入用户名 clearable / /el-form-item el-form-item el-button typeprimary clickhandleSearch查询/el-button el-button clickresetSearch重置/el-button el-button typesuccess clickhandleAdd新增用户/el-button /el-form-item /el-form /el-card !-- 用户数据表格 -- el-card shadownever stylemargin-top: 20px; el-table :datauserList border stripe stylewidth: 100% el-table-column propid labelID width80 / el-table-column propusername label用户名 / el-table-column propemail label邮箱 / el-table-column propphone label手机号 / el-table-column propcreateTime label创建时间 / el-table-column label操作 width200 template #defaultscope el-button sizesmall clickhandleEdit(scope.row)编辑/el-button el-button sizesmall typedanger clickhandleDelete(scope.row.id)删除/el-button /template /el-table-column /el-table !-- 分页组件 -- div stylemargin-top: 20px; text-align: right; el-pagination v-model:current-pagepageParams.pageNum v-model:page-sizepageParams.pageSize :page-sizes[5, 10, 20, 50] :totaltotal layouttotal, sizes, prev, pager, next, jumper size-changehandleSizeChange current-changehandleCurrentChange / /div /el-card !-- 新增/编辑用户的对话框 -- el-dialog v-modeldialogVisible :titledialogTitle width500px el-form refuserFormRef :modeluserForm :rulesrules label-width80px el-form-item label用户名 propusername el-input v-modeluserForm.username placeholder请输入用户名 / /el-form-item el-form-item label密码 proppassword v-ifdialogType add el-input v-modeluserForm.password typepassword placeholder请输入密码 / /el-form-item el-form-item label邮箱 propemail el-input v-modeluserForm.email placeholder请输入邮箱 / /el-form-item el-form-item label手机号 propphone el-input v-modeluserForm.phone placeholder请输入手机号 / /el-form-item /el-form template #footer span classdialog-footer el-button clickdialogVisible false取消/el-button el-button typeprimary clicksubmitForm确定/el-button /span /template /el-dialog /div /template script setup import { ref, reactive, onMounted } from vue import { ElMessage, ElMessageBox } from element-plus import request from /utils/request // 导入我们封装的 axios 实例 // ---------- 数据定义 ---------- // 用户列表数据 const userList ref([]) // 搜索表单 const searchForm reactive({ username: }) // 分页参数 const pageParams reactive({ pageNum: 1, pageSize: 10 }) const total ref(0) // 对话框相关 const dialogVisible ref(false) const dialogTitle ref() const dialogType ref(add) // add 或 edit const userForm reactive({ id: null, username: , password: , email: , phone: }) const userFormRef ref() // 表单引用用于校验 // 表单校验规则 const rules reactive({ username: [{ required: true, message: 请输入用户名, trigger: blur }], email: [{ required: true, message: 请输入邮箱, trigger: blur }] }) // ---------- 生命周期 ---------- onMounted(() { fetchUserList() }) // ---------- 方法 ---------- // 获取用户列表分页 const fetchUserList async () { try { // 调用后端分页接口 const params { ...pageParams, ...searchForm } const res await request.get(/user/page, { params }) userList.value res.records || res.list || [] // 适配不同后端分页返回结构 total.value res.total } catch (error) { console.error(获取用户列表失败:, error) } } // 搜索 const handleSearch () { pageParams.pageNum 1 // 搜索时回到第一页 fetchUserList() } // 重置搜索 const resetSearch () { searchForm.username handleSearch() } // 每页条数改变 const handleSizeChange (val) { pageParams.pageSize val pageParams.pageNum 1 fetchUserList() } // 当前页改变 const handleCurrentChange (val) { pageParams.pageNum val fetchUserList() } // 新增用户 - 打开对话框 const handleAdd () { dialogType.value add dialogTitle.value 新增用户 // 重置表单 Object.keys(userForm).forEach(key { userForm[key] key id ? null : }) dialogVisible.value true } // 编辑用户 - 打开对话框并填充数据 const handleEdit (row) { dialogType.value edit dialogTitle.value 编辑用户 // 将行数据复制到表单 Object.assign(userForm, row) dialogVisible.value true } // 删除用户 const handleDelete (id) { ElMessageBox.confirm(确定要删除该用户吗, 提示, { confirmButtonText: 确定, cancelButtonText: 取消, type: warning }).then(async () { try { await request.delete(/user/${id}) ElMessage.success(删除成功) fetchUserList() // 刷新列表 } catch (error) { ElMessage.error(删除失败) } }).catch(() { // 用户点击了取消 }) } // 提交表单新增/编辑 const submitForm async () { // 表单校验 if (!userFormRef.value) return await userFormRef.value.validate(async (valid) { if (valid) { try { let url /user let method post if (dialogType.value edit) { method put } // 发送请求 await request({ url, method, data: userForm }) ElMessage.success(dialogType.value add ? 新增成功 : 更新成功) dialogVisible.value false fetchUserList() // 刷新列表 } catch (error) { ElMessage.error(操作失败) } } }) } /script style scoped .search-box { margin-bottom: 20px; } /style配置路由修改src/router/index.js将我们的用户页面添加到路由中。import { createRouter, createWebHistory } from vue-router import UserView from ../views/UserView.vue // 导入组件 const router createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ // ... 其他路由 { path: /user, name: user, component: UserView } ] }) export default router在导航菜单中添加入口修改src/App.vue或你的布局组件添加一个指向/user的菜单项。例如在App.vue的模板部分简单加个链接template div idapp div router-link to/Home/router-link | router-link to/user用户管理/router-link /div router-view/ /div /template3.4 启动前端并测试完整流程在demo-frontend目录下运行npm run dev控制台会输出本地访问地址通常是http://localhost:5173。用浏览器打开它。点击“用户管理”链接进入我们开发的页面。现在可以进行测试查询点击查询按钮目前搜索功能后端还没实现但列表应该能显示。新增点击“新增用户”填写表单提交。观察浏览器开发者工具F12的“网络(Network)”标签应该能看到一个POST请求发送到http://localhost:8080/user并且返回成功。列表会自动刷新。编辑点击某行数据的“编辑”按钮修改信息后提交。应看到PUT请求。删除点击“删除”确认后应看到DELETE请求。分页改变每页条数或点击页码应看到GET请求的pageNum和pageSize参数变化。如果所有操作都成功并且数据能正确显示、修改那么前后端联调就基本成功了。4. 前后端联调、部署与简历包装要点项目能跑起来只是第一步要能作为毕设或简历项目还需要处理一些关键问题。4.1 解决跨域问题开发环境前后端分离项目前端运行在localhost:5173后端在localhost:8080浏览器会因为同源策略阻止请求这就是跨域问题。后端解决方案推荐在 SpringBoot 后端添加一个配置类。在demo-backend项目中创建config/CorsConfig.javapackage com.example.demo.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); // 允许所有域名进行跨域调用生产环境应指定具体前端地址 config.addAllowedOriginPattern(*); // 允许跨越发送cookie config.setAllowCredentials(true); // 放行全部原始头信息 config.addAllowedHeader(*); // 允许所有请求方法跨域调用GET, POST, PUT, DELETE等 config.addAllowedMethod(*); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }重启后端服务。这样前端就可以正常调用后端 API 了。注意addAllowedOriginPattern(*)在生产环境是不安全的上线时应替换为具体的前端域名如http://your-frontend-domain.com。4.2 项目打包与部署简易版后端打包在 IDEA 右侧 Maven 工具栏找到你的项目展开Lifecycle双击package。等待构建完成在target目录下会生成demo-backend-0.0.1-SNAPSHOT.jar。在服务器或本地命令行确保有 Java 环境运行java -jar demo-backend-0.0.1-SNAPSHOT.jar后端就作为一个独立服务启动了。前端打包在前端项目目录下运行npm run build构建完成后会生成dist文件夹里面是静态文件HTML, JS, CSS。你可以将这些文件放到任何静态文件服务器上如 Nginx、Apache或者 SpringBoot 的静态资源目录。最简单的方式是使用 Nginx安装 Nginx。将dist文件夹内的所有文件复制到 Nginx 的html目录下。修改 Nginx 配置nginx.conf确保 location/指向这些静态文件并配置反向代理将/api之类的请求转发到后端 Java 服务。server { listen 80; server_name localhost; location / { root html; index index.html index.htm; try_files $uri $uri/ /index.html; # 支持 Vue Router 的 history 模式 } # 代理后端 API 请求 location /api/ { proxy_pass http://localhost:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }4.3 简历与毕设包装要点一个能运行的项目只是基础要让它在简历或答辩中出彩你需要讲清楚你做了什么和为什么这么做。技术选型理由为什么用 SpringBoot 而不是传统的 SSM为什么用 Vue3 而不是 Vue2 或 React为什么用 MyBatis-Plus 而不是 JPAMyBatis-Plus 的 Lambda 查询、分页插件如何提升了开发效率项目架构图画一张简单的前后端分离架构图说明浏览器、Nginx前端、SpringBoot后端、MySQL 之间的关系和数据流向。核心功能实现重点阐述你实现的增删改查不是简单的调用而是后端如何设计 RESTful API如何统一响应格式如何做全局异常处理如何用 MyBatis-Plus 的Page对象实现分页密码如何加密存储哪怕只是 MD5前端如何封装 Axios 实现请求拦截和响应拦截如何利用 Element Plus 组件快速搭建页面如何处理表单验证如何管理页面状态遇到的问题与解决方案这是最能体现你能力的地方。跨域问题你是怎么发现并解决的配置CorsConfig前后端数据格式不对应比如日期格式、布尔值处理你是怎么解决的使用JsonFormat注解或配置全局 Jackson打包部署问题前端路由在刷新后 404Nginx 配置try_files数据库连接失败如何排查检查 MySQL 服务、用户名密码、时区设置可以扩展的功能加分项在你的项目基础上很容易扩展出这些常见功能在简历中可以作为“已实现”或“了解其原理”来写JWT 登录认证实现一个/auth/login接口返回 token前端存起来每次请求带在 Header 里。权限管理基于角色RBAC控制菜单和按钮的显示。文件上传使用 SpringBoot 的MultipartFile接收文件前端用 Element Plus 的 Upload 组件。数据导出使用 EasyExcel 库将用户列表导出为 Excel。接口文档集成Swagger或Knife4j自动生成 API 文档。最后把项目源码整理好放到 GitHub 或 Gitee 上确保 README.md 写清楚项目介绍、技术栈、如何启动。这是你能力最直观的证明。按照这个流程走下来你得到的不仅仅是一套能运行的代码而是一个理解透彻、可以自如扩展、能够应对面试官提问的完整项目。动手做一遍比看十篇教程都管用。