diff --git a/server/src/main/java/com/aisino/iles/lawenforcement/exception/DataSyncException.java b/server/src/main/java/com/aisino/iles/lawenforcement/exception/DataSyncException.java new file mode 100644 index 0000000..c0d322d --- /dev/null +++ b/server/src/main/java/com/aisino/iles/lawenforcement/exception/DataSyncException.java @@ -0,0 +1,12 @@ +package com.aisino.iles.lawenforcement.exception; + +public class DataSyncException extends RuntimeException { + + public DataSyncException(String message) { + super(message); + } + + public DataSyncException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/server/src/main/java/com/aisino/iles/lawenforcement/schedule/config/SchedulerConfig.java b/server/src/main/java/com/aisino/iles/lawenforcement/schedule/config/SchedulerConfig.java new file mode 100644 index 0000000..c78c96c --- /dev/null +++ b/server/src/main/java/com/aisino/iles/lawenforcement/schedule/config/SchedulerConfig.java @@ -0,0 +1,16 @@ +package com.aisino.iles.lawenforcement.schedule.config; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * 当 application 属性schedule.enabled设置为true的时候,开启调度任务 + */ + +@Configuration +@EnableScheduling +@ConditionalOnProperty(prefix = "scheduler", name = "enabled", havingValue = "true") +public class SchedulerConfig { + +} diff --git a/server/src/main/java/com/aisino/iles/lawenforcement/schedule/service/AgencyScheduleService.java b/server/src/main/java/com/aisino/iles/lawenforcement/schedule/service/AgencyScheduleService.java new file mode 100644 index 0000000..de7d2e9 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/lawenforcement/schedule/service/AgencyScheduleService.java @@ -0,0 +1,520 @@ +package com.aisino.iles.lawenforcement.schedule.service; + +import com.aisino.iles.common.util.StringUtils; +import com.aisino.iles.lawenforcement.model.Agency; +import com.aisino.iles.lawenforcement.repository.AgencyRepository; +import com.aisino.iles.lawenforcement.service.AuthService; +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.smartlx.sso.client.model.AccessToken; +import lombok.Getter; +import lombok.Setter; +import lombok.SneakyThrows; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 同步执法机构信息数据 + */ +@Service +@Slf4j +public class AgencyScheduleService { + + private final AgencyRepository agencyRepository; + private final RestTemplate restTemplate; + private final ObjectMapper objectMapper; + private final AuthService authService; // 用于获取和刷新令牌 + + @Value("${third-party.agency.query:}") + private String agencyUrl; + + @Value("${third-party.enterprise.yddh:}") // 使用正确的配置项获取令牌标识 + private String agencyTokenMobilePhone; + + public AgencyScheduleService(AgencyRepository agencyRepository, RestTemplate restTemplate, ObjectMapper objectMapper, AuthService authService) { + this.agencyRepository = agencyRepository; + this.restTemplate = restTemplate; + this.objectMapper = objectMapper; + this.authService = authService; + } + + @Scheduled(cron = "${scheduler.task.agency.cron.expression:0 0 1 * * ?}") + @Transactional + public void scheduleTask() { + log.info("开始执行执法机构信息同步任务..."); + + if (agencyUrl == null || agencyUrl.isEmpty()) { + log.error("执法机构API URL (third-party.agency.query) 未配置."); + return; + } + if (agencyTokenMobilePhone == null || agencyTokenMobilePhone.isEmpty()) { + log.error("执法机构API令牌IDNO (third-party.enterprise.idno) 未配置."); + return; + } + + try { + // 单次请求获取所有 dwlb=1 的机构信息 + Map requestBody = new HashMap<>(); + requestBody.put("page", 1); + requestBody.put("limit", 4000); // 设置较大的限制以获取所有数据,根据实际情况调整 + Map entity = new HashMap<>(); + entity.put("dwlb", "1"); // 只获取 dwlb=1 的机构 + requestBody.put("entity", entity); + + log.info("准备从API获取所有 dwlb=1 的机构数据. URL: {}, TokenIDNO: {}", agencyUrl, agencyTokenMobilePhone); + + String responseBody = fetchDataFromApi(requestBody, agencyUrl); + + List allApiAgencies = new ArrayList<>(); + if (responseBody != null) { + ApiResponse apiResponse = objectMapper.readValue(responseBody, ApiResponse.class); + if (apiResponse != null && apiResponse.isSuccess() && apiResponse.getData() != null) { + allApiAgencies.addAll(apiResponse.getData().getList()); + + // 检查是否需要分页获取 + int totalPages = apiResponse.getData().getTotalPage(); + int currentPage = 1; + + // 如果有多页数据,继续获取剩余页 + while (currentPage < totalPages) { + currentPage++; + requestBody.put("page", currentPage); + + log.info("获取第 {} 页机构数据,共 {} 页", currentPage, totalPages); + String pageResponseBody = fetchDataFromApi(requestBody, agencyUrl); + + if (pageResponseBody != null) { + ApiResponse pageApiResponse = objectMapper.readValue(pageResponseBody, ApiResponse.class); + if (pageApiResponse != null && pageApiResponse.isSuccess() && pageApiResponse.getData() != null) { + allApiAgencies.addAll(pageApiResponse.getData().getList()); + } else { + log.warn("获取第 {} 页数据失败. Code: {}, Msg: {}", + currentPage, + pageApiResponse != null ? pageApiResponse.getCode() : "N/A", + pageApiResponse != null ? pageApiResponse.getMsg() : "N/A"); + } + } + } + + log.info("共获取 {} 页数据,总计 {} 条机构记录", totalPages, allApiAgencies.size()); + } else { + log.warn("API响应表明操作未成功或数据为空. Code: {}, Msg: {}", + apiResponse != null ? apiResponse.getCode() : "N/A", + apiResponse != null ? apiResponse.getMsg() : "N/A"); + } + } + + if (allApiAgencies.isEmpty()) { + log.info("未从API获取到任何执法机构数据。"); + return; + } + + // 去重,以 gajgjgdm 为准 + List distinctApiAgencies = allApiAgencies.stream() + .filter(apiAgency -> apiAgency.getGajgjgdm() != null) + .collect(Collectors.toMap(ApiAgency::getGajgjgdm, p -> p, (p, q) -> p)) + .values() + .stream() + .toList(); + + log.info("从API获取到 {} 条去重后的执法机构数据,准备进行处理...", distinctApiAgencies.size()); + + // 筛选出 01610100000000000 及其所有下级机构 + String rootAgencyCode = "01610100000000000"; + + // 构建机构代码到API机构的映射,方便后续处理 + Map apiAgencyMap = distinctApiAgencies.stream() + .collect(Collectors.toMap(ApiAgency::getGajgjgdm, a -> a, (a1, a2) -> a1)); + + // 找出所有属于 01610100000000000 下级的机构(包括自身) + Set relevantAgencyCodes = new HashSet<>(); + relevantAgencyCodes.add(rootAgencyCode); // 添加根机构自身 + + // 递归查找所有下级机构 + findAllChildAgencies(rootAgencyCode, apiAgencyMap, relevantAgencyCodes); + + log.info("筛选出 {} 条与根机构 {} 相关的机构数据", relevantAgencyCodes.size(), rootAgencyCode); + + // 筛选出相关的机构数据 + List relevantApiAgencies = distinctApiAgencies.stream() + .filter(agency -> relevantAgencyCodes.contains(agency.getGajgjgdm())) + .toList(); + + Map processedAgencies = new HashMap<>(); + List agenciesToSave = new ArrayList<>(); + + // 先处理所有机构的基本信息,并存入 processedAgencies + for (ApiAgency apiAgency : relevantApiAgencies) { + if (apiAgency.getGajgmc() == null || apiAgency.getGajgmc().isEmpty()) { + log.warn("API返回的机构数据缺少 gajgmc (机构名称),跳过此条记录: {}", apiAgency); + continue; + } + // 检查 gajgjgdm 是否为空,因为它仍然是重要的属性 + if (apiAgency.getGajgjgdm() == null) { + log.warn("API返回的机构数据缺少 gajgjgjgdm (机构代码),但仍会尝试按名称处理: {}", apiAgency); + // 决定是否跳过,或者允许没有代码的机构(如果业务允许) + continue; // 如果 gajgjgdm 必须存在,则取消注释此行 + } + + Agency agency = agencyRepository.findByAgencyCode(apiAgency.getGajgjgdm()).orElse(new Agency()); + + agency.setAgencyName(apiAgency.getGajgmc()); + agency.setAgencyCode(apiAgency.getGajgjgdm()); // 执法机构代码,即使通过名称找到,也用API的code更新 + agency.setOrderNum(apiAgency.getXssx()); // 排序号 + + // 执法机构简码 - 使用StringUtils.trimEven0 + agency.setAgencySimpleCode(generateAgencySimpleCode(apiAgency.getGajgjgdm())); + agenciesToSave.add(agency); + processedAgencies.put(agency.getAgencyCode(), agency); + } + + // 再次遍历,设置父级关系 + for (Agency currentAgency : agenciesToSave) { + ApiAgency correspondingApiAgency = relevantApiAgencies.stream() + .filter(aa -> currentAgency.getAgencyCode().equals(aa.getGajgjgdm())) + .findFirst() + .orElse(null); + + if (correspondingApiAgency != null && correspondingApiAgency.getSjgajgjgdm() != null) { + Agency parentAgency = processedAgencies.get(correspondingApiAgency.getSjgajgjgdm()); + if (parentAgency != null) { + currentAgency.setParent(parentAgency); + } else { + // 如果在本次同步的数据中找不到父级,尝试从数据库中查找已存在的父级 + Optional parentInDb = agencyRepository.findByAgencyCode(correspondingApiAgency.getSjgajgjgdm()); + parentInDb.ifPresent(currentAgency::setParent); + if (parentInDb.isEmpty()) { + log.warn("机构 {} (代码: {}) 的上级机构 (代码: {}) 在本次同步和数据库中均未找到。", + currentAgency.getAgencyName(), currentAgency.getAgencyCode(), correspondingApiAgency.getSjgajgjgdm()); + } + } + } + } + + // 计算并设置执法机构级别 + Map agencyLevelsCache = new HashMap<>(); + int anchorLevel = 2; + // 第一遍:递归计算并缓存所有级别 + for (Agency agency : agenciesToSave) { + computeAndCacheLevelRecursive(agency, agencyLevelsCache, rootAgencyCode, anchorLevel, processedAgencies); + } + // 第二遍:从缓存中设置级别到实体 + for (Agency agency : agenciesToSave) { + agency.setAgencyLevel(agencyLevelsCache.get(agency.getAgencyCode())); + } + + // 根据级别设置是否为叶子节点 + for (Agency agency : agenciesToSave) { + agency.setLeaf(processedAgencies.values().stream() + .noneMatch(a -> a.getParent() != null && a.getParent().getAgencyCode().equals(agency.getAgencyCode()))); + } + + // 在所有父子关系和级别设置完毕后,计算路径 + for (Agency agency : agenciesToSave) { + agency.setAgencyPath(calculateAgencyPath(agency, processedAgencies, agencyRepository)); + } + + if (!agenciesToSave.isEmpty()) { + agencyRepository.saveAll(agenciesToSave); + log.info("成功同步 {} 条执法机构信息到数据库。", agenciesToSave.size()); + } else { + log.info("没有需要保存的执法机构信息。"); + } + + } catch (JsonProcessingException e) { + log.error("解析API响应时发生JSON处理错误。", e); + } catch (Exception e) { + log.error("执行执法机构信息同步任务时发生未知错误。", e); + } + log.info("执法机构信息同步任务结束。"); + } + + /** + * 递归查找所有下级机构 + * + * @param agencyCode 当前机构代码 + * @param apiAgencyMap 所有API机构的映射 + * @param collectedCodes 收集的相关机构代码集合 + */ + private void findAllChildAgencies(String agencyCode, Map apiAgencyMap, Set collectedCodes) { + // 查找所有直接下级机构 + apiAgencyMap.values().stream() + .filter(agency -> agencyCode.equals(agency.getSjgajgjgdm())) + .forEach(childAgency -> { + String childCode = childAgency.getGajgjgdm(); + if (childCode != null && !collectedCodes.contains(childCode)) { + collectedCodes.add(childCode); + // 递归查找子机构的下级 + findAllChildAgencies(childCode, apiAgencyMap, collectedCodes); + } + }); + } + + /** + * 从外部API获取数据,包含令牌刷新和重试逻辑 + * + * @param requestBody 请求体 + * @param url API的URL + * @return API响应体字符串,如果失败则返回null或抛出异常 + * @throws JsonProcessingException 如果请求体序列化失败 + */ + @SneakyThrows + private String fetchDataFromApi(Map requestBody, String url) throws JsonProcessingException { + int maxRetries = 2; // 允许一次初始尝试和一次重试 + + for (int attempt = 1; attempt <= maxRetries; attempt++) { + AccessToken accessToken = authService.getTokenBackend(agencyTokenMobilePhone); + if (accessToken == null || accessToken.getAccess_token() == null) { + log.error("获取访问令牌失败 (尝试 {}/{}),URL: {}. 请检查AuthService配置和令牌提供者。", attempt, maxRetries, url); + throw new RuntimeException("获取API调用的访问令牌失败。"); + } + String currentToken = accessToken.getAccess_token(); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBearerAuth(currentToken); + + HttpEntity entity = new HttpEntity<>(objectMapper.writeValueAsString(requestBody), headers); + ResponseEntity responseEntity; + + try { + log.info("尝试API调用 (尝试 {}/{}) 到 URL: {},请求体: {}", attempt, maxRetries, url, requestBody); + responseEntity = restTemplate.postForEntity(url, entity, String.class); + + if (responseEntity.getStatusCode().is2xxSuccessful()) { + log.info("API调用成功 (尝试 {}/{}),URL: {}", attempt, maxRetries, url); + return responseEntity.getBody(); + } else { + log.error("API调用失败 (尝试 {}/{}),状态码 {}: {}. URL: {}, 请求体: {}", + attempt, maxRetries, responseEntity.getStatusCode(), responseEntity.getBody(), url, requestBody); + // 对于非2xx但非401的错误,当前不重试 + return null; // 或根据需要抛出特定异常 + } + } catch (HttpClientErrorException e) { + if (e.getStatusCode() == HttpStatus.UNAUTHORIZED) { + log.warn("API调用未授权 (尝试 {}/{}),可能令牌已过期. URL: {}. 正在尝试刷新令牌...", + attempt, maxRetries, url); + if (attempt < maxRetries) { + try { + authService.refreshTokenBackend(accessToken.getAccess_token(), accessToken.getToken_type(), accessToken.getRefresh_token(), agencyTokenMobilePhone); // 请求刷新令牌 + log.info("请求刷新令牌成功。下一次尝试将使用新令牌。"); + // 循环将继续,下一次迭代将再次调用getTokenBackendByYddh + } catch (Exception refreshException) { + log.error("请求刷新令牌期间发生异常: {}. 中止对URL: {} 的重试。", + refreshException.getMessage(), url, refreshException); + throw new RuntimeException("API调用未授权后,请求刷新令牌失败。", refreshException); + } + } else { + log.error("API调用未授权,尝试 {} 次后仍然失败 (已达最大重试次数),URL: {}.", attempt, url); + throw new RuntimeException("多次重试后,API调用因授权问题失败,URL: " + url, e); + } + } else { + // 其他 HttpClientErrorException (非401的4xx或5xx错误) + log.error("API调用时发生HttpClientErrorException (尝试 {}/{}),URL {}: {} - {}. 请求体: {}", + attempt, maxRetries, url, e.getStatusCode(), e.getResponseBodyAsString(), requestBody, e); + throw e; // 重新抛出其他HTTP客户端错误 + } + } catch (RestClientException e) { + // 一般的 RestClientException (例如网络问题) + log.error("API调用时发生RestClientException (尝试 {}/{}),URL {}: {}. 请求体: {}", + attempt, maxRetries, url, e.getMessage(), requestBody, e); + if (attempt == maxRetries) { // 如果是最后一次尝试,则抛出异常 + throw e; + } + log.warn("发生RestClientException后进行重试,尝试 {}/{},URL: {}", attempt, maxRetries, url); + // 可选:在重试网络相关错误前添加短暂延迟 + // try { Thread.sleep(1000); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } + } + } + // 如果循环结束仍未返回或抛出异常 (理论上不应发生) + log.error("多次重试后未能成功从API获取数据,URL: {}", url); + throw new RuntimeException("多次重试后未能从API获取数据,URL: " + url); + } + + /** + * 生成执法机构简码。 + * + * @param gajgjgdm 完整的执法机构代码 + * @return 生成的执法机构简码 + */ + private String generateAgencySimpleCode(String gajgjgdm) { + if (gajgjgdm == null || gajgjgdm.isEmpty()) { + return null; + } + return StringUtils.trimEven0(gajgjgdm); + } + + /** + * 计算并生成机构的层级路径字符串。 + * 路径由机构代码组成,以 '.' 分隔,从最高层级到当前机构。 + * 例如:root.parent.child.currentAgencyCode + * + * @param currentAgency 需要计算路径的当前机构实体。父级关系应已在此实体中设置。 + * @param processedAgencies 当前批次中已处理的机构映射 (agencyCode -> Agency),此方法当前未使用,但保留以备将来扩展。 + * @param agencyRepository 机构仓库,此方法当前未使用,但保留以备将来扩展。 + * @return 生成的机构路径字符串,如果无法生成(例如,机构或其代码为空),则返回null。 + * 如果达到最大层级深度,则返回以 "ERROR_MAX_DEPTH_" 开头的路径。 + */ + private String calculateAgencyPath(Agency currentAgency, Map processedAgencies, AgencyRepository agencyRepository) { + if (currentAgency == null || currentAgency.getAgencyCode() == null) { + log.warn("计算机构路径时,当前机构或机构代码为空。"); + return null; + } + // Placeholder log removed as we are implementing the logic now. + + List pathParts = new ArrayList<>(); + Agency tempAgency = currentAgency; + int depth = 0; + final int maxDepth = 20; // Max depth to prevent infinite loops in case of data issues + + while (tempAgency != null && depth < maxDepth) { + if (tempAgency.getAgencyCode() == null) { + log.warn("在为起始机构 '{}' ({}) 计算路径时,遇到机构代码为空的机构。当前已构建路径: [{}]. 跳过此路径部分。", + currentAgency.getAgencyName(), currentAgency.getAgencyCode(), String.join(".", pathParts)); + break; // 或根据需求处理,例如,对此空代码使用占位符 + } + pathParts.add(tempAgency.getAgencySimpleCode()); + tempAgency = tempAgency.getParent(); // 正确地向上遍历父级链 + depth++; + } + + if (depth >= maxDepth) { + log.error("calculateAgencyPath: 机构 '{}' (代码: {}) 计算路径时达到最大深度 ({})。可能存在循环依赖或层级过深。已构建路径: [{}]", + maxDepth, currentAgency.getAgencyName(), currentAgency.getAgencyCode(), String.join(".", pathParts)); + // 返回一个独特的错误路径或根据需求处理,例如,返回已构建路径并添加错误前缀 + Collections.reverse(pathParts); // 反转已有的路径部分,使其顺序正确 + return "ERROR_MAX_DEPTH_" + String.join(".", pathParts); + } + + if (pathParts.isEmpty()) { + log.warn("calculateAgencyPath: 机构 '{}' (代码: {}) 的路径部分列表为空。将其自身代码作为路径返回。", currentAgency.getAgencyName(), currentAgency.getAgencyCode()); + return currentAgency.getAgencyCode(); // 理想情况下,如果当前机构及其代码有效,则不应发生此情况 + } + + Collections.reverse(pathParts); + return String.join(".", pathParts); + } + + /** + * 递归计算并缓存机构级别。 + * "西安市应急管理局" 为级别2,其子级为父级级别+1。 + * + * @param agency 当前处理的机构 + * @param levelCache 存储已计算级别的缓存 (agencyCode -> level) + * @param anchorAgencyCode 锚点机构代码 + * @param anchorLevel 锚点机构的级别 + * @param allAgenciesMap 当前批次中已处理的机构映射 (agencyCode -> Agency),用于查找父级实体 + */ + private void computeAndCacheLevelRecursive(Agency agency, Map levelCache, + String anchorAgencyCode, int anchorLevel, + Map allAgenciesMap) { + if (agency == null || agency.getAgencyCode() == null || levelCache.containsKey(agency.getAgencyCode())) { + return; // 无效机构或已处理 + } + + // 锚点机构处理 + if (anchorAgencyCode.equals(agency.getAgencyCode())) { + levelCache.put(agency.getAgencyCode(), anchorLevel); + return; + } + + // 非锚点机构,依赖父级级别 + Agency parent = agency.getParent(); // 父级关系应已在此前步骤中设置 + + if (parent == null) { + // 没有父级且不是锚点机构,无法通过此逻辑确定级别 + log.warn("机构 '{}' ({}) 非锚点且无父级,无法基于锚点逻辑确定其级别。", + agency.getAgencyName(), agency.getAgencyCode()); + levelCache.put(agency.getAgencyCode(), null); // 标记为无法计算或设为默认值 + return; + } + + // 确保父级级别已计算(递归调用) + computeAndCacheLevelRecursive(parent, levelCache, anchorAgencyCode, anchorLevel, allAgenciesMap); + + Integer parentLevel = levelCache.get(parent.getAgencyCode()); + if (parentLevel != null) { + levelCache.put(agency.getAgencyCode(), parentLevel + 1); + } else { + // 父级级别未能确定,则子级也无法确定 + log.warn("未能确定父级机构 '{}' ({}) 的级别,因此无法确定子级机构 '{}' ({}) 的级别。", + parent.getAgencyName(), parent.getAgencyCode(), + agency.getAgencyName(), agency.getAgencyCode()); + levelCache.put(agency.getAgencyCode(), null); // 标记为无法计算或设为默认值 + } + } + + // --- Inner DTO classes for API Response --- + + @Getter + @Setter + private static class ApiResponse { + private String code; + private boolean success; + private ApiData data; + private String msg; + + } + + @Getter + @Setter + private static class ApiData { + private int offset; + private int limit; + @JsonAlias("totalCount") + private String totalCountStr; + private List list; + @JsonProperty("totalPage") + private int totalPage; + + public int getTotalCount() { + try { + return Integer.parseInt(totalCountStr); + } catch (NumberFormatException e) { + return 0; + } + } + + + } + + @Getter + @Setter + @ToString + private static class ApiAgency { + private String ywlsh; + @JsonProperty("gajgjgdm") + private String gajgjgdm; + @JsonProperty("sjgajgjgdm") + private String sjgajgjgdm; + @JsonProperty("gajgmc") + private String gajgmc; + @JsonProperty("xssx") + private Integer xssx; + private String sjztdm; + @JsonProperty("gajgmcjc") + private String gajgmcjc; + private String dwlb; + private String xzqhdm; + @JsonProperty("gajgmclbdm") + private String gajgmclbdm; + @JsonProperty("gajgbmlbdm") + private String gajgbmlbdm; + private Integer subnum; + + } + +} diff --git a/server/src/main/java/com/aisino/iles/lawenforcement/schedule/service/EnterpriseJoinScheduleService.java b/server/src/main/java/com/aisino/iles/lawenforcement/schedule/service/EnterpriseJoinScheduleService.java new file mode 100644 index 0000000..778dca5 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/lawenforcement/schedule/service/EnterpriseJoinScheduleService.java @@ -0,0 +1,230 @@ +package com.aisino.iles.lawenforcement.schedule.service; + +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.aisino.iles.lawenforcement.model.Enterprise; +import com.aisino.iles.lawenforcement.model.Enterprise_; +import com.aisino.iles.lawenforcement.repository.*; +import com.aisino.iles.lawenforcement.service.AuthService; +import com.smartlx.sso.client.model.AccessToken; +import com.smartlx.sso.client.properties.SsoClientProperties; +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.jpa.domain.Specification; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 企业设备接入信息定时任务 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class EnterpriseJoinScheduleService { + + private final EnterpriseRepository enterpriseRepository; + private final EnterpriseCameraRepository enterpriseCameraRepository; + private final EnterpriseWarnRepository enterpriseWarnRepository; + private final AuthService authService; + private final SsoClientProperties ssoClientProperties; + + @Value("${third-party.enterprise.yddh:}") // 使用正确的配置项获取令牌标识 + private String tokenMobilePhone; + + @Value("${third-party.enterprise.selfInsAndSelfReport:}") + private String selfInsAndSelfReportUrl; + + + public Page pageEnterprise(int page, int limit) { + Specification spec = (root, query, cb) -> cb.and(cb.equal(root.get(Enterprise_.delFlag), 0), cb.isNotNull(root.get(Enterprise_.unifiedSocialCode))); + return enterpriseRepository.findAll(spec, PageRequest.of(page, limit)); + } + + + /** + * 企业摄像头信息和预警信息同步任务 + * 每天定时执行(通过配置文件动态指定 cron 表达式) + */ + @Scheduled(cron = "0 0 1 * * ?") + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void scheduledUpdateEnterpriseTask() { + log.info("===== 开始执行企业摄像头信息和预警信息同步任务 ====="); + + int page = 0; + final int limit = 2000; + Page pageResult; + + do { + // 分页查询企业信息 + pageResult = pageEnterprise(page, limit); + List enterprises = pageResult.getContent(); + + if (!enterprises.isEmpty()) { + // 更新企业摄像头信息和预警信息 + batchUpdate(enterprises); + log.info("已处理企业数据:page={}, size={}", page, enterprises.size()); + } + + page++; + } while (pageResult.hasNext()); + + log.info("===== 企业摄像头信息和预警信息同步任务完成 ====="); + } + + @Transactional(propagation = Propagation.MANDATORY) + public void batchUpdate(List enterprises) { + List unifiedSocialCodes = enterprises.stream().map(Enterprise::getUnifiedSocialCode).collect(Collectors.toList()); + List> cameraList = enterpriseCameraRepository.countGroupByUnifiedSocialCode(unifiedSocialCodes); + Map cameraMap = cameraList.stream().collect(Collectors.toMap(row -> (String) row.get("unified_social_code"), row -> ((Number) row.get("num")).longValue())); +// List> warnList = enterpriseWarnRepository.countGroupByUnifiedSocialCode(unifiedSocialCodes); +// Map warnMap = warnList.stream().collect(Collectors.toMap(row -> (String) row.get("unified_social_code"), row -> ((Number) row.get("num")).longValue())); + // 批量更新视频接入状态 + enterprises.forEach(enterprise -> { + Long count = cameraMap.getOrDefault(enterprise.getUnifiedSocialCode(), 0L); + enterprise.setVideoAccess(count > 0 ? "1" : "0"); + +// Long count2 = warnMap.getOrDefault(enterprise.getUnifiedSocialCode(), 0L); +// enterprise.setPermitAccess(count2 > 0 ? "1" : "0"); + }); + enterpriseRepository.saveAll(enterprises); + } + + + /** + * 企业感知信息同步任务 + * 每天定时执行(通过配置文件动态指定 cron 表达式) + */ + @Scheduled(cron = "0 0 2 * * ?") + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void scheduledUpdateEnterpriseTask2() { + log.info("===== 开始执行企业感知设备信息同步任务 ====="); + + int page = 0; + final int limit = 2000; + Page pageResult; + + do { + // 分页查询企业信息 + pageResult = pageEnterprise(page, limit); + List enterprises = pageResult.getContent(); + + if (!enterprises.isEmpty()) { + // 更新企业感知设备信息 + batchUpdate2(enterprises); + log.info("已处理企业数据:page={}, size={}", page, enterprises.size()); + } + + page++; + } while (pageResult.hasNext()); + + log.info("===== 企业感知设备信息同步任务完成 ====="); + } + + @Transactional(propagation = Propagation.MANDATORY) + public void batchUpdate2(List enterprises) { + String body = HttpUtil.createGet("http://10.22.245.227:1084/dah-api/public/monitor/units/list").execute().body(); + log.info("企业感知设备信息:body={}", body); + JSONObject jsonObject = JSONUtil.parseObj(body); + Long code = jsonObject.getLong("code"); + if (code == 200) { + List socialCodes = jsonObject.getJSONArray("data") + .stream() + .filter(JSONObject.class::isInstance) + .map(obj -> ((JSONObject) obj).getStr("socialCode")) + .collect(Collectors.toList()); + // 批量更新企业感知设备信息 + enterprises.forEach(enterprise -> { + String unifiedSocialCode = enterprise.getUnifiedSocialCode(); + boolean hasPerceptionAccess = socialCodes.contains(unifiedSocialCode); + enterprise.setPerceptionAccess(hasPerceptionAccess ? "1" : "0"); + }); + } + enterpriseRepository.saveAll(enterprises); + } + + + /** + * 企业自查自报信息同步任务 + * 每天定时执行(通过配置文件动态指定 cron 表达式) + */ + @Scheduled(cron = "0 0 3 * * ?") + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void scheduledUpdateEnterpriseTask3() { + if (tokenMobilePhone == null || tokenMobilePhone.isEmpty()) { + log.error("API令牌YDDH (third-party.enterprise.yddh) 未配置."); + return; + } + + log.info("===== 开始执行企业自查自报信息同步任务 ====="); + + int page = 0; + final int limit = 2000; + Page pageResult; + + do { + // 分页查询企业信息 + pageResult = pageEnterprise(page, limit); + List enterprises = pageResult.getContent(); + + if (!enterprises.isEmpty()) { + // 更新企业自查自报信息 + batchUpdate3(enterprises); + log.info("已处理企业数据:page={}, size={}", page, enterprises.size()); + } + + page++; + } while (pageResult.hasNext()); + + log.info("===== 企业自查自报信息同步任务完成 ====="); + } + + @Transactional(propagation = Propagation.MANDATORY) + public void batchUpdate3(List enterprises) { + try { + AccessToken tokenBackend = authService.getTokenBackend(tokenMobilePhone); + String accessToken = tokenBackend.getAccess_token(); + String tokenType = tokenBackend.getToken_type(); + String clientId = ssoClientProperties.getClientId(); + for (Enterprise enterprise : enterprises) { + try { + Map param = new HashMap<>(); + param.put("limit", "1"); + param.put("page", 1); + Map entity = new HashMap<>(); + entity.put("unifiedSocialCode", enterprise.getUnifiedSocialCode()); + param.put("entity", entity); + log.info("url:{}, token:{}, clientId:{}", selfInsAndSelfReportUrl, accessToken, clientId); + String body = HttpRequest.post(selfInsAndSelfReportUrl) + .header("Content-Type", "application/json") + .header("Authorization", tokenType + " " + accessToken) + .header("appId", clientId) + .body(JSONUtil.toJsonStr(param)) + .execute() + .body(); + JSONObject jsonObject = JSONUtil.parseObj(body); + log.info("企业自查自报信息结果:{}", jsonObject); + if (jsonObject.getBool("success")) { + Long totalCount = Long.valueOf(jsonObject.getJSONObject("data").getStr("totalCount")); + enterprise.setInfoAccess(totalCount > 0 ? "1" : "0"); + } + } catch (Exception e) { + log.error("处理企业 {} 时出错: {}", enterprise.getUnitName(), e.getMessage()); + } + } + enterpriseRepository.saveAll(enterprises); + } catch (Exception e) { + log.error("获取 Token 失败: {}", e.getMessage()); + } + } + +} diff --git a/server/src/main/java/com/aisino/iles/lawenforcement/schedule/service/EnterprisePoniteScheduleService.java b/server/src/main/java/com/aisino/iles/lawenforcement/schedule/service/EnterprisePoniteScheduleService.java new file mode 100644 index 0000000..315c86f --- /dev/null +++ b/server/src/main/java/com/aisino/iles/lawenforcement/schedule/service/EnterprisePoniteScheduleService.java @@ -0,0 +1,699 @@ +package com.aisino.iles.lawenforcement.schedule.service; + +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONObject; +import com.aisino.iles.lawenforcement.model.*; +import com.aisino.iles.lawenforcement.model.dto.IndustryEnterpriseDto; +import com.aisino.iles.lawenforcement.model.dto.WaferEnterpriseDto; +import com.aisino.iles.lawenforcement.repository.*; +import com.aisino.iles.lawenforcement.service.AuthService; +import com.aisino.iles.lawenforcement.service.DictInterfaceService; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.smartlx.sso.client.model.AccessToken; +import com.smartlx.sso.client.properties.SsoClientProperties; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionTemplate; +import org.springframework.util.StringUtils; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 企业信息定时任务 + */ +@Service +@Slf4j +public class EnterprisePoniteScheduleService { + private final EnterpriseRepository enterpriseRepo; + private final EnterpriseHistoryRepository enterpriseHistoryRepo; + private final AgencyRepository agencyRepo; + private final AuthService authService; + private final TransactionTemplate transactionTemplate; + private final String mobilePhone; + private final DictInterfaceService dictService; + private String accessToken; + private String tokenType; + private String refreshToken; + private final ObjectMapper objectMapper; + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + public EnterprisePoniteScheduleService(EnterpriseRepository enterpriseRepo, + EnterpriseHistoryRepository enterpriseHistoryRepo, + AgencyRepository agencyRepo, + AuthService authService, + ObjectMapper objectMapper, + @Value("${third-party.enterprise.yddh:}") String mobilePhone, + TransactionTemplate transactionTemplate, + DictInterfaceService dictService) { + this.enterpriseRepo = enterpriseRepo; + this.enterpriseHistoryRepo = enterpriseHistoryRepo; + this.agencyRepo = agencyRepo; + this.authService = authService; + this.objectMapper = objectMapper; + this.mobilePhone = mobilePhone; + this.transactionTemplate = transactionTemplate; + this.dictService = dictService; + } + + + /** + * 工作台信息导入 + * 每天10点执行一次 + * (cron = "0 0 15 * * ?") + * (cron = "${scheduler.expression}") + * (fixedRate = 120000) + */ + @Scheduled(cron = "${scheduler.task.enterprise.cron.expression}") + public void scheduledEnterpriseTask() { + getToken(); + dictService.getDictItem("YJ_CODE_9017", tokenType, accessToken); + //enterpriseTask(); + } + + /** + * 工作台信息导入 + * 每天15点执行一次 + * (cron = "0 15 * * * ?") + * (cron = "${scheduler.expression}") + * (fixedRate = 120000) + */ + // @Scheduled(cron = "0 * * * * ?") + @Scheduled(cron = "${scheduler.task.waferEnterprise.cron.expression}") + public void scheduledWaferEnterpriseTask() { + getToken(); + dictService.getDictItem("YJ_CODE_9017", tokenType, accessToken); + enterpriseTask(); + enterpriseWaferTask(); + } + + /** + * 导入企业信息 + */ + public void enterpriseTask() { + log.info("开始导入企业信息..."); + int page = 1; + boolean b = updateOrImportIndustryEnterprise(page); + while (b) { + page++; + b = updateOrImportIndustryEnterprise(page); + } + log.info("企业信息导入完成..."); + } + + /** + * 危化导入企业信息 + */ + public void enterpriseWaferTask() { + log.info("开始导入危化企业信息..."); + int page = 1; + boolean b = updateOrImportWaferEnterprise(page); + while (b) { + page++; + b = updateOrImportWaferEnterprise(page); + } + log.info("危化企业信息导入完成..."); + } + + /** + * 获取token + */ + private void getToken() { + try { + AccessToken accessTokenObj = authService.getTokenBackend(mobilePhone); + if (accessTokenObj != null) { + accessToken = accessTokenObj.getAccess_token(); + tokenType = accessTokenObj.getToken_type(); + refreshToken = accessTokenObj.getRefresh_token(); + log.info("获取token成功"); + } else { + log.error("获取token失败,返回为null"); + throw new RuntimeException("无法获取有效的访问令牌"); + } + } catch (Exception e) { + log.error("获取token失败", e); + throw new RuntimeException("无法获取有效的访问令牌", e); + } + } + + /** + * 刷新token + */ + private void refreshToken() { + try { + authService.refreshTokenBackend(accessToken, tokenType, refreshToken, mobilePhone); + // 刷新后重新获取token + getToken(); + log.info("刷新token成功"); + } catch (Exception e) { + log.error("刷新token失败,尝试重新获取token", e); + getToken(); + } + } + + @Transactional + public boolean updateOrImportIndustryEnterprise(int page) { + boolean flag = false; + try { + JSONObject jsonBody = new JSONObject(); + JSONObject jsonPram = new JSONObject(); + jsonBody.set("method", "GET"); + jsonBody.set("jsonObject", jsonPram); + jsonBody.set("pageNum", page); + jsonBody.set("pageSize", 1000); + jsonBody.set("path", "/edataservice/api/gmqyxx"); + jsonBody.set("secret", "805cd25953b1ba8852aac00564fe163213567518a5a509a849d8384b7b090b70"); + String body = HttpUtil.createPost("http://10.22.245.216:29999/jeecg-data-service/edataservice/api/") + .header("Content-Type", "application/json") + .body(jsonBody.toString()) + .execute() + .body(); + JsonNode jsonNode = objectMapper.readValue(body, JsonNode.class); + if (jsonNode.has("code") && 200 == jsonNode.get("code").asInt() && jsonNode.has("result")) { + JsonNode data = objectMapper.readValue(jsonNode.get("result").toString(), JsonNode.class); + if (null != data && data.has("records")) { + JsonNode arrNode = data.get("records"); + if (arrNode.isArray()) { + + long current = data.get("current").asLong(); + long pages = data.get("pages").asLong(); + if (current < pages) flag = true; + List dtos = objectMapper.readValue(arrNode.toString(), new TypeReference<>() {}); + + Set usccSet = dtos.stream().map(IndustryEnterpriseDto::getTyshxydm).filter(StringUtils::hasText).collect(Collectors.toSet()); + // 历史企业 + List enterpriseList = enterpriseRepo.findByUnifiedSocialCodeIn(usccSet); + List enterprises = new ArrayList<>(); + log.info("开始处理:{}条数据",dtos.size()); + for (IndustryEnterpriseDto dto : dtos) { + if (StringUtils.hasText(dto.getTyshxydm())) { + Enterprise oldLtd = enterpriseList.stream().filter(o -> o.getUnifiedSocialCode().equals(dto.getTyshxydm())).findFirst().orElse(null); + if (null != oldLtd) { + EnterpriseHistory history = new EnterpriseHistory(); + BeanUtils.copyProperties(oldLtd, history); + history.setTheType("定时任务修改"); + LocalDateTime now = LocalDateTime.now(); + history.setHistoryTime(now); + enterpriseHistoryRepo.save(history); + + oldLtd.setUnitName(dto.getQymc()); // 企业名称 + //oldLtd.setUnitCode(); // 生产经营单位编码 + oldLtd.setUnifiedSocialCode(dto.getTyshxydm()); // 统一社会信用代码 + if ("0".equals(dto.getScjyztxh())) { + oldLtd.setBusinessStatus("11"); // 营业状态 + } else { + oldLtd.setBusinessStatus("13"); // 营业状态 + } + Optional.ofNullable(dto.getQxbm()).filter(StringUtils::hasText).flatMap(agencyRepo::findByAgencyCode).ifPresent(oldLtd::setAgency); // 机构 + //oldLtd.setRegulatedIndustry(); // 国民经济行业分类 + //oldLtd.setAreaComb(); // 注册地址(全) + oldLtd.setDetailedAddress(dto.getDz()); // 注册详细地址 + //oldLtd.setOpLocAreaComb(); // 经营地址(全) + oldLtd.setOpLocAddress(dto.getDz()); // 经营详细地址 + if (null != dto.getHylb() && "1".equals(dto.getHylb())) { + oldLtd.setIndustryType("7"); // 监管行业 + } else { + oldLtd.setIndustryType("6"); // 监管行业 + } + oldLtd.setLegalRepresentative(dto.getFddbr()); // 法定代表人姓名 + oldLtd.setLegalPhone(dto.getFddbrlxdh()); // 法人联系电话 + //oldLtd.setLegalDuty(); // 法定代表人职位 + //oldLtd.setOpScope(); // 主营业务 + //oldLtd.setAffiliation(); // 行政隶属关系 + //oldLtd.setOilExtract(); // 是否石油开采 + //oldLtd.setSafetyFocus(); // 是否安全生产监管重点企业 + //oldLtd.setIsScaleEnterprise(); // 是否规模以上企业 + //oldLtd.setSafetyLic(); // 是否有安全生产许可证 + //oldLtd.setProvince(); // 注册地址-省 + //oldLtd.setCity(); // 注册地址-市 + //oldLtd.setCounty(); // 注册地址-县/区 + //国民经济行业分类代码《国民经济行业分类》(GB/T 4754—2017) +// if (StringUtils.hasText(dto.getGmjjhyfldm())) { +// String xxdm = dictService.getTopXxdm(dto.getGmjjhyfldm()); +// enterprise.setRegulatedIndustry(xxdm); +// String[] ss = xxdm.split("/"); +// for (int i = 0; i < ss.length; i++) { +// if (i == 0) enterprise.setEconCategory(ss[i]); // 国民经济行业分类代码 +// if (i == 1) enterprise.setEconCategoryTypea(ss[i]); // 国民经济行业分类A型 +// if (i == 2) enterprise.setEconCategoryTypeb(ss[i]); // 国民经济行业分类B型 +// if (i == 3) enterprise.setEconCategoryTypec(ss[i]); // 国民经济行业分类C型 +// } +// } + //oldLtd.setOpLocProvince(); // 经营地址-省 + //oldLtd.setOpLocCity(); // 经营地址-市 + oldLtd.setOpLocArea(dto.getQxmc()); // 经营地址-区 + //oldLtd.setBusinessRating(); // 企业分级 + //oldLtd.setBusinessCategory(); // 企业分类 + //oldLtd.setPostalCode(); // 邮政编码 + if (null != dto.getJjlxxh() && StringUtils.hasText(dto.getJjlxxh())) { // 国民经济类型 + String economicType = dto.getJjlxxh(); + switch (economicType) { + case "110" -> oldLtd.setEconomicType("1"); + case "120" -> oldLtd.setEconomicType("2"); + case "130" -> oldLtd.setEconomicType("3"); + case "140" -> oldLtd.setEconomicType("4"); + case "150" -> oldLtd.setEconomicType("5"); + case "160" -> oldLtd.setEconomicType("6"); + case "170" -> oldLtd.setEconomicType("7"); + case "190" -> oldLtd.setEconomicType("8"); + case "290" -> oldLtd.setEconomicType("9"); + case "310" -> oldLtd.setEconomicType("10"); + case "330" -> oldLtd.setEconomicType("11"); + case "159" -> oldLtd.setEconomicType("12"); + } + } + //oldLtd.setLegalPersonIdType(); // 法定代表人证件类型 + //oldLtd.setLegalPersonIdNumber(); // 法定代表人证件号码 + //oldLtd.setIsInChemicalPark(); // 是否位于化工园区 + //oldLtd.setExpiryDate(); // 营业执照有效期 + //oldLtd.setOrgCode(); // 组织机构代码 + oldLtd.setUnitScale(dto.getQygm()); // 企业规模 + //oldLtd.setEstablishmentDate(); // 成立日期 + //oldLtd.setRegisteredCapital(); // 注册资本(万元) + Optional.ofNullable(dto.getJd()).filter(StringUtils::hasText).ifPresent(oldLtd::setLongitude); // 经度 + Optional.ofNullable(dto.getWd()).filter(StringUtils::hasText).ifPresent(oldLtd::setLatitude); // 纬度 + + oldLtd.setRegistrationType(dto.getJjxz()); // 登记注册企业类别 + //oldLtd.setLicenseNumber(); // 许可证编号 + //oldLtd.setSafetyLevel(); // 安全生产标准化等级 + //oldLtd.setIssuingAuthority(); // 发证机关 + oldLtd.setEmployeeCount(dto.getZgzrs()); // 从业人员数量 + //oldLtd.setIsTraining(); // 是否为培训机构 + //oldLtd.setProductionManageInfoExp(); // 安全基础管理信息 + //oldLtd.setProductionEquipmentExp(); // 生产设备设施信息 + //oldLtd.setNocoalIndustryExpansion(); // 非煤行业扩展信息 + //oldLtd.setDangerFeatureInfo(); // 危化行业扩展信息 + //oldLtd.setFireworksAddInfo(); // 烟花爆竹行业扩展信息 + oldLtd.setIndustryExpandInfos(dto.toString()); // 工贸行业扩展信息 + oldLtd.setMainName(dto.getZyfzr()); // 主要负责人姓名 + oldLtd.setMainPhone(dto.getZyfzrlxdh()); // 主要负责人电话 + //oldLtd.setMajorAccidentHazard(); // 重大事故隐患年份 + //oldLtd.setMajorDanger(); // 是否有重大危险源 + //oldLtd.setInterid(dto.getYwlsh()); // 第三方对接ID + try { + Optional.ofNullable(dto.getCjsj()).filter(StringUtils::hasText).ifPresent(cjsj -> oldLtd.setEntryTime(LocalDateTime.parse(cjsj.substring(0, 19), DATE_TIME_FORMATTER))); // 录入时间 + Optional.ofNullable(dto.getXgsj()).filter(StringUtils::hasText).ifPresent(xgsj -> oldLtd.setModifyTime(LocalDateTime.parse(xgsj.substring(0, 19), DATE_TIME_FORMATTER)));// 修改时间 + + } catch (Exception e) { + log.info("时间转换问题:{}", e); + } + //oldLtd.setModifyUserName(); // 修改用户名 + //oldLtd.setModifyNickName(); // 修改用户昵称 + //oldLtd.setDataSource(); // 数据来源 + //oldLtd.setDelFlag(); // 是否删除 + //oldLtd.setIsRemove(); // 是否移除计划清单 + oldLtd.setPumpTime(now); // 抽取时间 + //oldLtd.setShortName(); // 单位简称 + oldLtd.setContactPerson(dto.getZyfzr()); // 单位联系人 + oldLtd.setContactPhone(dto.getZyfzrlxdh()); // 联系电话 + //oldLtd.setDynamicRiskLevel(); // 动态风险等级 + //oldLtd.setBusinessScope(); // 经营范围 + //oldLtd.setTown(); // 所在乡镇 + //oldLtd.setVillage(); // 所在村 + oldLtd.setStreet(dto.getXzjdmc()); // 所在街道 + //oldLtd.setRiskCategory(); // 风险类别 + //oldLtd.setUnitType(); // 单位类型 + if ("0".equals(dto.getScjyztxh())) { // 运营状态 + oldLtd.setOperationStatus("正常"); + } else { + oldLtd.setOperationStatus("停业"); + } + //oldLtd.setMunicipalRegulator(); // 市监管部门 + //oldLtd.setDistrictRegulator(); // 区县监管部门 + //oldLtd.setDistrictName(); // 区县名称 + //oldLtd.setVideoAccess("0"); // 视频接入状态 + //oldLtd.setPerceptionAccess("0"); // 感知接入状态 + //oldLtd.setInfoAccess("0"); // 资料接入状态 + //oldLtd.setPermitAccess("0"); // 许可接入状态 + //oldLtd.setInsEnterprses(); // 巡查企业列表 + //oldLtd.setIsWithin(); // 是否规上限上企业 + //oldLtd.setGyqyfl(); // 工业企业分类 + enterprises.add(oldLtd); + } + }else{ + Enterprise enterprise = new Enterprise(); + enterprise.setUnitName(dto.getQymc()); // 企业名称 + enterprise.setUnifiedSocialCode(dto.getTyshxydm()); // 统一社会信用代码 + if ("0".equals(dto.getScjyztxh())) { + enterprise.setBusinessStatus("11"); // 营业状态 + } else { + enterprise.setBusinessStatus("13"); // 营业状态 + } + Optional.ofNullable(dto.getQxbm()).filter(StringUtils::hasText).flatMap(agencyRepo::findByAgencyCode).ifPresent(enterprise::setAgency); // 机构 + enterprise.setDetailedAddress(dto.getDz()); // 注册详细地址 + enterprise.setOpLocAddress(dto.getDz()); // 经营详细地址 + enterprise.setLegalRepresentative(dto.getFddbr()); // 法定代表人姓名 + enterprise.setLegalPhone(dto.getFddbrlxdh()); // 法人联系电话 + //国民经济行业分类代码《国民经济行业分类》(GB/T 4754—2017) +// if (StringUtils.hasText(dto.getGmjjhyfldm())) { +// String xxdm = dictService.getTopXxdm(dto.getGmjjhyfldm()); +// enterprise.setRegulatedIndustry(xxdm); +// String[] ss = xxdm.split("/"); +// for (int i = 0; i < ss.length; i++) { +// if (i == 0) enterprise.setEconCategory(ss[i]); // 国民经济行业分类代码 +// if (i == 1) enterprise.setEconCategoryTypea(ss[i]); // 国民经济行业分类A型 +// if (i == 2) enterprise.setEconCategoryTypeb(ss[i]); // 国民经济行业分类B型 +// if (i == 3) enterprise.setEconCategoryTypec(ss[i]); // 国民经济行业分类C型 +// } +// } + if(null!=dto.getHylb()&&"1".equals(dto.getHylb())){ + enterprise.setIndustryType("7"); // 监管行业 + }else { + enterprise.setIndustryType("6"); // 监管行业 + } + + enterprise.setOpLocArea(dto.getQxmc()); // 经营地址-区 + if (null!=dto.getJjlxxh()&&StringUtils.hasText(dto.getJjlxxh())) { // 国民经济类型 + String economicType = dto.getJjlxxh(); + switch (economicType) { + case "110" -> enterprise.setEconomicType("1"); + case "120" -> enterprise.setEconomicType("2"); + case "130" -> enterprise.setEconomicType("3"); + case "140" -> enterprise.setEconomicType("4"); + case "150" -> enterprise.setEconomicType("5"); + case "160" -> enterprise.setEconomicType("6"); + case "170" -> enterprise.setEconomicType("7"); + case "190" -> enterprise.setEconomicType("8"); + case "290" -> enterprise.setEconomicType("9"); + case "310" -> enterprise.setEconomicType("10"); + case "330" -> enterprise.setEconomicType("11"); + case "159" -> enterprise.setEconomicType("12"); + } + } + enterprise.setUnitScale(dto.getQygm()); // 企业规模 + Optional.ofNullable(dto.getJd()).filter(StringUtils::hasText).ifPresent(enterprise::setLongitude); // 经度 + Optional.ofNullable(dto.getWd()).filter(StringUtils::hasText).ifPresent(enterprise::setLatitude); // 纬度 + + + enterprise.setRegistrationType(dto.getJjxz()); // 登记注册企业类别 + enterprise.setEmployeeCount(dto.getZgzrs()); // 从业人员数量 + enterprise.setIndustryExpandInfos(dto.toString()); // 工贸行业扩展信息 + enterprise.setMainName(dto.getZyfzr()); // 主要负责人姓名 + enterprise.setMainPhone(dto.getZyfzrlxdh()); // 主要负责人电话 + + try{ + Optional.ofNullable(dto.getCjsj()).filter(StringUtils::hasText).ifPresent(cjsj->enterprise.setEntryTime(LocalDateTime.parse(cjsj.substring(0,19), DATE_TIME_FORMATTER))); // 录入时间 + Optional.ofNullable(dto.getXgsj()).filter(StringUtils::hasText).ifPresent(xgsj->enterprise.setModifyTime(LocalDateTime.parse(xgsj.substring(0,19), DATE_TIME_FORMATTER)));// 修改时间 + }catch (Exception e){ + log.info("时间转换问题:{}",e); + } + enterprise.setPumpTime(LocalDateTime.now()); // 抽取时间 + enterprise.setContactPerson(dto.getZyfzr()); // 单位联系人 + enterprise.setContactPhone(dto.getZyfzrlxdh()); // 联系电话 + enterprise.setStreet(dto.getXzjdmc()); // 所在街道 + if ("0".equals(dto.getScjyztxh())) { // 运营状态 + enterprise.setOperationStatus("正常"); + } else { + enterprise.setOperationStatus("停业"); + } + enterprises.add(enterprise); + } + + + } + if (!enterprises.isEmpty()) { + transactionTemplate.executeWithoutResult((status) -> enterpriseRepo.saveAll(enterprises)); + log.info("保存成功:{}条数据", enterprises.size()); + } + } + } else log.info("没有数据list"); + } else { + log.info("刷新Token"); + refreshToken(); + } + } catch (Exception e) { + log.info("调用接口失败:{}",e); + } + return flag; + } + + @Transactional + public boolean updateOrImportWaferEnterprise(int page) { + boolean flag = false; + try { + JSONObject jsonBody = new JSONObject(); + JSONObject jsonPram = new JSONObject(); + jsonBody.set("method", "GET"); + jsonBody.set("jsonObject", jsonPram); + jsonBody.set("pageNum", page); + jsonBody.set("pageSize", 1000); + jsonBody.set("path", "/edataservice/api/whqyjbxx"); + jsonBody.set("secret", "41982853a9e69e48c6eb73e1a80695ae58cb4fdc914137c55d6219ae4ea49579"); + String body = HttpUtil.createPost("http://10.22.245.216:29999/jeecg-data-service/edataservice/api/") + .header("Content-Type", "application/json") + .body(jsonBody.toString()) + .execute() + .body(); + JsonNode jsonNode = objectMapper.readValue(body, JsonNode.class); + if (jsonNode.has("code") && 200 == jsonNode.get("code").asInt() && jsonNode.has("result")) { + JsonNode data = objectMapper.readValue(jsonNode.get("result").toString(), JsonNode.class); + + if (null != data && data.has("records")) { + JsonNode arrNode = data.get("records"); + + if (arrNode.isArray()) { + long current = data.get("current").asLong(); + long pages = data.get("pages").asLong(); + if (current < pages) flag = true; + List dtos = objectMapper.readValue(arrNode.toString(), new TypeReference<>() {}); + + Set usccSet = dtos.stream().map(WaferEnterpriseDto::getTyshxydm).filter(StringUtils::hasText).collect(Collectors.toSet()); + // 历史企业 + List enterpriseList = enterpriseRepo.findByUnifiedSocialCodeIn(usccSet); + List enterprises = new ArrayList<>(); + + for (WaferEnterpriseDto dto : dtos) { + if (StringUtils.hasText(dto.getTyshxydm())) { + Enterprise oldLtd = enterpriseList.stream().filter(o -> o.getUnifiedSocialCode().equals(dto.getTyshxydm())).findFirst().orElse(null); + if (null != oldLtd) { + EnterpriseHistory history = new EnterpriseHistory(); + BeanUtils.copyProperties(oldLtd, history); + history.setTheType("定时任务修改"); + LocalDateTime now = LocalDateTime.now(); + history.setHistoryTime(now); + enterpriseHistoryRepo.save(history); + + oldLtd.setUnitName(dto.getQymc()); // 企业名称 + oldLtd.setUnifiedSocialCode(dto.getTyshxydm()); // 统一社会信用代码 + if ("opening".equals(dto.getYyzt())) { + oldLtd.setBusinessStatus("11"); // 营业状态 + } else { + oldLtd.setBusinessStatus("13"); // 营业状态 + } + Optional.ofNullable(dto.getQxdm()).filter(StringUtils::hasText).ifPresent(qxbm-> oldLtd.setAgency(agencyRepo.findLikeAgencyCode("%"+qxbm+"%",3).stream().findFirst().orElse(null))); + //oldLtd.setRegulatedIndustry(); // 国民经济行业分类 + //oldLtd.setAreaComb(); // 注册地址(全) + oldLtd.setDetailedAddress(dto.getZcdz()); // 注册详细地址 + //oldLtd.setOpLocAreaComb(); // 经营地址(全) + oldLtd.setOpLocAddress(dto.getJydz()); // 经营详细地址 + + if(null != dto.getJghylbxl()){ + if(dto.getJghylbxl().contains("E040101")){ + oldLtd.setIndustryType("2"); // 监管行业 危险化学品生产 + } + else if(dto.getJghylbxl().contains("E040102")){ + oldLtd.setIndustryType("3"); // 监管行业 危险化学品经营(仓储) + } + }else{ + oldLtd.setIndustryType("4"); // 监管行业 危险化学品经营(无仓储) + } + + + oldLtd.setLegalRepresentative(dto.getFddbr()); // 法定代表人姓名 + //oldLtd.setLegalPhone(dto.getFddbrlxdh()); // 法人联系电话 + //oldLtd.setLegalDuty(); // 法定代表人职位 + //oldLtd.setOpScope(dto.getJyfw()); // 主营业务 + //oldLtd.setAffiliation(); // 行政隶属关系 + //oldLtd.setOilExtract(); // 是否石油开采 + //oldLtd.setSafetyFocus(); // 是否安全生产监管重点企业 + //oldLtd.setIsScaleEnterprise(); // 是否规模以上企业 + //oldLtd.setSafetyLic(); // 是否有安全生产许可证 + oldLtd.setProvince(dto.getSjdmmc()); // 注册地址-省 + oldLtd.setCity(dto.getSjdm01mc()); // 注册地址-市 + oldLtd.setCounty(dto.getXjdmmc()); // 注册地址-县/区 + oldLtd.setAreaComb(dto.getSjdmmc()+"/"+dto.getSjdm01mc()+"/"+dto.getXjdmmc()); + oldLtd.setEconCategory(dto.getMllb()); + //国民经济行业分类代码《国民经济行业分类》(GB/T 4754—2017) + StringBuilder regulatedIndustryStr = new StringBuilder(dto.getMllb()); + Optional.ofNullable(dto.getDllb()).filter(StringUtils::hasText).ifPresent(s-> { + oldLtd.setEconCategoryTypea(s.substring(1)); + regulatedIndustryStr.append("/"+s); + }); + Optional.ofNullable(dto.getZllb()).filter(StringUtils::hasText).ifPresent(s-> { + oldLtd.setEconCategoryTypeb(s.substring(1)); + regulatedIndustryStr.append("/"+s); + }); + Optional.ofNullable(dto.getXllb()).filter(StringUtils::hasText).ifPresent(s-> { + oldLtd.setEconCategoryTypec(s.substring(1)); + regulatedIndustryStr.append("/"+s); + }); + oldLtd.setRegulatedIndustry(regulatedIndustryStr.toString()); + + //oldLtd.setOpLocProvince(); // 经营地址-省 + //oldLtd.setOpLocCity(); // 经营地址-市 + //oldLtd.setOpLocArea(dto.getQxmc()); // 经营地址-区 + //oldLtd.setBusinessRating(); // 企业分级 + //oldLtd.setBusinessCategory(); // 企业分类 + //oldLtd.setPostalCode(); // 邮政编码 + + //oldLtd.setLegalPersonIdType(); // 法定代表人证件类型 + //oldLtd.setLegalPersonIdNumber(); // 法定代表人证件号码 + //oldLtd.setIsInChemicalPark(); // 是否位于化工园区 + //oldLtd.setExpiryDate(); // 营业执照有效期 + oldLtd.setOrgCode(dto.getZzjgdm()); // 组织机构代码 + oldLtd.setUnitScale(dto.getQygmmc()); // 企业规模 + oldLtd.setEstablishmentDate(dto.getClrq()); // 成立日期 + //oldLtd.setRegisteredCapital(); // 注册资本(万元) + oldLtd.setLongitude(dto.getJd()); // 经度 + oldLtd.setLatitude(dto.getWd()); // 纬度 + //oldLtd.setRegistrationType(dto.getJjxz()); // 登记注册企业类别 + //oldLtd.setLicenseNumber(); // 许可证编号 + //oldLtd.setSafetyLevel(); // 安全生产标准化等级 + //oldLtd.setIssuingAuthority(); // 发证机关 + //oldLtd.setEmployeeCount(dto.getZgzrs()); // 从业人员数量 + //oldLtd.setIsTraining(); // 是否为培训机构 + //oldLtd.setProductionManageInfoExp(); // 安全基础管理信息 + //oldLtd.setProductionEquipmentExp(); // 生产设备设施信息 + //oldLtd.setNocoalIndustryExpansion(); // 非煤行业扩展信息 + oldLtd.setDangerFeatureInfo(dto.toString()); // 危化行业扩展信息 + //oldLtd.setFireworksAddInfo(); // 烟花爆竹行业扩展信息 + //oldLtd.setIndustryExpandInfos(dto.toString()); // 工贸行业扩展信息 + //oldLtd.setMainName(dto.getLxr()); // 主要负责人姓名 + //oldLtd.setMainPhone(dto.getLxdh()); // 主要负责人电话 + //oldLtd.setMajorAccidentHazard(); // 重大事故隐患年份 + //oldLtd.setMajorDanger(); // 是否有重大危险源 + //oldLtd.setInterid(dto.getYwlsh()); // 第三方对接ID + //oldLtd.setEntryTime(dto.getCjsj()); // 录入时间 + //oldLtd.setEntryUserName(); // 录入用户名 + //oldLtd.setEntryNickName(); // 录入用户昵称 + //oldLtd.setModifyTime(dto.getXgsj()); // 修改时间 + //oldLtd.setModifyUserName(); // 修改用户名 + //oldLtd.setModifyNickName(); // 修改用户昵称 + //oldLtd.setDataSource(); // 数据来源 + //oldLtd.setDelFlag(); // 是否删除 + //oldLtd.setIsRemove(); // 是否移除计划清单 + oldLtd.setPumpTime(now); // 抽取时间 + //oldLtd.setShortName(); // 单位简称 + oldLtd.setContactPerson(dto.getLxr()); // 单位联系人 + oldLtd.setContactPhone(dto.getLxdh()); // 联系电话 + oldLtd.setDynamicRiskLevel(dto.getDtfxdjmc()); // 动态风险等级 + oldLtd.setBusinessScope(dto.getJyfw()); // 经营范围 + //oldLtd.setTown(); // 所在乡镇 + //oldLtd.setVillage(); // 所在村 + oldLtd.setStreet(dto.getJdmc()); // 所在街道 + //oldLtd.setRiskCategory(); // 风险类别 + //oldLtd.setUnitType(); // 单位类型 +// if ("0".equals(dto.getScjyztxh())) { // 运营状态 +// oldLtd.setOperationStatus("正常"); +// } else { +// oldLtd.setOperationStatus("停业"); +// } + //oldLtd.setMunicipalRegulator(); // 市监管部门 + //oldLtd.setDistrictRegulator(); // 区县监管部门 + //oldLtd.setDistrictName(); // 区县名称 + //oldLtd.setVideoAccess("0"); // 视频接入状态 + //oldLtd.setPerceptionAccess("0"); // 感知接入状态 + //oldLtd.setInfoAccess("0"); // 资料接入状态 + //oldLtd.setPermitAccess("0"); // 许可接入状态 + //oldLtd.setInsEnterprses(); // 巡查企业列表 + //oldLtd.setIsWithin(); // 是否规上限上企业 + //oldLtd.setGyqyfl(); // 工业企业分类 + enterprises.add(oldLtd); + } + } else { + Enterprise enterprise = new Enterprise(); + enterprise.setUnitName(dto.getQymc()); // 企业名称 + enterprise.setUnifiedSocialCode(dto.getTyshxydm()); // 统一社会信用代码 + + enterprise.setUnitName(dto.getQymc()); // 企业名称 + enterprise.setUnifiedSocialCode(dto.getTyshxydm()); // 统一社会信用代码 + if ("opening".equals(dto.getYyzt())) { + enterprise.setBusinessStatus("11"); // 营业状态 + } else { + enterprise.setBusinessStatus("13"); // 营业状态 + } + Optional.ofNullable(dto.getQxdm()).filter(StringUtils::hasText).ifPresent(qxbm-> enterprise.setAgency(agencyRepo.findLikeAgencyCode("%"+qxbm+"%",3).stream().findFirst().orElse(null))); + enterprise.setDetailedAddress(dto.getZcdz()); // 注册详细地址 + //oldLtd.setOpLocAreaComb(); // 经营地址(全) + enterprise.setOpLocAddress(dto.getJydz()); // 经营详细地址 + if(null!=dto.getJghylbxl()){ + if(dto.getJghylbxl().contains("E040101")){ + enterprise.setIndustryType("2"); // 监管行业 危险化学品生产 + } + else if(dto.getJghylbxl().contains("E040102")){ + enterprise.setIndustryType("3"); // 监管行业 危险化学品经营(仓储) + } + }else{ + enterprise.setIndustryType("4"); // 监管行业 危险化学品经营(无仓储) + } + + enterprise.setLegalRepresentative(dto.getFddbr()); // 法定代表人姓名 + enterprise.setDangerFeatureInfo(dto.toString()); // 危化行业扩展信息 + enterprise.setProvince(dto.getSjdmmc()); // 注册地址-省 + enterprise.setCity(dto.getSjdm01mc()); // 注册地址-市 + enterprise.setCounty(dto.getXjdmmc()); // 注册地址-县/区 + enterprise.setAreaComb(dto.getSjdmmc()+"/"+dto.getSjdm01mc()+"/"+dto.getXjdmmc()); + enterprise.setEconCategory(dto.getMllb()); + //国民经济行业分类代码《国民经济行业分类》(GB/T 4754—2017) + StringBuilder regulatedIndustryStr = new StringBuilder(dto.getMllb()); + Optional.ofNullable(dto.getDllb()).filter(StringUtils::hasText).ifPresent(s-> { + enterprise.setEconCategoryTypea(s.substring(1)); + regulatedIndustryStr.append("/"+s); + }); + Optional.ofNullable(dto.getZllb()).filter(StringUtils::hasText).ifPresent(s-> { + enterprise.setEconCategoryTypeb(s.substring(1)); + regulatedIndustryStr.append("/"+s); + }); + Optional.ofNullable(dto.getXllb()).filter(StringUtils::hasText).ifPresent(s-> { + enterprise.setEconCategoryTypec(s.substring(1)); + regulatedIndustryStr.append("/"+s); + }); + enterprise.setRegulatedIndustry(regulatedIndustryStr.toString()); + + + enterprise.setOrgCode(dto.getZzjgdm()); // 组织机构代码 + enterprise.setUnitScale(dto.getQygmmc()); // 企业规模 + enterprise.setEstablishmentDate(dto.getClrq()); // 成立日期 + enterprise.setLongitude(dto.getJd()); // 经度 + enterprise.setLatitude(dto.getWd()); // 纬度 + + + enterprise.setIndustryExpandInfos(dto.toString()); // 工贸行业扩展信息 + + enterprise.setPumpTime(LocalDateTime.now()); // 抽取时间 + + enterprise.setContactPerson(dto.getLxr()); // 单位联系人 + enterprise.setContactPhone(dto.getLxdh()); // 联系电话 + enterprise.setDynamicRiskLevel(dto.getDtfxdjmc()); // 动态风险等级 + enterprise.setBusinessScope(dto.getJyfw()); // 经营范围 + + enterprise.setStreet(dto.getJdmc()); // 所在街道 + enterprises.add(enterprise); + } + + } + if (!enterprises.isEmpty()) { + transactionTemplate.executeWithoutResult((status) -> enterpriseRepo.saveAll(enterprises)); + log.info("保存成功:{}条数据", enterprises.size()); + } + } + } else log.info("没有数据list"); + } else { + log.info("刷新Token"); + refreshToken(); + } + } catch (Exception e) { + log.info("危化企业信息查询查询异常:" + e); + } + return flag; + } +} diff --git a/server/src/main/java/com/aisino/iles/lawenforcement/schedule/service/EnterpriseScheduleService.java b/server/src/main/java/com/aisino/iles/lawenforcement/schedule/service/EnterpriseScheduleService.java new file mode 100644 index 0000000..78f822d --- /dev/null +++ b/server/src/main/java/com/aisino/iles/lawenforcement/schedule/service/EnterpriseScheduleService.java @@ -0,0 +1,778 @@ +package com.aisino.iles.lawenforcement.schedule.service; + +import cn.hutool.http.HttpUtil; +import com.aisino.iles.lawenforcement.model.*; +import com.aisino.iles.lawenforcement.model.dto.EnterpriseInterfaceDto; +import com.aisino.iles.lawenforcement.repository.*; +import com.aisino.iles.lawenforcement.service.AuthService; +import com.aisino.iles.lawenforcement.service.DictInterfaceService; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.smartlx.sso.client.model.AccessToken; +import com.smartlx.sso.client.properties.SsoClientProperties; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.support.TransactionTemplate; +import org.springframework.util.StringUtils; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 企业信息定时任务 + */ +@Service +@Slf4j +public class EnterpriseScheduleService { + private final EnterpriseRepository enterpriseRepo; + private final EnterpriseHistoryRepository enterpriseHistoryRepo; + private final AdministrativeLicenseRepository administrativeLicenseRepo; + private final AccidentHazardRepository accidentHazardRepo; + private final DangerInfoRepository dangerInfoRepo; + private final AgencyRepository agencyRepo; + private final SsoClientProperties ssoClientProperties; + private final TransactionTemplate transactionTemplate; + private final ObjectMapper objectMapper; + private final AuthService authService; + private final String queryUrl; + private final String mobilePhone; + private String accessToken; + private String tokenType; + private String refreshToken; + private final String produceLicensePage; + private final String hazardPage; + private final String sourcesDangerPage; + private final DictInterfaceService dictService; + private final DateTimeFormatter yyyy = DateTimeFormatter.ofPattern("yyyy"); + + public EnterpriseScheduleService(EnterpriseRepository enterpriseRepo, + EnterpriseHistoryRepository enterpriseHistoryRepo, + AdministrativeLicenseRepository administrativeLicenseRepo, + AccidentHazardRepository accidentHazardRepo, + DangerInfoRepository dangerInfoRepo, + AgencyRepository agencyRepo, + @Value("${third-party.enterprise.query:}") String queryUrl, + @Value("${third-party.enterprise.yddh:}") String mobilePhone, + SsoClientProperties ssoClientProperties, + TransactionTemplate transactionTemplate, + ObjectMapper objectMapper, + AuthService authService, + @Value("${third-party.enterprise.produceLicensePage:}") String produceLicensePage, + @Value("${third-party.enterprise.hazardPage:}") String hazardPage, + @Value("${third-party.enterprise.sourcesDangerPage:}") String sourcesDangerPage, + DictInterfaceService dictService) { + this.enterpriseRepo = enterpriseRepo; + this.enterpriseHistoryRepo = enterpriseHistoryRepo; + this.administrativeLicenseRepo = administrativeLicenseRepo; + this.accidentHazardRepo = accidentHazardRepo; + this.dangerInfoRepo = dangerInfoRepo; + this.agencyRepo = agencyRepo; + this.queryUrl = queryUrl; + this.ssoClientProperties = ssoClientProperties; + this.transactionTemplate = transactionTemplate; + this.objectMapper = objectMapper; + this.mobilePhone = mobilePhone; + this.authService = authService; + this.produceLicensePage = produceLicensePage; + this.hazardPage = hazardPage; + this.sourcesDangerPage = sourcesDangerPage; + this.dictService = dictService; + } + + + /** + * 工作台信息导入 + * 每天10点执行一次 + * (cron = "0 0 15 * * ?") + * (cron = "${scheduler.expression}") + * (fixedRate = 120000) + */ + @Scheduled(cron = "${scheduler.task.enterprise.cron.expression}") + public void scheduledEnterpriseTask() { + getToken(); + dictService.getDictItem("YJ_CODE_9017", tokenType, accessToken); +// enterpriseTask(); + administrativeLicenseTask(); + accidentHazardTask(); + dangerInfoTask(); + } + + /** + * 导入企业信息 + */ + private void enterpriseTask() { + log.info("开始导入企业信息..."); + int page = 1; + boolean b = importEnterprise(page); + while (b) { + page++; + b = importEnterprise(page); + } + log.info("企业信息导入完成..."); + } + + /** + * 刷新token + */ + private void refreshToken() { + try { + authService.refreshTokenBackend(accessToken, tokenType, refreshToken, mobilePhone); + // 刷新后重新获取token + getToken(); + log.info("刷新token成功"); + } catch (Exception e) { + log.error("刷新token失败,尝试重新获取token", e); + getToken(); + } + } + + /** + * 获取token + */ + private void getToken() { + try { + AccessToken accessTokenObj = authService.getTokenBackend(mobilePhone); + if (accessTokenObj != null) { + accessToken = accessTokenObj.getAccess_token(); + tokenType = accessTokenObj.getToken_type(); + refreshToken = accessTokenObj.getRefresh_token(); + log.info("获取token成功"); + } else { + log.error("获取token失败,返回为null"); + throw new RuntimeException("无法获取有效的访问令牌"); + } + } catch (Exception e) { + log.error("获取token失败", e); + throw new RuntimeException("无法获取有效的访问令牌", e); + } + } + + private boolean importEnterprise(int page) { + boolean goon = false; + try { + String body = HttpUtil.createPost(queryUrl) + .header("Content-Type", "application/json") + .header("Authorization", tokenType + " " + accessToken) + .header("appId", ssoClientProperties.getClientId()) + .body("{\"page\": " + page + ",\"limit\": 1000,\"entity\": {}}") + .execute() + .body(); + log.info("企业信息查询结果:{}", body); + JsonNode jsonNode = objectMapper.readValue(body, JsonNode.class); + if (jsonNode.has("code") && 200 == jsonNode.get("code").asInt() && jsonNode.has("data")) { + JsonNode data = objectMapper.readValue(jsonNode.get("data").toString(), JsonNode.class); + if (null != data && data.has("list")) { + JsonNode arrNode = data.get("list"); + if (arrNode.isArray()) { + long total = data.get("totalCount").asLong(); + int limit = data.get("limit").asInt(); + if (total > limit && arrNode.size() >= limit) + goon = true; + List enterprises = new ArrayList<>(); + List dtos = objectMapper.readValue(arrNode.toString(), new TypeReference<>() { + }); + Set usccSet = dtos.stream().map(EnterpriseInterfaceDto::getTyshxydm).filter(StringUtils::hasText).collect(Collectors.toSet()); + List enterpriseList = enterpriseRepo.findByUnifiedSocialCodeIn(usccSet); + for (EnterpriseInterfaceDto dto : dtos) { + if (StringUtils.hasText(dto.getTyshxydm())) { + Enterprise oldLtd = enterpriseList.stream().filter(o -> o.getUnifiedSocialCode().equals(dto.getTyshxydm())).findFirst().orElse(null); + if (null == oldLtd || !"31".equals(oldLtd.getDataSource())) { + Enterprise enterprise = new Enterprise(); + LocalDateTime now = LocalDateTime.now(); + if (null != oldLtd) { + BeanUtils.copyProperties(oldLtd, enterprise); + EnterpriseHistory history = new EnterpriseHistory(); + BeanUtils.copyProperties(enterprise, history); + history.setTheType("定时任务修改"); + history.setHistoryTime(now); + enterpriseHistoryRepo.save(history); + } else { + enterprise.setIsWithin("0"); + enterprise.setPumpTime(now); + enterprise.setBusinessCategory("1"); + } + enterprise.setEntryUserName(dto.getCjrYhwybs());//创建人用户唯一标识 + if (null != dto.getCjsj()) { + enterprise.setEntryTime(dto.getCjsj());//创建时间 + enterprise.setModifyUserName(dto.getGxrYhwybs());//更新人用户唯一标识 + if (null != dto.getGxsj()) enterprise.setModifyTime(dto.getGxsj());//更新时间 + else enterprise.setModifyTime(dto.getCjsj()); + } else { + enterprise.setEntryTime(now);//创建时间 + enterprise.setModifyTime(now); + } + enterprise.setUnitName(dto.getQymc());//企业名称 + enterprise.setUnifiedSocialCode(dto.getTyshxydm());//统一社会信用代码 + enterprise.setShortName(dto.getQyjc());//企业简称 + enterprise.setUnitScale(dto.getQygm());//企业规模代码 + enterprise.setEmployeeCount(dto.getZgrs());//职工人数 + enterprise.setDetailedAddress(dto.getZcdzXxdz());//注册地址详细地址 + enterprise.setAreaComb(dto.getZcdzXzqhdm());//注册地址行政区划代码 + enterprise.setOpLocAddress(dto.getScjydzXxdz());//生产经营地址详细地址 + enterprise.setOpLocAreaComb(dto.getScjydzXzqhdm());//生产经营地址行政区划代码 + enterprise.setOpScope(dto.getJyfw());//经营范围 + Optional.ofNullable(dto.getYygldwZzjgdm()).filter(StringUtils::hasText).flatMap(agencyRepo::findByAgencyCode).ifPresent(enterprise::setAgency);//发证机关代码 + if (StringUtils.hasText(dto.getGmjjhyfldm())) {//国民经济行业分类代码《国民经济行业分类》(GB/T 4754—2017) + String xxdm = dictService.getTopXxdm(dto.getGmjjhyfldm()); + enterprise.setRegulatedIndustry(xxdm); + String[] ss = xxdm.split("/"); + for (int i = 0; i < ss.length; i++) { + if (i == 0) enterprise.setEconCategory(ss[i]); + if (i == 1) enterprise.setEconCategoryTypea(ss[i]); + if (i == 2) enterprise.setEconCategoryTypeb(ss[i]); + if (i == 3) enterprise.setEconCategoryTypec(ss[i]); + } + } + enterprise.setOrgCode(dto.getFzjgdm());//应急管理单位组织机构代码 + enterprise.setLegalRepresentative(dto.getLxrxm());//企业联系人 + Optional.ofNullable(dto.getScjydzJd()).filter(StringUtils::hasText).ifPresent(enterprise::setLongitude);//经营地址经度 + Optional.ofNullable(dto.getScjydzWd()).filter(StringUtils::hasText).ifPresent(enterprise::setLatitude);//经营地址纬度 + Optional.ofNullable(dto.getYyzt()).filter(StringUtils::hasText).ifPresent(o -> { + if ("1".equals(o)) enterprise.setBusinessStatus("11"); + else enterprise.setBusinessStatus("13"); + });//1-正常(存续、在营、开业、注册、设立);2-异常(未年报、未年检、证书逾期、未年度考核);3-吊销;4-注销/撤销;9-其他 + String economicType = dto.getJjlxfldm(); + if (StringUtils.hasText(economicType)) {//经济类型分类代码 + switch (economicType) { + case "110" -> enterprise.setEconomicType("1"); + case "120" -> enterprise.setEconomicType("2"); + case "130" -> enterprise.setEconomicType("3"); + case "140" -> enterprise.setEconomicType("4"); + case "150" -> enterprise.setEconomicType("5"); + case "160" -> enterprise.setEconomicType("6"); + case "170" -> enterprise.setEconomicType("7"); + case "190" -> enterprise.setEconomicType("8"); + case "290" -> enterprise.setEconomicType("9"); + case "310" -> enterprise.setEconomicType("10"); + case "330" -> enterprise.setEconomicType("11"); + case "159" -> enterprise.setEconomicType("12"); + } + } + enterprise.setUnitCode(dto.getQybh()); + enterprise.setMainName(dto.getQyfzrxm()); + String qyLxdh = dto.getQyLxdh(); + enterprise.setContactPhone(qyLxdh); + enterprise.setMainPhone(qyLxdh); + enterprise.setLegalPhone(qyLxdh); + enterprise.setContactPerson(dto.getLxrxm()); + if (null != dto.getClrq()) + enterprise.setEstablishmentDate(LocalDate.from(dto.getClrq()));//成立日期 + if (null != dto.getZczb()) + enterprise.setRegisteredCapital(BigDecimal.valueOf(dto.getZczb()));//注册资本 + if (null != dto.getYyqx()) + enterprise.setExpiryDate(LocalDate.from(dto.getYyqx()));//营业期限 + //1-工贸企业 2-危化企业 3-矿山企业 4-建筑施工企业 5-民用爆炸物企业 6-食品药品企业 7-交通运输企业 + //1-矿山 2-危险化学品生产 3-危险化学品经营(仓储) 4-危险化学品经营(无仓储) 5-加油站 6-工业企业 7-商贸企业 8-安全生产中介机构 9-安全生产检验检测机构 10-安全生产培训机构 99-其他 + String gllx = dto.getGllx();//管理类型代码 + Optional.ofNullable(gllx).ifPresentOrElse(o -> { + switch (o) { + case "1" -> enterprise.setIndustryType("6"); + case "3" -> enterprise.setIndustryType("1"); + case "2" -> enterprise.setIndustryType("2"); + default -> enterprise.setIndustryType("99"); + } + }, () -> enterprise.setIndustryType("99")); + if (enterprise.getUnitName().contains("加油站")) + enterprise.setIndustryType("5"); + enterprise.setIsRemove((short) 0); + if ("0".equals(dto.getSjyxx())) + enterprise.setDelFlag((short) 1); + else enterprise.setDelFlag((short) 0); + if ("5".equals(enterprise.getIndustryType())) + enterprise.setBusinessCategory("2"); + else enterprise.setBusinessCategory("1"); + enterprise.setDataSource("3"); + enterprise.setInterid(dto.getYwlsh()); + enterprises.add(enterprise); + } + } else log.info("统一信用代码为空:{}", dto.getYwlsh()); + } + int i = 0; + if (!enterprises.isEmpty()) { + i++; + transactionTemplate.executeWithoutResult((status) -> enterpriseRepo.saveAll(enterprises)); + log.info("成功次数:{}", i); + } + } + } else log.info("没有数据list"); + } else { + log.info("刷新Token"); + refreshToken(); + } + } catch (Exception e) { + log.info("企业查询异常:" + e); + } + return goon; + } + + /** + * 企业生产许可信息导入 + */ + public void administrativeLicenseTask() { + log.info("开始企业生产许可信息导入..."); + int page = 1; + boolean b = importAdministrativeLicense(page); + while (b) { + page++; + b = importAdministrativeLicense(page); + } + log.info("企业生产许可信息导入完成..."); + } + + private boolean importAdministrativeLicense(int page) { + boolean goon = false; + try { + String body = HttpUtil.createPost(produceLicensePage) + .header("Content-Type", "application/json") + .header("Authorization", tokenType + " " + accessToken) + .header("appId", ssoClientProperties.getClientId()) + .body("{\"page\": " + page + ",\"limit\": 1000,\"entity\": {}}") + .execute() + .body(); + log.info("生产许可查询结果:{}", body); + JsonNode jsonNode = objectMapper.readValue(body, JsonNode.class); + if (jsonNode.has("code") && 200 == jsonNode.get("code").asInt() && jsonNode.has("data")) { + JsonNode data = objectMapper.readValue(jsonNode.get("data").toString(), JsonNode.class); + if (null != data && data.has("list")) { + JsonNode arrNode = data.get("list"); + if (arrNode.isArray()) { + long total = data.get("totalCount").asLong(); + int limit = data.get("limit").asInt(); + if (total > limit && arrNode.size() >= limit) + goon = true; + List licenses = new ArrayList<>(); + List dtos = objectMapper.readValue(arrNode.toString(), new TypeReference<>() { + }); + for (AdministrativeLicenseInterfaceDto dto : dtos) { + AdministrativeLicense license = new AdministrativeLicense(); + license.setLicenseNum(dto.getLicensenumber());//": "陕西危化经字【2021】012001", + license.setIssuingOffice(dto.getIssuingoffice());//": "西安市应急管理局", + if (null != dto.getValidfrom()) + license.setValidFrom(LocalDate.from(dto.getValidfrom()));//": "2021-06-23 00:00:00", + if (null != dto.getValidend()) + license.setValidFrom(LocalDate.from(dto.getValidend()));//": "2024-06-22 00:00:00", + license.setLicenseType(dto.getLicensetype());//": "510400P02", + enterpriseRepo.findByUnifiedSocialCode(dto.getUnitid()) + .ifPresentOrElse(enterprise -> { + license.setEnterpriseId(enterprise.getEnterpriseId()); + if (!StringUtils.hasText(dto.getQymc())) + license.setUnitName(enterprise.getUnitName()); + }, + () -> { + license.setEnterpriseId(dto.getUnitid()); + license.setUnitName(dto.getQymc()); + }); + + license.setInterid(dto.getId());//": "539392900627437727744", + license.setDataScope(dto.getDataScope());//": null, + license.setDwdmScope(dto.getDwdmScope());//": null, + license.setUsergw(dto.getUsergw());//": null, + license.setLicensename(dto.getLicensename());//": "危险化学品经营许可(有仓储)", + license.setResponsibleperson(dto.getResponsibleperson());//": null, + license.setLicensestatus(dto.getLicensestatus());//": "有效", + license.setChangerecord(dto.getChangerecord());//": null, + license.setDutydepartmentmanager(dto.getDutydepartmentmanager());//": null, + license.setDutypeoplemanager(dto.getDutypeoplemanager());//": null, + license.setApplydepartment(dto.getApplydepartment());//": null, + license.setReviewcomments(dto.getReviewcomments());//": null, + license.setReviewdepartment(dto.getReviewdepartment());//": null, + license.setReviewpeople(dto.getReviewpeople());//": null, + license.setRectificationnoncompliance(dto.getRectificationnoncompliance());//": null, + license.setCreatedby(dto.getCreatedby());//": "262", + license.setCreatedon(dto.getCreatedon());//": "2023-10-11 10:08:15", + license.setModifiedby(dto.getModifiedby());//": "262", + license.setModifiedon(dto.getModifiedon());//": "2023-10-11 10:08:15", + license.setDeletestatus(dto.getDeletestatus());//": 0, + license.setDataSource(dto.getSjly());//": "危化", + license.setPumpTime(LocalDateTime.now()); + licenses.add(license); + } + int i = 0; + if (!licenses.isEmpty()) { + i++; +// transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + transactionTemplate.executeWithoutResult((status) -> administrativeLicenseRepo.saveAll(licenses)); + log.info("企业生产许可成功次数:{}", i); + } + } + } else log.info("企业生产许可没有数据list"); + } else { + log.info("企业生产许可刷新Token"); + refreshToken(); + } + } catch (Exception e) { + log.info("企业生产许可查询异常:" + e); + } + return goon; + } + + @Data + public static class AdministrativeLicenseInterfaceDto { + /** + * 适用部门(50) + */ + private String applydepartment; + + /** + * 变更记录(50) + */ + private String changerecord; + + /** + * 创建人 + */ + private String createdby; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createdon; + + /** + * 当前用户等级 后台查看数据权限用 + */ + private String dataScope; + + /** + * 删除标记(默认为0; 0=未删除,1=已删除) + */ + private Integer deletestatus; + + /** + * 归口管理责任部门(50) + */ + private String dutydepartmentmanager; + + /** + * 归口管理责任人(50) + */ + private String dutypeoplemanager; + + /** + * 当前用户单位 后台查看数据权限用 + */ + private String dwdmScope; + + /** + * ID + */ + private String id; + + /** + * 发证机关 + */ + private String issuingoffice; + + /** + * 许可证书名称 + */ + private String licensename; + + /** + * 许可证编号 + */ + private String licensenumber; + + /** + * 许可证状态 + */ + private String licensestatus; + + /** + * 许可类型approvalType + */ + private String licensetype; + + /** + * 修改人 + */ + private String modifiedby; + + /** + * 修改时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime modifiedon; + + /** + * 企业名称 + */ + private String qymc; + + /** + * 不符合项整改情况(500) + */ + private String rectificationnoncompliance; + + /** + * 主要负责人 + */ + private String responsibleperson; + + /** + * 符合性评审意见(500) + */ + private String reviewcomments; + + /** + * 评审部门(50) + */ + private String reviewdepartment; + + /** + * 评审责任人(50) + */ + private String reviewpeople; + + /** + * 数据来源 危化 工贸等 + */ + private String sjly; + + /** + * 企业id + */ + private String unitid; + + /** + * 当前用户岗位 后台查看数据权限用 + */ + private String usergw; + + /** + * 有效期至 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime validend; + + /** + * 有效期自 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime validfrom; + } + + /** + * 重大事故隐患信息导入 + */ + public void accidentHazardTask() { + log.info("开始重大事故隐患信息导入..."); + int page = 1; + boolean b = importAccidentHazard(page); + while (b) { + page++; + b = importAccidentHazard(page); + } + log.info("企业重大事故隐患信息导入完成..."); + } + + private boolean importAccidentHazard(int page) { + boolean goon = false; + try { + String body = HttpUtil.createPost(hazardPage) + .header("Content-Type", "application/json") + .header("Authorization", tokenType + " " + accessToken) + .header("appId", ssoClientProperties.getClientId()) + .body("{\"page\": " + page + ",\"limit\": 1000,\"entity\": {}}") + .execute() + .body(); + log.info("重大事故隐患查询结果:{}", body); + JsonNode jsonNode = objectMapper.readValue(body, JsonNode.class); + if (jsonNode.has("code") && 200 == jsonNode.get("code").asInt() && jsonNode.has("data")) { + JsonNode data = objectMapper.readValue(jsonNode.get("data").toString(), JsonNode.class); + if (null != data && data.has("list")) { + JsonNode arrNode = data.get("list"); + if (arrNode.isArray()) { + long total = data.get("totalCount").asLong(); + int limit = data.get("limit").asInt(); + if (total > limit && arrNode.size() >= limit) + goon = true; + List hazards = new ArrayList<>(); + List dtos = objectMapper.readValue(arrNode.toString(), new TypeReference<>() { + }); + for (AccidentHazardDto dto : dtos) { + AccidentHazard hazard = new AccidentHazard(); + BeanUtils.copyProperties(dto, hazard); + enterpriseRepo.findByUnifiedSocialCode(dto.getUnitid()) + .ifPresentOrElse(enterprise -> { + hazard.setEnterpriseId(enterprise.getEnterpriseId()); + if (null != hazard.getCreatedon()) { + String year = hazard.getCreatedon().format(yyyy); + if (StringUtils.hasText(enterprise.getMajorAccidentHazard())) { + Set set = Arrays.stream(enterprise.getMajorAccidentHazard().split(",")).collect(Collectors.toSet()); + if (!set.contains(year)) { + set.add(year); + String majorAccidentHazard = String.join(",", set); + EnterpriseHistory history = new EnterpriseHistory(); + BeanUtils.copyProperties(enterprise, history); + LocalDateTime now = LocalDateTime.now(); + history.setTheType("修改重大事故隐患年份"); + history.setHistoryTime(now); + enterpriseHistoryRepo.save(history); + enterprise.setMajorAccidentHazard(majorAccidentHazard); + enterprise.setModifyTime(now); + enterpriseRepo.save(enterprise); + } + } else + enterprise.setMajorAccidentHazard(year); + } + if (!StringUtils.hasText(dto.getQymc())) + hazard.setUnitname(enterprise.getUnitName()); + }, + () -> { + hazard.setEnterpriseId(dto.getUnitid()); + hazard.setUnitname(dto.getQymc()); + }); + + hazard.setInterid(dto.getId()); + hazard.setDataSource(dto.getSjly()); + hazard.setPumpTime(LocalDateTime.now()); + hazards.add(hazard); + } + int i = 0; + if (!hazards.isEmpty()) { + i++; + transactionTemplate.executeWithoutResult((status) -> accidentHazardRepo.saveAll(hazards)); + log.info("重大事故隐患成功次数:{}", i); + } + } + } else log.info("重大事故隐患没有数据list"); + } else { + log.info("重大事故隐患刷新Token"); + refreshToken(); + } + } catch (Exception e) { + log.info("重大事故隐患查询异常:" + e); + } + return goon; + } + + @Data + public static class AccidentHazardDto extends AccidentHazard { + /** + * 企业统一信用社代码 + */ + private String unitid; + /** + * 企业名称 + */ + private String qymc; + /** + * 数据来源 危化 工贸等 + */ + private String sjly; + private String id; + } + + /** + * 重大危险源信息导入 + */ + public void dangerInfoTask() { + log.info("开始重大危险源信息导入..."); + int page = 1; + boolean b = importDangerInfo(page); + while (b) { + page++; + b = importDangerInfo(page); + } + log.info("重大危险源信息导入完成..."); + } + + private boolean importDangerInfo(int page) { + boolean goon = false; + try { + String body = HttpUtil.createPost(sourcesDangerPage) + .header("Content-Type", "application/json") + .header("Authorization", tokenType + " " + accessToken) + .header("appId", ssoClientProperties.getClientId()) + .body("{\"page\": " + page + ",\"limit\": 1000,\"entity\": {}}") + .execute() + .body(); + log.info("重大危险源查询结果:{}", body); + JsonNode jsonNode = objectMapper.readValue(body, JsonNode.class); + if (jsonNode.has("code") && 200 == jsonNode.get("code").asInt() && jsonNode.has("data")) { + JsonNode data = objectMapper.readValue(jsonNode.get("data").toString(), JsonNode.class); + if (null != data && data.has("list")) { + JsonNode arrNode = data.get("list"); + if (arrNode.isArray()) { + long total = data.get("totalCount").asLong(); + int limit = data.get("limit").asInt(); + if (total > limit && arrNode.size() >= limit) + goon = true; + List dangerInfos = new ArrayList<>(); + List dtos = objectMapper.readValue(arrNode.toString(), new TypeReference<>() { + }); + for (DangerInfoDto dto : dtos) { + DangerInfo dangerInfo = new DangerInfo(); + BeanUtils.copyProperties(dto, dangerInfo); + enterpriseRepo.findByUnifiedSocialCode(dto.getUnitid()) + .ifPresentOrElse(enterprise -> { + dangerInfo.setEnterpriseId(enterprise.getEnterpriseId()); + if (!StringUtils.hasText(dto.getQymc())) + dangerInfo.setUnitname(enterprise.getUnitName()); + EnterpriseHistory history = new EnterpriseHistory(); + BeanUtils.copyProperties(enterprise, history); + LocalDateTime now = LocalDateTime.now(); + history.setTheType("修改危险源状态"); + history.setHistoryTime(now); + enterpriseHistoryRepo.save(history); + enterprise.setMajorDanger("1"); + enterprise.setModifyTime(now); + enterpriseRepo.save(enterprise); + }, + () -> { + dangerInfo.setEnterpriseId(dto.getUnitid()); + dangerInfo.setUnitname(dto.getQymc()); + }); + dangerInfo.setInterid(dto.getId()); + dangerInfo.setDataSource(dto.getSjly()); + dangerInfo.setPumpTime(LocalDateTime.now()); + dangerInfos.add(dangerInfo); + } + int i = 0; + if (!dangerInfos.isEmpty()) { + i++; + transactionTemplate.executeWithoutResult((status) -> dangerInfoRepo.saveAll(dangerInfos)); + log.info("重大危险源成功次数:{}", i); + } + } + } else log.info("重大危险源没有数据list"); + } else { + log.info("重大危险源刷新Token"); + refreshToken(); + } + } catch (Exception e) { + log.info("重大危险源查询异常:" + e); + } + return goon; + } + + @Data + public static class DangerInfoDto extends DangerInfo { + /** + * 企业统一信用社代码 + */ + private String unitid; + /** + * 企业名称 + */ + private String qymc; + /** + * 数据来源 危化 工贸等 + */ + private String sjly; + private String id; + } + +} diff --git a/server/src/main/java/com/aisino/iles/lawenforcement/schedule/service/EquipmentHistoryVideoScheduleService.java b/server/src/main/java/com/aisino/iles/lawenforcement/schedule/service/EquipmentHistoryVideoScheduleService.java new file mode 100644 index 0000000..8372cd8 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/lawenforcement/schedule/service/EquipmentHistoryVideoScheduleService.java @@ -0,0 +1,238 @@ +package com.aisino.iles.lawenforcement.schedule.service; + +import cn.hutool.core.codec.Base64; +import cn.hutool.crypto.digest.DigestUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.aisino.iles.lawenforcement.model.EquipmentHistoryVideo; +import com.aisino.iles.lawenforcement.repository.EquipmentHistoryVideoRepository; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.net.HttpCookie; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + + +/** + * 执法装备历史视频信息同步调度服务 + *

+ * 通过定时任务从外部API同步装备历史视频信息到本地数据库 + */ +@Slf4j +@Service +public class EquipmentHistoryVideoScheduleService { + + @Value("${media.service.login.url:http://222.91.125.86:8090/rest/index/login/login}") + private String loginUrl; + @Value("${media.service.data.url:http://222.91.125.86:8090/rest/media/media/get}") + private String dataUrl; + @Value("${media.service.username:SLtest}") + private String username; + @Value("${media.service.password:Pe26957381}") + private String password; + @Value("${media.service.device.code:1001}") + private String deviceCode; + @Value("${media.service.page.size:100}") + private int pageSize; + @Value("${media.service.date.start:2000-01-01 00:00:00}") + private String defaultStartDate; + @Value("${media.service.date.end:2030-01-01 00:00:00}") + private String defaultEndDate; + + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + private static final String SESSION_COOKIE_NAME = "PHPSESSID"; + private static final int MAX_RETRIES = 3; + + private final EquipmentHistoryVideoRepository equipmentHistoryVideoRepository; + + @Autowired + public EquipmentHistoryVideoScheduleService(EquipmentHistoryVideoRepository equipmentHistoryVideoRepository) { + this.equipmentHistoryVideoRepository = equipmentHistoryVideoRepository; + } + + @Scheduled(cron = "0 * * * * ?") + public void scheduleTask() { + log.info("获取装备历史视频开始。。。。"); + try { + HttpCookie sessionCookie = login(); + if (sessionCookie != null) { + fetchAllMediaPages(sessionCookie); + } + } catch (Exception e) { + log.error("任务异常", e); + } + log.info("获取装备历史视频结束。。。。"); + } + + public HttpCookie login() { + String encryptedPassword = DigestUtil.md5Hex(password); + // 构造登录参数 + JSONObject param = new JSONObject(); + param.set("username", username); + param.set("password", encryptedPassword); + try { + // 编码处理 + String jsonString = param.toString(); + String urlEncodedString = URLEncoder.encode(jsonString, StandardCharsets.UTF_8.name()); + String loginInfo = Base64.encode(urlEncodedString); + + // 构造请求体 + JSONObject requestBody = new JSONObject(); + requestBody.set("login_info", loginInfo); + + // 发送登录请求 + HttpResponse response = HttpRequest.post(loginUrl) + .header("Content-Type", "application/json") + .body(requestBody.toString()) + .execute(); + if (response.getStatus() != 200) { + log.error("登录失败: {}", response.getStatus()); + return null; + } + HttpCookie sessionCookie = response.getCookie(SESSION_COOKIE_NAME); + if (sessionCookie == null) { + log.error("登录失败:PHPSESSID为空"); + return null; + } + + log.info("登录成功"); + return sessionCookie; + + } catch (Exception e) { + log.error("登录失败:", e); + return null; + } + } + + @Transactional + public void fetchAllMediaPages(HttpCookie sessionCookie) { + int curPage = 1; + int retryCount = 0; + boolean hasMorePages = true; + + while (hasMorePages && retryCount < MAX_RETRIES) { + try { + // 构造请求参数 + JSONObject param = buildRequestParams(curPage); + + // 发送请求 + HttpResponse response = HttpRequest.post(dataUrl) + .header("Content-Type", "application/json") + .body(param.toString()) + .cookie(sessionCookie) + .execute(); + + // 检查响应状态 + if (response.getStatus() != 200) { + log.error("请求失败: {}", response.getStatus()); + throw new RuntimeException("API请求失败 " + response.getStatus()); + } + + // 解析响应 + JSONObject result = JSONUtil.parseObj(response.body()); + if (result.getInt("code") != 200) { + log.error("API数据接口请求失败 code: {}, message: {}", result.getInt("code"), result.getStr("message")); + throw new RuntimeException("API数据接口请求失败 code: " + result.getInt("code")); + } + + // 处理数据 + boolean pageProcessed = processPageData(result); + if (!pageProcessed) { + throw new RuntimeException("当前页数据处理失败"); + } + + // 检查是否还有更多页 + JSONObject pageInfo = result.getJSONObject("page"); + int totalPages = pageInfo.getInt("page_nums"); + curPage = pageInfo.getInt("cur_page") + 1; + hasMorePages = (curPage <= totalPages); + retryCount = 0; // 重置重试计数器 + + log.info("当前页 {}/{} 处理成功", curPage - 1, totalPages); + } catch (Exception e) { + retryCount++; + log.error("当前页 {} 处理失败,重试次数 {}/{}", curPage, retryCount, MAX_RETRIES, e); + + if (retryCount >= MAX_RETRIES) { + log.error("当前页 {} 重试超过最大次数,终止任务", curPage); + throw new RuntimeException("分页请求失败,已达最大重试次数"); + } + + } + } + } + + private JSONObject buildRequestParams(int curPage) { + JSONObject param = new JSONObject(); + param.set("bh", deviceCode); + param.set("pssj_type", "3"); + param.set("pssj_start", defaultStartDate); + param.set("pssj_end", defaultEndDate); + param.set("scsj_type", "3"); + param.set("scsj_start", defaultStartDate); + param.set("scsj_end", defaultEndDate); + param.set("media_type", "1"); + param.set("sjly", "1"); + param.set("is_del", "0"); + param.set("page_size", String.valueOf(pageSize)); + param.set("cur_page", String.valueOf(curPage)); + return param; + } + + private boolean processPageData(JSONObject result) { + JSONArray lines = result.getJSONObject("data").getJSONArray("lines"); + if (lines == null || lines.isEmpty()) { + log.warn("当前页没有数据"); + return true; + } + List videos = new ArrayList<>(); + for (Object item : lines) { + try { + JSONObject jsonObject = JSONUtil.parseObj(item); + String id = jsonObject.getStr("id"); + boolean exists = equipmentHistoryVideoRepository.existsByLssjid(id); + if (!exists) { + EquipmentHistoryVideo video = new EquipmentHistoryVideo(); + // 基本字段映射 + video.setLssjid(id); + video.setZbmc(jsonObject.getStr("dname")); + video.setZbdm(jsonObject.getStr("hostbody")); + video.setZblxmc(jsonObject.getStr("sjly_name")); + video.setSpbfdz(jsonObject.getStr("play_url")); + video.setSpxzdz(jsonObject.getStr("download_url")); + // 日期字段特殊处理 + if (jsonObject.getStr("createdate") != null) { + video.setCjsj(LocalDateTime.parse(jsonObject.getStr("createdate"), DATE_TIME_FORMATTER)); + } + videos.add(video); + } + } catch (Exception e) { + log.error("当前数据解析失败: {}", item, e); + } + } + if (!videos.isEmpty()) { + try { + equipmentHistoryVideoRepository.saveAll(videos); + log.info("当前页数据保存成功", videos.size()); + return true; + } catch (Exception e) { + log.error("当前页数据保存失败:", e); + } + } + return true; + } + +} diff --git a/server/src/main/java/com/aisino/iles/lawenforcement/schedule/service/OfficerScheduleService.java b/server/src/main/java/com/aisino/iles/lawenforcement/schedule/service/OfficerScheduleService.java new file mode 100644 index 0000000..5999740 --- /dev/null +++ b/server/src/main/java/com/aisino/iles/lawenforcement/schedule/service/OfficerScheduleService.java @@ -0,0 +1,400 @@ +package com.aisino.iles.lawenforcement.schedule.service; + +import com.aisino.iles.common.util.StringUtils; +import com.aisino.iles.lawenforcement.config.ThirdPartyProperties; +import com.aisino.iles.lawenforcement.model.Agency; +import com.aisino.iles.lawenforcement.model.Officer; +import com.aisino.iles.lawenforcement.repository.AgencyRepository; +import com.aisino.iles.lawenforcement.repository.OfficerRepository; +import com.aisino.iles.lawenforcement.service.AuthService; +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.smartlx.sso.client.model.AccessToken; +import com.smartlx.sso.client.properties.SsoClientProperties; +import lombok.Getter; +import lombok.Setter; +import lombok.SneakyThrows; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 执法人员信息同步调度服务 + * + * 通过定时任务从外部API同步执法人员信息到本地数据库 + */ +@Slf4j +@Service +public class OfficerScheduleService { + + private final OfficerRepository officerRepository; + private final AgencyRepository agencyRepository; + private final AuthService authService; + private final RestTemplate restTemplate; + private final ObjectMapper objectMapper; + private final ThirdPartyProperties thirdPartyProperties; + private final SsoClientProperties ssoClientProperties; + + /** + * OfficerScheduleService constructor. + * + * @param officerRepository the officer repository + * @param agencyRepository the agency repository + * @param authService the auth service + * @param restTemplate the rest template + * @param objectMapper the object mapper + * @param thirdPartyProperties the third party properties + * @param ssoClientProperties the sso client properties + */ + @Autowired + public OfficerScheduleService(OfficerRepository officerRepository, AgencyRepository agencyRepository, AuthService authService, RestTemplate restTemplate, ObjectMapper objectMapper, ThirdPartyProperties thirdPartyProperties, SsoClientProperties ssoClientProperties) { + this.officerRepository = officerRepository; + this.agencyRepository = agencyRepository; + this.authService = authService; + this.restTemplate = restTemplate; + this.objectMapper = objectMapper; + this.thirdPartyProperties = thirdPartyProperties; + this.ssoClientProperties = ssoClientProperties; + } + + /** + * 定时任务,每天凌晨2点执行 + */ + /// @Scheduled(cron = "${scheduler.task.officer.cron.expression:0 0 2 * * ?}") + @Scheduled(fixedRate = 3600000, initialDelay = 5000) + public void scheduleTask() { + log.info("开始执行执法人员信息同步任务..."); + + if (StringUtils.isEmpty(thirdPartyProperties.getPerson().getQuery())) { + log.error("执法人员API URL (third-party.person.query) 未配置。"); + return; + } + if (StringUtils.isEmpty(thirdPartyProperties.getEnterprise().getYddh())) { + log.error("执法人员API令牌IDNO (third-party.enterprise.idno) 未配置。"); + return; + } + if (thirdPartyProperties.getPerson().getRoles() == null || thirdPartyProperties.getPerson().getRoles().isEmpty()) { + log.error("执法人员角色列表 (third-party.person.roles) 未配置。"); + return; + } + + try { + List allApiOfficers = new ArrayList<>(); + + // 遍历所有角色进行查询 + for (String role : thirdPartyProperties.getPerson().getRoles()) { + log.info("正在查询角色: {}", role); + // 构建查询条件 + Map requestBody = new HashMap<>(); + Map entity = new HashMap<>(); + entity.put("jsdm", role); // 使用当前角色 + requestBody.put("entity", entity); + + log.info("准备从API获取执法人员数据. URL: {}, Role: {}", thirdPartyProperties.getPerson().getQuery(), role); + + int currentPage = 1; + int totalPages = 1; + final int pageSize = 100; // 每页大小 + + do { + requestBody.put("page", currentPage); + requestBody.put("limit", pageSize); + + String responseBody = fetchDataFromApi(requestBody, thirdPartyProperties.getPerson().getQuery()); + + if (responseBody != null) { + ApiResponse apiResponse = objectMapper.readValue(responseBody, ApiResponse.class); + + if (apiResponse.isSuccess() && apiResponse.getData() != null && apiResponse.getData().getRecords() != null) { + allApiOfficers.addAll(apiResponse.getData().getRecords()); + totalPages = apiResponse.getData().getPages(); + log.info("成功获取第 {}/{} 页数据,记录数: {}", currentPage, totalPages, apiResponse.getData().getRecords().size()); + } else { + log.error("API响应失败或数据为空. 角色: {}, 响应: {}", role, responseBody); + break; // 如果某页失败,则停止该角色的后续分页 + } + } else { + log.error("从API获取数据失败. 角色: {}, 页码: {}", role, currentPage); + break; // 如果获取失败,则停止该角色的后续分页 + } + currentPage++; + } while (currentPage <= totalPages); + } + + if (allApiOfficers.isEmpty()) { + log.info("未从API获取到任何执法人员数据。"); + return; + } + + List officersToSave = processApiOfficers(allApiOfficers); + officerRepository.saveAll(officersToSave); + log.info("成功同步 {} 名执法人员信息。", officersToSave.size()); + + } catch (Exception e) { + log.error("同步执法人员信息时发生未知异常", e); + } + } + + /** + * 从第三方API获取数据 + * + * @param requestBody 请求体 + * @param url 请求URL + * @return 响应体 + */ + @SneakyThrows + private String fetchDataFromApi(Map requestBody, String url) { + int maxRetries = 2; // 允许一次初始尝试和一次重试 + + for (int attempt = 1; attempt <= maxRetries; attempt++) { + AccessToken accessToken = authService.getTokenBackend(thirdPartyProperties.getEnterprise().getYddh()); + if (accessToken == null || accessToken.getAccess_token() == null) { + log.error("获取访问令牌失败 (尝试 {}/{}),URL: {}. 请检查AuthService配置和令牌提供者。", attempt, maxRetries, url); + throw new RuntimeException("获取API调用的访问令牌失败。"); + } + + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(accessToken.getAccess_token()); + headers.setContentType(MediaType.APPLICATION_JSON); + + try { + String requestBodyJson = objectMapper.writeValueAsString(requestBody); + HttpEntity entity = new HttpEntity<>(requestBodyJson, headers); + + log.debug("向API发送请求. URL: {}, Body: {}", url, requestBodyJson); + ResponseEntity response = restTemplate.postForEntity(url, entity, String.class); + + if (response.getStatusCode().is2xxSuccessful()) { + return response.getBody(); + } + + // 处理401以外的非成功状态码 + log.error("API请求失败 (尝试 {}/{}),URL: {}, 状态码: {}, 响应: {}", + attempt, maxRetries, url, response.getStatusCode(), response.getBody()); + return null; // 对于其他客户端或服务器错误,直接返回null + + } catch (HttpClientErrorException.Unauthorized e) { + log.warn("API请求未授权 (401) (尝试 {}/{}),URL: {}. 令牌可能已过期。", + attempt, maxRetries, url); + if (attempt < maxRetries) { + try { + authService.refreshTokenBackend(accessToken.getAccess_token(), accessToken.getToken_type(), accessToken.getRefresh_token(), thirdPartyProperties.getEnterprise().getYddh()); + log.info("请求刷新令牌成功。下一次尝试将使用新令牌。"); + } catch (Exception refreshException) { + log.error("请求刷新令牌期间发生异常: {}. 中止对URL: {} 的重试。", + refreshException.getMessage(), url, refreshException); + return null; // 刷新失败,不再重试 + } + } else { + log.error("刷新令牌后重试仍然失败 (尝试 {}/{}),URL: {}", attempt, maxRetries, url); + } + } catch (Exception e) { + log.error("调用API时发生异常 (尝试 {}/{}),URL: {}", attempt, maxRetries, url, e); + } + } + return null; // 所有尝试失败后返回null + } + + /** + * 处理从API获取的人员信息 + * + * @param apiOfficers API返回的人员列表 + * @return 处理后的Officer对象列表,准备保存到数据库 + */ + private List processApiOfficers(List apiOfficers) { + log.info("开始处理 {} 条API执法人员数据...", apiOfficers.size()); + + // 去重,以人员唯一标识为准,后出现的会覆盖先出现的 + Map distinctApiOfficersMap = apiOfficers.stream() + .filter(officer -> StringUtils.isNotEmpty(officer.getYhwybs())) + .collect(Collectors.toMap(ApiOfficer::getYhwybs, p -> p, (existing, replacement) -> replacement)); + + List distinctApiOfficers = new ArrayList<>(distinctApiOfficersMap.values()); + + log.info("去重后还有 {} 条API执法人员数据", distinctApiOfficers.size()); + + // 预先加载所有机构以提高性能 + List allAgencies = agencyRepository.findAll(); + Map agencyCodeMap = allAgencies.stream() + .filter(agency -> StringUtils.isNotEmpty(agency.getAgencyCode())) + .collect(Collectors.toMap(agency -> StringUtils.trimEven0(agency.getAgencyCode()), agency -> agency, (a1, a2) -> a1)); + log.info("已加载 {} 个机构用于匹配", agencyCodeMap.size()); + + List officersToSave = new ArrayList<>(); + + for (ApiOfficer apiOfficer : distinctApiOfficers) { + // 仅处理有姓名的人员 + if (StringUtils.isEmpty(apiOfficer.getXm())) { + log.warn("API返回的执法人员数据缺少 xm (姓名),跳过此条记录: {}", apiOfficer); + continue; + } + + // 优先通过人员唯一标识查找,不存在则创建新记录 + Optional existingOfficer = officerRepository.findByCertificateNo(apiOfficer.getYhwybs()); + Officer officer = existingOfficer.orElse(new Officer()); + + // 设置基本信息 + officer.setOfficerName(apiOfficer.getXm()); + officer.setCertificateNo(apiOfficer.getYhwybs()); // 使用唯一标识作为证号 + officer.setRole(apiOfficer.getJsdm()); + officer.setRoleName(apiOfficer.getJsmc()); + + // 转换机构代码并查找对应机构 + String apiOfficerAgencyCode = apiOfficer.getGajgjgdm(); + Agency matchedAgency = findMatchingAgency(apiOfficerAgencyCode, agencyCodeMap); + + if (matchedAgency != null) { + officer.setAgencyId(matchedAgency.getAgencyId()); + log.debug("为执法人员 {} 找到匹配机构: {}({})", + apiOfficer.getXm(), matchedAgency.getAgencyName(), matchedAgency.getAgencyCode()); + } else { + log.warn("无法为执法人员 {} 找到匹配的机构,原机构代码: {}", + apiOfficer.getXm(), apiOfficerAgencyCode); + // 跳过无法匹配机构的人员 + continue; + } + + officersToSave.add(officer); + } + + log.info("成功处理 {} 条执法人员数据", officersToSave.size()); + return officersToSave; + } + + /** + * 从最大机构级别开始逐级递减匹配机构 + * + * @param officerAgencyCode 执法人员所属的机构代码 + * @param agencyCodeMap 所有本地机构的映射(键为去掉末尾偶数0后的机构代码) + * @return 匹配到的机构,如果没有匹配则返回null + */ + private Agency findMatchingAgency(String officerAgencyCode, Map agencyCodeMap) { + if (officerAgencyCode == null || officerAgencyCode.isEmpty()) { + return null; + } + + // 去掉执法人员机构代码末尾的偶数个0 + String trimmedOfficerCode = StringUtils.trimEven0(officerAgencyCode); + + // 1. 先尝试完全匹配 + Agency directMatch = agencyCodeMap.get(trimmedOfficerCode); + if (directMatch != null) { + log.debug("执法人员机构代码 {} 直接匹配到机构 {}", officerAgencyCode, directMatch.getAgencyName()); + return directMatch; + } + + // 2. 如果没有完全匹配,从最长的机构代码开始尝试前缀匹配 + // 按机构代码长度排序(从长到短,代表从较低级别到较高级别机构) + List sortedAgencyCodes = agencyCodeMap.keySet().stream() + .sorted((a, b) -> Integer.compare(b.length(), a.length())) + .toList(); + + // 只考虑级别2及以上的机构(一般来说代码长度较短) + for (String agencyCode : sortedAgencyCodes) { + // 检查该机构代码是否是执法人员机构代码的前缀 + if (trimmedOfficerCode.startsWith(agencyCode)) { + Agency matchedAgency = agencyCodeMap.get(agencyCode); + log.debug("执法人员机构代码 {} 通过前缀匹配到机构 {} ({})", + officerAgencyCode, matchedAgency.getAgencyName(), agencyCode); + return matchedAgency; + } + } + + // 3. 反向尝试:看执法人员的机构代码是否是某个本地机构的前缀 + // 注:这种情况比较少见,执法人员可能来自更高级别的机构 + for (String agencyCode : agencyCodeMap.keySet()) { + if (agencyCode.startsWith(trimmedOfficerCode)) { + Agency matchedAgency = agencyCodeMap.get(agencyCode); + log.debug("本地机构 {} ({}) 的代码以执法人员机构代码 {} 为前缀", + matchedAgency.getAgencyName(), agencyCode, officerAgencyCode); + return matchedAgency; + } + } + + return null; + } + + /** + * API响应结构定义 + */ + @Getter + @Setter + private static class ApiResponse { + private String code; + private boolean success; + private ApiData data; + private String msg; + + public boolean isSuccess() { + return "200".equals(code) && success; + } + } + + /** + * API数据结构定义 + */ + @Getter + @Setter + private static class ApiData { + private int offset; + private int limit; + @JsonAlias("totalCount") + private String totalCountStr; + @JsonAlias("list") + private List records; + + public int getTotalPage() { + if (limit <= 0) { + return 0; + } + return (int) Math.ceil((double) getTotalCount() / limit); + } + + public int getTotalCount() { + try { + return Integer.parseInt(totalCountStr); + } catch (NumberFormatException e) { + log.warn("无法解析 totalCount: '{}'", totalCountStr, e); + return 0; + } + } + + public int getPages() { + return getTotalPage(); + } + } + + /** + * API人员信息结构定义 + */ + @Getter + @Setter + @ToString + private static class ApiOfficer { + // 人员唯一标识 + private String yhwybs; + + // 姓名 + private String xm; + + // 机构代码 + private String gajgjgdm; + + // 角色代码 + private String jsdm; + + // 角色名称 + private String jsmc; + } +} diff --git a/server/src/main/java/com/aisino/iles/lawenforcement/schedule/service/ReportCheckScheduleService.java b/server/src/main/java/com/aisino/iles/lawenforcement/schedule/service/ReportCheckScheduleService.java new file mode 100644 index 0000000..476d92d --- /dev/null +++ b/server/src/main/java/com/aisino/iles/lawenforcement/schedule/service/ReportCheckScheduleService.java @@ -0,0 +1,258 @@ +package com.aisino.iles.lawenforcement.schedule.service; + +import cn.hutool.http.HttpUtil; +import com.aisino.iles.common.util.BeanUtils; +import com.aisino.iles.common.util.Md5Utils; +import com.aisino.iles.lawenforcement.model.Materials; +import com.aisino.iles.lawenforcement.model.ReportCheck; +import com.aisino.iles.lawenforcement.repository.MaterialsRepository; +import com.aisino.iles.lawenforcement.repository.ReportCheckRepository; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 举报核查定时任务 + */ +@Service +@Slf4j +public class ReportCheckScheduleService { + private final String queryUrl; + private final String singleUrl; + private final String sign; + private final ObjectMapper objectMapper; + private final ReportCheckRepository reportCheckRepo; + private final MaterialsRepository materialsRepo; + + public ReportCheckScheduleService(@Value("${third-party.report_check.query:}") String queryUrl, + @Value("${third-party.report_check.single:}") String singleUrl, + @Value("${third-party.report_check.sign:}") String sign, + ObjectMapper objectMapper, + ReportCheckRepository reportCheckRepo, + MaterialsRepository materialsRepo) { + this.queryUrl = queryUrl; + this.singleUrl = singleUrl; + this.sign = sign; + this.objectMapper = objectMapper; + this.reportCheckRepo = reportCheckRepo; + this.materialsRepo = materialsRepo; + } + + /** + * 12350投诉举报信息导入 + * 每隔一小时执行一次 + */ + @Scheduled(fixedDelay = 120000) + @Transactional + public void scheduledTask() { + log.info("开始导入举报核查信息..."); + DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + String create_time = null; + LocalDateTime maxAcceptTime = reportCheckRepo.getMaxAcceptTime(); + if (null != maxAcceptTime) { + create_time = "[\"" + maxAcceptTime.format(df) + "\",\"" + LocalDateTime.now().format(df) + "\"]"; + } + extracted(create_time); + } + + private void extracted(String create_time) { + try { + int page = 1, pagesize = 99999; + log.info("条件={}", "{\"page\": " + page + ",\"pagesize\": " + pagesize + ",\"create_time\": " + create_time + "}"); + String body = HttpUtil.createPost(queryUrl) + .header("Content-Type", "application/json") + .header("Sign", Md5Utils.getMd5Str(sign)) + .body("{\"page\": " + page + ",\"pagesize\": " + pagesize + ",\"create_time\": " + create_time + "}") + .execute() + .body(); + log.info("信息查询结果:{}", body); + JsonNode jsonNode = objectMapper.readValue(body, JsonNode.class); + if (jsonNode.has("code") && 1 == jsonNode.get("code").asInt() && jsonNode.has("data")) { + JsonNode data = objectMapper.readValue(jsonNode.get("data").toString(), JsonNode.class); + if (null != data && data.has("result")) { + JsonNode arrNode = data.get("result"); + if (arrNode.isArray()) { + for (JsonNode node : arrNode) { + ReportCheckInterfaceDto dto = objectMapper.readValue(node.toString(), ReportCheckInterfaceDto.class); + ReportCheck reportCheck = new ReportCheck(); + LocalDateTime now = LocalDateTime.now(); + reportCheckRepo.findByPumpId(dto.getId()).ifPresent(o -> { + BeanUtils.copyProperties(o, reportCheck); + List ids = materialsRepo.findByLinkId(o.getPcid()).stream().map(Materials::getMaterialsId).collect(Collectors.toList()); + materialsRepo.deleteAllById(ids); + reportCheck.setUpdateTime(now); + }); + reportCheck.setPumpId(dto.getId());//数据id +// (dto.getWork_order_number());//工单编号 +// (dto.getWork_order_level());//工单等级 + reportCheck.setAcceptTime(dto.getCreate_time());//受理时间 + reportCheck.setReportCanal(dto.getComplaint_resource_one());//一级举报渠道-举报渠道 + reportCheck.setCanalCategory(dto.getComplaint_resource_two());//二级举报渠道-渠道分类 + reportCheck.setReportCategory(dto.getComplaint_kind_one());//一级投诉类别-投诉类别 + reportCheck.setCategorization(dto.getComplaint_kind_two());//二级投诉类别-类别分类 + reportCheck.setReporterName(dto.getComplainant_name());//姓名 + reportCheck.setReporterPhone(dto.getComplaint_phone());//联系方式 + if (StringUtils.hasText(dto.getComplaint_phone()) && dto.getComplaint_phone().contains("**")) + reportCheck.setIsAnon("1"); + reportCheck.setReporterSex(dto.getComplainant_sex());//性别 + reportCheck.setComplainTime(dto.getComplaint_time());//投诉时间 + reportCheck.setBriefFact(dto.getReport_situation());//举报情况 + reportCheck.setIndustryType(dto.getTrade());//所属行业 + reportCheck.setReportLocal(dto.getLocation_data());//举报问题所在地 + String detailBody = HttpUtil.createPost(singleUrl) + .header("Content-Type", "application/json") + .header("sign", Md5Utils.getMd5Str(sign)) + .body("{\"id\": \"" + dto.getId() + "\"}") + .execute() + .body(); + JsonNode detailNode = objectMapper.readValue(detailBody, JsonNode.class); + if (detailNode.has("code") && 1 == detailNode.get("code").asInt() && detailNode.has("data")) { + ReportCheckInterfaceDto detailDto = objectMapper.readValue(detailNode.get("data").toString(), ReportCheckInterfaceDto.class); + reportCheck.setRemark(detailDto.getRemark());//备注 + reportCheck.setTransferDistrict(detailDto.getTransfer_district());//转办区县 + reportCheck.setResTime(detailDto.getReply_time());//区县回复时间 + reportCheck.setReplyContent(detailDto.getReply_content());//回复内容 +// reportCheck.setComplainAcceptStatus(detailDto.getComplaint_status());//受理状态 + reportCheck.setDataSource("2"); + ReportCheck save = reportCheckRepo.save(reportCheck); + if (null != detailDto.getAttach() && !detailDto.getAttach().isEmpty()) { + detailDto.getAttach().forEach(attachDto -> { + Materials materials = new Materials(); + materials.setLrsj(now); + String name = attachDto.getName(); + materials.setFileType(StringUtils.hasText(name) ? name.split("\\.")[1] : null); + materials.setName(name); + materials.setSavePath(attachDto.getUrl()); + materials.setIsOriginal("2"); + materials.setMaterialsTypeCode("1"); + materials.setMaterialsTypeName("举报核查材料"); + materials.setLinkId(save.getPcid()); + materialsRepo.save(materials); + }); + } + } + } + } + } else log.info("没有数据list"); + } else log.info("没有数据"); + } catch (Exception e) { + log.info("举报核查获取异常:" + e); + } + } + + @Data + public static class ReportCheckInterfaceDto { + /** + * 数据id + */ + private String id; + /** + * 工单编号 + */ + private String work_order_number; + /** + * 工单等级 + */ + private String work_order_level; + /** + * 受理时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime create_time; + /** + * 一级举报渠道 + */ + private String complaint_resource_one; + /** + * 二级举报渠道 + */ + private String complaint_resource_two; + /** + * 一级投诉类别 + */ + private String complaint_kind_one; + /** + * 二级投诉类别 + */ + private String complaint_kind_two; + /** + * 姓名 + */ + private String complainant_name; + /** + * 联系方式 + */ + private String complaint_phone; + /** + * 性别 + */ + private String complainant_sex; + /** + * 投诉时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime complaint_time; + /** + * 举报情况 + */ + private String report_situation; + /** + * 所属行业 + */ + private String trade; + /** + * 举报问题所在地 + */ + private String location_data; + /* ************************详情字段***********************************/ + /** + * 备注 + */ + private String remark; + /** + * 转办区县 + */ + private String transfer_district; + /** + * 区县回复时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime reply_time; + /** + * 相关材料 + */ + private List attach; + /** + * 回复内容 + */ + private String reply_content; + /** + * 受理状态 1不予受理 2受理中 3 已受理 + */ + private String complaint_status; + /** + * 受理状态 + */ + private String complaint_status_text; + + } + + @Data + public static class AttachDto { + private String name; + private String uid; + private String url; + } +}