使用easyYapi生成文档
easyYapi生成文档
- 背景
- 1.安装配置
- 1.1 介绍
- 1.2 安装
- 1.3 配置
- 1.3.1 Export Postman
- 1.3.2 Export Yapi
- 1.3.3 Export Markdown
- 1.3.4 Export Api
- 1.3.6 常见问题补充
- 2. java注释规范
- 2.1 接口注释规范
- 2.2 出入参注释规范
- 3. 特定化支持
- 3.1 必填校验
- 3.2 忽略导出
- 3.3 返回不一致
- 3.4 设置header
- 3.5 设置tag
- 3.6 设置open
- 3.7 序列化相关
- 4. 自定义配置
- 5. 问题
背景
因为公司业务需要接口自动化测试,所以需要针对所有java项目的后端接口进行完整的文档梳理并同步到yapi接口管理平台,在使用swagger实操过程中,发现了一款比较好用的yapi生成工具,特别好用,不仅支持无侵入的方式生成文档,还支持定制化处理。下面一起来就通过笔者的这篇博文来了解EasyYapi这款插件的使用吧。
1.安装配置
1.1 介绍
基于java注释生成api接口文档的idea插件。
-
代码零侵入
-
通过注释生成api接口文档
-
实时生成并同步相关接口平台
-
灵活的配置规则适应项目特性
官方手册 easyYapi
1.2 安装
支持以下IDE
- 2017.3及以上版本。
从IDEA仓库中安装
Preferences(Settings)-->Plugins>Browse repositories-->find"EasyYapi"-->Install Plugin

手动下载安装:
- 下载插件 Jetbrains or Github ->
Preferences(Settings)>Plugins>Install plugin from disk...
下载地址: https://plugins.jetbrains.com/
重启IDE
1.3 配置
插件安装完成,可以直接通过在某个类或者某个文件夹下 右键->easyYapi->exportYapi

1.3.1 Export Postman
导出为postman支持的接口信息。
-
直接导出: 导出为json格式的api接口信息 可以导入Postman中
-
配置postman的token导出: 通过在
Preferences—>Other Settings—>EasyApi中配置postman对应的token 会直接同步到postman上
配置token

postman token获取方式
https://martian-spaceship-950587.postman.co/integrations/service/pm_pro_api

效果

1.3.2 Export Yapi
导出并同步到yapi服务端。
- 直接导出
首次使用: 需要配置yapi服务端地址 ip+port 和对应yapi分类中的token
1). 配置服务地址

2) . 配置yapi对应分类的token

3). token来源获取

1.3.3 Export Markdown
导出为md文件、直接导出为md文件
1.3.4 Export Api
导出各种类型的请求信息,export api可以针对某个接口进行导出。
##### 1.3.5 call api
Call Api:生成调试工具 可以进行请求调试调用。

1.3.6 常见问题补充
- yapi、postman配置错误或者变更: 可通过
Preferences—>Other Settings—>EasyApi修改。

- 导出yapi时 , 每个module需要配置相应的token, 即对应一个yapi中的项目
2. java注释规范
2.1 接口注释规范
[] 表示可选操作
/*** 分类名称* [分类备注/描述]** [@module 归属项目]*/
@RestController
@RequestMapping(value = "/pathOfCtrl")
public class MockCtrl {/*** api名称* [api描述]* [@param param1 参数1的名称或描述] 对于get请求有作用@RequestParam或者@PathVariable有效* [@param param2 可以用`@link`来表示当前参数的取值是某个枚举{@link some.enum.or.constant.class}]* [@param param3 当目标枚举字段与当前字段名不一致,额外指定{@link some.enum.or.constant.class#property1}]* [@return 响应描述]*/@RequestMapping(value = "/pathOfApi1/${orderCode}")public Result methodName1(long param1,@RequestParam String param2,@RequestParam(required = false, defaultValue = "defaultValueOfParam3") String param3){...}/*** 默认使用`application/x-www-form-urlencoded`,* 对于`@RequestBody`将使用`application/json`** 可以用注解`@Deprecated`来表示api废弃* 也可以用注释`@deprecated`* [@deprecated 改用{@link #methodName3(String)} 只能引用同一个方法]* [@deprecated 改用{@link #yapi地址}或者{@see #yapi地址}]*/@Deprecated@RequestMapping(value = "/pathOfApi2")public Result methodName2(@RequestBody MockDtoOrVo jsonModel){...}}
- GET请求的入参@RequestParam或者@PathVariable 中在注释上必须使用@param、出参使用@return。才能生成正常入参文档。
2.2 出入参注释规范
public class MockDtoOrVo {/*** 字段注释*/private Long field1;/*** 使用@see来说明当前字段的取值是某个枚举* @see some.enum.or.constant.enum*/private int field3;/*** 当目标枚举字段与当前字段名不一致,额外指定* @see some.enum.or.constant.enum#property1*/private int field4;/*** 可以用注解`@Deprecated`来表示字段被废弃* 也可以用注释`@deprecated`* @deprecated It's a secret*/@Deprecatedprivate int field5;/*** 如果使用javax.validation的话* 可以使用@NotBlank/@NotNull表示字段必须*/@NotBlank@NotNullprivate String field6;//序列化名称 @JSONField(name="aaa")@JsonAlias("aaa")@JsonProperty("aaa")private String field7;...
}
- 字段是常量或者枚举值、可以使用@see 引用枚举,注意枚举、常量对应的类也需要写对应的注释
3. 特定化支持
yapi有对应的通用配置能大致满足我们通用接口的生成,但是针对项目中一些特殊接口,yapi也提供了灵活的运用配置规则 通过自定义配配置来适应项目特性以减少代码侵入。
- 默认支持的通用配置:
Preferences—>Other Settings—>EasyApi-->Recommend

