From 1a15cdff8837a0af0b3273a16baa2b252bc41c4b Mon Sep 17 00:00:00 2001 From: "mula.liu" Date: Thu, 18 Dec 2025 19:58:38 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 10244 -> 10244 bytes app.zip | Bin 85076 -> 0 bytes app/api/endpoints/client_downloads.py | 262 ++++++++++++---- app/api/endpoints/dict_data.py | 413 ++++++++++++++++++++++++++ app/api/endpoints/meetings.py | 9 +- app/core/config.py | 12 +- app/main.py | 3 +- app/models/models.py | 13 +- app/utils/apk_parser.py | 36 +++ check_db_structure.py | 56 ++++ requirements-prod.txt | 3 + requirements.txt | 1 + sql/add_task_type_dict.sql | 10 + sql/create_client_downloads.sql | 37 +++ test_apk_parser.py | 24 ++ 15 files changed, 814 insertions(+), 65 deletions(-) delete mode 100644 app.zip create mode 100644 app/api/endpoints/dict_data.py create mode 100644 app/utils/apk_parser.py create mode 100644 check_db_structure.py create mode 100644 sql/add_task_type_dict.sql create mode 100644 sql/create_client_downloads.sql create mode 100644 test_apk_parser.py diff --git a/.DS_Store b/.DS_Store index 55b6e6291bcc9d7a120f512f0ef89dab144540fe..c8ee9a34f4b58e4ceb17a2d65c5820deeb4d7738 100644 GIT binary patch delta 257 zcmZn(XbG6$C7U^hRb%4Qw`dqz_k1_p)&|G@yrVqg$uNM^`jNClGF4Dk#p3`sz; zn4yHBh@liHRstl87*Z!|iKuRl7W~2DAOsXj1uKYWNMtAgYbpS;ihyFNKvEAVR_U3O zpPZDFp9C~s0EitK7#R7WR!;U8JIG;dW~ifJY-qVzLOhXUGrPhsmd)Em<}gy%@W~S5 FaR5->Ms@%I delta 78 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 MAX_CLIENT_SIZE: + return create_api_response( + code="400", + message=f"文件过大,最大允许 {MAX_CLIENT_SIZE / 1024 / 1024} MB" + ) + + # 保存文件 + with open(file_path, "wb") as buffer: + shutil.copyfileobj(file.file, buffer) + + # 构建下载URL + base_url = APP_CONFIG['base_url'].rstrip('/') + download_url = f"{base_url}/uploads/clients/{platform_code}/{file.filename}" + + # 准备返回数据 + result = { + "file_name": file.filename, + "file_size": file_size, + "download_url": download_url, + "platform_code": platform_code + } + + # 如果是APK文件,尝试解析版本信息 + if file_ext == '.apk': + apk_info = parse_apk_with_androguard(str(file_path)) + if apk_info: + result['version_code'] = apk_info.get('version_code') + result['version_name'] = apk_info.get('version_name') + result['note'] = apk_info.get('note', '') + else: + # APK解析失败,给出提示 + result['note'] = 'APK解析失败,请检查后台日志。您可以手动输入版本信息。' + + return create_api_response( + code="200", + message="文件上传成功", + data=result + ) + + except Exception as e: + return create_api_response( + code="500", + message=f"文件上传失败: {str(e)}" + ) diff --git a/app/api/endpoints/dict_data.py b/app/api/endpoints/dict_data.py new file mode 100644 index 0000000..b5b2114 --- /dev/null +++ b/app/api/endpoints/dict_data.py @@ -0,0 +1,413 @@ +from fastapi import APIRouter, HTTPException, Depends +from app.core.database import get_db_connection +from app.core.auth import get_current_user, get_current_admin_user +from app.core.response import create_api_response +from pydantic import BaseModel +from typing import Optional, List +import json + +router = APIRouter() + + +class DictDataItem(BaseModel): + """码表数据项""" + id: int + dict_type: str + dict_code: str + parent_code: str + tree_path: Optional[str] = None + label_cn: str + label_en: Optional[str] = None + sort_order: int + extension_attr: Optional[dict] = None + is_default: int + status: int + create_time: str + + +class CreateDictDataRequest(BaseModel): + """创建码表数据请求""" + dict_type: str = "client_platform" + dict_code: str + parent_code: str = "ROOT" + label_cn: str + label_en: Optional[str] = None + sort_order: int = 0 + extension_attr: Optional[dict] = None + is_default: int = 0 + status: int = 1 + + +class UpdateDictDataRequest(BaseModel): + """更新码表数据请求""" + parent_code: Optional[str] = None + label_cn: Optional[str] = None + label_en: Optional[str] = None + sort_order: Optional[int] = None + extension_attr: Optional[dict] = None + is_default: Optional[int] = None + status: Optional[int] = None + + +@router.get("/dict/types", response_model=dict) +async def get_dict_types(): + """ + 获取所有字典类型(公开接口) + """ + try: + with get_db_connection() as conn: + cursor = conn.cursor(dictionary=True) + + query = """ + SELECT DISTINCT dict_type + FROM dict_data + WHERE status = 1 + ORDER BY dict_type + """ + cursor.execute(query) + types = cursor.fetchall() + cursor.close() + + return create_api_response( + code="200", + message="获取成功", + data={"types": [t['dict_type'] for t in types]} + ) + + except Exception as e: + return create_api_response( + code="500", + message=f"获取字典类型失败: {str(e)}" + ) + + +@router.get("/dict/{dict_type}", response_model=dict) +async def get_dict_data_by_type(dict_type: str): + """ + 获取指定类型的所有码表数据(公开接口) + 支持树形结构 + + 参数: + dict_type: 字典类型,如 'client_platform' + """ + try: + with get_db_connection() as conn: + cursor = conn.cursor(dictionary=True) + + query = """ + SELECT id, dict_type, dict_code, parent_code, tree_path, + label_cn, label_en, sort_order, extension_attr, + is_default, status, create_time + FROM dict_data + WHERE dict_type = %s AND status = 1 + ORDER BY parent_code, sort_order, dict_code + """ + cursor.execute(query, (dict_type,)) + items = cursor.fetchall() + cursor.close() + + # 处理JSON字段 + for item in items: + if item.get('extension_attr'): + try: + item['extension_attr'] = json.loads(item['extension_attr']) + except: + item['extension_attr'] = {} + + # 构建树形结构 + tree_data = [] + nodes_map = {} + + # 第一遍:创建所有节点 + for item in items: + nodes_map[item['dict_code']] = { + **item, + 'children': [] + } + + # 第二遍:构建树形关系 + for item in items: + node = nodes_map[item['dict_code']] + parent_code = item['parent_code'] + + if parent_code == 'ROOT': + tree_data.append(node) + elif parent_code in nodes_map: + nodes_map[parent_code]['children'].append(node) + + return create_api_response( + code="200", + message="获取成功", + data={ + "items": items, # 平铺数据 + "tree": tree_data # 树形数据 + } + ) + + except Exception as e: + return create_api_response( + code="500", + message=f"获取码表数据失败: {str(e)}" + ) + + +@router.get("/dict/{dict_type}/{dict_code}", response_model=dict) +async def get_dict_data_by_code(dict_type: str, dict_code: str): + """ + 获取指定编码的码表数据(公开接口) + + 参数: + dict_type: 字典类型 + dict_code: 字典编码 + """ + try: + with get_db_connection() as conn: + cursor = conn.cursor(dictionary=True) + + query = """ + SELECT id, dict_type, dict_code, parent_code, tree_path, + label_cn, label_en, sort_order, extension_attr, + is_default, status, create_time + FROM dict_data + WHERE dict_type = %s AND dict_code = %s + LIMIT 1 + """ + cursor.execute(query, (dict_type, dict_code)) + item = cursor.fetchone() + cursor.close() + + if not item: + return create_api_response( + code="404", + message=f"未找到编码 {dict_code} 的数据" + ) + + # 处理JSON字段 + if item.get('extension_attr'): + try: + item['extension_attr'] = json.loads(item['extension_attr']) + except: + item['extension_attr'] = {} + + return create_api_response( + code="200", + message="获取成功", + data=item + ) + + except Exception as e: + return create_api_response( + code="500", + message=f"获取码表数据失败: {str(e)}" + ) + + +@router.post("/dict", response_model=dict) +async def create_dict_data( + request: CreateDictDataRequest, + current_user: dict = Depends(get_current_admin_user) +): + """ + 创建码表数据(仅管理员) + """ + try: + with get_db_connection() as conn: + cursor = conn.cursor() + + # 检查是否已存在 + cursor.execute( + "SELECT id FROM dict_data WHERE dict_type = %s AND dict_code = %s", + (request.dict_type, request.dict_code) + ) + if cursor.fetchone(): + cursor.close() + return create_api_response( + code="400", + message=f"编码 {request.dict_code} 已存在" + ) + + # 插入数据 + query = """ + INSERT INTO dict_data ( + dict_type, dict_code, parent_code, label_cn, label_en, + sort_order, extension_attr, is_default, status + ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) + """ + extension_json = json.dumps(request.extension_attr) if request.extension_attr else None + + cursor.execute(query, ( + request.dict_type, + request.dict_code, + request.parent_code, + request.label_cn, + request.label_en, + request.sort_order, + extension_json, + request.is_default, + request.status + )) + + new_id = cursor.lastrowid + conn.commit() + cursor.close() + + return create_api_response( + code="200", + message="创建成功", + data={"id": new_id} + ) + + except Exception as e: + return create_api_response( + code="500", + message=f"创建码表数据失败: {str(e)}" + ) + + +@router.put("/dict/{id}", response_model=dict) +async def update_dict_data( + id: int, + request: UpdateDictDataRequest, + current_user: dict = Depends(get_current_admin_user) +): + """ + 更新码表数据(仅管理员) + """ + try: + with get_db_connection() as conn: + cursor = conn.cursor(dictionary=True) + + # 检查是否存在 + cursor.execute("SELECT * FROM dict_data WHERE id = %s", (id,)) + existing = cursor.fetchone() + if not existing: + cursor.close() + return create_api_response( + code="404", + message="码表数据不存在" + ) + + # 构建更新语句 + update_fields = [] + params = [] + + if request.parent_code is not None: + update_fields.append("parent_code = %s") + params.append(request.parent_code) + + if request.label_cn is not None: + update_fields.append("label_cn = %s") + params.append(request.label_cn) + + if request.label_en is not None: + update_fields.append("label_en = %s") + params.append(request.label_en) + + if request.sort_order is not None: + update_fields.append("sort_order = %s") + params.append(request.sort_order) + + if request.extension_attr is not None: + update_fields.append("extension_attr = %s") + params.append(json.dumps(request.extension_attr)) + + if request.is_default is not None: + update_fields.append("is_default = %s") + params.append(request.is_default) + + if request.status is not None: + update_fields.append("status = %s") + params.append(request.status) + + if not update_fields: + cursor.close() + return create_api_response( + code="400", + message="没有要更新的字段" + ) + + # 执行更新 + update_query = f""" + UPDATE dict_data + SET {', '.join(update_fields)} + WHERE id = %s + """ + params.append(id) + cursor.execute(update_query, params) + conn.commit() + cursor.close() + + return create_api_response( + code="200", + message="更新成功" + ) + + except Exception as e: + return create_api_response( + code="500", + message=f"更新码表数据失败: {str(e)}" + ) + + +@router.delete("/dict/{id}", response_model=dict) +async def delete_dict_data( + id: int, + current_user: dict = Depends(get_current_admin_user) +): + """ + 删除码表数据(仅管理员) + 注意:如果有子节点或被引用,应该拒绝删除 + """ + try: + with get_db_connection() as conn: + cursor = conn.cursor(dictionary=True) + + # 检查是否存在 + cursor.execute("SELECT dict_code FROM dict_data WHERE id = %s", (id,)) + existing = cursor.fetchone() + if not existing: + cursor.close() + return create_api_response( + code="404", + message="码表数据不存在" + ) + + # 检查是否有子节点 + cursor.execute( + "SELECT COUNT(*) as count FROM dict_data WHERE parent_code = %s", + (existing['dict_code'],) + ) + if cursor.fetchone()['count'] > 0: + cursor.close() + return create_api_response( + code="400", + message="该节点存在子节点,无法删除" + ) + + # 检查是否被client_downloads引用 + cursor.execute( + "SELECT COUNT(*) as count FROM client_downloads WHERE platform_code = %s", + (existing['dict_code'],) + ) + if cursor.fetchone()['count'] > 0: + cursor.close() + return create_api_response( + code="400", + message="该平台编码已被客户端下载记录引用,无法删除" + ) + + # 执行删除 + cursor.execute("DELETE FROM dict_data WHERE id = %s", (id,)) + conn.commit() + cursor.close() + + return create_api_response( + code="200", + message="删除成功" + ) + + except Exception as e: + return create_api_response( + code="500", + message=f"删除码表数据失败: {str(e)}" + ) diff --git a/app/api/endpoints/meetings.py b/app/api/endpoints/meetings.py index aa350ab..fdaa93d 100644 --- a/app/api/endpoints/meetings.py +++ b/app/api/endpoints/meetings.py @@ -113,8 +113,8 @@ def get_meetings( # 构建基础查询 base_query = ''' - SELECT m.meeting_id, m.title, m.meeting_time, m.summary, m.created_at, m.tags, - m.user_id as creator_id, u.caption as creator_username, af.file_path as audio_file_path + SELECT m.meeting_id, m.title, m.meeting_time, m.summary, m.created_at, m.tags, m.access_password, + m.user_id as creator_id, u.caption as creator_username, MAX(af.file_path) as audio_file_path FROM meetings m JOIN users u ON m.user_id = u.user_id LEFT JOIN audio_files af ON m.meeting_id = af.meeting_id @@ -139,9 +139,8 @@ def get_meetings( cursor.execute(count_query, params) total = cursor.fetchone()['total'] - # 添加GROUP BY(如果联表了attendees) - if has_attendees_join: - base_query += " GROUP BY m.meeting_id" + # 添加GROUP BY(因为使用了MAX聚合函数,总是需要GROUP BY) + base_query += " GROUP BY m.meeting_id" # 计算分页 total_pages = (total + page_size - 1) // page_size diff --git a/app/core/config.py b/app/core/config.py index e906840..39da6b3 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -9,25 +9,29 @@ AUDIO_DIR = UPLOAD_DIR / "audio" TEMP_UPLOAD_DIR = UPLOAD_DIR / "temp_audio" MARKDOWN_DIR = UPLOAD_DIR / "markdown" VOICEPRINT_DIR = UPLOAD_DIR / "voiceprint" +CLIENT_DIR = UPLOAD_DIR / "clients" # 文件上传配置 ALLOWED_EXTENSIONS = {".mp3", ".wav", ".m4a", ".mpeg", ".mp4"} ALLOWED_IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png", ".gif", ".webp"} ALLOWED_VOICEPRINT_EXTENSIONS = {".wav"} +ALLOWED_CLIENT_EXTENSIONS = {".apk", ".exe", ".dmg", ".deb", ".rpm", ".pkg", ".msi", ".zip", ".tar.gz"} MAX_FILE_SIZE = 100 * 1024 * 1024 # 100MB MAX_IMAGE_SIZE = 10 * 1024 * 1024 # 10MB +MAX_CLIENT_SIZE = 500 * 1024 * 1024 # 500MB for client installers # 确保上传目录存在 UPLOAD_DIR.mkdir(exist_ok=True) AUDIO_DIR.mkdir(exist_ok=True) MARKDOWN_DIR.mkdir(exist_ok=True) VOICEPRINT_DIR.mkdir(exist_ok=True) +CLIENT_DIR.mkdir(exist_ok=True) # 数据库配置 DATABASE_CONFIG = { - 'host': os.getenv('DB_HOST', '10.100.51.161'), + 'host': os.getenv('DB_HOST', '10.100.51.51'), 'user': os.getenv('DB_USER', 'root'), - 'password': os.getenv('DB_PASSWORD', 'sagacity'), + 'password': os.getenv('DB_PASSWORD', 'Unis@123'), 'database': os.getenv('DB_NAME', 'imeeting_dev'), 'port': int(os.getenv('DB_PORT', '3306')), 'charset': 'utf8mb4' @@ -52,10 +56,10 @@ APP_CONFIG = { # Redis配置 REDIS_CONFIG = { - 'host': os.getenv('REDIS_HOST', '10.100.51.161'), + 'host': os.getenv('REDIS_HOST', '10.100.51.51'), 'port': int(os.getenv('REDIS_PORT', '6379')), 'db': int(os.getenv('REDIS_DB', '0')), - 'password': os.getenv('REDIS_PASSWORD', None), + 'password': os.getenv('REDIS_PASSWORD', 'Unis@123'), 'decode_responses': True } diff --git a/app/main.py b/app/main.py index 1d45b98..4e9e3b8 100644 --- a/app/main.py +++ b/app/main.py @@ -13,7 +13,7 @@ import uvicorn from fastapi import FastAPI, Request, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles -from app.api.endpoints import auth, users, meetings, tags, admin, admin_dashboard, tasks, prompts, knowledge_base, client_downloads, voiceprint, audio +from app.api.endpoints import auth, users, meetings, tags, admin, admin_dashboard, tasks, prompts, knowledge_base, client_downloads, voiceprint, audio, dict_data from app.core.config import UPLOAD_DIR, API_CONFIG from app.api.endpoints.admin import load_system_config @@ -50,6 +50,7 @@ app.include_router(tasks.router, prefix="/api", tags=["Tasks"]) app.include_router(prompts.router, prefix="/api", tags=["Prompts"]) app.include_router(knowledge_base.router, prefix="/api", tags=["KnowledgeBase"]) app.include_router(client_downloads.router, prefix="/api", tags=["ClientDownloads"]) +app.include_router(dict_data.router, prefix="/api", tags=["DictData"]) app.include_router(voiceprint.router, prefix="/api", tags=["Voiceprint"]) app.include_router(audio.router, prefix="/api", tags=["Audio"]) diff --git a/app/models/models.py b/app/models/models.py index 5cd9894..1a6ae9a 100644 --- a/app/models/models.py +++ b/app/models/models.py @@ -167,8 +167,9 @@ class KnowledgeBaseListResponse(BaseModel): # 客户端下载相关模型 class ClientDownload(BaseModel): id: int - platform_type: str # 'mobile' or 'desktop' - platform_name: str # 'ios', 'android', 'windows', 'mac_intel', 'mac_m', 'linux' + platform_type: Optional[str] = None # 兼容旧版:'mobile', 'desktop', 'terminal' + platform_name: Optional[str] = None # 兼容旧版:'ios', 'android', 'windows', 'mac_intel', 'mac_m', 'linux' + platform_code: str # 新版平台编码,关联 dict_data.dict_code version: str version_code: int download_url: str @@ -182,8 +183,9 @@ class ClientDownload(BaseModel): created_by: Optional[int] = None class CreateClientDownloadRequest(BaseModel): - platform_type: str - platform_name: str + platform_type: Optional[str] = None # 兼容旧版 + platform_name: Optional[str] = None # 兼容旧版 + platform_code: str # 必填,关联 dict_data version: str version_code: int download_url: str @@ -194,6 +196,9 @@ class CreateClientDownloadRequest(BaseModel): min_system_version: Optional[str] = None class UpdateClientDownloadRequest(BaseModel): + platform_type: Optional[str] = None + platform_name: Optional[str] = None + platform_code: Optional[str] = None version: Optional[str] = None version_code: Optional[int] = None download_url: Optional[str] = None diff --git a/app/utils/apk_parser.py b/app/utils/apk_parser.py new file mode 100644 index 0000000..226fd0c --- /dev/null +++ b/app/utils/apk_parser.py @@ -0,0 +1,36 @@ +""" +APK解析工具 +用于从APK文件中提取版本信息 +""" +import zipfile +import xml.etree.ElementTree as ET +import struct + + +# 如果安装了 androguard,使用更可靠的解析方法 +def parse_apk_with_androguard(apk_path): + """ + 使用 androguard 库解析 APK + 需要先安装: pip install androguard + """ + try: + from androguard.core.apk import APK + + apk = APK(apk_path) + version_code = apk.get_androidversion_code() + version_name = apk.get_androidversion_name() + + print(f"APK解析成功: version_code={version_code}, version_name={version_name}") + + return { + 'version_code': int(version_code) if version_code else None, + 'version_name': version_name + } + except ImportError as ie: + print(f"androguard 导入失败: {str(ie)}") + return parse_apk(apk_path) + except Exception as e: + print(f"使用androguard解析APK失败: {str(e)}") + import traceback + traceback.print_exc() + return None diff --git a/check_db_structure.py b/check_db_structure.py new file mode 100644 index 0000000..745f056 --- /dev/null +++ b/check_db_structure.py @@ -0,0 +1,56 @@ +import mysql.connector + +# 连接数据库 +conn = mysql.connector.connect( + host="10.100.51.51", + port=3306, + user="root", + password="Unis@123", + database="imeeting_dev" +) + +cursor = conn.cursor() + +print("=" * 80) +print("dict_data 表结构:") +print("=" * 80) +cursor.execute("SHOW CREATE TABLE dict_data") +result = cursor.fetchone() +print(result[1]) +print("\n") + +print("=" * 80) +print("dict_data 表数据:") +print("=" * 80) +cursor.execute("SELECT * FROM dict_data ORDER BY dict_code, sort_order") +rows = cursor.fetchall() +cursor.execute("SHOW COLUMNS FROM dict_data") +columns = [col[0] for col in cursor.fetchall()] +print(" | ".join(columns)) +print("-" * 80) +for row in rows: + print(" | ".join(str(val) for val in row)) +print("\n") + +print("=" * 80) +print("client_download 表结构:") +print("=" * 80) +cursor.execute("SHOW CREATE TABLE client_download") +result = cursor.fetchone() +print(result[1]) +print("\n") + +print("=" * 80) +print("client_download 表数据:") +print("=" * 80) +cursor.execute("SELECT * FROM client_download") +rows = cursor.fetchall() +cursor.execute("SHOW COLUMNS FROM client_download") +columns = [col[0] for col in cursor.fetchall()] +print(" | ".join(columns)) +print("-" * 80) +for row in rows: + print(" | ".join(str(val) for val in row)) + +cursor.close() +conn.close() diff --git a/requirements-prod.txt b/requirements-prod.txt index 9bd1d4e..840a5d4 100644 --- a/requirements-prod.txt +++ b/requirements-prod.txt @@ -18,3 +18,6 @@ python-multipart # System Monitoring psutil + +# APK Parsing +androguard diff --git a/requirements.txt b/requirements.txt index 3cdd46f..7573184 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,3 +10,4 @@ dashscope PyJWT>=2.8.0 python-jose[cryptography]>=3.3.0 psutil +androguard diff --git a/sql/add_task_type_dict.sql b/sql/add_task_type_dict.sql new file mode 100644 index 0000000..fe6fac8 --- /dev/null +++ b/sql/add_task_type_dict.sql @@ -0,0 +1,10 @@ +-- 添加 task_type 字典数据 +-- 用于会议任务和知识库任务的分类 + +INSERT INTO dict_data (dict_type, dict_code, parent_code, label_cn, label_en, sort_order, status, extension_attr) VALUES +('task_type', 'MEETING_TASK', 'ROOT', '会议任务', 'Meeting Task', 1, 1, NULL), +('task_type', 'KNOWLEDGE_TASK', 'ROOT', '知识库任务', 'Knowledge Task', 2, 1, NULL) +ON DUPLICATE KEY UPDATE label_cn=VALUES(label_cn), label_en=VALUES(label_en); + +-- 查看结果 +SELECT * FROM dict_data WHERE dict_type='task_type'; diff --git a/sql/create_client_downloads.sql b/sql/create_client_downloads.sql new file mode 100644 index 0000000..d0097fe --- /dev/null +++ b/sql/create_client_downloads.sql @@ -0,0 +1,37 @@ +-- 客户端下载管理表 +-- 保留 platform_type 和 platform_name 字段以兼容旧终端 +-- 新增 platform_code 关联 dict_data 表的码表数据 + +CREATE TABLE IF NOT EXISTS `client_downloads` ( + `id` INT NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `platform_type` VARCHAR(50) NULL COMMENT '平台类型(兼容旧版:mobile, desktop, terminal)', + `platform_name` VARCHAR(50) NULL COMMENT '平台名称(兼容旧版:ios, android, windows等)', + `platform_code` VARCHAR(64) NOT NULL COMMENT '平台编码(关联 dict_data.dict_code)', + `version` VARCHAR(50) NOT NULL COMMENT '版本号(如 1.0.0)', + `version_code` INT NOT NULL COMMENT '版本号数值(用于版本比较)', + `download_url` VARCHAR(512) NOT NULL COMMENT '下载链接', + `file_size` BIGINT NULL COMMENT '文件大小(bytes)', + `release_notes` TEXT NULL COMMENT '更新说明', + `is_active` BOOLEAN NOT NULL DEFAULT TRUE COMMENT '是否启用', + `is_latest` BOOLEAN NOT NULL DEFAULT FALSE COMMENT '是否为最新版本', + `min_system_version` VARCHAR(50) NULL COMMENT '最低系统版本要求', + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `created_by` INT NULL COMMENT '创建者用户ID', + PRIMARY KEY (`id`), + INDEX `idx_platform_code` (`platform_code`), + INDEX `idx_platform_type_name` (`platform_type`, `platform_name`), + INDEX `idx_is_latest` (`is_latest`), + INDEX `idx_is_active` (`is_active`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户端下载管理表'; + +-- 插入测试数据示例(包含新旧字段映射) +-- 旧终端使用 platform_type + platform_name +-- 新终端使用 platform_code +-- INSERT INTO client_downloads (platform_type, platform_name, platform_code, version, version_code, download_url, file_size, release_notes, is_active, is_latest, min_system_version, created_by) +-- VALUES +-- ('desktop', 'windows', 'WIN', '1.0.0', 100, 'https://download.example.com/imeeting-win-1.0.0.exe', 52428800, '首个正式版本', TRUE, TRUE, 'Windows 10', 1), +-- ('desktop', 'mac', 'MAC', '1.0.0', 100, 'https://download.example.com/imeeting-mac-1.0.0.dmg', 48234496, '首个正式版本', TRUE, TRUE, 'macOS 11.0', 1), +-- ('mobile', 'ios', 'IOS', '1.0.0', 100, 'https://apps.apple.com/app/imeeting', 45088768, '首个正式版本', TRUE, TRUE, 'iOS 13.0', 1), +-- ('mobile', 'android', 'ANDROID', '1.0.0', 100, 'https://download.example.com/imeeting-android-1.0.0.apk', 38797312, '首个正式版本', TRUE, TRUE, 'Android 8.0', 1); + diff --git a/test_apk_parser.py b/test_apk_parser.py new file mode 100644 index 0000000..10c03f9 --- /dev/null +++ b/test_apk_parser.py @@ -0,0 +1,24 @@ +""" +测试APK解析功能 +""" +from app.utils.apk_parser import parse_apk_with_androguard + +# 测试导入是否正常 +try: + from androguard.core.apk import APK + print("✅ androguard 导入成功") + print(f" androguard 模块路径: {APK.__module__}") +except ImportError as e: + print(f"❌ androguard 导入失败: {e}") + exit(1) + +# 如果有测试APK文件,可以在这里测试解析 +# apk_path = "path/to/your/test.apk" +# result = parse_apk_with_androguard(apk_path) +# print(f"解析结果: {result}") + +print("\n📋 测试结果:") +print(" 1. androguard 库已成功安装") +print(" 2. 导入路径正确: androguard.core.apk.APK") +print(" 3. 可以正常使用 APK 解析功能") +print("\n💡 提示: 请重启后台服务以使更改生效")