第十一章 框架升级之数据校验及文件上传(2023版本IDEA)


  第9~10章完成了功能的改造升级工作(如果没有了解可以去我主页看看 第一至十章的内容来学习)本章将继续对用户管理模块的其他功能进行改造,帮助同学们学习并掌握Spring MVC框架的文件上传功能,了解REST风格及JSR 303 数据验证框架等技术。

11.1 系统增、查、改功能

  增加、查询、删除(本章不涉及)、修改是Web项目的基本功能,也是开发人员涉及最多的工作。本节使用SpringMVC框架对用户模块进行升级改造。

11.1.1改造添加功能

  从用户添加功能入手,主要工作是替换控制层的实现技术,Spring MVC框架提供了接收对象入参的功能,可以直接把前端传入的参数封装到对应对象中,非常方便。

改造Java代码以添加新功能通常涉及几个关键步骤,包括需求分析、设计、编码、测试和部署。这里,我将给出一个简化的示例,说明如何在一个现有的Java类中添加一个新功能。

假设的场景
假设我们有一个简单的Calculator类,它目前支持加法和减法操作。我们的任务是给它添加一个新的功能:乘法。

现有的Calculator类

public class Calculator {  
  
    // 加法  
    public int add(int a, int b) {  
        return a + b;  
    }  
  
    // 减法  
    public int subtract(int a, int b) {  
        return a - b;  
    }  
  
    // 这里我们将添加乘法  
}

添加乘法功能

  1. 分析需求: 需要添加一个方法,该方法接受两个整数参数并返回它们的乘积。
  2. 设计: 在Calculator类中增加一个名为multiply的方法。
  3. 编码:
public class Calculator {  
  
    // 加法  
    public int add(int a, int b) {  
        return a + b;  
    }  
  
    // 减法  
    public int subtract(int a, int b) {  
        return a - b;  
    }  
  
    // 新增乘法  
    public int multiply(int a, int b) {  
        return a * b;  
    }  
  
    // 可以在这里继续添加其他功能  
}
  1. 测试: 编写单元测试来验证新添加的multiply方法是否按预期工作。
import org.junit.jupiter.api.Test;  
import static org.junit.jupiter.api.Assertions.*;  
  
public class CalculatorTest {  
  
    @Test  
    public void testMultiply() {  
        Calculator calc = new Calculator();  
        assertEquals(6, calc.multiply(2, 3), "2 * 3 should be 6");  
    }  
  
    // 可以添加更多测试来验证其他方法  
}
  1. 将修改后的代码部署到生产环境(对于小型项目或学习项目,这可能意味着简单地重新编译并运行你的程序)。

注意事项

  • 代码审查: 在提交更改之前,请确保进行代码审查,以确保新添加的功能符合项目标准和最佳实践。
  • 文档更新: 更新相关的开发文档和用户文档,以反映新的功能。
  • 版本控制: 使用版本控制系统(如Git)跟踪你的更改,并确保你的更改被正确提交到存储库。
  • 测试覆盖率: 尽量为新的和现有的功能编写足够的测试用例,以确保代码的稳定性和可靠性。
    通过遵循上述步骤,你可以有效地在Java项目中添加新功能。

11.1.2 改造查询功能(REST 风格)

  用户添加功能改造完成之后,接着改造查询详情功能。在此之前,先来了解一个比较流行的API开发规范——REST。

改造REST风格的查询功能通常意味着你正在开发一个基于RESTful API的Web服务,并且需要更新或优化该服务中的查询功能。RESTful API通过HTTP请求(如GET、POST、PUT、DELETE)来操作资源,并使用无状态通信,即每个请求都应该包含执行该请求所需的所有信息。

以下是一个简化的例子,说明如何改造一个基于Spring Boot的RESTful API中的查询功能。我们将从一个简单的Book资源开始,然后添加或修改一个查询Book的端点。

假设的Book资源
首先,我们有一个Book实体类和一个BookRepository接口(使用Spring Data JPA):

// Book.java  
@Entity  
public class Book {  
    @Id  
    @GeneratedValue(strategy = GenerationType.IDENTITY)  
    private Long id;  
    private String title;  
    private String author;  
    // 省略getter和setter  
}  
  
// BookRepository.java  
public interface BookRepository extends JpaRepository<Book, Long> {  
    // 这里可以添加一些特定查询,例如根据作者查询  
    List<Book> findByAuthor(String author);  
}

