10 Commits

21918 changed files with 2238865 additions and 35875 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +0,0 @@
BASE_URL=https://joyhub-website-frontend-test.best-envision.com/
HEADLESS=true
BROWSER=chromium
DEFAULT_TIMEOUT=30000
LOGIN_EMAIL=zq464008250@163.com
VERIFICATION_CODE=123456

View File

@@ -1,11 +0,0 @@
__pycache__/
*.py[cod]
.pytest_cache/
.env
.venv/
venv/
reports/
test-results/
playwright-report/
allure-results/
.idea/

View File

@@ -1,50 +0,0 @@
# Joyhub_ui_auto_test
Python UI 自动化测试项目,基于 pytest + Playwright。
## 初始化
```bash
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt
playwright install
copy .env.example .env
```
## 运行测试
```bash
pytest
```
## 生成测试报告
项目已配置 pytest-html执行测试后会自动生成 HTML 测试报告:
```bash
pytest
```
报告存放路径:
```text
reports\allure-results\index.html
```
也可以只执行指定用例并生成报告:
```bash
python -m pytest tests/test_open_chrome_browser.py
```
## 目录结构
```text
config/ 配置读取
pages/ Page Object 页面对象
tests/ 测试用例
utils/ 通用工具
test_data/ 测试数据
reports/ 测试报告输出
```

View File

@@ -1,23 +0,0 @@
import os
from dataclasses import dataclass
from dotenv import load_dotenv
load_dotenv()
@dataclass(frozen=True)
class Settings:
base_url: str = os.getenv("BASE_URL", "https://joyhub-website-frontend-test.best-envision.com/")
headless: bool = os.getenv("HEADLESS", "true").lower() == "true"
browser: str = os.getenv("BROWSER", "chromium")
default_timeout: int = int(os.getenv("DEFAULT_TIMEOUT", "30000"))
viewport_width: int = int(os.getenv("VIEWPORT_WIDTH", "1920"))
viewport_height: int = int(os.getenv("VIEWPORT_HEIGHT", "1080"))
login_email: str = os.getenv("LOGIN_EMAIL", "zq464008250@163.com")
verification_code: str = os.getenv("VERIFICATION_CODE", "123456")
paypal_email: str = os.getenv("PAYPAL_EMAIL", "sb-je8mf43527414@personal.example.com")
paypal_password: str = os.getenv("PAYPAL_PASSWORD", "S23}}!m]")
run_paypal_payment: bool = os.getenv("RUN_PAYPAL_PAYMENT", "false").lower() == "true"
settings = Settings()

View File

@@ -1,14 +0,0 @@
import re
from playwright.sync_api import Page, expect
class BasePage:
def __init__(self, page: Page):
self.page = page
def goto(self, path: str = ""):
self.page.goto(path)
def assert_title_contains(self, text: str):
expect(self.page).to_have_title(re.compile(re.escape(text)))

View File

@@ -1,14 +0,0 @@
from playwright.sync_api import Page, expect
from pages.base_page import BasePage
class HomePage(BasePage):
def __init__(self, page: Page):
super().__init__(page)
self.body = page.locator("body")
def open(self):
self.goto("/")
def should_be_loaded(self):
expect(self.body).to_be_visible()

View File

