diff --git a/server/src/main/java/com/aisino/iles/lawenforcement/service/DeliveryMethodService.java b/server/src/main/java/com/aisino/iles/lawenforcement/service/DeliveryMethodService.java new file mode 100644 index 0000000..986d984 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/lawenforcement/service/DeliveryMethodService.java @@ -0,0 +1,139 @@ +package com.aisino.iles.lawenforcement.service; + +import com.aisino.iles.common.util.PageableHelper; +import com.aisino.iles.lawenforcement.model.DeliveryMethod; +import com.aisino.iles.lawenforcement.model.query.DeliveryQuery; +import com.aisino.iles.lawenforcement.repository.DeliveryMethodRepository; +import com.smartlx.sso.client.model.RemoteUserInfo; +import jakarta.persistence.criteria.Predicate; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * 送达方式服务类 + */ +@Service +@Slf4j +public class DeliveryMethodService { + private final DeliveryMethodRepository deliveryMethodRepo; + + public DeliveryMethodService(DeliveryMethodRepository deliveryMethodRepo) { + this.deliveryMethodRepo = deliveryMethodRepo; + } + + /** + * 保存送达方式信息 + * + * @param deliveryMethod 送达方式信息 + * @return 保存后的送达方式信息 + */ + @Transactional + public DeliveryMethod saveDeliveryMethod(DeliveryMethod deliveryMethod, RemoteUserInfo user, String type) { + LocalDateTime now = LocalDateTime.now(); + if ("add".equals(type)) { + deliveryMethod.setIsDeleted(false); + deliveryMethod.setCreateTime(now); + if (null != user) { + deliveryMethod.setCreatedBy(user.getXm()); + deliveryMethod.setCreatedAccountBy(user.getYhwybs()); + } + } + deliveryMethod.setUpdateTime(now); + return deliveryMethodRepo.save(deliveryMethod); + } + + /** + * 根据ID查询送达方式信息 + * + * @param deliveryMethodId 送达方式ID + * @return 送达方式信息 + */ + public Optional findDeliveryMethodById(String deliveryMethodId) { + return deliveryMethodRepo.findById(deliveryMethodId); + } + + + /** + * 根据查询条件分页查询送达方式信息 + * + * @param query 查询条件 + * @return 分页送达方式信息 + */ + public Page findDeliveryMethodPage(DeliveryQuery query) { + return deliveryMethodRepo.findAll(build(query), + PageableHelper.buildPageRequest(query.page(), query.pageSize(), "updateTime", "desc")); + } + + /** + * 构建查询条件 + * + * @param query 查询条件 + * @return 规格 + */ + private Specification build(DeliveryQuery query) { + return (root, criteriaQuery, criteriaBuilder) -> { + List predicates = new ArrayList<>(); + Optional.ofNullable(query.getIsDeleted()).ifPresent(o -> predicates.add(criteriaBuilder.equal(root.get("isDeleted"), o))); + Optional.ofNullable(query.getMethodName()).filter(StringUtils::hasText).ifPresent(o -> predicates.add(criteriaBuilder.like(root.get("methodName"), "%" + o + "%"))); + Optional.ofNullable(query.getDescription()).filter(StringUtils::hasText).ifPresent(o -> predicates.add(criteriaBuilder.like(root.get("description"), "%" + o + "%"))); + return criteriaBuilder.and(predicates.toArray(new Predicate[0])); + }; + } + + /** + * 根据ID删除送达方式信息 + * + * @param deliveryMethodId 送达方式ID + */ + @Transactional + public void deleteDeliveryMethodById(String deliveryMethodId) { + deliveryMethodRepo.findById(deliveryMethodId).ifPresent(deliveryMethod -> { + deliveryMethod.setIsDeleted(true); + deliveryMethod.setUpdateTime(LocalDateTime.now()); + deliveryMethodRepo.save(deliveryMethod); + }); + } + + /** + * 送达方式是否存在 + * + * @param deliveryMethodId 送达方式ID + * @return 是否存在 + */ + public boolean existsDeliveryMethodById(String deliveryMethodId) { + return deliveryMethodRepo.existsById(deliveryMethodId); + } + + /** + * 批量删除送达方式 + * + * @param itemIds 送达方式ID列表 + */ + @Transactional + public void deleteDeliveryMethodByIds(List itemIds) { + deliveryMethodRepo.findAllById(itemIds).forEach(deliveryMethod -> { + deliveryMethod.setIsDeleted(true); + deliveryMethod.setUpdateTime(LocalDateTime.now()); + deliveryMethodRepo.save(deliveryMethod); + }); + } + + /** + * 根据查询条件获取送达方式列表 + * + * @param query 查询条件 + * @return 送达方式集合 + */ + public List getDeliveryMethodList(DeliveryQuery query) { + return deliveryMethodRepo.findAll(build(query)); + } +} diff --git a/server/src/main/java/com/aisino/iles/lawenforcement/service/DeliveryRecordService.java b/server/src/main/java/com/aisino/iles/lawenforcement/service/DeliveryRecordService.java new file mode 100644 index 0000000..60fa80a --- /dev/null +++ b/server/src/main/java/com/aisino/iles/lawenforcement/service/DeliveryRecordService.java @@ -0,0 +1,769 @@ +package com.aisino.iles.lawenforcement.service; + +import com.aisino.iles.common.model.enums.IndustryCategoryForFile; +import com.aisino.iles.common.service.FtpService; +import com.aisino.iles.common.util.BeanUtils; +import com.aisino.iles.common.util.KmsServer; +import com.aisino.iles.common.util.PageableHelper; +import com.aisino.iles.core.exception.BusinessError; +import com.aisino.iles.lawenforcement.model.*; +import com.aisino.iles.lawenforcement.model.dto.DeliveryRecordStatisticsDto; +import com.aisino.iles.lawenforcement.model.dto.DeliveryScenePicDto; +import com.aisino.iles.lawenforcement.model.query.DeliveryQuery; +import com.aisino.iles.lawenforcement.repository.AgencyRepository; +import com.aisino.iles.lawenforcement.repository.DeliveryRecordRepository; +import com.aisino.iles.lawenforcement.repository.MaterialsRepository; +import com.aisino.iles.lawenforcement.repository.SmsSendRecordRepository; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.smartlx.sso.client.model.RemoteUserInfo; +import jakarta.persistence.criteria.Predicate; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.time.DateUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.domain.Page; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.multipart.MultipartFile; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +import static java.nio.charset.StandardCharsets.US_ASCII; + +/** + * 文书送达记录服务类 + */ +@Service +@Slf4j +public class DeliveryRecordService { + private final DeliveryRecordRepository deliveryRecordRepo; + private final DeliveryMethodService deliveryMethodService; + private final AgencyRepository agencyRepo; + private final SmsSendRecordRepository smsSendRecordRepo; + private final MaterialsRepository materialsRepo; + private final RestTemplate restTemplate; + private final FtpService ftpService; + private final ObjectMapper objectMapper; + private final String deliveryUsername; + private final String deliveryPassword; + private final String publishSmsCallUrl; + private final String publishVoiceCallUrl; + + public DeliveryRecordService(DeliveryRecordRepository deliveryRecordRepo, + DeliveryMethodService deliveryMethodService, + AgencyRepository agencyRepo, + SmsSendRecordRepository smsSendRecordRepo, + MaterialsRepository materialsRepo, + RestTemplate restTemplate, + FtpService ftpService, + ObjectMapper objectMapper, + @Value("${third-party.delivery.username}") String deliveryUsername, + @Value("${third-party.delivery.password}") String deliveryPassword, + @Value("${third-party.delivery.publish-sms-call-url}") String publishSmsCallUrl, + @Value("${third-party.delivery.publish-voice-call-url}") String publishVoiceCallUrl) { + this.deliveryRecordRepo = deliveryRecordRepo; + this.deliveryMethodService = deliveryMethodService; + this.agencyRepo = agencyRepo; + this.smsSendRecordRepo = smsSendRecordRepo; + this.materialsRepo = materialsRepo; + this.restTemplate = restTemplate; + this.ftpService = ftpService; + this.objectMapper = objectMapper; + this.deliveryUsername = deliveryUsername; + this.deliveryPassword = deliveryPassword; + this.publishSmsCallUrl = publishSmsCallUrl; + this.publishVoiceCallUrl = publishVoiceCallUrl; + } + + /** + * 保存文书送达记录信息 + * + * @param deliveryRecord 文书送达记录信息 + * @return 保存后的文书送达记录信息 + */ + @Transactional + public DeliveryRecord saveDeliveryRecord(DeliveryRecord deliveryRecord, RemoteUserInfo user, String type) { + LocalDateTime now = LocalDateTime.now(); + if ("add".equals(type)) { + deliveryRecord.setCreateTime(now); + deliveryRecord.setDeliveryStatus((short) 0); + if (null != user) { + deliveryRecord.setCreatedBy(user.getXm()); + deliveryRecord.setCreatedAccountBy(user.getYhwybs()); + } + } else if ("send".equals(type)) { + deliveryRecord.setDeliveryStatus((short) 1); + deliveryRecord.setSendTime(now); + } + deliveryRecord.setUpdateTime(now); + DeliveryRecord save = deliveryRecordRepo.save(deliveryRecord); + if ("add".equals(type) || "up".equals(type)) { + List materialsList = deliveryRecord.getMaterials(); + if (null != materialsList && !materialsList.isEmpty()) { + List list = materialsRepo.findByLinkId(deliveryRecord.getDeliveryId()); + List newPics = list.stream().map(Materials::getSavePath).toList(); + materialsList.stream().filter(o -> !newPics.contains(o.getSavePath())) + .forEach(materials -> { + materials.setLrsj(now); + String name = materials.getName(); + materials.setFileType(StringUtils.hasText(name) ? name.split("\\.")[1] : null); + materials.setIsOriginal("1"); + materials.setMaterialsTypeCode("3"); + materials.setMaterialsTypeName("文书送达现场图片"); + materials.setLinkId(save.getDeliveryId()); + materials.setMaterialsId(null); + materialsRepo.save(materials); + }); + List paths = materialsList.stream().map(Materials::getSavePath).toList(); + list.stream().filter(o -> !paths.contains(o.getSavePath())).forEach(o -> { + if (StringUtils.hasText(o.getSavePath())) + ftpService.deletePathFile(o.getSavePath()); + materialsRepo.delete(o); + }); + } + } + return save; + } + + /** + * 根据ID查询文书送达记录信息 + * + * @param deliveryRecordId 文书送达记录ID + * @return 文书送达记录信息 + */ + @Transactional(readOnly = true) + public Optional findDeliveryRecordById(String deliveryRecordId) { + return deliveryRecordRepo.findById(deliveryRecordId).map(this::itemDeliveryRecord).map(deliveryRecord -> { + deliveryRecord.setMaterials(materialsRepo.findByLinkId(deliveryRecord.getDeliveryId()).stream().peek(scene -> { + String url = scene.getSavePath(); + if (StringUtils.hasText(url)) { + scene.setUrl(ftpService.getFileUrl(url)); + scene.setDownloadUrl(ftpService.getFileDownloadUrl(url)); + } + }).collect(Collectors.toList())); + return deliveryRecord; + }); + } + + @Transactional(readOnly = true) + public List findDeliveryRecordByIds(List deliveryIds) { + return deliveryRecordRepo.findAllById(deliveryIds).stream().map(this::itemDeliveryRecord).collect(Collectors.toList()); + } + + + /** + * 根据查询条件分页查询文书送达记录信息 + * + * @param query 查询条件 + * @return 分页文书送达记录信息 + */ + @Transactional(readOnly = true) + public Page findDeliveryRecordPage(DeliveryQuery query) { + return deliveryRecordRepo.findAll(build(query), PageableHelper.buildPageRequest(query.page(), query.pageSize(), "updateTime", "desc"), "delivery_record-all").map(this::itemDeliveryRecord); + } + + private DeliveryRecord itemDeliveryRecord(DeliveryRecord deliveryRecord) { + if (null != deliveryRecord.getSendTime() && deliveryRecord.getDeliveryStatus() != 2) { + // 计算两个时间之间的小时差 + Duration duration = Duration.between(deliveryRecord.getSendTime(), LocalDateTime.now()); + long hours = duration.toHours(); + deliveryRecord.setNeedCll(hours > 24); + } + return deliveryRecord; + } + + /** + * 构建查询条件 + * + * @param query 查询条件 + * @return 规格 + */ + private Specification build(DeliveryQuery query) { + return (root, criteriaQuery, criteriaBuilder) -> { + List predicates = new ArrayList<>(); + Optional.ofNullable(query.getAgencyCode()).filter(StringUtils::hasText) + .ifPresent(o -> predicates.add(criteriaBuilder.like(root.get(DeliveryRecord_.caseInfo).get(Case_.enforcementInfo).get(EnforcementInfo_.agency).get(Agency_.agencyCode), + com.aisino.iles.common.util.StringUtils.trimEven0(o) + "%"))); + Optional.ofNullable(query.getCaseId()).filter(StringUtils::hasText).ifPresent(o -> predicates.add(criteriaBuilder.equal(root.get(DeliveryRecord_.caseId), o))); + Optional.ofNullable(query.getRecipientId()).filter(StringUtils::hasText).ifPresent(o -> predicates.add(criteriaBuilder.equal(root.get(DeliveryRecord_.recipientId), o))); + Optional.ofNullable(query.getDeliveryMethodId()).filter(StringUtils::hasText).ifPresent(o -> predicates.add(criteriaBuilder.equal(root.get(DeliveryRecord_.deliveryMethodId), o))); + return criteriaBuilder.and(predicates.toArray(new Predicate[0])); + }; + } + + /** + * 根据ID删除文书送达记录信息 + * + * @param deliveryRecordId 文书送达记录ID + */ + @Transactional + public void deleteDeliveryRecordById(String deliveryRecordId) { + deliveryRecordRepo.deleteById(deliveryRecordId); + } + + /** + * 文书送达记录是否存在 + * + * @param deliveryRecordId 文书送达记录ID + * @return 是否存在 + */ + public Boolean existsDeliveryRecordById(String deliveryRecordId) { + return deliveryRecordRepo.existsById(deliveryRecordId); + } + + /** + * 批量删除文书送达记录 + * + * @param itemIds 文书送达记录ID列表 + */ + @Transactional + public void deleteDeliveryRecordByIds(List itemIds) { + deliveryRecordRepo.deleteAllById(itemIds); + } + + /** + * 统计 + * + * @param query 查询参数 + * @param user 用户 + * @return 统计结果 + */ + public List> statistics(DeliveryQuery query, RemoteUserInfo user) { + String agencyCode = query.getAgencyCode(); + if (!StringUtils.hasText(agencyCode)) agencyCode = user.getGajgjgdm(); + Integer agencyLevel = query.getAgencyLevel(); + if (null == agencyLevel) + agencyLevel = agencyRepo.findByAgencyCode(agencyCode).map(Agency::getAgencyLevel).orElseThrow(() -> new BusinessError("当前用户没有匹配到有效的机构信息")); + String code = com.aisino.iles.common.util.StringUtils.trimEven0(agencyCode) + "%"; + List agencyList = agencyRepo.findLikeAgencyCode(code, agencyLevel).stream().filter(o -> !"01610100000000001".equals(o.getAgencyCode())).toList(); + List list = deliveryRecordRepo.statistics(code); + Map> listMap = list.stream().collect(Collectors.groupingBy(DeliveryRecordStatisticsDto::getAgencyCode)); + Map> mm = new HashMap<>(); + Set map = categories(); + listMap.forEach((key, val) -> { + Map resMap = new HashMap<>(); + map.forEach(k -> { + val.stream() + .filter(dto -> k.equals(dto.getDeliveryMethodId())) + .findFirst() + .ifPresentOrElse( + dto -> resMap.put(k, dto.getNum()), + () -> resMap.put(k, 0L) + ); + }); + long sum = resMap.values().stream() + .mapToLong(o -> Long.parseLong(String.valueOf(o))) + .sum(); + resMap.put("total", sum); + mm.put(key, resMap); + }); + return agencyList.stream().map(o -> { + Map resMap = new HashMap<>(); + resMap.put("agencyCode", o.getAgencyCode()); + resMap.put("agencyName", o.getAgencyName()); + resMap.put("agencyLevel", o.getAgencyLevel()); + resMap.put("hasChildren", o.getLeaf() ? 0 : 1); + Set set = mm.keySet().stream().filter(v -> v.length() >= o.getAgencySimpleCode().length()) + .filter(v -> Objects.equals(v.substring(0, o.getAgencySimpleCode().length()), o.getAgencySimpleCode()))//过滤掉截取后不相同的数据 + .collect(Collectors.toSet()); + // 累加相同属性的值 + Map mapSum = set.stream() + .flatMap(str -> mm.get(str).entrySet().stream()) + .collect(Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue, + (v1, v2) -> (Long) v1 + (Long) v2 + )); + if (mapSum.isEmpty()) { + map.forEach(k -> resMap.put(k, 0L)); + resMap.put("total", 0L); + } else { + resMap.putAll(mapSum); + } + return resMap; + }).collect(Collectors.toList()); + } + + private Set categories() { + DeliveryQuery deliveryQuery = new DeliveryQuery(); + deliveryQuery.setIsDeleted(false); + deliveryQuery.setEnabled(true); + return deliveryMethodService.getDeliveryMethodList(deliveryQuery).stream().map(DeliveryMethod::getDeliveryMethodId).collect(Collectors.toSet()); + } + + /** + * 下发机器人语音叫应 + * 请求示例: + * { + * "JYBT": "日常叫应", + * "JYNR": "请立即应答", + * "YPDZ": "https://ip:port/static/audio_1.mp3", + * "TZRY": [ ... ], + * "TZDC": { ... }, + * "JZSJ": { ... }, + * "LYZH": "test001" + * } + * + * @param deliveryIds 送达记录ID列表 + * @param user 当前用户 + */ + @Transactional + public void publishSmsCall(List deliveryIds, RemoteUserInfo user) { + String auth = deliveryUsername + ":" + deliveryPassword; + String authHeader = "Basic " + Base64.getEncoder().encodeToString(auth.getBytes(US_ASCII)); + HttpHeaders headers = new HttpHeaders(); + headers.set("Authorization", authHeader); + headers.setContentType(MediaType.APPLICATION_JSON); + List list = findDeliveryRecordByIds(deliveryIds); + if (list.isEmpty()) + throw new BusinessError("送达记录不存在"); + list.stream().collect(Collectors.groupingBy(DeliveryRecord::getRecipientId)).forEach((recipientId, l) -> { + l.stream().collect(Collectors.groupingBy(DeliveryRecord::getCaseId)).forEach((caseid, ll) -> { + PublishSmsCallRequest smsRequest = new PublishSmsCallRequest(); + Case caseInfo = ll.stream().map(DeliveryRecord::getCaseInfo).findFirst().get(); + String jybt = "《" + caseInfo.getCaseName() + "》"; + String jynr = "《" + caseInfo.getCaseName() + "》的文书已通过短信送达,具体内容请查看附件."; + smsRequest.setJybt(jybt); + smsRequest.setJynr(jynr); + List attachments = new ArrayList<>(); + ll.stream().map(DeliveryRecord::getDocumentInfo).filter(documentInfo -> StringUtils.hasText(documentInfo.getFilePath())) + .forEach(documentInfo -> { + Attachment attachment = new Attachment(); + attachment.setWjbh(documentInfo.getDocumentId()); + String url = ftpService.getFileUrl(documentInfo.getFilePath(), DateUtils.addDays(new Date(), 2)); + attachment.setWjdz(url); + attachment.setWjlx("4"); + attachments.add(attachment); + }); + if (!attachments.isEmpty()) smsRequest.setFjzl(attachments); + RecipientInfo recipientInfo = l.stream().map(DeliveryRecord::getRecipientInfo).findFirst().get(); + NotifyPerson notifyPerson = new NotifyPerson(); + notifyPerson.setRybh(recipientInfo.getRecipientId()); + notifyPerson.setRyxm(recipientInfo.getName()); + notifyPerson.setYddh(recipientInfo.getPhone()); + smsRequest.setTzry(List.of(notifyPerson)); + NotifySupervision tzdc = new NotifySupervision();//通知督促 + tzdc.setSjjg(60); + tzdc.setDccs(1); + smsRequest.setTzdc(tzdc); + smsRequest.setDcfs("1"); + Deadline deadline = new Deadline();//截止时间 + deadline.setSjjg(24); + deadline.setSjdw("3"); + smsRequest.setJzsj(deadline); + smsRequest.setLyzh(deliveryUsername); + HttpEntity requestEntity = new HttpEntity<>(smsRequest, headers); + String smsUrl = publishSmsCallUrl; + try { + log.info("Publishing SMS to URL: {} with request: {}", smsUrl, objectMapper.writeValueAsString(smsRequest)); + ResponseEntity response = restTemplate.exchange(smsUrl, HttpMethod.POST, requestEntity, String.class); + log.info("status:{}", response.getStatusCode()); + if (response.getStatusCode() == HttpStatus.OK) { + String body = response.getBody(); +// String body = "{\"code\":0,\"msg\":\"请求成功\",\"data\":{\"JYBH\":\"460779fbe1d74eea90c9bac61dfe4b63\"}}"; + log.info("SMS publish response: {}", body); + PublishVoiceCallResponse callResponse = objectMapper.readValue(body, PublishVoiceCallResponse.class); + if (0 == callResponse.getCode()) { + ll.forEach(deliveryRecord -> { + String jybh = callResponse.getData().getJybh(); + saveDeliveryRecord(deliveryRecord, user, "send"); + SmsSendRecord smsSendRecord = new SmsSendRecord(); + smsSendRecord.setJybt(jybt); + smsSendRecord.setJynr(jynr); + smsSendRecord.setJybh(jybh); + smsSendRecord.setYddh(recipientInfo.getPhone()); + smsSendRecord.setDeliveryId(deliveryRecord.getDeliveryId()); + smsSendRecord.setOperator(user.getXm()); + smsSendRecord.setOperatorAccount(user.getYhwybs()); + smsSendRecord.setSendTime(LocalDateTime.now()); + // 检验加密签名保存 + Optional.ofNullable(smsSendRecord.getYddh()).filter(StringUtils::hasText).ifPresent(s -> smsSendRecord.setSginData(KmsServer.kmsSign(s))); + smsSendRecordRepo.save(smsSendRecord); + }); + } + } + log.info("SMS publish completed"); + } catch (Exception e) { + log.error("Error publishing SMS: {}", e.getMessage(), e); + } + }); + }); + } + + /** + * 下发机器人语音叫应 + * 请求示例: + * { + * "JYBT": "日常叫应", + * "JYNR": "请立即应答", + * "YPDZ": "https://ip:port/static/audio_1.mp3", + * "TZRY": [ ... ], + * "TZDC": { ... }, + * "JZSJ": { ... }, + * "LYZH": "test001" + * } + * + * @param deliveryRecord 送达记录 + * @param user 当前用户 + * 外部接口返回的调用结果 + */ + public void publishVoiceCall(DeliveryRecord deliveryRecord, RemoteUserInfo user) { + PublishVoiceCallRequest voiceRequest = new PublishVoiceCallRequest(); + String auth = deliveryUsername + ":" + deliveryPassword; + String authHeader = "Basic " + Base64.getEncoder().encodeToString(auth.getBytes(US_ASCII)); + HttpHeaders headers = new HttpHeaders(); + headers.set("Authorization", authHeader); + headers.setContentType(MediaType.APPLICATION_JSON); + String jybt = deliveryRecord.getCaseInfo().getCaseName(); + RecipientInfo recipientInfo = deliveryRecord.getRecipientInfo(); + String jynr = jybt + "的文书已通过短信送达,请尽快处理"; + voiceRequest.setJybt(jybt); + voiceRequest.setJynr(jynr); + NotifyPerson notifyPerson = new NotifyPerson(); + notifyPerson.setRybh(recipientInfo.getRecipientId()); + notifyPerson.setRyxm(recipientInfo.getName()); + notifyPerson.setYddh(recipientInfo.getPhone()); + voiceRequest.setTzry(List.of(notifyPerson)); + voiceRequest.setLyzh(deliveryUsername); + HttpEntity requestEntity = new HttpEntity<>(voiceRequest, headers); + try { + log.info("Publishing Voice call to URL: {} with request: {}", publishVoiceCallUrl, objectMapper.writeValueAsString(voiceRequest)); + ResponseEntity response = restTemplate.exchange(publishVoiceCallUrl, HttpMethod.POST, requestEntity, String.class); + log.info("Voice call publish response: {}", response.getBody()); + if (response.getStatusCode() == HttpStatus.OK) { + String body = response.getBody(); + log.info("Voice call response: {}", body); + PublishVoiceCallResponse callResponse = objectMapper.readValue(body, PublishVoiceCallResponse.class); + if (0 == callResponse.getCode()) { + String jybh = callResponse.getData().getJybh(); + SmsSendRecord smsSendRecord = new SmsSendRecord(); + smsSendRecord.setJybt(jybt); + smsSendRecord.setJynr(jynr); + smsSendRecord.setJybh(jybh); + smsSendRecord.setYddh(recipientInfo.getPhone()); + smsSendRecord.setDeliveryId(deliveryRecord.getDeliveryId()); + smsSendRecord.setOperator(user.getXm()); + smsSendRecord.setOperatorAccount(user.getYhwybs()); + smsSendRecord.setSendTime(LocalDateTime.now()); + // 检验加密签名保存 + Optional.ofNullable(smsSendRecord.getYddh()).filter(StringUtils::hasText).ifPresent(s -> smsSendRecord.setSginData(KmsServer.kmsSign(s))); + smsSendRecordRepo.save(smsSendRecord); + } + } + } catch (Exception e) { + log.error("Error publishing voice call: {}", e.getMessage(), e); + } + } + + /** + * 短信状态回调 + * + * @param smsSendRecord 短信发送记录 + */ + @Transactional + public void smsStatusCallback(SmsSendRecord smsSendRecord) { + smsSendRecordRepo.findByJybh(smsSendRecord.getJybh()).forEach(o -> { + BeanUtils.copyNoNullProperties(smsSendRecord, o); + // 检验加密签名保存 + Optional.ofNullable(smsSendRecord.getYddh()).filter(StringUtils::hasText).ifPresent(s -> o.setSginData(KmsServer.kmsSign(s))); + smsSendRecordRepo.save(o); + if ("0401".equals(smsSendRecord.getTzztCode())) { + deliveryRecordRepo.findById(o.getDeliveryId()).ifPresent(deliveryRecord -> { + deliveryRecord.setDeliveryStatus((short) 2); + LocalDateTime now = LocalDateTime.now(); + deliveryRecord.setUpdateTime(now); + deliveryRecord.setFinishTime(now); + deliveryRecordRepo.save(deliveryRecord); + }); + } + }); + } + + // DTO用于下发短信叫应 + @Data + public static class PublishSmsCallRequest { + /** + * 叫应标题, 必填, String + */ + @JsonProperty("JYBT") + private String jybt; + + /** + * 叫应内容, 必填, String + */ + @JsonProperty("JYNR") + private String jynr; + + /** + * 应答方式, 可选, 默认为0 (0:按键应答, 1:视频应答) + */ + @JsonProperty("YDFS") + private String ydfs; + + /** + * 叫应地点, 可选, Object 类型 + */ + @JsonProperty("JYDD") + private Location jydd; + + /** + * 附件资料, 可选, List + */ + @JsonProperty("FJZL") + private List fjzl; + + /** + * 通知人员, 必填, List + */ + @JsonProperty("TZRY") + private List tzry; + + /** + * 通知督促, 可选, Object 类型 + */ + @JsonProperty("TZDC") + private NotifySupervision tzdc; + + /** + * 督促方式, 可选, 默认0(0:普通,1:语音,2:视频) + */ + @JsonProperty("DCFS") + private String dcfs; + + /** + * 截止时间, 可选, Object 类型 + */ + @JsonProperty("JZSJ") + private Deadline jzsj; + + /** + * 超时提醒, 可选, List + */ + @JsonProperty("CSTX") + private List cstx; + + /** + * 来源帐号, 必填, String + */ + @JsonProperty("LYZH") + private String lyzh; + } + + // DTO用于下发机器人语音叫应 + @Data + public static class PublishVoiceCallRequest { + /** + * 叫应标题, 必填, String + */ + @JsonProperty("JYBT") + private String jybt; + + /** + * 叫应内容, 可选, String + */ + @JsonProperty("JYNR") + private String jynr; + + /** + * 音频地址, 可选, String (url 地址) + */ + @JsonProperty("YPDZ") + private String ypdz; + + /** + * 通知人员, 必填, List + */ + @JsonProperty("TZRY") + private List tzry; + + /** + * 通知督促, 可选, Object 类型 + */ + @JsonProperty("TZDC") + private NotifySupervision tzdc; + + /** + * 截止时间, 可选, Object 类型 + */ + @JsonProperty("JZSJ") + private Deadline jzsj; + + /** + * 来源帐号, 必填, String + */ + @JsonProperty("LYZH") + private String lyzh; + } + + @Data + public static class PublishVoiceCallResponse { + /** + * 状态码 0 表示成功 + */ + private Integer code; + private String msg; + private PublishVoiceCallData data; + } + + @Data + public static class PublishVoiceCallData { + @JsonProperty("JYBH") + private String jybh; + } + + + // 内部实体类定义 + @Data + public static class Location { + /** + * 地点名称, 可选, String + */ + @JsonProperty("WZMC") + private String wzmc; + + /** + * 经度, 可选, Double + */ + @JsonProperty("WZJD") + private Double wzjd; + + /** + * 纬度, 可选, Double + */ + @JsonProperty("WZWD") + private Double wzwd; + } + + @Data + public static class Attachment { + /** + * 文件编号, 必填, String + */ + @JsonProperty("WJBH") + private String wjbh; + + /** + * 文件地址 (url 链接), 必填, String + */ + @JsonProperty("WJDZ") + private String wjdz; + + /** + * 缩略图地址, 可选, String (url 链接) + */ + @JsonProperty("SLTP") + private String sltp; + + /** + * 文件类型, 必填, String (1:视频,2:音频,3:图片,4:文档,9:其他) + */ + @JsonProperty("WJLX") + private String wjlx; + } + + @Data + public static class NotifyPerson { + /** + * 人员编号, 可选, String + */ + @JsonProperty("RYBH") + private String rybh; + + /** + * 人员姓名, 可选, String + */ + @JsonProperty("RYXM") + private String ryxm; + + /** + * 移动电话, 必填, String + */ + @JsonProperty("YDDH") + private String yddh; + // 注:可根据需要增加人员工号(RYGH)、固定电话(GDDH)、所属单位(SSDW)、所属辖区(SSXQ) + } + + @Data + public static class NotifySupervision { + /** + * 通知督促的数值, 可选, Integer + */ + @JsonProperty("SJJG") + private Integer sjjg; + + /** + * 督促次数, 可选, Integer + */ + @JsonProperty("DCCS") + private Integer dccs; + } + + @Data + public static class Deadline { + /** + * 截止时间数值, 可选, Integer + */ + @JsonProperty("SJJG") + private Integer sjjg; + + /** + * 截止时间单位, 时间单位(1:秒,2:分钟,3:小时) 可选, String + */ + @JsonProperty("SJDW") + private String sjdw; + } + + @Data + public static class TimeoutRemind { + /** + * 截止时间数值, 可选, Integer + */ + @JsonProperty("SJJG") + private Integer sjjg; + + /** + * 截止时间单位, 时间单位(1:秒,2:分钟,3:小时) 可选, String + */ + @JsonProperty("SJDW") + private String sjdw; + } + + /** + * 添加场景图片 + * + * @param scenePicDto 图片信息 + */ + @Transactional + public void addScenePic(DeliveryScenePicDto scenePicDto) { + DeliveryRecord deliveryRecord = deliveryRecordRepo.findById(scenePicDto.getDeliveryRecordId()).orElseThrow(() -> new BusinessError("送达记录不存在")); + Materials materials = new Materials(); + materials.setLrsj(LocalDateTime.now()); + MultipartFile pic = scenePicDto.getPic(); + String name = pic.getName(); + materials.setFileType(StringUtils.hasText(name) ? name.split("\\.")[1] : null); + materials.setName(name); + try { + String url = ftpService.uploadTempFile(IndustryCategoryForFile.pub, name, pic.getInputStream()); + if (!StringUtils.hasText(url)) { + throw new BusinessError("文书送达现场图片接口返回失败:上传时异常"); + } + materials.setSavePath(url); + } catch (Exception e) { + throw new BusinessError("文书送达现场图片接口上传失败:" + e); + } + materials.setIsOriginal("3"); + materials.setMaterialsTypeCode("3"); + materials.setMaterialsTypeName("文书送达现场图片"); + materials.setLinkId(deliveryRecord.getDeliveryId()); + materialsRepo.save(materials); + } + +} diff --git a/server/src/main/java/com/aisino/iles/lawenforcement/service/DictInterfaceService.java b/server/src/main/java/com/aisino/iles/lawenforcement/service/DictInterfaceService.java new file mode 100644 index 0000000..c987da9 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/lawenforcement/service/DictInterfaceService.java @@ -0,0 +1,132 @@ +package com.aisino.iles.lawenforcement.service; + +import cn.hutool.http.HttpUtil; +import com.aisino.iles.lawenforcement.model.dto.DictItemDto; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.smartlx.sso.client.model.RemoteUserInfo; +import com.smartlx.sso.client.properties.SsoClientProperties; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +@Slf4j +@Service +public class DictInterfaceService { + private final String dictUrl; + private final ObjectMapper objectMapper; + private final SsoClientProperties ssoClientProperties; + private List dictItemDtos = new ArrayList<>(); + + public DictInterfaceService(@Value("${third-party.dict:}") String dictUrl, + ObjectMapper objectMapper, + SsoClientProperties ssoClientProperties) { + this.dictUrl = dictUrl; + this.objectMapper = objectMapper; + this.ssoClientProperties = ssoClientProperties; + } + + + public void getDictItemByUser(String dictCode, RemoteUserInfo user) { + String accessToken = user.getAccessToken().getAccess_token(); + String tokenType = user.getAccessToken().getToken_type(); + getDictItem(dictCode, tokenType, accessToken); + } + + public void getDictItem(String dictCode, String tokenType, String accessToken) { + String body = HttpUtil.createPost(dictUrl + dictCode) + .header("Content-Type", "application/json") + .header("Authorization", tokenType + " " + accessToken) + .header("appId", ssoClientProperties.getClientId()) + .execute() + .body(); + log.info("字典查询结果--------------"); + try { + JsonNode jsonNode = objectMapper.readValue(body, JsonNode.class); + if (jsonNode.has("code") && 200 == jsonNode.get("code").asInt() && jsonNode.has("data")) { + JsonNode dataNode = objectMapper.readValue(jsonNode.get("data").toString(), JsonNode.class); + dictItemDtos = objectMapper.readValue( + dataNode.traverse(), + objectMapper.getTypeFactory().constructCollectionType(List.class, DictItemDto.class) + ); + } + } catch (Exception e) { + log.error("解析字典结果失败", e); + } + } + + public String getFjdm(String xxdm) { + if (!StringUtils.hasText(xxdm)) + return null; + DictItemDto item = findItemByXxdm(dictItemDtos, xxdm); + return item != null ? item.getFjdm() : null; + } + + /** + * 根据xxdm递归查找DictItemDto对象 + * + * @param dtos 字典项列表 + * @param xxdm 需要查找的代码 + * @return 找到的DictItemDto对象,未找到返回null + */ + private DictItemDto findItemByXxdm(List dtos, String xxdm) { + if (dtos == null || dtos.isEmpty() || !StringUtils.hasText(xxdm)) + return null; + for (DictItemDto dto : dtos) { + // 如果当前节点匹配 + if (xxdm.equals(dto.getXxdm())) + return dto; + // 递归查找子节点 + if (dto.getChildren() != null && !dto.getChildren().isEmpty()) { + DictItemDto found = findItemByXxdm(dto.getChildren(), xxdm); + if (found != null) { + return found; + } + } + } + return null; + } + + public String getTopXxdm(String xxdm) { + // 构建从当前节点到顶级节点的路径 + StringBuilder path = new StringBuilder(); + buildPath(dictItemDtos, xxdm, path); + return path.toString(); + } + + private boolean buildPath(List dtos, String xxdm, StringBuilder path) { + for (DictItemDto dto : dtos) { + if (dto.getXxdm().equals(xxdm)) { + // 找到当前节点 + if (dto.getChildren() == null || dto.getChildren().isEmpty()) { + // 如果是叶子节点,则开始构建路径 + path.insert(0, dto.getXxdm()); + return true; + } else { + // 如果不是叶子节点,继续向下查找子节点 + for (DictItemDto child : dto.getChildren()) { + if (buildPath(List.of(child), xxdm, path)) { + // 在子树中找到目标节点,拼接当前节点的xxdm + path.insert(0, dto.getXxdm() + "/"); + return true; + } + } + } + } else { + // 当前节点不匹配,检查其子节点 + if (dto.getChildren() != null && !dto.getChildren().isEmpty()) { + if (buildPath(dto.getChildren(), xxdm, path)) { + // 子节点中找到了目标节点,拼接当前节点的xxdm + path.insert(0, dto.getXxdm() + "/"); + return true; + } + } + } + } + return false; // 没有找到目标节点 + } +} \ No newline at end of file diff --git a/server/src/main/java/com/aisino/iles/lawenforcement/service/DocumentService.java b/server/src/main/java/com/aisino/iles/lawenforcement/service/DocumentService.java new file mode 100644 index 0000000..1caa9a1 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/lawenforcement/service/DocumentService.java @@ -0,0 +1,177 @@ +package com.aisino.iles.lawenforcement.service; + +import com.aisino.iles.common.model.enums.IndustryCategoryForFile; +import com.aisino.iles.common.service.FtpService; +import com.aisino.iles.common.util.PageableHelper; +import com.aisino.iles.core.exception.BusinessError; +import com.aisino.iles.lawenforcement.event.DataChangeAction; +import com.aisino.iles.lawenforcement.event.aop.PublishDataChange; +import com.aisino.iles.lawenforcement.model.Case; +import com.aisino.iles.lawenforcement.model.Case_; +import com.aisino.iles.lawenforcement.model.Document; +import com.aisino.iles.lawenforcement.model.Document_; +import com.aisino.iles.lawenforcement.model.dto.FormDataDto; +import com.aisino.iles.lawenforcement.model.query.DocumentQuery; +import com.aisino.iles.lawenforcement.repository.DocumentRepository; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.JoinType; +import jakarta.persistence.criteria.Predicate; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +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.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Service +@Slf4j +public class DocumentService { + private final DocumentRepository documentRepository; + private final ObjectMapper objectMapper; + private final FtpService ftpService; + + public DocumentService(DocumentRepository documentRepository, + ObjectMapper objectMapper, + FtpService ftpService) { + this.objectMapper = objectMapper; + this.ftpService = ftpService; + this.documentRepository = documentRepository; + } + + /** + * 文书信息 + * + * @param query@return 分页查询文书信息 + */ + @Transactional(readOnly = true) + public Page findDocumentsPage(DocumentQuery query) { + return documentRepository.findAll(buildSpec(query), PageableHelper.buildPageRequest(query.page(), query.pageSize(), query.sort(), query.dir())).map(this::handleResult); + } + + private Document handleResult(Document document) { + Optional.ofNullable(document.getCaseInfo()).ifPresent(caseInfo -> caseInfo.getCaseName()); + Optional.ofNullable(document.getFilePath()).ifPresent(filePath -> document.setDownloadUrl(ftpService.getFileUrl(filePath))); + return document; + } + + public List listDocuments(DocumentQuery query) { + Sort sort = Sort.by(Sort.Direction.ASC, "documentNo"); + return documentRepository.findAll(buildSpec(query), sort, "").stream().map(document -> { + Optional.ofNullable(document.getFilePath()).ifPresent(filePath -> document.setDownloadUrl(ftpService.getFileUrl(filePath))); + return document; + }).toList(); + } + + + private Specification buildSpec(DocumentQuery query) { + return Specification.where((root, q, criteriaBuilder) -> { + List predicates = new ArrayList<>(); + Join caseDocumentRoot = root.join("caseInfo", JoinType.LEFT); + if (StringUtils.hasText(query.getCaseNum())) + predicates.add(criteriaBuilder.equal(caseDocumentRoot.get(Case_.caseNum), query.getCaseNum())); + if (StringUtils.hasText(query.getCaseName())) + predicates.add(criteriaBuilder.like(caseDocumentRoot.get(Case_.caseName), "%"+query.getCaseName()+"%")); + Optional.ofNullable(query.getCaseId()).ifPresent(o -> predicates.add(criteriaBuilder.equal(root.get(Document_.caseInfo).get(Case_.caseId), o))); + Optional.ofNullable(query.getDocumentNo()).ifPresent(o -> predicates.add(criteriaBuilder.equal(root.get(Document_.documentNo), o))); + Optional.ofNullable(query.getDocumentCode()).ifPresent(o -> predicates.add(criteriaBuilder.equal(root.get(Document_.documentCode), o))); + Optional.ofNullable(query.getStatus()).filter("done"::equals).ifPresent(o -> { + predicates.add(criteriaBuilder.equal(root.get(Document_.status), Document.DocumentStatus.done)); + }); + Optional.ofNullable(query.getDocumentName()).ifPresent(o -> predicates.add(criteriaBuilder.like(root.get(Document_.documentName), "%" + o + "%"))); + Optional.ofNullable(query.getDocumentDate()).filter(f -> f.length == 2).map(f -> { + List timePredicates = new ArrayList<>(); + Optional.ofNullable(f[0]).map(from -> criteriaBuilder.greaterThanOrEqualTo(root.get(Document_.documentDate), from)).ifPresent(timePredicates::add); + Optional.ofNullable(f[1]).map(to -> criteriaBuilder.lessThanOrEqualTo(root.get(Document_.documentDate), to)).ifPresent(timePredicates::add); + return timePredicates; + }).ifPresent(predicates::addAll); + Optional.ofNullable(query.getConditionlike()).filter(StringUtils::hasText).ifPresent(o -> { + List orPredicates = new ArrayList<>(); + Predicate p1 = criteriaBuilder.like(root.get(Document_.documentCode), "%" + o + "%"); + orPredicates.add(criteriaBuilder.or(p1)); + Predicate p2 = criteriaBuilder.like(root.get(Document_.documentName), "%" + o + "%"); + orPredicates.add(criteriaBuilder.or(p2)); + Predicate p = criteriaBuilder.or(orPredicates.toArray(new Predicate[0])); + predicates.add(p); + }); + return criteriaBuilder.and(predicates.toArray(new Predicate[0])); + }); + } + + /** + * 保存文书 + * + * @param document 文书信息 + * @return 保存后的文书信息 + */ + @PublishDataChange(action = DataChangeAction.SAVE) + @Transactional + public Document saveDocument(Document document) { + document.setDocumentDate(LocalDateTime.now()); + return documentRepository.save(document); + } + + /** + * 根据ID删除文书信息 + * + * @param documentId 文书ID + */ + @Transactional + public void deleteDocumentById(String documentId) { + documentRepository.deleteById(documentId); + } + + + /** + * 批量删除文书信息 + * + * @param documentIds 文书ID列表 + */ + @Transactional + public void deleteDocumentsByIds(List documentIds) { + documentRepository.deleteAllById(documentIds); + } + + + @PublishDataChange(action = DataChangeAction.SAVE) + @Transactional + public Document addDocumentFile(FormDataDto formData) { + MultipartFile file = formData.getFile1(); + Document document = new Document(); + try { + document = objectMapper.readValue(formData.getFormDataJson(), Document.class); + if (null != file) { + String url; + try { + url = ftpService.uploadTempFile(IndustryCategoryForFile.pub, file.getOriginalFilename(), file.getInputStream()); + if (!StringUtils.hasText(url)) { + throw new BusinessError(document.getDocumentName() + "返回失败:上传时异常"); + } + document.setFilePath(url); + document.setStatus(Document.DocumentStatus.done); + document.setDocumentDate(LocalDateTime.now()); + documentRepository.save(document); + document.setDownloadUrl(ftpService.getFileUrl(url)); + } catch (Exception e) { + throw new BusinessError(document.getDocumentName()+ "上传失败:" + e); + } + } + } catch (JsonProcessingException e) { + log.error(e.getMessage(), e); + } + return document; + } + + @Transactional(readOnly = true) + public Optional findByCaseIdAndDocumentName(String caseId,String documentName) { + return documentRepository.findByCaseIdAndDocumentName( caseId, documentName); + } +}