什么是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. 需求背景
隨著垃圾分類政策的推行,社區需要一個智能垃圾分類管理系統,實現以下功能:
- 居民賬戶管理:居民注冊/登錄,記錄垃圾分類行為
- 垃圾投遞記錄:記錄每次垃圾投遞的類型、重量和時間
- 環保積分系統:根據分類準確性獎勵積分
- 數據可視化:展示個人和社區的垃圾分類數據
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>--><!-- <!– SLF4J API(logback-classic 已經包含 slf4j-api,但顯式聲明版本可避免沖突) –>--><!-- <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),具體表現在:
- 當控制器方法返回視圖名稱時(如
return "home";
),Spring會使用RequestDispatcher.forward()
- 視圖解析器會找到對應的視圖資源進行渲染
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更好。