☰
Spring Boot + Vue后台管理系统毕设包:从环境对齐到答辩加分
2026/10/7 3:02:32 网站建设 项目流程

简介:这是一份基于Spring Boot与Ant Design Vue Pro搭建的简易后台管理系统源码包,定位为毕业设计或课程设计项目,适合计算机相关专业(计科、人工智能、通信工程、自动化、电子信息等)学生完成毕设选题、课设作业、初期立项演示,也适合具备Java与Vue基础的学习者作为前后端分离实战的完整参考。压缩包共391个文件,整体约960KB,以132个Vue页面组件、104个Java后端逻辑、83个JavaScript交互脚本为主,另有Less样式、XML配置、SQL数据库脚本与YML环境配置,可清晰对应前端页面、后端接口与数据库设计,目录结构也便于按模块定位和修改。项目代码经过实际运行验证,功能稳定,作者标注答辩评审平均分达96分,下载后可按README说明快速启动,并根据自身毕设或课设需求扩展业务模块、替换应用场景。已有58人学习/下载,适合在此基础上继续扩展后台管理功能,或用于完善权限管理、数据统计等模块,也可直接作为毕业设计答辩演示素材。

1. 拿到「基于springboot、蚂蚁设计vue pro的简易后台管理系统.zip」之后,先做这三件事

一个命名为「基于springboot,蚂蚁设计vue pro的简易后台管理系统.zip」的毕设包,解压后基本是后端一份 Spring Boot 工程、前端一份 Ant Design Vue Pro 工程,外加若干 SQL 脚本和说明文档。最反直觉的地方是:这个包越接近“能直接运行”,你越要警惕环境版本和数据库配置,因为zip里跑通过一次不代表换台电脑也能跑通。这类工程通常由脚手架生成,原作者本地的 JDK、Node、MySQL 版本和你并不一致,所以第一次打开时你真正要做的事不是写新功能,而是先让“能跑”这个目标达成。这篇笔记就是按这个顺序讲:从拆包对齐环境,到前后端联调,再到加一个演示模块,最后把最容易让答辩翻车的坑一次性排掉。

2. 从 zip 走到能跑的环境:Spring Boot 版本和前端脚手架先对齐

2.1 先把 zip 解压,并确认后端 Spring Boot 大版本

毕业设计的 zip 往往嵌套了一层文件夹,直接解压到桌面后先别双击 IDEA,先在命令行里看结构,免得 IDEA 把前端目录也当成 Maven 工程加载。

unzip -q '基于springboot和vue的简易后台管理系统.zip' -d 简易后台管理系统 cd 简易后台管理系统 ls -la find . -maxdepth 2 -name "pom.xml" -o -name "package.json"

这段命令的作用是从压缩包释放出全部文件,然后用find定位后端 Maven 配置和前端 Node 配置。正常情况下你会看到backend/、frontend/或者类似的独立目录;如果看到多个pom.xml,说明工程打包时把无关代码也带进来了,这时以路径最浅、结构最完整的那份为准。

接下来关键一步是看 Spring Boot 版本:

cd backend grep -A 3 'spring-boot-starter-parent' pom.xml

如果pom.xml里没有这个 parent,就搜dependencyManagement,同样能看到 Spring Boot 版本号。Spring Boot 2.7 和 3.x 之间不是小事,3.x 要求 JDK 17,并且把javax.*换成了jakarta.*;如果你本机只有 JDK 8,直接跑 3.x 会在编译阶段报错。反过来,Spring Boot 2.7 在 JDK 17 下虽然能编,但很多老模板里的spring-boot-starter-tomcat和 JSP 相关配置会出奇奇怪怪的兼容问题。所以先确认版本,再决定要不要换 JDK。

顺带说一个技术点:Spring Boot 默认优先使用 CGLIB 代理,而不是 JDK 动态代理。很多简易后台的 Service 没有单独抽接口,这在 Spring Boot 里能被正常代理,原因就是 CGLIB 针对类做子类化。这一点在答辩时经常被老师拿出来问,你在后续加切面日志时也会遇到它。

2.2 核对前端脚手架:Vue 2 的 Vue CLI 还是 Vue 3 的 Vite

Ant Design Vue Pro 有多个历史形态,老一批的毕设模板基于 Vue 2 + Vue CLI,依赖里会出现vue-cli-service和node-sass;新一些的模板基于 Vue 3 + Vite,依赖里会出现vite和@vitejs/plugin-vue。这两种项目的启动命令和代理配置位置完全不同。

cd frontend cat package.json

