fix:java项目性能优化

This commit is contained in:
彭帅
2026-05-28 15:51:39 +08:00
parent 8b65de36b8
commit 3bdd16cbd2
54 changed files with 3178 additions and 624 deletions

View File

@@ -3,7 +3,10 @@ import { getAuthToken } from "@/utils/token";
import {
NONE_SELECT_VALUE,
type ReferenceBasesRecord,
type ReferenceQuery,
type ReferenceRecord,
type ReferenceSetPageOptions,
type ReferenceSetQuery,
type ReferenceSetRecord,
type SelectOption,
} from "./types";
@@ -41,6 +44,11 @@ interface VariantSetResponse {
referenceSetDbId: string | null;
}
interface VariantResponse {
variantDbId: string;
referenceSetDbId: string | null;
}
type ReferenceSetPayload = Partial<Record<
| "id"
| "reference_set_name"
@@ -86,7 +94,7 @@ async function request<T>(path: string, init?: RequestInit): Promise<T> {
if (!response.ok) {
const detail = await response.text();
throw new Error(detail || `Request failed: ${response.status}`);
throw new Error(detail || `请求失败:${response.status}`);
}
return response.json() as Promise<T>;
}
@@ -105,7 +113,7 @@ const requiredText = (value: unknown, message: string) => {
const optionalNumber = (value: unknown) => {
const normalized = optionalText(value);
if (!normalized) return null;
if (normalized === null) return null;
const parsed = Number(normalized);
return Number.isNaN(parsed) ? null : parsed;
};
@@ -125,19 +133,22 @@ const optionalUrl = (value: unknown, label: string) => {
return normalized;
};
const validateBases = (value: unknown) => {
const validateBases = (value: unknown, required = false) => {
const normalized = optionalText(value);
if (!normalized) return null;
if (!normalized) {
if (required) throw new Error("碱基序列片段不能为空");
return null;
}
if (normalized.length > 2048) {
throw new Error("碱基序列片段不能超过 2048 字符");
}
if (!BASES_PATTERN.test(normalized)) {
throw new Error("碱基序列仅允A/C/G/T/N 及常见占位符");
throw new Error("碱基序列仅允<EFBFBD>?A/C/G/T/N 及常见占位符");
}
return normalized.toUpperCase();
};
const mapReferenceSet = (item: ReferenceSetRecord): ReferenceSetRecord => ({
export const mapReferenceSet = (item: ReferenceSetRecord): ReferenceSetRecord => ({
...item,
id: item.referenceSetDbId || item.id,
reference_set_name: item.reference_set_name || item.referenceSetName || null,
@@ -166,7 +177,7 @@ const mapReferenceSet = (item: ReferenceSetRecord): ReferenceSetRecord => ({
|| null,
});
const mapReference = (reference: ReferenceRecord): ReferenceRecord => ({
export const mapReference = (reference: ReferenceRecord): ReferenceRecord => ({
...reference,
id: reference.referenceDbId || reference.id,
reference_name: reference.reference_name || reference.referenceName || null,
@@ -175,7 +186,7 @@ const mapReference = (reference: ReferenceRecord): ReferenceRecord => ({
source_divergence: reference.source_divergence ?? reference.sourceDivergence ?? null,
});
const mapReferenceBases = (item: ReferenceBasesRecord): ReferenceBasesRecord => ({
export const mapReferenceBases = (item: ReferenceBasesRecord): ReferenceBasesRecord => ({
...item,
id: item.referenceBasesDbId || item.id,
reference_id: item.reference_id || item.referenceDbId || null,
@@ -183,63 +194,155 @@ const mapReferenceBases = (item: ReferenceBasesRecord): ReferenceBasesRecord =>
page_number: item.page_number ?? item.pageNumber ?? null,
});
const referenceSetBody = (payload: ReferenceSetPayload) => ({
referenceSetName: requiredText(payload.reference_set_name, "ReferenceSet 名称不能为空"),
assemblyPUI: optionalText(payload.assembly_pui),
description: optionalText(payload.description),
isDerived: optionalBoolean(payload.is_derived),
md5checksum: optionalText(payload.md5checksum),
sourceURI: optionalUrl(payload.source_uri, "来源 URI"),
species: optionalText(payload.species_ontology_term) || optionalUrl(payload.species_ontology_termuri, "物种本体 URI")
? {
term: optionalText(payload.species_ontology_term),
termURI: optionalUrl(payload.species_ontology_termuri, "物种本体 URI"),
}
: undefined,
sourceGermplasmDbId: optionalText(payload.source_germplasm_id),
});
function buildSpecies(payload: ReferenceSetPayload) {
const term = optionalText(payload.species_ontology_term);
const termURI = optionalUrl(payload.species_ontology_termuri, "物种本体 URI");
if (!term && !termURI) return undefined;
return { term, termURI };
}
const referenceBody = (payload: ReferencePayload) => ({
referenceName: requiredText(payload.reference_name, "Reference 名称不能为空"),
referenceSetDbId: requiredText(payload.reference_set_id, "ReferenceSet 不能为空"),
length: optionalNumber(payload.length),
md5checksum: optionalText(payload.md5checksum),
sourceDivergence: optionalNumber(payload.source_divergence),
});
function buildReferenceSetWriteBody(payload: ReferenceSetPayload) {
const body: Record<string, unknown> = {
referenceSetName: requiredText(payload.reference_set_name, "请填<EFBFBD>?ReferenceSet 名称"),
};
const optionalFields: Array<[string, unknown]> = [
["assemblyPUI", optionalText(payload.assembly_pui)],
["description", optionalText(payload.description)],
["isDerived", optionalBoolean(payload.is_derived)],
["md5checksum", optionalText(payload.md5checksum)],
["sourceURI", optionalUrl(payload.source_uri, "来源 URI")],
["sourceGermplasmDbId", optionalText(payload.source_germplasm_id)],
];
optionalFields.forEach(([key, value]) => {
if (value !== null && value !== undefined) body[key] = value;
});
const species = buildSpecies(payload);
if (species) body.species = species;
return body;
}
const referenceBasesBody = (payload: ReferenceBasesPayload) => ({
referenceDbId: requiredText(payload.reference_id, "Reference 不能为空"),
pageNumber: optionalNumber(payload.page_number),
bases: validateBases(payload.bases),
});
function buildReferenceWriteBody(payload: ReferencePayload) {
const body: Record<string, unknown> = {
referenceName: requiredText(payload.reference_name, "请填<E8AFB7>?Reference 名称"),
referenceSetDbId: requiredText(payload.reference_set_id, "请选择 ReferenceSet"),
};
const length = optionalNumber(payload.length);
const sourceDivergence = optionalNumber(payload.source_divergence);
const md5checksum = optionalText(payload.md5checksum);
if (length !== null) {
if (length < 0) throw new Error("序列长度不能为负");
body.length = length;
}
if (sourceDivergence !== null) body.sourceDivergence = sourceDivergence;
if (md5checksum) body.md5checksum = md5checksum;
return body;
}
const attachReferenceSetCounts = (
function buildReferenceBasesWriteBody(payload: ReferenceBasesPayload, creating: boolean) {
const pageNumber = optionalNumber(payload.page_number);
if (pageNumber === null) throw new Error("请填写分页序");
if (pageNumber < 0) throw new Error("分页序号不能为负");
return {
referenceDbId: requiredText(payload.reference_id, "请选择 Reference"),
pageNumber,
bases: validateBases(payload.bases, creating),
};
}
function filterReferenceSets(rows: ReferenceSetRecord[], query?: ReferenceSetQuery) {
const name = optionalText(query?.reference_set_name)?.toLowerCase();
const assembly = optionalText(query?.assembly_pui)?.toLowerCase();
return rows.filter((row) => {
if (name && !String(row.reference_set_name ?? "").toLowerCase().includes(name)) return false;
if (assembly && !String(row.assembly_pui ?? "").toLowerCase().includes(assembly)) return false;
return true;
});
}
function filterReferences(rows: ReferenceRecord[], query?: ReferenceQuery) {
const name = optionalText(query?.reference_name)?.toLowerCase();
const referenceSetId = optionalText(query?.reference_set_id);
return rows.filter((row) => {
if (referenceSetId && row.reference_set_id !== referenceSetId) return false;
if (name && !String(row.reference_name ?? "").toLowerCase().includes(name)) return false;
return true;
});
}
function attachReferenceSetCounts(
rows: ReferenceSetRecord[],
references: ReferenceRecord[],
variantSets: VariantSetResponse[],
) => rows.map((row) => ({
...row,
reference_count: references.filter((item) => item.reference_set_id === row.id).length,
variantset_count: variantSets.filter((item) => item.referenceSetDbId === row.id).length,
}));
variants: VariantResponse[],
) {
return rows.map((row) => ({
...row,
reference_count: references.filter((item) => item.reference_set_id === row.id).length,
variantset_count: variantSets.filter((item) => item.referenceSetDbId === row.id).length,
variant_count: variants.filter((item) => item.referenceSetDbId === row.id).length,
}));
}
function attachReferenceBasesStats(references: ReferenceRecord[], basesPages: ReferenceBasesRecord[]) {
return references.map((reference) => {
const pages = basesPages.filter((page) => page.reference_id === reference.id);
const basesTotalLength = pages.reduce((total, page) => total + String(page.bases ?? "").length, 0);
return {
...reference,
bases_page_count: pages.length,
bases_total_length: basesTotalLength,
};
});
}
function enrichReferenceSetNames(
references: ReferenceRecord[],
referenceSets: ReferenceSetRecord[],
): ReferenceRecord[] {
const label = new Map(referenceSets.map((item) => [item.id, item.reference_set_name || item.id]));
return references.map((reference) => ({
...reference,
reference_set_name: reference.reference_set_id
? label.get(reference.reference_set_id) ?? reference.reference_set_name
: reference.reference_set_name,
}));
}
function enrichGermplasmNames(
rows: ReferenceSetRecord[],
germplasm: SelectOption[],
): ReferenceSetRecord[] {
const label = new Map(germplasm.map((item) => [item.value, item.label]));
return rows.map((row) => ({
...row,
source_germplasm_name: row.source_germplasm_id
? label.get(row.source_germplasm_id) ?? row.source_germplasm_name ?? row.source_germplasm_id
: row.source_germplasm_name,
}));
}
const referenceSetRowsLoader = createCachedLoader(async () => {
const response = await request<BrapiListResponse<ReferenceSetRecord>>("/brapi/v2/referencesets?page=0&pageSize=1000");
const response = await request<BrapiListResponse<ReferenceSetRecord>>("/brapi/v2/referencesets?page=0&pageSize=10");
return response.result.data.map(mapReferenceSet);
});
const referenceRowsLoader = createCachedLoader(async () => {
const response = await request<BrapiListResponse<ReferenceRecord>>("/brapi/v2/references?page=0&pageSize=1000");
const response = await request<BrapiListResponse<ReferenceRecord>>("/brapi/v2/references?page=0&pageSize=10");
return response.result.data.map(mapReference);
});
const variantSetRowsLoader = createCachedLoader(async () => {
const response = await request<BrapiListResponse<VariantSetResponse>>("/brapi/v2/variantsets?page=0&pageSize=1000");
const response = await request<BrapiListResponse<VariantSetResponse>>("/brapi/v2/variantsets?page=0&pageSize=10");
return response.result.data;
});
const variantRowsLoader = createCachedLoader(async () => {
const response = await request<BrapiListResponse<VariantResponse>>("/brapi/v2/variants?page=0&pageSize=10");
return response.result.data;
});
const referenceBasesRowsLoader = createCachedLoader(async () => {
const response = await request<BrapiListResponse<ReferenceBasesRecord>>("/brapi/v2/referencebases?page=0&pageSize=1000");
const response = await request<BrapiListResponse<ReferenceBasesRecord>>("/brapi/v2/referencebases?page=0&pageSize=10");
return response.result.data.map(mapReferenceBases);
});
@@ -247,32 +350,11 @@ export function invalidateReferenceSetPageCache() {
referenceSetRowsLoader.invalidate();
referenceRowsLoader.invalidate();
variantSetRowsLoader.invalidate();
variantRowsLoader.invalidate();
referenceBasesRowsLoader.invalidate();
}
export async function fetchReferenceSetRows(force = false): Promise<ReferenceSetRecord[]> {
const [referenceSets, references, variantSets] = await Promise.all([
referenceSetRowsLoader.load(force),
referenceRowsLoader.load(force),
variantSetRowsLoader.load(force),
]);
return attachReferenceSetCounts(referenceSets, references, variantSets);
}
export async function fetchReferenceRows(force = false): Promise<ReferenceRecord[]> {
return referenceRowsLoader.load(force);
}
export async function fetchReferenceBasesRows(force = false): Promise<ReferenceBasesRecord[]> {
return referenceBasesRowsLoader.load(force);
}
export async function fetchReferenceSetOptions(force = false): Promise<{
referenceSets: SelectOption[];
references: SelectOption[];
germplasm: SelectOption[];
}> {
export async function fetchReferenceSetOptions(force = false): Promise<ReferenceSetPageOptions> {
const [sharedOptions, referenceSets, references] = await Promise.all([
loadDropdownBundle({ germplasms: true }, force),
referenceSetRowsLoader.load(force),
@@ -292,13 +374,127 @@ export async function fetchReferenceSetOptions(force = false): Promise<{
};
}
export async function loadReferenceSetPageData(params: {
referenceSetQuery?: ReferenceSetQuery;
referenceQuery?: ReferenceQuery;
force?: boolean;
} = {}) {
const force = params.force ?? false;
const [options, referenceSets, references, variantSets, variants, basesPages] = await Promise.all([
fetchReferenceSetOptions(force),
referenceSetRowsLoader.load(force),
referenceRowsLoader.load(force),
variantSetRowsLoader.load(force),
variantRowsLoader.load(force),
referenceBasesRowsLoader.load(force),
]);
const enrichedSets = enrichGermplasmNames(
attachReferenceSetCounts(referenceSets, references, variantSets, variants),
options.germplasm,
);
const enrichedReferences = attachReferenceBasesStats(
enrichReferenceSetNames(references, referenceSets),
basesPages,
);
return {
options,
referenceSets: filterReferenceSets(enrichedSets, params.referenceSetQuery),
references: filterReferences(enrichedReferences, params.referenceQuery),
basesPages,
variantSets,
variants,
};
}
export async function fetchReferenceSetRows(query?: ReferenceSetQuery, force = false): Promise<ReferenceSetRecord[]> {
const { referenceSets } = await loadReferenceSetPageData({ referenceSetQuery: query, force });
return referenceSets;
}
export async function fetchReferenceRows(query?: ReferenceQuery, force = false): Promise<ReferenceRecord[]> {
const { references } = await loadReferenceSetPageData({ referenceQuery: query, force });
return references;
}
export async function fetchReferenceSetDetail(id: string): Promise<ReferenceSetRecord> {
const response = await request<BrapiSingleResponse<ReferenceSetRecord>>(
`/brapi/v2/referencesets/${encodeURIComponent(id)}`,
);
const { options, references, variantSets, variants } = await loadReferenceSetPageData();
const [detail] = enrichGermplasmNames(
attachReferenceSetCounts([mapReferenceSet(response.result)], references, variantSets, variants),
options.germplasm,
);
return detail;
}
export async function fetchReferenceDetail(id: string): Promise<ReferenceRecord> {
const response = await request<BrapiSingleResponse<ReferenceRecord>>(
`/brapi/v2/references/${encodeURIComponent(id)}`,
);
const { referenceSets, basesPages } = await loadReferenceSetPageData();
const [detail] = attachReferenceBasesStats(
enrichReferenceSetNames([mapReference(response.result)], referenceSets),
basesPages,
);
return detail;
}
export async function fetchReferenceBasesRows(referenceDbId?: string, force = false): Promise<ReferenceBasesRecord[]> {
const pages = await referenceBasesRowsLoader.load(force);
const filtered = referenceDbId
? pages.filter((page) => page.reference_id === referenceDbId)
: pages;
return filtered.sort((a, b) => Number(a.page_number ?? 0) - Number(b.page_number ?? 0));
}
export function normalizeReferenceSetFormData(record: ReferenceSetRecord): Record<string, unknown> {
return {
id: record.id,
reference_set_name: record.reference_set_name ?? "",
assembly_pui: record.assembly_pui ?? "",
description: record.description ?? "",
is_derived: record.is_derived === true ? "true" : record.is_derived === false ? "false" : NONE_SELECT_VALUE,
md5checksum: record.md5checksum ?? "",
source_uri: record.source_uri ?? "",
species_ontology_term: record.species_ontology_term ?? "",
species_ontology_termuri: record.species_ontology_termuri ?? "",
source_germplasm_id: record.source_germplasm_id && record.source_germplasm_id !== NONE_SELECT_VALUE
? record.source_germplasm_id
: NONE_SELECT_VALUE,
};
}
export function normalizeReferenceFormData(record: ReferenceRecord): Record<string, unknown> {
return {
id: record.id,
reference_name: record.reference_name ?? "",
reference_set_id: record.reference_set_id ?? "",
length: record.length ?? "",
md5checksum: record.md5checksum ?? "",
source_divergence: record.source_divergence ?? "",
};
}
export function normalizeReferenceBasesFormData(record: ReferenceBasesRecord): Record<string, unknown> {
return {
id: record.id,
reference_id: record.reference_id ?? "",
page_number: record.page_number ?? "",
bases: record.bases ?? "",
};
}
export async function createReferenceSetRow(payload: ReferenceSetPayload): Promise<ReferenceSetRecord> {
const body = {
...buildReferenceSetWriteBody(payload),
...(optionalText(payload.id) ? { referenceSetDbId: optionalText(payload.id) } : {}),
};
const response = await request<BrapiListResponse<ReferenceSetRecord>>("/brapi/v2/referencesets", {
method: "POST",
body: JSON.stringify({
referenceSetDbId: requiredText(payload.id, "ReferenceSet ID 不能为空"),
...referenceSetBody(payload),
}),
body: JSON.stringify(body),
});
invalidateReferenceSetPageCache();
return mapReferenceSet(response.result.data[0]);
@@ -313,7 +509,7 @@ export async function updateReferenceSetRow(id: string, payload: ReferenceSetPay
`/brapi/v2/referencesets/${encodeURIComponent(id)}`,
{
method: "PUT",
body: JSON.stringify(referenceSetBody(payload)),
body: JSON.stringify(buildReferenceSetWriteBody(payload)),
},
);
invalidateReferenceSetPageCache();
@@ -329,12 +525,13 @@ export async function deleteReferenceSetRow(id: string): Promise<void> {
}
export async function createReferenceRow(payload: ReferencePayload): Promise<ReferenceRecord> {
const body = {
...buildReferenceWriteBody(payload),
...(optionalText(payload.id) ? { referenceDbId: optionalText(payload.id) } : {}),
};
const response = await request<BrapiListResponse<ReferenceRecord>>("/brapi/v2/references", {
method: "POST",
body: JSON.stringify({
referenceDbId: requiredText(payload.id, "Reference ID 不能为空"),
...referenceBody(payload),
}),
body: JSON.stringify(body),
});
invalidateReferenceSetPageCache();
return mapReference(response.result.data[0]);
@@ -349,7 +546,7 @@ export async function updateReferenceRow(id: string, payload: ReferencePayload):
`/brapi/v2/references/${encodeURIComponent(id)}`,
{
method: "PUT",
body: JSON.stringify(referenceBody(payload)),
body: JSON.stringify(buildReferenceWriteBody(payload)),
},
);
invalidateReferenceSetPageCache();
@@ -365,12 +562,13 @@ export async function deleteReferenceRow(id: string): Promise<void> {
}
export async function createReferenceBasesRow(payload: ReferenceBasesPayload): Promise<ReferenceBasesRecord> {
const body = {
...buildReferenceBasesWriteBody(payload, true),
...(optionalText(payload.id) ? { referenceBasesDbId: optionalText(payload.id) } : {}),
};
const response = await request<BrapiListResponse<ReferenceBasesRecord>>("/brapi/v2/referencebases", {
method: "POST",
body: JSON.stringify({
referenceBasesDbId: requiredText(payload.id, "ReferenceBases ID 不能为空"),
...referenceBasesBody(payload),
}),
body: JSON.stringify(body),
});
invalidateReferenceSetPageCache();
return mapReferenceBases(response.result.data[0]);
@@ -385,7 +583,7 @@ export async function updateReferenceBasesRow(id: string, payload: ReferenceBase
`/brapi/v2/referencebases/${encodeURIComponent(id)}`,
{
method: "PUT",
body: JSON.stringify(referenceBasesBody(payload)),
body: JSON.stringify(buildReferenceBasesWriteBody(payload, false)),
},
);
invalidateReferenceSetPageCache();