9c9c8f468a
恢复创作、项目、我的平台侧栏与现役搜索 恢复全宽个人页及资料编辑、充值兑换、社区反馈和 API Key 入口 迁移平台资料、钱包和设置客户端并移除旧创作依赖 新增 Vite 与 ESLint 退役模块门禁并补齐界面和搜索测试 同步旧创作退役方案、平台入口决策与踩坑记录
365 lines
9.2 KiB
TypeScript
365 lines
9.2 KiB
TypeScript
import type {
|
|
ConfirmWechatProfileRechargeOrderResponse,
|
|
CreateProfileRechargeOrderResponse,
|
|
ExternalApiKeyCreateResponse,
|
|
ExternalApiKeyListResponse,
|
|
ExternalApiKeyMutationResponse,
|
|
ProfileDashboardSummary,
|
|
ProfileRechargeCenterResponse,
|
|
ProfileRechargeOrder,
|
|
ProfileReferralInviteCenterResponse,
|
|
ProfileWalletLedgerResponse,
|
|
RedeemProfileReferralInviteCodeResponse,
|
|
RedeemProfileRewardCodeResponse,
|
|
} from '../../../packages/shared/src/contracts/runtime';
|
|
import {
|
|
appendApiErrorRequestId,
|
|
parseApiErrorMessage,
|
|
} from '../../../packages/shared/src/http';
|
|
import {
|
|
type ApiAuthImpact,
|
|
type ApiRetryOptions,
|
|
fetchWithApiAuth,
|
|
requestJson,
|
|
} from '../apiClient';
|
|
import { readSseJsonStream } from '../sseStream';
|
|
|
|
const PLATFORM_PROFILE_API_BASE = '/api/profile';
|
|
const PLATFORM_PROFILE_READ_RETRY: ApiRetryOptions = {
|
|
maxRetries: 1,
|
|
baseDelayMs: 180,
|
|
maxDelayMs: 480,
|
|
};
|
|
const PLATFORM_PROFILE_WRITE_RETRY: ApiRetryOptions = {
|
|
maxRetries: 1,
|
|
baseDelayMs: 240,
|
|
maxDelayMs: 640,
|
|
retryUnsafeMethods: true,
|
|
};
|
|
|
|
export type PlatformProfileRequestOptions = {
|
|
signal?: AbortSignal;
|
|
retry?: ApiRetryOptions;
|
|
skipAuth?: boolean;
|
|
skipRefresh?: boolean;
|
|
authImpact?: ApiAuthImpact;
|
|
notifyAuthStateChange?: boolean;
|
|
clearAuthOnUnauthorized?: boolean;
|
|
};
|
|
|
|
function requestPlatformProfileJson<T>(
|
|
path: string,
|
|
init: RequestInit,
|
|
fallbackMessage: string,
|
|
options: PlatformProfileRequestOptions = {},
|
|
) {
|
|
const method = (init.method ?? 'GET').toUpperCase();
|
|
const retry =
|
|
options.retry ??
|
|
(method === 'GET'
|
|
? PLATFORM_PROFILE_READ_RETRY
|
|
: PLATFORM_PROFILE_WRITE_RETRY);
|
|
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
|
|
|
return requestJson<T>(
|
|
`${PLATFORM_PROFILE_API_BASE}${normalizedPath}`,
|
|
{
|
|
...init,
|
|
signal: options.signal,
|
|
},
|
|
fallbackMessage,
|
|
{
|
|
retry,
|
|
skipAuth: options.skipAuth,
|
|
skipRefresh: options.skipRefresh,
|
|
authImpact: options.authImpact,
|
|
notifyAuthStateChange: options.notifyAuthStateChange,
|
|
clearAuthOnUnauthorized: options.clearAuthOnUnauthorized,
|
|
},
|
|
);
|
|
}
|
|
|
|
export function getPlatformProfileDashboard(
|
|
options: PlatformProfileRequestOptions = {},
|
|
) {
|
|
return requestPlatformProfileJson<ProfileDashboardSummary>(
|
|
'/dashboard',
|
|
{ method: 'GET' },
|
|
'读取个人看板失败',
|
|
options,
|
|
);
|
|
}
|
|
|
|
export function getPlatformProfileWalletLedger(
|
|
options: PlatformProfileRequestOptions = {},
|
|
) {
|
|
return requestPlatformProfileJson<ProfileWalletLedgerResponse>(
|
|
'/wallet-ledger',
|
|
{ method: 'GET' },
|
|
'读取资产流水失败',
|
|
options,
|
|
);
|
|
}
|
|
|
|
export function listPlatformProfileExternalApiKeys(
|
|
options: PlatformProfileRequestOptions = {},
|
|
) {
|
|
return requestPlatformProfileJson<ExternalApiKeyListResponse>(
|
|
'/api-keys',
|
|
{ method: 'GET' },
|
|
'读取 API Key 失败',
|
|
options,
|
|
);
|
|
}
|
|
|
|
export function createPlatformProfileExternalApiKey(
|
|
name: string,
|
|
options: PlatformProfileRequestOptions = {},
|
|
) {
|
|
return requestPlatformProfileJson<ExternalApiKeyCreateResponse>(
|
|
'/api-keys',
|
|
{
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ name }),
|
|
},
|
|
'创建 API Key 失败',
|
|
options,
|
|
);
|
|
}
|
|
|
|
export function revokePlatformProfileExternalApiKey(
|
|
keyId: string,
|
|
options: PlatformProfileRequestOptions = {},
|
|
) {
|
|
return requestPlatformProfileJson<ExternalApiKeyMutationResponse>(
|
|
`/api-keys/${encodeURIComponent(keyId)}`,
|
|
{ method: 'DELETE' },
|
|
'撤销 API Key 失败',
|
|
options,
|
|
);
|
|
}
|
|
|
|
export function getPlatformProfileRechargeCenter(
|
|
options: PlatformProfileRequestOptions = {},
|
|
) {
|
|
return requestPlatformProfileJson<ProfileRechargeCenterResponse>(
|
|
'/recharge-center',
|
|
{ method: 'GET' },
|
|
'读取泥点购买信息失败',
|
|
options,
|
|
);
|
|
}
|
|
|
|
export function createPlatformProfileRechargeOrder(
|
|
productId: string,
|
|
paymentChannel: string,
|
|
options: PlatformProfileRequestOptions = {},
|
|
) {
|
|
return requestPlatformProfileJson<CreateProfileRechargeOrderResponse>(
|
|
'/recharge/orders',
|
|
{
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ productId, paymentChannel }),
|
|
},
|
|
'充值失败',
|
|
options,
|
|
);
|
|
}
|
|
|
|
export function confirmWechatPlatformProfileRechargeOrder(
|
|
orderId: string,
|
|
options: PlatformProfileRequestOptions = {},
|
|
) {
|
|
return requestPlatformProfileJson<ConfirmWechatProfileRechargeOrderResponse>(
|
|
`/recharge/orders/${encodeURIComponent(orderId)}/wechat/confirm`,
|
|
{ method: 'POST' },
|
|
'确认微信支付订单失败',
|
|
options,
|
|
);
|
|
}
|
|
|
|
type PlatformProfileRechargeOrderSseEvent =
|
|
| {
|
|
type: 'order';
|
|
payload: ConfirmWechatProfileRechargeOrderResponse;
|
|
}
|
|
| {
|
|
type: 'done';
|
|
payload: { orderId: string; status: string };
|
|
}
|
|
| {
|
|
type: 'error';
|
|
payload: { message: string };
|
|
};
|
|
|
|
function normalizePlatformProfileRechargeOrderSseEvent(
|
|
eventName: string,
|
|
parsed: Record<string, unknown>,
|
|
): PlatformProfileRechargeOrderSseEvent | null {
|
|
if (eventName === 'order' && parsed.order && parsed.center) {
|
|
return {
|
|
type: 'order',
|
|
payload: parsed as ConfirmWechatProfileRechargeOrderResponse,
|
|
};
|
|
}
|
|
|
|
if (eventName === 'done') {
|
|
const orderId =
|
|
typeof parsed.orderId === 'string' ? parsed.orderId.trim() : '';
|
|
const status =
|
|
typeof parsed.status === 'string' ? parsed.status.trim() : '';
|
|
if (orderId && status) {
|
|
return {
|
|
type: 'done',
|
|
payload: { orderId, status },
|
|
};
|
|
}
|
|
}
|
|
|
|
if (eventName === 'error') {
|
|
const message =
|
|
typeof parsed.message === 'string' && parsed.message.trim()
|
|
? parsed.message.trim()
|
|
: '';
|
|
return {
|
|
type: 'error',
|
|
payload: { message },
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function isPlatformProfileRechargeOrderTerminal(
|
|
order: Pick<ProfileRechargeOrder, 'status' | 'expirationCheckedAt'>,
|
|
) {
|
|
if (order.status === 'pending') {
|
|
return false;
|
|
}
|
|
if (order.status === 'expired' && !order.expirationCheckedAt) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
export async function watchWechatPlatformProfileRechargeOrder(
|
|
orderId: string,
|
|
options: PlatformProfileRequestOptions = {},
|
|
): Promise<ConfirmWechatProfileRechargeOrderResponse> {
|
|
const response = await fetchWithApiAuth(
|
|
`${PLATFORM_PROFILE_API_BASE}/recharge/orders/${encodeURIComponent(orderId)}/wechat/events`,
|
|
{
|
|
method: 'GET',
|
|
headers: { Accept: 'text/event-stream' },
|
|
signal: options.signal,
|
|
},
|
|
{
|
|
skipRefresh: options.skipRefresh,
|
|
skipAuth: options.skipAuth,
|
|
authImpact: options.authImpact,
|
|
notifyAuthStateChange: options.notifyAuthStateChange,
|
|
clearAuthOnUnauthorized: options.clearAuthOnUnauthorized,
|
|
},
|
|
);
|
|
|
|
if (!response.ok) {
|
|
const responseText = await response.text();
|
|
throw new Error(
|
|
appendApiErrorRequestId(
|
|
parseApiErrorMessage(responseText, '订阅充值订单状态失败'),
|
|
response.headers.get('x-request-id'),
|
|
),
|
|
);
|
|
}
|
|
|
|
if (!response.body) {
|
|
throw new Error('streaming response body is unavailable');
|
|
}
|
|
|
|
let finalResponse: ConfirmWechatProfileRechargeOrderResponse | null = null;
|
|
let lastResponse: ConfirmWechatProfileRechargeOrderResponse | null = null;
|
|
|
|
await readSseJsonStream(response, ({ eventName, parsed }) => {
|
|
const normalized = normalizePlatformProfileRechargeOrderSseEvent(
|
|
eventName,
|
|
parsed,
|
|
);
|
|
if (!normalized) {
|
|
return;
|
|
}
|
|
|
|
if (normalized.type === 'order') {
|
|
lastResponse = normalized.payload;
|
|
if (isPlatformProfileRechargeOrderTerminal(normalized.payload.order)) {
|
|
finalResponse = normalized.payload;
|
|
return false;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (normalized.type === 'done') {
|
|
if (
|
|
!finalResponse &&
|
|
lastResponse &&
|
|
isPlatformProfileRechargeOrderTerminal(lastResponse.order)
|
|
) {
|
|
finalResponse = lastResponse;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
throw new Error(normalized.payload.message || '订阅充值订单状态失败');
|
|
});
|
|
|
|
if (!finalResponse) {
|
|
throw new Error('充值订单状态流返回不完整');
|
|
}
|
|
|
|
return finalResponse;
|
|
}
|
|
|
|
export function getPlatformProfileReferralInviteCenter(
|
|
options: PlatformProfileRequestOptions = {},
|
|
) {
|
|
return requestPlatformProfileJson<ProfileReferralInviteCenterResponse>(
|
|
'/referrals/invite-center',
|
|
{ method: 'GET' },
|
|
'读取邀请码失败',
|
|
options,
|
|
);
|
|
}
|
|
|
|
export function redeemPlatformProfileReferralInviteCode(
|
|
inviteCode: string,
|
|
options: PlatformProfileRequestOptions = {},
|
|
) {
|
|
return requestPlatformProfileJson<RedeemProfileReferralInviteCodeResponse>(
|
|
'/referrals/redeem-code',
|
|
{
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ inviteCode }),
|
|
},
|
|
'填写邀请码失败',
|
|
options,
|
|
);
|
|
}
|
|
|
|
export function redeemPlatformProfileRewardCode(
|
|
code: string,
|
|
options: PlatformProfileRequestOptions = {},
|
|
) {
|
|
return requestPlatformProfileJson<RedeemProfileRewardCodeResponse>(
|
|
'/redeem-codes/redeem',
|
|
{
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ code }),
|
|
},
|
|
'兑换失败',
|
|
options,
|
|
);
|
|
}
|