原始的REST Controller
我们可能已经有了一个基础的REST Controller来处理对Book资源的请求:

// BookController.java  
@RestController  
@RequestMapping("/books")  
public class BookController {  
  
    @Autowired  
    private BookRepository bookRepository;  
  
    // 获取所有书籍  
    @GetMapping  
    public List<Book> getAllBooks() {  
        return bookRepository.findAll();  
    }  
  
    // 添加改造的查询功能  
    // ...  
}

改造查询功能
现在,我们要在BookController中添加一个新的端点,以便可以通过作者名来查询书籍。我们将使用@GetMapping注解来定义这个新的端点,并使用查询参数来传递作者名。

// 在BookController.java中添加  
  
// 根据作者名查询书籍  
@GetMapping("/by-author")  
public List<Book> getBooksByAuthor(@RequestParam String author) {  
    return bookRepository.findByAuthor(author);  
}

这个新的端点/books/by-author将接受一个名为author的查询参数,并使用BookRepository中的findByAuthor方法来查询和返回具有该作者名的所有书籍。

测试改造后的查询功能
为了确保新的查询功能按预期工作,你应该编写单元测试或集成测试。然而,在这个简单的示例中,我们可以通过发送HTTP GET请求到/books/by-author?author=某作者名来手动测试它。

注意事项

  • 安全性: 在生产环境中,你可能需要确保这个查询功能是安全的,例如通过验证用户身份、限制可查询的作者等。
  • 分页和排序: 对于大型数据集,你可能希望实现分页和排序功能,以提高性能和用户体验。Spring Data JPA提供了强大的分页和排序支持。
  • 错误处理: 你应该在代码中处理潜在的错误情况,如作者名不存在时返回适当的HTTP状态码和消息。
  • 文档: 不要忘记更新你的API文档,以反映新的查询功能。
HTTP请求类型数据操作类型
GET查询
POST添加
PUT修改
DELETE删除

11.1.3 改造编辑功能

  改造编辑功能主要分为三步。
改造编辑功能(在Java中,特别是在Web应用程序的上下文中,如使用Spring Boot和Spring MVC)通常意味着更新或优化用于修改现有资源(如数据库中的记录)的API端点。以下是一个简化的例子,说明如何改造一个基于Spring Boot的RESTful API中的编辑功能。

假设的Book资源
首先,我们假设有一个Book实体类和一个BookRepository接口(使用Spring Data JPA),这些已经在之前的示例中定义过了。

原始的REST Controller
我们可能已经有了一个基础的REST Controller来处理对Book资源的请求,包括获取所有书籍的端点,但还没有编辑书籍的端点。

// BookController.java  
@RestController  
@RequestMapping("/books")  
public class BookController {  
  
    @Autowired  
    private BookRepository bookRepository;  
  
    // 获取所有书籍  
    @GetMapping  
    public List<Book> getAllBooks() {  
        return bookRepository.findAll();  
    }  
  
    // ... 其他端点  
  
    // 这里将添加编辑书籍的端点  
}

改造编辑功能
为了改造编辑功能,我们需要添加一个端点来接收书籍的更新信息,并更新数据库中的相应记录。这通常通过PUT或PATCH请求来实现,但PUT请求更常用于完全替换资源,而PATCH请求用于部分更新资源。在这个例子中,我们将使用PUT请求。

首先,我们需要一个方法来处理书籍的更新请求。这通常意味着我们需要接收一个书籍对象作为请求体,并使用其ID来找到并更新数据库中的记录。

// 在BookController.java中添加  
  
// 编辑书籍  
@PutMapping("/{id}")  
public ResponseEntity<Book> updateBook(@PathVariable Long id, @RequestBody Book bookDetails) {  
    // 检查传入的ID是否与书籍对象的ID匹配(可选,取决于你的业务逻辑)  
    if (!id.equals(bookDetails.getId())) {  
        return ResponseEntity.badRequest().body(null); // 或者返回一个错误消息  
    }  
  
    // 查找书籍并更新  
    Optional<Book> bookOptional = bookRepository.findById(id);  
    if (bookOptional.isPresent()) {  
        Book bookToUpdate = bookOptional.get();  
        // 更新书籍的属性(这里假设我们更新所有属性)  
        bookToUpdate.setTitle(bookDetails.getTitle());  
        bookToUpdate.setAuthor(bookDetails.getAuthor());  
        // ... 更新其他属性  
  
        // 保存更新  
        bookRepository.save(bookToUpdate);  
        return ResponseEntity.ok(bookToUpdate);  
    } else {  
        return ResponseEntity.notFound().build();  
    }  
}

