news 2026/9/3 6:11:26

SpringBoot+Vue3通用后台管理系统:从架构设计到部署实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+Vue3通用后台管理系统:从架构设计到部署实战

简介:这是一套面向Java与前端全栈开发者的通用管理系统实战项目,基于Spring Boot + MyBatis + Spring Security + MySQL后端架构与Vue 3 + Element Plus前端技术栈构建,完整覆盖权限控制、用户/角色/日志/字典/消息/配置等企业级管理功能模块,可作为快速搭建新管理系统的脚手架或教学参考案例。资源包共16671个文件,主体为4946个TypeScript、6484个JavaScript及44个Vue组件文件,辅以Java后端代码(188个.java)、配置文件(30个.properties、16个.yml)和样式资源(165个.css、138个.scss),整体压缩后大小为86.21MB。已有2035人学习下载,配套代码结构清晰、模块职责分明,包含完整前后端工程、数据库SQL脚本、登录验证码实现、全局搜索逻辑及系统配置热加载机制,开箱即用,显著降低重复开发成本。

1. 项目概述与核心价值

最近在整理过往项目代码时,翻出了一个我个人觉得非常具有代表性的“样板间”项目——一个基于 SpringBoot + MyBatis + Vue 3 + Element Plus 的通用后台管理系统。这个项目最初是我为了给团队新人做技术培训而搭建的,后来在实际工作中,它又多次作为新项目的“种子”,快速孵化出了客户关系、内容管理、内部运营等多个系统。之所以说它“通用”,是因为它剥离了具体的业务逻辑,专注于解决后台管理系统中最常见、最核心的那些问题:用户权限管理、菜单动态路由、数据表格的增删改查、表单验证与提交、文件上传等等。如果你正在寻找一个能跑起来、结构清晰、技术栈主流且可以直接作为脚手架的全栈项目参考,那么这个实例或许能给你带来不少启发。

这个技术栈组合,可以说是当前国内中后台系统开发的“黄金搭档”。SpringBoot 提供了高效、约定大于配置的后端服务;MyBatis 作为持久层框架,在灵活性和直观性上取得了很好的平衡;Vue 3 的 Composition API 带来了更优雅的逻辑复用方式;Element Plus 则提供了丰富且美观的 UI 组件库。将它们组合在一起,不仅能构建出功能强大的应用,其代码组织和开发体验也相当舒适。接下来,我将从整体设计到代码细节,为你完整拆解这个项目的构建思路与实现要点。

2. 技术栈选型与项目整体架构解析

2.1 为什么是这套技术组合?

在启动一个全栈项目前,技术选型是首要决策。我选择这套组合并非追逐潮流,而是基于多年的项目实战经验,权衡了开发效率、维护成本、团队技能栈和社区生态后的结果。

后端:SpringBoot + MyBatis

  • SpringBoot:它的核心优势在于“开箱即用”和“自动配置”。我们不再需要繁琐地配置 XML 文件来整合 Spring MVC、数据源、事务管理等。一个@SpringBootApplication注解和一份简洁的application.yml配置文件就能启动一个具备完整 Web 功能的项目。这对于快速搭建原型和保持项目结构简洁至关重要。
  • MyBatis:相比 JPA 的完全面向对象,MyBatis 允许开发者直接编写 SQL,这对于复杂查询、性能优化以及需要深度介入数据库操作的场景提供了极大的灵活性。同时,它的注解和 XML 配置两种方式可以混合使用,简单查询用@Select注解,复杂动态 SQL 用 XML,兼顾了开发效率和表达能力。

前端:Vue 3 + Element Plus + Vite

  • Vue 3:其 Composition API 是颠覆性的改进。它将逻辑关注点(如某个功能相关的数据、计算属性、方法)聚合在一起,而不是分散在datamethodscomputed等选项中。这使得在大型项目中,代码的组织和复用变得异常清晰。<script setup>语法糖更是极大地简化了组件的编写。
  • Element Plus:作为 Vue 3 的配套 UI 库,它提供了后台管理系统所需的几乎全部组件,如布局容器、表单、表格、弹窗、导航菜单等。其设计风格统一,文档详尽,社区活跃,能极大提升前端界面的搭建速度。
  • Vite:作为新一代前端构建工具,其基于原生 ES 模块的快速冷启动和按需编译,带来了远超 Webpack 的开发体验。在修改代码后,热更新几乎是瞬间完成的。

