Skip to content

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 技术架构全景 ​

分层技术选型版本核心作用
前端模板Thymeleaf3.x页面渲染、数据绑定,配合 Spring Security 标签做权限控制
控制层Spring MVC6.x请求映射、参数接收、结果返回,基于 Jakarta Servlet 6.0 规范
核心框架Spring Boot4.1.x自动配置、依赖管理、组件装配,项目基座
持久层MyBatis + PageHelper3.x / 2.1.x数据库操作、分页查询,注解 + XML 混合开发
连接池Druid1.2.x数据库连接池,监控、性能优化
缓存Spring Data Redis3.x热点文章缓存、降低数据库压力
安全认证Spring Security6.x登录认证、权限控制、CSRF 防护、密码加密
邮件服务Spring Boot Mail4.x定时发送站点统计邮件
定时任务Spring Scheduling内置注解式定时任务,无需额外中间件
数据库MySQL8.x业务数据持久化存储

1.3 4.x 版本核心升级点(新手必知) ​

这是从旧版升级最核心的变化,也是面试常考点:

  1. 包名全面迁移:javax.\* → jakarta.\*
    • Java EE 正式更名为 Jakarta EE,所有 Servlet、校验、安全相关的包全部改名
    • 比如 javax.servlet.http.HttpServletRequest → jakarta.servlet.http.HttpServletRequest
    • ⚠️ 新手坑:旧代码直接复制过来会报类找不到,就是因为包名变了
  2. 配置方式组件化
    • 废弃了 WebSecurityConfigurerAdapter 等继承式配置
    • 全面改用 声明 Bean 的组件化配置,更灵活、易扩展
    • 对应之前学的 @Configuration + @Bean 配置模式
  3. JDK 基线提升至 17
    • 最低要求 JDK 17,支持虚拟线程、AOT 编译等新特性
    • 性能、安全性、语法能力全面提升
  4. 依赖版本统一升级 所有第三方 Starter 同步升级至适配 Spring Boot 4.x 的版本,避免版本冲突。

二、项目设计与结构 ​

2.1 开发与运行环境 ​

类别工具/版本
操作系统Windows / Linux
JDK17 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 数据库准备 ​

  1. 新建数据库 blog_system,字符集设为 utf8mb4
  2. 执行数据库脚本,创建表并初始化管理员账号、测试文章数据

3.2 pom.xml 全解(对应核心知识点) ​

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 ​

yaml
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 文件里
java
@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 缓存策略:读操作先查缓存,缓存未命中查数据库,结果回写缓存;写操作先更数据库,再删缓存,保证数据一致性
java
@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.*
  • 请求映射、参数绑定、页面跳转逻辑和旧版完全兼容
java
// 后台文章管理控制器
@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 配置解决默认序列化的问题。

java
@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 配置:

  • 旧版:继承类,重写方法
  • 新版:声明 SecurityFilterChain Bean,链式配置规则
  • 优势:更灵活、易扩展、符合 Spring Bean 设计思想

6.3 配置类逐段详解 ​

java
@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 8JDK 17 LTS
EE 规范javax.*jakarta.*(Jakarta EE 9+)
Spring Security继承 WebSecurityConfigurerAdapter声明 SecurityFilterChain Bean
MySQL 驱动com.mysql.jdbc.Drivercom.mysql.cj.jdbc.Driver
Thymeleaf 安全标签thymeleaf-extras-springsecurity5thymeleaf-extras-springsecurity6
配置风格继承式为主组件化 Bean 为主

9.2 项目核心技术点总结 ​

  1. 经典三层架构:职责分离,结构清晰,可维护性强
  2. MyBatis 最佳实践:注解 + XML 混合,简单和复杂场景兼顾
  3. Redis 缓存设计:热点数据缓存、更新失效策略、自定义 JSON 序列化
  4. Spring Security 安全体系:JDBC 认证、权限控制、CSRF 防护、组件化配置
  5. 生产级细节:XSS 防护、Markdown 解析、表情处理
  6. 定时任务体系:注解式定时 + 邮件服务,运营数据自动推送
  7. 模块化配置:按业务拆分配置文件,便于管理

9.3 入门学习建议 ​

  1. 先跑通项目:先把项目启动、功能跑通,建立整体认知
  2. 再分层拆解:按 Controller → Service → Mapper 逐层理解每个部分的作用
  3. 对应基础知识点:每个组件对应之前学的注解、配置原理,理解为什么这么写
  4. 动手修改:试着改功能、加功能,比如新增分类、点赞功能,练手才是真学会

十、新手常见问题排查 ​

  1. 启动报类找不到:javax.servlet... → 4.x 包名改成 jakarta 了,所有导入替换成 jakarta.servlet.*
  2. Spring Security 登录一直失败 → 检查密码是不是明文,数据库必须存 BCrypt 加密后的密码 → 检查权限查询 SQL,权限前缀必须带 ROLE_
  3. Redis 连接失败 → 检查本地 Redis 有没有启动 → 检查配置文件地址、端口、密码对不对
  4. 页面访问 403 → 路径不在放行列表里,需要登录或者对应权限 → 检查 CSRF 配置,表单提交必须带 CSRF Token
  5. MyBatis 字段映射不上 → 检查是否开启了下划线转驼峰配置 → 检查字段名和属性名是否对应

十、实战核心补充(入门必掌握) ​

10.1 启动类与包扫描避坑 ​

启动类标准写法 ​

java
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 全局统一异常处理(企业标配) ​

博客系统包含前台页面和后台接口,异常不能直接抛出给用户,必须统一封装处理,避免暴露堆栈信息、提升体验。

为什么重要 ​

  • 前台页面:异常跳转到友好错误页,不暴露技术细节
  • 后台接口:统一返回错误格式,前端统一处理
  • 日志留痕:所有异常统一记录日志,便于线上排查

代码实现 ​

java
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, "系统繁忙,请稍后重试");
    }
}

自定义业务异常类 ​

java
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 事务避坑(写操作必看) ​

博客的文章发布、评论提交、统计更新都涉及多步数据库操作,必须用事务保证数据一致性。

项目中正确用法 ​

java
@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();
        // 任何一步报错,整体回滚,不会出现一半成功一半失败
    }
}

高频失效场景(新手必记) ​

  1. 方法不是 public 修饰:事务基于 AOP 动态代理实现,非 public 方法无法被代理,事务不生效。

  2. 同类方法调用:同一个类里用 this.方法名() 调用,走的是原始对象,不走代理对象,事务失效。

    例:Service 中 A 方法调用 B 方法,B 方法加了 @Transactional,事务不会生效。

  3. 异常被 try-catch 吃掉:异常被手动捕获处理,事务没感知到异常,不会触发回滚。

  4. 没指定 rollbackFor:默认只回滚 RuntimeException,检查型异常(如 IOException)不会回滚。

    最佳实践:统一写 @Transactional(rollbackFor = Exception.class)。

  5. 数据库引擎不支持: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 日志:

java
@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 目录下,浏览器缓存后无需重复下载,减轻服务器压力。

基于 Vite 强力驱动 | 纯静态轻量托管