springboot 不响应字段为空_实学:Java开发自己的博客系统-第二十三篇(整理,上传数据代码Spring Boot化)...

看一下前面的代码,这一段:

@RestController
public class ArticleApi {
    @PostMapping("/api/savearticle")
    public ResponseEntity<?> saveArticle(HttpServletRequest request) {
        String title = null, content = null, catalog = null;
        Map<String, String[]> map = request.getParameterMap();
        if (map.containsKey("title")) {
            title = map.get("title")[0];
        }
        if (map.containsKey("content")) {
            content = map.get("content")[0];
        }
        if (map.containsKey("catalog")) {
            catalog = map.get("catalog")[0];
        }

        Document document = new Document("title", title)
                .append("content", content)
                .append("catalog", catalog);
        DatabaseMan.Instance().GetCollection("article").insertOne(document);

        return ResponseEntity.ok("ok");
    }
}

这个方法里面,我们用了底层的HttpServletRequest来得到浏览器上传的数据。其中用了3个if判断,做相应的参数检测。这里可以修改的更Spring Boot一点:

  1. 可以让Spring Boot拿到数据后,直接把数据封装成想要的类的实例,比如用一个数据模型,放置title、content、catalog。而不是现在比较底层的HttpServletRequest。因为这里事实上我们只需要这些数据。
  2. 可以让Spring Boot帮我们在底层就验证好数据的有效性,这样几个if都不用再写。

来,跟我做!

创建一个Model,对应文章的字段
  • 工程中,java目录下建立一个包(文件夹),名字“Model”,用来放置后面的数据类。
  • Model文件夹中,创建一个java类,名字"Article"。里面对应的代码全部:
package Model;

public class Article {
    private String title;
    private String content;
    private String catalog;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getCatalog() {
        return catalog;
    }

    public void setCatalog(String catalog) {
        this.catalog = catalog;
    }
}
api中使用Model

打开ArticleApi.java,清理成这样:

package API;

import Model.Article;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ArticleApi {
    @PostMapping("/api/savearticle")
    public ResponseEntity<?> saveArticle(@RequestBody Article article) {
        return ResponseEntity.ok("ok");
    }
}

代码清理主要使用了@RequestBody这个注解,这个会让Spring Boot默认把浏览器传过来的数据当做json数据来装配成后端对应的类。

注解修饰了一个Article类,结合刚说的理论,Article中,对应的字段名字必须能和前端送过来的json的名字能对应。这样Spring Boot接收到浏览器的数据后,才能知道哪个数据对应到对象的那个字段。

前端ajax发送数据修改

打开newarticle.js,把原来的$ajax一段有效内容用下面的替换:

    var data = {'title': title, 'content': content, 'catalog': catalog};

    $.ajax({
        type: 'post',
        async: true,
        data: JSON.stringify(data),
        url: document.location.origin + '/api/savearticle',
        dataType:'json',
        contentType: "application/json; charset=utf-8",
        success: function(data) {
            console.log("保存成功");
        },
        error: function () {
            console.log("Ajax 发生错误!");
        }
    });

我们在原来的ajax代码中,

  1. 添加了conentType字段,指定数据格式是json,编码用utf-8
  2. dataType从原来的text改成现在的json
  3. data部分原来的内容用一个data变量存储起来,然后这个data最后必须是json格式,所以用js的stringify()方法处理

这三步完成后,我们就可以调试一下。如果你都跟我一样做的话,放个断点到java的saveArticle()中,能看到参数article能带上自动绑定好的数据。如图一:

5ccba4a57b06f777704627f613074ac0.png
图一:自动绑定的数据

到此我们解决掉了第一个问题,数据自动绑定。

剩下的问题是,让Spring Boot在调用我们的方法前,自动验证数据有效性。

利用Maven添加验证库

pom.xml文件中,加入一个依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

整体pom.xml内容:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.shixue</groupId>
    <artifactId>PersonalBlog</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.mongodb/mongo-java-driver -->
        <dependency>
            <groupId>org.mongodb</groupId>
            <artifactId>mongo-java-driver</artifactId>
            <version>3.12.6</version>
        </dependency>
    </dependencies>


</project>
利用注解让Spring Boot帮我们验证

验证库添加好以后,就是考虑怎么验证?Spring Boot的方法是:在要验证的数据上添加对应的注解。

在我们原来的代码中,我们会判断title是不是null,如果不是null,在判断是不是空。这些有了验证库,一个注解就能搞定,而且你只要写注解,验证过程自动由Spring Boot在调用你的方法前就帮你做完(只有验证通过才会调用你的方法,否则就返回错误给浏览器)。

打开Article.java,修改内容成下面:

package Model;

import javax.validation.constraints.NotBlank;

public class Article {
    @NotBlank(message = "title cannot be empty")
    private String title;
    @NotBlank(message = "content cannot be empty")
    private String content;
    @NotBlank(message = "catalog cannot be empty")
    private String catalog;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getCatalog() {
        return catalog;
    }

    public void setCatalog(String catalog) {
        this.catalog = catalog;
    }
}

相比较修改前的代码,只是多了三个注解。@NotBlank的意思是“不是null,trim()后长度不是0”。跟着的括弧中的表示如果验证不通过,报告的消息内容。

这是一步,解决哪些字段需要验证。

还有一步,我们打开ArticleApi.java,在@RequestBody前,必须加上@Valid,所以整体的代码会变成:

package API;

import Model.Article;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

