浏览代码

重构结构

XingHeYuZhuan 1 月之前
父节点
当前提交
a8bed82222

+ 34 - 0
index/root_index.yaml

@@ -0,0 +1,34 @@
+# index/root_index.yaml
+# 整个适配器仓库的根索引文件。
+# CI/CD 构建脚本会读取此文件,以确定需要处理哪些学校的适配器资源。
+
+schools:
+  - id: "GLOBAL_TOOLS"    # id 唯一值
+    name: "通用工具与服务" # 名称
+    initial: "G"          # 排序首字母
+    resource_folder: "GLOBAL_TOOLS"   # 资源文件夹名称
+
+  - id: "CUST"
+    name: "长春理工大学"
+    initial: "C"
+    resource_folder: "CUST" 
+
+  - id: "SHZQ"
+    name: "上海中侨职业技术大学"
+    initial: "S"
+    resource_folder: "SHZQ"  
+    
+  - id: "HNZY"
+    name: "河南职业技术学院"
+    initial: "H"
+    resource_folder: "HNZY"
+    
+  - id: "JYVTC"
+    name: "济源职业技术学院"
+    initial: "J"
+    resource_folder: "JYVTC"
+
+  - id: "YNUFE"
+    name: "云南财经大学"
+    initial: "Y"
+    resource_folder: "YNUFE"

+ 75 - 0
proto/school_index.proto

@@ -0,0 +1,75 @@
+syntax = "proto3";
+
+option java_multiple_files = true;
+
+option java_package = "school_index";
+
+// 定义包名,影响生成的 Python/Kotlin/Swift 模块名
+package school_index;
+
+// --- 1. 适配器类别枚举 ---
+// 使用枚举保证类型安全和编译期检查
+enum AdapterCategory {
+    // 必须从 0 开始
+    ADAPTER_CATEGORY_UNKNOWN = 0; 
+    
+    // 明确定义所有类别,编号不可更改!
+    GENERAL_TOOL = 1;               // 通用工具
+    BACHELOR_AND_ASSOCIATE = 2;     // 本科/专科教务系统
+    POSTGRADUATE = 3;               // 研究生教务系统
+
+}
+
+
+// --- 2. 顶层消息:整个索引文件 ---
+message SchoolIndex {
+  // 字段编号 1:显式协议版本号,用于客户端发现不兼容的大改动
+  int64 protocol_version = 1; 
+
+  // 字段编号 2:数据文件的版本ID (CI/CD自动生成的Commit Hash或时间戳)
+  string version_id = 2; 
+
+  // 字段编号 3:所有学校和通用工具的列表
+  repeated School schools = 3;
+}
+
+
+// --- 3. 消息:单个学校或通用工具集 ---
+message School {
+    // 字段编号 1:主键 (例如 CUST, YNUFE, GLOBAL_TOOLS)
+    string id = 1;
+    // 字段编号 2:用于显示的名称
+    string name = 2;
+    // 字段编号 3:拼音首字母,用于搜索和排序
+    string initial = 3; 
+    // 字段编号 4:资源文件夹名称 (与 id 相同,但保持独立字段以增加灵活性)
+    string resource_folder = 4; 
+    
+    // 字段编号 5:该学校下的所有适配器列表
+    repeated Adapter adapters = 5;
+}
+
+
+// --- 4. 消息:单个适配器的结构 ---
+message Adapter {
+    // 字段编号 1:适配器唯一 ID (例如 CUST_01, SHZQ_01)
+    string adapter_id = 1; 
+
+    // 字段编号 2:适配器名称
+    string adapter_name = 2;
+
+    // 字段编号 3:适配器类别,使用强类型枚举
+    AdapterCategory category = 3; 
+
+    // 字段编号 4:JS 文件的相对路径 (例如 cust.js)
+    string asset_js_path = 4;
+
+    // 字段编号 5:可选的外部导入 URL
+    optional string import_url = 5; 
+    
+    // 字段编号 6:适配器的详细描述(新增)
+    string description = 6; 
+    
+    // 字段编号 7:维护者信息(新增)
+    string maintainer = 7;
+}

