SpringMVC核心原理与实战指南

什么是MVC?

MVC英文是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计规范。

MVC是用一种业务逻辑、数据、界面显示分离的方法,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。

  • Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。
  • View(视图)是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的。
  • Controller(控制器)是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。

什么是SpringMVC?

Spring MVC是Spring在Spring Container Core和AOP等技术基础上,遵循上述Web MVC的规范推出的web开发框架,目的是为了简化Java栈的web开发。

Spring Web MVC 是一种基于Java 的实现了Web MVC 设计模式的请求驱动类型的轻量级Web 框架,即使用了MVC 架 构模式的思想,将 web 层进行职责解耦,基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,Spring Web MVC 也是要简化我们日常Web 开发的。

模型(Model):

模型代表应用程序的数据和业务逻辑。它通常包含数据对象(如 POJO)和服务层(如 Spring 服务)来处理业务逻辑。模型负责从数据库或其他数据源获取数据,并将数据传递给视图以显示给用户。

视图(View):

视图负责展示数据,通常是 HTML 页面或其他类型的用户界面。Spring MVC 支持多种视图技术,包括 JSP、Thymeleaf、FreeMarker 等。视图从模型获取数据并将其呈现给用户。

控制器(Controller):

控制器处理用户请求并决定将数据传递给哪个视图。它接收用户输入,调用模型进行处理,并选择合适的视图来显示结果。控制器通常使用 @Controller 注解来标识,并使用 @RequestMapping 注解来映射 URL 请求。

SpringMVC的组件

Spring MVC框架的核心组件:

1、DispatcherServlet(前端控制器) 这是Spring MVC的核心组件,作为整个框架的入口点。它接收所有的HTTP请求,然后根据配置将请求分发给相应的处理器。DispatcherServlet实现了前端控制器模式,统一处理请求的分发、异常处理、视图解析等工作。它在web.xml中配置,或通过Java配置类进行设置。

2、HandlerMapping(处理器映射器) 负责根据请求的URL找到对应的处理器(Controller)。Spring MVC提供了多种HandlerMapping实现,如RequestMappingHandlerMapping用于处理@RequestMapping注解,SimpleUrlHandlerMapping用于简单的URL映射。它会返回一个HandlerExecutionChain对象,包含处理器和相关的拦截器。

3、HandlerAdapter(处理器适配器) 由于处理器的类型多样(可能是Controller接口实现类、带@RequestMapping注解的方法等),HandlerAdapter负责调用具体的处理器方法。不同类型的处理器需要不同的适配器,如RequestMappingHandlerAdapter用于处理注解式控制器,SimpleControllerHandlerAdapter用于处理Controller接口的实现类。

4、Controller(控制器) 处理具体的业务逻辑,接收用户请求,调用服务层处理业务,然后返回ModelAndView对象。控制器可以通过实现Controller接口或使用@Controller注解来定义。现代Spring MVC主要使用注解式控制器,通过@RequestMapping等注解来映射请求。

5、ModelAndView 封装了模型数据和视图信息的对象。Model包含要传递给视图的数据,View指定要渲染的视图名称。控制器处理完请求后返回ModelAndView对象,或者分别返回模型数据和视图名称。

6、ViewResolver(视图解析器) 根据逻辑视图名解析出具体的视图对象。Spring MVC支持多种视图技术,如JSP、Thymeleaf、Freemarker等。常用的视图解析器包括InternalResourceViewResolver用于JSP视图,ThymeleafViewResolver用于Thymeleaf模板引擎。

7、View(视图) 负责渲染模型数据,生成响应内容返回给客户端。视图可以是JSP页面、Thymeleaf模板、JSON数据等。不同的视图技术有不同的View实现类。

8、HandlerInterceptor(拦截器) 提供请求处理的前置和后置处理功能。拦截器可以在请求到达控制器之前、控制器处理完成后、视图渲染完成后进行相应的处理。常用于日志记录、权限检查、性能监控等横切关注点。

9、HandlerExceptionResolver(异常解析器) 处理请求过程中抛出的异常,将异常转换为相应的视图或响应。Spring MVC提供了多种异常解析器,如SimpleMappingExceptionResolver用于简单的异常映射,ExceptionHandlerExceptionResolver用于处理@ExceptionHandler注解的方法。

10、MultipartResolver(文件上传解析器) 处理multipart类型的HTTP请求,主要用于文件上传功能。它将multipart请求解析为MultipartHttpServletRequest对象,使得控制器可以方便地处理上传的文件。

11、LocaleResolver(国际化解析器) 确定用户的区域设置,支持应用程序的国际化功能。它可以从HTTP请求头、Session、Cookie等地方获取用户的语言偏好。

12、ThemeResolver(主题解析器) 支持Web应用程序的主题功能,允许用户选择不同的界面主题。虽然在现代Web开发中使用较少,但在某些场景下仍然有用。

SpringMVC的工作原理

SpringMVC的执行流程:

步骤1:用户发送请求 用户通过浏览器向服务器发送HTTP请求(如GET /user/list),请求首先到达Web服务器,然后被转发到Spring MVC的DispatcherServlet。

步骤2:DispatcherServlet查找Handler DispatcherServlet作为前端控制器,接收到请求后,会调用HandlerMapping组件来查找能够处理该请求的Handler(通常是Controller中的某个方法)。HandlerMapping会根据请求的URL、HTTP方法等信息,通过注解映射或配置文件找到对应的处理器。

步骤3:返回执行链 HandlerMapping找到匹配的Handler后,不是直接返回Handler,而是返回一个HandlerExecutionChain对象。这个执行链包含了目标Handler以及与该Handler相关的所有拦截器(HandlerInterceptor1、HandlerInterceptor2等)。拦截器可以在Handler执行前后进行额外的处理。

步骤4:请求适配器执行 DispatcherServlet获得HandlerExecutionChain后,需要找到合适的HandlerAdapter来执行Handler。因为Handler的类型可能多样(实现Controller接口的类、带@RequestMapping注解的方法等),所以需要适配器模式来统一调用接口。DispatcherServlet会遍历所有注册的HandlerAdapter,找到支持当前Handler类型的适配器。

步骤5:执行Handler HandlerAdapter调用具体的Handler方法(通常是Controller中的业务方法)。在执行Handler之前,会先执行拦截器链的preHandle方法。Handler方法执行时会处理业务逻辑,可能调用Service层、DAO层等组件,处理完成后准备返回结果。

步骤6:返回ModelAndView Handler执行完毕后,返回处理结果。这个结果通常是ModelAndView对象,包含了模型数据(Model)和逻辑视图名(View name)。Model包含要传递给前端页面的数据,View name是一个字符串,表示要渲染的视图的逻辑名称。

步骤7:HandlerAdapter处理返回值 HandlerAdapter接收Handler的返回值,将其封装成标准的ModelAndView对象返回给DispatcherServlet。如果Handler返回的是其他类型(如字符串、@ResponseBody注解的对象等),HandlerAdapter会进行相应的转换处理。

步骤8:请求视图解析 DispatcherServlet拿到ModelAndView后,调用ViewResolver(视图解析器)来解析逻辑视图名。ViewResolver根据配置的规则(如前缀、后缀等),将逻辑视图名转换为具体的视图对象(View)。例如,逻辑视图名"userList"可能被解析为"/WEB-INF/views/userList.jsp"。

步骤9:返回View对象 ViewResolver将解析后的View对象返回给DispatcherServlet。View对象知道如何渲染特定类型的视图,比如JSP视图、Thymeleaf视图、JSON视图等。

步骤10:视图渲染 DispatcherServlet调用View对象的render方法,将Model中的数据填充到视图模板中,生成最终的HTML、JSON或其他格式的响应内容。在这个过程中,模型数据会被放到request域中,供视图模板使用。

步骤11:响应用户 视图渲染完成后,DispatcherServlet将最终的响应内容返回给用户的浏览器。在返回响应之前,还会执行拦截器链的postHandle和afterCompletion方法,完成一些清理工作。

简单描述:

Spring MVC 的工作流程:

1. 用户请求:用户通过浏览器发送 HTTP 请求到服务器。

2. 前端控制器(DispatcherServlet):Spring MVC 的前端控制器 DispatcherServlet 拦截所有请求并进行分发。

3. 处理器映射(Handler Mapping):根据请求 URL,DispatcherServlet 查找相应的控制器。

4. 控制器处理:控制器处理请求,调用服务层或数据访问层以获取数据,并将数据封装到模型中。

5. 视图解析器(View Resolver):控制器返回视图 名称,DispatcherServlet 使用视图解析器将视图名称解析为实际的视图对象。

6. 视图渲染:视图对象负责将模型数据渲染为用户界面,通常是 HTML 页面。

7. 响应返回:渲染后的视图返回给 DispatcherServlet,DispatcherServlet 将最终的响应发送回用户浏览器。

SpringMVC项目实战

1、 创建Maven Web项目

2、 完善项目结构

3、配置pom文件

<!-- Maven项目核心配置文件,定义项目结构、依赖、构建信息等 -->
<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/maven-v4_0_0.xsd"><!-- Maven模型版本,固定为4.0.0 --><modelVersion>4.0.0</modelVersion><!-- 项目组织唯一标识,通常是公司或组织域名倒写 --><groupId>com.example.springmvcdemo</groupId><!-- 项目模块名称 --><artifactId>springmvc-demo</artifactId><!-- 打包方式,war表示Web应用 --><packaging>war</packaging><!-- 项目版本号,SNAPSHOT表示开发中版本 --><version>1.0-SNAPSHOT</version><!-- 项目名称,用于描述 --><name>springmvc-demo</name><!-- 自定义属性,便于统一管理版本号和编码等信息 --><properties><!-- JDK编译版本为1.8 --><maven.compiler.source>8</maven.compiler.source><!-- JDK目标运行版本为1.8 --><maven.compiler.target>8</maven.compiler.target><!-- 项目构建时源码编码为UTF-8 --><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><!-- Spring框架版本 --><spring.version>5.3.21</spring.version><!-- MyBatis版本 --><mybatis.version>3.5.10</mybatis.version><!-- MySQL驱动版本 --><mysql.version>8.0.29</mysql.version><!-- Jackson JSON处理库版本 --><jackson.version>2.13.3</jackson.version></properties><!-- 项目依赖管理 --><dependencies><!-- ========== Spring 核心依赖 ========== --><!-- Spring 框架核心工具类 --><dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>${spring.version}</version></dependency><!-- Spring 上下文,提供框架式Bean访问方式 --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>${spring.version}</version></dependency><!-- Spring Bean管理 --><dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId><version>${spring.version}</version></dependency><!-- Spring 表达式语言支持 --><dependency><groupId>org.springframework</groupId><artifactId>spring-expression</artifactId><version>${spring.version}</version></dependency><!-- ========== Spring MVC 相关依赖 ========== --><!-- Spring Web MVC 核心,用于构建Web应用 --><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>${spring.version}</version></dependency><!-- Spring Web基础支持,包含HTTP相关功能 --><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>${spring.version}</version></dependency><!-- ========== Spring JDBC 与事务管理 ========== --><!-- Spring JDBC 支持,简化数据库操作 --><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>${spring.version}</version></dependency><!-- Spring 事务管理 --><dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>${spring.version}</version></dependency><!-- ========== MyBatis 相关依赖 ========== --><!-- MyBatis ORM框架核心 --><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>2.0.7</version></dependency><!-- ========== 数据库相关依赖 ========== --><!-- MySQL JDBC驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>${mysql.version}</version></dependency><!-- 数据库连接池Druid --><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.11</version></dependency><!-- ========== JSON处理相关依赖 ========== --><!-- Jackson核心库 --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>${jackson.version}</version></dependency><!-- Jackson数据绑定,用于对象与JSON互转 --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>${jackson.version}</version></dependency><!-- Jackson注解支持 --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId><version>${jackson.version}</version></dependency><!-- ========== Servlet 相关依赖 ========== --><!-- Servlet API,编译期需要,运行时由容器提供 --><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version><scope>provided</scope></dependency><!-- JSP API,编译期使用,运行时由容器提供 --><dependency><groupId>javax.servlet.jsp</groupId><artifactId>jsp-api</artifactId><version>2.2</version><scope>provided</scope></dependency><!-- JSTL标签库,用于JSP页面 --><dependency><groupId>javax.servlet</groupId><artifactId>jstl</artifactId><version>1.2</version></dependency><!-- ========== 日志相关依赖 ========== --><!-- Log4j日志框架 --><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.23.1</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-api</artifactId><version>2.23.1</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j2-impl</artifactId><version>2.23.1</version></dependency><!-- ========== 测试相关依赖 ========== --><!-- JUnit单元测试框架 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope></dependency><!-- Spring测试支持,用于集成测试 --><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>${spring.version}</version><scope>test</scope></dependency></dependencies><!-- 构建配置,如插件配置 --><build><plugins><!-- Maven编译插件,配置JDK版本与编码 --><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>8</source>  <!-- 源码编译使用JDK 8 --><target>8</target>  <!-- 目标字节码为JDK 8 --><encoding>UTF-8</encoding>  <!-- 源码和编译编码均为UTF-8 --></configuration></plugin><!-- Tomcat Maven插件,用于本地启动嵌入式Tomcat进行测试 --><plugin><groupId>org.apache.tomcat.maven</groupId><artifactId>tomcat7-maven-plugin</artifactId><version>2.2</version><configuration><port>8080</port>  <!-- Tomcat服务端口为8080 --><path>/ssm</path>  <!-- 应用上下文路径为 /ssm --></configuration></plugin></plugins></build>
</project>

4、配置文件

