package com.yunhai.flowcenter.service; import com.yunhai.flowcenter.engine.BranchScope; import com.yunhai.flowcenter.engine.FlowDefModel; import com.yunhai.flowcenter.engine.FlowEngine; import com.yunhai.flowcenter.entity.FlwBranch; import com.yunhai.flowcenter.entity.FlwCc; import com.yunhai.flowcenter.entity.FlwDef; import com.yunhai.flowcenter.entity.FlwInstance; import com.yunhai.flowcenter.entity.FlwRouteLog; import com.yunhai.flowcenter.entity.FlwTask; import com.yunhai.flowcenter.enums.FlwEnums; import com.yunhai.flowcenter.model.FlowModels; import com.yunhai.flowcenter.repository.FlwBranchRepository; import com.yunhai.flowcenter.repository.FlwCcRepository; import com.yunhai.flowcenter.repository.FlwDefRepository; import com.yunhai.flowcenter.repository.FlwInstanceRepository; import com.yunhai.flowcenter.repository.FlwRouteLogRepository; import com.yunhai.flowcenter.repository.FlwTaskRepository; import com.yunhai.platform.apistd.BizException; import com.yunhai.platform.utils.exception.ErrorCode; import com.yunhai.platform.utils.model.BasePageResult; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * 用户面查询(我的办件/待办/已办/办件详情)。 */ @Service @RequiredArgsConstructor public class FlowQueryService { private final FlwInstanceRepository instanceRepository; private final FlwTaskRepository taskRepository; private final FlwBranchRepository branchRepository; private final FlwCcRepository ccRepository; private final FlwRouteLogRepository routeLogRepository; private final FlwDefRepository defRepository; private final FlowDeptAdminService deptAdminService; private final FlowEngine flowEngine; private final BizUrlResolver bizUrlResolver; public BasePageResult pageMy(Long schId, Integer submitterId, Integer status, String appKey, Integer page, Integer size) { Page p = instanceRepository.pageMy(schId, submitterId, status, emptyToNull(appKey), PageRequest.of(normPage(page), normSize(size))); return toResult(p.getTotalElements(), p.getContent().stream().map(this::toInstanceItem) .collect(Collectors.toList()), page, size); } /** 抄送我的(NOTIFY 落库行,sent_time 倒序;知会不占待办,独立于 todo/done 口径) */ public BasePageResult pageCc(Long schId, Integer userId, String appKey, Integer page, Integer size) { Page p = ccRepository.pageCc(schId, userId, emptyToNull(appKey), PageRequest.of(normPage(page), normSize(size))); List items = p.getContent().stream() .map(this::toCcItem).collect(Collectors.toList()); fillCcInstanceInfo(items); return toResult(p.getTotalElements(), items, page, size); } private FlowModels.CcItem toCcItem(FlwCc cc) { FlowModels.CcItem item = new FlowModels.CcItem(); item.setCcId(cc.getId()); item.setInstanceId(cc.getInstanceId()); item.setNodeId(cc.getNodeId()); item.setNodeName(cc.getNodeName()); item.setUserId(cc.getUserId()); item.setUserName(cc.getUserName()); item.setSentTime(str(cc.getSentTime())); return item; } private void fillCcInstanceInfo(List items) { for (FlowModels.CcItem item : items) { FlwInstance instance = instanceRepository.findById(item.getInstanceId()).orElse(null); if (instance != null) { item.setBizTitle(instance.getBizTitle()); item.setBizType(instance.getBizType()); item.setBizId(instance.getBizId()); item.setInstanceStatus(instance.getStatus()); } } } public BasePageResult pageTodo(Long schId, Integer assigneeId, String appKey, Integer page, Integer size) { Page p = taskRepository.pageTodo(schId, assigneeId, emptyToNull(appKey), PageRequest.of(normPage(page), normSize(size))); List items = p.getContent().stream() .map(this::toTaskItem).collect(Collectors.toList()); fillInstanceInfo(items); return toResult(p.getTotalElements(), items, page, size); } /** 已办:一个办件一行(保留本人最后办理的那条),并回填累计办理次数 */ public BasePageResult pageDone(Long schId, Integer assigneeId, String appKey, Integer page, Integer size) { List handled = FlwEnums.TaskStatus.handledCodes(); Page p = taskRepository.pageDone(schId, assigneeId, handled, emptyToNull(appKey), PageRequest.of(normPage(page), normSize(size))); List items = p.getContent().stream() .map(this::toTaskItem).collect(Collectors.toList()); fillInstanceInfo(items); fillDoneCount(items, assigneeId, handled); return toResult(p.getTotalElements(), items, page, size); } /** 已办行的累计办理次数(同一办件多节点办理/退回重办时的"共办理 N 次") */ private void fillDoneCount(List items, Integer assigneeId, List handled) { if (items.isEmpty()) { return; } List ids = items.stream().map(FlowModels.TaskItem::getInstanceId) .distinct().collect(Collectors.toList()); Map counts = new HashMap<>(); for (Object[] row : taskRepository.countDoneByInstance(ids, assigneeId, handled)) { counts.put((Long) row[0], ((Number) row[1]).intValue()); } for (FlowModels.TaskItem item : items) { item.setDoneCount(counts.getOrDefault(item.getInstanceId(), 1)); } } /** * 办件详情:快照 + 任务轨迹 + 分支进度 + 本人待办标记(办理页/详情页共用数据源)。 * viewerId 必须由控制器从 BoaUser.getId() 传入(CAS 会话 LoginUser.userId 是登录名字符串, * 不是数字主键,不能直接转 Integer——2026-09-12 全链测试修复)。 */ public FlowModels.InstanceDetail detail(Long schId, Long instanceId, Integer viewerId) { FlwInstance instance = instanceRepository.findById(instanceId) .orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "办件不存在")); if (!instance.getSchId().equals(schId)) { throw new BizException(ErrorCode.FORBIDDEN, "办件不属于该学校"); } FlowModels.InstanceDetail detail = new FlowModels.InstanceDetail(); detail.setInstanceId(instance.getId()); detail.setSchId(instance.getSchId()); detail.setDefCode(instance.getDefCode()); detail.setDefVersion(instance.getDefVersion()); detail.setBizType(instance.getBizType()); detail.setBizId(instance.getBizId()); detail.setBizTitle(instance.getBizTitle()); detail.setBizUrl(instance.getBizUrl()); detail.setBizUrlAbs(bizUrlResolver.resolve( instance.getApplyAppCode(), instance.getApplyAppKey(), instance.getBizUrl())); detail.setBizSnapshot(instance.getBizSnapshot()); detail.setFormDataJson(instance.getFormDataJson()); detail.setStatus(instance.getStatus()); detail.setCurrentNodes(instance.getCurrentNodes()); detail.setSubmitterId(instance.getSubmitterId()); detail.setSubmitterName(instance.getSubmitterName()); detail.setSubmitTime(str(instance.getSubmitTime())); detail.setFinishTime(str(instance.getFinishTime())); detail.setVersion(instance.getVersion()); List tasks = taskRepository.findByInstanceIdOrderByIdAsc(instanceId); List ccs = ccRepository.findByInstanceIdOrderByIdAsc(instanceId); List routeLogs = routeLogRepository.findByInstanceIdOrderByIdAsc(instanceId); FlwDef def = defRepository.findById(instance.getDefId()).orElse(null); FlowDefModel model = def == null ? null : flowEngine.parseModel(def); detail.setTrace(buildTrace(instance, tasks, ccs, routeLogs, model)); List> branches = new ArrayList<>(); for (FlwBranch b : branchRepository.findByInstanceId(instanceId)) { Map row = new HashMap<>(); row.put("branchKey", b.getBranchKey()); row.put("status", b.getStatus()); branches.add(row); } detail.setBranches(branches); List myPending = new ArrayList<>(); if (viewerId != null) { for (FlwTask t : tasks) { if (viewerId.equals(t.getAssigneeId()) && t.getStatus() == FlwEnums.TaskStatus.PENDING.getCode()) { myPending.add(t.getId()); } } detail.setMyPendingTask(!myPending.isEmpty()); detail.setMyPendingTaskIds(myPending); } detail.setNodeSummary(def == null ? "" : FlowEngine.nodeSummary(model)); // 办理面板数据(出口/退回目标/移交标记):仅对持本人待办的查看者填充 FlwTask myPend = viewerId == null ? null : tasks.stream() .filter(t -> viewerId.equals(t.getAssigneeId()) && t.getStatus() == FlwEnums.TaskStatus.PENDING.getCode()) .findFirst().orElse(null); if (myPend != null && def != null) { String curNodeId = myPend.getNodeId(); List outs = model.outEdges(curNodeId); if (outs.size() > 1) { List> outlets = new ArrayList<>(); for (FlowDefModel.Edge e : outs) { Map o = new HashMap<>(); o.put("name", e.name); o.put("toNodeId", e.to); FlowDefModel.Node to = model.node(e.to); o.put("toNodeName", to == null ? e.to : (to.name == null || to.name.isEmpty() ? e.to : to.name)); outlets.add(o); } detail.setOutlets(outlets); } else { detail.setOutlets(new ArrayList<>()); } // 可退回目标:办件已办过的主线审批节点(去重,排除当前节点) Map> targets = new java.util.LinkedHashMap<>(); for (FlwTask t : tasks) { if (t.getStatus() != FlwEnums.TaskStatus.APPROVED.getCode() || t.getNodeId().equals(myPend.getNodeId()) || BranchScope.keyOf(model, t.getNodeId()) != null) { continue; } FlowDefModel.Node node = model.node(t.getNodeId()); if (node != null && FlowDefModel.TYPE_APPROVAL.equals(node.type) && !targets.containsKey(t.getNodeId())) { Map r = new HashMap<>(); r.put("nodeId", node.id); r.put("nodeName", node.name == null || node.name.isEmpty() ? node.id : node.name); targets.put(node.id, r); } } detail.setReturnTargets(new ArrayList<>(targets.values())); detail.setCanTransfer(true); } else { detail.setOutlets(new ArrayList<>()); detail.setReturnTargets(new ArrayList<>()); } return detail; } /** 任务定位办件详情(办理页 /task/{taskId} 数据源:UserMsg jumpPath 只带 taskId) */ public FlowModels.InstanceDetail taskDetail(Long schId, Long taskId, Integer viewerId) { FlwTask task = taskRepository.findById(taskId) .orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "任务不存在:" + taskId)); if (!task.getSchId().equals(schId)) { throw new BizException(ErrorCode.FORBIDDEN, "任务不属于该学校"); } return detail(schId, task.getInstanceId(), viewerId); } private FlowModels.InstanceItem toInstanceItem(FlwInstance instance) { FlowModels.InstanceItem item = new FlowModels.InstanceItem(); item.setInstanceId(instance.getId()); item.setBizType(instance.getBizType()); item.setBizId(instance.getBizId()); item.setBizTitle(instance.getBizTitle()); item.setStatus(instance.getStatus()); item.setCurrentNodes(instance.getCurrentNodes()); item.setSubmitterName(instance.getSubmitterName()); item.setSubmitTime(str(instance.getSubmitTime())); item.setFinishTime(str(instance.getFinishTime())); item.setDefVersion(instance.getDefVersion()); return item; } /** * 流程轨迹(2026-09-18 重排为"时间流"):审批任务行 + 留痕事件行(6 发起/重新提交、7 撤回) * 合成一条按时间升序的轨迹,替掉原先"按 id 升序"的排法(id 序在撤回重走场景下与 * 真实发生顺序不一致)。 * *

