pdd-skills 3.0.0

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.
Files changed (261) hide show
  1. package/README.md +1478 -0
  2. package/bin/pdd.js +354 -0
  3. package/config/bpmn-rules.yaml +166 -0
  4. package/config/checkstyle.xml +105 -0
  5. package/config/eslint.config.js +48 -0
  6. package/config/pmd.xml +91 -0
  7. package/config/prd-rules.yaml +113 -0
  8. package/config/ruff.toml +45 -0
  9. package/config/sqlfluff.cfg +82 -0
  10. package/hooks/hook-executor.js +332 -0
  11. package/index.js +43 -0
  12. package/lib/api-routes.js +750 -0
  13. package/lib/api-server.js +408 -0
  14. package/lib/cache/cache-config.js +209 -0
  15. package/lib/cache/system-cache.js +852 -0
  16. package/lib/config-manager.js +373 -0
  17. package/lib/generate.js +528 -0
  18. package/lib/grpc/grpc-routes.js +1134 -0
  19. package/lib/grpc/grpc-server.js +912 -0
  20. package/lib/grpc/proto-definitions.js +1033 -0
  21. package/lib/init.js +172 -0
  22. package/lib/iteration/auto-fixer.js +1025 -0
  23. package/lib/iteration/auto-reviewer.js +923 -0
  24. package/lib/iteration/controller.js +577 -0
  25. package/lib/list.js +130 -0
  26. package/lib/mcp-server.js +548 -0
  27. package/lib/openclaw/api-integration.js +535 -0
  28. package/lib/openclaw/cli-integration.js +567 -0
  29. package/lib/openclaw/data-sync.js +845 -0
  30. package/lib/openclaw/openclaw-adapter.js +783 -0
  31. package/lib/plugin/example-plugins/code-stats/index.js +332 -0
  32. package/lib/plugin/example-plugins/code-stats/plugin.json +1 -0
  33. package/lib/plugin/example-plugins/custom-linter/index.js +472 -0
  34. package/lib/plugin/example-plugins/custom-linter/plugin.json +1 -0
  35. package/lib/plugin/example-plugins/hello-world/index.js +86 -0
  36. package/lib/plugin/example-plugins/hello-world/plugin.json +1 -0
  37. package/lib/plugin/plugin-manager.js +655 -0
  38. package/lib/plugin/plugin-sdk.js +565 -0
  39. package/lib/plugin/sandbox.js +627 -0
  40. package/lib/quality/rules/maintainability.js +418 -0
  41. package/lib/quality/rules/performance.js +498 -0
  42. package/lib/quality/rules/readability.js +441 -0
  43. package/lib/quality/rules/robustness.js +504 -0
  44. package/lib/quality/rules/security.js +444 -0
  45. package/lib/quality/scorer.js +576 -0
  46. package/lib/report.js +669 -0
  47. package/lib/sdk-base.js +301 -0
  48. package/lib/sdk-js.js +446 -0
  49. package/lib/sdk-python/README.md +546 -0
  50. package/lib/sdk-python/examples/basic_usage.py +450 -0
  51. package/lib/sdk-python/pdd_sdk/__init__.py +180 -0
  52. package/lib/sdk-python/pdd_sdk/client.py +1170 -0
  53. package/lib/sdk-python/pdd_sdk/events.py +423 -0
  54. package/lib/sdk-python/pdd_sdk/exceptions.py +158 -0
  55. package/lib/sdk-python/pdd_sdk/models.py +518 -0
  56. package/lib/sdk-python/pdd_sdk/utils.py +759 -0
  57. package/lib/token/budget-alert.js +367 -0
  58. package/lib/token/budget-manager.js +485 -0
  59. package/lib/update.js +54 -0
  60. package/lib/utils/logger.js +88 -0
  61. package/lib/verify.js +741 -0
  62. package/lib/version.js +52 -0
  63. package/lib/vm/README.md +102 -0
  64. package/lib/vm/dashboard/api-routes.js +669 -0
  65. package/lib/vm/dashboard/server.js +391 -0
  66. package/lib/vm/dashboard/sse.js +358 -0
  67. package/lib/vm/dashboard/static/css/dashboard.css +1378 -0
  68. package/lib/vm/dashboard/static/index.html +118 -0
  69. package/lib/vm/dashboard/static/js/app.js +949 -0
  70. package/lib/vm/dashboard/static/js/charts.js +913 -0
  71. package/lib/vm/dashboard/static/js/kanban-view.js +1053 -0
  72. package/lib/vm/dashboard/static/js/pipeline-view.js +463 -0
  73. package/lib/vm/dashboard/static/js/quality-view.js +598 -0
  74. package/lib/vm/dashboard/static/js/system-view.js +1021 -0
  75. package/lib/vm/data-provider.js +1191 -0
  76. package/lib/vm/event-bus.js +402 -0
  77. package/lib/vm/hooks/extract-hook.js +307 -0
  78. package/lib/vm/hooks/generate-hook.js +374 -0
  79. package/lib/vm/hooks/hook-interface.js +458 -0
  80. package/lib/vm/hooks/report-hook.js +331 -0
  81. package/lib/vm/hooks/verify-hook.js +454 -0
  82. package/lib/vm/models.js +1003 -0
  83. package/lib/vm/reconciler.js +855 -0
  84. package/lib/vm/scanner.js +988 -0
  85. package/lib/vm/state-schema.js +955 -0
  86. package/lib/vm/state-store.js +733 -0
  87. package/lib/vm/tui/components/card.js +339 -0
  88. package/lib/vm/tui/components/progress-bar.js +368 -0
  89. package/lib/vm/tui/components/sparkline.js +327 -0
  90. package/lib/vm/tui/components/status-light.js +294 -0
  91. package/lib/vm/tui/components/table.js +370 -0
  92. package/lib/vm/tui/input.js +335 -0
  93. package/lib/vm/tui/renderer.js +548 -0
  94. package/lib/vm/tui/screens/kanban-screen.js +397 -0
  95. package/lib/vm/tui/screens/overview-screen.js +357 -0
  96. package/lib/vm/tui/screens/quality-screen.js +336 -0
  97. package/lib/vm/tui/screens/system-screen.js +379 -0
  98. package/lib/vm/tui/tui.js +805 -0
  99. package/package.json +1 -0
  100. package/scripts/cso-analyzer.js +198 -0
  101. package/scripts/eval-runner.js +359 -0
  102. package/scripts/i18n-checker.js +109 -0
  103. package/scripts/linter/activiti-linter.js +272 -0
  104. package/scripts/linter/prd-linter.js +162 -0
  105. package/scripts/linter/report-generator.js +207 -0
  106. package/scripts/linter/run-linters.js +285 -0
  107. package/scripts/linter/sql-linter.js +166 -0
  108. package/scripts/token-analyzer.js +162 -0
  109. package/scripts/vm-test.js +180 -0
  110. package/skills/core/official-doc-writer/LICENSE +21 -0
  111. package/skills/core/official-doc-writer/README.md +232 -0
  112. package/skills/core/official-doc-writer/SKILL.md +475 -0
  113. package/skills/core/official-doc-writer/_meta.json +1 -0
  114. package/skills/core/official-doc-writer/document_generator.py +580 -0
  115. package/skills/core/official-doc-writer/evals/default-evals.json +1 -0
  116. package/skills/core/official-doc-writer/examples.md +150 -0
  117. package/skills/core/official-doc-writer/fonts/FONTS_LIST.md +45 -0
  118. package/skills/core/official-doc-writer/fonts/README.md +141 -0
  119. package/skills/core/official-doc-writer/fonts/SIMFANG.TTF +0 -0
  120. package/skills/core/official-doc-writer/fonts/SIMHEI.TTF +0 -0
  121. package/skills/core/official-doc-writer/fonts/SIMKAI.TTF +0 -0
  122. package/skills/core/official-doc-writer/fonts/SIMSUN.TTC +0 -0
  123. package/skills/core/official-doc-writer/fonts//346/226/271/346/255/243/345/260/217/346/240/207/345/256/213GBK.TTF +0 -0
  124. package/skills/core/official-doc-writer/references/GBT_9704-2012_/345/205/232/346/224/277/346/234/272/345/205/263/345/205/254/346/226/207/346/240/274/345/274/217.md +422 -0
  125. package/skills/core/official-doc-writer/scripts/__pycache__/generate_official_doc.cpython-313.pyc +0 -0
  126. package/skills/core/official-doc-writer/scripts/dialog_manager.py +564 -0
  127. package/skills/core/official-doc-writer/scripts/generate_official_doc.py +252 -0
  128. package/skills/core/official-doc-writer/scripts/install_fonts.py +390 -0
  129. package/skills/core/official-doc-writer/scripts/smart_prompts.py +363 -0
  130. package/skills/core/pdd-ba/SKILL.md +305 -0
  131. package/skills/core/pdd-ba/_meta.json +1 -0
  132. package/skills/core/pdd-ba/evals/default-evals.json +1 -0
  133. package/skills/core/pdd-code-reviewer/SKILL.md +378 -0
  134. package/skills/core/pdd-code-reviewer/_meta.json +1 -0
  135. package/skills/core/pdd-code-reviewer/evals/default-evals.json +1 -0
  136. package/skills/core/pdd-doc-change/SKILL.md +350 -0
  137. package/skills/core/pdd-doc-change/_meta.json +1 -0
  138. package/skills/core/pdd-doc-change/evals/default-evals.json +1 -0
  139. package/skills/core/pdd-doc-gardener/SKILL.md +248 -0
  140. package/skills/core/pdd-doc-gardener/_meta.json +1 -0
  141. package/skills/core/pdd-doc-gardener/evals/default-evals.json +1 -0
  142. package/skills/core/pdd-entropy-reduction/SKILL.md +360 -0
  143. package/skills/core/pdd-entropy-reduction/_meta.json +1 -0
  144. package/skills/core/pdd-entropy-reduction/evals/default-evals.json +1 -0
  145. package/skills/core/pdd-entropy-reduction/references/entropy-report-template.md +287 -0
  146. package/skills/core/pdd-entropy-reduction/references/golden-principles.md +573 -0
  147. package/skills/core/pdd-entropy-reduction/scripts/entropy_scan.py +712 -0
  148. package/skills/core/pdd-extract-features/SKILL.md +320 -0
  149. package/skills/core/pdd-extract-features/_meta.json +1 -0
  150. package/skills/core/pdd-extract-features/evals/default-evals.json +1 -0
  151. package/skills/core/pdd-generate-spec/SKILL.md +418 -0
  152. package/skills/core/pdd-generate-spec/_meta.json +1 -0
  153. package/skills/core/pdd-generate-spec/evals/default-evals.json +1 -0
  154. package/skills/core/pdd-implement-feature/SKILL.md +332 -0
  155. package/skills/core/pdd-implement-feature/_meta.json +1 -0
  156. package/skills/core/pdd-implement-feature/evals/default-evals.json +1 -0
  157. package/skills/core/pdd-main/SKILL.md +540 -0
  158. package/skills/core/pdd-main/_meta.json +1 -0
  159. package/skills/core/pdd-main/evals/default-evals.json +1 -0
  160. package/skills/core/pdd-main/evals/evals.json +215 -0
  161. package/skills/core/pdd-verify-feature/SKILL.md +474 -0
  162. package/skills/core/pdd-verify-feature/_meta.json +1 -0
  163. package/skills/core/pdd-verify-feature/evals/default-evals.json +1 -0
  164. package/skills/core/pdd-vm/evals/default-evals.json +1 -0
  165. package/skills/core/traffic-accident-assessor/LICENSE +29 -0
  166. package/skills/core/traffic-accident-assessor/SKILL.md +439 -0
  167. package/skills/core/traffic-accident-assessor/evals/evals.json +1 -0
  168. package/skills/core/traffic-accident-assessor/references/accident-types.md +369 -0
  169. package/skills/core/traffic-accident-assessor/references/liability-rules.md +287 -0
  170. package/skills/core/traffic-accident-assessor/references/traffic-laws.md +226 -0
  171. package/skills/core/traffic-accident-assessor/references//351/253/230/345/260/224/345/244/253/350/257/264/346/230/216/344/271/246.pdf +32576 -106
  172. package/skills/core/traffic-accident-assessor/scripts/generate_official_statement.py +588 -0
  173. package/skills/core/traffic-accident-assessor/scripts/generate_report.py +495 -0
  174. package/skills/core/traffic-accident-assessor/scripts/generate_statement.py +528 -0
  175. package/skills/core/traffic-accident-assessor.zip +0 -0
  176. package/skills/entropy/expert-arch-enforcer/SKILL.md +292 -0
  177. package/skills/entropy/expert-arch-enforcer/_meta.json +1 -0
  178. package/skills/entropy/expert-arch-enforcer/evals/default-evals.json +1 -0
  179. package/skills/entropy/expert-auto-refactor/SKILL.md +327 -0
  180. package/skills/entropy/expert-auto-refactor/_meta.json +1 -0
  181. package/skills/entropy/expert-auto-refactor/evals/default-evals.json +1 -0
  182. package/skills/entropy/expert-code-quality/SKILL.md +468 -0
  183. package/skills/entropy/expert-code-quality/_meta.json +1 -0
  184. package/skills/entropy/expert-code-quality/evals/default-evals.json +1 -0
  185. package/skills/entropy/expert-code-quality/evals/evals.json +109 -0
  186. package/skills/entropy/expert-code-quality/references/code-smells.md +605 -0
  187. package/skills/entropy/expert-code-quality/references/design-patterns.md +1111 -0
  188. package/skills/entropy/expert-code-quality/references/refactoring-catalog.md +1281 -0
  189. package/skills/entropy/expert-code-quality/references/solid-principles.md +524 -0
  190. package/skills/entropy/expert-entropy-auditor/SKILL.md +276 -0
  191. package/skills/entropy/expert-entropy-auditor/_meta.json +1 -0
  192. package/skills/entropy/expert-entropy-auditor/evals/default-evals.json +1 -0
  193. package/skills/expert/expert-activiti/SKILL.md +497 -0
  194. package/skills/expert/expert-activiti/_meta.json +1 -0
  195. package/skills/expert/expert-mysql/SKILL.md +832 -0
  196. package/skills/expert/expert-mysql/_meta.json +1 -0
  197. package/skills/expert/expert-performance/SKILL.md +379 -0
  198. package/skills/expert/expert-performance/_meta.json +1 -0
  199. package/skills/expert/expert-performance/evals/default-evals.json +1 -0
  200. package/skills/expert/expert-ruoyi/SKILL.md +472 -0
  201. package/skills/expert/expert-ruoyi/_meta.json +1 -0
  202. package/skills/expert/expert-security/SKILL.md +1341 -0
  203. package/skills/expert/expert-security/_meta.json +1 -0
  204. package/skills/expert/expert-security/evals/default-evals.json +1 -0
  205. package/skills/expert/software-architect/SKILL.md +350 -0
  206. package/skills/expert/software-architect/_meta.json +1 -0
  207. package/skills/expert/software-engineer/SKILL.md +437 -0
  208. package/skills/expert/software-engineer/_meta.json +1 -0
  209. package/skills/expert/software-engineer/architecture.md +130 -0
  210. package/skills/expert/software-engineer/patterns.md +151 -0
  211. package/skills/expert/software-engineer/testing.md +135 -0
  212. package/skills/expert/system-architect/SKILL.md +628 -0
  213. package/skills/expert/system-architect/_meta.json +1 -0
  214. package/skills/expert/system-architect/assets/templates/ARCHITECTURE.md +25 -0
  215. package/skills/expert/system-architect/assets/templates/README.md +44 -0
  216. package/skills/expert/system-architect/references/js-ts-standards.md +18 -0
  217. package/skills/expert/system-architect/references/python-standards.md +19 -0
  218. package/skills/expert/system-architect/references/scaffolding.md +61 -0
  219. package/skills/expert/system-architect/references/security-checklist.md +21 -0
  220. package/skills/openspec/openspec-apply-change/SKILL.md +156 -0
  221. package/skills/openspec/openspec-apply-change/_meta.json +1 -0
  222. package/skills/openspec/openspec-archive-change/SKILL.md +114 -0
  223. package/skills/openspec/openspec-archive-change/_meta.json +1 -0
  224. package/skills/openspec/openspec-bulk-archive-change/SKILL.md +246 -0
  225. package/skills/openspec/openspec-bulk-archive-change/_meta.json +1 -0
  226. package/skills/openspec/openspec-continue-change/SKILL.md +118 -0
  227. package/skills/openspec/openspec-continue-change/_meta.json +1 -0
  228. package/skills/openspec/openspec-explore/SKILL.md +288 -0
  229. package/skills/openspec/openspec-explore/_meta.json +1 -0
  230. package/skills/openspec/openspec-ff-change/SKILL.md +101 -0
  231. package/skills/openspec/openspec-ff-change/_meta.json +1 -0
  232. package/skills/openspec/openspec-new-change/SKILL.md +74 -0
  233. package/skills/openspec/openspec-new-change/_meta.json +1 -0
  234. package/skills/openspec/openspec-onboard/SKILL.md +554 -0
  235. package/skills/openspec/openspec-onboard/_meta.json +1 -0
  236. package/skills/openspec/openspec-sync-specs/SKILL.md +138 -0
  237. package/skills/openspec/openspec-sync-specs/_meta.json +1 -0
  238. package/skills/openspec/openspec-verify-change/SKILL.md +168 -0
  239. package/skills/openspec/openspec-verify-change/_meta.json +1 -0
  240. package/skills/pr/pdd-multi-review/SKILL.md +534 -0
  241. package/skills/pr/pdd-multi-review/_meta.json +1 -0
  242. package/skills/pr/pdd-pr-batch/SKILL.md +303 -0
  243. package/skills/pr/pdd-pr-batch/_meta.json +1 -0
  244. package/skills/pr/pdd-pr-create/SKILL.md +344 -0
  245. package/skills/pr/pdd-pr-create/_meta.json +1 -0
  246. package/skills/pr/pdd-pr-merge/SKILL.md +286 -0
  247. package/skills/pr/pdd-pr-merge/_meta.json +1 -0
  248. package/skills/pr/pdd-pr-review/SKILL.md +217 -0
  249. package/skills/pr/pdd-pr-review/_meta.json +1 -0
  250. package/skills/pr/pdd-task-manager/SKILL.md +636 -0
  251. package/skills/pr/pdd-task-manager/_meta.json +1 -0
  252. package/skills/pr/pdd-template-engine/SKILL.md +306 -0
  253. package/skills/pr/pdd-template-engine/_meta.json +1 -0
  254. package/templates/behavior-shaping/iron-law-template.md +87 -0
  255. package/templates/behavior-shaping/rationalization-template.md +62 -0
  256. package/templates/behavior-shaping/red-flags-template.md +70 -0
  257. package/templates/bilingual-template.md +139 -0
  258. package/templates/config/default.yaml +47 -0
  259. package/templates/project/default/README.md +31 -0
  260. package/templates/project/frontend/README.md +46 -0
  261. package/templates/project/java/README.md +48 -0
