6aspec 2.0.0-dev.10
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/.6aspec/rules/biz/api_rule.md +578 -0
- package/.6aspec/rules/biz/background_job_rule.md +719 -0
- package/.6aspec/rules/biz/c_user_system_rule.md +240 -0
- package/.6aspec/rules/biz/code.md +39 -0
- package/.6aspec/rules/biz/event_subscriber_rule.md +529 -0
- package/.6aspec/rules/biz/project-structure.md +90 -0
- package/.6aspec/rules/biz/scheduled_job_rule.md +850 -0
- package/.6aspec/rules/brown/brown_archive_sop.md +132 -0
- package/.6aspec/rules/brown/brown_constitution.md +20 -0
- package/.6aspec/rules/brown/brown_continue_sop.md +97 -0
- package/.6aspec/rules/brown/brown_design_sop.md +155 -0
- package/.6aspec/rules/brown/brown_ff_sop.md +194 -0
- package/.6aspec/rules/brown/brown_impact_sop.md +293 -0
- package/.6aspec/rules/brown/brown_implement_sop.md +133 -0
- package/.6aspec/rules/brown/brown_list_sop.md +69 -0
- package/.6aspec/rules/brown/brown_new_sop.md +257 -0
- package/.6aspec/rules/brown/brown_proposal_sop.md +160 -0
- package/.6aspec/rules/brown/brown_quick_sop.md +134 -0
- package/.6aspec/rules/brown/brown_review_sop.md +270 -0
- package/.6aspec/rules/brown/brown_rollback_sop.md +188 -0
- package/.6aspec/rules/brown/brown_specs_sop.md +228 -0
- package/.6aspec/rules/brown/brown_status_sop.md +135 -0
- package/.6aspec/rules/brown/brown_tasks_sop.md +202 -0
- package/.6aspec/rules/brown/brown_understand_sop.md +208 -0
- package/.6aspec/rules/brown/brown_verify_sop.md +360 -0
- package/.6aspec/rules/green/6A_archive_sop.md +301 -0
- package/.6aspec/rules/green/6A_clarify_sop.md +238 -0
- package/.6aspec/rules/green/6A_code_implementation_sop.md +110 -0
- package/.6aspec/rules/green/6A_constitution.md +52 -0
- package/.6aspec/rules/green/6A_continue_sop.md +186 -0
- package/.6aspec/rules/green/6A_design_sop.md +228 -0
- package/.6aspec/rules/green/6A_import_model_table_sop.md +120 -0
- package/.6aspec/rules/green/6A_init_event_list_sop.md +62 -0
- package/.6aspec/rules/green/6A_init_map_sop.md +79 -0
- package/.6aspec/rules/green/6A_model_sop.md +210 -0
- package/.6aspec/rules/green/6A_new_sop.md +319 -0
- package/.6aspec/rules/green/6A_rollback_sop.md +198 -0
- package/.6aspec/rules/green/6A_status_sop.md +275 -0
- package/.6aspec/rules/green/6A_tasks_sop.md +181 -0
- package/.6aspec/rules/green/6A_visual_logic_sop.md +121 -0
- package/.6aspec/rules/green/green_status_schema.md +293 -0
- package/.6aspec/script/create_entities_from_markdown.py +688 -0
- package/.claude/commands/6aspec/brown/archive.md +11 -0
- package/.claude/commands/6aspec/brown/continue.md +11 -0
- package/.claude/commands/6aspec/brown/design.md +11 -0
- package/.claude/commands/6aspec/brown/ff.md +11 -0
- package/.claude/commands/6aspec/brown/impact.md +11 -0
- package/.claude/commands/6aspec/brown/implement.md +11 -0
- package/.claude/commands/6aspec/brown/list.md +11 -0
- package/.claude/commands/6aspec/brown/new.md +11 -0
- package/.claude/commands/6aspec/brown/proposal.md +11 -0
- package/.claude/commands/6aspec/brown/quick.md +11 -0
- package/.claude/commands/6aspec/brown/review.md +11 -0
- package/.claude/commands/6aspec/brown/rollback.md +11 -0
- package/.claude/commands/6aspec/brown/specs.md +11 -0
- package/.claude/commands/6aspec/brown/status.md +11 -0
- package/.claude/commands/6aspec/brown/tasks.md +11 -0
- package/.claude/commands/6aspec/brown/understand.md +11 -0
- package/.claude/commands/6aspec/brown/verify.md +11 -0
- package/.claude/commands/6aspec/green/archive.md +8 -0
- package/.claude/commands/6aspec/green/clarify.md +13 -0
- package/.claude/commands/6aspec/green/continue.md +8 -0
- package/.claude/commands/6aspec/green/design.md +8 -0
- package/.claude/commands/6aspec/green/execute-task.md +20 -0
- package/.claude/commands/6aspec/green/import-model-table.md +8 -0
- package/.claude/commands/6aspec/green/init.md +14 -0
- package/.claude/commands/6aspec/green/model.md +12 -0
- package/.claude/commands/6aspec/green/new.md +13 -0
- package/.claude/commands/6aspec/green/rollback.md +8 -0
- package/.claude/commands/6aspec/green/status.md +8 -0
- package/.claude/commands/6aspec/green/tasks.md +8 -0
- package/.claude/commands/6aspec/green/visual-logic.md +9 -0
- package/.claude/settings.local.json +8 -0
- package/.cursor/commands/6aspec/brown/archive.md +9 -0
- package/.cursor/commands/6aspec/brown/continue.md +9 -0
- package/.cursor/commands/6aspec/brown/design.md +9 -0
- package/.cursor/commands/6aspec/brown/ff.md +9 -0
- package/.cursor/commands/6aspec/brown/impact.md +9 -0
- package/.cursor/commands/6aspec/brown/implement.md +9 -0
- package/.cursor/commands/6aspec/brown/list.md +9 -0
- package/.cursor/commands/6aspec/brown/new.md +9 -0
- package/.cursor/commands/6aspec/brown/proposal.md +9 -0
- package/.cursor/commands/6aspec/brown/quick.md +9 -0
- package/.cursor/commands/6aspec/brown/review.md +9 -0
- package/.cursor/commands/6aspec/brown/rollback.md +9 -0
- package/.cursor/commands/6aspec/brown/specs.md +9 -0
- package/.cursor/commands/6aspec/brown/status.md +9 -0
- package/.cursor/commands/6aspec/brown/tasks.md +9 -0
- package/.cursor/commands/6aspec/brown/understand.md +9 -0
- package/.cursor/commands/6aspec/brown/verify.md +9 -0
- package/.cursor/commands/6aspec/green/archive.md +9 -0
- package/.cursor/commands/6aspec/green/clarify.md +14 -0
- package/.cursor/commands/6aspec/green/continue.md +9 -0
- package/.cursor/commands/6aspec/green/design.md +9 -0
- package/.cursor/commands/6aspec/green/execute-task.md +21 -0
- package/.cursor/commands/6aspec/green/import-model-table.md +9 -0
- package/.cursor/commands/6aspec/green/init.md +15 -0
- package/.cursor/commands/6aspec/green/model.md +13 -0
- package/.cursor/commands/6aspec/green/new.md +14 -0
- package/.cursor/commands/6aspec/green/rollback.md +9 -0
- package/.cursor/commands/6aspec/green/status.md +9 -0
- package/.cursor/commands/6aspec/green/tasks.md +9 -0
- package/.cursor/commands/6aspec/green/visual-logic.md +10 -0
- package/README.en.md +36 -0
- package/README.md +146 -0
- package/bin/6a-spec-install +54 -0
- package/bin/6aspec +64 -0
- package/lib/cli.js +451 -0
- package/lib/installer.js +333 -0
- 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
|