Série 2 de gerenciamento de automação de interface Tianqiong-Api: explicação detalhada do scanner de interface multiprotocolo MiApi

Endereço de código aberto

https://github.com/XiaoMi/mone/tree/master/miapi-all/mi-api Bem-vindos amigos interessados ​​em tecnologia nativa da nuvem /eficiência de P&D para se juntarem a nós (Fork, Star).

Visão geral

Na introdução geral da plataforma do artigo anterior, apresentamos os principais recursos e processos fornecidos pela plataforma e descrevemos aproximadamente a lógica principal para obter e gerar informações de dados de interface no carregamento e geração de informações de interface de serviço . Este artigo se aprofundará no nível do código-fonte para apresentar como implementamos a digitalização, análise e geração de dados de documentos de interface.

Pacote de dependência de negócios

No artigo anterior, mencionamos os principais componentes/módulos para obtenção de dados de projetos de negócios, nomeadamente anotações, scanners e caches .

Para obter os dados básicos da interface do projeto de negócios que precisamos acima, precisamos que o lado comercial introduza um conjunto de pacotes de dependência fornecidos por nós. Fornecemos pacotes de dependência direcionados para serviços de diferentes interfaces de protocolo, como fornecer interface Http e Interface Dubbo. Para os projetos, projetamos e fornecemos pacotes de dependências adaptados para Http e Dubbo respectivamente. Esses pacotes serão usados ​​para analisar dados de diferentes interfaces de protocolo, envio de dados, registro de serviço, etc.

Definição de anotação

efeito

As anotações são usadas para fazer algumas marcas específicas no projeto, como marcas de camada de módulo, marcas de camada de interface, marcas de camada de campo, etc., para posterior digitalização do scanner e processo de análise.

concluir

No início do design, primeiro deixamos claro quais dados precisávamos com base no processo de entrega da interface que definimos. Esperamos que, após a execução do projeto de negócios, o pessoal de P&D possa pesquisar informações sobre seus próprios módulos de projeto na plataforma e carregar e gerar dados de interface correspondentes com base nas informações do módulo selecionado. Esperamos que os módulos aqui sejam classes com as quais os desenvolvedores estão acostumados e claramente definidas no código.

Por exemplo, para uma interface Http, baseada na arquitetura mvc tradicional, normalmente a entrada da interface será implementada na classe xxxController, por exemplo:

@RestController
@RequestMapping()
public class HelloController {

@RequestMapping(path = "/hello",method = RequestMethod.POST)
public Response<String> getToken() {
    Response<String> r = new Response<>();
    r.setData("hello");
    return r;
}
}

Então, esperamos que os usuários possam pesquisar diretamente a palavra-chave HelloController na caixa de pesquisa para ver todas as informações do módulo e da interface do projeto e selecionar para carregar e gerar documentos de acordo.

Para uma interface Dubbo, é o seguinte:

public interface DubboHealthService {
    Result<Health> health2(AaReq aaReq);
}


@DubboService(timeout = 1000, group = "staging",version = "1.0")
@Slf4j
public class DubboHealthServiceImpl implements DubboHealthService {

    @Override
    public Result<Health> health(AaReq aaReq) {
        Health health = new Health();
        health.setVersion("1.0");
        return Result.success(health);
    }

Esperamos que o módulo pesquisável seja uma definição de interface, ou seja, DubboHealthService.Os usuários só precisam pesquisar a interface para obter uma lista de todas as interfaces e seus métodos no projeto, e filtrar e carregar para gerar documentos baseados nessas duas.

Em resumo, para os dados de interface que precisamos obter, fornecemos diversas anotações básicas no pacote de dependência de negócios: @EnableApiDocs, @ApiModule, @ApiDoc, @ParamDefine. (Para diferentes interfaces de protocolo, a nomenclatura da anotação é ligeiramente diferente)

  • @EnableApiDocs

  • @EnableApiDocs é usado para iniciar a classe Bootstrap e é usado como um switch. O usuário pode decidir se deseja habilitar a função de varredura de dados e push de acordo com a adição ou não do switch.

@EnableAutoConfiguration
@ComponentScan(basePackages = {"com.xxx.xxx.hello", "com.xxx.xxx"})
@DubboComponentScan(basePackages = "com.xxx.xxx.hello")
@ServletComponentScan
@EnableDubboApiDocspublic class HelloBootstrap {
    private static final Logger logger = LoggerFactory.getLogger(HelloBootstrap.class);

    public static void main(String... args) {
        try {
            SpringApplication.run(HelloBootstrap.class, args);
        } catch (Throwable throwable) {
            logger.error(throwable.getMessage(), throwable);
            System.exit(-1);
        }
    }
}

Por exemplo, o projeto acima que fornece a interface dubbo só precisa adicionar a anotação @EnableDubboApiDocs à classe de inicialização para habilitar esta função.

A implementação desta anotação de classe de inicialização também é muito simples:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Inherited
@Import({DubboApiDocsAnnotationScanner.class})
public @interface EnableDubboApiDocs {
}

Na verdade, apenas @Importamos a classe do scanner no pacote de dependência nesta anotação. Então, contanto que adicionemos essa anotação, o Spring nos ajudará a inicializar a classe do scanner no contêiner e as ações subsequentes serão executadas pelo scanner.

  • @ApiModule

  • Esta anotação é usada para anotar classes de módulos e sua implementação é a seguinte:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
public @interface ApiModule {

    /**
     * 用于自定义模块名
     */    String value();

