Java 速成:从零搭建 SpringBoot + Vue 前后端项目

本教程带你走通一遍最简单的前后端项目:后端 SpringBoot 提供几个接口,前端 Vue3 写一个页面调用接口、展示和操作数据。

我们只走一条主线,不做任何分支。中间出现的每个注解、每个技术名词,都会附上 菜鸟教程 的超链接,点进去自学即可。


📖 怎么用这份教程

  1. 跟着步骤一步步做,代码直接复制即可跑通。
  2. 看到 🔗 自学链接 的地方,点超链接去菜鸟教程看那个注解/知识点的详细用法和其他写法。
  3. 看到 🌿 分支路线 的地方,是告诉你要深入某个方向时该看什么,本教程不展开。

最终效果:一个「用户管理」页面,能查看列表 / 新增 / 删除用户,数据存在后端内存里。

浏览器 (Vue 页面, 端口 5173)
   │  axios 请求
   ▼
SpringBoot 后端 (端口 8080)
   │  内存里的 ArrayList
   ▼
3 条初始用户数据

📁 配套项目对照

本教程带了 4 个配套项目(都在教程同目录下),和章节一一对应,可直接用 IDEA / VS Code 打开对照学习。全部已托管在 Gitee:https://gitee.com/lxqcl/java-guide,可在线浏览或 git clone

配套项目 对应章节 说明
demo/ 第一部分 1.1–1.5 基础版:数据存内存,逻辑全在 Controller
demo-layered/ 1.6 分层架构 分层版:拆成 Controller + Service,数据仍存内存
demo-db/ 1.6 分包 + 1.7 数据库 完整版:按 controller/service/mapper/entity 分包 + 连 MySQL
vue-frontend/ 第二部分 2.1–2.5 Vue3 前端页面,调用上面任一后端

💡 三个后端接口完全一致(路径、返回数据都一样),配同一个 vue-frontend 切换无感;端口都是 8080,同时只能开一个。


第 0 步:环境准备

开工前请确认装好下面这些东西(点链接去菜鸟教程看安装方法):

工具 版本建议 自学链接
JDK 8 或以上(本教程示例基于 JDK 8 + SpringBoot 2.7,兼容多数存量环境) Java 教程(含环境安装)
Maven 3.6+ Maven 环境配置
Node.js 18+ 安装后自带 npm,Vue 需要它
IDEA 免费30天,长期使用需自行“想办法”。 后端开发用
VS Code 任意版本 前端开发用(也可用 IDEA)

💡 验证环境:分别执行 java -versionmvn -versionnode -vnpm -v,都能打印版本号就说明装好了。


第一部分:后端 SpringBoot

1.1 创建 SpringBoot 项目

⚠️ 重要提示:官方的 start.spring.io 已经不再支持 Java 8 和 Spring Boot 2.x(它现在只给 Spring Boot 3.x,要求 Java 17+)。本教程用 JDK 8 + SpringBoot 2.7,所以不能用官方那个网址。下面三种方式任选一种。

方式 A:阿里云镜像 start.aliyun.com(推荐)

