package com.kaidi.oa.web; import com.kaidi.oa.common.ApiResp; import com.kaidi.oa.domain.FormInstance; import com.kaidi.oa.domain.SysUser; import com.kaidi.oa.repository.FormInstanceRepository; import com.kaidi.oa.repository.ProjectRepository; import com.kaidi.oa.service.AuthorizationService; import com.kaidi.oa.service.WorkflowService; import com.kaidi.oa.web.AlertController.Alert; import jakarta.servlet.http.HttpServletRequest; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * Mobile work-bench aggregation (Request.MD 3.10 移动工作台后端聚合). Folds the * counts and short feeds the mobile home screen needs into a single response: * pending tasks, platform alerts, danger-level alerts and project total, plus * the first five alerts and first five todos. Alert aggregation is reused from * AlertController to avoid re-implementing the warning logic. */ @RestController @RequestMapping("/api/oa/mobile") public class MobileController { /** Maximum number of items in each short preview list. */ private static final int PREVIEW_LIMIT = 5; private final FormInstanceRepository instanceRepo; private final ProjectRepository projectRepo; private final AlertController alertController; private final CurrentUserResolver currentUser; private final AuthorizationService authz; private final WorkflowService workflowService; public MobileController(FormInstanceRepository instanceRepo, ProjectRepository projectRepo, AlertController alertController, CurrentUserResolver currentUser, AuthorizationService authz, WorkflowService workflowService) { this.instanceRepo = instanceRepo; this.projectRepo = projectRepo; this.alertController = alertController; this.currentUser = currentUser; this.authz = authz; this.workflowService = workflowService; } @GetMapping("/summary") public ApiResp> summary(HttpServletRequest http) { // 平台预警里含证件PII/预算/履约等敏感聚合,与 /alerts 同源——移动端汇总对非 ADMIN/APPROVER // 一律剥除预警内容与计数,只保留本人待办/项目数,避免移动端成为敏感读门槛的旁路。 SysUser su = currentUser.resolve(http); Set roles = su == null ? Collections.emptySet() : authz.roleCodesOf(su.getId()); boolean canSeeAlerts = roles.contains("ADMIN") || roles.contains("APPROVER"); // Pending tasks: same todo definition as TaskController (待办 / 办理中 / // 已退回 form instances)。**按查看者过滤为"本人发起或本人当前办理"的单据**——否则该聚合端点 // 会把全库待办的标题/类别/审批节点端给任意 USER,旁路 form-instance 详情的对象级读门槛(读 IDOR)。 List todoInstances = new ArrayList<>(); for (FormInstance it : instanceRepo.findAll()) { String st = it.getStatus(); boolean pending = "待办".equals(st) || "办理中".equals(st) || "已退回".equals(st); if (!pending) { continue; } boolean mine = authz.isOwner(su, it.getOriginUserId(), it.getOriginUser()) || workflowService.isCurrentHandler(it.getId(), su); if (mine) { todoInstances.add(it); } } // Platform alerts reused from AlertController.aggregate() —— 仅对有权看敏感聚合的角色计算/返回。 List alerts = canSeeAlerts ? alertController.aggregate() : Collections.emptyList(); int dangerCount = 0; for (Alert a : alerts) { if ("danger".equals(a.level())) { dangerCount++; } } Map data = new LinkedHashMap<>(); data.put("todoCount", todoInstances.size()); data.put("alertCount", alerts.size()); data.put("dangerCount", dangerCount); data.put("projectCount", (int) projectRepo.count()); data.put("alerts", preview(alerts)); data.put("todos", todoPreview(todoInstances)); return ApiResp.ok(data); } /** First five alerts, untouched. */ private List preview(List alerts) { return alerts.size() > PREVIEW_LIMIT ? new ArrayList<>(alerts.subList(0, PREVIEW_LIMIT)) : alerts; } /** First five todos projected to a compact map for the mobile list. */ private List> todoPreview(List todos) { List> out = new ArrayList<>(); int limit = Math.min(PREVIEW_LIMIT, todos.size()); for (int i = 0; i < limit; i++) { FormInstance it = todos.get(i); Map m = new LinkedHashMap<>(); m.put("id", it.getId()); m.put("title", it.getTitle()); m.put("category", it.getCategory()); m.put("status", it.getStatus()); m.put("currentNode", it.getCurrentNode()); out.add(m); } return out; } }