You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为Spring HATEOAS HAL Forms嵌套类配置属性与选项?

问题描述

JSON响应中,名为files的属性在HAL Forms的_templates配置里未包含自身内部属性与选项,该属性对应RegisterProductRequest类中嵌套的AddProductFileRequest类。此前处理DeliveryType枚举时,已通过HalFormsConfiguration类成功添加选项,现在需要为files属性配置其内部属性及对应选项。


当前JSON响应
{
    "id": 3,
    "name": "Esse é o novo nome do prodaa",
    "description": "description",
    "price": 1200.00,
    "quantity": 3,
    "active": true,
    "sku": "321",
    "tags": [
        "tag1",
        "tag2"
    ],
    "promotion": {
        "price": 500.00,
        "start": "2000-03-03T12:00:00",
        "finish": "2025-03-03T17:00:00",
        "available": true
    },
    "files": [
        {
            "id": 4,
            "type": "IMAGE",
            "position": 0,
            "file_data": {
                "file_name": "Default.jpeg"
            }
        },
        {
            "id": 5,
            "type": "IMAGE",
            "position": 1,
            "file_data": {
                "file_name": "Default.jpeg"
            }
        }
    ],
    "nameFirstImage": "Default.jpeg",
    "bar_code": "31231",
    "category_id": 1,
    "package_info": {
        "format": "ROLL",
        "height": 2.0,
        "width": 20.5,
        "length": 5.0,
        "weight": 24.0
    },
    "deliveries_types": [],
    "_links": {
        "self": {
            "href": "http://localhost:8080/public/products/3"
        },
        "all_products": {
            "href": "http://localhost:8080/public/products"
        }
    },
    "_templates": {
        "default": {
            "method": "POST",
            "properties": [
                {
                    "name": "active",
                    "required": true
                },
                {
                    "name": "bar_code",
                    "type": "text"
                },
                {
                    "name": "category_id",
                    "required": true,
                    "type": "number"
                },
                {
                    "name": "delivery_types",
                    "required": true,
                    "min": 1,
                    "max": 2,
                    "type": "checkbox",
                    "options": {
                        "inline": [
                            "CORREIOS_SEDEX",
                            "CORREIOS_PAC",
                            "EXPRESS",
                            "PICK_UP_IN_STORE",
                            "DIGITAL"
                        ]
                    }
                },
                {
                    "name": "description",
                    "regex": "^(?=\\s*\\S).*$",
                    "required": true,
                    "type": "textarea"
                },
                {
                    "name": "files",
                    "required": true
                },
                {
                    "name": "name",
                    "regex": "^(?=\\s*\\S).*$",
                    "required": true,
                    "type": "text"
                },
                {
                    "name": "package_info"
                },
                {
                    "name": "price",
                    "required": true,
                    "type": "number"
                },
                {
                    "name": "promotion"
                },
                {
                    "name": "quantity",
                    "required": true,
                    "type": "number"
                },
                {
                    "name": "sku",
                    "type": "text"
                },
                {
                    "name": "tags"
                }
            ],
            "target": "http://localhost:8080/admin/products"
        }
    }
}

相关Java类

RegisterProductRequest.java

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class RegisterProductRequest {
    @NotBlank
    private String name;
    @NotBlank
    @InputType(InputTypeHF.TEXTAREA)
    private String description;
    @NotNull
    @Positive
    private BigDecimal price;
    @NotNull
    private Integer quantity;
    @NotNull
    private Boolean active;
    @JsonProperty("bar_code")
    private String barCode;
    private String sku;
    private String[] tags;
    private ProductPromotionRequest promotion;
    @JsonProperty("package_info")
    private PackageInfo packageInfo;
    @NotNull
    @JsonProperty("category_id")
    private Long categoryId;
    @NotNull
    @JsonProperty("delivery_types")
    @Size(max = 2, min = 1)
    @InputType(InputTypeHF.CHECKBOX)
    private Set<DeliveryType> deliveryTypes;
    @NotNull
    private Set<AddProductFileRequest> files;
}

