Files
boss_dp/server/models.py
ddrwode 530d7fe135 haha
2026-02-27 13:56:15 +08:00

219 lines
9.7 KiB
Python
Raw 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 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 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
# ══════════════════════════════════════════════════════════════
# 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)