新增优惠券,订单,驿站等相关接口
This commit is contained in:
parent
772fc25a9d
commit
f096207aeb
@ -1,4 +1,4 @@
|
||||
from fastapi import Depends, HTTPException, Header
|
||||
from fastapi import Depends, HTTPException, Header, Cookie
|
||||
from typing import Optional
|
||||
from sqlalchemy.orm import Session
|
||||
from app.models.database import get_db
|
||||
@ -7,12 +7,19 @@ from app.core.security import verify_token
|
||||
|
||||
async def get_current_user(
|
||||
authorization: Optional[str] = Header(None),
|
||||
access_token: Optional[str] = Cookie(None),
|
||||
db: Session = Depends(get_db)
|
||||
) -> UserDB:
|
||||
if not authorization or not authorization.startswith("Bearer "):
|
||||
# 优先使用Header中的token,其次使用Cookie中的token
|
||||
token = None
|
||||
if authorization and authorization.startswith("Bearer "):
|
||||
token = authorization.split(" ")[1]
|
||||
elif access_token and access_token.startswith("Bearer "):
|
||||
token = access_token.split(" ")[1]
|
||||
|
||||
if not token:
|
||||
raise HTTPException(status_code=401, detail="未提供有效的认证信息")
|
||||
|
||||
token = authorization.split(" ")[1]
|
||||
phone = verify_token(token)
|
||||
if not phone:
|
||||
raise HTTPException(status_code=401, detail="Token已过期或无效")
|
||||
|
||||
149
app/api/endpoints/coupon.py
Normal file
149
app/api/endpoints/coupon.py
Normal file
@ -0,0 +1,149 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List, Optional
|
||||
from app.models.coupon import (
|
||||
CouponDB,
|
||||
UserCouponDB,
|
||||
CouponCreate,
|
||||
CouponUpdate,
|
||||
CouponInfo,
|
||||
UserCouponCreate,
|
||||
UserCouponInfo,
|
||||
CouponStatus
|
||||
)
|
||||
from app.models.database import get_db
|
||||
from app.api.deps import get_admin_user, get_current_user
|
||||
from app.models.user import UserDB
|
||||
from app.core.response import success_response, error_response, ResponseModel
|
||||
from datetime import datetime, timezone
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/", response_model=ResponseModel)
|
||||
async def create_coupon(
|
||||
coupon: CouponCreate,
|
||||
db: Session = Depends(get_db),
|
||||
admin: UserDB = Depends(get_admin_user)
|
||||
):
|
||||
"""创建优惠券(管理员)"""
|
||||
db_coupon = CouponDB(**coupon.model_dump())
|
||||
db.add(db_coupon)
|
||||
|
||||
try:
|
||||
db.commit()
|
||||
db.refresh(db_coupon)
|
||||
return success_response(data=CouponInfo.model_validate(db_coupon))
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
return error_response(code=500, message=f"创建优惠券失败: {str(e)}")
|
||||
|
||||
@router.put("/{coupon_id}", response_model=ResponseModel)
|
||||
async def update_coupon(
|
||||
coupon_id: int,
|
||||
coupon: CouponUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
admin: UserDB = Depends(get_admin_user)
|
||||
):
|
||||
"""更新优惠券(管理员)"""
|
||||
db_coupon = db.query(CouponDB).filter(CouponDB.id == coupon_id).first()
|
||||
if not db_coupon:
|
||||
return error_response(code=404, message="优惠券不存在")
|
||||
|
||||
update_data = coupon.model_dump(exclude_unset=True)
|
||||
for key, value in update_data.items():
|
||||
setattr(db_coupon, key, value)
|
||||
|
||||
try:
|
||||
db.commit()
|
||||
db.refresh(db_coupon)
|
||||
return success_response(data=CouponInfo.model_validate(db_coupon))
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
return error_response(code=500, message=f"更新优惠券失败: {str(e)}")
|
||||
|
||||
@router.post("/issue", response_model=ResponseModel)
|
||||
async def issue_coupon(
|
||||
user_coupon: UserCouponCreate,
|
||||
db: Session = Depends(get_db),
|
||||
admin: UserDB = Depends(get_admin_user)
|
||||
):
|
||||
"""发放优惠券给用户(管理员)"""
|
||||
# 查询优惠券信息
|
||||
coupon = db.query(CouponDB).filter(CouponDB.id == user_coupon.coupon_id).first()
|
||||
if not coupon:
|
||||
return error_response(code=404, message="优惠券不存在")
|
||||
|
||||
issued_coupons = []
|
||||
# 批量创建用户优惠券
|
||||
for _ in range(user_coupon.count):
|
||||
db_user_coupon = UserCouponDB(
|
||||
user_id=user_coupon.user_id,
|
||||
coupon_id=coupon.id,
|
||||
coupon_name=coupon.name,
|
||||
coupon_amount=coupon.amount,
|
||||
expire_time=user_coupon.expire_time
|
||||
)
|
||||
db.add(db_user_coupon)
|
||||
issued_coupons.append(db_user_coupon)
|
||||
|
||||
try:
|
||||
db.commit()
|
||||
return success_response(
|
||||
message=f"成功发放 {user_coupon.count} 张优惠券",
|
||||
data=[UserCouponInfo.model_validate(c) for c in issued_coupons]
|
||||
)
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
return error_response(code=500, message=f"发放优惠券失败: {str(e)}")
|
||||
|
||||
@router.get("/user/list", response_model=ResponseModel)
|
||||
async def get_user_coupons(
|
||||
skip: int = 0,
|
||||
limit: int = 10,
|
||||
status: Optional[CouponStatus] = None,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: UserDB = Depends(get_current_user)
|
||||
):
|
||||
"""获取用户的优惠券列表"""
|
||||
query = db.query(UserCouponDB).filter(
|
||||
UserCouponDB.user_id == current_user.userid
|
||||
)
|
||||
|
||||
# 如果指定了状态,添加状态过滤
|
||||
if status:
|
||||
query = query.filter(UserCouponDB.status == status)
|
||||
|
||||
# 更新过期状态
|
||||
now = datetime.now(timezone.utc)
|
||||
db.query(UserCouponDB).filter(
|
||||
UserCouponDB.user_id == current_user.userid,
|
||||
UserCouponDB.status == CouponStatus.UNUSED,
|
||||
UserCouponDB.expire_time < now
|
||||
).update({"status": CouponStatus.EXPIRED})
|
||||
|
||||
db.commit()
|
||||
|
||||
# 获取分页数据
|
||||
coupons = query.order_by(
|
||||
UserCouponDB.create_time.desc()
|
||||
).offset(skip).limit(limit).all()
|
||||
|
||||
return success_response(
|
||||
data=[UserCouponInfo.model_validate(c) for c in coupons]
|
||||
)
|
||||
|
||||
@router.get("/list", response_model=ResponseModel)
|
||||
async def get_all_coupons(
|
||||
skip: int = 0,
|
||||
limit: int = 10,
|
||||
db: Session = Depends(get_db),
|
||||
admin: UserDB = Depends(get_admin_user)
|
||||
):
|
||||
"""获取所有优惠券列表(管理员)"""
|
||||
coupons = db.query(CouponDB).order_by(
|
||||
CouponDB.create_time.desc()
|
||||
).offset(skip).limit(limit).all()
|
||||
|
||||
return success_response(
|
||||
data=[CouponInfo.model_validate(c) for c in coupons]
|
||||
)
|
||||
135
app/api/endpoints/order.py
Normal file
135
app/api/endpoints/order.py
Normal file
@ -0,0 +1,135 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
from app.models.order import (
|
||||
ShippingOrderDB,
|
||||
ShippingOrderPackageDB,
|
||||
OrderCreate,
|
||||
OrderInfo,
|
||||
OrderPackageInfo,
|
||||
generate_order_id
|
||||
)
|
||||
from app.models.database import get_db
|
||||
from app.api.deps import get_current_user
|
||||
from app.models.user import UserDB
|
||||
from app.core.response import success_response, error_response, ResponseModel
|
||||
from app.models.coupon import UserCouponDB, CouponStatus
|
||||
from datetime import datetime, timezone
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/", response_model=ResponseModel)
|
||||
async def create_shipping_order(
|
||||
order: OrderCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: UserDB = Depends(get_current_user)
|
||||
):
|
||||
"""创建配送订单"""
|
||||
|
||||
# 生成订单号
|
||||
orderid = generate_order_id()
|
||||
|
||||
# 计算优惠金额
|
||||
coupon_discount = 0
|
||||
if order.coupon_id:
|
||||
# 查询用户优惠券
|
||||
user_coupon = db.query(UserCouponDB).filter(
|
||||
UserCouponDB.id == order.coupon_id,
|
||||
UserCouponDB.user_id == current_user.userid,
|
||||
UserCouponDB.status == CouponStatus.UNUSED,
|
||||
UserCouponDB.expire_time > datetime.now(timezone.utc)
|
||||
).first()
|
||||
|
||||
if not user_coupon:
|
||||
return error_response(code=400, message="优惠券无效或已过期")
|
||||
|
||||
coupon_discount = user_coupon.coupon_amount
|
||||
# 更新优惠券状态
|
||||
user_coupon.status = CouponStatus.USED
|
||||
|
||||
# 计算最终金额
|
||||
final_amount = max(0, order.original_amount - coupon_discount)
|
||||
|
||||
# 创建订单
|
||||
db_order = ShippingOrderDB(
|
||||
orderid=orderid,
|
||||
userid=current_user.userid,
|
||||
addressid=order.addressid,
|
||||
package_count=order.package_count,
|
||||
original_amount=order.original_amount,
|
||||
coupon_discount_amount=coupon_discount,
|
||||
coupon_id=order.coupon_id,
|
||||
final_amount=final_amount
|
||||
)
|
||||
db.add(db_order)
|
||||
|
||||
# 创建订单包裹
|
||||
for package in order.packages:
|
||||
db_package = ShippingOrderPackageDB(
|
||||
orderid=orderid,
|
||||
station_id=package.station_id,
|
||||
pickup_codes=package.pickup_codes
|
||||
)
|
||||
db.add(db_package)
|
||||
|
||||
try:
|
||||
db.commit()
|
||||
db.refresh(db_order)
|
||||
|
||||
# 查询包裹信息
|
||||
packages = db.query(ShippingOrderPackageDB).filter(
|
||||
ShippingOrderPackageDB.orderid == orderid
|
||||
).all()
|
||||
|
||||
return success_response(
|
||||
message="订单创建成功",
|
||||
data={
|
||||
"order": OrderInfo.model_validate(db_order),
|
||||
"packages": [OrderPackageInfo.model_validate(p) for p in packages]
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
return error_response(code=500, message=f"订单创建失败: {str(e)}")
|
||||
|
||||
@router.get("/{orderid}", response_model=ResponseModel)
|
||||
async def get_order(
|
||||
orderid: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: UserDB = Depends(get_current_user)
|
||||
):
|
||||
"""获取订单详情"""
|
||||
# 查询订单
|
||||
order = db.query(ShippingOrderDB).filter(
|
||||
ShippingOrderDB.orderid == orderid,
|
||||
ShippingOrderDB.userid == current_user.userid
|
||||
).first()
|
||||
|
||||
if not order:
|
||||
return error_response(code=404, message="订单不存在")
|
||||
|
||||
# 查询包裹信息
|
||||
packages = db.query(ShippingOrderPackageDB).filter(
|
||||
ShippingOrderPackageDB.orderid == orderid
|
||||
).all()
|
||||
|
||||
return success_response(data={
|
||||
"order": OrderInfo.model_validate(order),
|
||||
"packages": [OrderPackageInfo.model_validate(p) for p in packages]
|
||||
})
|
||||
|
||||
@router.get("/", response_model=ResponseModel)
|
||||
async def get_user_orders(
|
||||
skip: int = 0,
|
||||
limit: int = 10,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: UserDB = Depends(get_current_user)
|
||||
):
|
||||
"""获取用户的订单列表"""
|
||||
orders = db.query(ShippingOrderDB).filter(
|
||||
ShippingOrderDB.userid == current_user.userid
|
||||
).order_by(
|
||||
ShippingOrderDB.create_time.desc()
|
||||
).offset(skip).limit(limit).all()
|
||||
|
||||
return success_response(data=[OrderInfo.model_validate(o) for o in orders])
|
||||
100
app/api/endpoints/station.py
Normal file
100
app/api/endpoints/station.py
Normal file
@ -0,0 +1,100 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List, Optional
|
||||
from app.models.station import StationDB, StationCreate, StationUpdate, StationInfo
|
||||
from app.models.database import get_db
|
||||
from app.api.deps import get_admin_user
|
||||
from app.models.user import UserDB
|
||||
from app.core.response import success_response, error_response, ResponseModel
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/", response_model=ResponseModel)
|
||||
async def create_station(
|
||||
station: StationCreate,
|
||||
db: Session = Depends(get_db),
|
||||
admin: UserDB = Depends(get_admin_user)
|
||||
):
|
||||
"""创建驿站"""
|
||||
db_station = StationDB(**station.model_dump())
|
||||
db.add(db_station)
|
||||
db.commit()
|
||||
db.refresh(db_station)
|
||||
return success_response(data=StationInfo.model_validate(db_station))
|
||||
|
||||
@router.get("/", response_model=ResponseModel)
|
||||
async def get_stations(
|
||||
skip: int = 0,
|
||||
limit: int = 10,
|
||||
community_id: Optional[int] = None,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取驿站列表"""
|
||||
query = db.query(StationDB)
|
||||
if community_id:
|
||||
query = query.filter(StationDB.community_id == community_id)
|
||||
|
||||
stations = query.offset(skip).limit(limit).all()
|
||||
return success_response(data=[StationInfo.model_validate(s) for s in stations])
|
||||
|
||||
@router.get("/{station_id}", response_model=ResponseModel)
|
||||
async def get_station(
|
||||
station_id: int,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取驿站详情"""
|
||||
station = db.query(StationDB).filter(StationDB.id == station_id).first()
|
||||
if not station:
|
||||
return error_response(code=404, message="驿站不存在")
|
||||
return success_response(data=StationInfo.model_validate(station))
|
||||
|
||||
@router.put("/{station_id}", response_model=ResponseModel)
|
||||
async def update_station(
|
||||
station_id: int,
|
||||
station: StationUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
admin: UserDB = Depends(get_admin_user)
|
||||
):
|
||||
"""更新驿站信息"""
|
||||
db_station = db.query(StationDB).filter(StationDB.id == station_id).first()
|
||||
if not db_station:
|
||||
return error_response(code=404, message="驿站不存在")
|
||||
|
||||
update_data = station.model_dump(exclude_unset=True)
|
||||
for key, value in update_data.items():
|
||||
setattr(db_station, key, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_station)
|
||||
return success_response(data=StationInfo.model_validate(db_station))
|
||||
|
||||
@router.delete("/{station_id}", response_model=ResponseModel)
|
||||
async def delete_station(
|
||||
station_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
admin: UserDB = Depends(get_admin_user)
|
||||
):
|
||||
"""删除驿站"""
|
||||
result = db.query(StationDB).filter(StationDB.id == station_id).delete()
|
||||
if not result:
|
||||
return error_response(code=404, message="驿站不存在")
|
||||
|
||||
db.commit()
|
||||
return success_response(message="驿站已删除")
|
||||
|
||||
@router.get("/community/{community_id}", response_model=ResponseModel)
|
||||
async def get_stations_by_community(
|
||||
community_id: int,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取指定社区的驿站列表"""
|
||||
stations = db.query(StationDB).filter(
|
||||
StationDB.community_id == community_id
|
||||
).all()
|
||||
|
||||
if not stations:
|
||||
return success_response(data=[]) # 返回空列表而不是错误
|
||||
|
||||
return success_response(
|
||||
data=[StationInfo.model_validate(s) for s in stations]
|
||||
)
|
||||
@ -1,6 +1,7 @@
|
||||
from fastapi import APIRouter, HTTPException, Depends
|
||||
from fastapi import APIRouter, HTTPException, Depends, Response
|
||||
from sqlalchemy.orm import Session
|
||||
from app.models.user import UserLogin, UserInfo, VerifyCodeRequest, UserDB
|
||||
from app.api.deps import get_current_user
|
||||
from app.models.database import get_db
|
||||
import random
|
||||
import string
|
||||
@ -9,8 +10,9 @@ from app.core.config import settings
|
||||
from unisdk.sms import UniSMS
|
||||
from unisdk.exception import UniException
|
||||
from datetime import timedelta
|
||||
from app.core.security import create_access_token
|
||||
from app.core.response import success_response, error_response
|
||||
from app.core.security import create_access_token, set_jwt_cookie, clear_jwt_cookie
|
||||
from app.core.response import success_response, error_response, ResponseModel
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@ -26,6 +28,10 @@ redis_client = redis.Redis(
|
||||
# 初始化短信客户端
|
||||
client = UniSMS(settings.UNI_APP_ID)
|
||||
|
||||
# 添加 Mock 登录请求模型
|
||||
class MockLoginRequest(BaseModel):
|
||||
phone: str = Field(..., pattern="^1[3-9]\d{9}$")
|
||||
|
||||
@router.post("/send-code")
|
||||
async def send_verify_code(request: VerifyCodeRequest):
|
||||
"""发送验证码"""
|
||||
@ -59,7 +65,11 @@ async def send_verify_code(request: VerifyCodeRequest):
|
||||
return error_response(message=f"发送验证码失败: {str(e)}")
|
||||
|
||||
@router.post("/login")
|
||||
async def login(user_login: UserLogin, db: Session = Depends(get_db)):
|
||||
async def login(
|
||||
user_login: UserLogin,
|
||||
db: Session = Depends(get_db),
|
||||
response: Response = None
|
||||
):
|
||||
"""用户登录"""
|
||||
phone = user_login.phone
|
||||
verify_code = user_login.verify_code
|
||||
@ -84,10 +94,13 @@ async def login(user_login: UserLogin, db: Session = Depends(get_db)):
|
||||
|
||||
# 创建访问令牌
|
||||
access_token = create_access_token(
|
||||
data={"sub": user.phone},
|
||||
expires_delta=timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
data={"sub": user.phone}
|
||||
)
|
||||
|
||||
# 设置JWT cookie
|
||||
if response:
|
||||
set_jwt_cookie(response, access_token)
|
||||
|
||||
return success_response(
|
||||
message="登录成功",
|
||||
data={
|
||||
@ -105,3 +118,51 @@ async def get_user_info(phone: str, db: Session = Depends(get_db)):
|
||||
return error_response(code=404, message="用户不存在")
|
||||
|
||||
return success_response(data=UserInfo.model_validate(user))
|
||||
|
||||
@router.post("/mock-login", response_model=ResponseModel)
|
||||
async def mock_login(
|
||||
request: MockLoginRequest,
|
||||
db: Session = Depends(get_db),
|
||||
response: Response = None
|
||||
):
|
||||
"""Mock登录接口(仅用于开发测试)"""
|
||||
if not settings.DEBUG:
|
||||
return error_response(code=403, message="该接口仅在开发环境可用")
|
||||
|
||||
# 查找或创建用户
|
||||
user = db.query(UserDB).filter(UserDB.phone == request.phone).first()
|
||||
if not user:
|
||||
user = UserDB(
|
||||
username=f"user_{request.phone[-4:]}",
|
||||
phone=request.phone
|
||||
)
|
||||
db.add(user)
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
|
||||
# 创建访问令牌
|
||||
access_token = create_access_token(
|
||||
data={"sub": user.phone}
|
||||
)
|
||||
|
||||
# 设置JWT cookie
|
||||
if response:
|
||||
set_jwt_cookie(response, access_token)
|
||||
|
||||
return success_response(
|
||||
message="登录成功",
|
||||
data={
|
||||
"user": UserInfo.model_validate(user),
|
||||
"access_token": access_token,
|
||||
"token_type": "bearer"
|
||||
}
|
||||
)
|
||||
|
||||
@router.post("/logout", response_model=ResponseModel)
|
||||
async def logout(
|
||||
response: Response,
|
||||
current_user: UserDB = Depends(get_current_user)
|
||||
):
|
||||
"""退出登录"""
|
||||
clear_jwt_cookie(response)
|
||||
return success_response(message="退出登录成功")
|
||||
@ -1,6 +1,8 @@
|
||||
from typing import Optional
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
class Settings(BaseSettings):
|
||||
DEBUG: bool = True # 开发模式标志
|
||||
API_V1_STR: str = "/api/v1"
|
||||
PROJECT_NAME: str = "FastAPI 项目"
|
||||
|
||||
@ -9,7 +11,7 @@ class Settings(BaseSettings):
|
||||
|
||||
# JWT 配置
|
||||
SECRET_KEY: str = "your-secret-key-here"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES: int | None = None # None 表示永不过期
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES: Optional[int] = None # None 表示永不过期
|
||||
|
||||
REDIS_HOST: str = "101.36.120.145"
|
||||
REDIS_PORT: int = 6379
|
||||
|
||||
@ -1,17 +1,39 @@
|
||||
from datetime import datetime, timedelta, UTC
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Optional
|
||||
from jose import JWTError, jwt
|
||||
from app.core.config import settings
|
||||
from fastapi import Response
|
||||
|
||||
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
|
||||
to_encode = data.copy()
|
||||
if expires_delta is not None:
|
||||
expire = datetime.now(UTC) + expires_delta
|
||||
expire = datetime.now(timezone.utc) + expires_delta
|
||||
to_encode.update({"exp": expire})
|
||||
|
||||
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm="HS256")
|
||||
return encoded_jwt
|
||||
|
||||
def set_jwt_cookie(response: Response, token: str):
|
||||
"""设置JWT cookie"""
|
||||
response.set_cookie(
|
||||
key="access_token",
|
||||
value=f"Bearer {token}",
|
||||
httponly=True, # 防止JavaScript访问
|
||||
secure=not settings.DEBUG, # 生产环境使用HTTPS
|
||||
samesite="lax", # CSRF保护
|
||||
max_age=None if settings.ACCESS_TOKEN_EXPIRE_MINUTES is None
|
||||
else settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60
|
||||
)
|
||||
|
||||
def clear_jwt_cookie(response: Response):
|
||||
"""清除JWT cookie"""
|
||||
response.delete_cookie(
|
||||
key="access_token",
|
||||
httponly=True,
|
||||
secure=not settings.DEBUG,
|
||||
samesite="lax"
|
||||
)
|
||||
|
||||
def verify_token(token: str) -> Optional[str]:
|
||||
try:
|
||||
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from app.api.endpoints import user, address, community
|
||||
from app.api.endpoints import user, address, community, station, order, coupon
|
||||
from app.models.database import Base, engine
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.responses import JSONResponse
|
||||
@ -29,6 +29,9 @@ app.add_middleware(
|
||||
app.include_router(user.router, prefix="/api/user", tags=["用户"])
|
||||
app.include_router(address.router, prefix="/api/address", tags=["配送地址"])
|
||||
app.include_router(community.router, prefix="/api/community", tags=["社区"])
|
||||
app.include_router(station.router, prefix="/api/station", tags=["驿站"])
|
||||
app.include_router(order.router, prefix="/api/order", tags=["订单"])
|
||||
app.include_router(coupon.router, prefix="/api/coupon", tags=["优惠券"])
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
|
||||
@ -2,6 +2,7 @@ from sqlalchemy import Column, Integer, String, ForeignKey, DateTime, Boolean
|
||||
from sqlalchemy.sql import func
|
||||
from pydantic import BaseModel, Field
|
||||
from .database import Base
|
||||
from typing import Optional
|
||||
|
||||
# 数据库模型
|
||||
class AddressDB(Base):
|
||||
@ -26,11 +27,11 @@ class AddressCreate(BaseModel):
|
||||
is_default: bool = False
|
||||
|
||||
class AddressUpdate(BaseModel):
|
||||
community_id: int | None = None
|
||||
address_detail: str | None = Field(None, max_length=200)
|
||||
name: str | None = Field(None, max_length=50)
|
||||
phone: str | None = Field(None, pattern="^1[3-9]\d{9}$")
|
||||
is_default: bool | None = None
|
||||
community_id: Optional[int] = None
|
||||
address_detail: Optional[str] = Field(None, max_length=200)
|
||||
name: Optional[str] = Field(None, max_length=50)
|
||||
phone: Optional[str] = Field(None, pattern="^1[3-9]\d{9}$")
|
||||
is_default: Optional[bool] = None
|
||||
|
||||
class AddressInfo(BaseModel):
|
||||
id: int
|
||||
|
||||
72
app/models/coupon.py
Normal file
72
app/models/coupon.py
Normal file
@ -0,0 +1,72 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from sqlalchemy import Column, Integer, String, Float, DateTime, ForeignKey, Enum
|
||||
from sqlalchemy.sql import func
|
||||
from pydantic import BaseModel, Field
|
||||
from .database import Base
|
||||
import enum
|
||||
|
||||
class CouponStatus(str, enum.Enum):
|
||||
UNUSED = "未使用"
|
||||
USED = "已使用"
|
||||
EXPIRED = "已过期"
|
||||
|
||||
# 数据库模型
|
||||
class CouponDB(Base):
|
||||
__tablename__ = "coupons"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String(100), nullable=False)
|
||||
amount = Column(Float, nullable=False)
|
||||
create_time = Column(DateTime(timezone=True), server_default=func.now())
|
||||
update_time = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
class UserCouponDB(Base):
|
||||
__tablename__ = "user_coupons"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
user_id = Column(Integer, ForeignKey("users.userid"), index=True)
|
||||
coupon_id = Column(Integer, ForeignKey("coupons.id"), index=True)
|
||||
coupon_name = Column(String(100), nullable=False)
|
||||
coupon_amount = Column(Float, nullable=False)
|
||||
expire_time = Column(DateTime(timezone=True), nullable=False)
|
||||
status = Column(Enum(CouponStatus), default=CouponStatus.UNUSED)
|
||||
create_time = Column(DateTime(timezone=True), server_default=func.now())
|
||||
update_time = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
# Pydantic 模型
|
||||
class CouponCreate(BaseModel):
|
||||
name: str = Field(..., max_length=100)
|
||||
amount: float = Field(..., gt=0)
|
||||
|
||||
class CouponUpdate(BaseModel):
|
||||
name: Optional[str] = Field(None, max_length=100)
|
||||
amount: Optional[float] = Field(None, gt=0)
|
||||
|
||||
class CouponInfo(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
amount: float
|
||||
create_time: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
class UserCouponCreate(BaseModel):
|
||||
user_id: int
|
||||
coupon_id: int
|
||||
expire_time: datetime
|
||||
count: int = Field(..., gt=0, description="发放数量")
|
||||
|
||||
class UserCouponInfo(BaseModel):
|
||||
id: int
|
||||
user_id: int
|
||||
coupon_id: int
|
||||
coupon_name: str
|
||||
coupon_amount: float
|
||||
expire_time: datetime
|
||||
status: CouponStatus
|
||||
create_time: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
72
app/models/order.py
Normal file
72
app/models/order.py
Normal file
@ -0,0 +1,72 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional, List
|
||||
from sqlalchemy import Column, String, Integer, Float, DateTime, ForeignKey
|
||||
from sqlalchemy.sql import func
|
||||
from pydantic import BaseModel, Field
|
||||
from .database import Base
|
||||
|
||||
# 数据库模型
|
||||
class ShippingOrderDB(Base):
|
||||
__tablename__ = "shipping_orders"
|
||||
|
||||
orderid = Column(String(32), primary_key=True)
|
||||
userid = Column(Integer, ForeignKey("users.userid"), index=True)
|
||||
addressid = Column(Integer, ForeignKey("delivery_addresses.id"), index=True)
|
||||
package_count = Column(Integer, nullable=False)
|
||||
original_amount = Column(Float, nullable=False)
|
||||
coupon_discount_amount = Column(Float, default=0)
|
||||
coupon_id = Column(Integer, ForeignKey("user_coupons.id"), nullable=True)
|
||||
final_amount = Column(Float, nullable=False)
|
||||
create_time = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
class ShippingOrderPackageDB(Base):
|
||||
__tablename__ = "shipping_order_packages"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
orderid = Column(String(32), ForeignKey("shipping_orders.orderid"), index=True)
|
||||
station_id = Column(Integer, ForeignKey("stations.id"), index=True)
|
||||
pickup_codes = Column(String(100), nullable=False)
|
||||
create_time = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
# Pydantic 模型
|
||||
class OrderPackage(BaseModel):
|
||||
station_id: int
|
||||
pickup_codes: str = Field(..., max_length=100)
|
||||
|
||||
class OrderCreate(BaseModel):
|
||||
addressid: int
|
||||
package_count: int = Field(..., gt=0)
|
||||
original_amount: float = Field(..., ge=0)
|
||||
coupon_id: Optional[int] = None
|
||||
packages: List[OrderPackage]
|
||||
|
||||
class OrderInfo(BaseModel):
|
||||
orderid: str
|
||||
userid: int
|
||||
addressid: int
|
||||
package_count: int
|
||||
original_amount: float
|
||||
coupon_discount_amount: float
|
||||
coupon_id: Optional[int]
|
||||
final_amount: float
|
||||
create_time: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
class OrderPackageInfo(BaseModel):
|
||||
id: int
|
||||
orderid: str
|
||||
station_id: int
|
||||
pickup_codes: str
|
||||
create_time: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
def generate_order_id() -> str:
|
||||
"""生成订单号:日期+时间戳"""
|
||||
now = datetime.now()
|
||||
date_str = now.strftime('%Y%m%d')
|
||||
timestamp = int(now.timestamp() * 1000)
|
||||
return f"{date_str}{timestamp}"
|
||||
32
app/models/station.py
Normal file
32
app/models/station.py
Normal file
@ -0,0 +1,32 @@
|
||||
from typing import Optional
|
||||
from sqlalchemy import Column, Integer, String, ForeignKey, DateTime
|
||||
from sqlalchemy.sql import func
|
||||
from pydantic import BaseModel, Field
|
||||
from .database import Base
|
||||
|
||||
# 数据库模型
|
||||
class StationDB(Base):
|
||||
__tablename__ = "stations"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String(100), nullable=False)
|
||||
community_id = Column(Integer, ForeignKey("communities.id"), index=True)
|
||||
create_time = Column(DateTime(timezone=True), server_default=func.now())
|
||||
update_time = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
# Pydantic 模型
|
||||
class StationCreate(BaseModel):
|
||||
name: str = Field(..., max_length=100)
|
||||
community_id: int
|
||||
|
||||
class StationUpdate(BaseModel):
|
||||
name: Optional[str] = Field(None, max_length=100)
|
||||
community_id: Optional[int] = None
|
||||
|
||||
class StationInfo(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
community_id: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
Loading…
Reference in New Issue
Block a user