国内可用的替代生成器,支持 Java 8 和 Spring Boot 2.7.x:

  1. 打开 https://start.aliyun.com
  2. 按下图选择:
    • Project:Maven
    • Language:Java
    • Spring Boot:选 2.7.x(务必选 2.7 系列,才支持 Java 8)
    • Groupcom.example
    • Artifactdemo
    • Java:8
  3. 右边 Add Dependencies → 搜索并添加 Spring Web(即 spring-boot-starter-web
  4. 点底部生成按钮,下载 zip
  5. 解压后用 IDEA 打开这个文件夹

方式 B:IDEA 内置创建(把地址换成阿里云)

IDEA 的 Spring Initializr 默认也连官方地址,要先手动换掉:

  1. File → New → Project → Spring Initializr
  2. Server URLhttps://start.spring.io 改成 https://start.aliyun.com
  3. 之后 Java 选 8、Spring Boot 选 2.7.x,依赖勾选 Spring Web

方式 C:手动创建 Maven 项目(不依赖任何网站)

如果上面两种都不方便,直接手动建,把 pom.xml 内容全部替换成下面这段即可:

  1. IDEA 里 File → New → Project → 选 Maven(不勾任何 archetype 模板)
  2. groupIdcom.exampleartifactIddemo
  3. 用下面这段覆盖项目自带的 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.18</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
  1. 然后按 1.3、1.4 节手动建好 User.javaUserController.java 等文件。

💡 懒人方案:其实本教程配套的 demo/ 文件夹就是完整后端,已经建好并验证可运行,你可以直接克隆 java-guide 仓库 后用 IDEA 打开对照学习,不必自己从头创建。

🔗 自学链接:想了解 SpringBoot 是什么、为什么能「开箱即用」?看 Java SpringBoot 框架 - 菜鸟教程


1.2 认识项目结构

用 IDEA 打开项目后,结构长这样:

demo/
├── pom.xml                 ← Maven 依赖配置文件
└── src/
    └── main/
        ├── java/
        │   └── com/example/demo/
        │       └── DemoApplication.java   ← 启动类(入口)
        └── resources/
            └── application.properties     ← 配置文件(端口等)

📄 pom.xml —— Maven 的「依赖清单」

我们创建时勾选了 Spring Web,所以 pom.xml 里会自动有这两个核心依赖:

🔗 自学链接:搞不懂 pom.xml 里的 <dependencies><groupId>?看 Maven POM - 菜鸟教程Maven 简介

📄 DemoApplication.java —— 启动类

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication   // 👈 这个注解是整个应用的入口开关
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

🔗 自学链接@SpringBootApplication 是个组合注解(= 自动配置 + 组件扫描 + 配置类)。想理解 Java 注解本身怎么看?看 Java 注解 - 菜鸟教程

🌿 分支路线:SpringBoot 自动配置原理、启动过程源码 → 暂时不用管,会用就行。


1.3 创建实体类 User

com.example.demo 包下新建一个 Java 类 User,代表「一个用户」。

image-20260813163337035

package com.example.demo;

public class User {
    private Long id;        // 用户ID
    private String name;    // 姓名
    private Integer age;    // 年龄
    private String email;   // 邮箱

    // 无参构造(框架需要)
    public User() {}

    // 全参构造(方便造数据)
    public User(Long id, String name, Integer age, String email) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.email = email;
    }

    // getter / setter(下面给全,直接复制)
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public Integer getAge() { return age; }
    public void setAge(Integer age) { this.age = age; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}

💡 IDEA 可以一键生成 getter/setter:菜单 Code → Generate → Getter and Setter,新手务必学一下这个快捷操作。


1.4 创建 Controller(核心!)

com.example.demo 包下新建类 UserController。这是整个后端最关键的文件——它定义了 4 个接口。

package com.example.demo;

import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

@RestController                 // ① 告诉 Spring:这是个接口控制器,返回数据而非页面
@RequestMapping("/api/users")   // ② 这个类下所有接口的公共前缀路径
@CrossOrigin                   // ③ 允许前端跨域访问(前后端分离必须)
public class UserController {

    // 用一个 List 在内存里存数据(教程阶段,先不连数据库)
    private final List<User> users = new ArrayList<>();
    // 用来生成自增 ID
    private final AtomicLong idCounter = new AtomicLong(3);

    // 构造方法:启动时造 3 条初始数据
    public UserController() {
        users.add(new User(1L, "张三", 25, "zhangsan@example.com"));
        users.add(new User(2L, "李四", 30, "lisi@example.com"));
        users.add(new User(3L, "王五", 28, "wangwu@example.com"));
    }

    // ====== 接口 1:获取所有用户(GET /api/users)======
    @GetMapping
    public List<User> list() {
        return users;
    }

    // ====== 接口 2:查询单个用户(GET /api/users/2)======
    @GetMapping("/{id}")
    public User getById(@PathVariable Long id) {   // @PathVariable 取路径里的 id
        return users.stream()
                .filter(u -> u.getId().equals(id))
                .findFirst()
                .orElse(null);
    }

    // ====== 接口 3:新增用户(POST /api/users)======
    @PostMapping
    public User create(@RequestBody User user) {   // @RequestBody 把前端发的 JSON 转成 User 对象
        user.setId(idCounter.incrementAndGet());   // 分配一个新 ID
        users.add(user);
        return user;                               // 把新增的对象返回给前端
    }

    // ====== 接口 4:删除用户(DELETE /api/users/2)======
    @DeleteMapping("/{id}")
    public String delete(@PathVariable Long id) {
        boolean removed = users.removeIf(u -> u.getId().equals(id));
        return removed ? "删除成功,id=" + id : "用户不存在,id=" + id;
    }
}

逐个认识上面的注解

注解 作用 自学链接
@RestController 标记这是个接口类,方法返回值会自动转成 JSON SpringBoot 框架
@RequestMapping 定义路径前缀 同上
@GetMapping 处理 GET 请求(查询用) 同上
@PostMapping 处理 POST 请求(新增用) 同上
@DeleteMapping 处理 DELETE 请求(删除用) 同上
@PathVariable 从 URL 路径里取参数(如 /api/users/2 里的 2 Java 注解基础
@RequestBody 把请求体里的 JSON 自动转成 Java 对象 同上
@CrossOrigin 允许跨域(前端不同端口访问后端时需要) 见下方「关于跨域」

🔗 自学链接@RestController 其实是 @Controller + @ResponseBody 的合体;@GetMapping@RequestMapping(method=GET) 的简写。想系统了解这套注解家族,看 Java SpringBoot 框架 - 菜鸟教程

http://localhost:8080/api/users 返回内容如下:

image-20260813163614478

关于跨域 @CrossOrigin

因为前端跑在 5173 端口、后端跑在 8080 端口,属于「跨域」,浏览器默认会拦截。在类上加 @CrossOrigin 就放行了。

🌿 分支路线:除了 @CrossOrigin,更规范的做法是前端配 Vite 代理 或后端写全局跨域配置类。本教程用最简单的注解方式。

跨域是个前端重点问题,想系统理解 CORS 机制,看阮一峰的经典文章:跨域资源共享 CORS 详解


1.5 启动后端

回到 DemoApplication.java,在 IDEA 里:

  1. 点类名左边绿色三角形 ▶️ 运行 main 方法
  2. 控制台出现 Tomcat started on port 8080Started DemoApplication 就说明启动成功

image-20260813163557723

测试接口

直接在浏览器地址栏访问:

🔗 自学链接:想用更专业的工具测接口(POST/DELETE)?搜索学习 PostmanApifox


1.6 后端分层架构(Controller / Service / DAO)

前面的 UserController 为了速成,把「接请求、写逻辑、存数据」全塞进了一个类。真实项目会分层,每层只管自己的事,代码才清晰、好维护、好测试。

为什么要分层

一条请求从进来到出去,经过几道「工序」,每道工序交给一层:

HTTP 请求
   ↓
Controller(控制层)── 接请求、校验参数、返回结果
   ↓
Service(业务层)──── 核心业务逻辑
   ↓
DAO / Mapper(数据访问层)── 操作数据库
   ↓
数据库

各层职责

标记方式 负责什么 不该做什么
Controller 控制层 @RestController 接收前端请求、校验参数、调用 Service、把结果返回前端 不写业务逻辑、不直接碰数据库
Service 业务层 @Service 核心业务逻辑(计算、判断、组合多步操作) 不关心 HTTP 细节、不直接写 SQL
DAO / Mapper 数据访问层 @Mapper(MyBatis)或 Repository(JPA) 和数据库打交道,执行增删改查 不写业务规则
Entity 实体 普通 Java 类(如 User 对应数据库表的一行,在各层之间传递数据

🔗 自学链接:分层能跑起来,靠的是 Spring 的 IoC / 依赖注入——Service 是怎么自动跑到 Controller 里的?看 Java Spring 框架 - 菜鸟教程

拆分示例(把 UserController 重构成三层)

① 业务层 Service(新建 UserService.java):

@Service
public class UserService {
    private final List<User> users = new ArrayList<>();
    private final AtomicLong idCounter = new AtomicLong(3);

    public UserService() {
        users.add(new User(1L, "张三", 25, "zhangsan@example.com"));
        users.add(new User(2L, "李四", 30, "lisi@example.com"));
        users.add(new User(3L, "王五", 28, "wangwu@example.com"));
    }

    public List<User> list() { return users; }

    public User getById(Long id) {
        return users.stream().filter(u -> u.getId().equals(id)).findFirst().orElse(null);
    }

    public User create(User user) {
        user.setId(idCounter.incrementAndGet());
        users.add(user);
        return user;
    }

    public boolean delete(Long id) {
        return users.removeIf(u -> u.getId().equals(id));
    }
}

② 控制层 Controller 变得很薄(只负责转发):

@RestController
@RequestMapping("/api/users")
@CrossOrigin
public class UserController {

    @Autowired              // 👈 Spring 自动把 UserService 注入进来
    private UserService userService;

    @GetMapping
    public List<User> list() {
        return userService.list();      // 不写逻辑,只转发给 Service
    }

    @GetMapping("/{id}")
    public User getById(@PathVariable Long id) {
        return userService.getById(id);
    }

    @PostMapping
    public User create(@RequestBody User user) {
        return userService.create(user);
    }

    @DeleteMapping("/{id}")
    public String delete(@PathVariable Long id) {
        return userService.delete(id) ? "删除成功,id=" + id : "用户不存在,id=" + id;
    }
}

💡 拆分后,前端调用方式、接口路径完全没变。这就是分层的好处:改内部结构,不影响外部。

真实项目的分包结构(按层建包)

类一多,还会按层建不同的包(package),让目录结构和分层一一对应。本教程配套的 demo-db/ 就是这样分包的:

src/main/java/com/example/demo/
├── DemoApplication.java     ← 启动类,留在根包,负责扫描下面所有子包
├── controller/              ← 控制层
│   └── UserController.java
├── service/                 ← 业务层
│   └── UserService.java
├── mapper/                  ← 数据访问层(DAO)
│   └── UserMapper.java
└── entity/                  ← 实体
    └── User.java

不同包之间的引用靠 import 串起来,例如 UserController 里:

package com.example.demo.controller;

import com.example.demo.entity.User;        // 用到实体
import com.example.demo.service.UserService; // 用到业务层

⚠️ 关键坑DemoApplication 必须放在最外层的根包com.example.demo),这样 @SpringBootApplication 的自动扫描才能覆盖到 controllerservicemapper 等所有子包。如果把启动类也塞进某个子包,其它包里的组件就扫不到、注解会失效。

image-20260813170104821

🌿 分支路线:项目变大后,还会出现 DTO(给前端用的传输对象)、VO统一异常处理 @ControllerAdvice参数校验 @Valid 等。先把三层跑顺,再学这些。


1.7 连接真实数据库(MySQL + MyBatis)

到现在数据还存在内存里,重启就没了。要持久化就得连数据库。下面以国内最常用的 MySQL + MyBatis 为例,4 步接上。

第 1 步:准备 MySQL,建库建表

🔗 自学链接:没装 MySQL?看 MySQL 教程 - 菜鸟教程(含安装、建库建表、增删改查)

CREATE DATABASE demo DEFAULT CHARSET utf8mb4;
USE demo;
CREATE TABLE user (
    id    INT PRIMARY KEY AUTO_INCREMENT,
    name  VARCHAR(50),
    age   INT,
    email VARCHAR(100)
);

第 2 步:在 pom.xml 加两个依赖

<!-- MySQL 驱动(2.7.18 的 BOM 不管该坐标,需显式指定版本) -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.33</version>
    <scope>runtime</scope>
</dependency>

<!-- MyBatis 与 SpringBoot 整合(适配 2.7.x) -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.3.1</version>
</dependency>

第 3 步:在 application.properties 配数据库连接

spring.datasource.url=jdbc:mysql://localhost:3306/demo?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=your_password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

第 4 步:写 Mapper 接口(这就是 DAO 层)

新建 UserMapper.java,用注解直接写 SQL:

@Mapper
public interface UserMapper {

    @Select("SELECT * FROM user")
    List<User> findAll();

    @Select("SELECT * FROM user WHERE id = #{id}")
    User findById(Long id);

    @Insert("INSERT INTO user(name, age, email) VALUES(#{name}, #{age}, #{email})")
    @Options(useGeneratedKeys = true, keyProperty = "id")   // 插入后回填自增 id
    void insert(User user);

    @Delete("DELETE FROM user WHERE id = #{id}")
    void deleteById(Long id);
}

最后,把 UserService 里操作 ArrayList 的代码改成调用 UserMapper(注入 @Autowired UserMapper userMapper)即可——Controller、前端一行都不用改。这就是分层带来的好处:换数据来源,只动一层。

🔗 自学链接:想系统学 MyBatis(XML 写 SQL、动态 SQL <if>/<foreach>、一对多关联)?看 Java MyBatis 框架 - 菜鸟教程

🌿 其他数据库方案(选学)

方案 特点 入口
MyBatis-Plus MyBatis 增强版,单表增删改查几乎零代码 MyBatis-Plus 教程 - w3cschool
Spring Data JPA 官方 ORM,面向对象操作,不用手写 SQL Spring Boot 整合 JPA(Spring 中文网)
Druid 连接池 阿里开源数据库连接池,带监控页面 Druid 官方 Wiki - GitHub

第二部分:前端 Vue3

2.1 创建 Vue 项目(命令行)

打开终端(PowerShell / Git Bash / cmd 都行),进入你想放项目的目录,执行:

npm create vite@latest vue-frontend -- --template vue

这条命令用 Vite 脚手架创建一个 Vue 模板项目(名字叫 vue-frontend)。

创建完成后,进入项目并安装依赖:

cd vue-frontend
npm install

🔗 自学链接


2.2 安装 axios(用来发请求调接口)

vue-frontend 目录下执行:

npm install axios

🔗 自学链接Vue3 Ajax(axios) - 菜鸟教程


2.3 认识前端项目结构

VS Code 打开 vue-frontend 文件夹:

vue-frontend/
├── index.html
├── package.json
├── vite.config.js       ← Vite 配置(端口、代理等)
└── src/
    ├── main.js          ← 入口
    ├── App.vue          ← 根组件(我们要改的就是它!)
    └── components/      ← 子组件放这里

🔗 自学链接Vue3 目录结构 - 菜鸟教程


2.4 编写页面(改 App.vue

src/App.vue 的内容全部替换成下面的代码:

<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'

// 后端接口地址(8080 是 SpringBoot 的端口)
const API = 'http://localhost:8080/api/users'

// 响应式数据
const users = ref([])                              // 用户列表
const form = ref({ name: '', age: '', email: '' }) // 新增表单

// 1. 获取用户列表
async function fetchUsers() {
  const res = await axios.get(API)
  users.value = res.data
}

// 2. 新增用户
async function addUser() {
  if (!form.value.name) {
    alert('请输入姓名')
    return
  }
  await axios.post(API, {
    name: form.value.name,
    age: Number(form.value.age) || 0,
    email: form.value.email
  })
  form.value = { name: '', age: '', email: '' }   // 清空表单
  fetchUsers()                                     // 刷新列表
}

// 3. 删除用户
async function deleteUser(id) {
  await axios.delete(`${API}/${id}`)
  fetchUsers()
}

// 页面加载完自动拉取一次列表
onMounted(fetchUsers)
</script>

<template>
  <div class="app">
    <h1>用户管理(SpringBoot + Vue)</h1>

    <!-- 用户列表 -->
    <table>
      <thead>
        <tr>
          <th>ID</th><th>姓名</th><th>年龄</th><th>邮箱</th><th>操作</th>
        </tr>
      </thead>
      <tbody>
        <!-- v-for 循环渲染每一行 -->
        <tr v-for="u in users" :key="u.id">
          <td>{{ u.id }}</td>
          <td>{{ u.name }}</td>
          <td>{{ u.age }}</td>
          <td>{{ u.email }}</td>
          <td><button @click="deleteUser(u.id)">删除</button></td>
        </tr>
      </tbody>
    </table>

    <!-- 新增表单 -->
    <h2>新增用户</h2>
    <form @submit.prevent="addUser">
      <input v-model="form.name" placeholder="姓名" />
      <input v-model="form.age" type="number" placeholder="年龄" />
      <input v-model="form.email" placeholder="邮箱" />
      <button type="submit">添加</button>
    </form>
  </div>
</template>

<style>
body { font-family: sans-serif; margin: 40px; background: #f9f9f9; }
table { border-collapse: collapse; margin-bottom: 24px; background: #fff; }
th, td { border: 1px solid #ccc; padding: 8px 16px; text-align: center; }
th { background: #f0f0f0; }
input { padding: 6px 8px; margin-right: 8px; }
button { padding: 6px 14px; cursor: pointer; }
</style>

逐个认识上面的 Vue 语法

语法 作用 自学链接
ref() 创建响应式变量(数据变了页面自动更新) Vue3 基础语法
onMounted() 页面加载完成后执行的钩子 Vue3 组件
v-for 循环渲染列表 Vue3 循环语句
v-model 双向绑定表单输入 Vue3 表单
@click / @submit 绑定点击 / 提交事件 Vue3 事件处理
{{ }} 插值,显示数据 Vue3 基础语法
axios.get/post/delete 调用后端接口 Vue3 Ajax(axios)

🌿 分支路线

体验增强(可选进阶)

上面的 App.vue 是最小可用版。真实项目还会加这些用户体验细节(配套项目 vue-frontend/ 已经是增强版):

增强后的完整 App.vue 如下(正好用到了上面学的 v-if 条件渲染):

<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'

const API = 'http://localhost:8080/api/users'

const users = ref([])
const form = ref({ name: '', age: '', email: '' })
const loading = ref(false)     // 列表加载中
const submitting = ref(false)  // 新增提交中
const toast = ref(null)        // 提示消息 { type, text }

// 弹一个提示,3 秒后自动消失
function showToast(type, text) {
  toast.value = { type, text }
  setTimeout(() => { toast.value = null }, 3000)
}

async function fetchUsers() {
  loading.value = true
  try {
    const res = await axios.get(API)
    users.value = res.data
  } catch (e) {
    showToast('error', '加载列表失败:' + (e.message || '未知错误'))
  } finally {
    loading.value = false
  }
}

async function addUser() {
  if (!form.value.name.trim()) {
    showToast('error', '请输入姓名')
    return
  }
  submitting.value = true
  try {
    await axios.post(API, {
      name: form.value.name.trim(),
      age: Number(form.value.age) || 0,
      email: form.value.email.trim()
    })
    form.value = { name: '', age: '', email: '' }
    showToast('success', '新增成功')
    await fetchUsers()
  } catch (e) {
    showToast('error', '新增失败:' + (e.message || '未知错误'))
  } finally {
    submitting.value = false
  }
}

async function deleteUser(user) {
  if (!confirm(`确定删除「${user.name}」吗?`)) return
  try {
    await axios.delete(`${API}/${user.id}`)
    showToast('success', `已删除「${user.name}」`)
    await fetchUsers()
  } catch (e) {
    showToast('error', '删除失败:' + (e.message || '未知错误'))
  }
}

onMounted(fetchUsers)
</script>

<template>
  <div class="app">
    <h1>用户管理(SpringBoot + Vue)</h1>

    <!-- 顶部提示 toast -->
    <div v-if="toast" :class="['toast', toast.type]">{{ toast.text }}</div>

    <table>
      <thead>
        <tr>
          <th>ID</th><th>姓名</th><th>年龄</th><th>邮箱</th><th>操作</th>
        </tr>
      </thead>
      <tbody>
        <tr v-if="loading"><td colspan="5" class="hint">加载中…</td></tr>
        <tr v-else-if="users.length === 0"><td colspan="5" class="hint">暂无数据</td></tr>
        <tr v-else v-for="u in users" :key="u.id">
          <td>{{ u.id }}</td>
          <td>{{ u.name }}</td>
          <td>{{ u.age }}</td>
          <td>{{ u.email }}</td>
          <td><button class="danger" @click="deleteUser(u)">删除</button></td>
        </tr>
      </tbody>
    </table>

    <h2>新增用户</h2>
    <form @submit.prevent="addUser">
      <input v-model="form.name" placeholder="姓名" />
      <input v-model="form.age" type="number" placeholder="年龄" />
      <input v-model="form.email" placeholder="邮箱" />
      <button type="submit" :disabled="submitting">
        {{ submitting ? '提交中…' : '添加' }}
      </button>
    </form>
  </div>
</template>

<style>
body { font-family: sans-serif; margin: 40px; background: #f9f9f9; }
h1 { margin-top: 0; }
table { border-collapse: collapse; margin-bottom: 24px; background: #fff; width: 100%; max-width: 760px; }
th, td { border: 1px solid #ccc; padding: 8px 16px; text-align: center; }
th { background: #f0f0f0; }
.hint { color: #888; padding: 18px; }
input { padding: 6px 8px; margin-right: 8px; border: 1px solid #ccc; border-radius: 4px; }
button { padding: 6px 14px; cursor: pointer; border: 1px solid #ccc; background: #fff; border-radius: 4px; }
button:disabled { opacity: .6; cursor: not-allowed; }
button.danger { color: #c0392b; border-color: #c0392b; }
button.danger:hover { background: #c0392b; color: #fff; }
.toast { padding: 10px 16px; border-radius: 6px; margin-bottom: 16px; max-width: 760px; font-size: 14px; }
.toast.success { background: #e8f5e9; color: #2e7d32; border: 1px solid #a5d6a7; }
.toast.error { background: #fdecea; color: #c62828; border: 1px solid #f5c6cb; }
</style>

💡 不想手敲的话,直接用配套项目 vue-frontend/src/App.vue(已是这个增强版)。


2.5 启动前端

vue-frontend 目录下执行:

npm run dev

终端会显示一个地址,通常是 http://localhost:5173/,用浏览器打开它。


第三部分:前后端联调(最终效果)

确保两边都在运行

打开浏览器 http://localhost:5173/,你应该能:

  1. 看到 3 条初始用户(张三、李四、王五)—— 说明 GET 接口通了 ✅
  2. 在下面表单填写信息,点 添加 —— 列表多一行,说明 POST 接口通了 ✅
  3. 点某行的 删除 —— 该行消失,说明 DELETE 接口通了 ✅

🎉 恭喜!你已经搭通了一个完整的前后端项目!


第四部分:下一步学什么(分支路线)

本教程主线已经带你跑通了前后端、分层(1.6)、数据库(1.7)。下面是主线之外、需要继续自学的方向,每个都附自学链接:

🗄️ 1. 数据库进阶(基础已在 1.7 教过)

👉 1.7 节已经带你用 MyBatis + MySQL 连上了数据库(配套项目 demo-db),基本的增删改查已经会了。下面是进一步深入的方向:

方向 说明 自学入口
MyBatis XML / 动态 SQL 复杂查询用 XML 写 <if>/<foreach>,比注解更强大 Java MyBatis 框架 - 菜鸟教程
MyBatis-Plus MyBatis 增强版,单表 CRUD 几乎零代码 MyBatis-Plus 教程 - w3cschool
Spring Data JPA 官方 ORM,面向对象操作,不用手写 SQL Spring Boot 整合 JPA(Spring 中文网)
Druid 连接池 阿里开源连接池,带 SQL 监控页面 Druid 官方 Wiki - GitHub
Redis 缓存 把热点数据放内存,减轻数据库压力 Redis 教程 - 菜鸟教程

🔁 2. 架构与代码规范进阶(分层已在 1.6 教过)

👉 1.6 节已经带你做了 Controller / Service / Mapper 分层(配套项目 demo-layereddemo-db)。下面是真实项目里更进一步的做法:

方向 说明
DTO / VO 给前端用的传输对象,和数据库实体分开,避免直接暴露表结构
统一返回结果 所有接口返回统一的 {code, message, data} 格式,前端更好处理
全局异常处理 @ControllerAdvice + @ExceptionHandler 统一捕获异常,不再直接抛 500
参数校验 @Valid + @NotBlank 等自动校验入参,不用手写 if
事务管理 @Transactional 保证多步操作要么全成功要么全回滚

🌿 理解分层背后的 IoC / 依赖注入,看 Java Spring 框架 - 菜鸟教程

✂️ 3. AOP 面向切面编程

想在所有接口执行前后自动记日志、做权限校验,又不改业务代码?用 AOP:

🔗 自学链接:

🔒 4. 安全与登录鉴权

接口现在谁都能调。要做登录、权限控制,学 Spring Security / JWT:

🔗 自学链接:Spring Security 中文文档 - SpringCloud

📦 5. 打包部署

🧩 6. 更多常用注解

下面这些在真实项目里几乎天天用,建议逐个了解:

注解 用途
@Service 标记业务层类
@Autowired 自动注入依赖(依赖注入)
@Component 通用组件标记
@Configuration 配置类
@RequestParam 取查询参数(如 ?name=xx
@Valid 参数校验
@ExceptionHandler 全局异常处理

🔗 系统了解注解:Java 注解 - 菜鸟教程

🎨 7. 前端进阶(UI 组件库 + 路由 + 状态管理)

后端讲了一堆,前端也有进阶方向:

UI 组件库(拿来即用的按钮、表格、表单、弹窗,不用自己写样式):

组件库 特点 适合场景 官网
Element Plus 国内最流行,组件全,中文文档 PC 后台管理系统 element-plus.org
Naive UI 现代、TypeScript 友好、主题灵活 追求现代感的 PC 应用 naiveui.com
Ant Design Vue 蚂蚁设计语言,企业级 企业中后台 antdv.com
Vant 有赞出品,移动端组件 手机 H5 / 小程序 vant-ui.github.io

💡 新手推荐 Element Plus:资料最多、踩坑最少。把本教程的「用户管理」页面换成它的表格 + 表单 + 弹窗,立刻就专业了。

其他前端方向


附录:配套项目文件清单

各项目和章节的对应关系,见教程开头的「📁 配套项目对照」表。这里列出每个项目的关键文件。

demo/ — 基础版(对应 1.1–1.5)

文件 作用
pom.xml Maven 依赖(Spring Web)
DemoApplication.java 启动类
User.java 用户实体类
UserController.java 4 个接口,逻辑全在这里

demo-layered/ — 分层版(对应 1.6)

demo/ 多/改了:

文件 作用
UserService.java 🆕 业务层,承接了原 Controller 的数据和逻辑
UserController.java ✏️ 变薄,只接请求转发给 Service

demo-db/ — 完整版(对应 1.6 分包 + 1.7)

controller / service / mapper / entity 分包,连 MySQL:

文件 作用
pom.xml 多了 MyBatis + MySQL 驱动依赖
controller/UserController.java 控制层
service/UserService.java 业务层(调用 Mapper)
mapper/UserMapper.java 🆕 数据访问层(注解写 SQL)
entity/User.java 实体
application.properties 数据源配置(英文注释,避免乱码)
schema.sql / data.sql 🆕 建表 + 初始数据(启动自动执行)

vue-frontend/ — 前端(对应第二部分)

文件 作用
App.vue 唯一需要改的文件(列表 + 新增表单)
package.json 装了 axios 后自动更新

各版本完整代码直接看对应项目文件夹即可,都已验证可运行。


✅ 学习路线总结

本教程已带你掌握
        │
        ├─ 前后端跑通(1.11.5 + 第二部分)
        ├─ 后端分层架构(1.6)
        └─ 连接数据库(1.7)
        │
        ▼
   下一步可深入
        │
        ├─ 后端:AOP → 安全鉴权 → 事务 → 缓存 → 打包部署
        └─ 前端:路由 → 状态管理(Pinia) → UI组件库 → 打包部署

每个方向,回到本教程「第四部分」点对应链接自学即可。