add public work share links
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-27 22:49:13 +08:00
parent 271db02e4a
commit 1348b2e940
23 changed files with 1038 additions and 248 deletions

View File

@@ -238,6 +238,60 @@ pub fn list_profile_wallet_ledger(
}
}
// 资产生成由 Axum 调用外部模型,钱包扣费必须先在 SpacetimeDB 内原子落账。
#[spacetimedb::procedure]
pub fn consume_profile_wallet_points_and_return(
ctx: &mut ProcedureContext,
input: RuntimeProfileWalletAdjustmentInput,
) -> RuntimeProfileWalletAdjustmentProcedureResult {
match ctx.try_with_tx(|tx| {
apply_profile_wallet_adjustment(
tx,
input.clone(),
RuntimeProfileWalletLedgerSourceType::AssetGenerationConsume,
true,
)
}) {
Ok(record) => RuntimeProfileWalletAdjustmentProcedureResult {
ok: true,
record: Some(record),
error_message: None,
},
Err(message) => RuntimeProfileWalletAdjustmentProcedureResult {
ok: false,
record: None,
error_message: Some(message),
},
}
}
// 生成链路失败时由 Axum 调用退款ledger_id 幂等保证重复补偿不会重复加钱。
#[spacetimedb::procedure]
pub fn refund_profile_wallet_points_and_return(
ctx: &mut ProcedureContext,
input: RuntimeProfileWalletAdjustmentInput,
) -> RuntimeProfileWalletAdjustmentProcedureResult {
match ctx.try_with_tx(|tx| {
apply_profile_wallet_adjustment(
tx,
input.clone(),
RuntimeProfileWalletLedgerSourceType::AssetGenerationRefund,
false,
)
}) {
Ok(record) => RuntimeProfileWalletAdjustmentProcedureResult {
ok: true,
record: Some(record),
error_message: None,
},
Err(message) => RuntimeProfileWalletAdjustmentProcedureResult {
ok: false,
record: None,
error_message: Some(message),
},
}
}
// play stats 与 dashboard 共用 dashboard projection 的 total_play_time / updated_at避免 Axum 侧拼装。
#[spacetimedb::procedure]
pub fn get_profile_play_stats(
@@ -1370,15 +1424,91 @@ fn apply_profile_wallet_delta(
ledger_id: &str,
created_at: Timestamp,
) -> Result<u64, String> {
let amount_delta =
i64::try_from(amount_delta).map_err(|_| "profile.wallet_amount 超出上限".to_string())?;
apply_profile_wallet_signed_delta(
ctx,
user_id,
amount_delta,
source_type,
ledger_id,
created_at,
false,
)
}
fn apply_profile_wallet_adjustment(
ctx: &ReducerContext,
input: RuntimeProfileWalletAdjustmentInput,
source_type: RuntimeProfileWalletLedgerSourceType,
consume: bool,
) -> Result<RuntimeProfileDashboardSnapshot, String> {
let validated_input = build_runtime_profile_wallet_adjustment_input(
input.user_id,
input.amount,
input.ledger_id,
input.created_at_micros,
)
.map_err(|error| error.to_string())?;
let created_at = Timestamp::from_micros_since_unix_epoch(validated_input.created_at_micros);
let amount_delta = if consume {
-(validated_input.amount as i64)
} else {
validated_input.amount as i64
};
apply_profile_wallet_signed_delta(
ctx,
&validated_input.user_id,
amount_delta,
source_type,
&validated_input.ledger_id,
created_at,
true,
)?;
get_profile_dashboard_snapshot(
ctx,
RuntimeProfileDashboardGetInput {
user_id: validated_input.user_id,
},
)
}
fn apply_profile_wallet_signed_delta(
ctx: &ReducerContext,
user_id: &str,
amount_delta: i64,
source_type: RuntimeProfileWalletLedgerSourceType,
ledger_id: &str,
created_at: Timestamp,
idempotent: bool,
) -> Result<u64, String> {
if idempotent
&& ctx
.db
.profile_wallet_ledger()
.wallet_ledger_id()
.find(&ledger_id.to_string())
.is_some()
{
return Ok(profile_wallet_balance(ctx, user_id));
}
let current = ctx
.db
.profile_dashboard_state()
.user_id()
.find(&user_id.to_string());
let previous_balance = current.as_ref().map(|row| row.wallet_balance).unwrap_or(0);
let next_balance = previous_balance
.checked_add(amount_delta)
.ok_or_else(|| "profile.wallet_balance 超出上限".to_string())?;
let next_balance = if amount_delta >= 0 {
previous_balance
.checked_add(amount_delta as u64)
.ok_or_else(|| "profile.wallet_balance 超出上限".to_string())?
} else {
previous_balance
.checked_sub(amount_delta.unsigned_abs())
.ok_or_else(|| "叙世币余额不足".to_string())?
};
let created_state_at = current
.as_ref()
.map(|row| row.created_at)
@@ -1413,7 +1543,7 @@ fn apply_profile_wallet_delta(
ctx.db.profile_wallet_ledger().insert(ProfileWalletLedger {
wallet_ledger_id: ledger_id.to_string(),
user_id: user_id.to_string(),
amount_delta: amount_delta as i64,
amount_delta,
balance_after: next_balance,
source_type,
created_at,