1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::ItemStruct;
use wheel_rs::str_utils::{CamelFormat, split_camel_case};
pub(crate) fn ctrl_macro(input: ItemStruct) -> TokenStream {
let struct_name = &input.ident;
// 解析结构体的名称,必须是Ctrl结尾,符合大驼峰命名规范
let struct_name_str = struct_name.to_string();
if !struct_name_str.ends_with("Ctrl") {
return syn::Error::new_spanned(struct_name, "Struct name must end with 'Ctrl'")
.to_compile_error()
.into();
}
let struct_name_split = split_camel_case(&struct_name_str, CamelFormat::Upper);
if struct_name_split.is_err() {
return syn::Error::new_spanned(
struct_name,
"Struct name must be a valid upper camel case",
)
.to_compile_error()
.into();
}
let mut struct_name_split = struct_name_split.unwrap();
struct_name_split.pop();
let entity_name = struct_name_split.join("");
let module_name = struct_name_split.join("_").to_lowercase();
let prefix = struct_name_split.remove(0).to_lowercase();
let module_path = struct_name_split.join("-").to_lowercase();
let crud_path = format!("/{prefix}/{module_path}");
let save_path = format!("{crud_path}/save");
let del_by_id_path = format!("{crud_path}/{{id}}");
let del_by_query_dto_path = crud_path.clone();
let get_by_id_path = format!("{crud_path}/{{id}}");
let get_by_query_dto_path = crud_path.clone();
let list_by_query_dto_path = format!("{crud_path}/list");
let page_by_query_dto_path = format!("{crud_path}/page");
let dto_module = format_ident!("{module_name}_dto");
let svc_name = format_ident!("{}Svc", entity_name);
let vo_name = format_ident!("{}Vo", entity_name);
let add_dto_name = format_ident!("{}AddDto", entity_name);
let modify_dto_name = format_ident!("{}ModifyDto", entity_name);
let save_dto_name = format_ident!("{}SaveDto", entity_name);
let query_dto_name = format_ident!("{}QueryDto", entity_name);
let mut generated_methods = Vec::new();
// 生成add方法
generated_methods.push(quote! {
/// # 添加新的记录
///
/// 该接口用于添加一个新的记录
///
/// ## 请求体
/// * `AddDto` - 包含记录信息的结构体
///
/// ## 请求头
/// * `USER_ID_HEADER_NAME` - 当前用户ID,必需项,类型为u64
///
/// ## 返回值
/// * 成功时返回添加后的信息的JSON格式数据
/// * 失败时返回相应的错误信息
///
/// ## 错误处理
/// * 当缺少必要参数时,返回`ValidationError`错误
/// * 当参数格式不正确时,返回`ValidationError`错误
/// * 其他业务逻辑错误将按相应规则处理
#[utoipa::path(
post,
path = #crud_path,
responses((status = OK, body = Ro<#vo_name>))
)]
#[debug_handler]
#[log_call]
pub async fn add(
headers: HeaderMap,
Json(mut dto): Json<#add_dto_name>,
) -> Result<Json<Ro<#vo_name>>, CtrlError> {
// 从header中解析当前用户ID,如果没有或解析失败则抛出ValidationError
dto._current_user_id = get_current_user_id(&headers)?;
let result = #svc_name::add::<DatabaseTransaction>(dto, None).await?;
Ok(Json(result))
}
});
// 生成modify方法
generated_methods.push(quote! {
/// # 修改记录的信息
///
/// 该接口用于修改一个已存在记录的信息
///
/// ## 请求体
/// * `ModifyDto` - 包含待修改记录信息的结构体
///
/// ## 请求头
/// * `USER_ID_HEADER_NAME` - 当前用户ID,必需项,类型为u64
///
/// ## 返回值
/// * 成功时返回修改后的信息的JSON格式数据
/// * 失败时返回相应的错误信息
///
/// ## 错误处理
/// * 当缺少必要参数时,返回`ValidationError`错误
/// * 当参数格式不正确时,返回`ValidationError`错误
/// * 其他业务逻辑错误将按相应规则处理
#[utoipa::path(
put,
path = #crud_path,
responses((status = OK, body = Ro<#vo_name>))
)]
#[debug_handler]
#[log_call]
pub async fn modify(
headers: HeaderMap,
Json(mut dto): Json<#modify_dto_name>,
) -> Result<Json<Ro<#vo_name>>, CtrlError> {
// 从header中解析当前用户ID,如果没有或解析失败则抛出ValidationError
dto._current_user_id = get_current_user_id(&headers)?;
let result = #svc_name::modify::<DatabaseTransaction>(dto, None).await?;
Ok(Json(result))
}
});
// 生成save方法
generated_methods.push(quote! {
/// # 保存记录的信息
///
/// 该接口用于保存记录的信息,如果记录不存在则创建新记录,如果记录已存在则更新记录
///
/// ## 请求体
/// * `SaveDto` - 包含记录信息的结构体
///
/// ## 请求头
/// * `USER_ID_HEADER_NAME` - 当前用户ID,必需项,类型为u64
///
/// ## 返回值
/// * 成功时返回保存后的信息的JSON格式数据
/// * 失败时返回相应的错误信息
///
/// ## 错误处理
/// * 当缺少必要参数时,返回`ValidationError`错误
/// * 当参数格式不正确时,返回`ValidationError`错误
/// * 其他业务逻辑错误将按相应规则处理
#[utoipa::path(
post,
path = #save_path,
responses((status = OK, body = Ro<#vo_name>))
)]
#[debug_handler]
#[log_call]
pub async fn save(
headers: HeaderMap,
Json(mut dto): Json<#save_dto_name>,
) -> Result<Json<Ro<#vo_name>>, CtrlError> {
// 从header中解析当前用户ID,如果没有或解析失败则抛出ValidationError
dto._current_user_id = get_current_user_id(&headers)?;
let result = #svc_name::save::<DatabaseTransaction>(dto, None).await?;
Ok(Json(result))
}
});
// 生成del_by_id方法
generated_methods.push(quote! {
/// # 删除记录
///
/// 该接口用于删除一个已存在的记录
///
/// ## 请求参数
/// * `id` - 待删除记录的唯一标识符,类型为u64
///
/// ## 错误处理
/// * 当缺少参数`id`时,返回`ValidationError`错误
/// * 当参数`id`格式不正确时,返回`ValidationError`错误
/// * 当根据ID找不到对应记录时,返回相应的错误信息
#[utoipa::path(
delete,
path = #del_by_id_path,
params(
("id" = u64, Path, description = "记录的唯一标识符")
),
responses((status = OK, body = Ro<#vo_name>))
)]
#[debug_handler]
#[log_call]
pub async fn del_by_id(
Path(id): Path<u64>,
) -> Result<Json<Ro<#vo_name>>, CtrlError> {
let ro = #svc_name::del_by_id::<DatabaseTransaction>(id, None).await?;
Ok(Json(ro))
}
});
// 生成del_by_query_dto方法
generated_methods.push(quote! {
/// # 根据查询条件删除记录
///
/// 该接口用于根据查询条件删除一个或多个已存在的记录
///
/// ## 请求体
/// * `DelByQueryDto` - 封装查询条件的结构体
///
/// ## 错误处理
/// * 当缺少必要参数时,返回`ValidationError`错误
/// * 当参数格式不正确时,返回`ValidationError`错误
/// * 当根据查询条件找不到对应记录时,返回相应的错误信息
#[utoipa::path(
post,
path = #del_by_query_dto_path,
params(#query_dto_name),
responses((status = OK, body = Ro<#vo_name>))
)]
#[debug_handler]
#[log_call]
pub async fn del_by_query_dto(Query(dto): Query<#query_dto_name>) -> Result<Json<Ro<()>>, CtrlError> {
let ro = #svc_name::del_by_query_dto::<DatabaseTransaction>(dto, None).await?;
Ok(Json(ro))
}
});
// 生成get_by_id方法
generated_methods.push(quote! {
/// # 根据ID获取记录的信息
///
/// 该接口通过查询参数中的ID获取对应记录的详细信息
///
/// ## 查询参数
/// * `id` - 记录的唯一标识符,类型为u64
///
/// ## 返回值
/// * 成功时返回对应的记录信息的JSON格式数据
/// * 失败时返回相应的错误信息
///
/// ## 错误处理
/// * 当缺少参数`id`时,返回`ValidationError`错误
/// * 当参数`id`格式不正确时,返回`ValidationError`错误
/// * 当根据ID找不到对应记录时,返回相应的错误信息
#[utoipa::path(
get,
path = #get_by_id_path,
params(
("id" = u64, Path, description = "记录的唯一标识符")
),
responses(
(status = OK, body = Ro<#vo_name>)
)
)]
#[debug_handler]
#[log_call]
pub async fn get_by_id(Path(id): Path<u64>) -> Result<Json<Ro<#vo_name>>, CtrlError> {
let ro = #svc_name::get_by_id::<DatabaseConnection>(id, None).await?;
Ok(Json(ro))
}
});
// 生成get_by_query_dto方法
generated_methods.push(quote! {
/// # 根据查询参数获取记录的信息
///
/// 该接口通过查询参数获取对应记录的详细信息
///
/// ## 查询参数
/// * `QueryDto` - 包含查询条件的结构体
///
/// ## 返回值
/// * 成功时返回对应的记录信息的JSON格式数据
/// * 失败时返回相应的错误信息
#[utoipa::path(
get,
path = #get_by_query_dto_path,
params(#query_dto_name),
responses(
(status = OK, body = Ro<#vo_name>)
)
)]
#[debug_handler]
#[log_call]
pub async fn get_by_query_dto(Query(dto): Query<#query_dto_name>) -> Result<Json<Ro<#vo_name>>, CtrlError> {
let ro = #svc_name::get_by_query_dto::<DatabaseConnection>(dto, None).await?;
Ok(Json(ro))
}
});
// 生成list_by_query_dto方法
generated_methods.push(quote! {
/// # 查询记录列表
///
/// 该接口通过查询参数获取对应记录列表的详细信息
///
/// ## 查询参数
/// * `QueryDto` - 包含查询条件的结构体
///
/// ## 返回值
/// * 成功时返回对应的记录信息的JSON格式数据
/// * 失败时返回相应的错误信息
#[utoipa::path(
get,
path = #list_by_query_dto_path,
params(#query_dto_name),
responses(
(status = OK, body = Ro<Vec<#vo_name>>)
)
)]
#[debug_handler]
#[log_call]
pub async fn list_by_query_dto(Query(dto): Query<#query_dto_name>) -> Result<Json<Ro<Vec<#vo_name>>>, CtrlError> {
let ro = #svc_name::list_by_query_dto::<DatabaseConnection>(dto, None).await?;
Ok(Json(ro))
}
});
// 生成page_by_query_dto方法
generated_methods.push(quote! {
/// # 查询记录列表
///
/// 该接口通过查询参数获取对应记录列表的详细信息
///
/// ## 查询参数
/// * `QueryDto` - 包含查询条件的结构体
///
/// ## 返回值
/// * 成功时返回对应的记录信息的JSON格式数据
/// * 失败时返回相应的错误信息
#[utoipa::path(
get,
path = #page_by_query_dto_path,
params(#query_dto_name),
responses(
(status = OK, body = Ro<PageRx<#vo_name>>)
)
)]
#[debug_handler]
#[log_call]
pub async fn page_by_query_dto(Query(dto): Query<#query_dto_name>) -> Result<Json<Ro<PageRx<#vo_name>>>, CtrlError> {
let ro = #svc_name::page_by_query_dto::<DatabaseConnection>(dto, None).await?;
Ok(Json(ro))
}
});
let expanded = quote! {
use axum::debug_handler;
use axum::extract::{Path, Query};
use axum::http::HeaderMap;
use axum::response::Json;
use robotech::macros::log_call;
use robotech::ro::Ro;
use robotech::rx::PageRx;
use robotech::web::ctrl_utils::get_current_user_id;
use robotech::web::CtrlError;
use sea_orm::{DatabaseConnection, DatabaseTransaction};
use validator::Validate;
use crate::dto::#dto_module::*;
use crate::svc::#svc_name;
use crate::vo::#vo_name;
#(#generated_methods)*
};
// 调试:打印完整展开的代码
// println!("Full expanded code:\n{expanded}");
TokenStream::from(expanded)
}