Spring自动添加“X-Total-Count”标头

10

我正在使用'admin-on-rest' UI来开发我的Web应用程序,但它有一个限制:

注意:jsonServer REST客户端期望API响应GET_LIST调用时包含X-Total-Count头信息。该值必须是集合中资源的总数。这使得admin-on-rest可以知道总共有多少页资源,并构建分页控件。

我通过手动将X-Total-Count头信息添加到我的返回列表的REST端点中来解决了这个问题,如下所示:response.addHeader("X-Total-Count", String.valueOf(outputList.size()));

但我想知道:在Spring中是否有一种优雅的方式可以自动添加此头信息及其正确的值,当某个端点返回JSON列表时?


如果您想为多个请求添加标头,可以使用过滤器:https://dev59.com/AWQo5IYBdhLWcg3wR9nD#16191770 - jlars62
@jlars62,我一直在考虑过滤器,但是如何正确区分返回JSON响应中单个项目的端点和返回多个项目的端点的适当方式是什么? 我只需要为最后一个添加标头。 - Dmytro Titov
@DmytroTitov 使用 PagingAndSortingRepository spring,大部分工作都已经为您完成,总数是真实的(基于查询),而不仅仅是返回列表的计数 :) 请查看我的答案。 - Michail Michailidis
3个回答

9

有的,(如果你使用的是 Spring 4.1 或以上版本)。

它被称为 ResponseBodyAdvice,它使您能够拦截调用(就在响应写入之前,访问原始的 http 响应)。

基本上,您需要实现像这样的控制器建议:

@ControllerAdvice
public class ResourceSizeAdvice implements ResponseBodyAdvice<Collection<?>> {

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        //Checks if this advice is applicable. 
        //In this case it applies to any endpoint which returns a collection.
        return Collection.class.isAssignableFrom(returnType.getParameterType()); 
    }

    @Override
    public Collection<?> beforeBodyWrite(Collection<?> body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        response.getHeaders().add("X-Total-Count", String.valueOf(body.size()));
        return body;
    }
}

你不会相信,但我刚刚几秒钟前想出了这个解决方案 :) 无论如何,谢谢你,我接受了这个答案。 - Dmytro Titov
1
我还发现了@RestControllerAdvice,我猜这更适合REST API的情况。 - Dmytro Titov
是的,如果你使用的是4.3及以上版本的话 :) - Bohdan Levchenko
1
如果您想获取存储库中元素的总数而不是请求,请检查我的答案 :) - Michail Michailidis

5
如果你不只是想得到响应中元素的总数,而是JPA方法PagingAndSortingRepository中对应实体的总数,可以像下面这样做,这对分页应用程序非常有用:)受Bohdan答案启发(https://dev59.com/4lcP5IYBdhLWcg3wd5qm#44376133)。
@ControllerAdvice
public class ResourceSizeAdvice implements ResponseBodyAdvice<Page<?>> {

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        //Checks if this advice is applicable.
        //In this case it applies to any endpoint which returns a page.
        return Page.class.isAssignableFrom(returnType.getParameterType());
    }

    @Override
    public Page<?> beforeBodyWrite(Page<?> page, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        serverHttpResponse.getHeaders().add("X-Total-Count",String.valueOf(page.getTotalElements()));
        return page;
    }

}

请确保调用默认方法的Pageable版本,这样它将返回一个Page而不是一个List

repository.findAll(new PageRequest(0,100));

如果您没有使用Repositories,则需要执行两个查询: Select * from ...Select count(*) from ... 并返回一个 Wrapper,其中包含结果列表的内容以及来自计数的总数。然后您可以更改 @ControllerAdvice 类来期望您的 Wrapper,并从中获取总数并将其放入标头中。

是的,很棒的组合!:) - Bohdan Levchenko
1
@BohdanLevchenko 謝謝!雖然我發現當你對存儲庫應用了一些過濾時,計數變得更加困難..不確定如何使其起作用- 如果我找到了方法,我會發佈的 :) - Michail Michailidis
1
@BohdanLevchenko做到了 :) - Michail Michailidis
整洁!简洁明了! - Bohdan Levchenko
如果是这种情况,那么您需要每次执行第二个查询来获取确切的计数,并将结果放入特定的包装器中,该包装器包含内容和总数。然后,您可以更改@ControllerAdvice类以期望您的“WrapperClass”,并从中获取总数并将其放入标头中。 - Michail Michailidis
显示剩余2条评论

1
因为可以从页面中获取所有元素,所以我已将标题添加到ResponseEntity。
@GetMapping(value = POSTS, headers = "Accept=application/json")
public ResponseEntity<?> getListPost(@RequestParam(required = false, defaultValue = "0") Integer page, @RequestParam(required = false, defaultValue = "25") Integer size) {
    // Create pageable
    Pageable pageable = new PageRequest(page, size);
    Page<Post> pagePost = postService.getPagePost(pageable);

    HttpHeaders headers = new HttpHeaders() {
        {
            add("Access-Control-Expose-Headers", "Content-Range");
            add("Content-Range", String.valueOf(pagePost.getTotalElements()));
        }
    };
    //        return new ResponseEntity<>(new CommonResponseBody("OK", 200, postList), HttpStatus.OK);
    return new ResponseEntity<>(new CommonResponseBody("OK", 200, new LinkedHashMap() {
        {
            put("data", pagePost.getContent());
        }
    }), headers, HttpStatus.OK);
}

getPagePost是一个服务方法,它使用Repository中的Page findAll(Pageable pageable)。

注意:如果Content-Range不起作用,请将其更改为X-Total-Count。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接