2026-07-11 02:26:23 +08:00

941 lines
33 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 { apiRequest } from "@/shared/api/request";
import { API_ENDPOINTS, API_OPERATIONS, apiEndpointPath } from "@/shared/api/generated/endpoints";
import type {
AdminJobDto,
ApiPage,
AppUserBanPayload,
AppUserBanRecordDto,
AppUserExportJobDto,
AppUserLevelAdjustmentPayload,
AppUserLoginLogDto,
AppUserDto,
AppUserPasswordPayload,
AppUserUnbanPayload,
AppUserUpdatePayload,
AdminGrantPrettyDisplayIDResultDto,
EntityId,
GeneratePrettyDisplayIDsPayload,
GrantPrettyDisplayIDPayload,
PageQuery,
PrettyDisplayIDDto,
PrettyDisplayIDGenerationBatchDto,
PrettyDisplayIDPoolDto,
PrettyDisplayIDPoolPayload,
RecyclePrettyDisplayIDPayload,
SetPrettyDisplayIDStatusPayload,
} from "@/shared/api/types";
export async function listAppUsers(query: PageQuery = {}): Promise<ApiPage<AppUserDto>> {
const endpoint = API_ENDPOINTS.appListUsers;
const data = await apiRequest<ApiPage<RawAppUser> & { page_size?: number }>(
apiEndpointPath(API_OPERATIONS.appListUsers),
{
method: endpoint.method,
query,
},
);
return normalizePage(data, normalizeAppUser);
}
export async function getAppUser(userId: EntityId): Promise<AppUserDto> {
const endpoint = API_ENDPOINTS.appGetUser;
const data = await apiRequest<RawAppUser>(apiEndpointPath(API_OPERATIONS.appGetUser, { id: userId }), {
method: endpoint.method,
});
return normalizeAppUser(data);
}
export async function listAppUserLoginLogs(query: PageQuery = {}): Promise<ApiPage<AppUserLoginLogDto>> {
const endpoint = API_ENDPOINTS.appListLoginLogs;
const data = await apiRequest<ApiPage<RawAppUserLoginLog> & { page_size?: number }>(
apiEndpointPath(API_OPERATIONS.appListLoginLogs),
{
method: endpoint.method,
query,
},
);
return normalizePage(data, normalizeAppUserLoginLog);
}
export async function listAppUserBans(query: PageQuery = {}): Promise<ApiPage<AppUserBanRecordDto>> {
const endpoint = API_ENDPOINTS.appListBannedUsers;
const data = await apiRequest<ApiPage<RawAppUserBanRecord> & { page_size?: number }>(
apiEndpointPath(API_OPERATIONS.appListBannedUsers),
{
method: endpoint.method,
query,
},
);
return normalizePage(data, normalizeAppUserBanRecord);
}
export async function updateAppUser(userId: EntityId, payload: AppUserUpdatePayload): Promise<AppUserDto> {
const endpoint = API_ENDPOINTS.appUpdateUser;
const data = await apiRequest<RawAppUser, AppUserUpdatePayload>(
apiEndpointPath(API_OPERATIONS.appUpdateUser, { id: userId }),
{
body: payload,
method: endpoint.method,
},
);
return normalizeAppUser(data);
}
export async function adjustAppUserLevels(
userId: EntityId,
payload: AppUserLevelAdjustmentPayload,
): Promise<AppUserDto> {
const endpoint = API_ENDPOINTS.appAdjustUserLevels;
const data = await apiRequest<RawAppUser, AppUserLevelAdjustmentPayload>(
apiEndpointPath(API_OPERATIONS.appAdjustUserLevels, { id: userId }),
{ body: payload, method: endpoint.method },
);
return normalizeAppUser(data);
}
export async function banAppUser(userId: EntityId, payload: AppUserBanPayload): Promise<AppUserDto> {
const endpoint = API_ENDPOINTS.appBanUser;
const data = await apiRequest<RawAppUserStatusMutation, AppUserBanPayload>(
apiEndpointPath(API_OPERATIONS.appBanUser, { id: userId }),
{ body: payload, method: endpoint.method },
);
// 新接口返回 SetUserStatusResult兼容旧环境直接返回 AppUser避免滚动发布期间列表补丁丢失 userId。
return normalizeAppUser(data.user ?? data);
}
export async function unbanAppUser(userId: EntityId, payload: AppUserUnbanPayload = {}): Promise<AppUserDto> {
const endpoint = API_ENDPOINTS.appUnbanUser;
const data = await apiRequest<RawAppUserStatusMutation, AppUserUnbanPayload>(
apiEndpointPath(API_OPERATIONS.appUnbanUser, { id: userId }),
{ body: payload, method: endpoint.method },
);
return normalizeAppUser(data.user ?? data);
}
export function createAppUserExport(query: PageQuery = {}): Promise<AppUserExportJobDto> {
const endpoint = API_ENDPOINTS.appExportUsers;
return apiRequest<AppUserExportJobDto>(apiEndpointPath(API_OPERATIONS.appExportUsers), {
method: endpoint.method,
query,
});
}
export function getAppUserExportJob(jobId: EntityId): Promise<AdminJobDto> {
const endpoint = API_ENDPOINTS.getJob;
return apiRequest<AdminJobDto>(apiEndpointPath(API_OPERATIONS.getJob, { id: jobId }), {
method: endpoint.method,
});
}
export function downloadAppUserExport(jobId: EntityId): Promise<Response> {
return apiRequest(apiEndpointPath(API_OPERATIONS.downloadJobArtifact, { id: jobId }), { raw: true });
}
export function setAppUserPassword(
userId: EntityId,
payload: AppUserPasswordPayload,
): Promise<{ passwordSet: boolean }> {
const endpoint = API_ENDPOINTS.appSetPassword;
return apiRequest<{ passwordSet: boolean }, AppUserPasswordPayload>(
apiEndpointPath(API_OPERATIONS.appSetPassword, { id: userId }),
{
body: payload,
method: endpoint.method,
},
);
}
// 靓号管理接口路径全部来自生成的 OpenAPI 端点,页面层只依赖这些函数,避免手写 URL 和契约漂移。
export async function listPrettyDisplayIDPools(query: PageQuery = {}): Promise<ApiPage<PrettyDisplayIDPoolDto>> {
const endpoint = API_ENDPOINTS.listPrettyIdPools;
const data = await apiRequest<ApiPage<RawPrettyDisplayIDPool>>(apiEndpointPath(API_OPERATIONS.listPrettyIdPools), {
method: endpoint.method,
query,
});
return normalizePage(data, normalizePrettyDisplayIDPool);
}
export async function createPrettyDisplayIDPool(payload: PrettyDisplayIDPoolPayload): Promise<PrettyDisplayIDPoolDto> {
const endpoint = API_ENDPOINTS.createPrettyIdPool;
const data = await apiRequest<RawPrettyDisplayIDPool, PrettyDisplayIDPoolPayload>(
apiEndpointPath(API_OPERATIONS.createPrettyIdPool),
{
body: payload,
method: endpoint.method,
},
);
return normalizePrettyDisplayIDPool(data);
}
export async function updatePrettyDisplayIDPool(
poolId: EntityId,
payload: PrettyDisplayIDPoolPayload,
): Promise<PrettyDisplayIDPoolDto> {
const endpoint = API_ENDPOINTS.updatePrettyIdPool;
const data = await apiRequest<RawPrettyDisplayIDPool, PrettyDisplayIDPoolPayload>(
apiEndpointPath(API_OPERATIONS.updatePrettyIdPool, { pool_id: poolId }),
{
body: payload,
method: endpoint.method,
},
);
return normalizePrettyDisplayIDPool(data);
}
export async function generatePrettyDisplayIDs(
poolId: EntityId,
payload: GeneratePrettyDisplayIDsPayload,
): Promise<PrettyDisplayIDGenerationBatchDto> {
const endpoint = API_ENDPOINTS.generatePrettyIds;
const data = await apiRequest<RawPrettyDisplayIDGenerationBatch, GeneratePrettyDisplayIDsPayload>(
apiEndpointPath(API_OPERATIONS.generatePrettyIds, { pool_id: poolId }),
{
body: payload,
method: endpoint.method,
},
);
return normalizePrettyDisplayIDGenerationBatch(data);
}
export async function listPrettyDisplayIDs(query: PageQuery = {}): Promise<ApiPage<PrettyDisplayIDDto>> {
const endpoint = API_ENDPOINTS.listPrettyIds;
const data = await apiRequest<ApiPage<RawPrettyDisplayID>>(apiEndpointPath(API_OPERATIONS.listPrettyIds), {
method: endpoint.method,
query,
});
return normalizePage(data, normalizePrettyDisplayID);
}
export async function grantPrettyDisplayID(
payload: GrantPrettyDisplayIDPayload,
): Promise<AdminGrantPrettyDisplayIDResultDto> {
const endpoint = API_ENDPOINTS.grantPrettyId;
const data = await apiRequest<RawAdminGrantPrettyDisplayIDResult, GrantPrettyDisplayIDPayload>(
apiEndpointPath(API_OPERATIONS.grantPrettyId),
{
body: payload,
method: endpoint.method,
},
);
return normalizeAdminGrantPrettyDisplayIDResult(data);
}
export async function recyclePrettyDisplayID(
prettyId: EntityId,
payload: RecyclePrettyDisplayIDPayload = {},
): Promise<PrettyDisplayIDDto> {
const endpoint = API_ENDPOINTS.recyclePrettyId;
const data = await apiRequest<RawPrettyDisplayID, RecyclePrettyDisplayIDPayload>(
apiEndpointPath(API_OPERATIONS.recyclePrettyId, { pretty_id: prettyId }),
{
body: payload,
method: endpoint.method,
},
);
return normalizePrettyDisplayID(data);
}
export async function setPrettyDisplayIDStatus(
prettyId: EntityId,
payload: SetPrettyDisplayIDStatusPayload,
): Promise<PrettyDisplayIDDto> {
const endpoint = API_ENDPOINTS.setPrettyIdStatus;
const data = await apiRequest<RawPrettyDisplayID, SetPrettyDisplayIDStatusPayload>(
apiEndpointPath(API_OPERATIONS.setPrettyIdStatus, { pretty_id: prettyId }),
{
body: payload,
method: endpoint.method,
},
);
return normalizePrettyDisplayID(data);
}
// 用户相关接口历史上既返回过 snake_case也返回过 camelCase在 API 边界归一化后,共享用户组件只消费稳定字段。
interface RawAppUser {
age?: number | null;
avatar?: string;
balances?: RawAppUserAssetBalance[];
ban?: RawAppUserBan;
birth?: string;
coin?: number;
country?: string;
country_display_name?: string;
countryDisplayName?: string;
country_name?: string;
countryName?: string;
created_at_ms?: number;
createdAtMs?: number;
default_display_user_id?: string;
defaultDisplayUserId?: string;
diamond?: number;
display_user_id?: string;
displayUserId?: string;
equipped_resources?: RawAppUserResource[];
equippedResources?: RawAppUserResource[];
gender?: string;
last_active_at_ms?: number;
lastActiveAtMs?: number;
last_login_at_ms?: number;
lastLoginAtMs?: number;
last_operated_at_ms?: number;
lastOperatedAtMs?: number;
last_operator?: RawAppUserOperator;
lastOperator?: RawAppUserOperator;
levels?: RawAppUserLevels;
pretty_display_user_id?: string;
prettyDisplayUserId?: string;
pretty_id?: string;
prettyId?: string;
region_id?: number;
regionId?: number;
region_name?: string;
regionName?: string;
register_device?: string;
registerDevice?: string;
resources?: RawAppUserResource[];
roles?: string[];
status?: string;
updated_at_ms?: number;
updatedAtMs?: number;
user_id?: EntityId;
userId?: EntityId;
username?: string;
vip?: RawAppUserVIP;
cumulative_recharge_usd_minor?: number;
cumulativeRechargeUsdMinor?: number;
}
interface RawAppUserStatusMutation extends RawAppUser {
user?: RawAppUser;
}
interface RawAppUserLevel {
display_level?: number;
displayLevel?: number;
display_value?: number;
displayValue?: number;
expires_at_ms?: number;
expiresAtMs?: number;
real_level?: number;
realLevel?: number;
real_total_value?: number;
realTotalValue?: number;
started_at_ms?: number;
startedAtMs?: number;
temporary_level_id?: string;
temporaryLevelId?: string;
temporary_target_level?: number;
temporaryTargetLevel?: number;
track?: string;
}
interface RawAppUserLevels {
charm?: RawAppUserLevel;
game?: RawAppUserLevel;
wealth?: RawAppUserLevel;
}
interface RawAppUserBan {
active?: boolean;
expires_at_ms?: number;
expiresAtMs?: number;
id?: string;
permanent?: boolean;
reason?: string;
source?: string;
}
interface RawAppUserOperator {
action?: string;
admin_id?: EntityId;
adminId?: EntityId;
name?: string;
}
type RawAppUserBrief = RawAppUser;
interface RawAppUserAssetBalance {
asset_type?: string;
assetType?: string;
available_amount?: number;
availableAmount?: number;
frozen_amount?: number;
frozenAmount?: number;
total_amount?: number;
totalAmount?: number;
updated_at_ms?: number;
updatedAtMs?: number;
version?: number;
}
interface RawAppUserVIP {
active?: boolean;
expires_at_ms?: number;
expiresAtMs?: number;
level?: number;
name?: string;
started_at_ms?: number;
startedAtMs?: number;
updated_at_ms?: number;
updatedAtMs?: number;
}
interface RawAppUserResource {
animation_url?: string;
animationUrl?: string;
asset_url?: string;
assetUrl?: string;
created_at_ms?: number;
createdAtMs?: number;
effective_at_ms?: number;
effectiveAtMs?: number;
entitlement_id?: string;
entitlementId?: string;
equipped?: boolean;
expires_at_ms?: number;
expiresAtMs?: number;
name?: string;
preview_url?: string;
previewUrl?: string;
quantity?: number;
remaining_quantity?: number;
remainingQuantity?: number;
resource_code?: string;
resourceCode?: string;
resource_id?: number;
resourceId?: number;
resource_type?: string;
resourceType?: string;
source_grant_id?: string;
sourceGrantId?: string;
status?: string;
updated_at_ms?: number;
updatedAtMs?: number;
}
interface RawAppUserBanOperator extends RawAppUserBrief {
account?: string;
admin_id?: EntityId;
adminId?: EntityId;
name?: string;
type?: string;
}
interface RawAppUserBanRecord {
created_at_ms?: number;
createdAtMs?: number;
id?: number;
new_status?: string;
newStatus?: string;
old_status?: string;
oldStatus?: string;
operator?: RawAppUserBanOperator | null;
reason?: string;
request_id?: string;
requestId?: string;
target?: RawAppUserBrief;
}
interface RawAppUserLoginLog extends RawAppUserBrief {
block_reason?: string;
blockReason?: string;
blocked?: boolean;
channel?: string;
country?: string;
created_at_ms?: number;
createdAtMs?: number;
failure_code?: string;
failureCode?: string;
id?: number;
ip_country_code?: string;
ipCountryCode?: string;
login_ip?: string;
loginIp?: string;
login_type?: string;
loginType?: string;
platform?: string;
provider?: string;
region_id?: number;
regionId?: number;
region_name?: string;
regionName?: string;
request_id?: string;
requestId?: string;
result?: string;
risk_highlighted?: boolean;
riskHighlighted?: boolean;
}
// Go 后台当前按 snake_case JSON 返回,生成类型和页面代码使用 camelCaseRaw 类型同时接收两种形态,便于后续切换生成客户端。
interface RawPrettyDisplayIDPool {
app_code?: string;
appCode?: string;
created_at_ms?: number;
createdAtMs?: number;
created_by_admin_id?: EntityId;
createdByAdminId?: EntityId;
level_track?: string;
levelTrack?: string;
max_level?: number;
maxLevel?: number;
min_level?: number;
minLevel?: number;
name?: string;
pool_id?: string;
poolId?: string;
rule_config_json?: string;
ruleConfigJson?: string;
rule_type?: string;
ruleType?: string;
sort_order?: number;
sortOrder?: number;
status?: string;
updated_at_ms?: number;
updatedAtMs?: number;
updated_by_admin_id?: EntityId;
updatedByAdminId?: EntityId;
}
interface RawPrettyDisplayID {
app_code?: string;
appCode?: string;
assigned_at_ms?: number;
assignedAtMs?: number;
assigned_lease_id?: string;
assignedLeaseId?: string;
assigned_user?: RawAppUserBrief;
assignedUser?: RawAppUserBrief;
assigned_user_id?: EntityId;
assignedUserId?: EntityId;
created_at_ms?: number;
createdAtMs?: number;
created_by_admin_id?: EntityId;
createdByAdminId?: EntityId;
display_user_id?: string;
displayUserId?: string;
generated_batch_id?: string;
generatedBatchId?: string;
operator?: RawPrettyDisplayIDOperator | null;
pool?: RawPrettyDisplayIDPool | null;
pool_id?: string;
poolId?: string;
pretty_id?: string;
prettyId?: string;
release_reason?: string;
releaseReason?: string;
released_at_ms?: number;
releasedAtMs?: number;
source?: string;
status?: string;
updated_at_ms?: number;
updatedAtMs?: number;
updated_by_admin_id?: EntityId;
updatedByAdminId?: EntityId;
}
interface RawPrettyDisplayIDOperator extends RawAppUserBrief {
account?: string;
admin_id?: EntityId;
adminId?: EntityId;
name?: string;
source?: string;
type?: string;
}
interface RawPrettyDisplayIDGenerationBatch {
app_code?: string;
appCode?: string;
batch_id?: string;
batchId?: string;
created_at_ms?: number;
createdAtMs?: number;
generated_count?: number;
generatedCount?: number;
operator_admin_id?: EntityId;
operatorAdminId?: EntityId;
pool_id?: string;
poolId?: string;
request_id?: string;
requestId?: string;
requested_count?: number;
requestedCount?: number;
rule_config_json?: string;
ruleConfigJson?: string;
rule_type?: string;
ruleType?: string;
skipped_conflict_count?: number;
skippedConflictCount?: number;
status?: string;
updated_at_ms?: number;
updatedAtMs?: number;
}
interface RawAdminGrantPrettyDisplayIDResult {
identity?: {
default_display_user_id?: string;
defaultDisplayUserId?: string;
display_user_id?: string;
displayUserId?: string;
pretty_display_user_id?: string;
prettyDisplayUserId?: string;
pretty_id?: string;
prettyId?: string;
user_id?: EntityId;
userId?: EntityId;
};
lease_id?: string;
leaseId?: string;
pretty_id?: string;
prettyId?: string;
}
// 分页结构也做一次兼容page_size 来自 Go 响应pageSize 来自前端 ApiPage 约定。
function normalizePage<TRaw, TItem>(
data: ApiPage<TRaw> & { page_size?: number },
normalize: (item: TRaw) => TItem,
): ApiPage<TItem> {
return {
items: (data.items || []).map(normalize),
page: Number(data.page || 1),
pageSize: Number(data.pageSize || data.page_size || 20),
total: Number(data.total || 0),
};
}
function normalizeAppUser(item: RawAppUser = {}): AppUserDto {
return {
age: optionalNumberValue(item.age),
avatar: stringValue(item.avatar),
balances: (item.balances || []).map(normalizeAppUserAssetBalance),
ban: normalizeAppUserBan(item.ban),
birth: stringValue(item.birth),
coin: numberValue(item.coin),
country: stringValue(item.country),
countryDisplayName: stringValue(item.countryDisplayName ?? item.country_display_name),
countryName: stringValue(item.countryName ?? item.country_name),
createdAtMs: numberValue(item.createdAtMs ?? item.created_at_ms),
defaultDisplayUserId: stringValue(item.defaultDisplayUserId ?? item.default_display_user_id),
diamond: numberValue(item.diamond),
displayUserId: stringValue(item.displayUserId ?? item.display_user_id),
equippedResources: (item.equippedResources ?? item.equipped_resources ?? []).map(normalizeAppUserResource),
gender: stringValue(item.gender),
lastActiveAtMs: numberValue(item.lastActiveAtMs ?? item.last_active_at_ms),
lastLoginAtMs: numberValue(item.lastLoginAtMs ?? item.last_login_at_ms),
lastOperatedAtMs: numberValue(item.lastOperatedAtMs ?? item.last_operated_at_ms),
lastOperator: normalizeAppUserOperator(item.lastOperator ?? item.last_operator),
levels: normalizeAppUserLevels(item.levels),
prettyDisplayUserId: stringValue(item.prettyDisplayUserId ?? item.pretty_display_user_id),
prettyId: stringValue(item.prettyId ?? item.pretty_id),
regionId: numberValue(item.regionId ?? item.region_id),
regionName: stringValue(item.regionName ?? item.region_name),
registerDevice: stringValue(item.registerDevice ?? item.register_device),
resources: (item.resources || []).map(normalizeAppUserResource),
roles: Array.isArray(item.roles) ? item.roles.map(stringValue).filter(Boolean) : [],
status: stringValue(item.status),
updatedAtMs: numberValue(item.updatedAtMs ?? item.updated_at_ms),
userId: stringValue(item.userId ?? item.user_id),
username: stringValue(item.username),
vip: normalizeAppUserVIP(item.vip),
cumulativeRechargeUsdMinor: numberValue(
item.cumulativeRechargeUsdMinor ?? item.cumulative_recharge_usd_minor,
),
};
}
function normalizeAppUserLevels(item: RawAppUserLevels = {}) {
return {
charm: normalizeAppUserLevel(item.charm, "charm"),
game: normalizeAppUserLevel(item.game, "game"),
wealth: normalizeAppUserLevel(item.wealth, "wealth"),
};
}
function normalizeAppUserLevel(item: RawAppUserLevel = {}, fallbackTrack: string) {
return {
displayLevel: numberValue(item.displayLevel ?? item.display_level),
displayValue: numberValue(item.displayValue ?? item.display_value),
expiresAtMs: numberValue(item.expiresAtMs ?? item.expires_at_ms),
realLevel: numberValue(item.realLevel ?? item.real_level),
realTotalValue: numberValue(item.realTotalValue ?? item.real_total_value),
startedAtMs: numberValue(item.startedAtMs ?? item.started_at_ms),
temporaryLevelId: stringValue(item.temporaryLevelId ?? item.temporary_level_id),
temporaryTargetLevel: numberValue(item.temporaryTargetLevel ?? item.temporary_target_level),
track: stringValue(item.track || fallbackTrack),
};
}
function normalizeAppUserBan(item: RawAppUserBan = {}) {
return {
active: Boolean(item.active),
expiresAtMs: numberValue(item.expiresAtMs ?? item.expires_at_ms),
id: stringValue(item.id),
permanent: Boolean(item.permanent),
reason: stringValue(item.reason),
source: stringValue(item.source),
};
}
function normalizeAppUserOperator(item?: RawAppUserOperator) {
if (!item) {
return undefined;
}
return {
action: stringValue(item.action),
adminId: stringValue(item.adminId ?? item.admin_id),
name: stringValue(item.name),
};
}
function normalizeAppUserAssetBalance(item: RawAppUserAssetBalance = {}) {
const availableAmount = numberValue(item.availableAmount ?? item.available_amount);
const frozenAmount = numberValue(item.frozenAmount ?? item.frozen_amount);
return {
assetType: stringValue(item.assetType ?? item.asset_type).toUpperCase(),
availableAmount,
frozenAmount,
totalAmount: numberValue(item.totalAmount ?? item.total_amount ?? availableAmount + frozenAmount),
updatedAtMs: numberValue(item.updatedAtMs ?? item.updated_at_ms),
version: numberValue(item.version),
};
}
function normalizeAppUserVIP(item: RawAppUserVIP = {}) {
return {
active: Boolean(item.active),
expiresAtMs: numberValue(item.expiresAtMs ?? item.expires_at_ms),
level: numberValue(item.level),
name: stringValue(item.name),
startedAtMs: numberValue(item.startedAtMs ?? item.started_at_ms),
updatedAtMs: numberValue(item.updatedAtMs ?? item.updated_at_ms),
};
}
function normalizeAppUserResource(item: RawAppUserResource = {}) {
return {
animationUrl: stringValue(item.animationUrl ?? item.animation_url),
assetUrl: stringValue(item.assetUrl ?? item.asset_url),
createdAtMs: numberValue(item.createdAtMs ?? item.created_at_ms),
effectiveAtMs: numberValue(item.effectiveAtMs ?? item.effective_at_ms),
entitlementId: stringValue(item.entitlementId ?? item.entitlement_id),
equipped: Boolean(item.equipped),
expiresAtMs: numberValue(item.expiresAtMs ?? item.expires_at_ms),
name: stringValue(item.name),
previewUrl: stringValue(item.previewUrl ?? item.preview_url),
quantity: numberValue(item.quantity),
remainingQuantity: numberValue(item.remainingQuantity ?? item.remaining_quantity),
resourceCode: stringValue(item.resourceCode ?? item.resource_code),
resourceId: numberValue(item.resourceId ?? item.resource_id),
resourceType: stringValue(item.resourceType ?? item.resource_type),
sourceGrantId: stringValue(item.sourceGrantId ?? item.source_grant_id),
status: stringValue(item.status),
updatedAtMs: numberValue(item.updatedAtMs ?? item.updated_at_ms),
};
}
function normalizeAppUserBrief(item: RawAppUserBrief = {}) {
const user = normalizeAppUser(item);
return {
avatar: user.avatar,
defaultDisplayUserId: user.defaultDisplayUserId,
displayUserId: user.displayUserId,
prettyDisplayUserId: user.prettyDisplayUserId,
prettyId: user.prettyId,
userId: user.userId,
username: user.username,
};
}
function normalizeAppUserBanOperator(item?: RawAppUserBanOperator | null) {
if (!item) {
return undefined;
}
const user = normalizeAppUserBrief(item);
return {
...user,
account: stringValue(item.account),
adminId: stringValue(item.adminId ?? item.admin_id),
name: stringValue(item.name),
type: stringValue(item.type || "system"),
};
}
function normalizePrettyDisplayIDOperator(item?: RawPrettyDisplayIDOperator | null, fallbackAdminId?: EntityId) {
if (!item) {
const adminId = stringValue(fallbackAdminId);
return adminId
? {
adminId,
name: "后台管理员",
type: "admin",
}
: undefined;
}
const user = normalizeAppUserBrief(item);
const adminId = stringValue(item.adminId ?? item.admin_id ?? fallbackAdminId);
return {
...user,
account: stringValue(item.account),
adminId,
name: stringValue(item.name),
source: stringValue(item.source),
type: stringValue(item.type || (adminId ? "admin" : "app_user")),
};
}
function normalizeOptionalAppUserBrief(item?: RawAppUserBrief | null) {
if (!item) {
return undefined;
}
const user = normalizeAppUserBrief(item);
return nonZeroStringValue(user.userId) || nonZeroStringValue(user.displayUserId) || user.username ? user : undefined;
}
function normalizeAppUserBanRecord(item: RawAppUserBanRecord = {}): AppUserBanRecordDto {
return {
createdAtMs: numberValue(item.createdAtMs ?? item.created_at_ms),
id: numberValue(item.id),
newStatus: stringValue(item.newStatus ?? item.new_status),
oldStatus: stringValue(item.oldStatus ?? item.old_status),
operator: normalizeAppUserBanOperator(item.operator),
reason: stringValue(item.reason),
requestId: stringValue(item.requestId ?? item.request_id),
target: normalizeAppUserBrief(item.target || {}),
};
}
function normalizeAppUserLoginLog(item: RawAppUserLoginLog = {}): AppUserLoginLogDto {
const user = normalizeAppUserBrief(item);
return {
...user,
blockReason: stringValue(item.blockReason ?? item.block_reason),
blocked: Boolean(item.blocked),
channel: stringValue(item.channel),
country: stringValue(item.country),
createdAtMs: numberValue(item.createdAtMs ?? item.created_at_ms),
failureCode: stringValue(item.failureCode ?? item.failure_code),
id: numberValue(item.id),
ipCountryCode: stringValue(item.ipCountryCode ?? item.ip_country_code),
loginIp: stringValue(item.loginIp ?? item.login_ip),
loginType: stringValue(item.loginType ?? item.login_type),
platform: stringValue(item.platform),
provider: stringValue(item.provider),
regionId: numberValue(item.regionId ?? item.region_id),
regionName: stringValue(item.regionName ?? item.region_name),
requestId: stringValue(item.requestId ?? item.request_id),
result: stringValue(item.result),
riskHighlighted: Boolean(item.riskHighlighted ?? item.risk_highlighted),
};
}
// 池 DTO 在这里完成字段名和数值兜底,页面表格不再关心后端是否省略可选字段。
function normalizePrettyDisplayIDPool(item: RawPrettyDisplayIDPool): PrettyDisplayIDPoolDto {
return {
appCode: item.appCode ?? item.app_code ?? "",
createdAtMs: item.createdAtMs ?? item.created_at_ms ?? 0,
createdByAdminId: item.createdByAdminId ?? item.created_by_admin_id ?? "",
levelTrack: item.levelTrack ?? item.level_track ?? "",
maxLevel: Number(item.maxLevel ?? item.max_level ?? 0),
minLevel: Number(item.minLevel ?? item.min_level ?? 0),
name: item.name ?? "",
poolId: item.poolId ?? item.pool_id ?? "",
ruleConfigJson: item.ruleConfigJson ?? item.rule_config_json ?? "",
ruleType: item.ruleType ?? item.rule_type ?? "",
sortOrder: Number(item.sortOrder ?? item.sort_order ?? 0),
status: item.status ?? "",
updatedAtMs: item.updatedAtMs ?? item.updated_at_ms ?? 0,
updatedByAdminId: item.updatedByAdminId ?? item.updated_by_admin_id ?? "",
};
}
function stringValue(value: unknown): string {
return value === undefined || value === null ? "" : String(value).trim();
}
function numberValue(value: unknown): number {
const parsed = Number(value || 0);
return Number.isFinite(parsed) ? parsed : 0;
}
function optionalNumberValue(value: unknown): number | undefined {
if (value === undefined || value === null || value === "") {
return undefined;
}
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : undefined;
}
// 靓号明细包含池号和后台发放两类来源pool 可能为空,归一化后页面只判断 source/status 即可。
function normalizePrettyDisplayID(item: RawPrettyDisplayID): PrettyDisplayIDDto {
const createdByAdminId = item.createdByAdminId ?? item.created_by_admin_id ?? "";
const updatedByAdminId = item.updatedByAdminId ?? item.updated_by_admin_id ?? "";
return {
appCode: item.appCode ?? item.app_code ?? "",
assignedAtMs: item.assignedAtMs ?? item.assigned_at_ms ?? 0,
assignedLeaseId: item.assignedLeaseId ?? item.assigned_lease_id ?? "",
assignedUser: normalizeOptionalAppUserBrief(item.assignedUser ?? item.assigned_user),
assignedUserId: optionalEntityID(item.assignedUserId ?? item.assigned_user_id),
createdAtMs: item.createdAtMs ?? item.created_at_ms ?? 0,
createdByAdminId,
displayUserId: item.displayUserId ?? item.display_user_id ?? "",
generatedBatchId: item.generatedBatchId ?? item.generated_batch_id ?? "",
operator: normalizePrettyDisplayIDOperator(item.operator, updatedByAdminId || createdByAdminId),
pool: item.pool ? normalizePrettyDisplayIDPool(item.pool) : null,
poolId: item.poolId ?? item.pool_id ?? "",
prettyId: item.prettyId ?? item.pretty_id ?? "",
releaseReason: item.releaseReason ?? item.release_reason ?? "",
releasedAtMs: item.releasedAtMs ?? item.released_at_ms ?? 0,
source: item.source ?? "",
status: item.status ?? "",
updatedAtMs: item.updatedAtMs ?? item.updated_at_ms ?? 0,
updatedByAdminId,
};
}
// 生成结果用于展示本次批量生成的真实落库数量和冲突跳过数量,数值统一转换,避免字符串数字影响统计。
function normalizePrettyDisplayIDGenerationBatch(
item: RawPrettyDisplayIDGenerationBatch,
): PrettyDisplayIDGenerationBatchDto {
return {
appCode: item.appCode ?? item.app_code ?? "",
batchId: item.batchId ?? item.batch_id ?? "",
createdAtMs: item.createdAtMs ?? item.created_at_ms ?? 0,
generatedCount: Number(item.generatedCount ?? item.generated_count ?? 0),
operatorAdminId: item.operatorAdminId ?? item.operator_admin_id ?? "",
poolId: item.poolId ?? item.pool_id ?? "",
requestId: item.requestId ?? item.request_id ?? "",
requestedCount: Number(item.requestedCount ?? item.requested_count ?? 0),
ruleConfigJson: item.ruleConfigJson ?? item.rule_config_json ?? "",
ruleType: item.ruleType ?? item.rule_type ?? "",
skippedConflictCount: Number(item.skippedConflictCount ?? item.skipped_conflict_count ?? 0),
status: item.status ?? "",
updatedAtMs: item.updatedAtMs ?? item.updated_at_ms ?? 0,
};
}
// 后台发放返回的是新 lease 和用户当前身份快照identity 归一化后能直接用于测试和后续结果提示。
function normalizeAdminGrantPrettyDisplayIDResult(
item: RawAdminGrantPrettyDisplayIDResult,
): AdminGrantPrettyDisplayIDResultDto {
const identity = item.identity || {};
return {
identity: {
defaultDisplayUserId: identity.defaultDisplayUserId ?? identity.default_display_user_id ?? "",
displayUserId: identity.displayUserId ?? identity.display_user_id ?? "",
prettyDisplayUserId: identity.prettyDisplayUserId ?? identity.pretty_display_user_id ?? "",
prettyId: identity.prettyId ?? identity.pretty_id ?? "",
userId: String(identity.userId ?? identity.user_id ?? ""),
},
leaseId: item.leaseId ?? item.lease_id ?? "",
prettyId: item.prettyId ?? item.pretty_id ?? "",
};
}
function optionalEntityID(value: unknown): string {
return nonZeroStringValue(value);
}
function nonZeroStringValue(value: unknown): string {
const normalized = stringValue(value);
return normalized === "0" ? "" : normalized;
}