前后端交互:RESTful API + Axios采用经典的 RESTful 风格设计 API 接口,使用 Axios 作为 HTTP 客户端。为了统一处理请求和响应,我们通常会对 Axios 进行二次封装,集成请求拦截器(如自动添加 Token)、响应拦截器(如统一处理错误消息)等功能。

2.2 项目目录结构设计

一个清晰、可扩展的目录结构是项目可维护性的基石。以下是这个通用管理系统的核心目录规划:

后端 (SpringBoot) 结构:

src/main/java/com/example/ ├── config/ # 配置类(如Web配置、MyBatis配置、跨域配置) ├── controller/ # 控制器层,接收请求,调用服务 ├── service/ # 业务逻辑层接口 │ └── impl/ # 业务逻辑层实现 ├── mapper/ # MyBatis Mapper接口层 ├── entity/ # 实体类,与数据库表对应 ├── dto/ # 数据传输对象,用于前后端交互 ├── vo/ # 视图对象,用于返回给前端的数据封装 ├── common/ # 通用工具类、常量、异常定义 │ ├── constant/ │ ├── utils/ │ └── exception/ └── Application.java # 主启动类

关键点:严格遵循分层架构(Controller -> Service -> Mapper),确保职责单一。dto用于接收前端参数(如创建用户的表单),vo用于返回给前端的数据(通常比entity更精简或包含计算字段)。

前端 (Vue 3 + Vite) 结构:

src/ ├── api/ # 所有API请求模块,按功能划分文件 ├── assets/ # 静态资源(图片、字体等) ├── components/ # 公共组件(如SearchBar, Pagination) ├── composables/ # Vue 3组合式函数(自定义hooks) ├── layout/ # 布局组件(如侧边栏+头部的主布局) ├── router/ # 路由配置,包含静态和动态路由 ├── stores/ # 状态管理(Pinia),如用户信息、权限 ├── utils/ # 工具函数(如日期格式化、请求封装) ├── views/ # 页面视图组件 │ ├── login/ # 登录页 │ ├── dashboard/ # 首页 │ └── system/ # 系统管理相关页面 │ ├── user/ # 用户管理 │ └── role/ # 角色管理 ├── App.vue └── main.js

关键点api目录集中管理所有后端接口调用,便于维护和复用。composables是 Vue 3 的特色,用于抽离可复用的业务逻辑。使用 Pinia 进行状态管理,比 Vuex 更简洁直观。

3. 后端核心模块实现详解

3.1 数据持久层:MyBatis 的灵活运用

MyBatis 的使用是这个项目数据访问层的核心。我们采用“注解 + XML”的混合模式。

1. 实体类与 Mapper 接口:首先定义User实体类,并使用 Lombok 简化代码。

// entity/User.java @Data @TableName("sys_user") // 指定表名,这里使用了MyBatis-Plus的注解 public class User { @TableId(type = IdType.AUTO) // 主键自增 private Long id; private String username; private String password; private String nickname; private String email; private String status; // 状态:启用、禁用 @TableField(fill = FieldFill.INSERT) // 自动填充创建时间 private LocalDateTime createTime; }

对应的 Mapper 接口:

// mapper/UserMapper.java @Mapper public interface UserMapper extends BaseMapper<User> { // 简单的条件查询可以直接用注解 @Select("SELECT * FROM sys_user WHERE status = #{status}") List<User> selectUsersByStatus(@Param("status") String status); // 复杂的多表关联分页查询,使用XML映射 Page<UserVO> selectUserPage(Page<UserVO> page, @Param("queryDTO") UserQueryDTO queryDTO); }

注意:这里引入了MyBatis-Plus,它是一个强大的 MyBatis 增强工具。BaseMapper提供了通用的 CRUD 方法,无需编写 XML。但对于复杂业务查询,我们依然保留自定义 XML 的能力。

2. 动态 SQL 编写(XML方式):对于上面selectUserPage方法对应的UserMapper.xml

