目录
- 🏗️一、表设计
-
- 1、User、Article、Comment表结构
- [2、SQL 语句](#2、SQL 语句)
- 🏗️二、新建项目,实现注册+登录接口
-
- [1. 编写User实体类 User](#1. 编写User实体类 User)
- [2. 编写Mapper接口 UserMapper](#2. 编写Mapper接口 UserMapper)
- [3. 业务逻辑层 UserService](#3. 业务逻辑层 UserService)
- [4. 控制器层 UserController](#4. 控制器层 UserController)
- [5. 测试(Apifox)](#5. 测试(Apifox))
- 🏗️三、JWT登录鉴权模块
-
- [1、JWT 模块核心目标](#1、JWT 模块核心目标)
- 2、模块结构设计
- 3、引入依赖(pom.xml)
- 4、核心逻辑代码框架
- [5、测试步骤(用 Apifox)](#5、测试步骤(用 Apifox))
- [💾四、 学习重点(面试能讲的点)](#💾四、 学习重点(面试能讲的点))
-
- 1、项目基础类问题:
- 2、安全与加密相关
- [3、Spring 相关问题](#3、Spring 相关问题)
- 4、数据库设计类问题
🔅提前看:
- 本文基于前几章的学习基础,开始正式写一个"多用户博客系统"项目。
- 用到bcrypt密码加密、JWT鉴权、apifox测试。
- 最后附面试可能询问的问题。适合初学者快速上手。
🏗️一、表设计
1、User、Article、Comment表结构



2、SQL 语句
sql
use blog_system_database;
CREATE TABLE user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(100) NOT NULL,
email VARCHAR(100),
role VARCHAR(20) DEFAULT 'user',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 文章表
CREATE TABLE post (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
title VARCHAR(200) NOT NULL,
content TEXT NOT NULL,
status VARCHAR(20) DEFAULT 'published', -- draft/published
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES user(id)
);
-- 评论表
CREATE TABLE comment (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
post_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
parent_id BIGINT DEFAULT NULL, -- 回复谁的评论
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (post_id) REFERENCES post(id),
FOREIGN KEY (user_id) REFERENCES user(id)
);
🏗️二、新建项目,实现注册+登录接口
这是整个项目的骨架,后面所有模块都照这个逻辑来写:
密码加密使用bcrypt,登陆时使用JWT鉴权。
1. 编写User实体类 User
java
package com.example.blog.entity;
import lombok.Data;
import java.time.LocalDateTime;
@Data // 💡 Lombok 自动生成 getter/setter/toString
public class User {
private Long id; // 主键
private String username; // 用户名
private String password; // 密码(存加密后的)
private String email; // 邮箱
private String role; // 角色(user / admin)
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
💡 核心点:
密码不存明文,注册时我们会加密。
createdAt / updatedAt 不用手动填,MySQL 自动生成。
2. 编写Mapper接口 UserMapper
java
package com.example.blog.mapper;
import com.example.blog.entity.User;
import org.apache.ibatis.annotations.*;
@Mapper
public interface UserMapper {
@Insert("INSERT INTO user(username, password, email, role) VALUES(#{username}, #{password}, #{email}, #{role})")
void insert(User user);
@Select("SELECT * FROM user WHERE username = #{username}")
User findByUsername(String username);
}
💡 核心点:
这里用 MyBatis 注解版,简单直接;
注册用户插入数据;
登录时根据用户名查找。
3. 业务逻辑层 UserService
java
package com.example.blog.service;
import com.example.blog.entity.User;
import com.example.blog.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
private final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
// 💡 注册:加密密码再存数据库
public void register(User user) {
user.setPassword(encoder.encode(user.getPassword()));
user.setRole("user");
userMapper.insert(user);
}
// 💡 登录:验证用户名存在 + 密码匹配
public boolean login(String username, String rawPassword) {
User dbUser = userMapper.findByUsername(username);
if (dbUser == null) return false;
return encoder.matches(rawPassword, dbUser.getPassword());
}
}
💡 核心点:
BCryptPasswordEncoder 是 Spring 自带加密器;
matches 方法会自动加盐校验;
永远不要自己用 MD5 或手写加密算法。
4. 控制器层 UserController
java
package com.example.blog.controller;
import com.example.blog.entity.User;
import com.example.blog.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
// 💡 注册接口
@PostMapping("/register")
public String register(@RequestBody User user) {
userService.register(user);
return "注册成功";
}
// 💡 登录接口
@PostMapping("/login")
public String login(@RequestBody User user) {
boolean success = userService.login(user.getUsername(), user.getPassword());
return success ? "登录成功" : "用户名或密码错误";
}
}
💡 核心点:
用 @RestController 直接返回字符串;
前端(或 Apifox)请求时用 POST + JSON;
示例 JSON:
{
"username": "Alice",
"password": "123456",
"email": "test@example.com"
}
5. 测试(Apifox)
1.在config软件包下新建SecurityConfig.java文件:
java
package com.example.blog.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// 新写法:禁用 CSRF
.csrf(csrf -> csrf.disable())
// 放行注册和登录接口
.authorizeHttpRequests(auth -> auth
.requestMatchers("/user/register", "/user/login").permitAll()
.anyRequest().authenticated()
)
// 不使用默认登录页
.formLogin(form -> form.disable())
// 不使用 HTTP Basic 认证
.httpBasic(basic -> basic.disable());
return http.build();
}
}
2.在apifox上新建注册和登录接口进行测试。


🏗️三、JWT登录鉴权模块
1、JWT 模块核心目标
JWT(JSON Web Token)是用来做 登录状态保持 的。
思路就是:
用户登录成功 → 服务端签发一个 token → 前端保存 →
后续请求时带上 token → 后端校验 → 放行请求。
2、模块结构设计
我们会增加以下几个部分(共四步)👇:
| 模块 | 作用 |
|---|---|
JwtUtil |
负责创建 & 解析 token |
JwtFilter |
拦截请求、校验 token 是否有效 |
LoginController |
登录成功后签发 token |
SecurityConfig |
注册过滤器,让接口受保护 |
3、引入依赖(pom.xml)
先确认你加上 JWT 依赖(Spring Boot 没自带):
xml
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- 用于JSON序列化 -->
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
4、核心逻辑代码框架
- JwtUtil ------ 工具类
生成 & 解析 JWT 的地方。
java
package com.example.demo.utils;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import java.util.Date;
public class JwtUtil {
private static final Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
private static final long EXPIRATION_TIME = 1000 * 60 * 60; // 1小时
public static String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(key)
.compact();
}
public static String parseToken(String token) {
try {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody()
.getSubject();
} catch (JwtException e) {
return null; // token 无效或过期
}
}
}
- 登录接口中签发 Token
修改你的UserController登录部分:
java
@PostMapping("/login")
public Result<String> login(@RequestBody User user) {
boolean success = userService.login(user.getUsername(), user.getPassword());
if (success) {
String token = JwtUtil.generateToken(user.getUsername());
return Result.success(token); // 登录成功返回 token
} else {
return Result.error("用户名或密码错误");
}
}
补充一个通用结果返回类Result,在UserController中导入
java
//com.example.blog.entity
package com.example.blog.entity;
/**
* 通用返回结果类,用于封装接口响应。
* @param <T> 数据类型
*/
public class Result<T> {
private Integer code; // 状态码:0成功,1失败
private String msg; // 提示信息
private T data; // 返回数据
// 构造函数
public Result(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
// 静态方法:快速返回成功
public static <T> Result<T> success(T data) {
return new Result<>(0, "success", data);
}
// 静态方法:快速返回错误
public static <T> Result<T> error(String msg) {
return new Result<>(1, msg, null);
}
// Getter / Setter
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
- 过滤器 JwtFilter
拦截请求,验证 header 中的 token:
java
package com.example.demo.filter;
import com.example.demo.utils.JwtUtil;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JwtFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
String path = req.getRequestURI();
// 注册、登录接口不拦截
if (path.contains("/register") || path.contains("/login")) {
chain.doFilter(request, response);
return;
}
String token = req.getHeader("Authorization");
if (token == null || JwtUtil.parseToken(token) == null) {
res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
res.getWriter().write("Unauthorized");
return;
}
chain.doFilter(request, response);
}
}
- 注册过滤器(配置类)
java
package com.example.demo.config;
import com.example.demo.filter.JwtFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<JwtFilter> jwtFilter() {
FilterRegistrationBean<JwtFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new JwtFilter());
bean.addUrlPatterns("/*"); // 拦截所有请求
bean.setOrder(1);
return bean;
}
}
5、测试步骤(用 Apifox)
1️⃣ 调用 /user/login → 得到一个 token(字符串)
→ 如果 token 正确,就能访问;否则返回 401。

- 目前项目结构:

💾四、 学习重点(面试能讲的点)
1、项目基础类问题:
💬 Q1:你这个项目的整体架构是怎样的?
A:
我这个项目是基于 Spring Boot 的三层架构,分为 Controller、Service、Mapper 三层:
Controller:负责接收前端请求、返回响应;
Service:封装业务逻辑;
Mapper(或DAO):通过 MyBatis 操作数据库;
另外我还设计了统一响应类 Result,保证接口返回格式一致,并用 JWT 实现用户登录状态管理。
💬 Q2:注册、登录接口的实现流程是怎样的?
A:
注册:
前端传入用户名、密码、邮箱 → 后端先检查用户名是否重复 → 用 BCryptPasswordEncoder 加密密码 → 存入数据库。
登录:
用户输入账号密码 → 从数据库查出用户 → 用 BCrypt 校验密码 → 如果正确,用 JwtUtil 生成 token 返回前端。
💬 Q3:你为什么选择 JWT 做登录认证?
A:
因为 JWT(JSON Web Token)是一种无状态认证方式,不需要服务器存储 session,比较适合前后端分离项目。
每次请求只要在 Header 里带上 token,后端就能校验身份,提高扩展性和性能。
2、安全与加密相关
💬 Q4:为什么要用 BCrypt,而不是 MD5?
A:
MD5 速度快但不安全,容易被暴力破解。
BCrypt 自带随机盐值和多次加密机制,即使密码相同也会得到不同密文,安全性更高,是 Spring Security 推荐的方案。
💬 Q5:JWT 的结构是什么样的?
A:
JWT 由三部分组成,用点 . 分隔:
Header.Payload.Signature
例如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VybmFtZSI6IkFsaWNlIn0.
y6jQLFXo7T3lKkODv-h8Z...
Header(头部):说明算法(如 HS256)
Payload(载荷):存放用户信息
Signature(签名):用密钥签名保证数据没被篡改
💬 Q6:token 原理是什么?为什么不用 Session?
A:
Token 是一种无状态认证机制。
当用户登录成功后,后端生成一个 token(通常是 JWT),里面包含了用户信息(如用户名、过期时间),并用密钥签名。
前端拿到 token 后会保存在 localStorage 或 cookie 里,每次请求都在 Header 里携带这个 token。
后端通过验证签名即可确认身份,而不需要保存 session。

3、Spring 相关问题
💬 Q7:Spring Boot 是怎么启动项目的?
A:
项目入口是 @SpringBootApplication 注解标注的主类(比如 BlogApplication)。
Spring Boot 在启动时会自动扫描同包下的所有组件(Controller、Service、Mapper 等),完成依赖注入。
💬 Q8:@Autowired 和 @Resource 有什么区别?
A:
@Autowired 是 Spring 的注解,按类型注入;
@Resource 是 JSR 标准注解,默认按名称注入;
在单实例 Bean 下效果类似,但推荐用 @Autowired 搭配 @Qualifier 更灵活。
💬 Q9:过滤器和拦截器的区别
A:
"Filter 属于 Servlet 层,适合做登录鉴权这种全局校验;
Interceptor 属于 Spring 层,更适合做权限或日志处理。"

4、数据库设计类问题
💬 Q10:你在设计用户表时考虑了哪些字段?
A:
id(主键)
username(唯一索引)
password(BCrypt 加密后)
email
created_at、updated_at(记录时间)
这样既能满足基本功能,也方便后续扩展,比如角色、状态、头像字段等。