Files
boss_dp/server/models.py
ddrwode 7b351039f8 haha
2026-03-06 10:47:46 +08:00

300 lines
13 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""
数据模型Django ORM 表模型 + Pydantic 内存模型。
"""
from __future__ import annotations
import time
import uuid
from typing import Any, Dict, List, Optional
from django.db import models
from pydantic import BaseModel, Field
from common.protocol import TaskStatus, TaskType
# ══════════════════════════════════════════════════════════════
# Django ORM 模型
# ══════════════════════════════════════════════════════════════
class BossAccount(models.Model):
"""BOSS 账号登录状态表。"""
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 直聘用户名")
boss_id = models.CharField(max_length=64, default="", blank=True, verbose_name="BOSS 直聘用户 ID")
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):
"""任务执行记录表。"""
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 Task(models.Model):
"""
任务表(数据库为唯一真相)。
- 所有任务的生命周期状态均保存在此表中;
- 内存中不再长期保存任务状态,只作为必要的临时变量。
"""
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")
account_name = models.CharField(max_length=128, default="", blank=True, verbose_name="账号(环境名称)")
status = models.CharField(max_length=32, default="", verbose_name="当前状态")
params = models.JSONField(null=True, blank=True, verbose_name="任务参数")
progress = models.TextField(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="创建时间")
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
class Meta:
db_table = "task"
verbose_name = "任务"
verbose_name_plural = verbose_name
def __str__(self):
return f"{self.task_id} ({self.task_type})"
class AuthToken(models.Model):
"""登录 token 表:每个用户名仅保留当前有效 token。"""
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="创建时间")
class Meta:
db_table = "auth_token"
verbose_name = "登录 Token"
verbose_name_plural = verbose_name
def __str__(self):
return self.username
class RecruitFilterSnapshot(models.Model):
"""Per-account site filter snapshot fetched from zhipin recommend page."""
worker_id = models.CharField(max_length=64, default="", db_index=True, verbose_name="Worker ID")
account_name = models.CharField(max_length=128, default="", db_index=True, verbose_name="环境名称")
browser_id = models.CharField(max_length=128, default="", blank=True, verbose_name="浏览器 ID")
groups = models.JSONField(default=list, blank=True, verbose_name="筛选分组")
flat_options = models.JSONField(default=list, blank=True, verbose_name="扁平筛选项")
raw_payload = models.JSONField(default=dict, blank=True, verbose_name="原始筛选数据")
synced_at = models.DateTimeField(auto_now=True, verbose_name="同步时间")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
class Meta:
db_table = "recruit_filter_snapshot"
unique_together = [("worker_id", "account_name")]
verbose_name = "招聘筛选快照"
verbose_name_plural = verbose_name
def __str__(self):
return f"{self.account_name}@{self.worker_id}"
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
class FollowUpConfig(models.Model):
"""复聊配置表。"""
name = models.CharField(max_length=128, verbose_name="配置名称")
position = models.CharField(max_length=64, 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 = "follow_up_config"
verbose_name = "复聊配置"
verbose_name_plural = verbose_name
def __str__(self):
return f"{self.name} ({self.position})"
class FollowUpScript(models.Model):
"""复聊话术表(支持多轮回复)。"""
config_id = models.IntegerField(verbose_name="关联的复聊配置ID")
day_number = models.IntegerField(verbose_name="第几天1=第一天2=第二天0=往后一直)")
content = models.TextField(verbose_name="话术内容")
interval_hours = models.IntegerField(default=24, verbose_name="间隔小时数")
order = models.IntegerField(default=0, verbose_name="排序")
is_active = models.BooleanField(default=True, verbose_name="是否启用")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
class Meta:
db_table = "follow_up_script"
verbose_name = "复聊话术"
verbose_name_plural = verbose_name
ordering = ['config_id', 'day_number', 'order']
def __str__(self):
return f"{self.day_number}天 - {self.content[:20]}"
class FollowUpRecord(models.Model):
"""复聊记录表(记录每次发送的话术和回复)。"""
contact_id = models.IntegerField(verbose_name="关联的联系人ID")
config_id = models.IntegerField(verbose_name="使用的复聊配置ID")
script_id = models.IntegerField(verbose_name="使用的话术ID")
day_number = models.IntegerField(verbose_name="第几天")
content = models.TextField(verbose_name="发送的内容")
sent_at = models.DateTimeField(auto_now_add=True, verbose_name="发送时间")
got_reply = models.BooleanField(default=False, verbose_name="是否得到回复")
reply_content = models.TextField(default="", blank=True, verbose_name="回复内容")
replied_at = models.DateTimeField(null=True, blank=True, verbose_name="回复时间")
class Meta:
db_table = "follow_up_record"
verbose_name = "复聊记录"
verbose_name_plural = verbose_name
ordering = ['-sent_at']
def __str__(self):
return f"联系人{self.contact_id} - 第{self.day_number}"
# ══════════════════════════════════════════════════════════════
# Pydantic 内存模型(非数据库,用于 Worker 运行时状态与任务调度)
# ══════════════════════════════════════════════════════════════
# ─── Worker ───
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
# ─── Task ───
class TaskCreate(BaseModel):
"""前端提交任务的请求体(也用于内部创建任务)。"""
task_type: TaskType
worker_id: Optional[str] = None
account_name: Optional[str] = None
params: Dict[str, Any] = {}
class TaskInfo(BaseModel):
"""任务完整信息(内存中保存)。"""
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] = {}
progress: Optional[str] = None
result: Any = None
error: Optional[str] = None
created_at: float = Field(default_factory=time.time)
updated_at: float = Field(default_factory=time.time)