用Rust生成Ant-Design Table Columns | 京东云技术团队
经常开发表格,是不是已经被手写Ant-Design Table的Columns整烦了?
尤其是ToB项目,表格经常动不动就几十列。每次照着后端给的接口文档一个个配置,太头疼了,主要是有时还会粘错就尴尬了。
那有没有办法能自动生成columns配置呢?
当然可以。
目前后端的接口文档一般是使用Swagger来生成的,Swagger是基于OpenAPI规范的一种实现。(OpenAPI
规范是一种描述RESTful API的语言无关的格式,它允许开发者定义API的操作、输入和输出参数、错误响应等信息,并提供了一种规范的方式来描述和交互API。)
那么我们只需要解析Swagger的配置就可以反向生成前端代码。
接下来我们就写个CLI工具来生成Table Columns。
平常我们实现一个CLI工具一般都是用Node,今天我们搞点不一样的,用Rust。
开始咯
swagger.json
打开后端用swagger生成的接口文档中的一个接口,一般是下面这样的,可以看到其json配置文件,如下图:
swagger: 2.0
表明了这个文档使用的swagger版本,不同版本json配置结构会不同。
paths
这里key是接口地址。
可以看到当前接口是“/api/operations/cate/rhythmTableList”。
顺着往下看,“post.responses.200.schema.originalRef”,这就是我们要找的,这个接口对应的返回值定义。
definitions
拿到上面的返回值定义,就可以在“definitions”里找到对应的值。
这里是“definitions.ResponseResult«List«CateInsightRhythmListVO»».properties.data.items.originalRef”
通过他就可找到返回的实体类定义CateInsightRhythmListVO
CateInsightRhythmListVO
这里就是我们生成Table Columns需要的字段定义了。
CLI
接下来制作命令行工具
起初我使用的是commander-rust,感觉用起来更符合直觉,全程采用macros定义即可。
但到发布的时候才发现,Rust依赖必须有一个确定的版本,commander-rust目前使用的是分支解析。。。
最后还是换了clap
clap的定义就要繁琐些,如下:
#[derive(Parser)] #[command(author, version)] #[command(about = "swagger_to - Generate code based on swagger.json")] struct Cli { #[command(subcommand)] command: Option, } #[derive(Subcommand)] enum Commands { /// Generate table columns for ant-design Columns(JSON), } #[derive(Args)] struct JSON { /// path/to/swagger.json path: Option, }
这里使用#[command(subcommand)]
和#[derive(Subcommand)]
来定义columns子命令
使用#[derive(Args)]
定义了path参数,用来让用户输入swagger.json的路径
实现columns子命令
columns命令实现的工作主要是下面几步:
-
读取用户输入的swagger.json
-
解析swager.json
-
生成ant-design table columns
-
生成对应Typescript类型定义
读取用户输入的swagger.json
这里用到了一个crate,serde_json
, 他可以将swagger.json转换为对象。
let file = File::open(json).expect("File should open"); let swagger_json: Value = serde_json::from_reader(file).expect("File should be proper JSON");
解析swager.json
有了swagger_json对象,我们就可以按照OpenAPI的结构来解析它。
/// openapi.rs pub fn parse_openapi(swagger_json: Value) -> Vec { let paths = swagger_json["paths"].as_object().unwrap(); let apis = paths .iter() .map(|(path, path_value)| { let post = path_value["post"].as_object().unwrap(); let responses = post["responses"].as_object().unwrap(); let response = responses["200"].as_object().unwrap(); let schema = response["schema"].as_object().unwrap(); let original_ref = schema["originalRef"].as_str().unwrap(); let data = swagger_json["definitions"][original_ref]["properties"]["data"] .as_object() .unwrap(); let items = data["items"].as_object().unwrap(); let original_ref = items["originalRef"].as_str().unwrap(); let properties = swagger_json["definitions"][original_ref]["properties"] .as_object() .unwrap(); let response = properties .iter() .map(|(key, value)| { let data_type = value["type"].as_str().unwrap(); let description = value["description"].as_str().unwrap(); ResponseDataItem { key: key.to_string(), data_type: data_type.to_string(), description: description.to_string(), } }) .collect(); Api { path: path.to_string(), model_name: original_ref.to_string(), response: response, } }) .collect(); return apis; }
这里我写了一个parse_openapi()
方法,用来将swagger.json解析成下面这种形式:
[ { path: 'xxx', model_name: 'xxx', response: [ { key: '字段key', data_type: 'number', description: '字段名' } ] } ]
对应的Rust结构定义是这样的:
pub struct ResponseDataItem { pub key: String, pub data_type: String, pub description: String, } pub struct Api { pub path: String, pub model_name: String, pub response: Vec<ResponseDataItem>, }
生成ant-design table columns
有了OpenAPI对象就可以生成Table Column了,这里写了个generate_columns()
方法:
/// generator.rs pub fn generate_columns(apis: &mut Vec) -> String { let mut output_text = String::new(); output_text.push_str("import type { ColumnsType } from 'antd'\n"); output_text.push_str("import type * as Types from './types'\n"); output_text.push_str("import * as utils from './utils'\n\n"); for api in apis { let api_name = api.path.split('/').last().unwrap(); output_text.push_str( &format!( "export const {}Columns: ColumnsType = [\n", api_name, api.model_name ) ); for data_item in api.response.clone() { output_text.push_str( &format!( " {{\n title: '{}',\n key: '{}',\n dataIndex: '{}',\n {}\n }},\n", data_item.description, data_item.key, data_item.key, get_column_render(data_item.clone()) ) ); } output_text.push_str("]\n"); } return output_text; }
这里主要就是采用字符串模版的形式,将OpenAPI对象遍历生成ts代码。
生成对应Typescript类型定义
Table Columns的类型使用generate_types()
来生成,原理和生成columns一样,采用字符串模版:
/// generator.rs pub fn generate_types(apis: &mut Vec) -> String { let mut output_text = String::new(); for api in apis { let api_name = api.path.split('/').last().unwrap(); output_text.push_str( &format!( "export type {} = {{\n", Some(api.model_name.clone()).unwrap_or(api_name.to_string()) ) ); for data_item in api.response.clone() { output_text.push_str(&format!(" {}: {},\n", data_item.key, data_item.data_type)); } output_text.push_str("}\n\n"); } return output_text; }
main.rs
然后我们在main.rs中分别调用上面这两个方法即可
/// main.rs let mut apis = parse_openapi(swagger_json); let columns = generator::generate_columns(&mut apis); let mut columns_ts = File::create("columns.ts").unwrap(); write!(columns_ts, "{}", columns).expect("Failed to write to output file"); let types = generator::generate_types(&mut apis); let mut types_ts = File::create("types.ts").unwrap(); write!(types_ts, "{}", types).expect("Failed to write to output file");
对于columns和types分别生成两个文件,columns.ts和types.ts。
!这里有一点需要注意
当时开发的时候对Rust理解不是很深,起初拿到parse_openapi返回的apis我是直接分别传给generate_columns(apis)和generate_types(apis)的。但编译的时候报错了:
这对于js很常见的操作竟然在Rust中报错了。原来Rust所谓不依赖运行时垃圾回收而管理变量分配引用的特点就体现在这里。
我就又回去读了遍Rust教程里的“引用和借用”那篇,算是搞懂了。这里实际上是Rust变量所有权、引用和借用的问题。读完了自然你也懂了。
看看效果
安装
cargo install swagger_to
使用
swagger_to columns path/to/swagger.json
会在swagger.json所在同级目录生成三个文件:
columns.ts
ant-design table columns的定义
types.ts
columns对应的类型定义
utils.ts
column中render对number类型的字段添加了格式化工具
Enjoy
作者:京东零售 于弘达
来源:京东云开发者社区

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
长连接:ChatGPT流式响应背后的逻辑 | 京东物流技术团队
一、前言: 提起长连接,我们并不陌生,最常见的长连接非websocket莫属了。即使没有在项目中实际用过,至少也应该有所接触。长连接指在一次网络通信中,客户端与服务器之间建立一条持久的连接,可以在多次请求和响应中重复使用该连接。这种方式的优点是减少了连接建立和关闭的开销,提高了通信效率,但需要注意控制连接的数量,避免资源浪费。短连接则是每次请求和响应都建立一个新的连接,完成后立即关闭,需要频繁进行连接建立和关闭,效率相对较低。但是这种方式更加灵活,适用于请求量较小、请求频率不高的场景。 二、背景: 最近项目在引用chatgpt智能小助手,最开始采用的是当chatgpt回答完成后一次性返回答案。但这种方式受限于网络及服务较慢的原因导致用户需要等待较长时间,极大的降低了用户的使用体验。经过项目组成员商议决定采取答案逐字返回的形式,以便于用户能更快的得到反馈。 关于长连接技术,主要考虑两种方案websocket和sse 三、原理: 1.websocket概念:WebSocket是HTML5定义的新协议,实现了服务器与客户端之间的全双工通信。WebSocket连接一旦建立,客户端和服务器端处于...
- 下一篇
并发编程-FutureTask解析 | 京东物流技术团队
1、FutureTask对象介绍 Future对象大家都不陌生,是JDK1.5提供的接口,是用来以阻塞的方式获取线程异步执行完的结果。 在Java中想要通过线程执行一个任务,离不开Runnable与Callable这两个接口。 Runnable与Callable的区别在于,Runnable接口只有一个run方法,该方法用来执行逻辑,但是并没有返回值;而Callable的call方法,同样用来执行业务逻辑,但是是有一个返回值的。 Callable执行任务过程中可以通过FutureTask获得任务的执行状态,并且可以在执行完成后通过Future.get()方式获取执行结果。 Future是一个接口,而FutureTask就是Future的实现类。并且FutureTask实现了 RunnableFuture(Runnable + Future),说明我们可以创建一个FutureTask并直接把它放到线程池执行,然后获取FutureTask的执行结果。 2、FutureTask源码解析 2.1 主要方法和属性 那么FutureTask是如何通过阻塞的方式来获取到异步线程执行的结果的呢?我们看下...
相关文章
文章评论
共有0条评论来说两句吧...