Commit 21ee05b2 authored by Data Governance Dev's avatar Data Governance Dev

feat(web3): task 表加 checked_at + table_comment 元数据列

两个独立功能(同一个表的两列)一起做,因为迁移逻辑都走 init_db 兜底:

1. task.checked_at: TIMESTAMP NULL

   schema.sql / Task ORM 加列

   _migrate_task_checked_at() 在 init_db 里兜底

   routers/queries.py::start_query 末尾:sm_put 后写 task.checked_at = utc_now

   try/except + rollback + warning:元数据写失败不能阻塞查询主流程

   时刻选 /start(不是最后一页 /page):中断也算「最近关注过」

2. task.table_comment: TEXT NULL

   schema.sql / Task ORM 加列

   _migrate_task_table_comment() 兜底

   routers/tasks.py: TaskBase/Update/Out 加字段 + _row_to_out + create_task 写入

   update_task 的 setattr 循环自动覆盖

   list_tasks 接口直接读这列返前端,零查询开销

3. CLAUDE.md「只做查询」边界

   这里写 task 元数据,不是业务数据修改

   业务表(field/rule/connection_preset)一律不 UPDATE

4. 前端 TaskConfigView 现在用 row.table_comment 还没显示(commit 4 才加列)

   formatCheckedAt 已经备好(commit 2),checked_at 写完即可生效
