expo-duooomi-app/stores/UserBalanceStore.ts

144 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { makeObservable } from 'mobx'
import { AppState, type AppStateStatus } from 'react-native'
import { subscription } from '@/lib/auth'
import { type ApiError } from '@/lib/types'
class UserBalanceStore {
balance: number = 0
loading: boolean = false
error: ApiError | null = null
private lastLoadTime: number = 0
private loadingPromise: Promise<void> | null = null
private pollingInterval: ReturnType<typeof setInterval> | null = null
private pollingEnabled: boolean = false
private POLLING_INTERVAL = 60e3 // 60秒轮询一次
private appStateSubscription: { remove: () => void } | null = null
constructor() {
makeObservable(this)
this.setupAppStateListener()
}
private setupAppStateListener() {
this.appStateSubscription = AppState.addEventListener('change', (nextAppState: AppStateStatus) => {
if (nextAppState === 'active' && this.pollingEnabled) {
console.log('应用回到前台,重启余额轮询')
this.restartPolling()
} else if (nextAppState === 'background' || nextAppState === 'inactive') {
console.log('应用进入后台,暂停余额轮询')
this.stopPolling()
}
})
}
async load(force = false) {
const now = Date.now()
const timeSinceLastLoad = now - this.lastLoadTime
// 如果不是强制刷新且距离上次调用少于5秒则跳过
if (!force && timeSinceLastLoad < 5e3) {
console.log('跳过余额加载,距离上次调用时间过短:', timeSinceLastLoad + 'ms')
return
}
// 如果已经在加载中且不是强制刷新,直接返回
if (this.loading && !force) {
console.log('余额加载中,跳过重复请求')
return this.loadingPromise
}
// console.log('执行余额加载')
this.lastLoadTime = now
this.loadingPromise = this.performLoad()
return this.loadingPromise
}
private async performLoad() {
try {
const { data, error } = await subscription.list()
if (error) {
return
}
const meteredSubscriptions = data?.filter((sub) => sub.type === 'metered') || []
const creditBalance = meteredSubscriptions[0]?.creditBalance?.remainingTokenBalance || 0
this.setBalance(creditBalance)
} catch (e) {
console.error('加载余额失败:', e)
} finally {
this.loadingPromise = null
}
}
// 重置Store状态
reset() {
this.stopPolling() // 停止轮询
this.setBalance(0)
this.lastLoadTime = 0
this.loadingPromise = null
// 清理应用状态监听器
if (this.appStateSubscription) {
this.appStateSubscription.remove()
this.appStateSubscription = null
}
}
// 手动设置余额(用于乐观更新)
setBalance(newBalance: number) {
this.balance = newBalance
}
// 扣减余额(用于乐观更新)
deductBalance(amount: number) {
this.balance = Math.max(0, this.balance - amount)
}
// 开始余额轮询
startPolling() {
// 先清理可能存在的旧轮询状态
this.stopPolling()
console.log('开始余额轮询,间隔:', this.POLLING_INTERVAL / 1000, '秒')
this.pollingEnabled = true
// 立即执行一次
this.load(false)
// 设置定时器
this.pollingInterval = setInterval(() => {
if (this.pollingEnabled) {
this.load(false) // 使用防抖机制
}
}, this.POLLING_INTERVAL)
}
// 停止余额轮询
stopPolling() {
console.log('停止余额轮询')
this.pollingEnabled = false
if (this.pollingInterval) {
clearInterval(this.pollingInterval)
this.pollingInterval = null
}
}
// 重启轮询
restartPolling() {
this.stopPolling()
setTimeout(() => this.startPolling(), 1000) // 延迟1秒重启
}
// 检查轮询状态
get isPolling() {
return this.pollingEnabled && this.pollingInterval !== null
}
}
// 创建单例实例
export const userBalanceStore = new UserBalanceStore()