Răsfoiți Sursa

fix: new project functions

ryan 18 ore în urmă
părinte
comite
6a4630c5cb
2 a modificat fișierele cu 161 adăugiri și 62 ștergeri
  1. 82 0
      cloud/functions/optimize.sql
  2. 79 62
      cloud/functions/project-load.js

+ 82 - 0
cloud/functions/optimize.sql

@@ -0,0 +1,82 @@
+-- ============================================================
+-- project-load 云函数性能优化索引
+-- 执行前请先备份数据库!
+-- ============================================================
+
+-- 1. Project 表索引 - 核心查询优化
+-- 用于按公司+状态+时间范围查询
+CREATE INDEX IF NOT EXISTS "idx_project_company_status_deleted"
+ON "Project" ("company", "status", "isDeleted")
+WHERE "isDeleted" IS NOT TRUE AND "status" != '已完成';
+
+-- 用于时间范围查询(deadline 和 createdAt)
+CREATE INDEX IF NOT EXISTS "idx_project_company_deadline"
+ON "Project" ("company", "deadline")
+WHERE "isDeleted" IS NOT TRUE;
+
+CREATE INDEX IF NOT EXISTS "idx_project_company_created"
+ON "Project" ("company", "createdAt")
+WHERE "isDeleted" IS NOT TRUE;
+
+-- 复合索引:公司 + 更新时间(用于排序)
+CREATE INDEX IF NOT EXISTS "idx_project_company_updated"
+ON "Project" ("company", "updatedAt" DESC)
+WHERE "isDeleted" IS NOT TRUE;
+
+-- 2. Profile 表索引 - 工作负载查询优化
+CREATE INDEX IF NOT EXISTS "idx_profile_company_role"
+ON "Profile" ("company", "roleName")
+WHERE "isDeleted" IS NOT TRUE;
+
+-- 3. ProjectTeam 表索引 - 团队成员关联查询
+CREATE INDEX IF NOT EXISTS "idx_projectteam_project"
+ON "ProjectTeam" ("project")
+WHERE "isDeleted" IS NOT TRUE;
+
+CREATE INDEX IF NOT EXISTS "idx_projectteam_profile"
+ON "ProjectTeam" ("profile")
+WHERE "isDeleted" IS NOT TRUE;
+
+-- 4. Product 表索引 - 空间查询优化
+CREATE INDEX IF NOT EXISTS "idx_product_project"
+ON "Product" ("project")
+WHERE ("isDeleted" IS NULL OR "isDeleted" = false);
+
+-- 5. ProjectFile 表索引 - 交付物查询优化
+CREATE INDEX IF NOT EXISTS "idx_projectfile_project_type"
+ON "ProjectFile" ("project", "fileType")
+WHERE ("isDeleted" IS NULL OR "isDeleted" = false)
+  AND ("fileType" LIKE 'delivery_%' OR "data"->>'uploadStage' = 'delivery');
+
+-- 用于 spaceId/productId 查找
+CREATE INDEX IF NOT EXISTS "idx_projectfile_spaceid"
+ON "ProjectFile" (("data"->>'spaceId'))
+WHERE ("isDeleted" IS NULL OR "isDeleted" = false);
+
+-- 6. ProjectIssue 表索引
+CREATE INDEX IF NOT EXISTS "idx_issue_project_status"
+ON "ProjectIssue" ("project", "status")
+WHERE ("isDeleted" IS NULL OR "isDeleted" = false)
+  AND "status" IN ('待处理', '处理中');
+
+-- ============================================================
+-- 分析表统计信息(执行后让查询优化器更好地选择索引)
+-- ============================================================
+ANALYZE "Project";
+ANALYZE "Profile";
+ANALYZE "ProjectTeam";
+ANALYZE "Product";
+ANALYZE "ProjectFile";
+ANALYZE "ProjectIssue";
+
+-- ============================================================
+-- 查看索引使用情况(诊断用)
+-- ============================================================
+-- SELECT
+--   schemaname, tablename, indexname,
+--   idx_scan as index_scans,
+--   idx_tup_read as tuples_read,
+--   idx_tup_fetch as tuples_fetched
+-- FROM pg_stat_user_indexes
+-- WHERE tablename IN ('Project', 'Profile', 'ProjectTeam', 'Product', 'ProjectFile', 'ProjectIssue')
+-- ORDER BY tablename, idx_scan DESC;

+ 79 - 62
cloud/functions/project-load.js

