课程概述

在完成了纯 JDK 原生的 Java 用户管理系统后,进入第二阶段——使用 Spring Boot 3.x 框架重写整个项目。从框架搭建到 JWT 认证,完整体验了企业级框架带来的开发效率提升。

核心变化

方面JDK 原生Spring Boot 版本
Web 服务器手写 HttpServer内置 Tomcat
路由分发手写 if/switch@RequestMapping 注解
数据库操作手写 JDBC + SQLJPA 自动 CRUD
密码存储明文BCrypt 加密
认证方式JWT Token + 拦截器
依赖管理手动下载 JARMaven 自动管理
代码行数~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:

操作手写 JDBCJPA Repository
创建表CREATE TABLE users (...)@Entity + 自动建表
新增INSERT INTO users VALUES (?, ?)repository.save(user)
查询所有SELECT * FROM usersrepository.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 — 自动注入容器中的 Bean
  • ResponseEntity — 可自定义 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 应用