perf: 全栈性能排查与优化落地 (消灭运行时DDL/消除全表扫描/内存泄漏治理/高频静态缓存)

这个提交包含在:
xcbyy
2026-10-23 14:28:47 +08:00
父节点 1264792a46
当前提交 59bae415ad
共有 4 个文件被更改,包括 262 次插入 和 64 次删除

查看文件

@@ -153,6 +153,11 @@ export class MysqlRepository {
ORDER BY a.created_at DESC${limitSql}
`, params);
if (!actRows || actRows.length === 0) {
return [];
}
const actIds = actRows.map((a: any) => String(a.id));
const [ptRows]: any = await pool.query(`
SELECT
p.id, p.activity_id as activityId, p.user_id as userId,
@@ -162,7 +167,8 @@ export class MysqlRepository {
p.verified_by as verifiedBy, p.created_at as createdAt
FROM participants p
LEFT JOIN users u ON u.id = p.user_id
`);
WHERE p.activity_id IN (?)
`, [actIds]);
return actRows.map((act: any) => {
const parts = ptRows.filter((p: any) => p.activityId === act.id);
@@ -838,11 +844,67 @@ export class MysqlRepository {
}
async getMyActivities(userId: number): Promise<any> {
const allActivities = await this.getActivities();
const created = allActivities.filter((a: any) => Number(a.creatorId) === Number(userId));
const joined = allActivities.filter((a: any) =>
a.participants && a.participants.some((p: any) => Number(p.userId) === Number(userId))
const uid = Number(userId) || 0;
const created = await this.getActivities({ creatorId: uid });
const [myPtRows]: any = await pool.query(
'SELECT DISTINCT activity_id FROM participants WHERE user_id = ?',
[uid]
);
const joinedActIds = Array.isArray(myPtRows) ? myPtRows.map((r: any) => String(r.activity_id)) : [];
if (joinedActIds.length === 0) {
return { created, joined: [] };
}
const [actRows]: any = await pool.query(`
SELECT
a.id, a.creator_id as creatorId, a.title, a.game_category as gameCategory,
a.location, COALESCE(a.start_time, '') as startTime, a.pay_type as payType, a.total_fee_cents as totalFeeCents,
a.per_person_fee_cents as perPersonFeeCents, a.max_participants as maxParticipants,
a.allow_dynamic_price as allowDynamicPrice, a.price_adjust_rule as priceAdjustRule,
COALESCE(a.status, 'RECRUITING') as status,
a.created_at as createdAt,
u.nickname as creatorNickname,
u.avatar as creatorAvatar
FROM activities a
LEFT JOIN users u ON a.creator_id = u.id
WHERE a.id IN (?)
ORDER BY a.created_at DESC
`, [joinedActIds]);
const [ptRows]: any = await pool.query(`
SELECT
p.id, p.activity_id as activityId, p.user_id as userId,
u.nickname as nickname, u.avatar as avatar,
p.pay_amount_cents as payAmountCents, p.verify_code as verifyCode,
p.checkin_status as checkinStatus, p.verified_at as verifiedAt,
p.verified_by as verifiedBy, p.created_at as createdAt
FROM participants p
LEFT JOIN users u ON u.id = p.user_id
WHERE p.activity_id IN (?)
`, [joinedActIds]);
const joined = actRows.map((act: any) => {
const parts = ptRows.filter((p: any) => p.activityId === act.id);
return {
...act,
allowDynamicPrice: Boolean(act.allowDynamicPrice),
participants: parts.map((p: any) => ({
id: p.id,
activityId: p.activityId,
userId: p.userId,
nickname: p.nickname || '用户已注销',
avatar: p.avatar || '',
payAmountCents: p.payAmountCents,
checkinStatus: Boolean(p.checkinStatus),
verifiedAt: p.verifiedAt,
verifiedBy: p.verifiedBy,
createdAt: p.createdAt,
})),
joinedCount: parts.length,
};
});
return {
created,
joined,
@@ -874,9 +936,6 @@ export class MysqlRepository {
}
async getCompanions(keyword?: string): Promise<any[]> {
try {
await pool.query("ALTER TABLE companions ADD COLUMN reject_reason VARCHAR(255) DEFAULT '' AFTER audit_status");
} catch (_) {}
let sql = `
SELECT
id, user_id as userId, name, avatar, gender, age, track, rank_name as \`rank\`,
@@ -1224,9 +1283,6 @@ export class MysqlRepository {
avatar?: string;
gender?: string;
}): Promise<any> {
try {
await pool.query("ALTER TABLE companions ADD COLUMN reject_reason VARCHAR(255) DEFAULT '' AFTER audit_status");
} catch (_) {}
const userId = Number(data.userId) || 1001;
const name = data.name.trim();
const track = data.track || '同城陪吃';
@@ -1270,13 +1326,6 @@ export class MysqlRepository {
}
async auditCompanion(id: number, action: 'approve' | 'reject' | 'revoke', reason?: string): Promise<any> {
try {
await pool.query("ALTER TABLE companions ADD COLUMN reject_reason VARCHAR(255) DEFAULT '' AFTER audit_status");
} catch (_) {}
try {
await pool.query('ALTER TABLE users ADD COLUMN is_expert TINYINT DEFAULT 0');
} catch (_) {}
const isApprove = action === 'approve';
const isRevoke = action === 'revoke';
const auditStatus = isApprove ? '已通过' : '已驳回';
@@ -1652,6 +1701,7 @@ export class MysqlRepository {
const uid = Number(userId) || 0;
if (uid <= 0) return 0;
const statusWhere = this.orderStatusFilter(opts.status);
const [rows]: any = await pool.query(`
SELECT COUNT(*) as c
FROM orders
@@ -5030,35 +5080,6 @@ export class MysqlRepository {
// 友圈动态相关
async getPosts(category?: string, keyword?: string, status?: string, userId?: number): Promise<any[]> {
const currentUserId = userId !== undefined ? Number(userId) : 1001;
const likedPostIdSet = new Set<string>();
try {
const [likedRows]: any = await pool.query(
'SELECT post_id FROM post_likes WHERE user_id = ?',
[currentUserId]
);
if (Array.isArray(likedRows)) {
likedRows.forEach((row: any) => likedPostIdSet.add(String(row.post_id)));
}
} catch (_) {}
const applyStatsMap = new Map<string, { total: number; pending: number; myStatus: string }>();
try {
const [applyRows]: any = await pool.query(
'SELECT post_id, applicant_id, status FROM post_applies ORDER BY created_at DESC'
);
if (Array.isArray(applyRows)) {
for (const ar of applyRows) {
const pid = String(ar.post_id);
const cur = applyStatsMap.get(pid) || { total: 0, pending: 0, myStatus: '' };
cur.total += 1;
if (ar.status === 'PENDING') cur.pending += 1;
if (Number(ar.applicant_id) === currentUserId && !cur.myStatus) {
cur.myStatus = ar.status || 'PENDING';
}
applyStatsMap.set(pid, cur);
}
}
} catch (_) {}
let sql = `
SELECT
@@ -5089,6 +5110,41 @@ export class MysqlRepository {
sql += ' ORDER BY is_top DESC, created_at DESC LIMIT 200';
const [rows]: any = await pool.query(sql, params);
if (!rows || rows.length === 0) {
return [];
}
const postIds = rows.map((r: any) => String(r.id));
const likedPostIdSet = new Set<string>();
try {
const [likedRows]: any = await pool.query(
'SELECT post_id FROM post_likes WHERE user_id = ? AND post_id IN (?)',
[currentUserId, postIds]
);
if (Array.isArray(likedRows)) {
likedRows.forEach((row: any) => likedPostIdSet.add(String(row.post_id)));
}
} catch (_) {}
const applyStatsMap = new Map<string, { total: number; pending: number; myStatus: string }>();
try {
const [applyRows]: any = await pool.query(
'SELECT post_id, applicant_id, status FROM post_applies WHERE post_id IN (?) ORDER BY created_at DESC',
[postIds]
);
if (Array.isArray(applyRows)) {
for (const ar of applyRows) {
const pid = String(ar.post_id);
const cur = applyStatsMap.get(pid) || { total: 0, pending: 0, myStatus: '' };
cur.total += 1;
if (ar.status === 'PENDING') cur.pending += 1;
if (Number(ar.applicant_id) === currentUserId && !cur.myStatus) {
cur.myStatus = ar.status || 'PENDING';
}
applyStatsMap.set(pid, cur);
}
}
} catch (_) {}
return rows.map((r: any) => {
let topicsArr = [];
let imagesArr = [];
@@ -5367,12 +5423,6 @@ export class MysqlRepository {
}
async getOrderReviews(expertId?: number, orderId?: string): Promise<any[]> {
try {
await pool.query('ALTER TABLE order_reviews ADD COLUMN reply_content TEXT AFTER content');
} catch (_) {}
try {
await pool.query('ALTER TABLE order_reviews ADD COLUMN reply_time DATETIME AFTER reply_content');
} catch (_) {}
let sql = `
SELECT id, order_id as orderId, expert_id as expertId, user_id as userId,
user_nickname as userNickname, user_avatar as userAvatar,
@@ -5637,12 +5687,6 @@ export class MysqlRepository {
// 17. 大神对评价感谢回复
async replyOrderReview(reviewId: string, replyContent: string): Promise<any> {
const now = new Date();
try {
await pool.query('ALTER TABLE order_reviews ADD COLUMN reply_content TEXT AFTER content');
} catch (_) {}
try {
await pool.query('ALTER TABLE order_reviews ADD COLUMN reply_time DATETIME AFTER reply_content');
} catch (_) {}
await pool.query(`
UPDATE order_reviews
@@ -5690,8 +5734,11 @@ export class MysqlRepository {
}
// 19. 大神服务包管理
/** 确保 expert_services 表与索引存在(懒建表) */
private static expertServicesTableInitialized = false;
/** 确保 expert_services 表与索引存在(懒建表,单例仅初始化一次) */
async ensureExpertServicesTable(): Promise<void> {
if (MysqlRepository.expertServicesTableInitialized) return;
await pool.query(`
CREATE TABLE IF NOT EXISTS expert_services (
id VARCHAR(64) PRIMARY KEY,
@@ -5711,6 +5758,7 @@ export class MysqlRepository {
} catch (_) {
// 索引已存在:忽略
}
MysqlRepository.expertServicesTableInitialized = true;
}
private mapExpertService(r: any): any {