修复错误报告审查中的次要问题
修复后台错误报告刷新竞态、下载并发与查询参数构建复用。 修复错误报告清理分页、归档大小限制、分页总数和下载响应头异常。 修复 OSS 流式读取上限与 Codex 高频调试日志阻塞。 同步错误报告技术方案与项目决策记录,并在 review.txt 保留待决 breaking 项。
This commit is contained in:
@@ -261,7 +261,16 @@ export function listAdminTrackingEvents(
|
||||
query: AdminTrackingEventListQuery = {},
|
||||
) {
|
||||
return request<AdminTrackingEventListResponse>(
|
||||
`/admin/api/tracking/events${buildQueryString(query)}`,
|
||||
`/admin/api/tracking/events${buildQueryString((params) => {
|
||||
appendQueryParam(params, 'eventKey', query.eventKey);
|
||||
appendQueryParam(params, 'userId', query.userId);
|
||||
appendQueryParam(params, 'scopeKind', query.scopeKind);
|
||||
appendQueryParam(params, 'scopeId', query.scopeId);
|
||||
appendQueryParam(params, 'startDate', query.startDate);
|
||||
appendQueryParam(params, 'endDate', query.endDate);
|
||||
appendNumericQueryParam(params, 'limit', query.limit);
|
||||
if (query.exportAll) params.set('exportAll', 'true');
|
||||
})}`,
|
||||
{ token },
|
||||
);
|
||||
}
|
||||
@@ -844,22 +853,10 @@ async function postAdminDirectUploadFile(
|
||||
}
|
||||
}
|
||||
|
||||
function buildQueryString(query: AdminTrackingEventListQuery) {
|
||||
function buildQueryString(append: (params: URLSearchParams) => void) {
|
||||
const params = new URLSearchParams();
|
||||
appendQueryParam(params, 'eventKey', query.eventKey);
|
||||
appendQueryParam(params, 'userId', query.userId);
|
||||
appendQueryParam(params, 'scopeKind', query.scopeKind);
|
||||
appendQueryParam(params, 'scopeId', query.scopeId);
|
||||
appendQueryParam(params, 'startDate', query.startDate);
|
||||
appendQueryParam(params, 'endDate', query.endDate);
|
||||
if (typeof query.limit === 'number' && Number.isFinite(query.limit)) {
|
||||
params.set('limit', String(query.limit));
|
||||
}
|
||||
if (query.exportAll) {
|
||||
params.set('exportAll', 'true');
|
||||
}
|
||||
const queryString = params.toString();
|
||||
return queryString ? `?${queryString}` : '';
|
||||
append(params);
|
||||
return formatQueryString(params);
|
||||
}
|
||||
|
||||
function buildErrorReportQueryString(query: {
|
||||
@@ -869,39 +866,31 @@ function buildErrorReportQueryString(query: {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}) {
|
||||
const params = new URLSearchParams();
|
||||
appendQueryParam(params, 'status', query.status);
|
||||
appendQueryParam(params, 'fingerprint', query.fingerprint);
|
||||
appendQueryParam(params, 'source', query.source);
|
||||
if (typeof query.limit === 'number' && Number.isFinite(query.limit)) {
|
||||
params.set('limit', String(query.limit));
|
||||
}
|
||||
if (typeof query.offset === 'number' && Number.isFinite(query.offset)) {
|
||||
params.set('offset', String(query.offset));
|
||||
}
|
||||
const queryString = params.toString();
|
||||
return queryString ? `?${queryString}` : '';
|
||||
return buildQueryString((params) => {
|
||||
appendQueryParam(params, 'status', query.status);
|
||||
appendQueryParam(params, 'fingerprint', query.fingerprint);
|
||||
appendQueryParam(params, 'source', query.source);
|
||||
appendNumericQueryParam(params, 'limit', query.limit);
|
||||
appendNumericQueryParam(params, 'offset', query.offset);
|
||||
});
|
||||
}
|
||||
|
||||
function buildAdminRechargeOrderListQuery(query: AdminRechargeOrderListQuery) {
|
||||
const params = new URLSearchParams();
|
||||
appendQueryParam(params, 'orderId', query.orderId);
|
||||
appendQueryParam(
|
||||
params,
|
||||
'providerTransactionId',
|
||||
query.providerTransactionId,
|
||||
);
|
||||
appendQueryParam(params, 'userId', query.userId);
|
||||
appendQueryParam(params, 'publicUserCode', query.publicUserCode);
|
||||
appendQueryParam(params, 'paymentChannel', query.paymentChannel);
|
||||
appendQueryParam(params, 'status', query.status);
|
||||
appendQueryParam(params, 'createdAfter', query.createdAfter);
|
||||
appendQueryParam(params, 'createdBefore', query.createdBefore);
|
||||
if (typeof query.limit === 'number' && Number.isFinite(query.limit)) {
|
||||
params.set('limit', String(query.limit));
|
||||
}
|
||||
const queryString = params.toString();
|
||||
return queryString ? `?${queryString}` : '';
|
||||
return buildQueryString((params) => {
|
||||
appendQueryParam(params, 'orderId', query.orderId);
|
||||
appendQueryParam(
|
||||
params,
|
||||
'providerTransactionId',
|
||||
query.providerTransactionId,
|
||||
);
|
||||
appendQueryParam(params, 'userId', query.userId);
|
||||
appendQueryParam(params, 'publicUserCode', query.publicUserCode);
|
||||
appendQueryParam(params, 'paymentChannel', query.paymentChannel);
|
||||
appendQueryParam(params, 'status', query.status);
|
||||
appendQueryParam(params, 'createdAfter', query.createdAfter);
|
||||
appendQueryParam(params, 'createdBefore', query.createdBefore);
|
||||
appendNumericQueryParam(params, 'limit', query.limit);
|
||||
});
|
||||
}
|
||||
|
||||
function buildAdminUserDetailQuery(query: AdminUserDetailQuery) {
|
||||
@@ -977,6 +966,21 @@ function appendQueryParam(
|
||||
}
|
||||
}
|
||||
|
||||
function appendNumericQueryParam(
|
||||
params: URLSearchParams,
|
||||
key: string,
|
||||
value: number | null | undefined,
|
||||
) {
|
||||
if (typeof value === 'number' && Number.isFinite(value)) {
|
||||
params.set(key, String(value));
|
||||
}
|
||||
}
|
||||
|
||||
function formatQueryString(params: URLSearchParams) {
|
||||
const queryString = params.toString();
|
||||
return queryString ? `?${queryString}` : '';
|
||||
}
|
||||
|
||||
function parseJsonResponse(responseText: string): unknown {
|
||||
if (!responseText.trim()) {
|
||||
return null;
|
||||
|
||||
@@ -22,6 +22,7 @@ export function AdminErrorReportsPage({ token, onUnauthorized }: Props) {
|
||||
const [reports, setReports] = useState<AdminErrorReportEntry[]>([]);
|
||||
const [selected, setSelected] = useState<AdminErrorReportDetail | null>(null);
|
||||
const [status, setStatus] = useState('');
|
||||
const [downloading, setDownloading] = useState(false);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [filterStatus, setFilterStatus] = useState('');
|
||||
const [pageOffset, setPageOffset] = useState(0);
|
||||
@@ -50,6 +51,11 @@ export function AdminErrorReportsPage({ token, onUnauthorized }: Props) {
|
||||
}
|
||||
}, [filterStatus, onUnauthorized, pageOffset, token]);
|
||||
|
||||
const loadRef = useRef(load);
|
||||
useEffect(() => {
|
||||
loadRef.current = load;
|
||||
}, [load]);
|
||||
|
||||
useEffect(() => {
|
||||
void load();
|
||||
}, [load]);
|
||||
@@ -79,7 +85,7 @@ export function AdminErrorReportsPage({ token, onUnauthorized }: Props) {
|
||||
note: selected.note,
|
||||
}),
|
||||
);
|
||||
await load();
|
||||
await loadRef.current();
|
||||
} catch (error) {
|
||||
if (isAdminApiError(error) && error.status === 401) onUnauthorized();
|
||||
else setStatus(formatAdminApiError(error));
|
||||
@@ -89,6 +95,8 @@ export function AdminErrorReportsPage({ token, onUnauthorized }: Props) {
|
||||
}
|
||||
|
||||
async function download(batchId: string) {
|
||||
if (downloading) return;
|
||||
setDownloading(true);
|
||||
setStatus('');
|
||||
try {
|
||||
const blob = await downloadAdminErrorReport(token, batchId);
|
||||
@@ -99,10 +107,12 @@ export function AdminErrorReportsPage({ token, onUnauthorized }: Props) {
|
||||
document.body.appendChild(anchor);
|
||||
anchor.click();
|
||||
anchor.remove();
|
||||
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
||||
setTimeout(() => URL.revokeObjectURL(url), 10_000);
|
||||
} catch (error) {
|
||||
if (isAdminApiError(error) && error.status === 401) onUnauthorized();
|
||||
else setStatus(formatAdminApiError(error));
|
||||
} finally {
|
||||
setDownloading(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,8 +246,9 @@ export function AdminErrorReportsPage({ token, onUnauthorized }: Props) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void download(selected.batchId)}
|
||||
disabled={downloading}
|
||||
>
|
||||
下载诊断包
|
||||
{downloading ? '下载中…' : '下载诊断包'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user