小型图书管理系统案例(用于spring mvc 实践)

news2025/6/2 10:03:37

小型图书管理系统案例 (Spring MVC + Spring Data JPA + Thymeleaf)

本项目案例旨在基于先前模块学习的 Spring MVC 知识,构建一个贴近企业实际的简单 Web 应用:小型图书管理系统。通过实现图书的 CRUD 操作、列表展示(含分页概念)和简单用户认证,帮助初学者巩固和应用 Spring MVC 核心概念与技术。

1. 项目概述

  • 项目主题: 小型图书管理系统 (Small Book Management System)
  • 核心功能:
    • 图书列表展示 (带分页概念)
    • 图书详情查看
    • 新增图书
    • 编辑图书
    • 删除图书
    • 简单用户认证 (登录/注销)
  • 技术栈:
    • 构建工具:Maven
    • Web 框架:Spring MVC 5.x
    • ORM 框架:Spring Data JPA
    • 数据库:H2 (内嵌数据库,便于学习)
    • 模板引擎:Thymeleaf
    • 数据校验:Bean Validation (JSR 380) + Hibernate Validator
    • 日志:SLF4J + Logback
    • 辅助库:Lombok (可选,简化 POJO 代码)
  • Spring 配置方式: 完全基于 JavaConfig (对应模块一、四、六)
  • 部署方式: WAR 包部署到 Servlet 容器 (如 Tomcat)

2. 环境搭建与项目结构

2.1 Maven pom.xml 配置

使用 Maven 构建项目。创建一个新的 Maven Webapp 项目,并修改 pom.xml 文件,添加以下核心依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.yourcompany</groupId>
    <artifactId>book-management</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging> <!-- 打包方式为 WAR -->

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <spring.version>5.3.20</spring.version> <!-- Spring 版本 -->
        <thymeleaf.version>3.0.11.RELEASE</thymeleaf.version> <!-- Thymeleaf 版本 -->
        <thymeleaf-spring5.version>3.0.11.RELEASE</thymeleaf-spring5.version> <!-- Thymeleaf Spring 集成 -->
        <spring-data-jpa.version>2.7.2</spring-data-jpa.version> <!-- Spring Data JPA 版本 -->
        <hibernate.version>5.6.1.Final</hibernate.version> <!-- Hibernate 版本 (JPA 实现) -->
        <h2.version>1.4.200</h2.version> <!-- H2 数据库版本 -->
        <logback.version>1.2.11</logback.version> <!-- Logback 版本 -->
        <slf4j.version>1.7.36</slf4j> <!-- SLF4J 版本 -->
        <servlet.api.version>4.0.1</servlet.api.version> <!-- Servlet API 版本 -->
        <validation-api.version>2.0.1.Final</validation-api.version> <!-- Bean Validation API -->
        <hibernate-validator.version>6.2.0.Final</hibernate-validator.version> <!-- Bean Validation 实现 -->
        <lombok.version>1.18.24</lombok.version> <!-- Lombok (可选) -->
        <jackson.version>2.13.0</jackson.version> <!-- Jackson (用于可能的 JSON 处理或调试) -->
    </properties>

    <dependencies>
        <!-- Spring MVC -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- Spring Context (包含 IoC/DI) -->
         <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- Spring ORM (用于 JPA 集成) -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- Spring Data JPA -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>${spring-data-jpa.version}</version>
        </dependency>

        <!-- JPA Implementation (Hibernate) -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
        <!-- Hibernate EntityManager (JPA 规范实现) -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${hibernate.version}</version>
        </dependency>

        <!-- Database (H2 - for simplicity) -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>${h2.version}</version>
            <scope>runtime</scope> <!-- 运行时需要 -->
        </dependency>

        <!-- Thymeleaf for Spring MVC -->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf</artifactId>
            <version>${thymeleaf.version}</version>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
            <version>${thymeleaf-spring5.version}</version>
        </dependency>

        <!-- Servlet API (Provided by Tomcat/Servlet Container) -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>${servlet.api.version}</version>
            <scope>provided</scope>
        </dependency>

        <!-- JSTL (如果使用 JSP 需要) -->
        <!-- Thymeleaf 不需要 JSTL,这里不添加 -->

        <!-- Bean Validation API and Implementation -->
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>${validation-api.version}</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>${hibernate-validator.version}</version>
        </dependency>

        <!-- Logging -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>${logback.version}</version>
            <scope>runtime</scope>
        </dependency>

        <!-- Jackson (for JSON processing, useful if you add REST APIs later or for debugging) -->
         <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson.version}</version>
        </dependency>

        <!-- Lombok (Optional - Install Lombok plugin in IDE) -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>

        <!-- Test Dependencies (Optional) -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.8.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.3.2</version>
                 <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml> <!-- Servlet 3.0+ 可以不需要 web.xml -->
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>${maven.compiler.source}</source>
                    <target>${maven.compiler.target}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

说明:请根据实际情况调整依赖版本,并确保它们相互兼容。本项目使用 Java 8。

2.2 项目目录结构

遵循标准的 Maven 项目结构,并在此基础上为 Spring MVC 和 Thymeleaf 组织代码和资源文件。

.
├── pom.xml
└── src
    └── main
        ├── java
        │   └── com
        │       └── yourcompany
        │           └── bookmanagement
        │               ├── config          # Spring JavaConfig 配置类 (模块一, 四, 六)
        │               │   ├── AppConfig.java      # Root Context 配置 (DataSource, JPA, Service, Repo)
        │               │   └── WebMvcConfig.java   # Servlet Context 配置 (Controller, ViewResolver, Resources, Interceptor, Validation)
        │               ├── controller      # 控制器层 (模块三, 五, 六)
        │               │   ├── AuthController.java # 简单登录/注销
        │               │   └── BookController.java # 图书 CRUD
        │               ├── dto             # 数据传输对象 (用于表单绑定, 校验)
        │               │   └── BookDTO.java
        │               ├── entity          # 领域模型 (JPA 实体)
        │               │   ├── Book.java
        │               │   └── User.java
        │               ├── exception       # 自定义异常与全局异常处理 (模块六)
        │               │   ├── BookNotFoundException.java
        │               │   └── GlobalExceptionHandler.java
        │               ├── interceptor     # MVC 拦截器 (模块六)
        │               │   └── AuthInterceptor.java
        │               ├── repository    # 持久化层 (Spring Data JPA Repository)
        │               │   ├── BookRepository.java
        │               │   └── UserRepository.java
        │               └── service       # 业务逻辑层 (模块一)
        │                   ├── BookService.java
        │                   └── impl
        │                       ├── BookServiceImpl.java
        │                       └── UserServiceImpl.java
        ├── resources     # Spring 资源文件 (如 application.properties/yml - 本例用 JavaConfig 无需此文件, logback.xml 等)
        │   └── logback.xml
        └── webapp          # Web 应用根目录
            ├── WEB-INF
            │   └── templates   # Thymeleaf 模板文件 (根据 WebMvcConfig 中的前缀配置)
            │       ├── books
            │       │   ├── list.html
            │       │   ├── detail.html
            │       │   └── form.html
            │       └── auth
            │           └── login.html
            └── resources       # 静态资源 (CSS, JS, Images)
                └── css
                    └── style.css

说明:src/main/java 存放 Java 源代码,src/main/resources 存放配置和资源文件,src/main/webapp 存放 Web 相关文件,WEB-INF 下的内容不能通过浏览器直接访问,增加了安全性。Thymeleaf 模板建议放在 WEB-INF 下。

3. 领域模型与数据传输对象

3.1 Book.java (JPA 实体)

这是应用的核心领域对象,映射数据库中的图书表。使用 JPA 注解进行数据库映射。

package com.yourcompany.bookmanagement.entity;

import javax.persistence.*; // JPA 注解
import java.time.LocalDate; // 使用新日期 API
import java.util.Objects;

// 使用 Lombok 注解简化 boilerplate code (可选)
// import lombok.Getter;
// import lombok.Setter;
// import lombok.NoArgsConstructor;
// import lombok.AllArgsConstructor;

@Entity // 标记为 JPA 实体
@Table(name = "books") // 映射到数据库表 "books"
// @Getter // Lombok 注解,自动生成所有字段的 Getter
// @Setter // Lombok 注解,自动生成所有字段的 Setter
// @NoArgsConstructor // Lombok 注解,生成无参构造器
// @AllArgsConstructor // Lombok 注解,生成全参构造器
public class Book {

