From 828acabda3bc820f9eac5ea680a7ec476a104783 Mon Sep 17 00:00:00 2001 From: brunoGao Date: Mon, 1 Sep 2025 14:03:20 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8DFlask=E8=B7=AF?= =?UTF-8?q?=E7=94=B1=E5=86=B2=E7=AA=81=E5=92=8C=E4=BC=81=E4=B8=9A=E7=BA=A7?= =?UTF-8?q?UI=E7=BB=84=E4=BB=B6=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复ljwx-bigscreen中重复的api_health_profile_generate函数名导致的路由冲突 - 优化前端告警管理和租户配置页面UI组件 - 完善操作手册和配置说明文档 - 增强用户体验和系统稳定性 --- docs/ljwx-bigscreen系统错误诊断与修复方案.md | 305 +++++++++++++ ljwx-admin/src/typings/components.d.ts | 15 +- ljwx-admin/src/views/alert/info/index.vue | 75 +++- ljwx-admin/src/views/alert/rules/index.vue | 302 ++++++++++++- .../src/views/customer/config/index.vue | 141 +++++- ljwx-admin/src/views/customer/data/index.vue | 227 +++++++++- .../src/views/customer/interface/index.vue | 163 ++++++- ljwx-admin/src/views/home/index.vue | 419 +++++++++++++++++- ljwx-admin/src/views/manage/org/index.vue | 9 +- ljwx-admin/src/views/manage/role/index.vue | 59 ++- .../manage/role/modules/button-auth-modal.vue | 161 ++++++- .../manage/role/modules/menu-auth-modal.vue | 98 +++- .../manage/user/modules/user-page-table.vue | 53 +-- .../bigscreen/bigScreen/bigScreen.py | 381 ++++++++++++++++ ljwx-bigscreen/bigscreen/bigScreen/user.py | 22 + .../bigscreen/bigScreen/user_health_data.py | 157 ++++++- .../performance-summary.md | 47 ++ .../java/com/ljwx/test/base/BaseApiTest.java | 82 +++- 18 files changed, 2629 insertions(+), 87 deletions(-) create mode 100644 docs/ljwx-bigscreen系统错误诊断与修复方案.md create mode 100644 ljwx-boot/test-automation/performance-reports/performance-summary.md diff --git a/docs/ljwx-bigscreen系统错误诊断与修复方案.md b/docs/ljwx-bigscreen系统错误诊断与修复方案.md new file mode 100644 index 0000000..c2eb536 --- /dev/null +++ b/docs/ljwx-bigscreen系统错误诊断与修复方案.md @@ -0,0 +1,305 @@ +# ljwx-bigscreen系统错误诊断与修复方案 + +> **发现时间**: 2025-09-01 11:53 +> **影响范围**: 组织查询、Redis缓存、数据一致性 +> **紧急程度**: 🔴 高 (影响客户使用) + +## 📊 问题概览 + +### 核心错误 +```bash +ERROR:bigScreen.org:Failed to fetch departments for org 1939964806110937090 +WARNING:root:Redis hset 失败: Invalid input of type: 'NoneType'. Convert to a bytes, string, int or float first. +📊 组织 1939964806110937090 共找到 0 用户,有效设备 0 个 +``` + +### 问题影响 +- ❌ **组织查询异常**: 无法获取组织1939964806110937090的部门数据 +- ❌ **Redis缓存失败**: NoneType数据类型导致缓存写入失败 +- ❌ **数据一致性**: 个人查询正常但组织级查询失败 +- ❌ **用户体验**: 客户大屏显示0用户、0设备 + +## 🔍 根本原因分析 + +### 1. 组织查询失败 - 统一服务依赖问题 + +**问题代码位置**: `ljwx-bigscreen/bigscreen/bigScreen/org.py:31-33` +```python +# 使用统一组织服务 +org_service = get_unified_org_service() +result = org_service.get_org_tree(org_id, customer_id) +``` + +**分析**: +- `get_unified_org_service()` 可能返回None或初始化失败 +- `org_service.get_org_tree()` 方法调用异常 +- 缺少对统一服务的错误回退机制 + +### 2. Redis数据类型错误 - NoneType处理缺陷 + +**问题根源**: Redis `hset` 操作试图存储 `NoneType` 值 +```bash +WARNING:root:Redis hset 失败: Invalid input of type: 'NoneType'. Convert to a bytes, string, int or float first. +``` + +**分析**: +- 数据库查询返回 `None` 值直接传递给Redis +- Redis要求值必须是 bytes, string, int 或 float 类型 +- 缺少对 `NoneType` 的预处理和过滤 + +### 3. 数据一致性问题 - 服务层逻辑差异 + +**现象对比**: +- ✅ 个人设备查询: `deviceSn=CRFTQ23409001890` 正常返回数据 +- ❌ 组织查询: `orgId=1939964806110937090` 返回0用户 + +**分析**: +- 个人查询和组织查询使用不同的数据访问路径 +- 组织查询依赖统一服务,个人查询直接访问数据库 +- 可能存在服务版本不一致或配置差异 + +## 🛠️ 即时修复方案 + +### 修复1: 组织查询回退机制 + +```python +def fetch_departments_by_orgId(org_id, customer_id=None): + """递归获取组织下的所有部门信息,增加错误回退""" + try: + # 如果没有提供customer_id,尝试获取 + if customer_id is None: + try: + customer_id = get_current_customer_id() + except RuntimeError: + customer_id = 0 + logger.warning("无Flask上下文,使用默认customer_id=0") + + # 🔧 修复:增加统一服务可用性检查 + org_service = get_unified_org_service() + if org_service is None: + logger.warning(f"统一组织服务不可用,回退到legacy方法") + return fetch_departments_by_orgId_legacy(org_id, customer_id) + + try: + result = org_service.get_org_tree(org_id, customer_id) + + # 🔧 修复:验证结果有效性 + if not result or not result.get('success'): + logger.warning(f"统一服务返回无效结果,回退到legacy方法") + return fetch_departments_by_orgId_legacy(org_id, customer_id) + + logger.info(f"使用统一服务成功获取组织{org_id}的部门树") + return result + + except Exception as service_error: + logger.error(f"统一服务调用失败: {service_error},回退到legacy方法") + return fetch_departments_by_orgId_legacy(org_id, customer_id) + + except Exception as e: + logger.error(f"Error in fetch_departments_by_orgId: {str(e)}") + # 🔧 修复:最终回退到legacy方法 + return fetch_departments_by_orgId_legacy(org_id, customer_id) +``` + +### 修复2: Redis NoneType安全处理 + +```python +def hset_safe(self, key, mapping): + """安全的Redis hset操作,过滤NoneType值""" + try: + # 🔧 修复:过滤None值 + safe_mapping = {} + for k, v in mapping.items(): + if v is not None: + # 确保值是Redis支持的类型 + if isinstance(v, (str, int, float, bytes)): + safe_mapping[k] = v + else: + safe_mapping[k] = str(v) # 转换为字符串 + else: + safe_mapping[k] = '' # None值转为空字符串 + + return self.client.hset(key, mapping=safe_mapping) + except Exception as e: + logging.warning(f"Redis hset 失败: {e}") + return False + +def set_safe(self, key, value, ex=None): + """安全的Redis set操作,处理NoneType""" + try: + # 🔧 修复:None值处理 + if value is None: + value = '' + elif not isinstance(value, (str, int, float, bytes)): + value = str(value) + + return self.client.set(key, value, ex=ex) + except Exception as e: + logging.warning(f"Redis set 失败: {e}") + return False +``` + +### 修复3: 数据查询一致性保障 + +```python +def fetch_users_by_orgId_with_fallback(org_id, customer_id=None): + """增强的用户查询函数,带多重回退机制""" + try: + # 🔧 方法1:使用统一服务查询 + users = fetch_users_by_orgId(org_id, customer_id) + if users and len(users) > 0: + logger.info(f"统一服务查询成功: 组织{org_id}找到{len(users)}个用户") + return users + + # 🔧 方法2:直接数据库查询(绕过统一服务) + logger.warning(f"统一服务查询无结果,尝试直接数据库查询") + users_direct = fetch_users_by_orgId_direct(org_id, customer_id) + if users_direct and len(users_direct) > 0: + logger.info(f"直接数据库查询成功: 组织{org_id}找到{len(users_direct)}个用户") + return users_direct + + # 🔧 方法3:扩大查询范围(包含子组织) + logger.warning(f"直接查询也无结果,尝试查询子组织") + users_expanded = fetch_users_with_descendants(org_id, customer_id) + logger.info(f"扩展查询结果: 组织{org_id}及子组织找到{len(users_expanded)}个用户") + return users_expanded + + except Exception as e: + logger.error(f"所有用户查询方法均失败: {str(e)}") + return [] + +def fetch_users_by_orgId_direct(org_id, customer_id=None): + """直接数据库查询用户(绕过服务层)""" + try: + # 直接查询,不依赖组织服务 + users = db.session.query( + UserInfo, UserOrg, OrgInfo + ).join( + UserOrg, UserInfo.id == UserOrg.user_id + ).join( + OrgInfo, UserOrg.org_id == OrgInfo.id + ).filter( + UserOrg.org_id == org_id, + UserInfo.is_deleted.is_(False), + UserInfo.status == '1' + ) + + if customer_id is not None: + users = users.filter(UserInfo.customer_id == customer_id) + + users_result = users.all() + + # 格式化返回结果 + user_list = [] + for user_info, user_org, org_info in users_result: + user_list.append({ + 'id': str(user_info.id), + 'user_name': user_info.user_name, + 'device_sn': user_info.device_sn, + 'department_id': org_info.id, + 'department_name': org_info.name, + # ... 其他字段 + }) + + return user_list + + except Exception as e: + logger.error(f"直接数据库查询用户失败: {str(e)}") + return [] +``` + +## 🚀 实施计划 + +### 阶段1: 紧急修复 (2小时) +1. ✅ **立即修复组织查询错误** + - 修改 `fetch_departments_by_orgId` 增加回退机制 + - 测试验证组织 `1939964806110937090` 能正常查询 + +2. ✅ **修复Redis NoneType错误** + - 修改 `RedisHelper` 类增加类型安全检查 + - 替换所有 `hset`、`set` 调用为安全版本 + +### 阶段2: 数据一致性修复 (4小时) +1. ✅ **实现数据查询回退机制** + - 添加直接数据库查询函数 + - 实现多重查询策略 + +2. ✅ **验证数据完整性** + - 检查组织 `1939964806110937090` 的实际数据 + - 确认用户-组织关系的正确性 + +### 阶段3: 系统监控增强 (2小时) +1. ✅ **增加错误监控** + - 添加组织服务可用性检查 + - 实现Redis操作失败告警 + +2. ✅ **性能监控** + - 监控查询响应时间 + - 跟踪缓存命中率 + +## 🔧 代码修复示例 + +### 修复文件1: `redis_helper.py` + +```python +# 在第132行后添加 +def hset_safe(self, key, mapping): + """安全的hset操作,过滤NoneType""" + try: + safe_mapping = {} + for k, v in mapping.items(): + if v is not None: + safe_mapping[k] = str(v) if not isinstance(v, (str, int, float, bytes)) else v + else: + safe_mapping[k] = '' + return self.client.hset(key, mapping=safe_mapping) + except Exception as e: + logging.warning(f"Redis hset_safe 失败: {e}") + return False +``` + +### 修复文件2: `org.py` + +```python +# 在第31行修改为: +try: + org_service = get_unified_org_service() + if org_service is None: + logger.warning("统一组织服务不可用,使用legacy方法") + return fetch_departments_by_orgId_legacy(org_id, customer_id) + + result = org_service.get_org_tree(org_id, customer_id) + if not result or not result.get('success'): + logger.warning("统一服务返回无效结果,使用legacy方法") + return fetch_departments_by_orgId_legacy(org_id, customer_id) + +except Exception as service_error: + logger.error(f"统一服务失败: {service_error},使用legacy方法") + return fetch_departments_by_orgId_legacy(org_id, customer_id) +``` + +## 🎯 预期效果 + +### 修复后预期结果: +- ✅ **组织查询恢复正常**: 组织 `1939964806110937090` 能正确返回用户数据 +- ✅ **Redis错误消除**: 不再出现 NoneType 类型错误 +- ✅ **数据一致性**: 个人查询和组织查询结果一致 +- ✅ **用户体验改善**: 客户大屏正常显示用户和设备数据 + +### 性能指标: +- 组织查询响应时间: <500ms +- Redis操作成功率: 99.9% +- 数据查询一致性: 100% + +## 📈 长期优化建议 + +1. **服务依赖治理**: 建立服务健康检查机制 +2. **数据类型规范**: 建立统一的数据类型处理标准 +3. **错误监控**: 实现实时错误检测和告警 +4. **性能优化**: 基于前期架构分析,逐步迁移到Java主体架构 + +--- + +**修复负责人**: 系统架构师 +**预期完成时间**: 2025-09-01 20:00 +**验证方式**: 访问 `http://192.168.1.83:5001/main?customerId=1939964806110937090` 确认数据正常显示 \ No newline at end of file diff --git a/ljwx-admin/src/typings/components.d.ts b/ljwx-admin/src/typings/components.d.ts index f5448da..ad40de8 100644 --- a/ljwx-admin/src/typings/components.d.ts +++ b/ljwx-admin/src/typings/components.d.ts @@ -23,20 +23,13 @@ declare module 'vue' { 'IconIc:baselineCloudUpload': typeof import('~icons/ic/baseline-cloud-upload')['default'] 'IconIc:outlineRefresh': typeof import('~icons/ic/outline-refresh')['default'] 'IconIc:roundKeyboardArrowUp': typeof import('~icons/ic/round-keyboard-arrow-up')['default'] - 'IconIc:roundPlus': typeof import('~icons/ic/round-plus')['default'] IconIcRoundCheck: typeof import('~icons/ic/round-check')['default'] IconIcRoundClose: typeof import('~icons/ic/round-close')['default'] IconIcRoundDelete: typeof import('~icons/ic/round-delete')['default'] IconIcRoundPlus: typeof import('~icons/ic/round-plus')['default'] IconIcRoundRefresh: typeof import('~icons/ic/round-refresh')['default'] - IconIcRoundRemove: typeof import('~icons/ic/round-remove')['default'] IconIcRoundSearch: typeof import('~icons/ic/round-search')['default'] IconLocalLogo: typeof import('~icons/local/logo')['default'] - 'IconMaterialSymbols:checkCircle': typeof import('~icons/material-symbols/check-circle')['default'] - 'IconMaterialSymbols:cloudUpload': typeof import('~icons/material-symbols/cloud-upload')['default'] - 'IconMaterialSymbols:download': typeof import('~icons/material-symbols/download')['default'] - 'IconMaterialSymbols:info': typeof import('~icons/material-symbols/info')['default'] - 'IconMaterialSymbols:upload': typeof import('~icons/material-symbols/upload')['default'] IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default'] IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default'] IconMdiDrag: typeof import('~icons/mdi/drag')['default'] @@ -46,6 +39,7 @@ declare module 'vue' { LangSwitch: typeof import('./../components/common/lang-switch.vue')['default'] LookForward: typeof import('./../components/custom/look-forward.vue')['default'] MenuToggler: typeof import('./../components/common/menu-toggler.vue')['default'] + NAlert: typeof import('naive-ui')['NAlert'] NBreadcrumb: typeof import('naive-ui')['NBreadcrumb'] NBreadcrumbItem: typeof import('naive-ui')['NBreadcrumbItem'] NButton: typeof import('naive-ui')['NButton'] @@ -62,7 +56,6 @@ declare module 'vue' { NDrawer: typeof import('naive-ui')['NDrawer'] NDrawerContent: typeof import('naive-ui')['NDrawerContent'] NDropdown: typeof import('naive-ui')['NDropdown'] - NDynamicInput: typeof import('naive-ui')['NDynamicInput'] NEmpty: typeof import('naive-ui')['NEmpty'] NFlex: typeof import('naive-ui')['NFlex'] NForm: typeof import('naive-ui')['NForm'] @@ -71,8 +64,6 @@ declare module 'vue' { NGi: typeof import('naive-ui')['NGi'] NGrid: typeof import('naive-ui')['NGrid'] NGridItem: typeof import('naive-ui')['NGridItem'] - NH4: typeof import('naive-ui')['NH4'] - NIcon: typeof import('naive-ui')['NIcon'] NInput: typeof import('naive-ui')['NInput'] NInputGroup: typeof import('naive-ui')['NInputGroup'] NInputNumber: typeof import('naive-ui')['NInputNumber'] @@ -86,18 +77,14 @@ declare module 'vue' { NNotificationProvider: typeof import('naive-ui')['NNotificationProvider'] NPopconfirm: typeof import('naive-ui')['NPopconfirm'] NPopover: typeof import('naive-ui')['NPopover'] - NProgress: typeof import('naive-ui')['NProgress'] NRadio: typeof import('naive-ui')['NRadio'] NRadioGroup: typeof import('naive-ui')['NRadioGroup'] - NResult: typeof import('naive-ui')['NResult'] NScrollbar: typeof import('naive-ui')['NScrollbar'] NSelect: typeof import('naive-ui')['NSelect'] NSpace: typeof import('naive-ui')['NSpace'] NStatistic: typeof import('naive-ui')['NStatistic'] NSwitch: typeof import('naive-ui')['NSwitch'] NTab: typeof import('naive-ui')['NTab'] - NTable: typeof import('naive-ui')['NTable'] - NTabPane: typeof import('naive-ui')['NTabPane'] NTabs: typeof import('naive-ui')['NTabs'] NTag: typeof import('naive-ui')['NTag'] NThing: typeof import('naive-ui')['NThing'] diff --git a/ljwx-admin/src/views/alert/info/index.vue b/ljwx-admin/src/views/alert/info/index.vue index 4ee2b6b..d335cc2 100644 --- a/ljwx-admin/src/views/alert/info/index.vue +++ b/ljwx-admin/src/views/alert/info/index.vue @@ -1,7 +1,8 @@ + + diff --git a/ljwx-admin/src/views/customer/config/index.vue b/ljwx-admin/src/views/customer/config/index.vue index a2995fc..dea5a54 100644 --- a/ljwx-admin/src/views/customer/config/index.vue +++ b/ljwx-admin/src/views/customer/config/index.vue @@ -1,5 +1,5 @@