mm-qa-mcp 0.2.0__py3-none-any.whl

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.
@@ -0,0 +1,2848 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 代码搜索与分析工具
4
+
5
+ 该工具可以根据不同类型的输入提供相应的代码分析功能:
6
+ 1. --type API:API路径:获取API的入参、出参和接口逻辑
7
+ 2. --type FUNC 函数名:获取函数的调用链关系图谱
8
+ 3. --type CODE 代码片段:分析代码影响的函数
9
+ 4. --type ANY --limit 10 随意输入:模糊搜索相似代码
10
+ 5. --type FUNC_DETAIL 函数名:查询函数的详细代码实现
11
+
12
+ 使用方法:
13
+ $ python query_segments.py <输入内容> [--type API|FUNC|CODE|ANY|FUNC_DETAIL] [--limit 10] [--exact]
14
+
15
+ 参数说明:
16
+ --type : 指定输入类型,可选值:API, FUNC, CODE, ANY, FUNC_DETAIL(默认自动识别)
17
+ --limit : 结果数量限制,默认为10
18
+ --exact : 精确匹配模式
19
+ --output : 输出文件路径,用于保存函数调用图谱
20
+ """
21
+
22
+ import requests
23
+ import json
24
+ import sys
25
+ import re
26
+ import time
27
+ from functools import wraps
28
+ from tabulate import tabulate
29
+ import os
30
+ from prettytable import PrettyTable
31
+
32
+ from minimax_qa_mcp.utils.utils import Utils
33
+
34
+ # 配置常量
35
+ WEAVIATE_URL = Utils.get_conf('weaviate_url', 'url_port') + "/v1"
36
+ DEFAULT_LIMIT = 10
37
+ MAX_RETRIES = 3 # 最大重试次数
38
+ RETRY_DELAY = 2 # 重试间隔(秒)
39
+ BATCH_SIZE = 50 # 批处理大小
40
+ MAX_CONTENT_LENGTH = 250 # 显示的最大内容长度
41
+ CLASS_NAME = "GoCodeSegment" # 数据库中的实际类名
42
+
43
+ # 调试模式
44
+ DEBUG = True
45
+
46
+ # 输入类型常量
47
+ TYPE_API = "API"
48
+ TYPE_FUNC = "FUNC"
49
+ TYPE_CODE = "CODE"
50
+ TYPE_ANY = "ANY"
51
+ TYPE_FUNC_DETAIL = "FUNC_DETAIL"
52
+
53
+
54
+ def with_retry(max_retries=MAX_RETRIES, delay=RETRY_DELAY):
55
+ """
56
+ 装饰器: 为函数添加重试逻辑
57
+
58
+ 参数:
59
+ max_retries (int): 最大重试次数
60
+ delay (int): 重试间隔(秒)
61
+
62
+ 返回:
63
+ function: 被装饰的函数
64
+ """
65
+
66
+ def decorator(func):
67
+ @wraps(func)
68
+ def wrapper(*args, **kwargs):
69
+ retries = 0
70
+ while retries <= max_retries:
71
+ try:
72
+ return func(*args, **kwargs)
73
+ except Exception as e:
74
+ retries += 1
75
+ if retries > max_retries:
76
+ print(f"达到最大重试次数({max_retries}), 放弃操作")
77
+ raise e
78
+ print(f"操作失败: {str(e)}, 将在 {delay} 秒后重试 ({retries}/{max_retries})")
79
+ time.sleep(delay)
80
+ return None
81
+
82
+ return wrapper
83
+
84
+ return decorator
85
+
86
+
87
+ def validate_limit(limit):
88
+ """
89
+ 验证并规范化limit参数
90
+
91
+ 参数:
92
+ limit (int): 输入的limit值
93
+
94
+ 返回:
95
+ int: 规范化后的limit值
96
+ """
97
+ try:
98
+ limit = int(limit)
99
+ return max(1, min(limit, 100)) # 确保limit在1-100之间
100
+ except (ValueError, TypeError):
101
+ print(f"警告: 无效的limit值 '{limit}', 使用默认值 {DEFAULT_LIMIT}")
102
+ return DEFAULT_LIMIT
103
+
104
+
105
+ def detect_input_type(input_text):
106
+ """
107
+ 自动检测输入的类型
108
+
109
+ 参数:
110
+ input_text (str): 用户输入的文本
111
+
112
+ 返回:
113
+ str: 输入类型 (API, FUNC, CODE, FUNC_DETAIL, ANY)
114
+ """
115
+ if not input_text:
116
+ return TYPE_ANY
117
+
118
+ # 如果以斜杠开头,可能是API路径
119
+ if input_text.startswith('/') and '/' in input_text[1:]:
120
+ return TYPE_API
121
+
122
+ # 函数命名模式 + "实现" 或 "详情" 或 "代码",可能需要查询函数详情
123
+ if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*\s+(实现|详情|代码|细节|function detail)$', input_text, re.IGNORECASE):
124
+ return TYPE_FUNC_DETAIL
125
+
126
+ # 如果是单个词,可能是函数名
127
+ if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', input_text) and not '\n' in input_text:
128
+ return TYPE_FUNC
129
+
130
+ # 如果包含多行或特殊语法,可能是代码片段
131
+ if '\n' in input_text or '{' in input_text or ';' in input_text:
132
+ return TYPE_CODE
133
+
134
+ # 包含编程语言的常见关键字,可能是代码
135
+ code_keywords = ['func', 'return', 'if', 'else', 'for', 'while', 'class', 'struct', 'import', 'package']
136
+ for keyword in code_keywords:
137
+ if re.search(r'\b' + keyword + r'\b', input_text):
138
+ return TYPE_CODE
139
+
140
+ # 默认为模糊查询
141
+ return TYPE_ANY
142
+
143
+
144
+ def search_api_info(api_path, limit=10, exact=False):
145
+ """
146
+ 搜索API信息,包括出入参数和接口逻辑
147
+
148
+ 参数:
149
+ api_path (str): API路径
150
+ limit (int): 结果数量限制
151
+ exact (bool): 是否精确匹配
152
+
153
+ 返回:
154
+ dict: API信息,包括路径、入参、出参和实现逻辑
155
+ """
156
+ if not api_path:
157
+ return {"error": "API路径不能为空"}
158
+
159
+ limit = validate_limit(limit)
160
+
161
+ # 首先从IdlApiMapping中查找API定义
162
+ api_mapping = query_idl_api_mapping(api_path, limit, exact)
163
+
164
+ if api_mapping:
165
+ # 找到了API映射,构建结果
166
+ result = {
167
+ "api_path": api_path,
168
+ "definitions": []
169
+ }
170
+
171
+ # 如果exact为True且找到了多个结果,再次过滤确保路径完全匹配
172
+ if exact and len(api_mapping) > 1:
173
+ filtered_mapping = []
174
+ for mapping in api_mapping:
175
+ # 规范化路径(去掉开头的斜杠进行比较)
176
+ norm_api_path = api_path.lstrip('/')
177
+ norm_mapping_path = mapping.get("http_path", "").lstrip('/')
178
+
179
+ if norm_mapping_path == norm_api_path:
180
+ filtered_mapping.append(mapping)
181
+
182
+ # 如果找到了精确匹配的结果,使用这些结果
183
+ if filtered_mapping:
184
+ api_mapping = filtered_mapping
185
+
186
+ for mapping in api_mapping:
187
+ # 查找对应的请求和响应结构体
188
+ req_struct = None
189
+ resp_struct = None
190
+
191
+ # 查询请求结构体
192
+ if mapping.get("request_type"):
193
+ req_structs = query_idl_struct(mapping.get("request_type"), 1, True)
194
+ if req_structs:
195
+ req_struct = req_structs[0]
196
+
197
+ # 尝试查询响应结构体,有多种可能的名称格式
198
+
199
+ # 1. 从response_type字段获取
200
+ if mapping.get("response_type") and mapping.get("response_type") != mapping.get("request_type"):
201
+ resp_structs = query_idl_struct(mapping.get("response_type"), 1, True)
202
+ if resp_structs:
203
+ resp_struct = resp_structs[0]
204
+
205
+ # 2. 尝试根据方法名推断响应结构体名称
206
+ if not resp_struct and mapping.get("method_name"):
207
+ # 尝试查找"方法名+Resp"或"方法名+Response"格式的结构体
208
+ possible_resp_names = [
209
+ f"{mapping.get('method_name')}Resp",
210
+ f"{mapping.get('method_name')}Response",
211
+ # 将首字母大写
212
+ f"{mapping.get('method_name')[0].upper()}{mapping.get('method_name')[1:]}Resp",
213
+ f"{mapping.get('method_name')[0].upper()}{mapping.get('method_name')[1:]}Response"
214
+ ]
215
+
216
+ for resp_name in possible_resp_names:
217
+ resp_structs = query_idl_struct(resp_name, 1, True)
218
+ if resp_structs:
219
+ resp_struct = resp_structs[0]
220
+ break
221
+
222
+ # 3. 尝试将请求结构体的"Req"替换为"Resp"
223
+ if not resp_struct and mapping.get("request_type") and "Req" in mapping.get("request_type"):
224
+ resp_name = mapping.get("request_type").replace("Req", "Resp")
225
+ resp_structs = query_idl_struct(resp_name, 1, True)
226
+ if resp_structs:
227
+ resp_struct = resp_structs[0]
228
+
229
+ # 构建API信息
230
+ api_info = {
231
+ "function": mapping.get("method_name", ""),
232
+ "package": mapping.get("service_name", ""),
233
+ "file_path": mapping.get("idl_file", ""),
234
+ "http_method": mapping.get("http_method", ""),
235
+ "request_type": mapping.get("request_type", ""),
236
+ "response_type": resp_struct.get("struct_name") if resp_struct else mapping.get("response_type", ""),
237
+ "input_params": extract_struct_fields(req_struct.get("content", "")) if req_struct else [],
238
+ "output_params": extract_struct_fields(resp_struct.get("content", "")) if resp_struct else [],
239
+ "logic": mapping.get("idl_content", ""),
240
+ "comments": mapping.get("comments", "")
241
+ }
242
+
243
+ result["definitions"].append(api_info)
244
+
245
+ return result
246
+
247
+ # 如果IdlApiMapping中没有找到,尝试在CodeSegment中查找
248
+
249
+ # 首先查找API定义(路由定义)
250
+ api_def = query_by_content(f"API route {api_path}", limit)
251
+
252
+ # 如果找不到,尝试直接搜索路径
253
+ if not api_def:
254
+ api_def = query_by_field("file_path", api_path, limit, exact)
255
+
256
+ # 如果仍找不到,尝试搜索内容
257
+ if not api_def:
258
+ api_def = query_by_content(api_path, limit)
259
+
260
+ if not api_def:
261
+ return {"error": f"未找到API: {api_path}"}
262
+
263
+ # 提取API相关信息
264
+ result = {
265
+ "api_path": api_path,
266
+ "definitions": [],
267
+ }
268
+
269
+ # 遍历找到的定义
270
+ for segment in api_def:
271
+ # 提取代码中的参数信息
272
+ params = extract_api_params(segment.get("code", ""))
273
+
274
+ # 构建API信息
275
+ api_info = {
276
+ "function": segment.get("function", ""),
277
+ "package": segment.get("package", ""),
278
+ "file_path": segment.get("file_path", ""),
279
+ "input_params": params.get("input", []),
280
+ "output_params": params.get("output", []),
281
+ "logic": segment.get("code", ""),
282
+ "comments": segment.get("comments", "")
283
+ }
284
+ result["definitions"].append(api_info)
285
+
286
+ return result
287
+
288
+
289
+ def query_idl_api_mapping(api_path, limit=10, exact=False):
290
+ """
291
+ 查询IdlApiMapping类中的API定义
292
+
293
+ 参数:
294
+ api_path (str): API路径
295
+ limit (int): 结果数量限制
296
+ exact (bool): 是否精确匹配
297
+
298
+ 返回:
299
+ list: API映射列表
300
+ """
301
+ if not api_path:
302
+ return []
303
+
304
+ limit = validate_limit(limit)
305
+
306
+ operator = "Equal" if exact else "Like"
307
+
308
+ # 如果是模糊查询,但API路径是完整的,例如"/v1/music_upload"
309
+ # 尝试直接用Equal查询,因为API路径通常是完整的
310
+ if not exact and api_path.startswith("/") and "/" in api_path[1:]:
311
+ # 先尝试精确匹配
312
+ result = query_idl_api_mapping_with_operator(api_path, limit, "Equal")
313
+ if result:
314
+ return result
315
+
316
+ # 按指定的匹配模式查询
317
+ return query_idl_api_mapping_with_operator(api_path, limit, operator)
318
+
319
+
320
+ def query_idl_api_mapping_with_operator(api_path, limit, operator):
321
+ """
322
+ 使用特定操作符查询IdlApiMapping
323
+
324
+ 参数:
325
+ api_path (str): API路径
326
+ limit (int): 结果数量限制
327
+ operator (str): 查询操作符 (Equal, Like)
328
+
329
+ 返回:
330
+ list: API映射列表
331
+ """
332
+ value_str = api_path
333
+
334
+ # 对于Like操作符,如果api_path不包含通配符,添加通配符
335
+ if operator == "Like" and "*" not in api_path:
336
+ value_str = f"*{api_path}*"
337
+
338
+ graphql_query = f"""
339
+ {{
340
+ Get {{
341
+ IdlApiMapping(
342
+ where: {{
343
+ path: ["http_path"],
344
+ operator: {operator},
345
+ valueString: "{value_str}"
346
+ }}
347
+ limit: {limit}
348
+ ) {{
349
+ method_name
350
+ http_path
351
+ http_method
352
+ idl_file
353
+ service_name
354
+ idl_content
355
+ request_type
356
+ response_type
357
+ comments
358
+ }}
359
+ }}
360
+ }}
361
+ """
362
+
363
+ response = execute_graphql_query(graphql_query)
364
+ if not response or "error" in response:
365
+ error_msg = response.get("error") if response else "未知错误"
366
+ print(f"查询IdlApiMapping失败: {error_msg}")
367
+ return []
368
+
369
+ results = response.get("data", {}).get("Get", {}).get("IdlApiMapping", [])
370
+
371
+ # 增强精确匹配处理
372
+ if operator == "Equal" and results:
373
+ # 对结果进行后处理,确保http_path完全匹配
374
+ exact_results = []
375
+ for item in results:
376
+ if item.get("http_path") == api_path or item.get("http_path") == value_str:
377
+ exact_results.append(item)
378
+ return exact_results
379
+
380
+ return results
381
+
382
+
383
+ def query_idl_struct(struct_name, limit=1, exact=True):
384
+ """
385
+ 查询IdlStructDefinition类中的结构体定义
386
+
387
+ 参数:
388
+ struct_name (str): 结构体名称
389
+ limit (int): 结果数量限制
390
+ exact (bool): 是否精确匹配
391
+
392
+ 返回:
393
+ list: 结构体定义列表
394
+ """
395
+ if not struct_name:
396
+ return []
397
+
398
+ limit = validate_limit(limit)
399
+
400
+ operator = "Equal" if exact else "Like"
401
+
402
+ graphql_query = f"""
403
+ {{
404
+ Get {{
405
+ IdlStructDefinition(
406
+ where: {{
407
+ path: ["struct_name"],
408
+ operator: {operator},
409
+ valueString: "{struct_name}"
410
+ }}
411
+ limit: {limit}
412
+ ) {{
413
+ struct_name
414
+ idl_file
415
+ content
416
+ }}
417
+ }}
418
+ }}
419
+ """
420
+
421
+ response = execute_graphql_query(graphql_query)
422
+ if not response or "error" in response:
423
+ error_msg = response.get("error") if response else "未知错误"
424
+ print(f"查询IdlStructDefinition失败: {error_msg}")
425
+ return []
426
+
427
+ return response.get("data", {}).get("Get", {}).get("IdlStructDefinition", [])
428
+
429
+
430
+ def extract_struct_fields(struct_content):
431
+ """
432
+ 从结构体内容中提取字段信息
433
+
434
+ 参数:
435
+ struct_content (str): 结构体内容
436
+
437
+ 返回:
438
+ list: 字段列表
439
+ """
440
+ if not struct_content:
441
+ return []
442
+
443
+ # 解析结构体定义中的字段
444
+ fields = []
445
+
446
+ # 首先移除struct行和大括号行
447
+ lines = struct_content.split('\n')
448
+ content_lines = []
449
+ for line in lines:
450
+ if not line.strip().startswith('struct') and not line.strip() in ['{', '}']:
451
+ content_lines.append(line)
452
+
453
+ # 使用正则表达式匹配每个字段定义行
454
+ # 例如 "1: required string purpose // 用途"
455
+ for line in content_lines:
456
+ line = line.strip()
457
+ if not line or line.startswith('//'):
458
+ continue
459
+
460
+ # 尝试匹配字段定义
461
+ field_pattern = r'(\d+):\s*(required|optional)?\s*(\w+)\s+(\w+)(?:\s*//\s*(.*))?'
462
+ match = re.search(field_pattern, line)
463
+
464
+ if match:
465
+ field_id, required, field_type, field_name, comment = match.groups()
466
+
467
+ fields.append({
468
+ "name": field_name,
469
+ "type": field_type,
470
+ "required": required == "required",
471
+ "comment": comment.strip() if comment else ""
472
+ })
473
+
474
+ return fields
475
+
476
+
477
+ def extract_api_params(code):
478
+ """
479
+ 从API代码中提取入参和出参
480
+
481
+ 参数:
482
+ code (str): API实现代码
483
+
484
+ 返回:
485
+ dict: 包含input和output的字典
486
+ """
487
+ if not code:
488
+ return {"input": [], "output": []}
489
+
490
+ params = {
491
+ "input": [],
492
+ "output": []
493
+ }
494
+
495
+ # 查找函数定义行
496
+ func_def_match = re.search(r'func\s+\w+\s*\((.*?)\)(.*?){', code, re.DOTALL)
497
+
498
+ if func_def_match:
499
+ # 提取入参
500
+ input_params_str = func_def_match.group(1).strip()
501
+ if input_params_str:
502
+ for param in input_params_str.split(','):
503
+ param = param.strip()
504
+ if param:
505
+ name_type = param.split()
506
+ if len(name_type) >= 2:
507
+ params["input"].append({
508
+ "name": name_type[0],
509
+ "type": " ".join(name_type[1:])
510
+ })
511
+
512
+ # 提取出参
513
+ output_params_str = func_def_match.group(2).strip()
514
+ if output_params_str:
515
+ # 去掉前导的返回值括号
516
+ output_params_str = output_params_str.lstrip('(').rstrip(')')
517
+
518
+ # 分割多个返回值
519
+ for param in output_params_str.split(','):
520
+ param = param.strip()
521
+ if param:
522
+ params["output"].append({"type": param})
523
+
524
+ return params
525
+
526
+
527
+ def get_function_call_chain(function_name, limit=10, direction="both"):
528
+ """
529
+ 获取函数调用链
530
+
531
+ 参数:
532
+ function_name (str): 函数名
533
+ limit (int): 结果数量限制
534
+ direction (str): 调用方向,可选值:caller(调用者), callee(被调用者), both(双向)
535
+
536
+ 返回:
537
+ dict: 函数调用链信息
538
+ """
539
+ if not function_name:
540
+ return {"error": "函数名不能为空"}
541
+
542
+ limit = validate_limit(limit)
543
+
544
+ result = {
545
+ "function": function_name,
546
+ "callers": [], # 调用此函数的函数
547
+ "callees": [], # 此函数调用的函数
548
+ }
549
+
550
+ # 查找函数定义
551
+ func_defs = query_by_field("function", function_name, limit, True)
552
+
553
+ if not func_defs:
554
+ # 如果在CodeSegment中找不到函数定义,检查是否是接口定义的方法
555
+ # 尝试在FunctionCallRelation中查找
556
+ if direction in ["callee", "both"]:
557
+ result["callees"] = query_function_callees(function_name, limit)
558
+
559
+ if direction in ["caller", "both"]:
560
+ result["callers"] = query_function_callers(function_name, limit)
561
+
562
+ # 如果找到了调用关系但没有函数定义,这可能是一个接口方法
563
+ if result["callers"] or result["callees"]:
564
+ return result
565
+ else:
566
+ return {"error": f"未找到函数: {function_name}"}
567
+
568
+ # 获取调用关系
569
+ if direction in ["caller", "both"]:
570
+ # 使用FunctionCallRelation查找调用此函数的代码(谁调用了这个函数)
571
+ result["callers"] = query_function_callers(function_name, limit)
572
+
573
+ # 如果没有找到,尝试使用旧方法
574
+ if not result["callers"]:
575
+ # 查找调用此函数的代码(谁调用了这个函数)
576
+ caller_query = f"call {function_name}"
577
+ callers = query_by_content(caller_query, limit)
578
+
579
+ for caller in callers:
580
+ if caller.get("function") != function_name: # 排除自己调用自己
581
+ result["callers"].append({
582
+ "function": caller.get("function", ""),
583
+ "package": caller.get("package", ""),
584
+ "file_path": caller.get("file_path", ""),
585
+ "line": f"{caller.get('start_line', 0)}-{caller.get('end_line', 0)}"
586
+ })
587
+
588
+ if direction in ["callee", "both"]:
589
+ # 使用FunctionCallRelation查找此函数调用的代码(这个函数调用了谁)
590
+ result["callees"] = query_function_callees(function_name, limit)
591
+
592
+ # 如果没有找到,尝试使用旧方法
593
+ if not result["callees"]:
594
+ # 查找此函数调用的代码(这个函数调用了谁)
595
+ for func_def in func_defs:
596
+ code = func_def.get("code", "")
597
+ # 使用正则表达式查找函数调用
598
+ calls = re.findall(r'(\w+)\s*\(', code)
599
+
600
+ # 去重,筛选出真正的函数
601
+ unique_calls = set(calls)
602
+ for call in unique_calls:
603
+ if call != function_name and not call in ["if", "for", "switch", "select", "defer"]:
604
+ # 查找被调用函数的定义
605
+ callee_defs = query_by_field("function", call, 1, True)
606
+ if callee_defs:
607
+ callee = callee_defs[0]
608
+ result["callees"].append({
609
+ "function": call,
610
+ "package": callee.get("package", ""),
611
+ "file_path": callee.get("file_path", ""),
612
+ "line": f"{callee.get('start_line', 0)}-{callee.get('end_line', 0)}"
613
+ })
614
+
615
+ return result
616
+
617
+
618
+ def query_function_callers(function_name, limit=10):
619
+ """
620
+ 查询调用指定函数的函数列表(谁调用了这个函数)
621
+
622
+ 参数:
623
+ function_name (str): 被调用的函数名
624
+ limit (int): 结果数量限制
625
+
626
+ 返回:
627
+ list: 调用者列表
628
+ """
629
+ if not function_name:
630
+ return []
631
+
632
+ limit = validate_limit(limit)
633
+
634
+ # 使用精确匹配,避免匹配到其他包含该函数名的函数
635
+ graphql_query = f"""
636
+ {{
637
+ Get {{
638
+ FunctionCallRelation(
639
+ where: {{
640
+ operator: Equal,
641
+ path: ["callee_function"],
642
+ valueString: "{function_name}"
643
+ }}
644
+ limit: {limit}
645
+ ) {{
646
+ callee_function
647
+ caller_function
648
+ caller_file
649
+ call_line
650
+ }}
651
+ }}
652
+ }}
653
+ """
654
+
655
+ response = execute_graphql_query(graphql_query)
656
+ if not response or "errors" in response:
657
+ error_msg = response.get("errors")[0]["message"] if "errors" in response else "未知错误"
658
+ print(f"查询FunctionCallRelation失败: {error_msg}")
659
+ return []
660
+
661
+ calls = response.get("data", {}).get("Get", {}).get("FunctionCallRelation", [])
662
+
663
+ # 格式化结果
664
+ callers = []
665
+ for call in calls:
666
+ # 确认callee_function等于查询的函数名
667
+ if call.get("callee_function") != function_name:
668
+ continue
669
+
670
+ caller_func = call.get("caller_function", "")
671
+ if not caller_func:
672
+ continue
673
+
674
+ # 尝试获取包名
675
+ package_name = ""
676
+ caller_file = call.get("caller_file", "")
677
+ if caller_file:
678
+ # 从文件路径提取包名
679
+ parts = caller_file.split("/")
680
+ if len(parts) >= 2:
681
+ package_name = parts[-2]
682
+
683
+ callers.append({
684
+ "function": caller_func,
685
+ "package": package_name,
686
+ "file_path": caller_file,
687
+ "line": str(call.get("call_line", 0))
688
+ })
689
+
690
+ # 如果精确匹配没有结果,尝试模糊匹配
691
+ if not callers:
692
+ # 使用模糊匹配
693
+ fuzzy_query = f"""
694
+ {{
695
+ Get {{
696
+ FunctionCallRelation(
697
+ where: {{
698
+ operator: Like,
699
+ path: ["callee_function"],
700
+ valueString: "*{function_name}*"
701
+ }}
702
+ limit: {limit}
703
+ ) {{
704
+ callee_function
705
+ caller_function
706
+ caller_file
707
+ call_line
708
+ }}
709
+ }}
710
+ }}
711
+ """
712
+
713
+ fuzzy_response = execute_graphql_query(fuzzy_query)
714
+ if not fuzzy_response or "errors" in fuzzy_response:
715
+ return []
716
+
717
+ fuzzy_calls = fuzzy_response.get("data", {}).get("Get", {}).get("FunctionCallRelation", [])
718
+
719
+ for call in fuzzy_calls:
720
+ # 检查callee_function是否包含查询的函数名(不区分大小写)
721
+ if function_name.lower() not in call.get("callee_function", "").lower():
722
+ continue
723
+
724
+ caller_func = call.get("caller_function", "")
725
+ if not caller_func:
726
+ continue
727
+
728
+ # 尝试获取包名
729
+ package_name = ""
730
+ caller_file = call.get("caller_file", "")
731
+ if caller_file:
732
+ # 从文件路径提取包名
733
+ parts = caller_file.split("/")
734
+ if len(parts) >= 2:
735
+ package_name = parts[-2]
736
+
737
+ callers.append({
738
+ "function": caller_func,
739
+ "package": package_name,
740
+ "file_path": caller_file,
741
+ "line": str(call.get("call_line", 0))
742
+ })
743
+
744
+ return callers
745
+
746
+
747
+ def query_function_callees(function_name, limit=10):
748
+ """
749
+ 查询指定函数调用的函数列表(这个函数调用了谁)
750
+
751
+ 参数:
752
+ function_name (str): 调用者函数名
753
+ limit (int): 结果数量限制
754
+
755
+ 返回:
756
+ list: 被调用者列表
757
+ """
758
+ if not function_name:
759
+ return []
760
+
761
+ limit = validate_limit(limit)
762
+
763
+ # 使用精确匹配,避免匹配到其他包含该函数名的函数
764
+ graphql_query = f"""
765
+ {{
766
+ Get {{
767
+ FunctionCallRelation(
768
+ where: {{
769
+ operator: Equal,
770
+ path: ["caller_function"],
771
+ valueString: "{function_name}"
772
+ }}
773
+ limit: {limit}
774
+ ) {{
775
+ callee_function
776
+ caller_function
777
+ callee_file
778
+ call_line
779
+ }}
780
+ }}
781
+ }}
782
+ """
783
+
784
+ response = execute_graphql_query(graphql_query)
785
+ if not response or "errors" in response:
786
+ error_msg = response.get("errors")[0]["message"] if "errors" in response else "未知错误"
787
+ print(f"查询FunctionCallRelation失败: {error_msg}")
788
+ return []
789
+
790
+ calls = response.get("data", {}).get("Get", {}).get("FunctionCallRelation", [])
791
+
792
+ # 格式化结果
793
+ callees = []
794
+ for call in calls:
795
+ # 确认caller_function等于查询的函数名
796
+ if call.get("caller_function") != function_name:
797
+ continue
798
+
799
+ callee_func = call.get("callee_function", "")
800
+ if not callee_func:
801
+ continue
802
+
803
+ # 尝试获取包名
804
+ package_name = ""
805
+ callee_file = call.get("callee_file", "")
806
+ if callee_file:
807
+ # 从文件路径提取包名
808
+ parts = callee_file.split("/")
809
+ if len(parts) >= 2:
810
+ package_name = parts[-2]
811
+
812
+ callees.append({
813
+ "function": callee_func,
814
+ "package": package_name,
815
+ "file_path": callee_file,
816
+ "line": str(call.get("call_line", 0))
817
+ })
818
+
819
+ # 如果精确匹配没有结果,尝试模糊匹配
820
+ if not callees:
821
+ # 使用模糊匹配
822
+ fuzzy_query = f"""
823
+ {{
824
+ Get {{
825
+ FunctionCallRelation(
826
+ where: {{
827
+ operator: Like,
828
+ path: ["caller_function"],
829
+ valueString: "*{function_name}*"
830
+ }}
831
+ limit: {limit}
832
+ ) {{
833
+ callee_function
834
+ caller_function
835
+ callee_file
836
+ call_line
837
+ }}
838
+ }}
839
+ }}
840
+ """
841
+
842
+ fuzzy_response = execute_graphql_query(fuzzy_query)
843
+ if not fuzzy_response or "errors" in fuzzy_response:
844
+ return []
845
+
846
+ fuzzy_calls = fuzzy_response.get("data", {}).get("Get", {}).get("FunctionCallRelation", [])
847
+
848
+ for call in fuzzy_calls:
849
+ # 检查caller_function是否包含查询的函数名(不区分大小写)
850
+ if function_name.lower() not in call.get("caller_function", "").lower():
851
+ continue
852
+
853
+ callee_func = call.get("callee_function", "")
854
+ if not callee_func:
855
+ continue
856
+
857
+ # 尝试获取包名
858
+ package_name = ""
859
+ callee_file = call.get("callee_file", "")
860
+ if callee_file:
861
+ # 从文件路径提取包名
862
+ parts = callee_file.split("/")
863
+ if len(parts) >= 2:
864
+ package_name = parts[-2]
865
+
866
+ callees.append({
867
+ "function": callee_func,
868
+ "package": package_name,
869
+ "file_path": callee_file,
870
+ "line": str(call.get("call_line", 0))
871
+ })
872
+
873
+ return callees
874
+
875
+
876
+ def analyze_code_impact(code_snippet, limit=10):
877
+ """
878
+ 分析代码片段影响的函数
879
+
880
+ 参数:
881
+ code_snippet (str): 代码片段
882
+ limit (int): 结果数量限制
883
+
884
+ 返回:
885
+ dict: 受影响的函数列表
886
+ """
887
+ if not code_snippet:
888
+ return {"error": "代码片段不能为空"}
889
+
890
+ limit = validate_limit(limit)
891
+
892
+ result = {
893
+ "code_snippet": code_snippet[:MAX_CONTENT_LENGTH] + "..." if len(
894
+ code_snippet) > MAX_CONTENT_LENGTH else code_snippet,
895
+ "affected_functions": []
896
+ }
897
+
898
+ # 提取代码中的关键部分
899
+ # 提取代码中可能的函数调用
900
+ calls = re.findall(r'(\w+)\s*\(', code_snippet)
901
+
902
+ # 提取代码中可能的变量访问
903
+ vars = re.findall(r'(\w+)\s*=', code_snippet)
904
+
905
+ # 提取结构体和接口名称
906
+ structs = re.findall(r'type\s+(\w+)\s+(struct|interface)', code_snippet)
907
+ struct_names = [s[0] for s in structs]
908
+
909
+ # 合并关键词
910
+ keywords = set(calls + vars + struct_names)
911
+
912
+ # 查找包含这些关键词的函数
913
+ for keyword in keywords:
914
+ if len(keyword) > 3: # 忽略太短的词
915
+ funcs = query_by_content(keyword, limit // len(keywords) + 1)
916
+
917
+ for func in funcs:
918
+ # 检查是否是真正的函数
919
+ if func.get("type") == "function":
920
+ result["affected_functions"].append({
921
+ "function": func.get("function", ""),
922
+ "package": func.get("package", ""),
923
+ "file_path": func.get("file_path", ""),
924
+ "line": f"{func.get('start_line', 0)}-{func.get('end_line', 0)}",
925
+ "relevance": "直接调用" if keyword in calls else "使用变量"
926
+ })
927
+
928
+ # 去重
929
+ unique_functions = []
930
+ seen = set()
931
+
932
+ for func in result["affected_functions"]:
933
+ key = f"{func['function']}:{func['package']}"
934
+ if key not in seen:
935
+ seen.add(key)
936
+ unique_functions.append(func)
937
+
938
+ result["affected_functions"] = unique_functions[:limit]
939
+
940
+ return result
941
+
942
+
943
+ def generate_function_call_graph(call_chain, output_file=None):
944
+ """
945
+ 生成函数调用关系图
946
+
947
+ 参数:
948
+ call_chain (dict): 函数调用链信息
949
+ output_file (str): 输出文件路径
950
+
951
+ 返回:
952
+ bool: 是否成功
953
+ """
954
+ if not call_chain:
955
+ print("错误: 函数调用链信息为空")
956
+ return False
957
+
958
+ try:
959
+ # 构建简单的文本图
960
+ graph_text = [f"函数 '{call_chain['function']}' 的调用关系图:\n"]
961
+
962
+ # 添加调用者
963
+ if call_chain.get("callers"):
964
+ graph_text.append("\n调用此函数的函数:")
965
+ for i, caller in enumerate(call_chain["callers"]):
966
+ graph_text.append(f"{i + 1}. {caller['function']} (包: {caller['package']})")
967
+ else:
968
+ graph_text.append("\n没有找到调用此函数的函数")
969
+
970
+ # 添加被调用者
971
+ if call_chain.get("callees"):
972
+ graph_text.append("\n此函数调用的函数:")
973
+ for i, callee in enumerate(call_chain["callees"]):
974
+ graph_text.append(f"{i + 1}. {callee['function']} (包: {callee['package']})")
975
+ else:
976
+ graph_text.append("\n此函数没有调用其他函数")
977
+
978
+ # 拼接文本
979
+ graph_text = "\n".join(graph_text)
980
+
981
+ # 打印到控制台
982
+ print(graph_text)
983
+
984
+ # 如果指定了输出文件,写入文件
985
+ if output_file:
986
+ try:
987
+ with open(output_file, 'w', encoding='utf-8') as f:
988
+ f.write(graph_text)
989
+ print(f"\n调用关系图已保存到: {output_file}")
990
+ except IOError as e:
991
+ print(f"写入文件失败: {e}")
992
+ return False
993
+
994
+ return True
995
+ except Exception as e:
996
+ print(f"生成调用关系图时出错: {e}")
997
+ return False
998
+
999
+
1000
+ def query_segments(query_value, limit=10, exact=False, query_type=None):
1001
+ """
1002
+ 查询代码片段
1003
+
1004
+ 参数:
1005
+ query_value (str): 查询值
1006
+ limit (int): 结果数量限制
1007
+ exact (bool): 是否精确匹配
1008
+ query_type (str): 查询类型,可选值:function, package, file_path, all
1009
+
1010
+ 返回:
1011
+ list: 查询结果列表
1012
+ """
1013
+ if not query_value:
1014
+ print("错误: 查询内容不能为空")
1015
+ return []
1016
+
1017
+ limit = validate_limit(limit)
1018
+
1019
+ # 自动检测查询类型
1020
+ if not query_type:
1021
+ query_type = detect_query_type(query_value)
1022
+
1023
+ if query_type == "function":
1024
+ return query_by_field("function", query_value, limit, exact)
1025
+ elif query_type == "function_detail":
1026
+ # 提取函数名(去掉后面的"详情"、"实现"等词)
1027
+ function_name = re.match(r'^([a-zA-Z_][a-zA-Z0-9_]*)', query_value).group(1)
1028
+ return get_function_detail(function_name, exact)
1029
+ elif query_type == "package":
1030
+ return query_by_field("package", query_value, limit, exact)
1031
+ elif query_type == "file_path":
1032
+ return query_by_field("file_path", query_value, limit, exact)
1033
+ else:
1034
+ return query_all_types(query_value, limit, exact)
1035
+
1036
+
1037
+ def query_all_types(query_value, limit=10, exact=False):
1038
+ """
1039
+ 查询所有类型的代码片段,并按类型分组返回结果
1040
+ 同时查询四个主要类:GoCodeSegment、IdlApiMapping、IdlStructDefinition、FunctionCallRelation
1041
+
1042
+ 参数:
1043
+ query_value (str): 查询值
1044
+ limit (int): 结果数量限制
1045
+ exact (bool): 是否精确匹配
1046
+
1047
+ 返回:
1048
+ dict: 按类型分组的查询结果
1049
+ """
1050
+ if not query_value:
1051
+ return {}
1052
+
1053
+ limit = validate_limit(limit)
1054
+ all_results = []
1055
+
1056
+ # 调试信息 - 注释掉,实际使用时可以去掉
1057
+ # print(f"\n开始搜索 '{query_value}' (精确匹配: {exact})...")
1058
+
1059
+ # 1. 查询 GoCodeSegment 类
1060
+ go_code_results = query_code_segments(query_value, limit, exact)
1061
+ # print(f"GoCodeSegment 类搜索结果: {len(go_code_results)} 条")
1062
+ if go_code_results:
1063
+ all_results.extend(go_code_results)
1064
+
1065
+ # 2. 查询 IdlApiMapping 类
1066
+ api_results = query_api_mappings(query_value, limit, exact)
1067
+ # print(f"IdlApiMapping 类搜索结果: {len(api_results)} 条")
1068
+ if api_results:
1069
+ all_results.extend(api_results)
1070
+
1071
+ # 3. 查询 IdlStructDefinition 类
1072
+ struct_results = query_struct_definitions(query_value, limit, exact)
1073
+ # print(f"IdlStructDefinition 类搜索结果: {len(struct_results)} 条")
1074
+ if struct_results:
1075
+ all_results.extend(struct_results)
1076
+
1077
+ # 4. 查询 FunctionCallRelation 类
1078
+ relation_results = query_function_relations(query_value, limit, exact)
1079
+ # print(f"FunctionCallRelation 类搜索结果: {len(relation_results)} 条")
1080
+ if relation_results:
1081
+ all_results.extend(relation_results)
1082
+
1083
+ # print(f"总共找到 {len(all_results)} 条结果")
1084
+
1085
+ # 按类型分组结果
1086
+ grouped_results = {}
1087
+ for result in all_results[:limit]:
1088
+ result_type = result.get("type", "unknown")
1089
+ if result_type not in grouped_results:
1090
+ grouped_results[result_type] = []
1091
+ grouped_results[result_type].append(result)
1092
+
1093
+ return grouped_results
1094
+
1095
+
1096
+ def query_code_segments(query_value, limit=10, exact=False):
1097
+ """
1098
+ 查询 GoCodeSegment 类
1099
+
1100
+ 参数:
1101
+ query_value (str): 查询值
1102
+ limit (int): 结果数量限制
1103
+ exact (bool): 是否精确匹配
1104
+
1105
+ 返回:
1106
+ list: 查询结果列表
1107
+ """
1108
+ # 先尝试按字段查询
1109
+ fields = ["name", "package", "file_path"]
1110
+ results = []
1111
+
1112
+ for field in fields:
1113
+ field_results = query_by_field(field, query_value, limit // 3, exact)
1114
+ if field_results:
1115
+ results.extend(field_results)
1116
+
1117
+ # 再尝试按内容查询
1118
+ if len(results) < limit:
1119
+ content_results = query_by_content(query_value, limit - len(results))
1120
+ if content_results:
1121
+ results.extend(content_results)
1122
+
1123
+ return results
1124
+
1125
+
1126
+ def query_api_mappings(query_value, limit=10, exact=False):
1127
+ """
1128
+ 查询 IdlApiMapping 类中的API定义
1129
+
1130
+ 参数:
1131
+ query_value (str): 查询值
1132
+ limit (int): 结果数量限制
1133
+ exact (bool): 是否精确匹配
1134
+
1135
+ 返回:
1136
+ list: 格式化的API映射结果列表
1137
+ """
1138
+ limit = validate_limit(limit)
1139
+ operator = "Equal" if exact else "Like"
1140
+ value_str = query_value
1141
+
1142
+ # 对于Like操作符,添加通配符
1143
+ if operator == "Like" and "*" not in query_value:
1144
+ value_str = f"*{query_value}*"
1145
+
1146
+ # 尝试多个字段查询
1147
+ fields = ["method_name", "http_path", "request_type", "response_type", "idl_content"]
1148
+ api_results = []
1149
+
1150
+ for field in fields:
1151
+ graphql_query = f"""
1152
+ {{
1153
+ Get {{
1154
+ IdlApiMapping(
1155
+ where: {{
1156
+ path: ["{field}"],
1157
+ operator: {operator},
1158
+ valueString: "{value_str}"
1159
+ }}
1160
+ limit: {limit // len(fields)}
1161
+ ) {{
1162
+ method_name
1163
+ http_path
1164
+ http_method
1165
+ idl_file
1166
+ service_name
1167
+ idl_content
1168
+ request_type
1169
+ response_type
1170
+ comments
1171
+ _additional {{
1172
+ id
1173
+ }}
1174
+ }}
1175
+ }}
1176
+ }}
1177
+ """
1178
+
1179
+ response = execute_graphql_query(graphql_query)
1180
+ if response and "error" not in response:
1181
+ results = response.get("data", {}).get("Get", {}).get("IdlApiMapping", [])
1182
+
1183
+ for result in results:
1184
+ api_result = {
1185
+ "id": result.get("_additional", {}).get("id", ""),
1186
+ "function": result.get("method_name", ""),
1187
+ "package": result.get("service_name", ""),
1188
+ "file_path": result.get("idl_file", ""),
1189
+ "type": "api",
1190
+ "code": result.get("idl_content", ""),
1191
+ "http_path": result.get("http_path", ""),
1192
+ "http_method": result.get("http_method", ""),
1193
+ "request_type": result.get("request_type", ""),
1194
+ "response_type": result.get("response_type", ""),
1195
+ "comments": result.get("comments", "")
1196
+ }
1197
+ api_results.append(api_result)
1198
+
1199
+ return api_results
1200
+
1201
+
1202
+ def query_struct_definitions(query_value, limit=10, exact=False):
1203
+ """
1204
+ 查询 IdlStructDefinition 类中的结构体定义
1205
+
1206
+ 参数:
1207
+ query_value (str): 查询值
1208
+ limit (int): 结果数量限制
1209
+ exact (bool): 是否精确匹配
1210
+
1211
+ 返回:
1212
+ list: 格式化的结构体定义结果列表
1213
+ """
1214
+ limit = validate_limit(limit)
1215
+ operator = "Equal" if exact else "Like"
1216
+ value_str = query_value
1217
+
1218
+ # 对于Like操作符,添加通配符
1219
+ if operator == "Like" and "*" not in query_value:
1220
+ value_str = f"*{query_value}*"
1221
+
1222
+ # 尝试多个字段查询
1223
+ fields = ["struct_name", "content", "fields"]
1224
+ struct_results = []
1225
+
1226
+ for field in fields:
1227
+ graphql_query = f"""
1228
+ {{
1229
+ Get {{
1230
+ IdlStructDefinition(
1231
+ where: {{
1232
+ path: ["{field}"],
1233
+ operator: {operator},
1234
+ valueString: "{value_str}"
1235
+ }}
1236
+ limit: {limit // len(fields)}
1237
+ ) {{
1238
+ struct_name
1239
+ idl_file
1240
+ content
1241
+ fields
1242
+ line_start
1243
+ line_end
1244
+ comments
1245
+ _additional {{
1246
+ id
1247
+ }}
1248
+ }}
1249
+ }}
1250
+ }}
1251
+ """
1252
+
1253
+ response = execute_graphql_query(graphql_query)
1254
+ if response and "error" not in response:
1255
+ results = response.get("data", {}).get("Get", {}).get("IdlStructDefinition", [])
1256
+
1257
+ for result in results:
1258
+ struct_result = {
1259
+ "id": result.get("_additional", {}).get("id", ""),
1260
+ "function": result.get("struct_name", ""),
1261
+ "package": os.path.dirname(result.get("idl_file", "")),
1262
+ "file_path": result.get("idl_file", ""),
1263
+ "type": "struct",
1264
+ "code": result.get("content", ""),
1265
+ "fields": result.get("fields", ""),
1266
+ "comments": result.get("comments", ""),
1267
+ "start_line": result.get("line_start", 0),
1268
+ "end_line": result.get("line_end", 0)
1269
+ }
1270
+ struct_results.append(struct_result)
1271
+
1272
+ return struct_results
1273
+
1274
+
1275
+ def query_function_relations(query_value, limit=10, exact=False):
1276
+ """
1277
+ 查询 FunctionCallRelation 类中的函数调用关系
1278
+
1279
+ 参数:
1280
+ query_value (str): 查询值
1281
+ limit (int): 结果数量限制
1282
+ exact (bool): 是否精确匹配
1283
+
1284
+ 返回:
1285
+ list: 格式化的函数调用关系结果列表
1286
+ """
1287
+ limit = validate_limit(limit)
1288
+ operator = "Equal" if exact else "Like"
1289
+ value_str = query_value
1290
+
1291
+ # 对于Like操作符,添加通配符
1292
+ if operator == "Like" and "*" not in query_value:
1293
+ value_str = f"*{query_value}*"
1294
+
1295
+ # 尝试多个字段查询
1296
+ fields = ["caller_function", "callee_function", "caller_package", "callee_package", "call_context"]
1297
+ relation_results = []
1298
+
1299
+ for field in fields:
1300
+ graphql_query = f"""
1301
+ {{
1302
+ Get {{
1303
+ FunctionCallRelation(
1304
+ where: {{
1305
+ path: ["{field}"],
1306
+ operator: {operator},
1307
+ valueString: "{value_str}"
1308
+ }}
1309
+ limit: {limit // len(fields)}
1310
+ ) {{
1311
+ caller_function
1312
+ caller_file
1313
+ caller_package
1314
+ callee_function
1315
+ callee_file
1316
+ callee_package
1317
+ call_line
1318
+ call_context
1319
+ call_type
1320
+ _additional {{
1321
+ id
1322
+ }}
1323
+ }}
1324
+ }}
1325
+ }}
1326
+ """
1327
+
1328
+ response = execute_graphql_query(graphql_query)
1329
+ if response and "error" not in response:
1330
+ results = response.get("data", {}).get("Get", {}).get("FunctionCallRelation", [])
1331
+
1332
+ for result in results:
1333
+ relation_result = {
1334
+ "id": result.get("_additional", {}).get("id", ""),
1335
+ "function": result.get("caller_function", ""),
1336
+ "package": result.get("caller_package", ""),
1337
+ "file_path": result.get("caller_file", ""),
1338
+ "type": "function_call",
1339
+ "code": result.get("call_context", ""),
1340
+ "called_function": result.get("callee_function", ""),
1341
+ "called_package": result.get("callee_package", ""),
1342
+ "called_file": result.get("callee_file", ""),
1343
+ "line": result.get("call_line", 0),
1344
+ "call_type": result.get("call_type", "")
1345
+ }
1346
+ relation_results.append(relation_result)
1347
+
1348
+ return relation_results
1349
+
1350
+
1351
+ def detect_query_type(query_value):
1352
+ """
1353
+ 自动检测查询类型
1354
+
1355
+ 参数:
1356
+ query_value (str): 查询值
1357
+
1358
+ 返回:
1359
+ str: 查询类型 (function, function_detail, package, file_path, all)
1360
+ """
1361
+ if not query_value:
1362
+ return "all"
1363
+
1364
+ # 查询值为路径格式,例如 "path/to/file.go"
1365
+ if "/" in query_value and not query_value.startswith("/"):
1366
+ return "file_path"
1367
+
1368
+ # 查询值为完整路径格式,例如 "/abs/path/to/file.go"
1369
+ if query_value.startswith("/"):
1370
+ return "file_path"
1371
+
1372
+ # 函数详情查询模式:函数名+特定关键词
1373
+ if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*\s+(实现|详情|代码|细节|function detail)$', query_value, re.IGNORECASE):
1374
+ return "function_detail"
1375
+
1376
+ # 查询值包含点号,可能是包名,例如 "github.com/user/repo"
1377
+ if "." in query_value and "/" in query_value:
1378
+ return "package"
1379
+
1380
+ # 查询值是有效的标识符,可能是函数名
1381
+ if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', query_value):
1382
+ return "function"
1383
+
1384
+ # 默认为全部类型查询
1385
+ return "all"
1386
+
1387
+
1388
+ def query_by_field(field_name, field_value, limit=10, exact=False):
1389
+ """
1390
+ 按字段查询代码片段
1391
+
1392
+ 参数:
1393
+ field_name (str): 字段名称 (name, package, file_path)
1394
+ field_value (str): 字段值
1395
+ limit (int): 结果数量限制
1396
+ exact (bool): 是否精确匹配
1397
+
1398
+ 返回:
1399
+ list: 查询结果列表
1400
+ """
1401
+ if not field_name or not field_value:
1402
+ return []
1403
+
1404
+ # 字段名映射,兼容旧代码
1405
+ field_mapping = {
1406
+ "function": "name",
1407
+ "type": "segment_type"
1408
+ }
1409
+
1410
+ # 如果使用了旧字段名,转换为新字段名
1411
+ if field_name in field_mapping:
1412
+ field_name = field_mapping[field_name]
1413
+
1414
+ limit = validate_limit(limit)
1415
+
1416
+ operator = "" if exact else "Like"
1417
+
1418
+ graphql_query = f"""
1419
+ {{
1420
+ Get {{
1421
+ {CLASS_NAME}(
1422
+ where: {{
1423
+ path: ["{field_name}"],
1424
+ operator: {operator},
1425
+ valueString: "{field_value}"
1426
+ }}
1427
+ limit: {limit}
1428
+ ) {{
1429
+ _additional {{
1430
+ id
1431
+ }}
1432
+ name
1433
+ package
1434
+ file_path
1435
+ segment_type
1436
+ content
1437
+ imports
1438
+ line_start
1439
+ line_end
1440
+ }}
1441
+ }}
1442
+ }}
1443
+ """
1444
+
1445
+ response = execute_graphql_query(graphql_query)
1446
+ if not response or "error" in response:
1447
+ error_msg = response.get("error") if response else "未知错误"
1448
+ print(f"查询失败: {error_msg}")
1449
+ return []
1450
+
1451
+ segments = response.get("data", {}).get("Get", {}).get(CLASS_NAME, [])
1452
+ return [format_graphql_result(segment) for segment in segments]
1453
+
1454
+
1455
+ def query_by_content(query_text, limit=10):
1456
+ """
1457
+ 按内容查询代码片段
1458
+
1459
+ 参数:
1460
+ query_text (str): 查询文本
1461
+ limit (int): 结果数量限制
1462
+
1463
+ 返回:
1464
+ list: 查询结果列表
1465
+ """
1466
+ if not query_text:
1467
+ return []
1468
+
1469
+ limit = validate_limit(limit)
1470
+
1471
+ # 清理查询文本,避免特殊字符导致查询失败
1472
+ cleaned_query = query_text.replace('"', '\\"').replace('\n', ' ')
1473
+
1474
+ graphql_query = f"""
1475
+ {{
1476
+ Get {{
1477
+ {CLASS_NAME}(
1478
+ nearText: {{
1479
+ concepts: ["{cleaned_query}"]
1480
+ }}
1481
+ limit: {limit}
1482
+ ) {{
1483
+ _additional {{
1484
+ id
1485
+ certainty
1486
+ }}
1487
+ name
1488
+ package
1489
+ file_path
1490
+ segment_type
1491
+ content
1492
+ imports
1493
+ line_start
1494
+ line_end
1495
+ }}
1496
+ }}
1497
+ }}
1498
+ """
1499
+
1500
+ response = execute_graphql_query(graphql_query)
1501
+ if not response or "error" in response:
1502
+ error_msg = response.get("error") if response else "未知错误"
1503
+ print(f"查询失败: {error_msg}")
1504
+ return []
1505
+
1506
+ segments = response.get("data", {}).get("Get", {}).get(CLASS_NAME, [])
1507
+
1508
+ # 处理结果并按相关性(certainty)排序
1509
+ results = []
1510
+ for segment in segments:
1511
+ result = format_graphql_result(segment)
1512
+ certainty = segment.get("_additional", {}).get("certainty", 0)
1513
+ result["relevance"] = certainty
1514
+ results.append(result)
1515
+
1516
+ # 按相关性排序
1517
+ results.sort(key=lambda x: x.get("relevance", 0), reverse=True)
1518
+
1519
+ return results
1520
+
1521
+
1522
+ def format_graphql_result(graphql_segment):
1523
+ """
1524
+ 格式化GraphQL查询结果
1525
+
1526
+ 参数:
1527
+ graphql_segment (dict): GraphQL查询结果
1528
+
1529
+ 返回:
1530
+ dict: 格式化后的结果
1531
+ """
1532
+ if not graphql_segment:
1533
+ return {}
1534
+
1535
+ segment = {
1536
+ "id": graphql_segment.get("_additional", {}).get("id", ""),
1537
+ "function": graphql_segment.get("name", ""), # 映射name到function
1538
+ "package": graphql_segment.get("package", ""),
1539
+ "file_path": graphql_segment.get("file_path", ""),
1540
+ "type": graphql_segment.get("segment_type", ""), # 映射segment_type到type
1541
+ "code": graphql_segment.get("content", ""),
1542
+ "imports": graphql_segment.get("imports", []),
1543
+ "comments": graphql_segment.get("comments", ""),
1544
+ "start_line": graphql_segment.get("line_start", 0),
1545
+ "end_line": graphql_segment.get("line_end", 0)
1546
+ }
1547
+ return segment
1548
+
1549
+
1550
+ @with_retry()
1551
+ def execute_graphql_query(query):
1552
+ """
1553
+ 执行GraphQL查询
1554
+
1555
+ 参数:
1556
+ query (str): GraphQL查询语句
1557
+
1558
+ 返回:
1559
+ dict: 查询结果
1560
+ """
1561
+ if not query:
1562
+ return {"error": "查询语句不能为空"}
1563
+
1564
+ url = f"{WEAVIATE_URL}/graphql"
1565
+ headers = {
1566
+ "Content-Type": "application/json"
1567
+ }
1568
+
1569
+ payload = {"query": query}
1570
+
1571
+ try:
1572
+ response = requests.post(url, headers=headers, json=payload, timeout=30)
1573
+ if response.status_code == 200:
1574
+ return response.json()
1575
+ else:
1576
+ error_msg = f"GraphQL查询错误: {response.status_code} - {response.text}"
1577
+ if DEBUG:
1578
+ print(error_msg)
1579
+ return {"error": error_msg}
1580
+ except requests.RequestException as e:
1581
+ error_msg = f"请求异常: {e}"
1582
+ if DEBUG:
1583
+ print(error_msg)
1584
+ return {"error": error_msg}
1585
+ except Exception as e:
1586
+ error_msg = f"未知异常: {e}"
1587
+ if DEBUG:
1588
+ print(error_msg)
1589
+ return {"error": error_msg}
1590
+
1591
+
1592
+ def print_summary_table(segments):
1593
+ """
1594
+ 将查询结果格式化输出为表格
1595
+
1596
+ 参数:
1597
+ segments (dict|list): 查询结果,可以是列表或按类型分组的字典
1598
+ """
1599
+ if not segments:
1600
+ print("未找到匹配的结果")
1601
+ return
1602
+
1603
+ # 如果是按类型分组的字典
1604
+ if isinstance(segments, dict):
1605
+ # 检查是否有结果
1606
+ if sum(len(group) for group in segments.values()) == 0:
1607
+ print("未找到匹配的结果")
1608
+ return
1609
+
1610
+ # 分别显示每种类型的结果
1611
+ for segment_type, segment_list in segments.items():
1612
+ if not segment_list:
1613
+ continue
1614
+
1615
+ print(f"\n{'-' * 60}")
1616
+ print(f"类型: {segment_type.upper()} ({len(segment_list)} 个结果)")
1617
+ print(f"{'-' * 60}")
1618
+
1619
+ # 根据类型显示不同的表格
1620
+ if segment_type == "function":
1621
+ print("【函数定义】")
1622
+ _print_function_table(segment_list)
1623
+ elif segment_type == "file":
1624
+ print("【文件】")
1625
+ _print_file_table(segment_list)
1626
+ elif segment_type == "struct" or segment_type == "interface":
1627
+ print("【数据结构】")
1628
+ _print_struct_table(segment_list)
1629
+ elif segment_type == "api":
1630
+ print("【API接口】")
1631
+ _print_api_table(segment_list)
1632
+ elif segment_type == "function_call":
1633
+ print("【函数调用关系】")
1634
+ _print_function_call_table(segment_list)
1635
+ else:
1636
+ print(f"【{segment_type}】")
1637
+ _print_general_table(segment_list)
1638
+
1639
+ return
1640
+
1641
+ # 如果是列表但为空
1642
+ if len(segments) == 0:
1643
+ print("未找到匹配的结果")
1644
+ return
1645
+
1646
+ # 如果是列表,则不按类型分组显示
1647
+ print("【查询结果】")
1648
+ _print_general_table(segments)
1649
+
1650
+
1651
+ def _print_function_table(segments):
1652
+ """打印函数类型的表格"""
1653
+ table = PrettyTable()
1654
+ table.field_names = ["函数名", "包名", "文件路径", "行范围"]
1655
+ table.align = "l"
1656
+ table.max_width = 30 # 减小列宽以适应终端宽度
1657
+
1658
+ for segment in segments:
1659
+ # 获取行范围
1660
+ start_line = segment.get("start_line", 0)
1661
+ end_line = segment.get("end_line", 0)
1662
+ line_range = f"{start_line}-{end_line}" if start_line and end_line else "N/A"
1663
+
1664
+ # 处理长文件路径
1665
+ file_path = segment.get("file_path", "N/A")
1666
+ if len(file_path) > 30:
1667
+ file_path = "..." + file_path[-27:]
1668
+
1669
+ table.add_row([
1670
+ segment.get("function", "N/A")[:25], # 限制长度
1671
+ segment.get("package", "N/A")[:15], # 限制长度
1672
+ file_path,
1673
+ line_range
1674
+ ])
1675
+
1676
+ print(table)
1677
+
1678
+
1679
+ def _print_file_table(segments):
1680
+ """打印文件类型的表格"""
1681
+ table = PrettyTable()
1682
+ table.field_names = ["文件名", "包名", "文件路径", "行数"]
1683
+ table.align = "l"
1684
+ table.max_width = 30 # 减小列宽以适应终端宽度
1685
+
1686
+ for segment in segments:
1687
+ # 获取行数
1688
+ start_line = segment.get("start_line", 0)
1689
+ end_line = segment.get("end_line", 0)
1690
+ line_count = end_line - start_line + 1 if start_line and end_line else "N/A"
1691
+
1692
+ # 处理长文件路径
1693
+ file_path = segment.get("file_path", "N/A")
1694
+ if len(file_path) > 30:
1695
+ file_path = "..." + file_path[-27:]
1696
+
1697
+ # 处理长文件名
1698
+ file_name = segment.get("function", "N/A") # 对于文件类型,function字段存储文件名
1699
+ if len(file_name) > 25:
1700
+ file_name = file_name[:22] + "..."
1701
+
1702
+ table.add_row([
1703
+ file_name,
1704
+ segment.get("package", "N/A")[:15], # 限制长度
1705
+ file_path,
1706
+ line_count
1707
+ ])
1708
+
1709
+ print(table)
1710
+
1711
+
1712
+ def _print_struct_table(segments):
1713
+ """打印结构体或接口类型的表格"""
1714
+ table = PrettyTable()
1715
+ table.field_names = ["结构名", "包名", "文件路径", "行范围"]
1716
+ table.align = "l"
1717
+ table.max_width = 30 # 减小列宽以适应终端宽度
1718
+
1719
+ for segment in segments:
1720
+ # 获取行范围
1721
+ start_line = segment.get("start_line", 0)
1722
+ end_line = segment.get("end_line", 0)
1723
+ line_range = f"{start_line}-{end_line}" if start_line and end_line else "N/A"
1724
+
1725
+ # 处理长文件路径
1726
+ file_path = segment.get("file_path", "N/A")
1727
+ if len(file_path) > 30:
1728
+ file_path = "..." + file_path[-27:]
1729
+
1730
+ table.add_row([
1731
+ segment.get("function", "N/A")[:25], # 对于结构体类型,function字段存储结构体名
1732
+ segment.get("package", "N/A")[:15], # 限制长度
1733
+ file_path,
1734
+ line_range
1735
+ ])
1736
+
1737
+ print(table)
1738
+
1739
+
1740
+ def _print_api_table(segments):
1741
+ """打印API接口类型的表格"""
1742
+ table = PrettyTable()
1743
+ table.field_names = ["方法名", "服务名", "HTTP路径", "请求类型", "响应类型"]
1744
+ table.align = "l"
1745
+ table.max_width = 20 # 减小列宽以适应终端宽度
1746
+
1747
+ for segment in segments:
1748
+ # 截断长字符串
1749
+ method_name = segment.get("function", "N/A")
1750
+ service_name = segment.get("package", "N/A")
1751
+ http_path = segment.get("http_path", "N/A")
1752
+ request_type = segment.get("request_type", "N/A")
1753
+ response_type = segment.get("response_type", "N/A")
1754
+
1755
+ # 截断超长字符串
1756
+ if len(method_name) > 15:
1757
+ method_name = method_name[:12] + "..."
1758
+ if len(service_name) > 12:
1759
+ service_name = service_name[:9] + "..."
1760
+ if len(http_path) > 15:
1761
+ http_path = http_path[:12] + "..."
1762
+ if len(request_type) > 15:
1763
+ request_type = request_type[:12] + "..."
1764
+ if len(response_type) > 15:
1765
+ response_type = response_type[:12] + "..."
1766
+
1767
+ table.add_row([
1768
+ method_name, # 方法名
1769
+ service_name, # 服务名
1770
+ http_path, # HTTP路径
1771
+ request_type, # 请求类型
1772
+ response_type # 响应类型
1773
+ ])
1774
+
1775
+ print(table)
1776
+
1777
+
1778
+ def _print_function_call_table(segments):
1779
+ """打印函数调用关系类型的表格"""
1780
+ table = PrettyTable()
1781
+ table.field_names = ["调用者函数", "调用者包", "被调函数", "被调包", "文件路径", "行号"]
1782
+ table.align = "l"
1783
+ table.max_width = 30 # 减小列宽以适应终端宽度
1784
+
1785
+ for segment in segments:
1786
+ # 只截取文件路径的最后部分,以减少显示宽度
1787
+ file_path = segment.get("file_path", "N/A")
1788
+ if len(file_path) > 30:
1789
+ file_path = "..." + file_path[-27:]
1790
+
1791
+ table.add_row([
1792
+ segment.get("function", "N/A")[:25], # 调用者函数
1793
+ segment.get("package", "N/A")[:15], # 调用者包
1794
+ segment.get("called_function", "N/A")[:25], # 被调函数
1795
+ segment.get("called_package", "N/A")[:15], # 被调包
1796
+ file_path, # 文件路径
1797
+ segment.get("line", "N/A") # 行号
1798
+ ])
1799
+
1800
+ print(table)
1801
+
1802
+
1803
+ def _print_general_table(segments):
1804
+ """打印通用类型的表格"""
1805
+ table = PrettyTable()
1806
+ table.field_names = ["类型", "名称", "包名", "文件路径"]
1807
+ table.align = "l"
1808
+ table.max_width = 30 # 减小列宽以适应终端宽度
1809
+
1810
+ for segment in segments:
1811
+ # 处理长文件路径
1812
+ file_path = segment.get("file_path", "N/A")
1813
+ if len(file_path) > 30:
1814
+ file_path = "..." + file_path[-27:]
1815
+
1816
+ table.add_row([
1817
+ segment.get("type", "unknown")[:15],
1818
+ segment.get("function", "N/A")[:25],
1819
+ segment.get("package", "N/A")[:15],
1820
+ file_path
1821
+ ])
1822
+
1823
+ print(table)
1824
+
1825
+
1826
+ def print_api_info(api_info):
1827
+ """
1828
+ 打印API信息
1829
+
1830
+ 参数:
1831
+ api_info (dict): API信息
1832
+ """
1833
+ if api_info.get("error"):
1834
+ print(f"错误: {api_info['error']}")
1835
+ return
1836
+
1837
+ print(f"\nAPI路径: {api_info['api_path']}")
1838
+ print(f"找到 {len(api_info['definitions'])} 个相关定义")
1839
+
1840
+ for i, definition in enumerate(api_info['definitions']):
1841
+ print(f"\n定义 {i + 1}:")
1842
+ print(f" 函数: {definition['function']}")
1843
+ print(f" 包名: {definition['package']}")
1844
+ print(f" 文件: {definition['file_path']}")
1845
+ if definition.get('http_method'):
1846
+ print(f" HTTP方法: {definition['http_method']}")
1847
+
1848
+ print("\n 入参:")
1849
+ if definition.get('request_type'):
1850
+ print(f" [请求类型: {definition['request_type']}]")
1851
+
1852
+ if definition['input_params']:
1853
+ for param in definition['input_params']:
1854
+ req_str = "(必填)" if param.get("required") else "(可选)"
1855
+ comment = f" // {param.get('comment')}" if param.get('comment') else ""
1856
+ print(f" - {param.get('name', '')}: {param.get('type', '')} {req_str}{comment}")
1857
+ else:
1858
+ print(" 无")
1859
+
1860
+ print("\n 出参:")
1861
+ if definition.get('response_type'):
1862
+ print(f" [响应类型: {definition['response_type']}]")
1863
+
1864
+ if definition['output_params']:
1865
+ for param in definition['output_params']:
1866
+ if 'name' in param and 'type' in param:
1867
+ comment = f" // {param.get('comment')}" if param.get('comment') else ""
1868
+ print(f" - {param.get('name', '')}: {param.get('type', '')}{comment}")
1869
+ else:
1870
+ print(f" - {param.get('type', '')}")
1871
+ else:
1872
+ print(" 无")
1873
+
1874
+ print("\n 逻辑代码片段:")
1875
+ code_lines = definition['logic'].split('\n')
1876
+ for line in code_lines[:10]: # 只显示前10行
1877
+ print(f" {line}")
1878
+
1879
+ if len(code_lines) > 10:
1880
+ print(f" ... (共 {len(code_lines)} 行)")
1881
+
1882
+
1883
+ def print_function_call_chain(call_chain):
1884
+ """
1885
+ 打印函数调用链
1886
+
1887
+ 参数:
1888
+ call_chain (dict): 函数调用链信息
1889
+ """
1890
+ if call_chain.get("error"):
1891
+ print(f"错误: {call_chain['error']}")
1892
+ return
1893
+
1894
+ print(f"\n函数: {call_chain['function']}")
1895
+
1896
+ print("\n调用此函数的函数 (callers):")
1897
+ if call_chain.get("callers"):
1898
+ caller_data = []
1899
+ for i, caller in enumerate(call_chain["callers"]):
1900
+ caller_data.append([
1901
+ i + 1,
1902
+ caller.get("function", ""),
1903
+ caller.get("package", ""),
1904
+ caller.get("file_path", ""),
1905
+ caller.get("line", "")
1906
+ ])
1907
+ print(tabulate(caller_data, headers=["序号", "函数名", "包名", "文件路径", "行号"], tablefmt="grid"))
1908
+ else:
1909
+ print(" 无")
1910
+
1911
+ print("\n此函数调用的函数 (callees):")
1912
+ if call_chain.get("callees"):
1913
+ callee_data = []
1914
+ for i, callee in enumerate(call_chain["callees"]):
1915
+ callee_data.append([
1916
+ i + 1,
1917
+ callee.get("function", ""),
1918
+ callee.get("package", ""),
1919
+ callee.get("file_path", ""),
1920
+ callee.get("line", "")
1921
+ ])
1922
+ print(tabulate(callee_data, headers=["序号", "函数名", "包名", "文件路径", "行号"], tablefmt="grid"))
1923
+ else:
1924
+ print(" 无")
1925
+
1926
+
1927
+ def print_code_impact(code_impact):
1928
+ """
1929
+ 打印代码影响分析结果
1930
+
1931
+ 参数:
1932
+ code_impact (dict): 代码影响分析结果
1933
+ """
1934
+ if code_impact.get("error"):
1935
+ print(f"错误: {code_impact['error']}")
1936
+ return
1937
+
1938
+ print("\n代码片段影响分析:")
1939
+ print(f"代码片段: {code_impact['code_snippet']}")
1940
+
1941
+ print("\n受影响的函数:")
1942
+ if code_impact.get("affected_functions"):
1943
+ affected_data = []
1944
+ for i, func in enumerate(code_impact["affected_functions"]):
1945
+ affected_data.append([
1946
+ i + 1,
1947
+ func.get("function", ""),
1948
+ func.get("package", ""),
1949
+ func.get("file_path", ""),
1950
+ func.get("line", ""),
1951
+ func.get("relevance", "")
1952
+ ])
1953
+ print(
1954
+ tabulate(affected_data, headers=["序号", "函数名", "包名", "文件路径", "行号", "相关性"], tablefmt="grid"))
1955
+ else:
1956
+ print(" 无")
1957
+
1958
+
1959
+ def safe_read_file(file_path):
1960
+ """
1961
+ 安全地读取文件内容
1962
+
1963
+ 参数:
1964
+ file_path (str): 文件路径
1965
+
1966
+ 返回:
1967
+ str: 文件内容,失败则返回空字符串
1968
+ """
1969
+ try:
1970
+ with open(file_path, 'r', encoding='utf-8') as f:
1971
+ return f.read()
1972
+ except Exception as e:
1973
+ print(f"读取文件失败: {e}")
1974
+ return ""
1975
+
1976
+
1977
+ def generate_call_chain_topology(function_name, max_depth=5, direction="both", output_file=None):
1978
+ """
1979
+ 生成函数调用链拓扑图,按包名分组,最大深度5层
1980
+
1981
+ 参数:
1982
+ function_name (str): 函数名
1983
+ max_depth (int): 最大深度,默认为5
1984
+ direction (str): 调用方向,可选值:caller(调用者), callee(被调用者), both(双向)
1985
+ output_file (str): 输出文件路径
1986
+
1987
+ 返回:
1988
+ dict: 调用链拓扑图数据
1989
+ """
1990
+ if not function_name:
1991
+ return {"error": "函数名不能为空"}
1992
+
1993
+ # 限制最大深度,防止无限递归
1994
+ max_depth = min(max(1, max_depth), 5)
1995
+
1996
+ # 初始化拓扑图数据
1997
+ topology = {
1998
+ "root": function_name,
1999
+ "nodes": {}, # 节点信息:{function_name: {package, file_path, ...}}
2000
+ "edges": [], # 边信息:[{source, target, type}, ...]
2001
+ "packages": {} # 按包分组:{package_name: [function_names]}
2002
+ }
2003
+
2004
+ # 已访问的函数,避免循环引用
2005
+ visited = set()
2006
+
2007
+ # 递归获取调用链
2008
+ def build_topology(func, depth, dir):
2009
+ if depth > max_depth or func in visited:
2010
+ return
2011
+
2012
+ visited.add(func)
2013
+
2014
+ # 获取当前函数的调用关系
2015
+ call_chain = get_function_call_chain(func, 100, dir)
2016
+
2017
+ if "error" in call_chain:
2018
+ # 如果没有找到函数定义,仍将其添加为节点
2019
+ topology["nodes"][func] = {
2020
+ "function": func,
2021
+ "package": "unknown",
2022
+ "file_path": "",
2023
+ "is_found": False
2024
+ }
2025
+ return
2026
+
2027
+ # 添加当前函数节点
2028
+ if func not in topology["nodes"]:
2029
+ # 尝试获取函数定义信息
2030
+ func_defs = query_by_field("function", func, 1, True)
2031
+
2032
+ node_info = {
2033
+ "function": func,
2034
+ "package": "unknown",
2035
+ "file_path": "",
2036
+ "is_found": True
2037
+ }
2038
+
2039
+ # 首先尝试从函数定义中获取包名
2040
+ if func_defs:
2041
+ func_def = func_defs[0]
2042
+ package = func_def.get("package", "unknown")
2043
+
2044
+ node_info.update({
2045
+ "package": package,
2046
+ "file_path": func_def.get("file_path", ""),
2047
+ "start_line": func_def.get("start_line", 0),
2048
+ "end_line": func_def.get("end_line", 0)
2049
+ })
2050
+ else:
2051
+ # 如果没有找到函数定义,尝试从调用关系中获取包名
2052
+
2053
+ # 检查是否为当前调用链的根函数,如果是则优先从callers中获取
2054
+ if func == function_name:
2055
+ # 从调用者中寻找包信息
2056
+ for caller in call_chain.get("callers", []):
2057
+ if caller.get("package") and caller.get("package") != "unknown":
2058
+ node_info["package"] = caller.get("package")
2059
+ break
2060
+
2061
+ # 如果仍是unknown,尝试从callees中获取
2062
+ if node_info["package"] == "unknown":
2063
+ for callee in call_chain.get("callees", []):
2064
+ if callee.get("package") and callee.get("package") != "unknown":
2065
+ node_info["package"] = callee.get("package")
2066
+ break
2067
+
2068
+ # 更新包名分组
2069
+ package = node_info["package"]
2070
+ if package not in topology["packages"]:
2071
+ topology["packages"][package] = []
2072
+
2073
+ if func not in topology["packages"][package]:
2074
+ topology["packages"][package].append(func)
2075
+
2076
+ topology["nodes"][func] = node_info
2077
+
2078
+ # 处理调用者(谁调用了此函数)
2079
+ if dir in ["caller", "both"] and depth < max_depth:
2080
+ for caller in call_chain.get("callers", []):
2081
+ caller_func = caller.get("function")
2082
+ if not caller_func:
2083
+ continue
2084
+
2085
+ # 添加边
2086
+ edge = {
2087
+ "source": caller_func,
2088
+ "target": func,
2089
+ "type": "caller"
2090
+ }
2091
+
2092
+ if edge not in topology["edges"]:
2093
+ topology["edges"].append(edge)
2094
+
2095
+ # 递归处理调用者
2096
+ build_topology(caller_func, depth + 1, "caller")
2097
+
2098
+ # 同时更新节点的包信息
2099
+ if caller_func in topology["nodes"] and caller.get("package"):
2100
+ # 如果已有的包名是unknown,则更新
2101
+ if topology["nodes"][caller_func].get("package") == "unknown":
2102
+ package = caller.get("package", "unknown")
2103
+ topology["nodes"][caller_func]["package"] = package
2104
+
2105
+ # 更新包名分组
2106
+ if package not in topology["packages"]:
2107
+ topology["packages"][package] = []
2108
+
2109
+ # 从unknown包移除
2110
+ if "unknown" in topology["packages"] and caller_func in topology["packages"]["unknown"]:
2111
+ topology["packages"]["unknown"].remove(caller_func)
2112
+
2113
+ # 添加到正确的包
2114
+ if caller_func not in topology["packages"][package]:
2115
+ topology["packages"][package].append(caller_func)
2116
+
2117
+ # 处理被调用者(此函数调用了谁)
2118
+ if dir in ["callee", "both"] and depth < max_depth:
2119
+ for callee in call_chain.get("callees", []):
2120
+ callee_func = callee.get("function")
2121
+ if not callee_func:
2122
+ continue
2123
+
2124
+ # 添加边
2125
+ edge = {
2126
+ "source": func,
2127
+ "target": callee_func,
2128
+ "type": "callee"
2129
+ }
2130
+
2131
+ if edge not in topology["edges"]:
2132
+ topology["edges"].append(edge)
2133
+
2134
+ # 递归处理被调用者
2135
+ build_topology(callee_func, depth + 1, "callee")
2136
+
2137
+ # 同时更新节点的包信息
2138
+ if callee_func in topology["nodes"] and callee.get("package"):
2139
+ # 如果已有的包名是unknown,则更新
2140
+ if topology["nodes"][callee_func].get("package") == "unknown":
2141
+ package = callee.get("package", "unknown")
2142
+ topology["nodes"][callee_func]["package"] = package
2143
+
2144
+ # 更新包名分组
2145
+ if package not in topology["packages"]:
2146
+ topology["packages"][package] = []
2147
+
2148
+ # 从unknown包移除
2149
+ if "unknown" in topology["packages"] and callee_func in topology["packages"]["unknown"]:
2150
+ topology["packages"]["unknown"].remove(callee_func)
2151
+
2152
+ # 添加到正确的包
2153
+ if callee_func not in topology["packages"][package]:
2154
+ topology["packages"][package].append(callee_func)
2155
+
2156
+ # 从根函数开始构建拓扑图
2157
+ build_topology(function_name, 1, direction)
2158
+
2159
+ # 如果指定了输出文件,保存为JSON和DOT格式
2160
+ if output_file:
2161
+ # 保存为JSON格式
2162
+ json_file = f"{output_file}.json"
2163
+ try:
2164
+ with open(json_file, 'w', encoding='utf-8') as f:
2165
+ json.dump(topology, f, indent=2, ensure_ascii=False)
2166
+ print(f"拓扑图数据已保存为JSON: {json_file}")
2167
+ except Exception as e:
2168
+ print(f"保存JSON失败: {e}")
2169
+
2170
+ # 生成DOT文件(用于Graphviz可视化)
2171
+ dot_file = f"{output_file}.dot"
2172
+ try:
2173
+ with open(dot_file, 'w', encoding='utf-8') as f:
2174
+ f.write('digraph call_chain {\n')
2175
+ f.write(' rankdir=LR;\n') # 从左到右布局
2176
+ f.write(' node [shape=box, style=filled, fontname="Arial"];\n')
2177
+
2178
+ # 按包分组
2179
+ for package, funcs in topology["packages"].items():
2180
+ if package == "unknown":
2181
+ continue
2182
+
2183
+ f.write(f' subgraph cluster_{package.replace(".", "_")} {{\n')
2184
+ f.write(f' label="{package}";\n')
2185
+ f.write(' style=filled;\n')
2186
+ f.write(' color=lightgrey;\n')
2187
+
2188
+ for func in funcs:
2189
+ node_color = "lightblue" if func == function_name else "white"
2190
+ f.write(f' "{func}" [fillcolor="{node_color}"];\n')
2191
+
2192
+ f.write(' }\n')
2193
+
2194
+ # 未知包的节点
2195
+ if "unknown" in topology["packages"]:
2196
+ for func in topology["packages"]["unknown"]:
2197
+ node_color = "lightblue" if func == function_name else "white"
2198
+ f.write(f' "{func}" [fillcolor="{node_color}"];\n')
2199
+
2200
+ # 添加边
2201
+ for edge in topology["edges"]:
2202
+ source = edge["source"]
2203
+ target = edge["target"]
2204
+ edge_type = "solid" if edge["type"] == "callee" else "dashed"
2205
+ f.write(f' "{source}" -> "{target}" [style={edge_type}];\n')
2206
+
2207
+ f.write('}\n')
2208
+
2209
+ print(f"拓扑图已保存为DOT格式: {dot_file}")
2210
+ print(f"提示: 可使用Graphviz渲染DOT文件: dot -Tpng {dot_file} -o {output_file}.png")
2211
+ except Exception as e:
2212
+ print(f"保存DOT文件失败: {e}")
2213
+
2214
+ return topology
2215
+
2216
+
2217
+ def print_call_chain_topology(topology):
2218
+ """
2219
+ 打印调用链拓扑图
2220
+
2221
+ 参数:
2222
+ topology (dict): 调用链拓扑图数据
2223
+ """
2224
+ if "error" in topology:
2225
+ print(f"错误: {topology['error']}")
2226
+ return
2227
+
2228
+ root = topology.get("root", "")
2229
+ print(f"\n函数 '{root}' 的调用链拓扑图:")
2230
+
2231
+ # 打印按包分组的函数
2232
+ print("\n按包名分组的函数:")
2233
+ for package, funcs in topology.get("packages", {}).items():
2234
+ print(f"- 包: {package}")
2235
+ for func in funcs:
2236
+ is_root = "√" if func == root else ""
2237
+ print(f" - {func} {is_root}")
2238
+
2239
+ # 打印调用关系
2240
+ print("\n调用关系:")
2241
+ for edge in topology.get("edges", []):
2242
+ edge_type = "→" if edge["type"] == "callee" else "←"
2243
+ print(f" {edge['source']} {edge_type} {edge['target']}")
2244
+
2245
+ print(f"\n共有 {len(topology.get('nodes', {}))} 个函数节点, {len(topology.get('edges', []))} 条调用关系")
2246
+
2247
+
2248
+ def analyze_code_impact_advanced(code_snippet, limit=10):
2249
+ """
2250
+ 高级代码影响分析 - 分析代码片段影响的函数、API和业务逻辑
2251
+
2252
+ 参数:
2253
+ code_snippet (str): 代码片段
2254
+ limit (int): 结果数量限制
2255
+
2256
+ 返回:
2257
+ dict: 包含详细影响分析的结果
2258
+ """
2259
+ if not code_snippet:
2260
+ return {"error": "代码片段不能为空"}
2261
+
2262
+ result = {
2263
+ "code_snippet": code_snippet[:MAX_CONTENT_LENGTH] + "..." if len(
2264
+ code_snippet) > MAX_CONTENT_LENGTH else code_snippet,
2265
+ "affected_functions": [],
2266
+ "affected_apis": [],
2267
+ "business_logic_impact": [],
2268
+ "related_structs": []
2269
+ }
2270
+
2271
+ # 1. 提取关键元素
2272
+ # 提取函数名
2273
+ function_pattern = r'func\s+(\w+)\s*\('
2274
+ function_matches = re.findall(function_pattern, code_snippet)
2275
+
2276
+ # 提取API路径
2277
+ api_pattern = r'(\/v\d+\/[a-zA-Z0-9\/\-_]+)'
2278
+ api_matches = re.findall(api_pattern, code_snippet)
2279
+
2280
+ # 提取结构体名
2281
+ struct_pattern = r'([A-Z][a-zA-Z0-9]+(?:Req|Resp|Request|Response))'
2282
+ struct_matches = re.findall(struct_pattern, code_snippet)
2283
+
2284
+ # 提取模型名称和特殊条件
2285
+ model_pattern = r'([A-Z][a-zA-Z0-9]+(?:Chat|Model|GPT))'
2286
+ model_matches = re.findall(model_pattern, code_snippet)
2287
+
2288
+ # 提取错误处理
2289
+ error_pattern = r'(Err[a-zA-Z0-9]+)'
2290
+ error_matches = re.findall(error_pattern, code_snippet)
2291
+
2292
+ # 提取所有其他函数调用
2293
+ calls_pattern = r'(\w+)\s*\('
2294
+ calls_matches = re.findall(calls_pattern, code_snippet)
2295
+
2296
+ # 2. 查询相关元素
2297
+ # 查询API定义
2298
+ for api in api_matches:
2299
+ api_info = search_api_info(api, 1, False)
2300
+ if "error" not in api_info:
2301
+ result["affected_apis"].append(api_info)
2302
+
2303
+ # 如果没找到API,尝试从结构体名推断
2304
+ if not result["affected_apis"]:
2305
+ for struct in struct_matches:
2306
+ # 检查是否是请求或响应结构体
2307
+ if struct.endswith(("Req", "Request")):
2308
+ # 查找使用此请求结构体的API
2309
+ api_mappings = query_idl_api_mapping_by_request(struct, limit)
2310
+ for mapping in api_mappings:
2311
+ api_info = search_api_info(mapping.get("http_path", ""), 1, True)
2312
+ if "error" not in api_info:
2313
+ result["affected_apis"].append(api_info)
2314
+ elif struct.endswith(("Resp", "Response")):
2315
+ # 查找返回此响应结构体的API
2316
+ api_mappings = query_idl_api_mapping_by_response(struct, limit)
2317
+ for mapping in api_mappings:
2318
+ api_info = search_api_info(mapping.get("http_path", ""), 1, True)
2319
+ if "error" not in api_info:
2320
+ result["affected_apis"].append(api_info)
2321
+
2322
+ # 查询函数定义
2323
+ for func in function_matches + calls_matches:
2324
+ if len(func) > 3 and func not in ['if', 'len', 'for', 'range', 'switch', 'case', 'func']: # 忽略关键字和太短的函数名
2325
+ func_info = get_function_call_chain(func, 1, "both")
2326
+ if "error" not in func_info:
2327
+ result["affected_functions"].append(func_info)
2328
+
2329
+ # 查询结构体定义
2330
+ for struct in struct_matches:
2331
+ struct_info = query_idl_struct(struct, 1, True)
2332
+ if struct_info:
2333
+ result["related_structs"].append(struct_info[0])
2334
+
2335
+ # 3. 分析业务逻辑影响
2336
+ # 分析条件分支的影响
2337
+ if "if " in code_snippet:
2338
+ condition_parts = code_snippet.split("if ")[1].split("{")[0].strip()
2339
+ result["business_logic_impact"].append({
2340
+ "condition": condition_parts,
2341
+ "impact_type": "conditional_check",
2342
+ "description": f"业务逻辑在满足条件 '{condition_parts}' 时执行特定操作"
2343
+ })
2344
+
2345
+ # 分析错误返回的影响
2346
+ if "return " in code_snippet and "err" in code_snippet:
2347
+ for error in error_matches:
2348
+ result["business_logic_impact"].append({
2349
+ "error_type": error,
2350
+ "impact_type": "error_handling",
2351
+ "description": f"当条件满足时返回错误 '{error}', 影响API调用结果"
2352
+ })
2353
+
2354
+ # 分析模型限制的影响
2355
+ for model in model_matches:
2356
+ result["business_logic_impact"].append({
2357
+ "model": model,
2358
+ "impact_type": "model_limitation",
2359
+ "description": f"代码对模型 '{model}' 施加了特定限制或行为"
2360
+ })
2361
+
2362
+ return result
2363
+
2364
+
2365
+ # 辅助函数:根据请求结构体查询API
2366
+ def query_idl_api_mapping_by_request(request_type, limit=5):
2367
+ """
2368
+ 根据请求结构体名称查询API映射
2369
+
2370
+ 参数:
2371
+ request_type (str): 请求结构体名称
2372
+ limit (int): 结果数量限制
2373
+
2374
+ 返回:
2375
+ list: API映射列表
2376
+ """
2377
+ if not request_type:
2378
+ return []
2379
+
2380
+ limit = validate_limit(limit)
2381
+
2382
+ graphql_query = f"""
2383
+ {{
2384
+ Get {{
2385
+ IdlApiMapping(
2386
+ where: {{
2387
+ path: ["request_type"],
2388
+ operator: Equal,
2389
+ valueString: "{request_type}"
2390
+ }}
2391
+ limit: {limit}
2392
+ ) {{
2393
+ method_name
2394
+ http_path
2395
+ http_method
2396
+ idl_file
2397
+ service_name
2398
+ request_type
2399
+ response_type
2400
+ }}
2401
+ }}
2402
+ }}
2403
+ """
2404
+
2405
+ response = execute_graphql_query(graphql_query)
2406
+ if not response or "errors" in response:
2407
+ return []
2408
+
2409
+ return response.get("data", {}).get("Get", {}).get("IdlApiMapping", [])
2410
+
2411
+
2412
+ # 辅助函数:根据响应结构体查询API
2413
+ def query_idl_api_mapping_by_response(response_type, limit=5):
2414
+ """
2415
+ 根据响应结构体名称查询API映射
2416
+
2417
+ 参数:
2418
+ response_type (str): 响应结构体名称
2419
+ limit (int): 结果数量限制
2420
+
2421
+ 返回:
2422
+ list: API映射列表
2423
+ """
2424
+ if not response_type:
2425
+ return []
2426
+
2427
+ limit = validate_limit(limit)
2428
+
2429
+ graphql_query = f"""
2430
+ {{
2431
+ Get {{
2432
+ IdlApiMapping(
2433
+ where: {{
2434
+ path: ["response_type"],
2435
+ operator: Equal,
2436
+ valueString: "{response_type}"
2437
+ }}
2438
+ limit: {limit}
2439
+ ) {{
2440
+ method_name
2441
+ http_path
2442
+ http_method
2443
+ idl_file
2444
+ service_name
2445
+ request_type
2446
+ response_type
2447
+ }}
2448
+ }}
2449
+ }}
2450
+ """
2451
+
2452
+ response = execute_graphql_query(graphql_query)
2453
+ if not response or "errors" in response:
2454
+ return []
2455
+
2456
+ return response.get("data", {}).get("Get", {}).get("IdlApiMapping", [])
2457
+
2458
+
2459
+ def print_advanced_code_impact(analysis_result):
2460
+ """
2461
+ 打印高级代码影响分析结果
2462
+
2463
+ 参数:
2464
+ analysis_result (dict): 分析结果
2465
+ """
2466
+ if "error" in analysis_result:
2467
+ print(f"错误: {analysis_result['error']}")
2468
+ return
2469
+
2470
+ print("\n============ 代码影响分析 ============")
2471
+ print(f"代码片段:\n{analysis_result['code_snippet']}\n")
2472
+
2473
+ # 打印影响的API
2474
+ print("影响的API接口:")
2475
+ if analysis_result["affected_apis"]:
2476
+ for i, api in enumerate(analysis_result["affected_apis"]):
2477
+ print(f" {i + 1}. API路径: {api.get('api_path', 'unknown')}")
2478
+ for definition in api.get("definitions", []):
2479
+ print(f" - 函数: {definition.get('function', '')}")
2480
+ print(f" - HTTP方法: {definition.get('http_method', '')}")
2481
+ print(f" - 请求类型: {definition.get('request_type', '')}")
2482
+ print(f" - 响应类型: {definition.get('response_type', '')}")
2483
+ else:
2484
+ print(" 未找到直接影响的API")
2485
+
2486
+ # 打印影响的函数
2487
+ print("\n影响的函数:")
2488
+ if analysis_result["affected_functions"]:
2489
+ for i, func in enumerate(analysis_result["affected_functions"]):
2490
+ print(f" {i + 1}. 函数: {func.get('function', '')}")
2491
+ if func.get("callers"):
2492
+ print(f" - 被调用者: {len(func['callers'])} 个函数")
2493
+ if func.get("callees"):
2494
+ print(f" - 调用: {len(func['callees'])} 个函数")
2495
+ else:
2496
+ print(" 未找到直接影响的函数")
2497
+
2498
+ # 打印相关的结构体
2499
+ print("\n相关的数据结构:")
2500
+ if analysis_result["related_structs"]:
2501
+ for i, struct in enumerate(analysis_result["related_structs"]):
2502
+ print(f" {i + 1}. 结构体: {struct.get('struct_name', '')}")
2503
+ print(f" - 文件: {struct.get('idl_file', '')}")
2504
+ else:
2505
+ print(" 未找到相关的数据结构")
2506
+
2507
+ # 打印业务逻辑影响
2508
+ print("\n业务逻辑影响:")
2509
+ if analysis_result["business_logic_impact"]:
2510
+ for i, impact in enumerate(analysis_result["business_logic_impact"]):
2511
+ print(f" {i + 1}. 类型: {impact.get('impact_type', '')}")
2512
+ print(f" - 描述: {impact.get('description', '')}")
2513
+
2514
+ if "condition" in impact:
2515
+ print(f" - 条件: {impact['condition']}")
2516
+ if "error_type" in impact:
2517
+ print(f" - 错误类型: {impact['error_type']}")
2518
+ if "model" in impact:
2519
+ print(f" - 模型: {impact['model']}")
2520
+ else:
2521
+ print(" 未发现明显的业务逻辑影响")
2522
+
2523
+ print("\n=====================================")
2524
+
2525
+
2526
+ def get_function_detail(function_name, exact=True):
2527
+ """
2528
+ 获取函数的详细信息,包括完整代码实现、注释、导入依赖等
2529
+
2530
+ 参数:
2531
+ function_name (str): 函数名
2532
+ exact (bool): 是否精确匹配
2533
+
2534
+ 返回:
2535
+ dict: 函数详细信息
2536
+ """
2537
+ if not function_name:
2538
+ return {"error": "函数名不能为空"}
2539
+
2540
+ # 首先精确匹配查询函数定义
2541
+ func_defs = query_by_field("function", function_name, 1, exact)
2542
+
2543
+ if not func_defs:
2544
+ return {"error": f"未找到函数: {function_name}"}
2545
+
2546
+ func_def = func_defs[0]
2547
+
2548
+ # 获取函数所在包的其他信息
2549
+ package_name = func_def.get("package", "")
2550
+ file_path = func_def.get("file_path", "")
2551
+
2552
+ # 获取函数调用关系
2553
+ call_chain = get_function_call_chain(function_name, 10, "both")
2554
+
2555
+ # 查询函数是否被API调用
2556
+ api_usages = query_function_in_api(function_name)
2557
+
2558
+ # 构建详细信息
2559
+ result = {
2560
+ "function_name": function_name,
2561
+ "package": package_name,
2562
+ "file_path": file_path,
2563
+ "code": func_def.get("code", ""),
2564
+ "comments": func_def.get("comments", ""),
2565
+ "imports": func_def.get("imports", []),
2566
+ "start_line": func_def.get("start_line", 0),
2567
+ "end_line": func_def.get("end_line", 0),
2568
+ "callers": call_chain.get("callers", []),
2569
+ "callees": call_chain.get("callees", []),
2570
+ "api_usages": api_usages
2571
+ }
2572
+
2573
+ return result
2574
+
2575
+
2576
+ def query_function_in_api(function_name):
2577
+ """
2578
+ 查询函数是否被API调用或实现
2579
+
2580
+ 参数:
2581
+ function_name (str): 函数名
2582
+
2583
+ 返回:
2584
+ list: API使用情况列表
2585
+ """
2586
+ if not function_name:
2587
+ return []
2588
+
2589
+ # 查询IdlApiMapping中方法名与函数名匹配的记录
2590
+ graphql_query = f"""
2591
+ {{
2592
+ Get {{
2593
+ IdlApiMapping(
2594
+ where: {{
2595
+ path: ["method_name"],
2596
+ operator: Equal,
2597
+ valueString: "{function_name}"
2598
+ }}
2599
+ limit: 5
2600
+ ) {{
2601
+ method_name
2602
+ http_path
2603
+ http_method
2604
+ service_name
2605
+ idl_file
2606
+ }}
2607
+ }}
2608
+ }}
2609
+ """
2610
+
2611
+ response = execute_graphql_query(graphql_query)
2612
+ if not response or "error" in response:
2613
+ return []
2614
+
2615
+ api_mappings = response.get("data", {}).get("Get", {}).get("IdlApiMapping", [])
2616
+
2617
+ # 格式化结果
2618
+ api_usages = []
2619
+ for mapping in api_mappings:
2620
+ api_usages.append({
2621
+ "api_path": mapping.get("http_path", ""),
2622
+ "http_method": mapping.get("http_method", ""),
2623
+ "service": mapping.get("service_name", ""),
2624
+ "idl_file": mapping.get("idl_file", "")
2625
+ })
2626
+
2627
+ return api_usages
2628
+
2629
+
2630
+ def print_function_detail(func_detail):
2631
+ """
2632
+ 打印函数详细信息
2633
+
2634
+ 参数:
2635
+ func_detail (dict): 函数详细信息
2636
+ """
2637
+ if "error" in func_detail:
2638
+ print(f"错误: {func_detail['error']}")
2639
+ return
2640
+
2641
+ print("\n" + "=" * 80)
2642
+ print(f"函数: {func_detail['function_name']}")
2643
+ print("=" * 80)
2644
+
2645
+ print(f"\n包名: {func_detail['package']}")
2646
+ print(f"文件路径: {func_detail['file_path']}")
2647
+ print(f"行范围: {func_detail['start_line']}-{func_detail['end_line']}")
2648
+
2649
+ # 打印导入信息
2650
+ if func_detail.get("imports"):
2651
+ print("\n导入依赖:")
2652
+ for imp in func_detail["imports"]:
2653
+ print(f" {imp}")
2654
+
2655
+ # 打印注释
2656
+ if func_detail.get("comments"):
2657
+ print("\n函数注释:")
2658
+ for line in func_detail["comments"].split("\n"):
2659
+ print(f" {line}")
2660
+
2661
+ # 打印代码实现
2662
+ print("\n代码实现:")
2663
+ print("-" * 80)
2664
+ for line in func_detail["code"].split("\n"):
2665
+ print(line)
2666
+ print("-" * 80)
2667
+
2668
+ # 打印调用关系摘要
2669
+ print("\n调用关系摘要:")
2670
+ print(f" 被调用次数: {len(func_detail['callers'])}")
2671
+ print(f" 调用其他函数次数: {len(func_detail['callees'])}")
2672
+
2673
+ # 打印前3个调用者
2674
+ if func_detail.get("callers"):
2675
+ print("\n主要调用者:")
2676
+ for i, caller in enumerate(func_detail["callers"][:3]):
2677
+ print(f" {i + 1}. {caller['function']} (包: {caller['package']})")
2678
+ if len(func_detail["callers"]) > 3:
2679
+ print(f" ... 共{len(func_detail['callers'])}个调用者")
2680
+
2681
+ # 打印前3个被调用者
2682
+ if func_detail.get("callees"):
2683
+ print("\n主要被调用者:")
2684
+ for i, callee in enumerate(func_detail["callees"][:3]):
2685
+ print(f" {i + 1}. {callee['function']} (包: {callee['package']})")
2686
+ if len(func_detail["callees"]) > 3:
2687
+ print(f" ... 共{len(func_detail['callees'])}个被调用者")
2688
+
2689
+ # 打印API使用情况
2690
+ if func_detail.get("api_usages"):
2691
+ print("\nAPI接口实现:")
2692
+ for i, api in enumerate(func_detail["api_usages"]):
2693
+ print(f" {i + 1}. {api['http_method']} {api['api_path']} (服务: {api['service']})")
2694
+
2695
+
2696
+ def main(input_content=None, input_type=None, limit=DEFAULT_LIMIT, exact=False, output=None,
2697
+ advanced=False, depth=3, direction="both"):
2698
+ """
2699
+ 主函数,处理输入并执行对应操作
2700
+
2701
+ 参数:
2702
+ input_content (str): 查询内容
2703
+ input_type (str): 指定输入类型 (API, FUNC, CODE, ANY, FUNC_DETAIL)
2704
+ limit (int): 结果数量限制
2705
+ exact (bool): 精确匹配模式
2706
+ output (str): 输出文件路径,用于保存函数调用图谱
2707
+ advanced (bool): 使用高级代码影响分析
2708
+ depth (int): 调用链深度
2709
+ direction (str): 调用链方向,可选值: caller, callee, both
2710
+
2711
+ 返回:
2712
+ dict: 查询结果
2713
+ """
2714
+ # 如果没有输入内容,通过命令行参数处理
2715
+ if input_content is None:
2716
+ import argparse
2717
+
2718
+ parser = argparse.ArgumentParser(description="代码搜索与分析工具")
2719
+ parser.add_argument("input", nargs="?", help="查询内容")
2720
+ parser.add_argument("--type", choices=[TYPE_API, TYPE_FUNC, TYPE_CODE, TYPE_ANY, TYPE_FUNC_DETAIL],
2721
+ help="指定输入类型 (API, FUNC, CODE, ANY, FUNC_DETAIL)")
2722
+ parser.add_argument("--limit", type=int, default=DEFAULT_LIMIT, help="结果数量限制")
2723
+ parser.add_argument("--exact", action="store_true", help="精确匹配模式")
2724
+ parser.add_argument("--output", help="输出文件路径,用于保存函数调用图谱")
2725
+ parser.add_argument("--read-file", help="从文件读取输入内容")
2726
+ parser.add_argument("--depth", type=int, default=3, help="调用链深度,默认为3,最大为5")
2727
+ parser.add_argument("--direction", choices=["caller", "callee", "both"], default="both",
2728
+ help="调用链方向,可选值: caller(调用者), callee(被调用者), both(双向)")
2729
+ parser.add_argument("--advanced", action="store_true", help="使用高级代码影响分析")
2730
+
2731
+ args = parser.parse_args()
2732
+
2733
+ # 处理输入内容
2734
+ input_content = args.input
2735
+
2736
+ # 如果指定了从文件读取
2737
+ if args.read_file:
2738
+ input_content = safe_read_file(args.read_file)
2739
+ if not input_content:
2740
+ sys.exit(1)
2741
+
2742
+ # 检查参数
2743
+ if not input_content:
2744
+ parser.print_help()
2745
+ sys.exit(1)
2746
+
2747
+ # 其他参数
2748
+ input_type = args.type
2749
+ limit = validate_limit(args.limit)
2750
+ exact = args.exact
2751
+ output = args.output
2752
+ advanced = args.advanced
2753
+ depth = args.depth
2754
+ direction = args.direction
2755
+
2756
+ # 验证limit参数
2757
+ limit = validate_limit(limit)
2758
+
2759
+ # 识别输入类型
2760
+ if not input_type:
2761
+ input_type = detect_input_type(input_content)
2762
+
2763
+ # 结果集
2764
+ result = {}
2765
+
2766
+ # 如果指定了查询函数详情,或输入类型为FUNC_DETAIL
2767
+ if input_type == TYPE_FUNC_DETAIL:
2768
+ if input_type != TYPE_FUNC and input_type != TYPE_FUNC_DETAIL:
2769
+ if DEBUG:
2770
+ print(f"警告: 将输入内容 '{input_content}' 视为函数名")
2771
+ input_type = TYPE_FUNC
2772
+
2773
+ func_detail = get_function_detail(input_content, exact)
2774
+ if DEBUG:
2775
+ print_function_detail(func_detail)
2776
+ return func_detail
2777
+
2778
+ # 基于输入类型执行操作
2779
+ if input_type == TYPE_API:
2780
+ api_info = search_api_info(input_content, limit, exact)
2781
+ if DEBUG:
2782
+ print_api_info(api_info)
2783
+ return api_info
2784
+
2785
+ elif input_type == TYPE_FUNC:
2786
+ # 检查是否需要生成拓扑图
2787
+ if depth > 1:
2788
+ topology = generate_call_chain_topology(
2789
+ input_content,
2790
+ max_depth=depth,
2791
+ direction=direction,
2792
+ output_file=output
2793
+ )
2794
+ if DEBUG:
2795
+ print_call_chain_topology(topology)
2796
+ return topology
2797
+ else:
2798
+ # 只需要获取直接调用关系
2799
+ call_chain = get_function_call_chain(input_content, limit, direction)
2800
+ if DEBUG:
2801
+ print_function_call_chain(call_chain)
2802
+ if output:
2803
+ generate_function_call_graph(call_chain, output)
2804
+ return call_chain
2805
+
2806
+ elif input_type == TYPE_CODE:
2807
+ if advanced:
2808
+ advanced_impact = analyze_code_impact_advanced(input_content, limit)
2809
+ if DEBUG:
2810
+ print_advanced_code_impact(advanced_impact)
2811
+ return advanced_impact
2812
+ else:
2813
+ code_impact = analyze_code_impact(input_content, limit)
2814
+ if DEBUG:
2815
+ print_code_impact(code_impact)
2816
+ return code_impact
2817
+
2818
+ else: # TYPE_ANY
2819
+ if DEBUG:
2820
+ print(f"执行模糊搜索: '{input_content}' {'(精确匹配)' if exact else '(模糊匹配)'}")
2821
+ results = query_all_types(input_content, limit, exact)
2822
+
2823
+ if not results and DEBUG:
2824
+ print(f"未找到与 '{input_content}' 相关的结果")
2825
+ elif DEBUG:
2826
+ print_summary_table(results)
2827
+
2828
+ return results
2829
+
2830
+
2831
+ if __name__ == "__main__":
2832
+ # 单元测试1:查询API
2833
+ # print("\n" + "=" * 80)
2834
+ # print("测试1: 查询API路径 '/v1/text/chatcompletion'")
2835
+ # print("=" * 80)
2836
+ # main(input_content="/v1/text/chatcompletion", input_type=TYPE_API, limit=10, exact=False)
2837
+ #
2838
+ # # 单元测试2:查询函数
2839
+ # print("\n" + "=" * 80)
2840
+ # print("测试2: 查询函数 'chatCompletion'")
2841
+ # print("=" * 80)
2842
+ # main(input_content="chatCompletion", input_type=TYPE_FUNC, limit=10, exact=False)
2843
+
2844
+ # 单元测试3:查询函数详情
2845
+ print("\n" + "=" * 80)
2846
+ print("测试3: 查询函数详情 'chatCompletion'")
2847
+ print("=" * 80)
2848
+ main(input_content="chatCompletion", input_type=TYPE_FUNC_DETAIL, limit=10, exact=False)