diff --git a/app/api/endpoints/community.py b/app/api/endpoints/community.py index def7d5f..05503eb 100644 --- a/app/api/endpoints/community.py +++ b/app/api/endpoints/community.py @@ -1,7 +1,10 @@ from fastapi import APIRouter, Depends from sqlalchemy.orm import Session -from typing import List -from app.models.community import CommunityDB, CommunityCreate, CommunityUpdate, CommunityInfo +from typing import List, Optional +from app.models.community import ( + CommunityDB, CommunityCreate, CommunityUpdate, + CommunityInfo, CommunityStatus +) from app.models.database import get_db from app.api.deps import get_admin_user from app.models.user import UserDB @@ -22,32 +25,51 @@ async def create_community( db.refresh(db_community) return success_response(data=CommunityInfo.model_validate(db_community)) -@router.get("/", response_model=ResponseModel) +@router.get("", response_model=ResponseModel) async def get_communities( - latitude: float, - longitude: float, + latitude: Optional[float] = None, + longitude: Optional[float] = None, + status: Optional[CommunityStatus] = None, + skip: int = 0, + limit: int = 10, db: Session = Depends(get_db) ): """获取社区列表""" - communities = db.query(CommunityDB).all() + # 构建查询 + query = db.query(CommunityDB) + + # 状态过滤 + if status: + query = query.filter(CommunityDB.status == status) + + # 获取总数 + total = query.count() + + # 查询数据 + communities = query.offset(skip).limit(limit).all() - # 计算距离并排序 community_list = [] for community in communities: - # 使用 Haversine 公式计算两点之间的距离 - distance = calculate_distance( - latitude, longitude, - community.latitude, community.longitude - ) - community_info = CommunityInfo.model_validate(community) - community_info.distance = distance + + # 如果提供了经纬度,则计算距离 + if latitude is not None and longitude is not None: + distance = calculate_distance( + latitude, longitude, + community.latitude, community.longitude + ) + community_info.distance = distance + community_list.append(community_info) - # 按距离排序 - community_list.sort(key=lambda x: x.distance) + # 如果计算了距离,则按距离排序 + if latitude is not None and longitude is not None: + community_list.sort(key=lambda x: x.distance) - return success_response(data=community_list) + return success_response(data={ + "total": total, + "items": community_list + }) def calculate_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float: """计算两点之间的距离(米)""" diff --git a/app/api/endpoints/community_building.py b/app/api/endpoints/community_building.py index 5d5c9fa..bb153b0 100644 --- a/app/api/endpoints/community_building.py +++ b/app/api/endpoints/community_building.py @@ -1,6 +1,6 @@ from fastapi import APIRouter, Depends from sqlalchemy.orm import Session -from typing import List +from typing import List, Optional from app.models.community_building import ( CommunityBuildingDB, CommunityBuildingCreate, @@ -14,23 +14,28 @@ from app.core.response import success_response, error_response, ResponseModel router = APIRouter() -@router.get("/list/{community_id}", response_model=ResponseModel) -async def get_community_buildings( - community_id: int, +@router.get("/list", response_model=ResponseModel) +async def get_buildings( + community_id: Optional[int] = None, skip: int = 0, - limit: int = 20, + limit: int = 10, db: Session = Depends(get_db) ): - """获取小区的楼栋列表""" - buildings = db.query(CommunityBuildingDB).filter( - CommunityBuildingDB.community_id == community_id - ).order_by( - CommunityBuildingDB.building_number - ).offset(skip).limit(limit).all() + """获取楼栋列表""" + query = db.query(CommunityBuildingDB) + if community_id: + query = query.filter(CommunityBuildingDB.community_id == community_id) - return success_response(data=[ - CommunityBuildingInfo.model_validate(b) for b in buildings - ]) + # 获取总数 + total = query.count() + + # 查询数据 + buildings = query.offset(skip).limit(limit).all() + + return success_response(data={ + "total": total, + "items": [CommunityBuildingInfo.model_validate(b) for b in buildings] + }) @router.post("", response_model=ResponseModel) async def create_building( diff --git a/app/api/endpoints/station.py b/app/api/endpoints/station.py index 06fe236..bf0f74b 100644 --- a/app/api/endpoints/station.py +++ b/app/api/endpoints/station.py @@ -24,9 +24,9 @@ async def create_station( @router.get("/", response_model=ResponseModel) async def get_stations( + community_id: Optional[int] = None, skip: int = 0, limit: int = 10, - community_id: Optional[int] = None, db: Session = Depends(get_db) ): """获取驿站列表""" @@ -34,8 +34,16 @@ async def get_stations( if community_id: query = query.filter(StationDB.community_id == community_id) + # 获取总数 + total = query.count() + + # 查询数据 stations = query.offset(skip).limit(limit).all() - return success_response(data=[StationInfo.model_validate(s) for s in stations]) + + return success_response(data={ + "total": total, + "items": [StationInfo.model_validate(s) for s in stations] + }) @router.get("/{station_id}", response_model=ResponseModel) async def get_station( diff --git a/app/api/endpoints/user.py b/app/api/endpoints/user.py index 6a43aaf..81ba298 100644 --- a/app/api/endpoints/user.py +++ b/app/api/endpoints/user.py @@ -35,6 +35,10 @@ client = UniSMS(settings.UNI_APP_ID) class MockLoginRequest(BaseModel): phone: str = Field(..., pattern="^1[3-9]\d{9}$") +class ResetPasswordRequest(BaseModel): + user_id: int + new_password: str = Field(..., min_length=6, max_length=20) + @router.post("/send-code") async def send_verify_code(request: VerifyCodeRequest): """发送验证码""" @@ -251,7 +255,7 @@ async def update_user_roles( db.rollback() return error_response(code=500, message=f"更新失败: {str(e)}") -@router.post("/password_login", response_model=ResponseModel) +@router.post("/password-login", response_model=ResponseModel) async def password_login( login_data: UserPasswordLogin, db: Session = Depends(get_db) @@ -269,11 +273,12 @@ async def password_login( return error_response(code=401, message="密码错误") # 生成访问令牌 - access_token = create_access_token(user.phone) + access_token = create_access_token(data={"sub": user.phone}) return success_response( data={ - "access_token": f"Bearer {access_token}", + "access_token": access_token, + "token_type": "bearer", "user": UserInfo.model_validate(user) } ) @@ -328,4 +333,66 @@ def issue_register_coupons(db: Session, user_id: int): expire_time=expire_time, status="unused" ) - db.add(user_coupon) \ No newline at end of file + db.add(user_coupon) + +@router.get("/list", response_model=ResponseModel) +async def get_user_list( + skip: int = 0, + limit: int = 10, + db: Session = Depends(get_db), + admin: UserDB = Depends(get_admin_user) # 仅管理员可访问 +): + """获取用户列表(管理员) + Args: + skip: 跳过记录数 + limit: 返回记录数 + """ + total = db.query(UserDB).count() + users = db.query(UserDB).order_by( + UserDB.create_time.desc() + ).offset(skip).limit(limit).all() + + # 处理手机号脱敏 + def mask_phone(phone: str) -> str: + return f"{phone[:3]}****{phone[7:]}" + + user_list = [] + for user in users: + user_info = UserInfo.model_validate(user) + user_info.phone = mask_phone(user_info.phone) # 手机号脱敏 + user_list.append(user_info) + + return success_response(data={ + "total": total, + "items": user_list + }) + +@router.post("/reset-password", response_model=ResponseModel) +async def reset_password( + request: ResetPasswordRequest, + db: Session = Depends(get_db), + admin: UserDB = Depends(get_admin_user) # 仅管理员可操作 +): + """重置用户密码(管理员)""" + # 查找用户 + user = db.query(UserDB).filter(UserDB.userid == request.user_id).first() + if not user: + return error_response(code=404, message="用户不存在") + + # 重置密码 + hashed_password = get_password_hash(request.new_password) + user.password = hashed_password + + try: + db.commit() + return success_response( + message="密码重置成功", + data={ + "userid": user.userid, + "username": user.username, + "phone": f"{user.phone[:3]}****{user.phone[7:]}" # 手机号脱敏 + } + ) + except Exception as e: + db.rollback() + return error_response(code=500, message=f"密码重置失败: {str(e)}") \ No newline at end of file diff --git a/app/models/community.py b/app/models/community.py index d33d97e..86f5a37 100644 --- a/app/models/community.py +++ b/app/models/community.py @@ -1,9 +1,14 @@ from typing import Optional -from sqlalchemy import Column, Integer, String, DECIMAL, DateTime +import enum +from sqlalchemy import Column, Integer, String, DECIMAL, DateTime, Enum from sqlalchemy.sql import func from pydantic import BaseModel, Field from .database import Base +class CommunityStatus(str, enum.Enum): + UNOPEN = "UNOPEN" # 未运营 + OPENING = "OPENING" # 已运营 + # 数据库模型 class CommunityDB(Base): __tablename__ = "communities" @@ -13,6 +18,7 @@ class CommunityDB(Base): address = Column(String(200), nullable=False) longitude = Column(DECIMAL(9,6), nullable=False) # 经度,精确到小数点后6位 latitude = Column(DECIMAL(9,6), nullable=False) # 纬度,精确到小数点后6位 + status = Column(Enum(CommunityStatus), nullable=False, default=CommunityStatus.UNOPEN) create_time = Column(DateTime(timezone=True), server_default=func.now()) update_time = Column(DateTime(timezone=True), onupdate=func.now()) @@ -22,12 +28,14 @@ class CommunityCreate(BaseModel): address: str = Field(..., max_length=200) longitude: float = Field(..., ge=-180, le=180) latitude: float = Field(..., ge=-90, le=90) + status: CommunityStatus = Field(default=CommunityStatus.UNOPEN) class CommunityUpdate(BaseModel): name: Optional[str] = Field(None, max_length=100) address: Optional[str] = Field(None, max_length=200) longitude: Optional[float] = Field(None, ge=-180, le=180) latitude: Optional[float] = Field(None, ge=-90, le=90) + status: Optional[CommunityStatus] = None class CommunityInfo(BaseModel): id: int @@ -35,6 +43,7 @@ class CommunityInfo(BaseModel): address: str latitude: float longitude: float + status: CommunityStatus distance: Optional[float] = None # 距离,单位:米 class Config: diff --git a/app/models/user.py b/app/models/user.py index 42d0573..800d909 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -47,11 +47,12 @@ class UserInfo(BaseModel): username: str phone: str user_code: str - referral_code: Optional[str] + referral_code: Optional[str] = None avatar: Optional[str] = None - gender: Gender - points: float + gender: Gender = Gender.UNKNOWN + points: int = 0 roles: List[UserRole] + create_time: datetime class Config: from_attributes = True diff --git a/requirements.txt b/requirements.txt index b088b01..4967f36 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,5 @@ redis==5.0.1 pymysql==1.1.0 SQLAlchemy==2.0.27 unisms -cos-python-sdk-v5==1.9.25 \ No newline at end of file +cos-python-sdk-v5==1.9.25 +bcrypt \ No newline at end of file