    @Id // 标记为主键
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 主键生成策略,IDENTITY 表示数据库自增长
    private Long id;

    @Column(nullable = false) // 映射到数据库列 "title",不能为空
    private String title;

    @Column(nullable = false) // 映射到数据库列 "author",不能为空
    private String author;

    @Column // 映射到数据库列 "isbn"
    private String isbn;

    @Column(name = "publication_date") // 映射到数据库列 "publication_date"
    private LocalDate publicationDate; // 出版日期

    // 手动添加构造器 (如果不用 Lombok 的 @NoArgsConstructor, @AllArgsConstructor)
    public Book() {
    }

    public Book(String title, String author, String isbn, LocalDate publicationDate) {
        this.title = title;
        this.author = author;
        this.isbn = isbn;
        this.publicationDate = publicationDate;
    }

    // 手动添加 Getter 和 Setter (如果不用 Lombok)
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }
    public String getAuthor() { return author; }
    public void setAuthor(String author) { this.author = author; }
    public String getIsbn() { return isbn; }
    public void setIsbn(String isbn) { this.isbn = isbn; }
    public LocalDate getPublicationDate() { return publicationDate; }
    public void setPublicationDate(LocalDate publicationDate) { this.publicationDate = publicationDate; }

    @Override
    public String toString() {
        return "Book{" +
               "id=" + id +
               ", title='" + title + '\'' +
               ", author='" + author + '\'' +
               ", isbn='" + isbn + '\'' +
               ", publicationDate=" + publicationDate +
               '}';
    }

    // 实际应用中可能还需要 equals() 和 hashCode() 方法
    // @Override
    // public boolean equals(Object o) { ... }
    // @Override
    // public int hashCode() { ... }
}

3.2 BookDTO.java (数据传输对象)

用于在 Controller 和视图之间传输数据,特别是用于接收表单输入和进行数据校验。

package com.yourcompany.bookmanagement.dto;

import javax.validation.constraints.*; // Bean Validation 注解
import java.time.LocalDate;
import java.util.Objects;

// 使用 Lombok 注解简化 boilerplate code (可选)
// import lombok.Getter;
// import lombok.Setter;
// import lombok.NoArgsConstructor;

// @Getter // Lombok
// @Setter // Lombok
// @NoArgsConstructor // Lombok
public class BookDTO {

    private Long id; // 用于编辑时标识图书

    @NotBlank(message = "图书标题不能为空") // 标题不能为空白字符串
    @Size(max = 255, message = "图书标题长度不能超过255字符") // 标题最大长度
    private String title;

    @NotBlank(message = "图书作者不能为空") // 作者不能为空白字符串
    @Size(max = 255, message = "图书作者长度不能超过255字符") // 作者最大长度
    private String author;

    @Pattern(regexp = "^(?:ISBN(?:-13)?:?)(?=[0-9]{13}$)[0-9]{3}-?[0-9]{1}-?[0-9]{3}-?[0-9]{5}-?[0-9]{1}$", message = "ISBN格式不正确") // 简单的 ISBN 格式校验
    @Size(max = 20, message = "ISBN长度不能超过20字符")
    private String isbn;

    @PastOrPresent(message = "出版日期不能晚于今天") // 出版日期不能是未来日期
    // 注意:对于 LocalDate 这种对象类型,如果字段不是必须的,不使用 @NotNull,否则即使字符串为空也会因为无法绑定为 null 而报错。
    // 如果日期是必须的,则需要 @NotNull(message = "出版日期不能为空")
    private LocalDate publicationDate;

    // 手动添加构造器 (如果不用 Lombok 的 @NoArgsConstructor)
    public BookDTO() {
    }

    // 手动添加 Getter 和 Setter (如果不用 Lombok)
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }
    public String getAuthor() { return author; }
    public void setAuthor(String author) { this.author = author; }
    public String getIsbn() { return isbn; }
    public void setIsbn(String isbn) { this.isbn = isbn; }
    public LocalDate getPublicationDate() { return publicationDate; }
    public void setPublicationDate(LocalDate publicationDate) { this.publicationDate = publicationDate; }

    @Override
    public String toString() {
        return "BookDTO{" +
               "id=" + id +
               ", title='" + title + '\'' +
               ", author='" + author + '\'' +
               ", isbn='" + isbn + '\'' +
               ", publicationDate=" + publicationDate +
               '}';
    }
}

3.3 User.java (JPA 实体, 用于简单认证)

表示用户实体,用于登录校验。

package com.yourcompany.bookmanagement.entity;

import javax.persistence.*;
import java.util.Objects;

// 使用 Lombok 注解简化 boilerplate code (可选)
// import lombok.Getter;
// import lombok.Setter;
// import lombok.NoArgsConstructor;
// import lombok.AllArgsConstructor;

@Entity
@Table(name = "users")
// @Getter // Lombok
// @Setter // Lombok
// @NoArgsConstructor // Lombok
// @AllArgsConstructor // Lombok
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true) // 用户名唯一且不能为空
    private String username;

    @Column(nullable = false) // 密码不能为空
    private String password; // 实际应用中密码需要加密存储

    // 手动添加构造器 (如果不用 Lombok)
    public User() {
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    // 手动添加 Getter 和 Setter (如果不用 Lombok)
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }

    @Override
    public String toString() {
        return "User{" +
               "id=" + id +
               ", username='" + username + '\'' +
               ", password='[PROTECTED]'" + // 不输出密码
               '}';
    }
}

4. 持久化层 (Spring Data JPA)

使用 Spring Data JPA 简化数据访问。只需要定义 Repository 接口,Spring Data JPA 会自动生成实现。

4.1 BookRepository.java

package com.yourcompany.bookmanagement.repository;

import com.yourcompany.bookmanagement.entity.Book;
import org.springframework.data.jpa.repository.JpaRepository; // 引入 JpaRepository
import org.springframework.stereotype.Repository; // 标记为 Repository 组件

// JpaRepository<实体类型, 主键类型>
@Repository // 标记为 Repository Bean
public interface BookRepository extends JpaRepository<Book, Long> {
    // Spring Data JPA 会自动提供 CRUD 方法:save, findById, findAll, deleteById, count 等
    // 也可以定义查询方法,Spring Data JPA 会根据方法名自动生成查询实现,例如:
    // List<Book> findByTitleContainingIgnoreCase(String title);
    // List<Book> findByAuthorContainingIgnoreCase(String author);

    // 提供了分页查询功能,findAll 方法重载支持 Pageable 参数
    // Page<Book> findAll(Pageable pageable);
}

4.2 UserRepository.java

package com.yourcompany.bookmanagement.repository;

import com.yourcompany.bookmanagement.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository // 标记为 Repository Bean
public interface UserRepository extends JpaRepository<User, Long> {
    // 添加一个根据用户名查找用户的方法,用于登录
    User findByUsername(String username);
}

4.3 JPA 配置 (在 AppConfig.java 中)

在 Root Context 的配置类中配置 DataSource, EntityManagerFactory, TransactionManager 并启用 JPA Repository 扫描。

package com.yourcompany.bookmanagement.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories; // 启用 JPA Repository
import org.springframework.jdbc.datasource.DriverManagerDataSource; // JDBC DataSource
import org.springframework.orm.jpa.JpaTransactionManager; // JPA 事务管理器
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; // EntityManagerFactory
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; // Hibernate JPA 实现
import org.springframework.transaction.PlatformTransactionManager; // 事务管理器接口
import org.springframework.transaction.annotation.EnableTransactionManagement; // 启用事务注解 @Transactional
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.Properties;

@Configuration // 标记为配置类
@EnableTransactionManagement // 启用 @Transactional 注解支持
@EnableJpaRepositories(basePackages = "com.yourcompany.bookmanagement.repository") // 扫描 Repository 接口
@ComponentScan(basePackages = "com.yourcompany.bookmanagement.service", // 扫描 Service
               excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class)) // 排除 WebConfig
public class AppConfig { // Root Context 配置类

