简道云V2.0

This commit is contained in:
z66
2025-11-14 11:04:01 +08:00
parent 073f0646a1
commit 49fc75214f
33 changed files with 1811 additions and 4454 deletions
-89
View File
@@ -1,89 +0,0 @@
# 后台任务模块结构说明
## 模块结构
后台任务已按功能拆分为以下模块:
```
app/tasks/
├── __init__.py # 统一导出入口
├── common.py # 通用功能模块(简道云表单更新、工作流审批)
├── brand_tasks.py # 品牌相关任务
├── delete_tasks.py # 删除相关任务
└── customer_tasks.py # 客户相关任务
```
## 模块说明
### common.py - 通用功能模块
包含所有任务共用的功能:
- `update_jiandaoyun()` - 更新简道云表单
- `approve_workflow()` - 工作流审批
### brand_tasks.py - 品牌任务模块
品牌相关的后台任务:
- `create_brand_background()` - 品牌批量创建
### delete_tasks.py - 删除任务模块
删除相关的后台任务:
- `delete_history_background()` - 删除历史维修记录
- `delete_customer_background()` - 删除客户信息
- `delete_car_background()` - 删除客户车辆信息
### customer_tasks.py - 客户任务模块
客户相关的后台任务:
- `modify_customer_info_background()` - 修改客户信息
## 向后兼容
原有的 `app.back_ground_tasks` 模块仍然可用,它现在作为向后兼容的入口,实际功能已拆分到 `app.tasks` 模块中。
## 添加新功能模块
如需添加新的功能模块,请按以下步骤:
1.`app/tasks/` 目录下创建新的模块文件,例如 `new_feature_tasks.py`
2. 在新模块中实现相关功能函数
3.`app/tasks/__init__.py` 中导入并导出新函数
4.`app/back_ground_tasks.py` 中导入新函数以保持向后兼容
示例:
```python
# app/tasks/new_feature_tasks.py
from app.tasks.common import update_jiandaoyun, approve_workflow
def new_feature_background(data, cookies):
# 实现新功能
result = "执行结果"
msg = update_jiandaoyun(data, result)
if msg.get('msg'):
approve_workflow(data)
```
```python
# app/tasks/__init__.py 中添加
from app.tasks.new_feature_tasks import new_feature_background
__all__ = [
# ... 其他函数
'new_feature_background',
]
```
## 使用方式
### 方式一:使用新的模块结构(推荐)
```python
from app.tasks import create_brand_background
from app.tasks.brand_tasks import create_brand_background # 也可以直接导入
```
### 方式二:使用向后兼容的导入方式
```python
from app import back_ground_tasks
back_ground_tasks.create_brand_background(...)
```
两种方式都可以正常工作,代码执行逻辑完全一致。
+14 -1
View File
@@ -1,6 +1,14 @@
"""
后台任务模块统一导出入口
保持向后兼容,所有原有导入方式仍然有效
本模块统一导出所有后台任务函数,保持向后兼容。
所有原有导入方式仍然有效。
导出的任务包括:
- 通用功能: update_jiandaoyun, approve_workflow
- 品牌任务: create_brand_background
- 删除任务: delete_history_background, delete_customer_background, delete_car_background
- 客户任务: modify_customer_info_background
"""
# 通用功能
from app.tasks.common import update_jiandaoyun, approve_workflow
@@ -18,6 +26,9 @@ from app.tasks.delete_tasks import (
# 客户相关任务
from app.tasks.customer_tasks import modify_customer_info_background
# BI相关任务
from app.tasks.bi_tasks import bi_task_background
__all__ = [
# 通用功能
'update_jiandaoyun',
@@ -30,5 +41,7 @@ __all__ = [
'delete_car_background',
# 客户任务
'modify_customer_info_background',
# BI任务
'bi_task_background',
]
+86
View File
@@ -0,0 +1,86 @@
"""
BI相关后台任务模块
本模块包含BI相关的后台任务,包括:
- BI数据处理
- BI报表生成
这些任务在后台线程中执行,不会阻塞主请求。
执行完成后会更新简道云表单并自动提交工作流。
"""
import logging
import os
import requests
import pandas as pd
from typing import Dict, Any
from tqdm import tqdm
from app.tasks.common import update_jiandaoyun, approve_workflow
logger = logging.getLogger('app')
def bi_task_background(data: Dict[str, Any], cookies: Dict[str, str], df: pd.DataFrame = None, save_path: str = None):
"""
BI任务后台执行函数
在后台线程中执行BI相关任务,如数据处理、报表生成等。
执行完成后会更新简道云表单并自动提交工作流。
Args:
data: 包含表单ID(api_key)、表单ID(entry_id)、数据ID(data_id)的字典
cookies: 用户登录 F6 系统的 cookies 信息(如果需要)
df: Excel 文件读取的内容,DataFrame 格式(如果需要)
save_path: Excel 文件保存的地址,执行完成后会删除此文件(如果需要)
Returns:
None
注意:
- 这是一个示例函数,需要根据实际BI任务需求进行实现
- 执行完成后会自动删除上传的文件(如果提供了save_path)
- 执行结果会更新到简道云表单
"""
try:
# TODO: 在这里实现具体的BI任务逻辑
# 例如:数据处理、报表生成、数据同步等
# 示例:处理数据
results = []
if df is not None:
df = df.where(pd.notnull(df), None)
for index, row in tqdm(df.iterrows(), total=df.shape[0], desc="处理BI数据"):
# TODO: 实现具体的数据处理逻辑
# 例如:调用BI API、生成报表、数据转换等
result_item = {
'行号': index + 1,
'状态': '处理成功'
}
results.append(result_item)
else:
# 如果没有DataFrame,执行其他BI任务
# TODO: 实现其他BI任务逻辑
results.append({'状态': 'BI任务执行成功'})
# 删除文件(如果提供了save_path)
if save_path and os.path.exists(save_path):
os.remove(save_path)
logger.info(f'{save_path}已删除')
# 格式化结果
results_str = f'{results}' if results else 'BI任务执行完成'
logger.info(f"BI任务执行结果: {results_str}")
# 调用api回写改掉 执行明细与执行状态
msg = update_jiandaoyun(data, results_str)
if msg.get('msg'):
approve_workflow(data)
logger.info('表单已自动提交至下一步')
except Exception as e:
error_msg = f'BI任务执行失败: {str(e)}'
logger.error(error_msg, exc_info=True)
msg = update_jiandaoyun(data, error_msg)
if msg.get('msg'):
approve_workflow(data)
+23 -7
View File
@@ -1,6 +1,10 @@
"""
品牌相关后台任务模块
包含品牌批量创建等功能
本模块包含品牌相关的后台任务,包括:
- 品牌批量创建
这些任务在后台线程中执行,不会阻塞主请求。
"""
import logging
import os
@@ -15,12 +19,24 @@ logger = logging.getLogger('app')
def create_brand_background(data: Dict[str, Any], cookies: Dict[str, str], df: pd.DataFrame, save_path: str):
"""
品牌批量创建后台运行函数
:param data: 包含表单id、数据id等的字典
:param cookies: 用户登录f6系统的cookies信息
:param df: 表格读取到的内容,DataFrame格式
:param save_path: 文件保存的地址
:return: None
品牌批量创建后台任务
在后台线程中批量创建品牌,从 Excel 文件中读取品牌名称并创建。
执行完成后会更新简道云表单并自动提交工作流。
Args:
data: 包含表单ID(api_key)、表单ID(entry_id)、数据ID(data_id)的字典
cookies: 用户登录 F6 系统的 cookies 信息
df: Excel 文件读取的内容,DataFrame 格式,第一列为品牌名称
save_path: Excel 文件保存的地址,执行完成后会删除此文件
Returns:
None
注意:
- 无效的品牌名(None、空字符串)会被跳过
- 执行完成后会自动删除上传的文件
- 执行结果会更新到简道云表单
"""
df = df.where(pd.notnull(df), None)
# 定义请求URL
+136 -7
View File
@@ -1,10 +1,19 @@
"""
通用后台任务模块
包含简道云表单更新和工作流审批等通用功能
本模块包含所有后台任务通用的功能,包括:
- 简道云表单更新
- 工作流审批
- 获取门店ID
- 获取会员卡列表
这些功能被多个后台任务模块复用。
"""
import logging
import time
from typing import Dict, Any
import requests
from typing import Dict, Any, List, Optional, Callable
from tqdm import tqdm
from app.api import API
api_instance = API()
@@ -14,9 +23,15 @@ logger = logging.getLogger('app')
def update_jiandaoyun(data: Dict[str, Any], results: str):
"""
更新简道云表单
:param data: 包含表单id、应用id、数据id的字典
:param results: 执行结果信息
:return: 更新结果字典
将后台任务的执行结果更新到简道云表单中。
Args:
data: 包含表单ID(api_key)、表单ID(entry_id)、数据ID(data_id)的字典
results: 执行结果信息,将写入到表单的执行明细字段
Returns:
Dict: 更新结果字典,{'msg': True} 表示成功,{'msg': False} 表示失败
"""
# 定义简道云数据配置
jiandaoyun_data = {
@@ -44,8 +59,17 @@ def update_jiandaoyun(data: Dict[str, Any], results: str):
def approve_workflow(data: Dict[str, Any]):
"""
获取简道云当前流程节点并直接提交
:param data: 包含表单id、应用id、数据id的字典
:return: None
获取简道云工作流的当前待处理任务,并自动提交到下一步。
Args:
data: 包含表单ID(api_key)、表单ID(entry_id)、数据ID(data_id)的字典
Returns:
None
注意:
如果未找到待处理任务,函数会记录错误并返回,不会抛出异常
"""
# 获取简道云当前流程列表
json = api_instance.workflow_instance_get(data)
@@ -92,3 +116,108 @@ def approve_workflow(data: Dict[str, Any]):
except Exception as e:
logger.error(f"简道云工作流任务提交失败: {e}")
def get_operate_org_id(cookies: Dict[str, str]) -> Optional[str]:
"""
获取操作门店ID
从F6系统获取第一个门店的组织ID,用于后续操作。
Args:
cookies: 用户登录 F6 系统的 cookies 信息
Returns:
Optional[str]: 门店ID,如果获取失败返回 None
注意:
如果未获取到门店信息或门店ID为空,会记录错误日志并返回 None
"""
org_url = "https://yunxiu.f6car.cn/hive/org/getPageOrgGroupMembers?currentPage=1&pageSize=10&name="
try:
org_res = requests.get(url=org_url, cookies=cookies)
org_data = org_res.json().get("data", {})
org_list = org_data.get("list", [])
if not org_list or len(org_list) == 0:
logger.error("未获取到门店信息")
return None
operate_org_id = org_list[0].get("orgId")
if not operate_org_id:
logger.error("门店ID为空")
return None
logger.info(f"获取门店ID成功: {operate_org_id}")
return operate_org_id
except Exception as e:
logger.error(f"获取门店ID时发生错误: {e}")
return None
def get_card_list(
cookies: Dict[str, str],
operate_org_id: str,
extract_func: Callable[[Dict], Optional[str]] = None
) -> List[str]:
"""
获取会员卡列表
从F6系统获取指定门店的会员卡列表,支持自定义提取逻辑。
Args:
cookies: 用户登录 F6 系统的 cookies 信息
operate_org_id: 门店ID
extract_func: 自定义提取函数,用于从会员卡数据中提取ID
如果不提供,默认提取 idCustomer 字段
Returns:
List[str]: 会员卡ID列表
注意:
- 默认每页100条数据,会自动分页获取所有数据
- 每页请求间隔0.2秒,避免请求过快
"""
card_list = []
try:
# 获取第一页,确定总页数
card_url = f"https://yunxiu.f6car.cn/marketing/card/paging?useStationIdOwnOrgList={operate_org_id}&pageSize=100&pageNo=1"
card_res = requests.get(url=card_url, cookies=cookies)
total_card = int(card_res.json().get("data", {}).get("total", 0))
if total_card == 0:
logger.info("未找到会员卡数据")
return card_list
total_page = total_card // 100 + (total_card % 100 > 0)
logger.info(f"会员卡总数: {total_card}, 总页数: {total_page}")
# 定义默认提取函数(提取客户ID
if extract_func is None:
def default_extract(card_item: Dict) -> Optional[str]:
return card_item.get("idCustomer")
extract_func = default_extract
# 分页获取所有会员卡数据
for page in tqdm(range(1, total_page + 1), desc="查询会员卡"):
card_url = (f"https://yunxiu.f6car.cn/marketing/card/paging?useStationIdOwnOrgList={operate_org_id}"
f"&pageSize=100&pageNo={page}")
card_res = requests.get(url=card_url, cookies=cookies)
card_data_list = card_res.json().get("data", {}).get("data", [])
# 使用提取函数提取ID
for card_item in card_data_list:
extracted_id = extract_func(card_item)
if extracted_id is not None:
card_list.append(extracted_id)
time.sleep(0.2)
logger.info(f"获取会员卡列表成功,共 {len(card_list)}")
return card_list
except Exception as e:
logger.error(f"获取会员卡列表时发生错误: {e}")
return card_list
+20 -8
View File
@@ -1,6 +1,10 @@
"""
客户相关后台任务模块
包含修改客户信息等功能
本模块包含客户相关的后台任务,包括:
- 客户信息批量修改
这些任务在后台线程中执行,不会阻塞主请求。
"""
import logging
import requests
@@ -15,16 +19,24 @@ logger = logging.getLogger('app')
def modify_customer_info_background(data: Dict[str, Any], cookies: Dict[str, str], df: pd.DataFrame, save_path: str):
"""
修改客户信息后台任务
此函数用于后台任务,用于修改会员信息。
修改客户信息后台任务
在后台线程中批量修改客户信息,从 Excel 文件中读取客户手机号和修改信息。
执行完成后会更新简道云表单并自动提交工作流。
Args:
data (Dict[str, Any]): 前端请求发送过来的数据,包含文件信息和其他必要参数。
cookies (Dict[str, str]): 登录用户的Cookies
data: 包含表单ID(api_key)、表单ID(entry_id)、数据ID(data_id)的字典
cookies: 用户登录 F6 系统的 cookies 信息
df: Excel 文件读取的内容,DataFrame 格式,第一列为客户手机号
save_path: Excel 文件保存的地址,执行完成后会删除此文件
Returns:
None
注意:
- 根据客户手机号匹配客户信息
- 执行完成后会自动删除上传的文件
- 执行结果会更新到简道云表单
"""
df = df.where(pd.notnull(df), None)
params = {
+94 -98
View File
@@ -1,27 +1,41 @@
"""
删除相关后台任务模块
包含删除历史维修记录、删除客户信息、删除客户车辆信息等功能
本模块包含删除相关的后台任务,包括:
- 删除历史维修记录
- 删除客户信息
- 删除客户车辆信息
这些任务在后台线程中执行,不会阻塞主请求。
执行完成后会更新简道云表单并自动提交工作流。
"""
import logging
import traceback
import requests
import time
from typing import Dict, Any, List
from typing import Dict, Any, List, Optional
from datetime import datetime
from tqdm import tqdm
from app.tasks.common import update_jiandaoyun, approve_workflow
from app.tasks.common import update_jiandaoyun, approve_workflow, get_operate_org_id, get_card_list
logger = logging.getLogger('app')
def delete_history_background(data: Dict[str, Any], cookies: Dict[str, str], org_id: str, org_name: str):
"""
删除历史维修数据后台运行函数
:param data: 包含表单id、数据id等的字典
:param cookies: 用户登录F6系统的cookies信息
:param org_id: 需要删除历史维修记录的门店id
:param org_name: 需要删除历史维修记录的门店名称
:return: None
删除历史维修记录后台任务
在后台线程中删除指定门店的历史维修记录。
执行完成后会更新简道云表单并自动提交工作流。
Args:
data: 包含表单ID(api_key)、表单ID(entry_id)、数据ID(data_id)的字典
cookies: 用户登录 F6 系统的 cookies 信息
org_id: 需要删除历史维修记录的门店ID
org_name: 需要删除历史维修记录的门店名称
Returns:
None
"""
url = f'https://yunxiu.f6car.cn/maintain-dump/maintainHistory/?orgid={org_id}' # 删除url
res = requests.delete(url=url, cookies=cookies)
@@ -47,60 +61,37 @@ def delete_history_background(data: Dict[str, Any], cookies: Dict[str, str], org
def delete_customer_background(data: Dict[str, Any], cookies: Dict[str, str], json_data: List[Dict[str, Any]]):
"""
删除客户信息后台运行函数
:param data: 包含表单id、数据id等字典
:param cookies: 用户登录f6系统的cookies信息
:param json_data: 获取到的客户信息列表,列表最大值取决url里面的值
:return: None
删除客户信息后台任务
在后台线程中批量删除客户信息
执行完成后会更新简道云表单并自动提交工作流。
Args:
data: 包含表单ID(api_key)、表单ID(entry_id)、数据ID(data_id)的字典
cookies: 用户登录 F6 系统的 cookies 信息
json_data: 获取到的客户信息列表,包含要删除的客户信息
Returns:
None
注意:
- 8-20点之间每3.5秒删除一条数据,其余时间每1.5秒删除一条数据
- 执行结果会更新到简道云表单
"""
success = 0
fail = 0
# 获取门店ID
org_url = "https://yunxiu.f6car.cn/hive/org/getPageOrgGroupMembers?currentPage=1&pageSize=10&name="
org_res = requests.get(url=org_url, cookies=cookies)
# 安全地获取门店ID
org_data = org_res.json().get("data", {})
org_list = org_data.get("list", [])
if not org_list or len(org_list) == 0:
logger.error("未获取到门店信息")
operate_org_id = get_operate_org_id(cookies)
if not operate_org_id:
msg = update_jiandaoyun(data, '删除失败: 未获取到门店信息')
if msg.get('msg'):
approve_workflow(data)
return
operate_org_id = org_list[0].get("orgId")
if not operate_org_id:
logger.error("门店ID为空")
msg = update_jiandaoyun(data, '删除失败: 门店ID为空')
if msg.get('msg'):
approve_workflow(data)
return
print(operate_org_id)
# 获取会员卡列表
card_url = f"https://yunxiu.f6car.cn/marketing/card/paging?useStationIdOwnOrgList={operate_org_id}&pageSize=100&pageNo=1"
card_res = requests.get(url=card_url, cookies=cookies)
total_card = int(card_res.json().get("data").get("total"))
print(total_card)
total_page = total_card // 100 + (total_card % 100 > 0)
card_list_customers = []
for page in tqdm(range(1, total_page + 1), desc="查询会员卡"):
card_url = (f"https://yunxiu.f6car.cn/marketing/card/paging?useStationIdOwnOrgList={operate_org_id}"
f"&pageSize=100&pageNo={page}")
card_res = requests.get(url=card_url, cookies=cookies)
card_cars_list = card_res.json().get("data").get("data")
for card_customer in card_cars_list:
if card_customer.get("idCustomer") is None:
continue
else:
card_list_customers.append(card_customer.get("idCustomer", None))
time.sleep(0.2)
# 获取会员卡列表(提取客户ID
card_list_customers = get_card_list(cookies, operate_org_id)
for item in tqdm(json_data, desc="删除客户"):
id_customer = item['idCustomer']
@@ -135,10 +126,10 @@ def delete_customer_background(data: Dict[str, Any], cookies: Dict[str, str], js
continue
now = datetime.now()
if 20 <= now.hour <= 8:
time.sleep(1)
if 8 <= now.hour <= 20:
time.sleep(3.5)
else:
time.sleep(3)
time.sleep(1.5)
logger.info(f"客户删除结果: 成功次数={success}, 失败次数={fail}")
@@ -152,13 +143,26 @@ def delete_customer_background(data: Dict[str, Any], cookies: Dict[str, str], js
def delete_car_background(data: Dict[str, Any], url: str, cookies: Dict[str, str], header: Dict[str, Any],
all_page: str):
"""
删除客户车辆信息后台运行函数
:param header: 应包含账号登录的请求头
:param url: 包含请求客户车辆信息的url
:param all_page: 客户车辆信息的页数
:param data: 包含表单id、数据id等的字典
:param cookies: 登录F6系统后的请求信息
:return: None
删除客户车辆信息后台任务
在后台线程中批量删除客户车辆信息
会检查车辆是否有会员卡或最近消费记录,有则跳过删除。
执行完成后会更新简道云表单并自动提交工作流。
Args:
data: 包含表单ID(api_key)、表单ID(entry_id)、数据ID(data_id)的字典
url: 获取车辆列表的 API URL
cookies: 用户登录 F6 系统的 cookies 信息
header: HTTP 请求头字典,应包含账号登录的请求头
all_page: 总页数(字符串或整数),用于分页获取车辆列表
Returns:
None
注意:
- 8-20点之间每3.5秒删除一条数据,其余时间每1.5秒删除一条数据
- 有会员卡或最近消费记录的车辆会被跳过
- 执行结果会更新到简道云表单
"""
print(cookies)
success = 0
@@ -168,50 +172,42 @@ def delete_car_background(data: Dict[str, Any], url: str, cookies: Dict[str, str
all_page = int(all_page)
# 获取门店ID
org_url = "https://yunxiu.f6car.cn/hive/org/getPageOrgGroupMembers?currentPage=1&pageSize=10&name="
org_res = requests.get(url=org_url, cookies=cookies)
# 安全地获取门店ID
org_data = org_res.json().get("data", {})
org_list = org_data.get("list", [])
if not org_list or len(org_list) == 0:
logger.error("未获取到门店信息")
operate_org_id = get_operate_org_id(cookies)
if not operate_org_id:
msg = update_jiandaoyun(data, '删除失败: 未获取到门店信息')
if msg.get('msg'):
approve_workflow(data)
return
operate_org_id = org_list[0].get("orgId")
if not operate_org_id:
logger.error("门店ID为空")
msg = update_jiandaoyun(data, '删除失败: 门店ID为空')
if msg.get('msg'):
approve_workflow(data)
return
print(operate_org_id)
# 获取会员卡列表
card_url = (
f"https://yunxiu.f6car.cn/marketing/card/paging?useStationIdOwnOrgList={operate_org_id}&pageSize=100&pageNo=1"
)
card_res = requests.get(url=card_url, cookies=cookies)
total_card = int(card_res.json().get("data").get("total"))
print(total_card)
total_page = total_card // 100 + (total_card % 100 > 0)
# 获取会员卡列表(提取车辆ID
# 注意:需要获取所有车辆的ID,所以不能直接使用 get_card_list
# 需要自定义提取逻辑,返回所有车辆的ID列表
card_list_cars = []
for page in tqdm(range(1, total_page + 1), desc="查询会员卡"):
card_url = (f"https://yunxiu.f6car.cn/marketing/card/paging?useStationIdOwnOrgList={operate_org_id}"
f"&pageSize=100&pageNo={page}")
try:
# 获取第一页,确定总页数
card_url = f"https://yunxiu.f6car.cn/marketing/card/paging?useStationIdOwnOrgList={operate_org_id}&pageSize=100&pageNo=1"
card_res = requests.get(url=card_url, cookies=cookies)
card_cars_list = card_res.json().get("data").get("data")
for card_car in card_cars_list:
if card_car.get("cars") is None:
continue
for car in card_car.get("cars", []):
card_list_cars.append(car.get("idCar", None))
time.sleep(0.2)
total_card = int(card_res.json().get("data", {}).get("total", 0))
if total_card > 0:
total_page = total_card // 100 + (total_card % 100 > 0)
for page in tqdm(range(1, total_page + 1), desc="查询会员卡"):
card_url = (f"https://yunxiu.f6car.cn/marketing/card/paging?useStationIdOwnOrgList={operate_org_id}"
f"&pageSize=100&pageNo={page}")
card_res = requests.get(url=card_url, cookies=cookies)
card_cars_list = card_res.json().get("data", {}).get("data", [])
for card_car in card_cars_list:
if card_car.get("cars") is None:
continue
for car in card_car.get("cars", []):
car_id = car.get("idCar")
if car_id:
card_list_cars.append(car_id)
time.sleep(0.2)
except Exception as e:
logger.error(f"获取会员卡列表时发生错误: {e}")
itemlist = []
# 使用 range() 创建一个可迭代的对象