feat(platform): Fix pg-boss queue conflict and add safety standards

Summary:
- Fix pg-boss queue conflict (duplicate key violation on queue_pkey)
- Add global error listener to prevent process crash
- Reduce connection pool from 10 to 4
- Add graceful shutdown handling (SIGTERM/SIGINT)
- Fix researchWorker recursive call bug in catch block
- Make screeningWorker idempotent using upsert

Security Standards (v1.1):
- Prohibit recursive retry in Worker catch blocks
- Prohibit payload bloat (only store fileKey/ID in job.data)
- Require Worker idempotency (upsert + unique constraint)
- Recommend task-specific expireInSeconds settings
- Document graceful shutdown pattern

New Features:
- PKB signed URL endpoint for document preview/download
- pg_bigm installation guide for Docker
- Dockerfile.postgres-with-extensions for pgvector + pg_bigm

Documentation:
- Update Postgres-Only async task processing guide (v1.1)
- Add troubleshooting SQL queries
- Update safety checklist

Tested: Local verification passed
This commit is contained in:
2026-01-23 22:07:26 +08:00
parent 9c96f75c52
commit 61cdc97eeb
297 changed files with 1147 additions and 21 deletions

View File

@@ -202,3 +202,4 @@ export const jwtService = new JWTService();

View File

@@ -50,7 +50,7 @@ export class PgBossQueue implements JobQueue {
this.boss = new PgBoss({
connectionString,
schema, // 使用platform_schema
max: 10, // 最大连接数
max: 4, // 🛡️ 限制连接数,避免挤占 Prisma 连接配额RDS 限制 100
application_name: 'aiclinical-queue',
// 调度配置
@@ -61,6 +61,17 @@ export class PgBossQueue implements JobQueue {
maintenanceIntervalSeconds: 300, // 每5分钟运行维护任务
})
// 🛡️ 全局错误监听:防止未捕获错误导致进程崩溃
this.boss.on('error', (err: any) => {
// 静默处理 duplicate key 错误(队列并发初始化时的正常现象)
if (err.code === '23505' && err.constraint === 'queue_pkey') {
console.log(`[PgBossQueue] Queue concurrency conflict auto-resolved: ${err.detail}`);
} else {
console.error('[PgBossQueue] ❌ Critical error:', err);
// 记录到日志但不崩溃进程
}
});
console.log('[PgBossQueue] Initialized with schema:', schema)
}
@@ -192,13 +203,22 @@ export class PgBossQueue implements JobQueue {
console.log(`[PgBossQueue] 🔧 开始注册 Handler: ${type}`);
try {
// pg-boss 9.x 需要显式创建队列
await this.boss.createQueue(type, {
retryLimit: 3,
retryDelay: 60,
expireInSeconds: 6 * 60 * 60 // 6小时
});
console.log(`[PgBossQueue] ✅ Queue created: ${type}`);
// pg-boss 9.x 需要显式创建队列(幂等操作)
try {
await this.boss.createQueue(type, {
retryLimit: 3,
retryDelay: 60,
expireInSeconds: 6 * 60 * 60 // 6小时
});
console.log(`[PgBossQueue] ✅ Queue created: ${type}`);
} catch (createError: any) {
// 队列已存在时会报 duplicate key 错误,忽略
if (createError.code === '23505' || createError.message?.includes('already exists')) {
console.log(`[PgBossQueue] Queue already exists: ${type}`);
} else {
throw createError;
}
}
await this.boss.work<Record<string, any>>(type, {
batchSize: 1, // 每次处理1个任务

View File

@@ -332,5 +332,6 @@ export function getBatchItems<T>(

View File

@@ -85,3 +85,4 @@ export interface VariableValidation {

View File

@@ -355,3 +355,4 @@ export default ChunkService;

View File

@@ -51,3 +51,4 @@ export const DifyClient = DeprecatedDifyClient;

View File

@@ -206,3 +206,4 @@ export function createOpenAIStreamAdapter(

View File

@@ -212,3 +212,4 @@ export async function streamChat(

View File

@@ -30,3 +30,4 @@ export { THINKING_TAGS } from './types';

View File

@@ -105,3 +105,4 @@ export type SSEEventType =