IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 从零开始 Spring Boot 11:返回数据 -> 正文阅读

[Java知识库]从零开始 Spring Boot 11:返回数据

从零开始 Spring Boot 11:返回数据

spring boot

图源:简书 (jianshu.com)

在之前文章中,已经展示了如何在前后端分离的Web应用中封装并返回统一格式的返回值,但之前展示的返回值封装的相当简单,只能用于简单示例,今天对返回值进行进一步封装和重构。

本篇文章会在从零开始 Spring Boot 9:Shiro - 魔芋红茶’s blog (icexmoon.cn)中最终代码的基础上进行修改。你可以从下面的链接获取对应的示例代码:

为了说明之前设计的一些缺陷,这里为这个示例代码添加上Swagger文档支持,具体方法见从零开始 Spring Boot 8:Swagger - 魔芋红茶’s blog (icexmoon.cn),这里不再详细说明。

因为这个示例程序使用Shiro做权限验证,所以需要将Swagger相关路径添加为例外,不经过Shiro验证:

        // 配置不会被拦截的链接 顺序判断
        map.put("/swagger-ui/**", "anon");
        map.put("/swagger-resources/**", "anon");
        map.put("/v3/api-docs/**", "anon");
        map.put("/static/**", "anon");

关于Shiro可以阅读从零开始 Spring Boot 9:Shiro - 魔芋红茶’s blog (icexmoon.cn)

现在查看Swagger你就会发现一个问题:

image-20220702203345411

所有接口的返回值都是String类型,所以无法通过Swagger文档查看具体的返回值格式,也就谈不上前后端分离开发时让前端通过Swagger文档来协同开发。

之所以这样,是因为我们在Controller中返回的是String类型:

    @RequiresRoles(value = {"guest", "manager"}, logical = Logical.OR)
    @GetMapping("/book")
    @ApiOperation("获取所有图书列表")
    public String listAllBooks() {
        Result result = new Result();
        List<Book> books = bookService.list();
        result.setData(books);
        return result.toString();
    }

Swagger正是通过展示Controller中方法的返回值类型来阐述具体接口的返回值格式。

所以,我们需要让接口能直接返回具体类型而非String

但是这样会产生另一个问题,如果返回的是具体的业务类型,那如何将其封装为统一的返回值格式?

其实可以利用Spring的拦截器来实现这点。

处理返回值

我们先看新封装的统一返回值类型:

package cn.icexmoon.demo.books.system.result;

import com.alibaba.fastjson.JSONObject;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@Data
public class Result {
    public enum ErrorCode {
        SUCCESS(200, "成功"),
        DB_NO_DATA(500, "数据库数据缺失"),
        PARAM_CHECK(400, "没有通过参数校验"),
        LOGIN_ERROR(400, "登录失败"),

        NO_PRIORITY(401, "缺少操作的相关权限"),
        REQUEST_PARSE(402, "HTTP请求解析错误"),
        TIME_PARSE(403, "时间解析出错"),
        NEED_LOGIN(404, "需要登录"),
        DEFAULT_ERROR(501, "默认错误"),
        NETWORK_ERROR(502, "网络错误");

        private Integer code;
        private String desc;

        private ErrorCode(Integer code, String desc) {
            this.code = code;
            this.desc = desc;
        }

        public Integer getCode() {
            return code;
        }

        public String getDesc() {
            return desc;
        }

        @Override
        public String toString() {
            return this.code.toString();
        }
    }

    @ApiModelProperty("调用成功/失败")
    private boolean success = true;
    @ApiModelProperty("调用失败后的提示信息")
    private String msg = "";
    @ApiModelProperty("接口返回的数据")
    private Object data = null;
    @ApiModelProperty("错误码")
    private ErrorCode code = ErrorCode.SUCCESS;

    private static final Result SUCCESS_RESULT = new Result();

    /**
     * 获取成功Result
     *
     * @return
     */
    public static Result success() {
        return SUCCESS_RESULT;
    }

    /**
     * 获取成功的Result
     *
     * @param data
     * @return
     */
    public static Result success(Object data) {
        Result result = new Result();
        result.setData(data);
        return result;
    }

    /**
     * 获取失败Result
     *
     * @param errorCode 错误码
     * @param msg       错误信息
     * @return
     */
    public static Result fail(ErrorCode errorCode, String msg) {
        Result result = new Result();
        result.setSuccess(false);
        result.setCode(errorCode);
        result.setData(null);
        result.setMsg(msg);
        return result;
    }

    /**
     * 获取失败Result
     *
     * @param exp 产生错误的异常
     * @return
     */
    public static Result fail(ResultException exp) {
        Result result = new Result();
        result.setSuccess(false);
        result.setCode(exp.getCode());
        result.setData(null);
        result.setMsg(exp.getMessage());
        return result;
    }

    @Override
    public String toString() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("success", success);
        jsonObject.put("msg", msg);
        jsonObject.put("data", data);
        jsonObject.put("code", code.getCode());
        return jsonObject.toString();
    }
}

这个新的Result类型除了包含基本的datasuccess等,更便利的是使用内嵌的枚举封装了系统错误类型,这样在编程的时候就无需使用类属性或者字符串来指定错误,并且枚举自带desc属性可以对错误进行说明。

就像前边说的,因为之后Controller层的方法会直接返回具体业务类型,所以我们需要添加拦截器来对具体类型统一封装:

