SSM框架完整教程(包含实际项目代码)
第一章:项目基础与环境搭建
1.1 项目架构总览
浏览器客户端 嵌入式Tomcat 11 Spring MVC 6 Service业务层 MyBatis 3.5.19 HikariCP连接池 MySQL/MariaDB/PostgreSQL Thymeleaf模板引擎 PageHelper分页插件 条件化配置
1.2 项目启动流程时序图
客户端 Application.main() TomcatConfig SpringContext 数据库 启动应用程序 调用TomcatConfig.start() 创建Tomcat实例 设置端口和上下文 加载Spring配置 扫描组件和配置 初始化数据源 返回连接 Spring容器就绪 启动Web服务器 服务器启动完成 应用程序运行中 客户端 Application.main() TomcatConfig SpringContext 数据库
1.3 环境配置流程图
JDK 25 其他版本 是 否 是 否 开始环境配置 选择JDK版本 配置JAVA_HOME 升级到JDK 25 配置Maven环境 验证Maven安装 Maven正常? 创建项目目录 检查Maven配置 初始化Git仓库 创建pom.xml 配置项目依赖 验证依赖下载 依赖正常? 环境配置完成 检查网络和仓库配置
1.4 技术选型与优势
Spring 6.2.12 最新稳定版本 MyBatis 3.5.19 灵活SQL控制 Tomcat 11.0.14 Servlet 6.0支持 JDK 25 最新Java特性 Thymeleaf 现代模板引擎
1.5 依赖解析流程时序图
开发者 Maven构建工具 本地仓库 远程仓库 项目 执行mvn clean install 读取pom.xml 返回依赖列表 检查本地仓库 返回依赖文件 下载远程依赖 返回依赖文件 缓存到本地仓库 alt [依赖存在] [依赖不存在] 构建项目 构建成功 构建完成 开发者 Maven构建工具 本地仓库 远程仓库 项目
1.3 项目依赖配置
pom.xml 核心依赖配置:
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.lihaozhe</groupId>
<artifactId>ssm-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<java.version>25</java.version>
<maven.compiler.source>25</maven.compiler.target>
<maven.compiler.target>25</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- Spring版本 -->
<spring.version>6.2.12</spring.version>
<!-- MyBatis版本 -->
<mybatis.version>3.5.19</mybatis.version>
<!-- MyBatis-Spring集成版本 -->
<mybatis-spring.version>3.0.4</mybatis-spring.version>
<!-- 分页插件版本 -->
<pagehelper.version>6.1.0</pagehelper.version>
<!-- 数据库连接池版本 -->
<hikaricp.version>5.1.0</hikaricp.version>
<!-- MySQL驱动版本 -->
<mysql.version>9.1.0</mysql.version>
<!-- MariaDB驱动版本 -->
<mariadb.version>3.4.1</mariadb.version>
<!-- PostgreSQL驱动版本 -->
<postgresql.version>42.7.4</postgresql.version>
<!-- Thymeleaf版本 -->
<thymeleaf.version>3.1.2.RELEASE</thymeleaf.version>
<!-- Tomcat版本 -->
<tomcat.version>11.0.14</tomcat.version>
<!-- Servlet版本 -->
<servlet.version>6.1.0</servlet.version>
<!-- Lombok版本 -->
<lombok.version>1.18.34</lombok.version>
<!-- 日志版本 -->
<slf4j.version>2.0.16</slf4j.version>
<logback.version>1.5.12</logback.version>
</properties>
<dependencies>
<!-- Spring核心依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Spring Web MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Spring事务管理 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- MyBatis核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<!-- MyBatis-Spring集成 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis-spring.version}</version>
</dependency>
<!-- 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>${pagehelper.version}</version>
</dependency>
<!-- HikariCP连接池 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>${hikaricp.version}</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- MariaDB驱动 -->
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>${mariadb.version}</version>
</dependency>
<!-- PostgreSQL驱动 -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${postgresql.version}</version>
</dependency>
<!-- Thymeleaf模板引擎 -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring6</artifactId>
<version>${thymeleaf.version}</version>
</dependency>
<!-- 嵌入式Tomcat -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>${tomcat.version}</version>
</dependency>
<!-- Servlet API -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>${servlet.version}</version>
<scope>provided</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!-- 日志依赖 -->
<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>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>25</source>
<target>25</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
1.4 应用配置文件
application.properties 配置:
properties
# 服务器配置
server.port=8080
server.context-path=
# 数据库配置
app.db.enabled=true
app.db.type=mariadb
app.db.host=localhost
app.db.port=3306
app.db.database=ssm_demo
app.db.username=root
app.db.password=
# 静态资源配置
static.path=/static/**
# 日志配置
logging.level.com.lihaozhe=DEBUG
logging.level.org.springframework=INFO
logging.level.org.mybatis=DEBUG
1.5 项目启动入口
Application.java 主启动类:
java
package com.lihaozhe;
import com.lihaozhe.config.TomcatConfig;
import com.lihaozhe.util.LoggerUtil;
/**
* <p>功能描述:SSM项目主启动类</p>
* <p>开发思路:通过main方法启动嵌入式Tomcat服务器,实现独立运行的Web应用</p>
* <p>开发过程:</p>
* <p>1. 创建TomcatConfig实例</p>
* <p>2. 调用startTomcat方法启动服务器</p>
* <p>3. 处理启动过程中的异常</p>
*
* @author 李昊哲 李胜龙
* @version 0.0.1
*/
public class Application {
/**
* <p>功能描述:应用程序主入口方法</p>
* <p>开发思路:通过main方法启动嵌入式Tomcat服务器</p>
* <p>实现逻辑:创建TomcatConfig实例并调用启动方法</p>
*
* @param args 命令行参数
*/
public static void main(String[] args) {
try {
LoggerUtil.info("正在启动SSM项目...");
// 创建Tomcat配置实例
TomcatConfig tomcatConfig = new TomcatConfig();
// 启动嵌入式Tomcat服务器
tomcatConfig.startTomcat();
} catch (Exception e) {
LoggerUtil.error("启动SSM项目失败:{}", e.getMessage());
e.printStackTrace();
}
}
}
第二章:Spring框架核心配置
2.1 Spring配置架构
SpringConfig 组件扫描 Web MVC配置 静态资源处理 事务管理 MyBatisConfig 数据源配置 SqlSessionFactory 分页插件 事务管理器 ThymeleafConfig 模板解析器 模板引擎 视图解析器 WebConfig 过滤器配置 Servlet配置
2.2 Spring容器启动时序图
Application.main() TomcatConfig SpringContext SpringConfig MyBatisConfig 数据库 启动Tomcat服务器 初始化Spring容器 加载SpringConfig配置 返回配置信息 加载MyBatisConfig配置 创建数据源连接 返回连接池 返回SqlSessionFactory 扫描组件注册Bean Spring容器初始化完成 服务器启动成功 Application.main() TomcatConfig SpringContext SpringConfig MyBatisConfig 数据库
2.3 依赖注入流程图
2.2 Spring核心配置类
SpringConfig.java 配置类:
java
package com.lihaozhe.config;
import com.lihaozhe.util.ConfigUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* <p>功能描述:Spring框架配置类,负责Spring MVC相关配置</p>
* <p>开发思路:使用Spring的注解配置方式替代传统的XML配置,简化配置过程</p>
* <p>开发过程:</p>
* <p>1. 使用@Configuration注解标记此类为Spring配置类</p>
* <p>2. 启用Spring MVC功能以支持Web应用开发</p>
* <p>3. 配置组件扫描以自动发现和注册控制器等组件</p>
*/
@Configuration // 标记此类为Spring配置类
@EnableWebMvc // 启用Spring MVC功能,提供Web应用的基础支持
@EnableTransactionManagement // 启用注解驱动的事务管理
@PropertySource("classpath:application.properties") // 加载应用配置文件
@ComponentScan({"com.lihaozhe"}) // 扫描指定包路径下的控制器组件
public class SpringConfig implements WebMvcConfigurer {
/**
* <p>功能描述:默认构造方法</p>
* <p>开发思路:Spring配置类的默认构造函数</p>
* <p>实现逻辑:由Spring容器自动调用,无需手动实现</p>
*/
public SpringConfig() {
// Spring容器自动实例化配置类
}
/**
* <p>功能描述:配置HiddenHttpMethodFilter以支持REST风格的请求</p>
* <p>开发思路:创建HiddenHttpMethodFilter Bean,支持PUT、DELETE等HTTP方法</p>
* <p>实现逻辑:直接返回HiddenHttpMethodFilter实例</p>
*
* @return HiddenHttpMethodFilter实例
*/
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new HiddenHttpMethodFilter();
}
/**
* <p>功能描述:配置静态资源处理</p>
* <p>开发思路:配置静态资源的访问路径和存储位置</p>
* <p>实现逻辑:从配置文件读取静态路径配置,设置资源映射</p>
*
* @param registry 资源处理器注册器
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 读取配置文件中的静态路径配置
String staticPath = ConfigUtil.getProperty("static.path", "/static/**");
// 配置静态资源映射,将请求路径映射到classpath:/static/目录
registry.addResourceHandler(staticPath)
.addResourceLocations("classpath:/static/");
// 添加对根路径下静态资源的直接访问支持
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/");
}
}
2.4 MyBatis配置流程时序图
Spring容器 MyBatisConfig 数据源 SqlSessionFactory PageInterceptor 事务管理器 初始化MyBatis配置 创建数据源Bean 设置连接池参数 返回数据源实例 创建SqlSessionFactoryBean 设置数据源 加载Mapper XML 配置MyBatis参数 创建分页插件 返回插件实例 添加分页插件 返回SqlSessionFactory 创建事务管理器 返回事务管理器 配置完成 Spring容器 MyBatisConfig 数据源 SqlSessionFactory PageInterceptor 事务管理器
2.5 数据库连接池工作流程图
是 否 否 是 应用启动 初始化HikariCP 创建连接池 设置最小连接数 设置最大连接数 设置连接超时 连接池就绪 业务请求 获取数据库连接 连接池有空闲连接? 分配空闲连接 达到最大连接数? 创建新连接 等待连接释放 分配新连接 执行数据库操作 释放连接回池 连接变为空闲状态 等待下一个请求
MyBatisConfig.java 配置类:
java
package com.lihaozhe.config;
import com.github.pagehelper.PageInterceptor;
import com.zaxxer.hikari.HikariDataSource;
import com.lihaozhe.config.condition.ConditionalOnProperty;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.Properties;
/**
* <p>功能描述:MyBatis配置类,负责数据源、SqlSessionFactory和事务管理器的配置</p>
* <p>开发思路:使用Spring注解配置方式替代XML配置,简化MyBatis集成</p>
* <p>开发过程:</p>
* <p>1. 配置数据源,使用连接池管理数据库连接</p>
* <p>2. 配置SqlSessionFactory,设置MyBatis相关参数</p>
* <p>3. 配置事务管理器,支持声明式事务</p>
* <p>4. 配置分页插件,支持分页查询功能</p>
*/
@Configuration // 标记此类为Spring配置类
@ConditionalOnProperty(value = "app.db.enabled", havingValue = "true")
@MapperScan("com.lihaozhe.mapper") // 扫描Mapper接口
public class MyBatisConfig {
/**
* <p>功能描述:数据库类型</p>
* <p>取值范围:字符串,数据库类型(mysql/mariadb/postgresql)</p>
* <p>默认值:从配置文件读取</p>
*/
@Value("${app.db.type:mariadb}")
private String dbType;
/**
* <p>功能描述:数据库主机地址</p>
* <p>取值范围:字符串,数据库服务器地址</p>
* <p>默认值:从配置文件读取</p>
*/
@Value("${app.db.host:localhost}")
private String host;
/**
* <p>功能描述:数据库端口</p>
* <p>取值范围:字符串,数据库端口号</p>
* <p>默认值:从配置文件读取</p>
*/
@Value("${app.db.port:3306}")
private String port;
/**
* <p>功能描述:数据库名称</p>
* <p>取值范围:字符串,数据库名</p>
* <p>默认值:从配置文件读取</p>
*/
@Value("${app.db.database:ssm_demo}")
private String database;
/**
* <p>功能描述:数据库用户名</p>
* <p>取值范围:字符串,数据库登录用户名</p>
* <p>默认值:从配置文件读取</p>
*/
@Value("${app.db.username:root}")
private String username;
/**
* <p>功能描述:数据库密码</p>
* <p>取值范围:字符串,数据库登录密码</p>
* <p>默认值:从配置文件读取</p>
*/
@Value("${app.db.password:}")
private String password;
/**
* <p>功能描述:配置数据源</p>
* <p>开发思路:使用HikariCP作为高性能连接池</p>
* <p>实现逻辑:从配置文件读取数据库连接参数,创建数据源Bean</p>
*
* @return 数据源实例
*/
@Bean
public DataSource dataSource() {
// 创建Hikari连接池数据源
HikariDataSource dataSource = new HikariDataSource();
// 根据数据库类型设置驱动类名和URL
String driverClassName;
String url = switch (dbType.toLowerCase()) {
case "mysql" -> {
driverClassName = "com.mysql.cj.jdbc.Driver";
yield "jdbc:mysql://" + host + ":" + port + "/" + database
+ "?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";
}
case "postgresql" -> {
driverClassName = "org.postgresql.Driver";
yield "jdbc:postgresql://" + host + ":" + port + "/" + database;
}
default -> {
driverClassName = "org.mariadb.jdbc.Driver";
yield "jdbc:mariadb://" + host + ":" + port + "/" + database
+ "?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai";
}
};
// 设置数据库连接参数
dataSource.setDriverClassName(driverClassName);
dataSource.setJdbcUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
// 设置连接池参数
dataSource.setAutoCommit(false);
dataSource.setMaximumPoolSize(20);
dataSource.setMinimumIdle(5);
dataSource.setConnectionTimeout(30000);
dataSource.setIdleTimeout(600000);
dataSource.setMaxLifetime(1800000);
return dataSource;
}
/**
* <p>功能描述:配置SqlSessionFactory</p>
* <p>开发思路:创建SqlSessionFactoryBean并配置相关属性</p>
* <p>实现逻辑:设置数据源、Mapper XML文件位置、分页插件等</p>
*
* @return SqlSessionFactory实例
* @throws Exception 配置过程中可能抛出的异常
*/
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
// 创建SqlSessionFactoryBean
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
// 设置数据源
factoryBean.setDataSource(dataSource());
// 设置Mapper XML文件位置
factoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/*.xml"));
// 设置MyBatis配置
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true); // 开启驼峰命名转换
configuration.setCacheEnabled(true); // 开启二级缓存
configuration.setLazyLoadingEnabled(true); // 开启延迟加载
factoryBean.setConfiguration(configuration);
// 添加分页插件
factoryBean.setPlugins(pageInterceptor());
return factoryBean.getObject();
}
/**
* <p>功能描述:配置事务管理器</p>
* <p>开发思路:使用DataSourceTransactionManager作为事务管理器</p>
* <p>实现逻辑:创建事务管理器Bean,绑定数据源</p>
*
* @return 事务管理器实例
*/
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
/**
* <p>功能描述:配置分页插件</p>
* <p>开发思路:使用PageHelper插件实现分页功能</p>
* <p>实现逻辑:创建PageInterceptor并设置相关参数</p>
*
* @return 分页插件实例
*/
@Bean
public PageInterceptor pageInterceptor() {
PageInterceptor interceptor = new PageInterceptor();
Properties properties = new Properties();
// 根据数据库类型设置分页参数
String dialect;
switch (dbType.toLowerCase()) {
case "mysql":
dialect = "mysql";
break;
case "postgresql":
dialect = "postgresql";
break;
case "mariadb":
default:
dialect = "mysql";
break;
}
properties.setProperty("helperDialect", dialect);
properties.setProperty("reasonable", "true");
properties.setProperty("supportMethodsArguments", "true");
properties.setProperty("params", "count=countSql");
interceptor.setProperties(properties);
return interceptor;
}
}
第三章:嵌入式Tomcat深度解析
3.1 Tomcat配置架构
TomcatConfig Tomcat实例创建 连接器配置 Web上下文配置 Spring集成 过滤器配置 端口设置 URIEncoding配置 ServletContext绑定 DispatcherServlet注册 字符编码过滤器 HiddenHttpMethodFilter
3.3 Tomcat启动时序图
Application.main() TomcatConfig Tomcat实例 Web上下文 Spring容器 DispatcherServlet 过滤器链 调用startTomcat() 创建Tomcat实例 设置端口(8080) 初始化连接器 设置URIEncoding(UTF-8) 创建Web应用上下文 添加字符编码过滤器 添加HiddenHttpMethodFilter 创建Spring Web应用上下文 注册配置类 刷新容器 创建DispatcherServlet 添加Servlet映射 启动服务器 服务器启动成功 Application.main() TomcatConfig Tomcat实例 Web上下文 Spring容器 DispatcherServlet 过滤器链
3.4 Spring MVC请求处理流程图
HTTP请求 Tomcat接收请求 过滤器链处理 字符编码过滤器 HiddenHttpMethodFilter DispatcherServlet HandlerMapping 查找Controller 执行Controller方法 返回ModelAndView ViewResolver 渲染视图 返回HTTP响应 POST请求 设置请求编码 CharacterEncodingFilter 转换为UTF-8编码 继续后续处理 PUT/DELETE请求 HiddenHttpMethodFilter 转换_method参数 设置HTTP方法 继续后续处理
3.5 嵌入式Tomcat配置实现
TomcatConfig.java 配置类:
java
package com.lihaozhe.config;
import com.lihaozhe.filter.CharacterEncodingFilter;
import com.lihaozhe.util.ConfigUtil;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import jakarta.servlet.ServletContext;
/**
* <p>嵌入式Tomcat服务器配置类 (基于Tomcat 11 / Servlet 6)</p>
* <p>开发思路:通过编程方式配置和启动嵌入式Tomcat服务器,实现独立运行的Web应用</p>
* <p>开发过程:</p>
* <p>1. 创建Tomcat实例并配置基本参数</p>
* <p>2. 创建Spring Web应用上下文并注册配置类</p>
* <p>3. 将Spring MVC的DispatcherServlet集成到Tomcat中</p>
* <p>4. 启动服务器并保持运行状态</p>
*/
public class TomcatConfig {
/**
* <p>启动嵌入式Tomcat服务器 (Tomcat 11 / Servlet 6)</p>
* <p>功能描述:配置并启动嵌入式Tomcat服务器,集成Spring MVC框架</p>
* <p>实现逻辑:创建Tomcat实例,配置Spring上下文,注册DispatcherServlet,启动服务器</p>
*/
public void startTomcat() throws LifecycleException {
// 读取配置
int port = ConfigUtil.getIntProperty("server.port", 8080);
String contextPath = ConfigUtil.getProperty("server.context-path", "");
// 1. 创建Tomcat实例,指定端口
Tomcat tomcat = new Tomcat();
// 设置服务器监听端口
tomcat.setPort(port);
// 初始化连接器(Tomcat11版本必须显式调用此方法)
tomcat.getConnector();
// 设置Tomcat连接器的URI编码为UTF-8,解决中文乱码问题
tomcat.getConnector().setURIEncoding("UTF-8");
// 设置连接器使用UTF-8编码处理请求体
tomcat.getConnector().setUseBodyEncodingForURI(true);
// 2. 创建Tomcat的Web应用上下文
// 第一个参数为上下文路径,第二个参数为文档基础路径(null表示使用默认)
Context tomcatContext = tomcat.addContext(contextPath, null);
// 添加字符编码过滤器,解决POST请求中文乱码问题
org.apache.tomcat.util.descriptor.web.FilterDef filterDef = new org.apache.tomcat.util.descriptor.web.FilterDef();
filterDef.setFilterName("characterEncodingFilter");
filterDef.setFilterClass(CharacterEncodingFilter.class.getName());
tomcatContext.addFilterDef(filterDef);
org.apache.tomcat.util.descriptor.web.FilterMap filterMap = new org.apache.tomcat.util.descriptor.web.FilterMap();
filterMap.setFilterName("characterEncodingFilter");
filterMap.addURLPattern("/*");
tomcatContext.addFilterMap(filterMap);
// 添加HiddenHttpMethodFilter支持REST风格的请求(PUT, DELETE等)
org.apache.tomcat.util.descriptor.web.FilterDef methodFilterDef = new org.apache.tomcat.util.descriptor.web.FilterDef();
methodFilterDef.setFilterName("hiddenHttpMethodFilter");
methodFilterDef.setFilterClass("org.springframework.web.filter.HiddenHttpMethodFilter");
tomcatContext.addFilterDef(methodFilterDef);
org.apache.tomcat.util.descriptor.web.FilterMap methodFilterMap = new org.apache.tomcat.util.descriptor.web.FilterMap();
methodFilterMap.setFilterName("hiddenHttpMethodFilter");
methodFilterMap.addURLPattern("/*");
tomcatContext.addFilterMap(methodFilterMap);
// 3. 创建Spring Web应用上下文(基于注解配置)
AnnotationConfigWebApplicationContext springContext = new AnnotationConfigWebApplicationContext();
// 注册Spring配置类和Thymeleaf配置类
springContext.register(SpringConfig.class, ThymeleafConfig.class, WebConfig.class);
// 4. 将Tomcat的ServletContext绑定到Spring上下文
// 从Tomcat Context中获取标准的ServletContext实例
ServletContext servletContext = tomcatContext.getServletContext();
// 设置Spring上下文的ServletContext,建立Spring与Tomcat的关联
springContext.setServletContext(servletContext);
// 5. 注册Spring MVC的DispatcherServlet到Tomcat
// 创建DispatcherServlet实例,传入Spring上下文
DispatcherServlet dispatcherServlet = new DispatcherServlet(springContext);
// 将DispatcherServlet添加到Tomcat上下文中,命名为"dispatcher"
Tomcat.addServlet(tomcatContext, "dispatcher", dispatcherServlet);
// 配置Servlet映射,将所有请求路径("/")映射到dispatcher servlet
tomcatContext.addServletMappingDecoded("/", "dispatcher");
// 6. 启动Tomcat服务器
tomcat.start();
// 输出启动成功信息到控制台
System.out.println("嵌入式Tomcat11启动成功,端口:" + port + ",上下文路径:" + (contextPath.isEmpty() ? "/" : contextPath));
// 阻塞主线程,保持服务器运行状态(防止启动后立即退出)
tomcat.getServer().await();
}
}
3.3 字符编码过滤器
CharacterEncodingFilter.java 过滤器:
java
package com.lihaozhe.filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import java.io.IOException;
/**
* <p>功能描述:字符编码过滤器,解决请求和响应的中文乱码问题</p>
* <p>开发思路:实现Filter接口,在请求处理前后设置字符编码</p>
* <p>开发过程:</p>
* <p>1. 实现Filter接口的init、doFilter、destroy方法</p>
* <p>2. 在doFilter方法中设置请求和响应的字符编码</p>
* <p>3. 调用filterChain.doFilter继续执行后续过滤器</p>
*/
@WebFilter(urlPatterns = "/*")
public class CharacterEncodingFilter implements Filter {
/**
* <p>功能描述:过滤器初始化方法</p>
* <p>开发思路:在过滤器初始化时输出日志信息</p>
* <p>实现逻辑:调用FilterConfig的初始化方法</p>
*
* @param filterConfig 过滤器配置对象
* @throws ServletException Servlet异常
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
System.out.println("字符编码过滤器初始化完成");
}
/**
* <p>功能描述:过滤器核心处理方法</p>
* <p>开发思路:在请求处理前后设置字符编码</p>
* <p>实现逻辑:设置请求和响应的字符编码为UTF-8,然后继续执行后续过滤器</p>
*
* @param servletRequest Servlet请求对象
* @param servletResponse Servlet响应对象
* @param filterChain 过滤器链对象
* @throws IOException IO异常
* @throws ServletException Servlet异常
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
// 设置请求字符编码为UTF-8
servletRequest.setCharacterEncoding("UTF-8");
// 设置响应字符编码为UTF-8
servletResponse.setCharacterEncoding("UTF-8");
// 设置响应内容类型
servletResponse.setContentType("text/html;charset=UTF-8");
// 继续执行过滤器链中的下一个过滤器
filterChain.doFilter(servletRequest, servletResponse);
}
/**
* <p>功能描述:过滤器销毁方法</p>
* <p>开发思路:在过滤器销毁时输出日志信息</p>
* <p>实现逻辑:调用Filter的销毁方法</p>
*/
@Override
public void destroy() {
Filter.super.destroy();
System.out.println("字符编码过滤器销毁完成");
}
}
第四章:数据持久层开发
4.1 数据持久层架构
Controller Service Mapper Interface MyBatis SQL执行 HikariCP连接池 数据库 PageHelper 动态SQL 结果映射
4.3 数据持久层操作时序图
Controller Service层 Mapper接口 MyBatis 连接池 数据库 调用业务方法 调用数据访问方法 执行SQL映射 获取数据库连接 返回连接对象 执行SQL语句 返回查询结果 结果映射处理 释放连接回池 返回映射结果 返回数据对象 返回业务结果 Controller Service层 Mapper接口 MyBatis 连接池 数据库
4.4 MyBatis动态SQL流程图
是 否 Mapper接口调用 MyBatis解析方法签名 查找对应的SQL映射 解析动态SQL标签 是否包含动态条件? 解析if/choose/where标签 使用静态SQL 构建动态SQL语句 参数绑定 SQL预编译 执行SQL 结果集处理 对象映射 返回结果对象 分页查询 PageHelper拦截 计算分页参数 修改原始SQL 添加COUNT查询 执行分页SQL 包装分页结果
4.5 用户实体类设计
User.java 实体类:
java
package com.lihaozhe.entity;
import java.time.LocalDateTime;
/**
* <p>功能描述:用户实体类,对应数据库中的用户表</p>
* <p>开发思路:定义用户实体的基本属性和方法,用于数据传输和持久化</p>
* <p>开发过程:</p>
* <p>1. 定义用户的基本属性:ID、用户名、邮箱、创建时间、更新时间</p>
* <p>2. 提供无参构造函数和带参构造函数</p>
* <p>3. 为每个属性提供getter和setter方法</p>
* <p>4. 重写toString方法以便调试和日志输出</p>
*
* @author 李昊哲 李胜龙
* @version 0.0.1
*/
public class User {
/**
* <p>功能描述:用户唯一标识符</p>
* <p>取值范围:大于0的长整型数字</p>
*/
private Long id;
/**
* <p>功能描述:用户名</p>
* <p>取值范围:3-50个字符,不能为空</p>
*/
private String username;
/**
* <p>功能描述:用户邮箱</p>
* <p>取值范围:有效的邮箱格式字符串</p>
*/
private String email;
/**
* <p>功能描述:用户创建时间</p>
* <p>取值范围:LocalDateTime对象,表示用户创建的具体时间</p>
*/
private LocalDateTime createTime;
/**
* <p>功能描述:用户信息更新时间</p>
* <p>取值范围:LocalDateTime对象,表示用户信息最后更新的时间</p>
*/
private LocalDateTime updateTime;
/**
* <p>功能描述:无参构造函数</p>
* <p>开发思路:提供默认构造函数以便框架使用反射创建对象</p>
* <p>实现逻辑:创建User对象实例</p>
*/
public User() {
// 默认构造函数
}
/**
* <p>功能描述:带参构造函数</p>
* <p>开发思路:提供便捷方式创建包含指定属性的User对象</p>
* <p>实现逻辑:创建User对象实例并初始化指定属性</p>
*
* @param id 用户ID
* @param username 用户名
* @param email 用户邮箱
*/
public User(Long id, String username, String email) {
this.id = id;
this.username = username;
this.email = email;
}
/**
* <p>功能描述:获取用户ID</p>
* <p>开发思路:提供获取用户唯一标识符的方法</p>
* <p>实现逻辑:直接返回id属性值</p>
*
* @return 用户ID
*/
public Long getId() {
return id;
}
/**
* <p>功能描述:设置用户ID</p>
* <p>开发思路:提供设置用户唯一标识符的方法</p>
* <p>实现逻辑:将传入参数赋值给id属性</p>
*
* @param id 用户ID
*/
public void setId(Long id) {
this.id = id;
}
/**
* <p>功能描述:获取用户名</p>
* <p>开发思路:提供获取用户名的方法</p>
* <p>实现逻辑:直接返回username属性值</p>
*
* @return 用户名
*/
public String getUsername() {
return username;
}
/**
* <p>功能描述:设置用户名</p>
* <p>开发思路:提供设置用户名的方法</p>
* <p>实现逻辑:将传入参数赋值给username属性</p>
*
* @param username 用户名
*/
public void setUsername(String username) {
this.username = username;
}
/**
* <p>功能描述:获取用户邮箱</p>
* <p>开发思路:提供获取用户邮箱的方法</p>
* <p>实现逻辑:直接返回email属性值</p>
*
* @return 用户邮箱
*/
public String getEmail() {
return email;
}
/**
* <p>功能描述:设置用户邮箱</p>
* <p>开发思路:提供设置用户邮箱的方法</p>
* <p>实现逻辑:将传入参数赋值给email属性</p>
*
* @param email 用户邮箱
*/
public void setEmail(String email) {
this.email = email;
}
/**
* <p>功能描述:获取用户创建时间</p>
* <p>开发思路:提供获取用户创建时间的方法</p>
* <p>实现逻辑:直接返回createTime属性值</p>
*
* @return 用户创建时间
*/
public LocalDateTime getCreateTime() {
return createTime;
}
/**
* <p>功能描述:设置用户创建时间</p>
* <p>开发思路:提供设置用户创建时间的方法</p>
* <p>实现逻辑:将传入参数赋值给createTime属性</p>
*
* @param createTime 用户创建时间
*/
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
/**
* <p>功能描述:获取用户更新时间</p>
* <p>开发思路:提供获取用户更新时间的方法</p>
* <p>实现逻辑:直接返回updateTime属性值</p>
*
* @return 用户更新时间
*/
public LocalDateTime getUpdateTime() {
return updateTime;
}
/**
* <p>功能描述:设置用户更新时间</p>
* <p>开发思路:提供设置用户更新时间的方法</p>
* <p>实现逻辑:将传入参数赋值给updateTime属性</p>
*
* @param updateTime 用户更新时间
*/
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
/**
* <p>功能描述:重写toString方法</p>
* <p>开发思路:提供User对象的字符串表示,便于调试和日志输出</p>
* <p>实现逻辑:将User对象的所有属性格式化为字符串</p>
*
* @return User对象的字符串表示
*/
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", email='" + email + '\'' +
", createTime=" + createTime +
", updateTime=" + updateTime +
'}';
}
}
4.3 Mapper接口设计与实现
UserMapper.java 数据访问接口:
java
package com.lihaozhe.mapper;
import com.lihaozhe.entity.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
/**
* <p>功能描述:用户数据访问层接口,定义用户相关的数据库操作方法</p>
* <p>开发思路:使用MyBatis注解方式定义SQL操作,简化XML配置</p>
* <p>开发过程:</p>
* <p>1. 定义用户CRUD操作的基本方法</p>
* <p>2. 使用MyBatis注解配置SQL语句</p>
* <p>3. 支持分页查询和条件查询</p>
*/
@Mapper
public interface UserMapper {
/**
* <p>功能描述:插入用户信息</p>
* <p>开发思路:使用@Insert注解定义插入SQL语句</p>
* <p>实现逻辑:将用户对象插入到数据库中,自动生成主键</p>
*
* @param user 用户对象
* @return 受影响的行数
*/
@Insert("INSERT INTO user (username, email, created_at, updated_at) " +
"VALUES (#{username}, #{email}, NOW(), NOW())")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertUser(User user);
/**
* <p>功能描述:根据ID删除用户</p>
* <p>开发思路:使用@Delete注解定义删除SQL语句</p>
* <p>实现逻辑:根据用户ID删除对应的用户记录</p>
*
* @param id 用户ID
* @return 受影响的行数
*/
@Delete("DELETE FROM user WHERE id = #{id}")
int deleteUserById(Long id);
/**
* <p>功能描述:更新用户信息</p>
* <p>开发思路:使用@Update注解定义更新SQL语句</p>
* <p>实现逻辑:根据用户ID更新用户信息</p>
*
* @param user 用户对象
* @return 受影响的行数
*/
@Update("UPDATE user SET username = #{username}, " +
"email = #{email}, updated_at = NOW() " +
"WHERE id = #{id}")
int updateUser(User user);
/**
* <p>功能描述:根据ID查询用户</p>
* <p>开发思路:使用@Select注解定义查询SQL语句</p>
* <p>实现逻辑:根据用户ID查询对应的用户信息</p>
*
* @param id 用户ID
* @return 用户对象
*/
@Select("SELECT id, username, email, created_at as createTime, updated_at as updateTime " +
"FROM user WHERE id = #{id}")
User selectUserById(Long id);
/**
* <p>功能描述:查询所有用户</p>
* <p>开发思路:使用@Select注解定义查询SQL语句</p>
* <p>实现逻辑:查询数据库中所有用户信息</p>
*
* @return 用户列表
*/
@Select("SELECT id, username, email, created_at as createTime, updated_at as updateTime " +
"FROM user ORDER BY created_at DESC")
List<User> selectAllUsers();
/**
* <p>功能描述:根据用户名查询用户</p>
* <p>开发思路:使用@Select注解定义查询SQL语句</p>
* <p>实现逻辑:根据用户名查询对应的用户信息</p>
*
* @param username 用户名
* @return 用户对象
*/
@Select("SELECT id, username, email, created_at as createTime, updated_at as updateTime " +
"FROM user WHERE username = #{username}")
User selectUserByUsername(String username);
/**
* <p>功能描述:根据条件查询用户列表</p>
* <p>开发思路:使用@SelectProvider注解动态生成SQL语句</p>
* <p>实现逻辑:根据传入的条件动态构建查询SQL</p>
*
* @param user 查询条件对象
* @return 用户列表
*/
@SelectProvider(type = UserSqlProvider.class, method = "selectUsersByCondition")
List<User> selectUsersByCondition(User user);
/**
* <p>功能描述:SQL提供者类,用于动态生成SQL语句</p>
* <p>开发思路:提供动态SQL生成方法,支持条件查询</p>
* <p>实现逻辑:根据传入的条件动态构建SQL语句</p>
*/
class UserSqlProvider {
/**
* <p>功能描述:生成条件查询SQL</p>
* <p>开发思路:根据用户对象的属性动态构建WHERE条件</p>
* <p>实现逻辑:使用StringBuilder动态拼接SQL语句</p>
*
* @param user 查询条件对象
* @return 动态生成的SQL语句
*/
public String selectUsersByCondition(User user) {
// 创建SQL构建器
StringBuilder sql = new StringBuilder();
sql.append("SELECT id, username, email, created_at as createTime, updated_at as updateTime ");
sql.append("FROM user WHERE 1=1");
// 动态添加查询条件
if (user.getUsername() != null && !user.getUsername().isEmpty()) {
sql.append(" AND username LIKE CONCAT('%', #{username}, '%')");
}
if (user.getEmail() != null && !user.getEmail().isEmpty()) {
sql.append(" AND email LIKE CONCAT('%', #{email}, '%')");
}
// 添加排序条件
sql.append(" ORDER BY created_at DESC");
return sql.toString();
}
}
}
第五章:业务逻辑层开发
5.2 业务逻辑层处理时序图
Controller层 Service接口 Service实现 Mapper接口 事务管理器 调用业务方法 委托给实现类 开启事务 事务开始 执行数据操作 返回操作结果 业务逻辑处理 提交事务 事务提交成功 返回业务结果 返回处理结果 异常情况处理 回滚事务 事务回滚成功 抛出业务异常 Controller层 Service接口 Service实现 Mapper接口 事务管理器
5.3 事务管理流程图
5.4 业务逻辑层架构
Controller Service Interface Service Implementation Mapper Interface 事务管理 业务验证 日志记录 PageHelper 异常处理
5.5 Service接口设计
UserService.java 服务接口:
java
package com.lihaozhe.service;
import com.github.pagehelper.PageInfo;
import com.lihaozhe.entity.User;
import java.util.List;
/**
* <p>功能描述:用户服务接口,定义用户相关的业务逻辑操作</p>
* <p>开发思路:定义用户相关的业务逻辑操作接口,提供用户管理的标准方法</p>
* <p>开发过程:</p>
* <p>1. 定义用户查询方法:支持分页查询和非分页查询</p>
* <p>2. 定义用户CRUD操作:创建、读取、更新、删除</p>
* <p>3. 使用泛型和PageInfo提供类型安全的分页支持</p>
* <p>4. 所有方法返回明确的业务结果类型</p>
*
* @author 李昊哲 李胜龙
* @version 0.0.1
*/
public interface UserService {
/**
* <p>功能描述:获取所有用户(分页)</p>
* <p>开发思路:分页查询系统中的所有用户信息</p>
* <p>实现逻辑:根据页码和每页记录数进行分页查询,返回分页信息对象</p>
*
* @param pageNum 当前页码,从1开始计数
* @param pageSize 每页显示的记录数量
* @return 包含用户列表和分页信息的PageInfo对象
*/
PageInfo<User> getAllUsers(int pageNum, int pageSize);
/**
* <p>功能描述:获取所有用户(不分页)</p>
* <p>开发思路:查询系统中的所有用户信息,不进行分页</p>
* <p>实现逻辑:直接返回所有用户的列表,适用于数据量不大的场景</p>
*
* @return 包含所有用户信息的列表
*/
List<User> getAllUsers();
/**
* <p>功能描述:根据ID获取用户</p>
* <p>开发思路:根据用户唯一标识符查询特定用户的详细信息</p>
* <p>实现逻辑:通过用户ID在数据库中查找对应的用户记录</p>
*
* @param id 用户的唯一标识符
* @return 找到的用户对象,如果不存在则返回null
*/
User getUserById(Long id);
/**
* <p>功能描述:创建用户</p>
* <p>开发思路:在系统中创建新的用户记录</p>
* <p>实现逻辑:将用户对象保存到数据库中,返回操作结果</p>
*
* @param user 包含用户信息的对象,不能为null
* @return 创建成功返回true,失败返回false
*/
boolean createUser(User user);
/**
* <p>功能描述:更新用户</p>
* <p>开发思路:更新系统中已存在的用户信息</p>
* <p>实现逻辑:根据用户ID更新数据库中的用户记录</p>
*
* @param user 包含更新后用户信息的对象,ID不能为null
* @return 更新成功返回true,失败返回false
*/
boolean updateUser(User user);
/**
* <p>功能描述:删除用户</p>
* <p>开发思路:根据ID从系统中删除指定的用户记录</p>
* <p>实现逻辑:根据用户ID从数据库中删除对应的用户记录</p>
*
* @param id 要删除的用户的唯一标识符
* @return 删除成功返回true,失败返回false
*/
boolean deleteUser(Long id);
}
5.6 Service实现类详解
UserServiceImpl.java 服务实现类:
java
package com.lihaozhe.service.impl;
import com.github.pagehelper.PageInfo;
import com.lihaozhe.entity.User;
import com.lihaozhe.mapper.UserMapper;
import com.lihaozhe.service.UserService;
import com.lihaozhe.util.LoggerUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.List;
/**
* <p>功能描述:用户服务实现类,实现UserService接口定义的所有业务方法</p>
* <p>开发思路:通过Spring依赖注入获取UserMapper,实现用户相关的业务逻辑</p>
* <p>开发过程:</p>
* <p>1. 使用@Service注解标记此类为Spring服务组件</p>
* <p>2. 通过@Autowired注入UserMapper依赖</p>
* <p>3. 实现用户CRUD操作和分页查询功能</p>
*/
@Service // 标记此类为Spring服务组件
@Transactional // 启用声明式事务管理
public class UserServiceImpl implements UserService {
/**
* <p>功能描述:用户数据访问层接口实例</p>
* <p>取值范围:UserMapper接口的实现类实例</p>
* <p>默认值:通过Spring容器自动注入</p>
*/
@Autowired(required = false) // 设置为非必需,当数据库未启用时不注入
private UserMapper userMapper;
/**
* <p>功能描述:默认构造方法</p>
* <p>开发思路:Spring服务实现类的默认构造函数</p>
* <p>实现逻辑:由Spring容器自动调用,无需手动实现</p>
*/
public UserServiceImpl() {
// Spring容器自动实例化服务实现类
}
/**
* <p>功能描述:根据ID查询用户信息</p>
* <p>开发思路:调用UserMapper的selectByPrimaryKey方法获取用户数据</p>
* <p>实现逻辑:直接委托给数据访问层执行查询操作</p>
*
* @param id 用户ID,用于唯一标识用户
* @return 用户对象,如果不存在则返回null
*/
@Override
public User getUserById(Long id) {
// 如果userMapper未注入(数据库未启用),则返回null
if (userMapper == null) {
LoggerUtil.info("数据库未启用,无法查询用户: {}", id);
return null;
}
// 记录查询操作日志
LoggerUtil.info("根据ID查询用户: {}", id);
return userMapper.selectUserById(id);
}
/**
* <p>功能描述:查询所有用户信息</p>
* <p>开发思路:调用UserMapper的selectAll方法获取所有用户数据</p>
* <p>实现逻辑:直接委托给数据访问层执行查询操作</p>
*
* @return 用户列表,如果无数据则返回空列表
*/
@Override
public List<User> getAllUsers() {
// 如果userMapper未注入(数据库未启用),则返回空列表
if (userMapper == null) {
LoggerUtil.info("数据库未启用,无法查询所有用户信息");
return Collections.emptyList();
}
// 记录查询所有用户操作日志
LoggerUtil.info("查询所有用户信息");
return userMapper.selectAllUsers();
}
/**
* <p>功能描述:分页查询用户信息</p>
* <p>开发思路:使用PageHelper插件实现分页功能</p>
* <p>实现逻辑:设置分页参数后查询,然后封装为PageInfo对象</p>
*
* @param pageNum 页码,从1开始
* @param pageSize 每页记录数
* @return 分页信息对象,包含用户数据和分页参数
*/
@Override
public PageInfo<User> getAllUsers(int pageNum, int pageSize) {
// 如果userMapper未注入(数据库未启用),则返回空的PageInfo
if (userMapper == null) {
LoggerUtil.info("数据库未启用,无法分页查询用户信息");
return new PageInfo<>(Collections.emptyList());
}
// 记录分页查询操作日志
LoggerUtil.info("分页查询用户信息,页码: {}, 每页大小: {}", pageNum, pageSize);
// 设置分页参数
com.github.pagehelper.PageHelper.startPage(pageNum, pageSize);
// 执行查询
List<User> users = userMapper.selectAllUsers();
// 封装分页结果
return new PageInfo<>(users);
}
/**
* <p>功能描述:新增用户</p>
* <p>开发思路:调用UserMapper的insert方法插入用户数据</p>
* <p>实现逻辑:直接委托给数据访问层执行插入操作</p>
*
* @param user 用户对象,包含要插入的用户信息
* @return 受影响的行数,成功返回1,失败返回0
*/
@Override
public boolean createUser(User user) {
// 如果userMapper未注入(数据库未启用),则返回false
if (userMapper == null) {
LoggerUtil.info("数据库未启用,无法创建用户: {}", user.getUsername());
return false;
}
// 记录新增用户操作日志
LoggerUtil.info("新增用户: {}", user.getUsername());
return userMapper.insertUser(user) > 0;
}
/**
* <p>功能描述:更新用户信息</p>
* <p>开发思路:调用UserMapper的updateByPrimaryKey方法更新用户数据</p>
* <p>实现逻辑:直接委托给数据访问层执行更新操作</p>
*
* @param user 用户对象,包含要更新的用户信息
* @return 受影响的行数,成功返回1,失败返回0
*/
@Override
public boolean updateUser(User user) {
// 如果userMapper未注入(数据库未启用),则返回false
if (userMapper == null) {
LoggerUtil.info("数据库未启用,无法更新用户信息: {}", user.getId());
return false;
}
// 记录更新用户操作日志
LoggerUtil.info("更新用户信息: {}", user.getId());
return userMapper.updateUser(user) > 0;
}
/**
* <p>功能描述:根据ID删除用户</p>
* <p>开发思路:调用UserMapper的deleteByPrimaryKey方法删除用户数据</p>
* <p>实现逻辑:直接委托给数据访问层执行删除操作</p>
*
* @param id 用户ID,用于唯一标识要删除的用户
* @return 受影响的行数,成功返回1,失败返回0
*/
@Override
public boolean deleteUser(Long id) {
// 如果userMapper未注入(数据库未启用),则返回false
if (userMapper == null) {
LoggerUtil.info("数据库未启用,无法删除用户: {}", id);
return false;
}
// 记录删除用户操作日志
LoggerUtil.info("删除用户: {}", id);
return userMapper.deleteUserById(id) > 0;
}
}
第六章:控制器层开发
6.2 控制器请求处理时序图
客户端 Tomcat服务器 过滤器链 DispatcherServlet UserController UserService UserMapper 视图解析器 发送HTTP请求 应用过滤器链 字符编码处理 HiddenHttpMethod处理 转发请求 根据路径映射调用 调用业务方法 执行数据操作 返回数据结果 返回业务结果 数据处理和封装 返回视图名称和Model 解析视图 渲染HTML页面 返回渲染结果 返回HTTP响应 返回HTTP响应 返回HTML页面 客户端 Tomcat服务器 过滤器链 DispatcherServlet UserController UserService UserMapper 视图解析器
6.3 RESTful API设计流程图
是 否 是 否 是 否 是 否 是 否 HTTP请求 请求方法判断 GET请求? 查询操作 POST请求? 创建操作 PUT请求? 更新操作 DELETE请求? 删除操作 不支持的方法 参数绑定 请求体解析 路径参数提取 路径参数提取 数据验证 验证通过? 调用Service方法 返回错误响应 处理业务逻辑 返回结果 响应格式化 返回HTTP响应 错误信息封装 405 Method Not Allowed
6.4 控制器层架构
HTTP请求 DispatcherServlet UserController UserService 数据绑定 参数验证 异常处理 Thymeleaf视图 RESTful API 分页参数
6.5 用户控制器实现
UserController.java 控制器类:
java
package com.lihaozhe.controller;
import com.github.pagehelper.PageInfo;
import com.lihaozhe.entity.User;
import com.lihaozhe.service.UserService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
/**
* <p>功能描述:用户控制器,处理用户相关的HTTP请求</p>
* <p>开发思路:使用Spring MVC的@Controller注解创建控制器类,通过@RequestMapping及其变体注解映射不同的HTTP请求路径,调用UserService处理业务逻辑,并返回相应的视图页面</p>
* <p>开发过程:1. 定义用户相关的各种操作方法(增删改查);2. 使用合适的注解映射HTTP请求方法和路径;3. 处理请求参数并调用服务层方法;4. 将处理结果添加到Model中并返回视图名称</p>
*/
@Controller
@RequestMapping("/users")
public class UserController {
/**
* <p>功能描述:用户服务实例</p>
* <p>取值范围:通过Spring依赖注入的UserService实现类实例</p>
*/
private final UserService userService;
/**
* <p>功能描述:构造函数注入UserService</p>
* <p>开发思路:通过构造函数注入UserService实例,确保依赖的不可变性</p>
* <p>开发过程:使用final修饰符确保userService不可变,通过构造函数参数接收Spring容器注入的UserService实例</p>
*
* @param userService 用户服务实例
*/
public UserController(UserService userService) {
this.userService = userService;
}
/**
* <p>功能描述:获取用户列表(支持分页)</p>
* <p>开发思路:处理GET请求获取用户列表,支持分页功能,将查询结果封装到PageInfo对象中传递给前端页面</p>
* <p>开发过程:1. 接收分页参数pageNum和pageSize;2. 调用userService.getAllUsers方法获取分页数据;3. 将PageInfo对象添加到Model中;4. 返回user-list视图</p>
*
* @param pageNum 页码,默认为1
* @param pageSize 每页记录数,默认为10
* @param model Model对象
* @return 用户列表页面
*/
@GetMapping
public String getUsers(@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize,
Model model) {
// 调用服务层方法获取用户分页数据
PageInfo<User> pageInfo = userService.getAllUsers(pageNum, pageSize);
// 将分页数据添加到Model中
model.addAttribute("pageInfo", pageInfo);
// 如果pageInfo列表为空且数据库可能未启用,添加提示信息
if (pageInfo.getList().isEmpty()) {
model.addAttribute("message", "数据库未启用或无用户数据");
}
// 返回用户列表视图
return "user-list";
}
/**
* <p>功能描述:根据ID获取用户详情</p>
* <p>开发思路:处理GET请求根据用户ID获取用户详细信息,并将用户信息传递给前端详情页面</p>
* <p>开发过程:1. 接收用户ID参数;2. 调用userService.getUserById方法获取用户信息;3. 将用户信息添加到Model中;4. 如果用户不存在,添加错误信息;5. 返回user-detail视图</p>
*
* @param id 用户ID
* @param model Model对象
* @return 用户详情页面
*/
@GetMapping("/{id}")
public String getUser(@PathVariable Long id, Model model) {
// 调用服务层方法根据ID获取用户信息
User user = userService.getUserById(id);
// 将用户信息添加到Model中
model.addAttribute("user", user);
// 如果用户不存在,可以添加一个错误信息
if (user == null) {
model.addAttribute("error", "用户不存在或数据库未启用");
}
// 返回用户详情视图
return "user-detail";
}
/**
* <p>功能描述:显示创建用户表单</p>
* <p>开发思路:处理GET请求显示创建用户的表单页面</p>
* <p>开发过程:1. 直接返回user-form视图显示创建表单</p>
*
* @return 创建用户页面
*/
@GetMapping("/new")
public String showCreateForm() {
// 返回创建用户表单视图
return "user-form";
}
/**
* <p>功能描述:处理创建用户请求</p>
* <p>开发思路:处理POST请求创建新用户,将用户信息保存到数据库并根据结果进行相应跳转</p>
* <p>开发过程:1. 接收用户名和邮箱参数;2. 创建User对象并设置属性;3. 调用userService.createUser方法保存用户;4. 根据保存结果进行页面跳转或显示错误信息</p>
*
* @param username 用户名
* @param email 邮箱
* @param model Model对象
* @return 重定向到用户列表
*/
@PostMapping
public String createUser(@RequestParam String username,
@RequestParam String email,
Model model) {
// 创建User对象并设置属性
User user = new User();
user.setUsername(username);
user.setEmail(email);
// 调用服务层方法创建用户
boolean success = userService.createUser(user);
if (success) {
// 创建成功,重定向到用户列表页面
return "redirect:/users";
} else {
// 创建失败,返回表单页面并显示错误信息
model.addAttribute("error", "创建用户失败,可能是数据库未启用");
model.addAttribute("user", user);
return "user-form";
}
}
/**
* <p>功能描述:显示编辑用户表单</p>
* <p>开发思路:处理GET请求显示编辑用户的表单页面,预先加载用户信息填充到表单中</p>
* <p>开发过程:1. 接收用户ID参数;2. 调用userService.getUserById方法获取用户信息;3. 如果用户不存在,添加错误信息并重定向;4. 将用户信息添加到Model中;5. 返回user-form视图</p>
*
* @param id 用户ID
* @param model Model对象
* @return 编辑用户页面
*/
@GetMapping("/{id}/edit")
public String showEditForm(@PathVariable Long id, Model model) {
// 调用服务层方法根据ID获取用户信息
User user = userService.getUserById(id);
if (user == null) {
// 如果用户不存在,添加错误信息并重定向到用户列表
model.addAttribute("error", "用户不存在或数据库未启用");
return "redirect:/users";
}
// 将用户信息添加到Model中
model.addAttribute("user", user);
// 返回编辑用户表单视图
return "user-form";
}
/**
* <p>功能描述:处理更新用户请求</p>
* <p>开发思路:处理PUT请求更新用户信息,将更新后的用户信息保存到数据库并根据结果进行相应跳转</p>
* <p>开发过程:1. 接收用户ID、用户名和邮箱参数;2. 创建User对象并设置属性;3. 调用userService.updateUser方法更新用户;4. 根据更新结果进行页面跳转或显示错误信息</p>
*
* @param id 用户ID
* @param username 用户名
* @param email 邮箱
* @param model Model对象
* @return 重定向到用户列表
*/
@PutMapping("/{id}")
public String updateUser(@PathVariable Long id,
@RequestParam String username,
@RequestParam String email,
Model model) {
// 创建User对象并设置属性
User user = new User();
user.setId(id);
user.setUsername(username);
user.setEmail(email);
// 调用服务层方法更新用户
boolean success = userService.updateUser(user);
if (success) {
// 更新成功,重定向到用户列表页面
return "redirect:/users";
} else {
// 更新失败,返回表单页面并显示错误信息
model.addAttribute("error", "更新用户失败,可能是数据库未启用");
model.addAttribute("user", user);
return "user-form";
}
}
/**
* <p>功能描述:删除用户</p>
* <p>开发思路:处理POST请求删除指定用户,根据删除结果进行相应处理</p>
* <p>开发过程:1. 接收用户ID参数;2. 调用userService.deleteUser方法删除用户;3. 如果删除失败,添加错误信息;4. 重定向到用户列表页面</p>
*
* @param id 用户ID
* @param model Model对象
* @return 重定向到用户列表
*/
@PostMapping("/{id}/delete")
public String deleteUser(@PathVariable Long id, Model model) {
// 调用服务层方法删除用户
boolean success = userService.deleteUser(id);
if (!success) {
// 如果删除失败,添加错误信息到Model中
model.addAttribute("error", "删除用户失败,可能是数据库未启用");
}
// 重定向到用户列表页面
return "redirect:/users";
}
}
第七章:前端页面开发
7.2 前端页面渲染时序图
浏览器 Tomcat服务器 UserController UserService UserMapper Thymeleaf引擎 模板文件 请求用户列表页面 调用getUsers方法 获取分页用户数据 查询用户数据 返回用户列表 返回PageInfo对象 准备渲染视图 加载user-list.html模板 解析Thymeleaf表达式 数据绑定和循环渲染 生成分页导航 返回渲染后的HTML 返回HTTP响应 返回完整HTML页面 用户交互操作 点击删除按钮 调用deleteUser方法 删除用户 执行删除SQL 删除成功 返回操作结果 重定向到用户列表 返回重定向响应 请求用户列表页面 浏览器 Tomcat服务器 UserController UserService UserMapper Thymeleaf引擎 模板文件
7.3 Thymeleaf模板处理流程图
变量表达式 URL表达式 条件表达式 循环表达式 工具表达式 Controller返回视图名 ViewResolver解析视图 加载Thymeleaf模板文件 创建模板上下文 绑定Model数据 解析模板语法 表达式类型判断 获取上下文变量 生成链接地址 条件判断处理 迭代数据集合 调用工具方法 数据渲染 条件分支处理 循环生成HTML 生成HTML片段 合并模板结构 输出完整HTML 返回HTTP响应
7.4 前端技术架构
浏览器 HTML5 CSS3 JavaScript Thymeleaf模板引擎 Bootstrap样式 AJAX交互 响应式设计
7.5 用户列表页面设计
user-list.html 模板文件:
html
<!DOCTYPE html>
<!--
<p>功能描述:用户列表页面,显示用户数据表格并提供分页、增删改查操作</p>
<p>开发思路:使用Thymeleaf模板引擎动态渲染用户数据,提供完整的用户管理界面</p>
<p>开发过程:</p>
<p>1. 定义HTML5文档结构和响应式样式</p>
<p>2. 创建用户数据表格,显示用户基本信息</p>
<p>3. 实现分页导航功能,支持大量数据的分页显示</p>
<p>4. 添加操作按钮(查看、编辑、删除),支持用户管理操作</p>
<p>5. 集成错误和成功消息显示功能</p>
@author 李昊哲 李胜龙
@version 0.0.1
-->
<html xmlns:th="http://www.thymeleaf.org" lang="zh_CN">
<head>
<!-- 设置HTML文档字符编码为UTF-8,支持中文字符显示 -->
<meta charset="UTF-8">
<!-- 设置浏览器标签页显示的标题 -->
<title>用户列表</title>
<!-- 内联样式定义,提供页面布局和美化效果 -->
<style>
/* 页面主体样式设置 */
body {
font-family: Arial, sans-serif; /* 设置字体 */
margin: 20px; /* 设置外边距 */
}
/* 数据表格样式 */
table {
width: 100%; /* 表格宽度占满容器 */
border-collapse: collapse; /* 边框合并 */
margin-bottom: 20px; /* 底部外边距 */
}
/* 表格单元格样式 */
th, td {
border: 1px solid #ddd; /* 边框样式 */
padding: 8px; /* 内边距 */
text-align: left; /* 文本左对齐 */
}
/* 表头样式 */
th {
background-color: #f2f2f2; /* 背景色 */
}
/* 按钮基础样式 */
.btn {
display: inline-block; /* 行内块元素 */
padding: 6px 12px; /* 内边距 */
margin-bottom: 0; /* 底部外边距 */
font-size: 14px; /* 字体大小 */
font-weight: normal; /* 字体粗细 */
line-height: 1.42857143; /* 行高 */
text-align: center; /* 文本居中 */
white-space: nowrap; /* 禁止换行 */
vertical-align: middle; /* 垂直居中 */
cursor: pointer; /* 鼠标指针 */
border: 1px solid transparent; /* 透明边框 */
border-radius: 4px; /* 圆角 */
text-decoration: none; /* 去除下划线 */
}
/* 主要按钮样式(蓝色) */
.btn-primary {
color: #fff; /* 白色文字 */
background-color: #007bff; /* 蓝色背景 */
border-color: #007bff; /* 蓝色边框 */
}
/* 危险按钮样式(红色) */
.btn-danger {
color: #fff; /* 白色文字 */
background-color: #dc3545; /* 红色背景 */
border-color: #dc3545; /* 红色边框 */
}
/* 按钮悬停效果 */
.btn:hover {
opacity: 0.8; /* 透明度变化 */
}
/* 警告框基础样式 */
.alert {
padding: 15px; /* 内边距 */
margin-bottom: 20px; /* 底部外边距 */
border: 1px solid transparent; /* 透明边框 */
border-radius: 4px; /* 圆角 */
}
/* 错误警告框样式 */
.alert-error {
color: #a94442; /* 红色文字 */
background-color: #f2dede; /* 浅红色背景 */
border-color: #ebccd1; /* 红色边框 */
}
/* 成功警告框样式 */
.alert-success {
color: #3c763d; /* 绿色文字 */
background-color: #dff0d8; /* 浅绿色背景 */
border-color: #d6e9c6; /* 绿色边框 */
}
/* 分页导航容器样式 */
.pagination {
display: flex; /* 弹性布局 */
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
margin-top: 20px; /* 顶部外边距 */
}
/* 分页链接样式 */
.pagination a, .pagination span {
display: inline-block; /* 行内块元素 */
padding: 8px 12px; /* 内边距 */
margin: 0 4px; /* 左右外边距 */
text-decoration: none; /* 去除下划线 */
border: 1px solid #ddd; /* 边框样式 */
border-radius: 4px; /* 圆角 */
}
/* 当前页码样式 */
.pagination .current {
background-color: #007bff; /* 蓝色背景 */
color: white; /* 白色文字 */
border-color: #007bff; /* 蓝色边框 */
}
/* 分页链接悬停效果 */
.pagination a:hover {
background-color: #e9ecef; /* 灰色背景 */
color: #007bff; /* 蓝色文字 */
}
/* 禁用链接样式 */
.pagination .disabled {
color: #6c757d; /* 灰色文字 */
cursor: not-allowed; /* 禁用鼠标指针 */
}
/* 操作按钮组样式 */
.actions {
display: flex; /* 弹性布局 */
gap: 8px; /* 按钮间距 */
}
/* 页面标题样式 */
h1 {
color: #333; /* 深灰色文字 */
margin-bottom: 20px; /* 底部外边距 */
}
/* 添加用户按钮容器 */
.add-user-btn {
margin-bottom: 20px; /* 底部外边距 */
}
</style>
</head>
<body>
<!-- 页面标题 -->
<h1>用户列表</h1>
<!-- 添加用户按钮 -->
<div class="add-user-btn">
<a href="/users/new" class="btn btn-primary">添加用户</a>
</div>
<!-- 错误消息显示区域 -->
<div th:if="${error}" class="alert alert-error" th:text="${error}"></div>
<!-- 成功消息显示区域 -->
<div th:if="${message}" class="alert alert-success" th:text="${message}"></div>
<!-- 用户数据表格 -->
<table>
<!-- 表头 -->
<thead>
<tr>
<th>ID</th>
<th>用户名</th>
<th>邮箱</th>
<th>创建时间</th>
<th>更新时间</th>
<th>操作</th>
</tr>
</thead>
<!-- 表格数据 -->
<tbody>
<!-- 使用Thymeleaf循环遍历用户列表 -->
<tr th:each="user : ${pageInfo.list}">
<!-- 用户ID -->
<td th:text="${user.id}">1</td>
<!-- 用户名 -->
<td th:text="${user.username}">用户名</td>
<!-- 邮箱 -->
<td th:text="${user.email}">邮箱</td>
<!-- 创建时间 -->
<td th:text="${#temporals.format(user.createTime, 'yyyy-MM-dd HH:mm:ss')}">2024-01-01 12:00:00</td>
<!-- 更新时间 -->
<td th:text="${#temporals.format(user.updateTime, 'yyyy-MM-dd HH:mm:ss')}">2024-01-01 12:00:00</td>
<!-- 操作按钮 -->
<td>
<div class="actions">
<!-- 查看详情按钮 -->
<a th:href="@{/users/{id}(id=${user.id})}" class="btn btn-primary">查看</a>
<!-- 编辑按钮 -->
<a th:href="@{/users/{id}/edit(id=${user.id})}" class="btn btn-primary">编辑</a>
<!-- 删除按钮 -->
<form th:action="@{/users/{id}/delete(id=${user.id})}" method="post" style="display: inline;">
<button type="submit" class="btn btn-danger"
onclick="return confirm('确定要删除这个用户吗?')">删除</button>
</form>
</div>
</td>
</tr>
<!-- 无数据时的提示行 -->
<tr th:if="${#lists.isEmpty(pageInfo.list)}">
<td colspan="6" style="text-align: center;">暂无用户数据</td>
</tr>
</tbody>
</table>
<!-- 分页导航 -->
<div class="pagination" th:if="${pageInfo.pages > 1}">
<!-- 首页链接 -->
<a th:href="@{/users(pageNum=1)}"
th:class="${pageInfo.isFirstPage()} ? 'disabled' : ''">首页</a>
<!-- 上一页链接 -->
<a th:href="@{/users(pageNum=${pageInfo.prePage})}"
th:class="${pageInfo.hasPreviousPage} ? '' : 'disabled'">上一页</a>
<!-- 页码循环 -->
<span th:each="pageNum : ${#numbers.sequence(1, pageInfo.pages)}">
<a th:href="@{/users(pageNum=${pageNum})}"
th:class="${pageNum == pageInfo.pageNum} ? 'current' : ''"
th:text="${pageNum}">1</a>
</span>
<!-- 下一页链接 -->
<a th:href="@{/users(pageNum=${pageInfo.nextPage})}"
th:class="${pageInfo.hasNextPage} ? '' : 'disabled'">下一页</a>
<!-- 末页链接 -->
<a th:href="@{/users(pageNum=${pageInfo.pages})}"
th:class="${pageInfo.isLastPage()} ? 'disabled' : ''">末页</a>
</div>
<!-- 分页信息显示 -->
<div th:if="${pageInfo.pages > 0}" style="text-align: center; margin-top: 10px;">
<span th:text="'当前第 ' + ${pageInfo.pageNum} + ' 页,共 ' + ${pageInfo.pages} + ' 页,总计 ' + ${pageInfo.total} + ' 条记录'">
当前第 1 页,共 1 页,总计 0 条记录
</span>
</div>
</body>
</html>
7.6 用户表单页面设计
user-form.html 模板文件:
html
<!DOCTYPE html>
<!--
<p>功能描述:用户表单页面,用于创建和编辑用户信息</p>
<p>开发思路:使用Thymeleaf模板引擎动态渲染表单,支持用户信息的输入和验证</p>
<p>开发过程:</p>
<p>1. 定义HTML5表单结构</p>
<p>2. 创建用户名和邮箱输入字段</p>
<p>3. 实现表单验证和错误提示</p>
<p>4. 添加提交和取消按钮</p>
<p>5. 支持编辑和创建两种模式的动态切换</p>
@author 李昊哲 李胜龙
@version 0.0.1
-->
<html xmlns:th="http://www.thymeleaf.org" lang="zh_CN">
<head>
<!-- 设置HTML文档字符编码为UTF-8,支持中文字符显示 -->
<meta charset="UTF-8">
<!-- 设置浏览器标签页显示的标题 -->
<title th:text="${user.id != null} ? '编辑用户' : '添加用户'">用户表单</title>
<!-- 内联样式定义,提供表单布局和美化效果 -->
<style>
/* 页面主体样式设置 */
body {
font-family: Arial, sans-serif; /* 设置字体 */
margin: 20px; /* 设置外边距 */
}
/* 表单容器样式 */
.form-container {
max-width: 600px; /* 最大宽度 */
margin: 0 auto; /* 水平居中 */
padding: 20px; /* 内边距 */
border: 1px solid #ddd; /* 边框样式 */
border-radius: 5px; /* 圆角 */
background-color: #f9f9f9; /* 背景色 */
}
/* 表单组样式 */
.form-group {
margin-bottom: 15px; /* 底部外边距 */
}
/* 标签样式 */
label {
display: block; /* 块级元素 */
margin-bottom: 5px; /* 底部外边距 */
font-weight: bold; /* 字体粗细 */
}
/* 输入框样式 */
input[type="text"], input[type="email"] {
width: 100%; /* 宽度占满容器 */
padding: 8px; /* 内边距 */
border: 1px solid #ddd; /* 边框样式 */
border-radius: 4px; /* 圆角 */
box-sizing: border-box; /* 盒模型计算方式 */
}
/* 输入框聚焦样式 */
input[type="text"]:focus, input[type="email"]:focus {
border-color: #007bff; /* 聚焦时边框颜色 */
outline: none; /* 去除轮廓 */
}
/* 按钮基础样式 */
.btn {
display: inline-block; /* 行内块元素 */
padding: 10px 20px; /* 内边距 */
margin-right: 10px; /* 右外边距 */
border: none; /* 去除边框 */
border-radius: 4px; /* 圆角 */
cursor: pointer; /* 鼠标指针 */
text-decoration: none; /* 去除下划线 */
font-size: 14px; /* 字体大小 */
}
/* 主要按钮样式(蓝色) */
.btn-primary {
background-color: #007bff; /* 蓝色背景 */
color: white; /* 白色文字 */
}
/* 次要按钮样式(灰色) */
.btn-secondary {
background-color: #6c757d; /* 灰色背景 */
color: white; /* 白色文字 */
}
/* 按钮悬停效果 */
.btn:hover {
opacity: 0.8; /* 透明度变化 */
}
/* 警告框基础样式 */
.alert {
padding: 15px; /* 内边距 */
margin-bottom: 20px; /* 底部外边距 */
border: 1px solid transparent; /* 透明边框 */
border-radius: 4px; /* 圆角 */
}
/* 错误警告框样式 */
.alert-error {
color: #a94442; /* 红色文字 */
background-color: #f2dede; /* 浅红色背景 */
border-color: #ebccd1; /* 红色边框 */
}
/* 页面标题样式 */
h1 {
color: #333; /* 深灰色文字 */
text-align: center; /* 文本居中 */
margin-bottom: 30px; /* 底部外边距 */
}
/* 按钮组样式 */
.button-group {
text-align: center; /* 文本居中 */
margin-top: 20px; /* 顶部外边距 */
}
</style>
</head>
<body>
<!-- 页面标题 -->
<h1 th:text="${user.id != null} ? '编辑用户' : '添加用户'">用户表单</h1>
<!-- 表单容器 -->
<div class="form-container">
<!-- 错误消息显示区域 -->
<div th:if="${error}" class="alert alert-error" th:text="${error}"></div>
<!-- 用户表单 -->
<form th:action="${user.id != null} ? @{/users/{id}(id=${user.id})} : @{/users}"
th:method="${user.id != null} ? 'put' : 'post'">
<!-- 隐藏字段:用户ID(编辑模式时使用) -->
<input type="hidden" th:if="${user.id != null}" th:value="${user.id}" name="id">
<!-- 用户名输入组 -->
<div class="form-group">
<label for="username">用户名:</label>
<input type="text"
id="username"
name="username"
th:value="${user.username}"
required
placeholder="请输入用户名">
</div>
<!-- 邮箱输入组 -->
<div class="form-group">
<label for="email">邮箱:</label>
<input type="email"
id="email"
name="email"
th:value="${user.email}"
required
placeholder="请输入邮箱地址">
</div>
<!-- 按钮组 -->
<div class="button-group">
<!-- 提交按钮 -->
<button type="submit" class="btn btn-primary">
<span th:text="${user.id != null} ? '更新' : '保存'">保存</span>
</button>
<!-- 取消按钮 -->
<a href="/users" class="btn btn-secondary">取消</a>
</div>
</form>
</div>
<!-- 表单验证脚本 -->
<script>
// 表单提交验证
document.querySelector('form').addEventListener('submit', function(e) {
// 获取表单元素
const username = document.getElementById('username').value.trim();
const email = document.getElementById('email').value.trim();
// 验证用户名
if (!username) {
alert('请输入用户名');
e.preventDefault();
return false;
}
// 验证用户名长度
if (username.length < 3 || username.length > 50) {
alert('用户名长度应在3-50个字符之间');
e.preventDefault();
return false;
}
// 验证邮箱
if (!email) {
alert('请输入邮箱地址');
e.preventDefault();
return false;
}
// 验证邮箱格式
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
alert('请输入有效的邮箱地址');
e.preventDefault();
return false;
}
});
</script>
</body>
</html>
第九章:项目扩展与进阶
9.1 功能扩展架构
用户管理 权限管理 角色管理 菜单管理 系统配置 日志管理 系统监控 性能监控 告警系统
9.2 系统扩展时序图
用户 控制器 权限拦截器 业务服务 缓存层 数据库 日志系统 发起请求 权限验证 查询用户权限 返回权限信息 验证通过 调用业务方法 查询缓存数据 返回缓存数据 查询数据库 返回数据 更新缓存 alt [缓存命中] [缓存未命中] 记录操作日志 日志记录完成 返回处理结果 返回响应 用户 控制器 权限拦截器 业务服务 缓存层 数据库 日志系统
9.3 高级功能流程图
否 是 否 是 是 否 用户请求 身份认证 认证通过? 返回认证失败 权限检查 有权限? 返回权限不足 业务逻辑处理 数据访问 缓存处理 缓存存在? 返回缓存数据 查询数据库 更新缓存 返回数据 结果处理 日志记录 返回响应 定时任务 数据备份 缓存清理 日志归档 监控系统 性能指标收集 异常检测 告警通知
9.4 高级特性实现
权限管理扩展:
java
// 权限实体类
@Entity
public class Permission {
private Long id;
private String name;
private String code;
private String description;
// getter/setter方法
}
// 角色实体类
@Entity
public class Role {
private Long id;
private String name;
private String description;
private List<Permission> permissions;
// getter/setter方法
}
// 用户扩展实体
@Entity
public class User {
private Long id;
private String username;
private String email;
private List<Role> roles;
// getter/setter方法
}