build_data.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import sys
  2. import yaml
  3. import os
  4. import datetime
  5. from pathlib import Path
  6. SCRIPT_DIR = Path(__file__).resolve().parent
  7. # 确保该目录在模块搜索路径中
  8. if str(SCRIPT_DIR) not in sys.path:
  9. sys.path.insert(0, str(SCRIPT_DIR))
  10. # --- 1. Protobuf 依赖导入 ---
  11. # 注意:这依赖于 protoc 编译生成的 school_index_pb2.py 文件
  12. try:
  13. from school_index_pb2 import SchoolIndex, School, Adapter, AdapterCategory
  14. except ImportError:
  15. print("错误:无法导入 Protobuf 模块 (school_index_pb2)。请确认文件已存在。")
  16. sys.exit(1)
  17. # --- 2. 工程常量与版本定义 ---
  18. # 协议版本:结构大改动时手动递增。
  19. PROTOCOL_VERSION = 1
  20. # YAML 文件路径常量
  21. ROOT_INDEX_PATH = Path("index/root_index.yaml")
  22. RESOURCES_ROOT = Path("resources")
  23. OUTPUT_PB_FILE = "school_index.pb"
  24. # --- 3. 辅助函数:获取版本ID (用于 version_id 字段) ---
  25. def get_version_id():
  26. """
  27. 【统一标准】:只使用精确到毫秒的时间戳作为数据版本ID。
  28. """
  29. # 获取当前时间(包含微秒)
  30. now = datetime.datetime.now()
  31. # 格式化时间部分
  32. time_str = now.strftime("TIME_%Y%m%d%H%M%S")
  33. micro_str = f"{now.microsecond // 1000:03d}"
  34. return f"{time_str}_{micro_str}"
  35. # --- 4. 辅助函数:YAML 解析逻辑 ---
  36. def get_adapter_category_enum(category_str):
  37. """将 YAML 字符串类别映射到 Protobuf 枚举值。"""
  38. enum_value = getattr(AdapterCategory, category_str, AdapterCategory.ADAPTER_CATEGORY_UNKNOWN)
  39. return enum_value
  40. def parse_all_yaml():
  41. """解析所有 YAML 文件,返回结构化数据。"""
  42. if not ROOT_INDEX_PATH.exists():
  43. raise FileNotFoundError(f"根索引文件未找到: {ROOT_INDEX_PATH}")
  44. with open(ROOT_INDEX_PATH, 'r', encoding='utf-8') as f:
  45. root_data = yaml.safe_load(f)
  46. schools_data = root_data.get('schools', [])
  47. parsed_schools = []
  48. for school_entry in schools_data:
  49. # 1. 拼接 adapters.yaml 的路径
  50. folder_name = school_entry['resource_folder']
  51. adapter_yaml_path = RESOURCES_ROOT / folder_name / "adapters.yaml"
  52. if not adapter_yaml_path.exists():
  53. print(f"警告:未找到适配器配置 {adapter_yaml_path},跳过学校 {school_entry['id']}")
  54. continue
  55. # 2. 读取适配器详情
  56. with open(adapter_yaml_path, 'r', encoding='utf-8') as f:
  57. adapter_data = yaml.safe_load(f)
  58. # 将适配器列表添加到学校数据中
  59. school_entry['adapters'] = adapter_data.get('adapters', [])
  60. parsed_schools.append(school_entry)
  61. return parsed_schools
  62. # --- 5. 核心构建函数:数据映射与填充 ---
  63. def build_protobuf_index(parsed_schools_data):
  64. """将解析后的数据填充到 Protobuf 消息中。"""
  65. index = SchoolIndex()
  66. # 设置顶层版本信息
  67. index.protocol_version = PROTOCOL_VERSION
  68. index.version_id = get_version_id()
  69. for school_data in parsed_schools_data:
  70. # 1. 填充 School 消息
  71. school_msg = index.schools.add()
  72. school_msg.id = school_data.get('id', '')
  73. school_msg.name = school_data.get('name', '')
  74. school_msg.initial = school_data.get('initial', '')
  75. school_msg.resource_folder = school_data.get('resource_folder', '')
  76. # 2. 填充 Adapter 列表
  77. for adapter_data in school_data.get('adapters', []):
  78. adapter_msg = school_msg.adapters.add()
  79. # 填充普通 string 字段
  80. adapter_msg.adapter_id = adapter_data.get('adapter_id', '')
  81. adapter_msg.adapter_name = adapter_data.get('adapter_name', '')
  82. adapter_msg.asset_js_path = adapter_data.get('asset_js_path', '')
  83. adapter_msg.description = adapter_data.get('description', '')
  84. adapter_msg.maintainer = adapter_data.get('maintainer', '')
  85. # 填充 optional 字段:只有当字段不为 None 时才设置
  86. import_url = adapter_data.get('import_url')
  87. if import_url is not None:
  88. adapter_msg.import_url = import_url
  89. # 填充枚举字段
  90. category_str = adapter_data.get('category', 'ADAPTER_CATEGORY_UNKNOWN')
  91. adapter_msg.category = get_adapter_category_enum(category_str)
  92. return index
  93. # --- 6. 主执行流程 ---
  94. if __name__ == "__main__":
  95. OUTPUT_FILE = OUTPUT_PB_FILE
  96. print(f"目标输出文件: {OUTPUT_FILE}")
  97. try:
  98. print("--- 阶段一:解析 YAML 源文件 ---")
  99. parsed_data = parse_all_yaml()
  100. print("\n--- 阶段二:构建 Protobuf 消息 ---")
  101. index_message = build_protobuf_index(parsed_data)
  102. print(f"Protobuf 协议版本: {index_message.protocol_version}")
  103. print(f"数据版本ID: {index_message.version_id}")
  104. print("\n--- 阶段三:序列化并写入磁盘 ---")
  105. # 序列化并写入二进制文件
  106. with open(OUTPUT_FILE, "wb") as f:
  107. # Protobuf 核心序列化方法
  108. f.write(index_message.SerializeToString())
  109. print(f"\n构建成功!二进制文件已保存到: {OUTPUT_FILE}")
  110. print(f"文件大小: {os.path.getsize(OUTPUT_FILE) / 1024:.2f} KB")
  111. except Exception as e:
  112. print(f"构建失败!致命错误: {e}", file=sys.stderr)
  113. sys.exit(1)