枚举定义与转换
✨ 统一的枚举设计规范和自动转换机制,支持请求参数、JSON 序列化/反序列化的无缝集成。
- 🎯 统一接口:
BaseEnum<T>统一枚举值管理 - 🔄 自动转换:请求参数自动转换为枚举对象
- 📤 智能序列化:JSON 输出枚举值而非枚举名称
- 🔢 类型支持:支持 String、Integer、Long 等多种枚举值类型
- 🛠️ 附加值:支持扩展附加属性和 Map 附加值
- ⚡ 性能优化:基于 ConcurrentHashMap 的转换缓存
快速开始
定义枚举
推荐方式:实现 BaseEnum 接口
java
import com.fasterxml.jackson.annotation.JsonCreator;
import host.springboot.framework3.core.enumeration.BaseEnum;
/**
* 用户状态枚举
*/
public enum UserStatusEnum implements BaseEnum<Integer> {
INACTIVE(0, "未激活"),
ACTIVE(1, "已激活"),
LOCKED(2, "已锁定"),
DELETED(9, "已删除");
private final Integer value;
private final String reasonPhrase;
UserStatusEnum(Integer value, String reasonPhrase) {
this.value = value;
this.reasonPhrase = reasonPhrase;
}
@Override
public Integer getValue() {
return value;
}
@Override
public String getReasonPhrase() {
return reasonPhrase;
}
/**
* JSON 反序列化支持
* 必须添加此方法以支持 @RequestBody 参数绑定
*/
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static UserStatusEnum parse(Integer value) {
return BaseEnum.parse(value, UserStatusEnum.class);
}
}使用枚举
Controller 层
java
@RestController
@RequestMapping("/api/user")
public class UserController {
/**
* 场景1:URL 查询参数
* GET /api/user/list?status=1
*/
@GetMapping("/list")
public BaseVO list(
@RequestParam(required = false) UserStatusEnum status) {
// ✅ 框架自动将 "1" 转换为 UserStatusEnum.ACTIVE
return R.ok(userService.findByStatus(status));
}
/**
* 场景2:路径参数
* GET /api/user/status/1
*/
@GetMapping("/status/{status}")
public BaseVO getByStatus(@PathVariable UserStatusEnum status) {
// ✅ 框架自动转换
return R.ok(userService.findByStatus(status));
}
/**
* 场景3:请求体 JSON
* POST /api/user/create
* {"username":"test", "status":1}
*/
@PostMapping("/create")
public BaseVO create(@RequestBody UserCreateDTO dto) {
// ✅ dto.getStatus() 已自动转换为枚举对象
return R.ok(userService.create(dto));
}
}
/**
* 请求 DTO
*/
public class UserCreateDTO {
private String username;
private UserStatusEnum status; // ✅ 自动转换
// getter/setter...
}JSON 序列化输出
java
public class UserVO {
private Long id;
private String username;
private UserStatusEnum status; // ✅ 输出枚举值而非枚举名称
// getter/setter...
}
// 响应 JSON:
{
"code": 200,
"data": {
"id": 1,
"username": "test",
"status": 1 // ✅ 输出 Integer 值,而不是 "ACTIVE"
}
}BaseEnum 接口详解
核心方法
java
public interface BaseEnum<T> {
/**
* 获取枚举值(必须实现)
* @JsonValue 注解确保 JSON 序列化时输出此值
*/
@JsonValue
T getValue();
/**
* 获取枚举描述(必须实现)
*/
String getReasonPhrase();
/**
* 附加单个值(可选)
*/
default String additionalValue() {
return null;
}
/**
* 附加多个值(可选)
*/
default Map<String, String> additionalMapValue() {
return Collections.emptyMap();
}
/**
* 解析枚举值(静态工具方法)
*/
static <T, E extends BaseEnum<T>> E parse(
T value, Class<E> enumClass) {
// 智能解析,支持动态类型转换
}
}方法说明
1. getValue() - 枚举值
作用:返回枚举的实际值,用于 JSON 序列化和数据库存储。
支持的类型:
String- 字符串枚举值Integer- 整数枚举值Long- 长整数枚举值Boolean- 布尔枚举值BigDecimal- 大数枚举值- 其他数字类型
示例:
java
public enum GenderEnum implements BaseEnum<String> {
MALE("M", "男性"),
FEMALE("F", "女性");
private final String value;
private final String reasonPhrase;
// 构造器
@Override
public String getValue() {
return value; // 返回 "M" 或 "F"
}
@Override
public String getReasonPhrase() {
return reasonPhrase;
}
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static GenderEnum parse(String value) {
return BaseEnum.parse(value, GenderEnum.class);
}
}2. getReasonPhrase() - 枚举描述
作用:提供枚举的人类可读描述。
使用场景:
- 前端下拉框展示
- 日志记录
- 错误提示
示例:
java
public enum OrderStatusEnum implements BaseEnum<Integer> {
PENDING(0, "待支付"),
PAID(1, "已支付"),
SHIPPED(2, "已发货"),
COMPLETED(3, "已完成"),
CANCELLED(9, "已取消");
private final Integer value;
private final String reasonPhrase;
// 构造器、getter...
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static OrderStatusEnum parse(Integer value) {
return BaseEnum.parse(value, OrderStatusEnum.class);
}
}
// 使用
OrderStatusEnum status = OrderStatusEnum.PAID;
log.info("订单状态: {} - {}", status.getValue(), status.getReasonPhrase());
// 输出: 订单状态: 1 - 已支付3. additionalValue() - 附加单个值
作用:扩展单个附加属性。
示例:
java
public enum ColorEnum implements BaseEnum<String> {
RED("red", "红色", "#FF0000"),
GREEN("green", "绿色", "#00FF00"),
BLUE("blue", "蓝色", "#0000FF");
private final String value;
private final String reasonPhrase;
private final String hexCode; // 附加值:十六进制颜色代码
ColorEnum(String value, String reasonPhrase, String hexCode) {
this.value = value;
this.reasonPhrase = reasonPhrase;
this.hexCode = hexCode;
}
@Override
public String getValue() {
return value;
}
@Override
public String getReasonPhrase() {
return reasonPhrase;
}
@Override
public String additionalValue() {
return hexCode; // 返回十六进制代码
}
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static ColorEnum parse(String value) {
return BaseEnum.parse(value, ColorEnum.class);
}
}
// 使用
ColorEnum color = ColorEnum.RED;
String hex = color.additionalValue(); // "#FF0000"4. additionalMapValue() - 附加多个值
作用:扩展多个附加属性,以 Map 形式返回。
示例:
java
public enum ProductTypeEnum implements BaseEnum<Integer> {
PHYSICAL(1, "实物商品"),
VIRTUAL(2, "虚拟商品"),
SERVICE(3, "服务商品");
private final Integer value;
private final String reasonPhrase;
private final Map<String, String> additionalMap;
ProductTypeEnum(Integer value, String reasonPhrase) {
this.value = value;
this.reasonPhrase = reasonPhrase;
// 初始化附加值
this.additionalMap = new HashMap<>();
switch (this) {
case PHYSICAL:
additionalMap.put("needShipping", "true");
additionalMap.put("category", "goods");
break;
case VIRTUAL:
additionalMap.put("needShipping", "false");
additionalMap.put("category", "digital");
break;
case SERVICE:
additionalMap.put("needShipping", "false");
additionalMap.put("category", "service");
break;
}
}
@Override
public Integer getValue() {
return value;
}
@Override
public String getReasonPhrase() {
return reasonPhrase;
}
@Override
public Map<String, String> additionalMapValue() {
return additionalMap;
}
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static ProductTypeEnum parse(Integer value) {
return BaseEnum.parse(value, ProductTypeEnum.class);
}
}
// 使用
ProductTypeEnum type = ProductTypeEnum.PHYSICAL;
Map<String, String> additional = type.additionalMapValue();
String needShipping = additional.get("needShipping"); // "true"
String category = additional.get("category"); // "goods"5. parse() - 智能解析
作用:将值解析为枚举对象,支持动态类型转换。
方法重载:
java
// 1. 基础解析(推荐)
static <T, E extends BaseEnum<T>> E parse(T value, Class<E> enumClass)
// 2. 严格解析(找不到时抛异常)
static <T, E extends BaseEnum<T>> E parseThrowIfNotFound(
T value, Class<E> enumClass)
// 3. 完整解析(自定义行为)
static <T, E extends BaseEnum<T>> E parse(
T value, Class<E> enumClass,
boolean isDynamicParse, // 是否动态解析
boolean isNotFoundThrow) // 找不到时是否抛异常动态解析特性:
java
public enum StatusEnum implements BaseEnum<Integer> {
ACTIVE(1, "激活"),
INACTIVE(0, "未激活");
// ...
}
// ✅ 整数解析
StatusEnum s1 = BaseEnum.parse(1, StatusEnum.class);
// s1 = StatusEnum.ACTIVE
// ✅ 字符串自动转整数(动态解析)
StatusEnum s2 = BaseEnum.parse("1", StatusEnum.class, true, false);
// s2 = StatusEnum.ACTIVE
// ✅ 找不到返回 null
StatusEnum s3 = BaseEnum.parse(99, StatusEnum.class);
// s3 = null
// ❌ 找不到抛异常
StatusEnum s4 = BaseEnum.parseThrowIfNotFound(99, StatusEnum.class);
// 抛出: EnumIllegalArgumentException枚举转换机制
URL 参数转换
原理:基于 Spring 的 ConverterFactory 机制。
工作流程:
HTTP 请求: GET /api/user/list?status=1
↓
Spring MVC 参数解析
↓
EnumConverterFactory.getConverter(UserStatusEnum.class)
↓
检查缓存 CONVERTER_MAP
├─ 命中缓存 → 直接返回 Converter
└─ 未命中 → 创建 Converter
↓
检查是否有 @EnumConvertValue 注解
├─ 有 → 使用 StringToAnnotationEnumConverter
└─ 无 → 检查是否实现 BaseEnum
├─ 是 → 使用 StringToBaseEnumConverter
└─ 否 → 返回 null(使用默认转换)
↓
Converter.convert("1")
↓
BaseEnum.parse(1, UserStatusEnum.class)
↓
返回 UserStatusEnum.ACTIVE核心代码:
java
public final class EnumConverterFactory
implements ConverterFactory<String, Enum<?>> {
// 转换缓存(性能优化)
private static final Map<Class<? extends Enum<?>>,
EnumConverterHolder> CONVERTER_MAP = new ConcurrentHashMap<>();
@Override
public <E extends Enum<?>> Converter<String, E> getConverter(
Class<E> targetType) {
// 从缓存获取或创建转换器
EnumConverterHolder holder = CONVERTER_MAP.computeIfAbsent(
targetType, EnumConverterHolder::create);
// 优先使用 @EnumConvertValue 注解转换器
return (Converter<String, E>) (
holder.annotationConverter != null ?
holder.annotationConverter :
holder.baseEnumConverter
);
}
}JSON 序列化
原理:基于 Jackson 的自定义序列化器 BaseEnumSerializer。
序列化流程:
Java 对象: UserVO { status = UserStatusEnum.ACTIVE }
↓
Jackson 序列化
↓
BaseEnumSerializer.serialize(UserStatusEnum.ACTIVE, ...)
↓
获取 getValue() 返回值 → 1 (Integer)
↓
根据类型调用对应的 write 方法
├─ String → jsonGenerator.writeString()
├─ Integer → jsonGenerator.writeNumber()
├─ Long → jsonGenerator.writeNumber()
├─ Boolean → jsonGenerator.writeBoolean()
└─ BigDecimal → jsonGenerator.writeNumber()
↓
JSON 输出: {"status": 1}核心代码:
java
public final class BaseEnumSerializer
extends StdSerializer<BaseEnum<Object>> {
@Override
public void serialize(
BaseEnum<Object> object,
JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException {
if (Objects.isNull(object.getValue())) {
jsonGenerator.writeNull();
return;
}
Object value = object.getValue();
// 根据值类型选择序列化方法
if (value instanceof String) {
jsonGenerator.writeString((String) value);
} else if (value instanceof Integer) {
jsonGenerator.writeNumber((Integer) value);
} else if (value instanceof Long) {
jsonGenerator.writeNumber((Long) value);
}
// ... 其他类型
}
}JSON 反序列化
原理:基于 @JsonCreator 注解的工厂方法。
反序列化流程:
JSON 输入: {"status": 1}
↓
Jackson 反序列化
↓
查找 @JsonCreator 方法
↓
UserStatusEnum.parse(1) // 必须在枚举类中定义
↓
BaseEnum.parse(1, UserStatusEnum.class)
↓
遍历枚举值匹配
├─ INACTIVE.getValue() = 0 → 不匹配
├─ ACTIVE.getValue() = 1 → ✅ 匹配
└─ ...
↓
返回 UserStatusEnum.ACTIVE必须添加的代码:
java
public enum UserStatusEnum implements BaseEnum<Integer> {
// ... 枚举常量
/**
* ⚠️ 必须添加此方法,否则 @RequestBody 无法反序列化
*/
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static UserStatusEnum parse(Integer value) {
return BaseEnum.parse(value, UserStatusEnum.class);
}
}常见场景
场景1:用户状态枚举
java
public enum UserStatusEnum implements BaseEnum<Integer> {
INACTIVE(0, "未激活"),
ACTIVE(1, "已激活"),
LOCKED(2, "已锁定"),
DELETED(9, "已删除");
private final Integer value;
private final String reasonPhrase;
UserStatusEnum(Integer value, String reasonPhrase) {
this.value = value;
this.reasonPhrase = reasonPhrase;
}
@Override
public Integer getValue() {
return value;
}
@Override
public String getReasonPhrase() {
return reasonPhrase;
}
/**
* 业务方法:是否可以登录
*/
public boolean canLogin() {
return this == ACTIVE;
}
/**
* 业务方法:是否可以激活
*/
public boolean canActivate() {
return this == INACTIVE;
}
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static UserStatusEnum parse(Integer value) {
return BaseEnum.parse(value, UserStatusEnum.class);
}
}
// Controller
@GetMapping("/list")
public BaseVO list(@RequestParam UserStatusEnum status) {
// 业务逻辑中使用
if (!status.canLogin()) {
throw new ApplicationException(
ErrorCodeEnum.FORBIDDEN,
"该状态的用户不允许登录"
);
}
return R.ok(userService.findByStatus(status));
}场景2:订单状态流转
java
public enum OrderStatusEnum implements BaseEnum<Integer> {
PENDING(0, "待支付"),
PAID(1, "已支付"),
SHIPPED(2, "已发货"),
COMPLETED(3, "已完成"),
CANCELLED(9, "已取消");
private final Integer value;
private final String reasonPhrase;
OrderStatusEnum(Integer value, String reasonPhrase) {
this.value = value;
this.reasonPhrase = reasonPhrase;
}
@Override
public Integer getValue() {
return value;
}
@Override
public String getReasonPhrase() {
return reasonPhrase;
}
/**
* 获取下一个状态
*/
public OrderStatusEnum getNextStatus() {
return switch (this) {
case PENDING -> PAID;
case PAID -> SHIPPED;
case SHIPPED -> COMPLETED;
default -> throw new IllegalStateException(
"状态 " + this.getReasonPhrase() + " 不能流转"
);
};
}
/**
* 是否可以取消
*/
public boolean canCancel() {
return this == PENDING || this == PAID;
}
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static OrderStatusEnum parse(Integer value) {
return BaseEnum.parse(value, OrderStatusEnum.class);
}
}
// Service
public void updateOrderStatus(Long orderId, OrderStatusEnum newStatus) {
Order order = orderMapper.selectById(orderId);
OrderStatusEnum currentStatus = order.getStatus();
// 校验状态流转
if (currentStatus.getNextStatus() != newStatus) {
throw new ApplicationException(
ErrorCodeEnum.INVALID_USER_INPUT,
String.format("订单状态不能从 %s 直接变更为 %s",
currentStatus.getReasonPhrase(),
newStatus.getReasonPhrase())
);
}
order.setStatus(newStatus);
orderMapper.updateById(order);
}场景3:字符串枚举值
java
public enum LanguageEnum implements BaseEnum<String> {
ZH_CN("zh_CN", "简体中文"),
ZH_TW("zh_TW", "繁体中文"),
EN_US("en_US", "英语(美国)"),
JA_JP("ja_JP", "日语");
private final String value;
private final String reasonPhrase;
LanguageEnum(String value, String reasonPhrase) {
this.value = value;
this.reasonPhrase = reasonPhrase;
}
@Override
public String getValue() {
return value;
}
@Override
public String getReasonPhrase() {
return reasonPhrase;
}
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static LanguageEnum parse(String value) {
return BaseEnum.parse(value, LanguageEnum.class);
}
}
// 请求: GET /api/content/get?lang=zh_CN
@GetMapping("/get")
public BaseVO getContent(@RequestParam LanguageEnum lang) {
// ✅ lang = LanguageEnum.ZH_CN
return R.ok(contentService.getByLanguage(lang));
}场景4:带附加值的枚举
java
public enum PayMethodEnum implements BaseEnum<Integer> {
ALIPAY(1, "支付宝", "https://www.alipay.com"),
WECHAT(2, "微信支付", "https://pay.weixin.qq.com"),
UNION(3, "银联支付", "https://www.unionpay.com");
private final Integer value;
private final String reasonPhrase;
private final String payUrl; // 附加值:支付网关地址
PayMethodEnum(Integer value, String reasonPhrase, String payUrl) {
this.value = value;
this.reasonPhrase = reasonPhrase;
this.payUrl = payUrl;
}
@Override
public Integer getValue() {
return value;
}
@Override
public String getReasonPhrase() {
return reasonPhrase;
}
@Override
public String additionalValue() {
return payUrl;
}
/**
* 获取支付网关地址
*/
public String getPayUrl() {
return payUrl;
}
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static PayMethodEnum parse(Integer value) {
return BaseEnum.parse(value, PayMethodEnum.class);
}
}
// Service
public String createPayment(PayMethodEnum payMethod, BigDecimal amount) {
String payUrl = payMethod.getPayUrl();
// 或者: String payUrl = payMethod.additionalValue();
// 调用对应的支付网关
return paymentService.create(payUrl, amount);
}场景5:MyBatis-Plus 集成
java
// 实体类
@TableName("t_user")
public class User extends BaseDO {
private String username;
// ✅ MyBatis-Plus 会自动将数据库中的 Integer 值转换为枚举
private UserStatusEnum status;
// getter/setter...
}
// Mapper
public interface UserMapper extends BaseMapper<User> {
// ✅ 自动转换
@Select("SELECT * FROM t_user WHERE status = #{status}")
List<User> selectByStatus(@Param("status") UserStatusEnum status);
}
// Service
public List<User> findActiveUsers() {
// ✅ 查询条件自动转换
return userMapper.selectByStatus(UserStatusEnum.ACTIVE);
}@EnumConvertValue 注解(不推荐)
使用方式
注意:推荐使用 BaseEnum 接口,@EnumConvertValue 仅用于特殊场景。
java
public enum StatusEnum {
ACTIVE,
INACTIVE;
/**
* ⚠️ 使用 @EnumConvertValue 注解
* 必须是静态方法,参数为 String,返回枚举类型
*/
@EnumConvertValue
public static StatusEnum convertValue(String value) {
return switch (value.toUpperCase()) {
case "1", "ACTIVE" -> ACTIVE;
case "0", "INACTIVE" -> INACTIVE;
default -> null;
};
}
}
// Controller
@GetMapping("/list")
public BaseVO list(@RequestParam StatusEnum status) {
// ✅ 支持 "1", "ACTIVE" 等多种值转换
return R.ok(userService.findByStatus(status));
}限制条件
- ✅ 必须是静态方法
- ✅ 必须有一个 String 参数
- ✅ 返回值必须是枚举类型
- ✅ 只能标注在一个方法上
- ❌ 不支持 JSON 序列化(无
getValue()) - ❌ 不支持 MyBatis-Plus 自动转换
为什么不推荐?
| 对比项 | BaseEnum | @EnumConvertValue |
|---|---|---|
| URL 参数转换 | ✅ 支持 | ✅ 支持 |
| JSON 序列化 | ✅ 输出值 | ❌ 输出枚举名 |
| JSON 反序列化 | ✅ 支持(@JsonCreator) | ❌ 不支持 |
| MyBatis-Plus | ✅ 自动转换 | ❌ 需手动处理 |
| 附加值 | ✅ 支持 | ❌ 不支持 |
| 类型安全 | ✅ 泛型支持 | ⚠️ 仅 String |
| 代码简洁 | ✅ 接口统一 | ⚠️ 需自定义 |
结论:除非有特殊需求,否则始终使用 BaseEnum 接口。
@NumberToScaleString 注解
核心功能
解决问题:JavaScript 浮点数精度丢失。
原理:将数字类型序列化为带指定小数位的字符串。
使用方式
java
import host.springboot.framework.context.mvc.annotation.NumberToScaleString;
import java.math.RoundingMode;
public class ProductVO {
private Long id;
private String name;
/**
* ✅ 保留2位小数,四舍五入
* 输入: 99.996
* 输出: "100.00"
*/
@NumberToScaleString(scale = 2, roundingMode = RoundingMode.HALF_UP)
private BigDecimal price;
/**
* ✅ 保留4位小数,向下取整
* 输入: 0.123456
* 输出: "0.1234"
*/
@NumberToScaleString(
scale = 4,
roundingMode = RoundingMode.DOWN,
toStringType = NumberToScaleString.ToStringType.TO_PLAIN_STRING
)
private BigDecimal rate;
// getter/setter...
}
// 响应 JSON:
{
"id": 1,
"name": "商品名称",
"price": "100.00", // ✅ 字符串,保留2位小数
"rate": "0.1234" // ✅ 字符串,保留4位小数
}注解参数
java
public @interface NumberToScaleString {
/**
* 保留小数位数
* 默认: 2
*/
int scale() default 2;
/**
* 舍入模式
* 默认: HALF_UP(四舍五入)
*/
RoundingMode roundingMode() default RoundingMode.HALF_UP;
/**
* 输出字符串类型
* 默认: TO_PLAIN_STRING
*/
ToStringType toStringType() default ToStringType.TO_PLAIN_STRING;
}RoundingMode 说明
| 模式 | 说明 | 示例(scale=2) |
|---|---|---|
UP | 向上取整 | 1.234 → "1.24" |
DOWN | 向下取整 | 1.236 → "1.23" |
CEILING | 向正无穷取整 | 1.231 → "1.24" |
FLOOR | 向负无穷取整 | 1.239 → "1.23" |
HALF_UP | 四舍五入 | 1.235 → "1.24" |
HALF_DOWN | 五舍六入 | 1.235 → "1.23" |
HALF_EVEN | 银行家舍入 | 1.235 → "1.24" |
ToStringType 说明
java
public enum ToStringType {
/**
* 默认 toString()
* 可能使用科学计数法
*/
DEFAULT,
/**
* toPlainString()
* 不使用科学计数法(推荐)
*/
TO_PLAIN_STRING,
/**
* toEngineeringString()
* 工程计数法
*/
TO_ENGINEERING_STRING
}
// 示例
BigDecimal value = new BigDecimal("0.00000123");
// DEFAULT: 可能输出 "1.23E-6"
// TO_PLAIN_STRING: 输出 "0.00000123"
// TO_ENGINEERING_STRING: 输出 "1.23E-6"支持的类型
java
public class DTO {
// ✅ BigDecimal
@NumberToScaleString(scale = 2)
private BigDecimal amount1;
// ✅ BigInteger
@NumberToScaleString(scale = 2)
private BigInteger amount2;
// ✅ Long
@NumberToScaleString(scale = 2)
private Long amount3;
// ✅ Double
@NumberToScaleString(scale = 2)
private Double amount4;
// ✅ Float
@NumberToScaleString(scale = 2)
private Float amount5;
// ✅ Integer
@NumberToScaleString(scale = 2)
private Integer amount6;
// ✅ String(数字字符串)
@NumberToScaleString(scale = 2)
private String amount7; // "123.456" → "123.46"
// ❌ 非数字类型会抛异常
// @NumberToScaleString(scale = 2)
// private String name; // 错误!
}使用场景
场景1:金额字段
java
public class OrderVO {
/**
* 订单金额(元)
* 保留2位小数,四舍五入
*/
@NumberToScaleString(scale = 2, roundingMode = RoundingMode.HALF_UP)
private BigDecimal totalAmount;
/**
* 优惠金额(元)
* 保留2位小数,向下取整(有利于商家)
*/
@NumberToScaleString(scale = 2, roundingMode = RoundingMode.DOWN)
private BigDecimal discountAmount;
/**
* 实付金额(元)
* 保留2位小数,向上取整(避免少收)
*/
@NumberToScaleString(scale = 2, roundingMode = RoundingMode.UP)
private BigDecimal payAmount;
}
// JSON 输出:
{
"totalAmount": "100.00",
"discountAmount": "10.00",
"payAmount": "90.00"
}场景2:利率/汇率
java
public class ExchangeRateVO {
/**
* 汇率
* 保留6位小数,银行家舍入
*/
@NumberToScaleString(
scale = 6,
roundingMode = RoundingMode.HALF_EVEN
)
private BigDecimal rate;
/**
* 年化收益率(%)
* 保留4位小数
*/
@NumberToScaleString(scale = 4)
private BigDecimal annualYield;
}
// JSON 输出:
{
"rate": "6.890123", // 汇率保留6位
"annualYield": "3.6500" // 收益率保留4位
}最佳实践
✅ 推荐做法
java
// 1. 所有枚举都实现 BaseEnum 接口
public enum StatusEnum implements BaseEnum<Integer> {
// ✅ 统一规范
}
// 2. 必须添加 @JsonCreator 方法
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static StatusEnum parse(Integer value) {
return BaseEnum.parse(value, StatusEnum.class);
}
// 3. 使用有意义的枚举值
public enum UserStatusEnum implements BaseEnum<Integer> {
INACTIVE(0, "未激活"), // ✅ 0 表示未激活
ACTIVE(1, "已激活"), // ✅ 1 表示已激活
LOCKED(2, "已锁定"), // ✅ 2 表示已锁定
DELETED(9, "已删除"); // ✅ 9 表示删除状态
}
// 4. 添加业务方法增强枚举功能
public enum OrderStatusEnum implements BaseEnum<Integer> {
// ...
public boolean canCancel() {
return this == PENDING || this == PAID;
}
public OrderStatusEnum getNextStatus() {
return switch (this) {
case PENDING -> PAID;
case PAID -> SHIPPED;
// ...
};
}
}
// 5. 金额字段使用 @NumberToScaleString
public class OrderVO {
@NumberToScaleString(scale = 2, roundingMode = RoundingMode.HALF_UP)
private BigDecimal amount; // ✅ 解决精度问题
}❌ 不推荐做法
java
// ❌ 1. 不实现 BaseEnum 接口
public enum StatusEnum {
ACTIVE, INACTIVE; // 无法自动转换
}
// ❌ 2. 忘记添加 @JsonCreator 方法
public enum StatusEnum implements BaseEnum<Integer> {
ACTIVE(1, "激活");
// 缺少 parse() 方法
// @RequestBody 无法反序列化!
}
// ❌ 3. 使用无意义的枚举值
public enum StatusEnum implements BaseEnum<Integer> {
ACTIVE(123, "激活"), // ❌ 123 无意义
INACTIVE(456, "未激活"); // ❌ 456 无意义
}
// ✅ 正确做法
public enum StatusEnum implements BaseEnum<Integer> {
INACTIVE(0, "未激活"),
ACTIVE(1, "激活");
}
// ❌ 4. 过度使用 @EnumConvertValue
public enum StatusEnum {
ACTIVE, INACTIVE;
@EnumConvertValue
public static StatusEnum convertValue(String value) {
// ❌ 不如直接实现 BaseEnum
}
}
// ❌ 5. 金额字段不处理精度
public class OrderVO {
private BigDecimal amount; // ❌ 前端可能精度丢失
}
// ✅ 正确做法
public class OrderVO {
@NumberToScaleString(scale = 2)
private BigDecimal amount; // ✅ 转为字符串
}常见问题
Q: 为什么必须实现 BaseEnum 接口?
A: 框架基于 BaseEnum 接口提供了完整的转换和序列化支持:
- ✅ URL 参数转换:通过
EnumConverterFactory - ✅ JSON 序列化:通过
BaseEnumSerializer - ✅ JSON 反序列化:通过
@JsonCreator+BaseEnum.parse() - ✅ MyBatis-Plus:自动类型转换
- ✅ 附加值:支持扩展属性
不实现接口将失去这些自动化能力。
Q: 为什么必须添加 @JsonCreator 方法?
A: Jackson 反序列化 JSON 时需要知道如何将值转换为枚举:
java
// ❌ 没有 @JsonCreator
public enum StatusEnum implements BaseEnum<Integer> {
ACTIVE(1, "激活");
// 缺少 parse() 方法
}
// JSON: {"status": 1}
// 反序列化失败! Cannot construct instance of StatusEnum
// ✅ 有 @JsonCreator
public enum StatusEnum implements BaseEnum<Integer> {
ACTIVE(1, "激活");
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static StatusEnum parse(Integer value) {
return BaseEnum.parse(value, StatusEnum.class);
}
}
// JSON: {"status": 1}
// ✅ 反序列化成功! status = StatusEnum.ACTIVEQ: BaseEnum.parse() 和 parseThrowIfNotFound() 有什么区别?
A: 找不到匹配值时的行为不同:
java
// parse() - 找不到返回 null
StatusEnum s1 = BaseEnum.parse(99, StatusEnum.class);
// s1 = null,不抛异常
// parseThrowIfNotFound() - 找不到抛异常
StatusEnum s2 = BaseEnum.parseThrowIfNotFound(99, StatusEnum.class);
// 抛出: EnumIllegalArgumentException:
// No matching [StatusEnum] for [99]选择建议:
- URL 参数:使用
parse()(允许 null) - JSON 反序列化:使用
parse()(允许 null) - 业务校验:使用
parseThrowIfNotFound()(严格校验)
Q: 如何处理枚举值为 null 的情况?
A: 框架已自动处理:
URL 参数:
java
// GET /api/user/list?status=
@GetMapping("/list")
public BaseVO list(@RequestParam(required = false) UserStatusEnum status) {
// status = null(空字符串转换为 null)
if (status == null) {
// 查询所有状态
}
}JSON 序列化:
java
public class UserVO {
private UserStatusEnum status; // null
}
// JSON 输出:
{
"status": null // ✅ 输出 null
}JSON 反序列化:
java
// JSON: {"status": null}
public class UserDTO {
private UserStatusEnum status; // null
}
// ✅ status = nullQ: @NumberToScaleString 会影响性能吗?
A: 性能影响极小:
- 仅在序列化时执行:不影响业务逻辑
- BigDecimal 计算:现代 JVM 优化良好
- 无额外内存:直接输出字符串
性能对比:
java
// 未使用注解
private BigDecimal price; // 直接输出数字
// 序列化耗时: ~1μs
// 使用注解
@NumberToScaleString(scale = 2)
private BigDecimal price; // setScale + toString
// 序列化耗时: ~2μs
// 性能差异: 可忽略不计Q: 枚举值可以重复吗?
A: 不建议,但技术上可以:
java
// ❌ 不推荐:枚举值重复
public enum StatusEnum implements BaseEnum<Integer> {
INACTIVE(0, "未激活"),
DELETED(0, "已删除"); // ❌ 值重复
}
// 解析时只会匹配到第一个
StatusEnum s = BaseEnum.parse(0, StatusEnum.class);
// s = StatusEnum.INACTIVE(始终返回第一个匹配)
// ✅ 推荐:使用不同的值
public enum StatusEnum implements BaseEnum<Integer> {
INACTIVE(0, "未激活"),
DELETED(9, "已删除"); // ✅ 值唯一
}Q: 如何获取所有枚举值?
A: 使用标准 Java 方法:
java
// 获取所有枚举常量
UserStatusEnum[] values = UserStatusEnum.values();
// 转换为列表
List<UserStatusEnum> list = Arrays.asList(UserStatusEnum.values());
// 获取所有枚举值
List<Integer> valueList = Arrays.stream(UserStatusEnum.values())
.map(UserStatusEnum::getValue)
.collect(Collectors.toList());
// [0, 1, 2, 9]
// 构建前端下拉框数据
List<Map<String, Object>> options = Arrays.stream(UserStatusEnum.values())
.map(e -> Map.of(
"value", e.getValue(),
"label", e.getReasonPhrase()
))
.collect(Collectors.toList());
// [{"value":0,"label":"未激活"}, {"value":1,"label":"已激活"}, ...]💡 提示:
- 所有枚举必须实现
BaseEnum接口 - 必须添加
@JsonCreator方法支持 JSON 反序列化 - 使用有意义的枚举值(0/1/2/9 等)
- 金额字段使用
@NumberToScaleString避免精度丢失 - 不推荐使用
@EnumConvertValue注解