1、applicationContext.xml(Spring核心配置)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsd"><!-- 扫描com.example.springmvcdemo包下的Spring组件(如@Service, @Repository等),但不包括@Controller --><context:component-scan base-package="com.example.springmvcdemo"><context:exclude-filter type="annotation"expression="org.springframework.stereotype.Controller"/></context:component-scan><!-- 加载classpath下的jdbc.properties文件,用于读取数据库连接等配置信息 --><context:property-placeholder location="classpath:jdbc.properties"/><!-- 配置Druid数据源,使用属性占位符从jdbc.properties中读取数据库连接信息 --><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"destroy-method="close"><property name="driverClassName" value="${jdbc.driver}"/>      <!-- 数据库驱动类名 --><property name="url" value="${jdbc.url}"/>                    <!-- 数据库连接URL --><property name="username" value="${jdbc.username}"/>          <!-- 数据库用户名 --><property name="password" value="${jdbc.password}"/>          <!-- 数据库密码 --><property name="maxActive" value="20"/>                       <!-- 最大活跃连接数 --><property name="initialSize" value="1"/>                      <!-- 初始化连接数 --><property name="maxWait" value="60000"/>                      <!-- 获取连接的最大等待时间(毫秒) --><property name="minIdle" value="1"/>                          <!-- 最小空闲连接数 --></bean><!-- 配置MyBatis的SqlSessionFactory,指定数据源、MyBatis全局配置文件及Mapper XML文件位置 --><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="dataSource"/>               <!-- 引用上面配置的数据源 --><property name="configLocation" value="classpath:mybatis-config.xml"/>  <!-- MyBatis全局配置文件 --><property name="mapperLocations" value="classpath:mapper/*.xml"/>       <!-- Mapper XML文件位置 --></bean><!-- 配置MyBatis Mapper接口扫描器,自动将指定包下的Mapper接口与XML映射绑定 --><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="com.example.ssm.dao"/>    <!-- Mapper接口所在的包 --><property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>  <!-- 引用SqlSessionFactory的Bean名称 --></bean><!-- 配置Spring JDBC事务管理器,用于管理数据库事务 --><bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/>               <!-- 引用数据源 --></bean><!-- 开启基于注解的声明式事务管理,如使用@Transactional注解 --><tx:annotation-driven transaction-manager="transactionManager"/></beans>
2、spring-mvc.xml(SpringMVC配置)
<?xml version="1.0" encoding="UTF-8"?>
<!-- Spring MVC 配置文件的根节点,定义使用的 XML Schema -->
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsd"><!-- 扫描指定包(com.example.springmvcdemo.controller)下的组件,如@Controller等注解的类 --><context:component-scan base-package="com.example.springmvcdemo.controller"/><!-- 启用Spring MVC的注解驱动功能,支持如@RequestMapping、@ResponseBody等注解 --><mvc:annotation-driven><!-- 配置消息转换器,用于处理请求与响应体的格式转换 --><mvc:message-converters><!-- 配置Jackson库的JSON消息转换器,用于将Java对象转为JSON并响应给前端 --><bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"><property name="supportedMediaTypes"><list><!-- 支持的响应类型:application/json;charset=UTF-8 --><value>application/json;charset=UTF-8</value><!-- 支持的响应类型:text/json;charset=UTF-8 --><value>text/json;charset=UTF-8</value></list></property></bean></mvc:message-converters></mvc:annotation-driven><!-- 配置静态资源(如JS、CSS、图片等)的访问路径,无需经过Controller --><!-- 请求路径以 /static/ 开头的资源,将从 /static/ 目录下查找 --><mvc:resources mapping="/static/**" location="/static/"/><!-- 配置JSP视图解析器,用于将逻辑视图名解析为实际的JSP页面路径 --><bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"><!-- JSP文件所在的目录前缀,如:/ --><property name="prefix" value="/"/><!-- JSP文件的后缀,如:.jsp --><property name="suffix" value=".jsp"/><!-- 使用JSTL视图 --><property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/></bean><!-- 配置文件上传解析器,支持用户通过表单上传文件 --><bean id="multipartResolver"class="org.springframework.web.multipart.commons.CommonsMultipartResolver"><!-- 最大上传文件大小限制为10MB(10 * 1024 * 1024 = 10485760字节) --><property name="maxUploadSize" value="10485760"/><!-- 默认编码为UTF-8 --><property name="defaultEncoding" value="UTF-8"/></bean></beans>
3、 mybatis-config.xml (mybatis的配置文件)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><settings><!-- 开启延迟加载 --><setting name="lazyLoadingEnabled" value="true"/><!-- 开启驼峰命名转换 --><setting name="mapUnderscoreToCamelCase" value="true"/><!-- 打印SQL语句 - 使用Log4j --><setting name="logImpl" value="LOG4J"/><!-- 如果使用logback,改为下面这行 --><!-- <setting name="logImpl" value="SLF4J"/> --></settings><!-- 类型别名 --><typeAliases><package name="com.example.springmvcdemo.entity"/></typeAliases></configuration>
4、 jdbc.properties (数据库配置文件)
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/sql_springmvc_demo?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=root

5、 日志配置文件

1、log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="30"><Properties><Property name="LOG_HOME">./logs</Property><Property name="APP_NAME">ssm-demo</Property><Property name="LOG_PATTERN">[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%t] %-5level [%logger{36}:%L] - %msg%n</Property><Property name="SQL_PATTERN">%d{HH:mm:ss.SSS} [%t] %logger{36} - %msg%n</Property></Properties><Appenders><!-- 控制台输出 --><Console name="Console" target="SYSTEM_OUT"><PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/><ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/></Console><!-- 应用日志文件 --><RollingFile name="ApplicationFile" fileName="${LOG_HOME}/${APP_NAME}.log"filePattern="${LOG_HOME}/${APP_NAME}.%d{yyyy-MM-dd}.%i.log"><PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/><Policies><TimeBasedTriggeringPolicy interval="1" modulate="true"/><SizeBasedTriggeringPolicy size="100 MB"/></Policies><DefaultRolloverStrategy max="30" fileIndex="min"><Delete basePath="${LOG_HOME}" maxDepth="1"><IfFileName glob="${APP_NAME}*.log" /><IfLastModified age="30d" /></Delete></DefaultRolloverStrategy></RollingFile><!-- 错误日志单独输出 --><RollingFile name="ErrorFile" fileName="${LOG_HOME}/error.log"filePattern="${LOG_HOME}/error.%d{yyyy-MM-dd}.%i.log"><PatternLayout pattern="${LOG_PATTERN}" charset="UTF-8"/><ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/><Policies><TimeBasedTriggeringPolicy interval="1" modulate="true"/><SizeBasedTriggeringPolicy size="50 MB"/></Policies><DefaultRolloverStrategy max="60"/></RollingFile><!-- SQL日志单独输出 --><RollingFile name="SqlFile" fileName="${LOG_HOME}/sql.log"filePattern="${LOG_HOME}/sql.%d{yyyy-MM-dd}.log"><PatternLayout pattern="${SQL_PATTERN}" charset="UTF-8"/><Policies><TimeBasedTriggeringPolicy interval="1" modulate="true"/></Policies><DefaultRolloverStrategy max="7"/></RollingFile><!-- 异步Appender --><Async name="AsyncApplication" bufferSize="1024"><AppenderRef ref="ApplicationFile"/></Async></Appenders><Loggers><!-- MyBatis SQL日志 --><Logger name="com.example.ssm.dao" level="debug" additivity="false"><AppenderRef ref="SqlFile"/><AppenderRef ref="Console"/></Logger><Logger name="org.apache.ibatis" level="info" additivity="false"><AppenderRef ref="SqlFile"/></Logger><Logger name="java.sql.Connection" level="info" additivity="false"><AppenderRef ref="SqlFile"/></Logger><Logger name="java.sql.Statement" level="debug" additivity="false"><AppenderRef ref="SqlFile"/></Logger><Logger name="java.sql.PreparedStatement" level="debug" additivity="false"><AppenderRef ref="SqlFile"/></Logger><!-- 项目包日志 --><Logger name="com.example.ssm" level="debug"/><!-- Spring框架日志 --><Logger name="org.springframework" level="warn"/><!-- 数据源日志 --><Logger name="com.alibaba.druid" level="warn"/><!-- Root Logger --><Root level="info"><AppenderRef ref="Console"/><AppenderRef ref="AsyncApplication"/><AppenderRef ref="ErrorFile"/></Root></Loggers>
</Configuration>
2、logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds"><!-- 定义日志文件输出目录 --><property name="LOG_HOME" value="./logs" /><property name="APP_NAME" value="ssm-demo" /><property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] %-5level [%logger{36}:%line] - %msg%n" /><property name="SQL_PATTERN" value="[%d{HH:mm:ss.SSS}] [%thread] %logger{36} - %msg%n" /><!-- 控制台输出 --><appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>${LOG_PATTERN}</pattern><charset>UTF-8</charset></encoder><!-- 生产环境可以注释掉控制台输出 --><filter class="ch.qos.logback.classic.filter.ThresholdFilter"><level>INFO</level></filter></appender><!-- 应用日志文件输出 --><appender name="APPLICATION_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${LOG_HOME}/${APP_NAME}.log</file><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><fileNamePattern>${LOG_HOME}/${APP_NAME}.%d{yyyy-MM-dd}.%i.log</fileNamePattern><maxFileSize>100MB</maxFileSize><maxHistory>30</maxHistory><totalSizeCap>5GB</totalSizeCap></rollingPolicy><encoder><pattern>${LOG_PATTERN}</pattern><charset>UTF-8</charset></encoder></appender><!-- 错误日志单独输出 --><appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${LOG_HOME}/error.log</file><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><fileNamePattern>${LOG_HOME}/error.%d{yyyy-MM-dd}.%i.log</fileNamePattern><maxFileSize>50MB</maxFileSize><maxHistory>60</maxHistory></rollingPolicy><encoder><pattern>${LOG_PATTERN}</pattern><charset>UTF-8</charset></encoder><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>ERROR</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter></appender><!-- SQL日志单独输出 --><appender name="SQL_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${LOG_HOME}/sql.log</file><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${LOG_HOME}/sql.%d{yyyy-MM-dd}.log</fileNamePattern><maxHistory>7</maxHistory></rollingPolicy><encoder><pattern>${SQL_PATTERN}</pattern><charset>UTF-8</charset></encoder></appender><!-- 异步日志 --><appender name="ASYNC_APPLICATION" class="ch.qos.logback.classic.AsyncAppender"><queueSize>1024</queueSize><discardingThreshold>0</discardingThreshold><includeCallerData>true</includeCallerData><appender-ref ref="APPLICATION_FILE" /></appender><!-- MyBatis SQL日志配置 --><logger name="com.example.ssm.dao" level="DEBUG" additivity="false"><appender-ref ref="SQL_FILE"/><appender-ref ref="CONSOLE"/></logger><logger name="org.apache.ibatis" level="INFO" additivity="false"><appender-ref ref="SQL_FILE"/></logger><logger name="java.sql.Connection" level="INFO" additivity="false"><appender-ref ref="SQL_FILE"/></logger><logger name="java.sql.Statement" level="DEBUG" additivity="false"><appender-ref ref="SQL_FILE"/></logger><logger name="java.sql.PreparedStatement" level="DEBUG" additivity="false"><appender-ref ref="SQL_FILE"/></logger><!-- 项目包日志 --><logger name="com.example.ssm" level="DEBUG"/><!-- Spring框架日志 --><logger name="org.springframework" level="WARN"/><!-- 数据源日志 --><logger name="com.alibaba.druid" level="WARN"/><!-- Root Logger --><root level="INFO"><appender-ref ref="CONSOLE"/><appender-ref ref="ASYNC_APPLICATION"/><appender-ref ref="ERROR_FILE"/></root></configuration>

6、 web.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"version="3.0">  <!-- 声明这是一个 Java EE 3.0 版本的 Web 应用配置文件 --><display-name>SpringMvcDemo</display-name>  <!-- 当前 Web 应用的显示名称,通常用于管理工具中显示 --><!-- ===================== 字符编码过滤器 ===================== --><!-- 用于统一处理请求和响应的字符编码,防止中文乱码等问题 --><filter><filter-name>CharacterEncodingFilter</filter-name>  <!-- 过滤器名称 --><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>  <!-- 使用 Spring 提供的字符编码过滤器 --><init-param><param-name>encoding</param-name>  <!-- 设置编码为 UTF-8 --><param-value>UTF-8</param-value></init-param><init-param><param-name>forceEncoding</param-name>  <!-- 强制请求和响应都使用指定编码 --><param-value>true</param-value></init-param></filter><filter-mapping><filter-name>CharacterEncodingFilter</filter-name><url-pattern>/*</url-pattern>  <!-- 对所有 URL 请求生效 --></filter-mapping><!-- ===================== Spring 上下文监听器 ===================== --><!-- 用于在 Web 应用启动时加载 Spring 的根应用上下文(通常是业务层、数据层等 Bean) --><context-param><param-name>contextConfigLocation</param-name>  <!-- 指定 Spring 配置文件的位置 --><param-value>classpath:applicationContext.xml</param-value>  <!-- 类路径下的 applicationContext.xml --></context-param><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  <!-- Spring 提供的上下文加载监听器 --></listener><!-- ===================== Spring MVC 前端控制器 ===================== --><!-- Spring MVC 的核心 Servlet,负责接收所有请求并分发给对应的 Controller 处理 --><servlet><servlet-name>dispatcherServlet</servlet-name>  <!-- Servlet 名称 --><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  <!-- 使用 Spring 的 DispatcherServlet --><init-param><param-name>contextConfigLocation</param-name>  <!-- 指定 Spring MVC 的配置文件位置 --><param-value>classpath:spring-mvc.xml</param-value>  <!-- 类路径下的 spring-mvc.xml --></init-param><load-on-startup>1</load-on-startup>  <!-- Web 应用启动时立即加载该 Servlet,优先级为 1 --></servlet><servlet-mapping><servlet-name>dispatcherServlet</servlet-name><url-pattern>/</url-pattern>  <!-- 拦截所有请求,交由 Spring MVC 处理 --></servlet-mapping><!-- ===================== 静态资源处理 ===================== --><!-- 让默认 Servlet 处理静态资源请求,如 CSS、JS、PNG 等,避免被 Spring MVC 拦截 --><servlet-mapping><servlet-name>default</servlet-name>  <!-- 使用容器(如 Tomcat)提供的默认 Servlet --><url-pattern>*.css</url-pattern>  <!-- 处理所有 CSS 文件请求 --></servlet-mapping><servlet-mapping><servlet-name>default</servlet-name><url-pattern>*.js</url-pattern>  <!-- 处理所有 JS 文件请求 --></servlet-mapping><servlet-mapping><servlet-name>default</servlet-name><url-pattern>*.png</url-pattern>  <!-- 处理所有 PNG 图片文件请求 --></servlet-mapping>
</web-app>