判断方法简单:scripts.dev里是vue-cli-service serve就走 Vue CLI,是vite就走 Vite。老模板里的node-sass是重灾区,Node 16 以下比较稳妥,Node 18 以上经常在npm install阶段编译失败。新模板则用 Node 16 或 18 都行,个别依赖需要 Node 16 以上的 API,所以太老反而也不行。

给你一个粗略对照表:

前端依赖特征前端脚手架推荐 Node 版本代理配置文件
vue-cli-service+node-sassVue CLI14.x 或 16.xvue.config.js
vite+@vitejs/plugin-vueVite16.x 或 18.xvite.config.js

这一步不要跳,很多同学上来就npm install,结果被node-sass的安装日志折磨一小时。先看依赖,再决定要不要用 nvm 切换 Node 版本,能省掉大半麻烦。

2.3 看懂后端和前端目录,才能知道动手改哪里

简易后台管理系统的代码量不大,但结构是固定的。后端一般长这样:

backend/src/main/java/cn/xxx/admin/ common/ 通用返回体 Result、分页对象、全局异常处理 config/ MyBatis 配置、Cors 配置、JWT 配置 controller/ REST 接口层 service/ 业务逻辑层 mapper/ MyBatis Mapper 接口 entity/ 数据库表映射对象 backend/src/main/resources/ mapper/ Mapper XML,主要是原生 SQL application.yml

前端的关键路径是:

frontend/src/ api/ 接口封装,按模块拆文件 layout/ 后台整体框架,侧边栏和顶栏 router/ 路由配置和路由守卫 store/ Vuex 或 Pinia,存 token 与用户信息 views/ 页面组件 utils/ request.js 请求封装等工具

我一般会按这个顺序去读工程:先看pom.xml和package.json,再看application.yml,然后看router/index.js,最后看store/modules/user.js。只要把这四个文件看懂,就基本能描述清楚这个系统的登录流程和权限控制方式,答辩时被问到“代码结构”也不会心虚。

3. 把前后端真正跑在一起:数据库、后端、前端三级启动

3.1 先找到 SQL 初始化脚本,建库并改数据库密码

很多 zip 里会带一个sql/或doc/目录,里面放着建表脚本和假数据。没有脚本的话,你连登录页都到不了。执行顺序是先找 SQL,再建库,最后改后端配置。

find . -iname "*.sql" | head -20 mysql -uroot -p -e "CREATE DATABASE IF NOT EXISTS admin_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" mysql -uroot -p admin_system < sql/init.sql

第一句用于确认哪些 SQL 是建表,哪些是示例数据。第二句是显式创建数据库,因为部分原作者的脚本没有CREATE DATABASE语句,直接导入会报“没有选择数据库”。第三句把表结构和初始用户导入。

接着改后端配置:

server: port: 8080 servlet: context-path: /api spring: datasource: url: jdbc:mysql://localhost:3306/admin_system?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 你自己的密码 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true jwt: secret: 随便换一串比较长的字符串 expire-hours: 24

注意几个参数:context-path: /api表示后端所有接口都挂在/api前缀下,这会影响前端代理的写法;characterEncoding=utf8是为了让中文不乱码;map-underscore-to-camel-case保证数据库里的create_time能自动映射成 Java 的createTime。如果你拿到的是 MyBatis-Plus 版本,把mybatis换成mybatis-plus即可。

3.2 配置前端代理,让前端请求打到后端

前端页面访问的接口地址一般不是http://localhost:8080,而是/api/...,这层转发由开发服务器代理完成。Vue CLI 老模板在vue.config.js里配置:

// vue.config.js const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ devServer: { port: 8000, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, pathRewrite: { '^/api': '' } } } } })

Vite 新模板的写法稍有差别,但效果一样:

// vite.config.js import { defineConfig } from 'vite' export default defineConfig({ server: { port: 8000, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, rewrite: path => path.replace(/^\/api/, '') } } } })

这里最需要想清楚:后端context-path是/api,Controller 里的@RequestMapping写的又是/login,经过 Vue 开发服务器代理后,接口请求变成了/api/login。代理把/api剥掉,再交给http://localhost:8080,此时后端 context-path 又补了一个/api,于是最终访问/api/login,链路正好闭合。

如果后端没有设置context-path,而是直接在 Controller 的@RequestMapping里写了/api,那你就不应该重写/api,直接把 target 指到后端即可。判断方式很简单:登录成功后打开浏览器 Network,看请求 URL 是不是带了两层/api/api,如果是,就说明代理和后端前缀重复了。

3.3 后端启动、前端启动,再用登录接口验证链路

cd backend mvn spring-boot:run