    // 配置数据源 (H2 嵌入式数据库)
    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("org.h2.Driver");
        dataSource.setUrl("jdbc:h2:mem:bookdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE"); // 使用内存数据库
        dataSource.setUsername("sa");
        dataSource.setPassword("");
        return dataSource;
    }

    // 配置 JPA EntityManagerFactory (整合 Hibernate)
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource());
        em.setPackagesToScan("com.yourcompany.bookmanagement.entity"); // 扫描 JPA 实体所在的包

        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        em.setJpaProperties(additionalProperties()); // 配置 JPA/Hibernate 属性

        return em;
    }

    // 配置 JPA/Hibernate 属性
    Properties additionalProperties() {
        Properties properties = new Properties();
        // properties.setProperty("hibernate.hbm2ddl.auto", "none"); // 数据表生成策略: none/create/create-drop/update/validate
        // 首次运行时可以使用 "create" 或 "create-drop",后续开发或生产环境应使用 "none" 或 "validate"
        properties.setProperty("hibernate.hbm2ddl.auto", "create-drop"); // 示例:每次启动时创建新表并插入初始化数据 (仅限演示)
        properties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect"); // H2 数据库方言
        properties.setProperty("hibernate.show_sql", "true"); // 在控制台显示 SQL 语句
        properties.setProperty("hibernate.format_sql", "true"); // 格式化 SQL 语句
        // properties.setProperty("hibernate.use_sql_comments", "true");
        return properties;
    }

    // 配置 JPA 事务管理器
    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(emf);
        return transactionManager;
    }

    // Bean PostProcessor,将 JPA 异常转换为 Spring 的 DataAccessException
    // 使 Repository 层抛出的 JPA 异常被 Spring 统一处理
    @Bean
    public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
        return new PersistenceExceptionTranslationPostProcessor();
    }

    // *** 示例: 在 Root Context 中初始化一些数据 (实际应用中通常有数据迁移脚本) ***
    // 注意:这种方式简单,但不是处理初始化数据的标准企业实践
    // 需要在一个实现了 ApplicationRunner 或 CommandLineRunner 的 Bean 中执行初始化 (通常在 Spring Boot)
    // 或者使用 JPA 的 @EntityListeners 或 `@PostPersist` 等
    // 对于非 Spring Boot 应用,可以在一个 Bean 的 init 方法中执行
    // 简单的模拟数据插入 (仅在 hibernate.hbm2ddl.auto 设置为 create-drop 时有效)
    @Bean
    public Boolean initializeDatabase(BookRepository bookRepository, UserRepository userRepository) {
        // 启动后延迟执行,确保 JPA EntityManagerFactory 已创建且表已生成
        new Thread(() -> {
            try {
                Thread.sleep(2000); // 等待 JPA 初始化
                if (bookRepository.count() == 0) { // 只在表为空时初始化
                    System.out.println(">>> Initializing Book Data...");
                    bookRepository.save(new Book("Spring MVC 入门", "张三", "978-7-121-XXXX-X", LocalDate.of(2022, 1, 1)));
                    bookRepository.save(new Book("Spring Data JPA 实践", "李四", "978-7-121-YYYY-Y", LocalDate.of(2021, 5, 15)));
                    System.out.println(">>> Book Data Initialized.");
                }
                if (userRepository.count() == 0) { // 只在表为空时初始化
                    System.out.println(">>> Initializing User Data...");
                    // 实际应用中密码需要加密
                    userRepository.save(new User("admin", "password")); // 简单的硬编码用户
                    System.out.println(">>> User Data Initialized.");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        return true; // 返回任意 Bean
    }
}

5. 业务逻辑层

Service 层负责协调 Repository 和处理业务逻辑。

5.1 BookService.java (接口)

package com.yourcompany.bookmanagement.service;

import com.yourcompany.bookmanagement.entity.Book;
import org.springframework.data.domain.Page; // 用于分页
import org.springframework.data.domain.Pageable; // 用于分页参数

import java.util.List;
import java.util.Optional;

public interface BookService {
    List<Book> findAllBooks(); // 获取所有图书
    Page<Book> findBooks(Pageable pageable); // 获取分页图书数据
    Optional<Book> findBookById(Long id); // 根据ID查找图书
    Book saveBook(Book book); // 保存/更新图书
    void deleteBookById(Long id); // 根据ID删除图书
}

5.2 BookServiceImpl.java (实现类)

使用 @Service 注解标记为 Service Bean,并通过 @Autowired 注入 BookRepository

package com.yourcompany.bookmanagement.service.impl;

import com.yourcompany.bookmanagement.entity.Book;
import com.yourcompany.bookmanagement.repository.BookRepository;
import com.yourcompany.bookmanagement.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; // 引入事务注解

import java.util.List;
import java.util.Optional;

@Service // 标记为 Service Bean
@Transactional // 在类级别应用事务,默认对所有 public 方法生效
public class BookServiceImpl implements BookService {

    private final BookRepository bookRepository; // 注入 BookRepository

    @Autowired // 构造器注入
    public BookServiceImpl(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    @Override
    @Transactional(readOnly = true) // 查询方法设置为只读事务
    public List<Book> findAllBooks() {
        return bookRepository.findAll(); // 调用 JPA Repository 提供的方法
    }

    @Override
    @Transactional(readOnly = true) // 分页查询方法设置为只读事务
    public Page<Book> findBooks(Pageable pageable) {
        return bookRepository.findAll(pageable); // 调用 JPA Repository 的分页方法
    }

    @Override
    @Transactional(readOnly = true) // 查询方法设置为只读事务
    public Optional<Book> findBookById(Long id) {
        return bookRepository.findById(id); // 调用 JPA Repository 提供的方法
    }

    @Override
    // 对于保存操作,使用默认的可写事务
    public Book saveBook(Book book) {
        return bookRepository.save(book); // 调用 JPA Repository 提供的方法 (新增和更新都用 save)
    }

    @Override
    // 对于删除操作,使用默认的可写事务
    public void deleteBookById(Long id) {
        bookRepository.deleteById(id); // 调用 JPA Repository 提供的方法
    }
}

5.3 UserServiceImpl.java (实现类, 简单认证)

实现简单的用户查找和登录校验(这里是硬编码校验)。

package com.yourcompany.bookmanagement.service.impl;

import com.yourcompany.bookmanagement.entity.User;
import com.yourcompany.bookmanagement.repository.UserRepository;
import com.yourcompany.bookmanagement.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
public class UserServiceImpl implements UserService {

    private final UserRepository userRepository; // 注入 UserRepository

    @Autowired
    public UserServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    @Transactional(readOnly = true)
    public User findByUsername(String username) {
        return userRepository.findByUsername(username);
    }

    @Override
    @Transactional(readOnly = true)
    public boolean authenticate(String username, String password) {
        User user = findByUsername(username);
        // 简单校验:用户存在且密码匹配 (实际应用中密码需要加密比较)
        return user != null && user.getPassword().equals(password);
    }
}

5.4 UserService.java (接口)

package com.yourcompany.bookmanagement.service;

import com.yourcompany.bookmanagement.entity.User;

public interface UserService {
    User findByUsername(String username);
    boolean authenticate(String username, String password);
}

6. 控制器层

控制器负责接收 HTTP 请求,调用 Service 层处理业务,并选择合适的视图或数据作为响应。

6.1 BookController.java (图书 CRUD 控制器)

package com.yourcompany.bookmanagement.controller;

import com.yourcompany.bookmanagement.dto.BookDTO; // 引入 DTO
import com.yourcompany.bookmanagement.entity.Book; // 引入 Entity
import com.yourcompany.bookmanagement.exception.BookNotFoundException; // 引入自定义异常
import com.yourcompany.bookmanagement.service.BookService; // 引入 Service
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; // 用于分页
import org.springframework.data.domain.PageRequest; // 用于创建 Pageable
import org.springframework.data.domain.Pageable; // 用于方法参数
import org.springframework.data.domain.Sort; // 用于排序
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; // 用于传递数据到视图
import org.springframework.validation.BindingResult; // 用于接收数据绑定和校验结果
import org.springframework.web.bind.annotation.*; // 常用注解
import org.springframework.web.servlet.mvc.support.RedirectAttributes; // 用于重定向时传递参数

import javax.validation.Valid; // 引入 @Valid 注解
import java.time.LocalDate; // 用于日期转换
import java.util.Optional;
import java.util.stream.Collectors; // 可能用于 Entity 转 DTO

@Controller // 标记为 Controller
@RequestMapping("/books") // 所有方法的基础路径
public class BookController {

    private final BookService bookService; // 注入 BookService

    @Autowired // 构造器注入
    public BookController(BookService bookService) {
        this.bookService = bookService;
    }

    // 显示图书列表 (含分页和排序概念)
    // GET /books
    @GetMapping
    public String listBooks(
            @RequestParam(defaultValue = "0") int page, // 当前页码,默认第0页
            @RequestParam(defaultValue = "10") int size, // 每页记录数,默认10条
            @RequestParam(defaultValue = "title") String sortBy, // 排序字段,默认按标题
            @RequestParam(defaultValue = "asc") String sortOrder, // 排序顺序,默认升序
            Model model) {

        // 创建 Pageable 对象,用于传递分页和排序信息给 Service/Repository
        Sort sort = Sort.by(Sort.Direction.fromString(sortOrder), sortBy);
        Pageable pageable = PageRequest.of(page, size, sort);

        Page<Book> bookPage = bookService.findBooks(pageable); // 调用 Service 获取分页数据

        model.addAttribute("bookPage", bookPage); // 将分页数据添加到 Model
        model.addAttribute("currentPage", page); // 当前页码
        model.addAttribute("pageSize", size); // 每页大小
        model.addAttribute("sortBy", sortBy); // 排序字段
        model.addAttribute("sortOrder", sortOrder); // 排序顺序

        // Thymeleaf 视图名会是 books/list.html (根据 ViewResolver 配置)
        return "books/list";
    }

    // 显示图书详情
    // GET /books/{id}
    @GetMapping("/{id}")
    public String showBookDetail(@PathVariable("id") Long id, Model model) {
        Optional<Book> book = bookService.findBookById(id);
        if (book.isPresent()) {
            model.addAttribute("book", book.get()); // 将图书对象添加到 Model
            return "books/detail"; // Thymeleaf 视图名 books/detail.html
        } else {
            // 抛出自定义异常,由全局异常处理器处理 (对应模块六)
            throw new BookNotFoundException(id);
        }
    }

    // 显示新增图书表单
    // GET /books/new
    @GetMapping("/new")
    public String showAddBookForm(Model model) {
        // 在 Model 中添加一个空的 BookDTO 对象,供表单绑定使用 (@ModelAttribute 的另一种用法)
        model.addAttribute("bookDTO", new BookDTO());
        return "books/form"; // Thymeleaf 视图名 books/form.html (新增和编辑使用同一个表单视图)
    }

    // 显示编辑图书表单
    // GET /books/edit/{id}
    @GetMapping("/edit/{id}")
    public String showEditBookForm(@PathVariable("id") Long id, Model model) {
        Optional<Book> book = bookService.findBookById(id);
        if (book.isPresent()) {
            Book existingBook = book.get();
            // 将 Entity 对象转换为 DTO 对象,用于填充表单
            BookDTO bookDTO = new BookDTO();
            bookDTO.setId(existingBook.getId());
            bookDTO.setTitle(existingBook.getTitle());
            bookDTO.setAuthor(existingBook.getAuthor());
            bookDTO.setIsbn(existingBook.getIsbn());
            bookDTO.setPublicationDate(existingBook.getPublicationDate()); // 直接设置 LocalDate

            model.addAttribute("bookDTO", bookDTO); // 将填充好的 DTO 添加到 Model
            return "books/form"; // Thymeleaf 视图名 books/form.html
        } else {
            throw new BookNotFoundException(id);
        }
    }

    // 处理新增或编辑图书表单提交
    // POST /books
    // 使用 @ModelAttribute 绑定表单数据到 BookDTO
    // 使用 @Valid 进行数据校验
    // 使用 BindingResult 获取校验结果
    // 使用 RedirectAttributes 在重定向后传递消息
    @PostMapping
    public String saveBook(@ModelAttribute("bookDTO") @Valid BookDTO bookDTO, // @Valid 启用校验,BindingResult 紧随其后
                           BindingResult bindingResult, // 校验结果会存储在这里
                           RedirectAttributes redirectAttributes, // 用于重定向传参
                           Model model) {

        // 检查数据校验结果
        if (bindingResult.hasErrors()) {
            System.out.println("Validation errors: " + bindingResult.getAllErrors());
            // 如果有错误,返回到表单页面,错误信息会自动添加到 Model 中供 Thymeleaf th:errors 显示
            return "books/form";
        }

        // 将 DTO 转换为 Entity
        Book book = new Book();
        book.setId(bookDTO.getId()); // 如果是编辑,ID 不为 null
        book.setTitle(bookDTO.getTitle());
        book.setAuthor(bookDTO.getAuthor());
        book.setIsbn(bookDTO.getIsbn());
        book.setPublicationDate(bookDTO.getPublicationDate());

        // 调用 Service 保存图书
        Book savedBook = bookService.saveBook(book);

        // 使用 RedirectAttributes 在重定向后显示成功消息
        redirectAttributes.addFlashAttribute("successMessage", "图书信息保存成功!");

        // 重定向到图书详情页或列表页
        // 重定向到详情页: return "redirect:/books/" + savedBook.getId();
        // 重定向到列表页:
        return "redirect:/books"; // 对应模块六的重定向
    }

    // 处理删除图书请求
    // POST /books/delete/{id} 或 DELETE /books/{id} (POST 更兼容浏览器)
    @PostMapping("/delete/{id}")
    public String deleteBook(@PathVariable("id") Long id, RedirectAttributes redirectAttributes) {
        // 检查图书是否存在 (可选,Service 层的 delete 方法可能抛异常)
        Optional<Book> book = bookService.findBookById(id);
        if (!book.isPresent()) {
             throw new BookNotFoundException(id);
        }

        bookService.deleteBookById(id); // 调用 Service 删除图书
        redirectAttributes.addFlashAttribute("successMessage", "图书删除成功!");
        return "redirect:/books"; // 重定向到图书列表页
    }

    /*
     * 示例:使用 @ModelAttribute 方法为 Model 预填充数据
     * @ModelAttribute("genres")
     * public List<String> populateGenres() {
     *     return Arrays.asList("小说", "技术", "历史");
     * }
     * // 这样在所有由这个 Controller 处理的请求中,Model 都会有一个名为 "genres" 的属性
     */
}

6.2 AuthController.java (简单认证控制器)

处理登录页面的显示和登录逻辑。

package com.yourcompany.bookmanagement.controller;

import com.yourcompany.bookmanagement.service.UserService; // 引入 UserService
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.HttpSession; // 引入 HttpSession

@Controller // 标记为 Controller
@RequestMapping("/") // 登录相关通常在根路径或 /auth 路径下
public class AuthController {

    private final UserService userService; // 注入 UserService

    @Autowired // 构造器注入
    public AuthController(UserService userService) {
        this.userService = userService;
    }

    // 显示登录页面
    // GET /login
    @GetMapping("/login")
    public String showLoginForm(@RequestParam(value = "error", required = false) String error, Model model) {
        if (error != null) {
            model.addAttribute("errorMessage", "用户名或密码不正确。"); // 如果登录失败,显示错误消息
        }
        return "auth/login"; // Thymeleaf 视图名 auth/login.html
    }

    // 处理登录请求
    // POST /login
    @PostMapping("/login")
    public String processLogin(@RequestParam String username,
                               @RequestParam String password,
                               HttpSession session) { // 注入 HttpSession

        if (userService.authenticate(username, password)) {
            // 认证成功,将用户信息存储到 Session (这里只存用户名)
            session.setAttribute("loggedInUser", username);
            // 重定向到图书列表页
            return "redirect:/books";
        } else {
            // 认证失败,重定向回登录页,并附带错误参数
            return "redirect:/login?error";
        }
    }

    // 处理注销请求
    // GET /logout 或 POST /logout
    @GetMapping("/logout")
    public String logout(HttpSession session) {
        // 使当前 Session 无效
        session.invalidate();
        // 重定向到登录页
        return "redirect:/login?logout"; // 可以附带 logout 参数表示已注销
    }
}

7. 数据校验 (Bean Validation)

结合 Bean Validation API 和 Hibernate Validator 实现数据校验。

  1. 添加依赖: 已在 pom.xml 中添加 validation-apihibernate-validator
  2. 在 DTO 中添加注解:BookDTO.java 中使用了 @NotBlank, @Size, @Pattern, @PastOrPresent 等注解。
  3. 在 Controller 中启用校验:saveBook 方法的 BookDTO 参数前添加 @Valid 注解,并在其后紧跟 BindingResult 参数。
  4. 配置 Validator:WebMvcConfig.java 中配置 LocalValidatorFactoryBean Bean。
// 在 WebMvcConfig.java 中添加
import org.springframework.context.annotation.Bean;
import org.springframework.validation.Validator; // 引入 Validator 接口
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; // Bean Validation Validator

// ... 其他导入和类定义

// 在 WebMvcConfig 类中
@Override
public Validator getValidator() {
    LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
    // 可以配置 ValidationProviderResolver, MessageSource 等
    // validator.setValidationMessageSource(messageSource()); // 例如,配置国际化错误消息
    return validator;
}
  1. 在 Thymeleaf 视图中显示错误: 在表单视图 (form.html) 中使用 th:errors 标签显示校验错误信息。
<!-- 在 WEB-INF/templates/books/form.html 中 -->
<form th:object="${bookDTO}" th:action="@{/books}" method="post">
    <!-- ...其他字段 -->
    <div>
        <label for="title">标题:</label>
        <input type="text" id="title" th:field="*{title}"/>
        <!-- 显示 title 字段的校验错误 -->
        <span th:if="${#fields.hasErrors('title')}" th:errors="*{title}" style="color: red;">Title Error</span>
    </div>
    <div>
        <label for="author">作者:</label>
        <input type="text" id="author" th:field="*{author}"/>
         <!-- 显示 author 字段的校验错误 -->
        <span th:if="${#fields.hasErrors('author')}" th:errors="*{author}" style="color: red;">Author Error</span>
    </div>
    <!-- ...其他字段 -->
</form>

8. 视图层 (Thymeleaf)

使用 Thymeleaf 作为模板引擎渲染视图。

8.1 Thymeleaf 配置 (在 WebMvcConfig.java 中)

在 Servlet Context 的配置类中配置 Thymeleaf 相关的 Bean。

package com.yourcompany.bookmanagement.config;

import com.yourcompany.bookmanagement.interceptor.AuthInterceptor; // 引入认证拦截器
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.*; // 引入 WebMvcConfigurer 相关注解和类
import org.springframework.validation.Validator; // 引入 Validator
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; // 引入 Bean Validation 实现

import org.thymeleaf.spring5.SpringTemplateEngine; // Thymeleaf 模板引擎
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver; // Thymeleaf 资源解析器
import org.thymeleaf.spring5.view.ThymeleafViewResolver; // Thymeleaf 视图解析器
import org.thymeleaf.templatemode.TemplateMode; // 模板模式

@Configuration // 标记为配置类
@EnableWebMvc // 启用 Spring MVC 注解驱动功能 (对应模块一, 四, 五)
@ComponentScan(basePackages = "com.yourcompany.bookmanagement.controller") // 扫描 Controller
public class WebMvcConfig implements WebMvcConfigurer, ApplicationContextAware { // 实现 WebMvcConfigurer 扩展 MVC 配置,实现 ApplicationContextAware 获取 ApplicationContext

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    // 配置模板资源解析器 (对应模块四)
    @Bean
    public SpringResourceTemplateResolver templateResolver() {
        SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
        templateResolver.setApplicationContext(this.applicationContext);
        templateResolver.setPrefix("/WEB-INF/templates/"); // Thymeleaf 模板文件存放路径
        templateResolver.setSuffix(".html"); // 模板后缀
        templateResolver.setTemplateMode(TemplateMode.HTML); // 模板模式为 HTML
        templateResolver.setCharacterEncoding("UTF-8"); // 设置编码
        templateResolver.setCacheable(false); // 开发时建议关闭缓存,方便修改模板后查看效果
        return templateResolver;
    }

    // 配置模板引擎 (对应模块四)
    @Bean
    public SpringTemplateEngine templateEngine() {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(templateResolver()); // 设置模板资源解析器
        templateEngine.setEnableSpringELCompiler(true); // 启用 Spring EL 表达式
        // 可以添加 Thymeleaf 的布局方言等,用于更复杂的模板布局
        // templateEngine.addDialect(new LayoutDialect());
        return templateEngine;
    }

    // 配置视图解析器 (对应模块四)
    @Bean
    public ViewResolver thymeleafViewResolver() {
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setTemplateEngine(templateEngine()); // 设置模板引擎
        viewResolver.setCharacterEncoding("UTF-8"); // 设置编码
        // 可以设置 order 属性,如果存在多个 ViewResolver
        // viewResolver.setOrder(1);
        return viewResolver;
    }

    // 配置静态资源处理 (对应模块一)
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
        // 例如,CSS 文件放在 src/main/webapp/resources/css 下,可以通过 /resources/css/style.css 访问
    }

    // 配置默认 Servlet 处理,转发对静态资源的请求到容器默认的 Servlet
    // 通常 @EnableWebMvc 会自动处理,但明确配置可以避免问题
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    // 配置 Bean Validation Validator (对应本模块数据校验)
    @Bean
    @Override
    public Validator getValidator() {
        LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
        return validator;
    }

    // 配置拦截器 (对应模块六)
    @Bean
    public AuthInterceptor authInterceptor() {
        return new AuthInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor()) // 添加认证拦截器实例
                .addPathPatterns("/**") // 拦截所有路径
                .excludePathPatterns("/login", "/logout", "/resources/**", "/webjars/**"); // 排除登录、注销、静态资源、WebJars 路径
    }
}

8.2 视图模板 (.html)

src/main/webapp/WEB-INF/templates 目录下创建对应的 html 文件。

  • WEB-INF/templates/books/list.html (图书列表)

    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org"
          xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
          layout:decorate="~{layout}"> <!-- 可选:使用布局模板 -->
    <head>
        <title>图书列表</title>
    </head>
    <body>
    <div layout:fragment="content"> <!-- 可选:布局模板内容片段 -->
        <h1>图书列表</h1>
    
        <!-- 显示保存/删除成功消息 -->
        <div th:if="${successMessage}" style="color: green; margin-bottom: 10px;">
            <p th:text="${successMessage}"></p>
        </div>
    
        <table>
            <thead>
            <tr>
                <th>ID</th>
                <th><a th:href="@{/books(page=${currentPage}, size=${pageSize}, sortBy='title', sortOrder=${sortBy == 'title' ? (sortOrder == 'asc' ? 'desc' : 'asc') : 'asc'})}">标题</a></th>
                <th><a th:href="@{/books(page=${currentPage}, size=${pageSize}, sortBy='author', sortOrder=${sortBy == 'author' ? (sortOrder == 'asc' ? 'desc' : 'asc') : 'asc'})}">作者</a></th>
                <th>ISBN</th>
                <th>出版日期</th>
                <th>操作</th>
            </tr>
            </thead>
            <tbody>
            <!-- 使用 th:each 遍历 Model 中的 bookPage.content (当前页的图书列表) -->
            <tr th:each="book : ${bookPage.content}">
                <td th:text="${book.id}">1</td>
                <td th:text="${book.title}">书名</td>
                <td th:text="${book.author}">作者</td>
                <td th:text="${book.isbn}">ISBN</td>
                <td th:text="${book.publicationDate}">出版日期</td>
                <td>
                    <!-- th:href 生成 URL,使用 @{...} 语法 -->
                    <a th:href="@{/books/{id}(id=${book.id})}">详情</a> |
                    <a th:href="@{/books/edit/{id}(id=${book.id})}">编辑</a> |
                    <!-- 删除操作使用 POST 请求 -->
                    <form th:action="@{/books/delete/{id}(id=${book.id})}" method="post" style="display: inline;">
                        <button type="submit" onclick="return confirm('确定删除吗?');" style="color: blue; background: none; border: none; cursor: pointer; text-decoration: underline; padding: 0;">删除</button>
                    </form>
                </td>
            </tr>
            </tbody>
        </table>
    
        <!-- 分页链接 -->
        <div>
            <span th:text="'共 ' + ${bookPage.totalElements} + ' 条记录'"></span>
            <span th:text="' | 共 ' + ${bookPage.totalPages} + ' 页'"></span>
            <span th:text="' | 当前第 ' + ${bookPage.number + 1} + ' 页'"></span>
    
            <!-- 导航链接 -->
            <span th:if="${bookPage.hasPrevious()}">
                <a th:href="@{/books(page=${bookPage.number - 1}, size=${pageSize}, sortBy=${sortBy}, sortOrder=${sortOrder})}">上一页</a>
            </span>
            <span th:if="${bookPage.hasNext()}">
                <a th:href="@{/books(page=${bookPage.number + 1}, size=${pageSize}, sortBy=${sortBy}, sortOrder=${sortOrder})}">下一页</a>
            </span>
             <!-- 可以添加首页、尾页、页码列表等更复杂的分页控件 -->
        </div>
    
        <p><a th:href="@{/books/new}">新增图书</a></p>
        <p><a th:href="@{/logout}">注销</a></p>
    </div>
    </body>
    </html>
    
  • WEB-INF/templates/books/detail.html (图书详情)

    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org"
          xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
          layout:decorate="~{layout}">
    <head>
        <title th:text="${book.title} + ' - 图书详情'">图书详情</title>
    </head>
    <body>
    <div layout:fragment="content">
        <h1 th:text="${book.title}">图书详情</h1>
    
        <p><strong>ID:</strong> <span th:text="${book.id}">1</span></p>
        <p><strong>标题:</strong> <span th:text="${book.title}">书名</span></p>
        <p><strong>作者:</strong> <span th:text="${book.author}">作者</span></p>
        <p><strong>ISBN:</strong> <span th:text="${book.isbn}">ISBN</span></p>
        <p><strong>出版日期:</strong> <span th:text="${book.publicationDate}">出版日期</span></p>
    
        <p>
            <a th:href="@{/books/edit/{id}(id=${book.id})}">编辑</a> |
            <a th:href="@{/books}">返回列表</a>
        </p>
         <p><a th:href="@{/logout}">注销</a></p>
    </div>
    </body>
    </html>
    
  • WEB-INF/templates/books/form.html (新增/编辑表单)

    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org"
          xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
          layout:decorate="~{layout}">
    <head>
        <title th:text="${bookDTO.id == null ? '新增图书' : '编辑图书'}">图书表单</title>
        <style>
            /* 简单的错误样式 */
            .error-message { color: red; font-size: 0.9em; }
            input.is-invalid, textarea.is-invalid { border-color: red; }
        </style>
    </head>
    <body>
    <div layout:fragment="content">
        <h1 th:text="${bookDTO.id == null ? '新增图书' : '编辑图书'}">图书表单</h1>
    
        <!-- th:object 指定要绑定的对象,th:action 指定表单提交的 URL -->
        <form th:object="${bookDTO}" th:action="@{/books}" method="post">
            <!-- 对于编辑操作,需要提交图书 ID -->
            <input type="hidden" th:field="*{id}"/>
    
            <div>
                <label for="title">标题:</label>
                <!-- th:field 绑定输入框到对象的属性 -->
                <!-- 通过 th:errorclass 根据是否有校验错误添加 CSS 类 -->
                <input type="text" id="title" th:field="*{title}" th:errorclass="is-invalid"/>
                <!-- 显示 title 字段的校验错误 -->
                <span th:if="${#fields.hasErrors('title')}" th:errors="*{title}" class="error-message">Title Error</span>
            </div>
            <div>
                <label for="author">作者:</label>
                <input type="text" id="author" th:field="*{author}" th:errorclass="is-invalid"/>
                <!-- 显示 author 字段的校验错误 -->
                <span th:if="${#fields.hasErrors('author')}" th:errors="*{author}" class="error-message">Author Error</span>
            </div>
            <div>
                <label for="isbn">ISBN:</label>
                <input type="text" id="isbn" th:field="*{isbn}" th:errorclass="is-invalid"/>
                <!-- 显示 isbn 字段的校验错误 -->
                <span th:if="${#fields.hasErrors('isbn')}" th:errors="*{isbn}" class="error-message">ISBN Error</span>
            </div>
             <div>
                <label for="publicationDate">出版日期:</label>
                 <!-- 注意:HTML input type="date" 返回字符串 "YYYY-MM-DD",Spring MVC 会自动绑定到 LocalDate -->
                <input type="date" id="publicationDate" th:field="*{publicationDate}" th:errorclass="is-invalid"/>
                <!-- 显示 publicationDate 字段的校验错误 -->
                <span th:if="${#fields.hasErrors('publicationDate')}" th:errors="*{publicationDate}" class="error-message">Date Error</span>
            </div>
    
            <div>
                <button type="submit" th:text="${bookDTO.id == null ? '新增' : '保存'}">提交</button>
                <a th:href="@{/books}">取消</a>
            </div>
        </form>
        <p><a th:href="@{/logout}">注销</a></p>
    </div>
    </body>
    </html>
    
  • WEB-INF/templates/auth/login.html (登录页面)

    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>用户登录</title>
        <style>
            .error-message { color: red; }
        </style>
    </head>
    <body>
        <h1>用户登录</h1>
    
        <!-- 显示错误消息 -->
        <div th:if="${errorMessage}" class="error-message">
            <p th:text="${errorMessage}"></p>
        </div>
         <!-- 显示注销成功消息 (如果从 /logout 重定向过来) -->
         <div th:if="${param.logout}" style="color: green;">
            <p>您已成功注销。</p>
        </div>
    
        <!-- 登录表单,提交到 /login -->
        <form th:action="@{/login}" method="post">
            <div>
                <label for="username">用户名:</label>
                <input type="text" id="username" name="username" required/>
            </div>
            <div>
                <label for="password">密码:</label>
                <input type="password" id="password" name="password" required/>
            </div>
            <div>
                <button type="submit">登录</button>
            </div>
        </form>
    </body>
    </html>
    
  • WEB-INF/templates/layout.html (可选,布局模板)

    为了简化页面结构和维护,可以定义一个布局模板。使用 Thymeleaf Layout Dialect (需要添加到 pom.xmlWebMvcConfig)。

    <!-- pom.xml 添加 -->
    <dependency>
        <groupId>nz.net.ultraq.thymeleaf</groupId>
        <artifactId>thymeleaf-layout-dialect</artifactId>
        <version>2.5.3</version> <!-- 或更高兼容版本 -->
    </dependency>
    
    // WebMvcConfig.java 添加
    import nz.net.ultraq.thymeleaf.layoutdialect.LayoutDialect;
    
    // 在 templateEngine() Bean 方法中添加
    templateEngine.addDialect(new LayoutDialect());
    
    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org"
          xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
    <head>
        <meta charset="UTF-8">
        <title layout:title-pattern="$LAYOUT_TITLE | $CONTENT_TITLE">图书管理系统</title>
        <link rel="stylesheet" th:href="@{/resources/css/style.css}">
        <!-- 其他头部内容 -->
    </head>
    <body>
        <header>
            <h1>图书管理系统</h1>
            <!-- 导航或其他头部内容 -->
        </header>
    
        <main layout:fragment="content">
            <!-- 页面内容会在这里插入 -->
            <p>页面内容区域</p>
        </main>
    
        <footer>
            <p>&copy; 2023 Your Company</p>
        </footer>
    </body>
    </html>
    

    其他页面通过 <html layout:decorate="~{layout}"><div layout:fragment="content">...</div> 来使用布局。

9. Spring 配置 (JavaConfig)

使用 Java 类代替 XML 文件进行 Spring 和 Spring MVC 的配置。

9.1 MyWebAppInitializer.java (Servlet 容器初始化)

替代 web.xml 配置 DispatcherServlet,对应模块一。

package com.yourcompany.bookmanagement.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; // 引入抽象基类

// 继承 AbstractAnnotationConfigDispatcherServletInitializer
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    // 配置 Root Context (非 Web 层 Bean)
    @Override
    protected Class<?>[] getRootConfigClasses() {
        // 通常用于配置 Service, Repository, DataSource, TransactionManager 等
        return new Class<?>[]{AppConfig.class}; // 加载 AppConfig
    }

    // 配置 Servlet Context (Web 层 Bean)
    @Override
    protected Class<?>[] getServletConfigClasses() {
        // 通常用于配置 Controller, ViewResolver, ResourceHandler, Interceptor 等
        return new Class<?>[]{WebMvcConfig.class}; // 加载 WebMvcConfig
    }

    // 配置 DispatcherServlet 的映射路径
    @Override
    protected String[] getServletMappings() {
        // "/" 表示 DispatcherServlet 拦截所有请求 (除容器默认处理的,如 .jsp)
        return new String[]{"/"};
    }

    // 可选:配置 DispatcherServlet 名称
    // @Override
    // protected String getServletName() {
    //     return "dispatcher";
    // }
}

说明:Servlet 容器启动时会自动查找实现了 ServletContainerInitializer 接口的类,而 AbstractAnnotationConfigDispatcherServletInitializer 间接实现了这个接口,从而完成了 DispatcherServlet 的注册和 Spring 容器的加载。

9.2 AppConfig.java (Root Context 配置)

已在 JPA 配置部分给出代码。主要配置非 Web 层的 Bean,如 DataSource, JPA/Hibernate, Spring Data JPA, Service。

9.3 WebMvcConfig.java (Servlet Context 配置)

已在 Thymeleaf 和数据校验配置部分给出代码。主要配置 Web 层的 Bean,如 Controller 扫描、ViewResolver、资源处理、Validator、Interceptor。

10. 拦截器 (简单认证)

实现一个简单的拦截器检查用户是否登录,对应模块六。

10.1 AuthInterceptor.java

package com.yourcompany.bookmanagement.interceptor;

import org.springframework.web.servlet.HandlerInterceptor; // 引入拦截器接口
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession; // 引入 HttpSession

public class AuthInterceptor implements HandlerInterceptor {

    // 在 Controller 方法执行前调用
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 获取当前请求的路径
        String requestURI = request.getRequestURI();
        System.out.println("Intercepting request: " + requestURI);

        // 获取 Session
        HttpSession session = request.getSession();

        // 检查 Session 中是否存在 loggedInUser 属性
        Object user = session.getAttribute("loggedInUser");

        if (user != null) {
            // 用户已登录,继续执行后续流程 (到 Controller 方法)
            System.out.println("User is logged in. Continue request.");
            return true;
        } else {
            // 用户未登录
            System.out.println("User is NOT logged in. Redirecting to login page.");
            // 重定向到登录页面
            // 注意:这里需要使用 sendRedirect,并且路径是相对于 contextPath 的
            response.sendRedirect(request.getContextPath() + "/login");
            return false; // 阻止当前请求继续处理
        }
    }

    // 在 Controller 方法执行后,视图渲染前调用
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // System.out.println("AuthInterceptor postHandle...");
        // 可以在这里修改 Model 或 View
    }

    // 在整个请求处理完成后调用 (包括视图渲染后)
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // System.out.println("AuthInterceptor afterCompletion...");
        // 用于清理资源等
    }
}