@@ -0,0 +1,912 @@
1
+ /**
2
+ * PDD gRPC Server
3
+ * 基于 Node.js 内置 http2 模块的 Protocol-Buffer 风格 gRPC 兼容层
4
+ *
5
+ * 本模块实现了一个轻量级的 gRPC 风格服务器,无需引入 @grpc/grpc-js 等外部依赖。
6
+ * 核心设计决策:
7
+ * - 使用 Node.js http2 模块实现 HTTP/2 传输层
8
+ * - 采用 proto3 JSON 编码格式(而非二进制 protobuf)
9
+ * - 模拟 gRPC 的调用约定和错误处理模型
10
+ *
11
+ * 支持的 RPC 模式:
12
+ * - Unary (一元): 标准请求-响应
13
+ * - Server Streaming (服务端流式): SSE over HTTP/2
14
+ * - Client Streaming (客户端流式): chunked transfer
15
+ * - Bidi Streaming (双向流式): HTTP/2 multiplexing
16
+ *
17
+ * @module lib/grpc/grpc-server
18
+ * @author PDD-Skills Team
19
+ * @version 3.0.0
20
+ */
21
+
22
+ import http2 from 'http2';
23
+ import crypto from 'crypto';
24
+ import { EventEmitter } from 'events';
25
+ import { URL } from 'url';
26
+ import {
27
+ GrpcStatus,
28
+ StatusMessages,
29
+ ServiceDefinitions,
30
+ SchemaRegistry,
31
+ encode,
32
+ decode,
33
+ validate
34
+ } from './proto-definitions.js';
35
+
36
+ // ==================== 常量定义 ====================
37
+
38
+ /**
39
+ * gRPC 协议相关常量
40
+ */
41
+ const GRPC_CONSTANTS = {
42
+ /** Content-Type 头值 */
43
+ CONTENT_TYPE: 'application/grpc+protojson',
44
+ /** gRPC 状态头名称 */
45
+ STATUS_HEADER: 'grpc-status',
46
+ /** gRPC 消息头名称 */
47
+ MESSAGE_HEADER: 'grpc-message',
48
+ /** gRPC 超时头(毫秒) */
49
+ TIMEOUT_HEADER: 'grpc-timeout',
50
+ /** 默认超时时间(毫秒) */
51
+ DEFAULT_TIMEOUT: 30000,
52
+ /** 最大消息大小(字节) */
53
+ MAX_MESSAGE_SIZE: 4 * 1024 * 1024, // 4MB
54
+ /** 反射服务路径前缀 */
55
+ REFLECTION_PREFIX: '/grpc.reflection.v1.ServerReflection',
56
+ /** 健康检查服务路径 */
57
+ HEALTH_CHECK_PATH: '/grpc.health.v1.Health/Check'
58
+ };
59
+
60
+ // ==================== 拦截器系统 ====================
61
+
62
+ /**
63
+ * gRPC 拦截器基类
64
+ * 所有拦截器都应继承此类并实现 intercept 方法
65
+ */
66
+ export class GrpcInterceptor {
67
+ /**
68
+ * 创建拦截器实例
69
+ * @param {Object} options - 拦截器配置选项
70
+ */
71
+ constructor(options = {}) {
72
+ this.name = options.name || this.constructor.name;
73
+ this.priority = options.priority || 0; // 数值越小优先级越高
74
+ }
75
+
76
+ /**
77
+ * 拦截请求
78
+ * @param {Object} context - 调用上下文
79
+ * @param {Function} next - 下一个拦截器或处理器
80
+ * @returns {Promise<Object>} 响应数据
81
+ */
82
+ async intercept(context, next) {
83
+ throw new Error('intercept() must be implemented by subclass');
84
+ }
85
+ }
86
+
87
+ /**
88
+ * 日志拦截器 - 记录所有 RPC 调用信息
89
+ */
90
+ export class LoggingInterceptor extends GrpcInterceptor {
91
+ constructor(options = {}) {
92
+ super({ name: 'Logging', ...options });
93
+ this.logger = options.logger || console;
94
+ }
95
+
96
+ async intercept(context, next) {
97
+ const startTime = Date.now();
98
+ const callId = crypto.randomBytes(4).toString('hex');
99
+
100
+ this.logger.info(`[gRPC] [${callId}] → ${context.service}/${context.method}`);
101
+ this.logger.debug(`[gRPC] [${callId}] Request: ${JSON.stringify(context.request).slice(0, 200)}`);
102
+
103
+ try {
104
+ const response = await next();
105
+
106
+ const duration = Date.now() - startTime;
107
+ this.logger.info(
108
+ `[gRPC] [${callId}] ← ${context.service}/${context.method} ` +
109
+ `(${duration}ms) status=${context.status || 0}`
110
+ );
111
+
112
+ return response;
113
+ } catch (error) {
114
+ const duration = Date.now() - startTime;
115
+ this.logger.error(
116
+ `[gRPC] [${callId}] ✗ ${context.service}/${context.method} ` +
117
+ `(${duration}ms) error=${error.message}`
118
+ );
119
+ throw error;
120
+ }
121
+ }
122
+ }
123
+
124
+ /**
125
+ * 认证拦截器 - 验证 API 密钥或 Token
126
+ */
127
+ export class AuthInterceptor extends GrpcInterceptor {
128
+ constructor(options = {}) {
129
+ super({ name: 'Auth', priority: 10, ...options });
130
+ this.apiKeys = new Set(options.apiKeys || []);
131
+ this.tokenValidator = options.tokenValidator || null;
132
+ }
133
+
134
+ async intercept(context, next) {
135
+ // 如果没有配置任何认证方式,跳过验证
136
+ if (this.apiKeys.size === 0 && !this.tokenValidator) {
137
+ return next();
138
+ }
139
+
140
+ const headers = context.headers || {};
141
+ const apiKey = headers['x-api-key'] || headers['authorization']?.replace('Bearer ', '');
142
+
143
+ // 验证 API Key
144
+ if (this.apiKeys.size > 0) {
145
+ if (!apiKey || !this.apiKeys.has(apiKey)) {
146
+ const error = new Error('Invalid or missing API key');
147
+ error.grpcCode = GrpcStatus.UNAUTHENTICATED;
148
+ throw error;
149
+ }
150
+ }
151
+
152
+ // 验证 Token(如果有配置)
153
+ if (this.tokenValidator && apiKey) {
154
+ try {
155
+ context.auth = await this.tokenValidator(apiKey);
156
+ } catch (err) {
157
+ const error = new Error('Token validation failed');
158
+ error.grpcCode = GrpcStatus.UNAUTHENTICATED;
159
+ throw error;
160
+ }
161
+ }
162
+
163
+ return next();
164
+ }
165
+ }
166
+
167
+ /**
168
+ * 性能指标拦截器 - 收集调用统计信息
169
+ */
170
+ export class MetricsInterceptor extends GrpcInterceptor {
171
+ constructor(options = {}) {
172
+ super({ name: 'Metrics', priority: 20, ...options });
173
+ this.metrics = {
174
+ totalCalls: 0,
175
+ callsByMethod: {},
176
+ errorsByMethod: {},
177
+ latencyByMethod: {}
178
+ };
179
+ }
180
+
181
+ async intercept(context, next) {
182
+ const methodKey = `${context.service}.${context.method}`;
183
+ const startTime = Date.now();
184
+
185
+ this.metrics.totalCalls++;
186
+ this.metrics.callsByMethod[methodKey] =
187
+ (this.metrics.callsByMethod[methodKey] || 0) + 1;
188
+
189
+ try {
190
+ const response = await next();
191
+ const duration = Date.now() - startTime;
192
+
193
+ // 更新延迟统计
194
+ if (!this.metrics.latencyByMethod[methodKey]) {
195
+ this.metrics.latencyByMethod[methodKey] = {
196
+ count: 0,
197
+ total: 0,
198
+ min: Infinity,
199
+ max: 0
200
+ };
201
+ }
202
+ const lat = this.metrics.latencyByMethod[methodKey];
203
+ lat.count++;
204
+ lat.total += duration;
205
+ lat.min = Math.min(lat.min, duration);
206
+ lat.max = Math.max(lat.max, duration);
207
+
208
+ return response;
209
+ } catch (error) {
210
+ const duration = Date.now() - startTime;
211
+
212
+ // 更新错误统计
213
+ this.metrics.errorsByMethod[methodKey] =
214
+ (this.metrics.errorsByMethod[methodKey] || 0) + 1;
215
+
216
+ // 将指标附加到上下文
217
+ context.metrics = {
218
+ method: methodKey,
219
+ duration,
220
+ success: false
221
+ };
222
+
223
+ throw error;
224
+ }
225
+ }
226
+
227
+ /**
228
+ * 获取当前收集的指标数据
229
+ * @returns {Object} 指标数据
230
+ */
231
+ getMetrics() {
232
+ // 计算平均延迟
233
+ const avgLatencies = {};
234
+ for (const [method, data] of Object.entries(this.metrics.latencyByMethod)) {
235
+ avgLatencies[method] = {
236
+ avg: Math.round(data.total / data.count),
237
+ min: data.min === Infinity ? 0 : data.min,
238
+ max: data.max,
239
+ count: data.count
240
+ };
241
+ }
242
+
243
+ return {
244
+ totalCalls: this.metrics.totalCalls,
245
+ callsByMethod: this.metrics.callsByMethod,
246
+ errorsByMethod: this.metrics.errorsByMethod,
247
+ latency: avgLatencies,
248
+ timestamp: new Date().toISOString()
249
+ };
250
+ }
251
+ }
252
+
253
+ // ==================== gRPC Server 主类 ====================
254
+
255
+ /**
256
+ * PDD gRPC 兼容服务器
257
+ *
258
+ * 提供类似 gRPC 的远程过程调用接口,基于 HTTP/2 和 proto3 JSON 编码。
259
+ * 支持服务注册、拦截器链、健康检查和服务反射等功能。
260
+ *
261
+ * @example
262
+ * ```javascript
263
+ * import { GRPCServer } from './lib/grpc/grpc-server.js';
264
+ * import { registerGrpcRoutes } from './lib/grpc/grpc-routes.js';
265
+ *
266
+ * const server = new GRPCServer({ port: 50051 });
267
+ * registerGrpcRoutes(server);
268
+ * await server.start();
269
+ * ```
270
+ */
271
+ export class GRPCServer extends EventEmitter {
272
+ /**
273
+ * 创建 gRPC 服务器实例
274
+ * @param {Object} options - 服务器配置选项
275
+ * @param {number} options.port - 监听端口(默认 50051,标准 gRPC 端口)
276
+ * @param {string} options.host - 绑定地址(默认 'localhost')
277
+ * @param {boolean} options.enableTls - 是否启用 TLS(HTTP/2 要求 TLS 或明文升级)
278
+ * @param {Object} options.tlsOptions - TLS 配置(cert, key 等)
279
+ * @param {boolean} options.enableReflection - 是否启用反射服务(默认 true)
280
+ * @param {boolean} options.enableHealthCheck - 是否启用健康检查(默认 true)
281
+ * @param {Array<GrpcInterceptor>} options.interceptors - 全局拦截器列表
282
+ */
283
+ constructor(options = {}) {
284
+ super();
285
+
286
+ this.port = options.port || 50051;
287
+ this.host = options.host || 'localhost';
288
+ this.enableTls = options.enableTls || false;
289
+ this.tlsOptions = options.tlsOptions || null;
290
+ this.enableReflection = options.enableReflection !== false; // 默认启用
291
+ this.enableHealthCheck = options.enableHealthCheck !== false;
292
+
293
+ // 服务注册表: Map<serviceName, Map<methodName, handler>>
294
+ this.services = new Map();
295
+
296
+ // 拦截器链
297
+ this.interceptors = options.interceptors || [
298
+ new LoggingInterceptor(),
299
+ new AuthInterceptor(),
300
+ new MetricsInterceptor()
301
+ ];
302
+
303
+ // HTTP/2 服务器实例
304
+ this.server = null;
305
+
306
+ // 运行状态
307
+ this.started = false;
308
+
309
+ // 绑定方法到实例
310
+ this._handleRequest = this._handleRequest.bind(this);
311
+ this._handleStreamRequest = this._handleStreamRequest.bind(this);
312
+ }
313
+
314
+ /**
315
+ * 注册一个 gRPC 服务及其方法处理器
316
+ *
317
+ * @param {string} serviceName - 服务名称(如 'SpecService')
318
+ * @param {Object} methods - 方法名到处理函数的映射
319
+ * @param {Object} serviceDef - 可选的服务定义(用于反射)
320
+ *
321
+ * @example
322
+ * server.registerService('SpecService', {
323
+ * GenerateSpec: async (request, context) => {
324
+ * return { specId: 'spec-001', content: '...' };
325
+ * },
326
+ * GetSpec: async (request, context) => { ... }
327
+ * });
328
+ */
329
+ registerService(serviceName, methods, serviceDef = null) {
330
+ if (typeof serviceName !== 'string' || !serviceName.trim()) {
331
+ throw new Error('Service name must be a non-empty string');
332
+ }
333
+
334
+ if (!methods || typeof methods !== 'object') {
335
+ throw new Error('Methods must be an object with handler functions');
336
+ }
337
+
338
+ // 创建或获取服务的方法映射
339
+ let serviceMethods = this.services.get(serviceName);
340
+ if (!serviceMethods) {
341
+ serviceMethods = new Map();
342
+ this.services.set(serviceName, serviceMethods);
343
+ }
344
+
345
+ // 注册每个方法
346
+ for (const [methodName, handler] of Object.entries(methods)) {
347
+ if (typeof handler !== 'function') {
348
+ throw new Error(`Handler for ${serviceName}/${methodName} must be a function`);
349
+ }
350
+ serviceMethods.set(methodName, {
351
+ handler,
352
+ definition: serviceDef?.methods?.[methodName] || null
353
+ });
354
+
355
+ this.emit('method:registered', {
356
+ service: serviceName,
357
+ method: methodName
358
+ });
359
+ }
360
+
361
+ this.emit('service:registered', { service: serviceName, methods: Object.keys(methods) });
362
+ }
363
+
364
+ /**
365
+ * 启动 gRPC 服务器
366
+ *
367
+ * @returns {Promise<GRPCServer>} 服务器实例(支持链式调用)
368
+ * @throws {Error} 启动失败时抛出异常
369
+ */
370
+ async start() {
371
+ if (this.started) {
372
+ throw new Error('Server is already running');
373
+ }
374
+
375
+ return new Promise((resolve, reject) => {
376
+ // 创建 HTTP/2 服务器
377
+ if (this.enableTls && this.tlsOptions) {
378
+ this.server = http2.createSecureServer(this.tlsOptions, this._handleRequest);
379
+ } else {
380
+ // 明文 HTTP/2(需要客户端支持 HTTP/2 Knowledge 或使用 h2c)
381
+ this.server = http2.createServer(this._handleRequest);
382
+ }
383
+
384
+ // 会话错误处理
385
+ this.server.on('sessionError', (error) => {
386
+ this.emit('error', error);
387
+ console.error(`[gRPC] Session error: ${error.message}`);
388
+ });
389
+
390
+ // 流错误处理
391
+ this.server.on('streamError', (error, stream) => {
392
+ this.emit('stream:error', { error, stream });
393
+ console.error(`[gRPC] Stream error: ${error.message}`);
394
+ });
395
+
396
+ // 服务器级错误
397
+ this.server.on('error', (error) => {
398
+ if (error.code === 'EADDRINUSE') {
399
+ reject(new Error(`Port ${this.port} is already in use`));
400
+ return;
401
+ }
402
+ reject(error);
403
+ });
404
+
405
+ // 开始监听
406
+ this.server.listen(this.port, this.host, () => {
407
+ this.started = true;
408
+
409
+ const address = this.server.address();
410
+ const protocol = this.enableTls ? 'https' : 'http';
411
+
412
+ console.log('\n' + '='.repeat(70));
413
+ console.log('PDD gRPC Server Started');
414
+ console.log('='.repeat(70));
415
+ console.log(`Address: ${protocol}://${this.host}:${this.port}`);
416
+ console.log(`Protocol: HTTP/2 (${this.enableTls ? 'TLS' : 'plaintext'})`);
417
+ console.log(`Encoding: proto3 JSON`);
418
+ console.log('');
419
+
420
+ // 列出已注册的服务
421
+ this._printRegisteredServices();
422
+
423
+ console.log('');
424
+ console.log('-'.repeat(70));
425
+ console.log('Press Ctrl+C to stop the server');
426
+ console.log('-'.repeat(70) + '\n');
427
+
428
+ this.emit('started', { address, services: this._getServiceList() });
429
+ resolve(this);
430
+ });
431
+ });
432
+ }
433
+
434
+ /**
435
+ * 优雅关闭服务器
436
+ *
437
+ * @param {number} gracePeriodMs - 优雅关闭等待时间(毫秒),默认 5000ms
438
+ * @returns {Promise<void>}
439
+ */
440
+ async stop(gracePeriodMs = 5000) {
441
+ if (!this.started || !this.server) {
442
+ return;
443
+ }
444
+
445
+ return new Promise((resolve) => {
446
+ // 设置强制关闭定时器
447
+ const forceCloseTimeout = setTimeout(() => {
448
+ console.warn('[gRPC] Grace period exceeded, forcing close');
449
+ this.server.destroy();
450
+ resolve();
451
+ }, gracePeriodMs);
452
+
453
+ // 尝试优雅关闭
454
+ this.server.close(() => {
455
+ clearTimeout(forceCloseTimeout);
456
+ this.started = false;
457
+ this.server = null;
458
+ this.emit('stopped');
459
+ console.log('[gRPC] Server stopped gracefully');
460
+ resolve();
461
+ });
462
+ });
463
+ }
464
+
465
+ /**
466
+ * 处理 HTTP/2 请求
467
+ * @private
468
+ */
469
+ _handleRequest(req, res) {
470
+ // 仅接受 POST 方法(gRPC 规范要求)
471
+ if (req.method !== 'POST') {
472
+ this._sendErrorResponse(res, GrpcStatus.UNIMPLEMENTED, 'Method not allowed. Use POST.');
473
+ return;
474
+ }
475
+
476
+ // 解析请求路径
477
+ const pathname = new URL(req.url, `http://${this.host}`).pathname;
478
+
479
+ // 检查 Content-Type
480
+ const contentType = req.headers['content-type'] || '';
481
+ if (!contentType.includes('grpc') && !contentType.includes('json')) {
482
+ this._sendErrorResponse(res, GrpcStatus.INVALID_ARGUMENT, 'Invalid content type. Expected application/grpc+* or application/json');
483
+ return;
484
+ }
485
+
486
+ // 处理特殊端点
487
+ if (this.enableHealthCheck && pathname === GRPC_CONSTANTS.HEALTH_CHECK_PATH) {
488
+ this._handleHealthCheck(req, res);
489
+ return;
490
+ }
491
+
492
+ if (this.enableReflection && pathname.startsWith(GRPC_CONSTANTS.REFLECTION_PREFIX)) {
493
+ this._handleReflection(req, res, pathname);
494
+ return;
495
+ }
496
+
497
+ // 解析服务和方法
498
+ const parsedPath = this._parseRpcPath(pathname);
499
+ if (!parsedPath) {
500
+ this._sendErrorResponse(res, GrpcStatus.UNIMPLEMENTED, `Unknown path: ${pathname}`);
501
+ return;
502
+ }
503
+
504
+ const { service, method } = parsedPath;
505
+
506
+ // 查找处理器
507
+ const serviceHandlers = this.services.get(service);
508
+ if (!serviceHandlers) {
509
+ this._sendErrorResponse(res, GrpcStatus.UNIMPLEMENTED, `Service not found: ${service}`);
510
+ return;
511
+ }
512
+
513
+ const methodHandler = serviceHandlers.get(method);
514
+ if (!methodHandler) {
515
+ this._sendErrorResponse(res, GrpcStatus.UNIMPLEMENTED, `Method not found: ${service}/${method}`);
516
+ return;
517
+ }
518
+
519
+ // 读取请求体
520
+ this._readRequestBody(req)
521
+ .then(bodyData => {
522
+ // 构建调用上下文
523
+ const context = {
524
+ service,
525
+ method,
526
+ request: bodyData,
527
+ headers: req.headers,
528
+ stream: res, // HTTP/2 流引用
529
+ metadata: {}, // 自定义元数据
530
+ deadline: this._getDeadline(req.headers),
531
+ peer: req.socket.remoteAddress,
532
+ status: GrpcStatus.OK
533
+ };
534
+
535
+ // 执行拦截器链 + 处理器
536
+ return this._executeWithInterceptors(
537
+ context,
538
+ methodHandler.handler,
539
+ methodHandler.definition
540
+ );
541
+ })
542
+ .then(responseData => {
543
+ this._sendSuccessResponse(res, responseData);
544
+ })
545
+ .catch(error => {
546
+ this._handleError(error, res, context);
547
+ });
548
+ }
549
+
550
+ /**
551
+ * 处理流式请求(用于 Server Streaming / Bidi Streaming)
552
+ * @private
553
+ */
554
+ _handleStreamRequest(stream, headers) {
555
+ // TODO: 实现完整的流式 RPC 支持
556
+ // 当前版本主要支持 Unary 调用
557
+ console.warn('[gRPC] Streaming requests not fully implemented yet');
558
+ }
559
+
560
+ /**
561
+ * 执行带拦截器的调用链
562
+ * @private
563
+ */
564
+ async _executeWithInterceptors(context, handler, definition) {
565
+ // 构建拦截器链
566
+ const sortedInterceptors = [...this.interceptors]
567
+ .sort((a, b) => a.priority - b.priority);
568
+
569
+ // 创建执行链
570
+ let chain = async () => {
571
+ // 在调用实际处理器之前进行输入验证
572
+ if (definition?.requestType) {
573
+ const requestSchema = SchemaRegistry[definition.requestType];
574
+ if (requestSchema) {
575
+ const validation = validate(context.request, requestSchema);
576
+ if (!validation.valid) {
577
+ const error = new Error(`Validation failed: ${validation.errors.join('; ')}`);
578
+ error.grpcCode = GrpcStatus.INVALID_ARGUMENT;
579
+ error.validationErrors = validation.errors;
580
+ throw error;
581
+ }
582
+ }
583
+ }
584
+
585
+ // 调用实际的 RPC 处理器
586
+ const result = await handler(context.request, context);
587
+
588
+ // 输出验证
589
+ if (definition?.responseType) {
590
+ const responseSchema = SchemaRegistry[definition.responseType];
591
+ if (responseSchema) {
592
+ const validation = validate(result, responseSchema);
593
+ if (!validation.valid) {
594
+ console.warn(`[gRPC] Response validation warnings:`, validation.errors);
595
+ // 不抛出错误,只记录警告
596
+ }
597
+ }
598
+ }
599
+
600
+ return result;
601
+ };
602
+
603
+ // 从后向前包装拦截器
604
+ for (let i = sortedInterceptors.length - 1; i >= 0; i--) {
605
+ const interceptor = sortedInterceptors[i];
606
+ const next = chain;
607
+ chain = () => interceptor.intercept(context, next);
608
+ }
609
+
610
+ return chain();
611
+ }
612
+
613
+ /**
614
+ * 读取请求体
615
+ * @private
616
+ */
617
+ _readRequestBody(req) {
618
+ return new Promise((resolve, reject) => {
619
+ const chunks = [];
620
+ let size = 0;
621
+
622
+ req.on('data', chunk => {
623
+ size += chunk.length;
624
+ if (size > GRPC_CONSTANTS.MAX_MESSAGE_SIZE) {
625
+ req.destroy();
626
+ reject(new Error(`Request body too large (max ${GRPC_CONSTANTS.MAX_MESSAGE_SIZE} bytes)`));
627
+ return;
628
+ }
629
+ chunks.push(chunk);
630
+ });
631
+
632
+ req.on('end', () => {
633
+ const body = Buffer.concat(chunks).toString('utf-8');
634
+ try {
635
+ const data = body.trim() ? JSON.parse(body) : {};
636
+ resolve(data);
637
+ } catch (e) {
638
+ reject(new Error(`Invalid JSON in request body: ${e.message}`));
639
+ }
640
+ });
641
+
642
+ req.on('error', reject);
643
+ });
644
+ }
645
+
646
+ /**
647
+ * 发送成功响应
648
+ * @private
649
+ */
650
+ _sendSuccessResponse(res, data) {
651
+ const responseBody = JSON.stringify(data);
652
+
653
+ res.writeHead(200, {
654
+ 'Content-Type': GRPC_CONSTANTS.CONTENT_TYPE,
655
+ 'Content-Length': Buffer.byteLength(responseBody),
656
+ [GRPC_CONSTANTS.STATUS_HEADER]: String(GrpcStatus.OK),
657
+ [GRPC_CONSTANTS.MESSAGE_HEADER]: StatusMessages[GrpcStatus.OK],
658
+ 'Access-Control-Allow-Origin': '*',
659
+ 'Access-Control-Allow-Methods': 'POST, OPTIONS',
660
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization, x-api-key, grpc-timeout, grpc-encoding'
661
+ });
662
+
663
+ res.end(responseBody);
664
+ }
665
+
666
+ /**
667
+ * 发送错误响应
668
+ * @private
669
+ */
670
+ _sendErrorResponse(res, code, message, details = null) {
671
+ const errorBody = JSON.stringify({
672
+ code,
673
+ message,
674
+ details: details || []
675
+ });
676
+
677
+ res.writeHead(200, { // gRPC 错误也返回 200,通过 trailer/header 传递状态
678
+ 'Content-Type': GRPC_CONSTANTS.CONTENT_TYPE,
679
+ 'Content-Length': Buffer.byteLength(errorBody),
680
+ [GRPC_CONSTANTS.STATUS_HEADER]: String(code),
681
+ [GRPC_CONSTANTS.MESSAGE_HEADER]: encodeURIComponent(message),
682
+ 'Access-Control-Allow-Origin': '*'
683
+ });
684
+
685
+ res.end(errorBody);
686
+ }
687
+
688
+ /**
689
+ * 处理错误
690
+ * @private
691
+ */
692
+ _handleError(error, res, context) {
693
+ const statusCode = error.grpcCode || GrpcStatus.INTERNAL;
694
+ const message = error.message || 'Internal server error';
695
+
696
+ // 更新上下文状态
697
+ if (context) {
698
+ context.status = statusCode;
699
+ }
700
+
701
+ this.emit('error', { error, context });
702
+
703
+ this._sendErrorResponse(res, statusCode, message);
704
+ }
705
+
706
+ /**
707
+ * 处理健康检查请求
708
+ * @private
709
+ */
710
+ _handleHealthCheck(req, res) {
711
+ const healthStatus = this.started ? 1 : 2; // SERVING=1, NOT_SERVING=2
712
+
713
+ const responseBody = JSON.stringify({
714
+ status: healthStatus
715
+ });
716
+
717
+ res.writeHead(200, {
718
+ 'Content-Type': GRPC_CONSTANTS.CONTENT_TYPE,
719
+ 'Content-Length': Buffer.byteLength(responseBody),
720
+ [GRPC_CONSTANTS.STATUS_HEADER]: String(GrpcStatus.OK)
721
+ });
722
+
723
+ res.end(responseBody);
724
+ }
725
+
726
+ /**
727
+ * 处理服务反射请求
728
+ * 实现简单的 gRPC Server Reflection Protocol
729
+ * @private
730
+ */
731
+ _handleReflection(req, res, pathname) {
732
+ // 简化版反射:列出所有已注册的服务和方法
733
+ const reflectionData = {
734
+ services: [],
735
+ timestamp: new Date().toISOString()
736
+ };
737
+
738
+ for (const [serviceName, methods] of this.services.entries()) {
739
+ const serviceInfo = {
740
+ name: serviceName,
741
+ methods: []
742
+ };
743
+
744
+ for (const [methodName, def] of methods.entries()) {
745
+ serviceInfo.methods.push({
746
+ name: methodName,
747
+ requestType: def.definition?.requestType || 'unknown',
748
+ responseType: def.definition?.responseType || 'unknown'
749
+ });
750
+ }
751
+
752
+ reflectionData.services.push(serviceInfo);
753
+ }
754
+
755
+ const responseBody = JSON.stringify(reflectionData);
756
+
757
+ res.writeHead(200, {
758
+ 'Content-Type': GRPC_CONSTANTS.CONTENT_TYPE,
759
+ 'Content-Length': Buffer.byteLength(responseBody),
760
+ [GRPC_CONSTANTS.STATUS_HEADER]: String(GrpcStatus.OK)
761
+ });
762
+
763
+ res.end(responseBody);
764
+ }
765
+
766
+ /**
767
+ * 解析 RPC 路径
768
+ * 支持格式: /{package}.{Service}/{Method}
769
+ * @private
770
+ * @returns {{service: string, method: string}|null}
771
+ */
772
+ _parseRpcPath(pathname) {
773
+ // 移除开头的斜杠
774
+ const path = pathname.replace(/^\//, '');
775
+
776
+ // 分割服务和方法的分隔符是最后一个 /
777
+ const lastSlashIndex = path.lastIndexOf('/');
778
+
779
+ if (lastSlashIndex <= 0 || lastSlashIndex >= path.length - 1) {
780
+ return null;
781
+ }
782
+
783
+ const servicePart = path.substring(0, lastSlashIndex);
784
+ const methodPart = path.substring(lastSlashIndex + 1);
785
+
786
+ // 服务部分可能包含包名(如 pdd.SpecService)
787
+ // 提取最后的类名作为服务键
788
+ const serviceParts = servicePart.split('.');
789
+ const serviceName = serviceParts[serviceParts.length - 1];
790
+
791
+ if (!serviceName || !methodPart) {
792
+ return null;
793
+ }
794
+
795
+ return {
796
+ service: serviceName,
797
+ method: methodPart
798
+ };
799
+ }
800
+
801
+ /**
802
+ * 从请求头获取截止时间
803
+ * @private
804
+ */
805
+ _getDeadline(headers) {
806
+ const timeoutHeader = headers[GRPC_CONSTANTS.TIMEOUT_HEADER];
807
+
808
+ if (!timeoutHeader) {
809
+ return Date.now() + GRPC_CONSTANTS.DEFAULT_TIMEOUT;
810
+ }
811
+
812
+ // gRPC timeout 格式: Number followed by unit (H/M/S/m/u/n)
813
+ const match = String(timeoutHeader).match(/^(\d+)([HMSmun])$/);
814
+ if (!match) {
815
+ return Date.now() + GRPC_CONSTANTS.DEFAULT_TIMEOUT;
816
+ }
817
+
818
+ const value = parseInt(match[1], 10);
819
+ const unit = match[2];
820
+
821
+ const multipliers = {
822
+ 'H': 3600000, // 小时
823
+ 'M': 60000, // 分钟
824
+ 'S': 1000, // 秒
825
+ 'm': 1, // 毫秒
826
+ 'u': 0.001, // 微秒
827
+ 'n': 0.000001 // 纳秒
828
+ };
829
+
830
+ return Date.now() + value * (multipliers[unit] || 1000);
831
+ }
832
+
833
+ /**
834
+ * 打印已注册的服务信息
835
+ * @private
836
+ */
837
+ _printRegisteredServices() {
838
+ console.log('Registered Services:');
839
+ console.log('-'.repeat(50));
840
+
841
+ for (const [serviceName, methods] of this.services.entries()) {
842
+ console.log(`\n ${serviceName}:`);
843
+ for (const [methodName] of methods.entries()) {
844
+ console.log(` └─ ${methodName}()`);
845
+ }
846
+ }
847
+
848
+ if (this.enableHealthCheck) {
849
+ console.log(`\n Health Check:`);
850
+ console.log(` └─ Check() [${GRPC_CONSTANTS.HEALTH_CHECK_PATH}]`);
851
+ }
852
+
853
+ if (this.enableReflection) {
854
+ console.log(`\n Reflection Service: enabled`);
855
+ }
856
+ }
857
+
858
+ /**
859
+ * 获取已注册的服务列表
860
+ * @returns {Array<string>} 服务名称数组
861
+ * @private
862
+ */
863
+ _getServiceList() {
864
+ return Array.from(this.services.keys());
865
+ }
866
+
867
+ /**
868
+ * 获取服务器的性能指标
869
+ * @returns {Object} 指标数据
870
+ */
871
+ getMetrics() {
872
+ const metricsInterceptor = this.interceptors.find(
873
+ i => i instanceof MetricsInterceptor
874
+ );
875
+
876
+ return metricsInterceptor ? metricsInterceptor.getMetrics() : null;
877
+ }
878
+ }
879
+
880
+ // ==================== 导出 ====================
881
+
882
+ /**
883
+ * 创建并启动 gRPC 服务器的便捷函数
884
+ *
885
+ * @param {Object} options - 服务器配置
886
+ * @param {Function} registerFn - 服务注册回调函数,接收 server 实例作为参数
887
+ * @returns {Promise<GRPCServer>} 已启动的服务器实例
888
+ *
889
+ * @example
890
+ * ```javascript
891
+ * import { createGrpcServer } from './lib/grpc/grpc-server.js';
892
+ * import { registerGrpcRoutes } from './lib/grpc/grpc-routes.js';
893
+ *
894
+ * const server = await createGrpcServer(
895
+ * { port: 50051 },
896
+ * registerGrpcRoutes
897
+ * );
898
+ * ```
899
+ */
900
+ export async function createGrpcServer(options = {}, registerFn = null) {
901
+ const server = new GRPCServer(options);
902
+
903
+ // 执行服务注册
904
+ if (typeof registerFn === 'function') {
905
+ await registerFn(server);
906
+ }
907
+
908
+ await server.start();
909
+ return server;
910
+ }
911
+
912
+ export default { GRPCServer, createGrpcServer };