7、配置tomcat(以tomcat8.5为例)

8、编写测试接口进行测试

package com.example.springmvcdemo.controller;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;/*** 测试类* @author liuwei* @version JDK 8* @className TestController* @date 2025/7/28* @description 测试类*/
@Controller
@RequestMapping("/test")
@Slf4j
public class TestController {/*** 测试方法* @return*/@GetMapping("/hello")public String test(){log.info("这是测试类:{}","ok");return "hello";}
}
<%--Created by IntelliJ IDEA.User: 24193Date: 2025/7/28Time: 18:09To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>hello</title>
</head>
<body>
ok
</body>
</html>

SSM项目案例:智能垃圾分类管理系统

一、项目需求分析

1. 需求背景

随着垃圾分类政策的推行,社区需要一个智能垃圾分类管理系统,实现以下功能:

  1. 居民账户管理:居民注册/登录,记录垃圾分类行为
  2. 垃圾投递记录:记录每次垃圾投递的类型、重量和时间
  3. 环保积分系统:根据分类准确性奖励积分
  4. 数据可视化:展示个人和社区的垃圾分类数据

2. 用户角色

  • 普通居民:记录投递、查看积分
  • 社区管理员:管理居民账户、查看社区数据

二、数据库设计(2张表)

1. 用户表(user)