AddProductFileRequest.java

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class AddProductFileRequest {
    
    private ProductFileType type;
    @JsonProperty("file_name")
    private String fileName;
    @JsonProperty("url_video")
    private String urlVideo;
    @NotNull
    private Byte position;
}

ProductFileType.java

public enum ProductFileType {
    IMAGE,
    VIDEO
}

当前配置类
@Configuration
@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL_FORMS)
public class HateoasConfiguration {
    @Bean
    HalFormsConfiguration halFormsConfiguration() {
        return new HalFormsConfiguration()
                .customize(new ObjectMapper())
                .withOptions(RegisterProductRequest.class, "delivery_types", metadata ->
                    HalFormsOptions.inline(DeliveryType.values()));
    }
}

预期效果
{
    "id": 3,
    "name": "Product Name",
    ...
    "files": [
        {
            "type": "IMAGE",
            "file_name": "Default.jpeg",
            "url_video": "http://example.com/video",
            "position": 0
        },
        {
            "type": "VIDEO",
            "file_name": "Video.mp4",
            "url_video": "http://example.com/video",
            "position": 1
        }
    ],
    ...
    "_templates": {
        "default": {
            "method": "POST",
            "properties": [
                ...
                {
                    "name": "files",
                    "required": true,
                    "properties": [
                        {
                            "name": "type",
                            "required": true,
                            "type": "select",
                            "options": {
                                "inline": [
                                    "IMAGE",
                                    "VIDEO",
                                    "DOCUMENT"
                                ]
                            }
                        },
                        {
                            "name": "file_name",
                            "required": true,
                            "type": "text"
                        },
                        {
                            "name": "url_video",
                            "type": "text"
                        },
                        {
                            "name": "position",
                            "required": true,
                            "type": "number"
                        }
                    ]
                }
                ...
            ]
        }
    }
}

解决方案

修改HateoasConfiguration类,通过withProperties方法为files属性定义内部元数据,同时为type字段添加枚举选项:

@Configuration
@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL_FORMS)
public class HateoasConfiguration {
    @Bean
    HalFormsConfiguration halFormsConfiguration() {
        return new HalFormsConfiguration()
                .customize(new ObjectMapper())
                // 原有delivery_types配置
                .withOptions(RegisterProductRequest.class, "delivery_types", metadata ->
                        HalFormsOptions.inline(DeliveryType.values()))
                // 新增files属性的内部配置
                .withProperties(RegisterProductRequest.class, "files", metadata -> {
                    // 定义type字段
                    HalFormsProperty typeProperty = HalFormsProperty.builder()
                            .name("type")
                            .required(true)
                            .type("select")
                            .options(HalFormsOptions.inline(ProductFileType.values()))
                            .build();
                    // 定义file_name字段
                    HalFormsProperty fileNameProperty = HalFormsProperty.builder()
                            .name("file_name")
                            .required(true)
                            .type("text")
                            .build();
                    // 定义url_video字段
                    HalFormsProperty urlVideoProperty = HalFormsProperty.builder()
                            .name("url_video")
                            .type("text")
                            .build();
                    // 定义position字段
                    HalFormsProperty positionProperty = HalFormsProperty.builder()
                            .name("position")
                            .required(true)
                            .type("number")
                            .build();
                    
                    return List.of(typeProperty, fileNameProperty, urlVideoProperty, positionProperty);
                });
    }
}

说明

  • 使用withProperties方法针对RegisterProductRequest的files属性,添加其内部嵌套的AddProductFileRequest类对应的字段配置。
  • 每个字段通过HalFormsProperty.builder()设置名称、必填性、输入类型,其中type字段关联ProductFileType枚举的选项。
  • 如果需要添加DOCUMENT枚举值,直接在ProductFileType枚举中新增即可,配置会自动同步。

内容的提问来源于stack exchange,提问作者Daniel Schiavo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 00:39:56