chore: 删除抖音养号任务类型,平台只保留 generic_steps

tasks/douyin/(task_type=douyin_nurture)整体删除,唯一任务类型是 generic_steps。
所有默认值/文档/接口示例同步改成 generic_steps:

- tasks/:删 douyin 包;__init__ 只注册 generic;base.py/generic 注释改为照 generic 抄
- 默认值:core/models.py(列默认 + 旧 JSON 迁移默认)、core/task_manager.py TaskJob、
  web/tasks_api.py 建任务默认、static/admin/tasks.js 新建任务默认
- core/task_manager.py:去掉 douyin 专属的"清理废弃 comment 参数"迁移块,改为**启动时告警**
  仍残留已删类型的任务(只告警不改数据);run_job_now 对已删类型直接返回明确错误,
  不再"报已触发、线程里静默失败"
- 清理残留:douyin_running 状态位(无任何读取方)、core/__init__、core/actions/*、
  core/logger.py 注释里的抖音示例
- 文档:README(特性/目录树/类型表/参数表/示例)、TASK_DEV(目录树/注册说明/模板引用)、
  ARCHITECTURE(注册示例/action 注册表示例)、API.md(task_types 与任务 JSON 示例)、
  AI_TASK_GEN(P1 去掉 douyin 预设)、DEVELOPMENT
- 注:示例里"抖音"作为**App 名**(MCP 列应用、AI 建任务的需求举例)保留,与任务类型无关

自测(全部通过):类型列表只剩 generic_steps;建任务不传类型默认 generic_steps;传
douyin_nurture 被 400 拒;库里塞残留旧类型任务 → 启动日志告警 + 执行返回明确错误 +
不自动删用户数据;前端新建任务下拉 1 项且默认选中、界面建任务成功;监控页卡片两个按钮 +
覆盖设备正常。临时任务/数据验完已清理,任务集合复原。
This commit is contained in:
2026-09-10 21:43:08 +08:00
parent 86c65236a2
commit 98cdc39224
23 changed files with 75 additions and 555 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
"""core 包:基础设施层。
config — 全局配置常量(STF、adb 路径、养号参数)
config — 全局配置常量(adb 路径、设备发现等)
adb_helper — adb 命令封装(并发安全)
device_worker — 设备生命周期 + 养号 worker + 全局状态注册表
device_worker — 设备生命周期 + 任务 worker 基类 + 全局状态注册表
task_manager — 任务管理框架(分组/计划/调度/重试/持久化)
"""
+1 -1
View File
@@ -1,7 +1,7 @@
"""全局操作框架:所有 app 任务共用的 Action 基类与注册机制。
为什么放这里:
抖音的点赞/评论 xpath 只适用抖音,留在 tasks/douyin/actions/。
某个 app 专属的 xpath 只适用那个 app,留在 tasks/<app>/actions/。
但"操作"的抽象(BaseAction 接口、概率触发、注册机制)是通用的,
放这里让所有 app 任务共享同一套操作开发范式。
+2 -2
View File
@@ -3,11 +3,11 @@
设计要点:
1. BaseAction 定义统一接口:execute(d, params, worker) -> bool
2. 每个 app 任务有自己的注册表(create_action_registry),
互不污染——抖音的 like 和快手的 like 各注册各的。
互不污染——A 应用的 like 和 B 应用的 like 各注册各的。
3. 概率触发、状态上报等通用逻辑放这里,子类不重复写。
4. ActionContext 封装执行上下文,方便扩展(传 d/worker/进度/计数器)。
新增 app 专属操作步骤(照着 tasks/douyin/actions/like.py 抄):
新增 app 专属操作步骤(在 tasks/<app>/actions/ 下新建,参考该包已有实现):
1. 在 tasks/<app>/actions/ 下新建 my_action.py
2. 写一个 BaseAction 子类,用 @register_action 装饰,实现 execute:
+1 -1
View File
@@ -41,7 +41,7 @@ def get_logger(name="core"):
"""获取指定模块的 logger。
name: 模块名(core/task/web/action),决定写哪个文件。
也可传子模块名如 "task.douyin",会归到 task.log。
也可传子模块名如 "task.generic",会归到 task.log。
返回配置好的 logging.Logger。
"""
if name in _LOGGERS:
+2 -2
View File
@@ -128,7 +128,7 @@ class TaskJob(db.Model):
"""
id = db.Column(db.String(32), primary_key=True) # uuid 前 8 位
name = db.Column(db.String(120), nullable=False)
task_type = db.Column(db.String(60), default="douyin_nurture")
task_type = db.Column(db.String(60), default="generic_steps")
target = db.Column(db.Text, default='{"mode":"all"}') # JSON
params = db.Column(db.Text, default="{}") # JSON
schedule = db.Column(db.Text, default='{"mode":"once"}') # JSON
@@ -384,7 +384,7 @@ def _migrate_old_json():
del actions["comment"]
_log.info(f"迁移任务 {j.get('name')}: 已剔除废弃的 comment 配置")
row = TaskJob(id=j["id"], name=j["name"],
task_type=j.get("task_type", "douyin_nurture"),
task_type=j.get("task_type", "generic_steps"),
enabled=j.get("enabled", True))
row.set_target(j.get("target", {"mode": "all"}))
row.set_params(params)
+18 -15
View File
@@ -1,9 +1,9 @@
"""通用任务管理框架:任务类型注册 + 设备分组 + 任务计划 + 定时调度 + 重试 + 持久化。
设计目标:可扩展,未来加非抖音任务只需注册新的 Task 类。
设计目标:可扩展——新增任务类型只需在 tasks/ 下注册新的 Task 类(见 doc/TASK_DEV.md)。
核心概念:
TaskType — 任务类型(如"抖音养号"),可注册,含默认参数和 worker 工厂
TaskType — 任务类型(如"通用步骤"),可注册,含默认参数和 worker 工厂
DeviceGroup — 设备分组,持久化到 SQLite(core.models.DeviceGroup)
TaskJob — 任务计划(类型+目标+参数+调度+重试),持久化到 SQLite(core.models.TaskJob)
TaskManager — 统管调度器、分组、任务、运行实例、状态
@@ -121,7 +121,7 @@ class DeviceGroup:
class TaskJob:
"""一个任务计划:什么任务、跑哪些设备、什么参数、何时跑、失败重试策略。"""
def __init__(self, job_id=None, name="", task_type="douyin_nurture",
def __init__(self, job_id=None, name="", task_type="generic_steps",
target=None, params=None, schedule=None, retry=None, enabled=True):
self.id = job_id or uuid.uuid4().hex[:8]
self.name = name
@@ -378,24 +378,22 @@ class TaskManager:
for row in GroupRow.query.all():
g = DeviceGroup(row.name, row.get_serials(), row.description or "")
self.groups[g.name] = g
dirty = False # 标记是否有任务参数需要写回
stale = [] # 任务类型已不存在(历史任务类型被删除)的任务
for row in JobRow.query.all():
j = TaskJob(job_id=row.id, name=row.name, task_type=row.task_type,
target=row.get_target(), params=row.get_params(),
schedule=row.get_schedule(), retry=row.get_retry(),
enabled=row.enabled)
# 数据规整:移除已废弃的 action 配置(如抖音的 comment 已删除)
if j.task_type == "douyin_nurture":
actions = j.params.get("actions", {})
if "comment" in actions:
del actions["comment"]
row.set_params(j.params)
dirty = True
_log.info(f"任务 {j.name}({j.id}): 已移除废弃的 comment 配置")
# 启动时告警:库里留着已删除的任务类型(如 douyin_nurture),
# 这类任务跑不起来也不该静默——提示人工删除或改用现有类型。
# 只告警不改数据(不自动删用户的任务)。
if not get_task_class(j.task_type):
stale.append(j)
self.jobs[j.id] = j
if dirty:
db.session.commit()
_log.info("已将规整后的任务参数写回数据库")
if stale:
_log.warning(
"以下任务的任务类型已不存在,无法执行,请在「任务」页删除或改用现有类型:"
+ ";".join(f"{j.name}({j.id}, type={j.task_type})" for j in stale))
_log.info(f"从数据库加载 {len(self.groups)} 个分组, {len(self.jobs)} 个任务")
except Exception as e:
_log.error(f"从数据库加载失败: {e}")
@@ -608,6 +606,11 @@ class TaskManager:
job = self.jobs.get(job_id)
if not job:
return {"ok": False, "error": "任务不存在"}
# 任务类型已不存在(历史类型被删除)→ 明确报错,别只返回"已触发"然后线程里静默失败
if not get_task_class(job.task_type):
return {"ok": False, "error":
f"任务类型 {job.task_type} 已不存在(该类型已被删除),"
f"请删除此任务或改用现有类型"}
# 在独立线程跑,不阻塞调用方
t = threading.Thread(target=self._run_job, args=(job,), daemon=True)
t.start()