parent 0c10c10a
......@@ -95,6 +95,10 @@ def init_db() -> None:
# 一次性迁移:rule 表加 rule_type / code 列(2026-08-21 扩展规则类型:数值 / 日期)
# 老数据全部保持 rule_type='regex',code 留空 → 跟旧逻辑完全等价
_migrate_rule_type_and_code()
# 一次性迁移:task 表加 checked_at 列(2026-08-21 接 UI「最近检测时间」)
_migrate_task_checked_at()
# 一次性迁移:task 表加 table_comment 列(2026-08-21 接「数据表注释」UI)
_migrate_task_table_comment()
logger.info(f"[init_db] DB 已存在,已执行增量建表检查:{DB_PATH}")
# 灌种子(每个 seed 函数内部判重,可重复调用)
seed.seed_task_groups()
......@@ -178,3 +182,36 @@ def _migrate_rule_type_and_code() -> None:
if "code" not in cols:
logger.info("[migrate] rule 缺 code 列,补建(默认 NULL,regex 规则用不上)")
conn.exec_driver_sql("ALTER TABLE rule ADD COLUMN code TEXT")
def _migrate_task_checked_at() -> None:
"""给 task 表加 checked_at 列(2026-08-21 接上「最近检测时间」UI)。
ORM 里新加了 checked_at,Base.metadata.create_all 不会给已存在的表加列。
SQLite ALTER TABLE ADD COLUMN 不可逆(不能 DROP COLUMN),但加 nullable 列无破坏性。
老任务这一列是 NULL,UI 显示「—」;新查询由 routers/queries.py::start_query 回填。
"""
with engine.begin() as conn:
cols = {
row[1]: row
for row in conn.exec_driver_sql("PRAGMA table_info(task)").fetchall()
}
if "checked_at" not in cols:
logger.info("[migrate] task 缺 checked_at 列,补建(默认 NULL,老任务显示「—」)")
conn.exec_driver_sql("ALTER TABLE task ADD COLUMN checked_at TIMESTAMP")
def _migrate_task_table_comment() -> None:
"""给 task 表加 table_comment 列(2026-08-21 接「数据表注释」UI)。
选表时由 TaskModal 调 list_tables 拿到,写入这列。
任务列表直接读,避免 list_tasks 接口 N+1 查询 list_tables。
"""
with engine.begin() as conn:
cols = {
row[1]: row
for row in conn.exec_driver_sql("PRAGMA table_info(task)").fetchall()
}
if "table_comment" not in cols:
logger.info("[migrate] task 缺 table_comment 列,补建(默认 NULL,老任务「—」)")
conn.exec_driver_sql("ALTER TABLE task ADD COLUMN table_comment TEXT")
\ No newline at end of file
......@@ -41,7 +41,13 @@ CREATE TABLE task (
db_type TEXT NOT NULL DEFAULT 'MySQL',
conn_json TEXT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- 2026-08-21:最近一次检测完成的时间,由 /api/queries/start 在校验结束后回填
-- 老任务这个列是 NULL,UI 显示「—」
checked_at TIMESTAMP,
-- 2026-08-21:数据表的注释,由 TaskModal 选表时从 list_tables 查一次写入
-- 任务列表直接读这列,避免 N+1 查询
table_comment TEXT
);
CREATE INDEX idx_task_group ON task(group_id);
......
......@@ -39,6 +39,12 @@ class Task(Base):
updated_at = Column(DateTime, nullable=False,
server_default=func.current_timestamp(),
onupdate=func.current_timestamp())
# 2026-08-21:最近一次检测时间,由 routers/queries.py::start_query 回填
# 老任务这个列是 NULL(迁移前建库的),UI 显示「—」
checked_at = Column(DateTime, nullable=True)
# 2026-08-21:数据表注释,由 TaskModal 选表时从 list_tables 查一次写入
# 任务列表直接读这列,避免 N+1 查询
table_comment = Column(Text, nullable=True)
# 关联(不强制 eager load,需要时再 query)
group = relationship("TaskGroup", lazy="joined")
......@@ -60,7 +66,8 @@ class Task(Base):
"conn_json": self.conn_json,
"fields": fields_count,
"rules": rules_count,
"checked_at": None, # P6/P7 检测引擎接上后再回填
"checked_at": self.checked_at.isoformat() if isinstance(self.checked_at, datetime) else self.checked_at,
"table_comment": self.table_comment,
"created_at": self.created_at.isoformat() if isinstance(self.created_at, datetime) else self.created_at,
"updated_at": self.updated_at.isoformat() if isinstance(self.updated_at, datetime) else self.updated_at,
}
......
......@@ -33,6 +33,7 @@ from __future__ import annotations
import re
import time
import uuid
from datetime import datetime, timezone
from typing import Any, Optional
from fastapi import APIRouter, Depends, HTTPException
......@@ -324,6 +325,18 @@ async def start_query(req: StartQueryRequest, db: Session = Depends(get_session)
)
sm_put(sess)
# 2026-08-21:回写「最近检测时间」—— 任务被启动过查询就算检测行为
# 用户切到任务配置页能看到时间;中途中断也照样记录(代表「最近关注过」)
# 注意:CLAUDE.md「只做查询」针对业务数据;task.checked_at 是任务元数据,与规则语义对齐
try:
task.checked_at = datetime.now(timezone.utc).replace(tzinfo=None)
db.add(task)
db.commit()
except Exception as e:
# 元数据写失败不能让查询接口失败;回滚 + 记 warning
db.rollback()
logger.warning(f"[queries] 回写 task.checked_at 失败(不影响查询):{type(e).__name__}: {e}")
elapsed_ms = (time.monotonic() - started) * 1000
logger.info(
f"✅ session={sess.session_id} total_rows={total_rows} total_pages={total_pages} "
......
......@@ -62,6 +62,9 @@ class TaskBase(BaseModel):
status: str = PydField("启用", description="启用 / 停用")
description: Optional[str] = None
conn_json: Optional[str] = PydField(None, description="UI 数据连接表单的 JSON 字符串")
# 2026-08-21:数据表注释,由前端 TaskModal 选表时从 list_tables 拿到一起提交
# 后端不主动查(避免 list_tasks 接口 N+1 查询 list_tables)
table_comment: Optional[str] = None
class TaskCreate(TaskBase):
......@@ -77,6 +80,7 @@ class TaskUpdate(BaseModel):
status: Optional[str] = None
description: Optional[str] = None
conn_json: Optional[str] = None
table_comment: Optional[str] = None
fields: Optional[list[FieldPayload]] = PydField(None, description="提供则整体替换;不传则保留旧 fields")
......@@ -121,6 +125,7 @@ class TaskOut(BaseModel):
rules: int # 规则总数(列表视图用)
field_list: list[FieldOut] = [] # 详情用(含 rules);列表视图为空(节省带宽)
checked_at: Optional[str]
table_comment: Optional[str] = None # 数据表注释(任务列表 UI 用)
created_at: str
updated_at: str
......@@ -270,6 +275,7 @@ def _row_to_out(db: Session, t: Task, *, include_field_list: bool = False) -> Ta
rules=rules_count,
field_list=field_list,
checked_at=d["checked_at"],
table_comment=d["table_comment"],
created_at=d["created_at"],
updated_at=d["updated_at"],
)
......@@ -308,6 +314,8 @@ def create_task(payload: TaskCreate, db: Session = Depends(get_session)) -> Task
description=payload.description,
db_type=payload.db_type or "MySQL",
conn_json=payload.conn_json,
# 2026-08-21:数据表注释由前端 TaskModal 选表时拉取一起提交,后端不主动查
table_comment=payload.table_comment,
)
db.add(t)
try:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment