jackson序列化时传入前端Long类型缺失精度问题

无拦截器

因为我们id是Long类型的会存在精度缺失,所以全局转换下


import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

/**
 * @author baoyuankai
 */
@Configuration
public class JacksonConfig {

    /**
     * Jackson全局转化long类型为String,解决jackson序列化时传入前端Long类型缺失精度问题
     */
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        Jackson2ObjectMapperBuilderCustomizer cunstomizer = new Jackson2ObjectMapperBuilderCustomizer() {
            @Override
            public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
                //变成字符串
                jacksonObjectMapperBuilder.serializerByType(Long.class, ToStringSerializer.instance);
            }
        };
        return cunstomizer;
    }

拦截器

/**
 * 自定义请求头拦截器,将Header数据封装到线程变量中方便获取
 * @author 
 */
@Configuration
public class HeaderInterceptor implements AsyncHandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        SecurityContextHolder.setUserId(request.getHeader(SecurityConstants.USER_ID));
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        SecurityContextHolder.remove();
    }
}

/**
 *@Author: 
 *定义日志切面
 */
//@Component
//@Aspect
@Slf4j
public class WebLogAspect {

    // 以 controller 包下定义的所有请求为切入点
    @Pointcut("execution (* com.controllers..*.*(..))")
    public void webLog() {
    }

    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {

        // 开始打印请求日志
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();


        // 打印请求相关参数
        LogUtil.logApplicationInfo("========================================== Start ==========================================");
        // 打印请求 userId
        LogUtil.logApplicationInfo("请求userId        : %s", request.getHeader(SecurityConstants.USER_ID));
        // 打印请求 url
        LogUtil.logApplicationInfo("请求地址        : %s", request.getRequestURL().toString());
        // 打印 Http method
        LogUtil.logApplicationInfo("请求方式        : %s", request.getMethod());
        // 打印调用 controller 的全路径以及执行方法
        LogUtil.logApplicationInfo("请求方法所在路径 : %s.%s", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());

        ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
        String[] keys = pnd.getParameterNames(method);
        Object[] values = joinPoint.getArgs();
        Map<String, Object> paramsMap = new HashMap<>();

        if (values != null) {
            for (int i = 0; i < values.length; i++) {
                if (values[i] instanceof BindingResult ||
                        values[i] instanceof HttpServletRequest ||
                        values[i] instanceof HttpServletResponse ||
                        values[i] instanceof MultipartFile ||
                        values[i] instanceof MultipartFile[]) {
                    continue;
                }
                paramsMap.put(keys[i], values[i]);
            }
        }


        // 打印请求入参
        LogUtil.logApplicationInfo("请求参数        : %s", JSON.toJSONString(paramsMap));

    }

    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();

        Object result = proceedingJoinPoint.proceed();
        // 打印出参
        LogUtil.logApplicationInfo("响应参数        : %s", JSON.toJSONString(result));
        // 执行耗时
        LogUtil.logApplicationInfo("响应时间        : %s ms", System.currentTimeMillis() - startTime);
        LogUtil.logApplicationInfo("=========================================== End ===========================================");
        LogUtil.logApplicationInfo("");
        return result;
    }

    @After("webLog()")
    public void doAfter() throws Throwable {
//        ThreadLocalUtil.clear();
//        LogUtil.logApplicationInfo("=========================================== End ===========================================");
        // 每个请求之间空一行
//        LogUtil.logApplicationInfo("");
    }
/**
 * 拦截器配置
 * @author 
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private HeaderInterceptor headerInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //拦截路径可自行配置多个 可用 ,分隔开
        InterceptorRegistration interceptorRegistration = registry.addInterceptor(headerInterceptor);
        interceptorRegistration.addPathPatterns("/**"); // 需要拦截的路径
        interceptorRegistration.excludePathPatterns(    // 不拦截的路径
                // 放行swagger相关的路径
                "/swagger-ui/**",
                "/swagger-resources/**",
                "/v3/api-docs"
        );
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //配置拦截器访问静态资源
        registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/favicon.ico").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }


    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters){
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = converter.getObjectMapper();
        // 生成JSON时,将所有Long转换成String
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        objectMapper.registerModule(simpleModule);
//        // 时间格式化
//        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//        objectMapper.setDateFormat(new SimpleDateFormat(DateUtil.PATTERN_TIME));
        // 设置格式化内容
        converter.setObjectMapper(objectMapper);
        converters.add(0, converter);
    }


//    /**
//     * 开启跨域
//     */
//    @Override
//    public void addCorsMappings(CorsRegistry registry) {
//        // 设置允许跨域的路由
//        registry.addMapping("/**")
//                // 设置允许跨域请求的域名
//                .allowedOriginPatterns("*")
//                // 是否允许证书(cookies)
//                .allowCredentials(true)
//                // 设置允许的方法
//                .allowedMethods("*")
//                // 跨域允许时间
//                .maxAge(3600);
//    }