启动后看到Tomcat started on port(s): 8080就证明后端服务已经起来。然后新开一个终端:

cd frontend npm install npm run dev

看到类似App running at: http://localhost:8000的提示后,浏览器访问这个地址。正常的登录流程是:打开登录页,输入管理员账号密码,拿到 Token,跳转到首页。

如果登录时验证码报错,先看后端日志里有没有 Redis 连接异常。很多前后端分离的登录验证码是把图片验证码的答案暂存在 Redis 里,本机没起 Redis 服务,验证码接口就会一直报错。检查方式:

redis-cli ping

只要能回PONG,就说明 Redis 在线。如果项目里连缓存都没有,验证码可能就是纯前端生成,跟后端无关,那问题往往出在后端接口的跨域或代理配置上。到这一步,链路算真正通了,接下来才是改造功能。

4. 把它改造成能答辩的模块:从一组权限表到一个新业务页面

4.1 权限模型:为什么老师爱问三张表,而包里常常只有一张用户表

简易后台管理系统最常见的简化操作是在sys_user表里加一个role字段,比如role = 'admin',登录后根据字符串判断角色。这种设计能跑,但答辩时老师几乎必问“如果你有新角色怎么办?”所以至少要把用户-角色-菜单这条路理顺。

一组最小可用表结构如下:

CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(100) NOT NULL, status TINYINT DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE sys_role ( id BIGINT PRIMARY KEY AUTO_INCREMENT, role_code VARCHAR(50) NOT NULL, role_name VARCHAR(50) NOT NULL ); CREATE TABLE sys_user_role ( user_id BIGINT, role_id BIGINT ); CREATE TABLE sys_menu ( id BIGINT PRIMARY KEY AUTO_INCREMENT, parent_id BIGINT DEFAULT 0, name VARCHAR(100), path VARCHAR(200), permission VARCHAR(100) ); CREATE TABLE sys_role_menu ( role_id BIGINT, menu_id BIGINT );

这里的核心思想是:用户不直接拥有菜单,用户通过user_role关联到角色,角色再通过role_menu关联到菜单。查询某个用户看到的菜单,就是两条关联表 join 的事情。这样改完之后,你就能在演示时说“新增一个角色不需要改代码,只要在菜单表里配置权限”。

如果时间紧张,至少把sys_user_role加上,并把登录返回的结果从原来的字符串角色改成角色代码数组。前端根据角色数组做按钮级权限控制,老师问起来也有得说。

4.2 后端新增一个公告管理模块的最小四件套

新模块无论叫公告管理、消息管理还是校园资讯管理,后端套路是一致的。以常见的 MyBatis-Plus 工程为例,先写实体:

@Data @TableName("bus_announcement") public class Announcement { @TableId(type = IdType.AUTO) private Long id; private String title; private String content; private Integer status; private LocalDateTime createTime; }

这段代码的作用是让 ORM 知道bus_announcement表和这个 Java 对象的字段对应关系。@TableId(type = IdType.AUTO)表示主键自增,@TableName里写数据库真实表名,如果字段名和表字段完全一致,就不需要额外写@TableField。

再写 Mapper:

@Mapper public interface AnnouncementMapper extends BaseMapper<Announcement> { }

一个空接口就能拥有增删改查和分页查询能力,这是 MyBatis-Plus 提供的 BaseMapper 内置方法。注意接口上要加@Mapper注解,或者在启动类上加@MapperScan扫描包,二选一即可。

然后是 Service 和 Controller:

@Service public class AnnouncementService { @Autowired private AnnouncementMapper announcementMapper; public IPage<Announcement> pageList(int page, int limit) { Page<Announcement> p = new Page<>(page, limit); return announcementMapper.selectPage(p, new LambdaQueryWrapper<Announcement>() .orderByDesc(Announcement::getCreateTime)); } }
@RestController @RequestMapping("/announcement") public class AnnouncementController { @Autowired private AnnouncementService announcementService; @GetMapping("/page") public Result<IPage<Announcement>> page(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer limit) { return Result.ok(announcementService.pageList(page, limit)); } }

这里有两个地方值得解释:LambdaQueryWrapper可以避免拼接 SQL 字符串,orderByDesc(Announcement::getCreateTime)等价于ORDER BY create_time DESC,它用的是方法引用,编译期就能发现字段名字错误。Controller 返回的Result是后台系统里的统一返回体,一般包含code、message、data三个字段;如果你拿到的工程没有,就在common包里自己补一个,否则前端请求封装会解析不出来。

