增加图片取件码
This commit is contained in:
parent
30b68a662d
commit
6da0d075ad
32
app/api/endpoints/ocr.py
Normal file
32
app/api/endpoints/ocr.py
Normal file
@ -0,0 +1,32 @@
|
||||
from fastapi import APIRouter, Depends, UploadFile, File
|
||||
from app.core.response import success_response, error_response, ResponseModel
|
||||
from app.core.ocr_service import ocr_service
|
||||
from app.api.deps import get_current_user
|
||||
from app.models.user import UserDB
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/pickup_code", response_model=ResponseModel)
|
||||
async def recognize_pickup_code(
|
||||
file: UploadFile = File(...),
|
||||
current_user: UserDB = Depends(get_current_user)
|
||||
):
|
||||
"""识别收件码图片"""
|
||||
try:
|
||||
# 检查文件类型
|
||||
if not file.content_type.startswith('image/'):
|
||||
return error_response(code=400, message="只能上传图片文件")
|
||||
|
||||
# 读取文件内容
|
||||
content = await file.read()
|
||||
|
||||
# 调用OCR服务识别图片
|
||||
result = await ocr_service.recognize_pickup_code(content)
|
||||
|
||||
if not result.get("stations") or not any(station["pickup_codes"] for station in result["stations"]):
|
||||
return error_response(code=400, message="未能识别到取件码")
|
||||
|
||||
return success_response(data=result)
|
||||
|
||||
except Exception as e:
|
||||
return error_response(code=500, message=f"识别失败: {str(e)}")
|
||||
@ -51,20 +51,23 @@ def calculate_price(price_request: OrderPriceCalculateRequest,user: UserDB,db: S
|
||||
OrderPriceResult: 包含价格信息和使用的优惠券/积分信息
|
||||
"""
|
||||
# 计算所有包裹中的取件码总数
|
||||
package_count = sum(
|
||||
# 如果package.pickup_codes是空字符串,则取0
|
||||
0 if len(package.pickup_codes.split(',')) == 0 else len(package.pickup_codes.split(','))
|
||||
for package in price_request.packages
|
||||
if package.pickup_codes
|
||||
)
|
||||
if price_request.package_count > 0:
|
||||
package_count = price_request.package_count
|
||||
else:
|
||||
package_count = sum(
|
||||
# 如果package.pickup_codes是空字符串,则取0
|
||||
0 if len(package.pickup_codes.split(',')) == 0 else len(package.pickup_codes.split(','))
|
||||
for package in price_request.packages
|
||||
if package.pickup_codes
|
||||
)
|
||||
|
||||
result = OrderPriceResult(
|
||||
price_info=OrderPriceInfo(
|
||||
package_count=package_count,
|
||||
original_amount=settings.ORDER_BASE_PRICE,
|
||||
coupon_discount_amount=0,
|
||||
points_discount_amount=0,
|
||||
final_amount=settings.ORDER_BASE_PRICE
|
||||
original_amount=0,
|
||||
final_amount=0
|
||||
),
|
||||
price_detail_text=settings.ORDER_PREORDER_PRICE_TEXT
|
||||
)
|
||||
@ -185,6 +188,7 @@ async def create_order(
|
||||
address_community_name=address.community_name,
|
||||
address_community_building_name=address.community_building_name,
|
||||
address_detail=address.address_detail,
|
||||
pickup_images=order.price_request.pickup_images,
|
||||
package_count=price_info.package_count,
|
||||
original_amount=original_amount,
|
||||
coupon_discount_amount=coupon_discount,
|
||||
@ -197,20 +201,21 @@ async def create_order(
|
||||
db.add(db_order)
|
||||
|
||||
# 创建订单包裹
|
||||
for package in order.price_request.packages:
|
||||
# 如果包裹有取件码,则创建包裹
|
||||
if len(package.pickup_codes) > 0:
|
||||
station = db.query(StationDB).filter(
|
||||
StationDB.id == package.station_id
|
||||
).first()
|
||||
if order.price_request.packages:
|
||||
for package in order.price_request.packages:
|
||||
# 如果包裹有取件码,则创建包裹
|
||||
if len(package.pickup_codes) > 0:
|
||||
station = db.query(StationDB).filter(
|
||||
StationDB.id == package.station_id
|
||||
).first()
|
||||
|
||||
db_package = ShippingOrderPackageDB(
|
||||
orderid=orderid,
|
||||
station_id=package.station_id,
|
||||
station_name=station.name,
|
||||
pickup_codes=package.pickup_codes
|
||||
)
|
||||
db.add(db_package)
|
||||
db_package = ShippingOrderPackageDB(
|
||||
orderid=orderid,
|
||||
station_id=package.station_id,
|
||||
station_name=station.name,
|
||||
pickup_codes=package.pickup_codes
|
||||
)
|
||||
db.add(db_package)
|
||||
|
||||
try:
|
||||
# 如果使用了优惠券,更新优惠券状态
|
||||
@ -293,17 +298,6 @@ async def get_order_detail(
|
||||
|
||||
if not order:
|
||||
return error_response(code=404, message="订单不存在")
|
||||
|
||||
# 查询包裹信息,包含驿站名称
|
||||
packages = db.query(
|
||||
ShippingOrderPackageDB,
|
||||
StationDB.name.label('station_name')
|
||||
).join(
|
||||
StationDB,
|
||||
ShippingOrderPackageDB.station_id == StationDB.id
|
||||
).filter(
|
||||
ShippingOrderPackageDB.orderid == orderid
|
||||
).all()
|
||||
|
||||
# 如果有配送员 id,则获取配送员信息
|
||||
if order.deliveryman_user_id:
|
||||
@ -326,16 +320,32 @@ async def get_order_detail(
|
||||
# 计算配送员分账金额
|
||||
deliveryman_share = round(order.original_amount * settings.ORDER_DELIVERYMAN_SHARE_RATIO, 1)
|
||||
|
||||
# 构建完成图片
|
||||
complete_images = []
|
||||
if order.complete_images:
|
||||
complete_images = order.complete_images.split(",")
|
||||
complete_images = [process_image(image).thumbnail(500, 500).format(ImageFormat.WEBP).build() for image in complete_images]
|
||||
|
||||
# 查询包裹信息
|
||||
packages = db.query(
|
||||
ShippingOrderPackageDB
|
||||
).filter(
|
||||
ShippingOrderPackageDB.orderid == orderid
|
||||
).all()
|
||||
|
||||
if packages:
|
||||
# 构建包裹信息,包含驿站名称
|
||||
package_list = [{
|
||||
"id": p.id,
|
||||
"orderid": p.orderid,
|
||||
"station_id": p.station_id,
|
||||
"station_name": p.station_name,
|
||||
"pickup_codes": p.pickup_codes,
|
||||
"create_time": p.create_time
|
||||
} for p in packages]
|
||||
else:
|
||||
package_list = []
|
||||
|
||||
# 构建响应数据
|
||||
order_data = {
|
||||
"orderid": order.orderid,
|
||||
"userid": order.userid,
|
||||
"pickup_images": order.optimized_pickup_images,
|
||||
"package_count": order.package_count,
|
||||
"original_amount": order.original_amount,
|
||||
"coupon_discount_amount": order.coupon_discount_amount,
|
||||
@ -343,7 +353,8 @@ async def get_order_detail(
|
||||
"final_amount": order.final_amount,
|
||||
"deliveryman_share": deliveryman_share,
|
||||
"status": order.status,
|
||||
"complete_images": complete_images,
|
||||
"complete_images": order.optimized_complete_images,
|
||||
"packages": package_list,
|
||||
|
||||
"create_time": order.create_time,
|
||||
"complete_time": order.completed_time,
|
||||
@ -366,20 +377,11 @@ async def get_order_detail(
|
||||
"community_id": order.address_community_id,
|
||||
"community_name": order.address_community_name
|
||||
}
|
||||
|
||||
|
||||
# 构建包裹信息,包含驿站名称
|
||||
package_list = [{
|
||||
"id": p.ShippingOrderPackageDB.id,
|
||||
"orderid": p.ShippingOrderPackageDB.orderid,
|
||||
"station_id": p.ShippingOrderPackageDB.station_id,
|
||||
"station_name": p.station_name,
|
||||
"pickup_codes": p.ShippingOrderPackageDB.pickup_codes,
|
||||
"create_time": p.ShippingOrderPackageDB.create_time
|
||||
} for p in packages]
|
||||
|
||||
return success_response(data={
|
||||
"order": order_data,
|
||||
"packages": package_list
|
||||
"order": order_data
|
||||
})
|
||||
|
||||
# 提供一个接口,传入community_id,返回订单状态数量
|
||||
@ -526,13 +528,15 @@ async def get_user_orders(
|
||||
).all()
|
||||
|
||||
# 格式化包裹信息
|
||||
package_list = [{
|
||||
"id": package.id,
|
||||
"station_id": package.station_id,
|
||||
"station_name": package.station_name,
|
||||
"pickup_codes": package.pickup_codes
|
||||
} for package in packages]
|
||||
|
||||
if packages:
|
||||
package_list = [{
|
||||
"id": package.id,
|
||||
"station_id": package.station_id,
|
||||
"station_name": package.station_name,
|
||||
"pickup_codes": package.pickup_codes
|
||||
} for package in packages]
|
||||
else:
|
||||
package_list = []
|
||||
|
||||
#查询子订单
|
||||
sub_orders = db.query(PointProductOrderDB).filter(
|
||||
@ -543,6 +547,7 @@ async def get_user_orders(
|
||||
"orderid": order.orderid,
|
||||
"userid": order.userid,
|
||||
"status": order.status,
|
||||
"pickup_images": order.optimized_pickup_images,
|
||||
"package_count": order.package_count,
|
||||
"create_time": order.create_time,
|
||||
"delivery_method": order.delivery_method,
|
||||
@ -706,22 +711,21 @@ async def get_deliveryman_orders(
|
||||
for order in results:
|
||||
# 查询订单包裹信息
|
||||
packages = db.query(
|
||||
ShippingOrderPackageDB,
|
||||
StationDB.name.label('station_name')
|
||||
).join(
|
||||
StationDB,
|
||||
ShippingOrderPackageDB.station_id == StationDB.id
|
||||
ShippingOrderPackageDB
|
||||
).filter(
|
||||
ShippingOrderPackageDB.orderid == order.orderid
|
||||
).all()
|
||||
|
||||
# 格式化包裹信息
|
||||
package_list = [{
|
||||
"id": package.ShippingOrderPackageDB.id,
|
||||
"station_id": package.ShippingOrderPackageDB.station_id,
|
||||
"station_name": package.station_name,
|
||||
"pickup_codes": package.ShippingOrderPackageDB.pickup_codes
|
||||
} for package in packages]
|
||||
if packages:
|
||||
package_list = [{
|
||||
"id": package.id,
|
||||
"station_id": package.station_id,
|
||||
"station_name": package.station_name,
|
||||
"pickup_codes": package.pickup_codes
|
||||
} for package in packages]
|
||||
else:
|
||||
package_list = []
|
||||
|
||||
# 查询子订单
|
||||
sub_orders = db.query(PointProductOrderDB).filter(
|
||||
@ -732,9 +736,14 @@ async def get_deliveryman_orders(
|
||||
"orderid": order.orderid,
|
||||
"userid": order.userid,
|
||||
"status": order.status,
|
||||
"pickup_images": order.optimized_pickup_images,
|
||||
"package_count": order.package_count,
|
||||
"create_time": order.create_time,
|
||||
"delivery_method": order.delivery_method,
|
||||
"original_amount": order.original_amount,
|
||||
"coupon_discount_amount": order.coupon_discount_amount,
|
||||
"point_discount_amount": order.point_discount_amount,
|
||||
"final_amount": order.final_amount,
|
||||
"packages": package_list,
|
||||
"sub_orders": [PointProductOrderInfo.model_validate(sub_order) for sub_order in sub_orders],
|
||||
"address": {
|
||||
@ -1087,27 +1096,29 @@ async def get_admin_orders(
|
||||
for order in results:
|
||||
# 查询订单包裹信息
|
||||
packages = db.query(
|
||||
ShippingOrderPackageDB,
|
||||
StationDB.name.label('station_name')
|
||||
).join(
|
||||
StationDB,
|
||||
ShippingOrderPackageDB.station_id == StationDB.id
|
||||
ShippingOrderPackageDB
|
||||
).filter(
|
||||
ShippingOrderPackageDB.orderid == order.orderid
|
||||
).all()
|
||||
|
||||
# 格式化包裹信息
|
||||
package_list = [{
|
||||
"id": package.ShippingOrderPackageDB.id,
|
||||
"station_id": package.ShippingOrderPackageDB.station_id,
|
||||
"id": package.id,
|
||||
"station_id": package.station_id,
|
||||
"station_name": package.station_name,
|
||||
"pickup_codes": package.ShippingOrderPackageDB.pickup_codes
|
||||
"pickup_codes": package.pickup_codes
|
||||
} for package in packages]
|
||||
|
||||
# 查询子订单
|
||||
sub_orders = db.query(PointProductOrderDB).filter(
|
||||
PointProductOrderDB.delivery_order_id == order.orderid
|
||||
).all()
|
||||
|
||||
orders.append({
|
||||
"orderid": order.orderid,
|
||||
"userid": order.userid,
|
||||
"status": order.status,
|
||||
"pickup_images": order.optimized_pickup_images,
|
||||
"package_count": order.package_count,
|
||||
"create_time": order.create_time,
|
||||
"delivery_method": order.delivery_method,
|
||||
@ -1116,13 +1127,17 @@ async def get_admin_orders(
|
||||
"point_discount_amount": order.point_discount_amount,
|
||||
"final_amount": order.final_amount,
|
||||
"packages": package_list,
|
||||
"sub_orders": [PointProductOrderInfo.model_validate(sub_order) for sub_order in sub_orders],
|
||||
"address": {
|
||||
"name": order.address_customer_name,
|
||||
"phone": order.address_customer_phone,
|
||||
"gender": order.address_customer_gender,
|
||||
"community_id": order.address_community_id,
|
||||
"community_name": order.address_community_name,
|
||||
"building_id": order.address_community_building_id,
|
||||
"building_name": order.address_community_building_name,
|
||||
"address_detail": order.address_detail
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return success_response(data={
|
||||
|
||||
154
app/core/ocr_service.py
Normal file
154
app/core/ocr_service.py
Normal file
@ -0,0 +1,154 @@
|
||||
from tencentcloud.common import credential
|
||||
from tencentcloud.common.profile.client_profile import ClientProfile
|
||||
from tencentcloud.common.profile.http_profile import HttpProfile
|
||||
from tencentcloud.ocr.v20181119 import ocr_client, models
|
||||
from app.core.config import settings
|
||||
import json
|
||||
import base64
|
||||
|
||||
class OCRService:
|
||||
def __init__(self):
|
||||
cred = credential.Credential(settings.TENCENT_SECRET_ID, settings.TENCENT_SECRET_KEY)
|
||||
httpProfile = HttpProfile()
|
||||
httpProfile.endpoint = "ocr.tencentcloudapi.com"
|
||||
|
||||
clientProfile = ClientProfile()
|
||||
clientProfile.httpProfile = httpProfile
|
||||
self.client = ocr_client.OcrClient(cred, settings.TENCENT_REGION, clientProfile)
|
||||
|
||||
async def recognize_pickup_code(self, image_content: bytes) -> dict:
|
||||
"""识别收件码图片"""
|
||||
try:
|
||||
# 将图片内容转为base64
|
||||
img_base64 = base64.b64encode(image_content).decode()
|
||||
|
||||
req = models.GeneralAccurateOCRRequest()
|
||||
req.ImageBase64 = img_base64
|
||||
|
||||
resp = self.client.GeneralAccurateOCR(req)
|
||||
result = json.loads(resp.to_json_string())
|
||||
|
||||
print(result)
|
||||
|
||||
# 解析文本内容
|
||||
text_list = []
|
||||
for item in result.get("TextDetections", []):
|
||||
text_list.append(item["DetectedText"])
|
||||
|
||||
# 提取关键信息
|
||||
pickup_info = self._extract_pickup_info(text_list)
|
||||
return pickup_info
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"识别失败: {str(e)}")
|
||||
|
||||
def _is_valid_pickup_code(self, text: str) -> bool:
|
||||
"""验证是否是有效的取件码格式"""
|
||||
import re
|
||||
# 匹配格式:xx-x-xxx 或 xx-xx-xxx 等类似格式
|
||||
patterns = [
|
||||
r'\b\d{1,2}-\d{1,2}-\d{2,3}\b', # 15-4-223
|
||||
r'\b\d{4,8}\b', # 普通4-8位数字
|
||||
]
|
||||
|
||||
for pattern in patterns:
|
||||
if re.search(pattern, text):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _extract_pickup_info(self, text_list: list) -> dict:
|
||||
"""提取收件码信息"""
|
||||
# 存储所有驿站信息
|
||||
stations = []
|
||||
current_station = None
|
||||
current_codes = []
|
||||
|
||||
pickup_info = {
|
||||
"stations": [], # 驿站列表
|
||||
"app_type": None # APP类型(菜鸟/京东等)
|
||||
}
|
||||
|
||||
# 识别APP类型
|
||||
app_keywords = {
|
||||
"菜鸟": "CAINIAO",
|
||||
"京东": "JD",
|
||||
"顺丰": "SF"
|
||||
}
|
||||
|
||||
for text in text_list:
|
||||
# 查找APP类型
|
||||
for keyword, app_type in app_keywords.items():
|
||||
if keyword in text:
|
||||
pickup_info["app_type"] = app_type
|
||||
break
|
||||
|
||||
# 查找驿站名称
|
||||
is_station = False
|
||||
if "驿站" in text:
|
||||
is_station = True
|
||||
elif "站点" in text:
|
||||
is_station = True
|
||||
elif "仓" in text:
|
||||
is_station = True
|
||||
elif "站" in text:
|
||||
is_station = True
|
||||
elif "分拨" in text:
|
||||
is_station = True
|
||||
elif "分拣" in text:
|
||||
is_station = True
|
||||
elif "分拨" in text:
|
||||
is_station = True
|
||||
|
||||
if is_station:
|
||||
# 如果之前有未保存的驿站信息,先保存
|
||||
if current_station and current_codes:
|
||||
stations.append({
|
||||
"station_name": current_station,
|
||||
"pickup_codes": current_codes
|
||||
})
|
||||
# 开始新的驿站信息收集
|
||||
current_station = text
|
||||
current_codes = []
|
||||
|
||||
# 查找取件码
|
||||
if self._is_valid_pickup_code(text):
|
||||
# 清理文本中的多余字符
|
||||
cleaned_text = ''.join(c for c in text if c.isdigit() or c == '-')
|
||||
# 提取所有匹配的取件码
|
||||
import re
|
||||
for pattern in [r'\d{1,2}-\d{1,2}-\d{2,3}', r'\d{4,8}']:
|
||||
matches = re.finditer(pattern, cleaned_text)
|
||||
for match in matches:
|
||||
code = match.group()
|
||||
# 如果已找到驿站,将取件码添加到当前驿站
|
||||
if current_station and code not in current_codes:
|
||||
current_codes.append(code)
|
||||
# 如果还没找到驿站,暂存取件码
|
||||
elif code not in current_codes:
|
||||
current_codes.append(code)
|
||||
|
||||
# 保存最后一个驿站的信息
|
||||
if current_station and current_codes:
|
||||
stations.append({
|
||||
"station_name": current_station,
|
||||
"pickup_codes": current_codes
|
||||
})
|
||||
# 如果有未分配到驿站的取件码,创建一个默认驿站
|
||||
elif current_codes:
|
||||
stations.append({
|
||||
"station_name": None,
|
||||
"pickup_codes": current_codes
|
||||
})
|
||||
|
||||
# 如果找到了取件码但没找到APP类型,根据取件码格式推测
|
||||
if stations and not pickup_info["app_type"]:
|
||||
# 如果任一取件码包含连字符,判定为菜鸟
|
||||
for station in stations:
|
||||
if any('-' in code for code in station["pickup_codes"]):
|
||||
pickup_info["app_type"] = "CAINIAO"
|
||||
break
|
||||
|
||||
pickup_info["stations"] = stations
|
||||
return pickup_info
|
||||
|
||||
ocr_service = OCRService()
|
||||
@ -1,6 +1,6 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from app.api.endpoints import wechat,user, address, community, station, order, coupon, community_building, upload, merchant, merchant_product, merchant_order, point, config, merchant_category, log, account,merchant_pay_order, message, bank_card, withdraw, mp, point_product, point_product_order, coupon_activity
|
||||
from app.api.endpoints import wechat,user, address, community, station, order, coupon, community_building, upload, merchant, merchant_product, merchant_order, point, config, merchant_category, log, account,merchant_pay_order, message, bank_card, withdraw, mp, point_product, point_product_order, coupon_activity, ocr
|
||||
from app.models.database import Base, engine
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.responses import JSONResponse
|
||||
@ -58,6 +58,7 @@ app.include_router(message.router, prefix="/api/message", tags=["消息中心"])
|
||||
app.include_router(upload.router, prefix="/api/upload", tags=["文件上传"])
|
||||
app.include_router(config.router, prefix="/api/config", tags=["系统配置"])
|
||||
app.include_router(log.router, prefix="/api/logs", tags=["系统日志"])
|
||||
app.include_router(ocr.router, prefix="/api/ai/ocr", tags=["图像识别"])
|
||||
|
||||
|
||||
@app.get("/")
|
||||
|
||||
@ -51,6 +51,8 @@ class ShippingOrderDB(Base):
|
||||
address_community_building_name = Column(String(50), nullable=False, default='') # 楼栋名称快照
|
||||
address_detail = Column(String(100), nullable=False, default='') # 详细地址快照
|
||||
|
||||
# 取件图片
|
||||
pickup_images = Column(String(1000), nullable=True) # 取件图片URL,多个URL用逗号分隔
|
||||
|
||||
delivery_method = Column(Enum(DeliveryMethod), nullable=False)
|
||||
package_count = Column(Integer, nullable=False)
|
||||
@ -81,6 +83,12 @@ class ShippingOrderDB(Base):
|
||||
if self.complete_images:
|
||||
return [process_image(image).quality(80).thumbnail(width=450, height=450).format(ImageFormat.WEBP).build() for image in self.complete_images.split(",")]
|
||||
return []
|
||||
|
||||
@property
|
||||
def optimized_pickup_images(self):
|
||||
if self.pickup_images:
|
||||
return [process_image(image).quality(80).thumbnail(width=450, height=450).format(ImageFormat.WEBP).build() for image in self.pickup_images.split(",")]
|
||||
return []
|
||||
|
||||
class ShippingOrderPackageDB(Base):
|
||||
__tablename__ = "shipping_order_packages"
|
||||
@ -99,7 +107,9 @@ class OrderPackage(BaseModel):
|
||||
|
||||
# 先定义 OrderPriceCalculateRequest
|
||||
class OrderPriceCalculateRequest(BaseModel):
|
||||
packages: List[OrderPackage]
|
||||
package_count: Optional[int] = None
|
||||
pickup_images: Optional[str] = None
|
||||
packages: Optional[List[OrderPackage]] = None
|
||||
|
||||
# 然后再定义 OrderCreate
|
||||
class OrderCreate(BaseModel):
|
||||
@ -123,6 +133,7 @@ class OrderInfo(BaseModel):
|
||||
address_detail: str
|
||||
address_customer_gender: Gender
|
||||
|
||||
pickup_images: Optional[str] = None
|
||||
package_count: int
|
||||
original_amount: float
|
||||
coupon_discount_amount: float
|
||||
@ -165,12 +176,12 @@ class OrderPackageInfo(BaseModel):
|
||||
from_attributes = True
|
||||
|
||||
class OrderPriceInfo(BaseModel):
|
||||
package_count: int
|
||||
original_amount: float
|
||||
package_count: int = 0
|
||||
original_amount: float = 0
|
||||
coupon_discount_amount: float = 0
|
||||
points_discount_amount: float = 0
|
||||
coupon_id: Optional[int] = None
|
||||
final_amount: float
|
||||
final_amount: float = 0
|
||||
|
||||
# 添加取消订单请求模型
|
||||
class OrderCancel(BaseModel):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user