10.2 拦截器配置 (在 WebMvcConfig.java 中)

已在 Thymeleaf 配置部分给出代码。在 WebMvcConfig 中定义 AuthInterceptor Bean,并在 addInterceptors 方法中注册并配置拦截规则 (addPathPatterns, excludePathPatterns)。

11. 统一异常处理

使用 @ControllerAdvice@ExceptionHandler 实现全局异常处理,对应模块六。

11.1 BookNotFoundException.java (自定义异常)

package com.yourcompany.bookmanagement.exception;

// 自定义异常,继承 RuntimeException
public class BookNotFoundException extends RuntimeException {
    public BookNotFoundException(Long id) {
        super("Book not found with ID: " + id);
    }
}

11.2 GlobalExceptionHandler.java (@ControllerAdvice)

package com.yourcompany.bookmanagement.exception;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus; // 引入 HTTP 状态码
import org.springframework.web.bind.annotation.ControllerAdvice; // 引入 @ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler; // 引入 @ExceptionHandler
import org.springframework.web.bind.annotation.ResponseStatus; // 引入 @ResponseStatus
import org.springframework.web.servlet.ModelAndView; // 用于返回错误视图

// @ControllerAdvice 应用于所有 Controller
@ControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); // 记录日志

    // 处理 BookNotFoundException 异常
    @ExceptionHandler(BookNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND) // 设置响应状态码为 404
    public ModelAndView handleBookNotFound(BookNotFoundException ex) {
        logger.warn("Book not found: " + ex.getMessage()); // 记录警告日志
        ModelAndView mav = new ModelAndView("error/404"); // 返回错误视图 error/404.html
        mav.addObject("message", ex.getMessage()); // 将错误信息添加到 Model
        return mav;
    }

    // 处理所有其他未捕获的 Exception
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) // 设置响应状态码为 500
    public ModelAndView handleAllExceptions(Exception ex) {
        logger.error("Internal Server Error: ", ex); // 记录错误日志
        ModelAndView mav = new ModelAndView("error/500"); // 返回错误视图 error/500.html
        mav.addObject("message", "Internal Server Error. Please try again later.");
        // 在开发环境中,可以添加更详细的错误信息:
        // mav.addObject("details", ex.getMessage());
        return mav;
    }

    /*
     * 可以添加更多针对特定异常类型的处理方法,例如:
     * @ExceptionHandler(MethodArgumentNotValidException.class) // 处理 @RequestBody 参数校验失败
     * @ResponseStatus(HttpStatus.BAD_REQUEST)
     * @ResponseBody // 通常用于 REST API 返回 JSON
     * public ErrorResponse handleValidationExceptions(MethodArgumentNotValidException ex) {
     *     // 构建并返回包含所有校验错误的响应体
     * }
     */
}

