5bf036bb81
Project CI / AI game creator shell Rust shard 4/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 2/4 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust shard 1/4 (push) Has been cancelled
HTTP 请求取消后,在途计数原先无法释放;项目元数据和 External API 鉴权依赖完整 AppState,相同鉴权和追踪配置也散落在多个入口。本次集中装配这些依赖和横切能力,保持现有公开 API、权限、计费、幂等和事务规则。 ## 修改 - 91 个受保护路由集中应用鉴权;保留方法级 404/405/HEAD/Allow、公开入口、MCP、精选缓存头及 2/4 MiB 请求限制。 - 七个项目元数据入口改用缓存的 EditorProjectState,External/MCP 鉴权改用 ExternalApiAuthState;生产实现复用 SpacetimeClient,媒体修复维持原有上传和登记顺序。 - RAII 覆盖请求 Future 取消和 panic unwind 的计数清理;正常与降级服务复用 TraceLayer,指标采用 MatchedPath 模板及固定兜底。 - LLM 普通与流式调用增加跳过参数的异步 span,保持父上下文、流式回调、错误与重试行为;补充替代依赖测试并同步锁文件和文档。 ## 验证 | 验证面 | 结果 | | --- | --- | | platform-llm 完整本地回归 | 161 个单元测试、3 个集成测试通过;1 个真实 Provider 用例按原配置忽略 | | api-server 完整回归 | 执行时 1075 通过、12 失败、6 忽略;其中 1 个新增公开读取 fixture 断言已修正,14 个路由契约回归随后全部通过;剩余 11 个是下述既有 Windows 失败 | | 窄依赖、取消与追踪 | 元数据 owner/幂等/revision、鉴权及 MCP 错误传播、取消/panic/流式响应、追踪父子关系与敏感参数省略均通过 | | 实际本地服务 | 独立 SpacetimeDB 上 102/102 检查通过,两个动态项目 ID 的路由模板及请求 ID 日志核验 3/3 通过 | | 编译与边界 | api-server cargo check、AGC 锁文件下 platform-llm cargo check、rustfmt、编码、文档索引、DDD 与 diff 检查通过 | | 合入最新 master 后 | 后端源码及锁文件保持已测内容;再次通过 14 个路由契约测试、3 个 Provider 追踪测试及编码/文档/DDD/diff 检查 | 实际服务检查覆盖 health/ready、两账号登录、项目 CRUD、幂等重复、跨 owner 拒绝、revision 冲突、External/MCP 读取、Key 撤销及 404/405。使用既有 test 环境的本地 Router 拒绝 fixture,未调用真实付费 Provider;自建服务已关闭,原开发实例保留。 ## 已知测试限制 API 全量测试尚未全绿:11 个 wallet_refund_outbox 用例在 Windows 的目录同步处失败。其生产文件与变更前内容一致;标准库隔离复现确认 File::open(目录) 返回 OS 5,而普通文件写入、同步及 hard_link 正常。这个已有的目录持久化问题未混入本次重构,也未通过跳过或弱化相关断言掩盖。 --------- Co-authored-by: kdletters <61648117+kdletters@users.noreply.github.com> Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/425
196 lines
5.9 KiB
Rust
196 lines
5.9 KiB
Rust
use std::sync::Arc;
|
|
|
|
use axum::extract::FromRef;
|
|
use futures_util::future::BoxFuture;
|
|
use platform_oss::OssClient;
|
|
use spacetime_client::{
|
|
EditorProjectCreateRecordInput, EditorProjectDeleteRecordInput, EditorProjectGetRecordInput,
|
|
EditorProjectLayoutSaveV2AckRecord, EditorProjectLayoutSaveV2RecordInput, EditorProjectRecord,
|
|
EditorProjectRenameRecordInput, SpacetimeClient, SpacetimeClientError,
|
|
};
|
|
|
|
use super::AppState;
|
|
|
|
// 元数据入口只依赖现役 typed facade 的项目能力,不暴露其它领域的数据库操作。
|
|
pub(crate) trait EditorProjectRepository: Send + Sync {
|
|
fn get_recent_editor_project(
|
|
&self,
|
|
owner_user_id: String,
|
|
) -> BoxFuture<'_, Result<Option<EditorProjectRecord>, SpacetimeClientError>>;
|
|
|
|
fn list_editor_projects(
|
|
&self,
|
|
owner_user_id: String,
|
|
) -> BoxFuture<'_, Result<Vec<EditorProjectRecord>, SpacetimeClientError>>;
|
|
|
|
fn get_editor_project(
|
|
&self,
|
|
input: EditorProjectGetRecordInput,
|
|
) -> BoxFuture<'_, Result<EditorProjectRecord, SpacetimeClientError>>;
|
|
|
|
fn create_editor_project(
|
|
&self,
|
|
input: EditorProjectCreateRecordInput,
|
|
) -> BoxFuture<'_, Result<EditorProjectRecord, SpacetimeClientError>>;
|
|
|
|
fn rename_editor_project(
|
|
&self,
|
|
input: EditorProjectRenameRecordInput,
|
|
) -> BoxFuture<'_, Result<EditorProjectRecord, SpacetimeClientError>>;
|
|
|
|
fn delete_editor_project(
|
|
&self,
|
|
input: EditorProjectDeleteRecordInput,
|
|
) -> BoxFuture<'_, Result<String, SpacetimeClientError>>;
|
|
|
|
fn save_editor_project_layout_v2_ack(
|
|
&self,
|
|
input: EditorProjectLayoutSaveV2RecordInput,
|
|
) -> BoxFuture<'_, Result<EditorProjectLayoutSaveV2AckRecord, SpacetimeClientError>>;
|
|
}
|
|
|
|
impl EditorProjectRepository for SpacetimeClient {
|
|
fn get_recent_editor_project(
|
|
&self,
|
|
owner_user_id: String,
|
|
) -> BoxFuture<'_, Result<Option<EditorProjectRecord>, SpacetimeClientError>> {
|
|
Box::pin(SpacetimeClient::get_recent_editor_project(
|
|
self,
|
|
owner_user_id,
|
|
))
|
|
}
|
|
|
|
fn list_editor_projects(
|
|
&self,
|
|
owner_user_id: String,
|
|
) -> BoxFuture<'_, Result<Vec<EditorProjectRecord>, SpacetimeClientError>> {
|
|
Box::pin(SpacetimeClient::list_editor_projects(self, owner_user_id))
|
|
}
|
|
|
|
fn get_editor_project(
|
|
&self,
|
|
input: EditorProjectGetRecordInput,
|
|
) -> BoxFuture<'_, Result<EditorProjectRecord, SpacetimeClientError>> {
|
|
Box::pin(SpacetimeClient::get_editor_project(self, input))
|
|
}
|
|
|
|
fn create_editor_project(
|
|
&self,
|
|
input: EditorProjectCreateRecordInput,
|
|
) -> BoxFuture<'_, Result<EditorProjectRecord, SpacetimeClientError>> {
|
|
Box::pin(SpacetimeClient::create_editor_project(self, input))
|
|
}
|
|
|
|
fn rename_editor_project(
|
|
&self,
|
|
input: EditorProjectRenameRecordInput,
|
|
) -> BoxFuture<'_, Result<EditorProjectRecord, SpacetimeClientError>> {
|
|
Box::pin(SpacetimeClient::rename_editor_project(self, input))
|
|
}
|
|
|
|
fn delete_editor_project(
|
|
&self,
|
|
input: EditorProjectDeleteRecordInput,
|
|
) -> BoxFuture<'_, Result<String, SpacetimeClientError>> {
|
|
Box::pin(SpacetimeClient::delete_editor_project(self, input))
|
|
}
|
|
|
|
fn save_editor_project_layout_v2_ack(
|
|
&self,
|
|
input: EditorProjectLayoutSaveV2RecordInput,
|
|
) -> BoxFuture<'_, Result<EditorProjectLayoutSaveV2AckRecord, SpacetimeClientError>> {
|
|
Box::pin(SpacetimeClient::save_editor_project_layout_v2_ack(
|
|
self, input,
|
|
))
|
|
}
|
|
}
|
|
|
|
pub(crate) trait EditorProjectMediaRepair: Send + Sync {
|
|
fn repair_project(&self, project: EditorProjectRecord) -> BoxFuture<'_, EditorProjectRecord>;
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct EditorProjectState {
|
|
projects: Arc<dyn EditorProjectRepository>,
|
|
media_repair: Arc<dyn EditorProjectMediaRepair>,
|
|
}
|
|
|
|
impl EditorProjectState {
|
|
pub(crate) fn new(
|
|
projects: Arc<dyn EditorProjectRepository>,
|
|
media_repair: Arc<dyn EditorProjectMediaRepair>,
|
|
) -> Self {
|
|
Self {
|
|
projects,
|
|
media_repair,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn projects(&self) -> &dyn EditorProjectRepository {
|
|
self.projects.as_ref()
|
|
}
|
|
|
|
pub(crate) async fn repair_project_media(
|
|
&self,
|
|
project: EditorProjectRecord,
|
|
) -> EditorProjectRecord {
|
|
self.media_repair.repair_project(project).await
|
|
}
|
|
}
|
|
|
|
impl FromRef<AppState> for EditorProjectState {
|
|
fn from_ref(state: &AppState) -> Self {
|
|
state.editor_project_state.clone()
|
|
}
|
|
}
|
|
|
|
// 读项目时的内联媒体修复与生成写入共用相同存储依赖,保持原有上传和登记顺序。
|
|
#[derive(Clone)]
|
|
pub(crate) struct EditorMediaStorageState {
|
|
spacetime_client: SpacetimeClient,
|
|
oss_client: Option<OssClient>,
|
|
http_client: reqwest::Client,
|
|
}
|
|
|
|
impl EditorMediaStorageState {
|
|
pub(super) fn new(
|
|
spacetime_client: SpacetimeClient,
|
|
oss_client: Option<OssClient>,
|
|
http_client: reqwest::Client,
|
|
) -> Self {
|
|
Self {
|
|
spacetime_client,
|
|
oss_client,
|
|
http_client,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn spacetime_client(&self) -> &SpacetimeClient {
|
|
&self.spacetime_client
|
|
}
|
|
|
|
pub(crate) fn oss_client(&self) -> Option<&OssClient> {
|
|
self.oss_client.as_ref()
|
|
}
|
|
|
|
pub(crate) fn editor_oss_http_client(&self) -> &reqwest::Client {
|
|
&self.http_client
|
|
}
|
|
}
|
|
|
|
impl FromRef<AppState> for EditorMediaStorageState {
|
|
fn from_ref(state: &AppState) -> Self {
|
|
Self::new(
|
|
state.spacetime_client().clone(),
|
|
state.oss_client().cloned(),
|
|
state.editor_oss_http_client().clone(),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl EditorProjectMediaRepair for EditorMediaStorageState {
|
|
fn repair_project(&self, project: EditorProjectRecord) -> BoxFuture<'_, EditorProjectRecord> {
|
|
Box::pin(crate::editor_project::repair_editor_project_record_inline_media(self, project))
|
|
}
|
|
}
|