前文提过 Spring Boot 默认使用 CGLIB 代理,所以这里的AnnouncementService即使没有接口,也能被@Transactional、日志切面等机制代理。答辩时如果有人问“你的 Service 为什么没有接口”,可以从这里解释,而不是当场愣住。

4.3 前端加页面、加接口、加路由菜单

后端接口写完后,前端需要做三件事:封装 API、写页面、注册路由。先在src/api/下新建announcement.js:

import request from '@/utils/request' export function pageAnnouncement(params) { return request({ url: '/announcement/page', method: 'get', params }) }

这个文件把后端的/announcement/page接口封装成pageAnnouncement函数,页面里不需要关心 Token 怎么加、请求超时怎么处理,一切由request工具统一处理。

然后写一个列表页面:

<template> <a-card title="公告管理"> <a-table :data-source="list" :columns="columns" row-key="id" :loading="loading" /> </a-card> </template> <script setup> import { ref, onMounted } from 'vue' import { pageAnnouncement } from '@/api/announcement' const list = ref([]) const loading = ref(false) const columns = [ { title: '标题', dataIndex: 'title' }, { title: '状态', dataIndex: 'status' }, { title: '创建时间', dataIndex: 'createTime' } ] onMounted(async () => { loading.value = true try { const res = await pageAnnouncement({ page: 1, limit: 10 }) list.value = res.data.records } finally { loading.value = false } }) </script>

这段代码用了 Vue 3 的组合式 API,ref定义响应式数据,onMounted在页面加载后请求接口。Ant Design Vue Pro 的表格组件只负责接收>{ path: '/announcement', component: Layout, children: [ { path: 'index', name: 'Announcement', component: () => import('@/views/announcement/index.vue'), meta: { title: '公告管理', icon: 'notification' } } ] }

Ant Design Vue Pro 的侧边栏菜单是由路由表递归生成的,所以路由加进去,菜单就会自动出现。如果原工程用了动态路由,也就是登录后根据后端返回的菜单生成路由,那么还需要把这条路由配置加到后端菜单表里,前端路由只留一个“通配”即可。判断方法是看router/index.js里有没有addRoute关键字,有就说明是动态路由。

5. 避坑:从 Spring Boot 启动到 Vue 打包进后端的 5 个拦路坑

5.1 后端一直报Failed to configure a DataSource

现象:执行mvn spring-boot:run后,控制台输出Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.,随后应用退出。

原因:Spring Boot 自动配置机制在 classpath 里发现数据库驱动,于是触发DataSourceAutoConfiguration,但你的application.yml里没有可用的数据源连接信息。常见于 zip 包里没带application.yml,或者 yml 文件名不是application,例如原来是application-dev.yml,但启动时没有指定devprofile。

解决:先确认配置文件是否真的存在,再确认启动环境。临时做法是启动时指定环境:

mvn spring-boot:run -Dspring-boot.run.profiles=dev

如果想彻底解决,就把application-dev.yml里的数据源配置合并到application.yml,并确保spring.datasource.url、username、password三项都有值。这个报错也经常被老师拿来问“Spring Boot 自动装配原理”,你可以顺着说:启动时的自动配置类会根据条件注解判断,只要有DataSource相关的类并且没有手动排除,它就会尝试创建连接池。

5.2 前端npm install翻车:node-sass 补不上

现象:执行npm install时,窗口里出现gyp ERR! stack Error: EACCES: permission denied,或者node-sass在下载二进制文件时卡住然后失败。

原因:老式 Ant Design Vue Pro 模板用了node-sass,这是一个需要编译原生模块的包。Node 版本太高时,它找不到对应版本的预编译二进制,只能现场编译,而编译环境往往缺少 Python、C++ 编译工具或者权限不够。

解决:切换到 Node 16,再删除node_modules和package-lock.json后重新安装:

node -v nvm install 16 nvm use 16 rm -rf node_modules package-lock.json npm install

如果你在安装又卡住了,可以先设置镜像再装,不要反复 Ctrl+C,否则会产生半截的node_modules,下次执行时问题更隐蔽。这个坑属于典型的“环境问题”,不是代码逻辑问题,但每年都有大量项目在这里翻车。

5.3 登录成功后一刷新页面就回到登录页

现象:在登录页输入账号密码后能正常进入首页,但只要按 F5 刷新,马上跳回登录页;查看浏览器开发者工具,发现 localStorage 里明明有 Token。

原因:Ant Design Vue Pro 的登录状态不只存在 localStorage,还会初始化到 Vuex 或 Pinia。刷新后 store 被重建,如果store/modules/user.js里初始 Token 没有从 localStorage 读取,而是写死为空字符串,路由守卫一看“没有 Token”,就把人拦回登录页。