需要创建对应的错误视图文件,例如 WEB-INF/templates/error/404.htmlWEB-INF/templates/error/500.html

  • WEB-INF/templates/error/404.html

    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>资源未找到 (404)</title>
    </head>
    <body>
        <h1>404 资源未找到</h1>
        <p th:text="${message != null ? message : '您请求的资源不存在。'}">您请求的资源不存在。</p>
        <p><a th:href="@{/}">返回首页</a></p>
    </body>
    </html>
    
  • WEB-INF/templates/error/500.html

    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>内部服务器错误 (500)</title>
    </head>
    <body>
        <h1>500 内部服务器错误</h1>
        <p th:text="${message != null ? message : '服务器处理您的请求时发生错误,请稍后重试。'}">服务器处理您的请求时发生错误,请稍后重试。</p>
         <!-- 在开发时可以显示更多错误详情 -->
        <!-- <p th:if="${details != null}" th:text="'详情: ' + ${details}"></p> -->
        <p><a th:href="@{/}">返回首页</a></p>
    </body>
    </html>
    

12. 运行与部署

12.1 Maven 构建 WAR 包

在项目根目录打开终端,执行 Maven 命令:

mvn clean package

构建成功后,会在项目的 target 目录下生成 book-management-1.0-SNAPSHOT.war 文件。

