haha
This commit is contained in:
@@ -1,52 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Filter APIs:
|
||||
- CRUD for FilterConfig (legacy/manual configs)
|
||||
- Recruit filter options (synced from site at worker startup)
|
||||
"""
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import api_view
|
||||
|
||||
from server.core.response import api_error, api_success
|
||||
from server.models import BossAccount, FilterConfig, RecruitFilterSnapshot
|
||||
from server.models import BossAccount, RecruitFilterSnapshot
|
||||
from server.serializers import (
|
||||
FilterConfigSerializer,
|
||||
RecruitFilterSnapshotSerializer,
|
||||
)
|
||||
|
||||
|
||||
@api_view(["GET", "POST"])
|
||||
def filter_list(request):
|
||||
if request.method == "GET":
|
||||
qs = FilterConfig.objects.all().order_by("-updated_at")
|
||||
return api_success(FilterConfigSerializer(qs, many=True).data)
|
||||
|
||||
ser = FilterConfigSerializer(data=request.data)
|
||||
ser.is_valid(raise_exception=True)
|
||||
ser.save()
|
||||
return api_success(ser.data, http_status=status.HTTP_201_CREATED)
|
||||
|
||||
|
||||
@api_view(["GET", "PUT", "DELETE"])
|
||||
def filter_detail(request, pk):
|
||||
try:
|
||||
obj = FilterConfig.objects.get(pk=pk)
|
||||
except FilterConfig.DoesNotExist:
|
||||
return api_error(status.HTTP_404_NOT_FOUND, "筛选配置不存在")
|
||||
|
||||
if request.method == "GET":
|
||||
return api_success(FilterConfigSerializer(obj).data)
|
||||
|
||||
if request.method == "PUT":
|
||||
ser = FilterConfigSerializer(obj, data=request.data, partial=True)
|
||||
ser.is_valid(raise_exception=True)
|
||||
ser.save()
|
||||
return api_success(ser.data)
|
||||
|
||||
obj.delete()
|
||||
return api_success(msg="筛选配置已删除")
|
||||
|
||||
|
||||
def _snapshot_payload(snapshot: RecruitFilterSnapshot) -> dict:
|
||||
data = RecruitFilterSnapshotSerializer(snapshot).data
|
||||
return {
|
||||
@@ -102,4 +68,3 @@ def recruit_filter_options(request):
|
||||
}
|
||||
)
|
||||
return api_success(_snapshot_payload(snapshot))
|
||||
|
||||
|
||||
16
server/migrations/0008_delete_filterconfig.py
Normal file
16
server/migrations/0008_delete_filterconfig.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# Generated by Codex on 2026-03-06
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("server", "0007_recruitfiltersnapshot"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name="FilterConfig",
|
||||
),
|
||||
]
|
||||
35
server/migrations/0009_task.py
Normal file
35
server/migrations/0009_task.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# Generated by Django 6.0.2 on 2026-03-06 10:45
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('server', '0008_delete_filterconfig'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Task',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('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(default='', max_length=64, verbose_name='执行的 Worker')),
|
||||
('account_name', models.CharField(blank=True, default='', max_length=128, verbose_name='账号(环境名称)')),
|
||||
('status', models.CharField(default='', max_length=32, verbose_name='当前状态')),
|
||||
('params', models.JSONField(blank=True, null=True, verbose_name='任务参数')),
|
||||
('progress', models.TextField(blank=True, null=True, verbose_name='进度信息')),
|
||||
('result', models.JSONField(blank=True, null=True, verbose_name='任务结果')),
|
||||
('error', models.TextField(blank=True, null=True, verbose_name='错误信息')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '任务',
|
||||
'verbose_name_plural': '任务',
|
||||
'db_table': 'task',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -105,42 +105,6 @@ class AuthToken(models.Model):
|
||||
return self.username
|
||||
|
||||
|
||||
class FilterConfig(models.Model):
|
||||
"""筛选条件配置表。"""
|
||||
name = models.CharField(max_length=128, verbose_name="配置名称")
|
||||
position_keywords = models.CharField(max_length=512, default="", blank=True, verbose_name="岗位关键词列表")
|
||||
city = models.CharField(max_length=64, default="", blank=True, verbose_name="城市")
|
||||
salary_min = models.CharField(max_length=32, default="", blank=True, verbose_name="最低薪资(K)")
|
||||
salary_max = models.CharField(max_length=32, default="", blank=True, verbose_name="最高薪资(K)")
|
||||
experience = models.CharField(max_length=64, default="", blank=True, verbose_name="工作经验")
|
||||
education = models.CharField(max_length=32, default="不限", verbose_name="学历要求")
|
||||
is_active = models.BooleanField(default=True, 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="性别")
|
||||
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="打招呼间隔秒")
|
||||
min_amount = models.IntegerField(null=True, blank=True, verbose_name="最小金额")
|
||||
max_amount = models.IntegerField(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 = "filter_config"
|
||||
verbose_name = "筛选配置"
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class RecruitFilterSnapshot(models.Model):
|
||||
"""Per-account site filter snapshot fetched from zhipin recommend page."""
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ DRF 序列化器。
|
||||
from rest_framework import serializers
|
||||
|
||||
from server.models import (
|
||||
BossAccount, TaskLog, FilterConfig, RecruitFilterSnapshot, ChatScript, ContactRecord, SystemConfig,
|
||||
BossAccount, TaskLog, RecruitFilterSnapshot, ChatScript, ContactRecord, SystemConfig,
|
||||
FollowUpConfig, FollowUpScript, FollowUpRecord
|
||||
)
|
||||
|
||||
@@ -89,49 +89,6 @@ class LoginSerializer(serializers.Serializer):
|
||||
password = serializers.CharField(max_length=128)
|
||||
|
||||
|
||||
# ────────────────────────── 筛选配置 ──────────────────────────
|
||||
|
||||
# 请求中可能出现的“金额”字段别名,统一映射为 min_amount / max_amount,保证请求与响应字段名一致
|
||||
FILTER_AMOUNT_ALIASES = {
|
||||
"minAmount": "min_amount",
|
||||
"maxAmount": "max_amount",
|
||||
"最小金额": "min_amount",
|
||||
"最大金额": "max_amount",
|
||||
}
|
||||
|
||||
|
||||
class FilterConfigSerializer(serializers.ModelSerializer):
|
||||
"""筛选配置:列表/详情返回 name, position_keywords, city, salary_min, salary_max, experience, education, is_active 等。"""
|
||||
|
||||
class Meta:
|
||||
model = FilterConfig
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id", "created_at", "updated_at"]
|
||||
|
||||
def to_internal_value(self, data):
|
||||
"""请求中兼容 minAmount/maxAmount 等别名;支持 multipart/form-data(QueryDict/多值列表)。"""
|
||||
if data is None:
|
||||
data = {}
|
||||
else:
|
||||
data = dict(data)
|
||||
# multipart 解析后字段值可能是 list(如 ["测试配置"]),需展平为标量
|
||||
for key in list(data.keys()):
|
||||
val = data[key]
|
||||
if isinstance(val, (list, tuple)):
|
||||
data[key] = val[0] if len(val) > 0 else ""
|
||||
for alias, canonical in FILTER_AMOUNT_ALIASES.items():
|
||||
if alias in data and canonical not in data:
|
||||
data[canonical] = data.pop(alias)
|
||||
# 请求里 is_active 可能是字符串 "true"/"false" 或单元素列表
|
||||
raw = data.get("is_active")
|
||||
if raw is not None:
|
||||
if isinstance(raw, (list, tuple)):
|
||||
raw = raw[0] if raw else ""
|
||||
if isinstance(raw, str):
|
||||
data["is_active"] = raw.lower() in ("true", "1", "yes", "是")
|
||||
return super().to_internal_value(data)
|
||||
|
||||
|
||||
# ────────────────────────── 话术 ──────────────────────────
|
||||
|
||||
class RecruitFilterSnapshotSerializer(serializers.ModelSerializer):
|
||||
|
||||
@@ -35,10 +35,8 @@ urlpatterns = [
|
||||
path("api/accounts/worker/<str:worker_id>", accounts.account_list_by_worker),
|
||||
path("api/accounts/<int:account_id>", accounts.account_detail),
|
||||
|
||||
# ─── 筛选配置 ───
|
||||
path("api/filters", filters.filter_list),
|
||||
# ─── 招聘筛选快照 ───
|
||||
path("api/filters/options", filters.recruit_filter_options),
|
||||
path("api/filters/<int:pk>", filters.filter_detail),
|
||||
|
||||
# ─── 话术管理 ───
|
||||
path("api/scripts", scripts.script_list),
|
||||
|
||||
Reference in New Issue
Block a user