Skip to content

Commit 235b731

Browse files
committed
feat: classMode增加rpc-group模式
1 parent 2e553b0 commit 235b731

File tree

9 files changed

+164
-66
lines changed

9 files changed

+164
-66
lines changed

README.md

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -122,43 +122,29 @@ openapi本地或者远程文件,支持格式:`yaml | json`
122122

123123
### classMode
124124

125-
类型:`'rest' | 'rpc'`<br>
125+
类型:`'rest' | 'rpc' | 'rpc-group'`<br>
126126
默认值:`'rest'`
127127

128128
类的生成方式。
129129

130-
- `rest`,仅生成 **get|post|put|patch|delete** 几个方法,uri作为第一个参数传入。参考:[rest-mode.js](./openapi/rest-mode.js)
131-
- `rpc`,把 method+uri 拼接成一个新方法。参考:[rpc-mode.js](./openapi/rpc-mode.js)
130+
| 模式 | 描述 | 优点 |
131+
| --------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
132+
| rest | 仅生成统一的 **get,post,put,patch,delete** 几个方法<br>参考:[rest-mode.js](./openapi/rest-mode.js) | 1. 运行时代码少<br>2. 不暴露接口,安全性高 |
133+
| rpc | 把 method+uri 拼接成一个新方法<br>参考:[rpc-mode.js](./openapi/rpc-mode.js) | 1. 拥有独立的注释文档 |
134+
| rpc-group | 基于rpc模式,根据tags把方法归类到不同的分组中<br>参考:[rpc-group-mode.js](./openapi/rpc-group-mode.js) | 1. 拥有独立的注释文档<br>2. 能更快地找到目标接口 |
132135

133136
```typescript
134137
const client = new OpenapiClient();
135138

136-
// 有一个接口 -> GET /users/{id}
137-
138139
// rest模式
139-
await client.get('/users/{id}', { params: { id: 1 } });
140+
await client.get('/users/{id}', opts);
140141
// rpc模式
141-
await client.getUsersById({ params: { id: 1 } });
142+
await client.getUsersById(opts);
143+
// rpc-group模式
144+
await client.user.getUsersById(opts);
142145
```
143146

144-
rest模式的优点:
145-
146-
- 运行时代码少且相对固定,几乎不受接口数量影响
147-
- 不会暴露接口名称,安全性较高
148-
149-
rpc模式的优点:
150-
151-
- 可以生成独立的注释文档
152-
- 可以利用tag生成分组,接口越多越方便
153-
154-
### tagToGroup
155-
156-
类型:`boolean`<br>
157-
默认值:`true`
158-
159-
根据Tag生成不同的分组,以类似 **client.user.getUsers()** 这种方式调用。仅在 `classMode=rpc` 场景下生效。
160-
161-
如果没有提供tags,则默认合并到`default`分组
147+
**注意**:rpc-group模式下,如果没有提供tags,则默认合并到`default`分组
162148

163149
### onDocumentLoaded
164150