12.2 部署到 Servlet 容器

将生成的 .war 文件复制到 Tomcat (或其他 Servlet 容器) 的 webapps 目录下。启动 Tomcat,它会自动解压并部署 WAR 包。

12.3 访问应用

部署成功后,可以通过浏览器访问应用。默认情况下,应用的 URL 结构为:

http://localhost:8080/book-management/

或者,如果部署为 ROOT 应用(将 war 包重命名为 ROOT.war),则为:

http://localhost:8080/
  • 登录页面:http://localhost:8080/book-management/login
  • 图书列表:http://localhost:8080/book-management/books (需要先登录)

使用用户名 admin 和密码 password 进行登录。

13. 总结与扩展

通过这个小型图书管理系统案例,我们实践了 Spring MVC 在企业应用中的典型用法,包括:

  • 使用 Maven 管理项目和依赖。
  • 采用 JavaConfig 进行 Spring 和 Spring MVC 的配置。
  • 结合 Spring Data JPA 实现数据持久化。
  • 使用 Service 层封装业务逻辑。
  • 编写 Controller 处理 Web 请求,使用 @RequestMapping 系列注解进行请求映射。
  • 通过 @ModelAttribute@RequestParam 获取请求参数。
  • 利用 Bean Validation 进行数据校验,并在视图层展示错误。
  • 使用 Thymeleaf 模板引擎渲染动态 HTML 页面,展示 Model 数据。
  • 实现简单的用户认证拦截器,保护页面访问。
  • 实现全局异常处理,提升应用健壮性。
  • 理解并应用了重定向 (redirect:) 和转发 (默认) 的页面跳转方式。
  • 实现了基本的图书 CRUD 和列表(含分页概念和排序)。