package cn.icexmoon.demo.books.system;

import cn.icexmoon.demo.books.system.result.IResult;
import cn.icexmoon.demo.books.system.result.Result;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

@RestControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }

    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        if (o instanceof IResult) {
            return Result.success(o);
        } else if (o == null) {
            return Result.success(null);
        } else {
            ;
        }
        return o;
    }
}

这里拦截了所有HTTP请求的返回值,这其中包含一些静态资源,如果对其进行封装显然会造成系统错误。所以这里我使用了一个标记接口IResult来识别是否是Controller层返回的需要封装的对象,如果是,再进行封装。

package cn.icexmoon.demo.books.system.result;


/**
 * 标记需要被包装为Result的返回类型
 */
public interface IResult {
}

现在我们修改Controller中的接口方法,让其返回具体类型:

package cn.icexmoon.demo.books.book.controller;


import cn.icexmoon.demo.books.book.entity.Book;
import cn.icexmoon.demo.books.book.service.IBookService;
import cn.icexmoon.demo.books.system.result.IResultArrayList;
import cn.icexmoon.demo.books.system.result.Result;
import cn.icexmoon.demo.books.user.entity.User;
import cn.icexmoon.demo.books.user.service.IUserService;
import io.swagger.annotations.ApiOperation;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * <p>
 * 前端控制器
 * </p>
 *
 * @author icexmoon
 * @since 2022-05-06
 */
@RestController
public class BookController {
    @Autowired
    private IBookService bookService;
    @Autowired
    private IUserService userService;


    @RequiresRoles(value = {"guest", "manager"}, logical = Logical.OR)
    @GetMapping("/book")
    @ApiOperation("获取所有图书列表")
    public List<Book> listAllBooks() {
        Result result = new Result();
        List<Book> books = new IResultArrayList<>();
        books.addAll(bookService.list());
        return books;
    }

    @RequiresRoles("manager")
    @PostMapping("/book/add")
    @ApiOperation("添加书籍")
    public Result addBook(@RequestBody Book book) {
        //添加图书
        Subject subject = SecurityUtils.getSubject();
        String name = (String) subject.getPrincipal();
        User user = userService.getUserByName(name);
        book.setUserId(user.getId());
        bookService.save(book);
        return Result.success();
    }
}

这里为了能封装返回的List,我定义了一个实现了IResultIResultArrayList

package cn.icexmoon.demo.books.system.result;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

public class IResultArrayList<T> extends ArrayList<T> implements IResult {
    public <E> IResultArrayList() {
        super();
    }

    public <E> IResultArrayList(List<E> items, Function<E, T> mapFunc) {
        this();
        for (E item : items) {
            add(mapFunc.apply(item));
        }
    }
}

实际上也可以在拦截器中用反射检查对象是否为List的子类型来决定是否封装。

现在再看Swagger中的类型:

image-20220702210008861

这样就可以愉快地让前端自己去看返回值格式了。

处理异常

最后还要重构下对错误的处理,定义一个统一的系统异常:

package com.sjcx.ccsp.system.result;


public class ResultException extends RuntimeException {

    private Result.ErrorCode code;

    public ResultException(Result.ErrorCode code, String msg) {
        super(msg);
        this.code = code;
    }

    public Result.ErrorCode getCode() {
        return code;
    }
}

在全局异常拦截器中拦截该类型的异常并包装成Result类型返回:

    /**
     * 拦截并处理ResultException异常
     *
     * @param exp
     * @param model
     * @return
     */
    @ExceptionHandler(ResultException.class)
    public Result doHandleResultException(ResultException exp, Model model) {
        return Result.fail(exp);
    }

现在可以将之前产生错误并返回Result的地方全部改成简单的抛出异常:

package cn.icexmoon.demo.books.system;

import cn.icexmoon.demo.books.system.result.Result;
import cn.icexmoon.demo.books.system.result.ResultException;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;


@Component
public class Login {
    /**
     * 根据用户名和密码检查身份并登录
     *
     * @param name
     * @param password
     * @return
     */
    public Result checkAndLogin(String name, String password) {
        Result result = new Result();
        if (ObjectUtils.isEmpty(name) || ObjectUtils.isEmpty(password)) {
            throw new ResultException(Result.ErrorCode.LOGIN_ERROR, "用户名或密码为空。");
        }
        //用户认证信息
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
                name,
                password
        );
        try{
            subject.login(usernamePasswordToken);
        }
        catch (UnknownAccountException e){
            throw new ResultException(Result.ErrorCode.LOGIN_ERROR, "账户不存在");
        }
        catch (AuthenticationException e){
            throw new ResultException(Result.ErrorCode.LOGIN_ERROR, "账号或密码错误");
        }
        catch (AuthorizationException e){
            throw new ResultException(Result.ErrorCode.DEFAULT_ERROR, "没有权限");
        }
        result.setData(subject.getSession().getId());
        return result;
    }
}

故意输错密码试一下:

image-20220702211439594

可以看到拦截器很好地发挥了作用,将异常封装成了统一返回值。

好了,这篇文章就到这里了,谢谢阅读。

本篇文章的最终代码见learn_spring_boot/ch11 at main · icexmoon/learn_spring_boot (github.com)

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-07-05 23:25:28  更:2022-07-05 23:26:50 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 15:35:48-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码