推进 server-rs DDD 分层与新接口接线

This commit is contained in:
Codex
2026-04-29 15:46:16 +08:00
parent 9d3fcfae77
commit f82775b852
89 changed files with 3657 additions and 9636 deletions

View File

@@ -1,3 +1,136 @@
//! 大鱼吃小鱼应用编排过渡落位。
//!
//! 这里只组合领域规则并返回结果或事件,不直接调用外部图片、视频或存储服务。
use shared_kernel::normalize_required_string;
use crate::{
BigFishAssetSlotSnapshot, build_asset_coverage,
commands::EvaluateBigFishPublishReadinessCommand, domain::BigFishPublishReadiness,
errors::BigFishApplicationError, events::BigFishDomainEvent,
};
/// 发布门禁应用结果,供 adapter 持久化快照或转换成 API DTO。
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EvaluateBigFishPublishReadinessResult {
pub readiness: BigFishPublishReadiness,
pub events: Vec<BigFishDomainEvent>,
}
/// 评估 Big Fish 作品是否具备发布条件。
///
/// 规则只依赖草稿和资产槽:草稿必须存在,等级主图、基础动作和背景图
/// 必须满足 `build_asset_coverage` 的统一口径。
pub fn evaluate_publish_readiness(
command: EvaluateBigFishPublishReadinessCommand,
asset_slots: &[BigFishAssetSlotSnapshot],
) -> Result<EvaluateBigFishPublishReadinessResult, BigFishApplicationError> {
let session_id = normalize_required_string(command.session_id)
.ok_or(BigFishApplicationError::MissingSessionId)?;
let owner_user_id = normalize_required_string(command.owner_user_id)
.ok_or(BigFishApplicationError::MissingOwnerUserId)?;
let coverage = build_asset_coverage(command.draft.as_ref(), asset_slots);
let readiness = BigFishPublishReadiness {
session_id: session_id.clone(),
owner_user_id: owner_user_id.clone(),
publish_ready: coverage.publish_ready,
blockers: coverage.blockers.clone(),
evaluated_at_micros: command.evaluated_at_micros,
};
let event = BigFishDomainEvent::PublishReadinessEvaluated {
session_id,
owner_user_id,
publish_ready: readiness.publish_ready,
blockers: readiness.blockers.clone(),
occurred_at_micros: readiness.evaluated_at_micros,
};
Ok(EvaluateBigFishPublishReadinessResult {
readiness,
events: vec![event],
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
BigFishAssetKind, build_generated_asset_slot, compile_default_draft, infer_anchor_pack,
};
fn build_command() -> EvaluateBigFishPublishReadinessCommand {
EvaluateBigFishPublishReadinessCommand {
session_id: "big-fish-session-1".to_string(),
owner_user_id: "user-1".to_string(),
draft: Some(compile_default_draft(&infer_anchor_pack("机械深海", None))),
evaluated_at_micros: 1_713_680_000_000_000,
}
}
#[test]
fn evaluate_publish_readiness_reports_blockers_when_assets_missing() {
let result = evaluate_publish_readiness(build_command(), &[]).expect("result");
assert!(!result.readiness.publish_ready);
assert!(
result
.readiness
.blockers
.iter()
.any(|item| item.contains("等级主图"))
);
assert_eq!(result.events.len(), 1);
}
#[test]
fn evaluate_publish_readiness_accepts_complete_assets() {
let command = build_command();
let draft = command.draft.clone().expect("draft");
let mut slots = Vec::new();
for level in 1..=draft.runtime_params.level_count {
slots.push(
build_generated_asset_slot(
&command.session_id,
&draft,
BigFishAssetKind::LevelMainImage,
Some(level),
None,
Some(format!("/assets/level-{level}.png")),
command.evaluated_at_micros + level as i64,
)
.expect("main image slot"),
);
for motion_key in ["idle_float", "move_swim"] {
slots.push(
build_generated_asset_slot(
&command.session_id,
&draft,
BigFishAssetKind::LevelMotion,
Some(level),
Some(motion_key.to_string()),
Some(format!("/assets/level-{level}-{motion_key}.webm")),
command.evaluated_at_micros + 100 + level as i64,
)
.expect("motion slot"),
);
}
}
slots.push(
build_generated_asset_slot(
&command.session_id,
&draft,
BigFishAssetKind::StageBackground,
None,
None,
Some("/assets/bg.png".to_string()),
command.evaluated_at_micros + 1_000,
)
.expect("background slot"),
);
let result = evaluate_publish_readiness(command, &slots).expect("result");
assert!(result.readiness.publish_ready);
assert!(result.readiness.blockers.is_empty());
}
}