课程概述
在完成了纯 JDK 原生的 Java 用户管理系统后,进入第二阶段——使用 Spring Boot 3.x 框架重写整个项目。从框架搭建到 JWT 认证,完整体验了企业级框架带来的开发效率提升。
核心变化
| 方面 | JDK 原生 | Spring Boot 版本 |
|---|---|---|
| Web 服务器 | 手写 HttpServer | 内置 Tomcat |
| 路由分发 | 手写 if/switch | @RequestMapping 注解 |
| 数据库操作 | 手写 JDBC + SQL | JPA 自动 CRUD |
| 密码存储 | 明文 | BCrypt 加密 |
| 认证方式 | 无 | JWT Token + 拦截器 |
| 依赖管理 | 手动下载 JAR | Maven 自动管理 |
| 代码行数 | ~800 行 | ~600 行(含注释) |
1. 项目初始化
Spring Initializr
访问 start.spring.io 创建项目,选择:
- Spring Boot 3.2.5
- JDK 17
- Maven 项目
- 依赖:Spring Web、Spring Data JPA、SQLite JDBC、Spring Security Crypto、JJWT
mvn 命令
mvn spring-boot:run # 运行
2. Spring Boot 启动入口
@SpringBootApplication // 组合注解:@Configuration + @ComponentScan + @EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args); // 启动 Spring Boot
}
}
3. 项目结构
java-spring/
├── pom.xml # Maven 依赖配置
├── src/main/java/com/example/
│ ├── Application.java # 启动入口
│ ├── entity/
│ │ └── User.java # JPA 实体类
│ ├── repository/
│ │ └── UserRepository.java # JPA Repository
│ ├── controller/
│ │ ├── UserController.java # CRUD REST API
│ │ └── LoginController.java # 登录 API
│ └── config/
│ ├── PasswordConfig.java # BCrypt 配置
│ ├── JwtUtil.java # JWT 工具类
│ ├── JwtInterceptor.java # JWT 拦截器
│ └── WebConfig.java # MVC 配置
├── src/main/resources/
│ ├── application.properties # Spring Boot 配置
│ └── static/
│ ├── login.html # 登录页面
│ └── index.html # 管理页面
└── user.db # SQLite 数据库
4. JPA ORM — 告别手写 SQL
实体类
@Entity // 标注为实体类,对应数据库表
@Table(name = "users") // 指定表名
public class User {
@Id // 主键
@GeneratedValue(strategy = GenerationType.IDENTITY) // 自增
private Long id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
@JsonIgnore // 返回 JSON 时隐藏密码
private String password;
// Getter / Setter...
}
核心概念:
@Entity— 告诉 JPA 这是一个实体类@Table— 指定对应的数据库表名@Id+@GeneratedValue— 自增主键@Column— 字段约束(非空、唯一等)- 不再需要手写
CREATE TABLE,JPA 自动建表
Repository — 零代码 CRUD
public interface UserRepository extends JpaRepository<User, Long> {
// 根据方法名自动生成 SQL 查询
Optional<User> findByUsername(String username);
}
对比手写 JDBC:
| 操作 | 手写 JDBC | JPA Repository |
|---|---|---|
| 创建表 | CREATE TABLE users (...) | @Entity + 自动建表 |
| 新增 | INSERT INTO users VALUES (?, ?) | repository.save(user) |
| 查询所有 | SELECT * FROM users | repository.findAll() |
| 查询单个 | SELECT * FROM users WHERE id = ? | repository.findById(id) |
| 更新 | UPDATE users SET ... WHERE id = ? | repository.save(user) |
| 删除 | DELETE FROM users WHERE id = ? | repository.deleteById(id) |
5. RESTful API — Spring MVC 注解
UserController 完整代码
@RestController // = @Controller + @ResponseBody(返回 JSON)
@RequestMapping("/api/users")
public class UserController {
@Autowired // 自动注入 Bean
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@GetMapping // GET /api/users
public List<User> getAll() {
return userRepository.findAll();
}
@GetMapping("/{id}") // GET /api/users/{id}
public ResponseEntity<?> getById(@PathVariable Long id) {
var user = userRepository.findById(id);
if (user.isPresent()) {
return ResponseEntity.ok(user.get()); // 200 OK
}
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(Map.of("error", "用户不存在")); // 404 Not Found
}
@PostMapping // POST /api/users
public ResponseEntity<?> create(@RequestBody Map<String, String> body) {
String encodedPassword = passwordEncoder.encode(body.get("password"));
User user = new User(body.get("username"), encodedPassword, body.get("email"));
userRepository.save(user);
return ResponseEntity.status(HttpStatus.CREATED).body(user); // 201 Created
}
@PutMapping("/{id}") // PUT /api/users/{id}
public ResponseEntity<?> update(@PathVariable Long id, @RequestBody Map<String, String> body) {
var user = userRepository.findById(id);
if (user.isEmpty()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(Map.of("error", "用户不存在"));
}
User u = user.get();
u.setUsername(body.getOrDefault("username", u.getUsername()));
u.setEmail(body.getOrDefault("email", u.getEmail()));
if (body.containsKey("password")) {
u.setPassword(passwordEncoder.encode(body.get("password")));
}
userRepository.save(u);
return ResponseEntity.ok(u);
}
@DeleteMapping("/{id}") // DELETE /api/users/{id}
public ResponseEntity<?> delete(@PathVariable Long id) {
if (userRepository.existsById(id)) {
userRepository.deleteById(id);
return ResponseEntity.ok(Map.of("message", "用户已删除"));
}
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(Map.of("error", "用户不存在"));
}
}
注解说明:
@RestController— 所有方法默认返回 JSON,不需要手动转换@GetMapping / @PostMapping / @PutMapping / @DeleteMapping— RESTful 方法映射@PathVariable— 自动提取 URL 路径参数@RequestBody— 自动解析请求体 JSON 为对象@Autowired— 自动注入容器中的 BeanResponseEntity— 可自定义 HTTP 状态码和响应体
6. BCrypt 密码加密
@Configuration
public class PasswordConfig {
@Bean // 注册为 Bean,其他类可用 @Autowired 注入
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
BCrypt 特点:
- 每次加密结果不同(自动加盐)
- 即使相同的明文密码,两次加密结果也不一样
- 使用
matches(明文, 密文)方法验证
7. JWT 认证
JWT 工具类
@Component
public class JwtUtil {
private static final SecretKey SECRET_KEY = Keys.hmacShaKeyFor(
"secret-key-32-bytes-min".getBytes(StandardCharsets.UTF_8));
private static final long EXPIRATION = 7 × 24 × 60 × 60 × 1000L; // 7天
public String generateToken(Long userId, String username) {
return Jwts.builder()
.subject(username) // 用户名
.claim("id", userId) // 自定义字段
.issuedAt(new Date()) // 签发时间
.expiration(new Date(System.currentTimeMillis() + EXPIRATION))
.signWith(SECRET_KEY) // HMAC-SHA256 签名
.compact();
}
public boolean validateToken(String token) {
try {
Jwts.parser().verifyWith(SECRET_KEY).build().parseSignedClaims(token);
return true;
} catch (Exception e) {
return false;
}
}
}
登录接口
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody Map<String, String> body) {
var user = userRepository.findByUsername(body.get("username"));
if (user.isPresent() && passwordEncoder.matches(body.get("password"), user.get().getPassword())) {
String token = jwtUtil.generateToken(user.get().getId(), user.get().getUsername());
return ResponseEntity.ok(Map.of(
"token", token,
"username", user.get().getUsername(),
"message", "登录成功"
));
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(Map.of("error", "用户名或密码错误"));
}
8. JWT 拦截器
@Component
public class JwtInterceptor implements HandlerInterceptor {
@Autowired
private JwtUtil jwtUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("{\"error\": \"未提供 token\"}");
return false; // 拦截请求
}
String token = authHeader.substring(7); // 去掉 "Bearer " 前缀
if (!jwtUtil.validateToken(token)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("{\"error\": \"无效或过期的 token\"}");
return false;
}
return true; // 放行,进入 Controller
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private JwtInterceptor jwtInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor)
.addPathPatterns("/api/**") // 拦截 /api 开头的请求
.excludePathPatterns("/api/login"); // 放行登录接口
}
}
请求流程:
浏览器请求 /api/users(带 Authorization: Bearer xxx)
↓
Spring 的 DispatcherServlet 收到请求
↓
JwtInterceptor.preHandle() 执行
├─ token 有效 → true → 进入 UserController
└─ token 无效 → false → 直接返回 401
9. 前端页面
两个精美页面——登录页 + 管理页,都带 JWT 认证。
登录页面
async function handleLogin(event) {
event.preventDefault();
const res = await fetch("/api/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password })
});
const data = await res.json();
if (res.ok) {
localStorage.setItem("token", data.token);
localStorage.setItem("username", data.username);
window.location.href = "/index.html";
}
}
管理页面
// 获取带 token 的请求头
function getHeaders() {
return {
"Content-Type": "application/json",
"Authorization": "Bearer " + localStorage.getItem("token")
};
}
// 加载用户列表
async function loadUsers() {
const res = await fetch("/api/users", {
headers: getHeaders() // 携带 JWT token
});
if (res.status === 401) { // token 失效
localStorage.removeItem("token");
window.location.href = "/login.html";
return;
}
const users = await res.json();
// 渲染用户表格
}
// 新增用户(带 token)
async function addUser(username, password, email) {
const res = await fetch("/api/users", {
method: "POST",
headers: getHeaders(),
body: JSON.stringify({ username, password, email })
});
}
10. 配置文件
# 数据源配置
spring.datasource.url=jdbc:sqlite:user.db
spring.datasource.driver-class-name=org.sqlite.JDBC
# JPA 配置
spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect
spring.jpa.hibernate.ddl-auto=update # 自动建表/更新
spring.jpa.open-in-view=false # 关闭 Open Session In View
# 服务器端口
server.port=8080
11. RESTful API 列表
| 方法 | 路径 | 功能 | 认证 |
|---|---|---|---|
| POST | /api/login | 用户登录 | 无需 |
| GET | /api/users | 获取所有用户 | 需要 |
| GET | /api/users/{id} | 获取单个用户 | 需要 |
| POST | /api/users | 新增用户 | 需要 |
| PUT | /api/users/{id} | 修改用户 | 需要 |
| DELETE | /api/users/{id} | 删除用户 | 需要 |
12. 运行和打包
# 开发模式运行(自动热加载)
mvn spring-boot:run
# 打包为可执行 JAR
mvn clean package
# 运行打包后的 JAR
java -jar target/java-spring-1.0.0.jar
浏览器访问:
http://localhost:8080— 首页http://localhost:8080/login.html— 登录页面http://localhost:8080/index.html— 管理页面
课程路线图
Spring Initializr 项目初始化
↓
JPA 实体类与 Repository
↓
RESTful Controller 注解开发
↓
BCrypt 密码加密
↓
JWT 工具类(签发 + 验证)
↓
JWT 拦截器(请求鉴权)
↓
前端登录页 + 管理页(带 JWT)
↓
Maven 打包部署
核心收获
完成本课程后,你已理解以下概念:
- 框架思维 — 框架替你做了什么,你怎么配合框架
- IoC / DI — @Autowired 自动注入,不再手动 new 对象
- ORM — 对象-关系映射,JPA 如何把对象变成数据库行
- 注解驱动 — 声明式编程,用注解代替样板代码
- AOP 思想 — 拦截器在请求前后插入逻辑
- JWT 无状态认证 — 相比 Session 认证的区别和优势
- BCrypt 加密 — 为什么不用 MD5 存密码
下一步学习方向
- Spring Security — 更强大的安全框架,支持 OAuth2、RBAC 等
- Spring Cache + Redis — 缓存加速数据库查询
- Spring Validation — @Valid 注解自动校验请求参数
- MyBatis — 另一种 ORM 方式,适合复杂查询
- 单元测试 — JUnit + Mockito 测试 Spring Boot 应用
