Spring Boot 4.x 博客系统实战项目笔记
技术栈:JDK 17 + Spring Boot 4.1.1 + MyBatis 3.x + Spring Security 6.x + Thymeleaf 3.x 前置知识:具备 Java 基础,了解 Spring Boot 核心注解、配置文件、三层架构 定位:从 0 到 1 搭建完整个人博客系统,同时巩固 Spring Boot 核心知识点,适配最新 4.x 行业规范
一、项目整体概述
1.1 功能定位
本项目是一套前后端不分离的个人博客系统,包含两大业务模块:
- 前台用户端:文章浏览、评论互动、文章搜索、站点统计展示
- 后台管理端:文章发布编辑、评论管理、用户权限管理、数据统计查看
- 扩展能力:定时邮件统计、Redis 热点缓存、权限分级控制、XSS 安全防护
1.2 技术架构全景
| 分层 | 技术选型 | 版本 | 核心作用 |
|---|---|---|---|
| 前端模板 | Thymeleaf | 3.x | 页面渲染、数据绑定,配合 Spring Security 标签做权限控制 |
| 控制层 | Spring MVC | 6.x | 请求映射、参数接收、结果返回,基于 Jakarta Servlet 6.0 规范 |
| 核心框架 | Spring Boot | 4.1.x | 自动配置、依赖管理、组件装配,项目基座 |
| 持久层 | MyBatis + PageHelper | 3.x / 2.1.x | 数据库操作、分页查询,注解 + XML 混合开发 |
| 连接池 | Druid | 1.2.x | 数据库连接池,监控、性能优化 |
| 缓存 | Spring Data Redis | 3.x | 热点文章缓存、降低数据库压力 |
| 安全认证 | Spring Security | 6.x | 登录认证、权限控制、CSRF 防护、密码加密 |
| 邮件服务 | Spring Boot Mail | 4.x | 定时发送站点统计邮件 |
| 定时任务 | Spring Scheduling | 内置 | 注解式定时任务,无需额外中间件 |
| 数据库 | MySQL | 8.x | 业务数据持久化存储 |
1.3 4.x 版本核心升级点(新手必知)
这是从旧版升级最核心的变化,也是面试常考点:
- 包名全面迁移:
javax.\*→jakarta.\*- Java EE 正式更名为 Jakarta EE,所有 Servlet、校验、安全相关的包全部改名
- 比如
javax.servlet.http.HttpServletRequest→jakarta.servlet.http.HttpServletRequest - ⚠️ 新手坑:旧代码直接复制过来会报类找不到,就是因为包名变了
- 配置方式组件化
- 废弃了
WebSecurityConfigurerAdapter等继承式配置 - 全面改用 声明 Bean 的组件化配置,更灵活、易扩展
- 对应之前学的
@Configuration + @Bean配置模式
- 废弃了
- JDK 基线提升至 17
- 最低要求 JDK 17,支持虚拟线程、AOT 编译等新特性
- 性能、安全性、语法能力全面提升
- 依赖版本统一升级 所有第三方 Starter 同步升级至适配 Spring Boot 4.x 的版本,避免版本冲突。
二、项目设计与结构
2.1 开发与运行环境
| 类别 | 工具/版本 |
|---|---|
| 操作系统 | Windows / Linux |
| JDK | 17 LTS |
| 项目构建 | Maven 3.9+ |
| 开发工具 | IntelliJ IDEA 2023+ |
| 数据库 | MySQL 8.x |
| 缓存中间件 | Redis 6.x |
2.2 标准文件结构
对应经典三层架构,按职责分包,结构清晰,符合企业开发规范:
blog_system/
├── src/main/java/com/itheima/
│ ├── config/ # 配置类 → @Configuration 标注,放各种自定义配置
│ │ ├── RedisConfig.java # Redis 序列化、缓存管理器配置
│ │ └── SecurityConfig.java # Spring Security 安全规则配置
│ ├── model/domain/ # 实体类 → 和数据库表一一对应
│ ├── dao/ # 数据访问层 → MyBatis Mapper,操作数据库
│ ├── service/ # 业务层 → 业务逻辑、事务控制、缓存逻辑
│ ├── web/ # 控制层 → 接收请求、调用业务、返回结果
│ │ ├── client/ # 前台用户接口
│ │ ├── admin/ # 后台管理接口
│ │ └── interceptor/ # 自定义拦截器
│ ├── utils/ # 工具类 → 通用工具方法
│ └── scheduletask/ # 定时任务 → @Scheduled 定时任务
├── src/main/resources/
│ ├── mapper/ # MyBatis XML 映射文件
│ ├── static/ # 静态资源:CSS、JS、图片
│ ├── templates/ # Thymeleaf 模板页面
│ ├── application.yml # 主配置文件
│ ├── application-jdbc.properties # 数据源配置(按模块拆分)
│ ├── application-redis.properties # Redis 配置
│ └── application-mail.properties # 邮件服务配置
└── pom.xml # Maven 依赖管理2.3 数据库设计
共 5 张核心业务表 + 1 张关联表:
t_article:文章表,存文章标题、内容、作者、发布时间t_comment:评论表,存评论内容、关联文章、评论人t_statistic:统计表,存访问量、评论量等统计数据t_user:用户表,存用户名、密码、状态t_authority:权限表,存权限标识、权限名称t_user_authority:用户-权限关联表,多对多关系
三、环境搭建与核心依赖
3.1 数据库准备
- 新建数据库
blog_system,字符集设为utf8mb4 - 执行数据库脚本,创建表并初始化管理员账号、测试文章数据
3.2 pom.xml 全解(对应核心知识点)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="[http://maven.apache.org/POM/4.0.0](http://maven.apache.org/POM/4.0.0)"
xmlns:xsi="[http://www.w3.org/2001/XMLSchema-instance](http://www.w3.org/2001/XMLSchema-instance)"
xsi:schemaLocation="[http://maven.apache.org/POM/4.0.0](http://maven.apache.org/POM/4.0.0)
[http://maven.apache.org/xsd/maven-4.0.0.xsd](http://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>4.1.1</version>
</parent>
<groupId>com.itheima</groupId>
<artifactId>blog_system</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>blog_system</name>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 1. Web 核心 Starter:包含 Spring MVC、Tomcat、JSON 处理 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 2. 持久层:Druid 连接池 Starter,适配 Spring Boot 4.x -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.20</version>
</dependency>
<!-- MyBatis Starter:自动配置 SqlSessionFactory、Mapper 扫描 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<!-- 分页插件 Starter -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<!-- MySQL 驱动:运行时依赖,编译不需要 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 3. 缓存 Starter:自动配置 RedisTemplate、连接工厂 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 4. 模板引擎 Starter:自动配置 Thymeleaf 视图解析器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Thymeleaf 适配 Spring Security 6.x 标签库 -->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>
<!-- 5. 安全 Starter:认证、授权、加密 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 6. 邮件 Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- 7. 工具类 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- Markdown 转 HTML -->
<dependency>
<groupId>com.atlassian.commonmark</groupId>
<artifactId>commonmark</artifactId>
<version>0.11.0</version>
</dependency>
<!-- Emoji 表情解析 -->
<dependency>
<groupId>com.vdurmont</groupId>
<artifactId>emoji-java</artifactId>
<version>5.1.1</version>
</dependency>
<!-- 8. 开发工具:热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 测试 Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Spring Boot 打包插件:打成可执行 JAR 包 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>💡 对应之前的知识点:
- 所有 Starter 都遵循「约定大于配置」,引入即自动配置
- 继承父工程后,官方 Starter 不用写版本号,自动版本兼容
scope控制依赖生效范围,runtime只运行时需要,编译不需要
3.3 配置文件体系
采用主配置 + 按模块拆分的方式,结构清晰便于维护,对应之前学的多配置文件加载。
主配置 application.yml
server:
port: 80
spring:
profiles:
# 激活 3 个外置配置文件,逗号分隔
active: jdbc,redis,mail
thymeleaf:
cache: false # 开发环境关闭页面缓存,改完立即生效
encoding: UTF-8
messages:
basename: i18n.logo
# MyBatis 配置
mybatis:
configuration:
map-underscore-to-camel-case: true # 下划线自动转驼峰,不用手动映射
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.itheima.model.domain
# 分页插件配置
pagehelper:
helper-dialect: mysql
reasonable: true
support-methods-arguments: true
params: count=countSql
# Cookie 有效期配置
COOKIE:
VALIDITY: 1800 # 30分钟分模块外置配置
application-jdbc.properties:数据源连接配置、连接池参数application-redis.properties:Redis 地址、端口、超时配置application-mail.properties:邮件服务器、账号、授权码配置
✅ 最佳实践:按业务模块拆分配置,修改某一类配置不用动主文件,职责清晰。
四、核心业务模块实现
4.1 分层架构设计
经典三层架构,职责单一,便于维护和团队协作,对应之前学的分层思想:
浏览器请求 → Controller → Service → Mapper → 数据库- Controller:接收请求、参数校验、调用业务、返回结果,不写业务逻辑
- Service:核心业务逻辑、事务控制、缓存处理
- Mapper:纯数据库操作,增删改查
4.2 数据访问层(DAO/Mapper)
采用注解为主、XML 为辅的开发方式:
- 简单单表 SQL:直接用
@Select、@Insert、@Update注解写在 Mapper 接口上 - 复杂动态 SQL、多表关联:写在
resources/mapper/下的 XML 文件里
@Mapper
public interface ArticleMapper {
// 简单查询用注解
@Select("select * from t_article where id = #{id}")
Article getById(Long id);
// 复杂查询用XML
List<Article> listByCondition(ArticleQuery query);
}⚠️ 新手注意:
@Mapper注解标记接口,MyBatis 自动生成实现类,交给 Spring 管理。
4.3 业务层(Service)
核心处理业务逻辑、事务控制、缓存策略。
@Transactional声明式事务:写操作加事务,出错自动回滚- Redis 缓存策略:读操作先查缓存,缓存未命中查数据库,结果回写缓存;写操作先更数据库,再删缓存,保证数据一致性
@Service
public class ArticleServiceImpl implements ArticleService {
@Autowired
private ArticleMapper articleMapper;
@Autowired
private RedisTemplate redisTemplate;
@Override
public Article getById(Long id) {
// 1. 先查缓存
String key = "article:" + id;
Article article = (Article) redisTemplate.opsForValue().get(key);
if (article == null) {
// 2. 缓存未命中,查数据库
article = articleMapper.getById(id);
// 3. 结果写入缓存
redisTemplate.opsForValue().set(key, article, 1, TimeUnit.DAYS);
}
return article;
}
@Override
@Transactional
public void update(Article article) {
// 1. 更新数据库
articleMapper.updateById(article);
// 2. 删除缓存(下次查询自动加载最新)
redisTemplate.delete("article:" + article.getId());
}
}4.4 控制层(Controller)
分前台、后台两个包,分别对应用户端和管理端。
- 所有 Servlet API 导入全部替换为
jakarta.servlet.* - 请求映射、参数绑定、页面跳转逻辑和旧版完全兼容
// 后台文章管理控制器
@Controller
@RequestMapping("/admin/article")
public class AdminArticleController {
@Autowired
private ArticleService articleService;
// 文章列表页面
@GetMapping("/list")
public String list(@RequestParam(defaultValue = "1") int pageNum,
Model model) {
PageInfo<Article> pageInfo = articleService.pageList(pageNum, 10);
model.addAttribute("page", pageInfo);
return "admin/article_list";
}
}五、Redis 缓存集成
5.1 为什么用缓存
- 热点文章频繁访问,直接查数据库压力大
- 缓存存内存,访问速度是数据库的几十上百倍
- 保护数据库,降低并发压力
5.2 自定义配置类详解
对应之前学的 @Configuration + @Bean 配置模式,自定义 Redis 配置解决默认序列化的问题。
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
/**
* 自定义 RedisTemplate,统一用 Jackson JSON 序列化
* 为什么不直接用默认?默认是 JDK 序列化,存进去是二进制,可读性差、跨语言不兼容
*/
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// Jackson JSON 序列化器
Jackson2JsonRedisSerializer<Object> jacksonSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSerializer.setObjectMapper(om);
template.setDefaultSerializer(jacksonSerializer);
return template;
}
/**
* 自定义缓存管理器:设置缓存有效期、空值不缓存
*/
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisSerializer<String> strSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer<Object> jacksonSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofDays(7)) // 缓存默认7天过期
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(strSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jacksonSerializer))
.disableCachingNullValues(); // 空值不缓存,防止缓存穿透
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(config)
.build();
}
}六、Spring Security 安全认证(4.x 组件化版)
6.1 安全框架核心能力
- 认证:验证用户名密码对不对,能不能登录
- 授权:登录后能访问哪些页面、哪些接口
- 密码加密:数据库存加密后的密码,不存明文
- CSRF 防护:防止跨站请求伪造攻击
- 记住我:Cookie 持久化登录状态,不用每次都登录
6.2 4.x 重大变化
Spring Boot 3.0 之后,正式废弃 WebSecurityConfigurerAdapter 继承式配置,改用组件化 Bean 配置:
- 旧版:继承类,重写方法
- 新版:声明
SecurityFilterChainBean,链式配置规则 - 优势:更灵活、易扩展、符合 Spring Bean 设计思想
6.3 配置类逐段详解
@Configuration
@EnableMethodSecurity // 开启方法级权限注解
public class SecurityConfig {
@Value("${COOKIE.VALIDITY}")
private Integer COOKIE_VALIDITY;
/**
* 核心:安全过滤器链,所有安全规则都在这里配置
*/
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// 1. 请求权限控制:哪些路径放行、哪些需要权限
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/page/**", "/article/**", "/login").permitAll() // 公开路径
.requestMatchers("/admin/**").hasRole("admin") // 后台需要admin角色
.anyRequest().authenticated() // 其他都需要登录
);
// 2. 自定义登录表单
http.formLogin(form -> form
.loginPage("/login") // 自定义登录页
.usernameParameter("username")
.passwordParameter("password")
// 登录成功处理器:记住原路径跳转
.successHandler((request, response, authentication) -> {
// 逻辑:有原路径就跳原路径,没有就按角色跳转
})
// 登录失败处理器
.failureHandler((request, response, exception) -> {
response.sendRedirect("/login?error");
})
.permitAll()
);
// 3. 记住我:Cookie 持久化
http.rememberMe(remember -> remember
.alwaysRemember(true)
.tokenValiditySeconds(COOKIE_VALIDITY)
);
// 4. 退出登录
http.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.permitAll()
);
// 5. 异常处理:403 权限不足跳转到错误页
http.exceptionHandling(ex -> ex
.accessDeniedHandler((request, response, accessDeniedException) -> {
request.getRequestDispatcher("/error/403").forward(request, response);
})
);
return http.build();
}
/**
* JDBC 用户认证:从数据库查用户、权限
*/
@Bean
public UserDetailsService userDetailsService(DataSource dataSource) {
JdbcUserDetailsManager manager = new JdbcUserDetailsManager(dataSource);
// 用户查询 SQL
manager.setUsersByUsernameQuery(
"select username,password,valid from t_user where username = ?");
// 权限查询 SQL
manager.setAuthoritiesByUsernameQuery(
"select u.username,a.authority from t_user u,t_authority a,t_user_authority ua " +
"where ua.user_id=u.id and ua.authority_id=a.id and u.username = ?");
return manager;
}
/**
* 密码加密器:BCrypt 强哈希加密
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}⚠️ 新手坑:Spring Security 默认会生成随机密码,配置了自定义
UserDetailsService才会走数据库认证。
七、定时任务与邮件服务
7.1 定时任务
Spring 内置注解式定时任务,不用额外引入组件。
开启定时任务
启动类上加 @EnableScheduling 注解开启功能。
定时任务实现
@Component
public class ScheduleTask {
@Autowired
private StatisticMapper statisticMapper;
@Autowired
private MailUtils mailUtils;
@Value("${spring.mail.username}")
private String mailto;
/**
* 每月1日中午12点发送站点统计邮件
* cron表达式:秒 分 时 日 月 周
*/
@Scheduled(cron = "0 0 12 1 * ?")
public void sendStatisticsEmail(){
long totalVisit = statisticMapper.getTotalVisit();
long totalComment = statisticMapper.getTotalComment();
String content = "博客总访问量:" + totalVisit + "人次\n"
+ "博客总评论量:" + totalComment + "人次";
mailUtils.sendSimpleEmail(mailto, "博客月度流量统计", content);
}
}7.2 邮件服务
Spring Boot 自动封装 JavaMailSender,配置好邮箱参数即可发送简单文本、附件邮件。
八、拦截器与安全防护
8.1 全局拦截器配置
对应之前学的 WebMvcConfigurer + 拦截器 知识点,用于全局请求处理。
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private BaseInterceptor baseInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册全局拦截器,注入公共数据供页面使用
registry.addInterceptor(baseInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/css/**", "/js/**", "/images/**");
}
}8.2 XSS 防护
对用户输入的评论、文章内容进行 HTML 标签转义、脚本过滤,防止恶意脚本注入攻击,是生产项目必备的安全措施。
8.3 核心工具类
- Commons 工具:日期格式化、字符串处理、摘要截取
- MyUtils:HTML 转纯文本、Markdown 转 HTML、XSS 过滤
- MailUtils:邮件发送封装
九、版本迁移与技术总结
9.1 2.x → 4.x 核心迁移对照表
| 类别 | 2.x 旧版 | 4.x 新版 |
|---|---|---|
| JDK 版本 | JDK 8 | JDK 17 LTS |
| EE 规范 | javax.* | jakarta.*(Jakarta EE 9+) |
| Spring Security | 继承 WebSecurityConfigurerAdapter | 声明 SecurityFilterChain Bean |
| MySQL 驱动 | com.mysql.jdbc.Driver | com.mysql.cj.jdbc.Driver |
| Thymeleaf 安全标签 | thymeleaf-extras-springsecurity5 | thymeleaf-extras-springsecurity6 |
| 配置风格 | 继承式为主 | 组件化 Bean 为主 |
9.2 项目核心技术点总结
- 经典三层架构:职责分离,结构清晰,可维护性强
- MyBatis 最佳实践:注解 + XML 混合,简单和复杂场景兼顾
- Redis 缓存设计:热点数据缓存、更新失效策略、自定义 JSON 序列化
- Spring Security 安全体系:JDBC 认证、权限控制、CSRF 防护、组件化配置
- 生产级细节:XSS 防护、Markdown 解析、表情处理
- 定时任务体系:注解式定时 + 邮件服务,运营数据自动推送
- 模块化配置:按业务拆分配置文件,便于管理
9.3 入门学习建议
- 先跑通项目:先把项目启动、功能跑通,建立整体认知
- 再分层拆解:按 Controller → Service → Mapper 逐层理解每个部分的作用
- 对应基础知识点:每个组件对应之前学的注解、配置原理,理解为什么这么写
- 动手修改:试着改功能、加功能,比如新增分类、点赞功能,练手才是真学会
十、新手常见问题排查
- 启动报类找不到:javax.servlet... → 4.x 包名改成 jakarta 了,所有导入替换成
jakarta.servlet.* - Spring Security 登录一直失败 → 检查密码是不是明文,数据库必须存 BCrypt 加密后的密码 → 检查权限查询 SQL,权限前缀必须带
ROLE_ - Redis 连接失败 → 检查本地 Redis 有没有启动 → 检查配置文件地址、端口、密码对不对
- 页面访问 403 → 路径不在放行列表里,需要登录或者对应权限 → 检查 CSRF 配置,表单提交必须带 CSRF Token
- MyBatis 字段映射不上 → 检查是否开启了下划线转驼峰配置 → 检查字段名和属性名是否对应
十、实战核心补充(入门必掌握)
10.1 启动类与包扫描避坑
启动类标准写法
package com.itheima;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling // 开启定时任务,必须标注在启动类上
public class BlogSystemApplication {
public static void main(String[] args) {
SpringApplication.run(BlogSystemApplication.class, args);
}
}核心避坑点
- 启动类必须放在根包下:所有业务包(
controller/service/dao等)都必须是启动类所在包的子包。Spring Boot 默认只扫描启动类所在包及其子包,放错位置会导致所有组件扫描不到,接口全报 404。 - 自定义包扫描:如果启动类位置特殊,可手动补充
@ComponentScan(basePackages = "com.itheima")指定扫描范围。 - 注解对应关系:
@SpringBootApplication是组合注解,对应三大核心能力:@SpringBootConfiguration:标记启动类本身是配置类@EnableAutoConfiguration:开启自动配置(Spring Boot 核心)@ComponentScan:组件包扫描
10.2 全局统一异常处理(企业标配)
博客系统包含前台页面和后台接口,异常不能直接抛出给用户,必须统一封装处理,避免暴露堆栈信息、提升体验。
为什么重要
- 前台页面:异常跳转到友好错误页,不暴露技术细节
- 后台接口:统一返回错误格式,前端统一处理
- 日志留痕:所有异常统一记录日志,便于线上排查
代码实现
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
// 后台接口专用:返回JSON格式异常
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
// 1. 业务异常:预期内的业务错误(文章不存在、评论非法等)
@ExceptionHandler(BizException.class)
public Result<Void> handleBizException(BizException e) {
log.warn("业务异常:{}", e.getMessage());
return Result.fail(e.getCode(), e.getMessage());
}
// 2. 参数校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<Void> handleValidException(MethodArgumentNotValidException e) {
String msg = e.getBindingResult().getFieldError().getDefaultMessage();
return Result.fail(400, "参数错误:" + msg);
}
// 3. 系统异常:兜底处理,不暴露堆栈
@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception e) {
log.error("系统异常", e);
return Result.fail(500, "系统繁忙,请稍后重试");
}
}自定义业务异常类
public class BizException extends RuntimeException {
private Integer code;
public BizException(String message) {
super(message);
this.code = 500;
}
public BizException(Integer code, String message) {
super(message);
this.code = code;
}
public Integer getCode() { return code; }
}本项目应用:文章不存在、评论内容违规、权限不足等场景,直接抛出
BizException,全局统一处理返回。 如果是前台页面请求异常,可改用@ControllerAdvice+ModelAndView跳转到友好错误页。
10.3 @Transactional 事务避坑(写操作必看)
博客的文章发布、评论提交、统计更新都涉及多步数据库操作,必须用事务保证数据一致性。
项目中正确用法
@Service
public class ArticleServiceImpl implements ArticleService {
@Autowired
private ArticleMapper articleMapper;
@Autowired
private StatisticMapper statisticMapper;
// rollbackFor = Exception.class:所有异常都回滚
@Transactional(rollbackFor = Exception.class)
public void addArticle(Article article) {
// 1. 保存文章主体
articleMapper.insert(article);
// 2. 更新站点文章统计
statisticMapper.addArticleCount();
// 任何一步报错,整体回滚,不会出现一半成功一半失败
}
}高频失效场景(新手必记)
方法不是 public 修饰:事务基于 AOP 动态代理实现,非 public 方法无法被代理,事务不生效。
同类方法调用:同一个类里用
this.方法名()调用,走的是原始对象,不走代理对象,事务失效。例:Service 中 A 方法调用 B 方法,B 方法加了
@Transactional,事务不会生效。异常被 try-catch 吃掉:异常被手动捕获处理,事务没感知到异常,不会触发回滚。
没指定 rollbackFor:默认只回滚
RuntimeException,检查型异常(如IOException)不会回滚。最佳实践:统一写
@Transactional(rollbackFor = Exception.class)。数据库引擎不支持:MySQL MyISAM 引擎不支持事务,必须使用 InnoDB 引擎。
10.4 MyBatis 核心避坑:#{} 与 ${}
本质区别
| 写法 | 原理 | SQL注入风险 | 适用场景 |
|---|---|---|---|
#{id} | 预编译占位符,参数值当成字符串处理 | 无 | 绝大多数参数传递(id、标题、内容等) |
${name} | 字符串直接拼接,值原样拼进SQL | 有 | 必须动态拼接SQL的场景(表名、列名、排序字段) |
本项目应用示例
✅ 正确:普通参数用 #{}
@Select("select * from t_article where id = #{id}")
Article getById(Long id);✅ 模糊查询正确写法(避免注入)
@Select("select * from t_article where title like concat('%', #{keyword}, '%')")
List<Article> searchByKeyword(String keyword);❌ 错误写法:直接拼接参数,存在注入风险
@Select("select * from t_article where title like '%${keyword}%'") // 严禁这么写分页插件注意
PageHelper 的排序字段如果来自前端传入,必须做白名单校验,防止通过排序字段进行 SQL 注入。
10.5 Thymeleaf 实战常用语法
本项目用 Thymeleaf 做页面模板,以下是开发中最高频的语法:
1. 变量输出
<h1 th:text="${article.title}">默认标题</h1>
<p th:utext="${article.content}">文章内容</p>
<!-- utext:不转义HTML,用于渲染富文本内容 -->2. 循环遍历(文章列表)
<div class="article-item" th:each="article : ${articleList}">
<h3 th:text="${article.title}"></h3>
<span th:text="${#dates.format(article.createTime, 'yyyy-MM-dd')}"></span>
</div>3. 条件判断
<!-- 只有管理员角色才显示编辑按钮 -->
<button th:if="${#authorization.hasRole('admin')}" class="edit-btn">编辑</button>4. 表单提交(自动携带 CSRF)
<form th:action="@{/admin/article/add}" method="post">
<input type="text" name="title" placeholder="文章标题">
<textarea name="content"></textarea>
<button type="submit">发布</button>
<!-- Thymeleaf 自动生成 CSRF 隐藏域,无需手动编写 -->
</form>5. 链接路径
<!-- 自动拼接项目上下文路径 -->
<a th:href="@{/article/{id}(id=${article.id})}">查看全文</a>6. Spring Security 标签
<!-- 登录后才显示 -->
<div sec:authorize="isAuthenticated()">
欢迎你,<span sec:authentication="name"></span>
</div>10.6 日志配置与调试技巧
开发调试和线上排查都依赖日志,Spring Boot 默认集成 Logback。
配置日志级别
在 application.yml 中配置:
logging:
level:
com.itheima: debug # 自己的业务包设为 debug,看详细执行日志
org.mybatis: debug # 打印完整SQL、参数、结果,开发调试必备
root: info # 全局基础日志级别
file:
name: logs/blog.log # 日志输出到文件开发阶段开启 MyBatis debug 日志,可以直接在控制台看到执行的 SQL 语句、传入参数、返回行数,排查 SQL 问题效率极高。
正确使用日志
不要用 System.out.println,统一用 SLF4J 日志:
@Service
@Slf4j // Lombok 注解,自动生成 log 对象
public class ArticleServiceImpl {
public Article getById(Long id) {
log.debug("开始查询文章,id:{}", id);
Article article = articleMapper.getById(id);
log.info("文章查询成功,标题:{}", article.getTitle());
return article;
}
}10.7 生产环境部署要点
1. 多环境切换
对应之前学的 Profile 机制,区分开发和生产配置:
application-dev.yml:开发环境配置(关闭缓存、打开日志、本地数据库)application-prod.yml:生产环境配置(开启缓存、生产数据库、优化参数)- 启动命令:
java -jar blog-system.jar --spring.profiles.active=prod
2. 外置配置文件
生产环境的敏感配置(数据库密码、Redis 密码、邮箱密钥)不要打进 jar 包:
- 在 jar 包同级目录新建
config文件夹,放入application-prod.yml - 根据配置优先级,外置配置优先级高于 jar 包内,启动自动加载,修改配置不用重新打包。
3. 生产优化项
- 开启 Thymeleaf 缓存:
spring.thymeleaf.cache=true,大幅提升页面渲染性能 - 调优 Druid 连接池:根据服务器配置调整最大连接数、超时时间
- 日志按天切割,保留历史日志,配置日志清理策略
- 关闭 Actuator 敏感端点,只开放必要的监控端点
10.8 静态资源缓存优化
博客的图片、CSS、JS 等静态资源,合理配置缓存可显著提升访问速度:
spring:
web:
resources:
static-locations: classpath:/static/
cache:
period: 7d # 浏览器缓存静态资源7天,减少重复请求本项目应用:文章封面、前端样式、脚本文件都放在
static目录下,浏览器缓存后无需重复下载,减轻服务器压力。