feat(api): order public goods by country > mid-category > style > priority
This commit is contained in:
@@ -71,13 +71,19 @@ const PUBLIC_GOOD_LIST_INCLUDE = {
|
||||
tag: { include: { tagGroup: true } },
|
||||
position: true,
|
||||
originGood: {
|
||||
select: { sdsGoodId: true, goodImage: true, goodPrice: true },
|
||||
select: { sdsGoodId: true, goodImage: true, goodPrice: true, sdsCategoryId: true },
|
||||
},
|
||||
goodTags: { include: { tag: { include: { tagGroup: true } } } },
|
||||
} satisfies Prisma.GoodInclude;
|
||||
|
||||
type PublicGoodListRow = Prisma.GoodGetPayload<{ include: typeof PUBLIC_GOOD_LIST_INCLUDE }>;
|
||||
|
||||
interface TreeOrderMeta {
|
||||
countryOrder: Map<string, number>;
|
||||
/** key: origin_goods.sds_category_id → 款所属二级(c2)/款(c3) 的顺序值 */
|
||||
leafOrder: Map<string, { c2: number; c3: number }>;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class PublicService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
@@ -220,12 +226,7 @@ export class PublicService {
|
||||
? [{ originGood: { goodPrice: 'desc' } }, { id: 'asc' }]
|
||||
: query.sort === 'NEWEST'
|
||||
? [{ createdAt: 'desc' }, { id: 'asc' }]
|
||||
: [
|
||||
{ goodPriority: 'desc' },
|
||||
{ position: { indexVal: 'asc' } },
|
||||
{ createdAt: 'desc' },
|
||||
{ id: 'asc' },
|
||||
];
|
||||
: [{ id: 'asc' }]; // DEFAULT:排序移到内存做(树序,见下)
|
||||
|
||||
// 契约族化:一族对外只暴露一条(代表行=排序第一条,goodId=族ID);
|
||||
// 无族 Good(自定义商品)各自成一条。商品量级为百级,先取全量匹配
|
||||
@@ -235,6 +236,15 @@ export class PublicService {
|
||||
include: PUBLIC_GOOD_LIST_INCLUDE,
|
||||
orderBy,
|
||||
});
|
||||
if (!query.sort || query.sort === 'DEFAULT') {
|
||||
// 默认排序 = 款序树:国家 → 款所属二级 → 款 → 优先级。
|
||||
// 款顺序存于新树 categories.sort_order(回填自排序表),商品经
|
||||
// origin_goods.sds_category_id 定位到款;同一款下多条 Good 共享
|
||||
// 前三层键,仅按 goodPriority 分先后。缺键(如款不在树中)沉到
|
||||
// 所属国家分组末尾。
|
||||
const meta = await this.loadTreeOrderMeta();
|
||||
rows.sort((a, b) => this.compareByTreeOrder(meta, a, b));
|
||||
}
|
||||
const familyMinPrices = await this.loadFamilyMinPrices();
|
||||
const grouped = new Map<string, PublicGoodListRow[]>();
|
||||
for (const good of rows) {
|
||||
@@ -270,6 +280,52 @@ export class PublicService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 款序元数据:countries.sort_order(一级)+ 新树二/三级 categories.sort_order
|
||||
* (款顺序,回填自排序表)。key 用 origin_goods.sds_category_id 关联商品→款。
|
||||
*/
|
||||
private async loadTreeOrderMeta(): Promise<TreeOrderMeta> {
|
||||
const [countries, leaves] = await Promise.all([
|
||||
this.prisma.country.findMany({ select: { id: true, sortOrder: true } }),
|
||||
this.prisma.$queryRaw<
|
||||
Array<{ sds_category_id: string; c2: number; c3: number }>
|
||||
>`
|
||||
SELECT leaf.sds_category_id,
|
||||
COALESCE(mid.sort_order, 2147483647) AS c2,
|
||||
COALESCE(leaf.sort_order, 2147483647) AS c3
|
||||
FROM categories leaf
|
||||
JOIN categories mid ON mid.category_id = leaf.parent_category_id
|
||||
WHERE leaf.sds_category_id IS NOT NULL AND leaf.sds_category_id <> ''
|
||||
`,
|
||||
]);
|
||||
return {
|
||||
countryOrder: new Map(countries.map((c) => [c.id.toString(), c.sortOrder])),
|
||||
leafOrder: new Map(
|
||||
leaves.map((l) => [l.sds_category_id, { c2: Number(l.c2), c3: Number(l.c3) }]),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
private compareByTreeOrder(meta: TreeOrderMeta, a: PublicGoodListRow, b: PublicGoodListRow): number {
|
||||
const MAX = Number.MAX_SAFE_INTEGER;
|
||||
const key = (g: PublicGoodListRow): [number, number, number, number, number] => {
|
||||
const leaf = meta.leafOrder.get(g.originGood.sdsCategoryId ?? '');
|
||||
return [
|
||||
meta.countryOrder.get(g.countryId.toString()) ?? MAX,
|
||||
leaf?.c2 ?? MAX,
|
||||
leaf?.c3 ?? MAX,
|
||||
-(g.goodPriority ?? 0),
|
||||
Number(g.id),
|
||||
];
|
||||
};
|
||||
const ka = key(a);
|
||||
const kb = key(b);
|
||||
for (let i = 0; i < ka.length; i++) {
|
||||
if (ka[i] !== kb[i]) return ka[i] - kb[i];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 族最低价一次 SQL 聚合:price_matrix 是每族 ~11KB 的 JSONB,按行 include
|
||||
* 会让每个商品都携带整份矩阵(实测全量 ~330ms);PG 端展开聚合只回传
|
||||
|
||||
Reference in New Issue
Block a user