客户信息删除代码更新

This commit is contained in:
z66
2025-12-04 09:46:44 +08:00
parent 49fc75214f
commit 1e83d5b19a
6 changed files with 192 additions and 32 deletions
+9 -6
View File
@@ -106,7 +106,7 @@ GET /health
```json
{
"status": "ok",
"version": "1.0.0"
"version": "2.0.0"
}
```
@@ -370,13 +370,16 @@ curl -X POST http://localhost:5003/webhook \
## 🔄 版本历史
- **v1.0.0**: 初始版本
- **v2.0.0**:
- fastapi 完全重构
- 支持 F6 系统相关操作
- 支持BI任务处理
- **1.0.0**: 初始版本
- 实现基本的 Webhook 接口
- 支持 F6 系统相关操作
- 支持文件上传和校验
- 支持品牌、客户、车辆管理
- 支持数据删除操作
- 支持BI任务处理
## 📄 许可证
+48 -7
View File
@@ -10,6 +10,7 @@ API 路由定义模块
from fastapi import APIRouter, Request, HTTPException, status, Depends
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
from pydantic import ValidationError
from typing import Dict, Any
import json
import anyio
@@ -37,7 +38,7 @@ async def healthcheck():
用于检查服务是否正常运行
"""
return HealthResponse(status="ok", version="1.0.0")
return HealthResponse(status="ok", version="2.0.0")
@router.post("/webhook", response_model=WebhookResponse, tags=["业务"])
@@ -73,14 +74,14 @@ async def webhook(
raw_data = await request.json()
# 使用 Pydantic 进行数据验证(允许额外字段)
webhook_data = WebhookRequest(**raw_data)
data = webhook_data.dict(exclude_none=True)
data = webhook_data.model_dump(exclude_none=True)
except json.JSONDecodeError:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="请求体必须是有效的 JSON 格式"
)
except Exception as e:
logger.warning(f"请求数据验证失败: {str(e)}")
logger.warning(f"请求数据验证失败: {str(e)}, 原始数据: {raw_data if 'raw_data' in locals() else 'N/A'}")
# 如果验证失败,仍然尝试使用原始数据(向后兼容)
data = raw_data if 'raw_data' in locals() else {}
@@ -88,9 +89,11 @@ async def webhook(
header = request.headers
decoded_header = app_tools.decode_headers(header)
# 验证 Action 字段
action = decoded_header.get('Action')
# 验证 Action 字段HTTP头在FastAPI中会被转换为小写)
# 同时检查 'Action' 和 'action' 以兼容不同情况
action = decoded_header.get('Action') or decoded_header.get('action')
if not action:
logger.warning(f"请求头中缺少 Action 字段,请求头: {decoded_header}")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="请求头中缺少必需的 Action 字段"
@@ -98,7 +101,8 @@ async def webhook(
# 处理 F6_Plugin 特殊逻辑
if action == 'F6_Plugin':
check = decoded_header.get('Check')
# 同时检查 'Check' 和 'check' 以兼容不同情况
check = decoded_header.get('Check') or decoded_header.get('check')
if check == '':
handler = f6_plugin_module.check_file
elif check == '':
@@ -150,13 +154,50 @@ async def webhook(
if not isinstance(result, dict):
result = {"msg": str(result)}
# 处理 msg 字段:如果 msg 是字典,将其内容展开到结果中
if "msg" in result and isinstance(result["msg"], dict):
msg_dict = result.pop("msg")
logger.warning(f"操作 {action} 返回的 msg 字段是字典类型,正在自动转换。原始数据: {json.dumps(msg_dict, ensure_ascii=False)}")
# 如果字典中有 msg 字段,使用它;否则使用 JSON 字符串
if "msg" in msg_dict:
result["msg"] = msg_dict.pop("msg")
else:
result["msg"] = json.dumps(msg_dict, ensure_ascii=False)
# 将字典中的其他字段合并到结果中
result.update(msg_dict)
if "msg" not in result:
result["msg"] = "操作完成"
# 确保 msg 是字符串类型
if not isinstance(result.get("msg"), str):
logger.warning(f"操作 {action} 返回的 msg 字段类型为 {type(result.get('msg'))},正在转换为字符串")
result["msg"] = str(result.get("msg", "操作完成"))
logger.info(f"操作完成: {action}, 结果: {json.dumps(result, ensure_ascii=False)}")
# 返回响应(使用 Pydantic 模型验证)
return WebhookResponse(**result)
try:
return WebhookResponse(**result)
except ValidationError as validation_error:
# 捕获 Pydantic 验证错误,提供更清晰的错误信息
error_messages = []
for error in validation_error.errors():
field = " -> ".join(str(loc) for loc in error.get("loc", []))
error_type = error.get("type", "unknown")
error_msg = error.get("msg", "验证失败")
error_messages.append(f"字段 '{field}': {error_msg} (类型: {error_type})")
error_detail = "; ".join(error_messages)
logger.error(
f"响应数据验证失败 - 操作: {action}, "
f"错误详情: {error_detail}, "
f"原始数据: {json.dumps(result, ensure_ascii=False, default=str)}"
)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"响应数据格式错误: {error_detail}。请检查操作 '{action}' 的返回格式是否符合 API 规范(msg 字段必须是字符串类型)。"
)
except HTTPException:
# 重新抛出 HTTP 异常
+8 -5
View File
@@ -278,24 +278,27 @@ class F6PluginModule:
Returns:
Dict[str, str]: 包含执行状态的字典
"""
print('执行 删除客户')
entry_data = api_instance.entry_data_get(data=data)
username = entry_data['data']['账号']
password = entry_data['data']['密码']
company_name = entry_data['data']['公司名称']
res = F6Module.login_in(username, password, company_name)
print(res.json())
if res is not None:
cookies = requests.utils.dict_from_cookiejar(res.cookies)
url = "https://yunxiu.f6car.cn/member/customer/listForPermission?pageSize=50000&pageNo=1"
url = "https://yunxiu.f6car.cn/member/customer/listForPermission?pageSize=30000&pageNo=1"
res = requests.get(url, cookies=cookies)
json = res.json()
total = res.json().get('data', {}).get('total', 0)
if json:
if total:
total = int(total)
thread = threading.Thread(target=delete_customer_background,
args=(data, cookies, json['data']['data'],))
args=(data, cookies, total,))
thread.start()
return {'msg': '正在执行中', 'msg_details': '8-20点3.5s一条数据,其余时间1.5s一条数据'}
return {'msg': '正在执行中', 'msg_details': f'总计{total}条数据,8-20点3.5s一条数据,其余时间1.5s一条数据'}
else:
return {'msg': '未执行', 'msg_details': '无客户信息'}
else:
+53 -9
View File
@@ -59,7 +59,7 @@ def delete_history_background(data: Dict[str, Any], cookies: Dict[str, str], org
print('表单已自动提交至下一步')
def delete_customer_background(data: Dict[str, Any], cookies: Dict[str, str], json_data: List[Dict[str, Any]]):
def delete_customer_background(data: Dict[str, Any], cookies: Dict[str, str], total:int ):
"""
删除客户信息后台任务
@@ -78,9 +78,18 @@ def delete_customer_background(data: Dict[str, Any], cookies: Dict[str, str], js
- 8-20点之间每3.5秒删除一条数据,其余时间每1.5秒删除一条数据
- 执行结果会更新到简道云表单
"""
print('开始删除客户信息')
success = 0
fail = 0
json_data = []
total_page = total // 100 + (total % 100 > 0)
for page in tqdm(range(1, total_page + 1)):
url = f"https://yunxiu.f6car.cn/member/customer/listForPermission?pageSize=100&pageNo={page}"
res = requests.get(url, cookies=cookies)
json_data.extend(res.json().get('data', {}).get('data',[]))
# 获取门店ID
operate_org_id = get_operate_org_id(cookies)
if not operate_org_id:
@@ -109,21 +118,56 @@ def delete_customer_background(data: Dict[str, Any], cookies: Dict[str, str], js
try:
url = f"https://yunxiu.f6car.cn/member/customer/{id_customer}" # 客户信息删除url
res = requests.delete(url, cookies=cookies) # 客户信息删除
res_data = res.json()
if res_data.get('success'):
res = requests.delete(url, cookies=cookies, timeout=10) # 客户信息删除
# 检查HTTP状态码
if res.status_code != 200:
fail += 1
error_msg = f"HTTP状态码错误: {res.status_code}"
logger.error(f"客户删除失败: ID={id_customer}, 手机号={phone}, {error_msg}")
print(f"删除失败: ID={id_customer}, 手机号={phone}, {error_msg}")
time.sleep(0.2)
continue
# 解析响应数据
try:
res_data = res.json()
except ValueError as json_error:
fail += 1
error_msg = f"响应不是有效的JSON格式: {json_error}, 响应内容: {res.text[:200]}"
logger.error(f"客户删除失败: ID={id_customer}, 手机号={phone}, {error_msg}")
print(f"删除失败: ID={id_customer}, 手机号={phone}, {error_msg}")
time.sleep(0.2)
continue
# 检查多种可能的成功标识
# 有些API返回 success 字段,有些返回 code=200,有些返回 data 字段
is_success = (
res_data.get('success') is True or
res_data.get('code') == 200 or
(res_data.get('code') is None and res_data.get('data') is not None)
)
if is_success:
success += 1
logger.info(f"客户删除成功: ID={id_customer}, 手机号={phone}")
else:
fail += 1
logger.error(f"客户删除失败: ID={id_customer}, 手机号={phone}, 错误信息: {res_data.get('message')}")
error_msg = res_data.get('message') or res_data.get('msg') or '未知错误'
error_detail = f"错误信息: {error_msg}, 完整响应: {res_data}"
logger.error(f"客户删除失败: ID={id_customer}, 手机号={phone}, {error_detail}")
print(f"删除失败: ID={id_customer}, 手机号={phone}, {error_msg}")
time.sleep(0.2)
except requests.exceptions.RequestException as e:
fail += 1
error_msg = f"网络请求异常: {str(e)}"
print(f"删除失败: ID={id_customer}, 手机号={phone}, {error_msg}")
logger.error(f"删除客户时发生网络错误: ID={id_customer}, 手机号={phone}, {error_msg}")
except Exception as e:
fail += 1
print("删除失败,", item, id_customer, phone, e)
logger.error(f"删除客户时发生错误: ID={id_customer}, 手机号={phone}, 错误信息: {e}")
if success + fail < len(json_data):
continue
error_msg = f"未知错误: {str(e)}"
print(f"删除失败: ID={id_customer}, 手机号={phone}, {error_msg}")
logger.error(f"删除客户时发生错误: ID={id_customer}, 手机号={phone}, {error_msg}", exc_info=True)
now = datetime.now()
if 8 <= now.hour <= 20:
+25 -5
View File
@@ -8,7 +8,7 @@
4. 中间件配置
作者: 项目团队
版本: 1.0.0
版本: 2.0.0
"""
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request, HTTPException, status
@@ -90,7 +90,7 @@ async def lifespan(app: FastAPI):
app = FastAPI(
title="简道云FastAPI服务",
description="简道云插件后端服务,提供数据同步和处理功能",
version="1.0.0",
version="2.0.0",
lifespan=lifespan
)
@@ -106,6 +106,26 @@ app.add_middleware(
app.include_router(router)
@app.get("/", tags=["系统"])
async def root():
"""
根路径端点
返回服务基本信息和可用端点
"""
return {
"service": "简道云FastAPI服务",
"version": "2.0.0",
"status": "running",
"endpoints": {
"health": "/health",
"webhook": "/webhook",
"docs": "/docs",
"redoc": "/redoc"
}
}
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
"""
@@ -128,7 +148,7 @@ async def http_exception_handler(request: Request, exc: HTTPException):
content=ErrorResponse(
detail=exc.detail or "HTTP error",
error_code=f"HTTP_{exc.status_code}"
).dict(),
).model_dump(),
)
@@ -154,7 +174,7 @@ async def validation_exception_handler(request: Request, exc: RequestValidationE
content=ErrorResponse(
detail="请求数据验证失败",
error_code="VALIDATION_ERROR"
).dict(),
).model_dump(),
)
@@ -180,7 +200,7 @@ async def general_exception_handler(request: Request, exc: Exception):
content=ErrorResponse(
detail="服务器内部错误",
error_code="INTERNAL_ERROR"
).dict(),
).model_dump(),
)
+49
View File
@@ -0,0 +1,49 @@
import requests
cookies = {
'memberSESSIONID': '43f77327-7a6b-4844-a1c6-d1acd7e0c970',
'erpLanguage': 'zh-CN',
'prodOrg': '11240984669917217520',
'unp': '15865484595890778191',
'un': '15865484595890778191',
'_up': '-NillNN-qyBEJ--t3vnSknvoOF53y_SJuMkA2n43U-daUfnArpjQjaZJ9Q3d-WrAAGgt60MgQHajHWBHMKKxj0CuWypi1JgKCFP1EPEk-HbqEvcTrYkr0wcI-fBRv-ZNHu3M-GTc1p60EX-sq-RQgeIal1HLPxpurEj9mEe9rIrrcGQ.',
'sensorsdata2015jssdkcross': '%7B%22distinct_id%22%3A%2215865484595890778191%22%2C%22first_id%22%3A%2219a48e066e68e2-067b1e693596828-4c657b58-2073600-19a48e066e71500%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22%24device_id%22%3A%2219a48e066e68e2-067b1e693596828-4c657b58-2073600-19a48e066e71500%22%7D',
'tmall': 'false',
'Hm_lvt_25f5e7a3a5dbb293d7dd35d5f1be8d0a': '1764311728,1764643482,1764662823,1764742943',
'Hm_lpvt_25f5e7a3a5dbb293d7dd35d5f1be8d0a': '1764742943',
'HMACCOUNT': '55F2182717FD6AE6',
}
headers = {
'accept': 'application/json, text/plain, */*',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'cache-control': 'no-cache',
'pragma': 'no-cache',
'priority': 'u=1, i',
'referer': 'https://yunxiu.f6car.cn/erp/view/index.html',
'sec-ch-ua': '"Chromium";v="142", "Microsoft Edge";v="142", "Not_A Brand";v="99"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-origin',
'traceparent': '00-b018ff54506f4db58f1c139e9cb30525-22df753b9da6e050-01',
'tracestate': 'rum=v2&browser&dz2uw0c5ay@e5930ea8eb782ae&273f4c9d3aeb40c586714fd3270a28e9&uid_48oaftj52d5ybkwt',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0',
'x-requested-with': 'XMLHttpRequest',
# 'cookie': 'memberSESSIONID=43f77327-7a6b-4844-a1c6-d1acd7e0c970; erpLanguage=zh-CN; prodOrg=11240984669917217520; unp=15865484595890778191; un=15865484595890778191; _up=-NillNN-qyBEJ--t3vnSknvoOF53y_SJuMkA2n43U-daUfnArpjQjaZJ9Q3d-WrAAGgt60MgQHajHWBHMKKxj0CuWypi1JgKCFP1EPEk-HbqEvcTrYkr0wcI-fBRv-ZNHu3M-GTc1p60EX-sq-RQgeIal1HLPxpurEj9mEe9rIrrcGQ.; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%2215865484595890778191%22%2C%22first_id%22%3A%2219a48e066e68e2-067b1e693596828-4c657b58-2073600-19a48e066e71500%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22%24device_id%22%3A%2219a48e066e68e2-067b1e693596828-4c657b58-2073600-19a48e066e71500%22%7D; tmall=false; Hm_lvt_25f5e7a3a5dbb293d7dd35d5f1be8d0a=1764311728,1764643482,1764662823,1764742943; Hm_lpvt_25f5e7a3a5dbb293d7dd35d5f1be8d0a=1764742943; HMACCOUNT=55F2182717FD6AE6',
}
params = {
'pageSize': '10',
'pageNo': '1',
}
response = requests.get(
'https://yunxiu.f6car.cn/member/customer/listForPermission',
params=params,
cookies=cookies,
headers=headers,
)
print(response.json())