    /**
    * 用于定位模块类的类型
     * dubbo api interface class
    * 若为http接口,则该选项为 apiController
     */    Class<?> apiInterface();

}

Esta anotação será usada para filtrar informações de classe que precisam ser analisadas do contêiner Spring durante o processo de varredura do scanner e também fornecer informações básicas sobre essas classes.

O uso normal da interface HTTP é o seguinte:

@RestController
@RequestMapping()
@HttpApiModule(value = "这是一个controller类HelloController", apiController = HelloController.class)
public class HelloController {
}

O uso normal da interface Dubbo é o seguinte:

@DubboService(timeout = 1000, group = "staging",version = "1.0")
@Slf4j
@ApiModule(value = "健康检测服务", apiInterface = DubboHealthService.class)public class DubboHealthServiceImpl implements DubboHealthService {
}
  • @ApiDoc

  • Esta anotação é usada para marcar a interface específica que precisa gerar documentos e é implementada da seguinte forma:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface ApiDoc {

    /**
     * api name.用于自定义接口名
     */    String value();

    /**
     * api description.自定义接口描述文档
     */    String description() default "";

}

O uso comum é o seguinte:

@Override
@ApiDoc(value = "健康监测方法",description = "这是一个用于健康监测的方法")
public Result<Health> health(AaReq aaReq) {
    Health health = new Health();
    health.setVersion("1.0");
    return Result.success(health);
}
  • @ParamDefine

  • Esta anotação é usada para definir campos de parâmetros específicos e é implementada da seguinte forma:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Documented
@Inherited
public @interface ParamDefine {

    /**
     * 参数名
     */String value();

    /**
     * 该参数是否必填
     */boolean required() default false;

    /**
     * 是否忽略该字段,若是,在生成文档时该字段将被忽略
     */boolean ignore() default false;
    
    /**
     * 字段说明描述
     */
    String description() default "";


    /**
     * 默认值,若有默认值,将以该值生成mock数据
     */String defaultValue() default "";

}

O uso comum é o seguinte:

@Data
public class AaReq implements Serializable {
    @ApiDocClassDefine(value = "用户名a",required = true,description = "这里是用户名参数",defaultValue = "dongzhenixng")
    private String username;

    @ApiDocClassDefine(value ="年龄",required = false,description = "用户年龄",defaultValue = "23")
    private Integer age;
    
   /**
* 也可以不使用该字段,平台将默认提取该字段基本信息,如参数名、类型等
     */    private double age21;

}

scanner

efeito

O scanner é o núcleo deste projeto. Com base na capacidade de reflexão do jdk, obtemos os dados de informações de serviço e interface do projeto em tempo de execução.

concluir

O scanner é importado para o contêiner Spring pela opção @EnableApiDocs. Como precisamos usar os dados do bean após a inicialização do contêiner Spring como destino de análise, a ação de varredura e análise deve ocorrer após o Spring concluir a operação básica de inicialização. Portanto, A interface aberta ApplicationListener do spring é implementada aqui . Aqui recebemos o ApplicationReadyEvent, que é acionado após a inicialização do framework spring e completa as informações básicas do projeto.

public class ApiDocsScanner implements ApplicationListener<ApplicationReadyEvent> {

@Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
    //扫描解析逻辑......
}
}

Após a inicialização do Spring ser concluída, as informações do bean do projeto são mantidas no contexto Spring ApplicationContext. Em seguida, a coleção de módulos marcada com @ApiModule pode ser obtida do scanner e, em seguida, uma determinada informação de classe marcada com a anotação pode ser obtida um por um por meio de reflexão., construa a estrutura de dados do módulo, em seguida, obtenha a lista de métodos em cada módulo por meio de reflexão, filtre os métodos com a anotação @ApiDoc , construa a estrutura de dados da camada de método de interface e, em seguida, processe ainda mais os métodos marcados com a anotação.

No processamento em nível de método, a reflexão também é usada para obter informações específicas do campo de parâmetro.Aqui, a estrutura de dados em nível de campo é construída recursivamente de acordo com o tipo de campo. Claro, a operação de análise aqui é mais complicada e complicada. Precisamos diferenciar e realizar análise direcionada para diferentes tipos de parâmetros. Por exemplo, como lidar com parâmetros de tipo básico? Como lidar com objetos, Listas, Mapas, Conjuntos, Filas e até parâmetros genéricos aninhados? Quais tipos não podem ser repetidos e repetidos? Quais precisam ser ignorados... Levamos muita consideração e compatibilidade com esses detalhes durante o processo de desenvolvimento e teste. Não os apresentaremos em detalhes aqui. Amigos interessados ​​podem dar uma olhada em nosso código-fonte aberto.

Depois de concluir a varredura e análise de dados das etapas acima, agregue todos os dados de acordo com certas regras, em seguida, obtenha o IP local e a porta usada quando o projeto está em execução, chame a interface aberta fornecida pela plataforma e envie os dados acima para o plataforma de forma unificada, e a plataforma usará ip:port é um índice exclusivo para armazenar dados no banco de dados da plataforma. A razão pela qual ip:port é usado como o único índice aqui é porque projetos gerais de negócios de microsserviços, estejam eles em processo de desenvolvimento ou liberados para o ambiente de teste ou ambiente de produção, provavelmente terão múltiplas instâncias, ou seja, o mesmo projeto será usado por várias partes, executado em vários locais, as versões de código e o progresso de desenvolvimento de diferentes instâncias podem não ser completamente consistentes.Portanto, esperamos que os usuários possam selecionar uma instância específica na plataforma e carregar e gerar os dados de interface dessa instância de maneira direcionada.

Fluxograma de execução do scanner

Acho que você gosta

Origin blog.csdn.net/shanwenbang/article/details/128871851
Recomendado
Clasificación