smartpush 1.3.7__tar.gz → 1.3.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {smartpush-1.3.7 → smartpush-1.3.9}/PKG-INFO +1 -1
- {smartpush-1.3.7 → smartpush-1.3.9}/setup.py +1 -1
- smartpush-1.3.9/smartpush/flow/MockFlow.py +150 -0
- smartpush-1.3.9/smartpush/test.py +192 -0
- smartpush-1.3.9/smartpush/utils/EmailUtlis.py +305 -0
- {smartpush-1.3.7 → smartpush-1.3.9}/smartpush/utils/ListDictUtils.py +7 -4
- {smartpush-1.3.7 → smartpush-1.3.9}/smartpush.egg-info/PKG-INFO +1 -1
- {smartpush-1.3.7 → smartpush-1.3.9}/smartpush.egg-info/SOURCES.txt +1 -0
- smartpush-1.3.7/smartpush/flow/MockFlow.py +0 -100
- smartpush-1.3.7/smartpush/test.py +0 -90
- {smartpush-1.3.7 → smartpush-1.3.9}/README.md +0 -0
- {smartpush-1.3.7 → smartpush-1.3.9}/setup.cfg +0 -0
- {smartpush-1.3.7 → smartpush-1.3.9}/smartpush/__init__.py +0 -0
- {smartpush-1.3.7 → smartpush-1.3.9}/smartpush/export/__init__.py +0 -0
- {smartpush-1.3.7 → smartpush-1.3.9}/smartpush/export/basic/ExcelExportChecker.py +0 -0
- {smartpush-1.3.7 → smartpush-1.3.9}/smartpush/export/basic/GetOssUrl.py +0 -0
- {smartpush-1.3.7 → smartpush-1.3.9}/smartpush/export/basic/ReadExcel.py +0 -0
- {smartpush-1.3.7 → smartpush-1.3.9}/smartpush/export/basic/__init__.py +0 -0
- {smartpush-1.3.7 → smartpush-1.3.9}/smartpush/flow/__init__.py +0 -0
- {smartpush-1.3.7 → smartpush-1.3.9}/smartpush/get_jira_info.py +0 -0
- {smartpush-1.3.7 → smartpush-1.3.9}/smartpush/utils/DataTypeUtils.py +0 -0
- {smartpush-1.3.7 → smartpush-1.3.9}/smartpush/utils/StringUtils.py +0 -0
- {smartpush-1.3.7 → smartpush-1.3.9}/smartpush/utils/__init__.py +0 -0
- {smartpush-1.3.7 → smartpush-1.3.9}/smartpush.egg-info/dependency_links.txt +0 -0
- {smartpush-1.3.7 → smartpush-1.3.9}/smartpush.egg-info/top_level.txt +0 -0
@@ -0,0 +1,150 @@
|
|
1
|
+
import json
|
2
|
+
import random
|
3
|
+
import time
|
4
|
+
import requests
|
5
|
+
from smartpush.utils import ListDictUtils
|
6
|
+
|
7
|
+
|
8
|
+
def get_current_flow(host_domain, cookies, flow_id):
|
9
|
+
# 提取flow所有节点数据
|
10
|
+
_url = host_domain + "/flow/getFlowDetail"
|
11
|
+
headers = {
|
12
|
+
"cookie": cookies
|
13
|
+
}
|
14
|
+
params = {
|
15
|
+
"flowId": flow_id,
|
16
|
+
"active": True,
|
17
|
+
"includeActivityDetail": True
|
18
|
+
}
|
19
|
+
result = json.loads(requests.request(method="get", url=_url, headers=headers, params=params).text)
|
20
|
+
# 按节点id存储
|
21
|
+
node_counts = []
|
22
|
+
|
23
|
+
def process_node(node):
|
24
|
+
node_counts.append({node["id"]: {"completedCount": node["data"]["completedCount"],
|
25
|
+
"skippedCount": node["data"]["skippedCount"],
|
26
|
+
"openUserCount": node["data"]["openUserCount"],
|
27
|
+
"clickUserCount": node["data"]["clickUserCount"],
|
28
|
+
"waitingCount": node["data"]["waitingCount"]
|
29
|
+
}
|
30
|
+
}
|
31
|
+
)
|
32
|
+
# 处理split节点
|
33
|
+
if "split" in node["data"].keys():
|
34
|
+
for branch_node in node['data']['split']['branches']["false"]:
|
35
|
+
process_node(branch_node)
|
36
|
+
for branch_node in node['data']['split']['branches']["true"]:
|
37
|
+
process_node(branch_node)
|
38
|
+
# 处理abTesting节点
|
39
|
+
elif "abTesting" in node["data"].keys():
|
40
|
+
for branch_node in node['data']['abTesting']['branches']["a"]:
|
41
|
+
process_node(branch_node)
|
42
|
+
for branch_node in node['data']['abTesting']['branches']["b"]:
|
43
|
+
process_node(branch_node)
|
44
|
+
|
45
|
+
# 处理所有顶层节点
|
46
|
+
for node in result['resultData']['nodes']:
|
47
|
+
process_node(node)
|
48
|
+
return node_counts, result["resultData"]["version"]
|
49
|
+
|
50
|
+
|
51
|
+
def update_flow(host_domain, cookies, **kwargs):
|
52
|
+
"""
|
53
|
+
# 更新flow
|
54
|
+
update_flow_params: 必填,saveFlow接口所有参数,dict格式
|
55
|
+
version: 非必填,flow版本号
|
56
|
+
"""
|
57
|
+
_url = host_domain + "/flow/saveFlow"
|
58
|
+
headers = {
|
59
|
+
"cookie": cookies,
|
60
|
+
"Content-Type": "application/json"
|
61
|
+
}
|
62
|
+
kwargs["update_flow_params"]["version"] = kwargs.get("version", kwargs["update_flow_params"]["version"])
|
63
|
+
params = kwargs["update_flow_params"]
|
64
|
+
result = requests.request(method="post", url=_url, headers=headers, json=params).text
|
65
|
+
|
66
|
+
|
67
|
+
def start_flow(host_domain, cookies, flow_id, version):
|
68
|
+
# 开启flow
|
69
|
+
_url = host_domain + "/flow/publishFlow"
|
70
|
+
headers = {
|
71
|
+
"cookie": cookies,
|
72
|
+
"Content-Type": "application/json"
|
73
|
+
}
|
74
|
+
params = {
|
75
|
+
"flowId": flow_id,
|
76
|
+
"version": str(version)
|
77
|
+
}
|
78
|
+
result = requests.request(method="post", url=_url, headers=headers, json=params).text
|
79
|
+
|
80
|
+
|
81
|
+
def mock_pulsar(mock_domain, pulsar, limit=1):
|
82
|
+
"""
|
83
|
+
# post请求
|
84
|
+
# times:为触发次数,默认1次即可
|
85
|
+
"""
|
86
|
+
_url = mock_domain + "/flow/testEventMulti"
|
87
|
+
headers = {
|
88
|
+
"Content-Type": "application/json"
|
89
|
+
}
|
90
|
+
# 生成随机message_id
|
91
|
+
prefix = 179
|
92
|
+
pulsar["messageId"] = f"{prefix}{random.randint(10 ** 15, 10 ** 16 - 1)}"
|
93
|
+
params = {
|
94
|
+
"times": limit,
|
95
|
+
"mq": pulsar
|
96
|
+
}
|
97
|
+
result = requests.request(method="post", url=_url, headers=headers, json=params).text
|
98
|
+
return json.loads(result)
|
99
|
+
|
100
|
+
|
101
|
+
def check_flow(mock_domain, host_domain, cookies, **kwargs):
|
102
|
+
"""
|
103
|
+
完整触发流程
|
104
|
+
params
|
105
|
+
mock_domain:必填,触发接口域名
|
106
|
+
host_domain:必填,spflow接口域名
|
107
|
+
cookies:必填,sp登录态
|
108
|
+
flow_id:必填
|
109
|
+
pulsar:必填,模拟的触发数据
|
110
|
+
old_flow_counts: is_split_steps不为all时必填,参数内容为步骤1返回的old_flow_counts
|
111
|
+
|
112
|
+
limit:非必填,默认为1 - mock_pulsar函数用于控制模拟触发的次数
|
113
|
+
sleep_time: 非必填, 默认60s, 等待时间,用于触发后等待各节点计数后获取新数据
|
114
|
+
update_flow_params: 非必填,dict格式,需更新flow时传参,参数结构为sp的saveFlow接口内容
|
115
|
+
num:非必填,默认为1 - compare_lists函数用于断言方法做差值计算
|
116
|
+
all_key: 非必填,bool,默认false,输入true时,检查指标节点常用5个字段
|
117
|
+
check_key: 非必填, 默认只有completedCount, list格式,传入需检查节点的指标key,如:completedCount、skippedCount、openRate等
|
118
|
+
"""
|
119
|
+
# todo: 还差邮件校验部分,后续补充
|
120
|
+
is_split_steps = kwargs.get("split_steps", "all")
|
121
|
+
# 步骤1
|
122
|
+
if is_split_steps == "one" or is_split_steps == "all":
|
123
|
+
# 触发前提取flow数据,后续做对比
|
124
|
+
old_flow_counts, old_versions = get_current_flow(host_domain=host_domain, cookies=cookies,
|
125
|
+
flow_id=kwargs["flow_id"])
|
126
|
+
kwargs["old_flow_counts"] = old_flow_counts
|
127
|
+
# 更新flow
|
128
|
+
if kwargs.get("update_flow_params", False):
|
129
|
+
update_flow(host_domain=host_domain, cookies=cookies, update_flow_params=kwargs.get("update_flow_params"),
|
130
|
+
version=old_versions)
|
131
|
+
# 启动flow
|
132
|
+
start_flow(host_domain=host_domain, cookies=cookies, flow_id=kwargs["flow_id"], version=old_versions)
|
133
|
+
# 触发flow
|
134
|
+
mock_pulsar(mock_domain=mock_domain, pulsar=kwargs["pulsar"], limit=kwargs.get("limit", 1))
|
135
|
+
if is_split_steps == "one":
|
136
|
+
return old_flow_counts, old_versions
|
137
|
+
|
138
|
+
# 步骤2
|
139
|
+
if is_split_steps == "two" or is_split_steps == "all":
|
140
|
+
if is_split_steps == "all":
|
141
|
+
time.sleep(kwargs.get("sleep_time", 60))
|
142
|
+
# 触发后提取flow数据,做断言
|
143
|
+
new_flow_counts, new_versions = get_current_flow(host_domain=host_domain, cookies=cookies,
|
144
|
+
flow_id=kwargs["flow_id"])
|
145
|
+
# 断言
|
146
|
+
result = ListDictUtils.compare_lists(temp1=kwargs.get("old_flow_counts"),
|
147
|
+
temp2=new_flow_counts, num=kwargs.get("num", 1),
|
148
|
+
check_key=kwargs.get("check_key", ["completedCount"]),
|
149
|
+
all_key=kwargs.get("all_key", False))
|
150
|
+
return [True, "断言成功"] if len(result) == 0 else [False, result]
|
@@ -0,0 +1,192 @@
|
|
1
|
+
# -*- codeing = utf-8 -*-
|
2
|
+
# @Time :2025/2/20 00:27
|
3
|
+
# @Author :luzebin
|
4
|
+
import pandas as pd
|
5
|
+
|
6
|
+
from smartpush.export.basic import ExcelExportChecker
|
7
|
+
from smartpush.export.basic.ReadExcel import read_excel_from_oss
|
8
|
+
from smartpush.export.basic.ReadExcel import read_excel_and_write_to_dict
|
9
|
+
from smartpush.export.basic.GetOssUrl import get_oss_address_with_retry
|
10
|
+
from smartpush.utils.DataTypeUtils import DataTypeUtils
|
11
|
+
from smartpush.flow import MockFlow
|
12
|
+
from smartpush.utils import EmailUtlis
|
13
|
+
|
14
|
+
if __name__ == '__main__':
|
15
|
+
# 导出流程
|
16
|
+
oss1 = "https://cdn.smartpushedm.com/material_ec2/2025-02-26/31c1a577af244c65ab9f9a984c64f3d9/ab%E5%BC%B9%E7%AA%97%E6%B5%8B%E8%AF%952.10%E5%88%9B%E5%BB%BA-%E6%9C%89%E5%85%A8%E9%83%A8%E6%95%B0%E6%8D%AE%E9%94%80%E5%94%AE%E9%A2%9D%E6%98%8E%E7%BB%86%E6%95%B0%E6%8D%AE.xlsx"
|
17
|
+
oss2 = "https://cdn.smartpushedm.com/material_ec2/2025-02-26/31c1a577af244c65ab9f9a984c64f3d9/ab%E5%BC%B9%E7%AA%97%E6%B5%8B%E8%AF%952.10%E5%88%9B%E5%BB%BA-%E6%9C%89%E5%85%A8%E9%83%A8%E6%95%B0%E6%8D%AE%E9%94%80%E5%94%AE%E9%A2%9D%E6%98%8E%E7%BB%86%E6%95%B0%E6%8D%AE.xlsx"
|
18
|
+
# # print(check_excel_all(oss1, oss1))
|
19
|
+
oss3 = "https://cdn.smartpushedm.com/material_ec2/2025-03-07/dca03e35cb074ac2a46935c85de9f510/导出全部客户.csv"
|
20
|
+
oss4 = "https://cdn.smartpushedm.com/material_ec2/2025-03-07/c5fa0cc24d05416e93579266910fbd3e/%E5%AF%BC%E5%87%BA%E5%85%A8%E9%83%A8%E5%AE%A2%E6%88%B7.csv"
|
21
|
+
expected_oss = "https://cdn.smartpushedm.com/material_ec2/2025-02-26/757df7e77ce544e193257c0da35a4983/%E3%80%90%E8%87%AA%E5%8A%A8%E5%8C%96%E5%AF%BC%E5%87%BA%E3%80%91%E8%90%A5%E9%94%80%E6%B4%BB%E5%8A%A8%E6%95%B0%E6%8D%AE%E6%A6%82%E8%A7%88.xlsx"
|
22
|
+
# actual_oss = "https://cdn.smartpushedm.com/material_ec2/2025-02-26/757df7e77ce544e193257c0da35a4983/%E3%80%90%E8%87%AA%E5%8A%A8%E5%8C%96%E5%AF%BC%E5%87%BA%E3%80%91%E8%90%A5%E9%94%80%E6%B4%BB%E5%8A%A8%E6%95%B0%E6%8D%AE%E6%A6%82%E8%A7%88.xlsx"
|
23
|
+
url = "https://cdn.smartpushedm.com/material_ec2_prod/2025-03-06/fe6f042f50884466979155c5ef825736/copy%20of%202025-01-16%20%E5%88%9B%E5%BB%BA%E7%9A%84%20A%2FB%20%E6%B5%8B%E8%AF%95%20copy%20of%202025-01-16%20app-%E6%99%AE%E9%80%9A%E6%A8%A1%E6%9D%BF%201%E6%95%B0%E6%8D%AE%E6%80%BB%E8%A7%88.xlsx"
|
24
|
+
|
25
|
+
# e_person_oss1 = "https://cdn.smartpushedm.com/material_ec2/2025-02-27/b48f34b3e88045d189631ec1f0f23d51/%E5%AF%BC%E5%87%BA%E5%85%A8%E9%83%A8%E5%AE%A2%E6%88%B7.csv"
|
26
|
+
# a_person_oss2 = "https://cdn.smartpushedm.com/material_ec2/2025-02-27/c50519d803c04e3b9b52d9f625fed413/%E5%AF%BC%E5%87%BA%E5%85%A8%E9%83%A8%E5%AE%A2%E6%88%B7.csv"
|
27
|
+
|
28
|
+
# # #actual_oss= get_oss_address_with_retry("23161","https://cdn.smartpushedm.com/material_ec2_prod/2025-02-20/dae941ec20964ca5b106407858676f89/%E7%BE%A4%E7%BB%84%E6%95%B0%E6%8D%AE%E6%A6%82%E8%A7%88.xlsx","",'{"page":1,"pageSize":10,"type":null,"status":null,"startTime":null,"endTime":null}')
|
29
|
+
# # res=read_excel_and_write_to_dict(read_excel_from_oss(actual_oss))
|
30
|
+
# # print(res)
|
31
|
+
# # print(read_excel_and_write_to_dict(read_excel_from_oss(oss1), type=".xlsx"))
|
32
|
+
# print(check_excel(check_type="all", actual_oss=actual_oss, expected_oss=expected_oss))
|
33
|
+
# print(check_excel_all(actual_oss=oss1, expected_oss=oss2,skiprows =1))
|
34
|
+
# print(check_excel_all(actual_oss=oss1, expected_oss=oss2,ignore_sort=True))
|
35
|
+
# print(check_excel_all(actual_oss=a_person_oss2, expected_oss=e_person_oss1, check_type="including"))
|
36
|
+
# print(ExcelExportChecker.check_excel_all(actual_oss=oss3, expected_oss=oss4, check_type="including"))
|
37
|
+
# read_excel_csv_data(type=)
|
38
|
+
# print(DataTypeUtils().check_email_format())
|
39
|
+
# errors = ExcelExportChecker.check_field_format(actual_oss=oss1, fileds={0: {5: "time"}}, skiprows=1)
|
40
|
+
# ExcelExportChecker.check_excel_name(actual_oss=oss1, expected_oss=url)
|
41
|
+
|
42
|
+
# flow触发流程
|
43
|
+
_url = "http://sp-go-flow-test.inshopline.com"
|
44
|
+
host_domain = "https://test.smartpushedm.com/api-em-ec2"
|
45
|
+
cookies = "a_lang=zh-hans-cn; ecom_http_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NDc3OTMyMzgsImp0aSI6ImU1YWY4ZGE5LTlhMTQtNGY0Yi04NmM5LTNlNzAwZTFjY2RiYyIsInVzZXJJbmZvIjp7ImlkIjowLCJ1c2VySWQiOiI0MjEzNzg1MjQ3IiwidXNlcm5hbWUiOiIiLCJlbWFpbCI6ImZlbGl4LnNoYW9Ac2hvcGxpbmVhcHAuY29tIiwidXNlclJvbGUiOiJvd25lciIsInBsYXRmb3JtVHlwZSI6Nywic3ViUGxhdGZvcm0iOjEsInBob25lIjoiIiwibGFuZ3VhZ2UiOiJ6aC1oYW5zLWNuIiwiYXV0aFR5cGUiOiIiLCJhdHRyaWJ1dGVzIjp7ImNvdW50cnlDb2RlIjoiQ04iLCJjdXJyZW5jeSI6IkpQWSIsImN1cnJlbmN5U3ltYm9sIjoiSlDCpSIsImRvbWFpbiI6InNtYXJ0cHVzaDQubXlzaG9wbGluZXN0Zy5jb20iLCJsYW5ndWFnZSI6ImVuIiwibWVyY2hhbnRFbWFpbCI6ImZlbGl4LnNoYW9Ac2hvcGxpbmUuY29tIiwibWVyY2hhbnROYW1lIjoiU21hcnRQdXNoNF9lYzJf6Ieq5Yqo5YyW5bqX6ZO6IiwicGhvbmUiOiIiLCJzY29wZUNoYW5nZWQiOnRydWUsInN0YWZmTGFuZ3VhZ2UiOiJ6aC1oYW5zLWNuIiwic3RhdHVzIjowLCJ0aW1lem9uZSI6IkFzaWEvTWFjYW8ifSwic3RvcmVJZCI6IjE2NDQzOTU5MjA0NDQiLCJoYW5kbGUiOiJzbWFydHB1c2g0IiwiZW52IjoiQ04iLCJzdGUiOiIiLCJ2ZXJpZnkiOiIifSwibG9naW5UaW1lIjoxNzQ1MjAxMjM4NjA0LCJzY29wZSI6WyJlbWFpbC1tYXJrZXQiLCJjb29raWUiLCJzbC1lY29tLWVtYWlsLW1hcmtldC1uZXctdGVzdCIsImVtYWlsLW1hcmtldC1uZXctZGV2LWZzIiwiYXBpLXVjLWVjMiIsImFwaS1zdS1lYzIiLCJhcGktZW0tZWMyIiwiZmxvdy1wbHVnaW4iLCJhcGktc3AtbWFya2V0LWVjMiJdLCJjbGllbnRfaWQiOiJlbWFpbC1tYXJrZXQifQ.NgpEGF_8BJgTOh1LNz_Is43JBd7uZhyTGQtQq8SOdsc; osudb_appid=SMARTPUSH; osudb_lang=; osudb_oar=#01#SID0000126BLmmyWXDjlSc59ljxo2axjoWExJ1oHkAkRqVtfzm4UUtWvBGvzF6FeTbrvTHmfJLrtJz+Dpthge0dIaJpI2ukTujazNNJbLjZFu9kkuOrtRAXNL02fNqpUb6MwTCNxOnm03YIlfb7X2482UvDm36; osudb_subappid=1; osudb_uid=4213785247; JSESSIONID=8DEE7103CFA4505DF68ECE629B00A0F3"
|
46
|
+
|
47
|
+
params = {
|
48
|
+
"abandonedOrderId": "c2c4a695a36373f56899b370d0f1b6f2",
|
49
|
+
"areaCode": "",
|
50
|
+
"context": {
|
51
|
+
"order": {
|
52
|
+
"buyerSubscribeEmail": True,
|
53
|
+
"checkoutId": "c2c4a695a36373f56899b370d0f1b6f2",
|
54
|
+
"discountCodes": [],
|
55
|
+
"orderAmountSet": {
|
56
|
+
"amount": 3,
|
57
|
+
"currency": "JPY"
|
58
|
+
},
|
59
|
+
"orderDetails": [
|
60
|
+
{
|
61
|
+
"productId": "16060724900402692190790343",
|
62
|
+
"title": "测试2.0-商品同步AutoSync-2023-08-17 20:52:00",
|
63
|
+
"titleTranslations": []
|
64
|
+
}
|
65
|
+
],
|
66
|
+
"receiverCountryCode": "HK"
|
67
|
+
},
|
68
|
+
"user": {
|
69
|
+
"addresses": [],
|
70
|
+
"areaCode": "",
|
71
|
+
"email": "testsmart200+10@gmail.com",
|
72
|
+
"firstName": "testsmart200+10",
|
73
|
+
"gender": "others",
|
74
|
+
"id": "1911625831177650177",
|
75
|
+
"lastName": "",
|
76
|
+
"phone": "",
|
77
|
+
"tags": [],
|
78
|
+
"uid": "4603296300",
|
79
|
+
"userName": "testsmart200+10"
|
80
|
+
}
|
81
|
+
},
|
82
|
+
"controlObjectId": "c2c4a695a36373f56899b370d0f1b6f2",
|
83
|
+
"controlObjectType": 4,
|
84
|
+
"email": "testsmart200+10@gmail.com",
|
85
|
+
"handle": "smartpush4",
|
86
|
+
"language": "en",
|
87
|
+
"messageId": "1911625832100397058",
|
88
|
+
"phone": "",
|
89
|
+
"platform": 4,
|
90
|
+
"storeId": "1644395920444",
|
91
|
+
"timezone": "Asia/Macao",
|
92
|
+
"triggerId": "c1001",
|
93
|
+
"uid": "4603296300",
|
94
|
+
"userId": "1911625831177650177"
|
95
|
+
}
|
96
|
+
update_flow_params = {"id": "FLOW6941975456855532553", "version": "10", "triggerId": "c1001",
|
97
|
+
"templateId": "TEMP6911595896571704333", "showData": False, "flowChange": True, "nodes": [
|
98
|
+
{"type": "trigger", "data": {
|
99
|
+
"trigger": {"trigger": "c1001", "group": "", "suggestionGroupId": "", "triggerStock": False,
|
100
|
+
"completedCount": 4, "skippedCount": 0}, "completedCount": 4, "skippedCount": 0},
|
101
|
+
"id": "92d115e7-8a86-439a-8cfb-1aa3ef075edf"}, {"type": "delay", "data": {
|
102
|
+
"delay": {"type": "relative", "relativeTime": 0, "relativeUnit": "HOURS", "designatedTime": ""},
|
103
|
+
"completedCount": 4}, "id": "e0fc258b-fcfc-421c-b215-8e41638072ca"}, {"type": "sendLetter", "data": {
|
104
|
+
"sendLetter": {"id": 367462, "activityTemplateId": 367462, "activityName": "flowActivity_EwEi3d",
|
105
|
+
"activityImage": "http://cdn.smartpushedm.com/frontend/smart-push/staging/1644395920444/1744102089665/1744102093754_f99e3703.jpeg",
|
106
|
+
"emailName": "A Message from Your Cart", "merchantId": "1644395920444",
|
107
|
+
"merchantName": "SmartPush4_ec2_自动化店铺",
|
108
|
+
"brandName": "SmartPush4_ec2_自动化店铺 AutoTestName", "currency": "JP¥",
|
109
|
+
"activityType": "NORMAL", "activityStatus": "ACTIVE", "createTime": 1745201732286,
|
110
|
+
"updateTime": 1745201825819, "createDate": "2025-04-21 10:15:32",
|
111
|
+
"updateDate": "2025-04-21 10:17:05", "pickContactPacks": [], "excludeContactPacks": [],
|
112
|
+
"customerGroupIds": [], "excludeCustomerGroupIds": [], "pickContactInfos": [],
|
113
|
+
"excludeContactInfos": [], "customerGroupInfos": [], "excludeCustomerGroupInfos": [],
|
114
|
+
"sender": "SmartPush4_ec2_自动化店铺", "senderDomain": "DEFAULT_DOMAIN", "domainType": 3,
|
115
|
+
"receiveAddress": "", "originTemplate": 33,
|
116
|
+
"currentJsonSchema": "{\"id\":\"a4a9fba2a\",\"type\":\"Stage\",\"props\":{\"backgroundColor\":\"#EAEDF1\",\"width\":\"600px\",\"fullWidth\":\"normal-width\"},\"children\":[{\"id\":\"84ba788da\",\"type\":\"Header\",\"props\":{\"backgroundColor\":\"#ffffff\",\"borderLeft\":\"1px none #ffffff\",\"borderRight\":\"1px none #ffffff\",\"borderTop\":\"1px none #ffffff\",\"borderBottom\":\"1px none #ffffff\",\"paddingTop\":\"0px\",\"paddingBottom\":\"0px\",\"paddingLeft\":\"0px\",\"paddingRight\":\"0px\",\"cols\":[12]},\"children\":[{\"id\":\"98d909a48\",\"type\":\"Column\",\"props\":{},\"children\":[]}]},{\"id\":\"84ba7bbda\",\"type\":\"Section\",\"props\":{\"backgroundColor\":\"#ffffff\",\"borderLeft\":\"1px none #ffffff\",\"borderRight\":\"1px none #ffffff\",\"borderTop\":\"1px none #ffffff\",\"borderBottom\":\"1px none #ffffff\",\"paddingTop\":\"0px\",\"paddingBottom\":\"0px\",\"paddingLeft\":\"0px\",\"paddingRight\":\"0px\",\"cols\":[12]},\"children\":[{\"id\":\"8cab9aa48\",\"type\":\"Column\",\"props\":{},\"children\":[]}]},{\"id\":\"b8bbabad9\",\"type\":\"Footer\",\"props\":{\"backgroundColor\":\"#ffffff\",\"borderLeft\":\"1px none #ffffff\",\"borderRight\":\"1px none #ffffff\",\"borderTop\":\"1px none #ffffff\",\"borderBottom\":\"1px none #ffffff\",\"paddingTop\":\"0px\",\"paddingBottom\":\"0px\",\"paddingLeft\":\"0px\",\"paddingRight\":\"0px\",\"cols\":[12]},\"children\":[{\"id\":\"b3bcabad7\",\"type\":\"Column\",\"props\":{},\"children\":[{\"id\":\"b39b6a94a\",\"type\":\"Subscribe\",\"props\":{\"content\":\"<p style=\\\"text-align:center;\\\"><span style=\\\"font-size:12px\\\"><span style=\\\"font-family:Arial, Helvetica, sans-serif\\\">在此處輸入聯繫地址,可以讓你的顧客更加信任這封郵件</span></span></p>\"},\"children\":[]}]}]}],\"extend\":{\"version\":\"1.0.0\",\"updateTime\":\"2025-03-18T09:57:40.953Z\"}}",
|
117
|
+
"currentHtml": "<!doctype html>\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:v=\"urn:schemas-microsoft-com:vml\" xmlns:o=\"urn:schemas-microsoft-com:office:office\">\n <head>\n <title></title>\n <!--[if !mso]><!-->\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n <!--<![endif]-->\n <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <style type=\"text/css\">\n #outlook a { padding:0; }\n body { margin:0;padding:0;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%; }\n table, td { border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt; }\n img { border:0;line-height:100%; outline:none;text-decoration:none;-ms-interpolation-mode:bicubic; }\n pre{margin: 0;}p{ display: block;margin:0;}\n </style>\n <!--[if mso]>\n <noscript>\n <xml>\n <o:OfficeDocumentSettings>\n <o:AllowPNG/>\n <o:PixelsPerInch>96</o:PixelsPerInch>\n </o:OfficeDocumentSettings>\n </xml>\n </noscript>\n <![endif]-->\n <!--[if lte mso 11]>\n <style type=\"text/css\">\n .mj-outlook-group-fix { width:100% !important; }\n </style>\n <![endif]-->\n \n <!--[if !mso]><!-->\n <link href=\"https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700\" rel=\"stylesheet\" type=\"text/css\">\n <style type=\"text/css\">\n @import url(https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);\n </style>\n <!--<![endif]-->\n\n \n \n <style type=\"text/css\">\n @media only screen and (min-width:480px) {\n .mj-column-per-100 { width:100% !important; max-width: 100%; }\n }\n </style>\n <style media=\"screen and (min-width:480px)\">\n .moz-text-html .mj-column-per-100 { width:100% !important; max-width: 100%; }\n </style>\n \n \n <style type=\"text/css\">\n \n \n </style>\n <style type=\"text/css\">\n @import url(https://fonts.googleapis.com/css?family=Arvo:400|Bodoni+Moda:400|DM+Sans:400|Poppins:400|Hammersmith+One:400|Libre+Baskerville:400|Lexend+Giga:400|Ubuntu:400|Montserrat:400|Nunito:400|News+Cycle:400|Roboto:400|Oswald:400);@import url(https://fonts.googleapis.com/css?family=Asar:400|Bruno+Ace:400|Cantata+One:400|League+Gothic:400|Long+Cang:400|Lovers+Quarrel:400|Nanum+Gothic+Coding:400|Nanum+Myeongjo:400|Noto+Sans+Kaithi:400|Noto+Sans+Kannada:400|Noto+Sans+Math:400|Noto+Sans+Syloti+Nagri:400|Noto+Serif+JP:400|Playwrite+AT+Guides:400|Saira+Extra+Condensed:400|Tsukimi+Rounded:400|Waiting+for+the+Sunrise:400);h1,\nh2,\nh3,\nh4,\nh5 {\n font-weight: bold;\n margin-bottom: 0;\n}\np {\n margin-top: 0;\n margin-bottom: 0;\n min-height: 1em;\n}\n\nul {\n margin-bottom: 0;\n}\n\nth {\n font-weight: bold;\n}\n\na {\n text-decoration: none;\n}\n\na::-webkit-scrollbar {\n -webkit-appearance: none;\n}\n\na::-webkit-scrollbar:horizontal {\n max-height: 8px;\n}\n\na::-webkit-scrollbar-thumb {\n border-radius: 8px;\n background-color: rgba(0, 0, 0, 0.5);\n}\n\nul,\nol,\ndl {\n margin-top: 16px !important;\n}\n\npre {\n word-break: break-word;\n padding: 0;\n margin: 0;\n white-space: inherit !important;\n}\n\npre p {\n word-break: break-word;\n padding: 0;\n margin: 0;\n color: #000000;\n}\n\nspan[style*='color'] a {\n color: inherit;\n}\n\n.mj-column-no-meida-100{\n width: 100% !important;\n}\n.mj-column-no-meida-50{\n width: 50% !important;\n}\n.mj-column-no-meida-33-333333333333336{\n width: 33.333333333333336% !important;\n}\n.mj-column-no-meida-25{\n width: 25% !important;\n}\n\n.white-nowrap {\n white-space: nowrap !important;\n}\n\n/* 间距 */\n.sp-m-p-0 {\n margin: 0;\n padding: 0;\n}\n\n.nps-content-span a {\n color: inherit !important;\n}.sp-font-12 {\n font-size: 12px !important;\n}\n\n.sp-font-14 {\n font-size: 14px !important;\n}\n\n.sp-font-16 {\n font-size: 16px !important;\n}\n\n.sp-font-18 {\n font-size: 18px !important;\n}\n\n.sp-font-20 {\n font-size: 20px !important;\n}\n\n.sp-font-22 {\n font-size: 22px !important;\n}\n\n.sp-font-24 {\n font-size: 24px !important;\n}\n\n.sp-font-26 {\n font-size: 26px !important;\n}\n\n.sp-font-28 {\n font-size: 28px !important;\n}\n\n.sp-font-30 {\n font-size: 30px !important;\n}\n\n.sp-font-32 {\n font-size: 32px !important;\n}\n\n.sp-font-34 {\n font-size: 34px !important;\n}\n\n.sp-font-36 {\n font-size: 36px !important;\n}\n\n.sp-font-38 {\n font-size: 38px !important;\n}\n\n.sp-font-40 {\n font-size: 40px !important;\n}\n\n.sp-font-42 {\n font-size: 42px !important;\n}\n\n.sp-font-44 {\n font-size: 44px !important;\n}\n\n.sp-font-46 {\n font-size: 46px !important;\n}\n\n.sp-font-48 {\n font-size: 48px !important;\n}\n\n.sp-font-50 {\n font-size: 50px !important;\n}\n\n.sp-font-52 {\n font-size: 52px !important;\n}\n\n.sp-font-54 {\n font-size: 54px !important;\n}\n\n.sp-font-56 {\n font-size: 56px !important;\n}\n\n.sp-font-58 {\n font-size: 58px !important;\n}\n\n.sp-font-60 {\n font-size: 60px !important;\n}\n\n.sp-image-icon {\n width: 50px;\n}\n\n@media only screen and (max-width:600px) {\n\n .sp-font-12 {\n font-size: 12px !important;\n }\n\n .sp-font-14 {\n font-size: 12px !important;\n }\n\n .sp-font-16 {\n font-size: 12px !important;\n }\n\n .sp-font-18 {\n font-size: 13px !important;\n }\n\n .sp-font-20 {\n font-size: 15px !important;\n }\n\n .sp-font-22 {\n font-size: 16px !important;\n }\n\n .sp-font-24 {\n font-size: 18px !important;\n }\n\n .sp-font-26 {\n font-size: 19px !important;\n }\n\n .sp-font-28 {\n font-size: 21px !important;\n }\n\n .sp-font-30 {\n font-size: 22px !important;\n }\n\n .sp-font-32 {\n font-size: 24px !important;\n }\n\n .sp-font-34 {\n font-size: 25px !important;\n }\n\n .sp-font-36 {\n font-size: 27px !important;\n }\n\n .sp-font-38 {\n font-size: 28px !important;\n }\n\n .sp-font-40 {\n font-size: 30px !important;\n }\n\n .sp-font-42 {\n font-size: 31px !important;\n }\n\n .sp-font-44 {\n font-size: 32px !important;\n }\n\n .sp-font-46 {\n font-size: 33px !important;\n }\n\n .sp-font-48 {\n font-size: 34px !important;\n }\n\n .sp-font-50 {\n font-size: 35px !important;\n }\n\n .sp-font-52 {\n font-size: 36px !important;\n }\n\n .sp-font-54 {\n font-size: 37px !important;\n }\n\n .sp-font-56 {\n font-size: 38px !important;\n }\n\n .sp-font-58 {\n font-size: 39px !important;\n }\n\n .sp-font-60 {\n font-size: 40px !important;\n }\n\n .sp-image-icon {\n width: 28px !important;\n }\n\n}@media only screen and (max-width:480px) {\n\n .sp-img-h-1-TwoVertical-11,\n .sp-img-h-1-TwoHorizontalColumns-11 {\n height: 170px !important;\n }\n\n .sp-img-h-1-TwoVertical-23,\n .sp-img-h-1-TwoHorizontalColumns-23 {\n height: 243px !important;\n }\n\n .sp-img-h-1-TwoVertical-34,\n .sp-img-h-1-TwoHorizontalColumns-34 {\n height: 227px !important;\n }\n\n .sp-img-h-1-TwoVertical-43,\n .sp-img-h-1-TwoHorizontalColumns-43 {\n height: 127px !important;\n }\n\n .sp-img-h-1-ThreeHorizontalColumns-11 {\n height: 109px !important;\n }\n\n .sp-img-h-1-ThreeHorizontalColumns-23 {\n height: 163px !important;\n }\n\n .sp-img-h-1-ThreeHorizontalColumns-34 {\n height: 145px !important;\n }\n\n .sp-img-h-1-ThreeHorizontalColumns-43 {\n height: 81px !important;\n }\n\n .sp-img-h-2-TwoVertical-11 {\n height: 164px !important;\n }\n\n .sp-img-h-2-TwoVertical-23 {\n height: 246px !important;\n }\n\n .sp-img-h-2-TwoVertical-34 {\n height: 218px !important;\n }\n\n .sp-img-h-2-TwoVertical-43 {\n height: 123px !important;\n }\n\n .sp-img-h-2-ThreeHorizontalColumns-11,\n .sp-img-h-2-TwoHorizontalColumns-11 {\n height: 76px !important;\n }\n\n .sp-img-h-2-ThreeHorizontalColumns-23,\n .sp-img-h-2-TwoHorizontalColumns-23 {\n height: 113px !important;\n }\n\n .sp-img-h-2-ThreeHorizontalColumns-34,\n .sp-img-h-2-TwoHorizontalColumns-34 {\n height: 101px !important;\n }\n\n .sp-img-h-2-ThreeHorizontalColumns-43,\n .sp-img-h-2-TwoHorizontalColumns-43 {\n height: 57px !important;\n }\n}@media only screen and (min-width: 320px) and (max-width: 599px) {\n .mj-w-50 {\n width: 50% !important;\n max-width: 50%;\n }\n}\n\n@media only screen and (max-width: 480px) {\n .shop-white-mobile {\n width: 100% !important;\n }\n .mj-td-force-100{\n display: inline-block;\n width: 100% !important;\n }\n\n .mj-ImageText-screen {\n width: 100% !important;\n }\n .mj-ImageText-img {\n margin: 0 auto;\n }\n\n .mj-ImageText-margin {\n margin: 0 !important;\n }\n\n .mj-ImageText-margin-zero {\n margin: 0 5px 0 0 !important;\n }\n\n .mj-ImageText-margin-one {\n margin: 0 0 0 5px !important;\n }\n .mj-column-per-50-force {\n width: 50% !important;\n max-width: 50%;\n }\n}\n\n@media only screen and (min-width: 480px) {\n .mj-imagetext-force-100{\n display: inline-block;\n width: 100% !important;\n }\n .mj-column-per-100 {\n width: 100% !important;\n max-width: 100%;\n }\n\n .mj-column-per-50 {\n width: 50% !important;\n max-width: 50%;\n }\n\n .mj-column-per-33-333333333333336 {\n width: 33.333333333333336% !important;\n max-width: 33.333333333333336%;\n }\n\n .mj-column-per-25 {\n width: 25% !important;\n max-width: 25%;\n }\n\n\n .mg-column-per-46-5 {\n width: 46.5% !important;\n max-width: 46.5%;\n }\n\n .mj-AbandonProduct-cloum-align-center {\n align-items: flex-start !important;\n }\n\n\n .mj-OrderInformation-padding-top-220 {\n padding-top: 20px !important;\n }\n\n .mj-OrderInformation-float-left {\n float: left !important;\n }\n}@media only screen and (max-width: 480px) {\n .mt-1{\n margin-top: 1px;\n }\n .mt-2{\n margin-top: 2px;\n }\n .mt-3{\n margin-top: 3px;\n }\n .mt-4{\n margin-top: 4px;\n }\n .mt-5{\n margin-top: 5px;\n }\n .mt-6{\n margin-top: 7px;\n }\n .mt-8{\n margin-top: 9px;\n }\n .mt-10{\n margin-top: 10px;\n }\n\n}\n </style>\n \n </head>\n <body style=\"word-spacing:normal;background-color:#EAEDF1;\">\n \n \n <div\n style=\"background-color:#EAEDF1;\"\n >\n <table className=\"pv-stage\">\n <tbody>\n <tr>\n <td style=\"display:table-column;\"><div style=\"width:1px; height:1px;\"><img style=\"width:1px; height:1px;\" width=\"1\" src=\"${SP_OPEN_EMAIL_URL}\" />\n </div></td>\n </tr>\n </tbody>\n </table><div mso-hide: all; position: fixed; height: 0; max-height: 0; overflow: hidden; font-size: 0; style=\"display:none;\">${emailSubtitle}</div>\n \n <!--[if mso | IE]><table align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" class=\"\" role=\"presentation\" style=\"width:600px;\" width=\"600\" bgcolor=\"#ffffff\" ><tr><td style=\"line-height:0px;font-size:0px;mso-line-height-rule:exactly;\"><![endif]-->\n \n \n <div style=\"background:#ffffff;background-color:#ffffff;margin:0px auto;max-width:600px;\">\n \n <table\n align=\"center\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\" style=\"background:#ffffff;background-color:#ffffff;width:100%;\"\n >\n <tbody>\n <tr>\n <td\n style=\"border-bottom:1px none #ffffff;border-left:1px none #ffffff;border-right:1px none #ffffff;border-top:1px none #ffffff;direction:ltr;font-size:0px;padding:20px 0;padding-bottom:0px;padding-left:0px;padding-right:0px;padding-top:0px;text-align:center;\"\n >\n <!--[if mso | IE]><table role=\"presentation\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\"><tr><td class=\"\" style=\"vertical-align:top;width:598px;\" ><![endif]-->\n \n <div\n class=\"mj-column-per-100 mj-outlook-group-fix\" style=\"font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;\"\n >\n \n <table\n border=\"0\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\" style=\"vertical-align:top;\" width=\"100%\"\n >\n <tbody>\n \n <tr>\n <td\n align=\"left\" style=\"font-size:0px;padding:0;word-break:break-word;\"\n >\n \n <table\n cellpadding=\"0\" cellspacing=\"0\" width=\"100%\" border=\"0\" style=\"color:#000000;font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:22px;table-layout:fixed;width:100%;border:none;\"\n >\n <table style=\"margin:0;padding:0;width:100%;table-layout:fixed\" class=\"\" sp-id=\"subscribe-area-b39b6a94a\"><tbody><tr style=\"width:100%\"><td style=\"margin:0;padding:0;width:100%;text-align:center;padding-top:20px;padding-left:20px;padding-right:20px;padding-bottom:20px;background-color:transparent;font-family:arial,helvetica,sans-serif,Arial, Helvetica, sans-serif\" class=\"Subscribe\"><table cellPadding=\"0\" cellSpacing=\"0\" style=\"width:100%\"><tbody><tr><td align=\"center\" class=\"sp-font-16\" valign=\"middle\" style=\"padding-left:10px;padding-right:10px;padding-top:10px;text-align:left;font-size:16px\"><div><p style=\"text-align:center;\"><span style=\"font-size:12px\"><span style=\"font-family:Arial, Helvetica, sans-serif\">在此處輸入聯繫地址,可以讓你的顧客更加信任這封郵件</span></span></p></div></td></tr><tr><td align=\"center\" valign=\"middle\" height=\"20\" style=\"font-size:12px;font-family:arial,helvetica,sans-serif,Arial, Helvetica, sans-serif;padding-left:10px;padding-right:10px;padding-bottom:20px\"></td></tr><div><tr style=\"${hide_logo}\" sp-id=\"subscribe-dom-b39b6a94a\">\n <td class='sp-font-16' style=\"padding-left:20px;padding-right:20px;padding-top:20px;text-align:center;font-size:16px\" >\n <div style=\"border-top: 1px solid #EEF1F6\">\n <img src=\"https://cdn.smartpushedm.com/frontend/smart-push/product/image/1731577171577_83853d55.png\" style=\"padding: 10px;vertical-align: middle;width: 158px;\"alt=\"\" />\n </div>\n <p style=\"color:#343434;font-size:12px\">Providing content services for [[shopName]]</p>\n </td>\n </tr></div></tbody></table></td></tr></tbody></table>\n </table>\n \n </td>\n </tr>\n \n </tbody>\n </table>\n \n </div>\n \n <!--[if mso | IE]></td></tr></table><![endif]-->\n </td>\n </tr>\n </tbody>\n </table>\n \n </div>\n \n \n <!--[if mso | IE]></td></tr></table><![endif]-->\n \n \n </div>\n \n </body>\n</html>\n ",
|
118
|
+
"previewJsonSchema": "{\"id\":\"a4a9fba2a\",\"type\":\"Stage\",\"props\":{\"backgroundColor\":\"#EAEDF1\",\"width\":\"600px\",\"fullWidth\":\"normal-width\"},\"children\":[{\"id\":\"84ba788da\",\"type\":\"Header\",\"props\":{\"backgroundColor\":\"#ffffff\",\"borderLeft\":\"1px none #ffffff\",\"borderRight\":\"1px none #ffffff\",\"borderTop\":\"1px none #ffffff\",\"borderBottom\":\"1px none #ffffff\",\"paddingTop\":\"0px\",\"paddingBottom\":\"0px\",\"paddingLeft\":\"0px\",\"paddingRight\":\"0px\",\"cols\":[12]},\"children\":[{\"id\":\"98d909a48\",\"type\":\"Column\",\"props\":{},\"children\":[]}]},{\"id\":\"84ba7bbda\",\"type\":\"Section\",\"props\":{\"backgroundColor\":\"#ffffff\",\"borderLeft\":\"1px none #ffffff\",\"borderRight\":\"1px none #ffffff\",\"borderTop\":\"1px none #ffffff\",\"borderBottom\":\"1px none #ffffff\",\"paddingTop\":\"0px\",\"paddingBottom\":\"0px\",\"paddingLeft\":\"0px\",\"paddingRight\":\"0px\",\"cols\":[12]},\"children\":[{\"id\":\"8cab9aa48\",\"type\":\"Column\",\"props\":{},\"children\":[]}]},{\"id\":\"b8bbabad9\",\"type\":\"Footer\",\"props\":{\"backgroundColor\":\"#ffffff\",\"borderLeft\":\"1px none #ffffff\",\"borderRight\":\"1px none #ffffff\",\"borderTop\":\"1px none #ffffff\",\"borderBottom\":\"1px none #ffffff\",\"paddingTop\":\"0px\",\"paddingBottom\":\"0px\",\"paddingLeft\":\"0px\",\"paddingRight\":\"0px\",\"cols\":[12]},\"children\":[{\"id\":\"b3bcabad7\",\"type\":\"Column\",\"props\":{},\"children\":[{\"id\":\"b39b6a94a\",\"type\":\"Subscribe\",\"props\":{\"content\":\"<p style=\\\"text-align:center;\\\"><span style=\\\"font-size:12px\\\"><span style=\\\"font-family:Arial, Helvetica, sans-serif\\\">在此處輸入聯繫地址,可以讓你的顧客更加信任這封郵件</span></span></p>\"},\"children\":[]}]}]}],\"extend\":{\"version\":\"1.0.0\",\"updateTime\":\"2025-03-18T09:57:40.953Z\"}}",
|
119
|
+
"generatedHtml": False,
|
120
|
+
"templateUrl": "https://cdn2.smartpushedm.com/material/2021-11-29/d4f96fc873e942a397be708c932bbbe4-自定义排版.png",
|
121
|
+
"sendStrategy": "NOW", "totalReceiver": 0, "utmConfigEnable": False,
|
122
|
+
"subtitle": "Items in your cart are selling out fast!", "language": "en",
|
123
|
+
"languageName": "英语", "timezone": "Asia/Shanghai", "timezoneGmt": "GMT+08:00",
|
124
|
+
"type": "FLOW", "relId": "FLOW6941975456855532553",
|
125
|
+
"parentId": "TEMP6911595896571704333", "nodeId": "2503b475-ce3e-4906-ab04-0ebc387f0d7e",
|
126
|
+
"version": "10", "nodeOrder": 0, "sendType": "EMAIL", "productInfos": [], "blocks": [
|
127
|
+
{"domId": "subscribe-dom-b39b6a94a", "blockId": "", "areaId": "", "type": "SP_LOGO",
|
128
|
+
"column": 1, "fillStyle": 0, "ratio": ""}], "discountCodes": [], "reviews": [], "awards": [],
|
129
|
+
"selectProducts": [], "createSource": "BUILD_ACTIVITY", "contentChange": True,
|
130
|
+
"activityChange": False, "imageVersion": "1744102089665", "subActivityList": [],
|
131
|
+
"warmupPack": 0, "boosterEnabled": False, "smartSending": False, "boosterCreated": False,
|
132
|
+
"gmailPromotion": False, "sendTimeType": "FIXED", "sendTimezone": "B_TIMEZONE",
|
133
|
+
"sendTimeDelay": False, "sendOption": 1, "hasUserBlock": False, "hasAutoBlock": False,
|
134
|
+
"smsSendDelay": True, "payFunctionList": [], "minSendTime": "2025-04-21 10:15:32",
|
135
|
+
"completedCount": 4, "skippedCount": 0, "openRate": 1, "clickRate": 0, "orderIncome": 0,
|
136
|
+
"openDistinctUserRate": 1, "clickDistinctUserRate": 0}, "completedCount": 4,
|
137
|
+
"skippedCount": 0, "openRate": 1, "clickRate": 0, "orderIncome": 0, "openDistinctUserRate": 1,
|
138
|
+
"clickDistinctUserRate": 0}, "id": "2503b475-ce3e-4906-ab04-0ebc387f0d7e"}],
|
139
|
+
"showDataStartTime": 1745164800000, "showDataEndTime": 1745251199000}
|
140
|
+
# mock_pulsar = MockFlow.check_flow(mock_domain=_url, host_domain=host_domain, cookies=cookies,
|
141
|
+
# flow_id="FLOW6941975456855532553", pulsar=params,
|
142
|
+
# update_flow_params=update_flow_params)
|
143
|
+
# print(mock_pulsar)
|
144
|
+
|
145
|
+
# mock_pulsar_step1, _ = MockFlow.check_flow(mock_domain=_url, host_domain=host_domain, cookies=cookies,
|
146
|
+
# flow_id="FLOW6941975456855532553", pulsar=params,
|
147
|
+
# update_flow_params=update_flow_params, split_steps="one")
|
148
|
+
# mock_pulsar_step2 = MockFlow.check_flow(mock_domain=_url, host_domain=host_domain, cookies=cookies,
|
149
|
+
# flow_id="FLOW6941975456855532553", old_flow_counts=mock_pulsar_step1,
|
150
|
+
# print(mock_pulsar_step1)
|
151
|
+
# print(mock_pulsar_step2)
|
152
|
+
|
153
|
+
# split_steps="two")
|
154
|
+
# # node_counts, versions = MockFlow.get_current_flow(host_domain=host_domain, cookies=cookies,
|
155
|
+
# flow_id="FLOW6749144046546626518")
|
156
|
+
|
157
|
+
# 调试
|
158
|
+
# # 示例字典
|
159
|
+
# my_dict = {'name': 'Alice', 'age': 25, 'city': 'New York'}
|
160
|
+
# # 提取所有键到列表
|
161
|
+
# key_list = list(my_dict)
|
162
|
+
# print(key_list)
|
163
|
+
a = ["1", "3", "5"]
|
164
|
+
# b = ["3", "1", "5"]
|
165
|
+
# if a.sort() == b.sort():
|
166
|
+
# print(1)
|
167
|
+
# else:
|
168
|
+
# print(2)
|
169
|
+
|
170
|
+
# 断言邮件
|
171
|
+
loginEmail, password = 'lulu9600000@gmail.com', 'evvurakhttndwspx'
|
172
|
+
email_property = [{'1AutoTest-固定B-营销-生产2.0-2025-04-24 10:19:47.341333-🔥🔥': {'activityId': 408764,
|
173
|
+
'utmConfigInfo': {'utmSource': '1',
|
174
|
+
'utmMedium': '2',
|
175
|
+
'utmCampaign': '3'},
|
176
|
+
'receiveAddress': 'autotest-smartpushpro5@smartpush.com',
|
177
|
+
'sender': 'SmartPush_Pro5_ec2自动化店铺 AutoTestName',
|
178
|
+
'subtitle': 'AutoTest-2025-04-24 10:19:47.341333-subtitle-[[contact.name]]-🔥🔥'}},
|
179
|
+
{'AutoTest-固定B-营销-生产2.0-2025-04-24 10:19:59.023150-🔥🔥': {'activityId': 408765,
|
180
|
+
'utmConfigInfo': {'utmSource': '1',
|
181
|
+
'utmMedium': '2',
|
182
|
+
'utmCampaign': '3'},
|
183
|
+
'receiveAddress': '1autotest-smartpushpro5@smartpush.com',
|
184
|
+
'sender': 'SmartPush_Pro5_ec2自动化店铺 AutoTestName',
|
185
|
+
'subtitle': 'AutoTest-2025-04-24 10:19:59.023150-subtitle-[[contact.name]]-🔥🔥'}},
|
186
|
+
{'测试邮件-AutoTest_营销_生产2.0_2025_04_24 10:20:28.529290_🔥🔥-😈': {'activityId': None,
|
187
|
+
'utmConfigInfo': None,
|
188
|
+
'receiveAddress': 'autotest-smartpushpro5@smartpush.com',
|
189
|
+
'sender': '1SmartPush_Pro5_ec2自动化店铺 AutoTestName',
|
190
|
+
'subtitle': '营销测试邮件-2025-04-24 10:20:29.560357-😈'}}]
|
191
|
+
result = EmailUtlis.check_email_content(emailProperty=email_property, loginEmail=loginEmail, password=password)
|
192
|
+
print(result)
|
@@ -0,0 +1,305 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# _*_ coding:utf-8 _*_
|
3
|
+
import random
|
4
|
+
import re
|
5
|
+
import ssl
|
6
|
+
from datetime import timedelta, datetime
|
7
|
+
from urllib.parse import parse_qs
|
8
|
+
from retry import retry
|
9
|
+
import imapclient
|
10
|
+
import pyzmail
|
11
|
+
import requests
|
12
|
+
from bs4 import BeautifulSoup
|
13
|
+
from urllib import parse
|
14
|
+
|
15
|
+
junkEmail, allEmail = "[Gmail]/垃圾邮件", "[Gmail]/所有邮件"
|
16
|
+
|
17
|
+
|
18
|
+
def forParseEmailContent(UIDs, isReturnHtml=False):
|
19
|
+
"""
|
20
|
+
解析邮件内容
|
21
|
+
@param UIDs:
|
22
|
+
@return:
|
23
|
+
"""
|
24
|
+
global _subject, _sender, _from_email, _recipient, _receiveDate, _replay
|
25
|
+
messageInfo = {}
|
26
|
+
subjectList = []
|
27
|
+
if len(UIDs) > 0:
|
28
|
+
rawMessages = imapObj.fetch(UIDs, ['BODY[]'])
|
29
|
+
|
30
|
+
for uid in UIDs:
|
31
|
+
message = pyzmail.PyzMessage.factory(rawMessages[uid][b'BODY[]'])
|
32
|
+
# 解析邮件内容
|
33
|
+
_subject = message.get_subject()
|
34
|
+
subjectList.append(_subject)
|
35
|
+
_sender = message.get_addresses('from')[0][0]
|
36
|
+
_from_email = message.get_addresses('from')[0][1]
|
37
|
+
_recipient = message.get_addresses('to')[0][0]
|
38
|
+
if message.get_addresses('reply-to'):
|
39
|
+
_replay = message.get_addresses('reply-to')[0][0]
|
40
|
+
_receiveDate = message.get('date')
|
41
|
+
tempData = {"sender": _sender, "from_email": _from_email, "recipient": _recipient,
|
42
|
+
"receiveAddress": _replay if _replay else None, "receiveDate": _receiveDate}
|
43
|
+
if len(UIDs) == 1:
|
44
|
+
messageInfo.update({_subject: tempData})
|
45
|
+
else:
|
46
|
+
if messageInfo.get(_subject) is None:
|
47
|
+
messageInfo.update({_subject: [tempData]})
|
48
|
+
else:
|
49
|
+
tempList = messageInfo.get(_subject)
|
50
|
+
tempList.append(tempData)
|
51
|
+
messageInfo.update({_subject: tempList})
|
52
|
+
# html内容解析
|
53
|
+
if isReturnHtml:
|
54
|
+
htmlBody = message.html_part.get_payload().decode(message.html_part.charset)
|
55
|
+
# print('邮件html:', htmlBody)
|
56
|
+
|
57
|
+
if not isReturnHtml:
|
58
|
+
# print("------------------")
|
59
|
+
# print("邮件主旨:" + _subject)
|
60
|
+
# print("发件人:" + _sender)
|
61
|
+
# print("发件邮箱:" + _from_email)
|
62
|
+
# print("收件人邮箱:" + _recipient)
|
63
|
+
# print("回复邮箱:" + _replay)
|
64
|
+
# print('接收邮件日期:' + _receiveDate)
|
65
|
+
return messageInfo
|
66
|
+
else:
|
67
|
+
return messageInfo, htmlBody
|
68
|
+
|
69
|
+
|
70
|
+
def selectFolderGetUids(imapObj, foldersName, text=None):
|
71
|
+
"""
|
72
|
+
@param imapObj:邮件对象
|
73
|
+
@param foldersName: 文件夹
|
74
|
+
@param text: 搜索文本
|
75
|
+
@return: Uid,文件的id
|
76
|
+
"""
|
77
|
+
imapObj.select_folder(foldersName, readonly=True)
|
78
|
+
# print(f"在 【{foldersName}】 中搜索关键词---> :{text}")
|
79
|
+
if text:
|
80
|
+
# 通过标题搜索
|
81
|
+
UIDs = imapObj.gmail_search(text)
|
82
|
+
else:
|
83
|
+
# 查找所有
|
84
|
+
UIDs = imapObj.gmail_search(u'All')
|
85
|
+
return UIDs
|
86
|
+
|
87
|
+
|
88
|
+
def loginShowFoldersEmail(email, password):
|
89
|
+
# 登录邮件
|
90
|
+
# imaplib._MAXLINE = 10000000 # 最大行数限制
|
91
|
+
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
|
92
|
+
imapObj = imapclient.IMAPClient('imap.gmail.com', ssl=True, ssl_context=context)
|
93
|
+
imapObj.login(email, password)
|
94
|
+
folders = imapObj.list_folders()
|
95
|
+
print("所有的邮件文件夹:", [folder[2] for folder in folders])
|
96
|
+
return imapObj
|
97
|
+
|
98
|
+
|
99
|
+
def getTodayDateTime():
|
100
|
+
# 获取当天日期
|
101
|
+
since = datetime.utcnow() - timedelta(minutes=1)
|
102
|
+
receiveTime = since.strftime('%d-%b-%Y')
|
103
|
+
return receiveTime
|
104
|
+
|
105
|
+
|
106
|
+
def fetchEmail(imapObj, UIDs):
|
107
|
+
"""
|
108
|
+
标识为已读(打开邮件)
|
109
|
+
@param imapObj:
|
110
|
+
@param UIDs:
|
111
|
+
@return:
|
112
|
+
"""
|
113
|
+
imapObj.fetch(UIDs, ['BODY[]'])
|
114
|
+
return True
|
115
|
+
|
116
|
+
|
117
|
+
def assertEmail(emailProperty=None, is_check_email_property=True):
|
118
|
+
errors = []
|
119
|
+
for temp in emailProperty:
|
120
|
+
_subject = list(temp.keys())[0]
|
121
|
+
# print("\n----执行遍历断言----> ", _subject)
|
122
|
+
try:
|
123
|
+
UIDs = selectFolderGetUids(imapObj, allEmail, _subject)
|
124
|
+
assert UIDs
|
125
|
+
except AssertionError:
|
126
|
+
UIDs = selectFolderGetUids(imapObj, junkEmail, _subject)
|
127
|
+
try:
|
128
|
+
assert UIDs
|
129
|
+
except AssertionError:
|
130
|
+
errors.append({_subject: "未收到邮件"})
|
131
|
+
is_check_email_property = False
|
132
|
+
# raise AssertionError(f"找不到相符的邮件内容:{_subject}")
|
133
|
+
finally:
|
134
|
+
# 断言回复邮箱和发件人
|
135
|
+
if is_check_email_property:
|
136
|
+
EmailContentAll = forParseEmailContent(UIDs)
|
137
|
+
property_result = assertEmailProperty(temp[_subject], EmailContentAll[_subject])
|
138
|
+
if len(property_result) > 0:
|
139
|
+
errors.append({_subject: property_result})
|
140
|
+
return [True, "邮件内容断言成功"] if len(errors) == 0 else [False, {"邮件内容断言失败": errors}]
|
141
|
+
|
142
|
+
|
143
|
+
def assertEmailProperty(emailProperty, ParseEmailContent):
|
144
|
+
"""
|
145
|
+
断言邮件内容
|
146
|
+
@param emailProperty:
|
147
|
+
@param ParseEmailContent:assertEmail
|
148
|
+
@return:
|
149
|
+
"""
|
150
|
+
property_errors = []
|
151
|
+
# print("原始数据", ParseEmailContent)
|
152
|
+
if isinstance(ParseEmailContent, list):
|
153
|
+
_parseEmailContent = ParseEmailContent[0]
|
154
|
+
else:
|
155
|
+
# 新增-发件人、回复邮箱断言
|
156
|
+
_parseEmailContent = ParseEmailContent
|
157
|
+
if emailProperty.get('sender') != _parseEmailContent.get('sender'):
|
158
|
+
property_errors.append(
|
159
|
+
["发件人断言失败", {"预期": emailProperty.get('sender'), "实际": _parseEmailContent.get('sender')}])
|
160
|
+
if emailProperty.get('receiveAddress') != _parseEmailContent.get('receiveAddress'):
|
161
|
+
property_errors.append(["回复邮箱断言失败",
|
162
|
+
{"预期": emailProperty.get('receiveAddress'),
|
163
|
+
"实际": _parseEmailContent.get('receiveAddress')}])
|
164
|
+
return property_errors
|
165
|
+
|
166
|
+
|
167
|
+
def getRandomHyperlinksInHtml(EmailContent):
|
168
|
+
"""
|
169
|
+
获取html中的随机超链接
|
170
|
+
@param html:
|
171
|
+
@return:
|
172
|
+
"""
|
173
|
+
flag = 1
|
174
|
+
# page = BeautifulSoup(EmailContent[1], features="html.parser")
|
175
|
+
pages = BeautifulSoup(EmailContent[1], features="html.parser")
|
176
|
+
alinks = pages.findAll('a')
|
177
|
+
new_links = []
|
178
|
+
for url in alinks:
|
179
|
+
if url.get('href', None) != '#' and 'mailto:' not in url.get('href', None) and url.get('href',
|
180
|
+
None) is not None:
|
181
|
+
new_links.append(url.get('href', None))
|
182
|
+
# 随机拿一个
|
183
|
+
print(new_links)
|
184
|
+
url = new_links[random.randint(0, len(new_links) - 1)]
|
185
|
+
redirect_url = originalLinkGetsRedirectUrl(url)
|
186
|
+
# print("初始获取重定向后的链接:", redirect_url)
|
187
|
+
while 'unsubscribe' in redirect_url:
|
188
|
+
url = new_links[random.randint(0, len(new_links) - 1)]
|
189
|
+
redirect_url = originalLinkGetsRedirectUrl(url)
|
190
|
+
print("获取非退订包含utm链接:", originalLinkGetsRedirectUrl(url))
|
191
|
+
return url
|
192
|
+
|
193
|
+
|
194
|
+
def originalLinkGetsRedirectUrl(url):
|
195
|
+
"""
|
196
|
+
原始链接获取重定向后带有utm的链接
|
197
|
+
@param url:
|
198
|
+
@return:
|
199
|
+
"""
|
200
|
+
global final_location_url
|
201
|
+
print(url)
|
202
|
+
try:
|
203
|
+
session = requests.session()
|
204
|
+
resp_1 = session.get(url, allow_redirects=True)
|
205
|
+
# print("第一次重定向:",resp_1.headers.get('Location'))
|
206
|
+
# location_url_1 = resp_1.headers.get('Location')
|
207
|
+
# resp_2 = session.get(location_url_1, allow_redirects=False)
|
208
|
+
# print("第二次重定向:",resp_2.headers.get('Location'))
|
209
|
+
# final_location_url = resp_2.headers.get('Location')
|
210
|
+
final_location_url = resp_1.url
|
211
|
+
return final_location_url
|
212
|
+
except:
|
213
|
+
raise
|
214
|
+
|
215
|
+
|
216
|
+
def parseUrlParameters(url):
|
217
|
+
"""
|
218
|
+
返回url后的拼接参数
|
219
|
+
@param url:
|
220
|
+
@return:
|
221
|
+
"""
|
222
|
+
return parse_qs(requests.utils.urlparse(url).query)
|
223
|
+
|
224
|
+
|
225
|
+
def name_convert_to_snake(name: str) -> str:
|
226
|
+
"""驼峰转下划线"""
|
227
|
+
if re.search(r'[^_][A-Z]', name):
|
228
|
+
name = re.sub(r'([^_])([A-Z][a-z]+)', r'\1_\2', name)
|
229
|
+
return name_convert_to_snake(name)
|
230
|
+
return name.lower()
|
231
|
+
|
232
|
+
|
233
|
+
def assertUtmConfig(emailProperty):
|
234
|
+
"""
|
235
|
+
断言utm
|
236
|
+
@param utmConfigInfo: 请求时存的utm参数
|
237
|
+
@param emailUtmConfig: 邮件内容获取的utm参数
|
238
|
+
@param activityId:
|
239
|
+
@return:
|
240
|
+
"""
|
241
|
+
global EmailContentAll
|
242
|
+
for _email in emailProperty:
|
243
|
+
_subject = list(_email.keys())[0]
|
244
|
+
try:
|
245
|
+
UIDs = selectFolderGetUids(imapObj, allEmail, _subject)
|
246
|
+
assert UIDs
|
247
|
+
EmailContentAll = forParseEmailContent(UIDs, True)
|
248
|
+
except AssertionError:
|
249
|
+
UIDs = selectFolderGetUids(imapObj, junkEmail, _subject)
|
250
|
+
assert UIDs
|
251
|
+
EmailContentAll = forParseEmailContent(UIDs, True)
|
252
|
+
except Exception as e:
|
253
|
+
raise e
|
254
|
+
finally:
|
255
|
+
email = _email[_subject]
|
256
|
+
if email['utmConfigInfo'] is None:
|
257
|
+
print("--->测试邮件不进行utm校验")
|
258
|
+
break
|
259
|
+
temp = originalLinkGetsRedirectUrl(getRandomHyperlinksInHtml(EmailContentAll))
|
260
|
+
params = parseUrlParameters(temp)
|
261
|
+
for utmKey, utmValue in email['utmConfigInfo'].items():
|
262
|
+
assert params.get(name_convert_to_snake(utmKey))[0] == utmValue
|
263
|
+
# 断言sp参数
|
264
|
+
# ec1
|
265
|
+
if 'utm_term' in params:
|
266
|
+
utm_term = dict(parse.parse_qsl(params.get('utm_term')[0]))
|
267
|
+
_params = {**params, **utm_term}
|
268
|
+
params = _params
|
269
|
+
assert params.get('sp_medium') == 'email'
|
270
|
+
assert params.get('sp_source') == 'smartpush'
|
271
|
+
if email['activityId'] is not None:
|
272
|
+
assert params.get('sp_campaign') == str(email['activityId'])
|
273
|
+
# ec2
|
274
|
+
else:
|
275
|
+
assert params.get('sp_medium')[0] == 'email'
|
276
|
+
assert params.get('sp_source')[0] == 'smartpush'
|
277
|
+
if email['activityId'] is not None:
|
278
|
+
assert params.get('sp_campaign')[0] == str(email['activityId'])
|
279
|
+
# flow的断言需要另外写
|
280
|
+
print(f"UTM断言成功-->>{email['utmConfigInfo']} || {params} || {str(email['activityId'])}")
|
281
|
+
|
282
|
+
|
283
|
+
@retry(tries=3, delay=3, backoff=3, max_delay=20)
|
284
|
+
def check_email_content(emailProperty, loginEmail, password, **kwargs):
|
285
|
+
"""
|
286
|
+
校验邮件送达情况
|
287
|
+
loginEmail: 接收邮箱账号,必填
|
288
|
+
password: 邮箱密码,必填
|
289
|
+
emailProperty: list类型,格式: [{主旨: {receiveAddress: 回复邮箱, sender: 发件人}}], 必填
|
290
|
+
|
291
|
+
is_check_email_property: bool, 非必填,是否断言回复邮箱或发件人
|
292
|
+
is_check_utm: bool, 非必填,是否断言utm
|
293
|
+
"""
|
294
|
+
global imapObj
|
295
|
+
try:
|
296
|
+
print("--->获取emailProperty变量", emailProperty)
|
297
|
+
imapObj = loginShowFoldersEmail(loginEmail, password)
|
298
|
+
result = assertEmail(emailProperty=emailProperty, **kwargs)
|
299
|
+
# assertUtmConfig(emailProperty)
|
300
|
+
return result
|
301
|
+
except Exception:
|
302
|
+
raise Exception
|
303
|
+
finally:
|
304
|
+
# 退出登录
|
305
|
+
imapObj.logout()
|
@@ -1,4 +1,4 @@
|
|
1
|
-
def compare_lists(temp1, temp2, num=1):
|
1
|
+
def compare_lists(temp1, temp2, check_key=["completedCount"], all_key=False, num=1):
|
2
2
|
"""对比两个list中字典,a中字典对应的键值+num等于b字典中键值
|
3
3
|
ab值示例:
|
4
4
|
a = [{"123": {"a": 1, "b": 2}}, {"456": {"a": 5, "b": 6}}]
|
@@ -14,15 +14,18 @@ def compare_lists(temp1, temp2, num=1):
|
|
14
14
|
# 获取内层字典
|
15
15
|
inner_dict_a = temp1_a[outer_key]
|
16
16
|
inner_dict_b = temp2_b[outer_key]
|
17
|
+
inner_dict_a_keys = list(inner_dict_a)
|
18
|
+
inner_dict_b_keys = list(inner_dict_b)
|
19
|
+
if all_key is False:
|
20
|
+
inner_dict_a_keys = inner_dict_b_keys = check_key
|
17
21
|
# 遍历内层字典的键
|
18
|
-
for inner_key in
|
22
|
+
for inner_key in inner_dict_a_keys:
|
19
23
|
# 确保 temp2 对应的内层字典中也有相同的键
|
20
|
-
if inner_key in
|
24
|
+
if inner_key in inner_dict_b_keys:
|
21
25
|
# 检查是否满足条件
|
22
26
|
if inner_dict_a[inner_key] + num != inner_dict_b[inner_key]:
|
23
27
|
error.append({
|
24
28
|
outer_key: {
|
25
|
-
|
26
29
|
f"{inner_key}_in_a": inner_dict_a[inner_key],
|
27
30
|
f"{inner_key}_in_b": inner_dict_b[inner_key]
|
28
31
|
}
|
@@ -15,6 +15,7 @@ smartpush/export/basic/__init__.py
|
|
15
15
|
smartpush/flow/MockFlow.py
|
16
16
|
smartpush/flow/__init__.py
|
17
17
|
smartpush/utils/DataTypeUtils.py
|
18
|
+
smartpush/utils/EmailUtlis.py
|
18
19
|
smartpush/utils/ListDictUtils.py
|
19
20
|
smartpush/utils/StringUtils.py
|
20
21
|
smartpush/utils/__init__.py
|
@@ -1,100 +0,0 @@
|
|
1
|
-
import json
|
2
|
-
import random
|
3
|
-
import time
|
4
|
-
import requests
|
5
|
-
from smartpush.utils import ListDictUtils
|
6
|
-
|
7
|
-
|
8
|
-
def get_current_flow(host_domain, cookies, flow_id, oppor="forward"):
|
9
|
-
# 提取flow所有节点数据
|
10
|
-
_url = host_domain + "/flow/getFlowDetail"
|
11
|
-
headers = {
|
12
|
-
"cookie": cookies
|
13
|
-
}
|
14
|
-
params = {
|
15
|
-
"flowId": flow_id,
|
16
|
-
"active": True,
|
17
|
-
"includeActivityDetail": True
|
18
|
-
}
|
19
|
-
result = json.loads(requests.request(method="get", url=_url, headers=headers, params=params).text)
|
20
|
-
|
21
|
-
# 按节点id存储
|
22
|
-
node_counts = []
|
23
|
-
for node in result["resultData"]["nodes"]:
|
24
|
-
node_counts.append({node["id"]: {"completedCount": node["data"]["completedCount"],
|
25
|
-
"skippedCount": node["data"]["skippedCount"],
|
26
|
-
"openUserCount": node["data"]["openUserCount"],
|
27
|
-
"clickUserCount": node["data"]["clickUserCount"],
|
28
|
-
"waitingCount": node["data"]["waitingCount"]}
|
29
|
-
}
|
30
|
-
)
|
31
|
-
return {oppor: node_counts}, result["resultData"]["version"]
|
32
|
-
|
33
|
-
|
34
|
-
def start_flow(host_domain, cookies, flow_id, version):
|
35
|
-
# 开启flow
|
36
|
-
_url = host_domain + "/flow/publishFlow"
|
37
|
-
headers = {
|
38
|
-
"cookie": cookies,
|
39
|
-
"Content-Type": "application/json"
|
40
|
-
}
|
41
|
-
params = {
|
42
|
-
"flowId": flow_id,
|
43
|
-
"version": str(version)
|
44
|
-
}
|
45
|
-
result = requests.request(method="post", url=_url, headers=headers, json=params).text
|
46
|
-
|
47
|
-
|
48
|
-
def mock_pulsar(mock_domain, pulsar, limit=1):
|
49
|
-
# post请求
|
50
|
-
# times:为触发次数,默认1次即可
|
51
|
-
# 第三步提取的content直接替换mq的值即可
|
52
|
-
# 需参数化字段:
|
53
|
-
# abandonedOrderId、checkoutId、controlObjectId取第一步control_object_id做参数化
|
54
|
-
# platform为平台字段,枚举: 1:SF 2:ec1 4:ec2 8:domain
|
55
|
-
# messageId:需更改,保证不是之前使用过的
|
56
|
-
# userId:需参数化,兼容不同平台和环境
|
57
|
-
# handle、storeId:兼容不同店铺
|
58
|
-
#
|
59
|
-
# 备注:
|
60
|
-
# 可通过弃单id、userId字段测异常场景:如无联系人时,能触发但不发送,记录跳过数等;
|
61
|
-
_url = mock_domain + "/flow/testEventMulti"
|
62
|
-
headers = {
|
63
|
-
"Content-Type": "application/json"
|
64
|
-
}
|
65
|
-
# 生成随机message_id
|
66
|
-
prefix = 179
|
67
|
-
pulsar["messageId"] = f"{prefix}{random.randint(10 ** 15, 10 ** 16 - 1)}"
|
68
|
-
params = {
|
69
|
-
"times": limit,
|
70
|
-
"mq": pulsar
|
71
|
-
}
|
72
|
-
result = requests.request(method="post", url=_url, headers=headers, json=params).text
|
73
|
-
return json.loads(result)
|
74
|
-
|
75
|
-
|
76
|
-
def check_flow(mock_domain, host_domain, cookies, **kwargs):
|
77
|
-
"""
|
78
|
-
params
|
79
|
-
mock_domain:必填,触发接口域名
|
80
|
-
host_domain:必填,spflow接口域名
|
81
|
-
cookies:必填,sp登录态
|
82
|
-
flow_id:必填
|
83
|
-
pulsar:必填,模拟的触发数据
|
84
|
-
limit:非必填,默认为1 - mock_pulsar函数用于控制模拟触发的次数
|
85
|
-
num:非必填,默认为1 - compare_lists函数用于断言方法做差值计算
|
86
|
-
"""
|
87
|
-
# 触发前提取flow数据,后续做对比
|
88
|
-
old_flow_counts, old_versions = get_current_flow(host_domain=host_domain, cookies=cookies,
|
89
|
-
flow_id=kwargs["flow_id"])
|
90
|
-
# 启动flow
|
91
|
-
start_flow(host_domain=host_domain, cookies=cookies, flow_id=kwargs["flow_id"], version=old_versions)
|
92
|
-
# 触发flow
|
93
|
-
mock_pulsar(mock_domain=mock_domain, pulsar=kwargs["pulsar"], limit=kwargs.get("limit", 1))
|
94
|
-
# 触发后提取flow数据,做断言
|
95
|
-
time.sleep(30)
|
96
|
-
new_flow_counts, new_versions = get_current_flow(host_domain=host_domain, cookies=cookies,
|
97
|
-
flow_id=kwargs["flow_id"])
|
98
|
-
# 断言
|
99
|
-
result = ListDictUtils.compare_lists(temp1=old_flow_counts, temp2=new_flow_counts, num=kwargs.get("num", 1))
|
100
|
-
return [True, "断言成功"] if len(result) == 0 else [False, result]
|
@@ -1,90 +0,0 @@
|
|
1
|
-
# -*- codeing = utf-8 -*-
|
2
|
-
# @Time :2025/2/20 00:27
|
3
|
-
# @Author :luzebin
|
4
|
-
import pandas as pd
|
5
|
-
|
6
|
-
from smartpush.export.basic import ExcelExportChecker
|
7
|
-
from smartpush.export.basic.ReadExcel import read_excel_from_oss
|
8
|
-
from smartpush.export.basic.ReadExcel import read_excel_and_write_to_dict
|
9
|
-
from smartpush.export.basic.GetOssUrl import get_oss_address_with_retry
|
10
|
-
from smartpush.utils.DataTypeUtils import DataTypeUtils
|
11
|
-
from smartpush.flow import MockFlow
|
12
|
-
|
13
|
-
if __name__ == '__main__':
|
14
|
-
oss1 = "https://cdn.smartpushedm.com/material_ec2/2025-02-26/31c1a577af244c65ab9f9a984c64f3d9/ab%E5%BC%B9%E7%AA%97%E6%B5%8B%E8%AF%952.10%E5%88%9B%E5%BB%BA-%E6%9C%89%E5%85%A8%E9%83%A8%E6%95%B0%E6%8D%AE%E9%94%80%E5%94%AE%E9%A2%9D%E6%98%8E%E7%BB%86%E6%95%B0%E6%8D%AE.xlsx"
|
15
|
-
oss2 = "https://cdn.smartpushedm.com/material_ec2/2025-02-26/31c1a577af244c65ab9f9a984c64f3d9/ab%E5%BC%B9%E7%AA%97%E6%B5%8B%E8%AF%952.10%E5%88%9B%E5%BB%BA-%E6%9C%89%E5%85%A8%E9%83%A8%E6%95%B0%E6%8D%AE%E9%94%80%E5%94%AE%E9%A2%9D%E6%98%8E%E7%BB%86%E6%95%B0%E6%8D%AE.xlsx"
|
16
|
-
# # print(check_excel_all(oss1, oss1))
|
17
|
-
oss3 = "https://cdn.smartpushedm.com/material_ec2/2025-03-07/dca03e35cb074ac2a46935c85de9f510/导出全部客户.csv"
|
18
|
-
oss4 = "https://cdn.smartpushedm.com/material_ec2/2025-03-07/c5fa0cc24d05416e93579266910fbd3e/%E5%AF%BC%E5%87%BA%E5%85%A8%E9%83%A8%E5%AE%A2%E6%88%B7.csv"
|
19
|
-
expected_oss = "https://cdn.smartpushedm.com/material_ec2/2025-02-26/757df7e77ce544e193257c0da35a4983/%E3%80%90%E8%87%AA%E5%8A%A8%E5%8C%96%E5%AF%BC%E5%87%BA%E3%80%91%E8%90%A5%E9%94%80%E6%B4%BB%E5%8A%A8%E6%95%B0%E6%8D%AE%E6%A6%82%E8%A7%88.xlsx"
|
20
|
-
# actual_oss = "https://cdn.smartpushedm.com/material_ec2/2025-02-26/757df7e77ce544e193257c0da35a4983/%E3%80%90%E8%87%AA%E5%8A%A8%E5%8C%96%E5%AF%BC%E5%87%BA%E3%80%91%E8%90%A5%E9%94%80%E6%B4%BB%E5%8A%A8%E6%95%B0%E6%8D%AE%E6%A6%82%E8%A7%88.xlsx"
|
21
|
-
url = "https://cdn.smartpushedm.com/material_ec2_prod/2025-03-06/fe6f042f50884466979155c5ef825736/copy%20of%202025-01-16%20%E5%88%9B%E5%BB%BA%E7%9A%84%20A%2FB%20%E6%B5%8B%E8%AF%95%20copy%20of%202025-01-16%20app-%E6%99%AE%E9%80%9A%E6%A8%A1%E6%9D%BF%201%E6%95%B0%E6%8D%AE%E6%80%BB%E8%A7%88.xlsx"
|
22
|
-
|
23
|
-
# e_person_oss1 = "https://cdn.smartpushedm.com/material_ec2/2025-02-27/b48f34b3e88045d189631ec1f0f23d51/%E5%AF%BC%E5%87%BA%E5%85%A8%E9%83%A8%E5%AE%A2%E6%88%B7.csv"
|
24
|
-
# a_person_oss2 = "https://cdn.smartpushedm.com/material_ec2/2025-02-27/c50519d803c04e3b9b52d9f625fed413/%E5%AF%BC%E5%87%BA%E5%85%A8%E9%83%A8%E5%AE%A2%E6%88%B7.csv"
|
25
|
-
|
26
|
-
# # #actual_oss= get_oss_address_with_retry("23161","https://cdn.smartpushedm.com/material_ec2_prod/2025-02-20/dae941ec20964ca5b106407858676f89/%E7%BE%A4%E7%BB%84%E6%95%B0%E6%8D%AE%E6%A6%82%E8%A7%88.xlsx","",'{"page":1,"pageSize":10,"type":null,"status":null,"startTime":null,"endTime":null}')
|
27
|
-
# # res=read_excel_and_write_to_dict(read_excel_from_oss(actual_oss))
|
28
|
-
# # print(res)
|
29
|
-
# # print(read_excel_and_write_to_dict(read_excel_from_oss(oss1), type=".xlsx"))
|
30
|
-
# print(check_excel(check_type="all", actual_oss=actual_oss, expected_oss=expected_oss))
|
31
|
-
# print(check_excel_all(actual_oss=oss1, expected_oss=oss2,skiprows =1))
|
32
|
-
# print(check_excel_all(actual_oss=oss1, expected_oss=oss2,ignore_sort=True))
|
33
|
-
# print(check_excel_all(actual_oss=a_person_oss2, expected_oss=e_person_oss1, check_type="including"))
|
34
|
-
print(ExcelExportChecker.check_excel_all(actual_oss=oss3, expected_oss=oss4, check_type="including"))
|
35
|
-
# read_excel_csv_data(type=)
|
36
|
-
# print(DataTypeUtils().check_email_format())
|
37
|
-
# errors = ExcelExportChecker.check_field_format(actual_oss=oss1, fileds={0: {5: "time"}}, skiprows=1)
|
38
|
-
# ExcelExportChecker.check_excel_name(actual_oss=oss1, expected_oss=url)
|
39
|
-
|
40
|
-
_url = "http://sp-go-flow-test.inshopline.com"
|
41
|
-
params = {
|
42
|
-
"abandonedOrderId": "c2c4a695a36373f56899b370d0f1b6f2",
|
43
|
-
"areaCode": "",
|
44
|
-
"context": {
|
45
|
-
"order": {
|
46
|
-
"buyerSubscribeEmail": True,
|
47
|
-
"checkoutId": "c2c4a695a36373f56899b370d0f1b6f2",
|
48
|
-
"discountCodes": [],
|
49
|
-
"orderAmountSet": {
|
50
|
-
"amount": 3,
|
51
|
-
"currency": "JPY"
|
52
|
-
},
|
53
|
-
"orderDetails": [
|
54
|
-
{
|
55
|
-
"productId": "16060724900402692190790343",
|
56
|
-
"title": "测试2.0-商品同步AutoSync-2023-08-17 20:52:00",
|
57
|
-
"titleTranslations": []
|
58
|
-
}
|
59
|
-
],
|
60
|
-
"receiverCountryCode": "HK"
|
61
|
-
},
|
62
|
-
"user": {
|
63
|
-
"addresses": [],
|
64
|
-
"areaCode": "",
|
65
|
-
"email": "testsmart200+10@gmail.com",
|
66
|
-
"firstName": "testsmart200+10",
|
67
|
-
"gender": "others",
|
68
|
-
"id": "1911625831177650177",
|
69
|
-
"lastName": "",
|
70
|
-
"phone": "",
|
71
|
-
"tags": [],
|
72
|
-
"uid": "4603296300",
|
73
|
-
"userName": "testsmart200+10"
|
74
|
-
}
|
75
|
-
},
|
76
|
-
"controlObjectId": "c2c4a695a36373f56899b370d0f1b6f2",
|
77
|
-
"controlObjectType": 4,
|
78
|
-
"email": "testsmart200+10@gmail.com",
|
79
|
-
"handle": "smartpush4",
|
80
|
-
"language": "en",
|
81
|
-
"messageId": "1911625832100397058",
|
82
|
-
"phone": "",
|
83
|
-
"platform": 4,
|
84
|
-
"storeId": "1644395920444",
|
85
|
-
"timezone": "Asia/Macao",
|
86
|
-
"triggerId": "c1001",
|
87
|
-
"uid": "4603296300",
|
88
|
-
"userId": "1911625831177650177"
|
89
|
-
}
|
90
|
-
mock_pulsar = MockFlow.mock_pulsar(_url, params)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|