🎉初次提交
This commit is contained in:
287
provider/aufe/aac/__init__.py
Normal file
287
provider/aufe/aac/__init__.py
Normal file
@@ -0,0 +1,287 @@
|
||||
from typing import Optional
|
||||
from urllib.parse import unquote
|
||||
from loguru import logger
|
||||
from provider.aufe.aac.model import (
|
||||
LoveACScoreInfo,
|
||||
LoveACScoreInfoResponse,
|
||||
LoveACScoreListResponse,
|
||||
SimpleResponse,
|
||||
ErrorLoveACScoreInfo,
|
||||
ErrorLoveACScoreInfoResponse,
|
||||
ErrorLoveACScoreListResponse,
|
||||
ErrorLoveACScoreCategory,
|
||||
)
|
||||
from provider.aufe.client import (
|
||||
AUFEConnection,
|
||||
AUFEConfig,
|
||||
activity_tracker,
|
||||
retry_async,
|
||||
AUFEConnectionError,
|
||||
AUFELoginError,
|
||||
AUFEParseError,
|
||||
RetryConfig
|
||||
)
|
||||
|
||||
|
||||
class AACConfig:
|
||||
"""AAC 模块配置常量"""
|
||||
BASE_URL = "http://api-dekt-ac-acxk-net.vpn2.aufe.edu.cn:8118"
|
||||
WEB_URL = "http://dekt-ac-acxk-net.vpn2.aufe.edu.cn:8118"
|
||||
LOGIN_SERVICE_URL = "http://uaap-aufe-edu-cn.vpn2.aufe.edu.cn:8118/cas/login?service=http%3a%2f%2fapi.dekt.ac.acxk.net%2fUser%2fIndex%2fCoreLoginCallback%3fisCASGateway%3dtrue"
|
||||
|
||||
|
||||
@retry_async()
|
||||
async def get_system_token(vpn_connection: AUFEConnection) -> Optional[str]:
|
||||
"""
|
||||
获取系统令牌 (sys_token)
|
||||
|
||||
Args:
|
||||
vpn_connection: VPN连接实例
|
||||
|
||||
Returns:
|
||||
Optional[str]: 系统令牌,失败时返回None
|
||||
|
||||
Raises:
|
||||
AUFEConnectionError: 连接失败
|
||||
AUFEParseError: 令牌解析失败
|
||||
"""
|
||||
try:
|
||||
next_location = AACConfig.LOGIN_SERVICE_URL
|
||||
max_redirects = 10 # 防止无限重定向
|
||||
redirect_count = 0
|
||||
|
||||
while redirect_count < max_redirects:
|
||||
response = await vpn_connection.requester().get(
|
||||
next_location, follow_redirects=False
|
||||
)
|
||||
|
||||
# 如果是重定向,继续跟踪
|
||||
if response.status_code in (301, 302, 303, 307, 308):
|
||||
next_location = response.headers.get("Location")
|
||||
if not next_location:
|
||||
raise AUFEParseError("重定向响应中缺少Location头")
|
||||
|
||||
logger.debug(f"重定向到: {next_location}")
|
||||
redirect_count += 1
|
||||
|
||||
if "register?ticket=" in next_location:
|
||||
logger.info(f"重定向到爱安财注册页面: {next_location}")
|
||||
try:
|
||||
sys_token = next_location.split("ticket=")[-1]
|
||||
# URL编码转为正常字符串
|
||||
sys_token = unquote(sys_token)
|
||||
if sys_token:
|
||||
logger.info(f"获取到系统令牌: {sys_token[:10]}...")
|
||||
return sys_token
|
||||
else:
|
||||
raise AUFEParseError("提取的系统令牌为空")
|
||||
except Exception as e:
|
||||
raise AUFEParseError(f"解析系统令牌失败: {str(e)}") from e
|
||||
else:
|
||||
break
|
||||
|
||||
if redirect_count >= max_redirects:
|
||||
raise AUFEConnectionError(f"重定向次数过多 ({max_redirects})")
|
||||
|
||||
raise AUFEParseError("未能从重定向中获取到系统令牌")
|
||||
|
||||
except (AUFEConnectionError, AUFEParseError):
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"获取系统令牌异常: {str(e)}")
|
||||
raise AUFEConnectionError(f"获取系统令牌失败: {str(e)}") from e
|
||||
|
||||
|
||||
class AACClient:
|
||||
"""爱安财系统客户端"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vpn_connection: AUFEConnection,
|
||||
ticket: Optional[str] = None,
|
||||
retry_config: Optional[RetryConfig] = None
|
||||
):
|
||||
"""
|
||||
初始化爱安财系统客户端
|
||||
|
||||
Args:
|
||||
vpn_connection: VPN连接实例
|
||||
ticket: 系统令牌
|
||||
retry_config: 重试配置
|
||||
"""
|
||||
self.vpn_connection = vpn_connection
|
||||
self.base_url = AACConfig.BASE_URL.rstrip("/")
|
||||
self.web_url = AACConfig.WEB_URL.rstrip("/")
|
||||
self.twfid = vpn_connection.get_twfid()
|
||||
self.system_token: Optional[str] = ticket
|
||||
self.retry_config = retry_config or RetryConfig()
|
||||
|
||||
logger.info(
|
||||
f"爱安财系统客户端初始化: base_url={self.base_url}, web_url={self.web_url}"
|
||||
)
|
||||
|
||||
def _get_default_headers(self) -> dict:
|
||||
"""获取默认请求头"""
|
||||
return {
|
||||
**AUFEConfig.DEFAULT_HEADERS,
|
||||
"ticket": self.system_token or "",
|
||||
"sdp-app-session": self.twfid or "",
|
||||
}
|
||||
|
||||
@activity_tracker
|
||||
@retry_async()
|
||||
async def validate_connection(self) -> bool:
|
||||
"""
|
||||
验证爱安财系统连接
|
||||
|
||||
Returns:
|
||||
bool: 连接是否有效
|
||||
|
||||
Raises:
|
||||
AUFEConnectionError: 连接失败
|
||||
"""
|
||||
try:
|
||||
headers = AUFEConfig.DEFAULT_HEADERS.copy()
|
||||
|
||||
response = await self.vpn_connection.requester().get(
|
||||
f"{self.web_url}/", headers=headers
|
||||
)
|
||||
is_valid = response.status_code == 200
|
||||
|
||||
logger.info(
|
||||
f"爱安财系统连接验证结果: {'有效' if is_valid else '无效'} (HTTP状态码: {response.status_code})"
|
||||
)
|
||||
|
||||
if not is_valid:
|
||||
raise AUFEConnectionError(f"爱安财系统连接验证失败,状态码: {response.status_code}")
|
||||
|
||||
return is_valid
|
||||
|
||||
except AUFEConnectionError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"验证爱安财系统连接异常: {str(e)}")
|
||||
raise AUFEConnectionError(f"验证连接失败: {str(e)}") from e
|
||||
|
||||
@activity_tracker
|
||||
async def fetch_score_info(self) -> LoveACScoreInfo:
|
||||
"""
|
||||
获取爱安财总分信息,使用重试机制
|
||||
|
||||
Returns:
|
||||
LoveACScoreInfo: 总分信息,失败时返回错误模型
|
||||
"""
|
||||
try:
|
||||
logger.info("开始获取爱安财总分信息")
|
||||
|
||||
headers = self._get_default_headers()
|
||||
|
||||
# 使用新的重试机制
|
||||
score_response = await self.vpn_connection.model_request(
|
||||
model=LoveACScoreInfoResponse,
|
||||
url=f"{self.base_url}/User/Center/DoGetScoreInfo?sf_request_type=ajax",
|
||||
method="POST",
|
||||
headers=headers,
|
||||
data={}, # 空的POST请求体
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
if score_response and score_response.code == 0 and score_response.data:
|
||||
logger.info(
|
||||
f"爱安财总分信息获取成功: {score_response.data.total_score}分"
|
||||
)
|
||||
return score_response.data
|
||||
else:
|
||||
error_msg = score_response.msg if score_response else '未知错误'
|
||||
logger.error(f"获取爱安财总分信息失败: {error_msg}")
|
||||
# 返回错误模型
|
||||
return ErrorLoveACScoreInfo(
|
||||
TotalScore=-1.0,
|
||||
IsTypeAdopt=False,
|
||||
TypeAdoptResult=f"请求失败: {error_msg}",
|
||||
)
|
||||
except (AUFEConnectionError, AUFEParseError) as e:
|
||||
logger.error(f"获取爱安财总分信息失败: {str(e)}")
|
||||
return ErrorLoveACScoreInfo(
|
||||
TotalScore=-1.0,
|
||||
IsTypeAdopt=False,
|
||||
TypeAdoptResult=f"请求失败: {str(e)}",
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"获取爱安财总分信息异常: {str(e)}")
|
||||
# 返回错误模型
|
||||
return ErrorLoveACScoreInfo(
|
||||
TotalScore=-1.0,
|
||||
IsTypeAdopt=False,
|
||||
TypeAdoptResult="系统错误,请稍后重试",
|
||||
)
|
||||
|
||||
@activity_tracker
|
||||
async def fetch_score_list(
|
||||
self, page_index: int = 1, page_size: int = 10
|
||||
) -> LoveACScoreListResponse:
|
||||
"""
|
||||
获取爱安财分数列表,使用重试机制
|
||||
|
||||
Args:
|
||||
page_index: 页码,默认为1
|
||||
page_size: 每页大小,默认为10
|
||||
|
||||
Returns:
|
||||
LoveACScoreListResponse: 分数列表响应,失败时返回错误模型
|
||||
"""
|
||||
def _create_error_response(error_msg: str) -> ErrorLoveACScoreListResponse:
|
||||
"""创建错误响应模型"""
|
||||
return ErrorLoveACScoreListResponse(
|
||||
code=-1,
|
||||
msg=error_msg,
|
||||
data=[
|
||||
ErrorLoveACScoreCategory(
|
||||
ID="error",
|
||||
ShowNum=-1,
|
||||
TypeName="请求失败",
|
||||
TotalScore=-1.0,
|
||||
children=[],
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
try:
|
||||
logger.info(
|
||||
f"开始获取爱安财分数列表,页码: {page_index}, 每页大小: {page_size}"
|
||||
)
|
||||
|
||||
headers = self._get_default_headers()
|
||||
data = {"pageIndex": str(page_index), "pageSize": str(page_size)}
|
||||
|
||||
# 使用新的重试机制
|
||||
score_list_response = await self.vpn_connection.model_request(
|
||||
model=LoveACScoreListResponse,
|
||||
url=f"{self.base_url}/User/Center/DoGetScoreList?sf_request_type=ajax",
|
||||
method="POST",
|
||||
headers=headers,
|
||||
data=data,
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
if (
|
||||
score_list_response
|
||||
and score_list_response.code == 0
|
||||
and score_list_response.data
|
||||
):
|
||||
logger.info(
|
||||
f"爱安财分数列表获取成功,分类数量: {len(score_list_response.data)}"
|
||||
)
|
||||
return score_list_response
|
||||
else:
|
||||
error_msg = score_list_response.msg if score_list_response else '未知错误'
|
||||
logger.error(f"获取爱安财分数列表失败: {error_msg}")
|
||||
return _create_error_response(f"请求失败: {error_msg}")
|
||||
|
||||
except (AUFEConnectionError, AUFEParseError) as e:
|
||||
logger.error(f"获取爱安财分数列表失败: {str(e)}")
|
||||
return _create_error_response(f"请求失败: {str(e)}")
|
||||
except Exception as e:
|
||||
logger.error(f"获取爱安财分数列表异常: {str(e)}")
|
||||
return _create_error_response("系统错误,已进行多次重试")
|
||||
|
||||
66
provider/aufe/aac/depends.py
Normal file
66
provider/aufe/aac/depends.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from fastapi import Depends, HTTPException
|
||||
from loguru import logger
|
||||
from provider.loveac.authme import fetch_user_by_token
|
||||
from provider.aufe.aac import AACClient, get_system_token
|
||||
from provider.aufe.client import AUFEConnection
|
||||
from database.user import User, AACTicket
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from database.creator import get_db_session
|
||||
from sqlalchemy import select
|
||||
|
||||
|
||||
async def get_aac_client(
|
||||
user: User = Depends(fetch_user_by_token),
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
) -> AACClient:
|
||||
"""
|
||||
获取AAC客户端
|
||||
:param user: 用户信息
|
||||
:return: AACClient
|
||||
:raises HTTPException: 如果用户无效或登录失败
|
||||
"""
|
||||
|
||||
if not user:
|
||||
raise HTTPException(status_code=400, detail="无效的令牌或用户不存在")
|
||||
aufe = AUFEConnection.create_or_get_connection("vpn.aufe.edu.cn", user.userid)
|
||||
if not aufe.login_status():
|
||||
userid = user.userid
|
||||
easyconnect_password = user.easyconnect_password
|
||||
if not await aufe.login(userid, easyconnect_password):
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="VPN登录失败,请检查用户名和密码",
|
||||
)
|
||||
if not aufe.uaap_login_status():
|
||||
userid = user.userid
|
||||
password = user.password
|
||||
if not await aufe.uaap_login(userid, password):
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="大学登录失败,请检查用户名和密码",
|
||||
)
|
||||
# 检查AAC Ticket是否存在
|
||||
async with db as session:
|
||||
result = await session.execute(
|
||||
select(AACTicket).where(AACTicket.userid == user.userid)
|
||||
)
|
||||
aac_ticket = result.scalars().first()
|
||||
if not aac_ticket:
|
||||
# 如果不存在,尝试获取新的AAC Ticket
|
||||
logger.info(f"用户 {user.userid} 的 AAC Ticket 不存在,正在获取新的 Ticket")
|
||||
aac_ticket = await get_system_token(aufe)
|
||||
if not aac_ticket:
|
||||
logger.error(f"用户 {user.userid} 获取 AAC Ticket 失败")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="获取AAC Ticket失败,请稍后再试",
|
||||
)
|
||||
# 保存到数据库
|
||||
async with db as session:
|
||||
session.add(AACTicket(userid=user.userid, aac_token=aac_ticket))
|
||||
await session.commit()
|
||||
logger.success(f"用户 {user.userid} 成功获取并保存新的 AAC Ticket")
|
||||
else:
|
||||
logger.info(f"用户 {user.userid} 使用现有的 AAC Ticket")
|
||||
aac_ticket = aac_ticket.aac_token
|
||||
return AACClient(aufe, aac_ticket)
|
||||
105
provider/aufe/aac/model.py
Normal file
105
provider/aufe/aac/model.py
Normal file
@@ -0,0 +1,105 @@
|
||||
from typing import List, Optional, Any
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class LoveACScoreInfo(BaseModel):
|
||||
"""爱安财总分信息"""
|
||||
|
||||
total_score: float = Field(0.0, alias="TotalScore")
|
||||
is_type_adopt: bool = Field(False, alias="IsTypeAdopt")
|
||||
type_adopt_result: str = Field("", alias="TypeAdoptResult")
|
||||
|
||||
|
||||
class LoveACScoreItem(BaseModel):
|
||||
"""爱安财分数明细条目"""
|
||||
|
||||
id: str = Field("", alias="ID")
|
||||
title: str = Field("", alias="Title")
|
||||
type_name: str = Field("", alias="TypeName")
|
||||
user_no: str = Field("", alias="UserNo")
|
||||
score: float = Field(0.0, alias="Score")
|
||||
add_time: str = Field("", alias="AddTime")
|
||||
|
||||
|
||||
class LoveACScoreCategory(BaseModel):
|
||||
"""爱安财分数类别"""
|
||||
|
||||
id: str = Field("", alias="ID")
|
||||
show_num: int = Field(0, alias="ShowNum")
|
||||
type_name: str = Field("", alias="TypeName")
|
||||
total_score: float = Field(0.0, alias="TotalScore")
|
||||
children: List[LoveACScoreItem] = Field([], alias="children")
|
||||
|
||||
|
||||
class LoveACBaseResponse(BaseModel):
|
||||
"""爱安财系统响应基础模型"""
|
||||
|
||||
code: int = 0
|
||||
msg: str = ""
|
||||
data: Any = None
|
||||
|
||||
|
||||
class LoveACScoreInfoResponse(LoveACBaseResponse):
|
||||
"""爱安财总分响应"""
|
||||
|
||||
data: Optional[LoveACScoreInfo] = None
|
||||
|
||||
|
||||
class LoveACScoreListResponse(LoveACBaseResponse):
|
||||
"""爱安财分数列表响应"""
|
||||
|
||||
data: Optional[List[LoveACScoreCategory]] = None
|
||||
|
||||
|
||||
class SimpleResponse(BaseModel):
|
||||
"""简单响应类,用于解析基本的JSON结构"""
|
||||
|
||||
code: int = 0
|
||||
msg: str = ""
|
||||
data: Any = None
|
||||
|
||||
|
||||
class ErrorLoveACScoreInfo(LoveACScoreInfo):
|
||||
"""错误的爱安财总分信息模型,用于重试失败时返回"""
|
||||
|
||||
total_score: float = Field(-1.0, alias="TotalScore")
|
||||
is_type_adopt: bool = Field(False, alias="IsTypeAdopt")
|
||||
type_adopt_result: str = Field("请求失败,请稍后重试", alias="TypeAdoptResult")
|
||||
|
||||
|
||||
class ErrorLoveACScoreCategory(BaseModel):
|
||||
"""错误的爱安财分数类别模型"""
|
||||
|
||||
id: str = Field("error", alias="ID")
|
||||
show_num: int = Field(-1, alias="ShowNum")
|
||||
type_name: str = Field("请求失败", alias="TypeName")
|
||||
total_score: float = Field(-1.0, alias="TotalScore")
|
||||
children: List[LoveACScoreItem] = Field([], alias="children")
|
||||
|
||||
|
||||
class ErrorLoveACBaseResponse(BaseModel):
|
||||
"""错误的爱安财系统响应基础模型"""
|
||||
|
||||
code: int = -1
|
||||
msg: str = "网络请求失败,已进行多次重试"
|
||||
data: Any = None
|
||||
|
||||
|
||||
class ErrorLoveACScoreInfoResponse(ErrorLoveACBaseResponse):
|
||||
"""错误的爱安财总分响应"""
|
||||
|
||||
data: Optional[ErrorLoveACScoreInfo] = ErrorLoveACScoreInfo(
|
||||
TotalScore=-1.0, IsTypeAdopt=False, TypeAdoptResult="请求失败,请稍后重试"
|
||||
)
|
||||
|
||||
|
||||
class ErrorLoveACScoreListResponse(LoveACScoreListResponse):
|
||||
"""错误的爱安财分数列表响应"""
|
||||
|
||||
code: int = -1
|
||||
msg: str = "网络请求失败,已进行多次重试"
|
||||
data: Optional[List[ErrorLoveACScoreCategory]] = [
|
||||
ErrorLoveACScoreCategory(
|
||||
ID="error", ShowNum=-1, TypeName="请求失败", TotalScore=-1.0, children=[]
|
||||
)
|
||||
]
|
||||
1176
provider/aufe/client.py
Normal file
1176
provider/aufe/client.py
Normal file
File diff suppressed because it is too large
Load Diff
1328
provider/aufe/jwc/__init__.py
Normal file
1328
provider/aufe/jwc/__init__.py
Normal file
File diff suppressed because it is too large
Load Diff
36
provider/aufe/jwc/depends.py
Normal file
36
provider/aufe/jwc/depends.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from fastapi import Depends, HTTPException
|
||||
from provider.loveac.authme import fetch_user_by_token
|
||||
from provider.aufe.jwc import JWCClient
|
||||
from provider.aufe.client import AUFEConnection
|
||||
from database.user import User
|
||||
|
||||
|
||||
async def get_jwc_client(
|
||||
user: User = Depends(fetch_user_by_token),
|
||||
) -> JWCClient:
|
||||
"""
|
||||
获取教务处客户端
|
||||
:param authme_request: AuthmeRequest
|
||||
:return: JWCClient
|
||||
"""
|
||||
|
||||
if not user:
|
||||
raise HTTPException(status_code=400, detail="无效的令牌或用户不存在")
|
||||
aufe = AUFEConnection.create_or_get_connection("vpn.aufe.edu.cn", user.userid)
|
||||
if not aufe.login_status():
|
||||
userid = user.userid
|
||||
easyconnect_password = user.easyconnect_password
|
||||
if not await aufe.login(userid, easyconnect_password):
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="VPN登录失败,请检查用户名和密码",
|
||||
)
|
||||
if not aufe.uaap_login_status():
|
||||
userid = user.userid
|
||||
password = user.password
|
||||
if not await aufe.uaap_login(userid, password):
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="大学登录失败,请检查用户名和密码",
|
||||
)
|
||||
return JWCClient(aufe)
|
||||
296
provider/aufe/jwc/model.py
Normal file
296
provider/aufe/jwc/model.py
Normal file
@@ -0,0 +1,296 @@
|
||||
from typing import Dict, List, Optional, Any
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class AcademicDataItem(BaseModel):
|
||||
"""学术信息数据项,用于直接反序列化JSON数组中的元素"""
|
||||
|
||||
completed_courses: int = Field(0, alias="courseNum")
|
||||
failed_courses: int = Field(0, alias="coursePas")
|
||||
gpa: float = Field(0, alias="gpa")
|
||||
current_term: str = Field("", alias="zxjxjhh")
|
||||
pending_courses: int = Field(0, alias="courseNum_bxqyxd")
|
||||
|
||||
|
||||
class AcademicInfo(BaseModel):
|
||||
"""学术信息数据模型 - 兼容旧版API"""
|
||||
|
||||
completed_courses: int = Field(0, alias="count")
|
||||
failed_courses: int = Field(0, alias="countNotPass")
|
||||
gpa: float = Field(0, alias="gpa")
|
||||
|
||||
|
||||
# ==================== 学期和成绩相关模型 ====================
|
||||
|
||||
|
||||
class TermInfo(BaseModel):
|
||||
"""学期信息模型"""
|
||||
|
||||
term_id: str = Field("", description="学期ID,如:2024-2025-2-1")
|
||||
term_name: str = Field("", description="学期名称,如:2024-2025春季学期")
|
||||
|
||||
|
||||
class ScoreRecord(BaseModel):
|
||||
"""成绩记录模型"""
|
||||
|
||||
sequence: int = Field(0, description="序号")
|
||||
term_id: str = Field("", description="学期ID")
|
||||
course_code: str = Field("", description="课程代码")
|
||||
course_class: str = Field("", description="课程班级")
|
||||
course_name_cn: str = Field("", description="课程名称(中文)")
|
||||
course_name_en: str = Field("", description="课程名称(英文)")
|
||||
credits: str = Field("", description="学分")
|
||||
hours: int = Field(0, description="学时")
|
||||
course_type: str = Field("", description="课程性质")
|
||||
exam_type: str = Field("", description="考试性质")
|
||||
score: str = Field("", description="成绩")
|
||||
retake_score: Optional[str] = Field(None, description="重修成绩")
|
||||
makeup_score: Optional[str] = Field(None, description="补考成绩")
|
||||
|
||||
|
||||
class TermScoreResponse(BaseModel):
|
||||
"""学期成绩响应模型"""
|
||||
|
||||
page_size: int = Field(50, description="每页大小")
|
||||
page_num: int = Field(1, description="页码")
|
||||
total_count: int = Field(0, description="总记录数")
|
||||
records: List[ScoreRecord] = Field(default_factory=list, description="成绩记录列表")
|
||||
|
||||
|
||||
# ==================== 原有模型继续 ====================
|
||||
|
||||
|
||||
class TrainingPlanDataItem(BaseModel):
|
||||
"""培养方案数据项"""
|
||||
|
||||
plan_name: str = "" # 第一项为培养方案名称
|
||||
plan_id: str = "" # 第二项为培养方案ID
|
||||
|
||||
|
||||
class TrainingPlanResponseWrapper(BaseModel):
|
||||
"""培养方案响应模型"""
|
||||
|
||||
count: int = 0
|
||||
data: List[List[str]] = []
|
||||
|
||||
|
||||
class TrainingPlanInfo(BaseModel):
|
||||
"""培养方案信息模型 - 兼容旧版API"""
|
||||
|
||||
plan_name: str = Field("", alias="pyfa")
|
||||
current_term: str = Field("", alias="term")
|
||||
pending_courses: int = Field(0, alias="courseCount")
|
||||
major_name: str = Field("", alias="major")
|
||||
grade: str = Field("", alias="grade")
|
||||
|
||||
|
||||
class CourseSelectionStatusDirectResponse(BaseModel):
|
||||
"""选课状态响应模型新格式"""
|
||||
|
||||
term_name: str = Field("", alias="zxjxjhm")
|
||||
status_code: str = Field("", alias="retString")
|
||||
|
||||
|
||||
class CourseSelectionStatus(BaseModel):
|
||||
"""选课状态信息"""
|
||||
|
||||
can_select: bool = Field(False, alias="isCanSelect")
|
||||
start_time: str = Field("", alias="startTime")
|
||||
end_time: str = Field("", alias="endTime")
|
||||
|
||||
|
||||
class CourseId(BaseModel):
|
||||
"""课程ID信息"""
|
||||
|
||||
evaluated_people: str = Field("", alias="evaluatedPeople")
|
||||
coure_sequence_number: str = Field("", alias="coureSequenceNumber")
|
||||
evaluation_content_number: str = Field("", alias="evaluationContentNumber")
|
||||
|
||||
|
||||
class Questionnaire(BaseModel):
|
||||
"""问卷信息"""
|
||||
|
||||
questionnaire_number: str = Field("", alias="questionnaireNumber")
|
||||
questionnaire_name: str = Field("", alias="questionnaireName")
|
||||
|
||||
|
||||
class Course(BaseModel):
|
||||
"""课程基本信息"""
|
||||
|
||||
id: Optional[CourseId] = None
|
||||
questionnaire: Optional[Questionnaire] = Field(None, alias="questionnaire")
|
||||
evaluated_people: str = Field("", alias="evaluatedPeople")
|
||||
is_evaluated: str = Field("", alias="isEvaluated")
|
||||
evaluation_content: str = Field("", alias="evaluationContent")
|
||||
|
||||
|
||||
class CourseListResponse(BaseModel):
|
||||
"""课程列表响应"""
|
||||
|
||||
not_finished_num: int = Field(0, alias="notFinishedNum")
|
||||
evaluation_num: int = Field(0, alias="evaluationNum")
|
||||
data: List[Course] = Field(default_factory=list, alias="data")
|
||||
msg: str = Field("", alias="msg")
|
||||
result: str = "success" # 设置默认值
|
||||
|
||||
|
||||
class EvaluationResponse(BaseModel):
|
||||
"""评价提交响应"""
|
||||
|
||||
result: str = ""
|
||||
msg: str = ""
|
||||
data: Any = None
|
||||
|
||||
|
||||
class EvaluationRequestParam(BaseModel):
|
||||
"""评价请求参数"""
|
||||
|
||||
opt_type: str = "submit"
|
||||
token_value: str = ""
|
||||
questionnaire_code: str = ""
|
||||
evaluation_content: str = ""
|
||||
evaluated_people_number: str = ""
|
||||
count: str = ""
|
||||
zgpj: str = ""
|
||||
rating_items: Dict[str, str] = {}
|
||||
|
||||
def to_form_data(self) -> Dict[str, str]:
|
||||
"""将对象转换为表单数据映射"""
|
||||
form_data = {
|
||||
"optType": self.opt_type,
|
||||
"tokenValue": self.token_value,
|
||||
"questionnaireCode": self.questionnaire_code,
|
||||
"evaluationContent": self.evaluation_content,
|
||||
"evaluatedPeopleNumber": self.evaluated_people_number,
|
||||
"count": self.count,
|
||||
"zgpj": self.zgpj,
|
||||
}
|
||||
# 添加评分项
|
||||
form_data.update(self.rating_items)
|
||||
return form_data
|
||||
|
||||
|
||||
class ExamScheduleItem(BaseModel):
|
||||
"""考试安排项目 - 校统考格式"""
|
||||
|
||||
title: str = "" # 考试标题,包含课程名、时间、地点等信息
|
||||
start: str = "" # 考试日期 (YYYY-MM-DD)
|
||||
color: str = "" # 显示颜色
|
||||
|
||||
|
||||
class OtherExamRecord(BaseModel):
|
||||
"""其他考试记录"""
|
||||
|
||||
term_code: str = Field("", alias="ZXJXJHH") # 学期代码
|
||||
term_name: str = Field("", alias="ZXJXJHM") # 学期名称
|
||||
exam_name: str = Field("", alias="KSMC") # 考试名称
|
||||
course_code: str = Field("", alias="KCH") # 课程代码
|
||||
course_name: str = Field("", alias="KCM") # 课程名称
|
||||
class_number: str = Field("", alias="KXH") # 课序号
|
||||
student_id: str = Field("", alias="XH") # 学号
|
||||
student_name: str = Field("", alias="XM") # 姓名
|
||||
exam_location: str = Field("", alias="KSDD") # 考试地点
|
||||
exam_date: str = Field("", alias="KSRQ") # 考试日期
|
||||
exam_time: str = Field("", alias="KSSJ") # 考试时间
|
||||
note: str = Field("", alias="BZ") # 备注
|
||||
row_number: str = Field("", alias="RN") # 行号
|
||||
|
||||
|
||||
class OtherExamResponse(BaseModel):
|
||||
"""其他考试查询响应"""
|
||||
|
||||
page_size: int = Field(0, alias="pageSize")
|
||||
page_num: int = Field(0, alias="pageNum")
|
||||
page_context: Dict[str, int] = Field(default_factory=dict, alias="pageContext")
|
||||
records: List[OtherExamRecord] = Field(default_factory=list, alias="records")
|
||||
|
||||
|
||||
class UnifiedExamInfo(BaseModel):
|
||||
"""统一考试信息模型 - 对外提供的统一格式"""
|
||||
|
||||
course_name: str = "" # 课程名称
|
||||
exam_date: str = "" # 考试日期 (YYYY-MM-DD)
|
||||
exam_time: str = "" # 考试时间
|
||||
exam_location: str = "" # 考试地点
|
||||
exam_type: str = "" # 考试类型 (校统考/其他考试)
|
||||
note: str = "" # 备注信息
|
||||
|
||||
|
||||
class ExamInfoResponse(BaseModel):
|
||||
"""考试信息统一响应模型"""
|
||||
|
||||
exams: List[UnifiedExamInfo] = Field(default_factory=list)
|
||||
total_count: int = 0
|
||||
|
||||
|
||||
# ==================== 错误响应模型 ====================
|
||||
|
||||
|
||||
class ErrorAcademicInfo(AcademicInfo):
|
||||
"""错误的学术信息数据模型"""
|
||||
|
||||
completed_courses: int = Field(-1, alias="count")
|
||||
failed_courses: int = Field(-1, alias="countNotPass")
|
||||
gpa: float = Field(-1.0, alias="gpa")
|
||||
|
||||
|
||||
class ErrorTrainingPlanInfo(TrainingPlanInfo):
|
||||
"""错误的培养方案信息模型"""
|
||||
|
||||
plan_name: str = Field("请求失败,请稍后重试", alias="pyfa")
|
||||
current_term: str = Field("", alias="term")
|
||||
pending_courses: int = Field(-1, alias="courseCount")
|
||||
major_name: str = Field("请求失败", alias="major")
|
||||
grade: str = Field("", alias="grade")
|
||||
|
||||
|
||||
class ErrorCourseSelectionStatus(CourseSelectionStatus):
|
||||
"""错误的选课状态信息"""
|
||||
|
||||
can_select: bool = Field(False, alias="isCanSelect")
|
||||
start_time: str = Field("请求失败", alias="startTime")
|
||||
end_time: str = Field("请求失败", alias="endTime")
|
||||
|
||||
|
||||
class ErrorCourse(Course):
|
||||
"""错误的课程基本信息"""
|
||||
|
||||
id: Optional[CourseId] = None
|
||||
questionnaire: Optional[Questionnaire] = None
|
||||
evaluated_people: str = Field("请求失败", alias="evaluatedPeople")
|
||||
is_evaluated: str = Field("否", alias="isEvaluated")
|
||||
evaluation_content: str = Field("请求失败,请稍后重试", alias="evaluationContent")
|
||||
|
||||
|
||||
class ErrorCourseListResponse(CourseListResponse):
|
||||
"""错误的课程列表响应"""
|
||||
|
||||
not_finished_num: int = Field(-1, alias="notFinishedNum")
|
||||
evaluation_num: int = Field(-1, alias="evaluationNum")
|
||||
data: List[Course] = Field(default_factory=list, alias="data")
|
||||
msg: str = Field("网络请求失败,已进行多次重试", alias="msg")
|
||||
result: str = "failed"
|
||||
|
||||
|
||||
class ErrorEvaluationResponse(EvaluationResponse):
|
||||
"""错误的评价提交响应"""
|
||||
|
||||
result: str = "failed"
|
||||
msg: str = "网络请求失败,已进行多次重试"
|
||||
data: Any = None
|
||||
|
||||
|
||||
class ErrorExamInfoResponse(ExamInfoResponse):
|
||||
"""错误的考试信息响应模型"""
|
||||
|
||||
exams: List[UnifiedExamInfo] = Field(default_factory=list)
|
||||
total_count: int = -1
|
||||
|
||||
|
||||
class ErrorTermScoreResponse(BaseModel):
|
||||
"""错误的学期成绩响应模型"""
|
||||
|
||||
page_size: int = Field(-1, description="每页大小")
|
||||
page_num: int = Field(-1, description="页码")
|
||||
total_count: int = Field(-1, description="总记录数")
|
||||
records: List[ScoreRecord] = Field(default_factory=list, description="成绩记录列表")
|
||||
0
provider/aufe/labor_club/__init__.py
Normal file
0
provider/aufe/labor_club/__init__.py
Normal file
0
provider/aufe/labor_club/depends.py
Normal file
0
provider/aufe/labor_club/depends.py
Normal file
0
provider/aufe/labor_club/model.py
Normal file
0
provider/aufe/labor_club/model.py
Normal file
Reference in New Issue
Block a user