目录
【任务1】:实现上传头像的功能。用户选择本地图片,单击上传按钮调用后端接口可以实现将图片保存到文件服务器中,同时在页面中显示上传的图片。
【任务3】:在SpringMVC项目中定义全局异常处理器实现全局异常处理功能。具体要求如下:当前端调用后端接口出现异常时,
1.文件上传
1.异步文件上传
【任务1】:实现上传头像的功能。用户选择本地图片,单击上传按钮调用后端接口可以实现将图片保存到文件服务器中,同时在页面中显示上传的图片。
【实现步骤】
1. 使用Tomcat搭建简易文件服务器
2. 当单击上传按钮时,通过fetch发送异步请求调用后端API接口,同时将图片传送到后端。
3. 定义后端接口,接收图片文件内容并将图片存储到文件服务器。
4. 后端接口将服务器图片地址返回给前端进行展示。
1.1.异步文件上传-简易文件服务器搭建
1.在本机另外部署一个tomcat作为文件服务器
2.在webapps下新建文件夹uploadfiles,用于存储上传的文件
3.修改conf/server.xml文件,配置文件服务器的端口与上传文件夹的访问
4.修改conf/web.xml,配置文件可读写
验证文件服务器的搭建
运行tomcat控制台输出乱码
1.1.异步文件上传-视图设计
<%--
Created by IntelliJ IDEA.
User: flowerfog
Date: 2024/11/28
Time: 13:49
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>文件上传</title>
<script type="text/javascript" src="${pageContext.request.contextPath}/static/jquery-3.6.1.js"></script>
</head>
<body>
<form id="fileForm">
选择文件:<input type="file" id="myFile" />
<br/><br/>
文件描述:<input type="text" id="description"/>
<br/><br/>
显示上传图片:<img alt="" id="img" src="">
<br/><br/>
<input type="button" onclick="ajaxUpload()" value="上传文件"/>
</form>
</body>
<script>
function ajaxUpload() {
// 获取表单数据,并添加到FormData中
let myFile = $("#myFile")[0].files[0];
let description = $("#description").val();
let formData = new FormData();
formData.append("myFile", myFile);
formData.append("description", description);
$.ajax({
url: "${pageContext.request.contextPath}/doRemoteUpload",// 定义请求地址
type: "post", // 定义请求类型
data: formData,// 定义请求参数(使用js对象)
// 告诉jQuery不要去处理发送数据,因已被我们通过FormData处理
processData: false,
contentType: false,
dataType: "json",// 定义服务器响应的数据rData格式为json
success: function (rData) {
if (rData.code === 200) {
$("#img").attr("src", rData.data);
}else{
alert(rData.message)
}
}
});
}
</script>
</html>
1.3.异步文件上传-解析配置
在SpringMVC框架中,如果要使用上传文件解析器,需要在web.xml中配置
<multipart-config>标签。
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee">
<display-name>Archetype Created Web Application</display-name>
<!-- 配置DispatcherServlet -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 自动加载spring-mvc.xml -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<!--当值≥0时,启动时就加载;当值<0或不指定时,则表示第一次请求时加载-->
<load-on-startup>1</load-on-startup>
<!--配置上传文件解析-->
<multipart-config>
<!--上传文件大小限制,示例:5M-->
<max-file-size>5242880</max-file-size>
<!--一次表单提交中文件的大小限制,示例:10M-->
<max-request-size>10485760</max-request-size>
<!-- 多大文件会被自动保存到硬盘上。0 代表所有 -->
<file-size-threshold>0</file-size-threshold>
</multipart-config>
</servlet>
<!-- 配置DispatcherServlet接受所有URL请求 -->
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 编码过滤器,解决中文乱码问题 -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<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>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
1.4.异步文件上传-远程调用依赖导入
Jersey是一个WebService框架,用于服务器之间的远程调用。是一种跨编程语言和跨操作系统平台的远程调用技术,可用于实现跨服务器的文件上传。使用Jersey框架,需要在项目中引入Jersy依赖。
<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
https://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.flowerfog</groupId>
<artifactId>SpringMvcDemo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>MvcDataInteraction</artifactId>
<packaging>war</packaging>
<name>MvcDataInteraction Maven Webapp</name>
<url>https://maven.apache.org</url>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<org.springframework.version>5.3.29</org.springframework.version>
<jersey-client.version>1.19.4</jersey-client.version>
</properties>
<dependencies>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<!-- Spring Web MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<!-- Java EE Servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!-- Java EE JSP API -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
<scope>provided</scope>
</dependency>
<!-- JSTL -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- Jackson Databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.1</version>
</dependency>
<!-- JUnit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.3.1</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>
<!--Jersey-->
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<version>${jersey-client.version}</version>
</dependency>
</dependencies>
<build>
<finalName>MvcDataInteraction</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
1.5. 异步文件上传-后端接口定义
package org.flowerfog.controller;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
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.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.UUID;
@Controller
public class UploadController {
// 打开文件上传页面
@GetMapping("/upload/index")
public String index(){
return "upload/index";
}
// 处理文件上传请求
@PostMapping("/doRemoteUpload")
@ResponseBody
public Object doRemoteUpload(MultipartFile myFile,
String description) {
HashMap<String, Object> ret = new HashMap<>();
try {
String path = "http://localhost:8088/uploadfiles/";
// 为上传到服务器的文件取名,使用UUID防止文件名重复
String fileName = UUID.randomUUID().toString() + "-" + myFile.getOriginalFilename();
// 创建Jersey客户端
Client client = Client.create();
// 使用客户端完成文件上传
String filePath = path + "/" + URLEncoder.encode(fileName, StandardCharsets.UTF_8);
WebResource webResource = client.resource(filePath);
webResource.put(myFile.getBytes());
// 返回值
System.out.println(description);
ret.put("code", 200);
ret.put("data", path + fileName);
ret.put("msg", "success");
} catch (Exception e) {
ret.put("code", -1);
ret.put("data", null);
ret.put("msg", e.getMessage());
}
return ret;
}
}
2.Spring MVC拦截器
综述
l拦截器是SpringMVC框架中的一种组件,主要用来拦截客户端的请求,在请求的控制器方法前后根据业务执行预先设定的代码,也是AOP(面向切面编程)思想的一种实现方式。
拦截器应用场景
登录验证:在用户访问需要登录的页面之前,拦截器可以检查用户是否已经登录,如果没有登录则重定向到登录页面
权限检查:在用户访问需要特定权限的资源之前,拦截器可以检查用户是否具有相应的权限,如果没有则返回无权限的错误信息
日志记录:在前端访问后端每个接口时,拦截器可以记录日志,包括请求的 IP 地址、请求时间、请求的 URL 等信息
数据校验:在请求处理之前,拦截器可以校验请求参数的有效性,如果参数不合法则返回错误信息
统一异常处理:在请求处理出现异常时,拦截器可以统一处理异常信息,避免程序抛出异常页面
拦截器创建
在SpringMVC项目中,可以通过实现HandlerInterceptor接口并重写接口中的方法来创建拦截器类。
在HandlerInterceptor接口中有三个方法,分别是preHandle、postHandle和afterCompletion方法。
拦截器preHandle方法
preHandle方法是在控制器目标方法执行之前执行的方法
拦截器postHandle方法
postHandle方法是在控制器目标方法执行之后执行的方法,目标方法内部异常时不执行
拦截器afterCompletion方法
afterCompletion方法是在给与客户端最终响应之后执行的方法,无论目标方法内部是否异常。
运行原理
单个拦截器执行流程
多个拦截器执行流程
拦截器配置
拦截器类创建好之后,还需要通过在SpringMVC配置文件中配置<mvc:interceptors>标签完成拦截器的配置才能生效。
子元素<bean>定义的是全局拦截器,即拦截所有请求
也可以通过<mvc:interceptor>标签定义指定拦截路径的拦截器
子元素<mvc:mapping>标签用于配置拦截器需要拦截的路径
<mvc:exclude-mapping>标签用于配置拦截器不需要拦截,也就是放行的路径。
<mvc:interceptors>标签下可以配置多个<mvc:interceptor>子标签
【任务2】:定义拦截器实现登陆验证功能。
视图设计
登录页面
<%--
Created by IntelliJ IDEA.
User: flowerfog
Date: 2024/11/28
Time: 13:59
To change this template use File | Settings | File Templates.
--%>
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>login</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/user/login" method="POST">
用户名: <input type="text" name="account" /><br />
密 码 :
<input type="password" name="password" /><br />
<input type="submit" value="登录" />
<div style="color: red">${msg}</div>
</form>
</body>
</html>
系统首页
<%--
Created by IntelliJ IDEA.
User: flowerfog
Date: 2024/11/28
Time: 14:00
To change this template use File | Settings | File Templates.
--%>
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>main.jsp</title>
</head>
<body>
当前用户: ${loginUser.name}
<a href="${pageContext.request.contextPath}/user/logout">退出</a>
</body>
</html>
控制器设计
登录get请求,打开登录页面
登录post请求,验证登录
退出登录get请求,退出登录
UserController
package org.flowerfog.controller;
import org.flowerfog.pojo.User;
import org.flowerfog.service.UserService;
import org.flowerfog.vo.UserVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
//1.向页面传值
//1.1.原生方式,使用方法的参数reques和response进行数据分享和页面跳转
@GetMapping("/list1")
public void list1(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
List<UserVo> users = userService.findAll();
request.setAttribute("userList", users);
request.getRequestDispatcher("/WEB-INF/views/user/list.jsp").forward(request, response);
}
@RequestMapping("/list12")
public String list12(HttpSession session){
List<UserVo> users = userService.findAll();
session.setAttribute("userList", users);
return "user/list";
}
@RequestMapping("/list13")
public String list13(HttpServletRequest request){
List<UserVo> users = userService.findAll();
ServletContext ctx = request.getServletContext();
ctx.setAttribute("userList", users);
return "user/list";
}
//1.2.使用控制ModelAndView对象进行数据共享和页面的跳转
@GetMapping("/list2")
public ModelAndView list2() {
ModelAndView mv = new ModelAndView();
List<UserVo> users = userService.findAll();
mv.addObject("userList", users);
mv.setViewName("user/list");
return mv;
}
//1.3.使用Model对象,使用返回的字符串控制页面的跳转,可使用试图解析器自动补充前后缀
@GetMapping("/list3")
public String list3(Model model){
List<UserVo> users = userService.findAll();
model.addAttribute("userList", users);
return "user/list";
}
@GetMapping("/list32")
public String list32(ModelMap model){
List<UserVo> users = userService.findAll();
model.addAttribute("userList", users);
return "user/list";
}
@GetMapping("/list33")
public String list33(Map<String,Object> map){
List<UserVo> users = userService.findAll();
map.put("userList", users);
return "user/list";
}
//1.4.返回值为对象类型,一般返回JSON格式的对象字符串
//新增Get
@GetMapping("/add")
public String add(Model model){
User user = new User();
user.setStatus(0);//设置默认值
model.addAttribute("user", user);
return "user/edit";
}
//修改Get
@GetMapping("/edit")
public String edit(Model model,Integer id){
User user = userService.getById(id);//根据主键获取实体
model.addAttribute("user", user);
return "user/edit";
}
//新增与修改的Post
@PostMapping(value="/save")
@ResponseBody
public Object save(@RequestBody User user) {
HashMap<String,Object> ret = new HashMap<>();
try {
userService.save(user);
ret.put("code", 200);
ret.put("msg", "success");
} catch (Exception e) {
ret.put("code", -1);
ret.put("msg", e.getMessage());
}
return ret;
}
//删除Get
@GetMapping("/delete/{id}")
@ResponseBody
public Object delete(@PathVariable Integer id){
HashMap<String,Object> ret = new HashMap<>();
try {
userService.deleteById(id);
ret.put("code", 200);
ret.put("msg", "success");
} catch (Exception e) {
ret.put("code", -1);
ret.put("msg", e.getMessage());
}
return ret;
}
// 打开登录页面
@GetMapping(value = "/login")
public String toLogin() {
return "user/login";
}
// 登录验证
@PostMapping(value = "/login")
public String Login(User user, Model model, HttpSession session) {
String account = user.getAccount();
String password = user.getPassword();
if (account != null && account.equals("admin")
&& password != null && password.equals("123456")) {
user.setName("管理员");
session.setAttribute("loginUser", user);
return "home/index";
}
model.addAttribute("msg", "账号密码有误,请重新登录!");
return "user/login";
}
// 退出登录
@GetMapping(value = "/logout")
public String Logout(HttpSession session) {
// 清除登录信息
session.invalidate();
return "user/login";
}
}
登录拦截器设计
登录页面放行
其他页面,检查是否已经登录
已经登录,放行
否则跳转登录页面
LoginInterceptor
package org.flowerfog.intercptor;
import org.flowerfog.pojo.User;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws ServletException, IOException {
// 获取请求Url
String url = request.getRequestURI();
if (url.contains("/user/login")){
// 登录页面,放行
return true;
}
// 其他请求,检查用户是否已登录
HttpSession session = request.getSession();
User loginUser = (User) session.getAttribute("loginUser");
if (loginUser == null){
// 用户已经登录,放行
return true;
}
// 用户未登录,跳转到登录页面
// request.setAttribute("msg", "请先登录系统");
request.getRequestDispatcher("/WEN-INF/views/user/login.jsp").forward(request,response);
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response
, Object handler, Exception ex)
throws Exception {
}
}
配置拦截器
<?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:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="org.flowerfog.service"/>
<context:component-scan base-package="org.flowerfog.controller"/>
<!-- 开启SpringMVC框架的注解驱动 -->
<mvc:annotation-driven/>
<!--视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!--静态资源访问映射路径-->
<mvc:resources location="/static/" mapping="/static/**"/>
<!--配置拦截器-->
<mvc:interceptors>
<!--配置全局拦截器,拦截所有用户请求-->
<bean class="org.flowerfog.intercptor.LoginInterceptor"/>
</mvc:interceptors>
</beans>
3.全局异常处理
异常可以分为两类:
异常处理是指对在请求处理过程中可能发生的异常情况进行捕获、处理和响应的机制。
在Java中的异常处理主要通过try-catch语句块来实现,但是这样异常处理的代码就与处理业务的代码耦合在一起,不仅有大量的冗余代码,而且还影响代码的可读性。
全局异常处理将所有类型的异常处理从各处理过程解耦出来,既保证了相关处理过程的功能单一,也实现了异常信息的统一处理和维护。
全局异常处理优点:
【任务3】:在SpringMVC项目中定义全局异常处理器实现全局异常处理功能。具体要求如下:当前端调用后端接口出现异常时,
(1)如果是业务异常,则在页面中弹出对话框显示具体的业务异常信息
(2)如果是系统异常,则在页面中弹出对话框显示“抱歉,服务器出现了异常,正在加紧修复中”
SpringMVC全局异常处理机制
SpringMVC框架是通过HandlerExceptionResolver机制来实现全局异常处理功能的。
HandlerExceptionResolver是一个接口,全局异常处理器就是通过实现该接口的resolveException方法实现全局异常处理。
全局异常处理器实现
ExceptionHandlerExceptionResolver是SpringMVC中使用最简单、最常用的异常处理器之一,可以进行全局异常统一处理。
该异常处理器是通过对@ControllerAdvice和@ExceptionHandler标注的异常处理方法进行缓存,构筑异常-处理方法的映射。在处理请求时,如果控制器方法中抛出异常到DispatcherServlet,
ExceptionHandlerExceptionResolver会根据异常对象找到对应处理方法,进行统一异常处理。
SpringMVC全局异常处理机制
全局异常处理器实现
在SpringMVC项目中可以通过@ControllerAdvice和@ExceptionHandler注解定义全局异常处理组件,实现捕获所有控制器抛出的异常并进行处理。
@RestControllerAdvice注解是@ControllerAdvice和@ResponseBody的组合注解。
在@ExceptionHandler注解中,我们可以使用该注解的value属性指定为不同类型的异常定义不同的异常处理方法。
需要在SpringMVC配置文件中扫描全局异常处理组件所在的包,将全局异常处理组件注册到Spring容器中,该组件才能起作用。
在springmvc.xml中加入如下配置:
至此,项目设计结束。