由于spring中没有提供类似于@RequestParam注解,对单个参数的post请求数据进行绑定的注解,所以当需要根据id删除记录的时候,可以使用以下几种写法

  • 使用 Map 接收 post 请求参数:
@PostMapping("/delete")
public ApiResponse delete(@RequestBody Map<String,String> params){
    Long id = params.get("id");
    if (id == null) {
        throw AppException("参数错误");
    }
    service.deleteById(id);
    return ApiResponse.createBySuccess();
}
  • 使用 String 类型接收 post 请求参数
@PostMapping("/delete")
public ApiResponse delete(@RequestBody String params){
    JSONObject paramsJSONObject = JSONObject.parseObject(params);
    Long id = paramsJSONObject.getLong("id");
    if (id == null) {
        throw AppException("参数错误");
    }
    service.deleteById(id);
    return ApiResponse.createBySuccess();
}
  • 从 request 中获取参数
@PostMapping("/delete")
public ApiResponse delete(HttpServletRequest request) {
    String body = getRequestBody(request);
    JSONObject paramsJSONObject = JSONObject.parseObjec(body);
    Long id = paramsJSONObject.getLong("id");
    if (id == null) {
        throw AppException("参数错误");
    }
    service.deleteById(id);
    return ApiResponse.createBySuccess();
}

/**
 * 从 request 中获取 body
 */
private String getRequestBody(HttpServletRequest servletRequest) {
    StringBuilder stringBuilder = new StringBuilder();
    try {
        BufferedReader reader = servletRequest.getReader();
        char[] buf = new char[1024];
        int length;
        while ((length = reader.read(buf)) != -1) {
            stringBuilder.append(buf, 0, length);
        }
    } catch (IOException e) {
        log.error("读取流异常", e);
        throw new AppException(SystemError.PARSE_PARAMS_FAIL);
    }
    return stringBuilder.toString();
}
  • 使用 java bean 接收请求参数
@PostMapping("/delete")
public ApiResponse delete(@RequestBody IdBean idBean) {
    if (idBean == null || idBean.getId() == null) {
        throw AppException("参数错误");
    }
    service.deleteById(id);
    return ApiResponse.createBySuccess();
}
@Data
public class IdBean {
    private Long id;
}

上面几种方式虽然都能满足需求,但是前三种方式,由于没有明确的参数声明,在以后代码维护的时候,需要看代码/文档才知道接口需要接收的参数有哪些,增加维护难度。最后一种方法虽说能直接从 bean 属性中知道接口接收的参数。但是需要创建一个只有一个字段的 java bean,但是总感觉没那么优雅,那么有没有更加优雅的实现方式呢?

其实很简单,可以模仿@RequestParam自定义一个注解@CustomParam,实现与@RequestParam同样的功能,只不过@RequestParam注解是从请求路径上获取参数,而我们自定义的@CustomParam注解则是从 request body 中获取参数

  1. 第一步,定义注解
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomParam {

    /**
     * Alias for {@link #name}.
     */
    @AliasFor("name")
    String value() default "";

    /**
     * The name of the request parameter to bind to.
     *
     * @since 4.2
     */
    @AliasFor("value")
    String name() default "";

    /**
     * Whether the parameter is required.
     * <p>Default is {@code true}, leading to an exception thrown in case
     * of the parameter missing in the request. Switch this to {@code false}
     * if you prefer a {@code null} in case of the parameter missing.
     * <p>Alternatively, provide a {@link #defaultValue() defaultValue},
     * which implicitly sets this flag to {@code false}.
     */
    boolean required() default true;

    /**
     * The default value to use as a fallback when the request parameter value
     * is not provided or empty. Supplying a default value implicitly sets
     * {@link #required()} to false.
     */
    String defaultValue() default ValueConstants.DEFAULT_NONE;
}
  1. 第二步,编写参数解析器
@Slf4j
public class CustomMethodArgumentResolver implements HandlerMethodArgumentResolver {

    private static final String POST = "post";
    private static final String APPLICATION_JSON = "application/json";

    /**
     * 判断是否需要处理该参数
     *
     * @param parameter the method parameter to check
     * @return {@code true} if this resolver supports the supplied parameter;
     * {@code false} otherwise
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        // 只处理带有@CustomParam注解的参数
        return parameter.hasParameterAnnotation(CustomParam.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
        String contentType = Objects.requireNonNull(servletRequest).getContentType();

        if (contentType == null || !contentType.contains(APPLICATION_JSON)) {
            log.error("解析参数异常,contentType需为{}", APPLICATION_JSON);
            throw new RuntimeException("解析参数异常,contentType需为application/json");
        }

        if (!POST.equalsIgnoreCase(servletRequest.getMethod())) {
            log.error("解析参数异常,请求类型必须为post");
            throw new RuntimeException("解析参数异常,请求类型必须为post");
        }
        return bindRequestParams(parameter, servletRequest);
    }

    private Object bindRequestParams(MethodParameter parameter, HttpServletRequest servletRequest) {
        CustomParam customParam = parameter.getParameterAnnotation(CustomParam.class);

        Class<?> parameterType = parameter.getParameterType();
        String requestBody = getRequestBody(servletRequest);
        Map<String, Object> params = ObjectMapperUtil.str2Obj(requestBody, new TypeReference<Map<String, Object>>() {
        });

        params = MapUtils.isEmpty(params) ? new HashMap<>(0) : params;
        String name = StringUtils.isBlank(customParam.value()) ? parameter.getParameterName() : customParam.value();
        Object value = params.get(name);

        if (parameterType.equals(String.class)) {
            if (StringUtils.isBlank((String) value)) {
                log.error("参数解析异常,String类型参数不能为空");
                throw new RuntimeException("参数解析异常,String类型参数不能为空");
            }
        }

        if (customParam.required()) {
            if (value == null) {
                log.error("参数解析异常,require=true,值不能为空");
                throw new RuntimeException("参数解析异常,require=true,值不能为空");
            }
        } else {
            if (customParam.defaultValue().equals(ValueConstants.DEFAULT_NONE)) {
                log.error("参数解析异常,require=false,必须指定默认值");
                throw new RuntimeException("参数解析异常,require=false,必须指定默认值");
            }
            if (value == null) {
                value = customParam.defaultValue();
            }
        }

        return ConvertUtils.convert(value, parameterType);
    }

    /**
     * 获取请求body
     *
     * @param servletRequest request
     * @return 请求body
     */
    private String getRequestBody(HttpServletRequest servletRequest) {
        StringBuilder stringBuilder = new StringBuilder();
        try {
            BufferedReader reader = servletRequest.getReader();
            char[] buf = new char[1024];
            int length;
            while ((length = reader.read(buf)) != -1) {
                stringBuilder.append(buf, 0, length);
            }
        } catch (IOException e) {
            log.error("读取流异常", e);
            throw new RuntimeException("读取流异常");
        }
        return stringBuilder.toString();
    }
}
  1. 第三部,注册参数解析器
@Configuration
public class CustomParamResolverConfigurer implements WebMvcConfigurer {

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new CustomMethodArgumentResolver());
        WebMvcConfigurer.super.addArgumentResolvers(resolvers);
    }
}

这样,一个自定义注解就写好啦,在接收单个参数的 post 请求的时候,只要像 @RequestParam 一样使用就好啦

@PostMapping("/delete")
public ApiResponse delete(@CustomParam Long id) {
    service.deleteById(id);
    return ApiResponse.createBySuccess();
}

最后,需要源码的同学,可以关注公众号:huangxy,发送源码到公众号即可获取本文源码
在这里插入图片描述

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