注意事项

  • 安全性: 确保只有授权用户才能更新书籍。你可能需要实现身份验证和授权机制。
  • 验证: 对传入的书籍对象进行验证,以确保其包含有效且必要的数据。
  • 错误处理: 在更新过程中可能会遇到各种错误(如数据库错误、验证错误等),确保你的代码能够妥善处理这些错误,并向客户端返回适当的HTTP状态码和消息。
  • 部分更新: 如果你想要支持部分更新(即只更新书籍的某些属性),你可能需要使用PATCH请求,并在你的代码中相应地处理请求体中的部分数据。
  • 文档: 更新你的API文档,以反映新的编辑功能。

Spring 表单标签及数据校验

  通过前面的知识,已经完成了对用户模块基本操作的改造,其中的主要工作都集中在后台,接下来对前端页面进行一些优化和扩展。

11.2.1 Spring 表单标签

  在以往开发JSP项目时,通常会使用EL表达式和JSTL标签来完成页面视图的开发工作,其实,Spring也提供了自己的一套表单标签库。通过使用Spring表单标签,可以很容易地模型数据中的表单对象绑定到HTML表单元素中。

标签名称说明
<fm:form/>渲染表单元素
<fm:input/>文本框组件标签
<fm:password/>密码框组件标签
<fm:hidden/>隐藏组件标签
<fm:textarea/>多行文本框组件标签
<fm:radiobutton/>单选框组件标签
<fm:checkbox/>复选框组件标签
<fm:select/>下拉列表组件标签
<fm:error/>显示表单数据校验对应的错误信息

以上标签都用于绑定表单对象的属性值,基本上这些标签都拥有以下属性。

  • path:属性路径,表示表单对象属性,如account、realName等。
  • cssClass:表单组件对应的css样式类名。
  • cssErrorClass:当提交表单后报错(服务端错误),采用css样式类。
  • cssStyle:表单组件对应的css样式。
  • htmlEscape:绑定的表单属性值是否要对HTML特殊字符进行转换,默认为true。

在Spring框架中,处理表单通常涉及几个关键组件:Spring MVC控制器、表单模型对象(通常是一个Java类,其属性映射到表单的输入字段)、Spring的表单标签库(用于在JSP、Thymeleaf等视图中创建表单)。这里,我将展示一个简化的例子,说明如何在Spring MVC应用中处理表单,并展示如何使用Java代码来定义模型对象以及控制器方法。

步骤 1: 定义模型类
首先,定义一个Java类,其属性将映射到表单的输入字段。

public class User {  
    private String name;  
    private String email;  
  
    // 构造器、getter和setter省略  
  
    public User() {  
    }  
  
    public String getName() {  
        return name;  
    }  
  
    public void setName(String name) {  
        this.name = name;  
    }  
  
    public String getEmail() {  
        return email;  
    }  
  
    public void setEmail(String email) {  
        this.email = email;  
    }  
}

步骤 2: 创建Spring MVC控制器
然后,创建一个Spring MVC控制器来处理表单的提交。

import org.springframework.stereotype.Controller;  
import org.springframework.ui.Model;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.PostMapping;  
  
@Controller  
public class UserController {  
  
    @GetMapping("/userForm")  
    public String showForm(Model model) {  
        // 在模型中添加一个空的User对象,用于表单绑定  
        model.addAttribute("user", new User());  
        return "userForm"; // 返回视图名,假设是JSP页面  
    }  
  
    @PostMapping("/submitUser")  
    public String submitForm(User user) {  
        // 处理表单提交,例如保存到数据库  
        // 这里只是简单地返回成功页面  
        return "userSubmitted"; // 返回视图名,表示提交成功  
    }  
}

步骤 3: 创建表单视图
假设你使用JSP作为视图技术,你需要创建两个JSP文件:userForm.jsp 和 userSubmitted.jsp。

userForm.jsp 示例(使用Spring表单标签):

<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>  
<html>  
<body>  
    <form:form action="submitUser" modelAttribute="user" method="post">  
        <label for="name">Name:</label>  
        <form:input path="name" id="name" />  
  
        <label for="email">Email:</label>  
        <form:input path="email" id="email" type="email" />  
  
        <input type="submit" value="Submit" />  
    </form:form>  