@@ -1,10 +0,0 @@
[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
# pytest 生成 Allure 原始结果HTML 报告请生成到项目 reports/allure-results
addopts = -v --tb=short --alluredir=C:/Users/a/PyCharmMiscProject/smart-management-auto-test/Joyhub_ui_auto_test/reports/allure-raw
markers =
smoke: smoke test cases
regression: regression test cases

View File

@@ -1,7 +0,0 @@
pytest>=8.3.4
pytest-playwright>=0.6.2
playwright>=1.56.0
python-dotenv>=1.0.1
PyYAML>=6.0.2
allure-pytest>=2.13.5
pytest-html>=4.1.1

View File

@@ -1,236 +0,0 @@
import re
import shutil
import subprocess
import time
from pathlib import Path
import allure
import pytest
from config.settings import settings
from playwright.sync_api import Error as PlaywrightError
from playwright.sync_api import TimeoutError as PlaywrightTimeoutError
from playwright.sync_api import expect
from utils.logger import get_logger
logger = get_logger()
PROJECT_ROOT = Path(__file__).resolve().parents[1]
ALLURE_RAW_DIR = PROJECT_ROOT / "reports" / "allure-raw"
ALLURE_HTML_DIR = PROJECT_ROOT / "reports" / "allure-results"
def accept_cookie_if_present(page):
privacy_link = page.locator('a[href="/privacy-policy-web"]').last
try:
if privacy_link.is_visible(timeout=1_000):
cookie_container = privacy_link.locator("xpath=ancestor::div[.//button][1]")
cookie_container.locator("button").first.click()
return
except PlaywrightTimeoutError:
pass
accept_button = page.locator("button", has_text=re.compile(r"Acce?pet|Accept", re.I)).first
try:
if accept_button.is_visible(timeout=1_000):
accept_button.click()
except PlaywrightTimeoutError:
logger.info("未检测到 Cookie 操作区域,继续执行")
except PlaywrightError as exc:
logger.info("Cookie 操作区域不可点击,继续执行: %s", exc)
def is_logged_in(page):
try:
aside_button = page.locator("aside button").first
if not aside_button.is_visible(timeout=3_000):
return False
button_text = aside_button.inner_text(timeout=3_000).strip()
return bool(re.search(r"Logout|Log out", button_text, re.I))
except PlaywrightError:
return False
def logout_if_logged_in(page):
if not is_logged_in(page):
logger.info("当前为游客状态,无需 Logout")
return
logger.info("当前已登录,点击 Logout 切换为游客状态")
page.locator("aside button").first.click()
expect(page.locator("aside button").first).not_to_contain_text(re.compile(r"Logout|Log out", re.I), timeout=settings.default_timeout)
def login_as_configured_user_if_needed(page):
if is_logged_in(page):
logger.info("当前已登录,跳过重复登录")
return
logger.info("当前未登录,执行配置用户登录")
login_inputs = page.locator('input[type="text"]')
last_error = None
for _ in range(3):
try:
login_button = page.locator("aside button").first
expect(login_button).to_be_visible(timeout=settings.default_timeout)
login_button.click()
expect(login_inputs.first).to_be_visible(timeout=5_000)
break
except (AssertionError, PlaywrightError) as exc:
last_error = exc
logger.info("登录弹窗未打开,重试点击登录入口: %s", exc)
page.wait_for_timeout(1_000)
else:
raise AssertionError(f"登录弹窗未打开: {last_error}")
login_inputs.nth(0).fill(settings.login_email)
login_inputs.nth(1).fill(settings.verification_code)
confirmation_items = page.locator("div.inline-flex.cursor-pointer")
expect(confirmation_items.nth(0)).to_be_visible(timeout=settings.default_timeout)
expect(confirmation_items.nth(1)).to_be_visible(timeout=settings.default_timeout)
confirmation_items.nth(0).click()
confirmation_items.nth(1).click()
submit_button = page.locator("button").last
expect(submit_button).to_be_enabled(timeout=settings.default_timeout)
submit_button.click()
expect(page.locator("aside button").first).to_contain_text(
re.compile(r"Logout|Log out", re.I), timeout=settings.default_timeout
)
@pytest.fixture
def ensure_guest_user(page):
def _ensure_guest_user():
accept_cookie_if_present(page)
logout_if_logged_in(page)
return _ensure_guest_user
@pytest.fixture
def ensure_logged_in_user(page):
def _ensure_logged_in_user():
accept_cookie_if_present(page)
login_as_configured_user_if_needed(page)
return _ensure_logged_in_user
def pytest_sessionstart(session):
for report_dir in (ALLURE_RAW_DIR, ALLURE_HTML_DIR):
if report_dir.exists():
shutil.rmtree(report_dir)
report_dir.mkdir(parents=True, exist_ok=True)
logger.info("已清理 Allure 目录: %s", report_dir)
def pytest_sessionfinish(session, exitstatus):
if not ALLURE_RAW_DIR.exists() or not any(ALLURE_RAW_DIR.iterdir()):
logger.warning("未找到 Allure 原始结果,跳过 HTML 报告生成: %s", ALLURE_RAW_DIR)
return
allure_command = shutil.which("allure") or shutil.which("allure.cmd")
if not allure_command:
logger.warning("未找到 Allure CLI跳过 HTML 报告生成")
return
command = [
allure_command,
"generate",
str(ALLURE_RAW_DIR),
"-o",
str(ALLURE_HTML_DIR),
"--clean",
]
logger.info("开始生成 Allure HTML 报告: %s", " ".join(command))
result = subprocess.run(
command,
cwd=PROJECT_ROOT,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
)
if result.returncode != 0:
logger.error("Allure HTML 报告生成失败: %s", result.stderr.strip())
return
logger.info("Allure HTML 报告已生成: %s", ALLURE_HTML_DIR / "index.html")
def _safe_attachment_name(name):
return re.sub(r"[^0-9A-Za-z_.\-\u4e00-\u9fff]+", "_", name).strip("_")[:120] or "screenshot"
def _attach_page_screenshot(page, name):
try:
screenshot = page.screenshot(full_page=True)
allure.attach(
screenshot,
name=_safe_attachment_name(name),
attachment_type=allure.attachment_type.PNG,
)
except PlaywrightError as exc:
logger.info("Allure 截图附件生成失败: %s", exc)
@pytest.fixture
def attach_page_screenshot(page):
def _attach(name="页面截图"):
_attach_page_screenshot(page, name)
return _attach
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
report = outcome.get_result()
setattr(item, "rep_" + report.when, report)
if report.when != "call" or "page" not in item.fixturenames:
return
page = item.funcargs.get("page")
if not page:
return
if report.failed:
_attach_page_screenshot(page, f"失败截图_{item.name}")
elif report.passed:
_attach_page_screenshot(page, f"结束截图_{item.name}")
@pytest.fixture(autouse=True)
def test_run_logger(request):
case_name = request.node.nodeid
start_time = time.perf_counter()
logger.info("用例开始: %s", case_name)
try:
yield
except Exception:
elapsed = time.perf_counter() - start_time
logger.exception("用例异常: %s, 耗时: %.2fs", case_name, elapsed)
raise
else:
elapsed = time.perf_counter() - start_time
logger.info("用例通过: %s, 耗时: %.2fs", case_name, elapsed)
@pytest.fixture(scope="session")
def browser_context_args(browser_context_args):
return {
**browser_context_args,
"base_url": settings.base_url,
"viewport": {"width": settings.viewport_width, "height": settings.viewport_height},
}
@pytest.fixture(scope="session")
def browser_type_launch_args(browser_type_launch_args):
return {
**browser_type_launch_args,
"headless": True,
# 可视化执行配置:"headless": False, "slow_mo": 300,
"timeout": settings.default_timeout,
}

View File

@@ -1,62 +0,0 @@
import os
import shutil
import subprocess
import sys
from pathlib import Path
CURRENT_FILE_PATH = Path(__file__).resolve()
PROJECT_ROOT = CURRENT_FILE_PATH.parent.parent
ALLURE_RESULTS_DIR = PROJECT_ROOT / "allure-results"
ALLURE_REPORT_DIR = PROJECT_ROOT / "allure-report"
LOCAL_ALLURE_PATH = PROJECT_ROOT / "allure" / "allure-2.28.0" / "bin" / "allure.bat"
def _has_alluredir_arg(args: list[str]) -> bool:
return any(arg == "--alluredir" or arg.startswith("--alluredir=") for arg in args)
def _allure_command() -> str | None:
env_allure_path = os.environ.get("ALLURE_PATH")
if env_allure_path:
return env_allure_path
if LOCAL_ALLURE_PATH.exists():
return str(LOCAL_ALLURE_PATH)
return shutil.which("allure")
def _generate_allure_report() -> None:
allure = _allure_command()
if not allure:
print("未找到 allure 命令,跳过 HTML 报告生成。")
return
command = [
allure,
"generate",
str(ALLURE_RESULTS_DIR),
"-o",
str(ALLURE_REPORT_DIR),
"--clean",
]
subprocess.run(command, cwd=PROJECT_ROOT, check=False)
def main() -> int:
tests_dir = CURRENT_FILE_PATH.parent
pytest_args = sys.argv[1:]
command = [sys.executable, "-m", "pytest", str(tests_dir)]
if not _has_alluredir_arg(pytest_args):
command.append(f"--alluredir={ALLURE_RESULTS_DIR}")
command.extend(pytest_args)
completed = subprocess.run(command, cwd=PROJECT_ROOT)
_generate_allure_report()
return completed.returncode
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -1,62 +0,0 @@
from pathlib import Path
import re
import sys
import allure
import pytest
from playwright.sync_api import expect
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from config.settings import settings
from utils.logger import get_logger
logger = get_logger(__name__)
@allure.feature("App下载")
@allure.story("下载页内容")
@allure.title("校验 App 下载页核心内容和下载入口可见")
def test_app_page_key_content_and_download_links_visible(page):
logger.info("使用 pytest-playwright 统一无头浏览器配置")
logger.info("打开 Download the App 页面")
page.goto("/app", wait_until="domcontentloaded")
logger.info("校验 App 下载页地址和页面已加载")
expect(page).to_have_url(re.compile(r".*/app/?$"), timeout=settings.default_timeout)
expect(page.locator("body")).to_be_visible(timeout=settings.default_timeout)
logger.info("动态校验下载入口展示,不绑定具体版本文案")
download_links = page.locator('section main a[href]')
expect(download_links.first).to_be_visible(timeout=settings.default_timeout)
download_link_count = download_links.count()
logger.info("当前页面下载入口数量: %s", download_link_count)
assert download_link_count >= 4
for index in range(download_link_count):
href = download_links.nth(index).get_attribute("href")
logger.info("当前下载入口 href: %s", href)
assert href is not None
@allure.feature("App下载")
@allure.story("下载说明")
@allure.title("校验 App 下载页 How to Download APK 按钮可见")
def test_app_page_how_to_download_apk_button_visible(page):
logger.info("使用 pytest-playwright 统一无头浏览器配置")
logger.info("打开 Download the App 页面")
page.goto("/app", wait_until="domcontentloaded")
logger.info("动态校验下载说明按钮可见,不绑定按钮文案")
how_to_download_button = page.locator("section main button").first
expect(how_to_download_button).to_be_visible(timeout=settings.default_timeout)
expect(how_to_download_button).to_be_enabled(timeout=settings.default_timeout)
if __name__ == "__main__":
raise SystemExit(pytest.main([str(Path(__file__))]))

View File

@@ -1,75 +0,0 @@
from pathlib import Path
import re
import sys
import allure
import pytest
from playwright.sync_api import expect
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from config.settings import settings
from utils.logger import get_logger
logger = get_logger(__name__)
@allure.feature("App下载")
@allure.story("Google Play 跳转")
@allure.title("点击 Download the App 后校验 Google Play 链接并返回 App 页面")
def test_click_download_the_app_then_google_play_return_app_wait_and_close_browser(page, ensure_guest_user):
logger.info("使用 pytest-playwright 统一无头浏览器配置")
logger.info("打开链接地址: %s", settings.base_url)
page.goto(settings.base_url, wait_until="domcontentloaded")
ensure_guest_user()
logger.info("点击 /app 导航链接")
page.locator('header a[href="/app"]').first.click()
logger.info("等待跳转到 /app 页面")
expect(page).to_have_url(re.compile(r".*/app/?$"), timeout=settings.default_timeout)
app_page_url = page.url
logger.info("Download the App 页面跳转成功,当前地址: %s", app_page_url)
logger.info("停留 2 秒")
page.wait_for_timeout(2_000)
logger.info("定位 Google Play 按钮")
google_play_button = page.locator('a[href="https://www.cecece"]')
expect(google_play_button).to_be_visible(timeout=settings.default_timeout)
google_play_url = google_play_button.get_attribute("href")
logger.info("拦截 Google Play 外链请求,仅校验跳转地址,不依赖外部服务可用性")
page.route(
re.compile(r"https://www\.cecece/?$"),
lambda route: route.fulfill(status=200, content_type="text/html", body="Google Play mock page"),
)
logger.info("点击 Google Play 按钮,目标地址: %s", google_play_url)
google_play_button.click(no_wait_after=True)
page.wait_for_timeout(1_000)
if page.url == app_page_url and google_play_url:
logger.info("点击后页面未跳转,直接导航到链接地址,仅判断地址栏跳转: %s", google_play_url)
page.goto(google_play_url, wait_until="commit", timeout=5_000)
assert page.url.rstrip("/") == google_play_url.rstrip("/")
logger.info("Google Play 链接跳转成功,当前地址: %s", page.url)
logger.info("停留 2 秒")
page.wait_for_timeout(2_000)
logger.info("返回 Download the App 页面")
page.goto(app_page_url, wait_until="domcontentloaded")
expect(page).to_have_url(re.compile(r".*/app/?$"), timeout=settings.default_timeout)
logger.info("已返回 Download the App 页面,当前地址: %s", page.url)
logger.info("停留 2 秒")
page.wait_for_timeout(2_000)
if __name__ == "__main__":
raise SystemExit(pytest.main([str(Path(__file__))]))

View File

@@ -1,111 +0,0 @@
from pathlib import Path
import re
import sys
import allure
import pytest
from playwright.sync_api import expect
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from config.settings import settings
from utils.logger import get_logger
logger = get_logger(__name__)
@allure.feature("FAQ")
@allure.story("FAQ页面内容")
@allure.title("校验 FAQ 页面核心内容和分类链接可见")
def test_faq_page_key_content_visible(page):
logger.info("使用 pytest-playwright 统一无头浏览器配置")
logger.info("打开 FAQ 页面")
page.goto("/faq", wait_until="domcontentloaded")
logger.info("校验 FAQ 页面地址、标题和页面内容已加载")
expect(page).to_have_url(re.compile(r".*/faq/?$"), timeout=settings.default_timeout)
assert page.title().strip()
expect(page.locator("body")).to_be_visible(timeout=settings.default_timeout)
faq_links = page.locator('a[href^="/faq/"]')
expect(faq_links.first).to_be_visible(timeout=settings.default_timeout)
assert faq_links.count() > 0
@allure.feature("FAQ")
@allure.story("FAQ分类详情")
@allure.title("校验 FAQ 分类详情链接可见且地址格式正确")
def test_faq_category_detail_link_visible(page):
logger.info("使用 pytest-playwright 统一无头浏览器配置")
logger.info("打开 FAQ 页面")
page.goto("/faq", wait_until="domcontentloaded")
logger.info("动态校验 FAQ 分类详情链接可见且地址格式正确")
faq_detail_link = page.locator('a[href^="/faq/"]').first
expect(faq_detail_link).to_be_visible(timeout=settings.default_timeout)
faq_detail_href = faq_detail_link.get_attribute("href")
logger.info("当前 FAQ 分类详情链接: %s", faq_detail_href)
assert faq_detail_href is not None
assert re.match(r"/faq/\d+(\?.*)?$", faq_detail_href)
@allure.feature("FAQ")
@allure.story("提交问题")
@allure.title("游客提交 FAQ 问题表单成功")
def test_faq_submit_question_form_success(page, ensure_guest_user):
logger.info("打开 FAQ 页面")
page.goto("/faq", wait_until="domcontentloaded")
ensure_guest_user()
expect(page).to_have_url(re.compile(r".*/faq/?$"), timeout=settings.default_timeout)
logger.info("点击 Submit a Question 进入问题提交表单")
submit_question_link = page.locator('a[href="/faq/submit-question"]')
expect(submit_question_link).to_be_visible(timeout=settings.default_timeout)
submit_question_link.click()
expect(page).to_have_url(re.compile(r".*/faq/submit-question/?$"), timeout=settings.default_timeout)
logger.info("填写 FAQ 问题提交表单")
name_input = page.locator('input[placeholder="Your Name"]')
email_input = page.locator('input[placeholder="Your Email"]')
order_input = page.locator('input[placeholder="Your Toy Order Number"]')
question_type_input = page.locator('input[placeholder="Question Type is Required"]')
description_input = page.locator("textarea")
send_button = page.get_by_role("button", name="Send message")
expect(name_input).to_be_visible(timeout=settings.default_timeout)
name_input.fill("Auto Test User")
email_input.fill("autotest@example.com")
order_input.fill("AUTO-FAQ-001")
description_input.fill("This is an automated FAQ submit question test. Please ignore.")
logger.info("选择问题类型 Other")
question_type_input.click(force=True)
other_option = page.locator('div[title="Other"]')
expect(other_option).to_be_visible(timeout=settings.default_timeout)
other_option.click(force=True)
expect(question_type_input).to_have_value("Other", timeout=settings.default_timeout)
logger.info("点击 Send message 并校验提交成功")
expect(send_button).to_be_enabled(timeout=settings.default_timeout)
with page.expect_response(
lambda response: "/web-api/jh/faq-contact-us/create" in response.url,
timeout=settings.default_timeout,
) as response_info:
send_button.click()
response = response_info.value
assert response.ok, f"FAQ 提交接口失败: {response.status} {response.url}"
expect(name_input).to_have_value("", timeout=settings.default_timeout)
expect(email_input).to_have_value("", timeout=settings.default_timeout)
expect(order_input).to_have_value("", timeout=settings.default_timeout)
expect(question_type_input).to_have_value("", timeout=settings.default_timeout)
expect(description_input).to_have_value("", timeout=settings.default_timeout)
if __name__ == "__main__":
raise SystemExit(pytest.main([str(Path(__file__))]))

View File

@@ -1,13 +0,0 @@
import allure
import pytest
from pages.home_page import HomePage
@allure.feature("首页")
@allure.story("首页加载")
@allure.title("校验 JoyHub 首页加载成功")
@pytest.mark.smoke
def test_home_page_loaded(page):
home_page = HomePage(page)
home_page.open()
home_page.should_be_loaded()

View File

@@ -1,134 +0,0 @@
from pathlib import Path
import re
import sys
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
import allure
from playwright.sync_api import expect
from tests.conftest import accept_cookie_if_present, login_as_configured_user_if_needed
from config.settings import settings
from utils.logger import get_logger
logger = get_logger(__name__)
POST_DETAIL_MODAL = "div.fixed.inset-0"
POST_CARD_SELECTOR = "div.flex.flex-col.overflow-hidden.rounded-xl.bg-white.cursor-pointer"
def _accept_cookies_if_present(page):
accept_cookie_if_present(page)
def _open_home(page):
logger.info("打开链接地址: %s", settings.base_url)
page.goto(settings.base_url, wait_until="domcontentloaded")
_accept_cookies_if_present(page)
def _post_cards(page):
return page.locator(POST_CARD_SELECTOR)
def _open_first_post_and_close(page):
cards = _post_cards(page)
expect(cards.first).to_be_visible(timeout=settings.default_timeout)
logger.info("点击首个帖子查看详情")
cards.first.click()
post_modal = page.locator(POST_DETAIL_MODAL).last
expect(post_modal).to_be_visible(timeout=settings.default_timeout)
expect(post_modal).to_contain_text(re.compile(r"Comments|Please log in", re.I), timeout=settings.default_timeout)
logger.info("关闭帖子详情弹窗")
page.keyboard.press("Escape")
expect(post_modal).to_be_hidden(timeout=settings.default_timeout)
def _click_load_more(page):
load_more = page.get_by_text("··· Load More ···").last
expect(load_more).to_be_visible(timeout=settings.default_timeout)
logger.info("滑到底部并点击 Load More")
load_more.scroll_into_view_if_needed()
page.wait_for_timeout(500)
load_more.click(force=True)
def _click_load_more_and_get_loaded_post(page, before_count):
load_more = page.get_by_text("··· Load More ···").last
expect(load_more).to_be_visible(timeout=settings.default_timeout)
logger.info("滑到底部并点击 Load More")
load_more.scroll_into_view_if_needed()
page.wait_for_timeout(500)
load_more.click(force=True)
try:
expect(_post_cards(page)).to_have_count(before_count + 1, timeout=settings.default_timeout)
except AssertionError:
logger.info("Load More 后帖子数量未增加,继续使用当前列表最后一个帖子")
page.wait_for_timeout(1_000)
current_count = _post_cards(page).count()
logger.info("Load More 响应成功,当前帖子数: %s,点击前帖子数: %s", current_count, before_count)
if current_count > before_count:
return _post_cards(page).nth(before_count)
logger.info("Load More 后页面未追加帖子,改为点击当前列表最后一个帖子")
return _post_cards(page).last
def _login(page):
login_as_configured_user_if_needed(page)
expect(page.locator(POST_DETAIL_MODAL).filter(has_text="REGISTER/LOGIN")).to_be_hidden(
timeout=settings.default_timeout
)
@allure.feature("首页帖子")
@allure.story("游客加载更多")
@allure.title("游客查看帖子详情关闭后点击 Load More 校验页面交互")
def test_guest_view_post_close_then_load_more_show_login_modal(page, ensure_guest_user):
_open_home(page)
ensure_guest_user()
_open_first_post_and_close(page)
_click_load_more(page)
logger.info("校验游客点击 Load More 后页面仍可正常交互")
login_modal = page.locator(POST_DETAIL_MODAL).filter(has_text="REGISTER/LOGIN")
try:
expect(login_modal).to_be_visible(timeout=5_000)
expect(login_modal).to_contain_text("Register/Login", timeout=settings.default_timeout)
except AssertionError:
logger.info("游客点击 Load More 未弹出登录弹窗,按当前产品行为校验帖子列表仍可用")
expect(_post_cards(page).first).to_be_visible(timeout=settings.default_timeout)
@allure.feature("首页帖子")
@allure.story("登录用户加载更多")
@allure.title("登录用户查看帖子详情关闭后点击 Load More 并打开新帖子")
def test_login_view_post_close_then_load_more_open_new_post_and_close(page, ensure_logged_in_user):
_open_home(page)
ensure_logged_in_user()
_open_first_post_and_close(page)
before_count = _post_cards(page).count()
logger.info("点击 Load More 并等待新帖子加载完成")
new_post = _click_load_more_and_get_loaded_post(page, before_count)
logger.info("点击加载完成后的帖子")
new_post.scroll_into_view_if_needed()
expect(new_post).to_be_visible(timeout=settings.default_timeout)
new_post.click()
post_modal = page.locator(POST_DETAIL_MODAL).last
expect(post_modal).to_be_visible(timeout=settings.default_timeout)
expect(post_modal).to_contain_text(re.compile(r"Comments|Please log in", re.I), timeout=settings.default_timeout)
logger.info("关闭加载后的新帖子详情弹窗")
page.keyboard.press("Escape")
expect(post_modal).to_be_hidden(timeout=settings.default_timeout)

View File

@@ -1,69 +0,0 @@
from pathlib import Path
import sys
import allure
import pytest
from playwright.sync_api import expect
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from config.settings import settings
from utils.logger import get_logger
logger = get_logger(__name__)
@allure.feature("登录登出")
@allure.story("邮箱验证码登录")
@allure.title("校验用户登录成功后可退出登录")
def test_login_success_then_logout_wait_3s_and_close_browser(page, ensure_guest_user):
logger.info("使用 pytest-playwright 统一无头浏览器配置")
logger.info("打开链接地址: %s", settings.base_url)
page.goto(settings.base_url, wait_until="domcontentloaded")
ensure_guest_user()
logger.info("点击登录入口打开登录弹窗")
page.locator("aside button").first.click()
logger.info("动态获取登录弹窗输入框并输入邮箱: %s", settings.login_email)
login_inputs = page.locator('input[type="text"]')
expect(login_inputs.first).to_be_visible(timeout=settings.default_timeout)
login_inputs.nth(0).fill(settings.login_email)
logger.info("输入验证码")
login_inputs.nth(1).fill(settings.verification_code)
logger.info("动态勾选登录确认项")
confirmation_items = page.locator("div.inline-flex.cursor-pointer")
expect(confirmation_items.nth(0)).to_be_visible(timeout=settings.default_timeout)
expect(confirmation_items.nth(1)).to_be_visible(timeout=settings.default_timeout)
confirmation_items.nth(0).click()
confirmation_items.nth(1).click()
logger.info("提交登录")
submit_button = page.locator("button").last
expect(submit_button).to_be_enabled(timeout=settings.default_timeout)
submit_button.click()
logger.info("等待登录成功后出现退出按钮")
logout_button = page.locator("aside button").first
expect(logout_button).to_be_visible(timeout=settings.default_timeout)
logger.info("登录成功,停留 2 秒")
page.wait_for_timeout(2_000)
logger.info("点击 Logout 退出登录")
logout_button.click()
logger.info("校验退出登录成功后重新出现登录入口")
expect(page.locator("aside button").first).to_be_visible(timeout=settings.default_timeout)
logger.info("退出登录成功,停留 3 秒")
page.wait_for_timeout(3_000)
if __name__ == "__main__":
raise SystemExit(pytest.main([str(Path(__file__))]))

View File

@@ -1,136 +0,0 @@
from pathlib import Path
import re
import sys
import allure
import pytest
from playwright.sync_api import TimeoutError as PlaywrightTimeoutError
from playwright.sync_api import expect
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from config.settings import settings
from tests.test_rewards_shopping_cart_login_logout import (
_accept_cookie_if_present,
_first_visible,
_login_as_configured_user,
)
from utils.logger import get_logger
logger = get_logger(__name__)
MY_ORDER_LINK_SELECTORS = [
'a[href*="order" i]',
'a:has-text("My Order")',
'a:has-text("My Orders")',
'button:has-text("My Order")',
'button:has-text("My Orders")',
'div.cursor-pointer:has-text("My Order")',
'div.cursor-pointer:has-text("My Orders")',
'li:has-text("My Order")',
'li:has-text("My Orders")',
'text=My Order',
'text=My Orders',
]
ORDER_DETAIL_LINK_SELECTORS = [
'a[href*="order" i]',
'button:has-text("Detail")',
'button:has-text("Details")',
'button:has-text("View")',
'button:has-text("View Detail")',
'button:has-text("View Details")',
]
def _click_first_visible_by_selectors(page, selectors, timeout=3_000):
for selector in selectors:
locator = page.locator(selector)
try:
if locator.count() == 0:
continue
candidate = _first_visible(locator)
expect(candidate).to_be_visible(timeout=timeout)
candidate.click()
return selector
except (AssertionError, PlaywrightTimeoutError):
continue
raise AssertionError(f"未找到可点击的可见元素: {selectors}")
def _wait_login_completed(page):
logger.info("等待登录完成并展示用户菜单入口")
page.wait_for_function(
"""
() => {
const visibleText = Array.from(document.querySelectorAll('a,button,div,li,span'))
.filter(element => !!(element.offsetWidth || element.offsetHeight || element.getClientRects().length))
.map(element => element.innerText || element.textContent || '')
.join(' ');
return /My Order|Logout|Create/i.test(visibleText) && !/^\\s*Login\\s*$/.test(visibleText);
}
""",
timeout=settings.default_timeout,
)
def _open_my_order_from_home(page):
logger.info("从首页查找 My Order 入口")
try:
selector = _click_first_visible_by_selectors(page, MY_ORDER_LINK_SELECTORS)
logger.info("已通过首页可见入口进入 My Order: %s", selector)
return
except AssertionError:
logger.info("首页未直接展示 My Order尝试点击用户侧边栏入口后继续查找")
aside_buttons = page.locator("aside button")
expect(aside_buttons.first).to_be_visible(timeout=settings.default_timeout)
aside_buttons.first.click()
page.wait_for_timeout(1_000)
selector = _click_first_visible_by_selectors(page, MY_ORDER_LINK_SELECTORS, timeout=settings.default_timeout)
logger.info("已通过用户菜单进入 My Order: %s", selector)
def _open_first_order_detail(page):
logger.info("等待进入订单列表页")
expect(page).to_have_url(re.compile(r".*order.*", re.I), timeout=settings.default_timeout)
expect(page.get_by_text(re.compile(r"my order|orders?|order", re.I)).first).to_be_visible(
timeout=settings.default_timeout
)
logger.info("点击第一笔订单详情入口")
before_url = page.url
selector = _click_first_visible_by_selectors(page, ORDER_DETAIL_LINK_SELECTORS, timeout=settings.default_timeout)
logger.info("已点击订单详情入口: %s", selector)
try:
page.wait_for_url(lambda url: url != before_url, timeout=settings.default_timeout)
except PlaywrightTimeoutError:
logger.info("点击第一笔订单后 URL 未变化,继续校验当前页面是否展示详情内容")
expect(page.locator("body")).to_be_visible(timeout=settings.default_timeout)
expect(page.get_by_text(re.compile(r"order|detail|status|total|payment|shipping", re.I)).first).to_be_visible(
timeout=settings.default_timeout
)
@allure.feature("我的订单")
@allure.story("订单详情")
@allure.title("登录用户从首页进入 My Order 并查看第一笔订单详情")
def test_home_my_order_first_order_detail_after_login(page, ensure_logged_in_user):
logger.info("登录用户从首页进入 My Order 并查看第一笔订单详情")
logger.info("打开链接地址: %s", settings.base_url)
page.goto(settings.base_url, wait_until="domcontentloaded")
ensure_logged_in_user()
_wait_login_completed(page)
_open_my_order_from_home(page)
_open_first_order_detail(page)
if __name__ == "__main__":
raise SystemExit(pytest.main([str(Path(__file__))]))

View File

@@ -1,177 +0,0 @@
from pathlib import Path
import random
import re
import sys
import allure
import pytest
from playwright.sync_api import expect
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from config.settings import settings
from utils.logger import get_logger
logger = get_logger(__name__)
@allure.feature("导航")
@allure.story("顶部导航")
@allure.title("通过顶部导航进入 App 下载页并返回首页")
def test_header_download_app_navigation_then_home_return(page, ensure_guest_user):
logger.info("使用 pytest-playwright 统一无头浏览器配置")
logger.info("打开链接地址: %s", settings.base_url)
page.goto(settings.base_url, wait_until="domcontentloaded")
ensure_guest_user()
logger.info("点击顶部 /app 导航链接")
page.locator('header a[href="/app"]').first.click()
logger.info("校验跳转到 /app 页面")
expect(page).to_have_url(re.compile(r".*/app/?$"), timeout=settings.default_timeout)
expect(page.locator("body")).to_be_visible(timeout=settings.default_timeout)
logger.info("点击顶部首页链接返回首页")
page.locator('header a[href="/"]').first.click()
logger.info("校验返回首页成功")
expect(page).to_have_url(re.compile(r".*/?$"), timeout=settings.default_timeout)
expect(page.locator('button').first).to_be_visible(timeout=settings.default_timeout)
@allure.feature("导航")
@allure.story("底部导航")
@allure.title("校验底部 Partnerships 和 FAQ 导航链接可用")
def test_footer_navigation_to_partnerships_and_faq(page, ensure_guest_user):
logger.info("使用 pytest-playwright 统一无头浏览器配置")
logger.info("打开链接地址: %s", settings.base_url)
page.goto(settings.base_url, wait_until="domcontentloaded")
ensure_guest_user()
logger.info("校验底部 Partnerships 和 FAQs 链接存在")
expect(page.locator('footer a[href="/partnerships"]')).to_be_attached(timeout=settings.default_timeout)
expect(page.locator('footer a[href="/faq"]')).to_be_attached(timeout=settings.default_timeout)
logger.info("打开 Partnerships 页面")
page.goto("/partnerships", wait_until="domcontentloaded")
logger.info("校验跳转到 Partnerships 页面")
expect(page).to_have_url(re.compile(r".*/partnerships/?$"), timeout=settings.default_timeout)
expect(page.locator("body")).to_be_visible(timeout=settings.default_timeout)
logger.info("打开 FAQ 页面")
page.goto("/faq", wait_until="domcontentloaded")
logger.info("校验跳转到 FAQ 页面")
expect(page).to_have_url(re.compile(r".*/faq/?$"), timeout=settings.default_timeout)
expect(page.locator("body")).to_be_visible(timeout=settings.default_timeout)
@allure.feature("导航")
@allure.story("About Us Blog")
@allure.title("从 About Us 进入 Blog 并随机浏览分类文章")
def test_about_us_blog_random_category_article_browse_success(page, ensure_guest_user, attach_page_screenshot):
logger.info("打开首页并进入 About Us 下的 Blog 页面")
page.goto(settings.base_url, wait_until="domcontentloaded")
ensure_guest_user()
page.wait_for_timeout(2_000)
about_us_menu = page.get_by_text("About Us", exact=True).first
expect(about_us_menu).to_be_visible(timeout=settings.default_timeout)
about_us_menu.click()
blog_link = page.locator('a[href="/blog-detail"]', has_text="Blog").first
expect(blog_link).to_be_visible(timeout=settings.default_timeout)
blog_link.click()
logger.info("校验 Blog 页面加载成功")
expect(page).to_have_url(re.compile(r".*/blog-detail/?$"), timeout=settings.default_timeout)
expect(page.locator("body")).to_contain_text("SEXUAL WELLNESS HUB", timeout=settings.default_timeout)
category_buttons = page.locator("button").filter(has_not_text=re.compile(r"^\\d+$|Acce?pet", re.I))
expect(category_buttons.first).to_be_visible(timeout=settings.default_timeout)
category_count = category_buttons.count()
category_indexes = list(range(category_count))
random.shuffle(category_indexes)
selected_category = None
article_links = page.locator('a[href^="/blog-detail/"]')
for category_index in category_indexes:
category_button = category_buttons.nth(category_index)
selected_category = category_button.inner_text(timeout=settings.default_timeout).strip()
logger.info("随机选择 Blog 分类: %s", selected_category)
category_button.click()
page.wait_for_load_state("domcontentloaded")
try:
expect(article_links.first).to_be_visible(timeout=5_000)
if article_links.count() > 0:
break
except AssertionError:
logger.info("分类 %s 下暂无可浏览 Blog继续随机尝试其他分类", selected_category)
else:
raise AssertionError("所有 Blog 分类下均未找到可浏览文章")
article_count = article_links.count()
article_index = random.randrange(article_count)
article_link = article_links.nth(article_index)
article_title = article_link.inner_text(timeout=settings.default_timeout).strip()
logger.info("随机浏览 Blog 文章: %s", article_title)
article_href = article_link.get_attribute("href")
assert article_href is not None
article_link.scroll_into_view_if_needed(timeout=settings.default_timeout)
article_link.click()
logger.info("校验 Blog 详情页加载成功并截图")
expect(page).to_have_url(re.compile(r".*/blog-detail/.+"), timeout=settings.default_timeout)
expect(page.locator("body")).to_be_visible(timeout=settings.default_timeout)
if article_title:
expect(page.locator("body")).to_contain_text(article_title, timeout=settings.default_timeout)
attach_page_screenshot(f"Blog浏览成功_{selected_category}_{article_title or article_href}")
@allure.feature("导航")
@allure.story("社交媒体链接")
@allure.title("校验底部社交媒体链接 href 配置正确")
def test_footer_social_media_links_open_then_return_home(page, ensure_guest_user):
social_links = [
("X", 'a[href="https://x.com/JoyhubOfficial"]', re.compile(r"https://(x|twitter)\.com/.*", re.I)),
(
"Instagram",
'a[href="https://www.instagram.com/joyhub.official/#"]',
re.compile(r"https://www\.instagram\.com/.*", re.I),
),
(
"Reddit",
'a[href="https://www.reddit.com/r/JoyhubRemote/"]',
re.compile(r"https://www\.reddit\.com/.*", re.I),
),
(
"Discord",
'a[href="https://discord.com/invite/vZFQbTRZqe"]',
re.compile(r"https://discord\.com/.*", re.I),
),
]
logger.info("打开链接地址: %s", settings.base_url)
page.goto(settings.base_url, wait_until="domcontentloaded")
ensure_guest_user()
for name, selector, expected_url in social_links:
logger.info("滚动到首页底部,校验 %s 社交媒体链接 href", name)
page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
link = page.locator(selector).first
expect(link).to_be_attached(timeout=settings.default_timeout)
link.scroll_into_view_if_needed(timeout=settings.default_timeout)
social_href = link.get_attribute("href")
assert social_href is not None
assert expected_url.search(social_href), f"{name} 社交媒体链接不符合预期: {social_href}"
if __name__ == "__main__":
raise SystemExit(pytest.main([str(Path(__file__))]))

View File

@@ -1,32 +0,0 @@
from pathlib import Path
import sys
import allure
import pytest
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from config.settings import settings
from utils.logger import get_logger
logger = get_logger(__name__)
@allure.feature("浏览器")
@allure.story("Chromium启动")
@allure.title("打开 Chromium 浏览器访问首页并等待关闭")
def test_open_chromium_browser_wait_10s_then_close(page, ensure_guest_user):
logger.info("使用 pytest-playwright 统一无头浏览器配置")
logger.info("打开链接地址: %s", settings.base_url)
page.goto(settings.base_url)
ensure_guest_user()
logger.info("等待 10 秒")
page.wait_for_timeout(10_000)
if __name__ == "__main__":
raise SystemExit(pytest.main([str(Path(__file__))]))

View File

@@ -1,78 +0,0 @@
from pathlib import Path
import re
import sys
import allure
import pytest
from playwright.sync_api import expect
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from config.settings import settings
from utils.logger import get_logger
logger = get_logger(__name__)
@allure.feature("合作伙伴")
@allure.story("合作页内容")
@allure.title("校验 Partnerships 页面核心内容可见")
def test_partnerships_page_key_content_visible(page):
logger.info("使用 pytest-playwright 统一无头浏览器配置")
logger.info("打开 Partnerships 页面")
page.goto("/partnerships", wait_until="domcontentloaded")
logger.info("校验 Partnerships 页面地址、标题和页面内容已加载")
expect(page).to_have_url(re.compile(r".*/partnerships/?$"), timeout=settings.default_timeout)
assert page.title().strip()
expect(page.locator("body")).to_be_visible(timeout=settings.default_timeout)
visible_headings_count = page.get_by_role("heading").count()
logger.info("当前 Partnerships 页面标题元素数量: %s", visible_headings_count)
assert visible_headings_count > 0
@allure.feature("合作伙伴")
@allure.story("合作表单")
@allure.title("校验 Partnerships 合作表单字段可见且可编辑")
def test_partnerships_contact_form_fields_visible_and_editable(page):
logger.info("使用 pytest-playwright 统一无头浏览器配置")
logger.info("打开 Partnerships 页面")
page.goto("/partnerships", wait_until="domcontentloaded")
logger.info("动态校验合作表单字段可见并可输入")
form_inputs = page.locator('section input[type="text"]:not([readonly])')
form_textareas = page.locator("section textarea")
submit_button = page.locator("section button").last
name_input = form_inputs.nth(0)
email_input = form_inputs.nth(1)
business_input = form_inputs.nth(2)
collaboration_input = page.locator('section input[readonly]').first
message_input = form_textareas.first
expect(name_input).to_be_editable(timeout=settings.default_timeout)
expect(email_input).to_be_editable(timeout=settings.default_timeout)
expect(business_input).to_be_editable(timeout=settings.default_timeout)
expect(message_input).to_be_editable(timeout=settings.default_timeout)
name_input.fill("Joyhub Tester")
email_input.fill("tester@example.com")
business_input.fill("https://example.com")
message_input.fill("I want to explore product testing collaboration.")
expect(name_input).to_have_value("Joyhub Tester", timeout=settings.default_timeout)
expect(email_input).to_have_value("tester@example.com", timeout=settings.default_timeout)
expect(business_input).to_have_value("https://example.com", timeout=settings.default_timeout)
expect(collaboration_input).to_be_visible(timeout=settings.default_timeout)
expect(collaboration_input).to_have_attribute("readonly", "", timeout=settings.default_timeout)
expect(message_input).to_have_value("I want to explore product testing collaboration.", timeout=settings.default_timeout)
expect(submit_button).to_be_visible(timeout=settings.default_timeout)
if __name__ == "__main__":
raise SystemExit(pytest.main([str(Path(__file__))]))

View File

@@ -1,159 +0,0 @@
from pathlib import Path
import random
import re
import sys
import allure
import pytest
from playwright.sync_api import TimeoutError as PlaywrightTimeoutError
from playwright.sync_api import expect
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from config.settings import settings
from tests.test_rewards_shopping_cart_login_logout import _accept_cookie_if_present
from utils.logger import get_logger
logger = get_logger(__name__)
REDEEM_NOW_SELECTORS = [
'button:has-text("Redeem Now"):not([disabled])',
'button:has-text("Redeem now"):not([disabled])',
'button:has-text("立即兑换"):not([disabled])',
]
POINTS_PAY_SELECTORS = [
'button:has-text("Pay"):not([disabled])',
'button:has-text("Confirm"):not([disabled])',
'button:has-text("Submit"):not([disabled])',
'button:has-text("Place Order"):not([disabled])',
'button:has-text("Redeem"):not([disabled])',
'button:has-text("Confirm Redemption"):not([disabled])',
'button:has-text("确认"):not([disabled])',
'button:has-text("提交"):not([disabled])',
'button:has-text("支付"):not([disabled])',
]
def _visible_locator(page, selectors, timeout=5_000):
for selector in selectors:
locator = page.locator(selector).first
try:
if locator.is_visible(timeout=timeout):
return locator
except PlaywrightTimeoutError:
continue
raise AssertionError(f"未找到可见元素: {selectors}")
def _login_in_open_dialog(page):
logger.info("在 Redeem Now 触发的登录弹窗中登录")
login_inputs = page.locator('input[type="text"]')
expect(login_inputs.first).to_be_visible(timeout=settings.default_timeout)
login_inputs.nth(0).fill(settings.login_email)
login_inputs.nth(1).fill(settings.verification_code)
confirmation_items = page.locator("div.inline-flex.cursor-pointer")
expect(confirmation_items.nth(0)).to_be_visible(timeout=settings.default_timeout)
expect(confirmation_items.nth(1)).to_be_visible(timeout=settings.default_timeout)
confirmation_items.nth(0).click()
confirmation_items.nth(1).click()
submit_button = page.locator("button").last
expect(submit_button).to_be_enabled(timeout=settings.default_timeout)
submit_button.click()
logger.info("等待登录弹窗关闭或兑换流程继续")
expect(login_inputs.first).not_to_be_visible(timeout=settings.default_timeout)
def _open_points_redemption_from_rewards(page):
logger.info("未登录用户点击 Rewards 并进入 Points Redemption")
rewards_link = page.locator('header a[href^="javascript:"]').filter(has_text="Rewards").first
expect(rewards_link).to_be_attached(timeout=settings.default_timeout)
rewards_link.click(force=True)
try:
page.wait_for_function(
"""
() => Array.from(document.querySelectorAll('a[href="/points-redemption"]'))
.some(element => !!(element.offsetWidth || element.offsetHeight || element.getClientRects().length))
""",
timeout=5_000,
)
page.locator('a[href="/points-redemption"]').filter(has_text="Points Redemption").first.click()
except PlaywrightTimeoutError:
logger.info("Rewards 下拉入口未稳定展示,直接进入 Points Redemption 页面")
page.goto(settings.base_url.rstrip("/") + "/points-redemption", wait_until="domcontentloaded")
expect(page).to_have_url(re.compile(r".*/points-redemption/?"), timeout=settings.default_timeout)
expect(page.locator("body")).to_be_visible(timeout=settings.default_timeout)
def _open_random_redeemable_points_product(page):
logger.info("随机选择一个积分商品并查找 Redeem Now")
category_buttons = page.locator("section button").filter(has_not_text=re.compile(r"Accepet|Accept", re.I))
expect(category_buttons.first).to_be_visible(timeout=settings.default_timeout)
category_indices = list(range(category_buttons.count()))
random.shuffle(category_indices)
last_error = None
for category_index in category_indices:
try:
logger.info("尝试积分商品分类/商品入口索引: %s", category_index)
category_buttons.nth(category_index).click()
page.wait_for_timeout(1_500)
if page.get_by_text(re.compile(r"currently do not support delivery", re.I)).first.is_visible(timeout=1_000):
last_error = "当前国家/地区不支持积分商品配送"
continue
redeem_button = _visible_locator(page, REDEEM_NOW_SELECTORS, timeout=5_000)
expect(redeem_button).to_be_enabled(timeout=settings.default_timeout)
redeem_button.click()
return
except (AssertionError, PlaywrightTimeoutError) as exc:
last_error = exc
logger.info("当前积分商品不可兑换,继续尝试下一个: %s", exc)
pytest.skip(f"当前测试环境未展示可兑换积分商品,无法执行 Redeem Now 主流程: {last_error}")
def _complete_points_payment_after_login(page):
logger.info("登录后继续完成积分商品支付/兑换")
try:
pay_button = _visible_locator(page, POINTS_PAY_SELECTORS, timeout=settings.default_timeout)
expect(pay_button).to_be_enabled(timeout=settings.default_timeout)
pay_button.click()
except AssertionError:
logger.info("登录后未出现独立确认按钮,检查是否已自动提交兑换")
result_text = page.get_by_text(
re.compile(r"success|successful|completed|paid|payment|order|redeem|redemption|成功|订单|兑换", re.I)
).first
expect(result_text).to_be_visible(timeout=settings.default_timeout)
@allure.feature("积分兑换")
@allure.story("积分商品支付")
@allure.title("游客兑换积分商品后登录并完成积分支付")
def test_guest_points_product_redeem_now_login_then_points_payment_result(page, ensure_guest_user):
logger.info("未登录用户从 Rewards 选择积分商品Redeem Now 后登录并完成积分支付")
page.goto(settings.base_url, wait_until="domcontentloaded")
ensure_guest_user()
_open_points_redemption_from_rewards(page)
_open_random_redeemable_points_product(page)
logger.info("校验 Redeem Now 后弹出登录弹窗")
expect(page.locator('input[type="text"]').first).to_be_visible(timeout=settings.default_timeout)
_login_in_open_dialog(page)
_complete_points_payment_after_login(page)
if __name__ == "__main__":
raise SystemExit(pytest.main([str(Path(__file__))]))

View File

@@ -1,167 +0,0 @@
from pathlib import Path
import random
import re
import sys
import allure
import pytest
from playwright.sync_api import TimeoutError as PlaywrightTimeoutError
from playwright.sync_api import expect
from tests.conftest import accept_cookie_if_present, login_as_configured_user_if_needed
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from config.settings import settings
from utils.logger import get_logger
logger = get_logger(__name__)
def _accept_cookie_if_present(page):
accept_cookie_if_present(page)
def _first_visible(locator):
locator_count = locator.count()
for index in range(locator_count):
candidate = locator.nth(index)
if candidate.is_visible():
return candidate
raise AssertionError("未找到可见元素")
def _login_as_configured_user(page):
login_as_configured_user_if_needed(page)
def _add_random_shopping_product_to_cart(page):
logger.info("直接进入 Shopping 页面")
page.goto(settings.base_url.rstrip("/") + "/shopping", wait_until="domcontentloaded")
expect(page).to_have_url(re.compile(r".*/shopping/?$"), timeout=settings.default_timeout)
expect(page.locator("body")).to_be_visible(timeout=settings.default_timeout)
logger.info("动态获取分类列表并随机选择商品加购")
category_buttons = page.locator("section > div:nth-of-type(1) > div:nth-of-type(2) > div:nth-of-type(1) button")
expect(category_buttons.first).to_be_visible(timeout=settings.default_timeout)
category_count = category_buttons.count()
logger.info("当前分类数量: %s", category_count)
assert category_count > 0
category_indices = list(range(category_count))
random.shuffle(category_indices)
logger.info("随机分类尝试顺序: %s", category_indices)
last_error = None
for selected_category_index in category_indices:
logger.info("随机选择分类索引: %s", selected_category_index)
category_buttons.nth(selected_category_index).click()
page.wait_for_timeout(1_000)
logger.info("动态获取商品列表")
product_links = page.locator('section a[href^="/shopping/"][href*="selectedSkuId"]').evaluate_all(
"""
links => Array.from(new Map(
links
.map(link => link.getAttribute('href'))
.filter(Boolean)
.map(href => [href, href])
).values())
"""
)
logger.info("当前可进入详情的商品数量: %s", len(product_links))
random.shuffle(product_links)
for selected_product_href in product_links:
try:
logger.info("尝试加购商品: %s", selected_product_href)
page.goto(settings.base_url.rstrip("/") + selected_product_href, wait_until="domcontentloaded")
expect(page).to_have_url(re.compile(r".*/shopping/.+selectedSkuId=.+"), timeout=settings.default_timeout)
expect(page.locator("body")).to_be_visible(timeout=settings.default_timeout)
logger.info("动态获取商品规格行,逐行随机选择一个可用规格")
spec_rows = page.locator(".product-specs-scrollbar > div").filter(has=page.locator("button"))
expect(spec_rows.first).to_be_visible(timeout=settings.default_timeout)
spec_row_count = spec_rows.count()
logger.info("当前规格行数量: %s", spec_row_count)
assert spec_row_count > 0
selected_spec_count = 0
for row_index in range(spec_row_count):
spec_options = spec_rows.nth(row_index).locator("button:not([disabled])")
spec_option_count = spec_options.count()
logger.info("规格行 %s 可选项数量: %s", row_index, spec_option_count)
if spec_option_count == 0:
continue
selected_spec_index = random.randrange(spec_option_count)
logger.info("规格行 %s 随机选择选项索引: %s", row_index, selected_spec_index)
spec_options.nth(selected_spec_index).click()
selected_spec_count += 1
page.wait_for_timeout(300)
assert selected_spec_count > 0
logger.info("动态点击数量增加按钮")
increase_quantity_button = page.locator("button:has(svg.lucide-plus):not([disabled])").first
try:
if increase_quantity_button.is_visible(timeout=3_000):
increase_click_count = random.randint(1, 3)
logger.info("随机增加商品数量次数: %s", increase_click_count)
for _ in range(increase_click_count):
increase_quantity_button.click()
page.wait_for_timeout(200)
except PlaywrightTimeoutError:
logger.info("当前商品未展示数量增加按钮,使用默认购买数量")
logger.info("动态获取加购按钮并点击")
add_to_cart_button = page.locator("button.bg-primary.text-white:not([disabled])").first
expect(add_to_cart_button).to_be_visible(timeout=settings.default_timeout)
expect(add_to_cart_button).to_be_enabled(timeout=settings.default_timeout)
add_to_cart_button.click()
logger.info("校验购物车入口仍可见,表示加购流程已完成点击")
expect(page.locator('a[href="/view-cart"]').first).to_be_visible(timeout=settings.default_timeout)
return
except (AssertionError, PlaywrightTimeoutError) as exc:
last_error = exc
logger.info("当前商品不可加购,继续尝试下一个商品: %s", exc)
page.goto(settings.base_url.rstrip("/") + "/shopping", wait_until="domcontentloaded")
expect(page).to_have_url(re.compile(r".*/shopping/?$"), timeout=settings.default_timeout)
category_buttons = page.locator(
"section > div:nth-of-type(1) > div:nth-of-type(2) > div:nth-of-type(1) button"
)
category_buttons.nth(selected_category_index).click()
page.wait_for_timeout(1_000)
raise AssertionError(f"未找到可加购的随机商品: {last_error}")
@allure.feature("Rewards Shopping")
@allure.story("游客加购")
@allure.title("游客随机选择 Rewards Shopping 商品并加入购物车")
def test_rewards_shopping_random_product_add_to_cart_no_login(page, ensure_guest_user):
logger.info("使用游客身份执行 Rewards Shopping 随机商品加购")
logger.info("打开链接地址: %s", settings.base_url)
page.goto(settings.base_url, wait_until="domcontentloaded")
ensure_guest_user()
_add_random_shopping_product_to_cart(page)
@allure.feature("Rewards Shopping")
@allure.story("登录用户加购")
@allure.title("登录用户随机选择 Rewards Shopping 商品并加入购物车")
def test_rewards_shopping_random_product_add_to_cart_after_login(page, ensure_logged_in_user):
logger.info("使用登录用户身份执行 Rewards Shopping 随机商品加购")
logger.info("打开链接地址: %s", settings.base_url)
page.goto(settings.base_url, wait_until="domcontentloaded")
ensure_logged_in_user()
_add_random_shopping_product_to_cart(page)
if __name__ == "__main__":
raise SystemExit(pytest.main([str(Path(__file__))]))

View File

@@ -1,278 +0,0 @@
from pathlib import Path
import random
import re
import sys
import allure
import pytest
from playwright.sync_api import Error as PlaywrightError
from playwright.sync_api import TimeoutError as PlaywrightTimeoutError
from playwright.sync_api import expect
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from config.settings import settings
from tests.test_rewards_shopping_cart_login_logout import (
_accept_cookie_if_present,
_first_visible,
_login_as_configured_user,
)
from utils.logger import get_logger
logger = get_logger(__name__)
PAYPAL_EMAIL_SELECTORS = [
'input[name="login_email"]',
'input[name="email"]',
'input#email',
]
PAYPAL_PASSWORD_SELECTORS = [
'input[name="login_password"]',
'input[name="password"]',
'input#password',
]
PAYPAL_NEXT_SELECTORS = [
'button:has-text("Next")',
'input[type="submit"][value="Next"]',
'button#btnNext',
]
PAYPAL_LOGIN_SELECTORS = [
'button:has-text("Log In")',
'button:has-text("Log in")',
'button:has-text("Login")',
'button#btnLogin',
'input[type="submit"][value="Log In"]',
]
PAYPAL_PAY_SELECTORS = [
'button:has-text("Complete Purchase")',
'button:has-text("Pay Now")',
'button:has-text("Agree and Pay")',
'button:has-text("Continue")',
'button:has-text("Review Order")',
'input[type="submit"][value*="Pay"]',
]
def _visible_locator(page, selectors, timeout=5_000):
for selector in selectors:
locator = page.locator(selector).first
try:
if locator.is_visible(timeout=timeout):
return locator
except PlaywrightTimeoutError:
continue
raise AssertionError(f"未找到可见元素: {selectors}")
def _add_random_shopping_product_directly(page):
logger.info("直接进入 Shopping 页面并随机加购商品")
page.goto(settings.base_url.rstrip("/") + "/shopping", wait_until="domcontentloaded")
expect(page).to_have_url(re.compile(r".*/shopping/?$"), timeout=settings.default_timeout)
category_buttons = page.locator("section > div:nth-of-type(1) > div:nth-of-type(2) > div:nth-of-type(1) button")
expect(category_buttons.first).to_be_visible(timeout=settings.default_timeout)
category_indices = list(range(category_buttons.count()))
random.shuffle(category_indices)
product_links = []
for selected_category_index in category_indices:
category_buttons.nth(selected_category_index).click()
page.wait_for_timeout(1_000)
product_links = page.locator('section a[href^="/shopping/"][href*="selectedSkuId"]').evaluate_all(
"""
links => Array.from(new Map(
links
.map(link => link.getAttribute('href'))
.filter(Boolean)
.map(href => [href, href])
).values())
"""
)
if product_links:
break
assert product_links
selected_product_href = random.choice(product_links)
_first_visible(page.locator(f'section a[href="{selected_product_href}"]')).click()
expect(page).to_have_url(re.compile(r".*/shopping/.+selectedSkuId=.+"), timeout=settings.default_timeout)
spec_rows = page.locator(".product-specs-scrollbar > div").filter(has=page.locator("button"))
expect(spec_rows.first).to_be_visible(timeout=settings.default_timeout)
selected_spec_count = 0
for row_index in range(spec_rows.count()):
spec_options = spec_rows.nth(row_index).locator("button:not([disabled])")
spec_option_count = spec_options.count()
if spec_option_count == 0:
continue
spec_options.nth(random.randrange(spec_option_count)).click()
selected_spec_count += 1
page.wait_for_timeout(300)
assert selected_spec_count > 0
increase_quantity_button = page.locator("button:has(svg.lucide-plus):not([disabled])").first
try:
if increase_quantity_button.is_visible(timeout=3_000):
for _ in range(random.randint(1, 3)):
increase_quantity_button.click()
page.wait_for_timeout(200)
except PlaywrightTimeoutError:
logger.info("当前商品未展示数量增加按钮,使用默认购买数量")
add_to_cart_button = page.locator("button.bg-primary.text-white:not([disabled])").first
expect(add_to_cart_button).to_be_visible(timeout=settings.default_timeout)
expect(add_to_cart_button).to_be_enabled(timeout=settings.default_timeout)
add_to_cart_button.click()
expect(page.locator('a[href="/view-cart"]').first).to_be_visible(timeout=settings.default_timeout)
def _go_to_cart(page):
logger.info("进入购物车页面")
page.goto(settings.base_url.rstrip("/") + "/view-cart", wait_until="domcontentloaded")
expect(page).to_have_url(re.compile(r".*/view-cart/?$"), timeout=settings.default_timeout)
expect(page.locator('iframe[title="PayPal-paypal"]').first).to_be_attached(timeout=settings.default_timeout)
def _add_random_one_or_more_products(page):
product_count = random.randint(1, 2)
logger.info("随机加入商品数量种类: %s", product_count)
for index in range(product_count):
logger.info("执行第 %s 次随机商品加购", index + 1)
_add_random_shopping_product_directly(page)
if index < product_count - 1:
page.goto(settings.base_url, wait_until="domcontentloaded")
def _trigger_paypal_payment(page):
logger.info("等待 PayPal 支付按钮渲染")
expect(page.locator('iframe[title="PayPal-paypal"]').first).to_be_attached(timeout=settings.default_timeout)
page.wait_for_timeout(3_000)
logger.info("坐标点击页面主 DOM 中可见的 PayPal 按钮")
paypal_buttons = page.locator('button[class*="FFCE0C"]')
for index in range(paypal_buttons.count()):
paypal_button = paypal_buttons.nth(index)
try:
if not paypal_button.is_visible(timeout=1_000):
continue
if not paypal_button.is_enabled(timeout=1_000):
continue
box = paypal_button.bounding_box()
if not box:
continue
try:
with page.expect_popup(timeout=10_000) as popup_info:
page.mouse.click(box["x"] + box["width"] / 2, box["y"] + box["height"] / 2)
paypal_page = popup_info.value
except PlaywrightTimeoutError:
logger.info("未捕获到 PayPal 弹窗,检查当前页是否已跳转")
page.wait_for_timeout(3_000)
if "paypal" in page.url.lower():
paypal_page = page
else:
continue
try:
paypal_page.wait_for_load_state("domcontentloaded", timeout=settings.default_timeout)
try:
paypal_page.wait_for_url(re.compile(r".*paypal.*"), timeout=settings.default_timeout)
except PlaywrightTimeoutError:
logger.info("PayPal 页面当前地址未包含 paypal: %s", paypal_page.url)
expect(paypal_page.locator("body")).to_be_visible(timeout=settings.default_timeout)
return paypal_page
except PlaywrightError as exc:
logger.info("PayPal 弹窗已被触发但提前关闭: %s", exc)
return None
except PlaywrightTimeoutError:
logger.info("当前 PayPal 按钮未触发支付页,继续尝试下一个按钮")
raise AssertionError("未找到可点击并能打开支付页的 PayPal 按钮")
def _login_paypal_if_required(paypal_page):
logger.info("处理 PayPal 登录页面")
try:
email_input = _visible_locator(paypal_page, PAYPAL_EMAIL_SELECTORS)
except AssertionError:
logger.info("PayPal 当前页面未出现邮箱输入框,可能已保持登录态")
return
email_input.fill(settings.paypal_email)
try:
next_button = _visible_locator(paypal_page, PAYPAL_NEXT_SELECTORS, timeout=3_000)
next_button.click()
except AssertionError:
logger.info("PayPal 未出现 Next 按钮,继续查找密码输入框")
password_input = _visible_locator(paypal_page, PAYPAL_PASSWORD_SELECTORS)
password_input.fill(settings.paypal_password)
login_button = _visible_locator(paypal_page, PAYPAL_LOGIN_SELECTORS)
expect(login_button).to_be_enabled(timeout=settings.default_timeout)
login_button.click()
paypal_page.wait_for_load_state("domcontentloaded", timeout=settings.default_timeout)
def _complete_paypal_payment(page, paypal_page):
logger.info("在 PayPal 页面确认支付")
pay_button = _visible_locator(paypal_page, PAYPAL_PAY_SELECTORS, timeout=settings.default_timeout)
expect(pay_button).to_be_enabled(timeout=settings.default_timeout)
pay_button.click()
logger.info("等待 PayPal 支付完成并返回商户站点")
try:
paypal_page.wait_for_close(timeout=settings.default_timeout)
except PlaywrightTimeoutError:
logger.info("PayPal 弹窗未自动关闭,继续等待主页面跳转")
page.wait_for_load_state("domcontentloaded", timeout=settings.default_timeout)
expect(page).to_have_url(re.compile(r".*joyhub-website-frontend-test.*"), timeout=settings.default_timeout)
success_text = page.get_by_text(re.compile(r"success|paid|payment|order", re.I)).first
expect(success_text).to_be_visible(timeout=settings.default_timeout)
def _pay_random_shopping_products(page):
_add_random_one_or_more_products(page)
_go_to_cart(page)
paypal_page = _trigger_paypal_payment(page)
if not settings.run_paypal_payment:
logger.info("RUN_PAYPAL_PAYMENT 未开启,仅校验支付入口可触发")
expect(page).to_have_url(re.compile(r".*/view-cart/?$"), timeout=settings.default_timeout)
return
assert paypal_page is not None, "PayPal 弹窗已触发但提前关闭,无法继续真实付款"
_login_paypal_if_required(paypal_page)
_complete_paypal_payment(page, paypal_page)
@allure.feature("Rewards Shopping")
@allure.story("游客支付")
@allure.title("游客随机选择 Rewards Shopping 商品并触发 PayPal 支付")
def test_rewards_shopping_random_products_payment_no_login(page, ensure_guest_user):
logger.info("使用游客身份执行随机商品 PayPal 支付")
page.goto(settings.base_url, wait_until="domcontentloaded")
ensure_guest_user()
_pay_random_shopping_products(page)
@allure.feature("Rewards Shopping")
@allure.story("登录用户支付")
@allure.title("登录用户随机选择 Rewards Shopping 商品并触发 PayPal 支付")
def test_rewards_shopping_random_products_payment_after_login(page, ensure_logged_in_user):
logger.info("使用登录用户身份执行随机商品 PayPal 支付")
page.goto(settings.base_url, wait_until="domcontentloaded")
ensure_logged_in_user()
_pay_random_shopping_products(page)
if __name__ == "__main__":
raise SystemExit(pytest.main([str(Path(__file__))]))

View File

@@ -1,192 +0,0 @@
from pathlib import Path
import random
import re
import sys
import allure
import pytest
from playwright.sync_api import TimeoutError as PlaywrightTimeoutError
from playwright.sync_api import expect
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from config.settings import settings
from utils.logger import get_logger
logger = get_logger(__name__)
LANGUAGE_OPTIONS = ["English", "Français"]
def _visible_header(page):
headers = page.locator("header")
for index in range(headers.count()):
header = headers.nth(index)
try:
if header.is_visible(timeout=1_000):
return header
except PlaywrightTimeoutError:
continue
raise AssertionError("未找到可见的页面头部")
def _visible_dropdown_options(page):
return page.locator('div[class*="hover:bg-grey700"]').filter(visible=True)
def _select_random_language(page):
header = _visible_header(page)
language_trigger = header.locator("div.cursor-pointer").nth(3)
expect(language_trigger).to_be_visible(timeout=settings.default_timeout)
language_trigger.click()
options = _visible_dropdown_options(page)
available_languages = []
for index in range(options.count()):
option_text = options.nth(index).inner_text(timeout=3_000).strip()
if option_text in LANGUAGE_OPTIONS:
available_languages.append(option_text)
assert available_languages, "语言下拉没有可选项"
selected_language = random.choice(available_languages)
logger.info("随机选择语言: %s,可选语言: %s", selected_language, available_languages)
options.filter(has_text=re.compile(rf"^{re.escape(selected_language)}$")).first.click()
page.wait_for_load_state("domcontentloaded", timeout=settings.default_timeout)
page.wait_for_timeout(1_000)
return selected_language
def _select_random_country(page):
header = _visible_header(page)
country_trigger = header.locator("div.cursor-pointer").nth(4)
expect(country_trigger).to_be_visible(timeout=settings.default_timeout)
country_trigger.click()
options = _visible_dropdown_options(page)
expect(options.first).to_be_visible(timeout=settings.default_timeout)
countries = []
for index in range(options.count()):
country_text = options.nth(index).inner_text(timeout=3_000).strip()
if country_text and country_text not in LANGUAGE_OPTIONS:
countries.append(country_text)
assert countries, "国家下拉没有可选项"
selected_country = random.choice(countries)
logger.info("随机选择国家: %s,可选国家数量: %s", selected_country, len(countries))
options.filter(has_text=re.compile(rf"^{re.escape(selected_country)}$")).first.click()
page.wait_for_load_state("domcontentloaded", timeout=settings.default_timeout)
page.wait_for_timeout(1_000)
return selected_country
def _shopping_product_links(page):
return page.locator('section a[href^="/shopping/"][href*="selectedSkuId"]').evaluate_all(
"""
links => Array.from(new Map(
links
.map(link => link.getAttribute('href'))
.filter(Boolean)
.map(href => [href, href])
).values())
"""
)
def _find_random_product_href_or_none(page):
logger.info("进入 Shopping 页面,查找当前国家可选商品")
page.goto(settings.base_url.rstrip("/") + "/shopping", wait_until="domcontentloaded")
expect(page).to_have_url(re.compile(r".*/shopping/?$"), timeout=settings.default_timeout)
expect(page.locator("body")).to_be_visible(timeout=settings.default_timeout)
page.wait_for_timeout(2_000)
category_buttons = page.locator("section > div:nth-of-type(1) > div:nth-of-type(2) > div:nth-of-type(1) button")
try:
if not category_buttons.first.is_visible(timeout=5_000):
logger.info("当前国家 Shopping 页面无分类,认为没有商品可选")
return None
except PlaywrightTimeoutError:
logger.info("当前国家 Shopping 页面未加载到分类,认为没有商品可选")
return None
category_indices = list(range(category_buttons.count()))
random.shuffle(category_indices)
logger.info("随机分类尝试顺序: %s", category_indices)
for category_index in category_indices:
category_buttons.nth(category_index).click()
page.wait_for_timeout(1_000)
product_links = _shopping_product_links(page)
logger.info("分类索引 %s 可进入详情的商品数量: %s", category_index, len(product_links))
if product_links:
return random.choice(product_links)
return None
def _add_product_to_cart(page, product_href):
logger.info("进入随机商品详情并加购: %s", product_href)
page.goto(settings.base_url.rstrip("/") + product_href, wait_until="domcontentloaded")
expect(page).to_have_url(re.compile(r".*/shopping/.+selectedSkuId=.+"), timeout=settings.default_timeout)
expect(page.locator("body")).to_be_visible(timeout=settings.default_timeout)
spec_rows = page.locator(".product-specs-scrollbar > div").filter(has=page.locator("button"))
expect(spec_rows.first).to_be_visible(timeout=settings.default_timeout)
selected_spec_count = 0
for row_index in range(spec_rows.count()):
spec_options = spec_rows.nth(row_index).locator("button:not([disabled])")
spec_option_count = spec_options.count()
if spec_option_count == 0:
continue
selected_spec_index = random.randrange(spec_option_count)
logger.info("规格行 %s 随机选择选项索引: %s", row_index, selected_spec_index)
spec_options.nth(selected_spec_index).click()
selected_spec_count += 1
page.wait_for_timeout(300)
assert selected_spec_count > 0, "商品详情没有可选规格"
increase_quantity_button = page.locator("button:has(svg.lucide-plus):not([disabled])").first
try:
if increase_quantity_button.is_visible(timeout=3_000):
increase_click_count = random.randint(1, 3)
logger.info("随机增加商品数量次数: %s", increase_click_count)
for _ in range(increase_click_count):
increase_quantity_button.click()
page.wait_for_timeout(200)
except PlaywrightTimeoutError:
logger.info("当前商品未展示数量增加按钮,使用默认购买数量")
add_to_cart_button = page.locator("button.bg-primary.text-white:not([disabled])").first
expect(add_to_cart_button).to_be_visible(timeout=settings.default_timeout)
expect(add_to_cart_button).to_be_enabled(timeout=settings.default_timeout)
add_to_cart_button.click()
expect(page.locator('a[href="/view-cart"]').first).to_be_visible(timeout=settings.default_timeout)
@allure.feature("Shopping")
@allure.story("语言国家切换")
@allure.title("随机切换语言和国家后选择 Shopping 商品加入购物车")
def test_switch_random_language_country_then_add_shopping_product_to_cart(page, ensure_guest_user):
logger.info("打开 JoyHub 首页并确保游客身份")
page.goto(settings.base_url, wait_until="domcontentloaded")
ensure_guest_user()
selected_language = _select_random_language(page)
selected_country = _select_random_country(page)
product_href = _find_random_product_href_or_none(page)
if not product_href:
logger.info("语言 %s / 国家 %s 没有商品可选,用例按预期通过", selected_language, selected_country)
assert True
return
_add_product_to_cart(page, product_href)
logger.info("语言 %s / 国家 %s 商品加购成功", selected_language, selected_country)
if __name__ == "__main__":
raise SystemExit(pytest.main([str(Path(__file__))]))

View File

@@ -1,38 +0,0 @@
from playwright.sync_api import sync_playwright
base = 'https://joyhub-website-frontend-test.best-envision.com'
countries = [
'Kenya',
'Côte divoire',
'South Korea',
'Great Britain (United Kingdom; England)',
'Vatican City (The Holy See)',
'Singapore',
'Sweden',
'Poland',
'Netherlands',
'Japan',
'Italy',
'Spain',
'Germany',
'Canada',
'Australia',
'France',
]
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page(viewport={'width': 1920, 'height': 1080})
for country in countries:
try:
page.goto(base + '/points-redemption', wait_until='domcontentloaded')
page.wait_for_timeout(1500)
page.locator('header .relative.inline-block').nth(5).click(force=True)
page.wait_for_timeout(500)
page.get_by_text(country, exact=True).first.click(force=True, timeout=5000)
page.wait_for_timeout(2500)
body = page.locator('body').inner_text()
print(country, '| url=', page.url, '| redeem=', 'Redeem' in body, '| unsupported=', 'currently do not support delivery' in body, '| sample=', body[:300].replace('\n', ' | '))
except Exception as exc:
print(country, '| ERROR=', type(exc).__name__, str(exc)[:200])
browser.close()

View File

@@ -1,25 +0,0 @@
import logging
from pathlib import Path
REPORTS_DIR = Path(__file__).resolve().parents[1] / "reports"
LOG_FILE = REPORTS_DIR / "test_run.log"
def get_logger(name: str = "ui-test") -> logging.Logger:
REPORTS_DIR.mkdir(parents=True, exist_ok=True)
logger = logging.getLogger(name)
if not logger.handlers:
formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(name)s - %(message)s")
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(formatter)
logger.addHandler(stream_handler)
file_handler = logging.FileHandler(LOG_FILE, encoding="utf-8")
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
logger.setLevel(logging.INFO)
return logger

View File

@@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2026 Anthropic, PBC.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,96 +0,0 @@
---
name: webapp-testing
description: Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.
license: Complete terms in LICENSE.txt
---
# Web Application Testing
To test local web applications, write native Python Playwright scripts.
**Helper Scripts Available**:
- `scripts/with_server.py` - Manages server lifecycle (supports multiple servers)
**Always run scripts with `--help` first** to see usage. DO NOT read the source until you try running the script first and find that a customized solution is abslutely necessary. These scripts can be very large and thus pollute your context window. They exist to be called directly as black-box scripts rather than ingested into your context window.
## Decision Tree: Choosing Your Approach
```
User task → Is it static HTML?
├─ Yes → Read HTML file directly to identify selectors
│ ├─ Success → Write Playwright script using selectors
│ └─ Fails/Incomplete → Treat as dynamic (below)
└─ No (dynamic webapp) → Is the server already running?
├─ No → Run: python scripts/with_server.py --help
│ Then use the helper + write simplified Playwright script
└─ Yes → Reconnaissance-then-action:
1. Navigate and wait for networkidle
2. Take screenshot or inspect DOM
3. Identify selectors from rendered state
4. Execute actions with discovered selectors
```
## Example: Using with_server.py
To start a server, run `--help` first, then use the helper:
**Single server:**
```bash
python scripts/with_server.py --server "npm run dev" --port 5173 -- python your_automation.py
```
**Multiple servers (e.g., backend + frontend):**
```bash
python scripts/with_server.py \
--server "cd backend && python server.py" --port 3000 \
--server "cd frontend && npm run dev" --port 5173 \
-- python your_automation.py
```
To create an automation script, include only Playwright logic (servers are managed automatically):
```python
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True) # Always launch chromium in headless mode
page = browser.new_page()
page.goto('http://localhost:5173') # Server already running and ready
page.wait_for_load_state('networkidle') # CRITICAL: Wait for JS to execute
# ... your automation logic
browser.close()
```
## Reconnaissance-Then-Action Pattern
1. **Inspect rendered DOM**:
```python
page.screenshot(path='/tmp/inspect.png', full_page=True)
content = page.content()
page.locator('button').all()
```
2. **Identify selectors** from inspection results
3. **Execute actions** using discovered selectors
## Common Pitfall
❌ **Don't** inspect the DOM before waiting for `networkidle` on dynamic apps
✅ **Do** wait for `page.wait_for_load_state('networkidle')` before inspection
## Best Practices
- **Use bundled scripts as black boxes** - To accomplish a task, consider whether one of the scripts available in `scripts/` can help. These scripts handle common, complex workflows reliably without cluttering the context window. Use `--help` to see usage, then invoke directly.
- Use `sync_playwright()` for synchronous scripts
- Always close the browser when done
- Use descriptive selectors: `text=`, `role=`, CSS selectors, or IDs
- Add appropriate waits: `page.wait_for_selector()` or `page.wait_for_timeout()`
## Reference Files
- **examples/** - Examples showing common patterns:
- `element_discovery.py` - Discovering buttons, links, and inputs on a page
- `static_html_automation.py` - Using file:// URLs for local HTML
- `console_logging.py` - Capturing console logs during automation

View File

@@ -1,35 +0,0 @@
from playwright.sync_api import sync_playwright
# Example: Capturing console logs during browser automation
url = 'http://localhost:5173' # Replace with your URL
console_logs = []
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page(viewport={'width': 1920, 'height': 1080})
# Set up console log capture
def handle_console_message(msg):
console_logs.append(f"[{msg.type}] {msg.text}")
print(f"Console: [{msg.type}] {msg.text}")
page.on("console", handle_console_message)
# Navigate to page
page.goto(url)
page.wait_for_load_state('networkidle')
# Interact with the page (triggers console logs)
page.click('text=Dashboard')
page.wait_for_timeout(1000)
browser.close()
# Save console logs to file
with open('/mnt/user-data/outputs/console.log', 'w') as f:
f.write('\n'.join(console_logs))
print(f"\nCaptured {len(console_logs)} console messages")
print(f"Logs saved to: /mnt/user-data/outputs/console.log")

View File

@@ -1,40 +0,0 @@
from playwright.sync_api import sync_playwright
# Example: Discovering buttons and other elements on a page
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
# Navigate to page and wait for it to fully load
page.goto('http://localhost:5173')
page.wait_for_load_state('networkidle')
# Discover all buttons on the page
buttons = page.locator('button').all()
print(f"Found {len(buttons)} buttons:")
for i, button in enumerate(buttons):
text = button.inner_text() if button.is_visible() else "[hidden]"
print(f" [{i}] {text}")
# Discover links
links = page.locator('a[href]').all()
print(f"\nFound {len(links)} links:")
for link in links[:5]: # Show first 5
text = link.inner_text().strip()
href = link.get_attribute('href')
print(f" - {text} -> {href}")
# Discover input fields
inputs = page.locator('input, textarea, select').all()
print(f"\nFound {len(inputs)} input fields:")
for input_elem in inputs:
name = input_elem.get_attribute('name') or input_elem.get_attribute('id') or "[unnamed]"
input_type = input_elem.get_attribute('type') or 'text'
print(f" - {name} ({input_type})")
# Take screenshot for visual reference
page.screenshot(path='/tmp/page_discovery.png', full_page=True)
print("\nScreenshot saved to /tmp/page_discovery.png")
browser.close()

View File

@@ -1,33 +0,0 @@
from playwright.sync_api import sync_playwright
import os
# Example: Automating interaction with static HTML files using file:// URLs
html_file_path = os.path.abspath('path/to/your/file.html')
file_url = f'file://{html_file_path}'
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page(viewport={'width': 1920, 'height': 1080})
# Navigate to local HTML file
page.goto(file_url)
# Take screenshot
page.screenshot(path='/mnt/user-data/outputs/static_page.png', full_page=True)
# Interact with elements
page.click('text=Click Me')
page.fill('#name', 'John Doe')
page.fill('#email', 'john@example.com')
# Submit form
page.click('button[type="submit"]')
page.wait_for_timeout(500)
# Take final screenshot
page.screenshot(path='/mnt/user-data/outputs/after_submit.png', full_page=True)
browser.close()
print("Static HTML automation completed!")

View File

@@ -1,106 +0,0 @@
#!/usr/bin/env python3
"""
Start one or more servers, wait for them to be ready, run a command, then clean up.
Usage:
# Single server
python scripts/with_server.py --server "npm run dev" --port 5173 -- python automation.py
python scripts/with_server.py --server "npm start" --port 3000 -- python test.py
# Multiple servers
python scripts/with_server.py \
--server "cd backend && python server.py" --port 3000 \
--server "cd frontend && npm run dev" --port 5173 \
-- python test.py
"""
import subprocess
import socket
import time
import sys
import argparse
def is_server_ready(port, timeout=30):
"""Wait for server to be ready by polling the port."""
start_time = time.time()
while time.time() - start_time < timeout:
try:
with socket.create_connection(('localhost', port), timeout=1):
return True
except (socket.error, ConnectionRefusedError):
time.sleep(0.5)
return False
def main():
parser = argparse.ArgumentParser(description='Run command with one or more servers')
parser.add_argument('--server', action='append', dest='servers', required=True, help='Server command (can be repeated)')
parser.add_argument('--port', action='append', dest='ports', type=int, required=True, help='Port for each server (must match --server count)')
parser.add_argument('--timeout', type=int, default=30, help='Timeout in seconds per server (default: 30)')
parser.add_argument('command', nargs=argparse.REMAINDER, help='Command to run after server(s) ready')
args = parser.parse_args()
# Remove the '--' separator if present
if args.command and args.command[0] == '--':
args.command = args.command[1:]
if not args.command:
print("Error: No command specified to run")
sys.exit(1)
# Parse server configurations
if len(args.servers) != len(args.ports):
print("Error: Number of --server and --port arguments must match")
sys.exit(1)
servers = []
for cmd, port in zip(args.servers, args.ports):
servers.append({'cmd': cmd, 'port': port})
server_processes = []
try:
# Start all servers
for i, server in enumerate(servers):
print(f"Starting server {i+1}/{len(servers)}: {server['cmd']}")
# Use shell=True to support commands with cd and &&
process = subprocess.Popen(
server['cmd'],
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
server_processes.append(process)
# Wait for this server to be ready
print(f"Waiting for server on port {server['port']}...")
if not is_server_ready(server['port'], timeout=args.timeout):
raise RuntimeError(f"Server failed to start on port {server['port']} within {args.timeout}s")
print(f"Server ready on port {server['port']}")
print(f"\nAll {len(servers)} server(s) ready")
# Run the command
print(f"Running: {' '.join(args.command)}\n")
result = subprocess.run(args.command)
sys.exit(result.returncode)
finally:
# Clean up all servers
print(f"\nStopping {len(server_processes)} server(s)...")
for i, process in enumerate(server_processes):
try:
process.terminate()
process.wait(timeout=5)
except subprocess.TimeoutExpired:
process.kill()
process.wait()
print(f"Server {i+1} stopped")
print("All servers stopped")
if __name__ == '__main__':
main()

File diff suppressed because one or more lines are too long

46
agent-skills.config.json Normal file
View File

@@ -0,0 +1,46 @@
{
"metadata": {
"name": "ui-automation-agent-skills-config",
"version": "1.0.0",
"description": "Configuration for UI automation agents and skills"
},
"defaults": {
"agent": "ui-automation-agent",
"skill": "webapp-testing",
"enabled": true
},
"agents": {
"ui-automation-agent": {
"enabled": true,
"type": "custom",
"description": "default agent profile for UI automation testing",
"model": "",
"entry": "",
"env": {
"PLAYWRIGHT_HEADLESS": "true"
},
"skills": [
"webapp-testing"
]
}
},
"skills": {
"webapp-testing": {
"enabled": true,
"display_name": "UI自动化测试",
"description": "Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.",
"source": "D:\\software\\SKILL.md",
"entry": "D:\\software\\SKILL.md",
"type": "ui-automation",
"framework": "playwright",
"language": "python",
"recommended_mode": "headless",
"params": {
"browser": "chromium",
"headless": true,
"wait_until": "networkidle",
"close_browser_on_finish": true
}
}
}
}

View File

@@ -5,6 +5,7 @@
zhyy = https://smart-management-api-st.best-envision.com/admin-api zhyy = https://smart-management-api-st.best-envision.com/admin-api
zhyy_login = https://smart-management-api-dev.best-envision.com/admin-api/system/auth/login-dev zhyy_login = https://smart-management-api-dev.best-envision.com/admin-api/system/auth/login-dev
ZZYY = https://smart-management-api-st.best-envision.com/admin-api ZZYY = https://smart-management-api-st.best-envision.com/admin-api
switch4 = https://switch4.best-envision.com/activity/form-review?userLanguage=jp&webKey=JyOJY1HaUWBQ00oooiNOEjTEwMDg3ODA3LCJ1c2VyTmFtZSI6InRveTFrbHNOeTExMSIsImVtYWlsIjoidG8xQGpveWh1Yi5uZXQiLCJwdXNoX2lkIjoxMDc4LCJwdXNoX3R5cGUiOjgsInByb2R1Y3RzIjoiXHU3MmVlXHU1YjUwXHU5OGRlXHU2NzNhXHU2NzZmIiwiYnJhbmRfdWlkIjoyMTA1LCJjaGFuIjoiaW0iLCJwcm9kdWN0X2lkIjo3fQO0O0OO0O0O
# uat环境 # uat环境
[UAT] [UAT]

View File

@@ -1,6 +1,6 @@
[run_evn_name] [run_evn_name]
current_business = hh current_business = hh
current_evn = qa current_evn = QA
current_team = ZZYY current_team = ZZYY
[run_jira_id] [run_jira_id]

View File

@@ -0,0 +1,3 @@
/logs/
/venv/
.idea/

View File

@@ -0,0 +1,4 @@
### IT接口管理
* git clone
* pip3 install -r requirements
* gunicorn --config=gunicorn.conf.py manage:app

View File

@@ -0,0 +1,18 @@
from flask import Flask
from ..app.api.views import api
from flask_docs import ApiDoc
from logger import logger
def create_app():
app = Flask(__name__)
app.register_blueprint(api, url_prefix='/api')
ApiDoc(
app,
title="Effekt Interface App",
version="1.0.0",
description="Effekt Interface app API",
)
app.config["API_DOC_MEMBER"] = ["api", "platform"]
logger.info("app start-------")
return app

View File

@@ -0,0 +1,128 @@
# encoding: UTF-8
import time
import subprocess
from subprocess import PIPE
import os
class OSType:
WIN, LINUX, UNKNOWN = range(3)
def __init__(self):
pass
@staticmethod
def get_type():
import platform
system_name = platform.system()
if system_name.lower() == 'windows':
return OSType.WIN
elif system_name.lower() == 'linux':
return OSType.LINUX
else:
return OSType.UNKNOWN
class tool(object):
def __init__(self):
self.env_port = 5011
def run_process(self, cmd_str, out_p=False):
"""
run command
cmd_str unicode string.
"""
if OSType.WIN == OSType.get_type():
# cmd_str = cmd_str.encode('gbk')
cmd_str = cmd_str
elif OSType.LINUX == OSType.get_type():
cmd_str = cmd_str.encode('utf-8')
else:
raise RuntimeError("your os is not support.")
close_fds = False if OSType.WIN == OSType.get_type() else True
if out_p:
p = subprocess.Popen(cmd_str, shell=True, close_fds=close_fds, stdout=PIPE)
p.wait()
return p.returncode, p.stdout.read()
else:
c = self.get_devnull()
p = subprocess.Popen(cmd_str, shell=True, close_fds=close_fds, stdout=c)
p.wait()
return p.returncode, None
def get_devnull(self):
try:
return subprocess.DEVNULL
except AttributeError:
# Python 2.x or older
return open(os.devnull, 'r+')
def _kills_pid(self):
if OSType.WIN == OSType.get_type():
kill_pid_cmd = "taskkill /f /pid {}".format(self.pid)
elif OSType.LINUX == OSType.get_type():
kill_pid_cmd = "kill -9 {}".format(self.pid)
else:
raise RuntimeError("your os is not support.")
res_code, res_context = self.run_process(kill_pid_cmd)
if res_code:
raise RuntimeError("kill pid: {} failed. error: {}".format(self.pid, res_context))
def check_port(self):
if OSType.WIN == OSType.get_type():
find_pid_win_cmd = 'netstat -ano | findstr {} | findstr LISTENING'.format(self.env_port)
print(find_pid_win_cmd)
res_code, res_context = self.run_process(find_pid_win_cmd, out_p=True)
if res_code == 0:
if len(res_context) > 0:
try:
self.pid = str(res_context).split()[-1].replace("\\r\\n'", "")
self._kills_pid()
except IndexError:
pass
elif OSType.LINUX == OSType.get_type():
find_pid_linux_cmd = "lsof -i:{}".format(self.env_port)
res_code, res_context = self.run_process(find_pid_linux_cmd, out_p=True)
if res_code == 0:
# 获取pid
if len(res_context) > 0:
try:
self.pid = str(res_context).split("\n")[1].split()[1]
self._kills_pid()
except IndexError:
pass
else:
raise RuntimeError("your os is not support.")
def run_manage(self):
count = 3
while count > 0:
self.run_manages()
find_pid_linux_cmd = "lsof -i:{}".format(self.env_port)
res_code, res_context = self.run_process(find_pid_linux_cmd, out_p=True)
print(res_code,"---res_code---",res_context,"---res_context---")
if len(res_context) > 0:
time.sleep(2)
pid = str(res_context).split("\n")[1].split()[1]
print(pid,"pid####")
count -= 1
if pid:
break
else:
continue
else:
break
def run_manages(self):
lod = "nohup python3 platform_tools/aida/manage.py &."
subprocess.call(["./5011.sh"])
# self.run_process(lod)
if __name__ == '__main__':
test = tool()
test.check_port()
time.sleep(3)
test.run_manage()

View File

@@ -0,0 +1,527 @@
# encoding: UTF-8
from ....common.sqlSession import SqlSession
from ..service.createDataDetailService import CreateDetailService
from ..service.createDataDictService import CreateDictService
from ..service.createDataInfoService import CreateInfoService
from ..service.createDataNumberService import CreateNumberService
from ..service.createDataResultService import CreateResultService
from ..model.createDataInfo import CreateInfo
from ..model.createDetailInfo import CreateDetail
from ..model.createDictInfo import CreateDict
from datetime import datetime, timedelta
from logger import logger
import ast
from operator import methodcaller
import astunparse
import os
import re
import time
from sqlalchemy.exc import SQLAlchemyError
class CommonController(object):
def __init__(self):
self.session = SqlSession()
def create_all_info(self, team_name, list_detail):
"""
对比造数基础表数据,新增的进行录库
:return:
"""
for detail in list_detail:
file_name = detail.get("file_name")
get_class_name = detail.get("class_name")
module_names = detail.get("module_name")
method_name = detail.get("method_name")
module_name = module_names.split(".")[0]
method_detail = detail.get("method_detail")
request_parameter = list(eval(detail.get("request_parameter")))
create_dict_service = CreateDictService()
create_detail_service = CreateDetailService()
create_info_service = CreateInfoService()
filter_detail_list = list()
filter_detail_list.append(CreateDetail.class_name == get_class_name)
filter_detail_list.append(CreateDetail.method_name == method_name)
filter_detail_list.append(CreateDetail.module_name == module_name)
get_data_detail = create_detail_service.get_detail_simple_data_by_filters(self.session,
filter_detail_list)
# 数据字典中业务线配置的路径,用于获取各个业务线下的业务名称
filter_dict_list = list()
filter_dict_list.append(CreateDict.data_type == 1)
filter_dict_list.append(CreateDict.dict_key == team_name)
get_dict_detail = create_dict_service.get_dict_simple_data_by_filters(self.session, filter_dict_list)
get_split_team_name = get_dict_detail.dict_value.split(".")[-1]
get_split_file_name = file_name.split(get_split_team_name)[-1]
if get_split_file_name:
get_file_name = get_split_file_name[1:].replace("\\", ".")
else:
get_file_name = ""
if get_data_detail:
compare_class_name = self.compare_string(get_data_detail.class_name, get_class_name)
compare_module_name = self.compare_string(get_data_detail.module_name, module_name)
compare_method_name = self.compare_string(get_data_detail.method_name, method_name)
compare_method_detail = self.compare_string(get_data_detail.method_detail, method_detail)
# compare_file_name = self.compare_string(get_data_detail.file_name, get_file_name)
update_info = {}
if compare_class_name:
update_info["class_name"] = get_class_name
elif compare_module_name:
update_info["module_name"] = module_name
elif compare_method_name:
update_info["method_name"] = method_name
elif compare_method_detail:
update_info["method_detail"] = method_detail
# elif compare_file_name:
# get_data_detail.file_name = get_file_name
else:
continue
update_info["modified_time"] = update_time()
print(update_info)
err_msg = create_detail_service.update_service_detail_data(self.session, update_info,
get_data_detail.create_data_detail_id)
if err_msg:
return err_msg
else:
if method_name == "__init__":
continue
if re.findall("__", method_name):
continue
if method_name.startswith("_"):
continue
dict_parameter = {}
if request_parameter:
for parameter in request_parameter:
if parameter != 'self':
dict_parameter[parameter] = ""
else:
dict_parameter = {}
get_method_detail = self.handle_method_detail(method_detail)
get_creator = self.get_creator_info(team=team_name, method_detail=method_detail)
if get_method_detail:
get_function_details = get_method_detail
else:
get_function_details = str(module_name) + "." + str(get_class_name) + "." + str(method_name)
add_detail_data = {}
add_info_data = {}
add_dict_data = {}
add_detail_data["create_data_detail_id"] = get_uuid()
add_detail_data["team_name"] = team_name
add_detail_data["file_name"] = get_file_name
add_detail_data["class_name"] = get_class_name
add_detail_data["del_flag"] = 0
add_detail_data["module_name"] = module_name
add_detail_data["method_name"] = method_name
add_detail_data["method_detail"] = method_detail
add_detail_data["method_function_detail"] = get_function_details
add_detail_data["creator"] = get_creator
add_detail_data["status"] = 0
add_detail_data["created_time"] = update_time()
add_detail_data["modified_time"] = update_time()
create_data_detail_id = create_detail_service.create_service_detail_data(self.session,
add_detail_data)
add_info_data["create_data_info_id"] = get_uuid()
add_info_data["create_data_detail_id"] = create_data_detail_id
add_info_data["class_name"] = get_class_name
add_info_data["del_flag"] = 0
add_info_data["method_name"] = method_name
add_info_data["request_parameter"] = dict_parameter
add_info_data["created_time"] = get_time()
add_info_data["modified_time"] = get_time()
info_id = create_info_service.create_service_info_data(self.session, add_info_data)
filter_dict_type_list = list()
filter_dict_type_list.append(CreateDict.team_name == team_name.upper())
filter_dict_type_list.append(CreateDict.data_type == 3)
filter_dict_type_list.append(CreateDict.dict_key == get_file_name)
get_dict_detail = create_dict_service.get_dict_simple_data_by_filters(self.session,
filter_dict_type_list)
add_dict_data["create_dict_data_id"] = get_uuid()
add_dict_data["data_type"] = 3
add_dict_data["team_name"] = team_name
add_dict_data["dict_key"] = get_file_name
add_dict_data["dict_value"] = get_file_name
add_dict_data["del_flag"] = 0
add_dict_data["created_time"] = get_time()
add_dict_data["modified_time"] = get_time()
if get_dict_detail or get_file_name == "":
continue
else:
dict_id = create_dict_service.create_service_dict_data(self.session, add_dict_data)
return True
def compare_string(self, sub_str1, sub_str2):
'''
对两个字符串进行比较,查看字符串是否相同
:param sub_str1:
:param sub_str2:
:return:
'''
if sub_str1 == sub_str2:
return False
else:
return True
def handle_dict_data(self, create_data_detail_id, data_type, dict_key):
'''
处理数据字典的路径类型,进行初始化路径组装
:return:
'''
create_detail_service = CreateDetailService()
create_dict_service = CreateDictService()
filter_detail_list = list()
filter_dict_list = list()
filter_detail_list.append(CreateDetail.create_data_detail_id == create_data_detail_id)
filter_dict_list.append(CreateDict.data_type == data_type)
filter_dict_list.append(CreateDict.dict_key == dict_key)
get_data_detail = create_detail_service.get_detail_simple_data_by_filters(self.session, filter_detail_list)
get_dict_data = create_dict_service.get_dict_simple_data_by_filters(self.session, filter_dict_list)
if get_dict_data:
if get_data_detail.file_name:
get_new_path = get_dict_data.dict_value + "." + get_data_detail.file_name + "." + get_data_detail.module_name
else:
get_new_path = get_dict_data.dict_value + "." + get_data_detail.module_name
return {"get_new_path": get_new_path, "class_name": get_data_detail.class_name}
else:
return False
def handle_result_data(self, create_data_detail_id, create_data_info_id):
"""
处理数据结果表
:return:
"""
create_detail_service = CreateDetailService()
create_info_service = CreateInfoService()
filter_detail_list = list()
filter_data_list = list()
filter_detail_list.append(CreateDetail.create_data_detail_id == create_data_detail_id)
filter_data_list.append(CreateInfo.create_data_info_id == create_data_info_id)
get_data_detail = create_detail_service.get_detail_simple_data_by_filters(self.session, filter_detail_list)
get_data_info = create_info_service.get_data_simple_data_by_filters(self.session, filter_data_list)
dict_data = {"team_name": get_data_detail.team_name, "module_name": get_data_detail.module_name,
"file_name": get_data_detail.file_name, "class_name": get_data_detail.class_name,
"method_name": get_data_detail.method_name, "request_parameter": get_data_info.request_parameter,
"method_function_detail": get_data_detail.method_function_detail}
return dict_data
def handle_method_detail(self, method_detail):
'''
对方法描述进行匹配出主要功能信息
:return:
'''
if method_detail:
get_function_detail = re.findall("功能说明: \|(.*)\|", method_detail)
else:
get_function_detail = "请补充方法注释"
if get_function_detail:
return get_function_detail
else:
get_function_details = re.findall("\s*功能(.+)", method_detail)
if get_function_details:
return get_function_details
else:
return False
def get_creator_info(self, team, method_detail):
'''
获取负责人信息
:return:
'''
if method_detail:
get_creators = re.findall("作者信息: \|(.*?)\|", method_detail)
else:
get_creators = ""
if get_creators:
return get_creators
else:
create_dict_service = CreateDictService()
filter_dict_list = list()
filter_dict_list.append(CreateDict.data_type == 5)
filter_dict_list.append(CreateDict.dict_key == team.upper())
get_data_detail = create_dict_service.get_dict_simple_data_by_filters(self.session, filter_dict_list)
if get_data_detail:
return get_data_detail.dict_value
else:
return "未找到负责人"
def get_login_creator(self, token):
'''
获取造数使用者信息
:return:
'''
if token:
return token
else:
return "administrator"
def get_team_name(self, team):
"""
各业务线对应枚举
:return:
"""
# 业务线枚举映射
create_dict_service = CreateDictService()
filter_list = list()
filter_list.append(CreateDict.dict_key == team.upper())
filter_list.append(CreateDict.data_type == 2)
get_dict_data = create_dict_service.get_dict_simple_data_by_filters(self.session, filter_list)
if get_dict_data:
return get_dict_data.dict_value
else:
return team
def get_module_name(self, team, dict_key):
"""
各业务线模块对应枚举
:return:
"""
# 业务线模块名称映射
create_dict_service = CreateDictService()
filter_list = list()
filter_list.append(CreateDict.team_name == team.upper())
filter_list.append(CreateDict.data_type == 3)
filter_list.append(CreateDict.dict_key == dict_key)
get_dict_data = create_dict_service.get_dict_simple_data_by_filters(self.session, filter_list)
if get_dict_data:
return get_dict_data.dict_value
else:
return dict_key
def get_keyword_path(self, team, file_name=""):
"""
获取某个组的关键字路径
:return:
"""
# 业务线模块名称映射
create_dict_service = CreateDictService()
filter_list = list()
filter_list.append(CreateDict.dict_key == team.upper())
filter_list.append(CreateDict.data_type == 1)
get_data_detail = create_dict_service.get_dict_simple_data_by_filters(self.session, filter_list)
get_list_path = get_data_detail.dict_value.split(".")
basic_path = os.path.dirname(os.path.abspath(__file__))
team_path = os.path.abspath(os.path.join(basic_path, '../../../../../../'))
if file_name.endswith(".py"):
return os.path.join(team_path, *get_list_path, file_name)
else:
return os.path.join(team_path, *get_list_path)
def get_model_name(self, team, dict_key):
'''
各业务线对应枚举
:param files_path:
:return:
'''
# 业务线模块名称映射
create_dict_service = CreateDictService()
filter_list = list()
filter_list.append(CreateDict.team_name == team.upper())
filter_list.append(CreateDict.dict_key == dict_key)
filter_list.append(CreateDict.data_type == 3)
get_data_detail = create_dict_service.get_dict_simple_data_by_filters(self.session, filter_list)
if get_data_detail:
return get_data_detail.dict_value
else:
return dict_key
def daily_task_update_month_number(self):
"""
定时任务,每天更新月使用次数
:return:
"""
create_detail_service = CreateDetailService()
create_number_service = CreateNumberService()
create_result_service = CreateResultService()
# 假设时间字段为 `created_time`,格式为 'YYYYMMDDHHMM'
time_field_format = '%Y%m%d%H%M'
# 获取当前时间
now = datetime.now()
# 计算一个月前的时间
one_month_ago = now - timedelta(days=30)
# 将时间转换为目标格式的字符串
one_month_ago_str = one_month_ago.strftime(time_field_format)
all_number_methods = create_number_service.get_service_number_all_no_filters(session=self.session)
if len(all_number_methods) == 0:
return ""
for number in all_number_methods:
try:
if not self.session.object_session(number):
self.session.add(number) # 重新绑定对象到会话
get_number = create_result_service.get_service_result_numbers_by_month(session=self.session,
create_data_detail_id=number.create_data_detail_id,
one_month_ago_str=one_month_ago_str)
if get_number is None:
get_number = 0
else:
get_number = get_number[0]
create_number_service.update_service_number_data(session=self.session,
update_info={"month_number": get_number},
create_data_detail_id=number.create_data_detail_id)
except SQLAlchemyError as e:
self.session.rollback()
finally:
self.session.close()
return {"code": 20000, "message": "更新成功"}
class LambdaCheck(ast.NodeVisitor):
def __init__(self):
self.illegal_args_list = []
self._cur_file = None
self._cur_lambda_args = []
def set_cur_file(self, cur_file):
assert os.path.isfile(cur_file), cur_file
self._cur_file = os.path.realpath(cur_file)
def visit_Lambda(self, node):
"""
lambda 闭包检查原则:
只需检测lambda expr body中args是否引用了lambda args list之外的参数
"""
self._cur_lambda_args = [a.id for a in node.args.args]
print(astunparse.unparse(node))
self.get_lambda_body_args(node.body)
self.generic_visit(node)
def record_args(self, name_node):
if isinstance(name_node, ast.Name) and name_node.id not in self._cur_lambda_args:
self.illegal_args_list.append((self._cur_file, 'line no:%s' % name_node.lineno, 'var:%s' % name_node.id))
def _is_args(self, node):
if isinstance(node, ast.Name):
self.record_args(node)
return True
if isinstance(node, ast.Call):
map(self.record_args, node.args)
return True
return False
def get_lambda_body_args(self, node):
if self._is_args(node): return
# for cnode in ast.walk(node):
for cnode in ast.iter_child_nodes(node):
if not self._is_args(cnode):
self.get_lambda_body_args(cnode)
list_info = [] # 存爬取出来的方法列表
class CodeVisitor(ast.NodeVisitor):
def __init__(self, file, file_name):
self.file = file
self.file_name = file_name
self.dict_info = {}
def visit_BinOp(self, node): # 这个函数的访问是由于 Visit_FunctionDef的先访问再generic_visit才访问的
# print('Bin') # 如果Visit_FunctionDef中没有generic_visit的话则这个函数是不会访问的
if isinstance(node.op, ast.Add):
node.op = ast.Sub()
self.generic_visit(node)
def visit_ClassDef(self, node):
# print('Class Name: %s' % node.name)
global class_name
class_name = node.name
self.generic_visit(node) # ClassDef中还包含有 BinOp,因此会进去visit BinOP
# print(dict_info)
def visit_FunctionDef(self, node):
# print('Function Name: %s' % node.name)
self.dict_info = {}
is_kwargs = node.args.kwarg
args = list([arg.arg for arg in node.args.args])
if is_kwargs is not None:
args.append("is_kwargs")
# print(str(args))
self.dict_info["file_name"] = self.file_name
self.dict_info["class_name"] = class_name
self.dict_info["module_name"] = self.file
self.dict_info["method_name"] = node.name
self.dict_info["request_parameter"] = str(args)
self.dict_info["method_detail"] = ast.get_docstring(node)
# print(ast.get_docstring(node))
list_info.append(self.dict_info)
self.generic_visit(node) # FunctionDef中还包含有 BinOp,因此会进去visit BinOP
def run(files_path, end_file, remove_file):
"""
用于执行获取各个类中的方法与方法注解
:param files_path:需要爬取关键字的路径
:param end_file:爬取需要什么结尾的文件
:param remove_file:不需要爬取的文件路径的文件名称
:return:
"""
if files_path.endswith(end_file):
checker = LambdaCheck()
checker.set_cur_file(files_path)
with open(files_path, 'rb') as f:
root_node = ast.parse(f.read())
visitor = CodeVisitor(os.path.basename(files_path), file_name="")
visitor.visit(root_node)
else:
for root, dirs, files in os.walk(files_path):
file_name = os.path.basename(root)
# if re.findall(remove_file, root):
# continue
py_files = filter(lambda file: file.endswith(end_file), files)
checker = LambdaCheck()
for file in py_files:
file_path = os.path.join(root, file)
checker.set_cur_file(file_path)
with open(file_path, 'rb') as f:
root_node = ast.parse(f.read())
# print(file_name)
visitor = CodeVisitor(file, root)
visitor.visit(root_node)
return list_info
def call_method(class_name, method_info):
'''
主要用于动态调用方法,
:param class_name:传入定义后的类名
:param method_name:传入方法与参数 eg:{"methodName":"kw_astwb_create_classes","requestParameter":{"user":"qiaoxinjiu","post_data_input={'studentMin':'2'}"}
:return:
'''
method_name = method_info.get("methodName")
if method_name:
request_data = method_info.get("requestParameter")
return methodcaller(method_name, **request_data)(class_name())
else:
return {"方法名输入错误"}
def get_time():
"""
获取当前时间
:return:
"""
# now_time = datetime.datetime.now().strftime("%Y%m%d%H%M%S.%f")
now_time = time.strftime("%Y%m%d%H%M%S", time.localtime())
return now_time
def update_time():
"""
录入数据库时间
:return:
"""
import datetime
now_time = datetime.datetime.now().strftime("%Y%m%d%H%M%S.%f")
return now_time
def get_uuid():
'''
获取uuid
:return:
'''
import uuid
test_id = str(uuid.uuid1())
return test_id

View File

@@ -0,0 +1,304 @@
# encoding: UTF-8
from ....common.sqlSession import SqlSession
from ..service.createDataDetailService import CreateDetailService
from ..service.createDataInfoService import CreateInfoService
from ..service.createDataGroupService import CreateGroupService
from ..service.createDataNumberService import CreateNumberService
from ..model.createDetailInfo import CreateDetail
from ..controller import commonController
from ..controller.createDataResultController import CreateDataResultController
from ..controller.createDataNumberController import CreateDataNumberController
from logger import logger
import time
import json
import importlib
import sys
class CreateDataDetailController(object):
def __init__(self, req_json, token):
self.session = SqlSession()
self.page = req_json.get('pageNo')
self.limit = req_json.get('pageSize')
self.create_data_detail_id = req_json.get('create_data_detail_id')
self.create_data_info_id = req_json.get('create_data_info_id')
self.team_name = req_json.get('team_name')
self.file_name = req_json.get('file_name')
self.module_name = req_json.get('module_name')
self.method_name = req_json.get('method_name')
self.detail_name = req_json.get('detail_name')
self.method_detail = req_json.get('method_detail')
self.method_function_detail = req_json.get('method_function_detail')
self.request_parameter = req_json.get('request_parameter')
self.dict_key = req_json.get('dict_key')
self.get_tag = req_json.get('get_tag')
self.email = req_json.get('email')
self.status = req_json.get('status')
self.token = token
self.del_flag = 0
self.req_json = req_json
def create_detail_data(self):
"""
对造数列表进行新增
:return:
"""
def update_detail_data(self):
"""
对造数信息进行修改
:return:
"""
def delete_detail_data(self):
"""
对造数信息进行删除
:return:
"""
create_detail_service = CreateDetailService()
del_info = create_detail_service.delete_service_detail_data(self.session,
create_data_detail_id=self.create_data_detail_id)
return del_info
def count_sucess_rate(self, total_number, right_number):
"""
计算成功率
:return:
"""
if total_number == 0:
return 0
else:
get_result = right_number / total_number
return round(get_result, 2)
def get_number_data(self, create_data_detail_id):
"""
根据造数详情id查询对应的次数信息
:return:
"""
create_number_service = CreateNumberService()
get_query_info, message = create_number_service.get_service_number_by_id(session=self.session,
create_data_detail_id=create_data_detail_id)
if message:
if get_query_info:
return {"total_number": get_query_info.total_number,
"success_rate": self.count_sucess_rate(get_query_info.total_number,
get_query_info.right_number)}
else:
return {"total_number": 0,
"success_rate": 0}
else:
return {"total_number": 0,
"success_rate": 0}
def approve_detail_status(self):
"""
审核状态
:return:
"""
create_detail_service = CreateDetailService()
update_info = {"status": 1}
err_msg = create_detail_service.update_service_detail_data(self.session, update_info,
self.create_data_detail_id)
if err_msg:
return {"code": 40010, "message": err_msg}
else:
return {"code": 20000, "message": "审核成功"}
def get_detail_query_advance(self):
"""
列表页查询
:return:
"""
create_group_service = CreateGroupService()
if self.page is None:
self.page = 1
if self.limit is None:
self.limit = 10
create_detail_service = CreateDetailService()
common_controller = commonController.CommonController()
get_group_data = create_group_service.get_service_group_info_data_by_email(self.session, email=self.email)
list_group_user = ["ALL"]
filter_list = []
team_name = self.team_name
file_name = self.file_name
module_name = self.module_name
detail_name = self.detail_name
status = self.status
if team_name:
filter_list.append(CreateDetail.team_name == team_name)
if file_name:
filter_list.append(CreateDetail.file_name == file_name)
if module_name:
filter_list.append(CreateDetail.module_name == module_name)
if detail_name:
filter_list.append(CreateDetail.method_function_detail.like('%' + self.detail_name + '%'))
if status:
filter_list.append(CreateDetail.status == 1)
if filter_list:
if get_group_data:
list_group_user.append(get_group_data.team_name)
detail_list, total = create_detail_service.get_detail_list_by_filters_team_page(self.session,
filter_list,
list_group_user,
self.page, self.limit)
else:
if get_group_data:
list_group_user.append(get_group_data.team_name)
detail_list, total = create_detail_service.get_detail_list_no_filters_team_page(self.session,
list_group_user, self.page,
self.limit)
ret_ls = []
for obj in detail_list:
ret_ls.append({
'create_data_detail_id': obj.create_data_detail_id,
'team_name': common_controller.get_team_name(obj.team_name),
'total_number': self.get_number_data(obj.create_data_detail_id).get("total_number"),
'success_rate': self.get_number_data(obj.create_data_detail_id).get("success_rate"),
'file_name': common_controller.get_module_name(team=obj.team_name, dict_key=obj.file_name),
'class_name': obj.class_name,
'module_name': obj.module_name,
'method_name': obj.method_name,
'method_function_detail': obj.method_function_detail,
'method_detail': obj.method_detail,
'status': obj.status,
'creator': obj.creator,
'created_time': obj.created_time,
'modified_time': obj.modified_time
})
return {"list": ret_ls, "total": total}
def get_request_parameter(self, request_parameter):
"""
组成详情获取组装请求参数信息
:return:
"""
try:
get_dict_parameter = request_parameter
# get_dict_parameter = json.loads(request_parameter)
dict_data = {}
for key, value in get_dict_parameter.items():
if key == "post_data_input":
dict_data.update(get_dict_parameter.get("post_data_input"))
else:
dict_data[key] = value
return dict_data
except:
return request_parameter
def get_detail_advance(self):
"""
关键字基础信息详情页查询
:return:
"""
create_info_service = CreateInfoService()
get_data_info = create_info_service.get_create_data_info_by_detail_id(session=self.session,
detail_id=self.create_data_detail_id)
create_detail_service = CreateDetailService()
get_detail_info = create_detail_service.get_create_detail_info_by_id(session=self.session,
create_data_detail_id=self.create_data_detail_id)
common_controller = commonController.CommonController()
_data = {'create_data_detail_id': get_data_info.create_data_detail_id,
'create_data_info_id': get_data_info.create_data_info_id,
'team_name': get_detail_info.team_name,
'file_name': common_controller.get_model_name(team=get_detail_info.team_name,
dict_key=get_detail_info.file_name),
'class_name': get_data_info.class_name,
'method_name': get_data_info.method_name,
'method_detail': get_detail_info.method_detail,
'method_function_detail': get_detail_info.method_function_detail,
'creator': get_detail_info.creator,
# 'request_parameter':unescape(str(_edit.request_parameter)),
'request_parameter': self.get_request_parameter(get_data_info.request_parameter),
'created_time': get_data_info.created_time,
'modified_time': get_data_info.modified_time}
return _data
def run_keyword_data(self):
"""
执行造数
:return:
"""
create_info_service = CreateInfoService()
common_controller = commonController.CommonController()
data_type = 1
# creator = dict_data["creator"]
creator = common_controller.get_login_creator(self.email)
time.sleep(2)
# 查询请求参数的表用于判断传入的类名与方法名是否正确
get_data_info = create_info_service.get_create_data_info_by_id(session=self.session,
sid=self.create_data_info_id)
chushi_name = get_data_info.class_name
try:
get_parameter = json.loads(self.request_parameter)
except:
return {"code": 40009, "message": "请求参数格式不正确"}
create_data_result_id = commonController.get_uuid()
created_time = commonController.get_time()
modified_time = commonController.get_time()
get_result_info = common_controller.handle_result_data(create_data_detail_id=self.create_data_detail_id,
create_data_info_id=self.create_data_info_id)
req_json = {"create_data_detail_id": self.create_data_detail_id,
"method_name": get_result_info.get("method_name")}
create_number_controller = CreateDataNumberController(req_json=req_json, token=self.token)
req_json = {"create_data_result_id": create_data_result_id,
"create_data_info_id": self.create_data_info_id,
"create_data_detail_id": self.create_data_detail_id,
"team_name": get_result_info.get("team_name"),
"file_name": get_result_info.get("file_name"),
"module_name": get_result_info.get("module_name"),
"class_name": get_result_info.get("class_name"),
"method_name": get_result_info.get("method_name"),
"tag": self.get_tag,
"status": 0,
"method_function_detail": get_result_info.get("method_function_detail"),
"request_parameter": get_parameter, "result_info": "",
"creator": creator, "created_time": created_time, "modified_time": modified_time}
add_result_controller = CreateDataResultController(req_json=req_json, token=self.token)
add_result_id = add_result_controller.create_result_data(req_json)
try:
if get_data_info:
dict_key = self.dict_key
get_dict_info = common_controller.handle_dict_data(create_data_detail_id=self.create_data_detail_id,
data_type=data_type, dict_key=dict_key.upper())
if get_dict_info is False:
return {"code": 500, "message": "字典中不存在该信息"}
method_info = {"methodName": get_data_info.method_name,
"requestParameter": get_data_info.request_parameter}
class_name = get_data_info.class_name
print("get_new_path:", get_dict_info.get("get_new_path"))
test = importlib.import_module(get_dict_info.get("get_new_path"))
get_data_info.class_name = getattr(test, get_dict_info.get("class_name")) # 实例化case
result_data = commonController.call_method(class_name=get_data_info.class_name,
method_info=method_info) # 返回造数结果
try:
if isinstance(result_data, str):
is_json = False
else:
is_json = True
get_result = json.dumps(result_data, ensure_ascii=False)
except:
is_json = False
get_result = result_data
add_result_controller.update_result_data(sid=add_result_id, status=1, result_info=str(result_data))
get_data_info.class_name = class_name
create_number_controller.update_number_data(is_right=True)
data = {"message": "造数成功", "data": {"create_data_result_id": create_data_result_id, "data": get_result,
"is_json": is_json}}
else:
data = {"message": "传入请求id未查询到相应信息"}
return data
except:
get_data_info.class_name = chushi_name
s = sys.exc_info()
create_number_controller.update_number_data(is_right=False)
add_result_controller.update_result_data(sid=add_result_id, status=2, result_info=str(s[1]))
logger.info('Error "%s" happend on line %d' % (s[1], s[2].tb_lineno))
data = {"message": "造数出现异常,异常为:{}".format(str(s[1]))}
return data

View File

@@ -0,0 +1,180 @@
# encoding: UTF-8
from datetime import datetime
from ....common.sqlSession import SqlSession
from ..controller import commonController
from ..service.createDataDictService import CreateDictService
from ..service.createDataGroupService import CreateGroupService
from ..model.createDictInfo import CreateDict
from ..controller.commonController import CommonController
from logger import logger
class CreateDataDictController(object):
def __init__(self, req_json, token):
self.session = SqlSession()
self.pageNo = req_json.get('pageNo')
self.pageSize = req_json.get('pageSize')
self.create_dict_data_id = req_json.get('create_dict_data_id')
self.team_name = req_json.get('team_name')
self.data_type = req_json.get('data_type')
self.dict_key = req_json.get('dict_key')
self.dict_value = req_json.get('dict_value')
self.email = req_json.get('email')
self.token = token
self.del_flag = 0
self.req_json = req_json
def create_dict_data(self):
"""
对数据字典进行新增
:return:
"""
common_controller = commonController.CommonController()
# 获取抓取的方法名称,描述等
create_dict_id = commonController.get_uuid()
created_time = commonController.get_time()
modified_time = commonController.get_time()
create_dict_service = CreateDictService()
filter_list = list()
filter_list.append(CreateDict.dict_value == self.team_name)
filter_list.append(CreateDict.data_type == 2)
# 获取抓取的方法名称,描述等
get_dict_value_data = create_dict_service.get_dict_simple_data_by_filters(self.session, filter_list)
if get_dict_value_data:
dict_keys = get_dict_value_data.dict_key
else:
dict_keys = self.dict_key
filter_list_data = list()
filter_list_data.append(CreateDict.dict_key == self.dict_key)
filter_list_data.append(CreateDict.data_type == self.data_type)
filter_list_data.append(CreateDict.team_name == dict_keys)
filter_list_data.append(CreateDict.data_type == 2)
get_dict_data = create_dict_service.get_dict_simple_data_by_filters(self.session, filter_list_data)
add_data = {
"create_dict_data_id": create_dict_id,
"data_type": self.data_type,
"team_name": self.team_name,
"dict_key": self.dict_key,
"dict_value": self.dict_value,
"del_flag": 0,
"created_time": created_time,
"modified_time": modified_time
}
create_data_info_id, err_msg = create_dict_service.create_service_dict_data(session=self.session,
create_info=add_data)
return err_msg
def update_dict_data(self):
"""
对数据字典进行修改
:return:
"""
create_dict_service = CreateDictService()
filter_list = list()
filter_list.append(CreateDict.dict_value == self.team_name)
filter_list.append(CreateDict.data_type == 2)
# 获取抓取的方法名称,描述等
get_dict_value_data = create_dict_service.get_dict_simple_data_by_filters(self.session, filter_list)
if get_dict_value_data:
dict_keys = get_dict_value_data.dict_key
else:
dict_keys = self.dict_key
filter_data_list = list()
filter_data_list.append(CreateDict.team_name == dict_keys)
filter_data_list.append(CreateDict.data_type == self.data_type)
filter_data_list.append(CreateDict.dict_key == self.dict_key)
get_dict_data = create_dict_service.get_dict_simple_data_by_filters(self.session, filter_data_list)
if get_dict_data:
edit_dict_id = self.create_dict_data_id
filter_edit_list = list()
filter_edit_list.append(CreateDict.create_dict_data_id == edit_dict_id)
get_edit_data = create_dict_service.get_dict_simple_data_by_filters(self.session, filter_edit_list)
edit_data = {'dict_value': self.dict_value, 'team_name': get_edit_data.team_name,
'dict_key': get_edit_data.dict_key}
err_msg = create_dict_service.update_service_dict_data(session=self.session, update_info=edit_data,
create_dict_data_id=edit_dict_id)
if err_msg:
return err_msg
else:
add_data = {
'data_type': self.data_type,
'team_name': self.team_name,
'dict_key': self.dict_key,
'dict_value': self.dict_value,
}
create_data_info_id, err_msg = create_dict_service.create_service_dict_data(session=self.session,
create_info=add_data)
if err_msg:
return err_msg
def get_dict_list_data(self):
"""
数据字典列表页查询
:return:
"""
if self.pageNo is None:
self.pageNo = 1
if self.pageSize is None:
self.pageSize = 10
create_dict_service = CreateDictService()
common_controller = CommonController()
create_group_service = CreateGroupService()
get_group_data = create_group_service.get_service_group_info_data_by_email(self.session, email=self.email)
list_group_user = ["ALL"]
filter_list = []
if self.team_name:
filter_list.append(CreateDict.team_name == self.team_name)
if self.dict_value:
filter_list.append(CreateDict.dict_value.like('%' + self.dict_value + '%'))
filter_list.append(CreateDict.data_type == 3)
if get_group_data:
list_group_user.append(get_group_data.team_name)
dict_list, total = create_dict_service.get_dict_list_by_team_filters_page(self.session, filter_list,
list_group_user,
self.pageNo, self.pageSize)
ret_ls = []
for obj in dict_list:
ret_ls.append({
'create_dict_data_id': obj.create_dict_data_id,
'team_name': common_controller.get_team_name(obj.team_name),
'data_type': obj.data_type,
'dict_key': obj.dict_key,
'dict_value': obj.dict_value,
'created_time': obj.created_time,
'modified_time': obj.modified_time,
})
return {"list": ret_ls, "total": total}
def get_team_name(self):
"""
获取各个业务线的数据字典,用于查询
:return:
"""
create_dict_service = CreateDictService()
create_group_service = CreateGroupService()
get_group_data = create_group_service.get_service_group_info_data_by_email(self.session, email=self.email)
list_group_user = ["ALL"]
filter_list = list()
filter_list.append(CreateDict.data_type == self.data_type)
if self.team_name:
filter_list.append(CreateDict.team_name == self.team_name)
if get_group_data:
list_group_user.append(get_group_data.team_name)
get_dict_data = create_dict_service.get_dict_list_by_team_filters_page(self.session, filter_list,
list_group_user, page=1,
limit=10)
list_data = []
if get_dict_data:
for value in get_dict_data[0]:
dict_value = {}
get_data_key = value.dict_key
get_data_value = value.dict_value
dict_value["value"] = get_data_key
dict_value["label"] = get_data_value
list_data.append(dict_value)
return list_data
else:
return get_dict_data

View File

@@ -0,0 +1,94 @@
# encoding: UTF-8
from ....common.sqlSession import SqlSession
from ..service.createDataInfoService import CreateInfoService
from ..controller import commonController
from ..model.createDataInfo import CreateInfo
from logger import logger
import json
class CreateDataInfoController(object):
def __init__(self, req_json, token):
self.session = SqlSession()
self.create_data_info_id = req_json.get('create_data_info_id')
self.create_data_detail_id = req_json.get('create_data_detail_id')
self.className = req_json.get('className')
self.methodName = req_json.get('methodName')
self.request_parameter = req_json.get('request_parameter')
self.token = token
self.del_flag = 0
self.req_json = req_json
def create_info_data(self):
"""
对造数详情进行新增
:return:
"""
get_time = commonController.get_time()
get_request_parameter = json.loads(self.request_parameter)
request_parameter = self.get_method_request_parameter(create_data_info_id=self.create_data_info_id,
request_param=get_request_parameter)
get_uuid = commonController.get_uuid()
add_data = {
'create_data_info_id': get_uuid,
'create_data_detail_id': self.create_data_detail_id,
'class_name': self.className,
'method_name': self.methodName,
'request_parameter': request_parameter,
'del_flag': self.del_flag,
'created_time': get_time,
'modified_time': get_time
}
create_info_service = CreateInfoService()
create_data_info_id, err_msg = create_info_service.create_service_info_data(session=self.session,
create_info=add_data)
if err_msg:
return err_msg
def update_info_data(self):
"""
编辑请求参数信息表
:return:
"""
get_time = commonController.get_time()
get_request_parameter = json.loads(self.request_parameter)
request_parameter = self.get_method_request_parameter(create_data_info_id=self.create_data_info_id,
request_param=get_request_parameter)
edit_data_id = self.create_data_info_id
edit_data = {'class_name': self.className, 'method_name': self.methodName,
'request_parameter': request_parameter,'modified_time':get_time}
create_info_service = CreateInfoService()
err_msg = create_info_service.update_service_info_data(session=self.session, update_info=edit_data,
create_data_info_id=edit_data_id)
if err_msg:
return err_msg
def get_method_request_parameter(self, create_data_info_id, request_param):
"""
组装方法的请求参数
:param parameter:
:return:
"""
# 定义请求的json
json_data = {}
# 查询抓取的请求参数信息
create_info_service = CreateInfoService()
get_data_info = create_info_service.get_create_data_info_by_id(session=self.session, sid=create_data_info_id)
get_db_parameter = get_data_info.request_parameter
# 判断如果请求参数里面含有**kwargs组装请求
if "is_kwargs" in get_db_parameter:
json_data = request_param
else:
# 定义请求的json如果是json格式的参数则组装json
post_data_input = {}
for key, value in request_param.items():
if key in get_db_parameter.keys():
json_data[key] = value
else:
post_data_input[key] = value
if "post_data_input" in get_db_parameter:
json_data["post_data_input"] = post_data_input
return json_data

View File

@@ -0,0 +1,134 @@
# encoding: UTF-8
from ....common.sqlSession import SqlSession
from ..service.createDataNumberService import CreateNumberService
from ..service.createDataDetailService import CreateDetailService
from ..service.createDataInfoService import CreateInfoService
from ..controller import commonController
class CreateDataNumberController(object):
def __init__(self, req_json, token):
self.session = SqlSession()
self.create_data_detail_id = req_json.get('create_data_detail_id')
self.method_name = req_json.get('method_name')
self.token = token
self.del_flag = 0
self.req_json = req_json
def get_number_data(self, create_data_detail_id):
"""
根据id查询对应的次数信息
:return:
"""
create_number_service = CreateNumberService()
get_query_info = create_number_service.get_service_number_by_id(session=self.session,
create_data_detail_id=create_data_detail_id)
def create_result_data(self, add_data):
"""
对造数结果数据进行统计
:return:
"""
create_number_service = CreateNumberService()
create_data_info_id = create_number_service.create_service_number_data(session=self.session,
create_info=add_data)
return create_data_info_id
def update_number_data(self, is_right):
"""
更新造数使用次数表
"""
create_number_service = CreateNumberService()
try:
get_db_data_number, success = create_number_service.get_service_number_by_id(session=self.session,
create_data_detail_id=self.create_data_detail_id)
if success:
if get_db_data_number:
total_number = get_db_data_number.total_number
error_number = get_db_data_number.error_number
right_number = get_db_data_number.right_number
month_number = get_db_data_number.month_number
update_total_number = total_number + 1
update_data = {"total_number": update_total_number}
if is_right:
update_right_number = right_number + 1
update_month_number = month_number + 1
update_data["right_number"] = update_right_number
update_data["month_number"] = update_month_number
else:
update_error_number = error_number + 1
update_data["error_number"] = update_error_number
create_number_service = CreateNumberService()
update_data_info_id = create_number_service.update_service_number_data(
create_data_detail_id=self.create_data_detail_id, session=self.session,
update_info=update_data)
return "更新数据成功"
else:
get_uuid = commonController.get_uuid()
get_time = commonController.get_time()
if is_right:
right_number = 1
error_number = 0
month_number = 1
else:
right_number = 0
error_number = 1
month_number = 0
add_data = {
'id': get_uuid,
'create_data_detail_id': self.create_data_detail_id,
'method_name': self.method_name,
'total_number': 1,
'error_number': error_number,
'right_number': right_number,
'month_number': month_number,
'del_flag': self.del_flag,
'created_time': get_time,
'modified_time': get_time,
}
self.create_result_data(add_data=add_data)
else:
return "更新数据失败"
except:
return "更新数据失败"
def do_history_data(self):
"""
处理历史数据
:return:
"""
create_detail_service = CreateDetailService()
create_number_service = CreateNumberService()
create_data_number_id = commonController.get_uuid()
created_time = commonController.get_time()
modified_time = commonController.get_time()
get_data_detail = create_detail_service.get_all_detail_list_no_filters_page(session=self.session)
list_update_data = []
for detail_info in get_data_detail:
get_create_number = create_number_service.get_service_number_by_id(session=self.session,
create_data_detail_id=detail_info.create_data_detail_id)
if get_create_number:
list_update_data.append(detail_info.create_data_detail_id)
create_number_service.update_service_number_data(session=self.session,
update_info={"method_name": detail_info.method_name},
create_data_detail_id=detail_info.create_data_detail_id)
else:
add_data = {
"id": create_data_number_id,
"create_data_detail_id": detail_info.create_data_detail_id,
"method_name": detail_info.method_name,
"total_number": 0,
"error_number": 0,
"right_number": 0,
"del_flag": 0,
"created_time": created_time,
"modified_time": modified_time,
}
create_number_service.create_service_number_data(session=self.session, create_info=add_data)
list_update_data.append(detail_info.create_data_detail_id)
return list_update_data

View File

@@ -0,0 +1,186 @@
# encoding: UTF-8
from ....common.sqlSession import SqlSession
from ..service.createDataResultService import CreateResultService
from ..service.createDataInfoService import CreateInfoService
from ..service.createDataDetailService import CreateDetailService
from ..service.createDataGroupService import CreateGroupService
from ..controller import commonController
from ..model.createResultInfo import CreateResult
from logger import logger
import json
class CreateDataResultController(object):
def __init__(self, req_json, token):
self.session = SqlSession()
self.page = req_json.get('pageNo')
self.limit = req_json.get('pageSize')
self.create_data_result_id = req_json.get('create_data_result_id')
self.create_data_info_id = req_json.get('create_data_info_id')
self.create_data_detail_id = req_json.get('create_data_detail_id')
self.team_name = req_json.get('team_name')
self.file_name = req_json.get('file_name')
self.module_name = req_json.get('module_name')
self.class_name = req_json.get('class_name')
self.method_name = req_json.get('method_name')
self.tag = req_json.get('get_tag')
self.status = req_json.get('status')
self.method_function_detail = req_json.get('method_function_detail')
self.detail_name = req_json.get('detail_name')
self.request_parameter = req_json.get('request_parameter')
self.result_info = req_json.get('result_info')
self.creator = req_json.get('creator')
self.email = req_json.get('email')
self.token = token
self.del_flag = 0
self.req_json = req_json
def create_result_data(self, add_data):
"""
对造数结果进行新增
:return:
"""
add_data["del_flag"] = self.del_flag
create_result_service = CreateResultService()
create_data_info_id = create_result_service.create_result_data(session=self.session,
create_info=add_data)
return create_data_info_id
def update_result_data(self, sid, status, result_info):
"""
对返回结果进行编辑
"""
edit_data = {'result_info': result_info, 'status': status}
create_result_service = CreateResultService()
err_msg = create_result_service.update_service_result_data(session=self.session, update_info=edit_data,
sid=sid)
if err_msg:
return err_msg
def get_method_request_parameter(self, create_data_info_id, request_param):
"""
组装方法的请求参数
:param parameter:
:return:
"""
# 定义请求的json
json_data = {}
# 查询抓取的请求参数信息
create_info_service = CreateInfoService()
get_data_info = create_info_service.get_create_data_info_by_id(session=self.session, sid=create_data_info_id)
get_db_parameter = get_data_info.request_parameter
# 判断如果请求参数里面含有**kwargs组装请求
if "is_kwargs" in get_db_parameter:
json_data = request_param
else:
# 定义请求的json如果是json格式的参数则组装json
post_data_input = {}
for key, value in request_param.items():
if key in get_db_parameter.keys():
json_data[key] = value
else:
post_data_input[key] = value
if "post_data_input" in get_db_parameter:
json_data["post_data_input"] = post_data_input
return json_data
def get_result_query_list(self):
"""
结果列表页查询
:return:
"""
create_group_service = CreateGroupService()
if self.page is None:
self.page = 1
if self.limit is None:
self.limit = 10
create_result_service = CreateResultService()
common_controller = commonController.CommonController()
get_group_data = create_group_service.get_service_group_info_data_by_email(self.session, email=self.email)
filter_list = []
team_name = self.team_name
get_tag = self.tag
detail_name = self.detail_name
list_group_user = ["ALL"]
if team_name:
filter_list.append(CreateResult.team_name == team_name)
if detail_name:
filter_list.append(CreateResult.method_function_detail.like('%' + detail_name + '%'))
if get_tag:
filter_list.append(CreateResult.tag.like('%' + get_tag + '%'))
if filter_list:
if get_group_data:
list_group_user.append(get_group_data.team_name)
detail_list, total = create_result_service.get_result_list_by_team_filters_page(self.session, filter_list,
list_group_user,
self.page, self.limit)
else:
if get_group_data:
list_group_user.append(get_group_data.team_name)
detail_list, total = create_result_service.get_result_list_no_team_filters_page(self.session,
list_group_user, self.page,
self.limit)
ret_ls = []
for obj in detail_list:
ret_ls.append({
'create_data_result_id': obj.create_data_result_id,
'create_data_info_id': obj.create_data_info_id,
'create_data_detail_id': obj.create_data_detail_id,
'team_name': common_controller.get_team_name(obj.team_name),
'request_parameter': json.dumps(obj.request_parameter),
'result_info': obj.result_info,
'tag': obj.tag,
'class_name': obj.class_name,
'module_name': obj.module_name,
'method_name': obj.method_name,
'method_function_detail': obj.method_function_detail,
'creator': obj.creator,
'created_time': obj.created_time,
'modified_time': obj.modified_time
})
return {"list": ret_ls, "total": total}
def get_result_advance(self):
"""
关键字造数结果详情页查询
:return:
"""
create_result_service = CreateResultService()
get_data_result = create_result_service.get_create_result_by_id(session=self.session,
create_data_result_id=self.create_data_result_id)
create_detail_service = CreateDetailService()
get_detail_info = create_detail_service.get_create_detail_info_by_id(session=self.session,
create_data_detail_id=get_data_result.create_data_detail_id)
common_controller = commonController.CommonController()
try:
is_json = True
get_result = json.dumps(eval(get_data_result.result_info), ensure_ascii=False)
except:
is_json = False
get_result = get_data_result.result_info
_data = {'create_data_result_id': get_data_result.create_data_result_id,
'create_data_detail_id': get_data_result.create_data_detail_id,
'team_name': get_detail_info.team_name,
'file_name': common_controller.get_model_name(team=get_detail_info.team_name,
dict_key=get_detail_info.file_name),
'module_name': get_data_result.module_name,
'class_name': get_data_result.class_name,
'method_name': get_data_result.method_name,
'method_detail': get_detail_info.method_detail,
'request_parameter': get_data_result.request_parameter,
# 'method_function_detail': CreateDictData.query.filter_by(del_flag=0, data_type=2,
# dict_key=get_detail_info.method_name).first().dict_value,
'method_function_detail': get_detail_info.method_function_detail,
'result_info': get_result,
'creator': get_data_result.creator,
'get_tag': get_data_result.tag,
'is_json': is_json,
'created_time': get_data_result.created_time,
'modified_time': get_data_result.modified_time}
return _data

View File

@@ -0,0 +1,112 @@
# encoding: UTF-8
from ....common.sqlSession import SqlSession
from ..service.createDataDictService import CreateDictService
from ..controller import commonController
from logger import logger
from ..model.createDictInfo import CreateDict
import os
from base_framework.public_tools.eureka_api import EurekaAPI
import configparser
class ScrapyController(object):
def __init__(self, req_json, token):
self.session = SqlSession()
self.file_name = req_json.get('fileName')
self.get_team = req_json.get('team')
self.team_name = self.get_team.upper()
self.username = req_json.get('username')
self.password = req_json.get('password')
self.token = token
self.del_flag = 0
self.req_json = req_json
def scrapy_enter_db(self):
"""
对抓取的数据进行入库
:return:
"""
get_common_controller = commonController.CommonController()
create_dict_service = CreateDictService()
filter_list = list()
filter_list.append(CreateDict.dict_key == self.team_name)
filter_list.append(CreateDict.data_type == 1)
get_team_data = create_dict_service.get_dict_simple_data_by_filters(self.session, filter_list)
if get_team_data:
get_path = get_common_controller.get_keyword_path(team=self.team_name, file_name=self.file_name) # 获取路径值
print("get_path:", get_path)
team_name = self.team_name
get_team = team_name.upper()
if get_team == "ALL":
get_list_data = commonController.run(files_path=get_path, end_file="_public_business.py",
remove_file="common")
else:
get_list_data = commonController.run(files_path=get_path, end_file=".py", remove_file="")
get_scrapy_info = get_common_controller.create_all_info(team_name=self.team_name, list_detail=get_list_data)
if get_scrapy_info:
err_msg = "抓取成功"
else:
err_msg = "未成功抓取数据,请重试"
else:
err_msg = "请检查数据字典,输入正确的组名"
return err_msg
def get_git_pull(self):
"""
拉取git上的代码到本地
:return:
"""
create_dict_service = CreateDictService()
filter_list = list()
filter_list.append(CreateDict.team_name == self.team_name)
filter_list.append(CreateDict.data_type == 4)
get_dict_data = create_dict_service.get_dict_simple_data_by_filters(self.session, filter_list)
if get_dict_data:
base = get_dict_data.dict_value
else:
base = self.team_name
username = self.username
password = self.password
print(base,"----base----")
basic_path = os.path.dirname(os.path.abspath(__file__))
base_path = os.path.abspath(os.path.join(basic_path, '../../../../{}'.format("base_framework")))
team_path = os.path.abspath(os.path.join(basic_path, '../../../../{}'.format(base)))
try:
os.system(f"cd " + base_path + "&& git stash drop")
os.system(f"cd " + base_path + "&& git pull")
os.system(f"cd " + team_path + "&& git stash drop")
os.system(f"cd " + team_path + "&& git pull")
err_msg = ""
except:
err_msg = "拉取代码失败"
return err_msg
def get_eureka_ip(self):
"""
重新获取eureka_ip地址重写下ip
:return:
"""
create_dict_service = CreateDictService()
filter_list = list()
filter_list.append(CreateDict.team_name == self.team_name)
filter_list.append(CreateDict.data_type == 4)
get_dict_data = create_dict_service.get_dict_simple_data_by_filters(self.session, filter_list)
if get_dict_data:
team = get_dict_data.dict_value
else:
team = self.team_name
get_eureka = EurekaAPI()
get_eureka.team = team
upper_team = team.upper()
get_eureka.get_all_server_ip_from_eureka()
get_eureka.get_all_server_ip_from_eureka(eureka="EC")
file_path = os.path.dirname(os.path.abspath(__file__))
env_choose_path = os.path.join(file_path, "../../../../../base_config/env_choose.ini")
cof = configparser.ConfigParser()
cof.read(env_choose_path, encoding='utf-8')
cof.set(section="run_evn_name", option="current_team", value=upper_team)
with open(env_choose_path, 'w') as fw: # 循环写入
cof.write(fw)
test_vi_path = os.path.join(file_path, "test_vi.py")
os.system(f"echo 1 >> {test_vi_path}")
return True

View File

@@ -0,0 +1,28 @@
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1

View File

@@ -0,0 +1,38 @@
# encoding: UTF-8
from ....common.sqlSession import SqlSession
import requests
from base_framework.public_tools.sqlhelper import MySqLHelper
sql = MySqLHelper()
class UserController(object):
def __init__(self, req_json, token):
self.session = SqlSession()
self.username = req_json.get('username')
self.password = req_json.get('password')
self.token = token
self.del_flag = 0
self.req_json = req_json
def get_user_email(self):
"""
获取用户邮箱信息
:return:
"""
username = self.username
password = self.password
if "@" not in username:
username = username + "@sparkedu.com"
header = {"Authorization": "Basic c3BhcmtsZS13ZWI6c3BhcmtsZS13ZWI="}
session = requests.session()
session.headers.update(header)
url = "https://sso.qa.huohua.cn/uim/oauth/token/pwd?username=%s&password=%s" % (
username, password)
resp = session.post(url).json()
if resp["code"] == 200:
# user_id = resp.get("data").get('userId')
# name_dict = sql.select_one(sql='select * from emp.employee where id = "%s"' % user_id, choose_db="huohua")
return {"code": 20000, "data": resp.get("data")}
else:
return {"code": 50012, "message": "用户名密码错误"}

View File

@@ -0,0 +1,139 @@
# encoding: UTF-8
from ..model.createDetailInfo import CreateDetail
from ..model.createDataNumber import CreateDataNumber
from sqlalchemy.exc import NoResultFound
from sqlalchemy import desc
from logger import logger
class CreateDetailDao(object):
@staticmethod
def get_all_detail_list_no_filters(session):
rets = session.query(CreateDetail) \
.filter(CreateDetail.del_flag == 0) \
.order_by(CreateDetail.modified_time.desc()) \
.all()
total = session.query(CreateDetail).filter(CreateDetail.del_flag == 0).count()
return rets, total
@staticmethod
def get_detail_list_by_filters(session, filter_list, page=1, limit=20):
rets = session.query(CreateDetail) \
.filter(*filter_list).filter(CreateDetail.del_flag == 0) \
.offset((int(page) - 1) * int(limit)) \
.limit(limit) \
.all()
total = session.query(CreateDetail).filter(*filter_list).filter(CreateDetail.del_flag == 0).count()
return rets, total
@staticmethod
def get_detail_list_by_filters_by_team(session, filter_list, list_team_name, page=1, limit=10):
# rets = session.query(CreateDetail) \
# .filter(*filter_list).filter(CreateDetail.del_flag == 0) \
# .filter(CreateDetail.team_name.in_(list_team_name)) \
# .offset((int(page) - 1) * int(limit)) \
# .limit(limit) \
# .all()
rets = session.query(CreateDetail) \
.outerjoin(CreateDataNumber,
CreateDetail.create_data_detail_id == CreateDataNumber.create_data_detail_id) \
.filter(*filter_list).filter(CreateDetail.del_flag == 0) \
.order_by(desc(CreateDataNumber.month_number)) \
.offset((int(page) - 1) * int(limit)) \
.limit(limit) \
.all()
# total = session.query(CreateDetail).filter(*filter_list).filter(CreateDetail.del_flag == 0).filter(
# CreateDetail.team_name.in_(list_team_name)).count()
total = session.query(CreateDetail).filter(*filter_list).filter(CreateDetail.del_flag == 0).count()
return rets, total
@staticmethod
def get_detail_list_no_filters_by_team(session, list_team_name, page=1, limit=10):
# rets = session.query(CreateDetail) \
# .join(CreateDataNumber,
# CreateDetail.create_data_detail_id == CreateDataNumber.create_data_detail_id) \
# .filter(CreateDetail.del_flag == 0) \
# .order_by(desc(CreateDataNumber.month_number)) \
# .offset((int(page) - 1) * int(limit)) \
# .limit(limit) \
# .all()
rets = session.query(CreateDetail) \
.filter(CreateDetail.del_flag == 0) \
.order_by(CreateDetail.modified_time.desc()) \
.offset((int(page) - 1) * int(limit)) \
.limit(limit) \
.all()
total = session.query(CreateDetail).filter(CreateDetail.del_flag == 0).count()
return rets, total
@staticmethod
def get_detail_list_no_filters(session, page=1, limit=20):
rets = session.query(CreateDetail) \
.filter(CreateDetail.del_flag == 0) \
.order_by(CreateDetail.modified_time.desc()) \
.offset((int(page) - 1) * int(limit)) \
.limit(limit) \
.all()
total = session.query(CreateDetail).filter(CreateDetail.del_flag == 0).count()
return rets, total
@staticmethod
def get_detail_simple_by_filters(session, filter_list):
rets = session.query(CreateDetail) \
.filter(*filter_list).filter(CreateDetail.del_flag == 0) \
.first()
return rets
@staticmethod
def get_create_detail_info_by_id(session, sid):
try:
ret = session.query(CreateDetail).filter(CreateDetail.create_data_detail_id == sid).one()
except Exception as e:
logger.warning(f'获取造数基础信息失败: id{sid}, {e}')
return None, f'获取造数基础信息失败: id{sid}'
return ret
@staticmethod
def save_detail_info_record(session, create_info):
if not isinstance(create_info, dict):
logger.error('只支持dict类型。build_info type: {}'.format(type(create_info)))
create_detail_info = CreateDetail(**create_info)
session.add(create_detail_info)
# session.flush()
err = session.done(close=False)
create_info_id = create_detail_info.create_data_detail_id
if err:
logger.error(f'新增失败!{err}')
return 0
if not create_info_id:
logger.error(f'{create_detail_info}获取关键字id失败')
return 0
# session.commit()
return create_info_id
@staticmethod
def update_detail_info_record(session, update_info, create_data_detail_id):
create_info = session.query(CreateDetail).filter(
CreateDetail.create_data_detail_id == create_data_detail_id).update(
update_info)
err_msg = session.commit()
session.done()
if err_msg:
return '修改场景失败!'
return ""
@staticmethod
def delete_detail_info_record(session, create_data_detail_id):
build_info = session.query(CreateDetail).filter(
CreateDetail.create_data_detail_id == create_data_detail_id).all()
if build_info:
del_info = session.query(CreateDetail).filter(
CreateDetail.create_data_detail_id == create_data_detail_id).update({'del_flag': 1})
session.commit()
if not del_info:
logger.error(f'删除记录失败res: {del_info}')
return del_info
else:
logger.error(f'id{create_data_detail_id} 不存在记录!')
return f'id{create_data_detail_id} 不存在记录!'

View File

@@ -0,0 +1,83 @@
# encoding: UTF-8
from ..model.createDictInfo import CreateDict
from sqlalchemy.exc import NoResultFound
from logger import logger
class CreateDictDao(object):
@staticmethod
def get_dict_list_by_filters(session, filter_list, page=1, limit=20):
rets = session.query(CreateDict) \
.filter(*filter_list).filter(CreateDict.del_flag == 0) \
.offset((int(page) - 1) * int(limit)) \
.limit(limit) \
.all()
total = session.query(CreateDict).filter(*filter_list).filter(CreateDict.del_flag == 0).count()
return rets, total
@staticmethod
def get_dict_list_by_team_filters(session, filter_list, list_team_name, page=1, limit=20):
# rets = session.query(CreateDict) \
# .filter(*filter_list).filter(CreateDict.del_flag == 0) \
# .filter(CreateDict.team_name.in_(list_team_name)) \
# .offset((int(page) - 1) * int(limit)) \
# .limit(limit) \
# .all()
# total = session.query(CreateDict).filter(*filter_list).filter(CreateDict.del_flag == 0).filter(
# CreateDict.team_name.in_(list_team_name)).count()
rets = session.query(CreateDict) \
.filter(*filter_list).filter(CreateDict.del_flag == 0) \
.offset((int(page) - 1) * int(limit)) \
.limit(limit) \
.all()
total = session.query(CreateDict).filter(*filter_list).filter(CreateDict.del_flag == 0).count()
return rets, total
@staticmethod
def get_dict_list_no_filters(session, page=1, limit=20):
rets = session.query(CreateDict) \
.filter(CreateDict.del_flag == 0) \
.order_by(CreateDict.modified_time.desc()) \
.offset((int(page) - 1) * int(limit)) \
.limit(limit) \
.all()
total = session.query(CreateDict).filter(CreateDict.del_flag == 0).count()
return rets, total
@staticmethod
def get_dict_simple_by_filters(session, filter_list):
rets = session.query(CreateDict) \
.filter(*filter_list).filter(CreateDict.del_flag == 0) \
.first()
return rets
@staticmethod
def save_dict_info_record(session, create_info):
if not isinstance(create_info, dict):
logger.error('只支持dict类型。build_info type: {}'.format(type(create_info)))
create_dict_info = CreateDict(**create_info)
session.add(create_dict_info)
# session.flush()
err = session.done(close=False)
create_info_id = create_dict_info.create_dict_data_id
if err:
logger.error(f'新增失败!{err}')
return 0
if not create_info_id:
logger.error(f'{create_dict_info}获取关键字id失败')
return 0
# session.commit()
return create_info_id
@staticmethod
def update_dict_info_record(session, update_info, create_dict_data_id):
create_info = session.query(CreateDict).filter(
CreateDict.create_dict_data_id == create_dict_data_id).one()
create_info.team_name = update_info["team_name"]
create_info.dict_key = update_info["dict_key"]
create_info.dict_value = update_info["dict_value"]
err_msg = session.done()
if err_msg:
return '修改失败!'
return ''

View File

@@ -0,0 +1,71 @@
# encoding: UTF-8
from ..model.createDataInfo import CreateInfo
from sqlalchemy.exc import NoResultFound
from logger import logger
class CreateInfoDao(object):
@staticmethod
def get_data_info(session, filter_list):
try:
ls = session.query(CreateInfo).filter(*filter_list).filter(CreateInfo.del_flag == 0).first()
total = session.query(CreateInfo).filter(*filter_list).filter(CreateInfo.del_flag == 0).count()
except Exception as e:
logger.warning(e)
return [], '获取基础信息失败!'
return ls, total
@staticmethod
def get_data_simple_by_filters(session, filter_list):
rets = session.query(CreateInfo) \
.filter(*filter_list).filter(CreateInfo.del_flag == 0) \
.first()
return rets
@staticmethod
def get_create_data_info_by_id(session, sid):
try:
ret = session.query(CreateInfo).filter(CreateInfo.create_data_info_id == sid).one()
except Exception as e:
logger.warning(f'获取造数信息失败: id{sid}, {e}')
return None, f'获取造数信息失败: id{sid}'
return ret
@staticmethod
def get_create_data_info_by_detail_id(session, detail_id):
try:
ret = session.query(CreateInfo).filter(CreateInfo.create_data_detail_id == detail_id).one()
except Exception as e:
logger.warning(f'获取造数信息失败: id{detail_id}, {e}')
return None, f'获取造数信息失败: id{detail_id}'
return ret
@staticmethod
def save_data_info_record(session, create_info):
if not isinstance(create_info, dict):
logger.error('只支持dict类型。build_info type: {}'.format(type(create_info)))
create_data_info = CreateInfo(**create_info)
session.add(create_data_info)
# session.flush()
err = session.done(close=False)
create_info_id = create_data_info.create_data_info_id
if err:
logger.error(f'新增失败!{err}')
return 0
if not create_info_id:
logger.error(f'{create_data_info}获取关键字id失败')
return 0
# session.commit()
return create_info_id
@staticmethod
def update_data_info_record(session, update_info, create_data_info_id):
create_info = session.query(CreateInfo).filter(CreateInfo.create_data_info_id == create_data_info_id).one()
create_info.class_name = update_info['class_name']
create_info.method_name = update_info['method_name']
create_info.request_parameter = update_info['request_parameter']
err_msg = session.done()
if err_msg:
return '修改失败!'
return ''

View File

@@ -0,0 +1,60 @@
# encoding: UTF-8
from ..model.createDataNumber import CreateDataNumber
from logger import logger
class CreateNumberDao(object):
@staticmethod
def get_number_no_filters(session):
rets = session.query(CreateDataNumber) \
.filter(CreateDataNumber.del_flag == 0) \
.all()
return rets
@staticmethod
def get_number_simple_by_filters(session, filter_list):
rets = session.query(CreateDataNumber) \
.filter(*filter_list).filter(CreateDataNumber.del_flag == 0) \
.first()
return rets
@staticmethod
def get_number_info_by_id(session, create_data_detail_id):
try:
ret = session.query(CreateDataNumber).filter(
CreateDataNumber.create_data_detail_id == create_data_detail_id).first()
except Exception as e:
logger.warning(f'获取造数使用次数失败: id{create_data_detail_id}, {e}')
return f'获取造数使用次数失败: id{create_data_detail_id}', False
return ret, True
@staticmethod
def save_number_info(session, create_info):
if not isinstance(create_info, dict):
logger.error('只支持dict类型。build_info type: {}'.format(type(create_info)))
create_detail_info = CreateDataNumber(**create_info)
session.add(create_detail_info)
# session.flush()
err = session.done(close=False)
create_info_id = create_detail_info.id
if err:
logger.error(f'新增失败!{err}')
return 0
if not create_info_id:
logger.error(f'{create_detail_info}获取关键字id失败')
return 0
# session.commit()
return create_info_id
@staticmethod
def update_number_info(session, update_info, create_data_detail_id):
update_res = session.query(CreateDataNumber).filter(
CreateDataNumber.create_data_detail_id == create_data_detail_id).update(
update_info)
print(update_res, "update_res")
err_msg = session.commit()
# session.done()
if err_msg:
return '基础信息修改失败!'
return update_res

View File

@@ -0,0 +1,125 @@
# encoding: UTF-8
from sqlalchemy import func
from ..model.createResultInfo import CreateResult
from sqlalchemy.exc import NoResultFound
from logger import logger
class CreateResultDao(object):
@staticmethod
def get_result_list_by_filters(session, filter_list, page=1, limit=20):
rets = session.query(CreateResult) \
.filter(*filter_list).filter(CreateResult.del_flag == 0, CreateResult.status == 1) \
.offset((int(page) - 1) * int(limit)) \
.limit(limit) \
.all()
total = session.query(CreateResult).filter(*filter_list).filter(CreateResult.del_flag == 0,
CreateResult.status == 1).count()
return rets, total
@staticmethod
def get_result_list_by_team_filters(session, filter_list, list_team_name, page=1, limit=20):
# rets = session.query(CreateResult) \
# .filter(*filter_list).filter(CreateResult.del_flag == 0, CreateResult.status == 1) \
# .filter(CreateResult.team_name.in_(list_team_name)) \
# .offset((int(page) - 1) * int(limit)) \
# .limit(limit) \
# .all()
# total = session.query(CreateResult).filter(*filter_list).filter(CreateResult.del_flag == 0,
# CreateResult.status == 1).filter(
# CreateResult.team_name.in_(list_team_name)).count()
rets = session.query(CreateResult) \
.filter(*filter_list).filter(CreateResult.del_flag == 0, CreateResult.status == 1) \
.offset((int(page) - 1) * int(limit)) \
.limit(limit) \
.all()
total = session.query(CreateResult).filter(*filter_list).filter(CreateResult.del_flag == 0,
CreateResult.status == 1).count()
return rets, total
@staticmethod
def get_result_list_no_filters(session, page=1, limit=20):
rets = session.query(CreateResult) \
.filter(CreateResult.del_flag == 0, CreateResult.status == 1) \
.order_by(CreateResult.modified_time.desc()) \
.offset((int(page) - 1) * int(limit)) \
.limit(limit) \
.all()
total = session.query(CreateResult).filter(CreateResult.del_flag == 0, CreateResult.status == 1).count()
return rets, total
@staticmethod
def get_result_list_no_team_filters(session, list_team_name, page=1, limit=20):
# rets = session.query(CreateResult) \
# .filter(CreateResult.del_flag == 0, CreateResult.status == 1) \
# .filter(CreateResult.team_name.in_(list_team_name)) \
# .order_by(CreateResult.modified_time.desc()) \
# .offset((int(page) - 1) * int(limit)) \
# .limit(limit) \
# .all()
# total = session.query(CreateResult).filter(CreateResult.del_flag == 0, CreateResult.status == 1).filter(
# CreateResult.team_name.in_(list_team_name)).count()
rets = session.query(CreateResult) \
.filter(CreateResult.del_flag == 0, CreateResult.status == 1) \
.order_by(CreateResult.modified_time.desc()) \
.offset((int(page) - 1) * int(limit)) \
.limit(limit) \
.all()
total = session.query(CreateResult).filter(CreateResult.del_flag == 0, CreateResult.status == 1).count()
return rets, total
@staticmethod
def get_result_simple_by_filters(session, filter_list):
rets = session.query(CreateResult) \
.filter(*filter_list).filter(CreateResult.del_flag == 0) \
.first()
return rets
@staticmethod
def get_result_detail_info_by_id(session, sid):
try:
ret = session.query(CreateResult).filter(CreateResult.create_data_result_id == sid).one()
except Exception as e:
logger.warning(f'获取造数基础信息失败: id{sid}, {e}')
return None, f'获取造数基础信息失败: id{sid}'
return ret
@staticmethod
def save_result_info_record(session, create_info):
if not isinstance(create_info, dict):
logger.error('只支持dict类型。build_info type: {}'.format(type(create_info)))
create_result_info = CreateResult(**create_info)
session.add(create_result_info)
# session.flush()
err = session.done(close=False)
create_info_id = create_result_info.create_data_result_id
if err:
logger.error(f'新增失败!{err}')
return 0
if not create_info_id:
logger.error(f'{create_result_info}获取造数结果id失败')
return 0
# session.commit()
return create_info_id
@staticmethod
def update_result_record(session, update_info, sid):
create_info = session.query(CreateResult).filter(
CreateResult.create_data_result_id == sid).one()
create_info.status = update_info["status"]
create_info.result_info = update_info["result_info"]
err_msg = session.done()
if err_msg:
return '修改结果失败!'
return ''
@staticmethod
def get_result_numbers_by_month(session, create_data_detail_id, one_month_ago_str):
ret = session.query(func.count(CreateResult.create_data_detail_id).label('count')).filter(
CreateResult.created_time >= one_month_ago_str,
CreateResult.create_data_detail_id == create_data_detail_id, CreateResult.status == 1).group_by(
CreateResult.method_function_detail
).first()
return ret

View File

@@ -0,0 +1,42 @@
# encoding: UTF-8
from ..model.createGroupUser import CreateGroupUser
from logger import logger
class CreateGroupDao(object):
@staticmethod
def get_group_list_by_filters(session, filter_list, page=1, limit=20):
rets = session.query(CreateGroupUser) \
.filter(*filter_list).filter(CreateGroupUser.del_flag == 0) \
.offset((int(page) - 1) * int(limit)) \
.limit(limit) \
.all()
total = session.query(CreateGroupUser).filter(*filter_list).filter(CreateGroupUser.del_flag == 0).count()
return rets, total
@staticmethod
def get_group_list_no_filters(session, page=1, limit=20):
rets = session.query(CreateGroupUser) \
.filter(CreateGroupUser.del_flag == 0) \
.offset((int(page) - 1) * int(limit)) \
.limit(limit) \
.all()
total = session.query(CreateGroupUser).filter(CreateGroupUser.del_flag == 0).count()
return rets, total
@staticmethod
def get_group_all_data(session):
rets = session.query(CreateGroupUser).filter(CreateGroupUser.del_flag == 0).all()
return rets
@staticmethod
def get_group_detail_info_by_email(session, email):
try:
if email is not None:
ret = session.query(CreateGroupUser).filter(CreateGroupUser.email == email).one()
else:
return None
except Exception as e:
logger.warning(f'获取组名失败: id{email}, {e}')
return None
return ret

View File

@@ -0,0 +1,23 @@
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, TIMESTAMP, text, JSON
from ....common.sqlSession import to_dict
Base = declarative_base()
Base.to_dict = to_dict
class CreateInfo(Base):
__tablename__ = 'create_data_info'
create_data_info_id = Column(String(120), primary_key=True, comment='传入参数的id')
create_data_detail_id = Column(String(120), unique=True, comment='基础信息的id')
class_name = Column(String(120), unique=True, comment='类名称')
method_name = Column(String(120), unique=True, comment='方法名称')
request_parameter = Column(JSON, unique=True, comment='请求参数')
del_flag = Column(Integer, unique=True, comment='0未删除1已删除')
created_time = Column(TIMESTAMP, server_default=text('CURRENT_TIMESTAMP'), comment='创建时间')
modified_time = Column(TIMESTAMP, server_default=text('CURRENT_TIMESTAMP'),
server_onupdate=text('CURRENT_TIMESTAMP'), comment='更新时间')
def __repr__(self):
return '<create_data_info %r>' % self.create_data_info_id

View File

@@ -0,0 +1,23 @@
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, TIMESTAMP, text
from ....common.sqlSession import to_dict
Base = declarative_base()
Base.to_dict = to_dict
class CreateDataNumber(Base):
__tablename__ = 'create_data_number'
id = Column(String(100), primary_key=True, comment='次数的id')
create_data_detail_id = Column(String(100), primary_key=True, comment='数据字典的id')
method_name = Column(String(120), unique=True, comment='方法名称')
total_number = Column(Integer, unique=True, comment='方法总使用次数')
error_number = Column(Integer, unique=True, comment='方法使用的错误次数')
right_number = Column(Integer, unique=True, comment='方法使用的正确次数')
month_number = Column(Integer, unique=True, comment='方法使用的最近一个月次数')
del_flag = Column(Integer, unique=True, comment='0未删除1已删除')
created_time = Column(String(100), unique=True, comment='创建时间')
modified_time = Column(String(100), unique=True, comment='更新时间')
def __repr__(self):
return '<id %r>' % self.id

View File

@@ -0,0 +1,28 @@
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, TIMESTAMP, text
from ....common.sqlSession import to_dict
Base = declarative_base()
Base.to_dict = to_dict
class CreateDetail(Base):
__tablename__ = 'create_data_detail'
create_data_detail_id = Column(String(120), primary_key=True, comment='基础信息的id')
team_name = Column(String(100), unique=True, comment='组名(项目名)')
file_name = Column(String(100), unique=True, comment='文件夹名称')
module_name = Column(String(100), unique=True, comment='模块名称')
class_name = Column(String(120), unique=True, comment='类名称')
method_name = Column(String(120), unique=True, comment='方法名称')
method_function_detail = Column(String(5000), unique=True, comment='方法简单说明')
method_detail = Column(String(5000), unique=True, comment='方法描述')
creator = Column(String(100), unique=True, comment='责任人')
status = Column(Integer, unique=True, comment='状态:0未审核1创建失败')
del_flag = Column(Integer, unique=True, comment='0未删除1已删除')
created_time = Column(TIMESTAMP, server_default=text('CURRENT_TIMESTAMP'), comment='创建时间')
modified_time = Column(TIMESTAMP, server_default=text('CURRENT_TIMESTAMP'),
server_onupdate=text('CURRENT_TIMESTAMP'), comment='更新时间')
def __repr__(self):
return '<create_data_detail_id %r>' % self.create_data_detail_id

View File

@@ -0,0 +1,23 @@
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, TIMESTAMP, text
from ....common.sqlSession import to_dict
Base = declarative_base()
Base.to_dict = to_dict
class CreateDict(Base):
__tablename__ = 'create_dict_data'
create_dict_data_id = Column(String(120), primary_key=True, comment='数据字典的id')
data_type = Column(Integer, unique=True, comment='1路径 2对应名称')
team_name = Column(String(100), unique=True, comment='业务线名称')
dict_key = Column(String(100), unique=True, comment='字典的key')
dict_value = Column(String(255), unique=True, comment='字典的value')
del_flag = Column(Integer, unique=True, comment='0未删除1已删除')
created_time = Column(TIMESTAMP, server_default=text('CURRENT_TIMESTAMP'), comment='创建时间')
modified_time = Column(TIMESTAMP, server_default=text('CURRENT_TIMESTAMP'),
server_onupdate=text('CURRENT_TIMESTAMP'), comment='更新时间')
def __repr__(self):
return '<create_dict_data_id %r>' % self.create_dict_data_id

View File

@@ -0,0 +1,19 @@
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, TIMESTAMP, text
from ....common.sqlSession import to_dict
Base = declarative_base()
Base.to_dict = to_dict
class CreateGroupUser(Base):
__tablename__ = 'create_group_user'
id = Column(Integer, primary_key=True, comment='用户的id')
email = Column(String(100), unique=True, comment='邮箱')
team_name = Column(String(100), unique=True, comment='组名')
name = Column(String(100), unique=True, comment='用户名称')
del_flag = Column(Integer, unique=True, comment='0未删除1已删除')
def __repr__(self):
return '<id %r>' % self.id

View File

@@ -0,0 +1,31 @@
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, TIMESTAMP, text, JSON
from ....common.sqlSession import to_dict
Base = declarative_base()
Base.to_dict = to_dict
class CreateResult(Base):
__tablename__ = 'create_data_result'
create_data_result_id = Column(String(120), primary_key=True, comment='执行结果的id')
create_data_info_id = Column(String(120), unique=True, comment='传入参数的id')
create_data_detail_id = Column(String(120), unique=True, comment='基础信息的id')
request_parameter = Column(JSON, unique=True, comment='请求参数')
result_info = Column(JSON, unique=True, comment='造数结果')
team_name = Column(String(100), unique=True, comment='组名(项目名)')
module_name = Column(String(100), unique=True, comment='模块名称')
class_name = Column(String(120), unique=True, comment='类名称')
file_name = Column(String(255), unique=True, comment='文件名称')
method_name = Column(String(120), unique=True, comment='方法名称')
tag = Column(String(1000), unique=True, comment='标签')
status = Column(Integer, unique=True, comment='状态')
method_function_detail = Column(String(5000), unique=True, comment='方法描述')
del_flag = Column(Integer, unique=True, comment='0未删除1已删除')
creator = Column(String(100), unique=True, comment='创建人')
created_time = Column(TIMESTAMP, server_default=text('CURRENT_TIMESTAMP'), comment='创建时间')
modified_time = Column(TIMESTAMP, server_default=text('CURRENT_TIMESTAMP'),
server_onupdate=text('CURRENT_TIMESTAMP'), comment='更新时间')
def __repr__(self):
return '<create_data_result_id %r>' % self.create_data_result_id

View File

@@ -0,0 +1,61 @@
# encoding: UTF-8
from ..dao.createDataDetailDao import CreateDetailDao
class CreateDetailService(object):
def __init__(self):
pass
@staticmethod
def get_all_detail_list_no_filters_page(session):
scene_list, total = CreateDetailDao.get_all_detail_list_no_filters(session)
return scene_list, total
@staticmethod
def get_detail_list_no_filters_page(session, page, limit):
scene_list, total = CreateDetailDao.get_detail_list_no_filters(session, page=page, limit=limit)
return scene_list, total
@staticmethod
def get_detail_list_by_filters_page(session, filter_list, page, limit):
ret, err_msg = CreateDetailDao.get_detail_list_by_filters(session, filter_list, page=page, limit=limit)
return ret, err_msg
@staticmethod
def get_detail_list_by_filters_team_page(session, filter_list, list_team_name, page, limit):
ret, err_msg = CreateDetailDao.get_detail_list_by_filters_by_team(session, filter_list,
list_team_name=list_team_name,
page=page, limit=limit)
return ret, err_msg
@staticmethod
def get_detail_list_no_filters_team_page(session, list_team_name, page, limit):
scene_list, total = CreateDetailDao.get_detail_list_no_filters_by_team(session, list_team_name=list_team_name,
page=page, limit=limit)
return scene_list, total
@staticmethod
def get_detail_simple_data_by_filters(session, filter_list):
ret = CreateDetailDao.get_detail_simple_by_filters(session, filter_list)
return ret
@staticmethod
def get_create_detail_info_by_id(session, create_data_detail_id):
err_msg = CreateDetailDao.get_create_detail_info_by_id(session, create_data_detail_id)
return err_msg
@staticmethod
def create_service_detail_data(session, create_info):
ret = CreateDetailDao.save_detail_info_record(session, create_info)
return ret
@staticmethod
def update_service_detail_data(session, update_info, create_data_detail_id):
err_msg = CreateDetailDao.update_detail_info_record(session, update_info, create_data_detail_id)
return err_msg
@staticmethod
def delete_service_detail_data(session, create_data_detail_id):
bf_dao = CreateDetailDao()
del_info = bf_dao.delete_detail_info_record(session, create_data_detail_id)
return del_info

View File

@@ -0,0 +1,38 @@
# encoding: UTF-8
from ..dao.createDataDictDao import CreateDictDao
class CreateDictService(object):
def __init__(self):
pass
@staticmethod
def get_dict_list_no_filters_page(session, page, limit):
scene_list, total = CreateDictDao.get_dict_list_no_filters(session, page=page, limit=limit)
return scene_list, total
@staticmethod
def get_dict_list_by_filters_page(session, filter_list, page, limit):
ret, total = CreateDictDao.get_dict_list_by_filters(session, filter_list, page=page, limit=limit)
return ret, total
@staticmethod
def get_dict_list_by_team_filters_page(session, filter_list, list_team_name, page, limit):
ret, total = CreateDictDao.get_dict_list_by_team_filters(session, filter_list, list_team_name, page=page,
limit=limit)
return ret, total
@staticmethod
def get_dict_simple_data_by_filters(session, filter_list):
ret = CreateDictDao.get_dict_simple_by_filters(session, filter_list)
return ret
@staticmethod
def create_service_dict_data(session, create_info):
ret = CreateDictDao.save_dict_info_record(session, create_info)
return ret
@staticmethod
def update_service_dict_data(session, update_info, create_dict_data_id):
err_msg = CreateDictDao.update_dict_info_record(session, update_info, create_dict_data_id)
return err_msg

View File

@@ -0,0 +1,22 @@
# encoding: UTF-8
from ..dao.createGroupUserDao import CreateGroupDao
class CreateGroupService(object):
def __init__(self):
pass
@staticmethod
def get_service_group_list_no_filters_page(session, page, limit):
scene_list, total = CreateGroupDao.get_group_list_no_filters(session, page=page, limit=limit)
return scene_list, total
@staticmethod
def get_service_group_list_all_data(session):
scene_list = CreateGroupDao.get_group_all_data(session)
return scene_list
@staticmethod
def get_service_group_info_data_by_email(session, email):
scene_list = CreateGroupDao.get_group_detail_info_by_email(session, email)
return scene_list

View File

@@ -0,0 +1,37 @@
# encoding: UTF-8
from ..dao.createDataInfoDao import CreateInfoDao
class CreateInfoService(object):
def __init__(self):
pass
@staticmethod
def get_service_info_data(session,filter_list):
ret, err_msg = CreateInfoDao.get_data_info(session,filter_list)
return ret, err_msg
@staticmethod
def get_data_simple_data_by_filters(session, filter_list):
ret = CreateInfoDao.get_data_simple_by_filters(session, filter_list)
return ret
@staticmethod
def get_create_data_info_by_id(session, sid):
scene_obj = CreateInfoDao.get_create_data_info_by_id(session, sid)
return scene_obj
@staticmethod
def get_create_data_info_by_detail_id(session, detail_id):
scene_obj = CreateInfoDao.get_create_data_info_by_detail_id(session, detail_id)
return scene_obj
@staticmethod
def create_service_info_data(session, create_info):
ret = CreateInfoDao.save_data_info_record(session, create_info)
return ret
@staticmethod
def update_service_info_data(session, update_info,create_data_info_id):
err_msg = CreateInfoDao.update_data_info_record(session,update_info,create_data_info_id)
return err_msg

View File

@@ -0,0 +1,32 @@
# encoding: UTF-8
from ..dao.createDataNumberDao import CreateNumberDao
class CreateNumberService(object):
def __init__(self):
pass
@staticmethod
def get_service_number_all_no_filters(session):
ret = CreateNumberDao.get_number_no_filters(session)
return ret
@staticmethod
def get_service_number_simple_by_filters(session, filter_list):
ret = CreateNumberDao.get_number_simple_by_filters(session, filter_list)
return ret
@staticmethod
def get_service_number_by_id(session, create_data_detail_id):
err_msg, success = CreateNumberDao.get_number_info_by_id(session, create_data_detail_id)
return err_msg, success
@staticmethod
def create_service_number_data(session, create_info):
ret = CreateNumberDao.save_number_info(session, create_info)
return ret
@staticmethod
def update_service_number_data(session, update_info, create_data_detail_id):
err_msg = CreateNumberDao.update_number_info(session, update_info, create_data_detail_id)
return err_msg

View File

@@ -0,0 +1,55 @@
# encoding: UTF-8
from ..dao.createDataResultDao import CreateResultDao
class CreateResultService(object):
def __init__(self):
pass
@staticmethod
def get_result_list_no_filters_page(session, page, limit):
scene_list, total = CreateResultDao.get_result_list_no_filters(session, page=page, limit=limit)
return scene_list, total
@staticmethod
def get_result_list_no_team_filters_page(session, list_team_name, page, limit):
scene_list, total = CreateResultDao.get_result_list_no_team_filters(session, list_team_name, page=page,
limit=limit)
return scene_list, total
@staticmethod
def get_result_list_by_filters_page(session, filter_list, page, limit):
ret, err_msg = CreateResultDao.get_result_list_by_filters(session, filter_list, page=page, limit=limit)
return ret, err_msg
@staticmethod
def get_result_list_by_team_filters_page(session, filter_list, list_team_name, page, limit):
ret, err_msg = CreateResultDao.get_result_list_by_team_filters(session, filter_list,
list_team_name=list_team_name,
page=page, limit=limit)
return ret, err_msg
@staticmethod
def get_result_simple_data_by_filters(session, filter_list):
ret = CreateResultDao.get_result_simple_by_filters(session, filter_list)
return ret
@staticmethod
def get_create_result_by_id(session, create_data_result_id):
err_msg = CreateResultDao.get_result_detail_info_by_id(session, create_data_result_id)
return err_msg
@staticmethod
def create_result_data(session, create_info):
ret = CreateResultDao.save_result_info_record(session, create_info)
return ret
@staticmethod
def update_service_result_data(session, update_info, sid):
err_msg = CreateResultDao.update_result_record(session, update_info, sid)
return err_msg
@staticmethod
def get_service_result_numbers_by_month(session, create_data_detail_id, one_month_ago_str):
err_msg = CreateResultDao.get_result_numbers_by_month(session, create_data_detail_id, one_month_ago_str)
return err_msg

View File

@@ -0,0 +1,593 @@
# encoding: UTF-8
from flask import Blueprint, request
from ...common.apiResponse import ApiResponse
from .controller.createDataInfoController import CreateDataInfoController
from .controller.createDataDetailController import CreateDataDetailController
from .controller.createDataDictController import CreateDataDictController
from .controller.createDataResultController import CreateDataResultController
from .controller.commonController import CommonController
from .controller.scrapyController import ScrapyController
from .controller.userController import UserController
api = Blueprint('api', __name__)
@api.route('/add/info/data', methods=['POST'])
def add_info_data():
"""新增造数请求参数数据
@@@
### 参数
| 参数名 | 是否必传 | 请求类型 |参数类型 | 参数描述 |
|-------|---------|--------|--------|----------|
| create_data_info_id | true | query | int | 基本信息的id |
| create_data_detail_id | true | query | int | 详细信息的id |
| className | true | query | string | 类名 |
| methodName | true | query | string | 方法名 |
| request_parameter | true | query | string | 请求参数 |
### 请求
```json
{"create_data_info_id":"","create_data_detail_id":102222,"className":"","methodName":"","request_parameter":{}}
```
### 响应
```json
{"code": x, "message": "x", "data":true,"success":}
```
@@@
"""
req_json = request.get_json()
access_token = request.headers['accesstoken']
controller = CreateDataInfoController(req_json, access_token)
ret = controller.create_info_data()
if ret:
return ApiResponse.build_failure(5000, data=ret)
return ApiResponse.build_success(20000, data=True)
@api.route('/edit/info/data', methods=['POST'])
def edit_info_data():
"""编辑造数请求参数数据
@@@
### 参数
| 参数名 | 是否必传 | 请求类型 |参数类型 | 参数描述 |
|-------|---------|--------|--------|----------|
| create_data_info_id | true | query | int | 基本信息的id |
| create_data_detail_id | true | query | int | 详细信息的id |
| className | true | query | string | 类名 |
| methodName | true | query | string | 方法名 |
| request_parameter | true | query | string | 请求参数 |
### 请求
```json
{"create_data_info_id":"","create_data_detail_id":102222,"className":"","methodName":"","request_parameter":{}}
```
### 响应
```json
{"code": x, "message": "x ", "data":true,"success":}
```
@@@
"""
req_json = request.get_json()
access_token = request.headers['accesstoken']
controller = CreateDataInfoController(req_json, access_token)
ret = controller.update_info_data()
if ret:
return ApiResponse.build_failure(5000, data=ret)
return ApiResponse.build_success(20000, data=True)
@api.route('/delete/detail/data', methods=['POST'])
def delete_detail_data():
"""删除造数详情数据
@@@
### 参数
| 参数名 | 是否必传 | 请求类型 |参数类型 | 参数描述 |
|-------|---------|--------|--------|----------|
| create_data_detail_id | true | query | int | 详细信息的id |
### 请求
```json
{"create_data_detail_id":102222}
```
### 响应
```json
{"code": x, "message": "删除成功", "data":true,"success":}
```
@@@
"""
req_json = request.get_json()
access_token = request.headers['accesstoken']
controller = CreateDataDetailController(req_json, access_token)
ret = controller.delete_detail_data()
return ApiResponse.build_success(20000, data=ret)
@api.route('/create/data/detail/list', methods=['POST'])
def get_keywors_detail_list():
"""查询造数关键字基本信息列表页数据
@@@
### 参数
| 参数名 | 是否必传 | 请求类型 |参数类型 | 参数描述 |
|-------|---------|--------|--------|----------|
| pageNo | true | query | int | 基本信息的id |
| pageSize | true | query | int | 详细信息的id |
| detail_name | true | query | string | 基本描述信息 |
| team_name | true | query | string | 业务线 |
| file_name | true | query | string | 业务对应业务线名称 |
| module_name | true | query | string | 模块名称 |
### 请求
```json
{"pageNo":1,"pageSize":10,"dict_value":"","team_name":""}
```
### 响应
```json
{"code": x, "message": "x", "data":list[{},{}],"success":}
```
@@@
"""
req_json = request.get_json()
accesstoken = request.headers['accesstoken']
controller = CreateDataDetailController(req_json, accesstoken)
ret = controller.get_detail_query_advance()
return ApiResponse.build_success(20000, data=ret)
@api.route('/create/data/detail/advance', methods=['POST'])
def get_keywors_detail_advance():
"""查询造数关键字基本信息详情页数据
@@@
### 参数
| 参数名 | 是否必传 | 请求类型 |参数类型 | 参数描述 |
|-------|---------|--------|--------|----------|
| create_data_detail_id | true | query | int | 基本信息的id |
### 请求
```json
{"create_data_detail_id":123}
```
### 响应
```json
{"code": x, "message": "x", "data":{},"success":True}
```
@@@
"""
req_json = request.get_json()
accesstoken = request.headers['accesstoken']
controller = CreateDataDetailController(req_json, accesstoken)
ret = controller.get_detail_advance()
return ApiResponse.build_success(20000, data=ret)
@api.route('/keyword/run', methods=['POST'])
def run_keywords():
"""执行造数
@@@
### 参数
| 参数名 | 是否必传 | 请求类型 |参数类型 | 参数描述 |
|-------|---------|--------|--------|----------|
| create_data_info_id | true | query | string | 基本信息的id |
| create_data_detail_id | true | query | string | 详细信息的id |
| dict_key | true | query | string | 组名 |
| get_tag | true | query | string | 标签,备注说明 |
| request_parameter | true | query | string | 请求参数 |
### 请求
```json
{"create_data_info_id":"12","create_data_detail_id":"13","dict_key":"StudentEvent","get_tag":"get_student_classroom","request_parameter":""}
```
### 响应
```json
{"code": x, "message": "x", "success":}
```
@@@
"""
req_json = request.get_json()
access_token = request.headers['accesstoken']
controller = CreateDataDetailController(req_json, access_token)
data = controller.run_keyword_data()
if data.get("message") == "造数成功":
return ApiResponse.build_success(20000, message="造数成功", data=data.get("data"))
return ApiResponse.build_failure(40012, msg=data.get("message"))
@api.route('/create/data/result/list', methods=['POST'])
def get_keywors_result_list():
"""查询造数关键字造数结果列表页数据
@@@
### 参数
| 参数名 | 是否必传 | 请求类型 |参数类型 | 参数描述 |
|-------|---------|--------|--------|----------|
| pageNo | true | query | int | 页码 |
| pageSize | true | query | int | 页码规格 |
| detail_name | true | query | string | 详细描述信息 |
| team_name | true | query | string | 业务线 |
| get_tag | true | query | string | 标签,备注 |
### 请求
```json
{"pageNo":1,"pageSize":10,"detail_name":"","team_name":"","get_tag":""}
```
### 响应
```json
{"code": x, "message": "x", "data":list[{},{}],"success":}
```
@@@
"""
req_json = request.get_json()
accesstoken = request.headers['accesstoken']
controller = CreateDataResultController(req_json, accesstoken)
ret = controller.get_result_query_list()
return ApiResponse.build_success(20000, data=ret)
@api.route('/create/data/result/advance', methods=['POST'])
def get_keywors_result_advance():
"""查询造数结果的详情信息
@@@
### 参数
| 参数名 | 是否必传 | 请求类型 |参数类型 | 参数描述 |
|-------|---------|--------|--------|----------|
| create_data_result_id | true | query | int | 基本信息的id |
### 请求
```json
{"create_data_result_id":123}
```
### 响应
```json
{"code": x, "message": "x", "data":{},"success":True}
```
@@@
"""
req_json = request.get_json()
accesstoken = request.headers['accesstoken']
controller = CreateDataResultController(req_json, accesstoken)
ret = controller.get_result_advance()
return ApiResponse.build_success(20000, data=ret)
@api.route('/add/dict/data', methods=['POST'])
def edit_dict_data():
"""编辑造数关键字进行的映射
@@@
### 参数
| 参数名 | 是否必传 | 请求类型 |参数类型 | 参数描述 |
|-------|---------|--------|--------|----------|
| create_dict_data_id | true | query | int | 基本信息的id |
| data_type | true | query | int | 详细信息的id |
| dict_key | true | query | string | 映射的key |
| dict_value | true | query | string | 字段映射对应的名称 |
| team_name | true | query | string | 组名 |
| created_time | true | query | string | 创建时间 |
| modified_time | true | query | string | 修改时间 |
### 请求
```json
{"create_dict_data_id":"e757b5cd-cec5-11ed-ae4a-c8b29bebcb51","team_name":"用户","data_type":3,"dict_key":"common","dict_value":"公共","created_time":"202303301440","modified_time":"202304111341"}
```
### 响应
```json
{"code": x, "message": "x", "data":list[{},{}],"success":}
```
@@@
"""
req_json = request.get_json()
accesstoken = request.headers['accesstoken']
controller = CreateDataDictController(req_json, accesstoken)
ret = controller.update_dict_data()
return ApiResponse.build_success(20000, data=ret)
@api.route('/dict/data/page', methods=['POST'])
def get_dict_data_list():
"""查询造数关键字配置列表
@@@
### 参数
| 参数名 | 是否必传 | 请求类型 |参数类型 | 参数描述 |
|-------|---------|--------|--------|----------|
| pageNo | true | query | int | 基本信息的id |
| pageSize | true | query | int | 详细信息的id |
| dict_value | true | query | string | 配置值 |
| team_name | true | query | string | 业务线 |
### 请求
```json
{"pageNo":1,"pageSize":10,"detail_name":"","team_name":"","file_name":"","module_name":""}
```
### 响应
```json
{"code": x, "message": "x", "data":list[{},{}],"success":}
```
@@@
"""
req_json = request.get_json()
accesstoken = request.headers['accesstoken']
controller = CreateDataDictController(req_json, accesstoken)
ret = controller.get_dict_list_data()
return ApiResponse.build_success(20000, data=ret)
@api.route('/get/pull/git', methods=['POST'])
def get_new_git_info():
"""获取git上的最新代码到本地
@@@
### 参数
| 参数名 | 是否必传 | 请求类型 |参数类型 | 参数描述 |
|-------|---------|--------|--------|----------|
| fileName | true | query | string | 传入需要抓取的文件名称 |
| team | true | query | string | 业务线名称 |
| username | true | query | string | git地址用户名 |
| password | true | query | string | git地址密码 |
### 请求
```json
{"team":"USER","fileName":"","username":"","password":""}
```
### 响应
```json
{"code": x, "message": "x", "success":}
```
@@@
"""
req_json = request.get_json()
# accesstoken = request.headers['accesstoken']
accesstoken = "22233"
controller = ScrapyController(req_json, accesstoken)
ret = controller.get_git_pull()
return ApiResponse.build_success(20000, data=ret)
@api.route('/get/eureka', methods=['POST'])
def get_eureka_ip():
"""更新eureka上的ip地址
@@@
### 参数
| 参数名 | 是否必传 | 请求类型 |参数类型 | 参数描述 |
|-------|---------|--------|--------|----------|
| team | true | query | string | 业务线名称 |
### 请求
```json
{"team":"USER"}
```
### 响应
```json
{"code": x, "message": "x", "success":}
```
@@@
"""
req_json = request.get_json()
accesstoken = request.headers['accesstoken']
controller = ScrapyController(req_json, accesstoken)
ret = controller.get_eureka_ip()
return ApiResponse.build_success(20000, data=ret)
@api.route('/create/data/detail/scrapy', methods=['POST'])
def scrapy_entery_db():
"""查找出全部的可用方法与描述进行录库
@@@
### 参数
| 参数名 | 是否必传 | 请求类型 |参数类型 | 参数描述 |
|-------|---------|--------|--------|----------|
| fileName | true | query | string | 传入需要抓取的文件名称 |
| team | true | query | string | 业务线名称 |
| username | true | query | string | git地址用户名 |
| password | true | query | string | git地址密码 |
### 请求
```json
{"team":"USER","fileName":"","username":"","password":""}
```
### 响应
```json
{"code": x, "message": "x", "success":}
```
@@@
"""
req_json = request.get_json()
# accesstoken = request.headers['accesstoken']
accesstoken = "123"
controller = ScrapyController(req_json, accesstoken)
ret = controller.scrapy_enter_db()
if ret != "抓取成功":
return ApiResponse.build_failure(40012, msg=ret)
return ApiResponse.build_success(20000, message=ret)
@api.route('/get/team/name', methods=['POST'])
def get_team_name():
"""获取各个业务线的数据字典,用于查询
@@@
### 参数
| 参数名 | 是否必传 | 请求类型 |参数类型 | 参数描述 |
|-------|---------|--------|--------|----------|
| data_type | true | query | int | 类型 |
| team_name | true | query | string | 组名 |
### 请求
```json
{"data_type":2,"team_name":""}
```
### 响应
```json
{"code": x, "message": "x", "data":"","success":}
```
@@@
"""
req_json = request.get_json()
accesstoken = request.headers['accesstoken']
controller = CreateDataDictController(req_json, accesstoken)
ret = controller.get_team_name()
return ApiResponse.build_success(20000, data=ret)
@api.route('/create/data/detail/delete', methods=['POST'])
def delete_data_detail():
"""删除造数关键字基本信息
@@@
### 参数
| 参数名 | 是否必传 | 请求类型 |参数类型 | 参数描述 |
|-------|---------|--------|--------|----------|
| create_data_detail_id | true | query | int | 基本信息的id |
### 请求
```json
{"create_data_detail_id":""}
```
### 响应
```json
{"code": x, "message": "x", "data":true,"success":}
```
@@@
"""
req_json = request.get_json()
accesstoken = request.headers['accesstoken']
controller = CreateDataDetailController(req_json, accesstoken)
ret = controller.delete_detail_data()
return ApiResponse.build_success(20000, data=ret)
@api.route('/create/data/info/add', methods=['POST'])
def add_keywors_data():
"""增加造数关键字的请求参数
@@@
### 参数
| 参数名 | 是否必传 | 请求类型 |参数类型 | 参数描述 |
|-------|---------|--------|--------|----------|
| create_data_info_id | true | query | string | 基本信息的id |
| create_data_detail_id | true | query | string | 详细信息的id |
| className | true | query | string | 类名 |
| methodName | true | query | string | 方法名 |
| request_parameter | true | query | string | 请求参数 |
### 请求
```json
{"create_data_info_id":"12","create_data_detail_id":"13","className":"StudentEvent","methodName":"get_student_classroom","request_parameter":""}
```
### 响应
```json
{"code": x, "message": "x", "success":}
```
@@@
"""
req_json = request.get_json()
accesstoken = request.headers['accesstoken']
controller = CreateDataInfoController(req_json, accesstoken)
err_msg = controller.create_info_data()
if err_msg:
return ApiResponse.build_failure(40012, msg=err_msg)
return ApiResponse.build_success(20000)
@api.route('/create/data/info/list', methods=['POST'])
def get_keywors_data_list():
"""查询造数关键字请求列表页数据
@@@
### 参数
| 参数名 | 是否必传 | 请求类型 |参数类型 | 参数描述 |
|-------|---------|--------|--------|----------|
| create_data_info_id | true | query | string | 基本信息的id |
| create_data_detail_id | true | query | string | 详细信息的id |
| className | true | query | string | 类名 |
| methodName | true | query | string | 方法名 |
| request_parameter | true | query | string | 请求参数 |
### 请求
```json
{"create_data_info_id":"12","create_data_detail_id":"13","className":"StudentEvent","methodName":"get_student_classroom","request_parameter":""}
```
### 响应
```json
{"code": x, "message": "x", "success":}
```
@@@
"""
req_json = request.get_json()
accesstoken = request.headers['accesstoken']
controller = CreateDataInfoController(req_json, accesstoken)
ret = controller.create_info_data()
return ApiResponse.build_success(20000, data=ret)
@api.route('/Login', methods=['POST'])
def get_user_login():
"""判断登录,根据登录信息获取邮箱
@@@
### 参数
| 参数名 | 是否必传 | 请求类型 |参数类型 | 参数描述 |
|-------|---------|--------|--------|----------|
| username | true | query | string | 用户名 |
| password | true | query | string | 密码 |
### 请求
```json
{"username":2,"password":""}
```
### 响应
```json
{"code": x, "message": "x", "data":"","success":}
```
@@@
"""
req_json = request.get_json()
accesstoken = request.headers['accesstoken']
controller = UserController(req_json, accesstoken)
ret = controller.get_user_email()
if ret.get("code") != 20000:
return ApiResponse.build_failure(40012, msg=ret.get("message"))
return ApiResponse.build_success(20000, data=ret.get("data"))
@api.route('/create/data/detail/approve', methods=['POST'])
def approve_detail_info():
"""对关键字进行审核
@@@
### 参数
| 参数名 | 是否必传 | 请求类型 |参数类型 | 参数描述 |
|-------|---------|--------|--------|----------|
| create_data_detail_id | true | query | int | 基本信息的id |
### 请求
```json
{"create_data_detail_id":123}
```
### 响应
```json
{"code": x, "message": "x", "data":{},"success":True}
```
@@@
"""
req_json = request.get_json()
accesstoken = request.headers['accesstoken']
controller = CreateDataDetailController(req_json, accesstoken)
ret = controller.approve_detail_status()
if ret.get("code") != 20000:
return ApiResponse.build_failure(40012, msg=ret.get("message"))
return ApiResponse.build_success(20000, data=ret)
@api.route('/update/month/use/number', methods=['GET'])
def update_month_use_number():
"""每月使用次数更新,定时任务
@@@
### 参数
| 参数名 | 是否必传 | 请求类型 |参数类型 | 参数描述 |
|-------|---------|--------|--------|----------|
### 请求
```json
```
### 响应
```json
{"code": x, "message": "x", "data":{},"success":True}
```
@@@
"""
controller = CommonController()
ret = controller.daily_task_update_month_number()
if ret.get("code") != 20000:
return ApiResponse.build_failure(40012, msg=ret.get("message"))
return ApiResponse.build_success(20000, data=ret)

View File

@@ -0,0 +1,54 @@
# encoding: UTF-8
from flask import make_response
import json
from ..const import RES_CODE
class ApiResponse(object):
def __init__(self):
self.success = False
self.code = ''
self.message = ''
self.data = {}
@staticmethod
def build_success(code=20000, message='', data=None):
if data is None:
data = {}
response = ApiResponse()
response.success = True
response.code = code
response.message = message
response.data = data
return response.cors_response(make_response(json.dumps(response, default=obj_2_json)))
@staticmethod
def build_failure(code, msg='', data=None):
response = ApiResponse()
if data is None:
data = {}
if not msg:
response.message = RES_CODE[code]
else:
response.message = msg
response.success = False
response.code = code
response.data = data
return response.cors_response(make_response(json.dumps(response, default=obj_2_json)))
@staticmethod
def cors_response(res):
res.headers['Access-Control-Allow-Origin'] = '*'
res.headers['Access-Control-Allow-Methods'] = 'POST'
res.headers['Access-Control-Allow-Headers'] = 'x-requested-with,content-type'
return res
def obj_2_json(obj):
return {
'success': obj.success,
'code': obj.code,
'message': obj.message,
'data': obj.data
}

View File

@@ -0,0 +1,35 @@
from ..const import CREATE_URI
from ..common.getRequest import Request
class CronRequest(object):
def __init__(self, token):
self.stress_api = CREATE_URI
self.headers = {'accesstoken': token, 'Accept': '*/*', 'content-type': 'application/json;charset=UTF-8'}
def create(self, params):
url = self.stress_api + '/back-end/stress/schedule/save'
ret = Request.go('post', url, params, self.headers)
if not ret:
return
return ret.get('id')
def pause(self, jid):
url = self.stress_api + '/back-end/stress/schedule/pause'
params = [jid]
Request.go('post', url, params, self.headers)
def resume(self, jid):
url = self.stress_api + '/back-end/stress/schedule/resume'
params = [jid]
Request.go('post', url, params, self.headers)
def remove(self, jid):
url = self.stress_api + '/back-end/stress/schedule/delete'
params = [jid]
Request.go('post', url, params, self.headers)
def update(self, req_params):
url = self.stress_api + '/back-end/stress/schedule/update'
Request.go('post', url, req_params, self.headers)

View File

@@ -0,0 +1,16 @@
import requests
class FeiShuMessage:
def __init__(self):
self.headers = {'Content-Type': 'application/json; charset=utf-8'}
self.webhook = "https://open.feishu.cn/open-apis/bot/v2/hook/180fa48e-1474-448e-a3d5-1a530f6ca689"
def send_message(self, msg, url=None):
url = url if url else self.webhook
res = requests.post(url, headers=self.headers, json=msg, verify=False)
if res.status_code == 200:
return True
else:
return False

View File

@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
import requests
import json
from requests.exceptions import ConnectionError
from logger import logger
class Request(object):
@classmethod
def go(cls, method, url, params, headers=None, noFormat=False):
# logger.info(f'发送{method}请求到: {url}, 参数: {params}')
try:
if method == 'get':
response = requests.get(url=url, params=params, headers=headers, timeout=20)
elif method == 'post':
response = requests.post(url=url, data=json.dumps(params), headers=headers, timeout=20)
else:
logger.error(f'暂不支持{method}方法')
return
except ConnectionRefusedError:
logger.error(f'服务请求失败:{url}')
return
except ConnectionError:
logger.error(f'服务无法链接: {url}')
return
if response.status_code != 200:
logger.error(f'返回码不等于200请检查服务{response.status_code}, {response.text}')
else:
resp_json = response.json()
# logger.info(f'返回内容:{resp_json}')
# noFormat: 不需要对返回内容进行校验直接返回整个response
if noFormat:
return resp_json
# 对response做校验返回体为qe平台的通用格式
if resp_json.get('success') or resp_json.get('code') == 20000:
return resp_json.get('data')
else:
logger.error(resp_json)
return

View File

@@ -0,0 +1,24 @@
from ..common.getRequest import Request
from ..const import STRESS_URI
class UserInfo(object):
@staticmethod
def get_user_info(access_token, url_prefix=None, info='userId'):
stress_uri = STRESS_URI
url = "/back-end/stress/user/info"
result = Request.go(method="get",
url=stress_uri + url if not url_prefix else url_prefix + url,
params=None,
headers={"accessToken": access_token})
return None if not result else result.get(info)
@staticmethod
def get_user_info_by_user_id(access_token, user_id, info):
stress_uri = STRESS_URI
url = "/back-end/stress/user/infoFromId"
result = Request.go(method="get", url=stress_uri+url, params={'userId': user_id},
headers={"accessToken": access_token})
return None if not result else result.get(info)

View File

@@ -0,0 +1,83 @@
# 创建连接相关
from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker
from base_framework.platform_tools.IDF.const import create_sql_url
from logger import logger
"""
sql操作
排序order_by(ChartsName.column.desc()/asc())
limit: .offset(n)过滤前面n条数据 .limit(n)
count: .count()计数
是否存在is_exist = session.query(exists().where(Book.id > 10)).scalar()
or: .filter(or_(Chart.column == x, Chart.column > y)).all()
one: .one()只获取一条,如不存在或存在多条都会报错
first: 通过主键获取记录 filter(**).first()
"""
class SqlSession:
def __init__(self, sql_uri=create_sql_url):
self.sql_uri = sql_uri
self._session = self.get_session()
# 创建连接对象,使用 pymsql 引擎
def get_session(self):
engine = create_engine(self.sql_uri, max_overflow=5, connect_args={'connect_timeout': 10})
Session = sessionmaker(bind=engine)
session = Session()
return session
def query(self, obj):
return self._session.query(obj)
def add(self, added):
self._session.add(added)
def add_all(self, added_list):
if isinstance(added_list, list):
self._session.add_all(added_list)
else:
logger.warning('只能传递list')
def flush(self):
self._session.flush()
def commit(self):
self._session.commit()
def close(self):
self._session.close()
def rollback(self):
self._session.rollback()
def object_session(self, instance):
self._session.object_session(instance)
def execute(self, sql):
return self._session.execute(text(sql))
def done(self, close=True):
"""
执行完插入、删除、修改等操作后执行done如报错回滚本次事务的sql操作
:return:
"""
try:
self.commit()
if close:
self.close()
except Exception as e:
logger.warning(e)
self._session.rollback()
return e
@property
def session(self):
return self._session
def to_dict(self):
return {c.name: getattr(self, c.name, None) for c in self.__table__.columns}

View File

@@ -0,0 +1,37 @@
# encoding: UTF-8
import os
from urllib.parse import quote_plus as urlquote
from urllib.parse import quote
# dev环境
# BE_URL = '127.0.0.1:6080'
# online环境
BE_URL = '127.0.0.1:6080'
BASEDIR = os.path.dirname(os.path.abspath(__file__))
# PROJDIR = os.path.dirname(BASEDIR)
LOG_DIR = os.path.join(BASEDIR, 'logs')
# 返回码
RES_CODE = {
40001: 'URL不正确请检查',
40002: '不支持该请求方法!',
40003: '参数有误!',
40004: 'header错误',
40005: 'user_id不能为空! ',
40006: '获取下拉框列表失败!',
40007: '获取接口列表失败!',
}
# sqlalchemy链接
password = urlquote("peppa@test")
create_sql_url = 'mysql+pymysql://root:{}@10.250.200.53/tools-dev?charset=utf8mb4'.format(password) #测试库
PASSWORD = quote('AcUVeRb8lN')
REDIS_URL = "redis://:{}@redis.qa.huohua.cn:6379/30".format(PASSWORD)
STRESS_URI = 'https://qe.bg.huohua.cn'
CREATE_URI = '172.19.24.100'
# dev环境 qe domain
# QE_DOMAIN = 'http://qe.qa.huohua.cn'
# prod环境 qe domain
QE_DOMAIN = 'https://qe.bg.huohua.cn'

View File

@@ -0,0 +1,15 @@
# encoding: UTF-8
from .const import BE_URL
workers = 1 # 定义同时开启的处理请求的进程数量,根据网站流量适当调整
bind = BE_URL
threads = 2 # 多线程2个暂时就够用
debug = False
reload = False
loglevel = 'debug'
pidfile = "logs/gunicorn.pid"
accesslog = "logs/access.log" # 每个接口调用会展示在access.log中
errorlog = "logs/debug.log" # 未处理的报错会在debug.log日志中展示
timeout = 300 # 每个接口的超时时间
daemon = False # 是否开启守护进程。不开启可直接在idea或命令行中查看日志在服务器上需要修改为True来开启守护进程

View File

@@ -0,0 +1,39 @@
import logging
import os
from logging.handlers import TimedRotatingFileHandler
from base_framework.platform_tools.IDF.const import LOG_DIR
class FunctionalTestsLogger(logging.Logger):
def critical(self, msg, *args, **kwargs):
super(FunctionalTestsLogger, self).critical(msg, *args, **kwargs)
raise Exception(msg)
logging.setLoggerClass(FunctionalTestsLogger)
logger = logging.getLogger(FunctionalTestsLogger.__name__)
logger.setLevel(logging.DEBUG)
LOG_FMT = logging.Formatter("%(asctime)s %(filename)-24s[:%(lineno)-4d] %(levelname)-8s %(message)s")
# log by day
# fh = TimedRotatingFileHandler(
# filename=os.path.join(LOG_DIR, f'{datetime.datetime.now().strftime("%Y%m%d")}.log'),
# when="MIDNIGHT",
# encoding='utf-8')
fh = TimedRotatingFileHandler(
filename=os.path.join(LOG_DIR, 'it-log'),
when="MIDNIGHT",
encoding='utf-8')
fh.setLevel(logging.DEBUG)
fh.setFormatter(LOG_FMT)
logger.addHandler(fh)
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
ch.setFormatter(LOG_FMT)
logger.addHandler(ch)
logging.basicConfig()

View File

@@ -0,0 +1,64 @@
# encoding: UTF-8
from flask_cors import CORS
from flask import make_response, jsonify, request, redirect
import os, sys
import configparser
import argparse
from apscheduler.schedulers.background import BackgroundScheduler
BASIC_PATH = os.path.dirname(os.path.abspath(__file__))
BASE_PATH = os.path.abspath(os.path.join(BASIC_PATH, '../../../{}'.format("base_framework")))
sys.path.append(BASE_PATH)
PROJECT_PATH = os.path.abspath(os.path.join(BASIC_PATH, '../../..'))
sys.path.append(PROJECT_PATH)
env_choose_path = os.path.join(BASE_PATH, 'base_config', 'env_choose.ini')
team_names = ["UBRD","TMO","H2R","CC","LaLive","SCM","TO"]
for team in team_names:
TEAM_PATH = os.path.abspath(os.path.join(BASIC_PATH, '../../../{}'.format(team)))
sys.path.append(TEAM_PATH)
from base_framework.platform_tools.IDF.app import create_app
from base_framework.platform_tools.IDF.app.api.controller.commonController import CommonController
def start_env():
parser = argparse.ArgumentParser(description="Choose current environment")
parser.add_argument("-e", dest='env', help="ST or PRE", default='QA')
args = parser.parse_args()
choose_env = args.env
return choose_env
get_env = start_env()
cof = configparser.ConfigParser()
cof.read(env_choose_path, encoding='utf-8')
cof.set(section="is_ip_from_ini", option="is_ip_from_ini", value="false")
cof.set(section="run_evn_name", option="current_evn", value=get_env.upper())
with open(env_choose_path, 'w') as fw: # 循环写入
cof.write(fw)
app = create_app()
CORS(app, resources=r'/*')
create_common_controll = CommonController()
# 初始化调度器
scheduler = BackgroundScheduler()
# 添加每日任务每天凌晨0点执行
scheduler.add_job(create_common_controll.daily_task_update_month_number, 'cron', hour=0, minute=0)
# 启动调度器
scheduler.start()
if get_env == "SIM":
app.run(host="0.0.0.0", port=int("5112"), debug=True, use_reloader=False)
else:
app.run(host="0.0.0.0", port=int("5113"), debug=True, use_reloader=False)
def cors_response(res):
response = make_response(jsonify(res))
response.headers['Access-Control-Allow-Origin'] = '*'
response.headers['Access-Control-Allow-Methods'] = '*'
response.headers['Access-Control-Allow-Headers'] = 'x-requested-with,content-type'
return response

View File

@@ -0,0 +1,175 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?><project>
<actions/>
<description>调度任务公共job</description>
<keepDependencies>false</keepDependencies>
<properties>
<jenkins.model.BuildDiscarderProperty>
<strategy class="hudson.tasks.LogRotator">
<daysToKeep>30</daysToKeep>
<numToKeep>100</numToKeep>
<artifactDaysToKeep>-1</artifactDaysToKeep>
<artifactNumToKeep>-1</artifactNumToKeep>
</strategy>
</jenkins.model.BuildDiscarderProperty>
<com.sonyericsson.jenkins.plugins.bfa.model.ScannerJobProperty plugin="build-failure-analyzer@2.0.0">
<doNotScan>false</doNotScan>
</com.sonyericsson.jenkins.plugins.bfa.model.ScannerJobProperty>
<com.chikli.hudson.plugin.naginator.NaginatorOptOutProperty plugin="naginator@1.18.1">
<optOut>false</optOut>
</com.chikli.hudson.plugin.naginator.NaginatorOptOutProperty>
<com.sonyericsson.rebuild.RebuildSettings plugin="rebuild@1.32">
<autoRebuild>false</autoRebuild>
<rebuildDisabled>false</rebuildDisabled>
</com.sonyericsson.rebuild.RebuildSettings>
<hudson.model.ParametersDefinitionProperty>
<parameterDefinitions>
<hudson.model.StringParameterDefinition>
<name>special_env</name>
<description>独立环境</description>
<defaultValue>qa</defaultValue>
<trim>false</trim>
</hudson.model.StringParameterDefinition>
<hudson.model.StringParameterDefinition>
<name>test_case_path</name>
<description>目录和robot都可以</description>
<trim>false</trim>
</hudson.model.StringParameterDefinition>
<hudson.model.StringParameterDefinition>
<name>test_case_id</name>
<description>需要构建的用例编号多个用逗号隔开默认空构建所有。例query_classroom-1001-正常请求有数据,query_classroom-1002-正常请求无数据</description>
<trim>false</trim>
</hudson.model.StringParameterDefinition>
<hudson.model.StringParameterDefinition>
<name>inculde</name>
<description>需要构建的用例tag多个用逗号隔开默认空构建所有。例p0,p1备注tag中不能出现关键字大写AND、OR、NOT可用小写platform-27418</description>
<trim>false</trim>
</hudson.model.StringParameterDefinition>
<hudson.model.StringParameterDefinition>
<name>exculde</name>
<description>不需要构建的用例tag多个用逗号隔开默认空不过滤。例norun,del</description>
<defaultValue>norun</defaultValue>
<trim>false</trim>
</hudson.model.StringParameterDefinition>
<hudson.model.StringParameterDefinition>
<name>rerun</name>
<description>是否需要二次构建失败用例。true/false</description>
<defaultValue>true</defaultValue>
<trim>false</trim>
</hudson.model.StringParameterDefinition>
<hudson.model.StringParameterDefinition>
<name>git_path</name>
<description>构建代码地址</description>
<trim>false</trim>
</hudson.model.StringParameterDefinition>
<hudson.model.StringParameterDefinition>
<name>team</name>
<description>组名</description>
<trim>false</trim>
</hudson.model.StringParameterDefinition>
<hudson.model.StringParameterDefinition>
<name>team_code_path</name>
<description>代码分支</description>
<trim>false</trim>
</hudson.model.StringParameterDefinition>
<hudson.model.StringParameterDefinition>
<name>build_info_id</name>
<description>本次构建对应数据库id</description>
<trim>false</trim>
</hudson.model.StringParameterDefinition>
<hudson.model.StringParameterDefinition>
<name>is_use_db</name>
<description>使用数据库中参数构建qe平台使用</description>
<defaultValue>0</defaultValue>
<trim>false</trim>
</hudson.model.StringParameterDefinition>
</parameterDefinitions>
</hudson.model.ParametersDefinitionProperty>
</properties>
<scm class="org.jenkinsci.plugins.multiplescms.MultiSCM" plugin="multiple-scms@0.8">
<scms>
<hudson.plugins.git.GitSCM plugin="git@4.3.0">
<configVersion>2</configVersion>
<userRemoteConfigs>
<hudson.plugins.git.UserRemoteConfig>
<url>${git_path}</url>
<credentialsId>a670722b-96ec-449f-a2dc-6e6676bf8dbc</credentialsId>
</hudson.plugins.git.UserRemoteConfig>
</userRemoteConfigs>
<branches>
<hudson.plugins.git.BranchSpec>
<name>*/${team_code_path}</name>
</hudson.plugins.git.BranchSpec>
</branches>
<doGenerateSubmoduleConfigurations>false</doGenerateSubmoduleConfigurations>
<gitTool> git-1.8.3.1</gitTool>
<submoduleCfg class="list"/>
<extensions>
<hudson.plugins.git.extensions.impl.RelativeTargetDirectory>
<relativeTargetDir>./${team}</relativeTargetDir>
</hudson.plugins.git.extensions.impl.RelativeTargetDirectory>
</extensions>
</hudson.plugins.git.GitSCM>
<hudson.plugins.git.GitSCM plugin="git@4.3.0">
<configVersion>2</configVersion>
<userRemoteConfigs>
<hudson.plugins.git.UserRemoteConfig>
<url>https://git.bg.huohua.cn/h2asatp/base_framework.git</url>
<credentialsId>a670722b-96ec-449f-a2dc-6e6676bf8dbc</credentialsId>
</hudson.plugins.git.UserRemoteConfig>
</userRemoteConfigs>
<branches>
<hudson.plugins.git.BranchSpec>
<name>*/master</name>
</hudson.plugins.git.BranchSpec>
</branches>
<doGenerateSubmoduleConfigurations>false</doGenerateSubmoduleConfigurations>
<gitTool> git-1.8.3.1</gitTool>
<submoduleCfg class="list"/>
<extensions>
<hudson.plugins.git.extensions.impl.RelativeTargetDirectory>
<relativeTargetDir>./base_framework</relativeTargetDir>
</hudson.plugins.git.extensions.impl.RelativeTargetDirectory>
</extensions>
</hudson.plugins.git.GitSCM>
</scms>
</scm>
<assignedNode>SparkATP</assignedNode>
<canRoam>false</canRoam>
<disabled>false</disabled>
<blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
<blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
<triggers/>
<concurrentBuild>false</concurrentBuild>
<builders>
<hudson.tasks.Shell>
<command>python3 $WORKSPACE/../sparkatp-scripts/case_runner10.py -w $WORKSPACE -c $WORKSPACE/$test_case_path -t "$test_case_id" -i "$inculde" -e "$exculde" -r "$rerun" -env "$special_env" -team "$team" -b_id "$build_info_id" -b_url "$BUILD_URL" -is_db "$is_use_db"</command>
<configuredLocalRules/>
</hudson.tasks.Shell>
</builders>
<publishers>
<hudson.plugins.robot.RobotPublisher plugin="robot@2.1.2">
<outputPath>$WORKSPACE/Report/ci_out/out</outputPath>
<reportFileName>report.html</reportFileName>
<logFileName>log.html</logFileName>
<outputFileName>output.xml</outputFileName>
<disableArchiveOutput>false</disableArchiveOutput>
<passThreshold>100.0</passThreshold>
<unstableThreshold>90.0</unstableThreshold>
<otherFiles>
<string/>
</otherFiles>
<enableCache>true</enableCache>
<onlyCritical>true</onlyCritical>
</hudson.plugins.robot.RobotPublisher>
</publishers>
<buildWrappers>
<hudson.plugins.ws__cleanup.PreBuildCleanup plugin="ws-cleanup@0.38">
<deleteDirs>false</deleteDirs>
<cleanupParameter/>
<externalDelete/>
<disableDeferredWipeout>false</disableDeferredWipeout>
</hudson.plugins.ws__cleanup.PreBuildCleanup>
<hudson.plugins.timestamper.TimestamperBuildWrapper plugin="timestamper@1.11.3"/>
</buildWrappers>
</project>

32
click_email_link.py Normal file
View File

@@ -0,0 +1,32 @@
from playwright.sync_api import sync_playwright
def click_email_link():
url = "https://switch4.best-envision.com/activity/form-review?userLanguage=jp&webKey=JyOJY1HaUWBQ00oooiNOEjTEwMDg3ODA3LCJ1c2VyTmFtZSI6InRveTFrbHNOeTExMSIsImVtYWlsIjoidG8xQGpveWh1Yi5uZXQiLCJwdXNoX2lkIjoxMDc4LCJwdXNoX3R5cGUiOjgsInByb2R1Y3RzIjoiXHU3MmVlXHU1YjUwXHU5OGRlXHU2NzNhXHU2NzZmIiwiYnJhbmRfdWlkIjoyMTA1LCJjaGFuIjoiaW0iLCJwcm9kdWN0X2lkIjo3fQO0O0OO0O0O"
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto(url)
# 等待页面加载完成
page.wait_for_load_state('networkidle')
# 查找并点击邮箱超链接
# 邮箱链接通常包含 mailto: 协议
email_links = page.query_selector_all('a[href^="mailto:"]')
if email_links:
print(f"找到 {len(email_links)} 个邮箱链接")
# 点击第一个邮箱链接
email_links[0].click()
print("已点击第一个邮箱链接")
else:
print("未找到邮箱链接")
# 等待一段时间以便查看结果
page.wait_for_timeout(3000)
browser.close()
if __name__ == "__main__":
click_email_link()

View File

@@ -1,160 +0,0 @@
# -*- coding:utf-8 -*-
"""
国家信息管理业务关键字层
"""
import allure
from dulizhan.library.Dlizhan_interface import DlzhanInterface
from base_framework.public_tools import log
obj_log = log.get_logger()
class AddressCountryManage(DlzhanInterface):
"""国家信息管理业务关键字类"""
def __init__(self):
super().__init__()
@allure.step("创建国家信息")
def kw_joyhub_address_country_create_post(self, country_code, country_name, country_name_en, phone_code,
lingxing_country_code=None, paypal_country_code=None, status=1, id=0):
"""
创建国家信息业务关键字
:param id: 主键新增为0
:param country_code: 国家代码(如 CN/US
:param country_name: 国家名称
:param country_name_en: 国家英文名称
:param phone_code: 电话区号
:param lingxing_country_code: 领星国家代码(可选)
:param paypal_country_code: paypal国家代码可选
:param status: 状态 (1正常 2停用)
:return: 响应结果
"""
obj_log.info(f"创建国家信息 - country_code: {country_code}, country_name: {country_name}, status: {status}")
params = {
"id": id,
"countryCode": country_code,
"countryName": country_name,
"countryNameEn": country_name_en,
"phoneCode": phone_code,
"status": status
}
if lingxing_country_code:
params["lingxingCountryCode"] = lingxing_country_code
if paypal_country_code:
params["paypalCountryCode"] = paypal_country_code
resp = self.kw_in_joyhub_address_country_create_post(**params)
obj_log.info(f"创建国家信息响应: {resp}")
return resp
@allure.step("删除国家信息")
def kw_joyhub_address_country_delete_delete(self, country_id):
"""
删除国家信息业务关键字
:param country_id: 国家信息ID
:return: 响应结果
"""
obj_log.info(f"删除国家信息 - country_id: {country_id}")
resp = self.kw_in_joyhub_address_country_delete_delete(country_id)
obj_log.info(f"删除国家信息响应: {resp}")
return resp
@allure.step("批量删除国家信息")
def kw_joyhub_address_country_delete_list_delete(self, country_ids):
"""
批量删除国家信息业务关键字
:param country_ids: 国家信息ID列表
:return: 响应结果
"""
obj_log.info(f"批量删除国家信息 - country_ids: {country_ids}")
resp = self.kw_in_joyhub_address_country_delete_list_delete(country_ids)
obj_log.info(f"批量删除国家信息响应: {resp}")
return resp
@allure.step("获得国家信息详情")
def kw_joyhub_address_country_get_get(self, country_id):
"""
获得国家信息详情业务关键字
:param country_id: 国家信息ID
:return: 响应结果
"""
obj_log.info(f"获得国家信息详情 - country_id: {country_id}")
resp = self.kw_in_joyhub_address_country_get_get(country_id)
obj_log.info(f"获得国家信息详情响应: {resp}")
return resp
@allure.step("获得国家信息分页")
def kw_joyhub_address_country_page_get(self, page_num=1, page_size=10, **kwargs):
"""
获得国家信息分页业务关键字
:param page_num: 页码
:param page_size: 每页大小
:param kwargs: 其他查询条件
:return: 响应结果
"""
obj_log.info(f"获得国家信息分页 - page_num: {page_num}, page_size: {page_size}")
params = {
"page": page_num,
"size": page_size
}
params.update(kwargs)
resp = self.kw_in_joyhub_address_country_page_get(**params)
obj_log.info(f"获得国家信息分页响应: {resp}")
return resp
@allure.step("更新国家信息")
def kw_joyhub_address_country_update_put(self, country_id, country_code, country_name, country_name_en, phone_code,
lingxing_country_code=None, paypal_country_code=None, status=1):
"""
更新国家信息业务关键字
:param country_id: 国家信息ID
:param country_code: 国家代码
:param country_name: 国家名称
:param country_name_en: 国家英文名称
:param phone_code: 电话区号
:param lingxing_country_code: 领星国家代码(可选)
:param paypal_country_code: paypal国家代码可选
:param status: 状态 (1正常 2停用)
:return: 响应结果
"""
obj_log.info(f"更新国家信息 - country_id: {country_id}, country_code: {country_code}, country_name: {country_name}")
params = {
"id": country_id,
"countryCode": country_code,
"countryName": country_name,
"countryNameEn": country_name_en,
"phoneCode": phone_code,
"status": status
}
if lingxing_country_code:
params["lingxingCountryCode"] = lingxing_country_code
if paypal_country_code:
params["paypalCountryCode"] = paypal_country_code
resp = self.kw_in_joyhub_address_country_update_put(**params)
obj_log.info(f"更新国家信息响应: {resp}")
return resp
@allure.step("批量更新国家信息状态")
def kw_joyhub_address_country_update_status_list_put(self, country_ids, status):
"""
批量更新国家信息状态业务关键字
:param country_ids: 国家信息ID列表
:param status: 状态 (1正常 2停用)
:return: 响应结果
"""
obj_log.info(f"批量更新国家信息状态 - country_ids: {country_ids}, status: {status}")
# 接口参数通过query传递ids需要用逗号分隔
ids_str = ','.join(map(str, country_ids))
resp = self.kw_in_joyhub_address_country_update_status_list_put(ids=ids_str, status=status)
obj_log.info(f"批量更新国家信息状态响应: {resp}")
return resp

View File

@@ -1,252 +0,0 @@
# -*- coding:utf-8 -*-
import os
import sys
current_file_path = os.path.abspath(__file__)
project_root = os.path.abspath(os.path.join(os.path.dirname(current_file_path), '../../../'))
if project_root not in sys.path:
sys.path.insert(0, project_root)
from dulizhan.library.Dlizhan_interface import DlzhanInterface
from base_framework.public_tools import log
import allure
obj_log = log.get_logger()
class AfterSalesPolicyManage(DlzhanInterface):
def __init__(self):
super().__init__()
# ============ 售后政策管理 ============
@allure.step("创建售后政策")
def kw_joyhub_after_sales_policy_create_post(self, title, content, lang, brand_id=0, status=1, id=0):
"""
创建售后政策业务关键字
:param id: 主键新增为0
:param brand_id: 品牌ID
:param title: 标题
:param content: 内容
:param lang: 语言 (en 英语 de 德语 ja 日语)
:param status: 状态 (1正常 2停用)
:return: 响应结果
"""
obj_log.info(f"创建售后政策 - title: {title}, content: {content[:50]}..., lang: {lang}, brand_id: {brand_id}, status: {status}")
params = {
"id": id,
"brandId": brand_id,
"title": title,
"content": content,
"lang": lang,
"status": status
}
resp = self.kw_in_joyhub_after_sales_policy_create_post(**params)
obj_log.info(f"创建售后政策响应: {resp}")
return resp
@allure.step("删除售后政策")
def kw_joyhub_after_sales_policy_delete_delete(self, policy_id):
"""
删除售后政策业务关键字
:param policy_id: 售后政策ID
:return: 响应结果
"""
obj_log.info(f"删除售后政策 - policy_id: {policy_id}")
resp = self.kw_in_joyhub_after_sales_policy_delete_delete(policy_id)
obj_log.info(f"删除售后政策响应: {resp}")
return resp
@allure.step("批量删除售后政策")
def kw_joyhub_after_sales_policy_delete_list_delete(self, policy_ids):
"""
批量删除售后政策业务关键字
:param policy_ids: 售后政策ID列表
:return: 响应结果
"""
obj_log.info(f"批量删除售后政策 - policy_ids: {policy_ids}")
resp = self.kw_in_joyhub_after_sales_policy_delete_list_delete(policy_ids)
obj_log.info(f"批量删除售后政策响应: {resp}")
return resp
@allure.step("获得售后政策详情")
def kw_joyhub_after_sales_policy_get_get(self, policy_id):
"""
获得售后政策详情业务关键字
:param policy_id: 售后政策ID
:return: 响应结果
"""
obj_log.info(f"获得售后政策详情 - policy_id: {policy_id}")
resp = self.kw_in_joyhub_after_sales_policy_get_get(policy_id)
obj_log.info(f"获得售后政策详情响应: {resp}")
return resp
@allure.step("获得售后政策分页列表")
def kw_joyhub_after_sales_policy_page_get(self, **kwargs):
"""
获得售后政策分页列表业务关键字
:param kwargs: 查询参数title, content, status, page_no, page_size
:return: 响应结果
"""
obj_log.info(f"获得售后政策分页列表 - 参数: {kwargs}")
resp = self.kw_in_joyhub_after_sales_policy_page_get(**kwargs)
obj_log.info(f"获得售后政策分页列表响应: {resp}")
return resp
@allure.step("更新售后政策")
def kw_joyhub_after_sales_policy_update_put(self, policy_id, title, content, lang, brand_id=0, status=1):
"""
更新售后政策业务关键字
:param policy_id: 售后政策ID
:param brand_id: 品牌ID
:param title: 标题
:param content: 内容
:param lang: 语言 (en 英语 de 德语 ja 日语)
:param status: 状态 (1正常 2停用)
:return: 响应结果
"""
obj_log.info(f"更新售后政策 - policy_id: {policy_id}, title: {title}, content: {content[:50]}..., lang: {lang}, brand_id: {brand_id}, status: {status}")
params = {
"id": policy_id,
"brandId": brand_id,
"title": title,
"content": content,
"lang": lang,
"status": status
}
resp = self.kw_in_joyhub_after_sales_policy_update_put(**params)
obj_log.info(f"更新售后政策响应: {resp}")
return resp
# ============ 售后政策-品牌管理 ============
@allure.step("创建售后政策-品牌")
def kw_joyhub_after_sales_brand_create_post(self, brand_name, after_sales_policy_id, status=1, id=0):
"""
创建售后政策-品牌业务关键字
:param id: 主键新增为0
:param brand_name: 品牌名称
:param after_sales_policy_id: 售后政策ID
:param status: 状态 (1正常 2停用)
:return: 响应结果
"""
obj_log.info(f"创建售后政策-品牌 - brand_name: {brand_name}, after_sales_policy_id: {after_sales_policy_id}, status: {status}")
params = {
"id": id,
"brandName": brand_name,
"afterSalesPolicyId": after_sales_policy_id,
"status": status
}
resp = self.kw_in_joyhub_after_sales_brand_create_post(**params)
obj_log.info(f"创建售后政策-品牌响应: {resp}")
return resp
@allure.step("删除售后政策-品牌")
def kw_joyhub_after_sales_brand_delete_delete(self, brand_id):
"""
删除售后政策-品牌业务关键字
:param brand_id: 售后政策-品牌ID
:return: 响应结果
"""
obj_log.info(f"删除售后政策-品牌 - brand_id: {brand_id}")
resp = self.kw_in_joyhub_after_sales_brand_delete_delete(brand_id)
obj_log.info(f"删除售后政策-品牌响应: {resp}")
return resp
@allure.step("批量删除售后政策-品牌")
def kw_joyhub_after_sales_brand_delete_list_delete(self, brand_ids):
"""
批量删除售后政策-品牌业务关键字
:param brand_ids: 售后政策-品牌ID列表
:return: 响应结果
"""
obj_log.info(f"批量删除售后政策-品牌 - brand_ids: {brand_ids}")
resp = self.kw_in_joyhub_after_sales_brand_delete_list_delete(brand_ids)
obj_log.info(f"批量删除售后政策-品牌响应: {resp}")
return resp
@allure.step("获得售后政策-品牌详情")
def kw_joyhub_after_sales_brand_get_get(self, brand_id):
"""
获得售后政策-品牌详情业务关键字
:param brand_id: 售后政策-品牌ID
:return: 响应结果
"""
obj_log.info(f"获得售后政策-品牌详情 - brand_id: {brand_id}")
resp = self.kw_in_joyhub_after_sales_brand_get_get(brand_id)
obj_log.info(f"获得售后政策-品牌详情响应: {resp}")
return resp
@allure.step("获得可用的品牌列表")
def kw_joyhub_after_sales_brand_list_available_get(self):
"""
获得可用的品牌列表业务关键字
:return: 响应结果
"""
obj_log.info("获得可用的品牌列表")
resp = self.kw_in_joyhub_after_sales_brand_list_available_get()
obj_log.info(f"获得可用的品牌列表响应: {resp}")
return resp
@allure.step("获得售后政策-品牌分页列表")
def kw_joyhub_after_sales_brand_page_get(self, **kwargs):
"""
获得售后政策-品牌分页列表业务关键字
:param kwargs: 查询参数brand_name, after_sales_policy_id, status, page_no, page_size
:return: 响应结果
"""
obj_log.info(f"获得售后政策-品牌分页列表 - 参数: {kwargs}")
resp = self.kw_in_joyhub_after_sales_brand_page_get(**kwargs)
obj_log.info(f"获得售后政策-品牌分页列表响应: {resp}")
return resp
@allure.step("更新售后政策-品牌")
def kw_joyhub_after_sales_brand_update_put(self, brand_id, brand_name, after_sales_policy_id, status=1):
"""
更新售后政策-品牌业务关键字
:param brand_id: 售后政策-品牌ID
:param brand_name: 品牌名称
:param after_sales_policy_id: 售后政策ID
:param status: 状态 (1正常 2停用)
:return: 响应结果
"""
obj_log.info(f"更新售后政策-品牌 - brand_id: {brand_id}, brand_name: {brand_name}, after_sales_policy_id: {after_sales_policy_id}, status: {status}")
params = {
"id": brand_id,
"brandName": brand_name,
"afterSalesPolicyId": after_sales_policy_id,
"status": status
}
resp = self.kw_in_joyhub_after_sales_brand_update_put(**params)
obj_log.info(f"更新售后政策-品牌响应: {resp}")
return resp

View File

@@ -1,130 +0,0 @@
# -*- coding:utf-8 -*-
"""
blog分类管理业务关键字层
"""
import allure
from dulizhan.library.Dlizhan_interface import DlzhanInterface
from base_framework.public_tools import log
obj_log = log.get_logger()
class BlogCateManage(DlzhanInterface):
"""blog分类管理业务关键字类"""
def __init__(self):
super().__init__()
@allure.step("创建blog分类")
def kw_joyhub_blog_cate_create_post(self, name, id=0, status=1, rank_num=None, route=None, cover_image=None):
"""
创建blog分类业务关键字
:param id: 主键新增为0
:param name: 分类名称
:param status: 状态 (1正常 2停用)
:param rank_num: 排序(可选)
:param route: 路由(可选)
:param cover_image: 封面图对象,格式: {"url": "xxx", "name": None, "alt": ""}(可选)
:return: 响应结果
"""
obj_log.info(f"创建blog分类 - name: {name}, status: {status}")
params = {
"id": id,
"name": name,
"status": status
}
if rank_num is not None:
params["rankNum"] = rank_num
if route is not None:
params["route"] = route
if cover_image is not None:
if isinstance(cover_image, str):
params["coverImage"] = {"url": cover_image, "name": None, "alt": ""}
else:
params["coverImage"] = cover_image
resp = self.kw_in_joyhub_blog_cate_create_post(**params)
obj_log.info(f"创建blog分类响应: {resp}")
return resp
@allure.step("删除blog分类")
def kw_joyhub_blog_cate_delete_delete(self, cate_id):
"""
删除blog分类业务关键字
:param cate_id: blog分类ID
:return: 响应结果
"""
obj_log.info(f"删除blog分类 - cate_id: {cate_id}")
resp = self.kw_in_joyhub_blog_cate_delete_delete(cate_id)
obj_log.info(f"删除blog分类响应: {resp}")
return resp
@allure.step("获得blog分类详情")
def kw_joyhub_blog_cate_get_get(self, cate_id):
"""
获得blog分类详情业务关键字
:param cate_id: blog分类ID
:return: 响应结果
"""
obj_log.info(f"获得blog分类详情 - cate_id: {cate_id}")
resp = self.kw_in_joyhub_blog_cate_get_get(cate_id)
obj_log.info(f"获得blog分类详情响应: {resp}")
return resp
@allure.step("获得blog分类分页")
def kw_joyhub_blog_cate_page_get(self, page_num=1, page_size=10, **kwargs):
"""
获得blog分类分页业务关键字
:param page_num: 页码
:param page_size: 每页大小
:param kwargs: 其他查询条件
:return: 响应结果
"""
obj_log.info(f"获得blog分类分页 - page_num: {page_num}, page_size: {page_size}")
params = {
"page": page_num,
"size": page_size
}
params.update(kwargs)
resp = self.kw_in_joyhub_blog_cate_page_get(**params)
obj_log.info(f"获得blog分类分页响应: {resp}")
return resp
@allure.step("更新blog分类")
def kw_joyhub_blog_cate_update_put(self, cate_id, name, status=1, rank_num=None, route=None, cover_image=None):
"""
更新blog分类业务关键字
:param cate_id: blog分类ID
:param name: 分类名称
:param status: 状态 (1正常 2停用)
:param rank_num: 排序(可选)
:param route: 路由(可选)
:param cover_image: 封面图对象,格式: {"url": "xxx", "name": None, "alt": ""}(可选)
:return: 响应结果
"""
obj_log.info(f"更新blog分类 - cate_id: {cate_id}, name: {name}, status: {status}")
params = {
"id": cate_id,
"name": name,
"status": status
}
if rank_num is not None:
params["rankNum"] = rank_num
if route is not None:
params["route"] = route
if cover_image is not None:
if isinstance(cover_image, str):
params["coverImage"] = {"url": cover_image, "name": None, "alt": ""}
else:
params["coverImage"] = cover_image
resp = self.kw_in_joyhub_blog_cate_update_put(**params)
obj_log.info(f"更新blog分类响应: {resp}")
return resp

View File

@@ -1,125 +0,0 @@
# -*- coding:utf-8 -*-
"""
二维码管理业务关键字层
"""
import allure
from dulizhan.library.Dlizhan_interface import DlzhanInterface
from base_framework.public_tools import log
obj_log = log.get_logger()
class DownloadQrcodeManage(DlzhanInterface):
"""二维码管理业务关键字类"""
def __init__(self):
super().__init__()
@allure.step("创建二维码")
def kw_joyhub_download_qrcode_create_post(self, title, id=0, status=1):
"""
创建二维码业务关键字
:param id: 主键新增为0
:param title: 标题
:param status: 状态 (1正常 2停用)
:return: 响应结果
"""
obj_log.info(f"创建二维码 - title: {title}, status: {status}")
params = {
"id": id,
"title": title,
"status": status
}
resp = self.kw_in_joyhub_download_qrcode_create_post(**params)
obj_log.info(f"创建二维码响应: {resp}")
return resp
@allure.step("获得二维码详情")
def kw_joyhub_download_qrcode_get_get(self, qrcode_id):
"""
获得二维码详情业务关键字
:param qrcode_id: 二维码ID
:return: 响应结果
"""
obj_log.info(f"获得二维码详情 - qrcode_id: {qrcode_id}")
resp = self.kw_in_joyhub_download_qrcode_get_get(qrcode_id)
obj_log.info(f"获得二维码详情响应: {resp}")
return resp
@allure.step("获得二维码分页")
def kw_joyhub_download_qrcode_page_get(self, page_no=1, page_size=10, **kwargs):
"""
获得二维码分页业务关键字
:param page_no: 页码
:param page_size: 每页大小
:param kwargs: 其他查询条件
:return: 响应结果
"""
obj_log.info(f"获得二维码分页 - page_no: {page_no}, page_size: {page_size}")
params = {
"pageNo": page_no,
"pageSize": page_size
}
params.update(kwargs)
resp = self.kw_in_joyhub_download_qrcode_page_get(**params)
obj_log.info(f"获得二维码分页响应: {resp}")
return resp
@allure.step("更新二维码")
def kw_joyhub_download_qrcode_update_put(self, qrcode_id, title, status=1):
"""
更新二维码业务关键字
:param qrcode_id: 二维码ID
:param title: 标题
:param status: 状态 (1正常 2停用)
:return: 响应结果
"""
obj_log.info(f"更新二维码 - qrcode_id: {qrcode_id}, title: {title}, status: {status}")
params = {
"id": qrcode_id,
"title": title,
"status": status
}
resp = self.kw_in_joyhub_download_qrcode_update_put(**params)
obj_log.info(f"更新二维码响应: {resp}")
return resp
def clean_test_data_from_db(self, title):
"""
从数据库表jh_download_qrcode中删除测试数据
:param title: 要删除的二维码标题
:return: 删除是否成功
"""
obj_log.info(f"从数据库删除测试数据 - title: {title}")
try:
import pymysql
# 数据库连接配置(需要根据实际环境配置)
connection = pymysql.connect(
host='localhost',
user='root',
password='password',
database='joyhub',
charset='utf8mb4'
)
with connection.cursor() as cursor:
sql = "DELETE FROM jh_download_qrcode WHERE title LIKE %s"
cursor.execute(sql, (f"%{title}%",))
connection.commit()
deleted_count = cursor.rowcount
obj_log.info(f"成功删除 {deleted_count} 条测试数据")
return True
except Exception as e:
obj_log.error(f"删除测试数据失败: {str(e)}")
return False
finally:
if 'connection' in locals():
connection.close()

View File

@@ -1,108 +0,0 @@
# -*- coding:utf-8 -*-
"""
FAQ分类管理业务关键字层
"""
import allure
from dulizhan.library.Dlizhan_interface import DlzhanInterface
from base_framework.public_tools import log
obj_log = log.get_logger()
class FaqCateManage(DlzhanInterface):
"""FAQ分类管理业务关键字类"""
def __init__(self):
super().__init__()
@allure.step("创建FAQ分类")
def kw_joyhub_faq_cate_create_post(self, title, lang, rank_num, pid=0, status=1, id=0):
"""
创建FAQ分类业务关键字
:param id: 主键新增为0
:param pid: 父分类ID默认为0顶级分类
:param title: 分类名称
:param status: 状态 (1正常 2停用)
:param rank_num: 排序号
:param lang: 语言 (en 英语 de 德语 ja 日语)
:return: 响应结果
"""
obj_log.info(f"创建FAQ分类 - title: {title}, lang: {lang}, pid: {pid}")
params = {
"id": id,
"pid": pid,
"title": title,
"status": status,
"rankNum": rank_num,
"lang": lang
}
resp = self.kw_in_joyhub_faq_cate_create_post(**params)
obj_log.info(f"创建FAQ分类响应: {resp}")
return resp
@allure.step("删除FAQ分类")
def kw_joyhub_faq_cate_delete_delete(self, faq_cate_id):
"""
删除FAQ分类业务关键字
:param faq_cate_id: FAQ分类ID
:return: 响应结果
"""
obj_log.info(f"删除FAQ分类 - faq_cate_id: {faq_cate_id}")
resp = self.kw_in_joyhub_faq_cate_delete_delete(faq_cate_id)
obj_log.info(f"删除FAQ分类响应: {resp}")
return resp
@allure.step("获得FAQ分类详情")
def kw_joyhub_faq_cate_get_get(self, faq_cate_id):
"""
获得FAQ分类详情业务关键字
:param faq_cate_id: FAQ分类ID
:return: 响应结果
"""
obj_log.info(f"获得FAQ分类详情 - faq_cate_id: {faq_cate_id}")
resp = self.kw_in_joyhub_faq_cate_get_get(faq_cate_id)
obj_log.info(f"获得FAQ分类详情响应: {resp}")
return resp
@allure.step("获得FAQ分类列表")
def kw_joyhub_faq_cate_list_get(self, **kwargs):
"""
获得FAQ分类列表业务关键字
:param kwargs: 其他查询条件
:return: 响应结果
"""
obj_log.info(f"获得FAQ分类列表 - kwargs: {kwargs}")
resp = self.kw_in_joyhub_faq_cate_list_get(**kwargs)
obj_log.info(f"获得FAQ分类列表响应: {resp}")
return resp
@allure.step("更新FAQ分类")
def kw_joyhub_faq_cate_update_put(self, faq_cate_id, title, lang, rank_num, pid=0, status=1):
"""
更新FAQ分类业务关键字
:param faq_cate_id: FAQ分类ID
:param pid: 父分类ID
:param title: 分类名称
:param status: 状态 (1正常 2停用)
:param rank_num: 排序号
:param lang: 语言 (en 英语 de 德语 ja 日语)
:return: 响应结果
"""
obj_log.info(f"更新FAQ分类 - faq_cate_id: {faq_cate_id}, title: {title}, lang: {lang}")
params = {
"id": faq_cate_id,
"pid": pid,
"title": title,
"status": status,
"rankNum": rank_num,
"lang": lang
}
resp = self.kw_in_joyhub_faq_cate_update_put(**params)
obj_log.info(f"更新FAQ分类响应: {resp}")
return resp

View File

@@ -1,148 +0,0 @@
# -*- coding:utf-8 -*-
"""
FAQ数据管理业务关键字层
"""
import allure
from dulizhan.library.Dlizhan_interface import DlzhanInterface
from base_framework.public_tools import log
obj_log = log.get_logger()
class FaqManage(DlzhanInterface):
"""FAQ数据管理业务关键字类"""
def __init__(self):
super().__init__()
@allure.step("获得FAQ分类下拉列表")
def kw_joyhub_faq_cate_list_get(self):
"""
获得FAQ分类下拉列表业务关键字
:return: 响应结果
"""
obj_log.info("获得FAQ分类下拉列表")
resp = self.kw_in_joyhub_faq_cate_list_get()
obj_log.info(f"获得FAQ分类下拉列表响应: {resp}")
return resp
@allure.step("创建FAQ数据")
def kw_joyhub_faq_create_post(self, faq_cate_id, question, answer, rank_num, lang, is_hot=0, status=1, id=0):
"""
创建FAQ数据业务关键字
:param id: 主键新增为0
:param faq_cate_id: 分类ID
:param question: 常见问题
:param answer: 回答
:param is_hot: 是否热门0否1是
:param status: 状态 (1正常 2停用)
:param rank_num: 排序号
:param lang: 语言 (en 英语 de 德语 ja 日语)
:return: 响应结果
"""
obj_log.info(f"创建FAQ数据 - question: {question}, lang: {lang}")
params = {
"id": id,
"faqCateId": faq_cate_id,
"question": question,
"answer": answer,
"isHot": is_hot,
"status": status,
"rankNum": rank_num,
"lang": lang
}
resp = self.kw_in_joyhub_faq_create_post(**params)
obj_log.info(f"创建FAQ数据响应: {resp}")
return resp
@allure.step("删除FAQ数据")
def kw_joyhub_faq_delete_delete(self, faq_id):
"""
删除FAQ数据业务关键字
:param faq_id: FAQ数据ID
:return: 响应结果
"""
obj_log.info(f"删除FAQ数据 - faq_id: {faq_id}")
resp = self.kw_in_joyhub_faq_delete_delete(faq_id)
obj_log.info(f"删除FAQ数据响应: {resp}")
return resp
@allure.step("批量删除FAQ数据")
def kw_joyhub_faq_delete_list_delete(self, ids):
"""
批量删除FAQ数据业务关键字
:param ids: FAQ数据ID列表
:return: 响应结果
"""
obj_log.info(f"批量删除FAQ数据 - ids: {ids}")
resp = self.kw_in_joyhub_faq_delete_list_delete(ids)
obj_log.info(f"批量删除FAQ数据响应: {resp}")
return resp
@allure.step("获得FAQ数据详情")
def kw_joyhub_faq_get_get(self, faq_id):
"""
获得FAQ数据详情业务关键字
:param faq_id: FAQ数据ID
:return: 响应结果
"""
obj_log.info(f"获得FAQ数据详情 - faq_id: {faq_id}")
resp = self.kw_in_joyhub_faq_get_get(faq_id)
obj_log.info(f"获得FAQ数据详情响应: {resp}")
return resp
@allure.step("获得FAQ数据分页")
def kw_joyhub_faq_page_get(self, page_no=1, page_size=10, **kwargs):
"""
获得FAQ数据分页业务关键字
:param page_no: 页码
:param page_size: 每页大小
:param kwargs: 其他查询条件
:return: 响应结果
"""
obj_log.info(f"获得FAQ数据分页 - page_no: {page_no}, page_size: {page_size}")
params = {
"pageNo": page_no,
"pageSize": page_size
}
params.update(kwargs)
resp = self.kw_in_joyhub_faq_page_get(**params)
obj_log.info(f"获得FAQ数据分页响应: {resp}")
return resp
@allure.step("更新FAQ数据")
def kw_joyhub_faq_update_put(self, faq_id, faq_cate_id, question, answer, rank_num, lang, is_hot=0, status=1):
"""
更新FAQ数据业务关键字
:param faq_id: FAQ数据ID
:param faq_cate_id: 分类ID
:param question: 常见问题
:param answer: 回答
:param is_hot: 是否热门0否1是
:param status: 状态 (1正常 2停用)
:param rank_num: 排序号
:param lang: 语言 (en 英语 de 德语 ja 日语)
:return: 响应结果
"""
obj_log.info(f"更新FAQ数据 - faq_id: {faq_id}, question: {question}, lang: {lang}")
params = {
"id": faq_id,
"faqCateId": faq_cate_id,
"question": question,
"answer": answer,
"isHot": is_hot,
"status": status,
"rankNum": rank_num,
"lang": lang
}
resp = self.kw_in_joyhub_faq_update_put(**params)
obj_log.info(f"更新FAQ数据响应: {resp}")
return resp

View File

@@ -1,167 +0,0 @@
# -*- coding:utf-8 -*-
"""
news分类管理业务关键字层
"""
import allure
from dulizhan.library.Dlizhan_interface import DlzhanInterface
from base_framework.public_tools import log
obj_log = log.get_logger()
class NewsCateManage(DlzhanInterface):
"""news分类管理业务关键字类"""
def __init__(self):
super().__init__()
@allure.step("创建news分类")
def kw_joyhub_news_cate_create_post(self, name, id=0, status=1, rank_num=1, route=None, cover_image=None):
"""
创建news分类业务关键字
:param id: 主键新增为0
:param name: 分类名称
:param status: 状态 (1正常 2停用)
:param rank_num: 排序号
:param route: 路由(可选)
:param cover_image: 缩略图(可选)
:return: 响应结果
"""
obj_log.info(f"创建news分类 - name: {name}")
params = {
"id": id,
"name": name,
"status": status,
"rankNum": rank_num
}
if route is not None:
params["route"] = route
if cover_image is not None:
params["coverImage"] = cover_image
resp = self.kw_in_joyhub_news_cate_create_post(**params)
obj_log.info(f"创建news分类响应: {resp}")
return resp
@allure.step("删除news分类")
def kw_joyhub_news_cate_delete_delete(self, news_cate_id):
"""
删除news分类业务关键字
:param news_cate_id: news分类ID
:return: 响应结果
"""
obj_log.info(f"删除news分类 - news_cate_id: {news_cate_id}")
resp = self.kw_in_joyhub_news_cate_delete_delete(news_cate_id)
obj_log.info(f"删除news分类响应: {resp}")
return resp
@allure.step("批量删除news分类")
def kw_joyhub_news_cate_delete_list_delete(self, ids):
"""
批量删除news分类业务关键字
:param ids: news分类ID列表
:return: 响应结果
"""
obj_log.info(f"批量删除news分类 - ids: {ids}")
resp = self.kw_in_joyhub_news_cate_delete_list_delete(ids)
obj_log.info(f"批量删除news分类响应: {resp}")
return resp
@allure.step("获得news分类详情")
def kw_joyhub_news_cate_get_get(self, news_cate_id):
"""
获得news分类详情业务关键字
:param news_cate_id: news分类ID
:return: 响应结果
"""
obj_log.info(f"获得news分类详情 - news_cate_id: {news_cate_id}")
resp = self.kw_in_joyhub_news_cate_get_get(news_cate_id)
obj_log.info(f"获得news分类详情响应: {resp}")
return resp
@allure.step("获得news分类分页")
def kw_joyhub_news_cate_page_get(self, page_no=1, page_size=10, **kwargs):
"""
获得news分类分页业务关键字
:param page_no: 页码
:param page_size: 每页大小
:param kwargs: 其他查询条件
:return: 响应结果
"""
obj_log.info(f"获得news分类分页 - page_no: {page_no}, page_size: {page_size}")
params = {
"pageNo": page_no,
"pageSize": page_size
}
params.update(kwargs)
resp = self.kw_in_joyhub_news_cate_page_get(**params)
obj_log.info(f"获得news分类分页响应: {resp}")
return resp
@allure.step("更新news分类")
def kw_joyhub_news_cate_update_put(self, news_cate_id, name, status=1, rank_num=1, route=None, cover_image=None):
"""
更新news分类业务关键字
:param news_cate_id: news分类ID
:param name: 分类名称
:param status: 状态 (1正常 2停用)
:param rank_num: 排序号
:param route: 路由(可选)
:param cover_image: 缩略图(可选)
:return: 响应结果
"""
obj_log.info(f"更新news分类 - news_cate_id: {news_cate_id}, name: {name}")
params = {
"id": news_cate_id,
"name": name,
"status": status,
"rankNum": rank_num
}
if route is not None:
params["route"] = route
if cover_image is not None:
params["coverImage"] = cover_image
resp = self.kw_in_joyhub_news_cate_update_put(**params)
obj_log.info(f"更新news分类响应: {resp}")
return resp
def clean_test_data_from_db(self, name):
"""
从数据库表jh_news_cate中删除测试数据
:param name: 要删除的分类名称
:return: 删除是否成功
"""
obj_log.info(f"从数据库删除测试数据 - name: {name}")
try:
import pymysql
# 数据库连接配置(需要根据实际环境配置)
connection = pymysql.connect(
host='localhost',
user='root',
password='password',
database='joyhub',
charset='utf8mb4'
)
with connection.cursor() as cursor:
sql = "DELETE FROM jh_news_cate WHERE name LIKE %s"
cursor.execute(sql, (f"%{name}%",))
connection.commit()
deleted_count = cursor.rowcount
obj_log.info(f"成功删除 {deleted_count} 条测试数据")
return True
except Exception as e:
obj_log.error(f"删除测试数据失败: {str(e)}")
return False
finally:
if 'connection' in locals():
connection.close()

View File

@@ -1,187 +0,0 @@
# -*- coding:utf-8 -*-
"""
news管理业务关键字层
"""
import allure
from dulizhan.library.Dlizhan_interface import DlzhanInterface
from base_framework.public_tools import log
obj_log = log.get_logger()
class NewsManage(DlzhanInterface):
"""news管理业务关键字类"""
def __init__(self):
super().__init__()
@allure.step("创建news管理")
def kw_joyhub_news_create_post(self, title, cover_image, content, id=0, status=1, rank_num=1,
seo_title=None, seo_keyword=None, seo_description=None,
likes_num=0, cate_ids=None, route=None, publish_time=None):
"""
创建news管理业务关键字
:param id: 主键新增为0
:param title: 标题
:param cover_image: 缩略图
:param content: PC页面内容
:param status: 状态 (1正常 2停用)
:param rank_num: 排序号
:param seo_title: SEO标题可选
:param seo_keyword: SEO关键词可选
:param seo_description: SEO描述可选
:param likes_num: 点赞数(可选)
:param cate_ids: news分类ID列表可选
:param route: 路由(可选)
:param publish_time: 发布时间(可选)
:return: 响应结果
"""
obj_log.info(f"创建news管理 - title: {title}")
params = {
"id": id,
"title": title,
"coverImage": cover_image,
"content": content,
"status": status,
"rankNum": rank_num,
"likesNum": likes_num
}
if seo_title is not None:
params["seoTitle"] = seo_title
if seo_keyword is not None:
params["seoKeyword"] = seo_keyword
if seo_description is not None:
params["seoDescription"] = seo_description
if cate_ids is not None:
params["cateIds"] = cate_ids
if route is not None:
params["route"] = route
if publish_time is not None:
params["publishTime"] = publish_time
resp = self.kw_in_joyhub_news_create_post(**params)
obj_log.info(f"创建news管理响应: {resp}")
return resp
@allure.step("删除news管理")
def kw_joyhub_news_delete_delete(self, news_id):
"""
删除news管理业务关键字
:param news_id: news管理ID
:return: 响应结果
"""
obj_log.info(f"删除news管理 - news_id: {news_id}")
resp = self.kw_in_joyhub_news_delete_delete(news_id)
obj_log.info(f"删除news管理响应: {resp}")
return resp
@allure.step("批量删除news管理")
def kw_joyhub_news_delete_list_delete(self, ids):
"""
批量删除news管理业务关键字
:param ids: news管理ID列表
:return: 响应结果
"""
obj_log.info(f"批量删除news管理 - ids: {ids}")
resp = self.kw_in_joyhub_news_delete_list_delete(ids)
obj_log.info(f"批量删除news管理响应: {resp}")
return resp
@allure.step("获得news管理详情")
def kw_joyhub_news_get_get(self, news_id):
"""
获得news管理详情业务关键字
:param news_id: news管理ID
:return: 响应结果
"""
obj_log.info(f"获得news管理详情 - news_id: {news_id}")
resp = self.kw_in_joyhub_news_get_get(news_id)
obj_log.info(f"获得news管理详情响应: {resp}")
return resp
@allure.step("获得news分类关联列表")
def kw_joyhub_news_cate_relation_list_get(self, news_id):
"""
获得news分类关联列表业务关键字
:param news_id: news管理ID
:return: 响应结果
"""
obj_log.info(f"获得news分类关联列表 - news_id: {news_id}")
resp = self.kw_in_joyhub_news_cate_relation_list_get(news_id)
obj_log.info(f"获得news分类关联列表响应: {resp}")
return resp
@allure.step("获得news管理分页")
def kw_joyhub_news_page_get(self, page_no=1, page_size=10, **kwargs):
"""
获得news管理分页业务关键字
:param page_no: 页码
:param page_size: 每页大小
:param kwargs: 其他查询条件
:return: 响应结果
"""
obj_log.info(f"获得news管理分页 - page_no: {page_no}, page_size: {page_size}")
params = {
"pageNo": page_no,
"pageSize": page_size
}
params.update(kwargs)
resp = self.kw_in_joyhub_news_page_get(**params)
obj_log.info(f"获得news管理分页响应: {resp}")
return resp
@allure.step("更新news管理")
def kw_joyhub_news_update_put(self, news_id, title, cover_image, content, status=1, rank_num=1,
seo_title=None, seo_keyword=None, seo_description=None,
likes_num=0, cate_ids=None, route=None, publish_time=None):
"""
更新news管理业务关键字
:param news_id: news管理ID
:param title: 标题
:param cover_image: 缩略图
:param content: PC页面内容
:param status: 状态 (1正常 2停用)
:param rank_num: 排序号
:param seo_title: SEO标题可选
:param seo_keyword: SEO关键词可选
:param seo_description: SEO描述可选
:param likes_num: 点赞数(可选)
:param cate_ids: news分类ID列表可选
:param route: 路由(可选)
:param publish_time: 发布时间(可选)
:return: 响应结果
"""
obj_log.info(f"更新news管理 - news_id: {news_id}, title: {title}")
params = {
"id": news_id,
"title": title,
"coverImage": cover_image,
"content": content,
"status": status,
"rankNum": rank_num,
"likesNum": likes_num
}
if seo_title is not None:
params["seoTitle"] = seo_title
if seo_keyword is not None:
params["seoKeyword"] = seo_keyword
if seo_description is not None:
params["seoDescription"] = seo_description
if cate_ids is not None:
params["cateIds"] = cate_ids
if route is not None:
params["route"] = route
if publish_time is not None:
params["publishTime"] = publish_time
resp = self.kw_in_joyhub_news_update_put(**params)
obj_log.info(f"更新news管理响应: {resp}")
return resp

View File

@@ -1,268 +0,0 @@
# -*- coding:utf-8 -*-
"""
产品属性+产品属性值管理业务关键字层
"""
import allure
from dulizhan.library.Dlizhan_interface import DlzhanInterface
from base_framework.public_tools import log
obj_log = log.get_logger()
class ProductAttrManage(DlzhanInterface):
"""产品属性+产品属性值管理业务关键字类"""
def __init__(self):
super().__init__()
# ============ 产品属性管理方法 ============
@allure.step("创建产品属性")
def kw_joyhub_product_attr_type_create_post(self, name, type=2, id=0, status=1, remark=None, rank_num=None):
"""
创建产品属性业务关键字
:param id: 主键新增为0
:param type: 属性类型1-颜色属性有色卡2-普通属性
:param name: 属性名称
:param status: 状态 (1正常 2停用)
:param remark: 备注(可选)
:param rank_num: 排序号(可选)
:return: 响应结果
"""
obj_log.info(f"创建产品属性 - name: {name}, type: {type}")
params = {
"id": id,
"type": type,
"name": name,
"status": status
}
if remark is not None:
params["remark"] = remark
if rank_num is not None:
params["rankNum"] = rank_num
resp = self.kw_in_joyhub_product_attr_type_create_post(**params)
obj_log.info(f"创建产品属性响应: {resp}")
return resp
@allure.step("删除产品属性")
def kw_joyhub_product_attr_type_delete_delete(self, product_attr_type_id):
"""
删除产品属性业务关键字
:param product_attr_type_id: 产品属性ID
:return: 响应结果
"""
obj_log.info(f"删除产品属性 - product_attr_type_id: {product_attr_type_id}")
resp = self.kw_in_joyhub_product_attr_type_delete_delete(product_attr_type_id)
obj_log.info(f"删除产品属性响应: {resp}")
return resp
@allure.step("批量删除产品属性")
def kw_joyhub_product_attr_type_delete_list_delete(self, ids):
"""
批量删除产品属性业务关键字
:param ids: 产品属性ID列表
:return: 响应结果
"""
obj_log.info(f"批量删除产品属性 - ids: {ids}")
resp = self.kw_in_joyhub_product_attr_type_delete_list_delete(ids)
obj_log.info(f"批量删除产品属性响应: {resp}")
return resp
@allure.step("获得产品属性详情")
def kw_joyhub_product_attr_type_get_get(self, product_attr_type_id):
"""
获得产品属性详情业务关键字
:param product_attr_type_id: 产品属性ID
:return: 响应结果
"""
obj_log.info(f"获得产品属性详情 - product_attr_type_id: {product_attr_type_id}")
resp = self.kw_in_joyhub_product_attr_type_get_get(product_attr_type_id)
obj_log.info(f"获得产品属性详情响应: {resp}")
return resp
@allure.step("获得产品属性分页")
def kw_joyhub_product_attr_type_page_get(self, page_no=1, page_size=10, **kwargs):
"""
获得产品属性分页业务关键字
:param page_no: 页码
:param page_size: 每页大小
:param kwargs: 其他查询条件
:return: 响应结果
"""
obj_log.info(f"获得产品属性分页 - page_no: {page_no}, page_size: {page_size}")
params = {
"pageNo": page_no,
"pageSize": page_size
}
params.update(kwargs)
resp = self.kw_in_joyhub_product_attr_type_page_get(**params)
obj_log.info(f"获得产品属性分页响应: {resp}")
return resp
@allure.step("更新产品属性")
def kw_joyhub_product_attr_type_update_put(self, product_attr_type_id, name, type=2, status=1, remark=None, rank_num=None):
"""
更新产品属性业务关键字
:param product_attr_type_id: 产品属性ID
:param name: 属性名称
:param type: 属性类型1-颜色属性有色卡2-普通属性
:param status: 状态 (1正常 2停用)
:param remark: 备注(可选)
:param rank_num: 排序号(可选)
:return: 响应结果
"""
obj_log.info(f"更新产品属性 - product_attr_type_id: {product_attr_type_id}, name: {name}")
params = {
"id": product_attr_type_id,
"type": type,
"name": name,
"status": status
}
if remark is not None:
params["remark"] = remark
if rank_num is not None:
params["rankNum"] = rank_num
resp = self.kw_in_joyhub_product_attr_type_update_put(**params)
obj_log.info(f"更新产品属性响应: {resp}")
return resp
@allure.step("修改产品属性状态")
def kw_joyhub_product_attr_type_change_status_put(self, product_attr_type_id, status):
"""
修改产品属性状态业务关键字
:param product_attr_type_id: 产品属性ID
:param status: 状态 (1正常 2停用)
:return: 响应结果
"""
obj_log.info(f"修改产品属性状态 - product_attr_type_id: {product_attr_type_id}, status: {status}")
params = {
"id": product_attr_type_id,
"status": status
}
resp = self.kw_in_joyhub_product_attr_type_change_status_put(**params)
obj_log.info(f"修改产品属性状态响应: {resp}")
return resp
# ============ 产品属性值管理方法 ============
@allure.step("创建产品属性值")
def kw_joyhub_product_attr_data_create_post(self, product_attr_type_id, attr_value, id=0, color=None):
"""
创建产品属性值业务关键字
:param id: 主键ID新增为0
:param product_attr_type_id: 关联产品属性表的主键ID
:param attr_value: 属性值名称
:param color: 色卡(可选,颜色属性类型时使用)
:return: 响应结果
"""
obj_log.info(f"创建产品属性值 - product_attr_type_id: {product_attr_type_id}, attr_value: {attr_value}")
params = {
"id": id,
"productAttrTypeId": product_attr_type_id,
"attrValue": attr_value
}
if color is not None:
params["color"] = color
resp = self.kw_in_joyhub_product_attr_data_create_post(**params)
obj_log.info(f"创建产品属性值响应: {resp}")
return resp
@allure.step("删除产品属性值")
def kw_joyhub_product_attr_data_delete_delete(self, product_attr_data_id):
"""
删除产品属性值业务关键字
:param product_attr_data_id: 产品属性值ID
:return: 响应结果
"""
obj_log.info(f"删除产品属性值 - product_attr_data_id: {product_attr_data_id}")
resp = self.kw_in_joyhub_product_attr_data_delete_delete(product_attr_data_id)
obj_log.info(f"删除产品属性值响应: {resp}")
return resp
@allure.step("批量删除产品属性值")
def kw_joyhub_product_attr_data_delete_list_delete(self, ids):
"""
批量删除产品属性值业务关键字
:param ids: 产品属性值ID列表
:return: 响应结果
"""
obj_log.info(f"批量删除产品属性值 - ids: {ids}")
resp = self.kw_in_joyhub_product_attr_data_delete_list_delete(ids)
obj_log.info(f"批量删除产品属性值响应: {resp}")
return resp
@allure.step("获得产品属性值详情")
def kw_joyhub_product_attr_data_get_get(self, product_attr_data_id):
"""
获得产品属性值详情业务关键字
:param product_attr_data_id: 产品属性值ID
:return: 响应结果
"""
obj_log.info(f"获得产品属性值详情 - product_attr_data_id: {product_attr_data_id}")
resp = self.kw_in_joyhub_product_attr_data_get_get(product_attr_data_id)
obj_log.info(f"获得产品属性值详情响应: {resp}")
return resp
@allure.step("获得产品属性值分页")
def kw_joyhub_product_attr_data_page_get(self, page_no=1, page_size=10, **kwargs):
"""
获得产品属性值分页业务关键字
:param page_no: 页码
:param page_size: 每页大小
:param kwargs: 其他查询条件
:return: 响应结果
"""
obj_log.info(f"获得产品属性值分页 - page_no: {page_no}, page_size: {page_size}")
params = {
"pageNo": page_no,
"pageSize": page_size
}
params.update(kwargs)
resp = self.kw_in_joyhub_product_attr_data_page_get(**params)
obj_log.info(f"获得产品属性值分页响应: {resp}")
return resp
@allure.step("更新产品属性值")
def kw_joyhub_product_attr_data_update_put(self, product_attr_data_id, product_attr_type_id, attr_value, color=None):
"""
更新产品属性值业务关键字
:param product_attr_data_id: 产品属性值ID
:param product_attr_type_id: 关联产品属性表的主键ID
:param attr_value: 属性值名称
:param color: 色卡(可选,颜色属性类型时使用)
:return: 响应结果
"""
obj_log.info(f"更新产品属性值 - product_attr_data_id: {product_attr_data_id}, attr_value: {attr_value}")
params = {
"id": product_attr_data_id,
"productAttrTypeId": product_attr_type_id,
"attrValue": attr_value
}
if color is not None:
params["color"] = color
resp = self.kw_in_joyhub_product_attr_data_update_put(**params)
obj_log.info(f"更新产品属性值响应: {resp}")
return resp

View File

@@ -1,145 +0,0 @@
# -*- coding:utf-8 -*-
"""
产品分类管理业务关键字层
"""
import allure
from dulizhan.library.Dlizhan_interface import DlzhanInterface
from base_framework.public_tools import log
obj_log = log.get_logger()
class ProductCateManage(DlzhanInterface):
"""产品分类管理业务关键字类"""
def __init__(self):
super().__init__()
@allure.step("创建产品分类")
def kw_joyhub_product_cate_create_post(self, cate_name, id=0, cate_type=1, status=1, rank_num=1):
"""
创建产品分类业务关键字
:param id: 主键ID新增为0
:param cate_name: 产品分类名称
:param cate_type: 类型(普通产品=1积分产品=2
:param status: 状态 (1正常 2停用)
:param rank_num: 排序号
:return: 响应结果
"""
obj_log.info(f"创建产品分类 - cate_name: {cate_name}, cate_type: {cate_type}")
params = {
"id": id,
"cateName": cate_name,
"cateType": cate_type,
"status": status,
"rankNum": rank_num
}
resp = self.kw_in_joyhub_product_cate_create_post(**params)
obj_log.info(f"创建产品分类响应: {resp}")
return resp
@allure.step("删除产品分类")
def kw_joyhub_product_cate_delete_delete(self, product_cate_id):
"""
删除产品分类业务关键字
:param product_cate_id: 产品分类ID
:return: 响应结果
"""
obj_log.info(f"删除产品分类 - product_cate_id: {product_cate_id}")
resp = self.kw_in_joyhub_product_cate_delete_delete(product_cate_id)
obj_log.info(f"删除产品分类响应: {resp}")
return resp
@allure.step("批量删除产品分类")
def kw_joyhub_product_cate_delete_list_delete(self, ids):
"""
批量删除产品分类业务关键字
:param ids: 产品分类ID列表
:return: 响应结果
"""
obj_log.info(f"批量删除产品分类 - ids: {ids}")
resp = self.kw_in_joyhub_product_cate_delete_list_delete(ids)
obj_log.info(f"批量删除产品分类响应: {resp}")
return resp
@allure.step("获得产品分类详情")
def kw_joyhub_product_cate_get_get(self, product_cate_id):
"""
获得产品分类详情业务关键字
:param product_cate_id: 产品分类ID
:return: 响应结果
"""
obj_log.info(f"获得产品分类详情 - product_cate_id: {product_cate_id}")
resp = self.kw_in_joyhub_product_cate_get_get(product_cate_id)
obj_log.info(f"获得产品分类详情响应: {resp}")
return resp
@allure.step("获得产品分类分页")
def kw_joyhub_product_cate_page_get(self, page_no=1, page_size=10, **kwargs):
"""
获得产品分类分页业务关键字
:param page_no: 页码
:param page_size: 每页大小
:param kwargs: 其他查询条件
:return: 响应结果
"""
obj_log.info(f"获得产品分类分页 - page_no: {page_no}, page_size: {page_size}")
params = {
"pageNo": page_no,
"pageSize": page_size
}
params.update(kwargs)
resp = self.kw_in_joyhub_product_cate_page_get(**params)
obj_log.info(f"获得产品分类分页响应: {resp}")
return resp
@allure.step("更新产品分类")
def kw_joyhub_product_cate_update_put(self, product_cate_id, cate_name, cate_type=1, status=1, rank_num=1):
"""
更新产品分类业务关键字
:param product_cate_id: 产品分类ID
:param cate_name: 产品分类名称
:param cate_type: 类型(普通产品=1积分产品=2
:param status: 状态 (1正常 2停用)
:param rank_num: 排序号
:return: 响应结果
"""
obj_log.info(f"更新产品分类 - product_cate_id: {product_cate_id}, cate_name: {cate_name}")
params = {
"id": product_cate_id,
"cateName": cate_name,
"cateType": cate_type,
"status": status,
"rankNum": rank_num
}
resp = self.kw_in_joyhub_product_cate_update_put(**params)
obj_log.info(f"更新产品分类响应: {resp}")
return resp
@allure.step("修改产品分类启用/停用状态")
def kw_joyhub_product_cate_change_status_put(self, product_cate_id, status):
"""
修改产品分类启用/停用状态业务关键字
:param product_cate_id: 产品分类ID
:param status: 状态 (1正常 2停用)
:return: 响应结果
"""
obj_log.info(f"修改产品分类状态 - product_cate_id: {product_cate_id}, status: {status}")
params = {
"id": product_cate_id,
"status": status
}
resp = self.kw_in_joyhub_product_cate_change_status_put(**params)
obj_log.info(f"修改产品分类状态响应: {resp}")
return resp

View File

@@ -1,269 +0,0 @@
# -*- coding:utf-8 -*-
"""
产品管理业务关键字层
"""
import allure
from dulizhan.library.Dlizhan_interface import DlzhanInterface
from base_framework.public_tools import log
obj_log = log.get_logger()
class ProductManage(DlzhanInterface):
"""产品管理业务关键字类"""
def __init__(self):
super().__init__()
@allure.step("创建产品")
def kw_joyhub_product_create_post(self, product_name, product_cate_id, shipping_template_id, route, intro,
brand_id, product_attrs, product_skus, id=0, product_type=1,
status=1, rank_num=None, seo_title=None, seo_keyword=None,
seo_description=None, single_user_exchange_limit=None,
single_product_exchange_limit=None, product_details=None):
"""
创建产品业务关键字
:param id: 主键新增为0
:param product_type: 产品类型(普通产品=1积分产品=2
:param product_name: 产品名称
:param product_cate_id: 产品分类
:param shipping_template_id: 运费模板ID
:param route: 跳转路由
:param intro: 产品简介
:param brand_id: 品牌id
:param status: 状态1上架2下架
:param rank_num: 序号(可选)
:param seo_title: SEO标题可选
:param seo_keyword: SEO关键词可选
:param seo_description: SEO描述可选
:param single_user_exchange_limit: 单用户兑换次数限制(可选)
:param single_product_exchange_limit: 单次兑换数量限制(可选)
:param product_attrs: 产品规格类型关联列表
:param product_skus: 产品规格列表
:param product_details: 产品详情列表(可选)
:return: 响应结果
"""
obj_log.info(f"创建产品 - product_name: {product_name}, product_type: {product_type}")
params = {
"id": id,
"productType": product_type,
"productName": product_name,
"productCateId": product_cate_id,
"shippingTemplateId": shipping_template_id,
"route": route,
"intro": intro,
"brandId": brand_id,
"status": status,
"productAttrs": product_attrs,
"productSkus": product_skus
}
if rank_num is not None:
params["rankNum"] = rank_num
if seo_title is not None:
params["seoTitle"] = seo_title
if seo_keyword is not None:
params["seoKeyword"] = seo_keyword
if seo_description is not None:
params["seoDescription"] = seo_description
if single_user_exchange_limit is not None:
params["singleUserExchangeLimit"] = single_user_exchange_limit
if single_product_exchange_limit is not None:
params["singleProductExchangeLimit"] = single_product_exchange_limit
if product_details is not None:
params["productDetails"] = product_details
resp = self.kw_in_joyhub_product_create_post(**params)
obj_log.info(f"创建产品响应: {resp}")
return resp
@allure.step("删除产品")
def kw_joyhub_product_delete_delete(self, product_id):
"""
删除产品业务关键字
:param product_id: 产品ID
:return: 响应结果
"""
obj_log.info(f"删除产品 - product_id: {product_id}")
resp = self.kw_in_joyhub_product_delete_delete(product_id)
obj_log.info(f"删除产品响应: {resp}")
return resp
@allure.step("批量删除产品")
def kw_joyhub_product_delete_list_delete(self, ids):
"""
批量删除产品业务关键字
:param ids: 产品ID列表
:return: 响应结果
"""
obj_log.info(f"批量删除产品 - ids: {ids}")
resp = self.kw_in_joyhub_product_delete_list_delete(ids)
obj_log.info(f"批量删除产品响应: {resp}")
return resp
@allure.step("获得产品详情")
def kw_joyhub_product_get_get(self, product_id):
"""
获得产品详情业务关键字
:param product_id: 产品ID
:return: 响应结果
"""
obj_log.info(f"获得产品详情 - product_id: {product_id}")
resp = self.kw_in_joyhub_product_get_get(product_id)
obj_log.info(f"获得产品详情响应: {resp}")
return resp
@allure.step("获得产品分页")
def kw_joyhub_product_page_get(self, page_no=1, page_size=10, **kwargs):
"""
获得产品分页业务关键字
:param page_no: 页码
:param page_size: 每页大小
:param kwargs: 其他查询条件
:return: 响应结果
"""
obj_log.info(f"获得产品分页 - page_no: {page_no}, page_size: {page_size}")
params = {
"pageNo": page_no,
"pageSize": page_size
}
params.update(kwargs)
resp = self.kw_in_joyhub_product_page_get(**params)
obj_log.info(f"获得产品分页响应: {resp}")
return resp
@allure.step("获得产品规格类型关联列表")
def kw_joyhub_product_product_attr_list_by_product_id_get(self, product_id):
"""
获得产品规格类型关联列表业务关键字
:param product_id: 产品ID
:return: 响应结果
"""
obj_log.info(f"获得产品规格类型关联列表 - product_id: {product_id}")
resp = self.kw_in_joyhub_product_product_attr_list_by_product_id_get(product_id)
obj_log.info(f"获得产品规格类型关联列表响应: {resp}")
return resp
@allure.step("获得产品详情列表")
def kw_joyhub_product_product_detail_list_by_product_id_get(self, product_id):
"""
获得产品详情列表业务关键字
:param product_id: 产品ID
:return: 响应结果
"""
obj_log.info(f"获得产品详情列表 - product_id: {product_id}")
resp = self.kw_in_joyhub_product_product_detail_list_by_product_id_get(product_id)
obj_log.info(f"获得产品详情列表响应: {resp}")
return resp
@allure.step("获得产品规格列表")
def kw_joyhub_product_product_sku_list_by_product_id_get(self, product_id):
"""
获得产品规格列表业务关键字
:param product_id: 产品ID
:return: 响应结果
"""
obj_log.info(f"获得产品规格列表 - product_id: {product_id}")
resp = self.kw_in_joyhub_product_product_sku_list_by_product_id_get(product_id)
obj_log.info(f"获得产品规格列表响应: {resp}")
return resp
@allure.step("获得产品及规格列表-优惠券中使用")
def kw_joyhub_product_product_sku_list_get(self, **kwargs):
"""
获得产品及规格列表-优惠券中使用业务关键字
:param kwargs: 查询条件
:return: 响应结果
"""
obj_log.info(f"获得产品及规格列表 - params: {kwargs}")
resp = self.kw_in_joyhub_product_product_sku_list_get(**kwargs)
obj_log.info(f"获得产品及规格列表响应: {resp}")
return resp
@allure.step("更新产品")
def kw_joyhub_product_update_put(self, product_id, product_name, product_cate_id, shipping_template_id, route, intro,
brand_id, product_attrs, product_skus, product_type=1,
status=1, rank_num=None, seo_title=None, seo_keyword=None,
seo_description=None, single_user_exchange_limit=None,
single_product_exchange_limit=None, product_details=None):
"""
更新产品业务关键字
:param product_id: 产品ID
:param product_type: 产品类型(普通产品=1积分产品=2
:param product_name: 产品名称
:param product_cate_id: 产品分类
:param shipping_template_id: 运费模板ID
:param route: 跳转路由
:param intro: 产品简介
:param brand_id: 品牌id
:param status: 状态1上架2下架
:param rank_num: 序号(可选)
:param seo_title: SEO标题可选
:param seo_keyword: SEO关键词可选
:param seo_description: SEO描述可选
:param single_user_exchange_limit: 单用户兑换次数限制(可选)
:param single_product_exchange_limit: 单次兑换数量限制(可选)
:param product_attrs: 产品规格类型关联列表
:param product_skus: 产品规格列表
:param product_details: 产品详情列表(可选)
:return: 响应结果
"""
obj_log.info(f"更新产品 - product_id: {product_id}, product_name: {product_name}")
params = {
"id": product_id,
"productType": product_type,
"productName": product_name,
"productCateId": product_cate_id,
"shippingTemplateId": shipping_template_id,
"route": route,
"intro": intro,
"brandId": brand_id,
"status": status,
"productAttrs": product_attrs,
"productSkus": product_skus
}
if rank_num is not None:
params["rankNum"] = rank_num
if seo_title is not None:
params["seoTitle"] = seo_title
if seo_keyword is not None:
params["seoKeyword"] = seo_keyword
if seo_description is not None:
params["seoDescription"] = seo_description
if single_user_exchange_limit is not None:
params["singleUserExchangeLimit"] = single_user_exchange_limit
if single_product_exchange_limit is not None:
params["singleProductExchangeLimit"] = single_product_exchange_limit
if product_details is not None:
params["productDetails"] = product_details
resp = self.kw_in_joyhub_product_update_put(**params)
obj_log.info(f"更新产品响应: {resp}")
return resp
@allure.step("批量上下架产品")
def kw_joyhub_product_change_status_put(self, ids, status):
"""
批量上下架产品业务关键字
:param ids: 产品ID列表
:param status: 状态1上架2下架
:return: 响应结果
"""
obj_log.info(f"批量上下架产品 - ids: {ids}, status: {status}")
params = {
"ids": ids,
"status": status
}
resp = self.kw_in_joyhub_product_change_status_put(**params)
obj_log.info(f"批量上下架产品响应: {resp}")
return resp

View File

@@ -1,124 +0,0 @@
# -*- coding:utf-8 -*-
"""
支付页产品推荐业务关键字层
"""
import allure
from dulizhan.library.Dlizhan_interface import DlzhanInterface
from base_framework.public_tools import log
obj_log = log.get_logger()
class ProductPaymentRecommendManage(DlzhanInterface):
"""支付页产品推荐业务关键字类"""
def __init__(self):
super().__init__()
@allure.step("修改支付页产品推荐排序号")
def kw_joyhub_product_payment_recommend_change_rank_num_put(self, recommend_id, rank_num):
"""
修改支付页产品推荐排序号业务关键字
:param recommend_id: 推荐ID
:param rank_num: 排序号
:return: 响应结果
"""
obj_log.info(f"修改支付页产品推荐排序号 - recommend_id: {recommend_id}, rank_num: {rank_num}")
resp = self.kw_in_joyhub_product_payment_recommend_change_rank_num_put(id=recommend_id, rankNum=rank_num)
obj_log.info(f"修改支付页产品推荐排序号响应: {resp}")
return resp
@allure.step("修改支付页产品推荐状态")
def kw_joyhub_product_payment_recommend_change_status_put(self, recommend_id, recommend_status):
"""
修改支付页产品推荐状态业务关键字
:param recommend_id: 推荐ID
:param recommend_status: 推荐状态
:return: 响应结果
"""
obj_log.info(f"修改支付页产品推荐状态 - recommend_id: {recommend_id}, recommend_status: {recommend_status}")
resp = self.kw_in_joyhub_product_payment_recommend_change_status_put(id=recommend_id, recommendStatus=recommend_status)
obj_log.info(f"修改支付页产品推荐状态响应: {resp}")
return resp
@allure.step("创建支付页产品推荐")
def kw_joyhub_product_payment_recommend_create_post(self, product_ids, recommend_id=None):
"""
创建支付页产品推荐业务关键字
:param product_ids: 产品ID列表
:param recommend_id: 推荐ID
:return: 响应结果
"""
obj_log.info(f"创建支付页产品推荐 - product_ids: {product_ids}")
params = {"productIds": product_ids}
if recommend_id is not None:
params["id"] = recommend_id
resp = self.kw_in_joyhub_product_payment_recommend_create_post(**params)
obj_log.info(f"创建支付页产品推荐响应: {resp}")
return resp
@allure.step("删除支付页产品推荐")
def kw_joyhub_product_payment_recommend_delete_delete(self, recommend_id):
"""
删除支付页产品推荐业务关键字
:param recommend_id: 推荐ID
:return: 响应结果
"""
obj_log.info(f"删除支付页产品推荐 - recommend_id: {recommend_id}")
resp = self.kw_in_joyhub_product_payment_recommend_delete_delete(recommend_id)
obj_log.info(f"删除支付页产品推荐响应: {resp}")
return resp
@allure.step("批量删除支付页产品推荐")
def kw_joyhub_product_payment_recommend_delete_list_delete(self, ids):
"""
批量删除支付页产品推荐业务关键字
:param ids: 推荐ID列表
:return: 响应结果
"""
obj_log.info(f"批量删除支付页产品推荐 - ids: {ids}")
resp = self.kw_in_joyhub_product_payment_recommend_delete_list_delete(ids)
obj_log.info(f"批量删除支付页产品推荐响应: {resp}")
return resp
@allure.step("获得支付页产品推荐分页")
def kw_joyhub_product_payment_recommend_page_get(self, page_no=1, page_size=10, **kwargs):
"""
获得支付页产品推荐分页业务关键字
:param page_no: 页码
:param page_size: 每页大小
:param kwargs: 其他查询条件
:return: 响应结果
"""
obj_log.info(f"获得支付页产品推荐分页 - page_no: {page_no}, page_size: {page_size}")
params = {
"pageNo": page_no,
"pageSize": page_size
}
params.update(kwargs)
resp = self.kw_in_joyhub_product_payment_recommend_page_get(**params)
obj_log.info(f"获得支付页产品推荐分页响应: {resp}")
return resp
@allure.step("获得C端支付页产品推荐分页")
def kw_joyhub_web_product_payment_recommend_page_get(self, page_no=1, page_size=10, **kwargs):
"""
获得支付页产品推荐分页业务关键字
:param page_no: 页码
:param page_size: 每页大小
:param kwargs: 其他查询条件
:return: 响应结果
"""
obj_log.info(f"获得支付页产品推荐分页 - page_no: {page_no}, page_size: {page_size}")
params = {
"pageNo": page_no,
"pageSize": page_size
}
params.update(kwargs)
resp = self.kw_in_joyhub_web_product_payment_recommend_page_get(**params)
obj_log.info(f"获得支付页产品推荐分页响应: {resp}")
return resp

Some files were not shown because too many files have changed in this diff Show More