2026-09-23 并入抄送行(flw_cc,kind=CC):NOTIFY 节点不建任务,若不并入则抄送在 * 轨迹上不可见;按 sentTime 与任务行同场归位、参与轮次进位(退回重走的第 N 轮抄送)。 * *

2026-09-24 并入路由判断行(flw_route_log,kind=ROUTE):CONDITION 判断留痕 * "系统判断 → 出口"(outlet=NULL 渲染为挂起);按 decideTime 同场归位。 * *

每条行补四个前端直接用、不再自行推断的字段: *

    *
  • {@code time} 该行实际发生的时间(已办/退回/移交/撤回=handleTime,在办/失效=arriveTime,抄送=sentTime,判断=decideTime);
  • *
  • {@code kind} SUBMIT|RESUBMIT|WITHDRAW|APPROVE|REJECT|RETURN|TRANSFER|INVALID|PENDING|CC|ROUTE * (前端配色与文案的唯一依据);
  • *
  • {@code round} 第几轮审批(遇"重新提交"进位,撤回属于它结束的那一轮);
  • *
  • {@code rowKey} 去重 key(任务行 t{id}、抄送行 cc{id}、合成行 submit —— taskId 可能为 null 不能当 key)。
  • *
* 科室口径(视图两档:dept/user)另需 assigneeType/deptKey/deptName —— 只有 DEPT_KEY 节点有 * 科室概念,故仅对该类节点回填;节点在定义里找不到(重提后老节点、留痕事件行)不回填, * 前端回落人名显示。 * *

