6aspec 2.0.0-dev.2

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 (110) hide show
  1. package/.6aspec/rules/biz/api_rule.md +578 -0
  2. package/.6aspec/rules/biz/background_job_rule.md +719 -0
  3. package/.6aspec/rules/biz/c_user_system_rule.md +240 -0
  4. package/.6aspec/rules/biz/code.md +39 -0
  5. package/.6aspec/rules/biz/event_subscriber_rule.md +529 -0
  6. package/.6aspec/rules/biz/project-structure.md +90 -0
  7. package/.6aspec/rules/biz/scheduled_job_rule.md +850 -0
  8. package/.6aspec/rules/brown/brown_archive_sop.md +132 -0
  9. package/.6aspec/rules/brown/brown_constitution.md +20 -0
  10. package/.6aspec/rules/brown/brown_continue_sop.md +97 -0
  11. package/.6aspec/rules/brown/brown_design_sop.md +155 -0
  12. package/.6aspec/rules/brown/brown_ff_sop.md +194 -0
  13. package/.6aspec/rules/brown/brown_impact_sop.md +296 -0
  14. package/.6aspec/rules/brown/brown_implement_sop.md +133 -0
  15. package/.6aspec/rules/brown/brown_list_sop.md +69 -0
  16. package/.6aspec/rules/brown/brown_new_sop.md +257 -0
  17. package/.6aspec/rules/brown/brown_proposal_sop.md +160 -0
  18. package/.6aspec/rules/brown/brown_quick_sop.md +134 -0
  19. package/.6aspec/rules/brown/brown_review_sop.md +270 -0
  20. package/.6aspec/rules/brown/brown_rollback_sop.md +188 -0
  21. package/.6aspec/rules/brown/brown_specs_sop.md +227 -0
  22. package/.6aspec/rules/brown/brown_status_sop.md +135 -0
  23. package/.6aspec/rules/brown/brown_tasks_sop.md +202 -0
  24. package/.6aspec/rules/brown/brown_understand_sop.md +211 -0
  25. package/.6aspec/rules/brown/brown_verify_sop.md +360 -0
  26. package/.6aspec/rules/green/6A_archive_sop.md +301 -0
  27. package/.6aspec/rules/green/6A_clarify_sop.md +238 -0
  28. package/.6aspec/rules/green/6A_code_implementation_sop.md +110 -0
  29. package/.6aspec/rules/green/6A_constitution.md +52 -0
  30. package/.6aspec/rules/green/6A_continue_sop.md +186 -0
  31. package/.6aspec/rules/green/6A_design_sop.md +228 -0
  32. package/.6aspec/rules/green/6A_import_model_table_sop.md +120 -0
  33. package/.6aspec/rules/green/6A_init_event_list_sop.md +62 -0
  34. package/.6aspec/rules/green/6A_init_map_sop.md +79 -0
  35. package/.6aspec/rules/green/6A_model_sop.md +210 -0
  36. package/.6aspec/rules/green/6A_new_sop.md +319 -0
  37. package/.6aspec/rules/green/6A_rollback_sop.md +198 -0
  38. package/.6aspec/rules/green/6A_status_sop.md +275 -0
  39. package/.6aspec/rules/green/6A_tasks_sop.md +181 -0
  40. package/.6aspec/rules/green/6A_visual_logic_sop.md +121 -0
  41. package/.6aspec/rules/green/green_status_schema.md +293 -0
  42. package/.6aspec/script/create_entities_from_markdown.py +688 -0
  43. package/.claude/commands/6aspec/brown/archive.md +11 -0
  44. package/.claude/commands/6aspec/brown/continue.md +11 -0
  45. package/.claude/commands/6aspec/brown/design.md +11 -0
  46. package/.claude/commands/6aspec/brown/ff.md +11 -0
  47. package/.claude/commands/6aspec/brown/impact.md +11 -0
  48. package/.claude/commands/6aspec/brown/implement.md +11 -0
  49. package/.claude/commands/6aspec/brown/list.md +11 -0
  50. package/.claude/commands/6aspec/brown/new.md +11 -0
  51. package/.claude/commands/6aspec/brown/proposal.md +11 -0
  52. package/.claude/commands/6aspec/brown/quick.md +11 -0
  53. package/.claude/commands/6aspec/brown/review.md +11 -0
  54. package/.claude/commands/6aspec/brown/rollback.md +11 -0
  55. package/.claude/commands/6aspec/brown/specs.md +11 -0
  56. package/.claude/commands/6aspec/brown/status.md +11 -0
  57. package/.claude/commands/6aspec/brown/tasks.md +11 -0
  58. package/.claude/commands/6aspec/brown/understand.md +11 -0
  59. package/.claude/commands/6aspec/brown/verify.md +11 -0
  60. package/.claude/commands/6aspec/green/archive.md +8 -0
  61. package/.claude/commands/6aspec/green/clarify.md +13 -0
  62. package/.claude/commands/6aspec/green/continue.md +8 -0
  63. package/.claude/commands/6aspec/green/design.md +8 -0
  64. package/.claude/commands/6aspec/green/execute-task.md +20 -0
  65. package/.claude/commands/6aspec/green/import-model-table.md +8 -0
  66. package/.claude/commands/6aspec/green/init.md +14 -0
  67. package/.claude/commands/6aspec/green/model.md +12 -0
  68. package/.claude/commands/6aspec/green/new.md +13 -0
  69. package/.claude/commands/6aspec/green/rollback.md +8 -0
  70. package/.claude/commands/6aspec/green/status.md +8 -0
  71. package/.claude/commands/6aspec/green/tasks.md +8 -0
  72. package/.claude/commands/6aspec/green/visual-logic.md +9 -0
  73. package/.claude/settings.local.json +8 -0
  74. package/.cursor/commands/6aspec/brown/archive.md +9 -0
  75. package/.cursor/commands/6aspec/brown/continue.md +9 -0
  76. package/.cursor/commands/6aspec/brown/design.md +9 -0
  77. package/.cursor/commands/6aspec/brown/ff.md +9 -0
  78. package/.cursor/commands/6aspec/brown/impact.md +9 -0
  79. package/.cursor/commands/6aspec/brown/implement.md +9 -0
  80. package/.cursor/commands/6aspec/brown/list.md +9 -0
  81. package/.cursor/commands/6aspec/brown/new.md +9 -0
  82. package/.cursor/commands/6aspec/brown/proposal.md +9 -0
  83. package/.cursor/commands/6aspec/brown/quick.md +9 -0
  84. package/.cursor/commands/6aspec/brown/review.md +9 -0
  85. package/.cursor/commands/6aspec/brown/rollback.md +9 -0
  86. package/.cursor/commands/6aspec/brown/specs.md +9 -0
  87. package/.cursor/commands/6aspec/brown/status.md +9 -0
  88. package/.cursor/commands/6aspec/brown/tasks.md +9 -0
  89. package/.cursor/commands/6aspec/brown/understand.md +9 -0
  90. package/.cursor/commands/6aspec/brown/verify.md +9 -0
  91. package/.cursor/commands/6aspec/green/archive.md +9 -0
  92. package/.cursor/commands/6aspec/green/clarify.md +14 -0
  93. package/.cursor/commands/6aspec/green/continue.md +9 -0
  94. package/.cursor/commands/6aspec/green/design.md +9 -0
  95. package/.cursor/commands/6aspec/green/execute-task.md +21 -0
  96. package/.cursor/commands/6aspec/green/import-model-table.md +9 -0
  97. package/.cursor/commands/6aspec/green/init.md +15 -0
  98. package/.cursor/commands/6aspec/green/model.md +13 -0
  99. package/.cursor/commands/6aspec/green/new.md +14 -0
  100. package/.cursor/commands/6aspec/green/rollback.md +9 -0
  101. package/.cursor/commands/6aspec/green/status.md +9 -0
  102. package/.cursor/commands/6aspec/green/tasks.md +9 -0
  103. package/.cursor/commands/6aspec/green/visual-logic.md +10 -0
  104. package/README.en.md +36 -0
  105. package/README.md +146 -0
  106. package/bin/6a-spec-install +54 -0
  107. package/bin/6aspec +48 -0
  108. package/lib/cli.js +318 -0
  109. package/lib/installer.js +333 -0
  110. package/package.json +62 -0