+ 9 - 0
resources/CUST/adapters.yaml

@@ -0,0 +1,9 @@
+# resources/CUST/adapters.yaml
+adapters:
+  - adapter_id: "CUST_01"
+    adapter_name: "长春理工大学教务系统"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "cust.js"
+    import_url: "https://mysso.cust.edu.cn/cas/login?service=https://jwgl.cust.edu.cn/welcome"
+    maintainer: "itdong"
+    description: ""

+ 0 - 0
schools/cust.js → resources/CUST/cust.js


+ 9 - 0
resources/GLOBAL_TOOLS/adapters.yaml

@@ -0,0 +1,9 @@
+# resources/GLOBAL_TOOLS/adapters.yaml
+adapters:
+  - adapter_id: "GENERAL_TOOL_01"
+    adapter_name: "测试大学"
+    category: "GENERAL_TOOL"
+    asset_js_path: "school.js"
+    import_url: ""
+    maintainer: "星河欲转"
+    description: "这是一个空网站,用于组件测试"

+ 0 - 0
schools/school.js → resources/GLOBAL_TOOLS/school.js


+ 9 - 0
resources/HNZY/adapters.yaml

@@ -0,0 +1,9 @@
+# resources/HNZY/adapters.yaml
+adapters:
+  - adapter_id: "HNZY_01"
+    adapter_name: "河南职业技术学院课表"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "hnzy.js"
+    import_url: "https://one.hnzj.edu.cn/common-service/kcb-pc/personage/personage"
+    maintainer: "一颗"
+    description: ""

+ 0 - 0
schools/hnzy.js → resources/HNZY/hnzy.js


+ 9 - 0
resources/JYVTC/adapters.yaml

@@ -0,0 +1,9 @@
+# resources/JYVTC/adapters.yaml
+adapters:
+  - adapter_id: "JYVTC_01" #
+    adapter_name: "济源职业技术学院教务系统"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "jyvtc.js"
+    import_url: "https://jwgl.jyvtc.edu.cn/jyvtcjw/"
+    maintainer: "星河欲转"
+    description: ""

+ 0 - 0
schools/jyvtc.js → resources/JYVTC/jyvtc.js


+ 9 - 0
resources/SHZQ/adapters.yaml

@@ -0,0 +1,9 @@
+# resources/SHZQ/adapters.yaml
+adapters:
+  - adapter_id: "SHZQ_01"
+    adapter_name: "上海中侨职业技术大学教务系统"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "shzq.js"
+    import_url: "https://jw.shzq.edu.cn/jwglxt/"
+    maintainer: "Kredenk"
+    description: ""

+ 0 - 0
schools/shzq.js → resources/SHZQ/shzq.js


+ 9 - 0
resources/YNUFE/adapters.yaml

@@ -0,0 +1,9 @@
+# resources/YNUFE/adapters.yaml
+adapters:
+  - adapter_id: "YNUFE_01" # 采用数字编号规范
+    adapter_name: "云南财经大学教务系统"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "ynufe.js"
+    import_url: "https://xjwis.ynufe.edu.cn/jsxsd/"
+    maintainer: "AxelPLN"
+    description: ""

+ 0 - 0
schools/ynufe.js → resources/YNUFE/ynufe.js


+ 0 - 56
schools.json

