这篇文章避开了网上泛滥的“TodoList”入门教程,而是以一个标准的企业级后台管理系统(Admin System)为蓝本,重点拆解登录鉴权流、MyBatis-Plus 高效CRUD、以及前后端接口规范化对接。代码可直接复用,逻辑深度对标大厂规范。

写在前面的话:

很多同学学完了 Vue3 和 Spring Boot,却卡在了“如何把它们无缝拼起来”这一步。本文将通过一个用户管理模块,带你从 0 到 1 实现一个标准的前后端分离项目。我们不搞花架子,只讲企业里天天在用的JWT鉴权、分页查询、统一响应体等核心痛点。


🏗️ 一、 技术选型与项目初始化

1. 技术栈(2026 稳如老狗版)

  • 前端:Vue 3.4+ (Composition API) + Vite + Element Plus + Axios + Pinia

  • 后端:Spring Boot 3.x + JDK 17 + MyBatis-Plus + MySQL 8.0 + Lombok

  • 工具:Knife4j (Swagger增强) + Hutool (工具集)

2. 数据库设计(User表)

CREATE TABLE `sys_user` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `username` VARCHAR(50) NOT NULL COMMENT '用户名',
  `password` VARCHAR(100) NOT NULL COMMENT '密码(加密存储)',
  `nick_name` VARCHAR(50) DEFAULT NULL COMMENT '昵称',
  `status` TINYINT DEFAULT '1' COMMENT '状态:1-正常 0-禁用',
  `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

🛠️ 二、 后端实战:MyBatis-Plus 极速开发

1. 核心依赖 (pom.xml)

<dependencies>
    <!-- Web & Test -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- MyBatis-Plus (注意是3.5.7+版本,对SpringBoot3支持最好) -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
        <version>3.5.7</version>
    </dependency>
    <!-- MySQL Driver -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>
    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <!-- JWT (jjwt) -->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-api</artifactId>
        <version>0.12.3</version>
    </dependency>
</dependencies>

2. 实体类与 Mapper(告别 XML)

Entity (SysUser.java)

@Data
@TableName("sys_user")
public class SysUser {
    @TableId(type = IdType.ASSIGN_ID) // 雪花算法生成ID
    private Long id;
    private String username;
    private String password;
    private String nickName;
    private Integer status;
    private LocalDateTime createTime;
}

Mapper (SysUserMapper.java)

@Mapper
public interface SysUserMapper extends BaseMapper<SysUser> {
    // MyBatis-Plus 提供了通用的CRUD,这里无需写任何方法
}

3. Service 层实现分页与查询

Service (UserService.java)

public interface UserService extends IService<SysUser> {
    PageResult<SysUser> queryUserPage(String username, Integer pageNum, Integer pageSize);
}

ServiceImpl (UserServiceImpl.java)

@Service
public class UserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements UserService {

    @Override
    public PageResult<SysUser> queryUserPage(String username, Integer pageNum, Integer pageSize) {
        LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
        // 动态SQL:如果用户名不为空,则拼接条件
        wrapper.like(StringUtils.isNotBlank(username), SysUser::getUsername, username)
               .orderByDesc(SysUser::getCreateTime);

        Page<SysUser> page = this.page(new Page<>(pageNum, pageSize), wrapper);
        
        return new PageResult<>(page.getTotal(), page.getRecords());
    }
}

4. 统一响应体与全局异常(规范!规范!)

统一返回类 (ApiResponse.java)

@Data
public class ApiResponse<T> {
    private int code;
    private String message;
    private T data;

    public static <T> ApiResponse<T> success(T data) {
        ApiResponse<T> response = new ApiResponse<>();
        response.setCode(200);
        response.setMessage("success");
        response.setData(data);
        return response;
    }
    // fail 方法省略...
}

Controller (UserController.java)

@RestController
@RequestMapping("/api/user")
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/page")
    public ApiResponse<PageResult<SysUser>> page(
            @RequestParam(required = false) String username,
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "10") Integer pageSize) {
        
        PageResult<SysUser> result = userService.queryUserPage(username, pageNum, pageSize);
        return ApiResponse.success(result);
    }
}

🎨 三、 前端实战:Vue3 + Element Plus

1. Axios 封装(拦截器是关键)

src/utils/request.js

import axios from 'axios';

const service = axios.create({
  baseURL: import.meta.env.VITE_APP_BASE_API, // 环境变量配置
  timeout: 5000
});

// 请求拦截器:注入 Token
service.interceptors.request.use(config => {
  const token = localStorage.getItem('TOKEN');
  if (token) {
    config.headers['Authorization'] = 'Bearer ' + token;
  }
  return config;
}, error => Promise.reject(error));

// 响应拦截器:统一处理错误
service.interceptors.response.use(response => {
  return response.data; // 直接返回 data 部分,简化调用
}, error => {
  ElMessage.error(error.response?.data?.message || '请求失败');
  return Promise.reject(error);
});

export default service;

2. 用户管理页面(核心 CRUD 组件)

src/views/user/index.vue

<template>
  <div class="app-container">
    <!-- 搜索栏 -->
    <el-form :inline="true">
      <el-form-item label="用户名">
        <el-input v-model="queryParams.username" placeholder="请输入用户名" clearable />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="getList">查询</el-button>
      </el-form-item>
    </el-form>

    <!-- 表格 -->
    <el-table :data="tableData" style="width: 100%" v-loading="loading">
      <el-table-column prop="id" label="ID" width="80" />
      <el-table-column prop="username" label="用户名" />
      <el-table-column prop="nickName" label="昵称" />
      <el-table-column prop="createTime" label="创建时间" />
      <el-table-column label="操作" width="180">
        <template #default="scope">
          <el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>
          <el-button size="small" type="danger" @click="handleDelete(scope.row.id)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>

    <!-- 分页 -->
    <div class="pagination-container">
      <el-pagination
        background
        layout="total, sizes, prev, pager, next"
        :total="total"
        v-model:current-page="queryParams.pageNum"
        v-model:page-size="queryParams.pageSize"
        @change="getList"
      />
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import { getUserPage } from '@/api/user'; // 封装的API方法

const tableData = ref([]);
const total = ref(0);
const loading = ref(false);

const queryParams = ref({
  pageNum: 1,
  pageSize: 10,
  username: ''
});

const getList = async () => {
  loading.value = true;
  try {
    const res = await getUserPage(queryParams.value);
    tableData.value = res.data.records; // MP 默认返回 records 字段
    total.value = res.data.total;
  } finally {
    loading.value = false;
  }
};

onMounted(() => {
  getList();
});
</script>

🔐 四、 前后端联调核心:登录与权限(JWT)

这是面试必问点,也是最容易出Bug的地方。

  1. 后端:登录接口验证密码后,使用 JJWT生成 Token 返回给前端。

  2. 前端:收到 Token 后存入 localStorage

  3. 路由守卫:在 Vue Router 的 beforeEach中检查是否有 Token,无则跳转登录页。

  4. 请求头:如上所述,Axios 拦截器自动将 Token 塞进 Authorization头。

  5. 后端校验:使用 Spring Security 或自定义拦截器解析 Token,获取用户ID并存入 ThreadLocal,方便后续业务逻辑使用(如获取当前操作用户ID)。


🚀 五、 2026 进阶优化点(面试加分项)

在文章末尾,你可以抛出这几个点,瞬间拉高逼格:

  1. MyBatis-Plus 逻辑删除:在实体类字段上加 @TableLogic,删除操作自动转为 Update,防止误删数据。

  2. 防抖搜索:在前端搜索框输入时,使用 lodash.debounce限制请求频率,减轻服务器压力。

  3. 后端字段脱敏:使用 MyBatis-Plus 的 TypeHandler 或 Jackson 注解,在返回给前端时自动将手机号、邮箱中间打星号(****)。

  4. 跨域配置:Spring Boot 使用 @CrossOrigin或全局配置 WebMvcConfigurer解决跨域问题,而不是让前端瞎折腾代理。


💡 总结:

这套架构是目前中小企业甚至大厂中台最常用的组合。核心在于后端提供标准 Restful API,前端负责渲染与交互。掌握了这套代码,你不仅能应对面试,直接拿到公司去改改就能上线。

如果这篇实战文档帮你理清了思路,欢迎在 CSDN 点赞、收藏、关注!我是[代码不加糖],我们下期见!

更多推荐