工作流在脚本收尾前失败(拉取/编译/runner 异常)或 GENURL 不可达时, 结束回调会永久丢失,而等待页 30 秒轮询与列表页此前只读本地库, 任务只能等 6 小时本地超时,平台侧已失败也无法及时反映。 - GitHub/Gitea 后端新增只读 get_run(),仅在平台报告终态时返回结论, 在途、非 200、网络异常、坏 JSON 一律返回 None;不取消、不写平台 - 等待页 30 秒轮询同步核对一次真实状态:20 秒节流、6 秒超时、异常静默 - 我的构建/后台仪表盘改为后台守护线程核对,页面渲染零等待 - 仅做非终态到终态的条件更新,回调抢先到达时不会被覆盖 - 停止/删除纯本地语义、6 小时本地超时兜底等现有逻辑保持不变
90 lines
3.4 KiB
Python
90 lines
3.4 KiB
Python
"""构建后端统一接口与共享逻辑。"""
|
||
from dataclasses import dataclass
|
||
|
||
|
||
class BackendError(Exception):
|
||
"""触发构建失败时抛出,message 为可直接展示给用户的中文提示。"""
|
||
|
||
def __init__(self, message, *, kind='error'):
|
||
super().__init__(message)
|
||
self.message = message
|
||
# rejected=平台明确拒绝(4xx/5xx);network=连不上平台
|
||
self.kind = kind
|
||
|
||
|
||
@dataclass
|
||
class DispatchResult:
|
||
run_id: object = None # 平台返回的运行编号(int 或 None)
|
||
html_url: str = '' # 平台上的运行详情页地址
|
||
|
||
|
||
@dataclass
|
||
class RunInfo:
|
||
"""平台单次运行的状态快照。"""
|
||
finished: bool = False # 是否已结束(拿到终态结论)
|
||
status: str = '' # finished=True 时为 rdgen 内部终态码
|
||
html_url: str = '' # 平台上的运行详情页地址
|
||
|
||
|
||
# rdgen 目标平台 -> 工作流文件名(两平台同名)
|
||
WORKFLOW_FILES = {
|
||
'windows': 'generator-windows.yml',
|
||
'windows-x86': 'generator-windows-x86.yml',
|
||
'linux': 'generator-linux.yml',
|
||
'android': 'generator-android.yml',
|
||
'macos': 'generator-macos.yml',
|
||
}
|
||
|
||
|
||
class BuildBackend:
|
||
"""各构建平台实现此接口。所有配置均通过 app_settings 实时读取。"""
|
||
|
||
name = ''
|
||
label = ''
|
||
|
||
# ---- 配置健康度(后台页 / 全局告警条使用) ----
|
||
def configured(self) -> bool:
|
||
raise NotImplementedError
|
||
|
||
def warnings(self) -> list:
|
||
"""配置隐患的中文提示列表;通用项(GENURL/ZIP_PASSWORD)各实现自行包含。"""
|
||
raise NotImplementedError
|
||
|
||
# ---- 构建任务 ----
|
||
def workflow_file_for(self, platform, selfhosted=False) -> str:
|
||
raise NotImplementedError
|
||
|
||
def dispatch(self, workflow_file, inputs, *, timeout=20) -> DispatchResult:
|
||
"""触发工作流。inputs 为完整输入字典(GitHub/Gitea 均为 {ref, inputs})。"""
|
||
raise NotImplementedError
|
||
|
||
def log_url(self, run_id) -> str:
|
||
raise NotImplementedError
|
||
|
||
def get_run(self, run_id, *, timeout=8):
|
||
"""只读查询单次运行:已结束返回 RunInfo(finished=True, status=内部终态码);
|
||
仍在途、查询失败或不支持时返回 None(调用方按「仍在进行」处理)。"""
|
||
return None
|
||
|
||
# ---- 后台连接测试 ----
|
||
def test_connection(self) -> dict:
|
||
"""返回 {'ok': bool, 'message': 中文说明},不抛异常。"""
|
||
raise NotImplementedError
|
||
|
||
# ---- 共享的配置检查 ----
|
||
def _shared_warnings(self, *, include_sh_secret=False):
|
||
"""两平台共用的回调/加密检查。"""
|
||
from .. import app_settings
|
||
warnings = []
|
||
if not app_settings.get_value('GENURL').strip():
|
||
warnings.append('平台外部访问地址(GENURL)未配置,构建机将无法回传构建产物。')
|
||
zip_password = app_settings.get_value('ZIP_PASSWORD')
|
||
if zip_password in ('', 'insecure'):
|
||
warnings.append(
|
||
'配置压缩包密码为空或仍为默认值 insecure,建议设置随机强密码,'
|
||
f'并同步更新{self.label}仓库 Secret。'
|
||
)
|
||
if include_sh_secret and app_settings.get_value('SH_SECRET') in ('', 'secret'):
|
||
warnings.append('自托管 Runner 密钥为空或仍为默认值 secret;如使用自托管 Runner,建议设置独立强密钥,避免被冒用触发。')
|
||
return warnings
|