@@ -1,56 +0,0 @@
-[
-  {
-    "id": "school_Cs",
-    "name": "测试大学(这是一个空网站,用于组件测试)",
-    "initial": "C",
-    "importUrl": "",
-    "assetJsPath" : "schools/school.js",
-    "maintainer": "星河欲转",
-    "category": "GENERAL_TOOL"
-  },
-  {
-    "id": "school_cust",
-    "name": "长春理工大学",
-    "initial": "C",
-    "importUrl": "https://mysso.cust.edu.cn/cas/login?service=https://jwgl.cust.edu.cn/welcome",
-    "assetJsPath": "schools/cust.js",
-    "maintainer": "itdong",
-    "category": "BACHELOR_AND_ASSOCIATE"
-  },
-  {
-    "id": "school_shzq",
-    "name": "上海中侨职业技术大学",
-    "initial": "S",
-    "importUrl": "https://jw.shzq.edu.cn/jwglxt/",
-    "assetJsPath" : "schools/shzq.js",
-    "maintainer": "Kredenk",
-    "category": "BACHELOR_AND_ASSOCIATE"
-  },
-  {
-    "id": "school_ynufe",
-    "name": "云南财经大学",
-    "initial": "Y",
-    "importUrl": "https://xjwis.ynufe.edu.cn/jsxsd/",
-    "assetJsPath" : "schools/ynufe.js",
-    "maintainer": "AxelPLN",
-    "category": "BACHELOR_AND_ASSOCIATE"
-  },
-    {
-    "id": "school_hnzy",
-    "name": "河南职业技术学院",
-    "initial": "H",
-    "importUrl": "https://one.hnzj.edu.cn/common-service/kcb-pc/personage/personage",
-    "assetJsPath" : "schools/hnzy.js",
-    "maintainer": "一颗",
-    "category": "BACHELOR_AND_ASSOCIATE"
-  },
-    {
-    "id": "school_jyvtc",
-    "name": "济源职业技术学院",
-    "initial": "J",
-    "importUrl": "https://jwgl.jyvtc.edu.cn/jyvtcjw/",
-    "assetJsPath" : "schools/jyvtc.js",
-    "maintainer": "星河欲转",
-    "category": "BACHELOR_AND_ASSOCIATE"
-  }
-]

+ 153 - 0
scripts/build_data.py

