Files
boss_dp/server/models.py

347 lines
15 KiB
Python
Raw Permalink Normal View History

# -*- coding: utf-8 -*-
"""
2026-02-14 16:49:44 +08:00
数据模型Django ORM 表模型 + Pydantic 内存模型
"""
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
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 直聘用户名")
2026-02-27 13:56:15 +08:00
boss_id = models.CharField(max_length=64, default="", blank=True, verbose_name="BOSS 直聘用户 ID")
2026-02-14 16:49:44 +08:00
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})"
2026-03-03 02:13:33 +08:00
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})"
2026-02-14 16:49:44 +08:00
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-03-06 10:05:49 +08:00
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}"
2026-02-14 17:58:29 +08:00
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-03-05 10:27:28 +08:00
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}"
2026-03-07 23:51:18 +08:00
class ScheduledTask(models.Model):
"""定时任务配置表。"""
REPEAT_CHOICES = [
("once", "仅一次"),
("daily", "每天"),
("weekdays", "工作日"),
("weekly", "每周"),
("monthly", "每月"),
]
TASK_TYPE_CHOICES = [
("greeting", "自动化打招呼"),
("rechat", "复聊任务"),
("collection", "收藏任务"),
]
name = models.CharField(max_length=128, verbose_name="任务名称")
task_type = models.CharField(max_length=32, choices=TASK_TYPE_CHOICES, verbose_name="任务类型")
account_id = models.IntegerField(verbose_name="关联的账号ID")
account_name = models.CharField(max_length=128, default="", verbose_name="账号名称")
# 执行时间
execute_time = models.TimeField(verbose_name="执行时间")
# 重复周期
repeat_type = models.CharField(max_length=32, choices=REPEAT_CHOICES, default="once", verbose_name="重复周期")
# 任务参数 (JSON)
params = models.JSONField(null=True, blank=True, verbose_name="任务参数")
# 状态
is_active = models.BooleanField(default=True, verbose_name="是否启用")
last_run_at = models.DateTimeField(null=True, blank=True, verbose_name="上次执行时间")
next_run_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 = "scheduled_task"
verbose_name = "定时任务"
verbose_name_plural = verbose_name
ordering = ["-created_at"]
def __str__(self):
return f"{self.name} ({self.get_task_type_display()})"
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 ───
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 ───
class TaskCreate(BaseModel):
2026-02-14 16:49:44 +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] = {}
class TaskInfo(BaseModel):
2026-02-14 16:49:44 +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
error: Optional[str] = None
created_at: float = Field(default_factory=time.time)
updated_at: float = Field(default_factory=time.time)