@@ -49,8 +49,9 @@ async function handler(request, response) {
 
     console.log(`📅 查询时间范围: ${rangeStart.toISOString()} ~ ${rangeEnd.toISOString()}`);
 
-    // --- SQL 定义 ---
+    // --- SQL 定义 (性能优化版) ---
 
+    // 1. Workload 查询 - 保持不变,已经够高效
     const workloadSql = `
       SELECT
         u."objectId" as "id",
@@ -70,8 +71,7 @@ async function handler(request, response) {
       ORDER BY "weightedLoad" DESC
     `;
 
-    // 项目查询 - 添加时间范围筛选
-    // 筛选逻辑:项目的 deadline 或 createdAt 在指定时间范围内
+    // 2. 项目查询 - 优化:分离子查询,先查项目基础信息
     const projectsSql = `
       SELECT
         p."objectId" as "id",
@@ -83,89 +83,93 @@ async function handler(request, response) {
         p."createdAt",
         p."data",
         p."date" as "projectDate",
-        EXTRACT(DAY FROM (p."deadline" - NOW())) as "daysLeft",
-        (
-          SELECT string_agg(pr."name", ', ')
-          FROM "ProjectTeam" pt
-          JOIN "Profile" pr ON pt."profile" = pr."objectId"
-          WHERE pt."project" = p."objectId" AND pt."isDeleted" IS NOT TRUE
-        ) as "designerName",
-        (
-          SELECT array_agg(pt."profile")
-          FROM "ProjectTeam" pt
-          WHERE pt."project" = p."objectId" AND pt."isDeleted" IS NOT TRUE
-        ) as "designerIds"
+        EXTRACT(DAY FROM (p."deadline" - NOW())) as "daysLeft"
       FROM "Project" p
       WHERE p."company" = $1
         AND p."isDeleted" IS NOT TRUE
         AND p."status" != '已完成'
         AND (
-          -- 项目截止时间在范围内
-          (p."deadline" >= $2 AND p."deadline" <= $3)
-          -- 或者项目创建时间在范围内
-          OR (p."createdAt" >= $2 AND p."createdAt" <= $3)
-          -- 或者项目跨越整个范围(开始于范围前,结束于范围后)
-          OR (p."createdAt" < $2 AND p."deadline" > $3)
-          -- 或者没有截止时间但状态为进行中
-          OR (p."deadline" IS NULL AND p."status" = '进行中')
+          p."deadline" >= $2 AND p."deadline" <= $3
+          OR p."createdAt" >= $2 AND p."createdAt" <= $3
+          OR p."createdAt" < $2 AND p."deadline" > $3
+          OR p."deadline" IS NULL
         )
       ORDER BY p."updatedAt" DESC
-      LIMIT 1000
+      LIMIT 500
     `;
 
+    // 2.1 项目团队成员查询 - 一次性获取所有相关项目的团队成员
+    const projectTeamsSql = `
+      SELECT
+        pt."project" as "projectId",
+        string_agg(pr."name", ', ' ORDER BY pr."name") as "designerName",
+        array_agg(pt."profile") as "designerIds"
+      FROM "ProjectTeam" pt
+      JOIN "Profile" pr ON pt."profile" = pr."objectId"
+      WHERE pt."isDeleted" IS NOT TRUE
+        AND pt."project" IN (
+          SELECT p."objectId"
+          FROM "Project" p
+          WHERE p."company" = $1
+            AND p."isDeleted" IS NOT TRUE
+            AND p."status" != '已完成'
+            AND (
+              p."deadline" >= $2 AND p."deadline" <= $3
+              OR p."createdAt" >= $2 AND p."createdAt" <= $3
+              OR p."createdAt" < $2 AND p."deadline" > $3
+              OR p."deadline" IS NULL
+            )
+        )
+      GROUP BY pt."project"
+    `;
+
+    // 3. Space Stats - 优化:简化查询结构
     const spaceStatsSql = `
-      WITH ActiveProjects AS (
+      WITH ActiveProjectIds AS (
           SELECT p."objectId"
           FROM "Project" p
           WHERE p."company" = $1
             AND p."isDeleted" IS NOT TRUE
             AND p."status" != '已完成'
             AND (
-              (p."deadline" >= $2 AND p."deadline" <= $3)
-              OR (p."createdAt" >= $2 AND p."createdAt" <= $3)
-              OR (p."createdAt" < $2 AND p."deadline" > $3)
-              OR (p."deadline" IS NULL AND p."status" = '进行中')
+              p."deadline" >= $2 AND p."deadline" <= $3
+              OR p."createdAt" >= $2 AND p."createdAt" <= $3
+              OR p."createdAt" < $2 AND p."deadline" > $3
+              OR p."deadline" IS NULL
             )
-          LIMIT 1000
+          LIMIT 500
       ),
       ProjectSpaces AS (
           SELECT
-              p."objectId" as "spaceId",
-              p."productName" as "spaceName",
-              p."productType" as "spaceType",
-              p."project" as "projectId"
-          FROM "Product" p
-          WHERE p."project" IN (SELECT "objectId" FROM ActiveProjects)
-            AND (p."isDeleted" IS NULL OR p."isDeleted" = false)
+              prod."objectId" as "spaceId",
+              prod."productName" as "spaceName",
+              prod."productType" as "spaceType",
+              prod."project" as "projectId"
+          FROM "Product" prod
+          WHERE prod."project" IN (SELECT "objectId" FROM ActiveProjectIds)
+            AND (prod."isDeleted" IS NULL OR prod."isDeleted" = false)
       ),
       Deliverables AS (
           SELECT
-              COALESCE(d."data"->>'spaceId', d."data"->>'productId') as "spaceId",
+              COALESCE(pf."data"->>'spaceId', pf."data"->>'productId') as "spaceId",
               COUNT(*) as "fileCount",
-              SUM(CASE WHEN
-                  d."fileType" = 'delivery_white_model' OR
-                  d."data"->>'deliveryType' IN ('white_model', 'delivery_white_model')
+              SUM(CASE WHEN pf."fileType" = 'delivery_white_model'
+                  OR pf."data"->>'deliveryType' IN ('white_model', 'delivery_white_model')
                   THEN 1 ELSE 0 END) as "whiteModelCount",
-              SUM(CASE WHEN
-                  d."fileType" = 'delivery_soft_decor' OR
-                  d."data"->>'deliveryType' IN ('soft_decor', 'delivery_soft_decor')
+              SUM(CASE WHEN pf."fileType" = 'delivery_soft_decor'
+                  OR pf."data"->>'deliveryType' IN ('soft_decor', 'delivery_soft_decor')
                   THEN 1 ELSE 0 END) as "softDecorCount",
-              SUM(CASE WHEN
-                  d."fileType" = 'delivery_rendering' OR
-                  d."data"->>'deliveryType' IN ('rendering', 'delivery_rendering')
+              SUM(CASE WHEN pf."fileType" = 'delivery_rendering'
+                  OR pf."data"->>'deliveryType' IN ('rendering', 'delivery_rendering')
                   THEN 1 ELSE 0 END) as "renderingCount",
-              SUM(CASE WHEN
-                  d."fileType" = 'delivery_post_process' OR
-                  d."data"->>'deliveryType' IN ('post_process', 'delivery_post_process')
+              SUM(CASE WHEN pf."fileType" = 'delivery_post_process'
+                  OR pf."data"->>'deliveryType' IN ('post_process', 'delivery_post_process')
                   THEN 1 ELSE 0 END) as "postProcessCount"
-          FROM "ProjectFile" d
-          WHERE d."project" IN (SELECT "objectId" FROM ActiveProjects)
-            AND (d."isDeleted" IS NULL OR d."isDeleted" = false)
-            AND (
-                d."fileType" LIKE 'delivery_%' OR
-                d."data"->>'uploadStage' = 'delivery'
-            )
-          GROUP BY COALESCE(d."data"->>'spaceId', d."data"->>'productId')
+          FROM "ProjectFile" pf
+          WHERE pf."project" IN (SELECT "objectId" FROM ActiveProjectIds)
+            AND (pf."isDeleted" IS NULL OR pf."isDeleted" = false)
+            AND (pf."fileType" LIKE 'delivery_%' OR pf."data"->>'uploadStage' = 'delivery')
+          GROUP BY COALESCE(pf."data"->>'spaceId', pf."data"->>'productId')
       )
       SELECT
           ps."projectId",
@@ -212,16 +216,26 @@ async function handler(request, response) {
       LIMIT 50
     `;
 
-    // --- 执行 SQL ---
+    // --- 执行 SQL (并行优化) ---
     const queryParams = [companyId, rangeStart, rangeEnd];
 
-    const [workloadResult, projectsResult, spaceStatsResult, issuesResult] = await Promise.all([
+    const [workloadResult, projectsResult, projectTeamsResult, spaceStatsResult, issuesResult] = await Promise.all([
       Psql.query(workloadSql, [companyId]),
       Psql.query(projectsSql, queryParams),
+      Psql.query(projectTeamsSql, queryParams),
       Psql.query(spaceStatsSql, queryParams),
       Psql.query(issuesSql, queryParams)
     ]);
 
+    // 构建项目团队成员映射
+    const projectTeamsMap = {};
+    projectTeamsResult.forEach(row => {
+      projectTeamsMap[row.projectId] = {
+        designerName: row.designerName || '待分配',
+        designerIds: row.designerIds || []
+      };
+    });
+
     // --- 格式化数据 ---
 
     // 1. Workload
@@ -248,6 +262,9 @@ async function handler(request, response) {
     // 2. Projects
     const spaceAssigneeMap = {};
     const projects = projectsResult.map(p => {
+      // 从映射中获取设计师信息
+      const teamInfo = projectTeamsMap[p.id] || { designerName: '待分配', designerIds: [] };
+
       // 解析设计师分配信息
       if (p.projectDate && p.projectDate.designerAssignmentStats) {
           const stats = p.projectDate.designerAssignmentStats;
@@ -292,8 +309,8 @@ async function handler(request, response) {
         daysLeft: Math.ceil(days),
         isOverdue: days < 0,
         statusStr,
-        designerName: p.designerName || '待分配',
-        designerIds: p.designerIds || [],
+        designerName: teamInfo.designerName,
+        designerIds: teamInfo.designerIds,
         // 扩展字段
         data: data
       };