@@ -0,0 +1,153 @@
+import sys
+import yaml
+import os
+import datetime
+from pathlib import Path
+
+# --- 1. Protobuf 依赖导入 ---
+# 注意:这依赖于 protoc 编译生成的 school_index_pb2.py 文件
+try:
+    # 导入生成的 Protobuf 类 (必须先运行 protoc)
+    from school_index_pb2 import SchoolIndex, School, Adapter, AdapterCategory 
+except ImportError:
+    print("错误:无法导入 Protobuf 模块。请确认您已运行 protoc 命令生成了 school_index_pb2.py。")
+    sys.exit(1)
+
+
+# --- 2. 工程常量与版本定义 ---
+# 协议版本:结构大改动时手动递增。
+PROTOCOL_VERSION = 1 
+
+# YAML 文件路径常量
+ROOT_INDEX_PATH = Path("index/root_index.yaml")
+RESOURCES_ROOT = Path("resources")
+OUTPUT_PB_FILE = "school_index.pb"
+
+
+# --- 3. 辅助函数:获取版本ID (用于 version_id 字段) ---
+def get_version_id():
+    """
+    【统一标准】:只使用精确到毫秒的时间戳作为数据版本ID,
+    确保每次构建的唯一性,并消除对 Git 环境的依赖。
+    标准格式:TIME_<年><月><日><时><分><秒>_<毫秒>
+    """
+    # 获取当前时间(包含微秒)
+    now = datetime.datetime.now()
+    
+    # 格式化时间部分
+    # 格式化为:YYYYMMDDhhmmss
+    time_str = now.strftime("TIME_%Y%m%d%H%M%S")
+    micro_str = f"{now.microsecond // 1000:03d}" 
+    
+    return f"{time_str}_{micro_str}"
+
+
+# --- 4. 辅助函数:YAML 解析逻辑 ---
+def get_adapter_category_enum(category_str):
+    """将 YAML 字符串类别映射到 Protobuf 枚举值。"""
+    # 使用 getattr 从 Protobuf 模块中查找对应的枚举值。
+    # 如果找不到,安全地返回 ADAPTER_CATEGORY_UNKNOWN (0)。
+    enum_value = getattr(AdapterCategory, category_str, AdapterCategory.ADAPTER_CATEGORY_UNKNOWN)
+    return enum_value
+
+def parse_all_yaml():
+    """解析所有 YAML 文件,返回结构化数据。"""
+    if not ROOT_INDEX_PATH.exists():
+        raise FileNotFoundError(f"根索引文件未找到: {ROOT_INDEX_PATH}")
+    
+    with open(ROOT_INDEX_PATH, 'r', encoding='utf-8') as f:
+        root_data = yaml.safe_load(f)
+
+    schools_data = root_data.get('schools', [])
+    parsed_schools = []
+
+    for school_entry in schools_data:
+        # 1. 拼接 adapters.yaml 的路径
+        folder_name = school_entry['resource_folder']
+        adapter_yaml_path = RESOURCES_ROOT / folder_name / "adapters.yaml"
+
+        if not adapter_yaml_path.exists():
+            print(f"警告:未找到适配器配置 {adapter_yaml_path},跳过学校 {school_entry['id']}")
+            continue
+            
+        # 2. 读取适配器详情
+        with open(adapter_yaml_path, 'r', encoding='utf-8') as f:
+            adapter_data = yaml.safe_load(f)
+            
+        # 将适配器列表添加到学校数据中
+        school_entry['adapters'] = adapter_data.get('adapters', [])
+        parsed_schools.append(school_entry)
+
+    return parsed_schools
+
+
+# --- 5. 核心构建函数:数据映射与填充 ---
+def build_protobuf_index(parsed_schools_data):
+    """将解析后的数据填充到 Protobuf 消息中。"""
+    index = SchoolIndex()
+    # 设置顶层版本信息
+    index.protocol_version = PROTOCOL_VERSION
+    index.version_id = get_version_id() 
+
+    for school_data in parsed_schools_data:
+        # 1. 填充 School 消息
+        school_msg = index.schools.add()
+        school_msg.id = school_data.get('id', '')
+        school_msg.name = school_data.get('name', '')
+        school_msg.initial = school_data.get('initial', '')
+        school_msg.resource_folder = school_data.get('resource_folder', '')
+
+        # 2. 填充 Adapter 列表
+        for adapter_data in school_data.get('adapters', []):
+            adapter_msg = school_msg.adapters.add()
+            
+            # 填充普通 string 字段
+            adapter_msg.adapter_id = adapter_data.get('adapter_id', '')
+            adapter_msg.adapter_name = adapter_data.get('adapter_name', '')
+            adapter_msg.asset_js_path = adapter_data.get('asset_js_path', '')
+            adapter_msg.description = adapter_data.get('description', '')
+            adapter_msg.maintainer = adapter_data.get('maintainer', '')
+
+            # 填充 optional 字段:只有当字段不为 None 时才设置
+            import_url = adapter_data.get('import_url')
+            if import_url is not None:
+                # 即使 import_url 为空字符串 "",也会被显式设置,满足 optional 的存在性要求
+                adapter_msg.import_url = import_url
+            
+            # 填充枚举字段
+            category_str = adapter_data.get('category', 'ADAPTER_CATEGORY_UNKNOWN')
+            adapter_msg.category = get_adapter_category_enum(category_str)
+
+    return index
+
+
+# --- 6. 主执行流程 ---
+if __name__ == "__main__":
+    
+    OUTPUT_FILE = OUTPUT_PB_FILE
+    print(f"目标输出文件: {OUTPUT_FILE}")
+
+    try:
+        print("--- 阶段一:解析 YAML 源文件 ---")
+        parsed_data = parse_all_yaml()
+        
+        print("\n--- 阶段二:构建 Protobuf 消息 ---")
+        index_message = build_protobuf_index(parsed_data)
+        
+        print(f"Protobuf 协议版本: {index_message.protocol_version}")
+        print(f"数据版本ID: {index_message.version_id}")
+
+        print("\n--- 阶段三:序列化并写入磁盘 ---")
+        # 序列化并写入二进制文件
+        with open(OUTPUT_FILE, "wb") as f:
+            # Protobuf 核心序列化方法
+            f.write(index_message.SerializeToString()) 
+        
+        print(f"\n构建成功!二进制文件已保存到: {OUTPUT_FILE}")
+        # 可选:打印文件大小,方便调试
+        print(f"文件大小: {os.path.getsize(OUTPUT_FILE) / 1024:.2f} KB")
+
+    except Exception as e:
+        print(f"构建失败!致命错误: {e}", file=sys.stderr)
+        # 如果是文件未找到等错误,请给出更明确的提示
+        sys.exit(1)