CREATE TABLE `user` (`id` int(11) NOT NULL AUTO_INCREMENT,`username` varchar(50) NOT NULL COMMENT '用户名',`password` varchar(100) NOT NULL COMMENT '密码(加密存储)',`phone` varchar(20) NOT NULL COMMENT '手机号',`address` varchar(200) DEFAULT NULL COMMENT '住址',`role` tinyint(1) DEFAULT '0' COMMENT '0-居民 1-管理员',`points` int(11) DEFAULT '0' COMMENT '环保积分',`create_time` datetime DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`),UNIQUE KEY `idx_username` (`username`),UNIQUE KEY `idx_phone` (`phone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2. 垃圾投递记录表(garbage_record)

CREATE TABLE `garbage_record` (`id` int(11) NOT NULL AUTO_INCREMENT,`user_id` int(11) NOT NULL COMMENT '投递用户ID',`type` tinyint(1) NOT NULL COMMENT '1-可回收 2-有害 3-厨余 4-其他',`weight` decimal(10,2) NOT NULL COMMENT '重量(kg)',`accuracy` tinyint(1) DEFAULT '100' COMMENT '分类准确率(%)',`points_earned` int(11) DEFAULT '0' COMMENT '获得积分',`image_url` varchar(255) DEFAULT NULL COMMENT '投递照片',`create_time` datetime DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`),KEY `idx_user_id` (`user_id`),KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

三、SSM项目代码完成

1、pom配置文件

<!-- Maven项目核心配置文件,定义项目结构、依赖、构建信息等 -->
<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/maven-v4_0_0.xsd"><!-- Maven模型版本,固定为4.0.0 --><modelVersion>4.0.0</modelVersion><!-- 项目组织唯一标识,通常是公司或组织域名倒写 --><groupId>com.example.springmvcdemo</groupId><!-- 项目模块名称 --><artifactId>springmvc-demo</artifactId><!-- 打包方式,war表示Web应用 --><packaging>war</packaging><!-- 项目版本号,SNAPSHOT表示开发中版本 --><version>1.0-SNAPSHOT</version><!-- 项目名称,用于描述 --><name>springmvc-demo</name><!-- 自定义属性,便于统一管理版本号和编码等信息 --><properties><!-- JDK编译版本为1.8 --><maven.compiler.source>8</maven.compiler.source><!-- JDK目标运行版本为1.8 --><maven.compiler.target>8</maven.compiler.target><!-- 项目构建时源码编码为UTF-8 --><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><!-- Spring框架版本 --><spring.version>5.3.21</spring.version><!-- MyBatis版本 --><mybatis.version>3.5.10</mybatis.version><!-- MySQL驱动版本 --><mysql.version>8.0.29</mysql.version><!-- Jackson JSON处理库版本 --><jackson.version>2.13.3</jackson.version></properties><!-- 项目依赖管理 --><dependencies><!-- ========== Spring 核心依赖 ========== --><!-- Spring 框架核心工具类 --><dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>${spring.version}</version></dependency><!-- Spring 上下文,提供框架式Bean访问方式 --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>${spring.version}</version></dependency><!-- Spring Bean管理 --><dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId><version>${spring.version}</version></dependency><!-- Spring 表达式语言支持 --><dependency><groupId>org.springframework</groupId><artifactId>spring-expression</artifactId><version>${spring.version}</version></dependency><!-- ========== Spring MVC 相关依赖 ========== --><!-- Spring Web MVC 核心,用于构建Web应用 --><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>${spring.version}</version></dependency><!-- Spring Web基础支持,包含HTTP相关功能 --><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>${spring.version}</version></dependency><!-- ========== Spring JDBC 与事务管理 ========== --><!-- Spring JDBC 支持,简化数据库操作 --><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>${spring.version}</version></dependency><!-- Spring 事务管理 --><dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>${spring.version}</version></dependency><!-- ========== MyBatis 相关依赖 ========== --><!-- MyBatis ORM框架核心 --><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>2.0.7</version></dependency><!-- ========== 数据库相关依赖 ========== --><!-- MySQL JDBC驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>${mysql.version}</version></dependency><!-- 数据库连接池Druid --><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.11</version></dependency><!-- ========== JSON处理相关依赖 ========== --><!-- Jackson核心库 --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>${jackson.version}</version></dependency><!-- Jackson数据绑定,用于对象与JSON互转 --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>${jackson.version}</version></dependency><!-- Jackson注解支持 --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId><version>${jackson.version}</version></dependency><!-- ========== Servlet 相关依赖 ========== --><!-- Servlet API,编译期需要,运行时由容器提供 --><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version><scope>provided</scope></dependency><!-- JSP API,编译期使用,运行时由容器提供 --><dependency><groupId>javax.servlet.jsp</groupId><artifactId>jsp-api</artifactId><version>2.2</version><scope>provided</scope></dependency><!-- JSTL标签库,用于JSP页面 --><dependency><groupId>javax.servlet</groupId><artifactId>jstl</artifactId><version>1.2</version></dependency><!-- Lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.32</version><scope>provided</scope></dependency><!-- ========== 日志相关依赖 ========== --><!-- 日志配置 - 方案一:使用Log4j --><!-- Log4j2核心依赖 --><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.23.1</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-api</artifactId><version>2.23.1</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j2-impl</artifactId><version>2.23.1</version></dependency><!-- 异步日志支持 --><dependency><groupId>com.lmax</groupId><artifactId>disruptor</artifactId><version>3.4.4</version></dependency><!-- 日志配置 - 方案二:使用Logback(推荐) --><!-- Logback 日志实现 --><!--        <dependency>--><!--            <groupId>ch.qos.logback</groupId>--><!--            <artifactId>logback-classic</artifactId>--><!--            <version>1.2.5</version>--><!--        </dependency>--><!--        &lt;!&ndash; SLF4J API(logback-classic 已经包含 slf4j-api,但显式声明版本可避免冲突) &ndash;&gt;--><!--        <dependency>--><!--            <groupId>org.slf4j</groupId>--><!--            <artifactId>slf4j-api</artifactId>--><!--            <version>1.7.36</version>--><!--        </dependency>--><!-- ========== 测试相关依赖 ========== --><!-- JUnit单元测试框架 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope></dependency><!-- Spring测试支持,用于集成测试 --><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>${spring.version}</version><scope>test</scope></dependency><!-- PageHelper 分页插件 --><dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>5.3.1</version> <!-- 请根据需要选择合适的版本 --></dependency><!-- Thymeleaf 核心库 --><dependency><groupId>org.thymeleaf</groupId><artifactId>thymeleaf</artifactId><version>3.1.2.RELEASE</version></dependency><!-- Thymeleaf 与 Spring 集成 --><dependency><groupId>org.thymeleaf</groupId><artifactId>thymeleaf-spring5</artifactId><version>3.1.2.RELEASE</version></dependency><!-- fastjson   --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.21</version></dependency><!-- 文件上传支持 --><dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.4</version></dependency><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.11.0</version></dependency></dependencies><!-- 构建配置,如插件配置 --><build><plugins><!-- Maven编译插件,配置JDK版本与编码 --><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>8</source>  <!-- 源码编译使用JDK 8 --><target>8</target>  <!-- 目标字节码为JDK 8 --><encoding>UTF-8</encoding>  <!-- 源码和编译编码均为UTF-8 --></configuration></plugin><!-- Tomcat Maven插件,用于本地启动嵌入式Tomcat进行测试 --><plugin><groupId>org.apache.tomcat.maven</groupId><artifactId>tomcat7-maven-plugin</artifactId><version>2.2</version><configuration><port>8080</port>  <!-- Tomcat服务端口为8080 --><path>/</path>  <!-- 应用上下文路径为 /ssm --></configuration></plugin></plugins></build>
</project>

2、mapper接口

package com.example.springmvcdemo.mapper;import com.example.springmvcdemo.entity.po.GarbageRecordPO;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;/*** 垃圾记录数据访问接口* 定义了垃圾记录相关的数据库操作方法*/
@Mapper
public interface GarbageMapper {/*** 插入一条垃圾记录* @param record 垃圾记录实体对象* @return 插入成功的记录数*/int insert(GarbageRecordPO record);/*** 根据用户ID查询垃圾记录列表* @param userId 用户ID* @return 该用户的所有垃圾记录列表*/List<GarbageRecordPO> selectByUserId(@Param("userId") Integer userId);/*** 统计指定用户的垃圾记录总数* @param userId 用户ID* @return 该用户的垃圾记录总数量*/int countByUserId(@Param("userId") Integer userId);/*** 计算指定用户获得的总积分* @param userId 用户ID* @return 该用户获得的积分总和*/int sumPointsByUserId(@Param("userId") Integer userId);/*** 按垃圾类型统计用户记录数量* @param userId 用户ID* @return 包含垃圾类型和对应数量的映射列表*/List<Map<String, Object>> countByType(@Param("userId") Integer userId);
}
package com.example.springmvcdemo.mapper;import java.util.List;import com.example.springmvcdemo.entity.po.UserPO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;/*** (user)表数据库访问层*/
@Mapper
public interface UserPOMapper {/*** 通过ID查询单条数据** @param id 主键* @return 实例对象*/UserPO queryById(Integer id);/*** 分页查询指定行数据** @param user 查询条件* @return 对象列表*/List<UserPO> queryAllByLimit(UserPO user);/*** 统计总行数** @param user 查询条件* @return 总行数*/long count(UserPO user);/*** 新增数据** @param user 实例对象* @return 影响行数*/int insert(UserPO user);/*** 批量新增数据** @param entities List<UserPO> 实例对象列表* @return 影响行数*/int insertBatch(@Param("entities") List<UserPO> entities);/*** 批量新增或按主键更新数据** @param entities List<UserPO> 实例对象列表* @return 影响行数*/int insertOrUpdateBatch(@Param("entities") List<UserPO> entities);/*** 更新数据** @param user 实例对象* @return 影响行数*/int update(UserPO user);/*** 通过主键删除数据** @param id 主键* @return 影响行数*/int deleteById(Integer id);/*** 通过用户名查询用户信息* @param username* @return*/UserPO selectByUsername(String username);/*** 添加用户积分* @param id 用户ID* @param points 要添加的积分数量*/void addPoints(@Param("id") int id, @Param("points") int points);/*** 根据用户ID查询用户信息* @param userId 用户ID* @return 用户信息对象*/UserPO selectById(Integer userId);}

3、mapper接口对印的XML

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.example.springmvcdemo.mapper.GarbageMapper"><resultMap id="BaseResultMap" type="com.example.springmvcdemo.entity.po.GarbageRecordPO"><id column="id" property="id"/><result column="user_id" property="userId"/><result column="type" property="type"/><result column="weight" property="weight"/><result column="accuracy" property="accuracy"/><result column="points_earned" property="pointsEarned"/><result column="image_url" property="imageUrl"/><result column="create_time" property="createTime"/></resultMap><insert id="insert" parameterType="com.example.springmvcdemo.entity.po.GarbageRecordPO"useGeneratedKeys="true" keyProperty="id">INSERT INTO garbage_record(user_id, type, weight, accuracy, points_earned, image_url)VALUES(#{userId}, #{type}, #{weight}, #{accuracy}, #{pointsEarned}, #{imageUrl})</insert><select id="selectByUserId" resultMap="BaseResultMap">SELECT * FROM garbage_recordWHERE user_id = #{userId}ORDER BY create_time DESC</select><select id="countByUserId" resultType="int">SELECT COUNT(*) FROM garbage_recordWHERE user_id = #{userId}</select><select id="sumPointsByUserId" resultType="int">SELECT IFNULL(SUM(points_earned), 0) FROM garbage_recordWHERE user_id = #{userId}</select><select id="countByType" resultType="map">SELECT`type` as type,COUNT(*) as count,SUM(weight) as total_weight,SUM(points_earned) as total_pointsFROM garbage_recordWHERE user_id = #{userId}GROUP BY `type`</select>
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.springmvcdemo.mapper.UserPOMapper"><resultMap type="com.example.springmvcdemo.entity.po.UserPO" id="UserPOMap"><result property="id" column="id" jdbcType="INTEGER"/><result property="username" column="username" jdbcType="VARCHAR"/><result property="password" column="password" jdbcType="VARCHAR"/><result property="phone" column="phone" jdbcType="VARCHAR"/><result property="address" column="address" jdbcType="VARCHAR"/><result property="role" column="role" jdbcType="TINYINT"/><result property="points" column="points" jdbcType="INTEGER"/><result property="createTime" column="create_time" jdbcType="TIMESTAMP"/></resultMap><!-- 通过ID查询单条数据 --><select id="queryById" resultMap="UserPOMap">selectid,username,password,phone,address,role,points,create_timefrom userwhere id = #{id}</select><!--分页查询指定行数据--><select id="queryAllByLimit" resultMap="UserPOMap">selectid,username,password,phone,address,role,points,create_timefrom user<where><if test="id != null">and id = #{id}</if><if test="username != null and username != ''">and username = #{username}</if><if test="password != null and password != ''">and password = #{password}</if><if test="phone != null and phone != ''">and phone = #{phone}</if><if test="address != null and address != ''">and address = #{address}</if><if test="role != null">and role = #{role}</if><if test="points != null">and points = #{points}</if><if test="createTime != null">and create_time = #{createTime}</if></where></select><!--统计总行数--><select id="count" resultType="java.lang.Long">select count(1)from user<where><if test="id != null and id != ''">and id = #{id}</if><if test="username != null and username != ''">and username = #{username}</if><if test="password != null and password != ''">and password = #{password}</if><if test="phone != null and phone != ''">and phone = #{phone}</if><if test="address != null and address != ''">and address = #{address}</if><if test="role != null">and role = #{role}</if><if test="points != null">and points = #{points}</if><if test="createTime != null">and create_time = #{createTime}</if></where></select><select id="selectByUsername" resultType="com.example.springmvcdemo.entity.po.UserPO">select * from user where username = #{username}</select><select id="selectById" resultType="com.example.springmvcdemo.entity.po.UserPO">select * from user where id = #{userId}</select><!--新增数据--><insert id="insert" keyProperty="id" useGeneratedKeys="true">insert into user(id,username,password,phone,address,role,points,create_time)values (#{id},#{username},#{password},#{phone},#{address},#{role},#{points},#{createTime})</insert><!-- 批量新增数据 --><insert id="insertBatch" keyProperty="id" useGeneratedKeys="true">insert into user(id,username,password,phone,address,role,points,create_time)values<foreach collection="entities" item="entity" separator=",">(#{entity.id},#{entity.username},#{entity.password},#{entity.phone},#{entity.address},#{entity.role},#{entity.points},#{entity.createTime})</foreach></insert><!-- 批量新增或按主键更新数据 --><insert id="insertOrUpdateBatch" keyProperty="id" useGeneratedKeys="true">insert into user(id,username,password,phone,address,role,points,create_time)values<foreach collection="entities" item="entity" separator=",">(#{entity.id},#{entity.username},#{entity.password},#{entity.phone},#{entity.address},#{entity.role},#{entity.points},#{entity.createTime})</foreach>on duplicate key updateid=values(id),username=values(username),password=values(password),phone=values(phone),address=values(address),role=values(role),points=values(points),create_time=values(create_time)</insert><insert id="addPoints">update user set points = points + #{points} where id = #{id}</insert><!-- 更新数据 --><update id="update">update user<set><if test="id != null">id = #{id},</if><if test="username != null and username != ''">username = #{username},</if><if test="password != null and password != ''">password = #{password},</if><if test="phone != null and phone != ''">phone = #{phone},</if><if test="address != null and address != ''">address = #{address},</if><if test="role != null">role = #{role},</if><if test="points != null">points = #{points},</if><if test="createTime != null">create_time = #{createTime},</if></set>where id = #{id}</update><!--通过主键删除--><delete id="deleteById">delete from user where id = #{id}</delete>
</mapper>

4、service接口

package com.example.springmvcdemo.service;import com.example.springmvcdemo.entity.dto.UserDTO;
import com.example.springmvcdemo.entity.po.UserPO;/*** @className UserService* @date 2025/7/28* @description 用户服务层*/
public interface UserService {/*** 用户登录功能* 根据用户名和密码验证用户身份,返回对应的用户信息** @param username 用户名* @param password 密码* @return UserPO 用户信息对象,如果登录失败返回null*/UserPO login(String username, String password);/*** 用户注册功能* 根据用户提供的注册信息创建新用户** @param userDTO 用户注册信息数据传输对象* @return UserPO 新创建的用户信息对象*/UserPO register(UserDTO userDTO);/*** 为指定用户添加积分* 根据用户ID为用户账户增加指定数量的积分** @param id 用户ID* @param points 要添加的积分数量*/void addPoints(int id, int points);
}
package com.example.springmvcdemo.service;import com.example.springmvcdemo.entity.dto.GarbageRecordDTO;
import com.example.springmvcdemo.entity.vo.GarbageRecordVO;
import com.github.pagehelper.PageInfo;
import java.util.Map;/*** 垃圾回收服务接口* 提供垃圾记录管理、用户记录查询和用户统计信息获取功能*/
public interface GarbageService {/*** 添加垃圾回收记录* @param record 垃圾记录数据传输对象,包含记录的详细信息* @return 添加成功返回true,失败返回false*/boolean addRecord(GarbageRecordDTO record);/*** 根据用户ID分页获取垃圾回收记录* @param userId 用户ID,用于筛选指定用户的记录* @param page 页码,从1开始计数* @param size 每页记录数量* @return 分页包装的垃圾记录视图对象列表*/PageInfo<GarbageRecordVO> getRecordsByUser(Integer userId, Integer page, Integer size);/*** 获取指定用户的垃圾回收统计信息* @param userId 用户ID,用于获取对应用户的统计数据* @return 包含用户统计信息的键值对映射,key为统计项名称,value为统计值*/Map<String, Object> getUserStats(Integer userId);}

5、service的实现

package com.example.springmvcdemo.service.impl;import com.example.springmvcdemo.entity.dto.UserDTO;
import com.example.springmvcdemo.entity.po.UserPO;
import com.example.springmvcdemo.mapper.UserPOMapper;
import com.example.springmvcdemo.service.UserService;
import com.example.springmvcdemo.utils.AESUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;import java.time.LocalDateTime;/*** @className UserServiceImpl* @date 2025/7/28* @description 用户服务层实现*/
@Slf4j
@Service
public class UserServiceImpl implements UserService {// 从配置文件中读取 AES 加密使用的密钥@Value("${aes.secret.key}")private String aesSecretKey;// 从配置文件中读取 AES 加密使用的初始化向量(IV)@Value("${aes.iv}")private String aesIV;// 自动注入 MyBatis 的 UserPOMapper 接口,用于操作用户数据@Autowiredprivate UserPOMapper userMapper;/*** 用户登录方法** @param username 用户名* @param password 密码(前端传明文,后端加密后与数据库比对)* @return 登录成功返回用户对象,失败返回 null*/@Overridepublic UserPO login(String username, String password) {// 1. 先根据用户名查询用户UserPO user = userMapper.selectByUsername(username);if (user == null) {log.info("登录失败,用户名不存在: {}", username);return null;}// 2. 验证密码:从数据库取出已加密的密码,和前端传来的明文密码加密后比对String passwordFromDB = user.getPassword();if (passwordFromDB == null) {log.info("登录失败,密码为空");return null;}if (!passwordFromDB.equals(AESUtil.encrypt(password, aesSecretKey, aesIV))) {log.info("登录失败,密码错误");return null;}return user;}/*** 用户注册方法** @param userDTO 前端传入的用户注册信息数据传输对象* @return 注册成功返回用户对象,失败返回 null*/@Overridepublic UserPO register(UserDTO userDTO) {if (userDTO == null) {log.warn("注册失败,用户信息为空");return null;}String username = userDTO.getUsername();String password = userDTO.getPassword();if (username == null || password == null) {log.warn("注册失败,用户名或密码为空");}UserPO user = new UserPO();user.setUsername(username);// 对用户密码进行 AES 加密后存入数据库user.setPassword(AESUtil.encrypt(password, aesSecretKey, aesIV));user.setPhone(userDTO.getPhone());user.setAddress(userDTO.getAddress());user.setRole((byte) 1); // 默认角色,例如普通用户user.setPoints(0); // 初始积分为 0user.setCreateTime(LocalDateTime.now()); // 设置创建时间为当前时间int insert = userMapper.insert(user);if (insert > 0) {log.info("注册成功,用户信息:{}", user);return user;}return null;}/*** 为用户增加积分** @param id     用户 ID* @param points 要增加的积分数值*/@Overridepublic void addPoints(int id, int points) {userMapper.addPoints(id, points);}}
package com.example.springmvcdemo.service.impl;import com.alibaba.fastjson2.JSON;
import com.example.springmvcdemo.entity.dto.GarbageRecordDTO;
import com.example.springmvcdemo.entity.po.GarbageRecordPO;
import com.example.springmvcdemo.entity.po.UserPO;
import com.example.springmvcdemo.entity.vo.GarbageRecordVO;
import com.example.springmvcdemo.enums.GarbageType;
import com.example.springmvcdemo.mapper.GarbageMapper;
import com.example.springmvcdemo.mapper.UserPOMapper;
import com.example.springmvcdemo.service.GarbageService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;/*** 垃圾服务实现类*/
@Slf4j
@Service
public class GarbageServiceImpl implements GarbageService {@Autowiredprivate GarbageMapper garbageMapper;/*** 添加垃圾记录* @param record 垃圾记录数据传输对象* @return 是否添加成功*/@Override@Transactionalpublic boolean addRecord(GarbageRecordDTO record) {GarbageRecordPO recordPO = new GarbageRecordPO();BeanUtils.copyProperties(record, recordPO); // 将DTO属性拷贝到POreturn garbageMapper.insert(recordPO) > 0; // 插入数据库并判断是否成功}/*** 根据用户ID分页获取垃圾记录,并转换为VO对象* @param userId 用户ID* @param page 页码* @param size 每页大小* @return 分页后的垃圾记录视图对象*/@Overridepublic PageInfo<GarbageRecordVO> getRecordsByUser(Integer userId, Integer page, Integer size) {PageHelper.startPage(page, size); // 开启分页List<GarbageRecordPO> records = garbageMapper.selectByUserId(userId); // 查询用户垃圾记录PageInfo<GarbageRecordPO> pageInfo = PageInfo.of(records); // 构造PO分页信息// 转换为VO并补充垃圾类型名称List<GarbageRecordVO> recordVOs = records.stream().map(po -> {GarbageRecordVO vo = new GarbageRecordVO();BeanUtils.copyProperties(po, vo); // 拷贝PO属性到VOvo.setTypeName(GarbageType.fromCode(po.getType()).getName()); // 设置垃圾类型名称return vo;}).collect(Collectors.toList());// 构造并返回VO分页信息PageInfo<GarbageRecordVO> pageInfoList = getGarbageRecordVOPageInfo(recordVOs, pageInfo);log.info("pageInfoList: {}", pageInfoList); // 日志记录分页信息return pageInfoList;}/*** 将PO分页信息转换为VO分页信息(手动拷贝分页参数)* @param recordVOs 垃圾记录视图对象列表* @param pageInfo 原始PO分页信息* @return 转换后的VO分页信息*/private PageInfo<GarbageRecordVO> getGarbageRecordVOPageInfo(List<GarbageRecordVO> recordVOs, PageInfo<GarbageRecordPO> pageInfo) {PageInfo<GarbageRecordVO> pageInfoList = PageInfo.of(recordVOs);pageInfoList.setTotal(pageInfo.getTotal()); // 总记录数pageInfoList.setPages(pageInfo.getPages()); // 总页数pageInfoList.setPageNum(pageInfo.getPageNum()); // 当前页码pageInfoList.setPageSize(pageInfo.getPageSize()); // 每页大小pageInfoList.setList(recordVOs); // 当前页数据列表pageInfoList.setNavigatePages(pageInfo.getNavigatePages()); // 导航页码数pageInfoList.setNavigatepageNums(pageInfo.getNavigatepageNums()); // 导航页码列表pageInfoList.setPrePage(pageInfo.getPrePage()); // 上一页页码pageInfoList.setNextPage(pageInfo.getNextPage()); // 下一页页码pageInfoList.setIsFirstPage(pageInfo.isIsFirstPage()); // 是否第一页pageInfoList.setIsLastPage(pageInfo.isIsLastPage()); // 是否最后一页pageInfoList.setHasPreviousPage(pageInfo.isHasPreviousPage()); // 是否有上一页return pageInfoList;}/*** 获取用户垃圾投放统计数据* @param userId 用户ID* @return 统计信息,包括总次数、总积分、各类型投放统计*/@Overridepublic Map<String, Object> getUserStats(Integer userId) {Map<String, Object> stats = new HashMap<>();stats.put("totalCount", garbageMapper.countByUserId(userId)); // 总投放次数stats.put("totalPoints", garbageMapper.sumPointsByUserId(userId)); // 总获得积分List<Map<String, Object>> maps = garbageMapper.countByType(userId); // 按类型统计投放次数log.info("maps: {}", JSON.toJSONString(maps)); // 打印类型统计日志stats.put("typeStats", maps); // 各类型统计数据return stats;}}

6、Controller控制器的实现

package com.example.springmvcdemo.controller;import com.example.springmvcdemo.entity.dto.UserDTO;
import com.example.springmvcdemo.entity.po.UserPO;
import com.example.springmvcdemo.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
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.ResponseBody;import javax.servlet.http.HttpSession;/*** 用户控制器*/
// 使用 Lombok 提供的日志注解,自动注入 Logger 对象(变量名为 log)
@Slf4j
// 声明该类为一个 Spring MVC 的控制器组件
@Controller
// 为该控制器类下的所有请求映射添加统一的前缀 /user
@RequestMapping("/user")
public class UserController {// 自动注入 UserService 组件,用于处理用户相关的业务逻辑@Autowiredprivate UserService userService;/*** 处理用户登录的 POST 请求,路径为 /user/login*/@PostMapping("/login")public String login(String username, String password, HttpSession session) {try {log.info("用户登录:{},登录密码:{}", username, password);// 如果用户名或密码为空,则重定向回登录页并附加错误参数 error=1,用于前端展示提示信息if (username == null || password == null) {return "redirect:/login.html?error=1";}// 调用服务层方法,根据用户名和密码进行登录验证,返回用户对象UserPO user = userService.login(username, password);if (user != null) {// 登录成功,将用户对象存入 Session 中,便于后续请求识别用户身份session.setAttribute("user", user);// 重定向到主页 /index.htmlreturn "redirect:/garbage/list"; // 重定向到垃圾列表 // 重定向到主页}} catch (Exception e) {e.printStackTrace();log.error("登录失败:{}", e.getMessage());}// 登录失败,重定向回登录页并附加错误参数 error=1,用于前端展示提示信息return "redirect:/login.html?error=1";}/*** 用户注册*/@PostMapping("/register")public String register(UserDTO userDTO, HttpSession session) {try {if (userDTO == null) {return "redirect:/register.html?error=1";}String username = userDTO.getUsername();String password = userDTO.getPassword();if (username == null || password == null) {return "redirect:/register.html?error=2";}// 调用服务层方法,根据用户名和密码进行注册UserPO user = userService.register(userDTO);if (user != null) {// 注册成功,将用户对象存入 Session 中,便于后续请求识别用户身份session.setAttribute("user", user);// 重定向到主页 /index.htmlreturn "redirect:/login.html"; // 重定向到主页}// 注册失败,重定向回注册页并附加错误参数 error=1,用于前端展示提示信息return "redirect:/register.html?error=3";} catch (Exception e) {e.printStackTrace();log.error("注册失败:{}", e.getMessage());return "redirect:/register.html?error=4";}}/*** 处理用户登出的 GET 请求,路径为 /user/logout** @param session* @return*/@GetMapping("/logout")public String logout(HttpSession session) {// 使当前 Session 失效,清除用户登录状态session.invalidate();// 重定向回登录页return "redirect:/login.html";}
}
package com.example.springmvcdemo.controller;import com.alibaba.fastjson.JSON;
import com.example.springmvcdemo.entity.dto.GarbageRecordDTO;
import com.example.springmvcdemo.entity.po.UserPO;
import com.example.springmvcdemo.entity.vo.GarbageRecordVO;
import com.example.springmvcdemo.service.GarbageService;
import com.example.springmvcdemo.service.UserService;
import com.github.pagehelper.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import javax.servlet.http.HttpSession;
import java.io.File;
import java.util.Map;
import java.util.UUID;@Slf4j
@Controller
@RequestMapping("/garbage")
public class GarbageController {@Autowiredprivate GarbageService garbageService; // 自动注入垃圾记录服务,用于处理垃圾投递相关业务逻辑@Autowiredprivate UserService userService; // 自动注入用户服务,用于处理用户相关操作,如积分管理@Value("${file.upload-dir}") private String uploadDir; // 从配置文件中读取文件上传目录路径@GetMapping("/add")public String showAddPage(HttpSession session) {log.info("新增 showAddPage"); // 打印日志:进入添加垃圾记录页面UserPO user = (UserPO) session.getAttribute("user"); // 从会话中获取当前登录用户if (user == null) {return "redirect:/login.html"; // 如果用户未登录,重定向到登录页}return "garbage/add"; // 返回添加垃圾记录的视图页面}@PostMapping("/add")public String addRecord(@RequestParam("type") Integer type,@RequestParam("weight") Double weight,@RequestParam("accuracy") Integer accuracy,@RequestParam("image") MultipartFile image,HttpSession session, Model model) {try {log.info("新增 addRecord: type:{}, weight:{}, accuracy:{}", type, weight, accuracy); // 打印日志:接收到新增记录请求参数UserPO user = (UserPO) session.getAttribute("user"); // 获取当前用户if (user == null) {return "redirect:/login.html"; // 未登录则跳转到登录页}GarbageRecordDTO record = new GarbageRecordDTO(); // 创建垃圾记录数据传输对象record.setType(type); // 设置垃圾类型record.setWeight(weight); // 设置垃圾重量record.setAccuracy(accuracy); // 设置识别准确度record.setUserId(user.getId()); // 设置当前用户ID// 处理文件上传if (!image.isEmpty()) {  // 如果上传的图片文件不为空File dir = new File(uploadDir);  // 根据配置的目录路径创建File对象if (!dir.exists()) {  // 如果目录不存在dir.mkdirs();  // 创建目录(包括多级目录)}// 获取用户上传文件的原始名称,如 "photo.jpg"String originalFilename = image.getOriginalFilename();// 提取文件后缀,例如 ".jpg"String fileExt = originalFilename.substring(originalFilename.lastIndexOf("."));// 使用UUID生成唯一文件名,避免文件覆盖,保留原扩展名String newFilename = UUID.randomUUID() + fileExt;// 构造目标文件对象,即上传目录 + 新文件名File dest = new File(dir, newFilename);// 将上传的临时文件保存到目标位置image.transferTo(dest);// 打印日志:文件上传成功及存储路径log.info("文件上传成功,保存路径为:{}", dest.getAbsolutePath());// 设置记录中的图片URL(此处应使用相对路径或网络访问路径,目前直接拼接了绝对路径,建议优化)record.setImageUrl(dest.getAbsolutePath() + newFilename); }// 根据重量和准确度计算获得的积分int points = (int) (record.getWeight() * 10 * record.getAccuracy() / 100);record.setPointsEarned(points); // 设置该记录获得的积分log.info("新增2 addRecord: {}", record); // 打印当前记录信息boolean success = garbageService.addRecord(record); // 调用服务保存垃圾记录if (success) {log.info("新增成功 addRecord: {}", record); // 保存成功,打印日志userService.addPoints(user.getId(), points); // 给用户增加对应积分model.addAttribute("message", "投递记录添加成功!获得" + points + "积分"); // 设置成功提示信息return "redirect:/garbage/list"; // 重定向到垃圾记录列表页}} catch (Exception e) {log.error("新增失败 addRecord", e); // 捕获并打印异常日志}model.addAttribute("error", "添加记录失败"); // 设置错误提示信息return "garbage/add"; // 返回添加页面并显示错误}@GetMapping("/list")public String listRecords(@RequestParam(defaultValue = "1") int page,@RequestParam(defaultValue = "5") int size,HttpSession session, Model model) {UserPO user = (UserPO) session.getAttribute("user"); // 获取当前登录用户if (user == null) {log.info("获取用户信息失败"); // 用户未登录,打印日志return "redirect:/login.html"; // 重定向到登录页}log.info("分页查询参数:page:{}, size:{}", page, size); // 打印分页查询的参数PageInfo<GarbageRecordVO> pageInfo = garbageService.getRecordsByUser(user.getId(), page, size); // 查询当前用户垃圾记录(分页)model.addAttribute("pageInfo", pageInfo); // 将分页结果放入模型,供视图展示log.info("分页查询结果:{}", JSON.toJSONString(pageInfo)); // 打印分页查询结果日志return "garbage/list"; // 返回垃圾记录列表视图页面}@GetMapping("/stats")public String getStats(HttpSession session, Model model) {UserPO user = (UserPO) session.getAttribute("user"); // 获取当前登录用户if (user == null) {return "redirect:/login.html"; // 未登录,重定向到登录页}Map<String, Object> stats = garbageService.getUserStats(user.getId()); // 查询该用户的统计信息,如总积分、投递次数等log.info("获取统计信息: {}", stats); // 打印统计信息日志model.addAttribute("stats", stats); // 将统计信息加入模型model.addAttribute("user", user); // 将用户信息也传入视图return "garbage/stats"; // 返回用户统计信息视图页面}}

7、entity的实现

package com.example.springmvcdemo.entity.po;import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;/*** 垃圾记录持久化对象(PO,Persistent Object),* 表示垃圾记录相关的数据实体,* 实现了 Serializable 接口以支持对象序列化,* 实现了 Cloneable 接口以支持对象克隆。*/
public class GarbageRecordPO implements Serializable, Cloneable {/*** ID*/private int id;/*** 投递用户ID,;*/private int userId;/*** 1-可回收 2-有害 3-厨余 4-其他,;*/private Integer type;/*** 重量(kg),;*/private Double weight;/*** 分类准确率(%),;*/private byte accuracy;/*** 获得积分,;*/private int pointsEarned;/*** 投递照片,;*/private String imageUrl;/*** 创建时间,;*/private LocalDateTime createTime;/*** ;*/public int getId() {return this.id;}/*** ;*/public void setId(int id) {this.id = id;}/*** 投递用户ID,;*/public int getUserId() {return this.userId;}/*** 投递用户ID,;*/public void setUserId(int userId) {this.userId = userId;}/*** 1-可回收 2-有害 3-厨余 4-其他,;*/public Integer getType() {return this.type;}/*** 1-可回收 2-有害 3-厨余 4-其他,;*/public void setType(Integer type) {this.type = type;}/*** 重量(kg),;*/public Double getWeight() {return this.weight;}/*** 重量(kg),;*/public void setWeight(Double weight) {this.weight = weight;}/*** 分类准确率(%),;*/public byte getAccuracy() {return this.accuracy;}/*** 分类准确率(%),;*/public void setAccuracy(byte accuracy) {this.accuracy = accuracy;}/*** 获得积分,;*/public int getPointsEarned() {return this.pointsEarned;}/*** 获得积分,;*/public void setPointsEarned(int pointsEarned) {this.pointsEarned = pointsEarned;}/*** 投递照片,;*/public String getImageUrl() {return this.imageUrl;}/*** 投递照片,;*/public void setImageUrl(String imageUrl) {this.imageUrl = imageUrl;}/*** 创建时间,;*/public LocalDateTime getCreateTime() {return this.createTime;}/*** 创建时间,;*/public void setCreateTime(LocalDateTime createTime) {this.createTime = createTime;}
}
package com.example.springmvcdemo.entity.po;import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Date;/*** 用户持久化对象(PO,Persistent Object),用于表示数据库中的用户数据* 实现了 Serializable 接口,支持对象的序列化(如网络传输、持久化存储)* 实现了 Cloneable 接口,支持对象的浅拷贝*/
public class UserPO implements Serializable, Cloneable {/** ID; */private int id ;/** 用户名,; */private String username ;/** 密码(加密存储),; */private String password ;/** 手机号,; */private String phone ;/** 住址,; */private String address ;/** 0-居民 1-管理员,; */private byte role ;/** 环保积分,; */private int points ;/** 创建时间,; */private LocalDateTime createTime ;/** ID,; */public int getId(){return this.id;}/** ID,; */public void setId(int id){this.id=id;}/** 用户名,; */public String getUsername(){return this.username;}/** 用户名,; */public void setUsername(String username){this.username=username;}/** 密码(加密存储),; */public String getPassword(){return this.password;}/** 密码(加密存储),; */public void setPassword(String password){this.password=password;}/** 手机号,; */public String getPhone(){return this.phone;}/** 手机号,; */public void setPhone(String phone){this.phone=phone;}/** 住址,; */public String getAddress(){return this.address;}/** 住址,; */public void setAddress(String address){this.address=address;}/** 0-居民 1-管理员,; */public byte getRole(){return this.role;}/** 0-居民 1-管理员,; */public void setRole(byte role){this.role=role;}/** 环保积分,; */public int getPoints(){return this.points;}/** 环保积分,; */public void setPoints(int points){this.points=points;}/** 创建时间,; */public LocalDateTime getCreateTime(){return this.createTime;}/** 创建时间,; */public void setCreateTime(LocalDateTime createTime){this.createTime=createTime;}
}
package com.example.springmvcdemo.entity.dto;import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;/*** 垃圾记录数据传输对象(DTO,Data Transfer Object),* 表示垃圾记录相关的数据实体,* 实现了 Serializable 接口以支持对象序列化,* 实现了 Cloneable 接口以支持对象克隆。*/
public class GarbageRecordDTO implements Serializable, Cloneable {/*** ID*/private int id;/*** 投递用户ID,;*/private int userId;/*** 1-可回收 2-有害 3-厨余 4-其他,;*/private Integer type;/*** 重量(kg),;*/private Double weight;/*** 分类准确率(%),;*/private Integer accuracy;/*** 获得积分,;*/private Integer pointsEarned;/*** 投递照片,;*/private String imageUrl;/*** 创建时间,;*/private LocalDateTime createTime;/*** ;*/public int getId() {return this.id;}/*** ;*/public void setId(int id) {this.id = id;}/*** 投递用户ID,;*/public int getUserId() {return this.userId;}/*** 投递用户ID,;*/public void setUserId(int userId) {this.userId = userId;}/*** 1-可回收 2-有害 3-厨余 4-其他,;*/public Integer getType() {return this.type;}/*** 1-可回收 2-有害 3-厨余 4-其他,;*/public void setType(Integer type) {this.type = type;}/*** 重量(kg),;*/public Double getWeight() {return this.weight;}/*** 重量(kg),;*/public void setWeight(Double weight) {this.weight = weight;}/*** 分类准确率(%),;*/public Integer getAccuracy() {return this.accuracy;}/*** 分类准确率(%),;*/public void setAccuracy(Integer accuracy) {this.accuracy = accuracy;}/*** 获得积分,;*/public Integer getPointsEarned() {return this.pointsEarned;}/*** 获得积分,;*/public void setPointsEarned(Integer pointsEarned) {this.pointsEarned = pointsEarned;}/*** 投递照片,;*/public String getImageUrl() {return this.imageUrl;}/*** 投递照片,;*/public void setImageUrl(String imageUrl) {this.imageUrl = imageUrl;}/*** 创建时间,;*/public LocalDateTime getCreateTime() {return this.createTime;}/*** 创建时间,;*/public void setCreateTime(LocalDateTime createTime) {this.createTime = createTime;}
}
package com.example.springmvcdemo.entity.dto;import java.io.Serializable;
import java.time.LocalDateTime;/*** 用户数据传输对象(DTO,Persistent Object),用于表示数据库中的用户数据* 实现了 Serializable 接口,支持对象的序列化(如网络传输、持久化存储)* 实现了 Cloneable 接口,支持对象的浅拷贝*/
public class UserDTO implements Serializable, Cloneable {/** 用户名,; */private String username ;/** 密码(加密存储),; */private String password ;/** 手机号,; */private String phone ;/** 住址,; */private String address ;/** 创建时间,; */private LocalDateTime createTime ;/** 用户名,; */public String getUsername(){return this.username;}/** 用户名,; */public void setUsername(String username){this.username=username;}/** 密码(加密存储),; */public String getPassword(){return this.password;}/** 密码(加密存储),; */public void setPassword(String password){this.password=password;}/** 手机号,; */public String getPhone(){return this.phone;}/** 手机号,; */public void setPhone(String phone){this.phone=phone;}/** 住址,; */public String getAddress(){return this.address;}/** 住址,; */public void setAddress(String address){this.address=address;}/** 创建时间,; */public LocalDateTime getCreateTime(){return this.createTime;}/** 创建时间,; */public void setCreateTime(LocalDateTime createTime){this.createTime=createTime;}
}
package com.example.springmvcdemo.entity.vo;import java.io.Serializable;
import java.time.LocalDateTime;/***  垃圾记录视图对象(VO,View Object),* 表示垃圾记录相关的数据实体,* 实现了 Serializable 接口以支持对象序列化,* 实现了 Cloneable 接口以支持对象克隆。*/
public class GarbageRecordVO implements Serializable, Cloneable {/*** ID*/private int id;/*** 投递用户ID,;*/private int userId;/*** 1-可回收 2-有害 3-厨余 4-其他,;*/private byte type;private String typeName;/*** 重量(kg),;*/private Double weight;/*** 分类准确率(%),;*/private byte accuracy;/*** 获得积分,;*/private int pointsEarned;/*** 投递照片,;*/private String imageUrl;/*** 创建时间,;*/private LocalDateTime createTime;/*** ;*/public int getId() {return this.id;}/*** ;*/public void setId(int id) {this.id = id;}/*** 投递用户ID,;*/public int getUserId() {return this.userId;}/*** 投递用户ID,;*/public void setUserId(int userId) {this.userId = userId;}/*** 1-可回收 2-有害 3-厨余 4-其他,;*/public byte getType() {return this.type;}/*** 1-可回收 2-有害 3-厨余 4-其他,;*/public void setType(byte type) {this.type = type;}public String getTypeName() {return typeName;}public void setTypeName(String typeName) {this.typeName = typeName;}/*** 重量(kg),;*/public Double getWeight() {return this.weight;}/*** 重量(kg),;*/public void setWeight(Double weight) {this.weight = weight;}/*** 分类准确率(%),;*/public byte getAccuracy() {return this.accuracy;}/*** 分类准确率(%),;*/public void setAccuracy(byte accuracy) {this.accuracy = accuracy;}/*** 获得积分,;*/public int getPointsEarned() {return this.pointsEarned;}/*** 获得积分,;*/public void setPointsEarned(int pointsEarned) {this.pointsEarned = pointsEarned;}/*** 投递照片,;*/public String getImageUrl() {return this.imageUrl;}/*** 投递照片,;*/public void setImageUrl(String imageUrl) {this.imageUrl = imageUrl;}/*** 创建时间,;*/public LocalDateTime getCreateTime() {return this.createTime;}/*** 创建时间,;*/public void setCreateTime(LocalDateTime createTime) {this.createTime = createTime;}
}

8、拦截器AuthInterceptor

package com.example.springmvcdemo.interceptor;import com.example.springmvcdemo.entity.po.UserPO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
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;// 使用 Lombok 提供的日志注解,自动生成日志对象 log
@Slf4j
// 标记该类为 Spring 组件,由 Spring 容器管理
@Component
// 实现 Spring MVC 的拦截器接口,用于拦截请求并进行前置、后置处理
public class AuthInterceptor implements HandlerInterceptor {/*** 在控制器方法执行前调用,用于做权限校验、登录检查等操作* 返回 true 表示放行,继续执行后续的处理器和拦截器;* 返回 false 则中断请求,不会继续执行后续逻辑。*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 获取当前请求的URI,用于判断请求路径String uri = request.getRequestURI();log.info("拦截器获取拦截请求:{}", uri);// 放行公开路径,比如登录、注册页面,不需要登录即可访问if (uri.contains("/login") || uri.contains("/register")) {return true;}// 获取当前用户的 SessionHttpSession session = request.getSession();// 从 session 中尝试获取用户信息,通常登录成功后会将用户对象存入 sessionUserPO user = (UserPO) session.getAttribute("user");// 如果 session 中没有用户信息,说明用户未登录if (user == null) {log.info("用户未登录,跳转到登录页面");// 重定向到登录页面,阻止当前请求继续执行response.sendRedirect(request.getContextPath() + "/login.html");return false; // 中断请求}// 用户已登录,允许访问当前请求return true;}/*** 在控制器方法执行后,视图渲染前调用* 可用于对 ModelAndView 对象进行修改,添加公共模型数据等*/@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception {log.info("拦截器postHandle获取拦截请求:{}", request.getRequestURI());// 此处可以添加一些需要在渲染视图前处理的逻辑,比如统一添加数据到模型}/*** 在整个请求完成之后调用,即视图已经渲染完毕* 一般用于资源清理、日志记录等收尾工作*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response,Object handler, Exception ex) throws Exception {// 请求完成后执行的逻辑,可用于记录请求耗时、异常处理等log.info("拦截器afterCompletion获取拦截请求:{}", request.getRequestURI());}
}
拦截器配置方式
1、XML配置
<!-- spring-mvc.xml -->
<mvc:interceptors><!-- 或指定拦截路径 --><mvc:interceptor><mvc:mapping path="/**"/><mvc:exclude-mapping path="/login.html"/><mvc:exclude-mapping path="/register.html"/><mvc:exclude-mapping path="/static/**"/><mvc:exclude-mapping path="/error"/><bean class="com.example.springmvcdemo.interceptor.AuthInterceptor"/></mvc:interceptor>
</mvc:interceptors>
2、java配置
@Slf4j
@ComponentScan(basePackages = "com.example.springmvcdemo")  // 扫描指定包及其子包中的组件,自动注册为Spring容器中的Bean
@EnableWebMvc   // 启用 Spring MVC,简化配置并激活注解驱动的开发模式,使用注解必须将xml配置文件注释掉<mvc:annotation-driven/>
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {@Autowiredprivate AuthInterceptor authInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {log.info("注册拦截器");// 认证拦截器 - 拦截特定路径registry.addInterceptor(authInterceptor).addPathPatterns("/**").excludePathPatterns("/login").excludePathPatterns("/register"); // 排除登录页面}
}

9、枚举类(垃圾分类枚举定义)

package com.example.springmvcdemo.enums;/*** 垃圾类型枚举类* 定义了四种垃圾分类类型及其对应的代码和名称*/
public enum GarbageType {RECYCLABLE(1, "可回收垃圾"),HAZARDOUS(2, "有害垃圾"),KITCHEN(3, "厨余垃圾"),OTHER(4, "其他垃圾");private final int code;private final String name;/*** 构造函数* @param code 垃圾类型代码* @param name 垃圾类型名称*/GarbageType(int code, String name) {this.code = code;this.name = name;}/*** 获取垃圾类型代码* @return 垃圾类型代码*/public int getCode() {return code;}/*** 获取垃圾类型名称* @return 垃圾类型名称*/public String getName() {return name;}/*** 根据代码获取对应的垃圾类型枚举值* @param code 垃圾类型代码* @return 对应的垃圾类型枚举值* @throws IllegalArgumentException 当代码无效时抛出异常*/public static GarbageType fromCode(int code) {// 遍历所有枚举值,查找匹配的代码for (GarbageType type : values()) {if (type.code == code) {return type;}}throw new IllegalArgumentException("无效的垃圾类型代码: " + code);}
}

10、AES加密解密工具类

package com.example.springmvcdemo.utils;import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;/*** AES加密解密工具类* 支持AES/CBC/PKCS5Padding模式*/
public class AESUtil {private static final String ALGORITHM = "AES";private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";private static final int KEY_SIZE = 128; // 128, 192 or 256/*** 生成AES密钥* @return 返回Base64编码的密钥字符串*/public static String generateKey() {try {KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);keyGenerator.init(KEY_SIZE);SecretKey secretKey = keyGenerator.generateKey();return Base64.getEncoder().encodeToString(secretKey.getEncoded());} catch (NoSuchAlgorithmException e) {throw new RuntimeException("生成AES密钥失败", e);}}/*** 加密* @param data 待加密数据* @param key Base64编码的密钥* @param iv Base64编码的初始化向量* @return 返回Base64编码的加密结果*/public static String encrypt(String data, String key, String iv) {try {SecretKeySpec secretKeySpec = new SecretKeySpec(Base64.getDecoder().decode(key), ALGORITHM);IvParameterSpec ivParameterSpec = new IvParameterSpec(Base64.getDecoder().decode(iv));Cipher cipher = Cipher.getInstance(TRANSFORMATION);cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));return Base64.getEncoder().encodeToString(encryptedBytes);} catch (Exception e) {e.printStackTrace();throw new RuntimeException("AES加密失败", e);}}/*** 解密* @param encryptedData Base64编码的加密数据* @param key Base64编码的密钥* @param iv Base64编码的初始化向量* @return 返回解密后的原始字符串*/public static String decrypt(String encryptedData, String key, String iv) {try {SecretKeySpec secretKeySpec = new SecretKeySpec(Base64.getDecoder().decode(key), ALGORITHM);IvParameterSpec ivParameterSpec = new IvParameterSpec(Base64.getDecoder().decode(iv));Cipher cipher = Cipher.getInstance(TRANSFORMATION);cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData);byte[] decryptedBytes = cipher.doFinal(encryptedBytes);return new String(decryptedBytes, StandardCharsets.UTF_8);} catch (Exception e) {throw new RuntimeException("AES解密失败", e);}}/*** 生成随机初始化向量(IV)* @return 返回Base64编码的IV*/public static String generateIV() {byte[] iv = new byte[16]; // AES块大小是128位(16字节)new SecureRandom().nextBytes(iv);return Base64.getEncoder().encodeToString(iv);}public static void main(String[] args) {String key = AESUtil.generateKey();String iv = AESUtil.generateIV();System.out.println("Key: " + key);System.out.println("IV: " + iv);}
}

11、配置文件

applicationContext.xml

log4j2.xml

logback.xml

mybatis-config.xml

spring-mvc.xml

web.xml

12、前端

bootstrap.min.css

bootstrap.bundle.min.js

chart.js

<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>智能垃圾分类系统 - 登录</title><style>* {margin: 0;padding: 0;box-sizing: border-box;}body {font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);min-height: 100vh;display: flex;align-items: center;justify-content: center;}.container {background: rgba(255, 255, 255, 0.95);padding: 40px;border-radius: 20px;box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);width: 100%;max-width: 400px;backdrop-filter: blur(10px);}.logo {text-align: center;margin-bottom: 30px;}.logo h1 {color: #4CAF50;font-size: 28px;margin-bottom: 10px;}.logo p {color: #666;font-size: 14px;}.form-group {margin-bottom: 20px;position: relative;}.form-group label {display: block;color: #333;font-weight: 500;margin-bottom: 8px;}.form-group input {width: 100%;padding: 12px 40px 12px 15px;border: 2px solid #e1e5e9;border-radius: 10px;font-size: 16px;transition: all 0.3s ease;background: #f8f9fa;}.form-group input:focus {outline: none;border-color: #4CAF50;background: white;box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.1);}.form-group .icon {position: absolute;right: 15px;top: 70%;transform: translateY(-50%);color: #999;font-size: 18px;}.btn {width: 100%;padding: 15px;background: linear-gradient(135deg, #4CAF50, #45a049);color: white;border: none;border-radius: 10px;font-size: 16px;font-weight: 600;cursor: pointer;transition: all 0.3s ease;margin-bottom: 20px;}.btn:hover {transform: translateY(-2px);box-shadow: 0 10px 20px rgba(76, 175, 80, 0.3);}.btn:active {transform: translateY(0);}.links {text-align: center;}.links a {color: #667eea;text-decoration: none;font-weight: 500;transition: color 0.3s ease;}.links a:hover {color: #4CAF50;}.error-message {background: #ffebee;color: #c62828;padding: 12px;border-radius: 8px;margin-bottom: 20px;border-left: 4px solid #c62828;display: none;}.features {display: grid;grid-template-columns: repeat(3, 1fr);gap: 15px;margin-top: 30px;}.feature {text-align: center;padding: 15px;background: #f8f9fa;border-radius: 10px;transition: transform 0.3s ease;}.feature:hover {transform: translateY(-2px);}.feature-icon {font-size: 24px;margin-bottom: 8px;}.feature-text {font-size: 12px;color: #666;}@media (max-width: 480px) {.container {padding: 30px 20px;margin: 20px;}.features {grid-template-columns: 1fr;}}</style>
</head>
<body>
<div class="container"><div class="logo"><h1>🌱 智能垃圾分类</h1><p>共建绿色家园,从正确分类开始</p></div><div class="error-message" id="errorMessage">登录失败,请检查用户名和密码</div><form action="/user/login" method="post" id="loginForm"><div class="form-group"><label for="username">用户名</label><input type="text" id="username" name="username" required><span class="icon">👤</span></div><div class="form-group"><label for="password">密码</label><input type="password" id="password" name="password" required><span class="icon">🔒</span></div><button type="submit" class="btn">登录</button></form><div class="links"><p>还没有账号?<a href="register.html">立即注册</a></p></div><div class="features"><div class="feature"><div class="feature-icon">♻️</div><div class="feature-text">智能识别</div></div><div class="feature"><div class="feature-icon">🏆</div><div class="feature-text">积分奖励</div></div><div class="feature"><div class="feature-icon">📊</div><div class="feature-text">数据统计</div></div></div>
</div><script>// 检查URL参数中是否有错误信息const urlParams = new URLSearchParams(window.location.search);const error = urlParams.get('error');if (error === '1') {document.getElementById('errorMessage').style.display = 'block';}// 表单提交处理document.getElementById('loginForm').addEventListener('submit', function(e) {const username = document.getElementById('username').value.trim();const password = document.getElementById('password').value.trim();if (!username || !password) {e.preventDefault();alert('请填写完整的登录信息');return;}});// 输入框动画效果const inputs = document.querySelectorAll('input');inputs.forEach(input => {input.addEventListener('focus', function() {this.parentElement.classList.add('focused');});input.addEventListener('blur', function() {if (!this.value) {this.parentElement.classList.remove('focused');}});});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>智能垃圾分类系统 - 注册</title><style>* {margin: 0;padding: 0;box-sizing: border-box;}body {font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);min-height: 100vh;display: flex;align-items: center;justify-content: center;padding: 20px;}.container {background: rgba(255, 255, 255, 0.95);padding: 40px;border-radius: 20px;box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);width: 100%;max-width: 500px;backdrop-filter: blur(10px);}.logo {text-align: center;margin-bottom: 30px;}.logo h1 {color: #4CAF50;font-size: 28px;margin-bottom: 10px;}.logo p {color: #666;font-size: 14px;}.form-group {margin-bottom: 20px;position: relative;}.form-group label {display: block;color: #333;font-weight: 500;margin-bottom: 8px;}.form-group input, .form-group textarea {width: 100%;padding: 12px 40px 12px 15px;border: 2px solid #e1e5e9;border-radius: 10px;font-size: 16px;transition: all 0.3s ease;background: #f8f9fa;font-family: inherit;}.form-group textarea {height: 80px;resize: vertical;}.form-group input:focus, .form-group textarea:focus {outline: none;border-color: #4CAF50;background: white;box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.1);}.form-group .icon {position: absolute;right: 15px;top: 70%;transform: translateY(-50%);color: #999;font-size: 18px;}.form-group.textarea-group .icon {top: 40px;}.btn {width: 100%;padding: 15px;background: linear-gradient(135deg, #4CAF50, #45a049);color: white;border: none;border-radius: 10px;font-size: 16px;font-weight: 600;cursor: pointer;transition: all 0.3s ease;margin-bottom: 20px;}.btn:hover {transform: translateY(-2px);box-shadow: 0 10px 20px rgba(76, 175, 80, 0.3);}.btn:active {transform: translateY(0);}.links {text-align: center;}.links a {color: #667eea;text-decoration: none;font-weight: 500;transition: color 0.3s ease;}.links a:hover {color: #4CAF50;}.error-message {background: #ffebee;color: #c62828;padding: 12px;border-radius: 8px;margin-bottom: 20px;border-left: 4px solid #c62828;display: none;}.password-strength {height: 4px;background: #e1e5e9;border-radius: 2px;margin-top: 5px;overflow: hidden;}.password-strength-bar {height: 100%;width: 0%;transition: all 0.3s ease;border-radius: 2px;}.strength-weak { background: #ff4444; width: 33%; }.strength-medium { background: #ffaa00; width: 66%; }.strength-strong { background: #4CAF50; width: 100%; }.form-row {display: grid;grid-template-columns: 1fr 1fr;gap: 15px;}@media (max-width: 580px) {.container {padding: 30px 20px;margin: 10px;}.form-row {grid-template-columns: 1fr;gap: 0;}}</style>
</head>
<body>
<div class="container"><div class="logo"><h1>🌱 智能垃圾分类</h1><p>注册账号,开启环保之旅</p></div><div class="error-message" id="errorMessage">注册失败,请检查填写信息</div><form action="/user/register" method="post" id="registerForm"><div class="form-group"><label for="username">用户名 *</label><input type="text" id="username" name="username" required><span class="icon">👤</span></div><div class="form-group"><label for="password">密码 *</label><input type="password" id="password" name="password" required><span class="icon">🔒</span><div class="password-strength"><div class="password-strength-bar" id="passwordStrengthBar"></div></div></div><div class="form-group"><label for="confirmPassword">确认密码 *</label><input type="password" id="confirmPassword" name="confirmPassword" required><span class="icon">🔐</span></div><div class="form-row"><div class="form-group"><label for="phone">手机号</label><input type="tel" id="phone" name="phone" placeholder="请输入手机号"><span class="icon">📱</span></div></div><div class="form-group textarea-group"><label for="address">住址</label><textarea id="address" name="address" placeholder="请输入详细地址"></textarea><span class="icon">🏠</span></div><button type="submit" class="btn">注册</button></form><div class="links"><p>已有账号?<a href="/login.html">立即登录</a></p></div>
</div><script>// 检查URL参数中是否有错误信息const urlParams = new URLSearchParams(window.location.search);const error = urlParams.get('error');const errorMessages = {'1': '注册失败,请检查填写信息','2': '用户名或密码不能为空','3': '注册失败,用户名可能已存在','4': '系统错误,请稍后重试'};if (error && errorMessages[error]) {const errorEl = document.getElementById('errorMessage');errorEl.textContent = errorMessages[error];errorEl.style.display = 'block';}// 密码强度检测document.getElementById('password').addEventListener('input', function() {const password = this.value;const strengthBar = document.getElementById('passwordStrengthBar');let strength = 0;if (password.length >= 6) strength++;if (/[A-Z]/.test(password)) strength++;if (/[0-9]/.test(password)) strength++;if (/[^A-Za-z0-9]/.test(password)) strength++;strengthBar.className = 'password-strength-bar';if (strength >= 1) strengthBar.classList.add('strength-weak');if (strength >= 2) strengthBar.classList.add('strength-medium');if (strength >= 3) strengthBar.classList.add('strength-strong');});// 表单验证document.getElementById('registerForm').addEventListener('submit', function(e) {const username = document.getElementById('username').value.trim();const password = document.getElementById('password').value;const confirmPassword = document.getElementById('confirmPassword').value;const phone = document.getElementById('phone').value.trim();if (!username || !password) {e.preventDefault();alert('用户名和密码不能为空');return;}if (password !== confirmPassword) {e.preventDefault();alert('两次输入的密码不一致');return;}if (password.length < 6) {e.preventDefault();alert('密码长度至少6位');return;}if (phone && !/^1[3-9]\d{9}$/.test(phone)) {e.preventDefault();alert('请输入正确的手机号码');return;}});// 输入框动画效果const inputs = document.querySelectorAll('input, textarea');inputs.forEach(input => {input.addEventListener('focus', function() {this.parentElement.classList.add('focused');});input.addEventListener('blur', function() {if (!this.value) {this.parentElement.classList.remove('focused');}});});
</script>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>垃圾投递</title><link rel="stylesheet" href="/css/style.css"><link href="../../../static/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5"><div class="card shadow"><div class="card-header bg-primary text-white"><h2 class="mb-0">垃圾投递</h2></div><div class="card-body"><form th:action="@{/garbage/add}" method="post" enctype="multipart/form-data"><div class="mb-3"><label class="form-label">垃圾类型</label><select name="type"  class="form-select" required><option value="" disabled selected>请选择垃圾类型</option><option value="1">可回收垃圾</option><option value="2">有害垃圾</option><option value="3">厨余垃圾</option><option value="4">其他垃圾</option></select></div><div class="mb-3"><label class="form-label">重量(kg)</label><input type="number" name="weight" class="form-control" step="0.1" min="0.1" placeholder="例如: 2.5" required></div><div class="mb-3"><label class="form-label">分类准确率(%)</label><input type="number" name="accuracy" class="form-control" min="0" max="100" value="100"><div class="form-text">请根据实际情况估计分类准确率</div></div><div class="mb-3"><label class="form-label">上传照片</label><input type="file" name="image" class="form-control" accept="image/*"><div class="form-text">上传垃圾照片(可选)</div></div><div class="d-grid gap-2 d-md-flex justify-content-md-end"><a href="/garbage/list" class="btn btn-outline-secondary me-md-2">查看记录</a><button type="submit" class="btn btn-primary">提交投递</button></div><div th:if="${error}" class="alert alert-danger mt-3" th:text="${error}"></div></form></div></div>
</div>
<script src="../../../static/js/bootstrap.bundle.min.js"></script>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>我的投递记录</title><link href="../../../static/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5"><div class="d-flex justify-content-between align-items-center mb-4"><h2>我的投递记录</h2><div><a href="/garbage/add" class="btn btn-primary me-2">新增投递</a><a href="/garbage/stats" class="btn btn-info">查看统计</a></div></div><div class="card shadow"><div class="card-body"><div class="table-responsive"><table class="table table-striped table-hover"><thead class="table-light"><tr><th>类型</th><th>重量(kg)</th><th>获得积分</th><th>投递时间</th></tr></thead><tbody><tr th:each="record : ${pageInfo.list}"><td th:text="${record.typeName}"></td><td th:text="${record.weight}"></td><td><span class="badge bg-success" th:text="${record.pointsEarned}"></span></td><td th:text="${#temporals.format(record.createTime, 'yyyy-MM-dd HH:mm')}"></td></tr></tbody></table></div><!-- 分页信息显示 --><div class="d-flex justify-content-between align-items-center mt-3"><div class="text-muted">共 <span th:text="${pageInfo.total}"></span> 条记录,第 <span th:text="${pageInfo.pageNum}"></span> / <span th:text="${pageInfo.pages}"></span> 页</div><div class="text-muted">每页显示 <span th:text="${pageInfo.pageSize}"></span> 条</div></div><!-- 分页导航 - 只有超过1页时才显示 --><nav aria-label="Page navigation" class="mt-4" th:if="${pageInfo.pages > 1}"><ul class="pagination justify-content-center"><!-- 首页 --><li class="page-item" th:classappend="${pageInfo.pageNum == 1} ? 'disabled'"><a class="page-link"th:href="${pageInfo.pageNum == 1} ? '#' : @{/garbage/list(page=1,size=${pageInfo.pageSize})}"th:onclick="${pageInfo.pageNum == 1} ? 'return false;' : 'showLoading()'"style="cursor: pointer;">首页</a></li><!-- 上一页 --><li class="page-item" th:classappend="${!pageInfo.hasPreviousPage} ? 'disabled'"><a class="page-link"th:href="${!pageInfo.hasPreviousPage} ? '#' : @{/garbage/list(page=${pageInfo.prePage},size=${pageInfo.pageSize})}"th:onclick="${!pageInfo.hasPreviousPage} ? 'return false;' : 'showLoading()'"style="cursor: pointer;">上一页</a></li><!-- 页码 --><li class="page-item" th:each="num : ${pageInfo.navigatepageNums}"th:classappend="${num == pageInfo.pageNum} ? 'active'"><a class="page-link"th:href="${num == pageInfo.pageNum} ? '#' : @{/garbage/list(page=${num},size=${pageInfo.pageSize})}"th:onclick="${num == pageInfo.pageNum} ? 'return false;' : 'showLoading()'"th:text="${num}"style="cursor: pointer;"></a></li><!-- 下一页 --><li class="page-item" th:classappend="${!pageInfo.hasNextPage} ? 'disabled'"><a class="page-link"th:href="${!pageInfo.hasNextPage} ? '#' : @{/garbage/list(page=${pageInfo.nextPage},size=${pageInfo.pageSize})}"th:onclick="${!pageInfo.hasNextPage} ? 'return false;' : 'showLoading()'"style="cursor: pointer;">下一页</a></li><!-- 尾页 --><li class="page-item" th:classappend="${pageInfo.pageNum == pageInfo.pages} ? 'disabled'"><a class="page-link"th:href="${pageInfo.pageNum == pageInfo.pages} ? '#' : @{/garbage/list(page=${pageInfo.pages},size=${pageInfo.pageSize})}"th:onclick="${pageInfo.pageNum == pageInfo.pages} ? 'return false;' : 'showLoading()'"style="cursor: pointer;">尾页</a></li></ul></nav><!-- 每页显示条数选择 --><div class="d-flex justify-content-center mt-3" th:if="${pageInfo.total > 0}"><div class="d-flex align-items-center"><span class="me-2">每页显示:</span><select class="form-select form-select-sm" style="width: auto;" onchange="changePageSize(this.value)"><option value="5" th:selected="${pageInfo.pageSize == 5}">5条</option><option value="10" th:selected="${pageInfo.pageSize == 10}">10条</option><option value="20" th:selected="${pageInfo.pageSize == 20}">20条</option><option value="50" th:selected="${pageInfo.pageSize == 50}">50条</option></select></div></div><!-- 当只有一页或没有数据时的提示 --><div class="text-center mt-4" th:if="${pageInfo.pages <= 1}"><span class="text-muted" th:if="${pageInfo.total == 0}">暂无投递记录</span><span class="text-muted" th:if="${pageInfo.total > 0 && pageInfo.pages == 1}">所有记录已显示完毕</span></div></div></div>
</div><script src="../../../static/js/bootstrap.bundle.min.js"></script><script>// 显示加载状态function showLoading() {// 显示加载中的提示const loadingToast = document.createElement('div');loadingToast.className = 'toast position-fixed top-0 start-50 translate-middle-x mt-3';loadingToast.style.zIndex = '9999';loadingToast.innerHTML = `<div class="toast-header bg-primary text-white"><strong class="me-auto">系统提示</strong></div><div class="toast-body"><div class="d-flex align-items-center"><div class="spinner-border spinner-border-sm me-2" role="status"><span class="visually-hidden">Loading...</span></div>正在加载数据...</div></div>`;document.body.appendChild(loadingToast);const toast = new bootstrap.Toast(loadingToast, { delay: 1000 });toast.show();}// 改变每页显示条数function changePageSize(newSize) {showLoading();// 跳转到第一页并改变页面大小window.location.href = `/garbage/list?page=1&size=${newSize}`;}// 页面加载完成后的处理document.addEventListener('DOMContentLoaded', function() {// 为所有分页链接添加点击事件监听(如果没有onclick的话)const paginationLinks = document.querySelectorAll('.pagination .page-link');paginationLinks.forEach(link => {if (!link.onclick && link.href) {link.addEventListener('click', function(e) {showLoading();});}});});
</script>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>我的统计</title><link rel="stylesheet" href="/css/style.css"><link href="../../../static/css/bootstrap.min.css" rel="stylesheet"><script src="../../../static/js/chart.js"></script>
</head>
<body>
<div class="container mt-5"><div class="d-flex justify-content-between align-items-center mb-4"><h2>我的环保统计</h2><div><a href="/garbage/add" class="btn btn-primary me-2">新增投递</a><a href="/garbage/list" class="btn btn-secondary">查看记录</a></div></div><div class="row mb-4"><div class="col-md-4"><div class="card text-white bg-primary mb-3"><div class="card-body text-center"><h5 class="card-title">总投递次数</h5><p class="card-text display-6" th:text="${stats?.totalCount ?: 0}">0</p></div></div></div><div class="col-md-4"><div class="card text-white bg-success mb-3"><div class="card-body text-center"><h5 class="card-title">总获得积分</h5><p class="card-text display-6" th:text="${stats?.totalPoints ?: 0}">0</p></div></div></div><div class="col-md-4"><div class="card text-white bg-info mb-3"><div class="card-body text-center"><h5 class="card-title">当前积分</h5><p class="card-text display-6" th:text="${user?.points ?: 0}">0</p></div></div></div></div><div class="card shadow mb-4"><div class="card-header"><h5 class="mb-0">垃圾类型分布</h5></div><div class="card-body"><div class="chart-container" style="position: relative; height:400px;"><canvas id="typeChart"></canvas></div></div></div><script th:inline="javascript">// 安全地获取数据,如果没有数据则使用默认值0var typeStats = /*[[${stats?.typeStats}]]*/ [];var type1Weight = 0;var type2Weight = 0;var type3Weight = 0;var type4Weight = 0;if (typeStats && typeStats.length > 0) {typeStats.forEach(function(item) {console.log('item:', item);switch(item.type) {case 1: type1Weight = item.total_weight || 0; break;case 2: type2Weight = item.total_weight || 0; break;case 3: type3Weight = item.total_weight || 0; break;case 4: type4Weight = item.total_weight || 0; break;}});// 打印 数据console.log('type1Weight:', type1Weight);console.log('type2Weight:', type2Weight);console.log('type3Weight:', type3Weight);console.log('type4Weight:', type4Weight);}const typeData = {labels: ['可回收', '有害', '厨余', '其他'],datasets: [{data: [type1Weight, type2Weight, type3Weight, type4Weight],backgroundColor: ['#4CAF50', '#F44336', '#FFC107', '#9E9E9E'],borderWidth: 1}]};// 创建图表document.addEventListener('DOMContentLoaded', function() {const ctx = document.getElementById('typeChart');if (ctx) {new Chart(ctx, {type: 'pie',data: typeData,options: {responsive: true,maintainAspectRatio: false,plugins: {legend: {position: 'right',},tooltip: {callbacks: {label: function(context) {const label = context.label || '';const value = context.raw || 0;const total = context.dataset.data.reduce((a, b) => a + b, 0);const percentage = total > 0 ? Math.round((value / total) * 100) : 0;return `${label}: ${value}kg (${percentage}%)`;}}}}}});}});</script>
</div>
<script src="../../../static/js/bootstrap.bundle.min.js"></script>
</body>
</html>

13、项目演示截图

重定向和转发的区别

转发(Forward):是通过服务器内部的转发机制实现的。当服务器接收到一个请求后,根据请求的URL地址找到对应的资源,然后将该请求转发给另一个资源进行处理,最终将该资源的处理结果返回给客户端。在这个过程中,客户端只知道自己访问了一个资源,而不知道这个资源是被转发到的。

重定向(Redirect):是通过HTTP响应头中的Location字段实现的。当服务器接收到一个请求后,发现该请求需要访问另一个资源才能得到响应,于是在HTTP响应头中设置Location字段,告诉客户端重新发送一个请求,访问另一个资源。当客户端收到这个响应后,会重新发送一个请求,访问另一个资源,因此客户端会知道自己访问了两个资源。

客户端和服务器端的处理不同:

重定向:服务器告诉客户端一个新的URL,客户端再发送新的请求。

转发:服务器内部直接调用资源处理请求,客户端并不知道发生了转发。

URL的变化:

重定向:浏览器的URL会变成新地址。

转发:浏览器的URL不会改变,仍然显示的是最初的地址。

请求次数:

重定向:会产生两次请求,第一次请求服务器,服务器返回新的URL,浏览器再次请求新URL。

转发:只有一次请求,服务器内部直接处理。

数据传递:

重定向:由于是两次请求,无法在请求间传递数据(除非使用Session或其他持久化手段)。

转发:可以在转发过程中共享Request对象中的数据。

Spring MVC默认使用的是转发(Forward),具体表现在:

  1. 当控制器方法返回视图名称时(如return "home";),Spring会使用RequestDispatcher.forward()
  2. 视图解析器会找到对应的视图资源进行渲染

Cookie和Session的区别

Cookie:

Cookie是一种机制,客户端保存服务端数据。当客户端(比如浏览器)访问网页时,服务器端可以把一些状态数据以k-v的形式写入Cookie,存储到客户端。如果客户端再次访问服务器端,就会携带Cookie发送到服务器端,服务器端根据Cookie携带的内容识别使用者,如果不关闭浏览器,那么Cookie变量一直是有效的,所以能够保证长时间不掉线。不过这里有个问题,如果你能够获取某个用户的Cookie,将这个Cookie发送到服务器,那么服务器还是认为你是合法的。所以,使用cookie被攻击的可能性比较大。

Session:

Session是一种会话,是服务器的一个容器对象。Servlet容器分配一个Session对象,用于存储当前会话产生的一些状态数据。当客户端初次发送请求到服务器端,服务器端会创建一个Session,同时会创建一个特殊的Cookie,然后将该Cookie发送至客户端。HTTP协议是无状态协议,服务器端不知道客户端发送的多次请求属于同一个用户,Session就用来弥补这个不足。通过服务器端的Session存储机制结合客户端的Cookie机制,实现一个“有状态”的HTTP协议。客户端第一次访问服务端时,服务器端会针对这次请求创建一个会话,生成唯一的SessionId标注会话,随后服务器端把SessionId写入到客户端的Cookie,保存客户端状态,后续的请求都携带SessionId,服务器端根据SessionId识别当前会话状态,以确定用户是否登录或具有某种权限。数据是存储在服务器上面,不能伪造,这里会比Cookie更好。

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

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

相关文章

【JavaEE】(7) 网络原理 TCP/IP 协议

一、应用层 应用层是程序员最关心的一层&#xff0c;需要自定义数据传输的格式&#xff0c;即前&#xff08;客户端&#xff09;后&#xff08;服务器&#xff09;端交互的接口&#xff0c;然后调用传输层的 socket api 来实现网络通信。 自定义数据传输的协议&#xff0c;主要…

深入理解 Slab / Buddy 分配器与 MMU 映射机制

&#x1f4d6; 推荐阅读&#xff1a;《Yocto项目实战教程:高效定制嵌入式Linux系统》 &#x1f3a5; 更多学习视频请关注 B 站&#xff1a;嵌入式Jerry 深入理解 Slab / Buddy 分配器与 MMU 映射机制 在现代 Linux 内核中&#xff0c;物理内存的管理和虚拟地址的映射是系统性能…

Layui核心语法快速入门指南

Layui 基本语法学习指南 Layui 是一个经典的模块化前端框架&#xff0c;以其轻量易用、组件丰富著称。以下是 Layui 的核心语法结构和使用方法&#xff1a; 一、模块加载机制&#xff08;核心基础&#xff09; // 标准模块加载语法 layui.use([module1, module2], function()…

基于百度 iframe 框架与语音解析服务的数字人交互系统实现

在智能化交互场景中,数字人作为人机交互的重要载体,其语音交互能力与指令响应效率直接影响用户体验。本文将详细介绍如何基于百度提供的 iframe 框架与语音解析服务,实现数字人语音播报、文字展示及指令响应的完整业务流程,涵盖从插件初始化到实时语音交互的全链路实现逻辑…

高防服务器租用的优势有哪些?

高防服务器具有着强大的防护能力&#xff0c;可以帮助企业抵御各种网络攻击&#xff0c;其中包括大规模的DDOS攻击&#xff0c;高防服务器中还有着防火墙、流量清洗和负载均衡等多种安全技术&#xff0c;能够保证业务持续稳定的运行&#xff0c;降低了企业整体的损失和安全风险…

7.28 进制交换|迭代器模式|map|子集按位或|带参递归

lc701.二叉搜索树插入void dfs不行TreeNode* dfs&#xff0c;带接受参数处理的dfs当为空的时候&#xff0c;就可以添加插入if (!root){return new TreeNode(val);}插入位置root->left insertIntoBST(root->left, val);class Solution {public:TreeNode* insertIntoBST(T…

方法学习(二)

.一、变量作为实参使用&#xff1a;1.定义一个方法&#xff0c;比较两个整数的大小&#xff0c;如果第一个整数比第二个整数大&#xff0c;返回true否则返回false。public static void main(String[] args) {int i 3;int j 5;//传递的是i和j&#xff0c;但是真正传递的是i和j…

计算机视觉CS231n学习(1)

面向视觉识别的卷积神经网络 CS231n Introduction计算机视觉的历史 the history of computer vision 重要节点&#xff1a;1959 Hubel & Wiesel 利用和人比较相像的猫的视觉神经做实验&#xff1a;简单细胞反应灯的位置&#xff1b;复杂细胞反应灯的位置和移动&#xff1b;…

【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - 微博内容IP地图可视化分析实现

大家好&#xff0c;我是java1234_小锋老师&#xff0c;最近写了一套【NLP舆情分析】基于python微博舆情分析可视化系统(flaskpandasecharts)视频教程&#xff0c;持续更新中&#xff0c;计划月底更新完&#xff0c;感谢支持。今天讲解微博内容IP地图可视化分析实现 视频在线地…

Z20K118库中寄存器及其库函数封装-SYSCTRL库

1. 系统设备识别寄存器(SCM)7个位域。 记录设备信息。Z20K11x[FAM_ID:Z20K/Z20M,SUBF_ID:1/3,SER_ID:1/4]特征ID版本号FLASH存储器大小封装类型。1-1 SYSCTRL_DeviceId_t SYSCTRL_GetDeviceId(void)读取设备信息。2.独一ID号寄存器&#xff08;SCM&#xff09;4个该寄存器存储完…

007TG洞察:波场TRON上市观察,Web3流量工具的技术解析与应用

引言&#xff1a;波场TRON&#xff08;TRX&#xff09;登陆资本市场及近期加密市场热点&#xff08;如MEME币&#xff09;&#xff0c;凸显了实时流量捕获与转化在Web3领域的战略地位。对于技术团队而言&#xff0c;构建支撑全球业务的Web3平台&#xff0c;核心挑战在于&#x…

STM32——HAL 库MDK工程创建

总&#xff1a;STM32——学习总纲 参考工程&#xff1a; 实验0-3&#xff0c;新建工程实验-HAL库版本 前置知识&#xff1a; STM32——HAL库 一、HAL 库 MDK工程新建步骤简介 例&#xff1a; 各个文件夹内容&#xff1a; 1.1 Drivers 1.2 Middlewares 1.3 Output 1.4 Pro…

【图像处理】霍夫变换:霍夫变换原理、霍夫空间、霍夫直线、霍夫圆详解与代码示例

霍夫变换详解与代码示例 霍夫变换&#xff08;Hough Transform&#xff09;是一种用于检测图像中几何形状&#xff08;如直线、圆&#xff09;的特征提取技术。其核心思想是将图像空间中的点映射到参数空间&#xff08;霍夫空间&#xff09;&#xff0c;通过累积投票机制识别形…

Java WEB技术-序列化和反序列化认识(SpringBoot的Jackson序列化行为?如何打破序列化过程的驼峰规则?如何解决学序列化循环引用问题?)

一、什么是序列化和反序列化 在java项目中&#xff0c;对象序列化和反序列化通常用于对象的存储或网络传输等。如&#xff1a;服务端创建一个JSON对象&#xff0c;对象如何在网络中进行传输呢&#xff1f;我们知道网络传输的数据通常都是字节流的形式&#xff0c;对象想要在网络…

【生活系列】MBTI探索 16 种性格类型

博客目录一、MBTI 的四个核心维度1. 精力来源&#xff1a;外向&#xff08;E&#xff09;vs 内向&#xff08;I&#xff09;2. 信息获取方式&#xff1a;感觉&#xff08;S&#xff09;vs 直觉&#xff08;N&#xff09;3. 决策方式&#xff1a;思考&#xff08;T&#xff09;v…

innovus在ccopt_design时设置update io latency

我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧? 拾陆楼知识星球入口 往期文章:

电脑出现英文字母开不了机怎么办 原因与修复方法

当您按下电脑开机键&#xff0c;屏幕上却只显示一串串陌生的英文字母&#xff0c;无法正常进入系统时&#xff0c;这通常是电脑在向您“求救”。这种情况可能由多种原因引起&#xff0c;从外部设备冲突到系统文件损坏&#xff0c;都可能导致电脑无法启动。不必过于焦虑&#xf…

CSS和XPATH选择器对比

1、优缺点比较特性CSS选择器XPath语法复杂度简洁易读较为复杂性能通常更快可能较慢向上遍历不支持支持&#xff08;可选择父元素&#xff09;文本内容选择有限支持完全支持索引选择支持&#xff08;:nth-child&#xff09;支持&#xff08;position()&#xff09;浏览器兼容性优…

libomxil-bellagio移植到OpenHarmony

当使用mesa3dcangh提供的amd显卡驱动时&#xff0c;想利用 Mesa 提供的图形硬件加速能力&#xff0c;来支持视频编解码操作时。需要依赖libomxil-bellagio库&#xff0c;现在成果分享如下&#xff1a; 基础知识 1.OpenHarmony中mesa3d amd显卡驱动编译 2.OpenHarmony中基于G…

uvm-tlm-sockets

TLM 2.0引入了套接字(Socket)机制&#xff0c;实现发起方(initiator)与目标方(target)组件间的异步双向数据传输。套接字与端口(port)和导出(export)同源&#xff0c;均继承自uvm_port_base基类。发起事务的组件使用发起方套接字(initiator socket)&#xff0c;称为发起方&…