⚡新增 ISIM 电费查缴系统
This commit is contained in:
338
router/isim/__init__.py
Normal file
338
router/isim/__init__.py
Normal file
@@ -0,0 +1,338 @@
|
||||
from fastapi import Depends
|
||||
from fastapi.routing import APIRouter
|
||||
from provider.aufe.isim import ISIMClient
|
||||
from provider.aufe.isim.depends import get_isim_client
|
||||
from provider.loveac.authme import AuthmeResponse
|
||||
from router.isim.model import (
|
||||
BuildingListResponse,
|
||||
FloorListResponse,
|
||||
RoomListResponse,
|
||||
RoomBindingResponse,
|
||||
ElectricityInfoResponse,
|
||||
PaymentInfoResponse,
|
||||
RoomBindingStatusData,
|
||||
RoomBindingStatusResponse,
|
||||
)
|
||||
from router.common_model import ErrorResponse
|
||||
from loguru import logger
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from database.creator import get_db_session
|
||||
from database.isim import ISIMRoomBinding
|
||||
from sqlalchemy import select, update
|
||||
from pydantic import BaseModel
|
||||
from provider.aufe.isim.model import BuildingInfo, FloorInfo, RoomInfo, RoomBindingInfo
|
||||
|
||||
|
||||
# 简化的请求模型,只需要业务参数
|
||||
class SetBuildingRequest(BaseModel):
|
||||
building_code: str
|
||||
|
||||
|
||||
class SetFloorRequest(BaseModel):
|
||||
floor_code: str
|
||||
|
||||
|
||||
class SetRoomRequest(BaseModel):
|
||||
building_code: str
|
||||
floor_code: str
|
||||
room_code: str
|
||||
|
||||
|
||||
isim_router = APIRouter(prefix="/api/v1/isim")
|
||||
|
||||
|
||||
# ==================== 房间选择器API ====================
|
||||
|
||||
|
||||
@isim_router.post(
|
||||
"/picker/building/get",
|
||||
summary="获取楼栋列表",
|
||||
response_model=BuildingListResponse | AuthmeResponse | ErrorResponse,
|
||||
)
|
||||
async def get_building_list(client: ISIMClient = Depends(get_isim_client)):
|
||||
"""获取所有可选楼栋列表"""
|
||||
try:
|
||||
result = await client.get_buildings()
|
||||
|
||||
response = BuildingListResponse.from_data(
|
||||
data=result,
|
||||
success_message="楼栋列表获取成功",
|
||||
error_message="获取楼栋列表失败,网络请求多次重试后仍无法连接后勤系统,请稍后重试或联系管理员",
|
||||
)
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取楼栋列表时发生系统错误: {str(e)}")
|
||||
return ErrorResponse(message=f"获取楼栋列表时发生系统错误:{str(e)}", code=500)
|
||||
|
||||
|
||||
@isim_router.post(
|
||||
"/picker/building/set",
|
||||
summary="设置楼栋并获取楼层列表",
|
||||
response_model=FloorListResponse | AuthmeResponse | ErrorResponse,
|
||||
)
|
||||
async def set_building_get_floors(
|
||||
request: SetBuildingRequest, client: ISIMClient = Depends(get_isim_client)
|
||||
):
|
||||
"""设置楼栋并获取对应的楼层列表"""
|
||||
try:
|
||||
result = await client.get_floors(request.building_code)
|
||||
|
||||
response = FloorListResponse.from_data(
|
||||
data=result,
|
||||
success_message="楼层列表获取成功",
|
||||
error_message="获取楼层列表失败,网络请求多次重试后仍无法连接后勤系统,请稍后重试或联系管理员",
|
||||
)
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取楼层列表时发生系统错误: {str(e)}")
|
||||
return ErrorResponse(message=f"获取楼层列表时发生系统错误:{str(e)}", code=500)
|
||||
|
||||
|
||||
@isim_router.post(
|
||||
"/picker/floor/set",
|
||||
summary="设置楼层并获取房间列表",
|
||||
response_model=RoomListResponse | AuthmeResponse | ErrorResponse,
|
||||
)
|
||||
async def set_floor_get_rooms(
|
||||
request: SetFloorRequest, client: ISIMClient = Depends(get_isim_client)
|
||||
):
|
||||
"""设置楼层并获取对应的房间列表"""
|
||||
try:
|
||||
result = await client.get_rooms(request.floor_code)
|
||||
|
||||
response = RoomListResponse.from_data(
|
||||
data=result,
|
||||
success_message="房间列表获取成功",
|
||||
error_message="获取房间列表失败,网络请求多次重试后仍无法连接后勤系统,请稍后重试或联系管理员",
|
||||
)
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取房间列表时发生系统错误: {str(e)}")
|
||||
return ErrorResponse(message=f"获取房间列表时发生系统错误:{str(e)}", code=500)
|
||||
|
||||
|
||||
@isim_router.post(
|
||||
"/picker/room/set",
|
||||
summary="绑定房间",
|
||||
response_model=RoomBindingResponse | AuthmeResponse | ErrorResponse,
|
||||
)
|
||||
async def bind_room(
|
||||
request: SetRoomRequest,
|
||||
client: ISIMClient = Depends(get_isim_client),
|
||||
asyncsession: AsyncSession = Depends(get_db_session),
|
||||
):
|
||||
"""绑定房间并保存到数据库"""
|
||||
try:
|
||||
# 执行房间绑定
|
||||
result = await client.bind_room(
|
||||
building_code=request.building_code,
|
||||
floor_code=request.floor_code,
|
||||
room_code=request.room_code,
|
||||
)
|
||||
|
||||
if result:
|
||||
# 保存绑定信息到数据库
|
||||
async with asyncsession as session:
|
||||
try:
|
||||
# 获取用户ID
|
||||
user_id = client.vpn_connection.student_id
|
||||
|
||||
# 检查是否已存在绑定记录
|
||||
existing_binding = await session.execute(
|
||||
select(ISIMRoomBinding).where(ISIMRoomBinding.userid == user_id)
|
||||
)
|
||||
existing = existing_binding.scalars().first()
|
||||
|
||||
if existing:
|
||||
# 更新现有记录
|
||||
await session.execute(
|
||||
update(ISIMRoomBinding)
|
||||
.where(ISIMRoomBinding.userid == user_id)
|
||||
.values(
|
||||
building_code=result.building.code,
|
||||
building_name=result.building.name,
|
||||
floor_code=result.floor.code,
|
||||
floor_name=result.floor.name,
|
||||
room_code=result.room.code,
|
||||
room_name=result.room.name,
|
||||
room_id=result.room_id,
|
||||
)
|
||||
)
|
||||
logger.info(
|
||||
f"更新用户房间绑定: {user_id} -> {result.display_text}"
|
||||
)
|
||||
else:
|
||||
# 创建新记录
|
||||
new_binding = ISIMRoomBinding(
|
||||
userid=user_id,
|
||||
building_code=result.building.code,
|
||||
building_name=result.building.name,
|
||||
floor_code=result.floor.code,
|
||||
floor_name=result.floor.name,
|
||||
room_code=result.room.code,
|
||||
room_name=result.room.name,
|
||||
room_id=result.room_id,
|
||||
)
|
||||
session.add(new_binding)
|
||||
logger.info(
|
||||
f"创建用户房间绑定: {user_id} -> {result.display_text}"
|
||||
)
|
||||
|
||||
await session.commit()
|
||||
|
||||
except Exception as db_error:
|
||||
await session.rollback()
|
||||
logger.error(f"保存房间绑定到数据库失败: {str(db_error)}")
|
||||
# 数据库保存失败不影响绑定结果返回
|
||||
|
||||
response = RoomBindingResponse.from_data(
|
||||
data=result,
|
||||
success_message="房间绑定成功",
|
||||
error_message="房间绑定失败,网络请求多次重试后仍无法连接后勤系统,请稍后重试或联系管理员",
|
||||
)
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"绑定房间时发生系统错误: {str(e)}")
|
||||
return ErrorResponse(message=f"绑定房间时发生系统错误:{str(e)}", code=500)
|
||||
|
||||
|
||||
# ==================== 电费查询API ====================
|
||||
|
||||
|
||||
@isim_router.post(
|
||||
"/electricity/info",
|
||||
summary="获取电费信息",
|
||||
response_model=ElectricityInfoResponse | AuthmeResponse | ErrorResponse,
|
||||
)
|
||||
async def get_electricity_info(
|
||||
client: ISIMClient = Depends(get_isim_client),
|
||||
session: AsyncSession = Depends(get_db_session),
|
||||
):
|
||||
"""获取电费余额和用电记录信息"""
|
||||
try:
|
||||
# 查询用户的房间绑定记录
|
||||
from database.isim import ISIMRoomBinding
|
||||
from sqlalchemy import select
|
||||
|
||||
result_query = await session.execute(
|
||||
select(ISIMRoomBinding).where(ISIMRoomBinding.userid == client.user_id)
|
||||
)
|
||||
binding_record = result_query.scalars().first()
|
||||
# 传递绑定记录给客户端
|
||||
result = await client.get_electricity_info(binding_record)
|
||||
|
||||
response = ElectricityInfoResponse.from_data(
|
||||
data=result,
|
||||
success_message="电费信息获取成功",
|
||||
error_message="获取电费信息失败,网络请求多次重试后仍无法连接后勤系统,请稍后重试或联系管理员",
|
||||
)
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取电费信息时发生系统错误: {str(e)}")
|
||||
return ErrorResponse(message=f"获取电费信息时发生系统错误:{str(e)}", code=500)
|
||||
|
||||
|
||||
@isim_router.post(
|
||||
"/payment/info",
|
||||
summary="获取充值信息",
|
||||
response_model=PaymentInfoResponse | AuthmeResponse | ErrorResponse,
|
||||
)
|
||||
async def get_payment_info(
|
||||
client: ISIMClient = Depends(get_isim_client),
|
||||
session: AsyncSession = Depends(get_db_session),
|
||||
):
|
||||
"""获取电费余额和充值记录信息"""
|
||||
try:
|
||||
# 查询用户的房间绑定记录
|
||||
from database.isim import ISIMRoomBinding
|
||||
from sqlalchemy import select
|
||||
|
||||
result_query = await session.execute(
|
||||
select(ISIMRoomBinding).where(ISIMRoomBinding.userid == client.user_id)
|
||||
)
|
||||
binding_record = result_query.scalars().first()
|
||||
# 传递绑定记录给客户端
|
||||
result = await client.get_payment_info(binding_record)
|
||||
|
||||
response = PaymentInfoResponse.from_data(
|
||||
data=result,
|
||||
success_message="充值信息获取成功",
|
||||
error_message="获取充值信息失败,网络请求多次重试后仍无法连接后勤系统,请稍后重试或联系管理员",
|
||||
)
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取充值信息时发生系统错误: {str(e)}")
|
||||
return ErrorResponse(message=f"获取充值信息时发生系统错误:{str(e)}", code=500)
|
||||
|
||||
|
||||
# ==================== 房间绑定状态API ====================
|
||||
|
||||
|
||||
@isim_router.post(
|
||||
"/room/binding/status",
|
||||
summary="检查用户房间绑定状态",
|
||||
response_model=RoomBindingStatusResponse | AuthmeResponse | ErrorResponse,
|
||||
)
|
||||
async def check_room_binding_status(
|
||||
client: ISIMClient = Depends(get_isim_client),
|
||||
asyncsession: AsyncSession = Depends(get_db_session),
|
||||
):
|
||||
"""检查用户是否已绑定宿舍房间"""
|
||||
try:
|
||||
# 获取用户ID
|
||||
user_id = client.vpn_connection.student_id
|
||||
|
||||
async with asyncsession as session:
|
||||
# 查询数据库中的房间绑定记录
|
||||
result = await session.execute(
|
||||
select(ISIMRoomBinding).where(ISIMRoomBinding.userid == user_id)
|
||||
)
|
||||
binding_record = result.scalars().first()
|
||||
|
||||
if binding_record:
|
||||
# 用户已绑定房间,构建绑定信息
|
||||
binding_info = RoomBindingInfo(
|
||||
building=BuildingInfo(
|
||||
code=binding_record.building_code,
|
||||
name=binding_record.building_name,
|
||||
),
|
||||
floor=FloorInfo(
|
||||
code=binding_record.floor_code, name=binding_record.floor_name
|
||||
),
|
||||
room=RoomInfo(
|
||||
code=binding_record.room_code, name=binding_record.room_name
|
||||
),
|
||||
room_id=binding_record.room_id,
|
||||
display_text=f"{binding_record.building_name}/{binding_record.floor_name}/{binding_record.room_name}",
|
||||
)
|
||||
|
||||
status_data = RoomBindingStatusData(
|
||||
is_bound=True, binding_info=binding_info
|
||||
)
|
||||
|
||||
logger.info(f"用户 {user_id} 已绑定房间: {binding_info.display_text}")
|
||||
|
||||
return RoomBindingStatusResponse.success(
|
||||
data=status_data, message="用户已绑定宿舍房间"
|
||||
)
|
||||
else:
|
||||
# 用户未绑定房间
|
||||
status_data = RoomBindingStatusData(is_bound=False, binding_info=None)
|
||||
|
||||
logger.info(f"用户 {user_id} 未绑定房间")
|
||||
|
||||
return RoomBindingStatusResponse.success(
|
||||
data=status_data, message="用户未绑定宿舍房间"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"检查房间绑定状态时发生系统错误: {str(e)}")
|
||||
return ErrorResponse(
|
||||
message=f"检查房间绑定状态时发生系统错误:{str(e)}", code=500
|
||||
)
|
||||
140
router/isim/model.py
Normal file
140
router/isim/model.py
Normal file
@@ -0,0 +1,140 @@
|
||||
from typing import List, Optional
|
||||
from pydantic import BaseModel, Field
|
||||
from provider.aufe.isim.model import (
|
||||
BuildingInfo,
|
||||
FloorInfo,
|
||||
RoomInfo,
|
||||
RoomBindingInfo,
|
||||
ElectricityInfo,
|
||||
PaymentInfo
|
||||
)
|
||||
from router.common_model import BaseResponse
|
||||
|
||||
|
||||
# ==================== 请求模型 ====================
|
||||
|
||||
class AuthmeRequest(BaseModel):
|
||||
"""认证请求基类"""
|
||||
authme_token: str = Field(..., description="认证令牌")
|
||||
|
||||
|
||||
class SetBuildingRequest(AuthmeRequest):
|
||||
"""设置楼栋请求"""
|
||||
building_code: str = Field(..., description="楼栋代码")
|
||||
|
||||
|
||||
class SetFloorRequest(AuthmeRequest):
|
||||
"""设置楼层请求"""
|
||||
floor_code: str = Field(..., description="楼层代码")
|
||||
|
||||
|
||||
class SetRoomRequest(AuthmeRequest):
|
||||
"""设置房间请求"""
|
||||
building_code: str = Field(..., description="楼栋代码")
|
||||
floor_code: str = Field(..., description="楼层代码")
|
||||
room_code: str = Field(..., description="房间代码")
|
||||
|
||||
|
||||
# ==================== 响应模型 ====================
|
||||
|
||||
class BuildingListResponse(BaseResponse):
|
||||
"""楼栋列表响应"""
|
||||
data: Optional[List[BuildingInfo]] = Field(default=None, description="楼栋信息列表")
|
||||
|
||||
@classmethod
|
||||
def from_data(cls, data: List[BuildingInfo], success_message: str, error_message: str):
|
||||
"""根据数据创建响应"""
|
||||
if data and len(data) > 0:
|
||||
# 检查是否是错误数据(第一个楼栋名称为"请求失败")
|
||||
if data[0].name == "请求失败":
|
||||
return cls.error(message=error_message, code=500, data=None)
|
||||
else:
|
||||
return cls.success(data=data, message=success_message)
|
||||
else:
|
||||
return cls.error(message=error_message, code=500, data=None)
|
||||
|
||||
|
||||
class FloorListResponse(BaseResponse):
|
||||
"""楼层列表响应"""
|
||||
data: Optional[List[FloorInfo]] = Field(default=None, description="楼层信息列表")
|
||||
|
||||
@classmethod
|
||||
def from_data(cls, data: List[FloorInfo], success_message: str, error_message: str):
|
||||
"""根据数据创建响应"""
|
||||
if data and len(data) > 0:
|
||||
return cls.success(data=data, message=success_message)
|
||||
else:
|
||||
return cls.error(message=error_message, code=500, data=None)
|
||||
|
||||
|
||||
class RoomListResponse(BaseResponse):
|
||||
"""房间列表响应"""
|
||||
data: Optional[List[RoomInfo]] = Field(default=None, description="房间信息列表")
|
||||
|
||||
@classmethod
|
||||
def from_data(cls, data: List[RoomInfo], success_message: str, error_message: str):
|
||||
"""根据数据创建响应"""
|
||||
if data and len(data) > 0:
|
||||
return cls.success(data=data, message=success_message)
|
||||
else:
|
||||
return cls.error(message=error_message, code=500, data=None)
|
||||
|
||||
|
||||
class RoomBindingResponse(BaseResponse):
|
||||
"""房间绑定响应"""
|
||||
data: Optional[RoomBindingInfo] = Field(default=None, description="房间绑定信息")
|
||||
|
||||
@classmethod
|
||||
def from_data(cls, data: Optional[RoomBindingInfo], success_message: str, error_message: str):
|
||||
"""根据数据创建响应"""
|
||||
if data and hasattr(data, 'building') and data.building.name != "请求失败":
|
||||
return cls.success(data=data, message=success_message)
|
||||
else:
|
||||
return cls.error(message=error_message, code=500, data=None)
|
||||
|
||||
|
||||
class ElectricityInfoResponse(BaseResponse):
|
||||
"""电费信息响应"""
|
||||
data: Optional[ElectricityInfo] = Field(default=None, description="电费信息")
|
||||
|
||||
@classmethod
|
||||
def from_data(cls, data: ElectricityInfo, success_message: str, error_message: str):
|
||||
"""根据数据创建响应"""
|
||||
# 检查是否是错误数据
|
||||
if data.balance.remaining_purchased >= 0 and data.balance.remaining_subsidy >= 0:
|
||||
return cls.success(data=data, message=success_message)
|
||||
elif data.balance.remaining_purchased == -2.0 and data.balance.remaining_subsidy == -2.0:
|
||||
# 未绑定房间的特定错误
|
||||
return cls.error(message="请先绑定宿舍房间后再查询电费信息", code=400, data=None)
|
||||
else:
|
||||
return cls.error(message=error_message, code=500, data=None)
|
||||
|
||||
|
||||
class PaymentInfoResponse(BaseResponse):
|
||||
"""充值信息响应"""
|
||||
data: Optional[PaymentInfo] = Field(default=None, description="充值信息")
|
||||
|
||||
@classmethod
|
||||
def from_data(cls, data: PaymentInfo, success_message: str, error_message: str):
|
||||
"""根据数据创建响应"""
|
||||
# 检查是否是错误数据
|
||||
if data.balance.remaining_purchased >= 0 and data.balance.remaining_subsidy >= 0:
|
||||
return cls.success(data=data, message=success_message)
|
||||
elif data.balance.remaining_purchased == -2.0 and data.balance.remaining_subsidy == -2.0:
|
||||
# 未绑定房间的特定错误
|
||||
return cls.error(message="请先绑定宿舍房间后再查询充值信息", code=400, data=None)
|
||||
else:
|
||||
return cls.error(message=error_message, code=500, data=None)
|
||||
|
||||
|
||||
# ==================== 房间绑定状态相关模型 ====================
|
||||
|
||||
class RoomBindingStatusData(BaseModel):
|
||||
"""房间绑定状态数据"""
|
||||
is_bound: bool = Field(..., description="是否已绑定房间")
|
||||
binding_info: Optional[RoomBindingInfo] = Field(default=None, description="绑定信息(如果已绑定)")
|
||||
|
||||
|
||||
class RoomBindingStatusResponse(BaseResponse):
|
||||
"""房间绑定状态响应"""
|
||||
data: Optional[RoomBindingStatusData] = Field(default=None, description="绑定状态信息")
|
||||
@@ -6,7 +6,7 @@ from provider.aufe.jwc.model import (
|
||||
ExamInfoResponse,
|
||||
TermScoreResponse,
|
||||
)
|
||||
from typing import List, Dict, Optional
|
||||
from typing import List, Dict
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ async def check_auth_status(
|
||||
userid=user.userid
|
||||
),
|
||||
)
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
# token无效或其他错误
|
||||
return AuthmeResponse(
|
||||
code=401,
|
||||
|
||||
@@ -4,7 +4,7 @@ from fastapi.routing import APIRouter
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from database.creator import get_db_session
|
||||
from database.user import UserProfile, User
|
||||
from database.user import UserProfile
|
||||
from provider.loveac.authme import fetch_user_by_token, AuthmeRequest
|
||||
from utils.file_manager import file_manager
|
||||
from .model import (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from router.common_model import BaseResponse
|
||||
from typing import Optional, Dict, Any, Union
|
||||
from typing import Optional, Union
|
||||
import json
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user