style: 统一 Ruff 代码格式
This commit is contained in:
@@ -91,7 +91,7 @@ def encrypt_value(value: str | None) -> str | None:
|
||||
|
||||
|
||||
def _decrypt_with_candidate(value: str, candidate: _KeyCandidate) -> str:
|
||||
raw = _b64d(value[len(_PREFIX):])
|
||||
raw = _b64d(value[len(_PREFIX) :])
|
||||
if len(raw) < 28:
|
||||
raise ValueError("密文字段长度无效")
|
||||
nonce = raw[:12]
|
||||
@@ -112,7 +112,9 @@ def decrypt_value(value: str | None) -> str | None:
|
||||
return _decrypt_with_candidate(value, candidate)
|
||||
except Exception as exc:
|
||||
last_error = exc
|
||||
raise ValueError("敏感字段解密失败,请确认 APP_ENCRYPTION_KEY 是否正确") from last_error
|
||||
raise ValueError(
|
||||
"敏感字段解密失败,请确认 APP_ENCRYPTION_KEY 是否正确"
|
||||
) from last_error
|
||||
|
||||
|
||||
def decrypt_value_with_key_name(value: str) -> tuple[str, str]:
|
||||
@@ -125,7 +127,9 @@ def decrypt_value_with_key_name(value: str) -> tuple[str, str]:
|
||||
return _decrypt_with_candidate(value, candidate), candidate.name
|
||||
except Exception as exc:
|
||||
last_error = exc
|
||||
raise ValueError("敏感字段解密失败,请确认 APP_ENCRYPTION_KEY 是否正确") from last_error
|
||||
raise ValueError(
|
||||
"敏感字段解密失败,请确认 APP_ENCRYPTION_KEY 是否正确"
|
||||
) from last_error
|
||||
|
||||
|
||||
class EncryptedText(TypeDecorator):
|
||||
@@ -184,9 +188,15 @@ def encrypt_existing_sensitive_data(engine: Engine) -> int:
|
||||
for table_name, column_name in _SENSITIVE_COLUMNS:
|
||||
if not _table_exists(engine, table_name):
|
||||
continue
|
||||
rows = conn.execute(
|
||||
text(f"SELECT id, {column_name} FROM {table_name} WHERE {column_name} IS NOT NULL")
|
||||
).mappings().all()
|
||||
rows = (
|
||||
conn.execute(
|
||||
text(
|
||||
f"SELECT id, {column_name} FROM {table_name} WHERE {column_name} IS NOT NULL"
|
||||
)
|
||||
)
|
||||
.mappings()
|
||||
.all()
|
||||
)
|
||||
for row in rows:
|
||||
raw_value = row[column_name]
|
||||
if raw_value is None or raw_value == "":
|
||||
@@ -204,7 +214,9 @@ def encrypt_existing_sensitive_data(engine: Engine) -> int:
|
||||
continue
|
||||
encrypted = encrypt_value(plain_value)
|
||||
conn.execute(
|
||||
text(f"UPDATE {table_name} SET {column_name} = :value WHERE id = :id"),
|
||||
text(
|
||||
f"UPDATE {table_name} SET {column_name} = :value WHERE id = :id"
|
||||
),
|
||||
{"value": encrypted, "id": row["id"]},
|
||||
)
|
||||
changed += 1
|
||||
|
||||
@@ -32,7 +32,9 @@ def _get_database_url() -> str:
|
||||
db_user = os.getenv("DB_USER", "douyu_login").strip()
|
||||
db_password = os.getenv("DB_PASSWORD", "")
|
||||
if not db_name or not db_user or not db_password:
|
||||
raise RuntimeError("使用 DB_HOST 时必须同时设置 DB_NAME、DB_USER 和 DB_PASSWORD")
|
||||
raise RuntimeError(
|
||||
"使用 DB_HOST 时必须同时设置 DB_NAME、DB_USER 和 DB_PASSWORD"
|
||||
)
|
||||
return (
|
||||
f"mysql+pymysql://{quote_plus(db_user)}:{quote_plus(db_password)}"
|
||||
f"@{db_host}:{db_port}/{db_name}?charset=utf8mb4"
|
||||
@@ -69,6 +71,7 @@ engine = create_engine(DATABASE_URL, **engine_options)
|
||||
|
||||
|
||||
if DATABASE_URL.startswith("sqlite"):
|
||||
|
||||
@event.listens_for(engine, "connect")
|
||||
def _set_sqlite_pragmas(dbapi_connection, connection_record):
|
||||
"""提升 SQLite 并发写入稳定性。"""
|
||||
@@ -78,6 +81,7 @@ if DATABASE_URL.startswith("sqlite"):
|
||||
cursor.execute("PRAGMA foreign_keys=ON")
|
||||
cursor.close()
|
||||
|
||||
|
||||
SessionLocal = sessionmaker(bind=engine, autocommit=False, autoflush=False)
|
||||
Base = declarative_base()
|
||||
|
||||
@@ -104,7 +108,9 @@ def run_migrations():
|
||||
from alembic.config import Config
|
||||
|
||||
config = Config(str(PROJECT_ROOT / "alembic.ini"))
|
||||
config.set_main_option("script_location", str(PROJECT_ROOT / "web" / "backend" / "migrations"))
|
||||
config.set_main_option(
|
||||
"script_location", str(PROJECT_ROOT / "web" / "backend" / "migrations")
|
||||
)
|
||||
config.set_main_option("sqlalchemy.url", DATABASE_URL)
|
||||
# 标记为应用内嵌调用,env.py 据此跳过 fileConfig,避免覆盖 uvicorn 日志配置。
|
||||
os.environ["ALEMBIC_EMBEDDED"] = "1"
|
||||
|
||||
+23
-3
@@ -12,7 +12,20 @@ from fastapi.responses import FileResponse
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
|
||||
from .database import init_db
|
||||
from .routers import auth, users, accounts, account_check, dashboard, login, proxy, cookies, huya, douyu, yyb, audit
|
||||
from .routers import (
|
||||
auth,
|
||||
users,
|
||||
accounts,
|
||||
account_check,
|
||||
dashboard,
|
||||
login,
|
||||
proxy,
|
||||
cookies,
|
||||
huya,
|
||||
douyu,
|
||||
yyb,
|
||||
audit,
|
||||
)
|
||||
from .schemas import AppInfo
|
||||
from .version import get_app_version
|
||||
from utils import setup_logger
|
||||
@@ -22,7 +35,9 @@ from utils import setup_logger
|
||||
async def lifespan(app: FastAPI):
|
||||
# 本地 dev.sh 和 Docker 均显式设置 LOG_DIR;直接运行时回退到项目 logs/。
|
||||
_log_level = os.getenv("LOG_LEVEL", "INFO")
|
||||
_log_dir = Path(os.getenv("LOG_DIR", str(Path(__file__).resolve().parents[2] / "logs")))
|
||||
_log_dir = Path(
|
||||
os.getenv("LOG_DIR", str(Path(__file__).resolve().parents[2] / "logs"))
|
||||
)
|
||||
setup_logger(level=_log_level, log_dir=str(_log_dir))
|
||||
|
||||
init_db()
|
||||
@@ -46,6 +61,7 @@ async def lifespan(app: FastAPI):
|
||||
if cleaned_relogin:
|
||||
logger.info(f"启动清理 CK 重登残留任务: {cleaned_relogin} 条")
|
||||
from .services.yyb_service import cleanup_orphan_yyb_tasks
|
||||
|
||||
cleaned_yyb = cleanup_orphan_yyb_tasks(db, message="任务已中断(服务重启)")
|
||||
if cleaned_yyb:
|
||||
logger.info(f"启动清理应用宝残留任务: {cleaned_yyb} 条")
|
||||
@@ -65,7 +81,11 @@ _cors_env = os.getenv("CORS_ORIGINS", "")
|
||||
if _cors_env:
|
||||
_cors_origins = [o.strip() for o in _cors_env.split(",") if o.strip()]
|
||||
else:
|
||||
_cors_origins = ["http://localhost:5174", "http://localhost:5173", "http://localhost:3000"]
|
||||
_cors_origins = [
|
||||
"http://localhost:5174",
|
||||
"http://localhost:5173",
|
||||
"http://localhost:3000",
|
||||
]
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
|
||||
@@ -39,7 +39,9 @@ def _add_column_if_missing(bind, table_name: str, column: sa.Column) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def _create_index_if_missing(bind, name: str, table_name: str, columns: list[str], unique: bool = False) -> None:
|
||||
def _create_index_if_missing(
|
||||
bind, name: str, table_name: str, columns: list[str], unique: bool = False
|
||||
) -> None:
|
||||
if name not in _indexes(bind, table_name):
|
||||
op.create_index(name, table_name, columns, unique=unique)
|
||||
|
||||
@@ -62,8 +64,12 @@ def upgrade() -> None:
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
else:
|
||||
_add_column_if_missing(bind, "users", sa.Column("custom_permissions", sa.JSON(), nullable=True))
|
||||
_create_index_if_missing(bind, "ix_users_username", "users", ["username"], unique=True)
|
||||
_add_column_if_missing(
|
||||
bind, "users", sa.Column("custom_permissions", sa.JSON(), nullable=True)
|
||||
)
|
||||
_create_index_if_missing(
|
||||
bind, "ix_users_username", "users", ["username"], unique=True
|
||||
)
|
||||
|
||||
if not _has_table(bind, "accounts"):
|
||||
op.create_table(
|
||||
@@ -84,8 +90,14 @@ def upgrade() -> None:
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
else:
|
||||
_add_column_if_missing(bind, "accounts", sa.Column("tag", sa.String(length=64), server_default=""))
|
||||
added_ssl = _add_column_if_missing(bind, "accounts", sa.Column("email_imap_ssl", sa.Boolean(), server_default=sa.text("1")))
|
||||
_add_column_if_missing(
|
||||
bind, "accounts", sa.Column("tag", sa.String(length=64), server_default="")
|
||||
)
|
||||
added_ssl = _add_column_if_missing(
|
||||
bind,
|
||||
"accounts",
|
||||
sa.Column("email_imap_ssl", sa.Boolean(), server_default=sa.text("1")),
|
||||
)
|
||||
if added_ssl:
|
||||
op.execute(
|
||||
"UPDATE accounts SET email_imap_port = 143, email_imap_ssl = 0 "
|
||||
@@ -95,7 +107,9 @@ def upgrade() -> None:
|
||||
"UPDATE accounts SET email_imap_port = 143, email_imap_ssl = 0 "
|
||||
"WHERE email_imap_server = 'mail.bdhg.xyz'"
|
||||
)
|
||||
_create_index_if_missing(bind, "ix_accounts_assigned_to", "accounts", ["assigned_to"])
|
||||
_create_index_if_missing(
|
||||
bind, "ix_accounts_assigned_to", "accounts", ["assigned_to"]
|
||||
)
|
||||
|
||||
if not _has_table(bind, "proxy_config"):
|
||||
op.create_table(
|
||||
@@ -127,7 +141,9 @@ def upgrade() -> None:
|
||||
sa.ForeignKeyConstraint(["created_by"], ["users.id"]),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
_create_index_if_missing(bind, "ix_login_tasks_batch_id", "login_tasks", ["batch_id"])
|
||||
_create_index_if_missing(
|
||||
bind, "ix_login_tasks_batch_id", "login_tasks", ["batch_id"]
|
||||
)
|
||||
|
||||
if not _has_table(bind, "audit_logs"):
|
||||
op.create_table(
|
||||
|
||||
@@ -19,16 +19,30 @@ depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
def upgrade() -> None:
|
||||
with op.batch_alter_table("accounts") as batch:
|
||||
batch.alter_column("password", existing_type=sa.String(length=256), type_=sa.Text())
|
||||
batch.alter_column("email", existing_type=sa.String(length=128), type_=sa.Text())
|
||||
batch.alter_column("email_password", existing_type=sa.String(length=256), type_=sa.Text())
|
||||
batch.alter_column(
|
||||
"password", existing_type=sa.String(length=256), type_=sa.Text()
|
||||
)
|
||||
batch.alter_column(
|
||||
"email", existing_type=sa.String(length=128), type_=sa.Text()
|
||||
)
|
||||
batch.alter_column(
|
||||
"email_password", existing_type=sa.String(length=256), type_=sa.Text()
|
||||
)
|
||||
|
||||
with op.batch_alter_table("proxy_config") as batch:
|
||||
batch.alter_column("api_url", existing_type=sa.String(length=512), type_=sa.Text())
|
||||
batch.alter_column(
|
||||
"api_url", existing_type=sa.String(length=512), type_=sa.Text()
|
||||
)
|
||||
batch.alter_column("http", existing_type=sa.String(length=256), type_=sa.Text())
|
||||
batch.alter_column("https", existing_type=sa.String(length=256), type_=sa.Text())
|
||||
batch.alter_column("whitelist_uid", existing_type=sa.String(length=64), type_=sa.Text())
|
||||
batch.alter_column("whitelist_ukey", existing_type=sa.String(length=128), type_=sa.Text())
|
||||
batch.alter_column(
|
||||
"https", existing_type=sa.String(length=256), type_=sa.Text()
|
||||
)
|
||||
batch.alter_column(
|
||||
"whitelist_uid", existing_type=sa.String(length=64), type_=sa.Text()
|
||||
)
|
||||
batch.alter_column(
|
||||
"whitelist_ukey", existing_type=sa.String(length=128), type_=sa.Text()
|
||||
)
|
||||
|
||||
with op.batch_alter_table("login_tasks") as batch:
|
||||
batch.alter_column("cookie", existing_type=sa.Text(), type_=sa.Text())
|
||||
@@ -39,13 +53,27 @@ def downgrade() -> None:
|
||||
batch.alter_column("cookie", existing_type=sa.Text(), type_=sa.Text())
|
||||
|
||||
with op.batch_alter_table("proxy_config") as batch:
|
||||
batch.alter_column("whitelist_ukey", existing_type=sa.Text(), type_=sa.String(length=128))
|
||||
batch.alter_column("whitelist_uid", existing_type=sa.Text(), type_=sa.String(length=64))
|
||||
batch.alter_column("https", existing_type=sa.Text(), type_=sa.String(length=256))
|
||||
batch.alter_column(
|
||||
"whitelist_ukey", existing_type=sa.Text(), type_=sa.String(length=128)
|
||||
)
|
||||
batch.alter_column(
|
||||
"whitelist_uid", existing_type=sa.Text(), type_=sa.String(length=64)
|
||||
)
|
||||
batch.alter_column(
|
||||
"https", existing_type=sa.Text(), type_=sa.String(length=256)
|
||||
)
|
||||
batch.alter_column("http", existing_type=sa.Text(), type_=sa.String(length=256))
|
||||
batch.alter_column("api_url", existing_type=sa.Text(), type_=sa.String(length=512))
|
||||
batch.alter_column(
|
||||
"api_url", existing_type=sa.Text(), type_=sa.String(length=512)
|
||||
)
|
||||
|
||||
with op.batch_alter_table("accounts") as batch:
|
||||
batch.alter_column("email_password", existing_type=sa.Text(), type_=sa.String(length=256))
|
||||
batch.alter_column("email", existing_type=sa.Text(), type_=sa.String(length=128))
|
||||
batch.alter_column("password", existing_type=sa.Text(), type_=sa.String(length=256))
|
||||
batch.alter_column(
|
||||
"email_password", existing_type=sa.Text(), type_=sa.String(length=256)
|
||||
)
|
||||
batch.alter_column(
|
||||
"email", existing_type=sa.Text(), type_=sa.String(length=128)
|
||||
)
|
||||
batch.alter_column(
|
||||
"password", existing_type=sa.Text(), type_=sa.String(length=256)
|
||||
)
|
||||
|
||||
@@ -22,9 +22,7 @@ def upgrade() -> None:
|
||||
batch.add_column(
|
||||
sa.Column("whitelist_platform", sa.String(32), server_default="xiequ")
|
||||
)
|
||||
batch.add_column(
|
||||
sa.Column("whitelist_credentials", sa.JSON(), nullable=True)
|
||||
)
|
||||
batch.add_column(sa.Column("whitelist_credentials", sa.JSON(), nullable=True))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -27,7 +27,9 @@ def _indexes(bind, table_name: str) -> set[str]:
|
||||
return {index["name"] for index in sa.inspect(bind).get_indexes(table_name)}
|
||||
|
||||
|
||||
def _create_index_if_missing(bind, name: str, table_name: str, columns: list[str], unique: bool = False) -> None:
|
||||
def _create_index_if_missing(
|
||||
bind, name: str, table_name: str, columns: list[str], unique: bool = False
|
||||
) -> None:
|
||||
if name not in _indexes(bind, table_name):
|
||||
op.create_index(name, table_name, columns, unique=unique)
|
||||
|
||||
@@ -59,7 +61,9 @@ def upgrade() -> None:
|
||||
)
|
||||
_create_index_if_missing(bind, "ix_huya_accounts_uid", "huya_accounts", ["uid"])
|
||||
_create_index_if_missing(bind, "ix_huya_accounts_yyuid", "huya_accounts", ["yyuid"])
|
||||
_create_index_if_missing(bind, "ix_huya_accounts_assigned_to", "huya_accounts", ["assigned_to"])
|
||||
_create_index_if_missing(
|
||||
bind, "ix_huya_accounts_assigned_to", "huya_accounts", ["assigned_to"]
|
||||
)
|
||||
|
||||
if not _has_table(bind, "huya_tasks"):
|
||||
op.create_table(
|
||||
@@ -79,7 +83,9 @@ def upgrade() -> None:
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
_create_index_if_missing(bind, "ix_huya_tasks_batch_id", "huya_tasks", ["batch_id"])
|
||||
_create_index_if_missing(bind, "ix_huya_tasks_task_type", "huya_tasks", ["task_type"])
|
||||
_create_index_if_missing(
|
||||
bind, "ix_huya_tasks_task_type", "huya_tasks", ["task_type"]
|
||||
)
|
||||
|
||||
if not _has_table(bind, "huya_config"):
|
||||
op.create_table(
|
||||
@@ -106,13 +112,17 @@ def upgrade() -> None:
|
||||
sa.Column("updated_at", sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
_create_index_if_missing(bind, "ix_huya_goods_snapshot_product_id", "huya_goods_snapshot", ["product_id"])
|
||||
_create_index_if_missing(
|
||||
bind, "ix_huya_goods_snapshot_product_id", "huya_goods_snapshot", ["product_id"]
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
bind = op.get_bind()
|
||||
if _has_table(bind, "huya_goods_snapshot"):
|
||||
op.drop_index("ix_huya_goods_snapshot_product_id", table_name="huya_goods_snapshot")
|
||||
op.drop_index(
|
||||
"ix_huya_goods_snapshot_product_id", table_name="huya_goods_snapshot"
|
||||
)
|
||||
op.drop_table("huya_goods_snapshot")
|
||||
if _has_table(bind, "huya_config"):
|
||||
op.drop_table("huya_config")
|
||||
|
||||
@@ -27,7 +27,9 @@ def _indexes(bind, table_name: str) -> set[str]:
|
||||
return {index["name"] for index in sa.inspect(bind).get_indexes(table_name)}
|
||||
|
||||
|
||||
def _create_index_if_missing(bind, name: str, table_name: str, columns: list[str], unique: bool = False) -> None:
|
||||
def _create_index_if_missing(
|
||||
bind, name: str, table_name: str, columns: list[str], unique: bool = False
|
||||
) -> None:
|
||||
if name not in _indexes(bind, table_name):
|
||||
op.create_index(name, table_name, columns, unique=unique)
|
||||
|
||||
@@ -71,7 +73,13 @@ def downgrade() -> None:
|
||||
if _has_table(bind, "huya_recharge_goods_snapshot"):
|
||||
indexes = _indexes(bind, "huya_recharge_goods_snapshot")
|
||||
if "ix_huya_recharge_goods_snapshot_sku_id" in indexes:
|
||||
op.drop_index("ix_huya_recharge_goods_snapshot_sku_id", table_name="huya_recharge_goods_snapshot")
|
||||
op.drop_index(
|
||||
"ix_huya_recharge_goods_snapshot_sku_id",
|
||||
table_name="huya_recharge_goods_snapshot",
|
||||
)
|
||||
if "ix_huya_recharge_goods_snapshot_spu_id" in indexes:
|
||||
op.drop_index("ix_huya_recharge_goods_snapshot_spu_id", table_name="huya_recharge_goods_snapshot")
|
||||
op.drop_index(
|
||||
"ix_huya_recharge_goods_snapshot_spu_id",
|
||||
table_name="huya_recharge_goods_snapshot",
|
||||
)
|
||||
op.drop_table("huya_recharge_goods_snapshot")
|
||||
|
||||
@@ -59,9 +59,17 @@ def upgrade() -> None:
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("batch_id"),
|
||||
)
|
||||
op.create_index("ix_huya_register_batches_batch_id", "huya_register_batches", ["batch_id"])
|
||||
op.create_index("ix_huya_register_batches_created_by", "huya_register_batches", ["created_by"])
|
||||
op.create_index("ix_huya_register_batches_status", "huya_register_batches", ["status"])
|
||||
op.create_index(
|
||||
"ix_huya_register_batches_batch_id", "huya_register_batches", ["batch_id"]
|
||||
)
|
||||
op.create_index(
|
||||
"ix_huya_register_batches_created_by",
|
||||
"huya_register_batches",
|
||||
["created_by"],
|
||||
)
|
||||
op.create_index(
|
||||
"ix_huya_register_batches_status", "huya_register_batches", ["status"]
|
||||
)
|
||||
|
||||
if not _has_table(bind, "huya_register_items"):
|
||||
op.create_table(
|
||||
@@ -91,10 +99,18 @@ def upgrade() -> None:
|
||||
sa.ForeignKeyConstraint(["batch_db_id"], ["huya_register_batches.id"]),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index("ix_huya_register_items_batch_db_id", "huya_register_items", ["batch_db_id"])
|
||||
op.create_index("ix_huya_register_items_batch_id", "huya_register_items", ["batch_id"])
|
||||
op.create_index("ix_huya_register_items_phone", "huya_register_items", ["phone"])
|
||||
op.create_index("ix_huya_register_items_status", "huya_register_items", ["status"])
|
||||
op.create_index(
|
||||
"ix_huya_register_items_batch_db_id", "huya_register_items", ["batch_db_id"]
|
||||
)
|
||||
op.create_index(
|
||||
"ix_huya_register_items_batch_id", "huya_register_items", ["batch_id"]
|
||||
)
|
||||
op.create_index(
|
||||
"ix_huya_register_items_phone", "huya_register_items", ["phone"]
|
||||
)
|
||||
op.create_index(
|
||||
"ix_huya_register_items_status", "huya_register_items", ["status"]
|
||||
)
|
||||
|
||||
if not _has_table(bind, "huya_register_success_logs"):
|
||||
op.create_table(
|
||||
@@ -116,10 +132,24 @@ def upgrade() -> None:
|
||||
sa.ForeignKeyConstraint(["created_by"], ["users.id"]),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index("ix_huya_register_success_logs_batch_id", "huya_register_success_logs", ["batch_id"])
|
||||
op.create_index("ix_huya_register_success_logs_phone", "huya_register_success_logs", ["phone"])
|
||||
op.create_index("ix_huya_register_success_logs_tag", "huya_register_success_logs", ["tag"])
|
||||
op.create_index("ix_huya_register_success_logs_created_at", "huya_register_success_logs", ["created_at"])
|
||||
op.create_index(
|
||||
"ix_huya_register_success_logs_batch_id",
|
||||
"huya_register_success_logs",
|
||||
["batch_id"],
|
||||
)
|
||||
op.create_index(
|
||||
"ix_huya_register_success_logs_phone",
|
||||
"huya_register_success_logs",
|
||||
["phone"],
|
||||
)
|
||||
op.create_index(
|
||||
"ix_huya_register_success_logs_tag", "huya_register_success_logs", ["tag"]
|
||||
)
|
||||
op.create_index(
|
||||
"ix_huya_register_success_logs_created_at",
|
||||
"huya_register_success_logs",
|
||||
["created_at"],
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -38,7 +38,9 @@ def _add_column_if_missing(bind, table_name: str, column: sa.Column) -> None:
|
||||
op.add_column(table_name, column)
|
||||
|
||||
|
||||
def _create_index_if_missing(bind, name: str, table_name: str, columns: list[str], unique: bool = False) -> None:
|
||||
def _create_index_if_missing(
|
||||
bind, name: str, table_name: str, columns: list[str], unique: bool = False
|
||||
) -> None:
|
||||
if name not in _indexes(bind, table_name):
|
||||
op.create_index(name, table_name, columns, unique=unique)
|
||||
|
||||
@@ -46,16 +48,40 @@ def _create_index_if_missing(bind, name: str, table_name: str, columns: list[str
|
||||
def upgrade() -> None:
|
||||
bind = op.get_bind()
|
||||
|
||||
_add_column_if_missing(bind, "accounts", sa.Column("uid", sa.String(length=32), nullable=True))
|
||||
_add_column_if_missing(bind, "accounts", sa.Column("nickname", sa.String(length=128), nullable=True))
|
||||
_add_column_if_missing(bind, "accounts", sa.Column("points", sa.Integer(), nullable=True))
|
||||
_add_column_if_missing(bind, "accounts", sa.Column("game_name", sa.String(length=128), nullable=True))
|
||||
_add_column_if_missing(bind, "accounts", sa.Column("game_channel", sa.String(length=128), nullable=True))
|
||||
_add_column_if_missing(bind, "accounts", sa.Column("gold_balance", sa.Integer(), nullable=True))
|
||||
_add_column_if_missing(bind, "accounts", sa.Column("exchange_balance", sa.Integer(), nullable=True))
|
||||
_add_column_if_missing(bind, "accounts", sa.Column("bind_status", sa.String(length=32), nullable=True))
|
||||
_add_column_if_missing(bind, "accounts", sa.Column("change_role_wait_time", sa.Integer(), nullable=True))
|
||||
_add_column_if_missing(bind, "accounts", sa.Column("updated_at", sa.DateTime(), nullable=True))
|
||||
_add_column_if_missing(
|
||||
bind, "accounts", sa.Column("uid", sa.String(length=32), nullable=True)
|
||||
)
|
||||
_add_column_if_missing(
|
||||
bind, "accounts", sa.Column("nickname", sa.String(length=128), nullable=True)
|
||||
)
|
||||
_add_column_if_missing(
|
||||
bind, "accounts", sa.Column("points", sa.Integer(), nullable=True)
|
||||
)
|
||||
_add_column_if_missing(
|
||||
bind, "accounts", sa.Column("game_name", sa.String(length=128), nullable=True)
|
||||
)
|
||||
_add_column_if_missing(
|
||||
bind,
|
||||
"accounts",
|
||||
sa.Column("game_channel", sa.String(length=128), nullable=True),
|
||||
)
|
||||
_add_column_if_missing(
|
||||
bind, "accounts", sa.Column("gold_balance", sa.Integer(), nullable=True)
|
||||
)
|
||||
_add_column_if_missing(
|
||||
bind, "accounts", sa.Column("exchange_balance", sa.Integer(), nullable=True)
|
||||
)
|
||||
_add_column_if_missing(
|
||||
bind, "accounts", sa.Column("bind_status", sa.String(length=32), nullable=True)
|
||||
)
|
||||
_add_column_if_missing(
|
||||
bind,
|
||||
"accounts",
|
||||
sa.Column("change_role_wait_time", sa.Integer(), nullable=True),
|
||||
)
|
||||
_add_column_if_missing(
|
||||
bind, "accounts", sa.Column("updated_at", sa.DateTime(), nullable=True)
|
||||
)
|
||||
_create_index_if_missing(bind, "ix_accounts_uid", "accounts", ["uid"])
|
||||
|
||||
if not _has_table(bind, "douyu_tasks"):
|
||||
@@ -75,8 +101,12 @@ def upgrade() -> None:
|
||||
sa.ForeignKeyConstraint(["created_by"], ["users.id"]),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
_create_index_if_missing(bind, "ix_douyu_tasks_batch_id", "douyu_tasks", ["batch_id"])
|
||||
_create_index_if_missing(bind, "ix_douyu_tasks_task_type", "douyu_tasks", ["task_type"])
|
||||
_create_index_if_missing(
|
||||
bind, "ix_douyu_tasks_batch_id", "douyu_tasks", ["batch_id"]
|
||||
)
|
||||
_create_index_if_missing(
|
||||
bind, "ix_douyu_tasks_task_type", "douyu_tasks", ["task_type"]
|
||||
)
|
||||
|
||||
if not _has_table(bind, "douyu_config"):
|
||||
op.create_table(
|
||||
@@ -121,7 +151,10 @@ def downgrade() -> None:
|
||||
if _has_table(bind, "douyu_goods_snapshot"):
|
||||
indexes = _indexes(bind, "douyu_goods_snapshot")
|
||||
if "ix_douyu_goods_snapshot_commodity_id" in indexes:
|
||||
op.drop_index("ix_douyu_goods_snapshot_commodity_id", table_name="douyu_goods_snapshot")
|
||||
op.drop_index(
|
||||
"ix_douyu_goods_snapshot_commodity_id",
|
||||
table_name="douyu_goods_snapshot",
|
||||
)
|
||||
op.drop_table("douyu_goods_snapshot")
|
||||
if _has_table(bind, "douyu_config"):
|
||||
op.drop_table("douyu_config")
|
||||
|
||||
@@ -27,7 +27,9 @@ def _indexes(bind, table_name: str) -> set[str]:
|
||||
return {index["name"] for index in sa.inspect(bind).get_indexes(table_name)}
|
||||
|
||||
|
||||
def _create_index_if_missing(bind, name: str, table_name: str, columns: list[str]) -> None:
|
||||
def _create_index_if_missing(
|
||||
bind, name: str, table_name: str, columns: list[str]
|
||||
) -> None:
|
||||
if name not in _indexes(bind, table_name):
|
||||
op.create_index(name, table_name, columns)
|
||||
|
||||
@@ -50,8 +52,16 @@ INDEXES = [
|
||||
("ix_huya_tasks_account_id", "huya_tasks", ["account_id", "id"]),
|
||||
("ix_huya_tasks_created_by_id", "huya_tasks", ["created_by", "id"]),
|
||||
("ix_huya_tasks_batch_status_id", "huya_tasks", ["batch_id", "status", "id"]),
|
||||
("ix_huya_register_items_batch_db_line", "huya_register_items", ["batch_db_id", "line"]),
|
||||
("ix_huya_register_success_logs_batch_id_id", "huya_register_success_logs", ["batch_id", "id"]),
|
||||
(
|
||||
"ix_huya_register_items_batch_db_line",
|
||||
"huya_register_items",
|
||||
["batch_db_id", "line"],
|
||||
),
|
||||
(
|
||||
"ix_huya_register_success_logs_batch_id_id",
|
||||
"huya_register_success_logs",
|
||||
["batch_id", "id"],
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -21,7 +21,10 @@ def upgrade() -> None:
|
||||
bind = op.get_bind()
|
||||
columns = {column["name"] for column in sa.inspect(bind).get_columns("accounts")}
|
||||
if "esports_can_change_time" not in columns:
|
||||
op.add_column("accounts", sa.Column("esports_can_change_time", sa.Integer(), nullable=True))
|
||||
op.add_column(
|
||||
"accounts",
|
||||
sa.Column("esports_can_change_time", sa.Integer(), nullable=True),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -23,7 +23,9 @@ def upgrade() -> None:
|
||||
return
|
||||
columns = {column["name"] for column in sa.inspect(bind).get_columns("accounts")}
|
||||
if "xpd_fragments" not in columns:
|
||||
op.add_column("accounts", sa.Column("xpd_fragments", sa.Integer(), nullable=True))
|
||||
op.add_column(
|
||||
"accounts", sa.Column("xpd_fragments", sa.Integer(), nullable=True)
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -56,10 +56,8 @@ def upgrade() -> None:
|
||||
return
|
||||
|
||||
for index in range(0, len(delete_ids), 500):
|
||||
chunk = delete_ids[index:index + 500]
|
||||
bind.execute(
|
||||
task_table.delete().where(task_table.c.id.in_(chunk))
|
||||
)
|
||||
chunk = delete_ids[index : index + 500]
|
||||
bind.execute(task_table.delete().where(task_table.c.id.in_(chunk)))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -18,7 +18,9 @@ def upgrade() -> None:
|
||||
"yyb_recharge_tasks",
|
||||
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
|
||||
sa.Column("task_id", sa.String(64), nullable=False),
|
||||
sa.Column("created_by", sa.Integer(), sa.ForeignKey("users.id"), nullable=False),
|
||||
sa.Column(
|
||||
"created_by", sa.Integer(), sa.ForeignKey("users.id"), nullable=False
|
||||
),
|
||||
sa.Column("worker_job_id", sa.String(64), nullable=False),
|
||||
sa.Column("provider", sa.String(16), nullable=False, server_default=""),
|
||||
sa.Column("platform", sa.String(16), nullable=False, server_default="android"),
|
||||
@@ -37,9 +39,15 @@ def upgrade() -> None:
|
||||
sa.Column("created_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("finished_at", sa.DateTime(), nullable=True),
|
||||
)
|
||||
op.create_index("uq_yyb_recharge_tasks_task_id", "yyb_recharge_tasks", ["task_id"], unique=True)
|
||||
op.create_index("ix_yyb_recharge_tasks_created_by", "yyb_recharge_tasks", ["created_by"])
|
||||
op.create_index("ix_yyb_recharge_tasks_worker_job_id", "yyb_recharge_tasks", ["worker_job_id"])
|
||||
op.create_index(
|
||||
"uq_yyb_recharge_tasks_task_id", "yyb_recharge_tasks", ["task_id"], unique=True
|
||||
)
|
||||
op.create_index(
|
||||
"ix_yyb_recharge_tasks_created_by", "yyb_recharge_tasks", ["created_by"]
|
||||
)
|
||||
op.create_index(
|
||||
"ix_yyb_recharge_tasks_worker_job_id", "yyb_recharge_tasks", ["worker_job_id"]
|
||||
)
|
||||
op.create_index("ix_yyb_recharge_tasks_status", "yyb_recharge_tasks", ["status"])
|
||||
|
||||
|
||||
|
||||
@@ -18,8 +18,12 @@ def _is_mysql() -> bool:
|
||||
|
||||
def upgrade() -> None:
|
||||
if _is_mysql():
|
||||
op.execute("ALTER TABLE yyb_recharge_tasks MODIFY login_qr_data MEDIUMTEXT NULL")
|
||||
op.execute("ALTER TABLE yyb_recharge_tasks MODIFY payment_qr_data MEDIUMTEXT NULL")
|
||||
op.execute(
|
||||
"ALTER TABLE yyb_recharge_tasks MODIFY login_qr_data MEDIUMTEXT NULL"
|
||||
)
|
||||
op.execute(
|
||||
"ALTER TABLE yyb_recharge_tasks MODIFY payment_qr_data MEDIUMTEXT NULL"
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -13,10 +13,21 @@ depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column("yyb_recharge_tasks", sa.Column("price_fen", sa.Integer(), nullable=True))
|
||||
op.add_column("yyb_recharge_tasks", sa.Column("payment_started_at", sa.DateTime(), nullable=True))
|
||||
op.add_column("yyb_recharge_tasks", sa.Column("payment_qr_created_at", sa.DateTime(), nullable=True))
|
||||
op.add_column("yyb_recharge_tasks", sa.Column("payment_last_checked_at", sa.DateTime(), nullable=True))
|
||||
op.add_column(
|
||||
"yyb_recharge_tasks", sa.Column("price_fen", sa.Integer(), nullable=True)
|
||||
)
|
||||
op.add_column(
|
||||
"yyb_recharge_tasks",
|
||||
sa.Column("payment_started_at", sa.DateTime(), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"yyb_recharge_tasks",
|
||||
sa.Column("payment_qr_created_at", sa.DateTime(), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"yyb_recharge_tasks",
|
||||
sa.Column("payment_last_checked_at", sa.DateTime(), nullable=True),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -23,7 +23,9 @@ def upgrade() -> None:
|
||||
op.add_column("users", sa.Column("deleted_at", sa.DateTime(), nullable=True))
|
||||
op.create_index("ix_users_deleted_at", "users", ["deleted_at"])
|
||||
if "deleted_username" not in columns:
|
||||
op.add_column("users", sa.Column("deleted_username", sa.String(length=64), nullable=True))
|
||||
op.add_column(
|
||||
"users", sa.Column("deleted_username", sa.String(length=64), nullable=True)
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -26,35 +26,71 @@ def upgrade() -> None:
|
||||
if "handbook_scope" not in _columns(bind, "douyu_tasks"):
|
||||
op.add_column(
|
||||
"douyu_tasks",
|
||||
sa.Column("handbook_scope", sa.String(length=16), nullable=False, server_default="legacy"),
|
||||
sa.Column(
|
||||
"handbook_scope",
|
||||
sa.String(length=16),
|
||||
nullable=False,
|
||||
server_default="legacy",
|
||||
),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_douyu_tasks_handbook_scope", "douyu_tasks", ["handbook_scope"]
|
||||
)
|
||||
op.create_index("ix_douyu_tasks_handbook_scope", "douyu_tasks", ["handbook_scope"])
|
||||
|
||||
if not sa.inspect(bind).has_table("douyu_workbench_accounts"):
|
||||
op.create_table(
|
||||
"douyu_workbench_accounts",
|
||||
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
|
||||
sa.Column("user_id", sa.Integer(), sa.ForeignKey("users.id"), nullable=False),
|
||||
sa.Column(
|
||||
"user_id", sa.Integer(), sa.ForeignKey("users.id"), nullable=False
|
||||
),
|
||||
sa.Column("handbook_scope", sa.String(length=16), nullable=False),
|
||||
sa.Column("account_id", sa.Integer(), sa.ForeignKey("accounts.id"), nullable=False),
|
||||
sa.Column(
|
||||
"account_id", sa.Integer(), sa.ForeignKey("accounts.id"), nullable=False
|
||||
),
|
||||
sa.Column("created_at", sa.DateTime(), nullable=True),
|
||||
sa.UniqueConstraint("user_id", "handbook_scope", "account_id", name="uq_douyu_workbench_account"),
|
||||
sa.UniqueConstraint(
|
||||
"user_id",
|
||||
"handbook_scope",
|
||||
"account_id",
|
||||
name="uq_douyu_workbench_account",
|
||||
),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_douyu_workbench_accounts_user_id",
|
||||
"douyu_workbench_accounts",
|
||||
["user_id"],
|
||||
)
|
||||
op.create_index(
|
||||
"ix_douyu_workbench_accounts_handbook_scope",
|
||||
"douyu_workbench_accounts",
|
||||
["handbook_scope"],
|
||||
)
|
||||
op.create_index(
|
||||
"ix_douyu_workbench_accounts_account_id",
|
||||
"douyu_workbench_accounts",
|
||||
["account_id"],
|
||||
)
|
||||
op.create_index("ix_douyu_workbench_accounts_user_id", "douyu_workbench_accounts", ["user_id"])
|
||||
op.create_index("ix_douyu_workbench_accounts_handbook_scope", "douyu_workbench_accounts", ["handbook_scope"])
|
||||
op.create_index("ix_douyu_workbench_accounts_account_id", "douyu_workbench_accounts", ["account_id"])
|
||||
|
||||
if not sa.inspect(bind).has_table("douyu_workbenches"):
|
||||
op.create_table(
|
||||
"douyu_workbenches",
|
||||
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
|
||||
sa.Column("user_id", sa.Integer(), sa.ForeignKey("users.id"), nullable=False),
|
||||
sa.Column(
|
||||
"user_id", sa.Integer(), sa.ForeignKey("users.id"), nullable=False
|
||||
),
|
||||
sa.Column("handbook_scope", sa.String(length=16), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(), nullable=True),
|
||||
sa.UniqueConstraint("user_id", "handbook_scope", name="uq_douyu_workbench"),
|
||||
)
|
||||
op.create_index("ix_douyu_workbenches_user_id", "douyu_workbenches", ["user_id"])
|
||||
op.create_index("ix_douyu_workbenches_handbook_scope", "douyu_workbenches", ["handbook_scope"])
|
||||
op.create_index(
|
||||
"ix_douyu_workbenches_user_id", "douyu_workbenches", ["user_id"]
|
||||
)
|
||||
op.create_index(
|
||||
"ix_douyu_workbenches_handbook_scope",
|
||||
"douyu_workbenches",
|
||||
["handbook_scope"],
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -30,7 +30,9 @@ def upgrade() -> None:
|
||||
sa.UniqueConstraint("user_id", "handbook_scope", name="uq_douyu_workbench"),
|
||||
)
|
||||
op.create_index("ix_douyu_workbenches_user_id", "douyu_workbenches", ["user_id"])
|
||||
op.create_index("ix_douyu_workbenches_handbook_scope", "douyu_workbenches", ["handbook_scope"])
|
||||
op.create_index(
|
||||
"ix_douyu_workbenches_handbook_scope", "douyu_workbenches", ["handbook_scope"]
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -27,9 +27,33 @@ def upgrade() -> None:
|
||||
return
|
||||
columns = _columns(bind)
|
||||
additions = (
|
||||
("gold_recharge_channel", sa.Column("gold_recharge_channel", sa.String(length=16), nullable=True, server_default="wechat_qr")),
|
||||
("gold_api_product_id", sa.Column("gold_api_product_id", sa.String(length=128), nullable=True, server_default="111570")),
|
||||
("gold_api_account_template_name", sa.Column("gold_api_account_template_name", sa.String(length=64), nullable=True, server_default="斗鱼UID")),
|
||||
(
|
||||
"gold_recharge_channel",
|
||||
sa.Column(
|
||||
"gold_recharge_channel",
|
||||
sa.String(length=16),
|
||||
nullable=True,
|
||||
server_default="wechat_qr",
|
||||
),
|
||||
),
|
||||
(
|
||||
"gold_api_product_id",
|
||||
sa.Column(
|
||||
"gold_api_product_id",
|
||||
sa.String(length=128),
|
||||
nullable=True,
|
||||
server_default="111570",
|
||||
),
|
||||
),
|
||||
(
|
||||
"gold_api_account_template_name",
|
||||
sa.Column(
|
||||
"gold_api_account_template_name",
|
||||
sa.String(length=64),
|
||||
nullable=True,
|
||||
server_default="斗鱼UID",
|
||||
),
|
||||
),
|
||||
)
|
||||
for name, column in additions:
|
||||
if name not in columns:
|
||||
@@ -41,6 +65,10 @@ def downgrade() -> None:
|
||||
if not sa.inspect(bind).has_table("douyu_config"):
|
||||
return
|
||||
columns = _columns(bind)
|
||||
for name in ("gold_api_account_template_name", "gold_api_product_id", "gold_recharge_channel"):
|
||||
for name in (
|
||||
"gold_api_account_template_name",
|
||||
"gold_api_product_id",
|
||||
"gold_recharge_channel",
|
||||
):
|
||||
if name in columns:
|
||||
op.drop_column("douyu_config", name)
|
||||
|
||||
@@ -24,7 +24,10 @@ def upgrade() -> None:
|
||||
return
|
||||
columns = {column["name"] for column in inspector.get_columns("douyu_tasks")}
|
||||
if "supplier_out_order_id" not in columns:
|
||||
op.add_column("douyu_tasks", sa.Column("supplier_out_order_id", sa.String(length=64), nullable=True))
|
||||
op.add_column(
|
||||
"douyu_tasks",
|
||||
sa.Column("supplier_out_order_id", sa.String(length=64), nullable=True),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_douyu_tasks_supplier_out_order_id",
|
||||
"douyu_tasks",
|
||||
|
||||
@@ -25,10 +25,17 @@ def upgrade() -> None:
|
||||
return
|
||||
columns = {column["name"] for column in inspector.get_columns("huya_accounts")}
|
||||
if "login_channel" not in columns:
|
||||
op.add_column("huya_accounts", sa.Column("login_channel", sa.String(16), nullable=False, server_default=""))
|
||||
op.add_column(
|
||||
"huya_accounts",
|
||||
sa.Column(
|
||||
"login_channel", sa.String(16), nullable=False, server_default=""
|
||||
),
|
||||
)
|
||||
indexes = {index["name"] for index in inspector.get_indexes("huya_accounts")}
|
||||
if "ix_huya_accounts_login_channel" not in indexes:
|
||||
op.create_index("ix_huya_accounts_login_channel", "huya_accounts", ["login_channel"])
|
||||
op.create_index(
|
||||
"ix_huya_accounts_login_channel", "huya_accounts", ["login_channel"]
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
|
||||
@@ -27,8 +27,16 @@ INDEXES = (
|
||||
["handbook_scope", "task_type", "id"],
|
||||
),
|
||||
# Supports terminal-task retention and finished-time ordered Cookie views.
|
||||
("ix_douyu_tasks_status_finished_at_id", "douyu_tasks", ["status", "finished_at", "id"]),
|
||||
("ix_login_tasks_status_finished_at_id", "login_tasks", ["status", "finished_at", "id"]),
|
||||
(
|
||||
"ix_douyu_tasks_status_finished_at_id",
|
||||
"douyu_tasks",
|
||||
["status", "finished_at", "id"],
|
||||
),
|
||||
(
|
||||
"ix_login_tasks_status_finished_at_id",
|
||||
"login_tasks",
|
||||
["status", "finished_at", "id"],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -36,7 +36,10 @@ def _audit_log_out(db: Session, row: AuditLog) -> dict:
|
||||
accounts = (
|
||||
db.query(DouyuTask, Account)
|
||||
.join(Account, Account.id == DouyuTask.account_id)
|
||||
.filter(DouyuTask.batch_id == batch_id, DouyuTask.task_type == "create_gold_qr")
|
||||
.filter(
|
||||
DouyuTask.batch_id == batch_id,
|
||||
DouyuTask.task_type == "create_gold_qr",
|
||||
)
|
||||
.order_by(DouyuTask.id.asc())
|
||||
.limit(100)
|
||||
.all()
|
||||
@@ -56,14 +59,19 @@ def _audit_log_out(db: Session, row: AuditLog) -> dict:
|
||||
batch_id = row.target.removeprefix("douyu_batch:")
|
||||
task = (
|
||||
db.query(DouyuTask)
|
||||
.filter(DouyuTask.batch_id == batch_id, DouyuTask.task_type == "create_gold_qr")
|
||||
.filter(
|
||||
DouyuTask.batch_id == batch_id,
|
||||
DouyuTask.task_type == "create_gold_qr",
|
||||
)
|
||||
.order_by(DouyuTask.id.asc())
|
||||
.first()
|
||||
)
|
||||
task_result = task.result if task and isinstance(task.result, dict) else {}
|
||||
recharge_channel = str(task_result.get("recharge_channel") or "wechat_qr")
|
||||
detail["recharge_channel"] = recharge_channel
|
||||
detail["payment_method"] = "API 直充支付" if recharge_channel == "supplier_api" else "微信扫码支付"
|
||||
detail["payment_method"] = (
|
||||
"API 直充支付" if recharge_channel == "supplier_api" else "微信扫码支付"
|
||||
)
|
||||
if isinstance(detail, dict):
|
||||
detail_text = json.dumps(detail, ensure_ascii=False, separators=(",", ":"))
|
||||
return {
|
||||
@@ -99,7 +107,9 @@ def list_audit_logs(
|
||||
query = query.filter(AuditLog.action == action.strip())
|
||||
if keyword:
|
||||
pattern = f"%{keyword.strip()}%"
|
||||
query = query.filter((AuditLog.target.ilike(pattern)) | (AuditLog.detail.ilike(pattern)))
|
||||
query = query.filter(
|
||||
(AuditLog.target.ilike(pattern)) | (AuditLog.detail.ilike(pattern))
|
||||
)
|
||||
if success is not None:
|
||||
query = query.filter(AuditLog.success == success)
|
||||
if start_time:
|
||||
@@ -107,7 +117,12 @@ def list_audit_logs(
|
||||
if end_time:
|
||||
query = query.filter(AuditLog.created_at <= end_time)
|
||||
total = query.count()
|
||||
rows = query.order_by(AuditLog.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
|
||||
rows = (
|
||||
query.order_by(AuditLog.id.desc())
|
||||
.offset((page - 1) * page_size)
|
||||
.limit(page_size)
|
||||
.all()
|
||||
)
|
||||
return {
|
||||
"items": [_audit_log_out(db, row) for row in rows],
|
||||
"total": total,
|
||||
|
||||
@@ -41,7 +41,9 @@ def login(req: LoginRequest, response: Response, db: Session = Depends(get_db)):
|
||||
)
|
||||
|
||||
# 审计
|
||||
db.add(AuditLog(user_id=user.id, username=user.username, action="login", target="auth"))
|
||||
db.add(
|
||||
AuditLog(user_id=user.id, username=user.username, action="login", target="auth")
|
||||
)
|
||||
db.commit()
|
||||
|
||||
return TokenResponse(
|
||||
@@ -66,8 +68,19 @@ def me(current_user: User = Depends(get_current_user)):
|
||||
|
||||
|
||||
@router.post("/logout")
|
||||
def logout(response: Response, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)):
|
||||
def logout(
|
||||
response: Response,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
response.delete_cookie(key="access_token", path="/")
|
||||
db.add(AuditLog(user_id=current_user.id, username=current_user.username, action="logout", target="auth"))
|
||||
db.add(
|
||||
AuditLog(
|
||||
user_id=current_user.id,
|
||||
username=current_user.username,
|
||||
action="logout",
|
||||
target="auth",
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
return {"message": "已登出"}
|
||||
|
||||
@@ -44,7 +44,9 @@ def _empty_task_summary() -> dict:
|
||||
|
||||
def _can_view_huya_all(user: User) -> bool:
|
||||
"""兼容旧 huya:account 全量权限。"""
|
||||
return user_has_permission(user, "huya:view_all") or user_has_permission(user, "huya:account")
|
||||
return user_has_permission(user, "huya:view_all") or user_has_permission(
|
||||
user, "huya:account"
|
||||
)
|
||||
|
||||
|
||||
@router.get("/summary")
|
||||
@@ -65,12 +67,17 @@ def dashboard_summary(
|
||||
)
|
||||
|
||||
login_tasks = _empty_task_summary()
|
||||
if any(user_has_permission(current, permission) for permission in (
|
||||
"login:batch",
|
||||
"login:view_all",
|
||||
"login:view_assigned",
|
||||
)):
|
||||
login_query = db.query(LoginTask).join(Account, LoginTask.account_id == Account.id)
|
||||
if any(
|
||||
user_has_permission(current, permission)
|
||||
for permission in (
|
||||
"login:batch",
|
||||
"login:view_all",
|
||||
"login:view_assigned",
|
||||
)
|
||||
):
|
||||
login_query = db.query(LoginTask).join(
|
||||
Account, LoginTask.account_id == Account.id
|
||||
)
|
||||
if not user_has_permission(current, "login:view_all"):
|
||||
login_query = login_query.filter(Account.assigned_to == current.id)
|
||||
login_tasks = _task_summary(login_query, LoginTask)
|
||||
@@ -87,27 +94,40 @@ def dashboard_summary(
|
||||
cookies = cookie_query.with_entities(func.count(LoginTask.id)).scalar() or 0
|
||||
|
||||
huya_accounts = 0
|
||||
can_view_huya_accounts = any(user_has_permission(current, permission) for permission in (
|
||||
"huya:account",
|
||||
"huya:view_all",
|
||||
"huya:view_assigned",
|
||||
))
|
||||
can_view_huya_accounts = any(
|
||||
user_has_permission(current, permission)
|
||||
for permission in (
|
||||
"huya:account",
|
||||
"huya:view_all",
|
||||
"huya:view_assigned",
|
||||
)
|
||||
)
|
||||
if can_view_huya_accounts:
|
||||
huya_account_query = db.query(HuyaAccount)
|
||||
if not _can_view_huya_all(current):
|
||||
huya_account_query = huya_account_query.filter(HuyaAccount.assigned_to == current.id)
|
||||
huya_accounts = huya_account_query.with_entities(func.count(HuyaAccount.id)).scalar() or 0
|
||||
huya_account_query = huya_account_query.filter(
|
||||
HuyaAccount.assigned_to == current.id
|
||||
)
|
||||
huya_accounts = (
|
||||
huya_account_query.with_entities(func.count(HuyaAccount.id)).scalar() or 0
|
||||
)
|
||||
|
||||
huya_tasks = _empty_task_summary()
|
||||
huya_goods = 0
|
||||
huya_recharge_goods = 0
|
||||
if user_has_permission(current, "huya:task"):
|
||||
huya_task_query = db.query(HuyaTask).join(HuyaAccount, HuyaTask.account_id == HuyaAccount.id)
|
||||
huya_task_query = db.query(HuyaTask).join(
|
||||
HuyaAccount, HuyaTask.account_id == HuyaAccount.id
|
||||
)
|
||||
if not _can_view_huya_all(current):
|
||||
huya_task_query = huya_task_query.filter(HuyaAccount.assigned_to == current.id)
|
||||
huya_task_query = huya_task_query.filter(
|
||||
HuyaAccount.assigned_to == current.id
|
||||
)
|
||||
huya_tasks = _task_summary(huya_task_query, HuyaTask)
|
||||
huya_goods = db.query(func.count(HuyaGoodsSnapshot.id)).scalar() or 0
|
||||
huya_recharge_goods = db.query(func.count(HuyaRechargeGoodsSnapshot.id)).scalar() or 0
|
||||
huya_recharge_goods = (
|
||||
db.query(func.count(HuyaRechargeGoodsSnapshot.id)).scalar() or 0
|
||||
)
|
||||
|
||||
return {
|
||||
"douyu": {
|
||||
|
||||
@@ -106,7 +106,11 @@ def list_tasks(
|
||||
current: User = Depends(get_current_user),
|
||||
):
|
||||
"""查看登录任务列表。"""
|
||||
query = db.query(LoginTask).options(defer(LoginTask.cookie)).join(Account, LoginTask.account_id == Account.id)
|
||||
query = (
|
||||
db.query(LoginTask)
|
||||
.options(defer(LoginTask.cookie))
|
||||
.join(Account, LoginTask.account_id == Account.id)
|
||||
)
|
||||
|
||||
# 客服只能看自己账号的任务
|
||||
if not user_has_permission(current, "login:view_all"):
|
||||
@@ -130,12 +134,20 @@ def list_tasks(
|
||||
|
||||
result = []
|
||||
for t in rows:
|
||||
result.append(LoginTaskOut(
|
||||
id=t.id, batch_id=t.batch_id, account_id=t.account_id,
|
||||
account_username=accounts_map.get(t.account_id, ""),
|
||||
status=t.status, cookie="", message=t.message or "",
|
||||
created_by=t.created_by, created_at=t.created_at, finished_at=t.finished_at,
|
||||
))
|
||||
result.append(
|
||||
LoginTaskOut(
|
||||
id=t.id,
|
||||
batch_id=t.batch_id,
|
||||
account_id=t.account_id,
|
||||
account_username=accounts_map.get(t.account_id, ""),
|
||||
status=t.status,
|
||||
cookie="",
|
||||
message=t.message or "",
|
||||
created_by=t.created_by,
|
||||
created_at=t.created_at,
|
||||
finished_at=t.finished_at,
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
@@ -178,7 +190,11 @@ def delete_tasks(
|
||||
ids = [int(x) for x in task_ids.split(",") if x.strip().isdigit()]
|
||||
if not ids:
|
||||
raise HTTPException(status_code=400, detail="无效的任务ID")
|
||||
deleted = db.query(LoginTask).filter(LoginTask.id.in_(ids)).delete(synchronize_session=False)
|
||||
deleted = (
|
||||
db.query(LoginTask)
|
||||
.filter(LoginTask.id.in_(ids))
|
||||
.delete(synchronize_session=False)
|
||||
)
|
||||
db.commit()
|
||||
return {"message": f"已删除 {deleted} 个任务", "deleted": deleted, "success": True}
|
||||
|
||||
|
||||
@@ -11,7 +11,9 @@ from ..schemas import ProxyConfigOut, ProxyConfigUpdate, PlatformInfo, PlatformF
|
||||
from ..deps import require_permission, authenticate_websocket
|
||||
from ..services.proxy_service import proxy_service
|
||||
from core.douyu.proxy_platforms import (
|
||||
get_platform_names, get_platform_labels, get_credential_fields,
|
||||
get_platform_names,
|
||||
get_platform_labels,
|
||||
get_credential_fields,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/api/proxy", tags=["代理与白名单"])
|
||||
@@ -37,7 +39,9 @@ def update_proxy_config(
|
||||
api_url=req.api_url if req.api_url is not None else "",
|
||||
http=req.http if req.http is not None else "",
|
||||
https=req.https if req.https is not None else "",
|
||||
whitelist_enabled=req.whitelist_enabled if req.whitelist_enabled is not None else False,
|
||||
whitelist_enabled=req.whitelist_enabled
|
||||
if req.whitelist_enabled is not None
|
||||
else False,
|
||||
whitelist_platform=req.whitelist_platform or "xiequ",
|
||||
whitelist_credentials=req.whitelist_credentials,
|
||||
whitelist_uid=req.whitelist_uid,
|
||||
@@ -53,16 +57,19 @@ def list_platforms():
|
||||
result = []
|
||||
for name in get_platform_names():
|
||||
fields = get_credential_fields(name)
|
||||
result.append(PlatformInfo(
|
||||
name=name,
|
||||
label=labels.get(name, name),
|
||||
credential_fields=[PlatformFieldDef(**f) for f in fields],
|
||||
))
|
||||
result.append(
|
||||
PlatformInfo(
|
||||
name=name,
|
||||
label=labels.get(name, name),
|
||||
credential_fields=[PlatformFieldDef(**f) for f in fields],
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
# ---- WebSocket 日志推送 ----
|
||||
|
||||
|
||||
@router.websocket("/ws/test/{test_id}")
|
||||
async def ws_test_logs(websocket: WebSocket, test_id: str):
|
||||
"""WebSocket 推送代理/白名单测试实时日志(需认证)。"""
|
||||
@@ -100,6 +107,7 @@ async def ws_test_logs(websocket: WebSocket, test_id: str):
|
||||
|
||||
# ---- 异步测试 API 端点 ----
|
||||
|
||||
|
||||
@router.post("/test")
|
||||
async def test_proxy(
|
||||
db: Session = Depends(get_db),
|
||||
|
||||
@@ -25,16 +25,18 @@ def list_users(
|
||||
users = db.query(User).filter(User.deleted_at.is_(None)).order_by(User.id).all()
|
||||
result = []
|
||||
for u in users:
|
||||
result.append(UserInfo(
|
||||
id=u.id,
|
||||
username=u.username,
|
||||
role=u.role,
|
||||
is_active=u.is_active,
|
||||
remark=u.remark or "",
|
||||
created_at=u.created_at,
|
||||
permissions=get_user_permissions(u),
|
||||
custom_permissions=u.custom_permissions,
|
||||
))
|
||||
result.append(
|
||||
UserInfo(
|
||||
id=u.id,
|
||||
username=u.username,
|
||||
role=u.role,
|
||||
is_active=u.is_active,
|
||||
remark=u.remark or "",
|
||||
created_at=u.created_at,
|
||||
permissions=get_user_permissions(u),
|
||||
custom_permissions=u.custom_permissions,
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
@@ -58,13 +60,22 @@ def create_user(
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
|
||||
db.add(AuditLog(user_id=current.id, username=current.username,
|
||||
action="user:create", target=user.username))
|
||||
db.add(
|
||||
AuditLog(
|
||||
user_id=current.id,
|
||||
username=current.username,
|
||||
action="user:create",
|
||||
target=user.username,
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
|
||||
return UserInfo(
|
||||
id=user.id, username=user.username, role=user.role,
|
||||
is_active=user.is_active, remark=user.remark or "",
|
||||
id=user.id,
|
||||
username=user.username,
|
||||
role=user.role,
|
||||
is_active=user.is_active,
|
||||
remark=user.remark or "",
|
||||
permissions=get_user_permissions(user),
|
||||
custom_permissions=user.custom_permissions,
|
||||
)
|
||||
@@ -97,13 +108,22 @@ def update_user(
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
|
||||
db.add(AuditLog(user_id=current.id, username=current.username,
|
||||
action="user:edit", target=user.username))
|
||||
db.add(
|
||||
AuditLog(
|
||||
user_id=current.id,
|
||||
username=current.username,
|
||||
action="user:edit",
|
||||
target=user.username,
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
|
||||
return UserInfo(
|
||||
id=user.id, username=user.username, role=user.role,
|
||||
is_active=user.is_active, remark=user.remark or "",
|
||||
id=user.id,
|
||||
username=user.username,
|
||||
role=user.role,
|
||||
is_active=user.is_active,
|
||||
remark=user.remark or "",
|
||||
permissions=get_user_permissions(user),
|
||||
custom_permissions=user.custom_permissions,
|
||||
)
|
||||
@@ -126,21 +146,27 @@ def rename_user(
|
||||
raise HTTPException(status_code=400, detail="用户名至少 2 个字符")
|
||||
if username == user.username:
|
||||
return UserInfo(
|
||||
id=user.id, username=user.username, role=user.role,
|
||||
is_active=user.is_active, remark=user.remark or "",
|
||||
permissions=get_user_permissions(user), custom_permissions=user.custom_permissions,
|
||||
id=user.id,
|
||||
username=user.username,
|
||||
role=user.role,
|
||||
is_active=user.is_active,
|
||||
remark=user.remark or "",
|
||||
permissions=get_user_permissions(user),
|
||||
custom_permissions=user.custom_permissions,
|
||||
)
|
||||
if db.query(User.id).filter(User.username == username).first():
|
||||
raise HTTPException(status_code=400, detail="用户名已存在")
|
||||
|
||||
old_username = user.username
|
||||
user.username = username
|
||||
db.add(AuditLog(
|
||||
user_id=current.id,
|
||||
username=current.username,
|
||||
action="user:rename",
|
||||
target=f"{old_username} -> {username}",
|
||||
))
|
||||
db.add(
|
||||
AuditLog(
|
||||
user_id=current.id,
|
||||
username=current.username,
|
||||
action="user:rename",
|
||||
target=f"{old_username} -> {username}",
|
||||
)
|
||||
)
|
||||
try:
|
||||
db.commit()
|
||||
except IntegrityError as exc:
|
||||
@@ -148,9 +174,13 @@ def rename_user(
|
||||
raise HTTPException(status_code=400, detail="用户名已存在") from exc
|
||||
db.refresh(user)
|
||||
return UserInfo(
|
||||
id=user.id, username=user.username, role=user.role,
|
||||
is_active=user.is_active, remark=user.remark or "",
|
||||
permissions=get_user_permissions(user), custom_permissions=user.custom_permissions,
|
||||
id=user.id,
|
||||
username=user.username,
|
||||
role=user.role,
|
||||
is_active=user.is_active,
|
||||
remark=user.remark or "",
|
||||
permissions=get_user_permissions(user),
|
||||
custom_permissions=user.custom_permissions,
|
||||
)
|
||||
|
||||
|
||||
@@ -182,8 +212,14 @@ def delete_user(
|
||||
.update({HuyaAccount.assigned_to: None}, synchronize_session=False)
|
||||
)
|
||||
deleted_username = user.username
|
||||
db.add(AuditLog(user_id=current.id, username=current.username,
|
||||
action="user:delete", target=deleted_username))
|
||||
db.add(
|
||||
AuditLog(
|
||||
user_id=current.id,
|
||||
username=current.username,
|
||||
action="user:delete",
|
||||
target=deleted_username,
|
||||
)
|
||||
)
|
||||
user.is_active = False
|
||||
user.deleted_at = datetime.now(timezone.utc)
|
||||
user.deleted_username = deleted_username
|
||||
@@ -202,6 +238,7 @@ def delete_user(
|
||||
def list_permissions(_: User = Depends(require_permission("user:assign_permissions"))):
|
||||
"""返回所有可用权限点及角色默认权限映射。"""
|
||||
from ..permissions import ROLE_PERMISSIONS
|
||||
|
||||
return {
|
||||
"permissions": PERMISSIONS,
|
||||
"role_permissions": ROLE_PERMISSIONS,
|
||||
|
||||
+172
-45
@@ -20,7 +20,9 @@ from ..services.audit_service import record_audit
|
||||
router = APIRouter(prefix="/api/yyb", tags=["应用宝充值"])
|
||||
|
||||
|
||||
def _get_task(db: Session, task_id: int, current: User, write: bool = False) -> YybRechargeTask:
|
||||
def _get_task(
|
||||
db: Session, task_id: int, current: User, write: bool = False
|
||||
) -> YybRechargeTask:
|
||||
"""读取任务。
|
||||
|
||||
查看:本人或 yyb:history;写操作:本人或 yyb:manage。
|
||||
@@ -53,14 +55,26 @@ def _creator_username(db: Session, task: YybRechargeTask) -> str:
|
||||
|
||||
|
||||
@router.post("/tasks")
|
||||
def create_task(payload: YybTaskCreateRequest, db: Session = Depends(get_db), current: User = Depends(require_permission("yyb:session"))):
|
||||
def create_task(
|
||||
payload: YybTaskCreateRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current: User = Depends(require_permission("yyb:session")),
|
||||
):
|
||||
data = _worker_call(YybWorkerClient().create_job)
|
||||
task = YybRechargeTask(task_id=uuid.uuid4().hex[:16], created_by=current.id,
|
||||
worker_job_id=str(data["job_id"]), status=str(data.get("status", "created")),
|
||||
phase="login", message="请选择登录方式")
|
||||
task = YybRechargeTask(
|
||||
task_id=uuid.uuid4().hex[:16],
|
||||
created_by=current.id,
|
||||
worker_job_id=str(data["job_id"]),
|
||||
status=str(data.get("status", "created")),
|
||||
phase="login",
|
||||
message="请选择登录方式",
|
||||
)
|
||||
db.add(task)
|
||||
record_audit(
|
||||
db, current, action="recharge:yyb:create", target=f"yyb_task:{task.task_id}",
|
||||
db,
|
||||
current,
|
||||
action="recharge:yyb:create",
|
||||
target=f"yyb_task:{task.task_id}",
|
||||
detail={"task_id": task.task_id, "status": task.status},
|
||||
)
|
||||
db.commit()
|
||||
@@ -69,9 +83,18 @@ def create_task(payload: YybTaskCreateRequest, db: Session = Depends(get_db), cu
|
||||
|
||||
|
||||
@router.post("/tasks/{task_id}/login")
|
||||
def login(task_id: int, payload: YybLoginRequest, db: Session = Depends(get_db), current: User = Depends(require_permission("yyb:session"))):
|
||||
def login(
|
||||
task_id: int,
|
||||
payload: YybLoginRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current: User = Depends(require_permission("yyb:session")),
|
||||
):
|
||||
task = _get_task(db, task_id, current, write=True)
|
||||
data = _worker_call(lambda: YybWorkerClient().login(task.worker_job_id, payload.provider, payload.timeout))
|
||||
data = _worker_call(
|
||||
lambda: YybWorkerClient().login(
|
||||
task.worker_job_id, payload.provider, payload.timeout
|
||||
)
|
||||
)
|
||||
task.provider = payload.provider
|
||||
task.status = str(data.get("status", "waiting_login"))
|
||||
task.phase = "login"
|
||||
@@ -79,31 +102,48 @@ def login(task_id: int, payload: YybLoginRequest, db: Session = Depends(get_db),
|
||||
if data.get("qr_data"):
|
||||
task.login_qr_data = data["qr_data"]
|
||||
record_audit(
|
||||
db, current, action="recharge:yyb:login", target=f"yyb_task:{task.task_id}",
|
||||
detail={"task_id": task.task_id, "provider": payload.provider, "status": task.status},
|
||||
db,
|
||||
current,
|
||||
action="recharge:yyb:login",
|
||||
target=f"yyb_task:{task.task_id}",
|
||||
detail={
|
||||
"task_id": task.task_id,
|
||||
"provider": payload.provider,
|
||||
"status": task.status,
|
||||
},
|
||||
)
|
||||
db.commit()
|
||||
return public_task(task, creator_username=_creator_username(db, task))
|
||||
|
||||
|
||||
@router.get("/tasks/{task_id}")
|
||||
def get_task(task_id: int, db: Session = Depends(get_db), current: User = Depends(get_current_user)):
|
||||
def get_task(
|
||||
task_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current: User = Depends(get_current_user),
|
||||
):
|
||||
task = _get_task(db, task_id, current)
|
||||
try:
|
||||
sync_task(db, task, YybWorkerClient())
|
||||
except YybWorkerError:
|
||||
# Worker 暂时重启时仍返回最近一次持久化状态。
|
||||
pass
|
||||
return public_task(task, include_qr=user_has_permission(current, "yyb:session"),
|
||||
include_payment_qr=user_has_permission(current, "yyb:recharge"),
|
||||
creator_username=_creator_username(db, task))
|
||||
return public_task(
|
||||
task,
|
||||
include_qr=user_has_permission(current, "yyb:session"),
|
||||
include_payment_qr=user_has_permission(current, "yyb:recharge"),
|
||||
creator_username=_creator_username(db, task),
|
||||
)
|
||||
|
||||
|
||||
@router.get("/tasks")
|
||||
def list_tasks(scope: str = Query("mine", pattern="^(mine|all)$"),
|
||||
status: str | None = Query(None, max_length=32),
|
||||
limit: int = Query(50, ge=1, le=200),
|
||||
db: Session = Depends(get_db), current: User = Depends(require_permission("yyb:session"))):
|
||||
def list_tasks(
|
||||
scope: str = Query("mine", pattern="^(mine|all)$"),
|
||||
status: str | None = Query(None, max_length=32),
|
||||
limit: int = Query(50, ge=1, le=200),
|
||||
db: Session = Depends(get_db),
|
||||
current: User = Depends(require_permission("yyb:session")),
|
||||
):
|
||||
if scope == "all" and not user_has_permission(current, "yyb:history"):
|
||||
raise HTTPException(403, "无权查看全部充值任务")
|
||||
query = db.query(YybRechargeTask)
|
||||
@@ -114,36 +154,80 @@ def list_tasks(scope: str = Query("mine", pattern="^(mine|all)$"),
|
||||
rows = query.order_by(YybRechargeTask.id.desc()).limit(limit).all()
|
||||
usernames = {
|
||||
user.id: user.username
|
||||
for user in db.query(User).filter(User.id.in_({row.created_by for row in rows})).all()
|
||||
for user in db.query(User)
|
||||
.filter(User.id.in_({row.created_by for row in rows}))
|
||||
.all()
|
||||
}
|
||||
return [public_task(task, include_qr=False, creator_username=usernames.get(task.created_by, ""))
|
||||
for task in rows]
|
||||
return [
|
||||
public_task(
|
||||
task, include_qr=False, creator_username=usernames.get(task.created_by, "")
|
||||
)
|
||||
for task in rows
|
||||
]
|
||||
|
||||
|
||||
@router.get("/tasks/{task_id}/selection-options")
|
||||
def selection_options(task_id: int, platform: str = Query("android"), points: int | None = Query(None), zone_id: str | None = Query(None), db: Session = Depends(get_db), current: User = Depends(require_permission("yyb:session"))):
|
||||
def selection_options(
|
||||
task_id: int,
|
||||
platform: str = Query("android"),
|
||||
points: int | None = Query(None),
|
||||
zone_id: str | None = Query(None),
|
||||
db: Session = Depends(get_db),
|
||||
current: User = Depends(require_permission("yyb:session")),
|
||||
):
|
||||
task = _get_task(db, task_id, current, write=True)
|
||||
data = _worker_call(lambda: YybWorkerClient().selection_options(task.worker_job_id, platform, points, zone_id))
|
||||
data = _worker_call(
|
||||
lambda: YybWorkerClient().selection_options(
|
||||
task.worker_job_id, platform, points, zone_id
|
||||
)
|
||||
)
|
||||
task.platform = platform
|
||||
db.commit()
|
||||
return data
|
||||
|
||||
|
||||
@router.post("/tasks/{task_id}/selection")
|
||||
def selection(task_id: int, payload: YybSelectionRequest, db: Session = Depends(get_db), current: User = Depends(require_permission("yyb:session"))):
|
||||
def selection(
|
||||
task_id: int,
|
||||
payload: YybSelectionRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current: User = Depends(require_permission("yyb:session")),
|
||||
):
|
||||
task = _get_task(db, task_id, current, write=True)
|
||||
data = _worker_call(lambda: YybWorkerClient().selection(task.worker_job_id, payload.model_dump()))
|
||||
data = _worker_call(
|
||||
lambda: YybWorkerClient().selection(task.worker_job_id, payload.model_dump())
|
||||
)
|
||||
selected = data.get("selection", payload.model_dump())
|
||||
for field in ("platform", "points", "product_id", "zone_id", "zone_name", "role_id", "role_name"):
|
||||
for field in (
|
||||
"platform",
|
||||
"points",
|
||||
"product_id",
|
||||
"zone_id",
|
||||
"zone_name",
|
||||
"role_id",
|
||||
"role_name",
|
||||
):
|
||||
setattr(task, field, selected[field])
|
||||
task.price_fen = int(selected.get("price_fen") or 0)
|
||||
task.phase, task.status, task.message = "payment", "ready", "选择已保存,可以生成付款码"
|
||||
task.phase, task.status, task.message = (
|
||||
"payment",
|
||||
"ready",
|
||||
"选择已保存,可以生成付款码",
|
||||
)
|
||||
record_audit(
|
||||
db, current, action="recharge:yyb:selection", target=f"yyb_task:{task.task_id}",
|
||||
db,
|
||||
current,
|
||||
action="recharge:yyb:selection",
|
||||
target=f"yyb_task:{task.task_id}",
|
||||
detail={
|
||||
"task_id": task.task_id, "product_id": task.product_id, "points": task.points,
|
||||
"zone_id": task.zone_id, "zone_name": task.zone_name, "role_id": task.role_id,
|
||||
"role_name": task.role_name, "price_fen": task.price_fen,
|
||||
"task_id": task.task_id,
|
||||
"product_id": task.product_id,
|
||||
"points": task.points,
|
||||
"zone_id": task.zone_id,
|
||||
"zone_name": task.zone_name,
|
||||
"role_id": task.role_id,
|
||||
"role_name": task.role_name,
|
||||
"price_fen": task.price_fen,
|
||||
},
|
||||
)
|
||||
db.commit()
|
||||
@@ -151,10 +235,19 @@ def selection(task_id: int, payload: YybSelectionRequest, db: Session = Depends(
|
||||
|
||||
|
||||
@router.post("/tasks/{task_id}/payment")
|
||||
def payment(task_id: int, db: Session = Depends(get_db), current: User = Depends(require_permission("yyb:recharge"))):
|
||||
def payment(
|
||||
task_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current: User = Depends(require_permission("yyb:recharge")),
|
||||
):
|
||||
# 接手他人任务需 yyb:manage;锁行做原子状态迁移,防止并发双击重复下单。
|
||||
_get_task(db, task_id, current, write=True)
|
||||
task = db.query(YybRechargeTask).filter(YybRechargeTask.id == task_id).with_for_update().first()
|
||||
task = (
|
||||
db.query(YybRechargeTask)
|
||||
.filter(YybRechargeTask.id == task_id)
|
||||
.with_for_update()
|
||||
.first()
|
||||
)
|
||||
if not task:
|
||||
raise HTTPException(404, "充值任务不存在")
|
||||
if task.status != "ready" or task.phase != "payment":
|
||||
@@ -174,8 +267,12 @@ def payment(task_id: int, db: Session = Depends(get_db), current: User = Depends
|
||||
task.message = "生成付款码失败,请稍后重试"
|
||||
task.payment_started_at = None
|
||||
record_audit(
|
||||
db, current, action="recharge:yyb:payment", target=f"yyb_task:{task.task_id}",
|
||||
detail="生成付款码失败", success=False,
|
||||
db,
|
||||
current,
|
||||
action="recharge:yyb:payment",
|
||||
target=f"yyb_task:{task.task_id}",
|
||||
detail="生成付款码失败",
|
||||
success=False,
|
||||
)
|
||||
db.commit()
|
||||
raise
|
||||
@@ -183,16 +280,30 @@ def payment(task_id: int, db: Session = Depends(get_db), current: User = Depends
|
||||
task.phase = str(data.get("phase", "payment"))
|
||||
task.message = str(data.get("message", task.message))
|
||||
record_audit(
|
||||
db, current, action="recharge:yyb:payment", target=f"yyb_task:{task.task_id}",
|
||||
detail={"task_id": task.task_id, "product_id": task.product_id, "points": task.points,
|
||||
"price_fen": task.price_fen, "status": task.status},
|
||||
db,
|
||||
current,
|
||||
action="recharge:yyb:payment",
|
||||
target=f"yyb_task:{task.task_id}",
|
||||
detail={
|
||||
"task_id": task.task_id,
|
||||
"product_id": task.product_id,
|
||||
"points": task.points,
|
||||
"price_fen": task.price_fen,
|
||||
"status": task.status,
|
||||
},
|
||||
)
|
||||
db.commit()
|
||||
return public_task(task, include_qr=False, creator_username=_creator_username(db, task))
|
||||
return public_task(
|
||||
task, include_qr=False, creator_username=_creator_username(db, task)
|
||||
)
|
||||
|
||||
|
||||
@router.post("/tasks/{task_id}/payment/check")
|
||||
def payment_check(task_id: int, db: Session = Depends(get_db), current: User = Depends(require_permission("yyb:recharge"))):
|
||||
def payment_check(
|
||||
task_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current: User = Depends(require_permission("yyb:recharge")),
|
||||
):
|
||||
task = _get_task(db, task_id, current, write=True)
|
||||
if task.status not in {"waiting_payment", "payment_timeout"}:
|
||||
raise HTTPException(409, "当前任务状态不支持检测到账")
|
||||
@@ -204,16 +315,29 @@ def payment_check(task_id: int, db: Session = Depends(get_db), current: User = D
|
||||
if task.status == "success":
|
||||
task.phase = "completed"
|
||||
record_audit(
|
||||
db, current, action="recharge:yyb:payment_check", target=f"yyb_task:{task.task_id}",
|
||||
detail={"task_id": task.task_id, "status": task.status, "message": task.message},
|
||||
db,
|
||||
current,
|
||||
action="recharge:yyb:payment_check",
|
||||
target=f"yyb_task:{task.task_id}",
|
||||
detail={
|
||||
"task_id": task.task_id,
|
||||
"status": task.status,
|
||||
"message": task.message,
|
||||
},
|
||||
success=task.status != "failed",
|
||||
)
|
||||
db.commit()
|
||||
return public_task(task, include_qr=False, creator_username=_creator_username(db, task))
|
||||
return public_task(
|
||||
task, include_qr=False, creator_username=_creator_username(db, task)
|
||||
)
|
||||
|
||||
|
||||
@router.post("/tasks/{task_id}/stop")
|
||||
def stop(task_id: int, db: Session = Depends(get_db), current: User = Depends(require_permission("yyb:session"))):
|
||||
def stop(
|
||||
task_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current: User = Depends(require_permission("yyb:session")),
|
||||
):
|
||||
task = _get_task(db, task_id, current, write=True)
|
||||
data = _worker_call(lambda: YybWorkerClient().stop(task.worker_job_id))
|
||||
task.status = "stopped"
|
||||
@@ -221,7 +345,10 @@ def stop(task_id: int, db: Session = Depends(get_db), current: User = Depends(re
|
||||
task.message = str(data.get("message", "任务已停止"))
|
||||
task.finished_at = _utcnow()
|
||||
record_audit(
|
||||
db, current, action="recharge:yyb:stop", target=f"yyb_task:{task.task_id}",
|
||||
db,
|
||||
current,
|
||||
action="recharge:yyb:stop",
|
||||
target=f"yyb_task:{task.task_id}",
|
||||
detail={"task_id": task.task_id, "status": task.status},
|
||||
)
|
||||
db.commit()
|
||||
|
||||
@@ -8,27 +8,31 @@ from sqlalchemy.orm import Session
|
||||
from ..models import Account, LoginTask
|
||||
|
||||
|
||||
EMAIL_PATTERN = re.compile(r'^[^\s@|]+@[^\s@|]+\.[^\s@|]+$')
|
||||
EMAIL_PATTERN = re.compile(r"^[^\s@|]+@[^\s@|]+\.[^\s@|]+$")
|
||||
|
||||
|
||||
def split_account_line(line: str) -> list[str]:
|
||||
"""拆分一行账号文本,支持 |、tab、逗号、空格分隔。"""
|
||||
if '|' in line:
|
||||
return line.split('|')
|
||||
if '\t' in line:
|
||||
return line.split('\t')
|
||||
if ',' in line:
|
||||
if "|" in line:
|
||||
return line.split("|")
|
||||
if "\t" in line:
|
||||
return line.split("\t")
|
||||
if "," in line:
|
||||
return next(csv.reader([line]))
|
||||
return line.split()
|
||||
|
||||
|
||||
def cookie_account_ids_query(db: Session):
|
||||
"""返回有成功登录记录(cookie非空)的账号ID子查询。"""
|
||||
return db.query(LoginTask.account_id).filter(
|
||||
LoginTask.status == 'success',
|
||||
LoginTask.cookie != '',
|
||||
LoginTask.cookie.isnot(None),
|
||||
).distinct()
|
||||
return (
|
||||
db.query(LoginTask.account_id)
|
||||
.filter(
|
||||
LoginTask.status == "success",
|
||||
LoginTask.cookie != "",
|
||||
LoginTask.cookie.isnot(None),
|
||||
)
|
||||
.distinct()
|
||||
)
|
||||
|
||||
|
||||
def parse_and_build_accounts(
|
||||
@@ -61,9 +65,9 @@ def parse_and_build_accounts(
|
||||
skipped = 0
|
||||
duplicated = 0
|
||||
seen_in_batch: set[str] = set()
|
||||
for line in text.strip().split('\n'):
|
||||
for line in text.strip().split("\n"):
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
parts = split_account_line(line)
|
||||
if len(parts) < 4:
|
||||
@@ -88,14 +92,16 @@ def parse_and_build_accounts(
|
||||
seen_in_batch.add(username_key)
|
||||
|
||||
email_cfg = get_email_config_for_account(email)
|
||||
accounts.append(Account(
|
||||
username=username,
|
||||
password=password,
|
||||
email=email,
|
||||
email_password=email_password,
|
||||
email_imap_server=email_cfg['server'],
|
||||
email_imap_port=email_cfg.get('port', 993),
|
||||
email_imap_ssl=email_cfg.get('ssl', True),
|
||||
tag=tag,
|
||||
))
|
||||
accounts.append(
|
||||
Account(
|
||||
username=username,
|
||||
password=password,
|
||||
email=email,
|
||||
email_password=email_password,
|
||||
email_imap_server=email_cfg["server"],
|
||||
email_imap_port=email_cfg.get("port", 993),
|
||||
email_imap_ssl=email_cfg.get("ssl", True),
|
||||
tag=tag,
|
||||
)
|
||||
)
|
||||
return accounts, skipped, duplicated
|
||||
|
||||
@@ -12,8 +12,17 @@ from ..models import AuditLog, User
|
||||
|
||||
|
||||
_SENSITIVE_KEY_PARTS = (
|
||||
"cookie", "token", "password", "passwd", "secret", "signature", "sign",
|
||||
"qr_data", "authorization", "credential", "private_key",
|
||||
"cookie",
|
||||
"token",
|
||||
"password",
|
||||
"passwd",
|
||||
"secret",
|
||||
"signature",
|
||||
"sign",
|
||||
"qr_data",
|
||||
"authorization",
|
||||
"credential",
|
||||
"private_key",
|
||||
)
|
||||
|
||||
|
||||
@@ -42,7 +51,9 @@ def record_audit(
|
||||
) -> AuditLog:
|
||||
"""加入一条审计记录;调用方负责与业务变更一起提交事务。"""
|
||||
if isinstance(detail, Mapping):
|
||||
detail_text = json.dumps(_safe_value(detail), ensure_ascii=False, separators=(",", ":"))
|
||||
detail_text = json.dumps(
|
||||
_safe_value(detail), ensure_ascii=False, separators=(",", ":")
|
||||
)
|
||||
elif detail is None:
|
||||
detail_text = ""
|
||||
else:
|
||||
|
||||
@@ -200,7 +200,9 @@ def snapshot_from_batch(batch: HuyaRegisterBatch) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def load_batch_snapshot(batch_id: str, *, recover_interrupted: bool = True) -> dict | None:
|
||||
def load_batch_snapshot(
|
||||
batch_id: str, *, recover_interrupted: bool = True
|
||||
) -> dict | None:
|
||||
"""从数据库加载批次详情;若服务中断则标记为 interrupted。"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
@@ -246,7 +248,9 @@ def load_batch_snapshot(batch_id: str, *, recover_interrupted: bool = True) -> d
|
||||
db.close()
|
||||
|
||||
|
||||
def list_batch_summaries(limit: int = 50, live_batch_ids: set[str] | None = None) -> list[dict]:
|
||||
def list_batch_summaries(
|
||||
limit: int = 50, live_batch_ids: set[str] | None = None
|
||||
) -> list[dict]:
|
||||
"""列出最近的注册批次摘要。live_batch_ids 中的 running 保持运行中。"""
|
||||
live = live_batch_ids or set()
|
||||
db = SessionLocal()
|
||||
@@ -269,42 +273,51 @@ def list_batch_summaries(limit: int = 50, live_batch_ids: set[str] | None = None
|
||||
if status == "running":
|
||||
running_count = max(
|
||||
0,
|
||||
int(row.total or 0) - int(row.success_count or 0) - int(row.failed_count or 0) - int(row.stopped_count or 0),
|
||||
int(row.total or 0)
|
||||
- int(row.success_count or 0)
|
||||
- int(row.failed_count or 0)
|
||||
- int(row.stopped_count or 0),
|
||||
)
|
||||
result.append({
|
||||
"batch_id": row.batch_id,
|
||||
"status": status,
|
||||
"message": message,
|
||||
"tag": row.tag or "",
|
||||
"created_by": row.created_by,
|
||||
"concurrency": int(row.concurrency or 1),
|
||||
"wait_seconds": float(row.wait_seconds or 180),
|
||||
"poll_interval": float(row.poll_interval or 5),
|
||||
"password_prefix": row.password_prefix or "hy",
|
||||
"use_proxy": bool(row.use_proxy),
|
||||
"total": int(row.total or 0),
|
||||
"success_count": int(row.success_count or 0),
|
||||
"failed_count": int(row.failed_count or 0),
|
||||
"stopped_count": int(row.stopped_count or 0),
|
||||
"running_count": running_count,
|
||||
"created_at": row.created_at,
|
||||
"started_at": row.started_at,
|
||||
"finished_at": row.finished_at,
|
||||
"items": [],
|
||||
})
|
||||
result.append(
|
||||
{
|
||||
"batch_id": row.batch_id,
|
||||
"status": status,
|
||||
"message": message,
|
||||
"tag": row.tag or "",
|
||||
"created_by": row.created_by,
|
||||
"concurrency": int(row.concurrency or 1),
|
||||
"wait_seconds": float(row.wait_seconds or 180),
|
||||
"poll_interval": float(row.poll_interval or 5),
|
||||
"password_prefix": row.password_prefix or "hy",
|
||||
"use_proxy": bool(row.use_proxy),
|
||||
"total": int(row.total or 0),
|
||||
"success_count": int(row.success_count or 0),
|
||||
"failed_count": int(row.failed_count or 0),
|
||||
"stopped_count": int(row.stopped_count or 0),
|
||||
"running_count": running_count,
|
||||
"created_at": row.created_at,
|
||||
"started_at": row.started_at,
|
||||
"finished_at": row.finished_at,
|
||||
"items": [],
|
||||
}
|
||||
)
|
||||
return result
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def _refresh_batch_counts(batch_row: HuyaRegisterBatchModel, items: list[HuyaRegisterItemModel]):
|
||||
def _refresh_batch_counts(
|
||||
batch_row: HuyaRegisterBatchModel, items: list[HuyaRegisterItemModel]
|
||||
):
|
||||
batch_row.total = len(items)
|
||||
batch_row.success_count = sum(1 for item in items if item.status == "success")
|
||||
batch_row.failed_count = sum(1 for item in items if item.status == "error")
|
||||
batch_row.stopped_count = sum(1 for item in items if item.status == "stopped")
|
||||
|
||||
|
||||
def format_success_export_line(username: str, uid: str, password: str, phone: str, sms_url: str) -> str:
|
||||
def format_success_export_line(
|
||||
username: str, uid: str, password: str, phone: str, sms_url: str
|
||||
) -> str:
|
||||
"""统一成功导出格式。"""
|
||||
account = (username or uid or "").strip()
|
||||
return f"{account}----{password or ''}----{phone or ''}----{sms_url or ''}"
|
||||
@@ -319,7 +332,9 @@ def export_success_logs_text(
|
||||
"""从成功流水表导出 txt。"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
query = db.query(HuyaRegisterSuccessLog).order_by(HuyaRegisterSuccessLog.id.asc())
|
||||
query = db.query(HuyaRegisterSuccessLog).order_by(
|
||||
HuyaRegisterSuccessLog.id.asc()
|
||||
)
|
||||
if batch_id:
|
||||
query = query.filter(HuyaRegisterSuccessLog.batch_id == batch_id)
|
||||
if tag:
|
||||
@@ -350,7 +365,9 @@ def list_success_logs(
|
||||
"""列出成功流水(含密码,供管理端展示/导出)。"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
query = db.query(HuyaRegisterSuccessLog).order_by(HuyaRegisterSuccessLog.id.desc())
|
||||
query = db.query(HuyaRegisterSuccessLog).order_by(
|
||||
HuyaRegisterSuccessLog.id.desc()
|
||||
)
|
||||
if batch_id:
|
||||
query = query.filter(HuyaRegisterSuccessLog.batch_id == batch_id)
|
||||
if tag:
|
||||
@@ -406,7 +423,11 @@ class HuyaRegisterRunner:
|
||||
|
||||
def _create_proxy_fetcher(self) -> ProxyFetcher | None:
|
||||
"""按需创建 API 代理获取器。"""
|
||||
if not self.batch.use_proxy or not self.proxy_config or not self.proxy_config.enabled:
|
||||
if (
|
||||
not self.batch.use_proxy
|
||||
or not self.proxy_config
|
||||
or not self.proxy_config.enabled
|
||||
):
|
||||
return None
|
||||
if not self.proxy_config.api_url:
|
||||
return None
|
||||
@@ -414,9 +435,15 @@ class HuyaRegisterRunner:
|
||||
wl_platform = "xiequ"
|
||||
wl_credentials = None
|
||||
if self.proxy_config.whitelist_enabled:
|
||||
wl_platform = getattr(self.proxy_config, "whitelist_platform", None) or "xiequ"
|
||||
wl_platform = (
|
||||
getattr(self.proxy_config, "whitelist_platform", None) or "xiequ"
|
||||
)
|
||||
wl_credentials = getattr(self.proxy_config, "whitelist_credentials", None)
|
||||
if not wl_credentials and self.proxy_config.whitelist_uid and self.proxy_config.whitelist_ukey:
|
||||
if (
|
||||
not wl_credentials
|
||||
and self.proxy_config.whitelist_uid
|
||||
and self.proxy_config.whitelist_ukey
|
||||
):
|
||||
wl_credentials = {
|
||||
"uid": self.proxy_config.whitelist_uid,
|
||||
"ukey": self.proxy_config.whitelist_ukey,
|
||||
@@ -446,7 +473,11 @@ class HuyaRegisterRunner:
|
||||
return
|
||||
db = SessionLocal()
|
||||
try:
|
||||
row = db.query(HuyaRegisterBatchModel).filter(HuyaRegisterBatchModel.id == self.batch.db_id).first()
|
||||
row = (
|
||||
db.query(HuyaRegisterBatchModel)
|
||||
.filter(HuyaRegisterBatchModel.id == self.batch.db_id)
|
||||
.first()
|
||||
)
|
||||
if not row:
|
||||
return
|
||||
row.status = self.batch.status
|
||||
@@ -485,7 +516,11 @@ class HuyaRegisterRunner:
|
||||
return
|
||||
db = SessionLocal()
|
||||
try:
|
||||
row = db.query(HuyaRegisterItemModel).filter(HuyaRegisterItemModel.id == item.db_id).first()
|
||||
row = (
|
||||
db.query(HuyaRegisterItemModel)
|
||||
.filter(HuyaRegisterItemModel.id == item.db_id)
|
||||
.first()
|
||||
)
|
||||
if not row:
|
||||
return
|
||||
row.status = item.status
|
||||
@@ -518,12 +553,16 @@ class HuyaRegisterRunner:
|
||||
setattr(item, key, value)
|
||||
self._persist_item(index)
|
||||
|
||||
def _save_success(self, index: int, result: HuyaAutoRegisterResult) -> tuple[int | None, str, str]:
|
||||
def _save_success(
|
||||
self, index: int, result: HuyaAutoRegisterResult
|
||||
) -> tuple[int | None, str, str]:
|
||||
"""成功时:写账号 + 成功流水(成功一个写一条,立即可导出)。"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# upsert_huya_cookie 内部会 commit 一次
|
||||
account = upsert_huya_cookie(db, result.cookie, tag=self.batch.tag, username_hint="")
|
||||
account = upsert_huya_cookie(
|
||||
db, result.cookie, tag=self.batch.tag, username_hint=""
|
||||
)
|
||||
account.game_phone = result.phone or account.game_phone or ""
|
||||
if result.username:
|
||||
account.username = result.username
|
||||
@@ -537,23 +576,29 @@ class HuyaRegisterRunner:
|
||||
account.updated_at = _now()
|
||||
|
||||
item = self.batch.items[index]
|
||||
db.add(HuyaRegisterSuccessLog(
|
||||
batch_id=self.batch.batch_id,
|
||||
item_id=item.db_id,
|
||||
account_id=account.id,
|
||||
phone=result.phone or item.phone,
|
||||
username=result.username or account.username or "",
|
||||
uid=result.uid or account.uid or account.yyuid or "",
|
||||
password=result.password or "",
|
||||
sms_url=sms_url,
|
||||
tag=self.batch.tag,
|
||||
provider=result.provider or item.provider,
|
||||
created_by=self.batch.created_by,
|
||||
created_at=_now(),
|
||||
))
|
||||
db.add(
|
||||
HuyaRegisterSuccessLog(
|
||||
batch_id=self.batch.batch_id,
|
||||
item_id=item.db_id,
|
||||
account_id=account.id,
|
||||
phone=result.phone or item.phone,
|
||||
username=result.username or account.username or "",
|
||||
uid=result.uid or account.uid or account.yyuid or "",
|
||||
password=result.password or "",
|
||||
sms_url=sms_url,
|
||||
tag=self.batch.tag,
|
||||
provider=result.provider or item.provider,
|
||||
created_by=self.batch.created_by,
|
||||
created_at=_now(),
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
db.refresh(account)
|
||||
return account.id, account.username or "", account.uid or account.yyuid or ""
|
||||
return (
|
||||
account.id,
|
||||
account.username or "",
|
||||
account.uid or account.yyuid or "",
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@@ -578,12 +623,16 @@ class HuyaRegisterRunner:
|
||||
|
||||
def _run_one(self, index: int, item: SmsLine):
|
||||
if self._stop.is_set():
|
||||
self._set_item(index, status="stopped", message="已停止", finished_at=_now())
|
||||
self._set_item(
|
||||
index, status="stopped", message="已停止", finished_at=_now()
|
||||
)
|
||||
return
|
||||
|
||||
proxies, proxy_error = self._resolve_proxy()
|
||||
if proxy_error:
|
||||
self._set_item(index, status="error", message=proxy_error, finished_at=_now())
|
||||
self._set_item(
|
||||
index, status="error", message=proxy_error, finished_at=_now()
|
||||
)
|
||||
return
|
||||
|
||||
self._set_item(
|
||||
@@ -714,7 +763,12 @@ class HuyaRegisterRunner:
|
||||
futures = []
|
||||
for index in indices:
|
||||
if self._stop.is_set():
|
||||
self._set_item(index, status="stopped", message="已停止", finished_at=_now())
|
||||
self._set_item(
|
||||
index,
|
||||
status="stopped",
|
||||
message="已停止",
|
||||
finished_at=_now(),
|
||||
)
|
||||
continue
|
||||
item = self.sms_lines[index]
|
||||
futures.append(executor.submit(self._run_one, index, item))
|
||||
@@ -841,7 +895,11 @@ class HuyaRegisterRegistry:
|
||||
# DB 同步为 running,避免返回 pending 导致前端误判
|
||||
db = SessionLocal()
|
||||
try:
|
||||
row = db.query(HuyaRegisterBatchModel).filter(HuyaRegisterBatchModel.id == db_id).first()
|
||||
row = (
|
||||
db.query(HuyaRegisterBatchModel)
|
||||
.filter(HuyaRegisterBatchModel.id == db_id)
|
||||
.first()
|
||||
)
|
||||
if row:
|
||||
row.status = "running"
|
||||
row.message = "批次运行中"
|
||||
@@ -850,7 +908,9 @@ class HuyaRegisterRegistry:
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
runner = HuyaRegisterRunner(batch=batch, sms_lines=sms_lines, proxy_config=proxy_config)
|
||||
runner = HuyaRegisterRunner(
|
||||
batch=batch, sms_lines=sms_lines, proxy_config=proxy_config
|
||||
)
|
||||
with self._lock:
|
||||
self._runners[batch_id] = runner
|
||||
return runner
|
||||
@@ -930,7 +990,9 @@ class HuyaRegisterRegistry:
|
||||
if poll_interval is not None:
|
||||
batch_row.poll_interval = int(max(1.0, float(poll_interval)))
|
||||
if password_prefix is not None:
|
||||
batch_row.password_prefix = (password_prefix or "hy").strip()[:8] or "hy"
|
||||
batch_row.password_prefix = (password_prefix or "hy").strip()[
|
||||
:8
|
||||
] or "hy"
|
||||
if fixed_password is not None:
|
||||
batch_row.fixed_password = (fixed_password or "").strip()
|
||||
if use_proxy is not None:
|
||||
@@ -965,7 +1027,12 @@ class HuyaRegisterRegistry:
|
||||
|
||||
batch = _batch_from_db(batch_row, item_rows)
|
||||
sms_lines = [
|
||||
SmsLine(phone=item.phone, url=item.sms_url, provider=item.provider, raw=f"{item.phone}----{item.sms_url}")
|
||||
SmsLine(
|
||||
phone=item.phone,
|
||||
url=item.sms_url,
|
||||
provider=item.provider,
|
||||
raw=f"{item.phone}----{item.sms_url}",
|
||||
)
|
||||
for item in batch.items
|
||||
]
|
||||
finally:
|
||||
|
||||
@@ -19,15 +19,24 @@ from .huya_runner_recharge import RechargeMixin
|
||||
|
||||
|
||||
class HuyaBatchRunner(
|
||||
HuyaBatchRunnerCore, BindMixin, GoodsMixin, RechargeMixin,
|
||||
HuyaBatchRunnerCore,
|
||||
BindMixin,
|
||||
GoodsMixin,
|
||||
RechargeMixin,
|
||||
):
|
||||
"""批量执行虎牙任务(功能域 Mixin 聚合 + 批次调度)。"""
|
||||
|
||||
def _execute_one(self, task_id: int, account_info: dict, config_info: dict, total: int):
|
||||
def _execute_one(
|
||||
self, task_id: int, account_info: dict, config_info: dict, total: int
|
||||
):
|
||||
worker_db = SessionLocal()
|
||||
try:
|
||||
task = worker_db.query(HuyaTask).filter(HuyaTask.id == task_id).first()
|
||||
account = worker_db.query(HuyaAccount).filter(HuyaAccount.id == account_info["account_id"]).first()
|
||||
account = (
|
||||
worker_db.query(HuyaAccount)
|
||||
.filter(HuyaAccount.id == account_info["account_id"])
|
||||
.first()
|
||||
)
|
||||
if not task or not account:
|
||||
return
|
||||
|
||||
@@ -59,28 +68,48 @@ class HuyaBatchRunner(
|
||||
"create_recharge_order",
|
||||
}:
|
||||
self._mark_task(worker_db, task, "failed", "该虎牙任务执行器暂未实现")
|
||||
self._push_log("warning", f"[{current}] {name} 暂未实现: {self.task_type}")
|
||||
self._push_log(
|
||||
"warning", f"[{current}] {name} 暂未实现: {self.task_type}"
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
if self.task_type == "query_points":
|
||||
self._execute_query_points(worker_db, task, account, account_info, config_info)
|
||||
self._execute_query_points(
|
||||
worker_db, task, account, account_info, config_info
|
||||
)
|
||||
elif self.task_type == "refresh_goods":
|
||||
self._execute_refresh_goods(worker_db, task, account, account_info, config_info)
|
||||
self._execute_refresh_goods(
|
||||
worker_db, task, account, account_info, config_info
|
||||
)
|
||||
elif self.task_type == "refresh_recharge_goods":
|
||||
self._execute_refresh_recharge_goods(worker_db, task, account, account_info, config_info)
|
||||
self._execute_refresh_recharge_goods(
|
||||
worker_db, task, account, account_info, config_info
|
||||
)
|
||||
elif self.task_type == "exchange_goods":
|
||||
self._execute_exchange_goods(worker_db, task, account, account_info, config_info)
|
||||
self._execute_exchange_goods(
|
||||
worker_db, task, account, account_info, config_info
|
||||
)
|
||||
elif self.task_type == "create_recharge_order":
|
||||
self._execute_create_recharge_order(worker_db, task, account, account_info, config_info)
|
||||
self._execute_create_recharge_order(
|
||||
worker_db, task, account, account_info, config_info
|
||||
)
|
||||
elif self.task_type == "get_bind_qr":
|
||||
self._execute_get_bind_qr(worker_db, task, account, account_info, config_info)
|
||||
self._execute_get_bind_qr(
|
||||
worker_db, task, account, account_info, config_info
|
||||
)
|
||||
elif self.task_type == "confirm_bind":
|
||||
self._execute_confirm_bind(worker_db, task, account, account_info, config_info)
|
||||
self._execute_confirm_bind(
|
||||
worker_db, task, account, account_info, config_info
|
||||
)
|
||||
elif self.task_type == "query_game_name":
|
||||
self._execute_query_game_name(worker_db, task, account, account_info, config_info)
|
||||
self._execute_query_game_name(
|
||||
worker_db, task, account, account_info, config_info
|
||||
)
|
||||
elif self.task_type == "query_exchange_records":
|
||||
self._execute_query_exchange_records(worker_db, task, account, account_info, config_info)
|
||||
self._execute_query_exchange_records(
|
||||
worker_db, task, account, account_info, config_info
|
||||
)
|
||||
worker_db.refresh(task)
|
||||
if task.status == "success":
|
||||
self._push_log("success", f"[{current}] {name} {task.message}")
|
||||
@@ -124,17 +153,19 @@ class HuyaBatchRunner(
|
||||
task.status = "pending"
|
||||
task.message = "等待执行"
|
||||
task.finished_at = None
|
||||
task_infos.append({
|
||||
"task_id": task.id,
|
||||
"account_info": {
|
||||
"account_id": account.id,
|
||||
"uid": account.uid or "",
|
||||
"yyuid": account.yyuid or "",
|
||||
"username": account.username or "",
|
||||
"nickname": account.nickname or "",
|
||||
"cookie": normalize_huya_cookie(account.cookie or ""),
|
||||
},
|
||||
})
|
||||
task_infos.append(
|
||||
{
|
||||
"task_id": task.id,
|
||||
"account_info": {
|
||||
"account_id": account.id,
|
||||
"uid": account.uid or "",
|
||||
"yyuid": account.yyuid or "",
|
||||
"username": account.username or "",
|
||||
"nickname": account.nickname or "",
|
||||
"cookie": normalize_huya_cookie(account.cookie or ""),
|
||||
},
|
||||
}
|
||||
)
|
||||
self.db.commit()
|
||||
|
||||
total = len(task_infos)
|
||||
@@ -149,13 +180,15 @@ class HuyaBatchRunner(
|
||||
if self._stop.is_set():
|
||||
self._push_log("warning", "任务已停止,跳过剩余账号")
|
||||
break
|
||||
futures.append(executor.submit(
|
||||
self._execute_one,
|
||||
item["task_id"],
|
||||
item["account_info"],
|
||||
config_info,
|
||||
total,
|
||||
))
|
||||
futures.append(
|
||||
executor.submit(
|
||||
self._execute_one,
|
||||
item["task_id"],
|
||||
item["account_info"],
|
||||
config_info,
|
||||
total,
|
||||
)
|
||||
)
|
||||
|
||||
for future in as_completed(futures):
|
||||
try:
|
||||
|
||||
@@ -20,8 +20,10 @@ from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from .huya_runner import HuyaBatchRunner
|
||||
|
||||
|
||||
class HuyaBatchRunnerCore:
|
||||
"""虎牙任务执行器公共基础:批次状态、日志、任务落库。"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
db: Session,
|
||||
@@ -156,7 +158,8 @@ class HuyaBatchRegistry:
|
||||
expired = [
|
||||
batch_id
|
||||
for batch_id, batch in self._batches.items()
|
||||
if batch.get("finished") and now - float(batch.get("finished_at") or now) > ttl_seconds
|
||||
if batch.get("finished")
|
||||
and now - float(batch.get("finished_at") or now) > ttl_seconds
|
||||
]
|
||||
for batch_id in expired:
|
||||
self._batches.pop(batch_id, None)
|
||||
@@ -206,4 +209,3 @@ class HuyaBatchRegistry:
|
||||
|
||||
|
||||
huya_batch_registry = HuyaBatchRegistry()
|
||||
|
||||
|
||||
@@ -37,7 +37,10 @@ def apply_huya_config_defaults(config: HuyaConfig) -> bool:
|
||||
"""补齐虎牙配置默认值,返回是否发生变更。"""
|
||||
changed = False
|
||||
for field in HUYA_CONFIG_FIELDS:
|
||||
if field == "bind_act_id" and str(getattr(config, field, "") or "").strip() == "17096":
|
||||
if (
|
||||
field == "bind_act_id"
|
||||
and str(getattr(config, field, "") or "").strip() == "17096"
|
||||
):
|
||||
setattr(config, field, HUYA_CONFIG_DEFAULTS[field])
|
||||
changed = True
|
||||
continue
|
||||
@@ -160,7 +163,9 @@ def split_huya_password_line(line: str) -> HuyaPasswordLine | None:
|
||||
)
|
||||
|
||||
|
||||
def import_huya_password_accounts(db: Session, text: str, tag: str = "") -> tuple[int, int]:
|
||||
def import_huya_password_accounts(
|
||||
db: Session, text: str, tag: str = ""
|
||||
) -> tuple[int, int]:
|
||||
"""导入虎牙账号密码,返回 (导入/更新数, 跳过数)。"""
|
||||
created_or_updated = 0
|
||||
skipped = 0
|
||||
@@ -207,13 +212,17 @@ def import_huya_password_accounts(db: Session, text: str, tag: str = "") -> tupl
|
||||
return created_or_updated, skipped
|
||||
|
||||
|
||||
def _upsert_huya_account(db: Session, parsed: dict, tag: str = "", status: str | None = None) -> HuyaAccount:
|
||||
def _upsert_huya_account(
|
||||
db: Session, parsed: dict, tag: str = "", status: str | None = None
|
||||
) -> HuyaAccount:
|
||||
"""按 uid/yyuid 新增或更新虎牙账号。"""
|
||||
account = None
|
||||
if parsed["uid"]:
|
||||
account = db.query(HuyaAccount).filter(HuyaAccount.uid == parsed["uid"]).first()
|
||||
if account is None and parsed["yyuid"]:
|
||||
account = db.query(HuyaAccount).filter(HuyaAccount.yyuid == parsed["yyuid"]).first()
|
||||
account = (
|
||||
db.query(HuyaAccount).filter(HuyaAccount.yyuid == parsed["yyuid"]).first()
|
||||
)
|
||||
|
||||
if account is None:
|
||||
account = HuyaAccount(
|
||||
@@ -273,13 +282,17 @@ def save_huya_login_cookie_to_account(
|
||||
return account
|
||||
|
||||
|
||||
def upsert_huya_cookie(db: Session, cookie: str, tag: str = "", username_hint: str = "") -> HuyaAccount:
|
||||
def upsert_huya_cookie(
|
||||
db: Session, cookie: str, tag: str = "", username_hint: str = ""
|
||||
) -> HuyaAccount:
|
||||
"""保存单条登录得到的虎牙 Cookie。"""
|
||||
line = f"{username_hint}----{cookie}" if username_hint else cookie
|
||||
parsed = parse_huya_cookie_line(line)
|
||||
if not parsed:
|
||||
raise ValueError("登录成功但 Cookie 中没有识别到虎牙 uid")
|
||||
account = _upsert_huya_account(db, parsed, tag=(tag or "").strip(), status="login_success")
|
||||
account = _upsert_huya_account(
|
||||
db, parsed, tag=(tag or "").strip(), status="login_success"
|
||||
)
|
||||
db.commit()
|
||||
db.refresh(account)
|
||||
return account
|
||||
@@ -368,18 +381,24 @@ def create_planned_tasks(
|
||||
batch_id = uuid.uuid4().hex[:12]
|
||||
payload = payload or {}
|
||||
accounts = db.query(HuyaAccount).filter(HuyaAccount.id.in_(account_ids)).all()
|
||||
if task_type in {"refresh_goods", "refresh_recharge_goods", "create_recharge_order"} and accounts:
|
||||
if (
|
||||
task_type
|
||||
in {"refresh_goods", "refresh_recharge_goods", "create_recharge_order"}
|
||||
and accounts
|
||||
):
|
||||
# 全局快照和单笔支付二维码使用一个选中的 CK 即可;兑换商品需要保留多账号批量任务。
|
||||
accounts = accounts[:1]
|
||||
for account in accounts:
|
||||
db.add(HuyaTask(
|
||||
batch_id=batch_id,
|
||||
account_id=account.id,
|
||||
task_type=task_type,
|
||||
status="planned",
|
||||
message="任务已创建,等待执行",
|
||||
result={"payload": payload} if payload else None,
|
||||
created_by=created_by,
|
||||
))
|
||||
db.add(
|
||||
HuyaTask(
|
||||
batch_id=batch_id,
|
||||
account_id=account.id,
|
||||
task_type=task_type,
|
||||
status="planned",
|
||||
message="任务已创建,等待执行",
|
||||
result={"payload": payload} if payload else None,
|
||||
created_by=created_by,
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
return batch_id, len(accounts)
|
||||
|
||||
@@ -8,7 +8,11 @@ from typing import Optional
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from core.douyu.proxy import resolve_working_proxy, verify_proxy_url, parse_proxy_response
|
||||
from core.douyu.proxy import (
|
||||
resolve_working_proxy,
|
||||
verify_proxy_url,
|
||||
parse_proxy_response,
|
||||
)
|
||||
from core.douyu.proxy_platforms import create_adapter, get_platform_labels
|
||||
from core.douyu.proxy_platforms.base import _get_local_exit_ip
|
||||
from ..models import ProxyConfig as ProxyConfigModel, AuditLog
|
||||
@@ -20,8 +24,8 @@ def _build_whitelist_params(cfg: ProxyConfigModel) -> dict:
|
||||
Returns:
|
||||
{"whitelist_platform": str, "whitelist_credentials": dict|None}
|
||||
"""
|
||||
platform = getattr(cfg, 'whitelist_platform', None) or "xiequ"
|
||||
credentials = getattr(cfg, 'whitelist_credentials', None)
|
||||
platform = getattr(cfg, "whitelist_platform", None) or "xiequ"
|
||||
credentials = getattr(cfg, "whitelist_credentials", None)
|
||||
|
||||
# 向后兼容:旧字段有值但新字段为空时,自动迁移
|
||||
if not credentials and cfg.whitelist_uid and cfg.whitelist_ukey:
|
||||
@@ -57,7 +61,10 @@ class ProxyService:
|
||||
# 应用层自动迁移:旧字段有值但新字段为空时,填充新字段
|
||||
if cfg.whitelist_uid and cfg.whitelist_ukey and not cfg.whitelist_credentials:
|
||||
cfg.whitelist_platform = "xiequ"
|
||||
cfg.whitelist_credentials = {"uid": cfg.whitelist_uid, "ukey": cfg.whitelist_ukey}
|
||||
cfg.whitelist_credentials = {
|
||||
"uid": cfg.whitelist_uid,
|
||||
"ukey": cfg.whitelist_ukey,
|
||||
}
|
||||
db.commit()
|
||||
|
||||
return cfg
|
||||
@@ -65,7 +72,10 @@ class ProxyService:
|
||||
@staticmethod
|
||||
def update_config(
|
||||
db: Session,
|
||||
enabled, api_url, http, https,
|
||||
enabled,
|
||||
api_url,
|
||||
http,
|
||||
https,
|
||||
whitelist_enabled,
|
||||
whitelist_platform="xiequ",
|
||||
whitelist_credentials=None,
|
||||
@@ -96,12 +106,14 @@ class ProxyService:
|
||||
db.refresh(cfg)
|
||||
|
||||
if current_user:
|
||||
db.add(AuditLog(
|
||||
user_id=current_user.id,
|
||||
username=current_user.username,
|
||||
action="proxy:update",
|
||||
target="proxy_config",
|
||||
))
|
||||
db.add(
|
||||
AuditLog(
|
||||
user_id=current_user.id,
|
||||
username=current_user.username,
|
||||
action="proxy:update",
|
||||
target="proxy_config",
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
return cfg
|
||||
|
||||
@@ -258,18 +270,21 @@ class ProxyService:
|
||||
|
||||
# 3. 检查并同步白名单
|
||||
records = adapter.get_whitelist()
|
||||
in_list = any(r.get('ip') == local_ip for r in records)
|
||||
in_list = any(r.get("ip") == local_ip for r in records)
|
||||
|
||||
if records:
|
||||
push("info", f"白名单共 {len(records)} 条记录")
|
||||
|
||||
if in_list:
|
||||
record = next((r for r in records if r.get('ip') == local_ip), {})
|
||||
memo = record.get('memo', '')
|
||||
record = next((r for r in records if r.get("ip") == local_ip), {})
|
||||
memo = record.get("memo", "")
|
||||
if memo == adapter.memo:
|
||||
push("success", f"本机IP {local_ip} 已在白名单中 (备注正确)")
|
||||
else:
|
||||
push("warning", f'本机IP {local_ip} 备注不匹配 (当前: "{memo}"),更新中...')
|
||||
push(
|
||||
"warning",
|
||||
f'本机IP {local_ip} 备注不匹配 (当前: "{memo}"),更新中...',
|
||||
)
|
||||
sync_ok, sync_msg = adapter.sync_ip(local_ip)
|
||||
push("success" if sync_ok else "error", f"白名单更新: {sync_msg}")
|
||||
else:
|
||||
|
||||
@@ -32,9 +32,22 @@ def _as_utc(value: datetime | None) -> datetime | None:
|
||||
|
||||
|
||||
def cleanup_orphan_yyb_tasks(db: Session, message: str) -> int:
|
||||
rows = db.query(YybRechargeTask).filter(
|
||||
YybRechargeTask.status.in_(["created", "waiting_login", "ready", "running", "ordering", "waiting_payment"])
|
||||
).all()
|
||||
rows = (
|
||||
db.query(YybRechargeTask)
|
||||
.filter(
|
||||
YybRechargeTask.status.in_(
|
||||
[
|
||||
"created",
|
||||
"waiting_login",
|
||||
"ready",
|
||||
"running",
|
||||
"ordering",
|
||||
"waiting_payment",
|
||||
]
|
||||
)
|
||||
)
|
||||
.all()
|
||||
)
|
||||
for task in rows:
|
||||
if task.status == "ordering":
|
||||
task.status = "waiting_payment"
|
||||
@@ -50,7 +63,9 @@ def cleanup_orphan_yyb_tasks(db: Session, message: str) -> int:
|
||||
return len(rows)
|
||||
|
||||
|
||||
def sync_task(db: Session, task: YybRechargeTask, worker: YybWorkerClient) -> YybRechargeTask:
|
||||
def sync_task(
|
||||
db: Session, task: YybRechargeTask, worker: YybWorkerClient
|
||||
) -> YybRechargeTask:
|
||||
data = worker.get_job(task.worker_job_id)
|
||||
task.status = str(data.get("status", task.status))
|
||||
task.phase = str(data.get("phase", task.phase))
|
||||
@@ -59,10 +74,16 @@ def sync_task(db: Session, task: YybRechargeTask, worker: YybWorkerClient) -> Yy
|
||||
task.provider = str(data["provider"])
|
||||
if data.get("qr_data"):
|
||||
task.login_qr_data = str(data["qr_data"])
|
||||
task.result = {**(task.result or {}), "login_qr_mime_type": data.get("qr_mime_type", "image/jpeg")}
|
||||
task.result = {
|
||||
**(task.result or {}),
|
||||
"login_qr_mime_type": data.get("qr_mime_type", "image/jpeg"),
|
||||
}
|
||||
if data.get("payment_qr_data"):
|
||||
task.payment_qr_data = str(data["payment_qr_data"])
|
||||
task.result = {**(task.result or {}), "payment_qr_mime_type": data.get("payment_qr_mime_type", "image/png")}
|
||||
task.result = {
|
||||
**(task.result or {}),
|
||||
"payment_qr_mime_type": data.get("payment_qr_mime_type", "image/png"),
|
||||
}
|
||||
task.result = {
|
||||
**(task.result or {}),
|
||||
"logs": data.get("logs", []),
|
||||
@@ -76,32 +97,53 @@ def sync_task(db: Session, task: YybRechargeTask, worker: YybWorkerClient) -> Yy
|
||||
task.payment_last_checked_at = last_checked_at
|
||||
if task.status in {"success", "failed"} and task.finished_at is None:
|
||||
task.finished_at = _utcnow()
|
||||
if task.status not in {"success", "failed", "stopped"} and task.finished_at is not None:
|
||||
if (
|
||||
task.status not in {"success", "failed", "stopped"}
|
||||
and task.finished_at is not None
|
||||
):
|
||||
task.finished_at = None
|
||||
db.commit()
|
||||
db.refresh(task)
|
||||
return task
|
||||
|
||||
|
||||
def public_task(task: YybRechargeTask, include_qr: bool = True,
|
||||
include_payment_qr: bool | None = None,
|
||||
creator_username: str = "") -> dict[str, Any]:
|
||||
def public_task(
|
||||
task: YybRechargeTask,
|
||||
include_qr: bool = True,
|
||||
include_payment_qr: bool | None = None,
|
||||
creator_username: str = "",
|
||||
) -> dict[str, Any]:
|
||||
result: dict[str, Any] = {
|
||||
"id": task.id, "task_id": task.task_id,
|
||||
"provider": task.provider, "platform": task.platform, "points": task.points,
|
||||
"id": task.id,
|
||||
"task_id": task.task_id,
|
||||
"provider": task.provider,
|
||||
"platform": task.platform,
|
||||
"points": task.points,
|
||||
"price_fen": task.price_fen,
|
||||
"product_id": task.product_id, "zone_id": task.zone_id, "zone_name": task.zone_name,
|
||||
"role_id": task.role_id, "role_name": task.role_name, "status": task.status,
|
||||
"phase": task.phase, "message": task.message, "result": task.result,
|
||||
"created_by": task.created_by, "created_by_username": creator_username,
|
||||
"created_at": _as_utc(task.created_at), "finished_at": _as_utc(task.finished_at),
|
||||
"product_id": task.product_id,
|
||||
"zone_id": task.zone_id,
|
||||
"zone_name": task.zone_name,
|
||||
"role_id": task.role_id,
|
||||
"role_name": task.role_name,
|
||||
"status": task.status,
|
||||
"phase": task.phase,
|
||||
"message": task.message,
|
||||
"result": task.result,
|
||||
"created_by": task.created_by,
|
||||
"created_by_username": creator_username,
|
||||
"created_at": _as_utc(task.created_at),
|
||||
"finished_at": _as_utc(task.finished_at),
|
||||
"payment_started_at": _as_utc(task.payment_started_at),
|
||||
"payment_qr_created_at": _as_utc(task.payment_qr_created_at),
|
||||
"payment_last_checked_at": _as_utc(task.payment_last_checked_at),
|
||||
}
|
||||
if task.result:
|
||||
result["login_qr_mime_type"] = task.result.get("login_qr_mime_type", "image/jpeg")
|
||||
result["payment_qr_mime_type"] = task.result.get("payment_qr_mime_type", "image/png")
|
||||
result["login_qr_mime_type"] = task.result.get(
|
||||
"login_qr_mime_type", "image/jpeg"
|
||||
)
|
||||
result["payment_qr_mime_type"] = task.result.get(
|
||||
"payment_qr_mime_type", "image/png"
|
||||
)
|
||||
if include_payment_qr is None:
|
||||
include_payment_qr = include_qr
|
||||
if include_qr:
|
||||
|
||||
@@ -18,13 +18,20 @@ class YybWorkerClient:
|
||||
self.key = os.getenv("YYB_WORKER_KEY", "")
|
||||
self.timeout = float(os.getenv("YYB_WORKER_TIMEOUT", "30"))
|
||||
|
||||
def _request(self, method: str, path: str, payload: dict[str, Any] | None = None) -> dict[str, Any]:
|
||||
def _request(
|
||||
self, method: str, path: str, payload: dict[str, Any] | None = None
|
||||
) -> dict[str, Any]:
|
||||
headers = {"Accept": "application/json"}
|
||||
if self.key:
|
||||
headers["Authorization"] = f"Bearer {self.key}"
|
||||
try:
|
||||
response = requests.request(method, self.base_url + path, json=payload,
|
||||
headers=headers, timeout=self.timeout)
|
||||
response = requests.request(
|
||||
method,
|
||||
self.base_url + path,
|
||||
json=payload,
|
||||
headers=headers,
|
||||
timeout=self.timeout,
|
||||
)
|
||||
data = response.json()
|
||||
except (requests.RequestException, ValueError) as exc:
|
||||
raise YybWorkerError(f"应用宝 Worker 不可用: {exc}") from exc
|
||||
@@ -35,19 +42,34 @@ class YybWorkerClient:
|
||||
def create_job(self) -> dict[str, Any]:
|
||||
return self._request("POST", "/v1/jobs")
|
||||
|
||||
def login(self, worker_job_id: str, provider: str, timeout: int = 600) -> dict[str, Any]:
|
||||
return self._request("POST", f"/v1/jobs/{worker_job_id}/login",
|
||||
{"provider": provider, "timeout": timeout})
|
||||
def login(
|
||||
self, worker_job_id: str, provider: str, timeout: int = 600
|
||||
) -> dict[str, Any]:
|
||||
return self._request(
|
||||
"POST",
|
||||
f"/v1/jobs/{worker_job_id}/login",
|
||||
{"provider": provider, "timeout": timeout},
|
||||
)
|
||||
|
||||
def get_job(self, worker_job_id: str) -> dict[str, Any]:
|
||||
return self._request("GET", f"/v1/jobs/{worker_job_id}")
|
||||
|
||||
def selection_options(self, worker_job_id: str, platform: str,
|
||||
points: int | None = None, zone_id: str | None = None) -> dict[str, Any]:
|
||||
return self._request("POST", f"/v1/jobs/{worker_job_id}/selection-options",
|
||||
{"platform": platform, "points": points, "zone_id": zone_id})
|
||||
def selection_options(
|
||||
self,
|
||||
worker_job_id: str,
|
||||
platform: str,
|
||||
points: int | None = None,
|
||||
zone_id: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return self._request(
|
||||
"POST",
|
||||
f"/v1/jobs/{worker_job_id}/selection-options",
|
||||
{"platform": platform, "points": points, "zone_id": zone_id},
|
||||
)
|
||||
|
||||
def selection(self, worker_job_id: str, selection: dict[str, Any]) -> dict[str, Any]:
|
||||
def selection(
|
||||
self, worker_job_id: str, selection: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
return self._request("POST", f"/v1/jobs/{worker_job_id}/selection", selection)
|
||||
|
||||
def payment(self, worker_job_id: str) -> dict[str, Any]:
|
||||
|
||||
Reference in New Issue
Block a user