解决:在 store 的 state 初始化阶段读取本地缓存:

const token = localStorage.getItem('admin_token') || ''

同理,用户信息也要有一个持久化策略。最简单的做法是登录成功后把用户信息也放进 localStorage,刷新后先判断 Token,再判断用户信息为空就去调getUserInfo接口。这样既避免每次刷新都请求一次接口,也保证页面重新加载时角色权限不过期。

5.4 前端请求路径出现两层/api,后端接口 404

现象:浏览器 Network 面板里看到请求 URL 是http://localhost:8000/api/api/login,后端明明启动成功,却一直返回 404。

原因:后端context-path设成了/api,前端代理又把/api重写掉了,导致代理向后端转发的路径是对的,但 Controller 里的@RequestMapping没有包含/api,于是拼不出正确接口。更常见的是后端 Controller 写了/api/login,前端代理也保留/api,最后后端收到/api/api/login。

解决:统一前缀,只保留一层。以后端为准:如果后端 Controller 的@RequestMapping已经带/api,前端代理就不要剥掉/api;如果后端靠context-path: /api统一加前缀,前端代理才需要pathRewrite剥离一层。这个坑适合用一张小表记录,避免每次换新电脑都盲调。

后端接口实际路径前端代理 rewrite转发后效果
Controller 里有/api/login不 rewrite到后端/api/login
Controller 里是/login,context-path 是/apirewrite 去掉/api到后端/api/login

5.5 Vue 打包放进 Spring Boot 后刷新 404

现象:执行npm run build得到dist目录,把里面的文件复制到backend/src/main/resources/static,放进 Spring Boot 后访问http://localhost:8080/能打开首页,但点击某个子菜单再刷新就变成白页或 404。

原因:Vue Router 默认是 history 模式,路由路径是真实的浏览器路径,比如/system/user。Spring Boot 的静态资源处理器只认识index.html和真实存在的文件,遇到/system/user就认为没有这个资源,于是返回 404。

解决:本地演示最省事的方法是改成 hash 模式,把createWebHistory改成createWebHashHistory,刷新后不会 404,代价是路径里多一个#/。如果你想保留 history 模式,就要在后端加 forward 规则,把非静态资源路径转发到index.html:

@Controller public class ForwardController { @GetMapping(value = {"/{path:[^\\.]*}", "/login", "/dashboard", "/system/**"}) public String forward() { return "forward:/index.html"; } }

这段代码的作用是把前端路由路径转交给index.html,由 Vue Router 接管页面渲染。注意[^\\.]*是为了排除静态资源后缀,比如.js、.css等。这个坑在答辩前最后一天最容易遇到,建议提前打包测试而不是现场改。

6. 答辩前最后三招:初始化数据、接口文档、默认密码

6.1 加一份可以直接演示的初始化数据脚本

答辩演示最怕打开页面发现列表是空的,所以我会单独做一个demo_data.sql,里面准备两个角色账号和 20 条以上业务数据。执行一遍恢复环境:

mysql -uroot -p admin_system < docs/sql/demo_data.sql

这样即使现场演示把数据改乱了,也能在几分钟内恢复。写脚本时注意把时间字段设为近期时间,太旧的数据在表格排序显示时看起来不真实。

6.2 把接口文档亮出来,而不是在 IDEA 里翻代码

很多老师会问你“系统里有哪些接口、参数是什么”。与其带着老师看代码,不如直接集成接口文档。Spring Boot 2 工程常见做法是加 Knife4j 依赖,Spring Boot 3 工程则用 springdoc 的 starter。启动后浏览器打开/doc.html,所有 Controller 接口、参数说明、返回示例都在页面上,演示时非常加分。

如果没时间集成,至少给每个 Controller 的类名上加一行@Api或@Tag注解,让它能显示中文模块名。接口文档不用写得很全,把登录、用户管理、你新增的公告管理这几个核心接口写清楚就足够应付问答了。

6.3 我最后会做的一件事

每次答辩前,我都先改掉系统默认密码,并且把 JWT 密钥换掉。这看起来是小事,但老师在你的电脑上第一次打开系统时,多半会顺手点开用户表看有没有admin/123456这种裸奔账号。改掉之后,我会重新打包一次 project,压缩包命名带上日期和版本信息,避免把旧包里的数据库密码留在演示环境里。这套流程帮助我把好几个原本乱糟糟的毕设包救回来,也让“简易后台管理系统”这种看起来普通的题目,实际答辩时长多出不少可讲内容。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询