SpringBoot 自定义消息转换器使用详解

本文详细介绍了SpringBoot消息转换器的知识,并通过案例操作演示了如何进行自定义消息转换器的定制开发和使用,感兴趣的朋友一起看看吧

目录
  • 一、前言
  • 二、SpringBoot 内容协商介绍
    • 2.1 什么是内容协商
    • 2.2 内容协商机制深入理解
      • 2.2.1 内容协商产生的场景
    • 2.3 内容协商实现的常用方式
      • 2.3.1 前置准备
      • 2.3.2 通过HTTP请求头
  • 三、SpringBoot 消息转换器介绍
    • 3.1 HttpMessageConvertor介绍
      • 3.1.1 常用的HttpMessageConvertor
    • 3.2 如何确定使用哪个消息转换器
      • 3.2.1 针对请求时的判断
      • 3.2.2 针对响应时的判断
    • 3.3 SpringMVC框架默认的消息转换器
      • 3.3.1 源码跟踪
  • 四、自定义消息转换器
    • 4.1 自定义yaml消息转换器
      • 4.1.1 引入如下的依赖
      • 4.1.2 自定义yaml媒体类型
      • 4.1.3 自定义HttpMessageConverter
      • 4.1.4 配置消息转换器
      • 4.1.5 测试与效果验证
  • 五、写在文末

    一、前言

    在微服务开发中,客户端与服务端数据格式的协商和转换是一个经常接触的场景,不同的业务场景下,对于数据格式的要求也不同,比如有的客户端需要服务器响应XML格式数据,有的需要响应Json格式数据,这就是HTTP消息内容协商机制的源头,如何满足复杂多变的HTTP消息转换需求呢,本篇将详细分享如何在SpringBoot框架中完成自定义消息转换器的定制开发与使用。

    二、SpringBoot 内容协商介绍

    2.1 什么是内容协商

    内容协商(Content Negotiation)是指服务器根据客户端请求来决定响应的内容类型(MIME 类型)。这使得应用程序可以根据客户端的需求返回不同格式的数据,如 JSON、XML 或 HTML 等。Spring Boot 通过 HttpMessageConverters 和 @RequestMapping 注解等机制来支持内容协商。

    2.2 内容协商机制深入理解

    内容协商机制是指服务器根据客户端的请求来决定返回资源的最佳表现形式

    白话描述:客户端需要什么格式的数据,服务端就返回什么格式的数据。

    比如:

    • 客户端需要json,就响应json;
    • 客户端需要xml,就响应xml;
    • 客户端需要yaml,就响应yaml;

    于是,你可能会有疑问,客户端接收数据时统一采用一种格式,例如Json不就行了,为什么还有那么多的格式要求呢?因为在实际开发中并不是这样的,比如在下面的场景:

    • 遗留的老的系统中的某些业务,处理数据时仍然使用的是xml格式;
    • 对于处理速度有要求的这种系统,明确要求使用json格式的数据;
    • 对于安全要求比较高的系统,一般要求使用xml格式的数据;
    • 某些业务场景下明确指定了某个类型的数据格式...

    基于上面的场景,在当下流行的微服务开发模式下,不同的客户端可能需要后端返回不同格式的数据,于是,对于后端来说,就需要尽可能的适配和满足这种多样化的需求场景。

    2.2.1 内容协商产生的场景

    内容协商的产生具有一定的背景,下面列举了产生内容协商的一些因素

    • 多客户端支持
      • 浏览器用户可能希望看到 HTML 页面。
      • 移动应用开发者可能更倾向于使用 JSON 数据来解析和展示信息。
      • 某些旧系统或特定工具可能依赖于 XML 格式的响应。
    • 提升用户体验
      • 不同的客户端有不同的偏好和要求。允许客户端指定他们想要的内容类型可以提高交互效率,减少不必要的数据处理步骤,并确保最终呈现给用户的界面是最优化的。例如,某些设备可能更适合处理压缩过的二进制格式,而不是文本格式的数据。
    • 遵照RESTful 原则
      • 遵循 REST 架构风格的应用程序通常会根据资源的状态来确定响应的内容类型,而不是依赖于 URL 的变化。这意味着同一个 URI 可以根据请求的不同部分(如 HTTP 方法、查询参数或头部信息)返回不同类型的内容。内容协商是实现这一设计理念的关键机制之一。

    2.3 内容协商实现的常用方式

    通常来说,通过HTTP请求头(比如Accept)获取请求参数(如Format),来指定客户端偏好接收的内容类型(JSON或XML等),服务器会根据这些信息选择合适的格式进行响应。下面介绍2种比较常用的方式。

    2.3.1 前置准备

    为了后续的操作演示,请提前在工程中导入下面几个基础依赖

     <properties>
            <maven.compiler.source>17</maven.compiler.source>
            <maven.compiler.target>17</maven.compiler.target>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>3.2.4</version>
            <relativePath/>
        </parent>
        <dependencies>
            <dependency>
                <groupId>com.fasterxml.jackson.dataformat</groupId>
                <artifactId>jackson-dataformat-yaml</artifactId>
            </dependency>
            <dependency>
                <groupId>com.fasterxml.jackson.dataformat</groupId>
                <artifactId>jackson-dataformat-xml</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <scope>provided</scope>
            </dependency>
        </dependencies>
    </project>

    2.3.2 通过HTTP请求头

    SpringBoot框架中,如果开发人员不做任何配置的情况下,优先使用这种方式。

    服务器会根据客户端发送请求时提交在请求头中的信息,比如:”Accept:application/json“或"Accept:text/html"来决定最终响应什么格式数据;

    2.3.2.1 操作示例

    添加一个接口

    @RestController
    public class UserController {
        //localhost:8081/getUser
        @GetMapping("/getUser")
        public Object getUser(){
            return new User("mike",18);
        }
    }

    正常调用,请求头不加任何参数默认得到的是json结构

    如果在请求头指定响应的数据格式,如下,在Accept中指定是json

    curl -H "Accept: application/json" localhost:8081/getUser

    如果此时我们指定返回xml格式的数据,此时发现并不好使

    curl -H "Accept: application/xml" localhost:8081/getUser

    如果需要支持该怎么办呢?需要做下面的2步:

    1)添加依赖jackson-dataformat-xml

    可以将Java对象转为xml格式的数据

    <dependency>
        <groupId>com.fasterxml.jackson.dataformat</groupId>
        <artifactId>jackson-dataformat-xml</artifactId>
    </dependency>

    2)为实体类增加注解

    在当前的User类上面添加注解 @JacksonXmlRootElement用于转换为xml

    package com.congge.entity;
    import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    @JacksonXmlRootElement
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
        private String name;
        private int age;
    }

    3)请求测试调用

    指定为json

    指定为xml

    总结:

    客户端请求的时候,通过在请求协议的请求头上面增加一个Accept字段,服务端接收到这个值之后,会根据这个参数值动态返回客户端要求的格式的数据;

    2.3.3 通过请求参数

    也可以在请求url中拼接指定的请求参数的方式实现,默认的请求参数名为format,格式如下:

    http://xxx?format=json

    仍然以上面的接口为例,测试一下这种方式的使用

    curl http://localhost:8081/getUser?format=json

    效果如下:

    但是如果指定format为xml,发现并不生效

    原因是springboot中在内容协商的处理上,优先使用Accept这种方式,所以如果你要使用format这种方式,还需在配置文件中增加下面的配置信息;

    #使用format的方式完成内容协商,如果没有配置,默认采用Accept的方式实现
    spring:
      mvc:
        contentnegotiation:
          favor-parameter: true
          #默认就叫format,也可以改为自定义的名称
          parameter-name: format

    设置完成后再次重启服务测试,此时可以看到两种格式的数据都支持

    三、SpringBoot 消息转换器介绍

    在上面通过案例操作演示介绍了什么是spring框架的内容协商机制,简单来说就是,客户端需要什么样格式的数据,服务端就响应什么格式数据,事实上真的就那么简单吗?这背后框架做了什么呢?是不是有什么组件在这个转换的过程中起作用了呢?接下来就要详细介绍springmvc框架中对于内容协商的重要技术组件,即HttpMessageConvertor。

    3.1 HttpMessageConvertor介绍

    HttpMessageConvertor是一个接口,被翻译为HTTP消息转换器,即对HTTP消息进行转换,什么是HTTP消息呢?HTTP消息本质上就是浏览器向服务端发送请求时提交的数据,或者是服务器向浏览器响应的数据。而HttpMessageConvertor接口就是负责完成请求/响应时数据格式转换用的。

    在springmvc框架中提供了很多种HttpMessageConvertor接口的实现类,不同的HTTP消息转换器具有不同的转换效果,使用的场景也有区别,有的是负责将Java对象转为JSON格式的数据,有的负责将Java对象转为XML格式的数据。

    3.1.1 常用的HttpMessageConvertor

    springmvc框架内置了一些常用的消息转换器,正是这些转换器完成了诸如上述json或xml格式的数据转换,下面介绍一些常用的框架内置的消息转换器:

    FormHttpMessageConvertor

    常用于处理提交表单数据时候使用的转换器;

    MappingJackson2HttpMessageConvertor

    客户端或浏览器提交JSON格式的数据转换为JAVA对象主要是由这个转换器处理,比如经常在POST请求接口上面添加的@RequestBody注解;

    JaxbRootElementHttpMessageConvertor

    将JAVA对象转为XML格式的数据通常由这个消息转换器完成;

    StringHttpMessageConvertor

    将String类型的的数据直接写入到响应中由这个转换器完成;

    3.2 如何确定使用哪个消息转换器

    有这么多的消息转换器,那么在具体使用的时候,框架是如何确定使用哪种类型的转换器的呢?

    3.2.1 针对请求时的判断

    请求时通常根据下面的条件来确定使用哪个消息转换器:

    请求的Content-Type头信息

    SpringMVC会检查Content-Type头信息,以确定请求体的数据格式,比如:application/json,application/xml...

    方法参数类型

    控制器方法中接收请求体的参数类型,比如POST请求中有@RequestBody注解;

    3.2.2 针对响应时的判断

    响应时通常根据以下条件来确定使用哪个消息转换器:

    请求提交时,请求头上的Accept字段

    Spring MVC 会检查客户端请求的 Accept 字段,以确定客户端期望的响应格式(例如 application/json、application/xml 等);

    方法返回值的类型

    控制器方法的返回值类型比如: @ResponseBody

    @ResponseBody + 控制器方法的返回值是String,则使用StringHttpMessageConverter转换器。(将字符串直接写入响应体)

    @ResponseBody + 控制器方法的返回值是Java对象,则使用MappingJackson2HttpMessageConverter转换器。(将java对象转换成json格式的字符串写入到响应体)

    3.3 SpringMVC框架默认的消息转换器

    SpringMVC框架自身已经内置了一些消息转换器,可以在启动的时候debug源码看到,主要包括下面6个

    ByteArrayHttpMessageConverter

    用于将字节数组(byte[])与HTTP消息体之间进行转换。这通常用于处理二进制数据,如图片或文件。

    StringHttpMessageConverter

    用于将字符串(String)与HTTP消息体之间进行转换。它支持多种字符集编码,能够处理纯文本内容。

    ResourceHttpMessageConverter

    用于将Spring的Resource对象与HTTP消息体之间进行转换。Resource是Spring中表示资源的接口,可以读取文件等资源。这个转换器对于下载文件或发送静态资源有用。

    ResourceRegionHttpMessageConverter

    用于处理资源的部分内容(即“Range”请求),特别是当客户端请求大文件的一部分时。这对于实现视频流媒体等功能很有用。

    AllEncompassingFormHttpMessageConverter

    用于处理表单,是一个比较全面的form消息转换器。处理标准的application/x-www-form-urlencoded格式的数据,以及包含文件上传的multipart/form-data格式的数据。

    MappingJackson2HttpMessageConverter

    使用Jackson库来序列化和反序列化JSON数据。可以将Java对象转换为JSON格式的字符串,反之亦然。

    3.3.1 源码跟踪

    入口类:WebMvcAutoConfiguration

    WebMvcAutoConfiguration内部类EnableWebMvcConfiguration

    EnableWebMvcConfiguration继承了DelegatingWebMvcConfiguration

    DelegatingWebMvcConfiguration继承了WebMvcConfigurationSupport

    DelegatingWebMvcConfiguration

    继续进入到WebMvcConfigurationSupport

    在这个类中,提供了一个方法 addDefaultHttpMessageConverters,在这个方法中,会将工程中的所有的消息转换器加进去。下面通过debug源码的方式跟进一下过程。

    启动springboot工程后,进入该方法,此时messageConverters这个列表还是空的

    从源码不难看出,方法中会new出几个内置的转换器加入到这个集合中

    在上面的方法中,注意到会有一个判断的方法,比如:jackson2XmlPresent,它是如何判断的呢?其实在当前的类中,在静态代码块中维护了一个全局的布尔变量,工程在加载的时候,通过ClassUtils.isPresent方法,传入类的全路径,从而判断是否满足条件,满足,则在addDefaultHttpMessageConverters方法执行时候加入进去。

    最后在这个方法执行完成的时候,列表中就添加了一些消息转换器

    通过debug源码不难看出,在实际开发中,只要引入相关的依赖,让类路径存在某个类,则对应的消息转换器就会被加载。

    四、自定义消息转换器

    实际项目开发过程中,来自客户端的需求场景是很多的,当系统内置的转换器格式不能满足要求时,比如需要返回yaml格式的数据,或者其他定制化类型的数据时,此时就可以考虑自定义消息转换器。下面以yaml这种特殊格式的数据为例进行说明。

    4.1 自定义yaml消息转换器

    下面看具体的操作步骤。

    4.1.1 引入如下的依赖

    任何一个能够处理yaml格式数据的库都可以,这里选择使用jackson的库,因为它既可以处理json,xml,又可以处理yaml。

    <dependency>
          <groupId>com.fasterxml.jackson.dataformat</groupId>
          <artifactId>jackson-dataformat-yaml</artifactId>
    </dependency>

    通过下面这段程序测试一下这个SDK的转换效果

     
    import com.congge.entity.User;
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
    import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
    public class JavaYamlTest {
        public static void main(String[] args) throws JsonProcessingException {
            // 创建YAML工厂类
            YAMLFactory yamlFactory = new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER); // 禁止使用文档头标记
            // 创建对象映射器
            ObjectMapper objectMapper = new ObjectMapper(yamlFactory);
            // 准备数据
            User user = new User("user01", 12);
            // 将数据转换成YAML格式
            String res = objectMapper.writeValueAsString(user);
            System.out.println(res);
        }
    }

    运行可以看到能够正常转换

    4.1.2 自定义yaml媒体类型

    Springboot 默认支持xml和json两种媒体类型,如果要支持yaml格式的,需新增一个yaml媒体类型,在springboot的配置文件中进行如下配置:

    spring:
      mvc:
        contentnegotiation:
          media-types:
            yaml: text/yaml

    注意:

    以上types后面的yaml是媒体类型的名字,名字可以自己修改,如果媒体类型起名为xyz,那么发送请求时的路径应该是这样的:http://localhost:8081/getUser?format=xyz

    4.1.3 自定义HttpMessageConverter

    编写一个类,比如:YamlHttpMessageConverter继承AbstractHttpMessageConverter,需要继承AbstractHttpMessageConverter这个类,参考下面的代码:

    package com.congge.config;
    import com.congge.entity.User;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
    import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
    import org.springframework.http.HttpInputMessage;
    import org.springframework.http.HttpOutputMessage;
    import org.springframework.http.MediaType;
    import org.springframework.http.converter.AbstractHttpMessageConverter;
    import org.springframework.http.converter.HttpMessageNotReadableException;
    import org.springframework.http.converter.HttpMessageNotWritableException;
    import java.io.IOException;
    import java.nio.charset.Charset;
    public class YamlHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
        private ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER));
        /**
         * 将自定义的消息转换器 和 配置文件中自定义的媒体类型 text/yaml 进行绑定
         */
        public YamlHttpMessageConverter() {
            super(new MediaType("text", "yaml", Charset.forName("UTF-8")));
        }
        /**
         * 用于指定消息转换器支持哪些类型的对象转换,比如这里指定User对象类型的数据进行转换
         * @param clazz
         * @return
         */
        @Override
        protected boolean supports(Class<?> clazz) {
            // 表示User类型的数据支持yaml,其他类型不支持
            return User.class.isAssignableFrom(clazz);
        }
        /**
         * 处理 @RequestBody(将提交的yaml格式数据转换为java对象)
         * @param clazz
         * @param inputMessage
         * @return
         * @throws IOException
         * @throws HttpMessageNotReadableException
         */
        @Override
        protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
            return null;
        }
        /**
         * 处理 @ResponseBody(将java对象转换为yaml格式的数据)
         * @param o
         * @param outputMessage
         * @throws IOException
         * @throws HttpMessageNotWritableException
         */
        @Override
        protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
            this.objectMapper.writeValue(outputMessage.getBody(), o);
            // 注意:spring框架会自动关闭输出流,无需程序员手动释放。
        }
    }

    补充说明:

    所有的消息转换器,包括自定义的,都需要实现HttpMessageConverter接口,或者继承AbstractHttpMessageConverter这个类,重写里面的核心方法。

    4.1.4 配置消息转换器

    重写WebMvcConfigurer接口的configureMessageConverters方法,将上面的自定义消息加入到全局的转换器列表中。

    package com.congge.config;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.converter.HttpMessageConverter;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    import java.util.List;
    @Configuration
    public class ConverterWebConfig implements WebMvcConfigurer {
        @Override
        public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
            converters.add(new YamlHttpMessageConverter());
        }
    }

    4.1.5 测试与效果验证

    启动工程,通过下面的curl命令再次测试,可以看到通过上面的自定义改造已经能够输出yaml格式的数据了

    curl -H "Accept: text/yaml" localhost:8081/getUser

    针对其他类型格式的转换器,也可以参照上面的步骤进行编写即可

    五、写在文末

    本文详细介绍了SpringBoot消息转换器的知识,并通过案例操作演示了如何进行自定义消息转换器的定制开发和使用,希望对看到的同学有用哦,本篇到此结束,感谢观看。

    到此这篇关于SpringBoot 自定义消息转换器使用详解的文章就介绍到这了,更多相关SpringBoot 自定义消息转换器内容请搜索骃骐网以前的文章或继续浏览下面的相关文章希望大家以后多多支持骃骐网!

    您可能感兴趣的文章:

    • SpringBoot HttpMessageConverter消息转换器的使用详解
    • SpringBoot自定义转换器用法详解
    • jackson在springboot中的使用方式-自定义参数转换器
    • 使用Springboot自定义转换器实现参数去空格功能
    • SpringBoot日期格式转换之配置全局日期格式转换器的实例详解
    • springboot日期转换器实现实例解析

    相关推荐:

    如何在Python中解决PyTorch自定义C++扩展编译失败_检查ATen库链接

    这是典型的ATen符号链接失败,主因是自定义扩展未正确传递编译/链接标志:需在setup.py中显式包含include_paths()和library_paths(),确保链接-ltorch_cpu -ltorch -lc10(顺序不可颠倒),并根据torch.__config__.show()结果在C++源码顶部定义对应ATEN_CPU_CAPABILITY宏;同时统一C++标准为c++17、启...

    怎样在Python Tkinter中使用自定义字体_通过tkFont模块注册本地字体文件

    Python 3 中 tkFont 已被移除,需改用 tkinter.font;Tkinter 不支持直接加载本地字体文件,必须先通过系统安装字体,再用 font.families() 确认名称后使用。 tkFont 在 Python 3 中已不存在,改用 font 模块 Python 3.0 起 tkFont 模块被移除,所有字体操作统一由 tkinter.font 模块提供。如果你在代码里写了...

    Python连续变量怎么进行分箱离散化_KBinsDiscretizer自定义区间划分

    应放弃KBinsDiscretizer,改用继承BaseEstimator和TransformerMixin的自定义分箱器,将业务边界存为self.bins,fit仅校验,transform用np.digitize确保训练/测试集边界一致。 KBinsDiscretizer 默认等宽分箱不满足业务需求怎么办 默认 strategy="uniform" 会把数据范围线性切分成 k 段,但实际中常需要...

    如何为 CTkInputDialog 自定义窗口图标

    CustomTkinter 的 CTkInputDialog 默认不支持直接设置图标,但可通过 iconphoto() 方法配合 after() 延迟调用实现;关键在于延迟至少 200ms,否则系统会覆盖设置。 customtkinter 的 ctkinputdialog 默认不支持直接设置图标,但可通过 `iconphoto()` 方法配合 `after()` 延迟调用实现;关键在于延迟至少 2...

    如何为 CTkInputDialog 自定义标题栏图标

    CustomTkinter 的 CTkInputDialog 默认不提供直接设置图标参数,但可通过 iconphoto() 方法配合 after() 延迟调用实现图标替换,避免因内部初始化覆盖导致失效。 customtkinter 的 ctkinputdialog 默认不提供直接设置图标参数,但可通过 `iconphoto()` 方法配合 `after()` 延迟调用实现图标替换,避免因内部初始化...

    如何解决Python Flask中Decimal类型无法序列化JSON_自定义JSONEncoder

    必须重写JSONEncoder,因jsonify依赖json.dumps默认不支持Decimal;需继承flask.json.JSONEncoder并重写default方法,对Decimal转float后交由父类处理,再通过app.json_encoder全局注册。 直接结论:必须重写 JSONEncoder,不能靠 int() 或 float() 强转字段再传给 jsonify —— 那只是掩盖...

    怎样处理Python Flask项目中的404错误_自定义errorhandler捕获

    @app.errorhandler(404)必须在app.run()前注册,且需显式返回状态码404;若在工厂函数、条件分支或蓝图中错误注册则失效,全局处理须用@app.errorhandler而非@bp.errorhandler。 @app.errorhandler(404) 必须在 app.run() 前注册 Flask 的 @app.errorhandler(404) 不是“写完就生效”的装...

    Python数据标准化用哪个方案好_StandardScaler与MinMaxScaler详解

    StandardScaler不适用于明显偏态或含大量异常值的数据,因其依赖的均值和标准差易被离群点污染,导致正常数据被压缩到极窄范围,影响模型性能;必须训练集fit_transform、测试集transform,避免数据泄露。 StandardScaler 什么时候不能用 当你的数据明显偏离正态分布,或者存在大量异常值时,StandardScaler 的效果会变差。它依赖均值和标准差,而这两个统计...

    如何在Python中实现PyTorch自定义损失函数_继承_Loss类或直接编写函数

    必须继承 nn.Module,不能只写纯函数;因训练循环依赖 loss.backward(),仅 nn.Module 实例返回的 Tensor 才能保留计算图,普通函数易致梯度静默失败或 RuntimeError。 PyTorch自定义损失函数:该继承 nn.Module 还是写纯函数? 直接说结论:**必须继承 nn.Module(或其子类 nn.Loss)**,不能只写一个普通 Python ...

    Django Admin 自定义关联字段的自动完成筛选器实现教程

    本文介绍如何在 django admin 中为多层外键关联(如 project → person → subject)添加支持搜索的自动完成下拉筛选器,替代默认的静态列表,提升后台数据过滤体验。 本文介绍如何在 django admin 中为多层外键关联(如 project → person → subject)添加支持搜索的自动完成下拉筛选器,替代默认的静态列表,提升后台数据过滤体验。 在 Dj...