package com.yunhai.svchall.service.impl; import com.fasterxml.jackson.databind.ObjectMapper; import com.yunhai.platform.apistd.BizException; import com.yunhai.platform.utils.exception.ErrorCode; import com.yunhai.platform.utils.model.BoaUser; import com.yunhai.svchall.entity.ServiceApply; import com.yunhai.svchall.entity.ServiceForm; import com.yunhai.svchall.entity.ServiceItem; import com.yunhai.svchall.enums.ApplyFlowStatusEnum; import com.yunhai.svchall.enums.ServiceItemStatusEnum; import com.yunhai.svchall.model.svc.SvcModels; import com.yunhai.svchall.model.svc.SvcModels.ApplyPageQuery; import com.yunhai.svchall.model.svc.SvcModels.ApplySubmitRequest; import com.yunhai.svchall.model.svc.SvcModels.ApplyVo; import com.yunhai.svchall.repository.ServiceApplyRepository; import com.yunhai.svchall.repository.ServiceItemRepository; import com.yunhai.svchall.service.FlowClient; import com.yunhai.svchall.service.SiteDirectory; import com.yunhai.svchall.service.SiteDirectory.SiteInfo; import com.yunhai.svchall.service.SiteGuard; import com.yunhai.svchall.utils.FormValidator; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionSynchronizationAdapter; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.StringUtils; import javax.persistence.criteria.Predicate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 办件服务(V2.0 Part B.4 + 验收报告 §四 D9/§四.2 范式对齐):用户发起(事项/表单快照落库 → * AFTER_COMMIT 后独立线程 FlowClient 代发起 → 回调驱动镜像状态)、我的办件/详情、管理端分页、 * 重试(复用同一 bizId 命中 flow-center uk 幂等)。提交纪律:快照自包含(item_snapshot_json/ * form_data_json),事项后续改名/下架不影响在途与历史办件;代发起出站不进 DB 事务(HTTP 长调用 * 拖住连接 + 回调先于提交到达找不到行),成功回填走窄列 markApproving(终态不回退)。 * 用户面撤回/催办/审批轨迹由前端直调 flow-center CAS 接口(同主域 cookie),本服务不代理。 */ @Slf4j @Service @RequiredArgsConstructor public class ServiceApplyService { private final ServiceApplyRepository applyRepository; private final ServiceItemRepository itemRepository; private final ServiceFormService formService; private final AttachmentService attachmentService; private final FlowClient flowClient; private final SiteGuard siteGuard; private final SiteDirectory siteDirectory; private final com.yunhai.svchall.service.ApplyViewGuard applyViewGuard; private final ObjectMapper objectMapper; private final org.springframework.transaction.support.TransactionTemplate transactionTemplate; /** 办事大厅前端对外地址(bizUrl 落地页用;projman 注入,本地默认 dev server) */ @Value("${svchall.front.base-url:http://localhost:8087}") private String frontBaseUrl; // ===== 用户面(CAS)===== /** * 发起:本校成员 + 事项发布态 + 表单数据强校验 → 落 service_apply(PENDING, bizId=id) * → AFTER_COMMIT 后独立线程代发起(失败保留 PENDING 行,重试复用同一 id)。 * 返回办件 VO(PENDING 态——代发起异步执行,状态秒级翻转,前端以"待提交/审批中"刷新可见)。 */ @Transactional public ApplyVo submit(BoaUser user, ApplySubmitRequest req) { if (user == null || user.getId() == null) { throw new BizException(ErrorCode.PARAM_ERROR, "未获取到登录用户"); } SiteInfo site = siteDirectory.requireSite(req.getSiteCode()); siteGuard.requireSchoolMember(user, site.getSchId()); ServiceItem item = itemRepository .findBySchIdAndSiteCodeAndItemCode(site.getSchId(), site.getSiteCode(), req.getItemCode()) .orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "事项不存在")); if (item.getStatus() == null || item.getStatus() != ServiceItemStatusEnum.PUBLISHED.getCode()) { throw new BizException(ErrorCode.PARAM_ERROR, "事项未发布,不可办理"); } if (item.getFlowGuid() == null || item.getFlowGuid().isEmpty()) { throw new BizException(ErrorCode.PARAM_ERROR, "该事项未开通在线办理"); } // 表单解析 + 提交数据强校验(按最新已发布版本;发起时快照 form_version) ServiceForm form = null; if (item.getFormCode() != null && !item.getFormCode().isEmpty()) { form = formService.latestPublished(site.getSchId(), item.getFormCode()) .orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "办理表单未发布,请联系管理员(事项 " + item.getItemCode() + ")")); FormValidator.validateSubmit(req.getFormData(), form.getSchemaJson()); } else if (StringUtils.hasText(req.getFormData())) { throw new BizException(ErrorCode.PARAM_ERROR, "该事项无办理表单,不应提交表单数据"); } ServiceApply apply = new ServiceApply(); apply.setSchId(site.getSchId()); apply.setSiteCode(site.getSiteCode()); apply.setItemId(item.getId()); apply.setItemCode(item.getItemCode()); apply.setItemTitle(item.getTitle()); apply.setItemSnapshotJson(itemSnapshotJson(item)); apply.setFormVersion(form == null ? null : form.getVersion()); apply.setFormDataJson(req.getFormData()); apply.setSubmitterId(user.getId()); apply.setSubmitterName(user.getName()); apply.setFlowGuid(item.getFlowGuid()); apply = applyRepository.save(apply); // 办件材料绑定(必须落库拿到 applyId 之后再绑) attachmentService.bindApply(site.getSchId(), apply.getId(), req.getAttachments()); apply.setItemSnapshotJson(itemSnapshotJson(item, attachmentService .applyAttachments(site.getSchId(), apply.getId()))); apply = applyRepository.save(apply); flowSubmitAfterCommit(apply.getId()); return stampFlowEnabled(ApplyVo.of(apply)); } /** 重试:PENDING 行复用同一 bizId 再次代发起(幂等);仅提交人本人可重试自己的办件(同步路径,无长事务) */ public ApplyVo retry(BoaUser user, Integer applyId) { ServiceApply apply = applyRepository.findByIdAndSchId(applyId, userSchId(user)) .orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "办件不存在")); if (!user.getId().equals(apply.getSubmitterId())) { throw new BizException(ErrorCode.FORBIDDEN, "仅可重试自己的办件"); } if (apply.getFlowStatus() == null || apply.getFlowStatus() != ApplyFlowStatusEnum.PENDING.getCode()) { throw new BizException(ErrorCode.PARAM_ERROR, "该办件不在待提交状态,无需重试"); } doFlowApply(applyId, false); return stampFlowEnabled(applyRepository.findById(applyId).map(ApplyVo::of).orElse(ApplyVo.of(apply))); } /** * 一键重提(P2-5/BP-R2-2):驳回(30)/撤回(40)后复用同一办件行重新发起——flow-center R4 * 按 bizType+bizId 命中 30/40 → 重启推进(定义刷新、快照覆盖、version 自增、轨迹跨轮连续)。 * 校验:提交人本人 + 终态 30/40 + flowGuid 非空;表单数据沿用原 formData * ("改后重提"属后续产品项,登记于验收报告)。出站失败保持原态可重入。 */ public ApplyVo resubmit(BoaUser user, Integer applyId) { ServiceApply apply = applyRepository.findByIdAndSchId(applyId, userSchId(user)) .orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "办件不存在")); if (!user.getId().equals(apply.getSubmitterId())) { throw new BizException(ErrorCode.FORBIDDEN, "仅可重提自己的办件"); } Integer status = apply.getFlowStatus(); if (status == null || (status != ApplyFlowStatusEnum.REJECTED.getCode() && status != ApplyFlowStatusEnum.WITHDRAWN.getCode())) { throw new BizException(ErrorCode.PARAM_ERROR, "仅驳回/已撤回状态的办件可重新提交"); } if (apply.getFlowGuid() == null || apply.getFlowGuid().isEmpty()) { throw new BizException(ErrorCode.PARAM_ERROR, "该办件未绑定流程,不可重提"); } doFlowApply(applyId, true); return stampFlowEnabled(applyRepository.findById(applyId).map(ApplyVo::of).orElse(ApplyVo.of(apply))); } /** * 撤回(用户面;2026-09-16 治理:原先前端直调 flow-center {@code /api/flow/instance/{id}/withdraw} * ——跨应用 CAS 票据 + CORS 双重外溢,且"谁能撤"落在前端)。现由本服务中转: * ①本应用内定位办件(同校 + 提交人本人 = 权威授权,也是列表自见规则);②状态须审批中(10) 且 * 已有流程实例;③经 FlowClient→SDK 门面调 flow-center 托管件(对端再按 submitterId 复核一次)。 * 撤回后的镜像状态由 flow.withdrawn 回调推进(40),本方法不改本地状态——单一写入口, * 避免"本地先改 + 回调后到"的版本比较歧义。 */ public void withdraw(BoaUser user, Integer applyId) { ServiceApply apply = requireOwnApply(user, applyId, "仅可撤回自己的办件"); if (apply.getFlowStatus() == null || apply.getFlowStatus() != ApplyFlowStatusEnum.APPROVING.getCode()) { throw new BizException(ErrorCode.PARAM_ERROR, "仅审批中的办件可撤回"); } if (apply.getFlowInstanceId() == null) { throw new BizException(ErrorCode.PARAM_ERROR, "该办件尚未进入流程,无需撤回"); } flowClient.withdraw(apply.getSchId(), apply.getFlowInstanceId(), apply.getSubmitterId()); } /** * 审批进度(结构化轨迹):可见性走与详情同一个守卫(本人/管理员/办理人三分支), * 再按 instanceId 取流程中心进度——对端只校验 schId,故守卫必须在本地先做。 * 未进入流程的办件返回空轨迹(前端渲染"待提交"提示),不报错。 */ public SvcModels.FlowProgressVo progress(BoaUser user, Integer applyId) { ServiceApply apply = requireVisibleApply(user, applyId); if (apply.getFlowInstanceId() == null) { SvcModels.FlowProgressVo empty = new SvcModels.FlowProgressVo(); empty.setInstanceId(null); empty.setBizType(FlowCallbackService.BIZ_TYPE); empty.setBizId(String.valueOf(apply.getId())); empty.setBizTitle(apply.getItemTitle()); empty.setStatus(apply.getFlowStatus()); empty.setSubmitterId(apply.getSubmitterId()); empty.setSubmitterName(apply.getSubmitterName()); empty.setSubmitTime(apply.getSubmitTime() == null ? null : apply.getSubmitTime().toString()); empty.setSteps(new ArrayList<>()); return empty; } return flowClient.progress(apply.getSchId(), apply.getFlowInstanceId(), null, null); } /** * 流程示意图(PNG 流):同守卫;instanceId 口径(带运行时节点状态)。 * 出图两步(取免签 token → 取图字节)全部在本服务侧完成,浏览器只拿同源 {@code /api/**} 图片。 */ public byte[] diagramImage(BoaUser user, Integer applyId, String mode) { ServiceApply apply = requireVisibleApply(user, applyId); if (apply.getFlowInstanceId() == null) { throw new BizException(ErrorCode.PARAM_ERROR, "该办件尚未进入流程,无流程图"); } return flowClient.diagramImage(apply.getSchId(), apply.getFlowInstanceId(), null, mode); } /** 本人办件(提交人校验;撤回/重提共用口径) */ private ServiceApply requireOwnApply(BoaUser user, Integer applyId, String forbiddenMsg) { ServiceApply apply = applyRepository.findByIdAndSchId(applyId, userSchId(user)) .orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "办件不存在")); if (!user.getId().equals(apply.getSubmitterId())) { throw new BizException(ErrorCode.FORBIDDEN, forbiddenMsg); } return apply; } /** 可见办件(提交人本人 / 管理员 / 办理人;非超管) */ private ServiceApply requireVisibleApply(BoaUser user, Integer applyId) { if (isSuperAdmin(user)) { return applyRepository.findById(applyId) .orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "办件不存在")); } ServiceApply apply = applyRepository.findByIdAndSchId(applyId, userSchId(user)) .orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "办件不存在")); applyViewGuard.requireVisible(apply, user); return apply; } /** 我的办件(提交人视角,flowStatus 可选筛选) */ public Map myApplies(BoaUser user, Integer flowStatus, Integer page, Integer pageSize) { int pageIndex = page == null || page < 1 ? 0 : page - 1; int size = pageSize == null || pageSize < 1 ? 10 : Math.min(pageSize, 100); Pageable pageable = PageRequest.of(pageIndex, size, Sort.by(Sort.Order.desc("createTime"), Sort.Order.desc("id"))); Specification spec = (root, cq, cb) -> { List predicates = new ArrayList<>(); predicates.add(cb.equal(root.get("schId"), userSchId(user))); predicates.add(cb.equal(root.get("submitterId"), user.getId())); if (flowStatus != null) { predicates.add(cb.equal(root.get("flowStatus"), flowStatus)); } return cb.and(predicates.toArray(new Predicate[0])); }; Page result = applyRepository.findAll(spec, pageable); Map out = new HashMap<>(); out.put("total", result.getTotalElements()); List list = new ArrayList<>(); for (ServiceApply apply : result.getContent()) { list.add(ApplyVo.of(apply)); } out.put("list", list); return out; } /** * 详情:提交人本人 / 管理员(type=1 或 roleId∈{2,5,7})/ 办理人(assigneeIds 实时含当前用户) * 三分支放行(BP-R2-3 修复,ApplyViewGuard=NoticeFlowGuard 同构;原先"同校即放行"越权已收紧)。 */ public ApplyVo detail(BoaUser user, String siteCode, Integer id) { ServiceApply apply; if (isSuperAdmin(user)) { apply = applyRepository.findById(id) .orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "办件不存在")); } else { Long schId = userSchId(user); apply = applyRepository.findByIdAndSchId(id, schId) .orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "办件不存在")); applyViewGuard.requireVisible(apply, user); } return ApplyVo.of(apply); } // ===== 管理面 ===== /** * 发起页数据(本校用户):事项发布态要点 + 绑定表单的已发布 schema。 * 校验链与 submit 一致(同校成员/发布态/已绑流程),前端据此渲染动态表单。 */ public Map applyItem(BoaUser user, String siteCode, String itemCode) { SiteInfo site = siteDirectory.requireSite(siteCode); siteGuard.requireSchoolMember(user, site.getSchId()); ServiceItem item = itemRepository .findBySchIdAndSiteCodeAndItemCode(site.getSchId(), site.getSiteCode(), itemCode) .orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "事项不存在")); if (item.getStatus() == null || item.getStatus() != ServiceItemStatusEnum.PUBLISHED.getCode()) { throw new BizException(ErrorCode.PARAM_ERROR, "事项未发布,不可办理"); } if (item.getFlowGuid() == null || item.getFlowGuid().isEmpty()) { throw new BizException(ErrorCode.PARAM_ERROR, "该事项未开通在线办理"); } Map out = new HashMap<>(); out.put("siteCode", site.getSiteCode()); out.put("itemCode", item.getItemCode()); out.put("title", item.getTitle()); out.put("summary", item.getSummary()); out.put("deptName", item.getDeptName()); out.put("handleMode", item.getHandleMode()); out.put("handleTimeLimit", item.getHandleTimeLimit()); out.put("promiseTimeLimit", item.getPromiseTimeLimit()); out.put("contactPhone", item.getContactPhone()); out.put("coverImage", com.yunhai.svchall.utils.FileUtil.absolutizePlain(item.getCoverImage())); out.put("flowGuid", item.getFlowGuid()); if (item.getFormCode() != null && !item.getFormCode().isEmpty()) { ServiceForm form = formService.latestPublished(site.getSchId(), item.getFormCode()) .orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "办理表单未发布,请联系管理员")); out.put("formVersion", form.getVersion()); out.put("schemaJson", form.getSchemaJson()); } return out; } public Map adminPage(BoaUser user, ApplyPageQuery query) { SiteInfo site = siteGuard.requireSiteManager(user, query.getSiteCode()); int pageIndex = query.getPage() == null || query.getPage() < 1 ? 0 : query.getPage() - 1; int size = query.getPageSize() == null || query.getPageSize() < 1 ? 10 : Math.min(query.getPageSize(), 100); Pageable pageable = PageRequest.of(pageIndex, size, Sort.by(Sort.Order.desc("createTime"), Sort.Order.desc("id"))); Specification spec = (root, cq, cb) -> { List predicates = new ArrayList<>(); predicates.add(cb.equal(root.get("schId"), site.getSchId())); predicates.add(cb.equal(root.get("siteCode"), site.getSiteCode())); if (StringUtils.hasText(query.getItemCode())) { predicates.add(cb.equal(root.get("itemCode"), query.getItemCode().trim())); } if (query.getFlowStatus() != null) { predicates.add(cb.equal(root.get("flowStatus"), query.getFlowStatus())); } if (StringUtils.hasText(query.getKeyword())) { String like = "%" + query.getKeyword().trim() + "%"; predicates.add(cb.or(cb.like(root.get("itemTitle"), like), cb.like(root.get("submitterName"), like))); } return cb.and(predicates.toArray(new Predicate[0])); }; Page result = applyRepository.findAll(spec, pageable); Map out = new HashMap<>(); out.put("total", result.getTotalElements()); List list = new ArrayList<>(); for (ServiceApply apply : result.getContent()) { list.add(ApplyVo.of(apply)); } out.put("list", list); return out; } // ===== 内部:代发起(D9 范式,Notice NoticeFlowService 同构)===== /** * 提交动作专用单线程池(串行化避免并发双 apply;uk 幂等兜底)。 * E2E 实测教训(验收报告 §四.1):afterCommit 回调若留在原请求线程执行,OSIV 绑定的 * EntityManager 已随事务提交关闭,后续 repository 写会话静默丢失——提交动作一律切独立线程。 */ private static final ExecutorService APPLY_EXECUTOR = Executors.newSingleThreadExecutor(r -> { Thread t = new Thread(r, "svchall-flow-submit"); t.setDaemon(true); return t; }); /** * 事务提交后再代发起(出站 HTTP 不进 DB 事务,防长调用拖住连接;且 flow-center 收到 apply * 后会立即推送回调,须保证办件行已提交可见)。无活动事务时立即执行(测试/兜底)。 */ private void flowSubmitAfterCommit(Integer applyId) { Runnable task = () -> tryApplyQuietly(applyId); if (TransactionSynchronizationManager.isSynchronizationActive()) { TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { @Override public void afterCommit() { APPLY_EXECUTOR.submit(task); } }); } else { task.run(); } } /** 异步路径提交失败静默保持 PENDING(行已落库不回滚;列表"待提交"态即提示,可重试) */ private void tryApplyQuietly(Integer applyId) { try { doFlowApply(applyId, false); } catch (Exception e) { log.warn("[svc-hall] 代发起失败(保持待提交,可重试)applyId={}:{}", applyId, e.toString()); } } /** * 代发起(幂等:bizId=apply.id,flow-center uk 防重复建件)。 * 成功回填走窄列 markApproving——整实体 save 是旧快照盲写,会覆盖出站期间并发送达的回调状态 * (验收报告 §四.2);BizException 透传(retry 同步路径给用户提示,异步路径由静默包装消化)。 * resubmit=true(一键重提):R4 命中 30/40 重启推进后改用 markResubmitted 回开为 10。 */ private void doFlowApply(Integer applyId, boolean resubmit) { if (!flowClient.enabled()) { log.warn("[svc-hall] 流程中心未接入,办件保留待提交状态 applyId={}", applyId); return; } ServiceApply apply = applyRepository.findById(applyId) .orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "办件不存在")); try { FlowClient.ApplyResult result = flowClient.apply( apply.getSchId(), apply.getFlowGuid(), FlowCallbackService.BIZ_TYPE, String.valueOf(apply.getId()), apply.getSubmitterName() + "的" + apply.getItemTitle(), // 前端详情页是 hash 路由 + query 传参(routes.ts path="/apply-detail"、 // Apply.vue router.replace("/apply-detail?id=")):路径参数形态会 no-match 空白页 frontBaseUrl + "/#/apply-detail?id=" + apply.getId(), apply.getItemSnapshotJson(), apply.getSubmitterId(), apply.getSubmitterName(), apply.getFormDataJson()); // @Modifying 更新须在事务内执行;本方法出站 HTTP 后运行在 AFTER_COMMIT 线程, // 无外层事务,用模板事务包住窄列回填( markApproving/markResubmitted) final Integer applyIdFinal = apply.getId(); final Long instanceId = result.getInstanceId(); transactionTemplate.executeWithoutResult(tx -> { if (resubmit) { applyRepository.markResubmitted(applyIdFinal, instanceId, LocalDateTime.now()); } else { applyRepository.markApproving(applyIdFinal, instanceId, LocalDateTime.now()); } }); if (result.isDuplicated()) { log.info("[svc-hall] 代发起命中幂等(flow-center 返回既有办件)applyId={} instanceId={}", apply.getId(), result.getInstanceId()); } } catch (BizException e) { throw e; } catch (Exception e) { // PENDING 行保留:用户可重试(复用同一 bizId,flow-center uk 幂等防重复建件) log.error("[svc-hall] 代发起失败,办件保留待提交 applyId={}", applyId, e); throw new BizException(ErrorCode.PARAM_ERROR, "流程中心提交失败,请稍后重试"); } } // ===== 内部:快照与工具 ===== /** * 提交类响应补"流程中心是否已接入"标志(BP-R4-1 误报根因,响应期字段不入库): * 代发起是 AFTER_COMMIT 异步,提交成功那一刻 flowStatus 必然是 0;只有结合本标志, * 前端才能区分"排队中(秒级翻转,等轮询)"与"压根没接入(不会翻转,需提示重试/联系人)"。 */ private ApplyVo stampFlowEnabled(ApplyVo vo) { vo.setFlowEnabled(flowClient.enabled()); return vo; } /** 事项要点快照(= biz_snapshot 内容源;附件清单仅文件名——flow-center biz_snapshot 内容边界) */ private String itemSnapshotJson(ServiceItem item) { return itemSnapshotJson(item, new ArrayList<>()); } private String itemSnapshotJson(ServiceItem item, List attachments) { Map snapshot = new LinkedHashMap<>(); snapshot.put("itemTitle", item.getTitle()); snapshot.put("summary", item.getSummary()); snapshot.put("deptName", item.getDeptName()); snapshot.put("handleTimeLimit", item.getHandleTimeLimit()); snapshot.put("contactPhone", item.getContactPhone()); List names = new ArrayList<>(); for (com.yunhai.svchall.model.svc.SvcModels.SvcItemAttachmentVo a : attachments) { names.add(a.getFileName()); } snapshot.put("attachmentNames", names); try { return objectMapper.writeValueAsString(snapshot); } catch (Exception e) { log.warn("[svc-hall] 事项快照序列化失败(存 null)itemCode={}", item.getItemCode()); return null; } } private Long userSchId(BoaUser user) { if (user == null || user.getCurrentRole() == null || user.getCurrentRole().getSchId() == null) { throw new BizException(ErrorCode.PARAM_ERROR, "当前用户无学校归属,无法操作办件"); } return user.getCurrentRole().getSchId().longValue(); } private static boolean isSuperAdmin(BoaUser user) { return user != null && user.getType() != null && user.getType() == 1; } }