老办件(改造前发起)没有"发起"留痕行,按办件快照的 submitTime/submitterName 合成一条 * 兜底插入到时间流对应位置,保证轨迹首条仍是"发起申请"。 */ private List> buildTrace(FlwInstance instance, List tasks, List ccs, List routeLogs, FlowDefModel model) { // 任务行、判断行与抄送行同场按发生时间排序:时间只到秒(DATETIME),同秒平局任务行恒在前、 // 判断行次之(办理→判断→抄送的因果序)、抄送行最后(秩隔开跨表 id 空间;稳排保证同源行相对次序不变)。 // 无判断/抄送的办件所有行 source 恒 0,排序与旧版 (time,id) 严格等价—— // 不能给任务行再分因果秩:驳回行与重提行同秒是常态,旧序按 id(真实先后),秩会颠倒。 List rows = new ArrayList<>(); Map deptNames = new HashMap<>(); for (FlwTask t : tasks) { rows.add(new TimedRow(happenedAt(t), 0, t.getId(), taskTraceRow(t, model, instance, deptNames))); } for (FlwRouteLog r : routeLogs) { rows.add(new TimedRow(r.getDecideTime(), 1, r.getId(), routeTraceRow(r))); } for (FlwCc c : ccs) { rows.add(new TimedRow(c.getSentTime(), 2, c.getId(), ccTraceRow(c, model, instance, deptNames))); } rows.sort(Comparator.comparing((TimedRow r) -> r.time, Comparator.nullsLast(Comparator.naturalOrder())) .thenComparing(r -> r.source) .thenComparing(r -> r.seq)); List> trace = new ArrayList<>(); int headIndex = 0; LocalDateTime submitTime = instance.getSubmitTime(); for (TimedRow r : rows) { trace.add(r.row); // 老办件兜底行的插入位:时间早于提交时间的行数(老办件里恒为 0;重提过的老办件按时间放在中段) if (submitTime != null && r.time != null && r.time.isBefore(submitTime)) { headIndex = trace.size(); } } if (!hasSubmitRow(tasks)) { trace.add(headIndex, syntheticSubmitRow(instance)); } int round = 1; for (Map row : trace) { if ("RESUBMIT".equals(row.get("kind"))) { round++; } row.put("round", round); } return trace; } private Map taskTraceRow(FlwTask t, FlowDefModel model, FlwInstance instance, Map deptNames) { Map row = new HashMap<>(); row.put("rowKey", "t" + t.getId()); row.put("taskId", t.getId()); row.put("kind", kindOf(t)); row.put("nodeId", t.getNodeId()); row.put("nodeName", t.getNodeName()); row.put("assigneeId", t.getAssigneeId()); row.put("assigneeName", t.getAssigneeName()); row.put("status", t.getStatus()); row.put("opinion", t.getOpinion()); row.put("arriveTime", str(t.getArriveTime())); row.put("handleTime", str(t.getHandleTime())); row.put("time", str(happenedAt(t))); row.put("deadline", str(t.getDeadline())); row.put("urgeCount", t.getUrgeCount()); row.put("edgeName", t.getEdgeName()); fillAssigneeScope(row, model, t.getNodeId(), instance.getSchId(), deptNames); return row; } /** 抄送轨迹行(kind=CC):no taskId/opinion,接收人记在 assignee* 字段(前端复用人列)。 * 2026-10-12 补科室口径:抄送节点用 receivers,走 {@link #fillCcScope} 富化 assigneeType, * 前端 dept 档据此显科室名 / "抄送对象",不再回落接收人真实姓名。 */ private Map ccTraceRow(FlwCc c, FlowDefModel model, FlwInstance instance, Map deptNames) { Map row = new HashMap<>(); row.put("rowKey", "cc" + c.getId()); row.put("ccId", c.getId()); row.put("kind", "CC"); row.put("nodeId", c.getNodeId()); row.put("nodeName", c.getNodeName() == null || c.getNodeName().isEmpty() ? "抄送" : c.getNodeName()); row.put("assigneeId", c.getUserId()); row.put("assigneeName", c.getUserName()); row.put("status", null); row.put("time", str(c.getSentTime())); fillCcScope(row, model, c.getNodeId(), instance.getSchId(), deptNames); return row; } /** 路由判断轨迹行(kind=ROUTE,2026-09-24):"系统判断 → 出口",outlet=NULL 渲染为挂起 */ private static Map routeTraceRow(FlwRouteLog r) { Map row = new HashMap<>(); row.put("rowKey", "r" + r.getId()); row.put("routeLogId", r.getId()); row.put("kind", "ROUTE"); row.put("nodeId", r.getNodeId()); row.put("nodeName", r.getNodeName() == null || r.getNodeName().isEmpty() ? "条件判断" : r.getNodeName()); row.put("routerKey", r.getRouterKey()); row.put("outlet", r.getOutlet()); row.put("attempt", r.getAttempt()); row.put("status", null); row.put("time", str(r.getDecideTime())); // 议题三②:调试弹窗「从路由日志导入」需要看失败摘要(>500 截断) row.put("rawResponse", r.getRawResponse() == null ? "" : (r.getRawResponse().length() > 500 ? r.getRawResponse().substring(0, 500) + "…" : r.getRawResponse())); return row; } /** 排序载体:发生时间 + 来源秩(任务 0/判断 1/抄送 2)+ 同表内序(id),跨表 id 不比大小 */ private static final class TimedRow { final LocalDateTime time; final int source; final long seq; final Map row; TimedRow(LocalDateTime time, int source, long seq, Map row) { this.time = time; this.source = source; this.seq = seq; this.row = row; } } private static boolean hasSubmitRow(List tasks) { for (FlwTask t : tasks) { if (FlwEnums.TaskStatus.NODE_SUBMIT.equals(t.getNodeId())) { return true; } } return false; } /** 老办件兜底:改造前发起不留痕,用快照补一条"发起申请"(taskId 为空,前端按 rowKey 去重) */ private static Map syntheticSubmitRow(FlwInstance instance) { Map row = new HashMap<>(); row.put("rowKey", "submit"); row.put("taskId", null); row.put("kind", "SUBMIT"); row.put("nodeId", FlwEnums.TaskStatus.NODE_SUBMIT); row.put("nodeName", "发起申请"); row.put("assigneeId", instance.getSubmitterId()); row.put("assigneeName", instance.getSubmitterName()); row.put("status", FlwEnums.TaskStatus.SUBMITTED.getCode()); row.put("time", str(instance.getSubmitTime())); row.put("synthetic", true); return row; } /** 该行实际发生的时间:办结过看 handleTime,否则看 arriveTime */ private static LocalDateTime happenedAt(FlwTask t) { return t.getHandleTime() != null ? t.getHandleTime() : t.getArriveTime(); } private static String kindOf(FlwTask t) { int s = t.getStatus() == null ? FlwEnums.TaskStatus.PENDING.getCode() : t.getStatus(); if (s == FlwEnums.TaskStatus.SUBMITTED.getCode()) { return FlwEnums.TaskStatus.NODE_RESUBMIT.equals(t.getNodeId()) ? "RESUBMIT" : "SUBMIT"; } if (s == FlwEnums.TaskStatus.WITHDRAWN.getCode()) { return "WITHDRAW"; } if (s == FlwEnums.TaskStatus.APPROVED.getCode()) { return "APPROVE"; } if (s == FlwEnums.TaskStatus.REJECTED.getCode()) { return "REJECT"; } if (s == FlwEnums.TaskStatus.INVALIDATED.getCode()) { return "INVALID"; } if (s == FlwEnums.TaskStatus.RETURNED.getCode()) { return "RETURN"; } if (s == FlwEnums.TaskStatus.TRANSFERRED.getCode()) { return "TRANSFER"; } return "PENDING"; } /** * 科室口径富化:节点是 DEPT_KEY(科室办理)时带上 deptKey + 映射里的科室名, * 前端"按科室显示"据此把人名换成科室名;USERS(指定人员)节点只带 assigneeType, * 由前端显示"指定办理人"(口径同示意图的科室视图)。 */ private void fillAssigneeScope(Map row, FlowDefModel model, String nodeId, Long schId, Map deptNames) { FlowDefModel.Node node = model == null ? null : model.node(nodeId); FlowDefModel.Assignee assignee = node == null ? null : node.assignee; applyAssigneeScope(row, assignee, schId, deptNames); } /** * 抄送行(kind=CC)科室口径富化(2026-10-12):NOTIFY 节点的接收人存在 {@code receivers} * 字段而非 {@code assignee},故不能直接复用 {@link #fillAssigneeScope}——否则取到的 assignee * 恒为 null,dept 档下抄送行只能回落接收人真实姓名(原先的泄露点)。 * 节点在定义里找不到(重提后的老节点、已删节点)不填,前端统一显示"抄送对象"。 */ private void fillCcScope(Map row, FlowDefModel model, String nodeId, Long schId, Map deptNames) { FlowDefModel.Node node = model == null ? null : model.node(nodeId); FlowDefModel.Assignee receivers = node == null ? null : node.receivers; applyAssigneeScope(row, receivers, schId, deptNames); } /** 两种口径的公共落值:assigneeType 必填;DEPT_KEY 另填 deptKey + 映射里的科室名 */ private void applyAssigneeScope(Map row, FlowDefModel.Assignee assignee, Long schId, Map deptNames) { if (assignee == null || assignee.type == null) { return; } row.put("assigneeType", assignee.type); if (!FlowDefModel.ASSIGNEE_DEPT_KEY.equals(assignee.type) || assignee.deptKey == null) { return; } row.put("deptKey", assignee.deptKey); String deptName = deptNames.computeIfAbsent(assignee.deptKey, k -> DeptLabelSupport.deptName(deptAdminService, schId, k)); if (deptName != null) { row.put("deptName", deptName); } } private FlowModels.TaskItem toTaskItem(FlwTask t) { FlowModels.TaskItem item = new FlowModels.TaskItem(); item.setTaskId(t.getId()); item.setInstanceId(t.getInstanceId()); item.setNodeId(t.getNodeId()); item.setNodeName(t.getNodeName()); item.setAssigneeName(t.getAssigneeName()); item.setStatus(t.getStatus()); item.setOpinion(t.getOpinion()); item.setArriveTime(str(t.getArriveTime())); item.setHandleTime(str(t.getHandleTime())); item.setDeadline(str(t.getDeadline())); item.setUrgeCount(t.getUrgeCount()); return item; } private void fillInstanceInfo(List items) { for (FlowModels.TaskItem item : items) { FlwInstance instance = instanceRepository.findById(item.getInstanceId()).orElse(null); if (instance != null) { item.setBizTitle(instance.getBizTitle()); item.setBizType(instance.getBizType()); item.setBizId(instance.getBizId()); item.setInstanceStatus(instance.getStatus()); item.setSubmitterName(instance.getSubmitterName()); } } } /** * 展示时间统一截断到秒:时间列已升毫秒精度(DATETIME(3),轨迹交织排序用), * 对外展示口径保持秒级不变(存量前端零适配);排序用未截断的原值。 */ private static String str(LocalDateTime t) { return t == null ? null : t.truncatedTo(java.time.temporal.ChronoUnit.SECONDS).toString(); } private static int normPage(Integer page) { return page == null || page < 1 ? 0 : page - 1; } /** 空串按「不过滤」处理(前端清筛选可能传空值,避免误筛成空列表) */ private static String emptyToNull(String s) { return s == null || s.trim().isEmpty() ? null : s.trim(); } private static int normSize(Integer size) { return size == null || size < 1 || size > 100 ? 10 : size; } private static BasePageResult toResult(long total, List content, Integer page, Integer size) { int pageIndex = page == null || page < 1 ? 1 : page; int pageSize = size == null || size < 1 || size > 100 ? 10 : size; int totalPages = (int) Math.ceil(total / (double) pageSize); return new BasePageResult<>(content, (int) total, pageIndex, pageSize, totalPages, pageIndex < totalPages); } }