openapi/openapi.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"openapi":"3.0.3","info":{"version":"0.0.0","title":"foca-openapi"},"tags":[{"name":"user"}],"paths":{"/users":{"get":{"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"result":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"name":{"description":"用户名","type":"string"},"age":{"nullable":true,"type":"integer"},"address":{"nullable":true,"type":"string"},"password":{"description":"即将删除","deprecated":true,"type":"string"}},"required":["id","name","age","password"]}}},"required":["page","limit","total","result"]}}}}},"parameters":[{"name":"page","in":"query","schema":{"default":1,"type":"integer","maximum":10,"minimum":1,"exclusiveMaximum":false,"exclusiveMinimum":false}},{"name":"limit","in":"query","description":"每页返回的资源数量","schema":{"description":"每页返回的资源数量","default":10,"type":"integer","maximum":100,"exclusiveMaximum":false}}],"tags":["user"],"operationId":"list_users"},"post":{"responses":{"201":{"description":"创建成功","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"name":{"description":"用户名","type":"string"},"age":{"nullable":true,"type":"integer"},"address":{"nullable":true,"type":"string"},"password":{"description":"即将删除","deprecated":true,"type":"string"}},"required":["id","name","age","password"]}}}}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"integer"},"address":{"type":"integer"}},"required":["name"]}}},"required":true},"tags":["user"],"operationId":"create_users","x-codegen-request-body-name":"body"}},"/users/{id}":{"get":{"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"name":{"description":"用户名","type":"string"},"age":{"nullable":true,"type":"integer"},"address":{"nullable":true,"type":"string"},"password":{"description":"即将删除","deprecated":true,"type":"string"}},"required":["id","name","age","password"]}}}},"404":{"description":"用户不存在"}},"tags":["user"],"operationId":"retrieve_users_by_id"},"put":{"responses":{"200":{"description":"更新成功","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"name":{"description":"用户名","type":"string"},"age":{"nullable":true,"type":"integer"},"address":{"nullable":true,"type":"string"},"password":{"description":"即将删除","deprecated":true,"type":"string"}},"required":["id","name","age","password"]}}}},"404":{"description":"用户不存在"}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"integer"},"address":{"type":"integer"}},"required":["name"]}}},"required":true},"tags":["user"],"operationId":"replace_users_by_id"},"delete":{"responses":{"204":{"description":"删除成功"},"404":{"description":"用户不存在"}},"tags":["user"],"operationId":"delete_users_by_id"},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"exclusiveMinimum":false}}]},"/users/v1":{"post":{"deprecated":true,"description":"接口已弃用,请使用 /users","responses":{"201":{"description":"创建成功","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"name":{"description":"用户名","type":"string"},"age":{"nullable":true,"type":"integer"},"address":{"nullable":true,"type":"string"},"password":{"description":"即将删除","deprecated":true,"type":"string"}},"required":["id","name","age","password"]}}}}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"integer"},"address":{"type":"integer"}},"required":["name"]}}},"required":true},"tags":["user"],"operationId":"create_users_v_1","x-codegen-request-body-name":"body"}},"/users/avatar":{"put":{"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"}},"required":["url"]}}}}},"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}},"required":true},"tags":["user"],"operationId":"replace_users_avatar"}}}}
1+
{"openapi":"3.0.3","info":{"version":"0.0.0","title":"foca-openapi"},"tags":[{"name":"user"}],"paths":{"/users":{"get":{"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"result":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"name":{"description":"用户名","type":"string"},"age":{"nullable":true,"type":"integer"},"address":{"nullable":true,"type":"string"},"password":{"description":"即将删除","deprecated":true,"type":"string"}},"required":["id","name","age","password"]}}},"required":["page","limit","total","result"]}}}}},"parameters":[{"name":"page","in":"query","schema":{"default":1,"type":"integer","maximum":10,"minimum":1,"exclusiveMaximum":false,"exclusiveMinimum":false}},{"name":"limit","in":"query","description":"每页返回的资源数量","schema":{"description":"每页返回的资源数量","default":10,"type":"integer","maximum":100,"exclusiveMaximum":false}}],"tags":["user"],"operationId":"list_users"},"post":{"responses":{"201":{"description":"创建成功","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"name":{"description":"用户名","type":"string"},"age":{"nullable":true,"type":"integer"},"address":{"nullable":true,"type":"string"},"password":{"description":"即将删除","deprecated":true,"type":"string"}},"required":["id","name","age","password"]}}}}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"integer"},"address":{"type":"integer"}},"required":["name"]}}},"required":true},"tags":["user"],"operationId":"create_users","x-codegen-request-body-name":"body"}},"/users/{id}":{"get":{"description":"获取单个用户信息","responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"name":{"description":"用户名","type":"string"},"age":{"nullable":true,"type":"integer"},"address":{"nullable":true,"type":"string"},"password":{"description":"即将删除","deprecated":true,"type":"string"}},"required":["id","name","age","password"]}}}},"404":{"description":"用户不存在"}},"tags":["user"],"operationId":"retrieve_users_by_id"},"put":{"responses":{"200":{"description":"更新成功","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"name":{"description":"用户名","type":"string"},"age":{"nullable":true,"type":"integer"},"address":{"nullable":true,"type":"string"},"password":{"description":"即将删除","deprecated":true,"type":"string"}},"required":["id","name","age","password"]}}}},"404":{"description":"用户不存在"}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"integer"},"address":{"type":"integer"}},"required":["name"]}}},"required":true},"tags":["user"],"operationId":"replace_users_by_id"},"delete":{"responses":{"204":{"description":"删除成功"},"404":{"description":"用户不存在"}},"tags":["user"],"operationId":"delete_users_by_id"},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"exclusiveMinimum":false}}]},"/users/v1":{"post":{"deprecated":true,"description":"接口已弃用,请使用 /users","responses":{"201":{"description":"创建成功","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"name":{"description":"用户名","type":"string"},"age":{"nullable":true,"type":"integer"},"address":{"nullable":true,"type":"string"},"password":{"description":"即将删除","deprecated":true,"type":"string"}},"required":["id","name","age","password"]}}}}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"integer"},"address":{"type":"integer"}},"required":["name"]}}},"required":true},"tags":["user"],"operationId":"create_users_v_1","x-codegen-request-body-name":"body"}},"/users/avatar":{"put":{"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"}},"required":["url"]}}}}},"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}},"required":true},"tags":["user"],"operationId":"replace_users_avatar"}}}}