<think>嗯,用户想知道如何在前端传递long类型的参数,并且需要方法和注意事项。首先,我得回忆一下前端和后端交互处理数据类型的常见方式。通常,前端使用JavaScript,而JS中的数字类型是双精度浮点数,能够精确表示的整数范围是-2^53到2^53。但long类型Java等后端语言中是64位的,范围更大,所以可能会有精度丢失的问题。这候,把long转成字符串传递可能更安全。 接下来要考虑的是具体的前端框架,比如axios或者fetch API,在发送请求如何传递参数。如果是GET请求,参数放在URL里,如果是POST,可以放在请求体里,比如JSON格式。但JSON中的数字同样会有精度问题,所以最好用字符串形式传递。 另外,用户可能没有明确说明他们使用的技术栈,但通常RESTful API会用JSON,所以需要确保后端能够正确解析字符串形式的long。比如在Spring Boot中,可以使用@RequestParam或者@PathVariable来接收字符串,然后转换成Long类型。 还要注意一些框架的自动转换可能会出问题,比如如果前端传的是数字,但超出JS的安全范围,后端解析可能会出错。所以必须前后端都统一用字符串来处理大整数。 安全性方面,传输过程中用HTTPS是必须的,防止中间人窃取数据。另外,参数校验也很重要,确保传入的是有效的数字字符串,避免注入攻击或者其他恶意输入。 可能还需要考虑间戳的情况,因为间戳经常是long类型前端可能需要格式化显示,这候传递long然后前端转换日期,但同样要注意精度问题。 总结一下,步骤应该是:前端long类型转为字符串,通过HTTPS传输,后端接收字符串并转换为long,同做好校验和错误处理。还要注意各个框架的配置,比如Jackson序列化的设置,避免自动转换导致的问题。</think>### 前端传递 long 类型参数的方法及注意事项 #### 方法 1. **字符串形式传递** 由于 JavaScript 的 `Number` 类型最大安全整数为 $2^{53}-1$,而 Java 的 `long` 类型为 64 位整数,直接传递可能造成精度丢失。建议将 long 类型转换为字符串传递: ```javascript // 前端代码示例(以 Axios 为例) axios.get('/api/data', { params: { id: "1234567890123456789" // 以字符串形式传递 } }); ``` 2. **JSON 序列化** 在 POST 请求中,通过 JSON 字段传递需显式声明为字符串: ```json { "timestamp": "1640995200000" } ``` 3. **结合间格式化工具** 若参数为间戳,可使用 `DateTimeFormatter` 等工具进行格式化后传递[^2]: ```javascript // 前端格式化间戳(如需要) const timestamp = new Date().getTime().toString(); ``` #### 注意事项 1. **精度问题** - 避免直接使用数字类型传递超过 $2^{53}-1$ 的值,否则前端可能丢失精度。 - 后端接收需从字符串解析为 long,例如 Spring Boot 中使用 `@RequestParam` 或 `@PathVariable`: ```java public ResponseEntity<?> getData(@RequestParam("id") String idStr) { long id = Long.parseLong(idStr); // 业务逻辑 } ``` 2. **数据校验** - 前端需验证字符串是否为有效数字格式。 - 后端需捕获 `NumberFormatException` 并返回友好错误提示。 3. **协议安全** 使用 HTTPS 协议加密传输,防止敏感参数被截获[^1]。 4. **框架兼容性** - 若使用 Spring Boot,可通过配置 `Jackson` 实现全局 long 转字符串: ```java @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); converter.getObjectMapper().configure(SerializationFeature.WRITE_NUMBERS_AS_STRINGS, true); converters.add(converter); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员黄小青

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值