按后台配置扣除创作泥点

前端创作表单泥点预校验改为读取入口契约配置

拼图和抓大鹅初始生成后端扣费改为解析后台配置

汪汪声浪初始三图生成按入口总成本拆分扣费

创作工作台按钮和确认弹窗展示后台配置泥点成本

补充泥点扣费回归测试并同步文档与共享记忆
This commit is contained in:
2026-06-08 15:47:48 +08:00
parent 3ca5a460f1
commit 5ea9f0a120
21 changed files with 425 additions and 45 deletions

View File

@@ -36,7 +36,7 @@ use time::{Duration as TimeDuration, OffsetDateTime};
use crate::{
api_response::json_success_body,
asset_billing::execute_billable_asset_operation,
asset_billing::execute_billable_asset_operation_with_cost,
auth::AuthenticatedAccessToken,
generated_image_assets::{
GeneratedImageAssetAdapter, GeneratedImageAssetDataUrl,
@@ -62,6 +62,8 @@ const BARK_BATTLE_RUN_ID_PREFIX: &str = "bark-battle-run-";
const BARK_BATTLE_RUN_TOKEN_PREFIX: &str = "bark-battle-token-";
const BARK_BATTLE_IMAGE_ID_PREFIX: &str = "bark-battle-image-";
const BARK_BATTLE_PLAY_TYPE_ID: &str = "bark-battle";
const BARK_BATTLE_INITIAL_DRAFT_GENERATION_BILLING_PURPOSE: &str = "initial_draft_generation";
const BARK_BATTLE_INITIAL_DRAFT_GENERATION_SLOT_COUNT: u64 = 3;
const BARK_BATTLE_RUN_TTL_SECONDS: i64 = 10 * 60;
const BARK_BATTLE_CHARACTER_IMAGE_SIZE: &str = "1024*1024";
const BARK_BATTLE_BACKGROUND_IMAGE_SIZE: &str = "1024*1792";
@@ -303,11 +305,13 @@ pub async fn generate_bark_battle_image_asset(
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToString::to_string);
let result = execute_billable_asset_operation(
let points_cost = resolve_bark_battle_image_asset_points_cost(&state, &payload).await;
let result = execute_billable_asset_operation_with_cost(
&state,
&owner_user_id,
bark_battle_slot_asset_kind(&slot),
asset_id.as_str(),
points_cost,
async {
generate_and_persist_bark_battle_image_asset(
&state,
@@ -328,6 +332,40 @@ pub async fn generate_bark_battle_image_asset(
Ok(json_success_body(Some(&request_context), result))
}
async fn resolve_bark_battle_image_asset_points_cost(
state: &AppState,
payload: &BarkBattleImageAssetGenerateRequest,
) -> u64 {
if payload.billing_purpose.as_deref()
!= Some(BARK_BATTLE_INITIAL_DRAFT_GENERATION_BILLING_PURPOSE)
{
return crate::asset_billing::ASSET_OPERATION_POINTS_COST;
}
let total_cost = crate::creation_entry_config::resolve_creation_entry_mud_point_cost(
state,
BARK_BATTLE_PLAY_TYPE_ID,
BARK_BATTLE_INITIAL_DRAFT_GENERATION_SLOT_COUNT
* crate::asset_billing::ASSET_OPERATION_POINTS_COST,
)
.await;
resolve_bark_battle_initial_generation_slot_points_cost(&payload.slot, total_cost)
}
fn resolve_bark_battle_initial_generation_slot_points_cost(
slot: &BarkBattleAssetSlot,
total_cost: u64,
) -> u64 {
let base_cost = total_cost / BARK_BATTLE_INITIAL_DRAFT_GENERATION_SLOT_COUNT;
let remainder = total_cost % BARK_BATTLE_INITIAL_DRAFT_GENERATION_SLOT_COUNT;
let slot_index = match slot {
BarkBattleAssetSlot::PlayerCharacter => 0,
BarkBattleAssetSlot::OpponentCharacter => 1,
BarkBattleAssetSlot::UiBackground => 2,
};
base_cost + u64::from(slot_index < remainder)
}
pub async fn publish_bark_battle_work(
State(state): State<AppState>,
Extension(request_context): Extension<RequestContext>,
@@ -1661,6 +1699,94 @@ mod tests {
);
}
#[test]
fn initial_generation_slot_cost_splits_creation_entry_total_cost() {
assert_eq!(
resolve_bark_battle_initial_generation_slot_points_cost(
&BarkBattleAssetSlot::PlayerCharacter,
1,
),
1,
);
assert_eq!(
resolve_bark_battle_initial_generation_slot_points_cost(
&BarkBattleAssetSlot::OpponentCharacter,
1,
),
0,
);
assert_eq!(
resolve_bark_battle_initial_generation_slot_points_cost(
&BarkBattleAssetSlot::UiBackground,
1,
),
0,
);
assert_eq!(
resolve_bark_battle_initial_generation_slot_points_cost(
&BarkBattleAssetSlot::PlayerCharacter,
2,
),
1,
);
assert_eq!(
resolve_bark_battle_initial_generation_slot_points_cost(
&BarkBattleAssetSlot::OpponentCharacter,
2,
),
1,
);
assert_eq!(
resolve_bark_battle_initial_generation_slot_points_cost(
&BarkBattleAssetSlot::UiBackground,
2,
),
0,
);
assert_eq!(
resolve_bark_battle_initial_generation_slot_points_cost(
&BarkBattleAssetSlot::PlayerCharacter,
6,
),
2,
);
assert_eq!(
resolve_bark_battle_initial_generation_slot_points_cost(
&BarkBattleAssetSlot::OpponentCharacter,
6,
),
2,
);
assert_eq!(
resolve_bark_battle_initial_generation_slot_points_cost(
&BarkBattleAssetSlot::UiBackground,
6,
),
2,
);
assert_eq!(
resolve_bark_battle_initial_generation_slot_points_cost(
&BarkBattleAssetSlot::PlayerCharacter,
8,
),
3,
);
assert_eq!(
resolve_bark_battle_initial_generation_slot_points_cost(
&BarkBattleAssetSlot::OpponentCharacter,
8,
),
3,
);
assert_eq!(
resolve_bark_battle_initial_generation_slot_points_cost(
&BarkBattleAssetSlot::UiBackground,
8,
),
2,
);
}
#[test]
fn draft_config_mapping_includes_stable_work_identity() {
let request_context = RequestContext::new(