进一步学习和扩展方向:

  • 完善分页功能: 在列表页面添加完整的页码导航、每页显示数量选择等。
  • 搜索功能: 在 Repository 层添加自定义查询方法,在 Controller 层接收搜索参数,在 Service 层调用,并在列表页面展示搜索结果。
  • 文件上传: 添加图书封面图片上传功能 (参考模块六文件上传)。
  • Spring Security: 将简单的认证机制替换为更强大和安全的 Spring Security 框架,实现更复杂的权限控制 (如不同角色用户)。
  • RESTful API: 除了基于视图的 Web 应用,可以为图书管理功能添加 RESTful API 接口 (使用 @RestController@RequestBody/@ResponseBody),供前端应用或第三方系统调用。
  • 国际化 (i18n): 为应用添加多语言支持 (Spring MVC 提供了 LocaleResolver 等组件)。
  • 缓存: 引入 Spring Cache 或其他缓存方案优化数据访问性能。
  • 日志增强: 配置更详细和灵活的日志策略。
  • 测试: 编写单元测试和集成测试,确保代码质量。
  • Spring Boot: 在熟悉了原生 Spring MVC 后,强烈建议学习 Spring Boot,它能极大地简化 Spring 应用的开发和部署。本项目案例很容易迁移到 Spring Boot。

