⚒️ 重大重构 LoveACE V2
引入了 mongodb 对数据库进行了一定程度的数据加密 性能改善 代码简化 统一错误模型和响应 使用 apifox 作为文档
This commit is contained in:
10
loveace/router/endpoint/ldjlb/__init__.py
Normal file
10
loveace/router/endpoint/ldjlb/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from loveace.router.endpoint.ldjlb.labor import ldjlb_labor_router
|
||||
|
||||
ldjlb_base_router = APIRouter(
|
||||
prefix="/ldjlb",
|
||||
tags=["劳动俱乐部"],
|
||||
)
|
||||
|
||||
ldjlb_base_router.include_router(ldjlb_labor_router)
|
||||
703
loveace/router/endpoint/ldjlb/labor.py
Normal file
703
loveace/router/endpoint/ldjlb/labor.py
Normal file
@@ -0,0 +1,703 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi.responses import JSONResponse
|
||||
from httpx import Headers, HTTPError
|
||||
from pydantic import ValidationError
|
||||
|
||||
from loveace.router.endpoint.ldjlb.model.base import LDJLBConfig
|
||||
from loveace.router.endpoint.ldjlb.model.ldjlb import (
|
||||
ActivityDetailResponse,
|
||||
LDJLBActivityListResponse,
|
||||
LDJLBApplyResponse,
|
||||
LDJLBClubListResponse,
|
||||
LDJLBProgressInfo,
|
||||
ScanSignRequest,
|
||||
ScanSignResponse,
|
||||
SignListResponse,
|
||||
)
|
||||
from loveace.router.endpoint.ldjlb.utils.ldjlb_ticket import get_ldjlb_header
|
||||
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
|
||||
|
||||
ldjlb_labor_router = APIRouter(
|
||||
prefix="/labor",
|
||||
responses=ProtectRouterErrorToCode().gen_code_table(),
|
||||
)
|
||||
|
||||
ENDPOINT = {
|
||||
"progress": "/User/Activity/GetMyFinishCount?sf_request_type=ajax",
|
||||
"joined_activities": "/User/Activity/DoGetJoinPageList?sf_request_type=ajax",
|
||||
"joined_clubs": "/User/Club/DoGetJoinList?sf_request_type=ajax",
|
||||
"club_activities": "/User/Activity/DoGetPageList?sf_request_type=ajax",
|
||||
"apply_join": "/User/Activity/DoApplyJoin?sf_request_type=ajax",
|
||||
"scan_sign": "/User/Center/DoScanSignQRImage",
|
||||
"sign_list": "/User/Activity/DoGetSignList",
|
||||
"activity_detail": "/User/Activity/DoGetDetail",
|
||||
}
|
||||
|
||||
|
||||
@ldjlb_labor_router.get(
|
||||
"/progress",
|
||||
response_model=UniResponseModel[LDJLBProgressInfo],
|
||||
summary="获取劳动俱乐部修课进度",
|
||||
)
|
||||
async def get_labor_progress(
|
||||
conn: AUFEConnection = Depends(get_aufe_conn),
|
||||
headers: Headers = Depends(get_ldjlb_header),
|
||||
) -> UniResponseModel[LDJLBProgressInfo] | JSONResponse:
|
||||
"""
|
||||
获取用户的劳动俱乐部修课进度
|
||||
|
||||
✅ 功能特性:
|
||||
- 获取已完成的劳动活动数量
|
||||
- 计算修课进度百分比(满分10次)
|
||||
- 实时从劳动俱乐部服务获取最新数据
|
||||
|
||||
💡 使用场景:
|
||||
- 个人中心显示劳动修课进度
|
||||
- 检查是否满足劳动教育要求
|
||||
- 了解还需完成的活动次数
|
||||
|
||||
Returns:
|
||||
LDJLBProgressInfo: 包含已完成次数和进度百分比
|
||||
"""
|
||||
try:
|
||||
conn.logger.info("开始获取劳动俱乐部修课进度")
|
||||
response = await conn.client.post(
|
||||
url=LDJLBConfig().to_full_url(ENDPOINT["progress"]),
|
||||
data={},
|
||||
headers=headers,
|
||||
timeout=6000,
|
||||
)
|
||||
if response.status_code != 200:
|
||||
conn.logger.error(
|
||||
f"获取劳动俱乐部修课进度失败,HTTP状态码: {response.status_code}"
|
||||
)
|
||||
return ProtectRouterErrorToCode().remote_service_error.to_json_response(
|
||||
conn.logger.trace_id, "获取劳动俱乐部修课进度失败,请稍后重试"
|
||||
)
|
||||
data = response.json()
|
||||
if data.get("code") != 0:
|
||||
conn.logger.error(
|
||||
f"获取劳动俱乐部修课进度失败,响应代码: {data.get('code')}"
|
||||
)
|
||||
return ProtectRouterErrorToCode().remote_service_error.to_json_response(
|
||||
conn.logger.trace_id, "获取劳动俱乐部修课进度失败,请稍后重试"
|
||||
)
|
||||
try:
|
||||
progress_info = LDJLBProgressInfo.model_validate(data)
|
||||
conn.logger.info(
|
||||
f"成功获取劳动俱乐部修课进度: 已完成 {progress_info.finish_count}/10 次"
|
||||
)
|
||||
return UniResponseModel[LDJLBProgressInfo](
|
||||
success=True,
|
||||
data=progress_info,
|
||||
message="获取劳动俱乐部修课进度成功",
|
||||
error=None,
|
||||
)
|
||||
except ValidationError as ve:
|
||||
conn.logger.error(f"解析劳动俱乐部修课进度失败: {str(ve)}")
|
||||
return ProtectRouterErrorToCode().validation_error.to_json_response(
|
||||
conn.logger.trace_id, "解析劳动俱乐部修课进度失败,请稍后重试"
|
||||
)
|
||||
|
||||
except HTTPError as he:
|
||||
conn.logger.error(f"获取劳动俱乐部修课进度异常: {str(he)}")
|
||||
return ProtectRouterErrorToCode().remote_service_error.to_json_response(
|
||||
conn.logger.trace_id, "获取劳动俱乐部修课进度异常,请稍后重试"
|
||||
)
|
||||
except Exception as e:
|
||||
conn.logger.error(f"获取劳动俱乐部修课进度未知异常: {str(e)}")
|
||||
return ProtectRouterErrorToCode().unknown_error.to_json_response(
|
||||
conn.logger.trace_id, "获取劳动俱乐部修课进度未知异常,请稍后重试"
|
||||
)
|
||||
|
||||
|
||||
@ldjlb_labor_router.get(
|
||||
"/joined/activities",
|
||||
response_model=UniResponseModel[LDJLBActivityListResponse],
|
||||
summary="获取已加入的劳动活动列表",
|
||||
)
|
||||
async def get_joined_activities(
|
||||
conn: AUFEConnection = Depends(get_aufe_conn),
|
||||
headers: Headers = Depends(get_ldjlb_header),
|
||||
) -> UniResponseModel[LDJLBActivityListResponse] | JSONResponse:
|
||||
"""
|
||||
获取用户已加入的劳动活动列表
|
||||
|
||||
✅ 功能特性:
|
||||
- 获取用户已报名的所有劳动活动
|
||||
- 包含活动状态、时间、负责人等详细信息
|
||||
- 支持分页查询
|
||||
|
||||
💡 使用场景:
|
||||
- 查看我的劳动活动页面
|
||||
- 了解已报名活动的详细信息
|
||||
- 查看活动进度和状态
|
||||
|
||||
Returns:
|
||||
LDJLBActivityListResponse: 包含活动列表和分页信息
|
||||
"""
|
||||
try:
|
||||
conn.logger.info("开始获取已加入的劳动活动列表")
|
||||
response = await conn.client.post(
|
||||
url=LDJLBConfig().to_full_url(ENDPOINT["joined_activities"]),
|
||||
data={},
|
||||
headers=headers,
|
||||
timeout=6000,
|
||||
)
|
||||
if response.status_code != 200:
|
||||
conn.logger.error(
|
||||
f"获取已加入的劳动活动列表失败,HTTP状态码: {response.status_code}"
|
||||
)
|
||||
return ProtectRouterErrorToCode().remote_service_error.to_json_response(
|
||||
conn.logger.trace_id, "获取已加入的劳动活动列表失败,请稍后重试"
|
||||
)
|
||||
data = response.json()
|
||||
if data.get("code") != 0:
|
||||
conn.logger.error(
|
||||
f"获取已加入的劳动活动列表失败,响应代码: {data.get('code')}"
|
||||
)
|
||||
return ProtectRouterErrorToCode().remote_service_error.to_json_response(
|
||||
conn.logger.trace_id, "获取已加入的劳动活动列表失败,请稍后重试"
|
||||
)
|
||||
try:
|
||||
activity_list = LDJLBActivityListResponse.model_validate(data)
|
||||
conn.logger.info(
|
||||
f"成功获取已加入的劳动活动列表,共 {len(activity_list.activities)} 个活动"
|
||||
)
|
||||
return UniResponseModel[LDJLBActivityListResponse](
|
||||
success=True,
|
||||
data=activity_list,
|
||||
message="获取已加入的劳动活动列表成功",
|
||||
error=None,
|
||||
)
|
||||
except ValidationError as ve:
|
||||
conn.logger.error(f"解析已加入的劳动活动列表失败: {str(ve)}")
|
||||
return ProtectRouterErrorToCode().validation_error.to_json_response(
|
||||
conn.logger.trace_id, "解析已加入的劳动活动列表失败,请稍后重试"
|
||||
)
|
||||
|
||||
except HTTPError as he:
|
||||
conn.logger.error(f"获取已加入的劳动活动列表异常: {str(he)}")
|
||||
return ProtectRouterErrorToCode().remote_service_error.to_json_response(
|
||||
conn.logger.trace_id, "获取已加入的劳动活动列表异常,请稍后重试"
|
||||
)
|
||||
except Exception as e:
|
||||
conn.logger.error(f"获取已加入的劳动活动列表未知异常: {str(e)}")
|
||||
return ProtectRouterErrorToCode().unknown_error.to_json_response(
|
||||
conn.logger.trace_id, "获取已加入的劳动活动列表未知异常,请稍后重试"
|
||||
)
|
||||
|
||||
|
||||
@ldjlb_labor_router.get(
|
||||
"/joined/clubs",
|
||||
response_model=UniResponseModel[LDJLBClubListResponse],
|
||||
summary="获取已加入的劳动俱乐部列表",
|
||||
)
|
||||
async def get_joined_clubs(
|
||||
conn: AUFEConnection = Depends(get_aufe_conn),
|
||||
headers: Headers = Depends(get_ldjlb_header),
|
||||
) -> UniResponseModel[LDJLBClubListResponse] | JSONResponse:
|
||||
"""
|
||||
获取用户已加入的劳动俱乐部列表
|
||||
|
||||
✅ 功能特性:
|
||||
- 获取用户已加入的所有劳动俱乐部
|
||||
- 包含俱乐部详细信息、负责人、成员数等
|
||||
- 用于后续查询俱乐部活动
|
||||
|
||||
💡 使用场景:
|
||||
- 查看我的俱乐部页面
|
||||
- 获取俱乐部ID用于查询活动
|
||||
- 了解俱乐部详细信息
|
||||
|
||||
Returns:
|
||||
LDJLBClubListResponse: 包含俱乐部列表
|
||||
"""
|
||||
try:
|
||||
conn.logger.info("开始获取已加入的劳动俱乐部列表")
|
||||
response = await conn.client.post(
|
||||
url=LDJLBConfig().to_full_url(ENDPOINT["joined_clubs"]),
|
||||
data={},
|
||||
headers=headers,
|
||||
timeout=6000,
|
||||
)
|
||||
if response.status_code != 200:
|
||||
conn.logger.error(
|
||||
f"获取已加入的劳动俱乐部列表失败,HTTP状态码: {response.status_code}"
|
||||
)
|
||||
return ProtectRouterErrorToCode().remote_service_error.to_json_response(
|
||||
conn.logger.trace_id, "获取已加入的劳动俱乐部列表失败,请稍后重试"
|
||||
)
|
||||
data = response.json()
|
||||
if data.get("code") != 0:
|
||||
conn.logger.error(
|
||||
f"获取已加入的劳动俱乐部列表失败,响应代码: {data.get('code')}"
|
||||
)
|
||||
return ProtectRouterErrorToCode().remote_service_error.to_json_response(
|
||||
conn.logger.trace_id, "获取已加入的劳动俱乐部列表失败,请稍后重试"
|
||||
)
|
||||
try:
|
||||
club_list = LDJLBClubListResponse.model_validate(data)
|
||||
conn.logger.info(
|
||||
f"成功获取已加入的劳动俱乐部列表,共 {len(club_list.clubs)} 个俱乐部"
|
||||
)
|
||||
return UniResponseModel[LDJLBClubListResponse](
|
||||
success=True,
|
||||
data=club_list,
|
||||
message="获取已加入的劳动俱乐部列表成功",
|
||||
error=None,
|
||||
)
|
||||
except ValidationError as ve:
|
||||
conn.logger.error(f"解析已加入的劳动俱乐部列表失败: {str(ve)}")
|
||||
return ProtectRouterErrorToCode().validation_error.to_json_response(
|
||||
conn.logger.trace_id, "解析已加入的劳动俱乐部列表失败,请稍后重试"
|
||||
)
|
||||
|
||||
except HTTPError as he:
|
||||
conn.logger.error(f"获取已加入的劳动俱乐部列表异常: {str(he)}")
|
||||
return ProtectRouterErrorToCode().remote_service_error.to_json_response(
|
||||
conn.logger.trace_id, "获取已加入的劳动俱乐部列表异常,请稍后重试"
|
||||
)
|
||||
except Exception as e:
|
||||
conn.logger.error(f"获取已加入的劳动俱乐部列表未知异常: {str(e)}")
|
||||
return ProtectRouterErrorToCode().unknown_error.to_json_response(
|
||||
conn.logger.trace_id, "获取已加入的劳动俱乐部列表未知异常,请稍后重试"
|
||||
)
|
||||
|
||||
|
||||
@ldjlb_labor_router.get(
|
||||
"/club/{club_id}/activities",
|
||||
response_model=UniResponseModel[LDJLBActivityListResponse],
|
||||
summary="获取指定俱乐部的活动列表",
|
||||
)
|
||||
async def get_club_activities(
|
||||
club_id: str,
|
||||
page_index: int = 1,
|
||||
page_size: int = 100,
|
||||
conn: AUFEConnection = Depends(get_aufe_conn),
|
||||
headers: Headers = Depends(get_ldjlb_header),
|
||||
) -> UniResponseModel[LDJLBActivityListResponse] | JSONResponse:
|
||||
"""
|
||||
获取指定俱乐部的活动列表
|
||||
|
||||
✅ 功能特性:
|
||||
- 根据俱乐部ID获取该俱乐部的所有活动
|
||||
- 支持分页查询(默认pageSize=100)
|
||||
- 包含活动的详细信息和报名状态
|
||||
|
||||
💡 使用场景:
|
||||
- 浏览某个俱乐部的活动列表
|
||||
- 查找可报名的劳动活动
|
||||
- 了解活动详情准备报名
|
||||
|
||||
Args:
|
||||
club_id: 俱乐部ID
|
||||
page_index: 页码,默认1
|
||||
page_size: 每页大小,默认100
|
||||
|
||||
Returns:
|
||||
LDJLBActivityListResponse: 包含活动列表和分页信息
|
||||
"""
|
||||
try:
|
||||
conn.logger.info(f"开始获取俱乐部 {club_id} 的活动列表")
|
||||
response = await conn.client.post(
|
||||
url=LDJLBConfig().to_full_url(ENDPOINT["club_activities"])
|
||||
+ f"?pageIndex={page_index}&pageSize={page_size}&clubID={club_id}",
|
||||
data={},
|
||||
headers=headers,
|
||||
timeout=6000,
|
||||
)
|
||||
if response.status_code != 200:
|
||||
conn.logger.error(
|
||||
f"获取俱乐部活动列表失败,HTTP状态码: {response.status_code}"
|
||||
)
|
||||
return ProtectRouterErrorToCode().remote_service_error.to_json_response(
|
||||
conn.logger.trace_id, "获取俱乐部活动列表失败,请稍后重试"
|
||||
)
|
||||
data = response.json()
|
||||
if data.get("code") != 0:
|
||||
conn.logger.error(f"获取俱乐部活动列表失败,响应代码: {data.get('code')}")
|
||||
return ProtectRouterErrorToCode().remote_service_error.to_json_response(
|
||||
conn.logger.trace_id, "获取俱乐部活动列表失败,请稍后重试"
|
||||
)
|
||||
try:
|
||||
activity_list = LDJLBActivityListResponse.model_validate(data)
|
||||
conn.logger.info(
|
||||
f"成功获取俱乐部 {club_id} 的活动列表,共 {len(activity_list.activities)} 个活动"
|
||||
)
|
||||
return UniResponseModel[LDJLBActivityListResponse](
|
||||
success=True,
|
||||
data=activity_list,
|
||||
message="获取俱乐部活动列表成功",
|
||||
error=None,
|
||||
)
|
||||
except ValidationError as ve:
|
||||
conn.logger.error(f"解析俱乐部活动列表失败: {str(ve)}")
|
||||
return ProtectRouterErrorToCode().validation_error.to_json_response(
|
||||
conn.logger.trace_id, "解析俱乐部活动列表失败,请稍后重试"
|
||||
)
|
||||
|
||||
except HTTPError as he:
|
||||
conn.logger.error(f"获取俱乐部活动列表异常: {str(he)}")
|
||||
return ProtectRouterErrorToCode().remote_service_error.to_json_response(
|
||||
conn.logger.trace_id, "获取俱乐部活动列表异常,请稍后重试"
|
||||
)
|
||||
except Exception as e:
|
||||
conn.logger.error(f"获取俱乐部活动列表未知异常: {str(e)}")
|
||||
return ProtectRouterErrorToCode().unknown_error.to_json_response(
|
||||
conn.logger.trace_id, "获取俱乐部活动列表未知异常,请稍后重试"
|
||||
)
|
||||
|
||||
|
||||
@ldjlb_labor_router.post(
|
||||
"/activity/{activity_id}/apply",
|
||||
response_model=UniResponseModel[LDJLBApplyResponse],
|
||||
summary="报名参加劳动活动",
|
||||
)
|
||||
async def apply_activity(
|
||||
activity_id: str,
|
||||
reason: str = "加入课程",
|
||||
conn: AUFEConnection = Depends(get_aufe_conn),
|
||||
headers: Headers = Depends(get_ldjlb_header),
|
||||
) -> UniResponseModel[LDJLBApplyResponse] | JSONResponse:
|
||||
"""
|
||||
报名参加劳动活动
|
||||
|
||||
✅ 功能特性:
|
||||
- 报名参加指定的劳动活动
|
||||
- 自动提交报名申请
|
||||
- 返回报名结果
|
||||
|
||||
💡 使用场景:
|
||||
- 用户点击报名按钮
|
||||
- 批量报名多个活动
|
||||
- 自动化报名流程
|
||||
|
||||
Args:
|
||||
activity_id: 活动ID
|
||||
reason: 报名理由,默认"加入课程"
|
||||
|
||||
Returns:
|
||||
LDJLBApplyResponse: 包含报名结果代码和消息
|
||||
"""
|
||||
try:
|
||||
conn.logger.info(f"开始报名活动 {activity_id}")
|
||||
response = await conn.client.post(
|
||||
url=LDJLBConfig().to_full_url(ENDPOINT["apply_join"]),
|
||||
data={"activityID": activity_id, "reason": reason},
|
||||
headers=headers,
|
||||
timeout=6000,
|
||||
)
|
||||
if response.status_code != 200:
|
||||
conn.logger.error(f"报名活动失败,HTTP状态码: {response.status_code}")
|
||||
return ProtectRouterErrorToCode().remote_service_error.to_json_response(
|
||||
conn.logger.trace_id, "报名活动失败,请稍后重试"
|
||||
)
|
||||
data = response.json()
|
||||
try:
|
||||
apply_result = LDJLBApplyResponse.model_validate(data)
|
||||
if apply_result.code == 0:
|
||||
conn.logger.success(f"成功报名活动 {activity_id}: {apply_result.msg}")
|
||||
else:
|
||||
conn.logger.warning(
|
||||
f"报名活动 {activity_id} 失败: {apply_result.msg} (code: {apply_result.code})"
|
||||
)
|
||||
return UniResponseModel[LDJLBApplyResponse](
|
||||
success=apply_result.code == 0,
|
||||
data=apply_result,
|
||||
message=apply_result.msg,
|
||||
error=None,
|
||||
)
|
||||
except ValidationError as ve:
|
||||
conn.logger.error(f"解析报名响应失败: {str(ve)}")
|
||||
return ProtectRouterErrorToCode().validation_error.to_json_response(
|
||||
conn.logger.trace_id, "解析报名响应失败,请稍后重试"
|
||||
)
|
||||
|
||||
except HTTPError as he:
|
||||
conn.logger.error(f"报名活动异常: {str(he)}")
|
||||
return ProtectRouterErrorToCode().remote_service_error.to_json_response(
|
||||
conn.logger.trace_id, "报名活动异常,请稍后重试"
|
||||
)
|
||||
except Exception as e:
|
||||
conn.logger.error(f"报名活动未知异常: {str(e)}")
|
||||
return ProtectRouterErrorToCode().unknown_error.to_json_response(
|
||||
conn.logger.trace_id, "报名活动未知异常,请稍后重试"
|
||||
)
|
||||
|
||||
|
||||
@ldjlb_labor_router.post(
|
||||
"/scan_sign",
|
||||
response_model=UniResponseModel[ScanSignResponse],
|
||||
summary="扫码签到",
|
||||
)
|
||||
async def scan_sign_in(
|
||||
request: ScanSignRequest,
|
||||
conn: AUFEConnection = Depends(get_aufe_conn),
|
||||
headers: Headers = Depends(get_ldjlb_header),
|
||||
) -> UniResponseModel[ScanSignResponse] | JSONResponse:
|
||||
"""
|
||||
扫码签到功能
|
||||
|
||||
✅ 功能特性:
|
||||
- 通过扫描二维码进行活动签到
|
||||
- 支持位置信息验证
|
||||
- 实时反馈签到结果
|
||||
|
||||
Args:
|
||||
request: 扫码签到请求,包含:
|
||||
- content: 扫描的二维码内容
|
||||
- location: 位置信息,格式为"经度,纬度"
|
||||
|
||||
Returns:
|
||||
UniResponseModel[ScanSignResponse]: 包含签到结果
|
||||
"""
|
||||
try:
|
||||
conn.logger.info(f"开始扫码签到,位置: {request.location}")
|
||||
|
||||
# 发送POST请求到劳动俱乐部签到接口
|
||||
response = await conn.client.post(
|
||||
url=LDJLBConfig().to_full_url(ENDPOINT["scan_sign"]),
|
||||
json={
|
||||
"content": request.content,
|
||||
"location": request.location,
|
||||
},
|
||||
headers=headers,
|
||||
timeout=6000,
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
conn.logger.error(f"扫码签到失败,HTTP状态码: {response.status_code}")
|
||||
return ProtectRouterErrorToCode().remote_service_error.to_json_response(
|
||||
conn.logger.trace_id, "扫码签到失败,请稍后重试"
|
||||
)
|
||||
|
||||
data = response.json()
|
||||
|
||||
try:
|
||||
sign_result = ScanSignResponse.model_validate(data)
|
||||
|
||||
if sign_result.code == 0:
|
||||
conn.logger.success(f"扫码签到成功: {sign_result.msg}")
|
||||
else:
|
||||
conn.logger.warning(
|
||||
f"扫码签到失败: {sign_result.msg} (code: {sign_result.code})"
|
||||
)
|
||||
|
||||
return UniResponseModel[ScanSignResponse](
|
||||
success=sign_result.code == 0,
|
||||
data=sign_result,
|
||||
message=sign_result.msg or "签到完成",
|
||||
error=None,
|
||||
)
|
||||
|
||||
except ValidationError as ve:
|
||||
conn.logger.error(f"解析签到响应失败: {str(ve)}")
|
||||
return ProtectRouterErrorToCode().validation_error.to_json_response(
|
||||
conn.logger.trace_id, "解析签到响应失败,请稍后重试"
|
||||
)
|
||||
|
||||
except HTTPError as he:
|
||||
conn.logger.error(f"扫码签到异常: {str(he)}")
|
||||
return ProtectRouterErrorToCode().remote_service_error.to_json_response(
|
||||
conn.logger.trace_id, "扫码签到异常,请稍后重试"
|
||||
)
|
||||
except Exception as e:
|
||||
conn.logger.error(f"扫码签到未知异常: {str(e)}")
|
||||
return ProtectRouterErrorToCode().unknown_error.to_json_response(
|
||||
conn.logger.trace_id, "扫码签到未知异常,请稍后重试"
|
||||
)
|
||||
|
||||
|
||||
@ldjlb_labor_router.get(
|
||||
"/{activity_id}/sign_list",
|
||||
response_model=UniResponseModel[SignListResponse],
|
||||
summary="获取活动签到列表",
|
||||
)
|
||||
async def get_sign_list(
|
||||
activity_id: str,
|
||||
conn: AUFEConnection = Depends(get_aufe_conn),
|
||||
headers: Headers = Depends(get_ldjlb_header),
|
||||
) -> UniResponseModel[SignListResponse] | JSONResponse:
|
||||
"""
|
||||
获取指定活动的签到列表
|
||||
|
||||
✅ 功能特性:
|
||||
- 获取活动的所有签到项
|
||||
- 支持分页查询
|
||||
- 查看签到状态和时间
|
||||
- 辅助扫码签到功能
|
||||
|
||||
Args:
|
||||
activity_id: 活动ID
|
||||
sign_type: 签到类型,默认1(签到)
|
||||
page_index: 页码,从1开始
|
||||
page_size: 每页大小,默认10
|
||||
|
||||
Returns:
|
||||
UniResponseModel[SignListResponse]: 包含签到列表数据
|
||||
"""
|
||||
sign_type: int = 1
|
||||
page_index: int = 1
|
||||
page_size: int = 10
|
||||
try:
|
||||
conn.logger.info(f"开始获取活动 {activity_id} 的签到列表")
|
||||
|
||||
# 发送POST请求到劳动俱乐部签到列表接口
|
||||
response = await conn.client.post(
|
||||
url=LDJLBConfig().to_full_url(ENDPOINT["sign_list"]),
|
||||
data={
|
||||
"activityID": activity_id,
|
||||
"type": sign_type,
|
||||
"pageIndex": page_index,
|
||||
"pageSize": page_size,
|
||||
},
|
||||
headers=headers,
|
||||
timeout=6000,
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
conn.logger.error(f"获取签到列表失败,HTTP状态码: {response.status_code}")
|
||||
return ProtectRouterErrorToCode().remote_service_error.to_json_response(
|
||||
conn.logger.trace_id, "获取签到列表失败,请稍后重试"
|
||||
)
|
||||
|
||||
data = response.json()
|
||||
|
||||
try:
|
||||
sign_list_result = SignListResponse.model_validate(data)
|
||||
|
||||
if sign_list_result.code == 0:
|
||||
sign_count = len(sign_list_result.data)
|
||||
signed_count = sum(1 for item in sign_list_result.data if item.is_sign)
|
||||
conn.logger.success(
|
||||
f"成功获取签到列表,共 {sign_count} 项,已签到 {signed_count} 项"
|
||||
)
|
||||
else:
|
||||
conn.logger.warning(f"获取签到列表失败 (code: {sign_list_result.code})")
|
||||
|
||||
return UniResponseModel[SignListResponse](
|
||||
success=sign_list_result.code == 0,
|
||||
data=sign_list_result,
|
||||
message="获取签到列表成功"
|
||||
if sign_list_result.code == 0
|
||||
else "获取签到列表失败",
|
||||
error=None,
|
||||
)
|
||||
|
||||
except ValidationError as ve:
|
||||
conn.logger.error(f"解析签到列表响应失败: {str(ve)}")
|
||||
return ProtectRouterErrorToCode().validation_error.to_json_response(
|
||||
conn.logger.trace_id, "解析签到列表响应失败,请稍后重试"
|
||||
)
|
||||
|
||||
except HTTPError as he:
|
||||
conn.logger.error(f"获取签到列表异常: {str(he)}")
|
||||
return ProtectRouterErrorToCode().remote_service_error.to_json_response(
|
||||
conn.logger.trace_id, "获取签到列表异常,请稍后重试"
|
||||
)
|
||||
except Exception as e:
|
||||
conn.logger.error(f"获取签到列表未知异常: {str(e)}")
|
||||
return ProtectRouterErrorToCode().unknown_error.to_json_response(
|
||||
conn.logger.trace_id, "获取签到列表未知异常,请稍后重试"
|
||||
)
|
||||
|
||||
|
||||
@ldjlb_labor_router.get(
|
||||
"/{activity_id}/detail",
|
||||
response_model=UniResponseModel[ActivityDetailResponse],
|
||||
summary="获取活动详情",
|
||||
)
|
||||
async def get_activity_detail(
|
||||
activity_id: str,
|
||||
conn: AUFEConnection = Depends(get_aufe_conn),
|
||||
headers: Headers = Depends(get_ldjlb_header),
|
||||
) -> UniResponseModel[ActivityDetailResponse] | JSONResponse:
|
||||
"""
|
||||
获取活动详细信息
|
||||
|
||||
✅ 功能特性:
|
||||
- 获取活动完整信息(标题、时间、地点等)
|
||||
- 查看活动地址和教室信息
|
||||
- 查看报名人数和限制
|
||||
- 查看审批流程和教师列表
|
||||
- 支持扫码签到功能的前置查询
|
||||
|
||||
Args:
|
||||
activity_id: 活动ID
|
||||
|
||||
Returns:
|
||||
UniResponseModel[ActivityDetailResponse]: 包含活动详细信息
|
||||
|
||||
说明:
|
||||
- formData 中包含"活动地址"等关键信息(如教室位置)
|
||||
- flowData 包含审批流程记录
|
||||
- teacherList 包含活动相关教师信息
|
||||
"""
|
||||
try:
|
||||
conn.logger.info(f"开始获取活动详情: {activity_id}")
|
||||
|
||||
# 发送POST请求到劳动俱乐部活动详情接口
|
||||
response = await conn.client.post(
|
||||
url=LDJLBConfig().to_full_url(ENDPOINT["activity_detail"]),
|
||||
data={"id": activity_id},
|
||||
headers=headers,
|
||||
timeout=6000,
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
conn.logger.error(f"获取活动详情失败,HTTP状态码: {response.status_code}")
|
||||
return ProtectRouterErrorToCode().remote_service_error.to_json_response(
|
||||
conn.logger.trace_id, "获取活动详情失败,请稍后重试"
|
||||
)
|
||||
|
||||
data = response.json()
|
||||
|
||||
try:
|
||||
detail_result = ActivityDetailResponse.model_validate(data)
|
||||
|
||||
if detail_result.code == 0 and detail_result.data:
|
||||
# 提取关键信息用于日志
|
||||
activity_title = detail_result.data.title
|
||||
activity_location = "未知"
|
||||
|
||||
# 从 formData 中提取活动地址
|
||||
for field in detail_result.form_data:
|
||||
if field.name == "活动地址" and field.value:
|
||||
activity_location = field.value
|
||||
break
|
||||
|
||||
conn.logger.success(
|
||||
f"成功获取活动详情 - 标题: {activity_title}, 地点: {activity_location}"
|
||||
)
|
||||
else:
|
||||
conn.logger.warning(f"获取活动详情失败 (code: {detail_result.code})")
|
||||
|
||||
return UniResponseModel[ActivityDetailResponse](
|
||||
success=detail_result.code == 0,
|
||||
data=detail_result,
|
||||
message="获取活动详情成功"
|
||||
if detail_result.code == 0
|
||||
else "获取活动详情失败",
|
||||
error=None,
|
||||
)
|
||||
|
||||
except ValidationError as ve:
|
||||
conn.logger.error(f"解析活动详情响应失败: {str(ve)}")
|
||||
return ProtectRouterErrorToCode().validation_error.to_json_response(
|
||||
conn.logger.trace_id, "解析活动详情响应失败,请稍后重试"
|
||||
)
|
||||
|
||||
except HTTPError as he:
|
||||
conn.logger.error(f"获取活动详情异常: {str(he)}")
|
||||
return ProtectRouterErrorToCode().remote_service_error.to_json_response(
|
||||
conn.logger.trace_id, "获取活动详情异常,请稍后重试"
|
||||
)
|
||||
except Exception as e:
|
||||
conn.logger.error(f"获取活动详情未知异常: {str(e)}")
|
||||
return ProtectRouterErrorToCode().unknown_error.to_json_response(
|
||||
conn.logger.trace_id, "获取活动详情未知异常,请稍后重试"
|
||||
)
|
||||
1
loveace/router/endpoint/ldjlb/model/__init__.py
Normal file
1
loveace/router/endpoint/ldjlb/model/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# 劳动俱乐部数据模型
|
||||
22
loveace/router/endpoint/ldjlb/model/base.py
Normal file
22
loveace/router/endpoint/ldjlb/model/base.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from pathlib import Path
|
||||
|
||||
from loveace.config.manager import config_manager
|
||||
|
||||
settings = config_manager.get_settings()
|
||||
|
||||
|
||||
class LDJLBConfig:
|
||||
"""劳动俱乐部模块配置常量"""
|
||||
|
||||
BASE_URL = "http://api-ldjlb-ac-acxk-net.vpn2.aufe.edu.cn:8118"
|
||||
WEB_URL = "http://ldjlb-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.ldjlb.ac.acxk.net%2fUser%2fIndex%2fCoreLoginCallback%3fisCASGateway%3dtrue"
|
||||
RSA_PRIVATE_KEY_PATH = str(
|
||||
Path(settings.app.rsa_protect_key_path).joinpath("aac_private_key.pem")
|
||||
)
|
||||
|
||||
def to_full_url(self, path: str) -> str:
|
||||
"""将路径转换为完整URL"""
|
||||
if path.startswith("http://") or path.startswith("https://"):
|
||||
return path
|
||||
return self.BASE_URL.rstrip("/") + "/" + path.lstrip("/")
|
||||
198
loveace/router/endpoint/ldjlb/model/ldjlb.py
Normal file
198
loveace/router/endpoint/ldjlb/model/ldjlb.py
Normal file
@@ -0,0 +1,198 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class LDJLBProgressInfo(BaseModel):
|
||||
"""劳动俱乐部修课进度信息"""
|
||||
|
||||
finish_count: int = Field(0, alias="data", description="已完成的活动数量")
|
||||
|
||||
@property
|
||||
def progress_percentage(self) -> float:
|
||||
"""计算修课进度百分比(满分10次)"""
|
||||
return min((self.finish_count / 10.0) * 100, 100.0)
|
||||
|
||||
|
||||
class LDJLBPageInfo(BaseModel):
|
||||
"""分页信息"""
|
||||
|
||||
total_item_count: int = Field(0, alias="TotalItemCount", description="总条目数")
|
||||
page_size: int = Field(20, alias="PageSize", description="每页大小")
|
||||
current_page_index: int = Field(1, alias="CurrentPageIndex", description="当前页码")
|
||||
|
||||
|
||||
class LDJLBActivity(BaseModel):
|
||||
"""劳动俱乐部活动信息"""
|
||||
|
||||
id: str = Field("", alias="ID", description="活动ID")
|
||||
ico: Optional[str] = Field(None, alias="Ico", description="活动图标")
|
||||
state: int = Field(0, alias="State", description="活动状态代码")
|
||||
state_name: str = Field("", alias="StateName", description="活动状态名称")
|
||||
type_id: str = Field("", alias="TypeID", description="活动类型ID")
|
||||
type_name: str = Field("", alias="TypeName", description="活动类型名称")
|
||||
title: str = Field("", alias="Title", description="活动标题")
|
||||
start_time: str = Field("", alias="StartTime", description="活动开始时间")
|
||||
end_time: str = Field("", alias="EndTime", description="活动结束时间")
|
||||
charge_user_no: str = Field("", alias="ChargeUserNo", description="负责人工号")
|
||||
charge_user_name: str = Field("", alias="ChargeUserName", description="负责人姓名")
|
||||
club_id: str = Field("", alias="ClubID", description="所属俱乐部ID")
|
||||
club_name: str = Field("", alias="ClubName", description="所属俱乐部名称")
|
||||
member_num: int = Field(0, alias="MemberNum", description="已报名人数")
|
||||
add_time: str = Field("", alias="AddTime", description="活动添加时间")
|
||||
people_num: int = Field(0, alias="PeopleNum", description="活动人数限制")
|
||||
people_num_min: Optional[int] = Field(None, alias="PeopleNumMin", description="最小人数限制")
|
||||
is_join: Optional[bool] = Field(None, alias="IsJson", description="是否已加入")
|
||||
is_close: Optional[bool] = Field(None, alias="IsClose", description="是否已关闭")
|
||||
sign_up_start_time: str = Field("", alias="SignUpStartTime", description="报名开始时间")
|
||||
sign_up_end_time: str = Field("", alias="SignUpEndTime", description="报名结束时间")
|
||||
|
||||
|
||||
class LDJLBActivityListResponse(BaseModel):
|
||||
"""劳动俱乐部活动列表响应"""
|
||||
|
||||
activities: List[LDJLBActivity] = Field([], alias="data", description="活动列表")
|
||||
page_info: LDJLBPageInfo = Field(..., alias="pageInfo", description="分页信息")
|
||||
|
||||
|
||||
class LDJLBClub(BaseModel):
|
||||
"""劳动俱乐部信息"""
|
||||
|
||||
id: str = Field("", alias="ID", description="俱乐部ID")
|
||||
name: str = Field("", alias="Name", description="俱乐部名称")
|
||||
type_id: str = Field("", alias="TypeID", description="俱乐部类型ID")
|
||||
people_num: int = Field(0, alias="PeopleNum", description="俱乐部总人数")
|
||||
project_id: str = Field("", alias="ProjectID", description="项目ID")
|
||||
project_name: str = Field("", alias="PorjectName", description="项目名称")
|
||||
type_name: str = Field("", alias="TypeName", description="类型名称")
|
||||
ico: str = Field("", alias="Ico", description="俱乐部图标")
|
||||
desc: Optional[str] = Field(None, alias="Desc", description="俱乐部描述")
|
||||
chairman_no: str = Field("", alias="ChairmanNo", description="主席工号")
|
||||
chairman_name: str = Field("", alias="CairmanName", description="主席姓名")
|
||||
depart_code: str = Field("", alias="DepartCode", description="部门代码")
|
||||
contact: Optional[str] = Field(None, alias="Contact", description="联系方式")
|
||||
is_enable: bool = Field(True, alias="IsEnable", description="是否启用")
|
||||
depart_name: str = Field("", alias="DpeartName", description="部门名称")
|
||||
member_num: int = Field(0, alias="MemberNum", description="俱乐部成员数")
|
||||
|
||||
|
||||
class LDJLBClubListResponse(BaseModel):
|
||||
"""劳动俱乐部列表响应"""
|
||||
|
||||
clubs: List[LDJLBClub] = Field([], alias="data", description="俱乐部列表")
|
||||
|
||||
|
||||
class LDJLBApplyResponse(BaseModel):
|
||||
"""劳动俱乐部报名响应"""
|
||||
|
||||
code: int = Field(0, description="响应代码")
|
||||
msg: str = Field("", description="响应消息")
|
||||
|
||||
|
||||
class ScanSignRequest(BaseModel):
|
||||
"""扫码签到请求模型"""
|
||||
|
||||
content: str = Field(..., description="扫码结果内容")
|
||||
location: str = Field(..., description="位置信息,格式: 经度,纬度")
|
||||
|
||||
|
||||
class ScanSignResponse(BaseModel):
|
||||
"""扫码签到响应模型"""
|
||||
|
||||
code: int = Field(..., description="响应码,0表示成功")
|
||||
msg: Optional[str] = Field(None, description="响应消息")
|
||||
data: Optional[dict] = Field(None, description="响应数据")
|
||||
|
||||
|
||||
class SignItem(BaseModel):
|
||||
"""签到项信息"""
|
||||
|
||||
id: str = Field("", alias="ID", description="签到项ID")
|
||||
type: int = Field(1, alias="Type", description="类型,1=签到")
|
||||
type_name: str = Field("", alias="TypeName", description="类型名称")
|
||||
start_time: str = Field("", alias="StartTime", description="签到开始时间")
|
||||
end_time: str = Field("", alias="EndTime", description="签到结束时间")
|
||||
is_sign: bool = Field(False, alias="IsSign", description="是否已签到")
|
||||
sign_time: str = Field("", alias="SignTime", description="签到时间")
|
||||
|
||||
|
||||
class SignListResponse(BaseModel):
|
||||
"""签到列表响应模型"""
|
||||
|
||||
code: int = Field(0, description="响应码,0表示成功")
|
||||
data: List[SignItem] = Field(default_factory=list, description="签到列表数据")
|
||||
|
||||
|
||||
class FormField(BaseModel):
|
||||
"""活动表单字段"""
|
||||
|
||||
id: str = Field("", alias="ID", description="字段ID")
|
||||
name: str = Field("", alias="Name", description="字段名称")
|
||||
is_must: bool = Field(False, alias="IsMust", description="是否必填")
|
||||
field_type: int = Field(1, alias="FieldType", description="字段类型")
|
||||
value: str = Field("", alias="Value", description="字段值")
|
||||
|
||||
|
||||
class FlowData(BaseModel):
|
||||
"""活动审批流程数据"""
|
||||
|
||||
id: str = Field("", alias="ID", description="流程ID")
|
||||
is_adopt: bool = Field(False, alias="IsAdopt", description="是否通过")
|
||||
flow_type: int = Field(0, alias="FlowType", description="流程类型")
|
||||
flow_type_name: str = Field("", alias="FlowTypeName", description="流程类型名称")
|
||||
user_no: Optional[str] = Field(None, alias="UserNo", description="用户工号")
|
||||
user_name: str = Field("", alias="UserName", description="用户姓名")
|
||||
exam_user_no: str = Field("", alias="ExamUserNo", description="审批人工号")
|
||||
exam_user_name: str = Field("", alias="ExamUserName", description="审批人姓名")
|
||||
exam_comment: str = Field("", alias="ExamComment", description="审批意见")
|
||||
add_time: str = Field("", alias="AddTime", description="提交时间")
|
||||
exam_time: str = Field("", alias="ExamTime", description="审批时间")
|
||||
|
||||
|
||||
class Teacher(BaseModel):
|
||||
"""活动教师信息"""
|
||||
|
||||
user_name: str = Field("", alias="UserName", description="教师姓名")
|
||||
id: str = Field("", alias="ID", description="记录ID")
|
||||
activity_id: str = Field("", alias="ActivityID", description="活动ID")
|
||||
user_no: str = Field("", alias="UserNo", description="教师工号")
|
||||
add_time: str = Field("", alias="AddTime", description="添加时间")
|
||||
add_user_no: str = Field("", alias="AddUserNo", description="添加人工号")
|
||||
|
||||
|
||||
class ActivityDetailData(BaseModel):
|
||||
"""活动详细信息数据"""
|
||||
|
||||
id: str = Field("", alias="ID", description="活动ID")
|
||||
title: str = Field("", alias="Title", description="活动标题")
|
||||
state: int = Field(0, alias="State", description="活动状态")
|
||||
ico: Optional[str] = Field(None, alias="Ico", description="活动图标")
|
||||
type_id: str = Field("", alias="TypeID", description="活动类型ID")
|
||||
type_name: str = Field("", alias="TypeName", description="活动类型名称")
|
||||
start_time: str = Field("", alias="StartTime", description="活动开始时间")
|
||||
end_time: str = Field("", alias="EndTime", description="活动结束时间")
|
||||
charge_user_no: str = Field("", alias="ChargeUserNo", description="负责人工号")
|
||||
charge_user_name: str = Field("", alias="ChargeUserName", description="负责人姓名")
|
||||
club_id: str = Field("", alias="ClubID", description="所属俱乐部ID")
|
||||
club_name: str = Field("", alias="ClubName", description="所属俱乐部名称")
|
||||
member_num: int = Field(0, alias="MemberNum", description="已报名人数")
|
||||
add_time: str = Field("", alias="AddTime", description="活动添加时间")
|
||||
apply_is_need_exam: bool = Field(False, alias="ApplyIsNeedExam", description="报名是否需要审批")
|
||||
is_member: bool = Field(False, alias="IsMember", description="是否为成员")
|
||||
is_manager: bool = Field(False, alias="IsManager", description="是否为管理员")
|
||||
people_num: int = Field(0, alias="PeopleNum", description="活动人数限制")
|
||||
people_num_min: Optional[int] = Field(None, alias="PeopleNumMin", description="最小人数限制")
|
||||
is_close: Optional[bool] = Field(None, alias="IsClose", description="是否已关闭")
|
||||
sign_up_start_time: str = Field("", alias="SignUpStartTime", description="报名开始时间")
|
||||
sign_up_end_time: str = Field("", alias="SignUpEndTime", description="报名结束时间")
|
||||
|
||||
|
||||
class ActivityDetailResponse(BaseModel):
|
||||
"""活动详情响应模型"""
|
||||
|
||||
code: int = Field(0, description="响应码,0表示成功")
|
||||
data: Optional[ActivityDetailData] = Field(None, description="活动详细信息")
|
||||
form_data: List[FormField] = Field(default_factory=list, alias="formData", description="表单数据")
|
||||
flow_data: List[FlowData] = Field(default_factory=list, alias="flowData", description="审批流程数据")
|
||||
venue_reserve_data: List = Field(default_factory=list, alias="VenueReserveData", description="场地预约数据")
|
||||
teacher_list: List[Teacher] = Field(default_factory=list, alias="teacherList", description="教师列表")
|
||||
1
loveace/router/endpoint/ldjlb/utils/__init__.py
Normal file
1
loveace/router/endpoint/ldjlb/utils/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# 劳动俱乐部工具函数
|
||||
167
loveace/router/endpoint/ldjlb/utils/ldjlb_ticket.py
Normal file
167
loveace/router/endpoint/ldjlb/utils/ldjlb_ticket.py
Normal file
@@ -0,0 +1,167 @@
|
||||
from urllib.parse import unquote
|
||||
|
||||
from fastapi import Depends
|
||||
from httpx import Headers
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from loveace.config.manager import config_manager
|
||||
from loveace.database.creator import get_db_session
|
||||
from loveace.database.ldjlb.ticket import LDJLBTicket
|
||||
from loveace.router.dependencies.auth import ProtectRouterErrorToCode
|
||||
from loveace.router.endpoint.ldjlb.model.base import LDJLBConfig
|
||||
from loveace.service.remote.aufe import AUFEConnection
|
||||
from loveace.service.remote.aufe.depends import get_aufe_conn
|
||||
from loveace.utils.rsa import RSAUtils
|
||||
|
||||
rsa = RSAUtils.get_or_create_rsa_utils(LDJLBConfig.RSA_PRIVATE_KEY_PATH)
|
||||
|
||||
|
||||
def _extract_and_encrypt_token(location: str, logger) -> str | None:
|
||||
"""从重定向URL中提取并加密系统令牌"""
|
||||
try:
|
||||
sys_token = location.split("ticket=")[-1]
|
||||
# URL编码转为正常字符串
|
||||
sys_token = unquote(sys_token)
|
||||
if not sys_token:
|
||||
logger.error("系统令牌为空")
|
||||
return None
|
||||
|
||||
logger.info(f"获取到系统令牌: {sys_token[:10]}...")
|
||||
# 加密系统令牌
|
||||
encrypted_token = rsa.encrypt(sys_token)
|
||||
return encrypted_token
|
||||
except Exception as e:
|
||||
logger.error(f"解析/加密系统令牌失败: {str(e)}")
|
||||
return None
|
||||
|
||||
|
||||
async def get_system_token(conn: AUFEConnection) -> str:
|
||||
next_location = LDJLBConfig.LOGIN_SERVICE_URL
|
||||
max_redirects = 10 # 防止无限重定向
|
||||
redirect_count = 0
|
||||
try:
|
||||
while redirect_count < max_redirects:
|
||||
response = await conn.client.get(
|
||||
next_location, follow_redirects=False, timeout=conn.timeout
|
||||
)
|
||||
|
||||
# 如果是重定向,继续跟踪
|
||||
if response.status_code in (301, 302, 303, 307, 308):
|
||||
next_location = response.headers.get("Location")
|
||||
if not next_location:
|
||||
conn.logger.error("重定向响应中缺少 Location 头")
|
||||
return ""
|
||||
|
||||
conn.logger.debug(f"重定向到: {next_location}")
|
||||
redirect_count += 1
|
||||
|
||||
if "register?ticket=" in next_location:
|
||||
conn.logger.info(f"重定向到劳动俱乐部注册页面: {next_location}")
|
||||
encrypted_token = _extract_and_encrypt_token(
|
||||
next_location, conn.logger
|
||||
)
|
||||
return encrypted_token if encrypted_token else ""
|
||||
else:
|
||||
break
|
||||
|
||||
if redirect_count >= max_redirects:
|
||||
conn.logger.error(f"重定向次数过多 ({max_redirects})")
|
||||
return ""
|
||||
|
||||
conn.logger.error("未能获取系统令牌")
|
||||
return ""
|
||||
|
||||
except Exception as e:
|
||||
conn.logger.error(f"获取系统令牌异常: {str(e)}")
|
||||
return ""
|
||||
|
||||
|
||||
async def get_ldjlb_header(
|
||||
conn: AUFEConnection = Depends(get_aufe_conn),
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
) -> Headers:
|
||||
"""
|
||||
获取 LDJLB Ticket 的依赖项。
|
||||
如果用户没有登录AUFE或UAAP,或者 LDJLB Ticket 不存在且无法获取新的 Ticket,则会抛出HTTP异常。
|
||||
否则,返回有效的 LDJLB Ticket 字符串。
|
||||
"""
|
||||
# 检查 LDJLB Ticket 是否存在
|
||||
async with db as session:
|
||||
result = await session.execute(
|
||||
select(LDJLBTicket).where(LDJLBTicket.userid == conn.userid)
|
||||
)
|
||||
ldjlb_ticket = result.scalars().first()
|
||||
|
||||
if not ldjlb_ticket:
|
||||
ldjlb_ticket = await _get_or_fetch_ticket(conn, db, is_new=True)
|
||||
else:
|
||||
ldjlb_ticket_token = ldjlb_ticket.ldjlb_token
|
||||
try:
|
||||
# 解密以验证Ticket有效性
|
||||
decrypted_ticket = rsa.decrypt(ldjlb_ticket_token)
|
||||
if not decrypted_ticket:
|
||||
raise ValueError("解密后的Ticket为空")
|
||||
ldjlb_ticket = decrypted_ticket
|
||||
except Exception as e:
|
||||
conn.logger.error(
|
||||
f"用户 {conn.userid} 的 LDJLB Ticket 无效,正在获取新的 Ticket: {str(e)}"
|
||||
)
|
||||
ldjlb_ticket = await _get_or_fetch_ticket(conn, db, is_new=False)
|
||||
else:
|
||||
conn.logger.info(f"用户 {conn.userid} 使用现有的 LDJLB Ticket")
|
||||
|
||||
return Headers(
|
||||
{
|
||||
**config_manager.get_settings().aufe.default_headers,
|
||||
"ticket": ldjlb_ticket,
|
||||
"sdp-app-session": conn.twf_id,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def _get_or_fetch_ticket(
|
||||
conn: AUFEConnection, db: AsyncSession, is_new: bool
|
||||
) -> str:
|
||||
"""获取或重新获取 LDJLB Ticket 并保存到数据库(返回解密后的ticket)"""
|
||||
action_type = "获取" if is_new else "重新获取"
|
||||
conn.logger.info(
|
||||
f"用户 {conn.userid} 的 LDJLB Ticket {'不存在' if is_new else '无效'},正在{action_type}新的 Ticket"
|
||||
)
|
||||
|
||||
encrypted_token = await get_system_token(conn)
|
||||
if not encrypted_token:
|
||||
conn.logger.error(f"用户 {conn.userid} {action_type} LDJLB Ticket 失败")
|
||||
raise ProtectRouterErrorToCode().remote_service_error.to_http_exception(
|
||||
conn.logger.trace_id,
|
||||
message="获取 LDJLB Ticket 失败,请检查 AUFE/UAAP 登录状态",
|
||||
)
|
||||
|
||||
# 解密token
|
||||
try:
|
||||
decrypted_token = rsa.decrypt(encrypted_token)
|
||||
if not decrypted_token:
|
||||
raise ValueError("解密后的Ticket为空")
|
||||
except Exception as e:
|
||||
conn.logger.error(f"用户 {conn.userid} 解密 LDJLB Ticket 失败: {str(e)}")
|
||||
raise ProtectRouterErrorToCode().remote_service_error.to_http_exception(
|
||||
conn.logger.trace_id,
|
||||
message="解密 LDJLB Ticket 失败",
|
||||
)
|
||||
|
||||
# 保存加密后的token到数据库
|
||||
async with db as session:
|
||||
if is_new:
|
||||
session.add(LDJLBTicket(userid=conn.userid, ldjlb_token=encrypted_token))
|
||||
else:
|
||||
result = await session.execute(
|
||||
select(LDJLBTicket).where(LDJLBTicket.userid == conn.userid)
|
||||
)
|
||||
existing_ticket = result.scalars().first()
|
||||
if existing_ticket:
|
||||
existing_ticket.ldjlb_token = encrypted_token
|
||||
await session.commit()
|
||||
|
||||
conn.logger.success(f"用户 {conn.userid} 成功{action_type}并保存新的 LDJLB Ticket")
|
||||
# 返回解密后的token
|
||||
return decrypted_token
|
||||
Reference in New Issue
Block a user