V3.0 交付体系 + 精准权限系统
交付环节优化: - 新建项目必填送达者(人)邮箱,项目完成/遇到无法绕开的难关时自动邮件及时通知 - 每个项目独立工作目录 data/workspace/project_<id>/,交付物分类存放互不污染(上传/下载/删除) - 网页交付物一键部署到 /demo/<id>/ 免登录公开 Demo,送达者直接打开链接查看 - 非网页交付物 zip 打包 data/packages/,随邮件附件发送送达者(含手动交付/完成交付/自动交付) - 任务全部完成自动收尾交付;失败任务触发难关通知(30分钟限频去重) 用户(人)管理 + 精准权限: - 管理员增删改用户,管理用户项目所属与 Worker 权限(授权弹窗 + 授权总览矩阵) - 角色体系:admin 全部 / auditor 全量只读 / member 按授权 - 项目授权 view/manage/admin;Worker 授权 view/use/manage;创建者自动成为项目管理员 - 仪表盘/成本报表/日志/协作/评估全部按权限过滤,越权访问 403 新表:project_deliverables / user_projects / user_workers;新模块 delivery.py
This commit is contained in:
@@ -14,6 +14,20 @@ import llm_gateway
|
||||
import config
|
||||
import rag
|
||||
import notify
|
||||
import delivery
|
||||
|
||||
|
||||
def _notify_failed(task, message):
|
||||
"""任务失败:写告警 + 推送渠道 + 邮件通知送达者(项目难关)"""
|
||||
notify.notify('task_failed', f'任务失败:{task["title"]}',
|
||||
f'项目 #{task["project_id"]} 任务「{task["title"]}」{message}',
|
||||
save_alert=True, level='warn', atype='task_failed')
|
||||
try:
|
||||
proj = db.q('SELECT * FROM projects WHERE id=?', (task['project_id'],), one=True)
|
||||
if proj and (proj.get('deliver_email') or '').strip():
|
||||
delivery.notify_blocker(proj, task['title'], message)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _log(task_id, level, message):
|
||||
@@ -182,9 +196,7 @@ def run_task(task_id):
|
||||
_set_task(task_id, status='failed', error='前置任务未完成:' + '、'.join(blockers),
|
||||
finished_at=db.now())
|
||||
_log(task_id, 'error', '❌ 依赖未满足,无法执行:' + '、'.join(blockers))
|
||||
notify.notify('task_failed', f'任务失败:{task["title"]}',
|
||||
f'项目 #{task["project_id"]} 任务「{task["title"]}」因依赖未完成被拒绝执行:'
|
||||
+ '、'.join(blockers), save_alert=True, level='warn', atype='task_failed')
|
||||
_notify_failed(task, '因依赖未完成被拒绝执行:' + '、'.join(blockers))
|
||||
return
|
||||
|
||||
# 确定 Worker
|
||||
@@ -195,9 +207,7 @@ def run_task(task_id):
|
||||
_set_task(task_id, status='failed', error='指定 Worker 不存在或已停用',
|
||||
finished_at=db.now())
|
||||
_log(task_id, 'error', '指定 Worker 不存在或已停用')
|
||||
notify.notify('worker_alert', f'Worker 异常:任务「{task["title"]}」',
|
||||
f'指定 Worker #{task["worker_id"]} 不存在或已停用', save_alert=True,
|
||||
level='warn', atype='worker_alert')
|
||||
_notify_failed(task, '指定 Worker 不存在或已停用,无法执行')
|
||||
return
|
||||
else:
|
||||
worker = pick_worker_auto(task)
|
||||
@@ -205,9 +215,7 @@ def run_task(task_id):
|
||||
_set_task(task_id, status='failed', error='无可用 Worker(自动路由失败)',
|
||||
finished_at=db.now())
|
||||
_log(task_id, 'error', '自动路由失败:无可用 Worker')
|
||||
notify.notify('worker_alert', f'Worker 异常:任务「{task["title"]}」',
|
||||
'自动路由失败:没有可用的 Worker', save_alert=True,
|
||||
level='warn', atype='worker_alert')
|
||||
_notify_failed(task, '自动路由失败:没有可用的 Worker')
|
||||
return
|
||||
_set_task(task_id, worker_id=worker['id'])
|
||||
_log(task_id, 'info', f'自动路由 → Worker「{worker["name"]}」({worker["provider"]}/{worker["model"]})')
|
||||
@@ -217,15 +225,13 @@ def run_task(task_id):
|
||||
if not ok:
|
||||
_set_task(task_id, status='failed', error=reason, finished_at=db.now())
|
||||
_log(task_id, 'error', reason)
|
||||
notify.notify('budget_alert', f'成本上限拦截:任务「{task["title"]}」', reason,
|
||||
save_alert=True, level='warn', atype='budget')
|
||||
_notify_failed(task, reason)
|
||||
return
|
||||
ok, reason = _check_project_budget(task)
|
||||
if not ok:
|
||||
_set_task(task_id, status='failed', error=reason, finished_at=db.now())
|
||||
_log(task_id, 'error', reason)
|
||||
notify.notify('budget_alert', f'预算拦截:任务「{task["title"]}」', reason,
|
||||
save_alert=True, level='warn', atype='budget')
|
||||
_notify_failed(task, reason)
|
||||
return
|
||||
|
||||
_set_task(task_id, status='running', started_at=db.now(), error='')
|
||||
@@ -239,9 +245,7 @@ def run_task(task_id):
|
||||
except Exception as e:
|
||||
_set_task(task_id, status='failed', error=str(e), finished_at=db.now())
|
||||
_log(task_id, 'error', f'执行失败: {e}')
|
||||
notify.notify('task_failed', f'任务失败:{task["title"]}',
|
||||
f'项目 #{task["project_id"]} 任务「{task["title"]}」执行出错:{str(e)[:300]}',
|
||||
save_alert=True, level='warn', atype='task_failed')
|
||||
_notify_failed(task, f'执行出错:{str(e)[:300]}')
|
||||
return
|
||||
|
||||
_cost_record(task, worker, usage)
|
||||
@@ -272,6 +276,13 @@ def run_task(task_id):
|
||||
|
||||
_budget_alert(task['project_id'])
|
||||
|
||||
if new_status == 'done':
|
||||
# V3:无需审核的任务直接完成后,检查项目是否全部完成 → 自动交付并通知送达者
|
||||
try:
|
||||
delivery.auto_complete_if_ready(task['project_id'])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# DAG:触发下游就绪任务
|
||||
downstream = _trigger_downstream(task)
|
||||
for t in downstream:
|
||||
|
||||
Reference in New Issue
Block a user