@RestController
public class ArticleApi {
    @PostMapping("/api/savearticle")
    public ResponseEntity<?> saveArticle(@Valid @RequestBody Article article) {
        return ResponseEntity.ok("ok");
    }
}

也就是说,参数article有了@Valid修饰后,Spring Boot就会知道,构造该实例的时候,需要先验证一下里面的字段。而需要验证的字段,都有类似@NotBlank这样的注解修饰。

测试自动验证

按我们原来的代码,测试肯定是没有问题的。客户端会先验证三个字段非空,只有通过才向服务器提交(这是多么好的全栈!)。

为了验证,我们在Article类中添加一个字段,因此整体代码会变成:

package Model;

import javax.validation.constraints.NotBlank;

public class Article {
    @NotBlank(message = "title cannot be empty")
    private String title;
    @NotBlank(message = "content cannot be empty")
    private String content;
    @NotBlank(message = "catalog cannot be empty")
    private String catalog;
    @NotBlank(message = "test cannot be empty")
    private String test;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getCatalog() {
        return catalog;
    }

    public void setCatalog(String catalog) {
        this.catalog = catalog;
    }

    public String getTest() {
        return test;
    }

    public void setTest(String test) {
        this.test = test;
    }
}

运行服务器,然后浏览器在编辑页面都输入点内容,点击保存。结果大致如图二(浏览器)、图三(服务器):

0c8ffe67ba80f29112ad2848dcec1853.png
图二:浏览器报的错误

73fb1cd8332fe6b091653d1812277860.png
图三:服务器报的错误

浏览器收到了400错误,服务器是一个异常,参数0验证出错(因为test字段为空)。说明我们的验证起了作用。

唯一的问题是,这样在浏览器(客户端)中,知道400错误,但是不知道详细的错误信息,我们给到的@NotBlank信息没有能送到客户端。下面来解决这个事情。

自定义出错信息
  • 工程中,java目录下创建一个包(文件夹),名字“Exception”
  • Exception包中,创建一个java类文件,名字“CustomExceptionHandler”,里面所有内容如下:
package Exception;

import java.util.ArrayList;
import java.util.List;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@SuppressWarnings({"unchecked","rawtypes"})
@ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler
{
    @ExceptionHandler(Exception.class)
    public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
        List<String> details = new ArrayList<>();
        details.add(ex.getLocalizedMessage());
        ErrorResponse error = new ErrorResponse("Server Error", details);
        return new ResponseEntity(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        List<String> details = new ArrayList<>();
        for(ObjectError error : ex.getBindingResult().getAllErrors()) {
            details.add(error.getDefaultMessage());
        }
        ErrorResponse error = new ErrorResponse("Validation Failed", details);
        return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
    }
}

这个类里面用了一个自定义的ErrorResponse类,马上来创建它。同样的包,创建一个ErrorResponse.java文件,里面内容如下:

package Exception;

import java.util.List;

public class ErrorResponse {
    private String message;
    private List<String> details;

    public ErrorResponse(String message, List<String> details) {
        super();
        this.message = message;
        this.details = details;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public List<String> getDetails() {
        return details;
    }

    public void setDetails(List<String> details) {
        this.details = details;
    }
}

CustomExceptionHandler继承自ResponseEntityExceptionHandler,并且重写了它的handleMethodArgumentNotValid()方法,Spring Boot如果看到这样的实例,就会在参数验证不通过的时候,调用该方法。由此我们可以做自己想做的事情,比如把自定义的出错信息回给浏览器。

要让Spring Boot看到这样的配置,除了里面的@ControllerAdvice注解外,我们还需要让Spring Boot扫描当前添加的Exception包,所以还得修改Program.java,修改后整体的代码如下:

package DefaultMain;

import Database.DatabaseMan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan({"API", "Exception", "DefaultMain"})
public class Program  {
    public static void main(String[] args) {
        DatabaseMan.Instance().Init();

        System.out.println("Spring boot application start");
        SpringApplication.run(Program.class, args);
    }
}

这样服务器端的工作就准备好了。着急的话,你可以自行先调试一把,结果是客户端还是只能看到400错误。

很正常!因为我们没有在客户端的代码中,把错误信息打印出来。跟我做!

打开newarticle.js,修改error: function() 这里的代码成为这样:

        error: function (xhr) {
            console.log("Ajax 发生错误: " + xhr.responseText);
        }

saveArticle()整体的代码是这样:

function saveArticle() {
    var title = $('#article-title').val();
    var catalog = $("#catlog-selection").val();
    var content = window.editor.getData();

    if (title == null || title.trim() == '') {
        console.log("题目不能是空的");
        return;
    }

    if (catalog == null || catalog.trim() == '') {
        console.log("分类不能是空的");
        return;
    }

    if (content == null || content.trim == '') {
        console.log("内容不能是空的");
        return;
    }

    var data = {'title': title, 'content': content, 'catalog': catalog};

    $.ajax({
        type: 'post',
        async: true,
        data: JSON.stringify(data),
        url: document.location.origin + '/api/savearticle',
        dataType:'json',
        contentType: "application/json; charset=utf-8",
        success: function(data) {
            console.log("保存成功");
        },
        error: function (xhr) {
            console.log("Ajax 发生错误: " + xhr.responseText);
        }
    });
}

好了。一切都准备完毕。运行服务器,调试。我这边客户端的结果如图四:

e1f6cf72ad2f70716a9a76e8b9ed1af7.png
图四:自定义信息给到了客户端

这一节就到这里。保存功能后面我们再重新添加进来。休息了~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值