这是为了解决 JavaScript 处理大数字精度问题,我们可以通过以下方式实现:

  1. 创建 Jackson 配置类:
// src/main/java/top/roozen/lab/competition/config/JacksonConfig.java

@Configuration
public class JacksonConfig {
    
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        
        // 注册Long类型序列化器
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        objectMapper.registerModule(simpleModule);
        
        // 其他常用配置
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        
        return objectMapper;
    }
}
  1. 如果使用 Spring MVC,还可以配置全局消息转换器:
// src/main/java/top/roozen/lab/competition/config/WebMvcConfig.java

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
        
        ObjectMapper objectMapper = new ObjectMapper();
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        objectMapper.registerModule(simpleModule);
        
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        
        jackson2HttpMessageConverter.setObjectMapper(objectMapper);
        converters.add(0, jackson2HttpMessageConverter);
    }
}

这样配置后:

  1. 所有Long类型的值都会以字符串形式返回
  2. 避免了JavaScript处理大数字时的精度丢失问题
  3. 同时配置了一些常用的Jackson序列化选项

注意事项:

  1. 这个配置会影响全局的JSON序列化
  2. 如果某些特殊场景不需要转换为字符串,可以使用@JsonSerialize注解单独配置
  3. 建议在项目初期就进行此配置,避免后期出现精度问题

simpleModule.addSerializer(Long.class, ToStringSerializer.instance); simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance); 这两句的区别在于处理的是不同的 Long 类型:

  1. Long.class 处理的是包装类型 Long
  2. Long.TYPE 处理的是基本类型 long

举例说明:

public class Example {
    private Long wrappedLong = 123L;    // 包装类型 Long
    private long primitiveLong = 456L;   // 基本类型 long
}

如果只配置其中一个:

// 只配置 Long.class
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);

// 结果
{
    "wrappedLong": "123",    // 会转换为字符串
    "primitiveLong": 456     // 仍然是数字
}
// 只配置 Long.TYPE
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);

// 结果
{
    "wrappedLong": 123,      // 仍然是数字
    "primitiveLong": "456"   // 会转换为字符串
}

所以需要同时配置这两句,才能确保所有的 Long 类型(无论是包装类型还是基本类型)都能被正确地序列化为字符串:

simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);

// 结果
{
    "wrappedLong": "123",    // 转换为字符串
    "primitiveLong": "456"   // 转换为字符串
}

这种情况在 Java 中很常见,因为:

  1. 类的属性通常使用包装类型(Long
  2. 基本类型(long)通常用在方法参数、局部变量等场景
  3. 有些框架(如 MyBatis)会自动在包装类型和基本类型之间转换

所以为了确保所有场景下的 Long 值都能正确序列化为字符串,需要同时配置这两种类型。

  1. 如果只配置 ObjectMapper Bean:
@Configuration
public class JacksonConfig {
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        objectMapper.registerModule(simpleModule);
        return objectMapper;
    }
}

这种配置足够处理 @RestController 返回的对象,因为:

  • Spring MVC 默认使用 MappingJackson2HttpMessageConverter
  • 这个转换器会自动使用容器中的 ObjectMapper Bean
  • 所以不需要额外配置 WebMvcConfigurer
  1. 什么时候需要配置全局消息转换器:
  • 当你需要自定义多个 HttpMessageConverter 的顺序时
  • 当你需要添加其他自定义的消息转换器时
  • 当你需要完全控制消息转换的过程时

例如:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 添加自定义的消息转换器并设置优先级
        converters.add(0, new CustomConverter());
        
        // 配置Jackson转换器
        MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = 
            new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = new ObjectMapper();
        // ... 配置 ObjectMapper
        jackson2HttpMessageConverter.setObjectMapper(objectMapper);
        converters.add(1, jackson2HttpMessageConverter);
    }
}
  1. 实际建议:
// src/main/java/top/roozen/lab/competition/config/JacksonConfig.java

@Configuration
public class JacksonConfig {
    
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        
        // 配置Long转字符串
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        objectMapper.registerModule(simpleModule);
        
        // 其他常用配置
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
        
        return objectMapper;
    }
}

这个配置就足够了,因为:

  1. 能满足 @RestController 的需求
  2. 配置简单清晰
  3. 其他需要使用 ObjectMapper 的地方也能复用这个配置

除非你有特殊需求(如上面提到的自定义转换器),否则不需要配置 WebMvcConfigurer

示例测试:

@RestController
@RequestMapping("/api/test")
public class TestController {
    
    @GetMapping("/long")
    public Map<String, Long> testLong() {
        Map<String, Long> result = new HashMap<>();
        result.put("wrappedLong", 123456789012345L);
        result.put("primitiveLong", 987654321098765L);
        return result;
    }
}

响应结果:

{
    "wrappedLong": "123456789012345",
    "primitiveLong": "987654321098765"
}

这证明只配置 ObjectMapper Bean 就能正确处理 Long 类型的序列化。