Files
LoveACE-EndF/loveace/router/endpoint/jwc/score.py
Sibuxiangx bbc86b8330 ⚒️ 重大重构 LoveACE V2
引入了 mongodb
对数据库进行了一定程度的数据加密
性能改善
代码简化
统一错误模型和响应
使用 apifox 作为文档
2025-11-20 20:44:25 +08:00

177 lines
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import re
from bs4 import BeautifulSoup
from fastapi import APIRouter, Depends
from fastapi.responses import JSONResponse
from httpx import HTTPError
from pydantic import ValidationError
from loveace.router.endpoint.jwc.model.base import JWCConfig
from loveace.router.endpoint.jwc.model.score import ScoreRecord, TermScoreResponse
from loveace.router.schemas.error import ProtectRouterErrorToCode
from loveace.router.schemas.uniresponse import UniResponseModel
from loveace.service.remote.aufe import AUFEConnection
from loveace.service.remote.aufe.depends import get_aufe_conn
jwc_score_router = APIRouter(
prefix="/score",
responses=ProtectRouterErrorToCode().gen_code_table(),
)
ENDPOINT = {
"term_score_pre": "/student/integratedQuery/scoreQuery/allTermScores/index",
"term_score": "/student/integratedQuery/scoreQuery/{dynamic_path}/allTermScores/data",
}
@jwc_score_router.get(
"/{term_code}/list",
summary="获取给定学期成绩列表",
response_model=UniResponseModel[TermScoreResponse],
)
async def get_term_score(
term_code: str,
conn: AUFEConnection = Depends(get_aufe_conn),
) -> UniResponseModel[TermScoreResponse] | JSONResponse:
"""
获取指定学期的详细成绩单
✅ 功能特性:
- 获取指定学期所有课程成绩
- 包含补考和重修成绩
- 显示学分、绩点等详细信息
💡 使用场景:
- 查看历史学期的成绩
- 导出成绩单
- 分析学业成绩趋势
Args:
term_code: 学期代码2023-2024-1
Returns:
TermScoreResponse: 包含该学期所有成绩记录和总数
"""
try:
response = await conn.client.get(
JWCConfig().to_full_url(ENDPOINT["term_score_pre"]),
follow_redirects=True,
timeout=conn.timeout,
)
if response.status_code != 200:
conn.logger.error(f"访问成绩查询页面失败,状态码: {response.status_code}")
return ProtectRouterErrorToCode().remote_service_error.to_json_response(
conn.logger.trace_id
)
# 从页面中提取动态路径参数
soup = BeautifulSoup(response.text, "html.parser")
# 查找表单或Ajax请求的URL
# 通常在JavaScript代码中或表单action中
dynamic_path = "M1uwxk14o6" # 默认值,如果无法提取则使用
# 尝试从页面中提取动态路径
scripts = soup.find_all("script")
for script in scripts:
try:
script_text = script.string # type: ignore
if script_text and "allTermScores/data" in script_text:
# 使用正则表达式提取路径
match = re.search(
r"/([A-Za-z0-9]+)/allTermScores/data", script_text
)
if match:
dynamic_path = match.group(1)
break
except AttributeError:
continue
data_url = JWCConfig().to_full_url(
ENDPOINT["term_score"].format(dynamic_path=dynamic_path)
)
data_params = {
"zxjxjhh": term_code,
"kch": "",
"kcm": "",
"pageNum": "1",
"pageSize": "50",
"sf_request_type": "ajax",
}
data_response = await conn.client.post(
data_url,
data=data_params,
follow_redirects=True,
timeout=conn.timeout,
)
if data_response.status_code != 200:
conn.logger.error(f"获取成绩数据失败,状态码: {data_response.status_code}")
return ProtectRouterErrorToCode().remote_service_error.to_json_response(
conn.logger.trace_id
)
data_json = data_response.json()
data_list = data_json.get("list", {})
if not data_list:
result = TermScoreResponse(records=[], total_count=0)
return UniResponseModel[TermScoreResponse](
success=True,
data=result,
message="获取成绩单成功",
error=None,
)
records = data_list.get("records", [])
r_total_count = data_list.get("pageContext", {}).get("totalCount", 0)
term_scores = []
for record in records:
term_scores.append(
ScoreRecord(
sequence=record[0],
term_id=record[1],
course_code=record[2],
course_class=record[3],
course_name_cn=record[4],
course_name_en=record[5],
credits=record[6],
hours=record[7],
course_type=record[8],
exam_type=record[9],
score=record[10],
retake_score=record[11] if record[11] else None,
makeup_score=record[12] if record[12] else None,
)
)
l_total_count = len(term_scores)
assert r_total_count == l_total_count
result = TermScoreResponse(records=term_scores, total_count=r_total_count)
return UniResponseModel[TermScoreResponse](
success=True,
data=result,
message="获取成绩单成功",
error=None,
)
except AssertionError as ae:
conn.logger.error(f"数据属性错误: {ae}")
return ProtectRouterErrorToCode().validation_error.to_json_response(
conn.logger.trace_id
)
except IndexError as ie:
conn.logger.error(f"数据解析错误: {ie}")
return ProtectRouterErrorToCode().validation_error.to_json_response(
conn.logger.trace_id
)
except ValidationError as ve:
conn.logger.error(f"数据验证错误: {ve}")
return ProtectRouterErrorToCode().validation_error.to_json_response(
conn.logger.trace_id
)
except HTTPError as he:
conn.logger.error(f"HTTP请求错误: {he}")
return ProtectRouterErrorToCode().remote_service_error.to_json_response(
conn.logger.trace_id
)
except Exception as e:
conn.logger.exception(e)
return ProtectRouterErrorToCode().server_error.to_json_response(
conn.logger.trace_id
)