1
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
use crate::*;
|
||||
|
||||
const PUBLIC_WORK_PLAY_DAY_MICROS: i64 = 86_400_000_000;
|
||||
const PUBLIC_WORK_RECENT_PLAY_WINDOW_DAYS: i64 = 7;
|
||||
|
||||
#[spacetimedb::table(accessor = profile_dashboard_state)]
|
||||
pub struct ProfileDashboardState {
|
||||
#[primary_key]
|
||||
@@ -116,6 +119,26 @@ pub struct ProfilePlayedWorld {
|
||||
pub(crate) last_observed_play_time_ms: u64,
|
||||
}
|
||||
|
||||
#[spacetimedb::table(
|
||||
accessor = public_work_play_daily_stat,
|
||||
index(
|
||||
accessor = by_public_work_play_daily_stat_work_day,
|
||||
btree(columns = [source_type, profile_id, played_day])
|
||||
)
|
||||
)]
|
||||
pub struct PublicWorkPlayDailyStat {
|
||||
#[primary_key]
|
||||
pub(crate) stat_id: String,
|
||||
// 中文注释:source_type 区分 custom-world / puzzle / big-fish,避免不同玩法 profile_id 撞桶。
|
||||
pub(crate) source_type: String,
|
||||
pub(crate) owner_user_id: String,
|
||||
pub(crate) profile_id: String,
|
||||
// 中文注释:UTC 自 Unix 纪元起的自然日桶,用于快速聚合近 7 日新增游玩次数。
|
||||
pub(crate) played_day: i64,
|
||||
pub(crate) play_count: u32,
|
||||
pub(crate) updated_at: Timestamp,
|
||||
}
|
||||
|
||||
pub(crate) struct ProfilePlayedWorkUpsertInput {
|
||||
pub(crate) user_id: String,
|
||||
pub(crate) world_key: String,
|
||||
@@ -127,6 +150,13 @@ pub(crate) struct ProfilePlayedWorkUpsertInput {
|
||||
pub(crate) played_at_micros: i64,
|
||||
}
|
||||
|
||||
pub(crate) struct PublicWorkPlayRecordInput {
|
||||
pub(crate) source_type: String,
|
||||
pub(crate) owner_user_id: String,
|
||||
pub(crate) profile_id: String,
|
||||
pub(crate) played_at_micros: i64,
|
||||
}
|
||||
|
||||
#[spacetimedb::table(accessor = profile_membership)]
|
||||
pub struct ProfileMembership {
|
||||
#[primary_key]
|
||||
@@ -420,7 +450,7 @@ pub fn get_profile_referral_invite_center(
|
||||
}
|
||||
}
|
||||
|
||||
// 填码绑定、每日邀请者奖励上限和双方叙世币发放都在同一事务内完成。
|
||||
// 填码绑定、每日邀请者奖励上限和双方陶泥币发放都在同一事务内完成。
|
||||
#[spacetimedb::procedure]
|
||||
pub fn redeem_profile_referral_invite_code(
|
||||
ctx: &mut ProcedureContext,
|
||||
@@ -705,6 +735,88 @@ pub(crate) fn add_profile_observed_play_time(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn record_public_work_play(
|
||||
ctx: &ReducerContext,
|
||||
input: PublicWorkPlayRecordInput,
|
||||
) -> Result<(), String> {
|
||||
let source_type = input.source_type.trim();
|
||||
let owner_user_id = input.owner_user_id.trim();
|
||||
let profile_id = input.profile_id.trim();
|
||||
if source_type.is_empty() || owner_user_id.is_empty() || profile_id.is_empty() {
|
||||
return Err("public_work_play_daily_stat 参数不能为空".to_string());
|
||||
}
|
||||
|
||||
let played_day = public_work_play_day_from_micros(input.played_at_micros);
|
||||
let stat_id = build_public_work_play_daily_stat_id(source_type, profile_id, played_day);
|
||||
let updated_at = Timestamp::from_micros_since_unix_epoch(input.played_at_micros);
|
||||
let next_count = ctx
|
||||
.db
|
||||
.public_work_play_daily_stat()
|
||||
.stat_id()
|
||||
.find(&stat_id)
|
||||
.map(|existing| {
|
||||
ctx.db
|
||||
.public_work_play_daily_stat()
|
||||
.stat_id()
|
||||
.delete(&existing.stat_id);
|
||||
existing.play_count.saturating_add(1)
|
||||
})
|
||||
.unwrap_or(1);
|
||||
|
||||
ctx.db
|
||||
.public_work_play_daily_stat()
|
||||
.insert(PublicWorkPlayDailyStat {
|
||||
stat_id,
|
||||
source_type: source_type.to_string(),
|
||||
owner_user_id: owner_user_id.to_string(),
|
||||
profile_id: profile_id.to_string(),
|
||||
played_day,
|
||||
play_count: next_count,
|
||||
updated_at,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn count_recent_public_work_plays(
|
||||
ctx: &ReducerContext,
|
||||
source_type: &str,
|
||||
profile_id: &str,
|
||||
now_micros: i64,
|
||||
) -> u32 {
|
||||
let source_type = source_type.trim();
|
||||
let profile_id = profile_id.trim();
|
||||
if source_type.is_empty() || profile_id.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let current_day = public_work_play_day_from_micros(now_micros);
|
||||
let first_day = current_day.saturating_sub(PUBLIC_WORK_RECENT_PLAY_WINDOW_DAYS - 1);
|
||||
|
||||
ctx.db
|
||||
.public_work_play_daily_stat()
|
||||
.iter()
|
||||
.filter(|row| {
|
||||
row.source_type == source_type
|
||||
&& row.profile_id == profile_id
|
||||
&& row.played_day >= first_day
|
||||
&& row.played_day <= current_day
|
||||
})
|
||||
.fold(0u32, |total, row| total.saturating_add(row.play_count))
|
||||
}
|
||||
|
||||
fn public_work_play_day_from_micros(value: i64) -> i64 {
|
||||
value.div_euclid(PUBLIC_WORK_PLAY_DAY_MICROS)
|
||||
}
|
||||
|
||||
fn build_public_work_play_daily_stat_id(
|
||||
source_type: &str,
|
||||
profile_id: &str,
|
||||
played_day: i64,
|
||||
) -> String {
|
||||
format!("{source_type}:{profile_id}:{played_day}")
|
||||
}
|
||||
|
||||
fn ensure_profile_dashboard_state(ctx: &ReducerContext, user_id: &str, updated_at: Timestamp) {
|
||||
if ctx
|
||||
.db
|
||||
@@ -1954,7 +2066,7 @@ fn apply_profile_wallet_signed_delta(
|
||||
} else {
|
||||
previous_balance
|
||||
.checked_sub(amount_delta.unsigned_abs())
|
||||
.ok_or_else(|| "叙世币余额不足".to_string())?
|
||||
.ok_or_else(|| "陶泥币余额不足".to_string())?
|
||||
};
|
||||
let created_state_at = current
|
||||
.as_ref()
|
||||
|
||||
Reference in New Issue
Block a user