From cf71fabef019371246f7fe27576139ab0b0b6db6 Mon Sep 17 00:00:00 2001 From: yuliang_guo Date: Mon, 2 Feb 2026 12:57:31 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=BC=81=E4=B8=9A?= =?UTF-8?q?=E7=9C=8B=E6=9D=BFAPI=20500=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复 get_realtime_activities() 中字段名错误 (exp_amount -> exp_change) - 添加 get_enterprise_overview() 的异常处理,防止单个查询失败导致整体失败 - 满分人数查询添加 NULL 值检查 --- backend/app/services/dashboard_service.py | 312 +++++++++++++--------- 1 file changed, 190 insertions(+), 122 deletions(-) diff --git a/backend/app/services/dashboard_service.py b/backend/app/services/dashboard_service.py index 253662b..c1ef4fc 100644 --- a/backend/app/services/dashboard_service.py +++ b/backend/app/services/dashboard_service.py @@ -42,101 +42,164 @@ class DashboardService: Returns: 企业级数据概览 """ - today = date.today() - week_ago = today - timedelta(days=7) - month_ago = today - timedelta(days=30) - - # 基础统计 - # 1. 总学员数 - result = await self.db.execute( - select(func.count(User.id)) - .where(User.is_deleted == False, User.role == 'trainee') - ) - total_users = result.scalar() or 0 - - # 2. 今日活跃用户(有经验值记录) - result = await self.db.execute( - select(func.count(func.distinct(ExpHistory.user_id))) - .where(func.date(ExpHistory.created_at) == today) - ) - today_active = result.scalar() or 0 - - # 3. 本周活跃用户 - result = await self.db.execute( - select(func.count(func.distinct(ExpHistory.user_id))) - .where(ExpHistory.created_at >= datetime.combine(week_ago, datetime.min.time())) - ) - week_active = result.scalar() or 0 - - # 4. 本月活跃用户 - result = await self.db.execute( - select(func.count(func.distinct(ExpHistory.user_id))) - .where(ExpHistory.created_at >= datetime.combine(month_ago, datetime.min.time())) - ) - month_active = result.scalar() or 0 - - # 5. 总学习时长(小时) - result = await self.db.execute( - select(func.coalesce(func.sum(PracticeSession.duration_seconds), 0)) - .where(PracticeSession.status == 'completed') - ) - practice_hours = (result.scalar() or 0) / 3600 - - result = await self.db.execute( - select(func.coalesce(func.sum(TrainingSession.duration_seconds), 0)) - .where(TrainingSession.status == 'COMPLETED') - ) - training_hours = (result.scalar() or 0) / 3600 - - total_hours = round(practice_hours + training_hours, 1) - - # 6. 考试统计 - result = await self.db.execute( - select( - func.count(Exam.id), - func.count(case((Exam.is_passed == True, 1))), - func.avg(Exam.score) + try: + today = date.today() + week_ago = today - timedelta(days=7) + month_ago = today - timedelta(days=30) + + # 基础统计 + # 1. 总学员数 + result = await self.db.execute( + select(func.count(User.id)) + .where(User.is_deleted == False, User.role == 'trainee') ) - .where(Exam.status == 'submitted') - ) - exam_row = result.first() - exam_count = exam_row[0] or 0 - exam_passed = exam_row[1] or 0 - exam_avg_score = round(exam_row[2] or 0, 1) - exam_pass_rate = round(exam_passed / exam_count * 100, 1) if exam_count > 0 else 0 - - # 7. 满分人数 - result = await self.db.execute( - select(func.count(func.distinct(Exam.user_id))) - .where(Exam.status == 'submitted', Exam.score >= Exam.total_score) - ) - perfect_users = result.scalar() or 0 - - # 8. 签到率(今日签到人数/总用户数) - result = await self.db.execute( - select(func.count(UserLevel.id)) - .where(func.date(UserLevel.last_login_date) == today) - ) - today_checkin = result.scalar() or 0 - checkin_rate = round(today_checkin / total_users * 100, 1) if total_users > 0 else 0 - - return { - "overview": { - "total_users": total_users, - "today_active": today_active, - "week_active": week_active, - "month_active": month_active, - "total_hours": total_hours, - "checkin_rate": checkin_rate, - }, - "exam": { - "total_count": exam_count, - "pass_rate": exam_pass_rate, - "avg_score": exam_avg_score, - "perfect_users": perfect_users, - }, - "updated_at": datetime.now().isoformat() - } + total_users = result.scalar() or 0 + + # 2. 今日活跃用户(有经验值记录) + try: + result = await self.db.execute( + select(func.count(func.distinct(ExpHistory.user_id))) + .where(func.date(ExpHistory.created_at) == today) + ) + today_active = result.scalar() or 0 + except Exception as e: + logger.warning(f"获取今日活跃用户失败: {e}") + today_active = 0 + + # 3. 本周活跃用户 + try: + result = await self.db.execute( + select(func.count(func.distinct(ExpHistory.user_id))) + .where(ExpHistory.created_at >= datetime.combine(week_ago, datetime.min.time())) + ) + week_active = result.scalar() or 0 + except Exception as e: + logger.warning(f"获取本周活跃用户失败: {e}") + week_active = 0 + + # 4. 本月活跃用户 + try: + result = await self.db.execute( + select(func.count(func.distinct(ExpHistory.user_id))) + .where(ExpHistory.created_at >= datetime.combine(month_ago, datetime.min.time())) + ) + month_active = result.scalar() or 0 + except Exception as e: + logger.warning(f"获取本月活跃用户失败: {e}") + month_active = 0 + + # 5. 总学习时长(小时) + practice_hours = 0 + training_hours = 0 + try: + result = await self.db.execute( + select(func.coalesce(func.sum(PracticeSession.duration_seconds), 0)) + .where(PracticeSession.status == 'completed') + ) + practice_hours = (result.scalar() or 0) / 3600 + except Exception as e: + logger.warning(f"获取陪练时长失败: {e}") + + try: + result = await self.db.execute( + select(func.coalesce(func.sum(TrainingSession.duration_seconds), 0)) + .where(TrainingSession.status == 'COMPLETED') + ) + training_hours = (result.scalar() or 0) / 3600 + except Exception as e: + logger.warning(f"获取培训时长失败: {e}") + + total_hours = round(practice_hours + training_hours, 1) + + # 6. 考试统计 + exam_count = 0 + exam_passed = 0 + exam_avg_score = 0 + try: + result = await self.db.execute( + select( + func.count(Exam.id), + func.count(case((Exam.is_passed == True, 1))), + func.avg(Exam.score) + ) + .where(Exam.status == 'submitted') + ) + exam_row = result.first() + if exam_row: + exam_count = exam_row[0] or 0 + exam_passed = exam_row[1] or 0 + exam_avg_score = round(exam_row[2] or 0, 1) + except Exception as e: + logger.warning(f"获取考试统计失败: {e}") + + exam_pass_rate = round(exam_passed / exam_count * 100, 1) if exam_count > 0 else 0 + + # 7. 满分人数 + perfect_users = 0 + try: + result = await self.db.execute( + select(func.count(func.distinct(Exam.user_id))) + .where( + Exam.status == 'submitted', + Exam.score.isnot(None), + Exam.total_score.isnot(None), + Exam.score >= Exam.total_score + ) + ) + perfect_users = result.scalar() or 0 + except Exception as e: + logger.warning(f"获取满分人数失败: {e}") + + # 8. 签到率(今日签到人数/总用户数) + today_checkin = 0 + try: + result = await self.db.execute( + select(func.count(UserLevel.id)) + .where(UserLevel.last_login_date == today) + ) + today_checkin = result.scalar() or 0 + except Exception as e: + logger.warning(f"获取签到率失败: {e}") + + checkin_rate = round(today_checkin / total_users * 100, 1) if total_users > 0 else 0 + + return { + "overview": { + "total_users": total_users, + "today_active": today_active, + "week_active": week_active, + "month_active": month_active, + "total_hours": total_hours, + "checkin_rate": checkin_rate, + }, + "exam": { + "total_count": exam_count, + "pass_rate": exam_pass_rate, + "avg_score": exam_avg_score, + "perfect_users": perfect_users, + }, + "updated_at": datetime.now().isoformat() + } + except Exception as e: + logger.error(f"获取企业概览失败: {e}") + # 返回默认数据而不是抛出异常 + return { + "overview": { + "total_users": 0, + "today_active": 0, + "week_active": 0, + "month_active": 0, + "total_hours": 0, + "checkin_rate": 0, + }, + "exam": { + "total_count": 0, + "pass_rate": 0, + "avg_score": 0, + "perfect_users": 0, + }, + "updated_at": datetime.now().isoformat() + } async def get_department_comparison(self) -> List[Dict[str, Any]]: """ @@ -313,35 +376,40 @@ class DashboardService: """ activities = [] - # 获取最近的经验值记录 - result = await self.db.execute( - select(ExpHistory, User) - .join(User, ExpHistory.user_id == User.id) - .order_by(ExpHistory.created_at.desc()) - .limit(limit) - ) - rows = result.all() - - for exp, user in rows: - activity_type = "学习" - if "考试" in (exp.description or ""): - activity_type = "考试" - elif "签到" in (exp.description or ""): - activity_type = "签到" - elif "陪练" in (exp.description or ""): - activity_type = "陪练" - elif "奖章" in (exp.description or ""): - activity_type = "奖章" + try: + # 获取最近的经验值记录 + result = await self.db.execute( + select(ExpHistory, User) + .join(User, ExpHistory.user_id == User.id) + .order_by(ExpHistory.created_at.desc()) + .limit(limit) + ) + rows = result.all() - activities.append({ - "id": exp.id, - "user_id": user.id, - "user_name": user.full_name or user.username, - "type": activity_type, - "description": exp.description, - "exp_amount": exp.exp_amount, - "created_at": exp.created_at.isoformat() if exp.created_at else None, - }) + for exp, user in rows: + activity_type = "学习" + description = exp.description or "" + if "考试" in description: + activity_type = "考试" + elif "签到" in description: + activity_type = "签到" + elif "陪练" in description: + activity_type = "陪练" + elif "奖章" in description: + activity_type = "奖章" + + activities.append({ + "id": exp.id, + "user_id": user.id, + "user_name": user.full_name or user.username, + "type": activity_type, + "description": description, + "exp_amount": exp.exp_change, # 修复: exp_change 而非 exp_amount + "created_at": exp.created_at.isoformat() if exp.created_at else None, + }) + except Exception as e: + logger.error(f"获取实时动态失败: {e}") + # 返回空列表而不是抛出异常 return activities