<!-- mapper/UserMapper.xml --> <select id="selectUserPage" resultType="com.example.vo.UserVO"> SELECT u.*, r.name as roleName FROM sys_user u LEFT JOIN sys_user_role ur ON u.id = ur.user_id LEFT JOIN sys_role r ON ur.role_id = r.id <where> <if test="queryDTO.username != null and queryDTO.username != ''"> AND u.username LIKE CONCAT('%', #{queryDTO.username}, '%') </if> <if test="queryDTO.nickname != null and queryDTO.nickname != ''"> AND u.nickname LIKE CONCAT('%', #{queryDTO.nickname}, '%') </if> <if test="queryDTO.roleId != null"> AND r.id = #{queryDTO.roleId} </if> </where> ORDER BY u.create_time DESC </select>

实操心得<where>标签会自动处理AND前缀,避免 SQL 语法错误。动态 SQL 是 MyBatis 的精华,它能优雅地构建复杂的查询条件。

3.2 业务逻辑层:Service 的设计与事务控制

Service 层承载核心业务逻辑。我们遵循面向接口编程的原则。

// service/UserService.java public interface UserService { PageResult<UserVO> getUserPage(UserQueryDTO queryDTO); UserVO getUserById(Long id); void addUser(UserAddDTO userAddDTO); void updateUser(UserUpdateDTO userUpdateDTO); void deleteUser(Long id); } // service/impl/UserServiceImpl.java @Service @Slf4j public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Autowired private PasswordEncoder passwordEncoder; // Spring Security 的密码编码器 @Override @Transactional(rollbackFor = Exception.class) // 声明式事务 public void addUser(UserAddDTO userAddDTO) { // 1. 校验用户名是否已存在 LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(User::getUsername, userAddDTO.getUsername()); if (userMapper.selectOne(queryWrapper) != null) { throw new BusinessException("用户名已存在"); } // 2. DTO 转 Entity,并对密码进行加密 User user = new User(); BeanUtils.copyProperties(userAddDTO, user); user.setPassword(passwordEncoder.encode(userAddDTO.getPassword())); user.setStatus("ENABLED"); // 3. 插入用户 if (userMapper.insert(user) != 1) { throw new BusinessException("新增用户失败"); } // 4. 处理用户-角色关联(如果有) if (userAddDTO.getRoleIds() != null && !userAddDTO.getRoleIds().isEmpty()) { // ... 批量插入 sys_user_role 表 } log.info("新增用户成功,用户名:{}", user.getUsername()); } @Override public PageResult<UserVO> getUserPage(UserQueryDTO queryDTO) { // 使用MyBatis-Plus的分页插件 Page<UserVO> page = new Page<>(queryDTO.getPageNum(), queryDTO.getPageSize()); Page<UserVO> resultPage = userMapper.selectUserPage(page, queryDTO); return new PageResult<>(resultPage.getRecords(), resultPage.getTotal()); } // ... 其他方法实现 }

关键点解析

  1. 事务管理@Transactional注解确保addUser方法中校验、插入用户、插入角色关联等操作在一个事务内,要么全部成功,要么全部回滚。
  2. 密码安全绝对不要明文存储密码!使用BCryptPasswordEncoder等强哈希算法进行加密。
  3. 异常处理:自定义BusinessException等异常,在全局异常处理器(@ControllerAdvice)中统一捕获并返回给前端友好的错误信息。
  4. 分页封装PageResult是一个自定义的通用分页响应类,包含数据列表List<T>和总条数Long,方便前端渲染分页组件。

3.3 控制层:RESTful API 设计与全局响应封装

Controller 层应保持“瘦”,它只负责接收参数、调用 Service、返回结果。

// controller/UserController.java @RestController @RequestMapping("/api/user") @Slf4j public class UserController { @Autowired private UserService userService; @GetMapping("/page") public Result<PageResult<UserVO>> getUserPage(UserQueryDTO queryDTO) { PageResult<UserVO> pageResult = userService.getUserPage(queryDTO); return Result.success(pageResult); } @GetMapping("/{id}") public Result<UserVO> getUserById(@PathVariable Long id) { UserVO userVO = userService.getUserById(id); return Result.success(userVO); } @PostMapping public Result<Void> addUser(@Valid @RequestBody UserAddDTO userAddDTO) { userService.addUser(userAddDTO); return Result.success(); } @PutMapping public Result<Void> updateUser(@Valid @RequestBody UserUpdateDTO userUpdateDTO) { userService.updateUser(userUpdateDTO); return Result.success(); } @DeleteMapping("/{id}") public Result<Void> deleteUser(@PathVariable Long id) { userService.deleteUser(id); return Result.success(); } }

核心设计

  1. 统一的Result封装:所有接口都返回Result<T>对象,包含code(状态码)、message(提示信息)、data(数据)和timestamp(时间戳)。这为前端提供了统一的响应格式。
    @Data public class Result<T> { private Integer code; private String message; private T data; private Long timestamp = System.currentTimeMillis(); // 成功/失败的静态工厂方法 public static <T> Result<T> success(T data) { ... } public static <T> Result<T> success() { ... } public static <T> Result<T> error(String message) { ... } }
  2. 参数校验:使用@Valid注解配合 JSR-303 校验注解(如@NotBlank@Email@Size)在 DTO 字段上,可以自动完成参数校验,无效的请求会在进入 Service 前被拦截并返回错误。
  3. 清晰的 URL 设计:遵循 RESTful 风格,GET /api/user/page(分页查询),POST /api/user(新增),PUT /api/user(更新),DELETE /api/user/{id}(删除)。

4. 前端核心模块实现详解

4.1 前端工程初始化与请求封装

使用 Vite 初始化 Vue 3 项目后,第一件重要的事是封装 Axios。

// utils/request.js import axios from 'axios'; import { ElMessage } from 'element-plus'; import router from '@/router'; import { useUserStore } from '@/stores/user'; const service = axios.create({ baseURL: import.meta.env.VITE_APP_BASE_API, // 从 .env 文件读取 timeout: 10000, }); // 请求拦截器 service.interceptors.request.use( (config) => { const userStore = useUserStore(); if (userStore.token) { config.headers['Authorization'] = `Bearer ${userStore.token}`; } return config; }, (error) => { return Promise.reject(error); } ); // 响应拦截器 service.interceptors.response.use( (response) => { const res = response.data; // 假设后端统一返回的 code: 200 成功,其他为异常 if (res.code === 200) { return res.data; // 直接返回业务数据 } else { // 业务逻辑错误(如用户名已存在) ElMessage.error(res.message || 'Error'); return Promise.reject(new Error(res.message || 'Error')); } }, (error) => { // HTTP 状态码错误(如 401, 403, 500) if (error.response?.status === 401) { ElMessage.error('登录已过期,请重新登录'); // 清空用户信息,跳转到登录页 const userStore = useUserStore(); userStore.logout(); router.push('/login'); } else if (error.response?.status === 403) { ElMessage.error('没有权限访问'); } else { ElMessage.error(error.message || '网络请求失败'); } return Promise.reject(error); } ); export default service;

封装价值:通过拦截器,我们实现了 Token 的自动携带、响应的统一处理、错误的全局提示以及未登录的自动跳转。之后在api目录下的所有模块,都引入这个service实例进行请求。

4.2 动态路由与权限管理实现

这是后台管理系统的灵魂。核心思路是:用户登录后,后端根据其角色返回有权限的菜单列表;前端根据这个列表,动态生成路由并添加到 Router 中。

1. 路由定义:我们将路由分为两类:constantRoutes(静态路由,如登录页、404页)和asyncRoutes(动态路由,根据权限添加)。

// router/index.js import { createRouter, createWebHistory } from 'vue-router'; import Layout from '@/layout/index.vue'; // 静态路由 export const constantRoutes = [ { path: '/login', component: () => import('@/views/login/index.vue'), hidden: true, // 不在侧边栏显示 }, { path: '/', component: Layout, redirect: '/dashboard', children: [ { path: 'dashboard', component: () => import('@/views/dashboard/index.vue'), name: 'Dashboard', meta: { title: '首页', icon: 'home' } } ] }, // 404 page must be placed at the end !!! { path: '/:pathMatch(.*)*', redirect: '/404', hidden: true } ]; // 动态路由(示例结构,实际从后端获取) export const asyncRoutes = [ { path: '/system', component: Layout, redirect: '/system/user', meta: { title: '系统管理', icon: 'setting' }, children: [ { path: 'user', component: () => import('@/views/system/user/index.vue'), name: 'User', meta: { title: '用户管理', icon: 'user' } }, { path: 'role', component: () => import('@/views/system/role/index.vue'), name: 'Role', meta: { title: '角色管理', icon: 'lock' } } ] } ]; const router = createRouter({ history: createWebHistory(), routes: constantRoutes }); export default router;

2. 权限控制逻辑(在登录后或应用初始化时调用):

// stores/permission.js (使用Pinia) import { defineStore } from 'pinia'; import router, { asyncRoutes } from '@/router'; import { getMenuList } from '@/api/user'; // 假设的获取菜单API export const usePermissionStore = defineStore('permission', { state: () => ({ routes: [], // 最终动态添加的路由 addRoutes: [] // 动态添加的路由对象 }), actions: { // 生成路由 generateRoutes(roles) { return new Promise((resolve, reject) => { // 1. 根据角色从后端获取菜单列表(这里简化,直接过滤本地asyncRoutes) // 实际项目中,这里应该调用 getMenuList() API getMenuList().then(menuList => { // 2. 将后端返回的菜单列表转换为路由组件 const accessedRoutes = filterAsyncRoutes(asyncRoutes, menuList); // 3. 动态添加到路由器 accessedRoutes.forEach(route => { router.addRoute(route); // 关键API:动态添加路由 }); this.addRoutes = accessedRoutes; this.routes = constantRoutes.concat(accessedRoutes); resolve(accessedRoutes); }).catch(error => { reject(error); }); }); } } }); // 一个简单的路由过滤函数(实际应根据后端返回的菜单权限标识进行过滤) function filterAsyncRoutes(routes, menuList) { const res = []; routes.forEach(route => { // 这里需要根据 menuList 中的权限标识判断 route.meta.permission // 假设 menuList 包含有权限的 route.name if (hasPermission(menuList, route)) { if (route.children) { route.children = filterAsyncRoutes(route.children, menuList); } res.push(route); } }); return res; }

核心难点router.addRoute()是 Vue Router 4 提供的动态添加路由的方法。关键在于,在用户登录成功后,需要先获取权限菜单,然后调用generateRoutes生成并添加路由,最后再跳转到首页。同时,侧边栏菜单组件需要根据permissionStore.routes来渲染。

4.3 基于 Element Plus 的通用表格与表单组件实践

利用 Vue 3 的组合式 API 和 Element Plus,我们可以封装高度复用的表格和表单组件。

1. 通用表格组件封装思路:创建一个ProTable组件,它接收以下 props:

  • columns:列配置数组,定义表头、数据键、宽度、格式化函数等。
  • data:表格数据(或一个获取数据的函数)。
  • pagination:分页配置。 它内部集成:
  • 搜索栏(可根据columns自动生成或自定义)。
  • 操作按钮栏(新增、删除、导出等)。
  • 分页组件。
  • 加载状态。

在页面中使用:

<!-- views/system/user/index.vue --> <template> <ProTable :columns="columns" :data="fetchTableData" :pagination="pagination" @selection-change="handleSelectionChange" > <template #toolbar> <el-button type="primary" @click="handleAdd">新增用户</el-button> </template> <template #action="{ row }"> <el-button link @click="handleEdit(row)">编辑</el-button> <el-button link type="danger" @click="handleDelete(row)">删除</el-button> </template> </ProTable> </template> <script setup> import { ref } from 'vue'; import ProTable from '@/components/ProTable/index.vue'; import { getUserPage, deleteUser } from '@/api/user'; const columns = ref([ { prop: 'username', label: '用户名', width: 120 }, { prop: 'nickname', label: '昵称' }, { prop: 'email', label: '邮箱' }, { prop: 'status', label: '状态', formatter: (row) => row.status === 'ENABLED' ? '启用' : '禁用' }, { prop: 'createTime', label: '创建时间', width: 180, formatter: formatDateTime } ]); const fetchTableData = async (params) => { // params 包含分页和查询条件 const res = await getUserPage(params); return { list: res.list, total: res.total }; }; // ... 其他方法 </script>

2. 通用表单弹窗封装:创建一个FormDialog组件,它接收一个表单配置formConfig(包含表单项规则、初始值等)和一个提交函数。内部使用 Element Plus 的ElFormElDialog。在父组件中通过ref控制其显示隐藏。

实操心得:这种封装将重复的 UI 和逻辑(如加载、分页、校验)抽离,使页面组件只需关注业务数据和方法,极大提升了开发效率和代码一致性。但要注意封装的“度”,避免过度抽象导致组件过于复杂难以维护。

5. 前后端联调与部署注意事项

5.1 开发环境联调:解决跨域问题

前后端分离开发,跨域(CORS)是第一个拦路虎。在后端解决是最推荐的方式。

// config/WebMvcConfig.java @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") // 对所有接口 .allowedOriginPatterns("*") // 允许所有源,生产环境应指定具体域名 .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }

注意:生产环境下,allowedOriginPatterns应替换为具体的前端部署域名(如https://admin.yourdomain.com),而不是*,以增强安全性。

5.2 生产环境部署

后端部署:

  1. 打包:使用mvn clean package -DskipTests生成可执行的 JAR 文件(内嵌 Tomcat)。
  2. 运行:在服务器上使用nohup java -jar your-app.jar --spring.profiles.active=prod > app.log 2>&1 &命令后台运行。--spring.profiles.active=prod会激活application-prod.yml配置文件,用于配置生产环境的数据库、Redis 等。
  3. 使用 Nginx 反向代理:不建议直接暴露 8080 端口。通过 Nginx 将域名请求代理到后端应用。
    server { listen 80; server_name api.yourdomain.com; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

前端部署:

  1. 构建:运行npm run build,生成静态文件在dist目录。
  2. 部署:将dist目录下的所有文件上传到 Web 服务器(如 Nginx)的指定目录。
  3. Nginx 配置
    server { listen 80; server_name admin.yourdomain.com; root /path/to/your/dist; index index.html; # 处理前端路由的 History 模式 location / { try_files $uri $uri/ /index.html; } # 反向代理 API 请求到后端 location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }
    try_files指令对于 Vue Router 的history模式至关重要,它确保直接访问非根路径时也能返回index.html,由前端路由接管。

5.3 常见问题与排查技巧实录

  1. 前端路由刷新后 404

    • 问题:在非首页刷新浏览器,页面显示 404。
    • 原因:Vue Router 使用history模式时,路径(如/system/user)对于静态文件服务器是一个未知的路径。
    • 解决:确保 Web 服务器(如 Nginx)正确配置了try_files(见上文),将所有非静态文件请求重定向到index.html
  2. MyBatis 查询结果映射失败

    • 问题:查询返回null或字段值丢失。
    • 排查
      • 检查实体类字段名与数据库列名是否一致(注意驼峰和下划线的自动映射,可在配置中开启map-underscore-to-camel-case: true)。
      • 检查 XML 中resultMapresultType配置是否正确。
      • 打开 MyBatis SQL 日志,查看实际执行的 SQL 和参数,复制到数据库客户端执行,看是否能查出数据。
      # application.yml mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL日志
  3. Element Plus 表格/表单性能问题

    • 问题:数据量大时表格渲染卡顿,表单字段多时响应慢。
    • 优化
      • 表格:使用el-tablevirtual-scroll(虚拟滚动)功能处理超大数据。确保columns是稳定的引用,避免不必要的重新渲染。
      • 表单:复杂的动态表单,考虑将表单拆分为多个子组件,并使用v-show而非v-if进行切换,减少组件销毁/创建的开销。
  4. SpringBoot 应用启动端口被占用

    • 问题Web server failed to start. Port 8080 was already in use.
    • 解决
      • 查找占用端口的进程:lsof -i:8080(Linux/Mac) 或netstat -ano | findstr :8080(Windows)。
      • 终止该进程,或修改application.yml中的server.port为其他端口。

这个通用管理系统实例,就像一套精心打磨的“乐高”积木,提供了构建现代后台应用所需的核心模块和最佳实践。从后端的数据流转、业务逻辑、API设计,到前端的工程化、状态管理、权限控制和组件封装,每一个环节都蕴含着在实际项目中踩坑后总结出的经验。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/3 6:11:12

三星为英伟达定制8Hi HBM:17~18Gbps高速内存解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/3 6:10:22

R语言绘制Cox回归森林图:单因素/多因素分析与双置信区间可视化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/3 6:09:47

基于SpringBoot的家居用品商城系统设计与实现(源码+讲解视频+LW)

联系博主 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 …

作者头像 李华
网站建设 2026/9/3 6:08:41

基于Python的手写数学公式识别系统:从图像处理到LaTeX生成全流程详解

简介&#xff1a;本资源是一套面向本科高年级学生与教育技术开发者的手写数学公式智能识别系统实现方案&#xff0c;聚焦深度学习与计算机视觉在教育数字化场景中的落地应用。系统支持手写公式图像采集、符号识别、语法树构建及LaTeX表达式生成&#xff0c;有效解决学术写作、在…

作者头像 李华
网站建设 2026/9/3 6:05:40

电竞比赛技术分析框架:从操作拆解到团队决策的理性复盘方法

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/3 6:05:20

C#硬件集成实战:从德卡T10读卡器DLL调用到通用设备控制

简介&#xff1a;本资源为德卡T10身份证读卡器的C#开发实战源码包&#xff0c;面向Windows平台软硬件集成开发者、政务/医疗系统二次开发工程师及智能卡应用学习者&#xff0c;解决身份证、社保卡、就诊卡等ISO 14443-A类卡片的快速接入与数据解析难题。压缩包为RAR格式&#x…

作者头像 李华