Spring Boot 3 + Vue 3 前后端分离项目开发实战
简介一套面向JavaWeb课程设计与毕业设计的浙江绍兴展示网站全栈源码包。项目采用Spring Boot 3 Vue Element Plus前后端分离架构涵盖美食展示、文化展示、景点展示与用户认证模块适合正在学习前后端分离开发、需要完整项目参考的高校学生。资源包共129个文件包含40个Java后端类、23个Vue前端组件、11个XML配置、5个JS脚本、4个JSON及SQL、YAML等另附项目文档与图片素材文件组织清晰导入后即可看到前后端完整代码结构。压缩包19.08MB已有97人浏览学习。内容包含完整可运行源代码、数据库脚本及项目说明文档可直接作为课程设计或毕业设计基础也可帮助读者理解Spring Boot 3与Vue3的整合方式、用户认证流程以及模块化开发思路。1. 这个 JavaWeb 大作业到底在做什么把“浙江绍兴展示网站”做成 Spring Boot 3 Vue 3 Element Plus 的前后端分离项目是近几年 JavaWeb 课程设计里最常见也最稳妥的选题方向。它覆盖了一个完整 Web 项目从数据库设计、后端接口、前端页面到用户认证的全部环节答辩时每一层都能拿出东西讲。这篇文章就是顺着这个标题把整个项目从零搭建的路径拆开先讲为何选这套技术栈再给后端和前端的关键实现最后落在部署和答辩时容易踩的细节上。适合正在做类似“基于 javaweb 的旅游管理系统”课程设计、或者想用 Spring Boot 3 Vue 3 重写旧项目的开发者。文中所有命令和代码都按可直接复制的粒度给出照着改就能跑。2. Spring Boot 3 Vue 3 Element Plus 的选型与前后端架构拆分2.1 为什么课程设计要选 Spring Boot 3 而不是 Spring Boot 2Spring Boot 3 在 2022 年底正式发布底层依赖 Spring Framework 6强制要求 JDK 17 及以上。相比 Spring Boot 2.x最直观的区别是javax.*命名空间整体迁移到jakarta.*这导致很多老教程里的 import 语句直接报错。另一个关键变化是 Spring Security 6 的配置方式从 WebSecurityConfigurerAdapter 改为基于 SecurityFilterChain 的 Bean 声明课程设计里写用户认证模块时网上搜到的老代码基本都是废弃写法需要格外注意。选择 Spring Boot 3 的理由不只是“新版更时髦”。JDK 17 带来的record类型、switch表达式、增强的var支持让实体类和配置代码明显更简洁Spring Boot 3 的 GraalVM 原生镜像支持虽然课程设计用不上但能体现你对新技术栈的跟进。更重要的是新项目直接上 Spring Boot 3 可以避免“做完就过时”的问题将来想往微服务方向扩展Spring Cloud 2023 系列已经全面兼容 Boot 3。2.2 前后端分离下的四层架构与目录规范这个项目不是单体 JSP 项目而是前端静态资源完全独立的前后端分离结构。后端只提供 RESTful API前端通过 Axios 请求 JSON 数据渲染页面部署时可以由 Nginx 托管 Vue 构建产物并把/api路径反向代理到后端服务。后端目录遵循 Spring Boot 四层架构Controller接口层、Service业务层、Mapper/Repository数据访问层、Entity实体层。课程设计里很多同学把业务逻辑全堆在 Controller 里答辩时被问“分层的作用”就答不上来。正确做法是 Controller 只做参数接收和响应封装业务判断放在 Service 接口的实现类中数据访问通过 Mapper 接口完成。建议的包结构如下com.shaoxing.demo ├── config # 安全配置、跨域配置、WebMvc 配置 ├── controller # 前端接口入口 ├── entity # 数据库实体类 ├── mapper # MyBatis/Mapper 接口 ├── service # 业务接口 │ └── impl # 业务实现 ├── common # 统一响应对象、异常处理、工具类 └── filter # JWT 认证过滤器这种按技术角色分包的方式对于单体应用仍然是最经典的规范。答辩时你只需要说清楚“请求先到 ControllerController 调 Service 接口Service 实现里调用 MapperMapper 对应 SQL 操作”整条链路就非常清晰。前端目录则按功能模块划分Vue 3 项目根目录下的src/views存放页面组件src/router管理路由src/api封装 Axios 请求src/store存放 Pinia 状态。和黑马 javaweb 笔记里传统的 HTML Ajax 写法相比Vue 的组件化让页面复用度高很多例如美食、文化、景点三个展示页结构高度相似抽成公共组件后只需维护一份。2.3 数据库设计五张表的字段、类型与关联关系数据库是这个题目的核心之一也是答辩老师必问的部分。很多同学一上来就建表结果字段冗余、关联混乱。我一般先按业务模块拆用户认证是一块美食/文化/景点展示是一块。展示模块又需要分类信息所以至少需要以下表。表名用途关键字段user登录用户id, username, password, email, avatar, role, create_timecategory内容分类美食/文化/景点id, name, description, sort_orderfood绍兴美食条目id, category_id, name, image, description, address, price, recommend_countculture绍兴文化条目id, category_id, title, content, image, source, create_timescenic绍兴景点条目id, category_id, name, opening_time, ticket_price, location, cover_image, introduction外键关系上food、culture、scenic都通过category_id关联category表这种设计支持后续扩展更多展示类型而不需要改表结构。用户表不与其他表产生外键因为浏览行为收藏、评论不在本版本范围内如果后续要加“我的收藏”再建favorites表关联user_id和target_id即可。建表时需要注意两点password字段长度至少 60 或 64因为 BCrypt 加密后的字符串长度在 60 左右varchar(20) 一定存不下所有表都建议设置create_time和update_time的默认值MyBatis 的自动填充功能可以在插入和更新时自动维护这两个字段。数据库课程设计里如果能画出一张正规的 ER 图写进项目文档会是明显的加分项。3. 后端落地从依赖、实体到 JWT 认证的完整实现3.1 pom.xml 关键依赖与 application.yml 参数配置创建 Spring Boot 3 项目时我推荐直接在 Spring Initializr 选择 Java 17 和 Spring Boot 3.x 版本生成骨架然后手动加入需要的依赖。核心依赖包括 Web、MyBatis、MySQL 驱动、Lombok、JWT 工具库和 Spring Security如果自己写 JWT 生成与校验可以不引入 Spring Security用拦截器实现课程设计完全够用也更好解释原理。dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version3.0.3/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.12.5/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-impl/artifactId version0.12.5/version scoperuntime/scope /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-jackson/artifactId version0.12.5/version scoperuntime/scope /dependency提示Spring Boot 3 中 MyBatis 官方 Starter 的坐标是mybatis-spring-boot-starter不要在旧资料里找mybatis-spring-boot-starter的 2.x 版本也不要在 Spring Boot 3 里引入spring-boot-starter-jdbc再手动配 MyBatis那样会产生冲突。application.yml里需要配置数据源、MyBatis 的 mapper 位置和 JWT 的密钥。JWT 密钥建议设置为至少 256 位32 字节的随机字符串jjwt 0.12.x 对密钥长度有强校验太短会直接抛异常。server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/shaoxing_web?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.shaoxing.demo.entity configuration: map-underscore-to-camel-case: true jwt: secret: A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0 expire-hours: 24map-underscore-to-camel-case非常关键它让数据库的snake_case字段自动映射到实体的camelCase属性create_time直接对应createTime省去大量 ResultMap 手写映射。连接 MySQL 8 时必须在 URL 里带上serverTimezone否则本地时间正常但部署到服务器后会出现时区偏差 8 小时的怪异问题。3.2 实体类与 Mapper 层的接口定义实体类使用 Lombok 的Data注解简化 getter/setter用TableNameMyBatis-Plus或 XML 里手动指定表名均可。如果不想引入 MyBatis-Plus 那套代码生成器纯 MyBatis XML 写法在课程设计中反而更被老师认可因为你能讲清楚 SQL 是手写的。Data public class Food { private Integer id; private Integer categoryId; private String name; private String image; private String description; private String address; private BigDecimal price; private Integer recommendCount; private LocalDateTime createTime; }Mapper 接口定义与 XML 分离接口中只写方法签名SQL 全部集中在resources/mapper/FoodMapper.xml中。一个常见的面试问题是“Mapper 接口和 XML 如何关联”答案就是接口全限定名与 XML 的namespace一致、方法名与 XML 中select的id一致。public interface FoodMapper { ListFood selectList(Param(keyword) String keyword, Param(categoryId) Integer categoryId, Param(offset) Integer offset, Param(limit) Integer limit); int count(Param(keyword) String keyword, Param(categoryId) Integer categoryId); Food selectById(Param(id) Integer id); }与之对应的 XML 片段select idselectList resultTypecom.shaoxing.demo.entity.Food SELECT id, category_id, name, image, description, address, price, recommend_count, create_time FROM food where if testkeyword ! null and keyword ! AND name LIKE CONCAT(%, #{keyword}, %) /if if testcategoryId ! null AND category_id #{categoryId} /if /where ORDER BY recommend_count DESC LIMIT #{offset}, #{limit} /selectwhere标签会自动去掉不必要的 AND防止 keyword 为空时生成WHERE AND xxx的语法错误。LIMIT 分页是手写的课程设计阶段不引入 PageHelper 完全没问题答辩时反而能体现你对分页原理的理解。注意recommend_count这种字段用int接收时数据库里不要让它为 NULL否则拆箱会空指针。3.3 Service 与 Controller 的请求链路和统一响应封装Service 层建议先写接口再写实现这不仅是规范也是 Spring 依赖注入的惯用方式。Controller 调 Service 接口Service 实现里做参数校验和业务拼接数据访问只通过 Mapper。为了前台页面拿到统一结构的数据定义一个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(String message) { ResultT r new Result(); r.setCode(500); r.setMessage(message); return r; } }Controller 中按 RESTful 风格暴露接口。展示模块是只读接口用户认证模块包含登录和注册。一个典型的列表接口写法RestController RequestMapping(/api/food) public class FoodController { Resource private FoodService foodService; GetMapping(/list) public ResultMapString, Object list(RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 9) Integer pageSize, RequestParam(required false) String keyword) { int offset (page - 1) * pageSize; ListFood records foodService.getPage(keyword, offset, pageSize); int total foodService.count(keyword); MapString, Object data new HashMap(); data.put(records, records); data.put(total, total); return Result.success(data); } }RequestParam(defaultValue 1)把页码默认值交给后端处理前端不用每次判断参数为空的边界。这里返回的是Map而不是自定义分页对象原因是 Vue 前端拿到 records 和 total 后配合 Element Plus 的el-pagination组件刚好一一对应。记住一个原则Controller 层不要出现任何业务逻辑判断和 SQL 拼接这样测试和排错都容易定位。3.4 用户认证模块的 JWT 实现方式与安全过滤链用户认证是本项目最有分量的功能点。常见做法是用 Spring Security JWT 的组合但完整引入 Spring Security 后配置繁琐且容易被忽略细节而题目只是“用户认证模块”因此更轻量的方案是手写 JWT 工具类 拦截器。答辩时你可以说该方案在课程设计规模下足够安全且逻辑完全可控。JWT 工具类负责生成和解析 Token使用 jjwt 0.12.x 的 API。注意这个版本和 0.9.x 的方法签名变化很大老代码在 Spring Boot 3 里编译不过。Component public class JwtUtil { Value(${jwt.secret}) private String secret; Value(${jwt.expire-hours}) private int expireHours; public String generateToken(Integer userId, String username) { return Jwts.builder() .subject(username) .claim(userId, userId) .issuedAt(new Date()) .expiration(new Date(System.currentTimeMillis() expireHours * 3600_000L)) .signWith(Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8))) .compact(); } public Claims parseToken(String token) { return Jwts.parser() .verifyWith(Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8))) .build() .parseSignedClaims(token) .getPayload(); } }siginWith方法接收 Key 对象不能直接传字符串verifyWith必须和生成时使用同一个 Key将密钥字符串通过getBytes转为字节数组是标准做法。claim(userId, userId)是把用户 ID 塞进 Token 的 payload 部分后续需要知道当前登录用户是谁时从 Claims 里取。登录接口的实现逻辑是根据用户名查出用户用 BCryptPasswordEncoder 比对密码成功后生成 Token 返回给前端。前端把 Token 存到 localStorage 或 Pinia并在每次请求的 Authorization 头里带上后端通过拦截器统一校验。public class JwtInterceptor implements HandlerInterceptor { Resource private JwtUtil jwtUtil; Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) { return true; } 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) { response.setStatus(401); response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:401,\message\:\登录已过期\}); return false; } } response.setStatus(401); response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:401,\message\:\未登录\}); return false; } }注意preHandle里的判断顺序只有请求路径匹配时才拦截放行接口比如/api/auth/login和/api/auth/register。request.setAttribute(userId, ...)把用户 ID 传递给 Controller避免了每次都要重新解析 Token。如果项目还引入了 Spring Boot Actuator 监控务必把/actuator/**排除在认证过滤之外或直接关闭敏感端点这正是“spring boot actuator 未授权访问”这类安全演练里暴露出来的问题课程设计阶段就应该养成习惯。4. 前端实现Vue 3 项目搭建与 Element Plus 页面组装4.1 Vite 创建 Vue 3 项目并配置 Element Plus 中文语言包前端环境搭建是很多人卡住的第一关核心是 Node.js 版本和 npm 源。建议 Node.js 使用 18 及以上 LTS 版本Vite 5 对 Node 版本有硬性要求低于 16 会直接启动失败。npm create vitelatest shaoxing-frontend -- --template vue cd shaoxing-frontend npm install npm install element-plus element-plus/icons-vue axios vue-router pinia提示运行npm create vite时如果响应卡住多半是 npm 镜像源问题先执行npm config set registry https://registry.npmmirror.com再创建项目。Element Plus 默认组件是英文语言直接使用日期选择器、分页等组件时会看到英文文本。在main.js中配置 ConfigProvider 包裹根组件即可切换为中文。这里处理不好就会出现“vue3 使用 element plus 的时候组件显示的是英文”这类频繁搜索的问题。import { createApp } from vue import { createPinia } from pinia import ElementPlus from element-plus import element-plus/dist/index.css import zhCn from element-plus/es/locale/lang/zh-cn import App from ./App.vue import router from ./router import ./style.css const app createApp(App) app.use(createPinia()) app.use(router) app.use(ElementPlus, { locale: zhCn }) app.mount(#app)ElementPlus是全局注册组件的方式好处是模板里直接使用el-button等组件无需逐个 import对课程设计这种体量的项目更省时间。全量引入的代价是打包体积变大但构建产物在 Nginx 上做 gzip 压缩后影响不大。如果追求性能可以改用自动按需导入但需要额外配置 unplugin-vue-components 和 unplugin-auto-import复杂度提升不少。4.2 路由配置与 Axios 封装的请求拦截、响应拦截Vue Router 4 在 Vue 3 项目中的配置方式很直观。针对游客可访问的展示页和需要登录的会员中心/后台页面通过路由守卫控制跳转。美食、文化、景点三个页面都是列表加详情结构路由定义如下import { createRouter, createWebHistory } from vue-router const routes [ { path: /, redirect: /scenic }, { path: /food, name: Food, component: () import(../views/FoodList.vue) }, { path: /culture, name: Culture, component: () import(../views/CultureList.vue) }, { path: /scenic, name: Scenic, component: () import(../views/ScenicList.vue) }, { path: /detail/:type/:id, name: Detail, component: () import(../views/DetailPage.vue) }, { path: /login, name: Login, component: () import(../views/Login.vue) }, { path: /admin, name: Admin, component: () import(../views/admin/AdminLayout.vue), meta: { requiresAuth: true } } ] router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { next(/login) } else { next() } })详情页:type/:id这种动态路由参数写法很通用:type区分 food/culture/scenic组件内部根据 type 调用不同的接口。这种方法比写三个详情页面合理得多也是 vue 路由参数最常见的应用场景。Axios 封装是每个 Vue 项目的基建核心是请求拦截器自动带上 token、响应拦截器统一处理 401。封装好了以后每个页面只需要调用api.getFoodList()不需要重复处理错误弹窗。import 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(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(token) router.push(/login) } ElMessage.error(网络异常请稍后重试) return Promise.reject(error) } )baseURL: /api是开发路径配合 Vite 的 proxy 把请求转发到http://localhost:8080。生产环境下 Nginx 同样把/api反向代理到后端服务前端代码完全不用改。响应拦截器里res.data的返回值直接是业务数据页面调用时不用再res.data.records层层取值。这种“统一入口、统一出口”的思路答辩时也值得作为亮点讲。4.3 美食展示页面与 Element Plus 组件的组合写法美食列表页是前端的样板页面涉及轮播图、卡片布局、分页和搜索。Element Plus 的el-carousel、el-card、el-pagination、el-input四个组件组合起来就构成了完整的展示页。template div classfood-page el-carousel height300px classbanner el-carousel-item v-foritem in banners :keyitem.id img :srcitem.image :altitem.name / /el-carousel-item /el-carousel div classsearch-bar el-input v-modelkeyword placeholder搜索绍兴美食 clearable keyup.enterloadFood(1) / el-button typeprimary clickloadFood(1)搜索/el-button /div div classfood-grid el-card v-forfood in foodList :keyfood.id classfood-card click$router.push(/detail/food/${food.id}) img :srcfood.image classfood-image / div classfood-name{{ food.name }}/div div classfood-price¥{{ food.price }}/div /el-card /div el-pagination v-model:current-pagepage v-model:page-sizepageSize :totaltotal :page-sizes[9, 18, 30] layouttotal, sizes, prev, pager, next current-changeloadFood(page) / /div /templatev-model:current-page是 Vue 3 中组件双向绑定的语法Element Plus 的分页组件通过这种方式把当前页码同步到page变量。keyup.enter让用户在输入框按回车即可搜索这是个经常被忽略的交互细节。卡片使用click做整卡跳转比单独放一个“查看详情”按钮更符合展示类页面的浏览习惯。列表页的样式重点在于flex布局和固定卡片宽度这部分直接写在style scoped里。文化展示页面与景点展示页面的结构高度类似唯一区别是字段渲染不同例如景点要展示开放时间opening_time、门票价格ticket_price、位置location。实际开发时可以直接复制美食页改造这也正是组件化开发带来的效率提升。4.4 登录页与管理后台的联调要点登录页的表单校验使用 Element Plus 的el-form加rules规则校验通过后调用登录接口把返回的 token 存入 localStorage随后跳转回用户之前想访问的页面。这里的逻辑很多初学者写错跳转目标应该是route.query.redirect而不是固定跳首页。后台页面使用侧边栏加顶栏的经典布局左侧菜单通过el-menu的router模式绑定路由右侧内容是router-view。后台里仅需要两个页面美食管理列表、新增、编辑、删除和用户管理查看列表、禁用用户。新增页面使用el-dialog弹窗内嵌el-form保存时调用新增接口后关闭弹窗并刷新表格不需要离开当前页面。后台表格如果需要多选删除功能会遇到 Element Plus 的selection-change事件和分页刷新后勾选丢失的问题。原因是切换页码后表格数据重新加载选中的行对象已经变成了新的对象没有勾起。这时需要给el-table加上row-key和reserve-selection选中状态才会通过唯一主键保留。el-table reftableRef :datatableData row-keyid selection-changehandleSelectionChange el-table-column typeselection reserve-selection width55 / /el-tablereserve-selection实现勾选保留后selection-change事件的参数rows数组里始终包含所有已选行即使它们不在当前页面数据中提交批量删除时拿到的是一个完整列表。这个细节在“element plus 复选框怎么保留勾选”相关问题里出现频率很高提前做进项目里能省很多麻烦。5. 部署、排错与答辩前的自检清单5.1 前后端分开部署的具体命令和 Nginx 反向代理后端打包时Spring Boot 3 的 Maven 插件默认打的包内置 Tomcat直接把 jar 上传服务器即可运行。打包前确认pom.xml里没有spring-boot-maven-plugin版本冲突然后执行mvn clean package -DskipTests java -jar target/shaoxing-demo-0.0.1-SNAPSHOT.jar --spring.profiles.activeprod前端构建命令是npm run build产物在dist目录。把dist下的文件上传到服务器的/usr/share/nginx/html然后 Nginx 配置一个 server 块server { listen 80; server_name your-domain.com; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://localhost:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }try_files指令是 Vue Router 的 history 模式部署核心不加它刷新/food/1页面会返回 404。location /api/把接口请求反向代理给后端 jar前端代码里baseURL写/api就是为了让开发环境和生产环境共用一套代码。如果服务器上有多个站点注意root路径和server_name的对应。5.2 答辩演示前 15 分钟的 5 项自检演示前最容易翻车的环节往往不是功能而是环境和数据。建议按下面的清单快速过一遍。检查项操作失败影响后端端口占用lsof -i:8080确认未被占用接口全部连接失败前端跨域打开浏览器控制台看请求是否 200页面白屏数据库编码查询美食名是否乱码展示效果大打折扣Token 过期检查系统时间是否与服务器一致登录后立即跳回登录页图片资源路径确认图片是绝对路径还是相对路径列表页图片裂开有一个容易被忽略的细节是 Element Plus 中 el-image 的引用路径。如果图片字段存的是相对路径如uploads/food1.jpg而前端部署在/下那么访问/uploads/food1.jpg时需要 Nginx 额外配置静态资源映射location /uploads/ { alias /var/www/shaoxing/uploads/; }5.3 一个提升演示体验的进阶技巧Mock 数据开关课程设计项目经常要面对“数据库里数据不完整”的尴尬。建议在 Service 层加一个Value(${app.mock-enabled})配置项当值为 true 时直接返回内置 Mock 数据不需要连接数据库。这种做法在演示环境没有 MySQL 时能救急也展示了配置外置的开发习惯。Service public class FoodServiceImpl implements FoodService { Value(${app.mock-enabled}) private boolean mockEnabled; Override public ListFood getPage(String keyword, int offset, int limit) { if (mockEnabled) { return MockData.foodList(keyword, offset, limit); } return foodMapper.selectList(keyword, null, offset, limit); } }Value注解把配置文件里的值注入到字段切换环境时只改配置文件不重新编译代码。MockData 类可以采用静态方法在内存里生成 20 条假数据字段和真实实体类一一对应。这个技巧实际价值不大但演示效果很好因为即使数据库连接断了页面依然可以完整跑通展示。本文还有配套的精品资源点击获取