+ 42 - 0
scripts/school_index_pb2.py

@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# NO CHECKED-IN PROTOBUF GENCODE
+# source: school_index.proto
+# Protobuf Python Version: 6.32.1
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import runtime_version as _runtime_version
+from google.protobuf import symbol_database as _symbol_database
+from google.protobuf.internal import builder as _builder
+_runtime_version.ValidateProtobufRuntimeVersion(
+    _runtime_version.Domain.PUBLIC,
+    6,
+    32,
+    1,
+    '',
+    'school_index.proto'
+)
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12school_index.proto\x12\x0cschool_index\"b\n\x0bSchoolIndex\x12\x18\n\x10protocol_version\x18\x01 \x01(\x03\x12\x12\n\nversion_id\x18\x02 \x01(\t\x12%\n\x07schools\x18\x03 \x03(\x0b\x32\x14.school_index.School\"u\n\x06School\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0f\n\x07initial\x18\x03 \x01(\t\x12\x17\n\x0fresource_folder\x18\x04 \x01(\t\x12\'\n\x08\x61\x64\x61pters\x18\x05 \x03(\x0b\x32\x15.school_index.Adapter\"\xcc\x01\n\x07\x41\x64\x61pter\x12\x12\n\nadapter_id\x18\x01 \x01(\t\x12\x14\n\x0c\x61\x64\x61pter_name\x18\x02 \x01(\t\x12/\n\x08\x63\x61tegory\x18\x03 \x01(\x0e\x32\x1d.school_index.AdapterCategory\x12\x15\n\rasset_js_path\x18\x04 \x01(\t\x12\x17\n\nimport_url\x18\x05 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x0b\x64\x65scription\x18\x06 \x01(\t\x12\x12\n\nmaintainer\x18\x07 \x01(\tB\r\n\x0b_import_url*o\n\x0f\x41\x64\x61pterCategory\x12\x1c\n\x18\x41\x44\x41PTER_CATEGORY_UNKNOWN\x10\x00\x12\x10\n\x0cGENERAL_TOOL\x10\x01\x12\x1a\n\x16\x42\x41\x43HELOR_AND_ASSOCIATE\x10\x02\x12\x10\n\x0cPOSTGRADUATE\x10\x03\x62\x06proto3')
+
+_globals = globals()
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'school_index_pb2', _globals)
+if not _descriptor._USE_C_DESCRIPTORS:
+  DESCRIPTOR._loaded_options = None
+  _globals['_ADAPTERCATEGORY']._serialized_start=462
+  _globals['_ADAPTERCATEGORY']._serialized_end=573
+  _globals['_SCHOOLINDEX']._serialized_start=36
+  _globals['_SCHOOLINDEX']._serialized_end=134
+  _globals['_SCHOOL']._serialized_start=136
+  _globals['_SCHOOL']._serialized_end=253
+  _globals['_ADAPTER']._serialized_start=256
+  _globals['_ADAPTER']._serialized_end=460
+# @@protoc_insertion_point(module_scope)