java错题集

Table of Contents

接收前端参数的Params类中有一个字段

/**
 *  启用时间
 */
private LocalDateTime startTime;
  • 报错如下:
"HttpMessageNotReadableException
JSON parse error: Cannot deserialize value of type `java.time.LocalDateTime` from String "2025-01-05 00:00:00": Failed to deserialize java.time.LocalDateTime: "
  • 如何解决

增加@JsonFormat,可以解决

/**
 *  启用时间
 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime startTime;
  • 原因:

java.time.LocalDateTime 默认的日期时间格式是“yyyy-MM-ddTHH:mm:ss”,而“2025-01-05 00:00:00”这种格式在日期和时间之间用空格分隔,与默认格式不符,所以在解析时会抛出异常。

最终解决方法

  • 全局配置日期格式
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * 关于Java8中localDateTime去掉中间的T
 *
 */
@Configuration
public class LocalDateTimeSerializerConfig {
    @Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}")
    private String pattern;
    @Bean
    public LocalDateTimeSerializer localDateTimeDeserializer() {
        return new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(pattern));
    }
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jsonMBCForLocalDateTime() {
        return builder -> builder.serializerByType(LocalDateTime.class, localDateTimeDeserializer());
    }
}
  • 在实体类字段上使用注解
    • 在包含 LocalDateTime 字段的实体类中,为该字段添加 @DateTimeFormat 和 @JsonFormat 注解,指定日期时间的格式。
    • @DateTimeFormat 注解主要用于处理前端传来的日期格式
    • @JsonFormat 注解主要用于处理返回给前端的日期格式以及实体类属性的序列化和反序列化。
import org.springframework.format.annotation.DateTimeFormat;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDateTime;

public class YourEntity {
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime yourDateTimeField;
}

long类型字段的值过大,返回给前端导致精度丢失

解决办法

  • 创建一个全局配置类,返回数据给前端时,把long类型转换为String类型
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;

@Configuration
public class LongToStringSerializerConfig {

    @Bean
    public LongToStringSerializer longToStringSerializer() {
        return new LongToStringSerializer();
    }

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jsonMBCForLong() {
        return builder -> builder.serializerByType(Long.class, longToStringSerializer());
    }

    static class LongToStringSerializer extends JsonSerializer<Long> {

        @Override
        public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            gen.writeString(Long.toString(value));
        }
    }
}

全局异常捕捉

import com.dpb.chucai.infrastructure.util.RequestUtil;
import com.dpb.chucai.infrastructure.util.api.CommonResult;
import com.dpb.chucai.infrastructure.util.api.ResultCode;
import com.dpb.chucai.infrastructure.util.dto.UserInfoDTO;
import com.dpb.chucai.service.log.exception.MyExceptionService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.method.HandlerMethod;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;


@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
    @Autowired
    private MyExceptionService myExceptionService;

    @Resource
    private RequestUtil requestUtil;


    /**
     * Exception
     */
    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public CommonResult handleException(HttpServletRequest request, HandlerMethod handlerMethod, Exception e) {
        log.error("handleException: {}", e.getMessage());

        return CommonResult.failed(ResultCode.FAILED, this.fix(request, handlerMethod, e));
    }

    /**
     * 处理所有接口数据验证异常
     * MethodArgumentNotValidException
     */
    @ResponseBody
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public CommonResult handleMethodArgumentNotValidException(
            HttpServletRequest request,
            HandlerMethod handlerMethod,
            MethodArgumentNotValidException e) {

        log.error("handleMethodArgumentNotValidException: {}", e.getMessage());
        return CommonResult.validateFailedData(
                "接口数据验证异常",
                e.getBindingResult().getFieldErrors().stream()
                        .map(x -> {
                            Map<String, Object> m = new HashMap<>();
                            m.put("field", x.getField());
                            m.put("value", x.getRejectedValue());
                            m.put("msg", x.getDefaultMessage());
                            return m;
                        }).collect(Collectors.toList())
        );
    }

    /**
     * RuntimeException
     */
    @ResponseBody
    @ExceptionHandler(value = RuntimeException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public CommonResult handleRuntimeException(HttpServletRequest request, HandlerMethod handlerMethod, Exception e) {
        log.error("handleRuntimeException: {}", e.getMessage());

        return CommonResult.failed(ResultCode.FAILED, this.fix(request, handlerMethod, e));
    }

    /**
     * Assert 异常:IllegalArgumentException
     */
    @ResponseBody
    @ExceptionHandler(value = IllegalArgumentException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public CommonResult handleIllegalArgumentException(HttpServletRequest request, HandlerMethod handlerMethod, Exception e) {
        String errMsg = e.getMessage();

        log.error("handleIllegalArgumentException: {}", errMsg);

        return CommonResult.validateFailedData(errMsg, this.fix(request, handlerMethod, e));
    }

    private Long fix(HttpServletRequest request, HandlerMethod handlerMethod, Exception e){
        UserInfoDTO user;
        try {
            user = requestUtil.findByAccount();
        } catch (Exception exception) {
            user = null;
        }
        return myExceptionService.fix(requestUtil.fixGetUrl(), user, request, e, handlerMethod);
    }


}

请求体body数据缓存

import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class RequestWrapperFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        filterChain.doFilter(new ContentCachingRequestWrapper(request), response);
    }
}

返回体数据处理

import com.alibaba.fastjson.JSON;
import com.dpb.chucai.infrastructure.util.api.CommonResult;
import lombok.extern.slf4j.Slf4j;
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.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.LinkedHashMap;

@Slf4j
@ControllerAdvice
public class MyResponseRuseltHandler implements ResponseBodyAdvice<Object> {

    @Resource
    private HttpServletRequest request;

  // 白名单
    private static final String[] WHITE_URL_LIST = new String[]{
            "xxx",
            "yyy"
    };

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        // 决定是否对当前的响应进行处理,可以根据需要自定义条件
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        String url = this.fixUrl();
        if(whetherInWhiteList(url)){
            return body;
        }

        if (returnType.getParameterType().equals(String.class)) {
            return JSON.toJSONString(CommonResult.success(body));
        }

        if(body instanceof String){
            String bodyStr = String.valueOf(body);
            if(bodyStr.contains("code")
                    && bodyStr.contains("message")
                    && bodyStr.contains("data") ){
                return body;
            }
            return JSON.toJSONString(CommonResult.success(body));
        }else if(body instanceof CommonResult){
            return body;
        }else if(body instanceof LinkedHashMap){
            return body;
        }

        return CommonResult.success(body);
    }

    private boolean whetherInWhiteList(String url){
        String[] list = WHITE_URL_LIST;
        for (String s:list){
            if(s.equals(url)){
                return true;
            }
        }
        return false;
    }

    private String fixUrl(){
        return request.getRequestURI();
    }

}

CROS跨域

  • springBoot中不要配置CROS
  • 由nginx统一集中配置
  • 例子:
  @Configuration
  public class WebMvcConfig  implements WebMvcConfigurer {

      @Override
      public void addCorsMappings(CorsRegistry registry) {
          registry.addMapping("/**")
                  .allowedOrigins("*")
                  .allowCredentials(true)
                  .allowedHeaders("*")
                  .allowedMethods("GET", "POST", "DELETE", "PUT","OPTIONS")
                  .maxAge(3600);
      }
    ...
}	  
  • .allowCredentials(true)
    • 当 Access-Control-Allow-Credentials 设置为 true 时,Access-Control-Allow-Origin不能设置为 *,必须是具体的域名。
    • 这会导致nginx中配置的 add_header 'Access-Control-Allow-Origin' '*'; 无效

Date: 2025-01-18 Sat 14:33