3.1 必填校验
默认配置
field.required: 用于标记字段是否为必须。 默认支持javax.validation annotations。
param.required: 用于标记API参数是否为必须。 默认支持javax.validation annotations。
#get请求入参
param.required=@javax.validation.constraints.NotBlank
param.required=@javax.validation.constraints.NotNull
param.required=@javax.validation.constraints.NotEmpty#post请求入参
field.required=@javax.validation.constraints.NotBlank
field.required=@javax.validation.constraints.NotNull
field.required=@javax.validation.constraints.NotEmpty
//get请求 入参
@GetMapping("/update/get")
public Map<String, Object> getHttp(Integer fileId) {}//get请求 参数为path
@GetMapping("/update/get/{orderCode}")
public Map<String, Object> getHttpPath(@PathVariable(name = "orderCode") Integer orderCode) {//@NotBlank @NotEmpty
//get请求 参数为path
@GetMapping("/update/get")
public Map<String, Object> getHttp(@NotNull Integer fileId) {}//post请求入参属性生成必填
public class AreaUpdateDTO {/*** 区域对象数组*/@NotNull//@NotBlank//@NotEmptyprivate List<AreaDTO> areaList;}
get请求中@RequestParam、@PathVariable修饰的请求参数生成的文档都是必填
自定义配置
//open接口有这种方法使用 @Validated 如果继续使用@NotNull等注解会破坏现有接口
public ResultBody<ContractAddResultDTO4Open> chCarCorverContractAdd(@Validated @RequestBody CarCoverPromotionContractAddDto dto){
- 自定义注解
/*** 字段必填注解标识* @author xieqx*/
@Target({ElementType.FIELD, ElementType.PARAMETER,ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
@Documented
public @interface CrmFieldRequired {
}
- 添加自定义配置规则
#设置字段必填 且支持使用自定义注解
field.required=@com.yiche.crm.base.annotations.CrmFieldRequiredparam.required=@com.yiche.crm.base.annotations.CrmFieldRequired
- 使用
//get请求
@GetMapping("/update/get")
public Map<String, Object> getHttp(@CrmRequired Integer fileId) {}//post请求入参属性生成必填
public class AreaUpdateDTO {/*** 区域对象数组*/@CrmRequiredprivate List<AreaDTO> areaList;
}
3.2 忽略导出
ignore: 整个类或者接口方法不导出。
/**
* Mock Apis
* @ignore 忽略当前类
*/
@RestController
@RequestMapping(value = "mock")
public class MockCtrl {/*** Mock String* @ignore 忽略当前api*/@GetMapping("/string")public String mockString() {return Result.success("mock string");}}
field.ignore:字段忽略不导出。
默认支持如下
#字段级别导出
field.ignore=@com.fasterxml.jackson.annotation.JsonIgnore#value
field.ignore=!@com.google.gson.annotations.Expose#serialize
- 自定义注解
/*** 字段忽略注解* @author xieqx*/
@Target({ElementType.FIELD, ElementType.PARAMETER,ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
@Documented
public @interface CrmFieIdIgnore {
}
自定义配置
field.ignore=@com.yiche.crm.base.annotations.CrmFieIdIgnore
param.ignore=@com.yiche.crm.base.annotations.CrmFieIdIgnore
3.3 返回不一致
**method.return: ** 设置方法的返回值。
常用于以下情况:
- 方法响应统一封装
- 方法返回Object
- 方法返回类型中的泛型类型未明确
<Object>/<?>/<*> - 方法返回类型与实际响应无关, 例如通过操作HttpServletResponse来返回响应
目前特殊接口
crm系统中大部分的请求出参情况如下:
//1.返回泛型未明确
public ResultBody getSpecialApprovalAttach();//2.出参与响应不一致
@CommonResult
public String getHttpPath()
@CommonResult
public PageInfo<Student> getHttpPath()
@CommonResult
public List<Student> getHttpPath() //3.下载导出
@CommonResult
public void download(HttpserveletResponse resp)
自定义配置规则
#该配置的意思是 无论返回值是怎样的只需在注释中使用@real_return 后引入真实的对象类型即可
#it.doc("real_return") 中real_return自定义字符串即可(最好项目统一)
method.return[#real_return] =groovy: "com.yiche.crm.common.rest.ResultBody<" + helper.resolveLink(it.doc("real_return")) +">"#对于只返回单个字段
#method.return.main[groovy:it.returnType().isExtend("com.yiche.crm.common.rest.ResultBody")]=data
- 使用
//1.返回泛型未明确
/*** @real_return {@link String}*/
public ResultBody getSpecialApprovalAttach(){return ResultBody.ok("hello");
}//2.出参与响应不一致
/*** @real_return {@link String}*/
@CommonResult
public String getHttpPath() {return "hello"
}
/*** @real_return {@link PageInfo<Student>}*/
@CommonResult
public PageInfo<Student> getHttpPath()/*** @real_return {@link List<Student>}*/
@CommonResult
public List<Student> getHttpPath() //3.下载导出
/*** @real_return {@link void}* 或者* @real_return {@link com.alibaba.excel.EasyExcel}*/
@CommonResult
public void download(HttpserveletResponse resp)
3.4 设置header
method.additional.header: API需要额外的header 。
- 配置
#所有接口都需要设置如下header
#method.additional.header={name: "Authorization",value: "",desc: "认证Token",required:true, example:""}api.tag=@open_header# 特定的接口需要添加header
# 对于注释使用@open_header的接口 需要特定的header(主要针对crm系统open接口)
method.additional.header[groovy:it.hasDoc("open_header")]={name: "token",value: "",desc: "认证Token",required:true}
method.additional.header[groovy:it.hasDoc("open_header")]={name: "source",value: "",desc: "来源,接口调用方",required:true}# 对于注释使用@open_yxs_header的接口 需要特定的header(主要针对crm系统open-yxs接口)
method.additional.header[groovy:it.hasDoc("open_yxs_header")]={name: "x-access-ework-token",value: "",desc: "鉴权token",required:true}
- 使用
/**** @open_header 添加open_header配置的header* @open_yxs_header 添加open_yxs_header配置的header*/
@GetMapping("/update/download")
@CommonResult
public ResultBody download(HttpServletResponse response){}
3.5 设置tag
api.tag: 标记接口,脚本中可以使用it.getDoc(“tagNam”)获取到。
- 配置
#语法 [#标记的名字] --- 注释中写@tagLabel 在yapi的tag中显示tagName
api.tag[#tagLabel]=tagNameapi.tag=@open_header
- 使用
/**** @open_header 添加open_header配置的header* @deprecated 注释中使用*/
@Deprecated //java注解
@GetMapping("/update/download")
@CommonResult
public ResultBody download(HttpServletResponse response){}
3.6 设置open
api.tag: 标记接口是否公开,从yapi导出api的时候可以选择只导出开放接口的api。
- 配置
#配置方式
api.open=#open
api.open=#myTag
- 使用
/**** @open* 或者* @myTag*/
@GetMapping("/update/download")
public Map<String, Object> areaUpdateNotice(@RequestBody AreaUpdateDTO dto) {
3.7 序列化相关
字段名称与返回的类型不一致的问题
@JSONField(name="aaa")
@JsonAlias("aaa")
@JsonProperty("aaa")
private Integer status;
easyYapi默认支持@JsonProperty(“aaa”)的转换,其他转换需要配置
#支持@JSONField注解
field.name=@com.alibaba.fastjson.annotation.JSONField#name#支持@JsonAlias注解
field.name=@com.fasterxml.jackson.annotation.JsonAlias#value
4. 自定义配置
设置字段必填 且支持使用自定义注解
field.required=@com.yiche.crm.base.annotations.YapiRequired#是否开放接口 不设置默认 非开放
api.open=#open
api.open=#xiu#设置标签
#api.tag[#xiu]=my_tag
api.tag=@open_header
api.tag=@open_yxs_header# 对于注释使用@的接口 需要特定的header(主要针对crm系统open接口)
method.additional.header[groovy:it.hasDoc("open_header")]={name: "token",value: "",desc: "认证Token",required:true}
method.additional.header[groovy:it.hasDoc("open_header")]={name: "source",value: "",desc: "来源接口调用方",required:true}# 对于注释使用@open的接口 需要特定的header(主要针对crm系统open_yxs接口)
method.additional.header[groovy:it.hasDoc("open_yxs_header")]={name: "x-access-ework-token",value: "",desc: "易小鲨认证Token",required:true}#支持@JSONField注解
field.name=@com.alibaba.fastjson.annotation.JSONField#name#支持@JsonAlias注解
field.name=@com.fasterxml.jackson.annotation.JsonAlias#value
5. 问题
-
Map、JsonObject问题处理
-
必填校验,如果多个接口使用同一个入参,必填字段不一样使用必填注解也是有问题的
-
返回单个字段 无法设置注释
-
Postman 调试添加用例
-
yapi整合测试、mock、生成测试报告
相关文章:
使用easyYapi生成文档
easyYapi生成文档 背景1.安装配置1.1 介绍1.2 安装1.3 配置1.3.1 Export Postman1.3.2 Export Yapi1.3.3 Export Markdown1.3.4 Export Api1.3.6 常见问题补充 2. java注释规范2.1 接口注释规范2.2 出入参注释规范 3. 特定化支持3.1 必填校验3.2 忽略导出3.3 返回不一致3.4 设置…...
蓝桥杯练习题总结(三)线性dp题(摆花、数字三角形加强版)
目录 一、摆花 思路一: 确定状态: 初始化: 思路二: 确定状态: 初始化: 循环遍历: 状态转移方程: 二、数字三角形加强版 一、摆花 题目描述 小明的花店新开张,为了吸…...
Elasticsearch(15) multi_match的使用
elasticsearch version: 7.10.1 multi_match是Elasticsearch中的一种查询类型,允许在一个或多个字段上执行全文本搜索,并合并各个字段的结果得分。这种查询有助于实现跨多个字段的统一搜索体验。 语法 {"query": {"multi_m…...
nodejs的线程模型和libuv库的基本使用
文章目录 nodejs中集成addon本地代码的回调问题单线程事件驱动模型libuvlibuv基本框架addon中使用libuv代码nodejs中集成addon本地代码的回调问题 在C++的代码中,回调函数是一个基本的代码调用方式。而在我自己的开发实践中,需要在addon这样一个nodejs的本地化模块中实现一个…...
Uni-app/Vue/Js本地模糊查询,匹配所有字段includes和some方法结合使用e
天梦星服务平台 (tmxkj.top)https://tmxkj.top/#/ 1.第一步 需要一个数组数据 {"week": "全部","hOutName": null,"weekendPrice": null,"channel": "门市价","hOutId": 98,"cTime": "…...
深度学习pytorch——激活函数损失函数(持续更新)
论生物神经元与神经网络中的神经元联系——为什么使用激活函数? 我们将生物体中的神经元与神经网络中的神经元共同分析。从下图可以看出神经网络中的神经元与生物体中的神经元有很多相似之处,由于只有刺激达到一定的程度人体才可以感受到刺激,…...
《苹果 iOS 应用开发与分发的关键问题解析》
一、背景 解决同事问的问题,来来回回被问好几次相同的问题,然后确认,我觉得不如写个文档 二、非研发人员安装iOS应用方式 TestFlightIPA 文件 对比 TestFlightIPA 文件安装方式TestFlight 是苹果提供的一个 beta 测试平台,开发者…...
爱上数据结构:顺序表和链表
一、线性表 线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使 用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串... 线性表在逻辑上是线性结构,也就说是连续的一条…...
python知识点总结(十)
python知识点总结十 1、装饰器的理解、并实现一个计时器记录执行性能,并且将执行结果写入日志文件中2、队列和栈的区别,并且用python实现3、设计实现遍历目录与子目录4、CPU处理进程最慢的情况通常发生在以下几种情况下:5、CPU处理线程最慢的…...
【Python】探索 Python 编程世界:常量、变量及数据类型解析
欢迎来CILMY23的博客 本篇主题为 探索 Python 编程世界:常量、变量及数据类型解析 个人主页:CILMY23-CSDN博客 Python系列专栏:http://t.csdnimg.cn/HqYo8 上一篇博客: http://t.csdnimg.cn/SEdbp C语言专栏: htt…...
vue页面实现左右div宽度,上下div高度分割线手动拖动高度或者宽度自动变化,两个div宽度或者高度拉伸调节,实现左右可拖动改变宽度的div内容显示区
实现左右或者上下div两部分拖动,宽度或者高度自动变化,实现流畅平滑的变化,还可以是实现拖动到一定宽度就不让拖动了,如果你不需要最小宽度,就直接去掉样式就行 这是页面。分左中右三部分,中间我是用来作为拖动的按钮…...
知攻善防应急靶场-Linux(1)
前言: 堕落了三个月,现在因为被找实习而困扰,着实自己能力不足,从今天开始 每天沉淀一点点 ,准备秋招 加油 注意: 本文章参考qax的网络安全应急响应和知攻善防实验室靶场,记录自己的学习过程&am…...
ffmpeg命令行
ffmpeg 如果要在linux gdb 调试,需要在configure 时候不优化 开启调试 ./configure --enable-debug --disable-optimizations make如何开启gdb 调试 gdb ffmpeg_gset args -i test.hevc -c:v copy -c:a copy output_265.mp4rh264 的流生成mp4 文件,不转…...
VMware虚拟机更换引导顺序
前言 我用wmware装了黑群晖测试,将img转成vmdisk的格式之后发现系统引导盘之后1G,有点太小了 我准备把wmware的黑群晖系统迁移到新添加的虚拟磁盘里 1.登录黑群晖的SSH 请先在黑群晖的控制面板中的终端机和SNMP里面启用SSH功能,才能使用ss…...
RAFT:让大型语言模型更擅长特定领域的 RAG 任务
RAFT(检索增强的微调)代表了一种全新的训练大语言模型(LLMs)以提升其在检索增强生成(RAG)任务上表现的方法。“检索增强的微调”技术融合了检索增强生成和微调的优点,目标是更好地适应各个特定领…...
Stable Diffusion 本地训练端口与云端训练端口冲突解决办法
方法之一,修改本地训练所用的端口 1 首先,进入脚本训练器的根目录 例如:C:\MarkDeng\lora-scripts-v1.7.3 找到gui.py 2 修改端口号 因为云端训练器也是占用28000和6006端口 那么本地改成27999和6007也是可以的 保存退出,运行启动…...
C++学习day1
思维导图 定义自己的命名空间,其中有string类型的变量,再定义两个函数,一个函数完成字符串的输入,一个函数完成求字符串长度,再定义一个全局函数完成对该字符串的反转 #include <iostream> using namespace std;…...
openGauss CM
CM 可获得性 本特性自openGauss 3.0.0版本开始引入。 特性简介 CM(Cluster Manager)是一款数据库管理软件,由cm_server和cm_agent组成。 cm_agent是部署在数据库每个主机上,用来启停和监控各个数据库实例进程的数据库管理组件…...
北斗短报文+4G应急广播系统:实时监控 自动预警 保护校园安全的新力量
安全无小事,生命重如山。学生是祖国的未来,校园安全是全社会安全工作的一个重要的组成部分。它直接关系到青少年学生能否安健康地成长,关系到千千万万个家庭的幸福安宁和社会稳定。 灾害事故和突发事件频频发生,给学生、教职员工…...
2024河北石家庄矿业矿山展览会|河北智慧矿山展会|河北矿博会
2024中国(石家庄)国际矿业博览会 时间:2024年7月4-6日 地点:石家庄国际会展中心.正定 随着全球经济的持续增长和矿产资源需求的不断攀升,矿业行业正迎来前所未有的发展机遇。作为矿业领域的盛会&…...
网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...
<6>-MySQL表的增删查改
目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表…...
JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...
selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...
【SpringBoot自动化部署】
SpringBoot自动化部署方法 使用Jenkins进行持续集成与部署 Jenkins是最常用的自动化部署工具之一,能够实现代码拉取、构建、测试和部署的全流程自动化。 配置Jenkins任务时,需要添加Git仓库地址和凭证,设置构建触发器(如GitHub…...
LCTF液晶可调谐滤波器在多光谱相机捕捉无人机目标检测中的作用
中达瑞和自2005年成立以来,一直在光谱成像领域深度钻研和发展,始终致力于研发高性能、高可靠性的光谱成像相机,为科研院校提供更优的产品和服务。在《低空背景下无人机目标的光谱特征研究及目标检测应用》这篇论文中提到中达瑞和 LCTF 作为多…...
