Files
svc-hall-service/src/main/java/com/yunhai/svchall/service/impl/ServiceApplyService.java
T

505 lines
27 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<String, Object> 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<ServiceApply> spec = (root, cq, cb) -> {
List<Predicate> 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<ServiceApply> result = applyRepository.findAll(spec, pageable);
Map<String, Object> out = new HashMap<>();
out.put("total", result.getTotalElements());
List<ApplyVo> 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<String, Object> 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<String, Object> 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<String, Object> 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<ServiceApply> spec = (root, cq, cb) -> {
List<Predicate> 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<ServiceApply> result = applyRepository.findAll(spec, pageable);
Map<String, Object> out = new HashMap<>();
out.put("total", result.getTotalElements());
List<ApplyVo> 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<com.yunhai.svchall.model.svc.SvcModels.SvcItemAttachmentVo> attachments) {
Map<String, Object> 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<String> 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;
}
}