</body>  
</html>

注意:为了使用Spring表单标签,你需要在JSP页面顶部添加<%@ taglib … %>声明。

步骤 4: 配置Spring MVC
确保你的Spring MVC应用已经正确配置,包括视图解析器、消息转换器等。这通常通过Java配置或XML配置来完成。

11.2.2 数据校验

使用Spring MVC框架时,有两种常用的方式校验输入的数据。

  • 利用Spring 框架自带的验证框架。
  • 利用JSR 303实现。
JSR 303约束
约束说明
@Null被注释的元素必须为null
@NotNull被注释的元素必须不为null
@AssertTrue被注释的元素必须为true
@AssertFalse被注释的元素必须为false
@Min(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DeclimalMax(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max,min)被注释的元素的大小必须在指定的范围内
@Digits(integer,fraction)被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past被注释的元素必须是一个过去的日期
@Future被注释的元素必须是一个将来的日期
@Pattern(value)被注释的元素必须符合指定的正则表达式

11.3 使用Spring MVC 实现文件上传

在企业开发中,文件上传是必不可少的功能,本节介绍Spring MVC框架的文件上传功能。

11.3.1 单文件上传

在Java中实现单文件上传,通常会用到Servlet API结合一些库,如Apache Commons FileUpload,来简化文件上传的处理。以下是一个简单的示例,展示了如何使用Servlet和Apache Commons FileUpload库来实现单文件上传。

首先,确保你的项目中已经包含了Apache Commons FileUpload和Apache Commons IO的依赖。如果你使用Maven,可以在pom.xml中添加如下依赖:

<!-- Apache Commons FileUpload -->  
<dependency>  
    <groupId>commons-fileupload</groupId>  
    <artifactId>commons-fileupload</artifactId>  
    <version>1.4</version>  
</dependency>  
<!-- Apache Commons IO -->  
<dependency>  
    <groupId>commons-io</groupId>  
    <artifactId>commons-io</artifactId>  
    <version>2.8.0</version>  
</dependency>

接下来,创建一个Servlet来处理文件上传:

import org.apache.commons.fileupload.FileItem;  
import org.apache.commons.fileupload.disk.DiskFileItemFactory;  
import org.apache.commons.fileupload.servlet.ServletFileUpload;  
  
import javax.servlet.ServletException;  
import javax.servlet.annotation.WebServlet;  
import javax.servlet.http.HttpServlet;  
import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletResponse;  
import java.io.File;  
import java.io.IOException;  
import java.util.List;  
  
@WebServlet("/upload")  
public class FileUploadServlet extends HttpServlet {  
  
    private static final String UPLOAD_DIRECTORY = "/uploads";  
  
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {  
        // 检查请求是否包含文件上传  
        if (!ServletFileUpload.isMultipartContent(request)) {  
            throw new ServletException("Content type is not multipart/form-data");  
        }  
  
        // 配置上传参数  
        DiskFileItemFactory factory = new DiskFileItemFactory();  
        ServletFileUpload upload = new ServletFileUpload(factory);  
  
        try {  
            // 解析请求的内容提取文件数据  
            List<FileItem> formItems = upload.parseRequest(request);  
  
            if (formItems != null && formItems.size() > 0) {  
                // 迭代表单数据  
                for (FileItem item : formItems) {  
                    // 处理不在表单中的字段  
                    if (!item.isFormField()) {  
                        String fileName = new File(item.getName()).getName();  
                        String filePath = getServletContext().getRealPath("") + File.separator + UPLOAD_DIRECTORY + File.separator + fileName;  
                        File storeFile = new File(filePath);  
  
                        // 在控制台输出文件的上传路径  
                        System.out.println(filePath);  
  
                        // 保存文件到硬盘  
                        item.write(storeFile);  
                        request.setAttribute("message", "文件上传成功!");  
                    }  
                }  
            }  
        } catch (Exception ex) {  
            request.setAttribute("message", "文件上传失败!");  
        }  
  
        // 跳转到 message.jsp  
        getServletContext().getRequestDispatcher("/message.jsp").forward(request, response);  
    }  
}

注意

  1. @WebServlet(“/upload”) 注解用于将Servlet映射到/upload URL。
  2. UPLOAD_DIRECTORY 是你希望保存上传文件的目录,这里假设是/uploads,它应该位于你的Web应用的根目录下。
  3. ServletFileUpload.isMultipartContent(request) 用于检查请求是否包含文件上传。
  4. DiskFileItemFactory 和 ServletFileUpload 用于处理文件上传。
  5. 遍历 formItems 列表,检查每个 FileItem 是否是文件(非表单字段)。
  6. 使用 item.write(storeFile) 将文件写入到指定的路径。

最后,你需要在你的Web应用中创建一个HTML表单来上传文件,并确保表单的enctype属性设置为multipart/form-data,如下所示:

<form action="upload" method="post" enctype="multipart/form-data">  
    <input type="file" name="file" />  
    <input type="submit" value="Upload" />  
</form>

确保表单的action属性指向你的Servlet URL(在这个例子中是/upload)。

11.3.2 多文件上传

在Java中实现多文件上传,与单文件上传类似,但你需要处理多个FileItem对象,这些对象代表了上传的文件。以下是一个使用Apache Commons FileUpload库和Servlet API实现的多文件上传的示例代码。

首先,确保你的项目中已经包含了Apache Commons FileUpload和Apache Commons IO的依赖,就像我们在单文件上传示例中所做的那样。

然后,创建一个Servlet来处理多文件上传:

import org.apache.commons.fileupload.FileItem;  
import org.apache.commons.fileupload.disk.DiskFileItemFactory;  
import org.apache.commons.fileupload.servlet.ServletFileUpload;  
  
import javax.servlet.ServletException;  
import javax.servlet.annotation.WebServlet;  
import javax.servlet.http.HttpServlet;  
import javax.servlet.http.HttpServletRequest;  
import javax.servlet.http.HttpServletResponse;  
import java.io.File;  
import java.io.IOException;  
import java.util.List;  
  
@WebServlet("/uploadMultiple")  
public class UploadMultipleServlet extends HttpServlet {  
  
    private static final String UPLOAD_DIRECTORY = "/uploads";  
  
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {  
        // 检查请求是否包含文件上传  
        if (!ServletFileUpload.isMultipartContent(request)) {  
            throw new ServletException("Content type is not multipart/form-data");  
        }  
  
        // 配置上传参数  
        DiskFileItemFactory factory = new DiskFileItemFactory();  
        ServletFileUpload upload = new ServletFileUpload(factory);  
  
        try {  
            // 解析请求的内容提取文件数据  
            List<FileItem> formItems = upload.parseRequest(request);  
  
            if (formItems != null && !formItems.isEmpty()) {  
                // 迭代表单数据  
                for (FileItem item : formItems) {  
                    // 处理不在表单中的字段  
                    if (!item.isFormField()) {  
                        String fileName = new File(item.getName()).getName();  
                        String filePath = getServletContext().getRealPath("") + File.separator + UPLOAD_DIRECTORY + File.separator + fileName;  
                        File storeFile = new File(filePath);  
  
                        // 在控制台输出文件的上传路径  
                        System.out.println(filePath);  
  
                        // 保存文件到硬盘  
                        item.write(storeFile);  
                    }  
                }  
  
                // 文件上传成功,可以重定向到另一个页面或发送消息  
                request.setAttribute("message", "文件上传成功!");  
            }  
        } catch (Exception ex) {  
            request.setAttribute("message", "文件上传失败: " + ex.getMessage());  
        }  
  
        // 跳转到 message.jsp 或其他页面来显示消息  
        getServletContext().getRequestDispatcher("/message.jsp").forward(request, response);  
    }  
}

在这个例子中,UploadMultipleServlet Servlet监听/uploadMultiple URL。当提交包含多个文件的表单时,它会检查请求是否为多部分请求(即包含文件上传),然后解析请求并遍历所有的FileItem对象。对于每个FileItem对象,如果它不是表单字段(即它是文件),则提取文件名、创建文件路径,并将文件写入到指定的位置。

请注意,你应该在你的Web应用的根目录下创建/uploads目录(或你选择的任何目录),以便Servlet能够保存上传的文件。

HTML表单示例,用于上传多个文件:

<form action="uploadMultiple" method="post" enctype="multipart/form-data">  
    <input type="file" name="file" multiple="multiple" />  
    <input type="submit" value="Upload" />  
</form>

注意:multiple="multiple"属性,它允许用户选择多个文件。这样,当表单提交时,所有选中的文件都将作为多部分请求的一部分发送到服务器。

  • 36
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值