@@ -0,0 +1,688 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ 从Markdown文档创建实体表的完整流程
5
+ 1. 解析Markdown文档获取表结构
6
+ 2. 转换为实体JSON格式
7
+ 3. 调用HTTP接口创建表
8
+ """
9
+
10
+ import os
11
+ import json
12
+ import re
13
+ import argparse
14
+ import uuid
15
+ from pathlib import Path
16
+ from typing import Dict, Any, List, Optional
17
+ import requests
18
+
19
+
20
+ # ==================== 第一部分:解析Markdown文档 ====================
21
+
22
+ def parse_length(length_str: str) -> Optional[int]:
23
+ """解析长度字段,返回整数或None"""
24
+ if not length_str or length_str.strip() == '-' or length_str.strip() == '':
25
+ return None
26
+ try:
27
+ return int(length_str.strip())
28
+ except ValueError:
29
+ return None
30
+
31
+
32
+ def parse_required(required_str: str) -> bool:
33
+ """解析是否必填字段"""
34
+ if not required_str:
35
+ return False
36
+ required_str = required_str.strip()
37
+ return required_str == '是' or required_str.lower() == 'true' or required_str == '1'
38
+
39
+
40
+ def is_primary_key(description: str) -> bool:
41
+ """判断是否是主键"""
42
+ if not description:
43
+ return False
44
+ return '主键' in description or 'GUID' in description.upper()
45
+
46
+
47
+ def extract_default_value(description: str) -> Optional[Any]:
48
+ """从字段说明中提取默认值"""
49
+ if not description:
50
+ return None
51
+
52
+ patterns = [
53
+ r'默认[为值]?[::]\s*(\d+)',
54
+ r'默认[为值]?(\d+)',
55
+ r',默认[为值]?[::]?\s*(\d+)',
56
+ ]
57
+
58
+ for pattern in patterns:
59
+ match = re.search(pattern, description)
60
+ if match:
61
+ try:
62
+ return int(match.group(1))
63
+ except ValueError:
64
+ return match.group(1)
65
+
66
+ return None
67
+
68
+
69
+ def parse_table_section(content: str) -> List[Dict[str, Any]]:
70
+ """解析数据库物理设计部分的所有表结构"""
71
+
72
+ # 只匹配「数据库物理设计」章节,不要求「第三步」等前缀(文档格式可能不同)
73
+ section_pattern = r'###\s*[^#\n]*数据库物理设计[^\n]*\n'
74
+ match = re.search(section_pattern, content)
75
+ if not match:
76
+ raise ValueError("未找到'数据库物理设计'章节")
77
+
78
+ start_pos = match.end()
79
+ next_section = re.search(r'\n###\s+', content[start_pos:])
80
+ if next_section:
81
+ section_content = content[start_pos:start_pos + next_section.start()]
82
+ else:
83
+ section_content = content[start_pos:]
84
+
85
+ tables = []
86
+ table_pattern = r'#####\s+\d+\.\s+([^(]+)\s*\(([^)]+)\)'
87
+ table_matches = list(re.finditer(table_pattern, section_content))
88
+
89
+ for i, table_match in enumerate(table_matches):
90
+ table_name_cn = table_match.group(1).strip()
91
+ table_name = table_match.group(2).strip()
92
+
93
+ if i + 1 < len(table_matches):
94
+ table_end = table_matches[i + 1].start()
95
+ else:
96
+ table_end = len(section_content)
97
+
98
+ table_section = section_content[table_match.end():table_end]
99
+
100
+ table_info_pattern = r'\|\s*表中文名称\s*\|\s*([^|]+)\s*\|\s*\n\|\s*[::]\s*---\s*\|\s*[::]\s*---\s*\|\s*\n\|\s*表英文名称\s*\|\s*([^|]+)\s*\|\s*\n\|\s*说明\s*\|\s*([^|]+)\s*\|'
101
+ table_info_match = re.search(table_info_pattern, table_section)
102
+
103
+ if table_info_match:
104
+ table_name_cn = table_info_match.group(1).strip()
105
+ table_name = table_info_match.group(2).strip()
106
+ description = table_info_match.group(3).strip()
107
+ else:
108
+ description = ""
109
+ desc_match = re.search(r'\|\s*说明\s*\|\s*([^|]+)\s*\|', table_section)
110
+ if desc_match:
111
+ description = desc_match.group(1).strip()
112
+
113
+ fields = []
114
+ field_table_pattern = r'\|\s*字段中文名称\s*\|\s*字段英文名称\s*\|\s*字段类型\s*\|\s*长度\s*\|\s*是否必填\s*\|\s*字段说明\s*\|'
115
+ field_table_match = re.search(field_table_pattern, table_section)
116
+
117
+ if field_table_match:
118
+ table_start = field_table_match.end()
119
+ table_end_match = re.search(r'\n\n|\n---|\n#####', table_section[table_start:])
120
+ if table_end_match:
121
+ table_content = table_section[table_start:table_start + table_end_match.start()]
122
+ else:
123
+ table_content = table_section[table_start:]
124
+
125
+ field_lines = [line.strip() for line in table_content.split('\n') if line.strip() and line.strip().startswith('|')]
126
+
127
+ for line in field_lines:
128
+ if re.match(r'^\|\s*[::]\s*---', line):
129
+ continue
130
+
131
+ parts = [p.strip() for p in line.split('|') if p.strip()]
132
+ if len(parts) >= 6:
133
+ field_name_cn = parts[0]
134
+ field_name = parts[1]
135
+ field_type = parts[2]
136
+ length = parse_length(parts[3])
137
+ required = parse_required(parts[4])
138
+ field_description = parts[5]
139
+
140
+ is_pk = is_primary_key(field_description) or 'GUID' in field_name.upper()
141
+ default_value = extract_default_value(field_description)
142
+
143
+ field = {
144
+ "fieldName": field_name,
145
+ "fieldNameCN": field_name_cn,
146
+ "fieldType": field_type,
147
+ "length": length,
148
+ "required": required,
149
+ "description": field_description,
150
+ "isPrimaryKey": is_pk
151
+ }
152
+
153
+ if default_value is not None:
154
+ field["defaultValue"] = default_value
155
+
156
+ fields.append(field)
157
+
158
+ table = {
159
+ "tableName": table_name,
160
+ "tableNameCN": table_name_cn,
161
+ "description": description,
162
+ "fields": fields
163
+ }
164
+
165
+ tables.append(table)
166
+
167
+ return tables
168
+
169
+
170
+ # ==================== 第二部分:转换为实体JSON ====================
171
+
172
+ def map_field_type_to_db_type(field_type: str, length: Optional[int]) -> Dict[str, Any]:
173
+ """将字段类型映射为数据库类型和字段类型"""
174
+ field_type_lower = field_type.lower()
175
+
176
+ if "唯一标识" in field_type or "guid" in field_type_lower:
177
+ return {
178
+ "dbType": "uniqueidentifier",
179
+ "fieldType": "guid",
180
+ "attributeType": "Guid",
181
+ "length": None,
182
+ "decimalPrecision": 0
183
+ }
184
+
185
+ if "单行文本" in field_type:
186
+ db_length = length if length and length > 0 else 512
187
+ return {
188
+ "dbType": "nvarchar",
189
+ "fieldType": "singleLineText",
190
+ "attributeType": f"文本(nvarchar({db_length}))",
191
+ "length": db_length,
192
+ "decimalPrecision": 0
193
+ }
194
+
195
+ if "多行文本" in field_type:
196
+ db_length = length if length and length > 0 else -1
197
+ if db_length > 0 and db_length <= 512:
198
+ return {
199
+ "dbType": "nvarchar",
200
+ "fieldType": "multiLineText",
201
+ "attributeType": f"文本(nvarchar({db_length}))",
202
+ "length": db_length,
203
+ "decimalPrecision": 0
204
+ }
205
+ else:
206
+ return {
207
+ "dbType": "nvarchar",
208
+ "fieldType": "multiLineText",
209
+ "attributeType": "文本(nvarchar(max))",
210
+ "length": -1,
211
+ "decimalPrecision": 0
212
+ }
213
+
214
+ if "日期时间" in field_type or "日期" in field_type or "时间" in field_type:
215
+ return {
216
+ "dbType": "datetime",
217
+ "fieldType": "dateTime",
218
+ "attributeType": "日期与时间",
219
+ "length": None,
220
+ "decimalPrecision": 0
221
+ }
222
+
223
+ if "整数" in field_type:
224
+ return {
225
+ "dbType": "int",
226
+ "fieldType": "digit",
227
+ "attributeType": "整数",
228
+ "length": None,
229
+ "decimalPrecision": 0
230
+ }
231
+
232
+ if "图片" in field_type:
233
+ return {
234
+ "dbType": "nvarchar",
235
+ "fieldType": "picture",
236
+ "attributeType": "图片",
237
+ "length": -1,
238
+ "decimalPrecision": 0
239
+ }
240
+
241
+ db_length = length if length and length > 0 else 512
242
+ return {
243
+ "dbType": "nvarchar",
244
+ "fieldType": "singleLineText",
245
+ "attributeType": f"文本(nvarchar({db_length}))",
246
+ "length": db_length,
247
+ "decimalPrecision": 0
248
+ }
249
+
250
+
251
+ def create_control_config(field_type: str, db_type: str, field_type_name: str,
252
+ display_name: str, is_required: bool, length: Optional[int]) -> Dict[str, Any]:
253
+ """创建控件配置对象"""
254
+
255
+ if field_type_name == "guid":
256
+ return {
257
+ "$type": "Mysoft.Map6.Metadata.Models.Entity.GuidField, Mysoft.Map6.Metadata.Models",
258
+ "isRequired": is_required,
259
+ "displayName": display_name,
260
+ "fillExample": ""
261
+ }
262
+ elif field_type_name == "singleLineText":
263
+ max_length = length if length and length > 0 else 512
264
+ return {
265
+ "$type": "Mysoft.Map6.Metadata.Models.Entity.SingleLineText, Mysoft.Map6.Metadata.Models",
266
+ "isRequired": is_required,
267
+ "displayName": display_name,
268
+ "maxLength": max_length,
269
+ "fillExample": ""
270
+ }
271
+ elif field_type_name == "multiLineText":
272
+ max_length = length if length and length > 0 else 0
273
+ return {
274
+ "$type": "Mysoft.Map6.Metadata.Models.Entity.MultiLineText, Mysoft.Map6.Metadata.Models",
275
+ "isRequired": is_required,
276
+ "displayName": display_name,
277
+ "maxLength": max_length,
278
+ "fillExample": ""
279
+ }
280
+ elif field_type_name == "dateTime":
281
+ return {
282
+ "$type": "Mysoft.Map6.Metadata.Models.Entity.DateTimeField, Mysoft.Map6.Metadata.Models",
283
+ "isRequired": is_required,
284
+ "displayName": display_name,
285
+ "format": "yyyy-MM-dd",
286
+ "fillExample": ""
287
+ }
288
+ elif field_type_name == "digit":
289
+ return {
290
+ "$type": "Mysoft.Map6.Metadata.Models.Entity.Digit, Mysoft.Map6.Metadata.Models",
291
+ "isRequired": is_required,
292
+ "displayName": display_name,
293
+ "unitText": "",
294
+ "unitTextType": 0,
295
+ "fillExample": ""
296
+ }
297
+ elif field_type_name == "picture":
298
+ return {
299
+ "$type": "Mysoft.Map6.Metadata.Models.Entity.PictureField, Mysoft.Map6.Metadata.Models",
300
+ "isRequired": is_required,
301
+ "displayName": display_name,
302
+ "limitFileCount": 1,
303
+ "fileLimitSize": 5,
304
+ "limitType": "*.*",
305
+ "fillExample": ""
306
+ }
307
+
308
+ return {
309
+ "$type": "Mysoft.Map6.Metadata.Models.Entity.SingleLineText, Mysoft.Map6.Metadata.Models",
310
+ "isRequired": is_required,
311
+ "displayName": display_name,
312
+ "maxLength": length if length and length > 0 else 512,
313
+ "fillExample": ""
314
+ }
315
+
316
+
317
+ def convert_field_to_attribute(field: Dict[str, Any], is_primary: bool) -> Dict[str, Any]:
318
+ """将字段转换为属性格式"""
319
+
320
+ field_type = field.get("fieldType", "")
321
+ length = field.get("length")
322
+ field_name = field.get("fieldName", "")
323
+ field_name_cn = field.get("fieldNameCN", "")
324
+ description = field.get("description", "")
325
+ required = field.get("required", False)
326
+ default_value = field.get("defaultValue")
327
+
328
+ type_info = map_field_type_to_db_type(field_type, length)
329
+
330
+ if is_primary and type_info["fieldType"] == "guid":
331
+ attr_default_value = "newid()"
332
+ elif default_value is not None:
333
+ attr_default_value = str(default_value)
334
+ else:
335
+ attr_default_value = ""
336
+
337
+ control = create_control_config(
338
+ field_type,
339
+ type_info["dbType"],
340
+ type_info["fieldType"],
341
+ field_name_cn,
342
+ required,
343
+ type_info["length"]
344
+ )
345
+
346
+ attribute = {
347
+ "attributeType": type_info["attributeType"],
348
+ "defaultValue": attr_default_value,
349
+ "dbType": type_info["dbType"],
350
+ "decimalPrecision": type_info["decimalPrecision"],
351
+ "fieldType": type_info["fieldType"],
352
+ "control": control,
353
+ "displayName": field_name_cn,
354
+ "name": field_name,
355
+ "remark": description,
356
+ "isPrimaryAttribute": is_primary,
357
+ "isNullable": not required,
358
+ "curEntityState": "modify",
359
+ "entityState": "Created"
360
+ }
361
+
362
+ if type_info["length"] is not None:
363
+ attribute["length"] = type_info["length"]
364
+
365
+ return attribute
366
+
367
+
368
+ def convert_table_to_entity(table: Dict[str, Any], application: str, function_guid: str) -> Dict[str, Any]:
369
+ """将表转换为实体格式"""
370
+
371
+ table_name = table.get("tableName", "")
372
+ table_name_cn = table.get("tableNameCN", "")
373
+ description = table.get("description", "")
374
+ fields = table.get("fields", [])
375
+
376
+ primary_key_field = None
377
+ primary_key_name = None
378
+ for field in fields:
379
+ if field.get("isPrimaryKey", False):
380
+ primary_key_field = field
381
+ primary_key_name = field.get("fieldName", "")
382
+ break
383
+
384
+ if not primary_key_name:
385
+ for field in fields:
386
+ if "GUID" in field.get("fieldName", "").upper() or "唯一标识" in field.get("fieldType", ""):
387
+ primary_key_name = field.get("fieldName", "")
388
+ primary_key_field = field
389
+ break
390
+ if not primary_key_name and fields:
391
+ primary_key_name = fields[0].get("fieldName", "")
392
+ primary_key_field = fields[0]
393
+
394
+ attributes = []
395
+ for field in fields:
396
+ field_name = field.get("fieldName", "")
397
+ if field_name == primary_key_name:
398
+ continue
399
+ is_primary = False
400
+ attribute = convert_field_to_attribute(field, is_primary)
401
+ attributes.append(attribute)
402
+
403
+ primary_key_display_name = ""
404
+ if primary_key_field:
405
+ primary_key_display_name = primary_key_field.get("fieldNameCN", "")
406
+
407
+ entity = {
408
+ "entity": {
409
+ "application": application,
410
+ "functionGUID": function_guid,
411
+ "displayName": table_name_cn + "\n",
412
+ "name": table_name,
413
+ "primaryKeyName": primary_key_name,
414
+ "isOpenApprove": False,
415
+ "isEnableIsolateForCompany": False,
416
+ "enableFieldAttribute": True,
417
+ "scope": "current",
418
+ "remark": description,
419
+ "attributes": attributes
420
+ },
421
+ "primaryKeyDisplayName": primary_key_display_name + " ",
422
+ "primaryKeyRemark": ""
423
+ }
424
+
425
+ return entity
426
+
427
+
428
+ def convert_tables_to_entities(tables: List[Dict[str, Any]], application: str, function_guid: str) -> List[Dict[str, Any]]:
429
+ """将表列表转换为实体列表"""
430
+ return [convert_table_to_entity(table, application, function_guid) for table in tables]
431
+
432
+
433
+ # ==================== 第三部分:HTTP请求 ====================
434
+
435
+ def load_config_from_file() -> Dict[str, str]:
436
+ """从用户目录下的.6aspec/modeling_env文件读取配置"""
437
+ config = {}
438
+ config_file = Path.home() / '.6aspec' / 'modeling_env'
439
+
440
+ if config_file.exists():
441
+ try:
442
+ with open(config_file, 'r', encoding='utf-8') as f:
443
+ for line in f:
444
+ line = line.strip()
445
+ if not line or line.startswith('#'):
446
+ continue
447
+ if '=' in line:
448
+ key, value = line.split('=', 1)
449
+ key = key.strip()
450
+ value = value.strip()
451
+ # 只保存非空值
452
+ if value:
453
+ config[key] = value
454
+ except Exception:
455
+ # 读取配置文件失败,忽略错误,继续使用环境变量
456
+ pass
457
+
458
+ return config
459
+
460
+
461
+ def get_host() -> str:
462
+ """从环境变量或配置文件获取host,如果没有则返回默认值"""
463
+ # 首先尝试从环境变量获取
464
+ host = os.environ.get('MODELING_HOST')
465
+ if host:
466
+ return host
467
+
468
+ # 如果环境变量没有,尝试从配置文件读取
469
+ config = load_config_from_file()
470
+ host = config.get('MODELING_HOST')
471
+ if host:
472
+ return host
473
+
474
+ # 都没有则返回默认值
475
+ return 'localhost'
476
+
477
+
478
+ def get_cookie() -> str:
479
+ """从环境变量或配置文件获取Cookie,如果没有则抛出异常"""
480
+ # 首先尝试从环境变量获取
481
+ cookie = os.environ.get('MODELING_COOKIE')
482
+ if cookie:
483
+ return cookie
484
+
485
+ # 如果环境变量没有,尝试从配置文件读取
486
+ config = load_config_from_file()
487
+ cookie = config.get('MODELING_COOKIE')
488
+ if cookie:
489
+ return cookie
490
+
491
+ # 都没有则抛出异常
492
+ raise ValueError(
493
+ "错误:未配置Cookie\n"
494
+ "请通过以下方式之一配置:\n"
495
+ "1. 设置环境变量 MODELING_COOKIE\n"
496
+ " 例如:export MODELING_COOKIE='your_cookie_value'\n"
497
+ "2. 在 ~/.6aspec/modeling_env 文件中配置\n"
498
+ " 例如:MODELING_COOKIE=your_cookie_value"
499
+ )
500
+
501
+
502
+ def send_create_entity_request(
503
+ design_data: str,
504
+ host: Optional[str] = None,
505
+ port: int = 9300,
506
+ cookie: Optional[str] = None
507
+ ) -> requests.Response:
508
+ """发送创建实体请求"""
509
+ if host is None:
510
+ host = get_host()
511
+
512
+ if cookie is None:
513
+ cookie = get_cookie()
514
+
515
+ url = f"http://{host}:{port}/ajax/Mysoft.Map6.Modeling.Handlers.EntityHandler/CreateEntity"
516
+
517
+ headers = {
518
+ 'Accept': 'application/json, text/plain, */*',
519
+ 'Accept-Language': 'zh-CN,zh;q=0.9',
520
+ 'Cache-Control': 'no-cache',
521
+ 'Connection': 'keep-alive',
522
+ 'Content-Type': 'application/x-www-form-urlencoded',
523
+ 'Origin': f'http://{host}:{port}',
524
+ 'Pragma': 'no-cache',
525
+ 'Referer': f'http://{host}:{port}/modeling/entity/importField?func=08de639b-1271-4b5b-8cdb-e7e67fe64bb8&appCode=8906',
526
+ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36',
527
+ 'X-Requested-With': 'XMLHttpRequest',
528
+ 'work-mode': 'design',
529
+ 'Cookie': cookie
530
+ }
531
+
532
+ data = {
533
+ 'designData': design_data
534
+ }
535
+
536
+ response = requests.post(url, headers=headers, data=data)
537
+ return response
538
+
539
+
540
+ # ==================== 主流程 ====================
541
+
542
+ def main():
543
+ parser = argparse.ArgumentParser(description='从Markdown文档创建实体表的完整流程')
544
+ parser.add_argument('markdown_file', type=str, help='Markdown文档路径')
545
+ parser.add_argument('-a', '--application', type=str, required=True, help='应用ID(必填)')
546
+ parser.add_argument('-g', '--function-guid', type=str, required=True, help='functionGUID(必填)')
547
+ parser.add_argument('-H', '--host', type=str, help='服务器地址(默认从环境变量MODELING_HOST获取,否则使用localhost)')
548
+ parser.add_argument('-p', '--port', type=int, default=9300, help='服务器端口(默认:9300)')
549
+ parser.add_argument('-c', '--cookie', type=str, help='Cookie值(默认从环境变量MODELING_COOKIE获取)')
550
+ parser.add_argument('-v', '--verbose', action='store_true', help='显示详细响应信息')
551
+
552
+ args = parser.parse_args()
553
+
554
+ # 步骤1:读取并解析Markdown文档
555
+ print("=" * 60)
556
+ print("步骤1:解析Markdown文档...")
557
+ print("=" * 60)
558
+
559
+ md_path = Path(args.markdown_file)
560
+ if not md_path.exists():
561
+ print(f"错误:文件不存在: {args.markdown_file}")
562
+ return 1
563
+
564
+ try:
565
+ with open(md_path, 'r', encoding='utf-8') as f:
566
+ content = f.read()
567
+ except Exception as e:
568
+ print(f"错误:读取文件失败: {e}")
569
+ return 1
570
+
571
+ try:
572
+ tables = parse_table_section(content)
573
+ print(f"✓ 成功解析 {len(tables)} 个表")
574
+ for i, table in enumerate(tables, 1):
575
+ print(f" {i}. {table['tableName']} - {table['tableNameCN']}")
576
+ except Exception as e:
577
+ print(f"错误:解析失败: {e}")
578
+ return 1
579
+
580
+ # 步骤2:转换为实体JSON
581
+ print("\n" + "=" * 60)
582
+ print("步骤2:转换为实体JSON格式...")
583
+ print("=" * 60)
584
+
585
+ try:
586
+ entities = convert_tables_to_entities(tables, args.application, args.function_guid)
587
+ print(f"✓ 成功转换 {len(entities)} 个实体")
588
+ except Exception as e:
589
+ print(f"错误:转换失败: {e}")
590
+ import traceback
591
+ traceback.print_exc()
592
+ return 1
593
+
594
+ # 步骤3:批量发送HTTP请求创建表
595
+ print("\n" + "=" * 60)
596
+ print("步骤3:批量创建实体表...")
597
+ print("=" * 60)
598
+
599
+ total = len(entities)
600
+ success_count = 0
601
+ fail_count = 0
602
+
603
+ print(f"检测到 {total} 个实体,开始批量发送...\n")
604
+
605
+ for index, entity in enumerate(entities, 1):
606
+ try:
607
+ entity_name = entity.get('entity', {}).get('name', f'实体#{index}')
608
+ print(f"[{index}/{total}] 处理实体: {entity_name}")
609
+
610
+ design_data = json.dumps(entity, ensure_ascii=False)
611
+
612
+ response = send_create_entity_request(
613
+ design_data=design_data,
614
+ host=args.host,
615
+ port=args.port,
616
+ cookie=args.cookie
617
+ )
618
+
619
+ if response.status_code == 200:
620
+ try:
621
+ response_json = response.json()
622
+ if response_json.get('isBusinessLogicError', False):
623
+ fail_count += 1
624
+ error_msg = response_json.get('exception', {}).get('message', '未知错误')
625
+ print(f" ✗ 失败: {error_msg}")
626
+ if args.verbose:
627
+ print(f" 响应: {json.dumps(response_json, ensure_ascii=False, indent=2)}")
628
+ elif 'entityId' in response_json:
629
+ success_count += 1
630
+ entity_id = response_json.get('entityId', '')
631
+ if args.verbose:
632
+ print(f" ✓ 成功: entityId={entity_id}")
633
+ print(f" 响应: {json.dumps(response_json, ensure_ascii=False, indent=2)}")
634
+ else:
635
+ print(f" ✓ 成功: entityId={entity_id}")
636
+ else:
637
+ if 'exception' in response_json:
638
+ fail_count += 1
639
+ error_msg = response_json.get('exception', {}).get('message', '未知错误')
640
+ print(f" ✗ 失败: {error_msg}")
641
+ else:
642
+ success_count += 1
643
+ print(f" ✓ 成功")
644
+ if args.verbose:
645
+ print(f" 响应: {json.dumps(response_json, ensure_ascii=False, indent=2)}")
646
+ except json.JSONDecodeError:
647
+ fail_count += 1
648
+ print(f" ✗ 失败: 响应不是有效的JSON格式")
649
+ if args.verbose:
650
+ print(f" 响应: {response.text[:200]}")
651
+ else:
652
+ fail_count += 1
653
+ print(f" ✗ 失败: HTTP状态码 {response.status_code}")
654
+ if args.verbose:
655
+ try:
656
+ response_json = response.json()
657
+ print(f" 响应: {json.dumps(response_json, ensure_ascii=False, indent=2)}")
658
+ except json.JSONDecodeError:
659
+ print(f" 响应: {response.text[:200]}")
660
+
661
+ except ValueError as e:
662
+ print(f" ✗ 错误: {str(e)}")
663
+ return 1
664
+ except requests.exceptions.RequestException as e:
665
+ fail_count += 1
666
+ print(f" ✗ 请求失败: {e}")
667
+ except Exception as e:
668
+ fail_count += 1
669
+ print(f" ✗ 处理失败: {e}")
670
+ if args.verbose:
671
+ import traceback
672
+ traceback.print_exc()
673
+
674
+ print()
675
+
676
+ # 输出最终结果
677
+ print("=" * 60)
678
+ print("批量处理完成:")
679
+ print(f" 总计: {total}")
680
+ print(f" 成功: {success_count}")
681
+ print(f" 失败: {fail_count}")
682
+ print("=" * 60)
683
+
684
+ return 0 if fail_count == 0 else 1
685
+
686
+
687
+ if __name__ == '__main__':
688
+ exit(main())
@@ -0,0 +1,11 @@
1
+ ---
2
+ name: "6aspec:brown: Archive"
3
+ description: 归档已完成的需求
4
+ category: Workflow
5
+ tags: [workflow, requirement, archive]
6
+ ---
7
+
8
+ 1. Immediate response upon activation: brown field requirement workflow - **Archive** has been activated
9
+ 2. **Read and strictly follow the brown field constitution**: `.6aspec/rules/brown/brown_constitution.md`
10
+ 3. Read file: `.6aspec/rules/brown/brown_archive_sop.md`
11
+ 4. Strictly follow the SOP defined in that file