@fastcar/cli 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +239 -226
- package/package.json +1 -1
- package/skills/AGENTS.md +251 -0
- package/skills/fastcar-database/SKILL.md +436 -337
- package/skills/fastcar-framework/SKILL.md +577 -856
- package/skills/fastcar-rpc-microservices/SKILL.md +19 -69
- package/skills/fastcar-serverless/SKILL.md +48 -48
- package/skills/fastcar-toolkit/SKILL.md +22 -31
- package/skills/typescript-coding-style/SKILL.md +144 -0
- package/src/init.js +708 -700
- package/src/pack.js +7 -7
- package/src/skill.js +493 -364
- package/src/update.js +301 -0
- package/src/utils.js +2 -2
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: fastcar-rpc-microservices
|
|
3
|
-
description: FastCar RPC 与微服务开发指南。Use when working with FastCar framework for: (1) Building RPC servers and clients with @fastcar/rpc, (2) Using WebSocket/SocketIO/MQTT/gRPC for service communication, (3) Setting up microservices architecture
|
|
3
|
+
description: FastCar RPC 与微服务开发指南。Use when working with FastCar framework for: (1) Building RPC servers and clients with @fastcar/rpc, (2) Using WebSocket/SocketIO/MQTT/gRPC for service communication, (3) Setting up microservices architecture, (4) Configuring RPC endpoints, authentication, retry policies, (5) Using protobuf with RPC.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# FastCar RPC & Microservices
|
|
7
7
|
|
|
8
|
-
FastCar RPC 模块提供基于多种协议(WS、SocketIO、MQTT、gRPC
|
|
8
|
+
FastCar RPC 模块提供基于多种协议(WS、SocketIO、MQTT、gRPC)的远程调用能力,并支持构建多服务微服务架构。
|
|
9
9
|
|
|
10
10
|
## RPC 核心概念
|
|
11
11
|
|
|
@@ -23,50 +23,24 @@ export default new APP();
|
|
|
23
23
|
|
|
24
24
|
### 服务端配置
|
|
25
25
|
|
|
26
|
-
通过 `ApplicationSetting` 注入 RPC 服务器列表配置类:
|
|
27
|
-
|
|
28
26
|
```typescript
|
|
29
27
|
import { Application, ApplicationSetting } from "@fastcar/core/annotation";
|
|
30
28
|
import { EnableRPC } from "@fastcar/rpc/annotation";
|
|
31
|
-
import
|
|
29
|
+
import { SocketEnum } from "@fastcar/rpc/constant/SocketEnum";
|
|
32
30
|
|
|
33
31
|
@Application
|
|
34
32
|
@EnableRPC
|
|
35
|
-
@ApplicationSetting(
|
|
36
|
-
class APP {}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
`RpcServerList.ts` 示例:
|
|
40
|
-
|
|
41
|
-
```typescript
|
|
42
|
-
import { SocketEnum } from "@fastcar/rpc/constant/SocketEnum";
|
|
43
|
-
import { Protocol } from "@fastcar/server";
|
|
44
|
-
import { CodeProtocolEnum } from "@fastcar/rpc/types/CodeProtocolEnum";
|
|
45
|
-
import * as path from "path";
|
|
46
|
-
|
|
47
|
-
export default {
|
|
33
|
+
@ApplicationSetting({
|
|
48
34
|
rpc: {
|
|
49
35
|
list: [
|
|
50
36
|
{ id: "rpc-1", type: SocketEnum.WS, server: { port: 1238 }, serviceType: "rpc" },
|
|
51
37
|
{ id: "rpc-2", type: SocketEnum.SocketIO, server: { port: 1235 }, serviceType: "rpc" },
|
|
52
|
-
{ id: "rpc-3", type: SocketEnum.MQTT, server: { port: 1236
|
|
53
|
-
{
|
|
54
|
-
id: "rpc-4",
|
|
55
|
-
type: SocketEnum.MQTT,
|
|
56
|
-
server: { port: 1239, protocol: Protocol.https, ssl: { key: "./ssl/server.key", cert: "./ssl/server.crt" } },
|
|
57
|
-
serviceType: "rpc",
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
id: "rpc-5",
|
|
61
|
-
type: SocketEnum.Grpc,
|
|
62
|
-
server: { port: 1240, ssl: { ca: path.join(__dirname, "cert/ca.crt"), key: "cert/server.key", cert: "cert/server.crt" } },
|
|
63
|
-
serviceType: "rpc",
|
|
64
|
-
codeProtocol: CodeProtocolEnum.PROTOBUF,
|
|
65
|
-
extra: { checkClientCertificate: true },
|
|
66
|
-
},
|
|
38
|
+
{ id: "rpc-3", type: SocketEnum.MQTT, server: { port: 1236 }, serviceType: "rpc" },
|
|
39
|
+
{ id: "rpc-4", type: SocketEnum.Grpc, server: { port: 1240 }, serviceType: "rpc" },
|
|
67
40
|
],
|
|
68
41
|
},
|
|
69
|
-
}
|
|
42
|
+
})
|
|
43
|
+
class APP {}
|
|
70
44
|
```
|
|
71
45
|
|
|
72
46
|
支持的协议类型:
|
|
@@ -77,15 +51,13 @@ export default {
|
|
|
77
51
|
|
|
78
52
|
### 安全认证
|
|
79
53
|
|
|
80
|
-
在服务端配置 `secure`:
|
|
81
|
-
|
|
82
54
|
```typescript
|
|
83
55
|
{
|
|
84
56
|
id: "rpc-auth",
|
|
85
57
|
type: SocketEnum.WS,
|
|
86
58
|
server: { port: 1238 },
|
|
87
59
|
serviceType: "rpc",
|
|
88
|
-
secure: { username: "user", password: "
|
|
60
|
+
secure: { username: "user", password: "your-password" },
|
|
89
61
|
}
|
|
90
62
|
```
|
|
91
63
|
|
|
@@ -93,7 +65,7 @@ export default {
|
|
|
93
65
|
|
|
94
66
|
```typescript
|
|
95
67
|
import { Controller } from "@fastcar/core/annotation";
|
|
96
|
-
import { RPC, RPCMethod
|
|
68
|
+
import { RPC, RPCMethod } from "@fastcar/rpc/annotation";
|
|
97
69
|
|
|
98
70
|
@Controller
|
|
99
71
|
@RPC("/hello")
|
|
@@ -130,14 +102,13 @@ const client = new RpcClient(
|
|
|
130
102
|
{
|
|
131
103
|
url: "ws://localhost:1238",
|
|
132
104
|
type: SocketEnum.WS,
|
|
133
|
-
secure: { username: "user", password: "
|
|
105
|
+
secure: { username: "user", password: "your-password" },
|
|
134
106
|
},
|
|
135
107
|
new NotifyHandle()
|
|
136
108
|
);
|
|
137
109
|
|
|
138
110
|
await client.start();
|
|
139
111
|
const result = await client.request("/hello");
|
|
140
|
-
console.log(result);
|
|
141
112
|
```
|
|
142
113
|
|
|
143
114
|
### 断线重连与重试策略
|
|
@@ -157,19 +128,6 @@ const client = new RpcClient(
|
|
|
157
128
|
);
|
|
158
129
|
```
|
|
159
130
|
|
|
160
|
-
### SSL 连接
|
|
161
|
-
|
|
162
|
-
```typescript
|
|
163
|
-
const client = new RpcClient(
|
|
164
|
-
{
|
|
165
|
-
url: "wss://localhost:1239",
|
|
166
|
-
type: SocketEnum.MQTT,
|
|
167
|
-
extra: { rejectUnauthorized: false },
|
|
168
|
-
},
|
|
169
|
-
new NotifyHandle()
|
|
170
|
-
);
|
|
171
|
-
```
|
|
172
|
-
|
|
173
131
|
### Protobuf 调用
|
|
174
132
|
|
|
175
133
|
```typescript
|
|
@@ -177,11 +135,10 @@ import { RpcClient } from "@fastcar/rpc";
|
|
|
177
135
|
import { SocketEnum } from "@fastcar/rpc/constant/SocketEnum";
|
|
178
136
|
import { CodeProtocolEnum } from "@fastcar/rpc/types/CodeProtocolEnum";
|
|
179
137
|
import { ClientRequestStatic } from "@fastcar/rpc/service/rpc/RequestStatic";
|
|
180
|
-
import * as path from "path";
|
|
181
138
|
|
|
182
139
|
const client = new RpcClient(
|
|
183
140
|
{
|
|
184
|
-
url: "
|
|
141
|
+
url: "localhost:1240",
|
|
185
142
|
type: SocketEnum.Grpc,
|
|
186
143
|
codeProtocol: CodeProtocolEnum.PROTOBUF,
|
|
187
144
|
ssl: {
|
|
@@ -189,15 +146,8 @@ const client = new RpcClient(
|
|
|
189
146
|
key: path.join(__dirname, "cert/client.key"),
|
|
190
147
|
cert: path.join(__dirname, "cert/client.crt"),
|
|
191
148
|
},
|
|
192
|
-
extra: {
|
|
193
|
-
options: {
|
|
194
|
-
"grpc.ssl_target_name_override": "example",
|
|
195
|
-
"grpc.default_authority": "example",
|
|
196
|
-
},
|
|
197
|
-
},
|
|
198
149
|
},
|
|
199
|
-
new NotifyHandle()
|
|
200
|
-
{ retryCount: 0 }
|
|
150
|
+
new NotifyHandle()
|
|
201
151
|
);
|
|
202
152
|
|
|
203
153
|
client.addProtoBuf({
|
|
@@ -224,7 +174,7 @@ FastCar 微服务模板将系统拆分为以下服务模块:
|
|
|
224
174
|
|------|------|
|
|
225
175
|
| center | 服务中心,提供服务注册与发现 |
|
|
226
176
|
| connector | 连接器服务,处理客户端连接(通常标记 `front: true`) |
|
|
227
|
-
|
|
|
177
|
+
| message | 消息服务,处理实时消息 |
|
|
228
178
|
| web | Web 服务,提供 HTTP 接口 |
|
|
229
179
|
| base | 基础服务,提供公共功能 |
|
|
230
180
|
|
|
@@ -234,7 +184,7 @@ FastCar 微服务模板将系统拆分为以下服务模块:
|
|
|
234
184
|
settings:
|
|
235
185
|
microservices:
|
|
236
186
|
center:
|
|
237
|
-
token: "
|
|
187
|
+
token: "your-token-here"
|
|
238
188
|
servers:
|
|
239
189
|
- host: "localhost"
|
|
240
190
|
clusters: 1
|
|
@@ -246,7 +196,7 @@ settings:
|
|
|
246
196
|
disconnectInterval: 1000
|
|
247
197
|
retry: { retryCount: 3, retryInterval: 3000, timeout: 30000, maxMsgNum: 10000, increase: true }
|
|
248
198
|
connector:
|
|
249
|
-
token: "
|
|
199
|
+
token: "your-token-here"
|
|
250
200
|
servers:
|
|
251
201
|
- host: "localhost"
|
|
252
202
|
clusters: 1
|
|
@@ -254,8 +204,8 @@ settings:
|
|
|
254
204
|
- front: true
|
|
255
205
|
type: "ws"
|
|
256
206
|
server: { port: 60100 }
|
|
257
|
-
|
|
258
|
-
token: "
|
|
207
|
+
message:
|
|
208
|
+
token: "your-token-here"
|
|
259
209
|
servers:
|
|
260
210
|
- host: "localhost"
|
|
261
211
|
clusters: 1
|
|
@@ -263,7 +213,7 @@ settings:
|
|
|
263
213
|
- type: "ws"
|
|
264
214
|
server: { port: 60200 }
|
|
265
215
|
web:
|
|
266
|
-
token: "
|
|
216
|
+
token: "your-token-here"
|
|
267
217
|
koa:
|
|
268
218
|
koaBodyParser:
|
|
269
219
|
enableTypes: ["json", "form", "text"]
|
|
@@ -17,58 +17,58 @@ FastCar Serverless 框架支持将 FastCar 应用部署到阿里云函数计算
|
|
|
17
17
|
import { ServerlessApp, Service, Handler, HttpTrigger, TimerTrigger, EventTrigger } from "@fastcar/serverless";
|
|
18
18
|
|
|
19
19
|
@Service
|
|
20
|
-
class
|
|
21
|
-
async
|
|
22
|
-
return {
|
|
20
|
+
class BizService {
|
|
21
|
+
async process(data: any) {
|
|
22
|
+
return { id: Date.now(), ...data };
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
@ServerlessApp({
|
|
27
|
-
name: "
|
|
27
|
+
name: "example-service",
|
|
28
28
|
version: "1.0.0",
|
|
29
|
-
components: [
|
|
29
|
+
components: [BizService],
|
|
30
30
|
init: async (app) => {
|
|
31
|
-
console.log("
|
|
31
|
+
console.log("Service initializing...");
|
|
32
32
|
},
|
|
33
33
|
})
|
|
34
|
-
class
|
|
35
|
-
@HttpTrigger({ path: "/
|
|
34
|
+
class ExampleApp {
|
|
35
|
+
@HttpTrigger({ path: "/items", method: "POST" })
|
|
36
36
|
@Handler()
|
|
37
|
-
async
|
|
38
|
-
const
|
|
39
|
-
const
|
|
37
|
+
async createItem(event: any, context: any) {
|
|
38
|
+
const service = (this as any).app.getFastCarApp().getComponentByName("BizService") as BizService;
|
|
39
|
+
const result = await service.process(event.body);
|
|
40
40
|
return {
|
|
41
41
|
statusCode: 200,
|
|
42
42
|
headers: { "Content-Type": "application/json" },
|
|
43
|
-
body: { success: true, data:
|
|
43
|
+
body: { success: true, data: result },
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
@HttpTrigger({ path: "/
|
|
47
|
+
@HttpTrigger({ path: "/items/:id", method: "GET" })
|
|
48
48
|
@Handler()
|
|
49
|
-
async
|
|
49
|
+
async getItem(event: any, context: any) {
|
|
50
50
|
return {
|
|
51
51
|
statusCode: 200,
|
|
52
|
-
body: {
|
|
52
|
+
body: { id: event.params?.id },
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
@TimerTrigger({ cron: "0 0 * * * *" })
|
|
57
57
|
@Handler()
|
|
58
|
-
async
|
|
59
|
-
console.log(`[${context.requestId}]
|
|
58
|
+
async hourlyTask(event: any, context: any) {
|
|
59
|
+
console.log(`[${context.requestId}] Running scheduled task`);
|
|
60
60
|
return { success: true };
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
@EventTrigger({ eventSource: "oss" })
|
|
64
64
|
@Handler()
|
|
65
|
-
async
|
|
66
|
-
console.log(`[${context.requestId}] Processing
|
|
65
|
+
async handleEvent(event: any, context: any) {
|
|
66
|
+
console.log(`[${context.requestId}] Processing event`);
|
|
67
67
|
return { success: true };
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
const
|
|
71
|
+
const app = new ExampleApp();
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
### 触发器类型
|
|
@@ -100,51 +100,51 @@ import {
|
|
|
100
100
|
import { startLocalDev } from "@fastcar/serverless/local";
|
|
101
101
|
|
|
102
102
|
@Service
|
|
103
|
-
class
|
|
104
|
-
private
|
|
105
|
-
{ id: "1", name: "
|
|
106
|
-
{ id: "2", name: "
|
|
103
|
+
class DataService {
|
|
104
|
+
private data = [
|
|
105
|
+
{ id: "1", name: "示例1" },
|
|
106
|
+
{ id: "2", name: "示例2" },
|
|
107
107
|
];
|
|
108
|
-
findAll() { return this.
|
|
109
|
-
findById(id: string) { return this.
|
|
110
|
-
create(
|
|
111
|
-
const
|
|
112
|
-
this.
|
|
113
|
-
return
|
|
108
|
+
findAll() { return this.data; }
|
|
109
|
+
findById(id: string) { return this.data.find(d => d.id === id); }
|
|
110
|
+
create(item: any) {
|
|
111
|
+
const newItem = { id: String(Date.now()), ...item };
|
|
112
|
+
this.data.push(newItem);
|
|
113
|
+
return newItem;
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
@Controller
|
|
118
|
-
class
|
|
118
|
+
class ApiController {
|
|
119
119
|
@Autowired
|
|
120
|
-
private
|
|
120
|
+
private service!: DataService;
|
|
121
121
|
|
|
122
|
-
@HttpTrigger({ path: "/
|
|
122
|
+
@HttpTrigger({ path: "/items", method: "GET" })
|
|
123
123
|
@Handler()
|
|
124
|
-
async
|
|
125
|
-
ctx.json({ code: 0, data: this.
|
|
124
|
+
async list(ctx: ServerlessContext) {
|
|
125
|
+
ctx.json({ code: 0, data: this.service.findAll(), requestId: ctx.requestId });
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
@HttpTrigger({ path: "/
|
|
128
|
+
@HttpTrigger({ path: "/items/:id", method: "GET" })
|
|
129
129
|
@Handler()
|
|
130
|
-
async
|
|
131
|
-
const
|
|
132
|
-
if (!
|
|
133
|
-
ctx.json({ code: 0, data:
|
|
130
|
+
async getOne(ctx: ServerlessContext) {
|
|
131
|
+
const item = this.service.findById(ctx.params.id);
|
|
132
|
+
if (!item) ctx.throw(404, "Not found");
|
|
133
|
+
ctx.json({ code: 0, data: item });
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
@HttpTrigger({ path: "/
|
|
136
|
+
@HttpTrigger({ path: "/items", method: "POST" })
|
|
137
137
|
@Handler()
|
|
138
|
-
async
|
|
139
|
-
const
|
|
138
|
+
async create(ctx: ServerlessContext) {
|
|
139
|
+
const item = this.service.create(ctx.bodyData);
|
|
140
140
|
ctx.status = 201;
|
|
141
|
-
ctx.json({ code: 0, data:
|
|
141
|
+
ctx.json({ code: 0, data: item });
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
async function main() {
|
|
146
146
|
const app = new ServerlessApplication();
|
|
147
|
-
app.register(
|
|
147
|
+
app.register(ApiController, DataService);
|
|
148
148
|
app.use(ErrorMiddleware({ includeStack: true }));
|
|
149
149
|
app.use(LoggerMiddleware({ logQuery: true }));
|
|
150
150
|
app.use(CorsMiddleware({ origin: "*" }));
|
|
@@ -167,21 +167,21 @@ main().catch(console.error);
|
|
|
167
167
|
|
|
168
168
|
```typescript
|
|
169
169
|
import { createFCAdapter } from "@fastcar/serverless";
|
|
170
|
-
export const handler = createFCAdapter((
|
|
170
|
+
export const handler = createFCAdapter((app as any).app);
|
|
171
171
|
```
|
|
172
172
|
|
|
173
173
|
### 腾讯云 SCF
|
|
174
174
|
|
|
175
175
|
```typescript
|
|
176
176
|
import { createSCFAdapter } from "@fastcar/serverless";
|
|
177
|
-
export const main_handler = createSCFAdapter((
|
|
177
|
+
export const main_handler = createSCFAdapter((app as any).app);
|
|
178
178
|
```
|
|
179
179
|
|
|
180
180
|
### AWS Lambda
|
|
181
181
|
|
|
182
182
|
```typescript
|
|
183
183
|
import { createLambdaAdapter } from "@fastcar/serverless";
|
|
184
|
-
export const handler = createLambdaAdapter((
|
|
184
|
+
export const handler = createLambdaAdapter((app as any).app);
|
|
185
185
|
```
|
|
186
186
|
|
|
187
187
|
## 常用中间件
|
|
@@ -28,41 +28,35 @@ import { Service, Autowired } from "@fastcar/core/annotation";
|
|
|
28
28
|
import { CacheApplication } from "@fastcar/cache";
|
|
29
29
|
|
|
30
30
|
@Service
|
|
31
|
-
class
|
|
31
|
+
class CacheService {
|
|
32
32
|
@Autowired
|
|
33
33
|
private cache!: CacheApplication;
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
setItem(id: string, data: any) {
|
|
36
36
|
// ttl 单位秒,0 为不过期
|
|
37
|
-
this.cache.set("
|
|
37
|
+
this.cache.set("dataStore", id, data, { ttl: 60 });
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
return this.cache.get("
|
|
40
|
+
getItem(id: string) {
|
|
41
|
+
return this.cache.get("dataStore", id);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
return this.cache.has("
|
|
44
|
+
hasItem(id: string) {
|
|
45
|
+
return this.cache.has("dataStore", id);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
return this.cache.delete("
|
|
48
|
+
deleteItem(id: string) {
|
|
49
|
+
return this.cache.delete("dataStore", id);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
return this.cache.
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
getAllUsers() {
|
|
57
|
-
return this.cache.getDictionary("userStore");
|
|
52
|
+
getAll() {
|
|
53
|
+
return this.cache.getDictionary("dataStore");
|
|
58
54
|
}
|
|
59
55
|
}
|
|
60
56
|
```
|
|
61
57
|
|
|
62
58
|
### 持久化缓存配置
|
|
63
59
|
|
|
64
|
-
通过 `@CacheMapping` 配置缓存节点,支持文件持久化或数据库持久化:
|
|
65
|
-
|
|
66
60
|
```typescript
|
|
67
61
|
import { CacheMapping } from "@fastcar/cache";
|
|
68
62
|
import { FSClient } from "@fastcar/cache";
|
|
@@ -80,11 +74,13 @@ class CacheConfig {}
|
|
|
80
74
|
|
|
81
75
|
## 定时任务 (@fastcar/timer)
|
|
82
76
|
|
|
77
|
+
> **推荐使用 `@fastcar/timer/scheduling2` 模块**
|
|
78
|
+
|
|
83
79
|
### 开启定时任务
|
|
84
80
|
|
|
85
81
|
```typescript
|
|
86
82
|
import { Application } from "@fastcar/core/annotation";
|
|
87
|
-
import { EnableScheduling } from "@fastcar/timer";
|
|
83
|
+
import { EnableScheduling } from "@fastcar/timer/scheduling2";
|
|
88
84
|
|
|
89
85
|
@Application
|
|
90
86
|
@EnableScheduling
|
|
@@ -94,7 +90,7 @@ class APP {}
|
|
|
94
90
|
### 间隔任务
|
|
95
91
|
|
|
96
92
|
```typescript
|
|
97
|
-
import { ScheduledInterval } from "@fastcar/timer";
|
|
93
|
+
import { ScheduledInterval } from "@fastcar/timer/scheduling2";
|
|
98
94
|
import { Component } from "@fastcar/core/annotation";
|
|
99
95
|
|
|
100
96
|
@Component
|
|
@@ -109,7 +105,7 @@ class HeartbeatTask {
|
|
|
109
105
|
### Cron 任务
|
|
110
106
|
|
|
111
107
|
```typescript
|
|
112
|
-
import { ScheduledCron } from "@fastcar/timer";
|
|
108
|
+
import { ScheduledCron } from "@fastcar/timer/scheduling2";
|
|
113
109
|
import { Component } from "@fastcar/core/annotation";
|
|
114
110
|
|
|
115
111
|
@Component
|
|
@@ -123,7 +119,7 @@ class ReportTask {
|
|
|
123
119
|
|
|
124
120
|
## 时间轮 (@fastcar/timewheel)
|
|
125
121
|
|
|
126
|
-
|
|
122
|
+
适用于需要大量延时任务的场景(如超时取消、消息延时投递)。
|
|
127
123
|
|
|
128
124
|
```typescript
|
|
129
125
|
import { HashedWheelTimer } from "@fastcar/timewheel";
|
|
@@ -135,7 +131,7 @@ const timer = new HashedWheelTimer<string>({
|
|
|
135
131
|
});
|
|
136
132
|
|
|
137
133
|
// 添加一个 5 秒后触发的任务
|
|
138
|
-
timer.addId("
|
|
134
|
+
timer.addId("job-123", 5000);
|
|
139
135
|
|
|
140
136
|
// 配合心跳循环处理
|
|
141
137
|
setInterval(() => {
|
|
@@ -148,7 +144,7 @@ setInterval(() => {
|
|
|
148
144
|
}, 100);
|
|
149
145
|
|
|
150
146
|
// 取消任务
|
|
151
|
-
timer.removeId("
|
|
147
|
+
timer.removeId("job-123", slotId);
|
|
152
148
|
```
|
|
153
149
|
|
|
154
150
|
## 工作线程池 (@fastcar/workerpool)
|
|
@@ -156,7 +152,7 @@ timer.removeId("order-123", slotId);
|
|
|
156
152
|
将 CPU 密集型操作卸载到 worker 线程执行,避免阻塞主线程。
|
|
157
153
|
|
|
158
154
|
```typescript
|
|
159
|
-
import { WorkerPool
|
|
155
|
+
import { WorkerPool } from "@fastcar/workerpool";
|
|
160
156
|
|
|
161
157
|
const pool = new WorkerPool({
|
|
162
158
|
minWorkers: 2,
|
|
@@ -167,7 +163,7 @@ const pool = new WorkerPool({
|
|
|
167
163
|
const result = await pool.exec("heavyComputation", [1, 2, 3, 4, 5]);
|
|
168
164
|
```
|
|
169
165
|
|
|
170
|
-
在 FastCar
|
|
166
|
+
在 FastCar 应用中通过 `@WorkerPool` / `@WorkerTask` 注解使用(参考 fastcar-framework skill)。
|
|
171
167
|
|
|
172
168
|
## 文件监听 (@fastcar/watchfile)
|
|
173
169
|
|
|
@@ -206,7 +202,7 @@ const sign = getSign(
|
|
|
206
202
|
{
|
|
207
203
|
appid: account.appid,
|
|
208
204
|
expireTime: Math.floor((Date.now() + 5 * 60 * 1000) / 1000),
|
|
209
|
-
dir_path: "/",
|
|
205
|
+
dir_path: "/",
|
|
210
206
|
mode: 7, // 1可读 2可写 4可查
|
|
211
207
|
},
|
|
212
208
|
account.serectkey
|
|
@@ -232,7 +228,6 @@ await cos.uploadfile("/test/text.txt", file);
|
|
|
232
228
|
|
|
233
229
|
// 下载文件
|
|
234
230
|
const res = await cos.getFile("/test/hello/test.txt");
|
|
235
|
-
console.log(res.data);
|
|
236
231
|
|
|
237
232
|
// 带鉴权下载
|
|
238
233
|
await cos.getFile("/test.txt", true);
|
|
@@ -256,10 +251,6 @@ await cos.rename("/test/old.txt", "/test/new.txt");
|
|
|
256
251
|
|
|
257
252
|
// 设置重定向
|
|
258
253
|
await cos.setRedirect({ redirectUrl: "/test/hello.txt", flag: false, bucket: "test" });
|
|
259
|
-
|
|
260
|
-
// 查询重定向
|
|
261
|
-
await cos.getRedirect();
|
|
262
|
-
await cos.queryRedirect({ bucketUrl: "http://xxx" });
|
|
263
254
|
```
|
|
264
255
|
|
|
265
256
|
## 完整模块列表
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: typescript-coding-style
|
|
3
|
+
description: TypeScript 编码规范与最佳实践。Use when writing TypeScript code for: (1) Defining reusable type aliases for complex intersection types, (2) Using enums instead of string literals for status fields, (3) Naming conventions for types and interfaces, (4) Code organization and maintainability tips.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# TypeScript 编码规范
|
|
7
|
+
|
|
8
|
+
## 1. 复用复杂类型别名
|
|
9
|
+
|
|
10
|
+
### 问题场景
|
|
11
|
+
当同一个交叉类型在多处使用时,直接重复书写会导致代码冗余、维护困难和可读性差。
|
|
12
|
+
|
|
13
|
+
### 反例
|
|
14
|
+
```typescript
|
|
15
|
+
// ❌ 重复书写复杂的交叉类型
|
|
16
|
+
private async getData(): Promise<(Detail & { related: Related })[]> {
|
|
17
|
+
const result: (Detail & { related: Related })[] = [];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private groupById(
|
|
21
|
+
items: (Detail & { related: Related })[]
|
|
22
|
+
): Map<string, (Detail & { related: Related })[]> {
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 正例
|
|
27
|
+
```typescript
|
|
28
|
+
// ✅ 定义类型别名,一处定义多处使用
|
|
29
|
+
type DetailWithRelated = Detail & { related: Related };
|
|
30
|
+
|
|
31
|
+
// 或者使用 interface
|
|
32
|
+
interface DetailWithRelated extends Detail {
|
|
33
|
+
related: Related;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 使用
|
|
37
|
+
private async getData(): Promise<DetailWithRelated[]> {
|
|
38
|
+
const result: DetailWithRelated[] = [];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private groupById(items: DetailWithRelated[]): Map<string, DetailWithRelated[]> {
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 规范建议
|
|
46
|
+
1. **当交叉类型在 2 处及以上使用时**,应提取为类型别名或接口
|
|
47
|
+
2. **命名规范**:使用 `With` 连接,如 `DetailWithRelated`
|
|
48
|
+
3. **文档注释**:为复杂类型添加 JSDoc 说明其用途
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 2. 使用枚举代替字符串字面量
|
|
53
|
+
|
|
54
|
+
### 问题场景
|
|
55
|
+
状态字段使用字符串字面量会导致拼写错误、IDE 无法自动补全、重构困难和类型安全性差。
|
|
56
|
+
|
|
57
|
+
### 反例
|
|
58
|
+
```typescript
|
|
59
|
+
// ❌ 使用字符串字面量
|
|
60
|
+
const result = await this.mapper.select({
|
|
61
|
+
where: { status: "pending" }
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
await this.mapper.updateOne({
|
|
65
|
+
where: { id },
|
|
66
|
+
row: { status: "running" }
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
switch (detail.status) {
|
|
70
|
+
case "success":
|
|
71
|
+
break;
|
|
72
|
+
case "failed":
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 正例
|
|
78
|
+
```typescript
|
|
79
|
+
// ✅ 定义枚举
|
|
80
|
+
export enum JobStatus {
|
|
81
|
+
pending = "pending", // 待执行
|
|
82
|
+
running = "running", // 执行中
|
|
83
|
+
success = "success", // 成功
|
|
84
|
+
failed = "failed", // 失败
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 使用枚举
|
|
88
|
+
const result = await this.mapper.select({
|
|
89
|
+
where: { status: JobStatus.pending }
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
await this.mapper.updateOne({
|
|
93
|
+
where: { id },
|
|
94
|
+
row: { status: JobStatus.running }
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
switch (detail.status) {
|
|
98
|
+
case JobStatus.success:
|
|
99
|
+
break;
|
|
100
|
+
case JobStatus.failed:
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 规范建议
|
|
106
|
+
1. **状态字段优先使用枚举**:任何表示状态的字段都应该使用枚举
|
|
107
|
+
2. **枚举命名**:使用 PascalCase,如 `JobStatus`、`ItemType`
|
|
108
|
+
3. **枚举值**:字符串枚举推荐,便于调试和序列化
|
|
109
|
+
4. **注释说明**:为每个枚举值添加 JSDoc 注释
|
|
110
|
+
|
|
111
|
+
### 完整示例
|
|
112
|
+
```typescript
|
|
113
|
+
// types/Enums.ts
|
|
114
|
+
export enum JobStatus {
|
|
115
|
+
pending = "pending",
|
|
116
|
+
running = "running",
|
|
117
|
+
success = "success",
|
|
118
|
+
failed = "failed",
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export enum ItemType {
|
|
122
|
+
typeA = "typeA",
|
|
123
|
+
typeB = "typeB",
|
|
124
|
+
typeC = "typeC",
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export enum ExecuteMode {
|
|
128
|
+
now = "now",
|
|
129
|
+
schedule = "schedule",
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 使用
|
|
133
|
+
import { JobStatus, ItemType } from "@/types/Enums";
|
|
134
|
+
|
|
135
|
+
class Service {
|
|
136
|
+
async create(type: ItemType) {
|
|
137
|
+
const item = new Record({
|
|
138
|
+
itemType: type,
|
|
139
|
+
status: JobStatus.pending,
|
|
140
|
+
executeMode: ExecuteMode.now,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|