feat: 支持 v2 插件、独立前端和签名制品发布 (#130)

* feat: 支持 v2 插件、原生扩展与签名发布

* fix: 修复测试

* feat: 增强宿主 SDK、运行观测与制品维护能力

* feat: 增强文件传输、配置观测与本地开发能力

* feat: 支持 SSE 实时事件流并完善开发脚手架

* feat: 完善长连接鉴权复核与关闭回收

* feat: 增强生命周期与任务关闭可靠性

* feat: 收紧健康检查契约并补齐前端与真实网络验收

* feat: 增加SDK溯源与离线更新工具并验证跨版本兼容

* feat: 增加持久化业务示例与受控 Python 交付

* feat: 增加统一运行诊断与多worker状态观测

* feat: 增加固定目标发布等待与自动验收

* fix: 修复多环境插件部署前缀与代理配置不一致

* fix: 修复测试
This commit is contained in:
insistence
2026-10-09 23:49:45 +08:00
committed by GitHub
parent a89aec54aa
commit 4bd83bc7cd
287 changed files with 41945 additions and 331 deletions
@@ -0,0 +1,134 @@
from collections.abc import Mapping
from typing import Any
from uuid import uuid4
from fastapi import HTTPException
from sqlalchemy import delete, func, insert, select, update
from plugins.core.sdk import PluginRequestContext
from utils.time_util import TimezoneUtil
from .models import TaskQuery, tasks
def can_write(context: PluginRequestContext) -> bool:
"""
判断当前用户是否能够修改任务。
:param context: 宿主提供的请求身份
:return: 是否具有写权限或管理员通配权限
"""
return bool({'task_demo:write', '*:*:*'} & context.permissions)
def _task_payload(row: Mapping[str, Any]) -> dict[str, Any]:
"""
将数据库任务转换为跨语言接口字段及 UTC 时间。
:param row: 数据库任务记录
:return: 可序列化的任务对象
"""
return {
'id': row['id'],
'title': row['title'],
'description': row['description'],
'status': row['status'],
'priority': row['priority'],
'createdAt': TimezoneUtil.format_rfc3339(row['created_at']),
'updatedAt': TimezoneUtil.format_rfc3339(row['updated_at']),
}
async def list_tasks(context: PluginRequestContext, query: TaskQuery) -> dict[str, Any]:
"""
在独立事务中读取筛选后的总数与当前页。
:param context: 宿主提供的请求身份
:param query: 已校验的分页与状态参数
:return: 任务分页结果和当前用户写权限
"""
filters = [tasks.c.status == query.status] if query.status else []
async with context.transaction() as transaction:
db = transaction.transaction_session()
total = await db.scalar(select(func.count()).select_from(tasks).where(*filters))
result = await db.execute(
select(tasks)
.where(*filters)
.order_by(tasks.c.created_at.desc(), tasks.c.id.desc())
.offset((query.page - 1) * query.page_size)
.limit(query.page_size)
)
items = [_task_payload(row) for row in result.mappings()]
return {
'items': items,
'total': total,
'page': query.page,
'pageSize': query.page_size,
'canWrite': can_write(context),
}
async def get_task(context: PluginRequestContext, task_id: str) -> dict[str, Any]:
"""
在独立事务中读取指定任务。
:param context: 宿主提供的请求身份
:param task_id: 已校验的任务标识
:return: 任务对象;不存在时返回 HTTP 404
"""
async with context.transaction() as transaction:
result = await transaction.transaction_session().execute(select(tasks).where(tasks.c.id == task_id))
row = result.mappings().one_or_none()
if row is None:
raise HTTPException(status_code=404, detail='任务不存在')
return _task_payload(row)
async def create_task(context: PluginRequestContext, values: dict[str, Any]) -> dict[str, Any]:
"""
在独立事务中写入任务,成功提交后返回任务对象。
:param context: 宿主提供的请求身份
:param values: 已校验的任务输入
:return: 新建的任务对象
"""
now = TimezoneUtil.to_utc_milliseconds(TimezoneUtil.utc_now())
row = {**values, 'id': uuid4().hex, 'created_at': now, 'updated_at': now}
async with context.transaction() as transaction:
await transaction.transaction_session().execute(insert(tasks).values(**row))
return _task_payload(row)
async def update_task(context: PluginRequestContext, task_id: str, values: dict[str, Any]) -> dict[str, Any]:
"""
在独立事务中完整更新任务,保留创建时间。
:param context: 宿主提供的请求身份
:param task_id: 已校验的任务标识
:param values: 已校验的任务输入
:return: 更新后的任务对象;不存在时返回 HTTP 404
"""
async with context.transaction() as transaction:
db = transaction.transaction_session()
result = await db.execute(
update(tasks).where(tasks.c.id == task_id).values(**values, updated_at=TimezoneUtil.utc_now())
)
if result.rowcount == 0:
raise HTTPException(status_code=404, detail='任务不存在')
result = await db.execute(select(tasks).where(tasks.c.id == task_id))
return _task_payload(result.mappings().one())
async def delete_task(context: PluginRequestContext, task_id: str) -> dict[str, bool]:
"""
在独立事务中删除指定任务。
:param context: 宿主提供的请求身份
:param task_id: 已校验的任务标识
:return: 删除确认;不存在时返回 HTTP 404
"""
async with context.transaction() as transaction:
result = await transaction.transaction_session().execute(delete(tasks).where(tasks.c.id == task_id))
if result.rowcount == 0:
raise HTTPException(status_code=404, detail='任务不存在')
return {'deleted': True}