2026-02-12 16:27:43 +08:00
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
"""
|
2026-02-14 16:49:44 +08:00
|
|
|
|
数据模型:Django ORM 表模型 + Pydantic 内存模型。
|
2026-02-12 16:27:43 +08:00
|
|
|
|
"""
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
import time
|
|
|
|
|
|
import uuid
|
|
|
|
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
|
|
|
2026-02-14 16:49:44 +08:00
|
|
|
|
from django.db import models
|
2026-02-12 16:27:43 +08:00
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
|
|
|
|
|
|
from common.protocol import TaskStatus, TaskType
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-02-12 17:10:05 +08:00
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
2026-02-14 16:49:44 +08:00
|
|
|
|
# Django ORM 模型
|
2026-02-12 17:10:05 +08:00
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
2026-02-14 16:49:44 +08:00
|
|
|
|
class BossAccount(models.Model):
|
2026-02-12 17:10:05 +08:00
|
|
|
|
"""BOSS 账号登录状态表。"""
|
2026-02-14 16:49:44 +08:00
|
|
|
|
worker_id = models.CharField(max_length=64, verbose_name="Worker 标识")
|
|
|
|
|
|
browser_id = models.CharField(max_length=128, default="", verbose_name="比特浏览器窗口 ID")
|
|
|
|
|
|
browser_name = models.CharField(max_length=128, default="", verbose_name="比特浏览器窗口名称(环境名)")
|
|
|
|
|
|
boss_username = models.CharField(max_length=128, default="", verbose_name="BOSS 直聘用户名")
|
|
|
|
|
|
is_logged_in = models.BooleanField(default=False, verbose_name="是否已登录")
|
|
|
|
|
|
current_task_id = models.CharField(max_length=32, null=True, blank=True, verbose_name="当前检测任务 ID")
|
|
|
|
|
|
current_task_status = models.CharField(max_length=32, null=True, blank=True, verbose_name="当前检测任务状态")
|
|
|
|
|
|
checked_at = models.DateTimeField(null=True, blank=True, verbose_name="最近一次检测时间")
|
|
|
|
|
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
|
|
|
|
|
|
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
|
|
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
|
db_table = "boss_account"
|
|
|
|
|
|
unique_together = [("worker_id", "browser_id")]
|
|
|
|
|
|
verbose_name = "BOSS 账号"
|
|
|
|
|
|
verbose_name_plural = verbose_name
|
|
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
|
return f"{self.browser_name}@{self.worker_id}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TaskLog(models.Model):
|
2026-02-12 17:10:05 +08:00
|
|
|
|
"""任务执行记录表。"""
|
2026-02-14 16:49:44 +08:00
|
|
|
|
task_id = models.CharField(max_length=32, unique=True, verbose_name="任务 ID")
|
|
|
|
|
|
task_type = models.CharField(max_length=64, verbose_name="任务类型")
|
|
|
|
|
|
worker_id = models.CharField(max_length=64, default="", verbose_name="执行的 Worker")
|
|
|
|
|
|
status = models.CharField(max_length=32, default="", verbose_name="最终状态")
|
|
|
|
|
|
params = models.JSONField(null=True, blank=True, verbose_name="任务参数")
|
|
|
|
|
|
result = models.JSONField(null=True, blank=True, verbose_name="任务结果")
|
|
|
|
|
|
error = models.TextField(null=True, blank=True, verbose_name="错误信息")
|
|
|
|
|
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
|
|
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
|
db_table = "task_log"
|
|
|
|
|
|
verbose_name = "任务日志"
|
|
|
|
|
|
verbose_name_plural = verbose_name
|
|
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
|
return f"{self.task_id} ({self.task_type})"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AuthToken(models.Model):
|
2026-02-12 18:17:15 +08:00
|
|
|
|
"""登录 token 表:每个用户名仅保留当前有效 token。"""
|
2026-02-14 16:49:44 +08:00
|
|
|
|
username = models.CharField(max_length=64, unique=True, verbose_name="用户名")
|
|
|
|
|
|
token = models.CharField(max_length=64, verbose_name="当前有效 token")
|
|
|
|
|
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
|
2026-02-12 18:17:15 +08:00
|
|
|
|
|
2026-02-14 16:49:44 +08:00
|
|
|
|
class Meta:
|
|
|
|
|
|
db_table = "auth_token"
|
|
|
|
|
|
verbose_name = "登录 Token"
|
|
|
|
|
|
verbose_name_plural = verbose_name
|
2026-02-12 18:17:15 +08:00
|
|
|
|
|
2026-02-14 16:49:44 +08:00
|
|
|
|
def __str__(self):
|
|
|
|
|
|
return self.username
|
2026-02-12 18:17:15 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-02-14 17:58:29 +08:00
|
|
|
|
class FilterConfig(models.Model):
|
|
|
|
|
|
"""筛选条件配置表。"""
|
|
|
|
|
|
name = models.CharField(max_length=128, verbose_name="配置名称")
|
|
|
|
|
|
age_min = models.IntegerField(default=18, verbose_name="最小年龄")
|
|
|
|
|
|
age_max = models.IntegerField(default=60, verbose_name="最大年龄")
|
|
|
|
|
|
gender = models.CharField(max_length=32, default="不限", verbose_name="性别")
|
|
|
|
|
|
education = models.CharField(max_length=32, default="不限", verbose_name="学历要求")
|
|
|
|
|
|
activity = models.CharField(max_length=32, default="不限", verbose_name="活跃度")
|
|
|
|
|
|
positions = models.JSONField(default=list, blank=True, verbose_name="期望岗位列表")
|
|
|
|
|
|
greeting_min = models.IntegerField(default=5, verbose_name="打招呼最少条数/天")
|
|
|
|
|
|
greeting_max = models.IntegerField(default=20, verbose_name="打招呼最多条数/天")
|
|
|
|
|
|
rest_minutes = models.IntegerField(default=30, verbose_name="每轮休息分钟")
|
|
|
|
|
|
collection_min = models.IntegerField(default=10, verbose_name="收藏最少个数/天")
|
|
|
|
|
|
collection_max = models.IntegerField(default=50, verbose_name="收藏最多个数/天")
|
|
|
|
|
|
message_interval = models.IntegerField(default=30, verbose_name="打招呼间隔秒")
|
|
|
|
|
|
is_active = models.BooleanField(default=True, verbose_name="是否启用")
|
|
|
|
|
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
|
|
|
|
|
|
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
|
|
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
|
db_table = "filter_config"
|
|
|
|
|
|
verbose_name = "筛选配置"
|
|
|
|
|
|
verbose_name_plural = verbose_name
|
|
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
|
return self.name
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ChatScript(models.Model):
|
|
|
|
|
|
"""复聊话术表。"""
|
|
|
|
|
|
SCRIPT_TYPE_CHOICES = [
|
|
|
|
|
|
("first", "首次回复"),
|
|
|
|
|
|
("followup", "跟进回复"),
|
|
|
|
|
|
("wechat", "微信交换"),
|
|
|
|
|
|
("closing", "结束语"),
|
|
|
|
|
|
]
|
|
|
|
|
|
position = models.CharField(max_length=64, verbose_name="岗位类型")
|
|
|
|
|
|
script_type = models.CharField(max_length=32, choices=SCRIPT_TYPE_CHOICES, verbose_name="话术类型")
|
|
|
|
|
|
content = models.TextField(verbose_name="话术内容")
|
|
|
|
|
|
keywords = models.CharField(max_length=256, default="", blank=True, verbose_name="触发关键词(逗号分隔)")
|
|
|
|
|
|
is_active = models.BooleanField(default=True, verbose_name="是否启用")
|
|
|
|
|
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
|
|
|
|
|
|
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
|
|
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
|
db_table = "chat_script"
|
|
|
|
|
|
verbose_name = "复聊话术"
|
|
|
|
|
|
verbose_name_plural = verbose_name
|
|
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
|
return f"{self.position} - {self.get_script_type_display()}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ContactRecord(models.Model):
|
|
|
|
|
|
"""联系人记录表(招聘过程中联系过的候选人)。"""
|
|
|
|
|
|
name = models.CharField(max_length=64, verbose_name="姓名")
|
|
|
|
|
|
position = models.CharField(max_length=64, default="", verbose_name="岗位")
|
|
|
|
|
|
contact = models.CharField(max_length=64, default="", verbose_name="联系方式")
|
|
|
|
|
|
reply_status = models.CharField(max_length=32, default="未回复", verbose_name="回复状态")
|
|
|
|
|
|
wechat_exchanged = models.BooleanField(default=False, verbose_name="是否交换微信")
|
|
|
|
|
|
account_id = models.IntegerField(null=True, blank=True, verbose_name="关联账号 ID")
|
|
|
|
|
|
worker_id = models.CharField(max_length=64, default="", verbose_name="Worker 标识")
|
|
|
|
|
|
notes = models.TextField(default="", blank=True, verbose_name="备注")
|
|
|
|
|
|
contacted_at = models.DateTimeField(null=True, blank=True, verbose_name="联系时间")
|
|
|
|
|
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
|
|
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
|
db_table = "contact_record"
|
|
|
|
|
|
verbose_name = "联系记录"
|
|
|
|
|
|
verbose_name_plural = verbose_name
|
|
|
|
|
|
ordering = ["-created_at"]
|
|
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
|
return f"{self.name} ({self.position})"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SystemConfig(models.Model):
|
|
|
|
|
|
"""系统配置键值表。"""
|
|
|
|
|
|
key = models.CharField(max_length=64, unique=True, verbose_name="配置项")
|
|
|
|
|
|
value = models.TextField(default="", verbose_name="配置值")
|
|
|
|
|
|
description = models.CharField(max_length=256, default="", blank=True, verbose_name="描述")
|
|
|
|
|
|
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
|
|
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
|
db_table = "system_config"
|
|
|
|
|
|
verbose_name = "系统配置"
|
|
|
|
|
|
verbose_name_plural = verbose_name
|
|
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
|
return self.key
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-02-12 17:10:05 +08:00
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
2026-02-14 16:49:44 +08:00
|
|
|
|
# Pydantic 内存模型(非数据库,用于 Worker 运行时状态与任务调度)
|
2026-02-12 17:10:05 +08:00
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
|
|
# ─── Worker ───
|
2026-02-12 16:27:43 +08:00
|
|
|
|
|
|
|
|
|
|
class BrowserProfile(BaseModel):
|
|
|
|
|
|
"""比特浏览器窗口信息(Worker 上报)。"""
|
|
|
|
|
|
id: str
|
|
|
|
|
|
name: str = ""
|
|
|
|
|
|
remark: str = ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class WorkerInfo(BaseModel):
|
|
|
|
|
|
"""一台 Worker 的运行时信息(内存中保存)。"""
|
|
|
|
|
|
worker_id: str
|
|
|
|
|
|
worker_name: str = ""
|
|
|
|
|
|
browsers: List[BrowserProfile] = []
|
|
|
|
|
|
online: bool = True
|
|
|
|
|
|
last_heartbeat: float = Field(default_factory=time.time)
|
|
|
|
|
|
connected_at: float = Field(default_factory=time.time)
|
|
|
|
|
|
current_task_id: Optional[str] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-02-12 17:10:05 +08:00
|
|
|
|
# ─── Task ───
|
2026-02-12 16:27:43 +08:00
|
|
|
|
|
|
|
|
|
|
class TaskCreate(BaseModel):
|
2026-02-14 16:49:44 +08:00
|
|
|
|
"""前端提交任务的请求体(也用于内部创建任务)。"""
|
2026-02-12 16:27:43 +08:00
|
|
|
|
task_type: TaskType
|
2026-02-12 17:10:05 +08:00
|
|
|
|
worker_id: Optional[str] = None
|
|
|
|
|
|
account_name: Optional[str] = None
|
|
|
|
|
|
params: Dict[str, Any] = {}
|
2026-02-12 16:27:43 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TaskInfo(BaseModel):
|
2026-02-14 16:49:44 +08:00
|
|
|
|
"""任务完整信息(内存中保存)。"""
|
2026-02-12 16:27:43 +08:00
|
|
|
|
task_id: str = Field(default_factory=lambda: uuid.uuid4().hex[:12])
|
|
|
|
|
|
task_type: TaskType
|
|
|
|
|
|
status: TaskStatus = TaskStatus.PENDING
|
|
|
|
|
|
worker_id: Optional[str] = None
|
|
|
|
|
|
account_name: Optional[str] = None
|
|
|
|
|
|
params: Dict[str, Any] = {}
|
2026-02-12 17:10:05 +08:00
|
|
|
|
progress: Optional[str] = None
|
|
|
|
|
|
result: Any = None
|
2026-02-12 16:27:43 +08:00
|
|
|
|
error: Optional[str] = None
|
|
|
|
|
|
created_at: float = Field(default_factory=time.time)
|
|
|
|
|
|
updated_at: float = Field(default_factory=time.time)
|