订单接口最小化与私有文件访问加固
- 订单列表使用独立最小 DTO 并分页,号主待办提供独立接口与统计 - 用户 token 增加版本控制,冻结/改密/退出即时撤销会话 - 移除 URL token 传参,SSE 与接口统一使用 HttpOnly Cookie - 私有文件按上传归属与业务关联授权,收款凭证转私有访问并校验归属 - 公开商品接口返回最小字段,隐藏号主身份与内部状态 - 每日清理超过 30 天未关联业务的上传归属,上传归属失败时补偿删除对象
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { Listing } from '@/features/listings/api/listings'
|
||||
import type { PublicListingItem } from '@/features/listings/api/listings'
|
||||
import { centToYuan } from '@/shared/utils/money'
|
||||
|
||||
export interface ListingDisplayChip {
|
||||
@@ -21,15 +21,15 @@ export interface ListingDisplaySkinGroup {
|
||||
options: string[]
|
||||
}
|
||||
|
||||
export function getCoinWan(item: Listing) {
|
||||
export function getCoinWan(item: PublicListingItem) {
|
||||
return Math.round(Number(item.haf_coin_amount || 0) / 10000)
|
||||
}
|
||||
|
||||
export function getCoinM(item: Listing) {
|
||||
export function getCoinM(item: PublicListingItem) {
|
||||
return getCoinWan(item) / 100
|
||||
}
|
||||
|
||||
export function formatListingCode(item: Listing) {
|
||||
export function formatListingCode(item: PublicListingItem) {
|
||||
return formatListingNo(item.listing_no, item.id)
|
||||
}
|
||||
|
||||
@@ -52,23 +52,23 @@ export function formatAssetNumber(value: number) {
|
||||
return Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1)
|
||||
}
|
||||
|
||||
export function getListingDisplayPrice(item: Listing) {
|
||||
export function getListingDisplayPrice(item: PublicListingItem) {
|
||||
return centToYuan(item.price_cent)
|
||||
}
|
||||
|
||||
export function getListingRentPrice(item: Listing) {
|
||||
export function getListingRentPrice(item: PublicListingItem) {
|
||||
const buyerCoinBasePrice = readPriceBreakdownNumber(item, 'buyer_coin_base_price')
|
||||
if (buyerCoinBasePrice > 0) return roundMoney(buyerCoinBasePrice)
|
||||
return Math.max(0, roundMoney(getListingDisplayPrice(item) - getListingConsumablePrice(item)))
|
||||
}
|
||||
|
||||
export function getListingConsumablePrice(item: Listing) {
|
||||
export function getListingConsumablePrice(item: PublicListingItem) {
|
||||
const consumablePrice = readPriceBreakdownNumber(item, 'consumable_price')
|
||||
if (consumablePrice > 0) return roundMoney(consumablePrice)
|
||||
return getListingResources(item).reduce((sum, resource) => sum + resource.amount, 0)
|
||||
}
|
||||
|
||||
export function getListingSellerPrice(item: Listing) {
|
||||
export function getListingSellerPrice(item: PublicListingItem) {
|
||||
const priceBreakdown = item.asset_summary?.price_breakdown
|
||||
if (typeof priceBreakdown === 'object' && priceBreakdown !== null) {
|
||||
const price = readUnknownNumber((priceBreakdown as Record<string, unknown>).seller_total_price)
|
||||
@@ -77,7 +77,7 @@ export function getListingSellerPrice(item: Listing) {
|
||||
return getListingDisplayPrice(item)
|
||||
}
|
||||
|
||||
export function getRatioValue(item: Listing) {
|
||||
export function getRatioValue(item: PublicListingItem) {
|
||||
const ratio = readAssetNumber(item, 'publish_ratio')
|
||||
if (ratio > 0) return ratio
|
||||
const price = getListingDisplayPrice(item)
|
||||
@@ -85,20 +85,20 @@ export function getRatioValue(item: Listing) {
|
||||
return getCoinWan(item) / price
|
||||
}
|
||||
|
||||
export function formatRatio(item: Listing) {
|
||||
export function formatRatio(item: PublicListingItem) {
|
||||
const ratio = getRatioValue(item)
|
||||
return ratio > 0 ? `1:${formatRatioNumber(ratio)}` : '--'
|
||||
}
|
||||
|
||||
export function getValuePerYuanText(item: Listing) {
|
||||
export function getValuePerYuanText(item: PublicListingItem) {
|
||||
return formatRatio(item)
|
||||
}
|
||||
|
||||
export function getLoginMethod(item: Listing) {
|
||||
export function getLoginMethod(item: PublicListingItem) {
|
||||
return item.login_platform.trim()
|
||||
}
|
||||
|
||||
export function getServerRegion(item: Listing) {
|
||||
export function getServerRegion(item: PublicListingItem) {
|
||||
return item.server_region.trim()
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ export function formatGameName(value: unknown, fallback = '-') {
|
||||
return text
|
||||
}
|
||||
|
||||
export function getListingTitle(item: Listing) {
|
||||
export function getListingTitle(item: PublicListingItem) {
|
||||
const parts = [
|
||||
`纯币${formatHafCoinM(getCoinWan(item))}`,
|
||||
formatInsuranceSlotText(readAssetString(item, 'season_insurance')),
|
||||
@@ -126,11 +126,11 @@ export function getListingTitle(item: Listing) {
|
||||
return parts.join('/')
|
||||
}
|
||||
|
||||
export function getListingSubtitle(item: Listing) {
|
||||
export function getListingSubtitle(item: PublicListingItem) {
|
||||
return getValuePerYuanText(item)
|
||||
}
|
||||
|
||||
export function getListingChips(item: Listing): ListingDisplayChip[] {
|
||||
export function getListingChips(item: PublicListingItem): ListingDisplayChip[] {
|
||||
const totalAsset = readAssetNumber(item, 'total_asset_wan')
|
||||
const chips: ListingDisplayChip[] = [
|
||||
{ label: '哈夫币', value: formatHafCoinM(getCoinWan(item)) },
|
||||
@@ -161,7 +161,7 @@ export function getListingChips(item: Listing): ListingDisplayChip[] {
|
||||
return chips.filter(chip => chip.value)
|
||||
}
|
||||
|
||||
export function getListingResources(item: Listing): ListingDisplayResource[] {
|
||||
export function getListingResources(item: PublicListingItem): ListingDisplayResource[] {
|
||||
const resources = item.asset_summary?.resources
|
||||
if (!Array.isArray(resources)) return []
|
||||
return resources
|
||||
@@ -188,15 +188,15 @@ export function getListingResources(item: Listing): ListingDisplayResource[] {
|
||||
})
|
||||
}
|
||||
|
||||
export function getResourceQuantity(item: Listing, resourceKey: string) {
|
||||
export function getResourceQuantity(item: PublicListingItem, resourceKey: string) {
|
||||
return getListingResources(item).find(resource => resource.key === resourceKey)?.quantity || 0
|
||||
}
|
||||
|
||||
export function hasGiftResources(item: Listing) {
|
||||
export function hasGiftResources(item: PublicListingItem) {
|
||||
return getListingResources(item).some(resource => resource.mode === '赠送')
|
||||
}
|
||||
|
||||
export function hasAcceleratedSaleRatio(item: Listing) {
|
||||
export function hasAcceleratedSaleRatio(item: PublicListingItem) {
|
||||
if (item.is_accelerated_sale) return true
|
||||
|
||||
const priceBreakdown = item.asset_summary?.price_breakdown
|
||||
@@ -211,7 +211,7 @@ export function hasAcceleratedSaleRatio(item: Listing) {
|
||||
return sellerRatio > referenceRatio || acceleratedRatio > referenceRatio
|
||||
}
|
||||
|
||||
export function getSkinGroup(item: Listing, groupKey: string) {
|
||||
export function getSkinGroup(item: PublicListingItem, groupKey: string) {
|
||||
const skinGroups = item.asset_summary?.skin_groups
|
||||
if (
|
||||
typeof skinGroups !== 'object' ||
|
||||
@@ -225,7 +225,7 @@ export function getSkinGroup(item: Listing, groupKey: string) {
|
||||
)
|
||||
}
|
||||
|
||||
export function getListingSkinGroups(item: Listing): ListingDisplaySkinGroup[] {
|
||||
export function getListingSkinGroups(item: PublicListingItem): ListingDisplaySkinGroup[] {
|
||||
const skinGroups = item.asset_summary?.skin_groups
|
||||
if (typeof skinGroups !== 'object' || skinGroups === null) return []
|
||||
const titles: Record<string, string> = {
|
||||
@@ -248,11 +248,11 @@ export function getListingSkinGroups(item: Listing): ListingDisplaySkinGroup[] {
|
||||
.filter(group => group.options.length)
|
||||
}
|
||||
|
||||
export function getSkinNames(item: Listing) {
|
||||
export function getSkinNames(item: PublicListingItem) {
|
||||
return getListingSkinGroups(item).flatMap(group => group.options)
|
||||
}
|
||||
|
||||
export function assetRegions(item: Listing) {
|
||||
export function assetRegions(item: PublicListingItem) {
|
||||
const regions = item.asset_summary?.common_regions
|
||||
return Array.isArray(regions)
|
||||
? regions.filter((region): region is string => typeof region === 'string')
|
||||
@@ -281,7 +281,7 @@ export function formatOnlineTimeRange(start: string, end: string) {
|
||||
return `${startLabel}-${endLabel}点`
|
||||
}
|
||||
|
||||
export function getOnlineTimeText(item: Listing) {
|
||||
export function getOnlineTimeText(item: PublicListingItem) {
|
||||
const onlineTime = item.asset_summary?.online_time
|
||||
if (typeof onlineTime !== 'object' || onlineTime === null) return ''
|
||||
const start = (onlineTime as Record<string, unknown>).start
|
||||
@@ -303,37 +303,37 @@ function formatClockLabel(value: string) {
|
||||
return value.endsWith(':00') ? value.slice(0, -3) : value
|
||||
}
|
||||
|
||||
export function getDailyLoss(item: Listing) {
|
||||
export function getDailyLoss(item: PublicListingItem) {
|
||||
const dailyLossM = getDailyLossM(item)
|
||||
return dailyLossM > 0 ? `${formatCompactNumber(dailyLossM)}M` : ''
|
||||
}
|
||||
|
||||
export function getDailyLossM(item: Listing) {
|
||||
export function getDailyLossM(item: PublicListingItem) {
|
||||
const configuredLoss = readAssetNumber(item, 'daily_loss_m')
|
||||
if (configuredLoss > 0) return configuredLoss
|
||||
return 0
|
||||
}
|
||||
|
||||
export function getEstimatedRentalDays(item: Listing) {
|
||||
export function getEstimatedRentalDays(item: PublicListingItem) {
|
||||
const coinM = getCoinM(item)
|
||||
const dailyLossM = getDailyLossM(item)
|
||||
if (coinM <= 0 || dailyLossM <= 0) return 0
|
||||
return coinM / dailyLossM
|
||||
}
|
||||
|
||||
export function formatEstimatedRentalDuration(item: Listing) {
|
||||
export function formatEstimatedRentalDuration(item: PublicListingItem) {
|
||||
const days = getEstimatedRentalDays(item)
|
||||
if (days <= 0) return '--'
|
||||
// 不足整天按整天向上取整,与后端 estimateOrderDurationHours 一致
|
||||
return `${Math.max(1, Math.ceil(days))}天`
|
||||
}
|
||||
|
||||
export function readAssetString(item: Listing, key: string) {
|
||||
export function readAssetString(item: PublicListingItem, key: string) {
|
||||
const value = item.asset_summary?.[key]
|
||||
return typeof value === 'string' ? value : ''
|
||||
}
|
||||
|
||||
export function readAssetNumber(item: Listing, key: string) {
|
||||
export function readAssetNumber(item: PublicListingItem, key: string) {
|
||||
return readUnknownNumber(item.asset_summary?.[key])
|
||||
}
|
||||
|
||||
@@ -346,7 +346,7 @@ function readUnknownNumber(value: unknown) {
|
||||
return 0
|
||||
}
|
||||
|
||||
function readPriceBreakdownNumber(item: Listing, key: string) {
|
||||
function readPriceBreakdownNumber(item: PublicListingItem, key: string) {
|
||||
const priceBreakdown = item.asset_summary?.price_breakdown
|
||||
if (typeof priceBreakdown !== 'object' || priceBreakdown === null) return 0
|
||||
return readUnknownNumber((priceBreakdown as Record<string, unknown>)[key])
|
||||
@@ -379,7 +379,7 @@ function formatLevelShort(value: string, suffix: string) {
|
||||
return level ? `${level}${suffix}` : value
|
||||
}
|
||||
|
||||
function formatResourceShort(item: Listing, key: string, label: string) {
|
||||
function formatResourceShort(item: PublicListingItem, key: string, label: string) {
|
||||
const quantity = getResourceQuantity(item, key)
|
||||
return quantity > 0 ? `${quantity}${label}` : ''
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user