@goplus123/core-api 1.0.1

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/README.md ADDED
@@ -0,0 +1,462 @@
1
+ # @goplus123/core-api
2
+
3
+ `core-api` 是一个以 `initSDK` 为装配点的单例 SDK,内部统一封装了三种传输层(HTTP / WS / gRPC),并提供:
4
+
5
+ - spec 驱动的统一调用入口:`requestApi`
6
+ - 类型安全的 service client:`sdk.cms.* / sdk.admin.* / sdk.platform.*`
7
+ - WS 推送订阅与本地事件分发:`notify(type)`
8
+ - 统一错误归一化与中间件:`apiError.use(...)`
9
+
10
+ 入口文件:[`src/index.ts`](file:///d:/working/GX/tsup/packages/api/src/index.ts)
11
+
12
+ ---
13
+
14
+ ## 项目目录结构
15
+
16
+ ```txt
17
+ packages/api
18
+ src/
19
+ index.ts SDK 入口(initSDK / requestApi / notify / apiError)
20
+ client/ HTTP/WS/gRPC client + unifiedClient + interceptors
21
+ routing/router.ts spec registry 构建 + requestApi 路由与兜底逻辑
22
+ spec/ spec 驱动路由表(apiMap / endpoints 定义)
23
+ modules/ legacy 模块式封装(主要用于部分 gRPC 服务兼容)
24
+ grpcWeb/ 生成代码(proto → ts),不要手改
25
+ utils/ 小工具(如 fetchGuid)
26
+ ```
27
+
28
+ 建议的扩展方式:
29
+
30
+ - 新增/调整业务接口路由:改 `src/spec/*`,由 `apiMap` 生效
31
+ - 调整调用链行为(鉴权/日志/错误归一化):改 `src/client/*` 与 `src/routing/router.ts`
32
+ - 兼容历史“模块式调用”:保留 `src/modules/*`(目前用于 auth/packet/... 等)
33
+
34
+ ## 调用链路概览
35
+
36
+ 从“推荐的 service client”到具体 transport,大致会走:
37
+
38
+ 1. `sdk.<service>.<method>(payload, options?)`
39
+ 2. `requestApi({ service, functionName, requestParam, wsOptions/callOptions })`
40
+ 3. 路由选择与兜底:[`src/routing/router.ts`](file:///d:/working/GX/tsup/packages/api/src/routing/router.ts)
41
+ - 先查 spec registry(由 `rebuildEndpointRegistry()` 从 `apiMap` 构建)
42
+ - 命中后交给 `UnifiedApiClient` 分发到 `grpc/ws/http`
43
+ - 未命中则尝试 legacy `serviceInstances`
44
+ - 再未命中则兜底走 raw WS(需要已初始化 `ws`)
45
+
46
+ ## 常见开发场景
47
+
48
+ - 新增 gRPC endpoint:先确认 `src/grpcWeb/grpc/**` 有对应 service/schema,再在 `src/spec/<service>.ts` 增加 `defineEndpoint`
49
+ - 给同一方法加多协议:使用 `defineEndpointGroup`,同时提供 `grpc/ws/http` 变体
50
+ - 改默认 transport 或自动选择策略:调整 [`pickEndpoint`](file:///d:/working/GX/tsup/packages/api/src/routing/router.ts#L267-L296)
51
+ - 新增全局鉴权/日志:通过 `initSDK({ auth, logger })`,或改 `src/client/interceptors.ts`
52
+
53
+ ---
54
+
55
+ ## 安装与导入
56
+
57
+ ```ts
58
+ import { initSDK, requestApi, notify, apiError, fetchGuid } from '@goplus123/core-api'
59
+ ```
60
+
61
+ ---
62
+
63
+ ## 快速开始
64
+
65
+ ### 1) 初始化
66
+
67
+ `initSDK` 是 SDK 的唯一初始化入口(单例,多次调用会覆盖旧状态)。
68
+
69
+ ```ts
70
+ const sdk = initSDK({
71
+ ws: {
72
+ url: 'wss://example.com/websocket',
73
+ protocols: fetchGuid(),
74
+ debug: true,
75
+ },
76
+ grpc: {
77
+ baseUrl: 'https://example.com/cms',
78
+ },
79
+ http: {
80
+ baseUrl: 'https://example.com/api',
81
+ },
82
+ headerConfig: {
83
+ version: '1.0.0',
84
+ platformId: '50',
85
+ isReload: false,
86
+ deviceType: 1,
87
+ deviceId: '',
88
+ childPlatformId: '50',
89
+ },
90
+ defaultTransport: 'auto',
91
+ auth: {
92
+ getToken: () => localStorage.getItem('token'),
93
+ getHeaders: () => ({ 'x-lang': 'zh-CN' }),
94
+ },
95
+ onApiError: (error) => {
96
+ console.error('[onApiError]', error)
97
+ },
98
+ logger: console,
99
+ })
100
+ ```
101
+
102
+ 初始化后得到:
103
+
104
+ - `sdk.api`:底层 `RequestApi`(包含 `http/ws/grpc` 三个 client)
105
+ - `sdk.client`:统一路由分发的 `UnifiedApiClient`
106
+ - `sdk.rpc`:对外暴露的 `WsRpcClient`(便于调试 / 订阅)
107
+ - `sdk.cms / sdk.admin / sdk.platform`:类型安全的 service client(由 spec 生成)
108
+ - `sdk.apiError / sdk.notify`:全局错误与通知中心(同名导出也可直接使用)
109
+
110
+ 对应实现:[`initSDK`](file:///d:/working/GX/tsup/packages/api/src/index.ts#L372-L578)
111
+
112
+ ### 2) 直接调用(推荐)
113
+
114
+ 优先使用 service client(类型提示完整,第二参随 transport 自动变化):
115
+
116
+ ```ts
117
+ await sdk.cms?.getPageResource({
118
+ resourceId: 'HOME_PAGE',
119
+ })
120
+ ```
121
+
122
+ ### 3) 统一调用(更底层、更灵活)
123
+
124
+ 当你想通过 `service + functionName` 动态调用时,使用 `requestApi`:
125
+
126
+ ```ts
127
+ await requestApi({
128
+ service: 'cms',
129
+ functionName: 'getPageResource',
130
+ requestParam: { resourceId: 'HOME_PAGE' },
131
+ })
132
+ ```
133
+
134
+ ---
135
+
136
+ ## Transport 选择规则
137
+
138
+ `requestApi` 的 transport 决策优先级:
139
+
140
+ 1. 调用参数 `args.transport`
141
+ 2. spec 上的 `defaultTransport`(仅对 `defineEndpointGroup` 生效)
142
+ 3. `initSDK({ defaultTransport })`
143
+ 4. `'auto'`(默认):按当前 SDK 初始化状态优先选择 `grpc → ws → http`
144
+
145
+ 对应逻辑:[`pickEndpoint`](file:///d:/working/GX/tsup/packages/api/src/routing/router.ts#L267-L296)
146
+
147
+ ---
148
+
149
+ ## WS 推送与事件
150
+
151
+ ### 订阅 WS 推送
152
+
153
+ `notify(type)` 会创建一个 channel,并在初始化了 `ws` 时自动挂载到 `ws.notify`。
154
+
155
+ ```ts
156
+ const off = notify<{ gameId: string }>('notifyGameInfo').subscribe((payload) => {
157
+ console.log('game info:', payload)
158
+ })
159
+
160
+ // off()
161
+ ```
162
+
163
+ ### 监听 WS 连接状态
164
+
165
+ SDK 内置了 `ws:status` 通道(初始化时自动绑定到 WS 事件):
166
+
167
+ ```ts
168
+ notify('ws:status').subscribe((s) => {
169
+ console.log('ws status:', s.state, s.readyState)
170
+ })
171
+ ```
172
+
173
+ 也可以同步读取快照:
174
+
175
+ ```ts
176
+ import { getWsSnapshot } from '@goplus123/core-api'
177
+
178
+ const { isConnected, readyState, status } = getWsSnapshot()
179
+ ```
180
+
181
+ ---
182
+
183
+ ## 错误处理
184
+
185
+ ### 全局错误中间件
186
+
187
+ ```ts
188
+ apiError.use((error, ctx, next) => {
189
+ console.error('[apiError]', ctx.source, error)
190
+ return next()
191
+ })
192
+ ```
193
+
194
+ - `initSDK({ onApiError })` 会自动注册一层中间件
195
+ - 未初始化时调用 `apiError.use/emit` 为 no-op(会输出 warn)
196
+
197
+ ---
198
+
199
+ ## API 参考(对外导出)
200
+
201
+ 来自:[`src/index.ts`](file:///d:/working/GX/tsup/packages/api/src/index.ts)
202
+
203
+ ### initSDK(config)
204
+
205
+ - 作用:初始化(或重置)SDK 单例,并按配置装配 HTTP/WS/gRPC、路由、拦截器与 service clients
206
+ - 入参类型:`RequestApiConfig`(见 [`RequestApiConfig`](file:///d:/working/GX/tsup/packages/api/src/client/requestApi.ts#L7-L36))
207
+ - 返回:`{ api, rpc?, client?, wsNotify?, apiError, notify, ...services }`
208
+
209
+ ### destroySDK()
210
+
211
+ - 作用:释放资源(断开 WS、清空全局状态、重置路由与中心)
212
+
213
+ ### getSDK()
214
+
215
+ - 作用:获取底层 `RequestApi` 实例
216
+ - 注意:未初始化会抛错
217
+
218
+ ### requestApi(args) / callApi(args)
219
+
220
+ - 作用:统一调用入口(spec 驱动路由;若 spec 未命中,会尝试 legacy serviceInstances,再兜底走 raw WS)
221
+ - 入参:
222
+ - `service: string`
223
+ - `functionName: string`
224
+ - `transport?: 'http' | 'ws' | 'grpc' | 'auto'`
225
+ - `requestParam?: any`
226
+ - `wsOptions?: WsRequestOptions`
227
+ - `callOptions?: CallOptions`
228
+ - `meta?: Record<string, any>`
229
+
230
+ ### notify(type)
231
+
232
+ - 作用:获取通知通道(可 `use/subscribe/emit/destroy`)
233
+ - 注意:未初始化会返回 dummy channel(不会报错,但会 warn)
234
+
235
+ ### apiError
236
+
237
+ - `apiError.use(middleware)`:注册错误中间件
238
+ - `apiError.emit(error, source?)`:手动注入一个归一化后的 `ApiError`
239
+
240
+ ### fetchGuid()
241
+
242
+ - 作用:生成一个短 id(playground 用于 WS `protocols`)
243
+
244
+ ---
245
+
246
+ ## spec 驱动的 Service 列表
247
+
248
+ spec 定义入口:[`spec/index.ts`](file:///d:/working/GX/tsup/packages/api/src/spec/index.ts)
249
+
250
+ 按 service 拆分的落点文件:
251
+
252
+ - cms:[`spec/cms.ts`](file:///d:/working/GX/tsup/packages/api/src/spec/cms.ts)
253
+ - admin:[`spec/admin.ts`](file:///d:/working/GX/tsup/packages/api/src/spec/admin.ts)
254
+ - platform:[`spec/platform.ts`](file:///d:/working/GX/tsup/packages/api/src/spec/platform.ts)
255
+
256
+ ### cms(gRPC 为主,部分接口可多协议)
257
+
258
+ - `getPageResource`
259
+ - `getPageGameList`(multi:grpc + ws)
260
+ - `getJackpotResources`
261
+ - `readPopup`
262
+ - `readEventPopup`
263
+ - `getAmbassadorConfig`
264
+ - `getAmbassadorConfigDetail`
265
+ - `searchGame`
266
+ - `searchGameV2`
267
+ - `getSportConfig`
268
+ - `getSportTodayMatch`
269
+ - `getGameSuggestions`
270
+
271
+ ### admin(gRPC)
272
+
273
+ - `signIn`
274
+ - `getAdminProfileByJwt`
275
+ - `validateAdmin`
276
+ - `signUpAdmin`
277
+ - `updatePlatformConfig`
278
+ - `checkHasPermission`
279
+ - `getGameName`
280
+ - `getPlayerCredibilityRemarkId`
281
+ - `getMarketingDepartmentConfig`
282
+ - `createMarketingDepartmentConfig`
283
+ - `updateMarketingDepartmentConfig`
284
+ - `deleteMarketingDepartmentConfig`
285
+
286
+ ### platform(WS)
287
+
288
+ - `getConfig`
289
+
290
+ ---
291
+
292
+ ## 为接口新增/扩展 transport(spec 写法)
293
+
294
+ spec 工具位于:[`spec/endpoint.ts`](file:///d:/working/GX/tsup/packages/api/src/spec/endpoint.ts)
295
+
296
+ ## 开发指南:如何补充 spec 路由
297
+
298
+ spec 的职责是给 `requestApi` 提供“路由表”:
299
+
300
+ - runtime 上它会被加载进 `apiMap`,并在 `initSDK()` 时构建 registry(见 [`rebuildEndpointRegistry`](file:///d:/working/GX/tsup/packages/api/src/routing/router.ts#L84-L132))
301
+ - 调用方可以通过 `sdk.<service>.<method>()` 或 `requestApi({ service, functionName })` 走到对应 transport
302
+ - 当调用方显式指定 `transport` 时,如果 spec 中没有对应变体,会在路由选择阶段同步抛错
303
+
304
+ ### 1) 选择落点文件
305
+
306
+ - cms 的 spec:[`src/spec/cms.ts`](file:///d:/working/GX/tsup/packages/api/src/spec/cms.ts)
307
+ - admin 的 spec:[`src/spec/admin.ts`](file:///d:/working/GX/tsup/packages/api/src/spec/admin.ts)
308
+ - platform(WS)spec:[`src/spec/platform.ts`](file:///d:/working/GX/tsup/packages/api/src/spec/platform.ts)
309
+ - 聚合与 apiMap:[`src/spec/index.ts`](file:///d:/working/GX/tsup/packages/api/src/spec/index.ts)
310
+
311
+ ### 2) 约定:id / service / functionName
312
+
313
+ - `id` 建议固定为:`<service>.<functionName>`,例如 `cms.getPageGameList`
314
+ - `requestApi` 侧入参使用:`service: '<service>'` + `functionName: '<functionName>'`
315
+ - WS spec 内的 `ws.service / ws.functionName` 是 WS 协议层的 service/functionName(通常与上面一致,但不是强制)
316
+
317
+ ### 3) 新增一个 endpoint(单协议)
318
+
319
+ #### gRPC(最常见)
320
+
321
+ 在 `src/spec/cms.ts`(或对应 service 的 spec 文件)中追加:
322
+
323
+ ```ts
324
+ import type { MessageInit } from '../client/grpcClient'
325
+ import { defineEndpoint } from './endpoint'
326
+ import { FrontendService as CmsFrontendService } from '../grpcWeb/grpc/cms/FrontendService_pb'
327
+ import type { PageResourceRes } from '../grpcWeb/grpc/cms/GetPageResource_pb'
328
+ import { PageResourceReqSchema } from '../grpcWeb/grpc/cms/GetPageResource_pb'
329
+
330
+ export const cmsEndpoints = {
331
+ // ...
332
+ getPageResource: defineEndpoint<MessageInit<typeof PageResourceReqSchema>, PageResourceRes>({
333
+ id: 'cms.getPageResource',
334
+ transport: 'grpc',
335
+ grpc: {
336
+ service: CmsFrontendService,
337
+ methodName: 'getPageResource',
338
+ requestSchema: PageResourceReqSchema,
339
+ },
340
+ }),
341
+ }
342
+ ```
343
+
344
+ 调用方式:
345
+
346
+ ```ts
347
+ await requestApi({
348
+ service: 'cms',
349
+ functionName: 'getPageResource',
350
+ requestParam: { resourceId: 'HOME_PAGE' },
351
+ })
352
+ ```
353
+
354
+ 或(更推荐)通过 service client:
355
+
356
+ ```ts
357
+ await sdk.cms?.getPageResource({ resourceId: 'HOME_PAGE' })
358
+ ```
359
+
360
+ #### WS
361
+
362
+ 在 `src/spec/platform.ts`(或你新增的 service 对应 spec 文件)追加:
363
+
364
+ ```ts
365
+ import { defineEndpoint } from './endpoint'
366
+
367
+ export const platformEndpoints = {
368
+ getConfig: defineEndpoint<any, any>({
369
+ id: 'platform.getConfig',
370
+ transport: 'ws',
371
+ ws: { service: 'platform', functionName: 'getConfig', timeout: 10000 },
372
+ }),
373
+ }
374
+ ```
375
+
376
+ 调用方式:
377
+
378
+ ```ts
379
+ await requestApi({
380
+ service: 'platform',
381
+ functionName: 'getConfig',
382
+ requestParam: { platformId: '50' },
383
+ })
384
+ ```
385
+
386
+ ### 4) 给同一个 endpoint 增加新的 transport 变体(多协议)
387
+
388
+ 当你希望同一业务方法同时支持 `grpc/ws/http` 时,用 `defineEndpointGroup`:
389
+
390
+ - `defaultTransport`:该 endpoint 在未指定 `transport` 时的默认选择
391
+ - `transports`:按 transport 分别提供配置
392
+
393
+ ```ts
394
+ import type { MessageInit } from '../client/grpcClient'
395
+ import { defineEndpointGroup } from './endpoint'
396
+ import { FrontendService as CmsFrontendService } from '../grpcWeb/grpc/cms/FrontendService_pb'
397
+ import type { PageGameListRes } from '../grpcWeb/grpc/cms/GetPageGameList_pb'
398
+ import { PageGameListReqSchema } from '../grpcWeb/grpc/cms/GetPageGameList_pb'
399
+
400
+ export const cmsEndpoints = {
401
+ // ...
402
+ getPageGameList: defineEndpointGroup<MessageInit<typeof PageGameListReqSchema>, PageGameListRes>({
403
+ id: 'cms.getPageGameList',
404
+ defaultTransport: 'grpc',
405
+ transports: {
406
+ grpc: { service: CmsFrontendService, methodName: 'getPageGameList', requestSchema: PageGameListReqSchema },
407
+ ws: { service: 'cms', functionName: 'getPageGameList' },
408
+ },
409
+ }),
410
+ }
411
+ ```
412
+
413
+ 调用时可显式选择:
414
+
415
+ ```ts
416
+ await requestApi({
417
+ service: 'cms',
418
+ functionName: 'getPageGameList',
419
+ transport: 'ws',
420
+ requestParam: { resourceId: 'GAME_PAGE_ALL_GAMES', pagination: { limit: 10, offset: 0 } },
421
+ })
422
+ ```
423
+
424
+ 注意:增加 WS/http 变体不仅是前端 spec 改动,服务端也必须实现对应 handler,否则会在 transport 层报错(但不会再卡在路由校验阶段)。
425
+
426
+ ### 5) 新增一个 service(扩展 apiMap / 返回的 sdk)
427
+
428
+ 如果要新增 `fooEndpoints` 这样的新 service:
429
+
430
+ 1. 新建 `src/spec/foo.ts` 并导出 `fooEndpoints`
431
+ 2. 在 `src/spec/index.ts` 里导出 `fooEndpoints`,并把它加入 `apiMap`
432
+ 3. 在 `src/index.ts` 的 `initSDK` 返回类型里补充 `foo?: ApiServiceClient<'foo'>`
433
+ 4. 在 `initSDK` 里按 transport 条件创建 `sdk.foo = createApiServiceClient('foo')`
434
+
435
+ `createApiServiceClient` 的实现位置:[`createApiServiceClient`](file:///d:/working/GX/tsup/packages/api/src/index.ts#L592-L683)
436
+
437
+ ### 单协议(defineEndpoint)
438
+
439
+ ```ts
440
+ import { defineEndpoint } from './endpoint'
441
+
442
+ export const demo = defineEndpoint({
443
+ id: 'cms.getPageResource',
444
+ transport: 'grpc',
445
+ grpc: { service: CmsFrontendService, methodName: 'getPageResource', requestSchema: PageResourceReqSchema },
446
+ })
447
+ ```
448
+
449
+ ### 多协议(defineEndpointGroup)
450
+
451
+ ```ts
452
+ import { defineEndpointGroup } from './endpoint'
453
+
454
+ export const demo = defineEndpointGroup({
455
+ id: 'cms.getPageGameList',
456
+ defaultTransport: 'grpc',
457
+ transports: {
458
+ grpc: { service: CmsFrontendService, methodName: 'getPageGameList', requestSchema: PageGameListReqSchema },
459
+ ws: { service: 'cms', functionName: 'getPageGameList' },
460
+ },
461
+ })
462
+ ```