希望这个案例能帮助你更好地理解和掌握 Spring MVC!

快速回顾Spring MVC基础知识

速通Spring MVC ,一篇就够

企业级实用技术讲解

企业级Spring MVC高级主题与实用技术讲解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2393686.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【清晰教程】利用Git工具将本地项目push上传至GitHub仓库中

Git 是一个分布式版本控制系统&#xff0c;由 Linus Torvalds 创建&#xff0c;用于有效、高速地处理从小到大的项目版本管理。GitHub 是一个基于 Git 的代码托管平台&#xff0c;提供了额外的协作和社交功能&#xff0c;使项目管理更加高效。它们为项目代码管理、团队协作和持…

20250529-C#知识:静态类、静态构造函数和拓展方法

C#知识&#xff1a;静态类、静态构造函数和拓展方法 静态类一般用来编写工具类 1、静态类 用static关键字修饰的类一般充当工具类只能包含静态成员,不能包含静态索引器不能被实例化静态方法只能使用静态成员非静态方法既可以使用非静态成员&#xff0c;也可以使用静态成员 sta…

实验设计与分析(第6版,Montgomery)第4章随机化区组,拉丁方, 及有关设计4.5节思考题4.18~4.19 R语言解题

本文是实验设计与分析&#xff08;第6版&#xff0c;Montgomery著&#xff0c;傅珏生译) 第章随机化区组&#xff0c;拉丁方&#xff0c; 及有关设计4.5节思考题4.18~4.19 R语言解题。主要涉及方差分析&#xff0c;拉丁方。 batch <- c(rep("batch1",5), rep(&quo…

【吾爱】逆向实战crackme160学习记录(一)

前言 最近想拿吾爱上的crackme程序练练手&#xff0c;发现论坛上已经有pk8900总结好的160个crackme&#xff0c;非常方便&#xff0c;而且有很多厉害的前辈已经写好经验贴和方法了&#xff0c;我这里只是做一下自己练习的记录&#xff0c;欢迎讨论学习&#xff0c;感谢吾爱论坛…

vue2 + webpack 老项目升级 node v22 + vite + vue2 实战全记录

前言 随着这些年前端技术的飞速发展&#xff0c;几年前的一些老项目在最新的环境下很可能会出现烂掉的情况。如果项目不需要升级&#xff0c;只需要把编译后的文件放在那里跑而不用管的话还好。但是&#xff0c;某一天产品跑过来给你讲要升级某一个功能&#xff0c;你不得不去…

STM32的HAL编码流程总结(上部)

目录 一、GPIO二、中断系统三、USART串口通信四、I2C通信五、定时器 一、GPIO 1.选择调试类型 在SYS中Debug选择Serial Wire模式 2.选择时钟源 在RCC中将HSE和LSH都选择为内部晶振 3.时钟树配置 4.GPIO配置 在芯片图上选择开启的引脚和其功能 配置引脚的各自属性 5.工…

深度学习|pytorch基本运算

【1】引言 pytorch是深度学习常用的包&#xff0c;顾名思义&#xff0c;就是python适用的torch包&#xff0c;在python里面使用时直接import torch就可以调用。 需要注意的是&#xff0c;pytorch包与电脑配置、python版本有很大关系&#xff0c;一定要仔细阅读安装要求、找到…

替代 WPS 的新思路?快速将 Word 转为图片 PDF

在这个数字化办公日益普及的时代&#xff0c;越来越多的人开始关注文档处理工具的功能与体验。当我们习惯了某些便捷操作时&#xff0c;却发现一些常用功能正逐渐变为付费项目——比如 WPS 中的一项实用功能也开始收费了。 这款工具最特别的地方在于&#xff0c;可以直接把 W…

【K8S】K8S基础概念

一、 K8S组件 1.1 控制平面组件 kube-apiserver&#xff1a;公开 Kubernetes HTTP API 的核心组件服务器。 etcd&#xff1a;具备一致性和高可用性的键值存储&#xff0c;用于所有 API 服务器的数据存储。 kube-scheduler&#xff1a;查找尚未绑定到节点的 Pod&#xff0c;并将…

包含Javascript的HTML静态页面调取本机摄像头

在实际业务开发中&#xff0c;需要在带有摄像头的工作机上拍摄施工现场工作过程的图片&#xff0c;然后上传到服务器备存。 这便需要编写可以运行在浏览器上的代码&#xff0c;并在代码中实现Javascript调取摄像头、截取帧保存为图片的功能。 为了使用户更快掌握JS调取摄像头…

PCB设计实践(三十一)PCB设计中机械孔的合理设计与应用指南

一、机械孔的基本概念与分类 机械孔是PCB设计中用于实现机械固定、结构支撑、散热及电气连接的关键结构元件&#xff0c;其分类基于功能特性、制造工艺和应用场景的差异&#xff0c;主要分为以下几类&#xff1a; 1. 金属化机械孔 通过电镀工艺在孔内壁形成导电层&#xff0c;…

【Linux篇章】Linux 进程信号2:解锁系统高效运作的 “隐藏指令”,开启性能飞跃新征程(精讲捕捉信号及OS运行机制)

本篇文章将以一个小白视角&#xff0c;通俗易懂带你了解信号在产生&#xff0c;保存之后如何进行捕捉&#xff1b;以及在信号这个话题中&#xff1b;OS扮演的角色及背后是如何进行操作的&#xff1b;如何理解用户态内核态&#xff1b;还有一些可以引出的其他知识点&#xff1b;…

多功能秒达开源工具箱源码|完全开源的中文工具箱

源码介绍 完全开源的中文工具箱永远的自由软件轻量级运行全平台支持&#xff08;包括ARMv8&#xff09;类似GPT的智能支持高效UI高度集成提供Docker映像和便携式版本支持桌面版开源插件库 下载地址 百度网盘下载 提取码&#xff1a;p9ck ▌本文由 6v6-博客网 整理分享 ▶ 更多…

用nz-tabel写一个合并表格

用nz-tabel写一个合并表格 <nz-table #basicTable [nzData]"tableSearchStatus.dataList" nzBordered><thead><tr><th>班级</th><th>姓名</th><th>年龄</th><th>电话</th></tr></thead&…

leetcode hot100刷题日记——29.合并两个有序链表

解答&#xff1a; 方法一&#xff1a;递归 递归的边界条件是啥呢&#xff1f; 递归别想那么多具体步骤&#xff0c;考虑大步骤&#xff0c;小的递归自己会去做的 class Solution { public:ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {//递归比较大小//先考虑…

【计算机网络】第1章:概述—分组延时、丢失和吞吐量

目录 一、分组延时、丢失 1. 节点处理延时&#xff1a; 2. 排队延时&#xff1a; 3. 传输延时: 4. 传播延时: 5. 节点延时 6. 排队延时 7. 分组丢失 二、吞吐量 三、总结 &#xff08;一&#xff09;分组延时 1. 处理延时&#xff08;Processing Delay&#xff09; …

DeepSeek R1 模型小版本升级,DeepSeek-R1-0528都更新了哪些新特性?

DeepSeek-R1‑0528 技术剖析&#xff1a;思维链再进化&#xff0c;推理性能飙升 目录 版本概览深度思考能力再升级基准测试成绩功能与体验更新API 变动与示例模型开源与下载结语 版本概览 DeepSeek 团队今日发布 DeepSeek‑R1‑0528 —— 基于 DeepSeek V3 Base&#xff08;2…

SQL正则表达式总结

这里写目录标题 一、元字符二、正则表达函数1、 regexp_like(x,pattern[,match_option])2、 regexp_instr(x,pattern[,start[,occurrence[,return_option[, match_option]]]]) 3、 REGEXP_SUBSTR(x,pattern[,start[,occurrence[, match_option]]]) 4、 REGEXP_REPLACE(x,patter…

力扣经典算法篇-13-接雨水(较难,动态规划,加法转减法优化,双指针法)

1、题干 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 示例 1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,1] 输出&#xff1a;6 解释&#xff1a;上面是由数组 [0,1,0,2,1,0,1,3…

STM32 -- USB虚拟串口通信

本篇操作: 通过CubeMX Keil&#xff0c;配置STM32作为USB设备端&#xff0c;与电脑上位机进行通信&#xff08;CDC&#xff09;&#xff1b;通用带USB功能的 STM32 芯片 &#xff08;如F1、F4等&#xff0c;系统时钟配置不同&#xff0c;代码通用&#xff09;。 目录 一、 S…