openapi/openapi.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ paths:
123123
x-codegen-request-body-name: body
124124
/users/{id}:
125125
get:
126+
description: 获取单个用户信息
126127
responses:
127128
"200":
128129
description: ""

openapi/routers/user.router.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ router.get('/users', {
3232
});
3333

3434
router.get('/users/:id', {
35+
docs: {
36+
description: '获取单个用户信息',
37+
},
3538
mount: [
3639
params({
3740
id: rule.int().min(1),

openapi/rpc-group-mode.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
var OpenapiClient = class extends BaseOpenapiClient {
2+
user = {
3+
getUsers: (opts) => {
4+
return this.request('/users', 'get', opts);
5+
},
6+
getUsersById: (opts) => {
7+
return this.request('/users/{id}', 'get', opts);
8+
},
9+
postUsers: (opts) => {
10+
return this.request('/users', 'post', opts);
11+
},
12+
postUsersV1: (opts) => {
13+
return this.request('/users/v1', 'post', opts);
14+
},
15+
putUsersById: (opts) => {
16+
return this.request('/users/{id}', 'put', opts);
17+
},
18+
putUsersAvatar: (opts) => {
19+
return this.request('/users/avatar', 'put', opts);
20+
},
21+
deleteUsersById: (opts) => {
22+
return this.request('/users/{id}', 'delete', opts);
23+
},
24+
};
25+
26+
pickContentTypes(uri, method) {
27+
return contentTypesOpenapiClient[method + ' ' + uri] || [void 0, void 0];
28+
}
29+
};
30+
31+
const contentTypesOpenapiClient = {
32+
'put /users/avatar': ['multipart/form-data', void 0],
33+
};

openapi/rpc-mode.js

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,31 @@
11
var OpenapiClient = class extends BaseOpenapiClient {
2-
user = {
3-
getUsers: (opts) => {
4-
return this.request('/users', 'get', opts);
5-
},
6-
getUsersById: (opts) => {
7-
return this.request('/users/{id}', 'get', opts);
8-
},
9-
postUsers: (opts) => {
10-
return this.request('/users', 'post', opts);
11-
},
12-
postUsersV1: (opts) => {
13-
return this.request('/users/v1', 'post', opts);
14-
},
15-
putUsersById: (opts) => {
16-
return this.request('/users/{id}', 'put', opts);
17-
},
18-
putUsersAvatar: (opts) => {
19-
return this.request('/users/avatar', 'put', opts);
20-
},
21-
deleteUsersById: (opts) => {
22-
return this.request('/users/{id}', 'delete', opts);
23-
},
24-
};
2+
getUsers(opts) {
3+
return this.request('/users', 'get', opts);
4+
}
5+
6+
getUsersById(opts) {
7+
return this.request('/users/{id}', 'get', opts);
8+
}
9+
10+
postUsers(opts) {
11+
return this.request('/users', 'post', opts);
12+
}
13+
14+
postUsersV1(opts) {
15+
return this.request('/users/v1', 'post', opts);
16+
}
17+
18+
putUsersById(opts) {
19+
return this.request('/users/{id}', 'put', opts);
20+
}
21+
22+
putUsersAvatar(opts) {
23+
return this.request('/users/avatar', 'put', opts);
24+
}
25+
26+
deleteUsersById(opts) {
27+
return this.request('/users/{id}', 'delete', opts);
28+
}
2529

2630
pickContentTypes(uri, method) {
2731
return contentTypesOpenapiClient[method + ' ' + uri] || [void 0, void 0];

src/define-config.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,22 @@ export interface OpenapiClientConfig {
2727
projectName?: string;
2828
/**
2929
* 类的生成方式。默认值:`rest`
30-
* - `rest`,仅生成 **get|post|put|patch|delete** 几个方法,uri作为第一个参数传入
31-
* - `rpc`,把 method+uri 拼接成一个方法,比如 **POST /users/{id}** 会变成 **postUsersById()**
32-
*/
33-
classMode?: 'rest' | 'rpc';
34-
/**
35-
* 根据Tag生成不同的分组,以类似 **client.user.getUsers()** 这种方式调用。仅在 `classMode=rpc` 场景下生效。默认值:`true`
30+
* - `rest` 仅生成 **get|post|put|patch|delete** 几个固定方法,uri作为第一个参数传入。
31+
* - `rpc` 把 method+uri 拼接成一个方法。
32+
* - `rpc-group` 在rpc模式的基础上,根据tags把方法归类到不同的分组中。如果没有提供tags,则默认合并到`default`分组
3633
*
37-
* 如果没有提供tags,则默认合并到`default`分组
38-
*/
39-
tagToGroup?: boolean;
34+
* ```typescript
35+
* const client = new OpenapiClient();
36+
*
37+
* // rest模式
38+
* await client.get('/users/{id}', opts);
39+
* // rpc模式
40+
* await client.getUsersById(opts);
41+
* // rpc-group模式
42+
* await client.user.getUsersById(opts);
43+
* ```
44+
*/
45+
classMode?: 'rest' | 'rpc' | 'rpc-group';
4046
/**
4147
* 加载完openapi文档后的事件,允许直接对文档进行修改
4248
*/

src/lib/generate-template.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@ import type { OpenapiClientConfig } from '../define-config';
88

99
export const generateTemplate = async (
1010
docs: OpenAPIV3.Document,
11-
config: Pick<OpenapiClientConfig, 'projectName' | 'classMode' | 'tagToGroup'>,
11+
config: Pick<OpenapiClientConfig, 'projectName' | 'classMode'>,
1212
) => {
13-
const { projectName, classMode = 'rest', tagToGroup = true } = config;
13+
const { projectName, classMode = 'rest' } = config;
1414
const className = `OpenapiClient${upperFirst(camelCase(projectName))}`;
1515
const metas = documentToMeta(docs);
1616

1717
const classTpl =
1818
classMode === 'rest'
1919
? generateMethodModeClass(className, metas)
20-
: tagToGroup
21-
? generateUriModelClassWithNamespace(className, metas)
20+
: classMode === 'rpc-group'
21+
? generateUriModelClassWithGroup(className, metas)
2222
: generateUriModelClass(className, metas);
2323

2424
const dts = `
@@ -160,7 +160,7 @@ var ${className} = class extends BaseOpenapiClient {
160160
};
161161
};
162162

163-
export const generateUriModelClassWithNamespace = (className: string, metas: Metas) => {
163+
export const generateUriModelClassWithGroup = (className: string, metas: Metas) => {
164164
const namespaces = [
165165
...new Set(
166166
methods.flatMap((method) => metas[method].flatMap((meta) => meta.tags || [])),

test/lib/generate-template.test.ts

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
generatePathRelationTpl,
77
generateTemplate,
88
generateUriModelClass,
9-
generateUriModelClassWithNamespace,
9+
generateUriModelClassWithGroup,
1010
} from '../../src/lib/generate-template';
1111
import prettier from 'prettier';
1212
import { getBasicMetas } from '../mocks/get-basic-matea';
@@ -119,7 +119,7 @@ test('完整的类型提示', async () => {
119119
}
120120
`);
121121

122-
await expect(generateTemplate(docs, { classMode: 'rpc' })).resolves
122+
await expect(generateTemplate(docs, { classMode: 'rpc-group' })).resolves
123123
.toMatchInlineSnapshot(`
124124
{
125125
"OpenapiClient": {
@@ -186,6 +186,71 @@ test('完整的类型提示', async () => {
186186
},
187187
}
188188
`);
189+
190+
await expect(generateTemplate(docs, { classMode: 'rpc' })).resolves
191+
.toMatchInlineSnapshot(`
192+
{
193+
"OpenapiClient": {
194+
"dts": "declare namespace OpenapiClient {
195+
interface GetUsersQuery {
196+
foo?: string;
197+
bar?: string;
198+
}
199+
interface GetUsersParams {
200+
baz: number;
201+
}
202+
interface GetUsersBody {}
203+
interface GetUsersResponse {
204+
foo?: string;
205+
}
206+
}
207+
208+
declare class OpenapiClient<T extends object = object> extends BaseOpenapiClient<T> {
209+
getUsers(
210+
opts: OpenapiClient_get_paths["/users"]["request"] & BaseOpenapiClient.UserInputOpts<T>,
211+
): Promise<OpenapiClient_get_paths["/users"]["response"]>;
212+
213+
getUsersById(
214+
opts?: OpenapiClient_get_paths["/users/{id}"]["request"] & BaseOpenapiClient.UserInputOpts<T>,
215+
): Promise<OpenapiClient_get_paths["/users/{id}"]["response"]>;
216+
}
217+
218+
interface OpenapiClient_get_paths {
219+
"/users": BaseOpenapiClient.Prettify<{
220+
request: {
221+
query?: OpenapiClient.GetUsersQuery;
222+
params: OpenapiClient.GetUsersParams;
223+
body: OpenapiClient.GetUsersBody;
224+
};
225+
response: OpenapiClient.GetUsersResponse;
226+
}>;
227+
"/users/{id}": BaseOpenapiClient.Prettify<{
228+
request: {
229+
query?: object;
230+
};
231+
response: unknown;
232+
}>;
233+
}
234+
",
235+
"js": "var OpenapiClient = class extends BaseOpenapiClient {
236+
getUsers(opts) {
237+
return this.request("/users", "get", opts);
238+
}
239+
240+
getUsersById(opts) {
241+
return this.request("/users/{id}", "get", opts);
242+
}
243+
244+
pickContentTypes(uri, method) {
245+
return contentTypesOpenapiClient[method + " " + uri] || [void 0, void 0];
246+
}
247+
};
248+
249+
const contentTypesOpenapiClient = {};
250+
",
251+
},
252+
}
253+
`);
189254
});
190255

191256
test('不同的项目名', async () => {
@@ -430,7 +495,7 @@ describe('类', () => {
430495
});
431496

432497
test('命名空间', async () => {
433-
const { dts, js } = generateUriModelClassWithNamespace('Client', metas);
498+
const { dts, js } = generateUriModelClassWithGroup('Client', metas);
434499

435500
await expect(formatDocs(dts)).resolves.toMatchInlineSnapshot(`
436501
"declare class Client<T extends object = object> extends BaseOpenapiClient<T> {

0 commit comments

Comments
 (0)