本教程带你走通一遍最简单的前后端项目:后端 SpringBoot 提供几个接口,前端 Vue3 写一个页面调用接口、展示和操作数据。
我们只走一条主线,不做任何分支。中间出现的每个注解、每个技术名词,都会附上 菜鸟教程 的超链接,点进去自学即可。
🔗 自学链接 的地方,点超链接去菜鸟教程看那个注解/知识点的详细用法和其他写法。🌿 分支路线 的地方,是告诉你要深入某个方向时该看什么,本教程不展开。最终效果:一个「用户管理」页面,能查看列表 / 新增 / 删除用户,数据存在后端内存里。
浏览器 (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,同时只能开一个。
开工前请确认装好下面这些东西(点链接去菜鸟教程看安装方法):
| 工具 | 版本建议 | 自学链接 |
|---|---|---|
| JDK | 8 或以上(本教程示例基于 JDK 8 + SpringBoot 2.7,兼容多数存量环境) | Java 教程(含环境安装) |
| Maven | 3.6+ | Maven 环境配置 |
| Node.js | 18+ | 安装后自带 npm,Vue 需要它 |
| IDEA | 免费30天,长期使用需自行“想办法”。 | 后端开发用 |
| VS Code | 任意版本 | 前端开发用(也可用 IDEA) |
💡 验证环境:分别执行
java -version、mvn -version、node -v、npm -v,都能打印版本号就说明装好了。
⚠️ 重要提示:官方的 start.spring.io 已经不再支持 Java 8 和 Spring Boot 2.x(它现在只给 Spring Boot 3.x,要求 Java 17+)。本教程用 JDK 8 + SpringBoot 2.7,所以不能用官方那个网址。下面三种方式任选一种。
国内可用的替代生成器,支持 Java 8 和 Spring Boot 2.7.x:
com.exampledemospring-boot-starter-web)IDEA 的 Spring Initializr 默认也连官方地址,要先手动换掉:
File → New → Project → Spring Initializrhttps://start.spring.io 改成 https://start.aliyun.com如果上面两种都不方便,直接手动建,把 pom.xml 内容全部替换成下面这段即可:
File → New → Project → 选 Maven(不勾任何 archetype 模板)groupId 填 com.example,artifactId 填 demopom.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>
User.java、UserController.java 等文件。💡 懒人方案:其实本教程配套的 demo/ 文件夹就是完整后端,已经建好并验证可运行,你可以直接克隆 java-guide 仓库 后用 IDEA 打开对照学习,不必自己从头创建。
🔗 自学链接:想了解 SpringBoot 是什么、为什么能「开箱即用」?看 Java SpringBoot 框架 - 菜鸟教程
用 IDEA 打开项目后,结构长这样:
demo/
├── pom.xml ← Maven 依赖配置文件
└── src/
└── main/
├── java/
│ └── com/example/demo/
│ └── DemoApplication.java ← 启动类(入口)
└── resources/
└── application.properties ← 配置文件(端口等)
pom.xml —— Maven 的「依赖清单」我们创建时勾选了 Spring Web,所以 pom.xml 里会自动有这两个核心依赖:
spring-boot-starter-web:用来写 Web 接口(内置 Tomcat 服务器)spring-boot-starter-test:测试用🔗 自学链接:搞不懂
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 自动配置原理、启动过程源码 → 暂时不用管,会用就行。
User在 com.example.demo 包下新建一个 Java 类 User,代表「一个用户」。
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,新手务必学一下这个快捷操作。
在 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 返回内容如下:
@CrossOrigin因为前端跑在 5173 端口、后端跑在 8080 端口,属于「跨域」,浏览器默认会拦截。在类上加 @CrossOrigin 就放行了。
🌿 分支路线:除了
@CrossOrigin,更规范的做法是前端配 Vite 代理 或后端写全局跨域配置类。本教程用最简单的注解方式。跨域是个前端重点问题,想系统理解 CORS 机制,看阮一峰的经典文章:跨域资源共享 CORS 详解
回到 DemoApplication.java,在 IDEA 里:
main 方法Tomcat started on port 8080 和 Started DemoApplication 就说明启动成功直接在浏览器地址栏访问:
http://localhost:8080/api/users → 看到 3 条用户的 JSON 数组 ✅http://localhost:8080/api/users/1 → 看到张三的信息 ✅🔗 自学链接:想用更专业的工具测接口(POST/DELETE)?搜索学习 Postman 或 Apifox。
前面的
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的自动扫描才能覆盖到controller、service、mapper等所有子包。如果把启动类也塞进某个子包,其它包里的组件就扫不到、注解会失效。
🌿 分支路线:项目变大后,还会出现 DTO(给前端用的传输对象)、VO、统一异常处理
@ControllerAdvice、参数校验@Valid等。先把三层跑顺,再学这些。
到现在数据还存在内存里,重启就没了。要持久化就得连数据库。下面以国内最常用的 MySQL + MyBatis 为例,4 步接上。
🔗 自学链接:没装 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)
);
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>
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
新建 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 |
打开终端(PowerShell / Git Bash / cmd 都行),进入你想放项目的目录,执行:
npm create vite@latest vue-frontend -- --template vue
这条命令用 Vite 脚手架创建一个 Vue 模板项目(名字叫 vue-frontend)。
创建完成后,进入项目并安装依赖:
cd vue-frontend
npm install
🔗 自学链接:
- 什么是 Vite、为什么用它?→ Vite 教程 - 菜鸟教程
- 另一种官方脚手架
npm create vue@latest(功能更全,会问路由/Pinia等)→ Vue3 创建项目 - 菜鸟教程- Vue3 安装 - 菜鸟教程
在 vue-frontend 目录下执行:
npm install axios
🔗 自学链接:Vue3 Ajax(axios) - 菜鸟教程
用 VS Code 打开 vue-frontend 文件夹:
vue-frontend/
├── index.html
├── package.json
├── vite.config.js ← Vite 配置(端口、代理等)
└── src/
├── main.js ← 入口
├── App.vue ← 根组件(我们要改的就是它!)
└── components/ ← 子组件放这里
🔗 自学链接:Vue3 目录结构 - 菜鸟教程
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>
| 语法 | 作用 | 自学链接 |
|---|---|---|
ref() |
创建响应式变量(数据变了页面自动更新) | Vue3 基础语法 |
onMounted() |
页面加载完成后执行的钩子 | Vue3 组件 |
v-for |
循环渲染列表 | Vue3 循环语句 |
v-model |
双向绑定表单输入 | Vue3 表单 |
@click / @submit |
绑定点击 / 提交事件 | Vue3 事件处理 |
{{ }} |
插值,显示数据 | Vue3 基础语法 |
axios.get/post/delete |
调用后端接口 | Vue3 Ajax(axios) |
🌿 分支路线:
- 多页面怎么办?→ 学 Vue3 路由 vue-router
- 想按条件显示/隐藏元素?→ 学 Vue3 条件语句 v-if
上面的 App.vue 是最小可用版。真实项目还会加这些用户体验细节(配套项目 vue-frontend/ 已经是增强版):
confirm 确认,防误删try/catch,失败有提示不会白屏增强后的完整 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(已是这个增强版)。
在 vue-frontend 目录下执行:
npm run dev
终端会显示一个地址,通常是 http://localhost:5173/,用浏览器打开它。
确保两边都在运行:
DemoApplication 在运行(端口 8080)npm run dev 在运行(端口 5173)打开浏览器 http://localhost:5173/,你应该能:
🎉 恭喜!你已经搭通了一个完整的前后端项目!
本教程主线已经带你跑通了前后端、分层(1.6)、数据库(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 教程 - 菜鸟教程 |
👉 1.6 节已经带你做了 Controller / Service / Mapper 分层(配套项目 demo-layered、demo-db)。下面是真实项目里更进一步的做法:
| 方向 | 说明 |
|---|---|
| DTO / VO | 给前端用的传输对象,和数据库实体分开,避免直接暴露表结构 |
| 统一返回结果 | 所有接口返回统一的 {code, message, data} 格式,前端更好处理 |
| 全局异常处理 | @ControllerAdvice + @ExceptionHandler 统一捕获异常,不再直接抛 500 |
| 参数校验 | @Valid + @NotBlank 等自动校验入参,不用手写 if |
| 事务管理 | @Transactional 保证多步操作要么全成功要么全回滚 |
🌿 理解分层背后的 IoC / 依赖注入,看 Java Spring 框架 - 菜鸟教程
想在所有接口执行前后自动记日志、做权限校验,又不改业务代码?用 AOP:
🔗 自学链接:
- Spring 基础知识汇总(含「为什么需要 AOP」)- 菜鸟教程
- AOP 底层用的代理模式:代理模式 - 菜鸟教程
接口现在谁都能调。要做登录、权限控制,学 Spring Security / JWT:
mvn clean package → 生成 jar → java -jar demo.jar 运行npm run build → 生成 dist/ 静态文件 → 用 nginx 部署下面这些在真实项目里几乎天天用,建议逐个了解:
| 注解 | 用途 |
|---|---|
@Service |
标记业务层类 |
@Autowired |
自动注入依赖(依赖注入) |
@Component |
通用组件标记 |
@Configuration |
配置类 |
@RequestParam |
取查询参数(如 ?name=xx) |
@Valid |
参数校验 |
@ExceptionHandler |
全局异常处理 |
🔗 系统了解注解:Java 注解 - 菜鸟教程
后端讲了一堆,前端也有进阶方向:
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.1–1.5 + 第二部分)
├─ 后端分层架构(1.6)
└─ 连接数据库(1.7)
│
▼
下一步可深入
│
├─ 后端:AOP → 安全鉴权 → 事务 → 缓存 → 打包部署
└─ 前端:路由 → 状态管理(Pinia) → UI组件库 → 打包部署
每个方向,回到本教程「第四部分」点对应链接自学即可。