diff --git a/web-adapter/src/main/java/com/zcloud/basic/info/web/UserController.java b/web-adapter/src/main/java/com/zcloud/basic/info/web/UserController.java index fe2be1d0..e10afe78 100644 --- a/web-adapter/src/main/java/com/zcloud/basic/info/web/UserController.java +++ b/web-adapter/src/main/java/com/zcloud/basic/info/web/UserController.java @@ -200,5 +200,4 @@ public class UserController { return userService.getUserIdByPhone(getUserIdByPhoneCmd); } -} - +} \ No newline at end of file diff --git a/web-app/src/main/java/com/zcloud/basic/info/command/UserAddExe.java b/web-app/src/main/java/com/zcloud/basic/info/command/UserAddExe.java index e5d54fda..f5d3cb8e 100644 --- a/web-app/src/main/java/com/zcloud/basic/info/command/UserAddExe.java +++ b/web-app/src/main/java/com/zcloud/basic/info/command/UserAddExe.java @@ -206,7 +206,75 @@ public class UserAddExe { } catch (Exception e) { log.error("用户变更记录保存失败(不影响用户主体), userId: {}, error: {}", userE.getId(), e.getMessage(), e); } - runAfterCommitAsync(() -> syncDaHuaPersonAndWriteBack(userE)); + log.info("========== 新增用户-开始查询大华是否已存在该人员 =========="); + Integer queryDahuaId = null; + String queryDahuaCode = null; + String plainIdCard = userE.getUserIdCard(); + if (plainIdCard != null && !plainIdCard.trim().isEmpty()) { + try { + byte[] decodedBytes = Base64.getDecoder().decode(plainIdCard.trim()); + plainIdCard = new String(decodedBytes, StandardCharsets.UTF_8); + } catch (Exception e) { + log.warn("身份证号非Base64格式,按原值处理: {}", plainIdCard); + } + } + log.info("查询大华入参paperNumber(身份证号明文): {}", plainIdCard); + SingleResponse> queryResp = null; + try { + queryResp = zcloudDaHuaFacade.findPersonByPaperNumber(plainIdCard); + log.info("查询大华人员返回完整结果: {}", JSONUtil.toJsonStr(queryResp)); + } catch (Exception e) { + log.error("查询大华人员异常(不影响本地新增), paperNumber={}", plainIdCard, e); + } + if (queryResp != null && queryResp.isSuccess() && queryResp.getData() != null) { + Map dataMap = queryResp.getData(); + Object idObj = dataMap.get("id"); + Object codeObj = dataMap.get("code"); + if (idObj == null && codeObj == null) { + Object innerDataObj = dataMap.get("data"); + if (innerDataObj instanceof Map) { + Map innerData = (Map) innerDataObj; + idObj = innerData.get("id"); + codeObj = innerData.get("code"); + } + } + if (idObj != null) { + queryDahuaId = Integer.valueOf(idObj.toString()); + } + if (codeObj != null) { + queryDahuaCode = codeObj.toString(); + } + } + if (queryDahuaId != null) { + userE.setDahuaId(queryDahuaId); + userE.setDahuaCode(queryDahuaCode); + try { + userRepository.updateDahuaInfo(userE.getId(), queryDahuaId, queryDahuaCode); + log.info("大华已存在该人员,跳过大华新增,直接写回 dahuaId={}, dahuaCode={}, userId={}", queryDahuaId, queryDahuaCode, userE.getId()); + } catch (Exception e) { + log.error("写回dahuaId/dahuaCode失败(不影响本地数据), userId={}, dahuaId={}, dahuaCode={}", + userE.getId(), queryDahuaId, queryDahuaCode, e); + } + } else { + log.info("大华未找到该人员,开始执行 syncDaHuaPerson 新增大华人员"); + DaHuaSyncResult syncResult = syncDaHuaPerson(userE); + if (syncResult.dahuaId != null) { + try { + UserE updateDahua = new UserE(); + updateDahua.setId(userE.getId()); + updateDahua.setDahuaId(syncResult.dahuaId); + updateDahua.setDahuaCode(syncResult.dahuaCode); + userRepository.updateDahuaInfo(updateDahua.getId(), updateDahua.getDahuaId(), updateDahua.getDahuaCode()); + log.info("写回dahuaId/dahuaCode成功, userId={}, dahuaId={}, dahuaCode={}", + userE.getId(), syncResult.dahuaId, syncResult.dahuaCode); + } catch (Exception e) { + log.error("写回dahuaId/dahuaCode失败(不影响本地数据), userId={}, dahuaId={}, dahuaCode={}", + userE.getId(), syncResult.dahuaId, syncResult.dahuaCode, e); + } + } else { + log.info("大华同步未返回dahuaId,跳过写回操作, userId={}, dahuaCode={}", userE.getId(), syncResult.dahuaCode); + } + } } catch (BizException e) { log.error("新增用户业务异常: {}", e.getMessage(), e); throw e; @@ -223,43 +291,6 @@ public class UserAddExe { return true; } - private void runAfterCommitAsync(Runnable runnable) { - if (TransactionSynchronizationManager.isSynchronizationActive()) { - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { - @Override - public void afterCommit() { - CompletableFuture.runAsync(runnable); - } - }); - return; - } - CompletableFuture.runAsync(runnable); - } - - private void syncDaHuaPersonAndWriteBack(UserE userE) { - try { - DaHuaSyncResult syncResult = syncDaHuaPerson(userE); - if (syncResult.dahuaId != null) { - try { - UserE updateDahua = new UserE(); - updateDahua.setId(userE.getId()); - updateDahua.setDahuaId(syncResult.dahuaId); - updateDahua.setDahuaCode(syncResult.dahuaCode); - userRepository.updateDahuaInfo(updateDahua.getId(), updateDahua.getDahuaId(), updateDahua.getDahuaCode()); - log.info("写回dahuaId/dahuaCode成功, userId={}, dahuaId={}, dahuaCode={}", - userE.getId(), syncResult.dahuaId, syncResult.dahuaCode); - } catch (Exception e) { - log.error("写回dahuaId/dahuaCode失败(不影响本地数据), userId={}, dahuaId={}, dahuaCode={}", - userE.getId(), syncResult.dahuaId, syncResult.dahuaCode, e); - } - } else { - log.info("大华同步未返回dahuaId,跳过写回操作, userId={}, dahuaCode={}", userE.getId(), syncResult.dahuaCode); - } - } catch (Exception e) { - log.error("异步同步大华异常(不影响新增用户主流程), userId={}", userE.getId(), e); - } - } - private DaHuaSyncResult syncDaHuaPerson(UserE userE) { return syncDaHuaPerson(userE, userE.getFaceFile(), true); } @@ -460,11 +491,6 @@ public class UserAddExe { DaHuaSyncResult result = new DaHuaSyncResult(); result.setDahuaCode(plainIdCard); - if (daHuaConfig.getDockFlag() == null || daHuaConfig.getDockFlag() != 1) { - log.info("大华对接未开启(dockFlag={}), 跳过同步人员到大华", daHuaConfig.getDockFlag()); - userE.setDahuaCode(plainIdCard); - return result; - } DaHuaPersonSyncCmd cmd = cn.hutool.core.bean.BeanUtil.toBean(personMap, DaHuaPersonSyncCmd.class); Object biosObj = personMap.get("personBiosignatures"); @@ -1237,4 +1263,4 @@ public class UserAddExe { userChangeRecordE.initUserAdd(userE, corpName, departmentName); userChangeRecordGateway.add(userChangeRecordE); } -} +} \ No newline at end of file diff --git a/web-app/src/main/java/com/zcloud/basic/info/command/UserUpdateExe.java b/web-app/src/main/java/com/zcloud/basic/info/command/UserUpdateExe.java index f8751f88..8599a7fd 100644 --- a/web-app/src/main/java/com/zcloud/basic/info/command/UserUpdateExe.java +++ b/web-app/src/main/java/com/zcloud/basic/info/command/UserUpdateExe.java @@ -40,6 +40,11 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.ObjectUtils; import java.nio.charset.StandardCharsets; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.Period; @@ -98,7 +103,74 @@ public class UserUpdateExe { userE.setDahuaCode(userDO.getDahuaCode()); log.info("【getInfoById查询结果】dahuaId={}, dahuaCode={}, name={}, userIdCard={}", userDO.getDahuaId(), userDO.getDahuaCode(), userDO.getName(), userDO.getUserIdCard()); - syncDaHuaPersonUpdate(userUpdateCmd, userDO); + + String newIdCardForQuery = userUpdateCmd.getUserIdCard() != null ? userUpdateCmd.getUserIdCard() : userDO.getUserIdCard(); + String plainIdCardForQuery = newIdCardForQuery; + if (newIdCardForQuery != null && !newIdCardForQuery.trim().isEmpty()) { + try { + byte[] decodedBytes = Base64.getDecoder().decode(newIdCardForQuery.trim()); + plainIdCardForQuery = new String(decodedBytes, StandardCharsets.UTF_8); + } catch (Exception e) { + log.warn("身份证号非Base64格式,按原值处理: {}", newIdCardForQuery); + } + } + + boolean dahuaPersonFound = false; + log.info("修改前查询大华人员入参paperNumber(身份证号明文): {}", plainIdCardForQuery); + Integer queryDahuaId = null; + String queryDahuaCode = null; + SingleResponse> queryResp = null; + try { + log.info("调用大华Dubbo接口: zcloudDaHuaFacade.findPersonByPaperNumber, 参数paperNumber={}", plainIdCardForQuery); + queryResp = zcloudDaHuaFacade.findPersonByPaperNumber(plainIdCardForQuery); + log.info("修改前查询大华人员返回完整结果: {}", JSONUtil.toJsonStr(queryResp)); + } catch (Exception e) { + log.error("修改前查询大华人员异常(不影响本地修改), paperNumber={}", plainIdCardForQuery, e); + } + if (queryResp != null && queryResp.isSuccess() && queryResp.getData() != null) { + Map dataMap = queryResp.getData(); + log.info("修改前查询大华返回data: {}", dataMap); + Object idObj = dataMap.get("id"); + Object codeObj = dataMap.get("code"); + if (idObj == null && codeObj == null) { + Object innerDataObj = dataMap.get("data"); + if (innerDataObj instanceof Map) { + Map innerData = (Map) innerDataObj; + idObj = innerData.get("id"); + codeObj = innerData.get("code"); + } + } + if (idObj != null) { + queryDahuaId = Integer.valueOf(idObj.toString()); + } + if (codeObj != null) { + queryDahuaCode = codeObj.toString(); + } + if (queryDahuaId != null) { + dahuaPersonFound = true; + userDO.setDahuaId(queryDahuaId); + userDO.setDahuaCode(queryDahuaCode); + userE.setDahuaId(queryDahuaId); + userE.setDahuaCode(queryDahuaCode); + log.info("修改前查询大华已存在该人员, dahuaId={}, dahuaCode={}, 走更新逻辑", queryDahuaId, queryDahuaCode); + } else { + log.info("修改前查询大华返回中无id字段,视为不存在, dataMap={}", dataMap); + } + } else { + log.info("修改前查询大华结果为null或失败,视为不存在, queryResp={}", queryResp); + } + + if (!dahuaPersonFound) { + log.info("修改前查询大华未找到该人员, 走新增逻辑, paperNumber={}", plainIdCardForQuery); + Integer addDahuaId = syncDaHuaPersonAdd(userUpdateCmd, userDO, userE); + if (addDahuaId != null) { + log.info("修改前新增大华人员成功, dahuaId={}, dahuaCode={}", userE.getDahuaId(), userE.getDahuaCode()); + } else { + log.info("修改前新增大华人员未获取到dahuaId/dahuaCode,仍继续本地修改"); + } + } else { + syncDaHuaPersonUpdate(userUpdateCmd, userDO); + } UserE oldUserE = new UserE(); BeanUtils.copyProperties(userDO, oldUserE); @@ -353,22 +425,34 @@ public class UserUpdateExe { if (singleObj instanceof Map) { Map faceFileMap = (Map) singleObj; Object thumbUrlObj = faceFileMap.get("thumbUrl"); - if (thumbUrlObj != null) { + if (thumbUrlObj != null && !thumbUrlObj.toString().trim().isEmpty()) { return thumbUrlObj.toString(); } + Object urlObj = faceFileMap.get("url"); + if (urlObj != null && !urlObj.toString().trim().isEmpty()) { + return urlObj.toString().trim().replaceAll("^`+|`+$", ""); + } } else if (singleObj instanceof cn.hutool.json.JSONObject) { cn.hutool.json.JSONObject faceJson = (cn.hutool.json.JSONObject) singleObj; Object thumbUrlObj = faceJson.get("thumbUrl"); - if (thumbUrlObj != null) { + if (thumbUrlObj != null && !thumbUrlObj.toString().trim().isEmpty()) { return thumbUrlObj.toString(); } + Object urlObj = faceJson.get("url"); + if (urlObj != null && !urlObj.toString().trim().isEmpty()) { + return urlObj.toString().trim().replaceAll("^`+|`+$", ""); + } } else { try { cn.hutool.json.JSONObject faceJson = cn.hutool.json.JSONUtil.parseObj(singleObj); Object thumbUrlObj = faceJson.get("thumbUrl"); - if (thumbUrlObj != null) { + if (thumbUrlObj != null && !thumbUrlObj.toString().trim().isEmpty()) { return thumbUrlObj.toString(); } + Object urlObj = faceJson.get("url"); + if (urlObj != null && !urlObj.toString().trim().isEmpty()) { + return urlObj.toString().trim().replaceAll("^`+|`+$", ""); + } } catch (Exception e) { log.warn("解析单个图片对象为JSON失败: {}", e.getMessage()); } @@ -411,29 +495,33 @@ public class UserUpdateExe { if (thumbUrl != null && !thumbUrl.trim().isEmpty()) { String trimmedThumb = thumbUrl.trim(); boolean startsWithHttp = trimmedThumb.startsWith("http://") || trimmedThumb.startsWith("https://"); - boolean containsComma = trimmedThumb.contains(","); - boolean startsWithDataImage = trimmedThumb.startsWith("data:image"); + String cleanBase64 = null; - String cleanBase64; - if (containsComma && startsWithDataImage) { - String prefixPart = trimmedThumb.substring(0, trimmedThumb.indexOf(",") + 1); - String bodyPart = trimmedThumb.substring(trimmedThumb.indexOf(",") + 1); - String cleanBody = bodyPart.replaceAll("\\s+", ""); - cleanBase64 = prefixPart + cleanBody; + if (startsWithHttp) { + log.info("同步大华人员(更新):人脸为HTTP URL,开始下载转base64, url={}", trimmedThumb); + cleanBase64 = downloadImageToBase64(trimmedThumb); + log.info("同步大华人员(更新):URL下载转base64完成, base64长度={}", cleanBase64 == null ? 0 : cleanBase64.length()); } else { - String pureBody = trimmedThumb.replaceAll("\\s+", ""); - cleanBase64 = "data:image/jpeg;base64," + pureBody; + boolean containsComma = trimmedThumb.contains(","); + boolean startsWithDataImage = trimmedThumb.startsWith("data:image"); + if (containsComma && startsWithDataImage) { + String prefixPart = trimmedThumb.substring(0, trimmedThumb.indexOf(",") + 1); + String bodyPart = trimmedThumb.substring(trimmedThumb.indexOf(",") + 1); + String cleanBody = bodyPart.replaceAll("\\s+", ""); + cleanBase64 = prefixPart + cleanBody; + } else { + String pureBody = trimmedThumb.replaceAll("\\s+", ""); + cleanBase64 = "data:image/jpeg;base64," + pureBody; + } } + int commaIdx = cleanBase64.indexOf(","); String base64BodyOnly = commaIdx >= 0 ? cleanBase64.substring(commaIdx + 1) : cleanBase64; boolean isLikelyBase64 = base64BodyOnly.matches("^[A-Za-z0-9+/=]+$"); int bodyLen = base64BodyOnly.length(); boolean finalHasPrefix = cleanBase64.startsWith("data:image"); - if (startsWithHttp) { - log.error("同步大华人员(更新)失败:人脸图片传的是HTTP/HTTPS URL,要求必须传带data:image前缀的base64图片"); - throw new BizException("同步大华平台失败:人脸图片不支持URL格式,请上传图片文件"); - } else if (!finalHasPrefix) { + if (!finalHasPrefix) { log.error("同步大华人员(更新)失败:人脸图片最终无data:image前缀,代码补齐逻辑失效"); throw new BizException("同步大华平台失败:人脸图片格式异常"); } else if (!isLikelyBase64) { @@ -453,11 +541,6 @@ public class UserUpdateExe { personMap.put("personBiosignatures", personBiosignatures); } - if (daHuaConfig.getDockFlag() == null || daHuaConfig.getDockFlag() != 1) { - log.info("大华对接未开启(dockFlag={}), 跳过更新大华人员", daHuaConfig.getDockFlag()); - return; - } - DaHuaPersonSyncCmd cmd = cn.hutool.core.bean.BeanUtil.toBean(personMap, DaHuaPersonSyncCmd.class); Object biosObj = personMap.get("personBiosignatures"); if (biosObj instanceof List) { @@ -489,6 +572,255 @@ public class UserUpdateExe { } } + private Integer syncDaHuaPersonAdd(UserUpdateCmd userUpdateCmd, UserDO userDO, UserE userE) { + log.info("========== 大华人员(新增)检查开始 =========="); + log.info("用户主键userDO.id={}, name={}, userIdCard={}", + userDO.getId(), userDO.getName(), userDO.getUserIdCard()); + + String newName = userUpdateCmd.getName() != null ? userUpdateCmd.getName() : userDO.getName(); + String newIdCard = userUpdateCmd.getUserIdCard() != null ? userUpdateCmd.getUserIdCard() : userDO.getUserIdCard(); + String newPhone = userUpdateCmd.getPhone() != null ? userUpdateCmd.getPhone() : userDO.getPhone(); + String newNation = userUpdateCmd.getNation() != null ? userUpdateCmd.getNation() : userDO.getNation(); + String newNationName = userUpdateCmd.getNationName() != null ? userUpdateCmd.getNationName() : userDO.getNationName(); + Object newFaceFile = userUpdateCmd.getUserImg() != null ? userUpdateCmd.getUserImg() : userUpdateCmd.getFaceFile(); + Long newCorpId = userUpdateCmd.getCorpinfoId() != null ? userUpdateCmd.getCorpinfoId() : userDO.getCorpinfoId(); + + String plainIdCard = newIdCard; + if (newIdCard != null && !newIdCard.trim().isEmpty()) { + try { + byte[] decodedBytes = Base64.getDecoder().decode(newIdCard.trim()); + plainIdCard = new String(decodedBytes, StandardCharsets.UTF_8); + } catch (Exception e) { + log.warn("身份证号非Base64格式,按原值处理: {}", newIdCard); + } + } + + Map personMap = new HashMap<>(); + personMap.put("name", newName); + personMap.put("code", plainIdCard); + personMap.put("paperType", 111); + personMap.put("paperNumber", plainIdCard); + personMap.put("phone", newPhone); + Long realCorpId = newCorpId != null ? newCorpId : 1L; + personMap.put("departmentId", realCorpId); + personMap.put("departmentType", 2); + personMap.put("type", 3); + personMap.put("nationName", newNationName != null && !newNationName.trim().isEmpty() ? newNationName : "汉族"); + try { + if (newNation != null && !newNation.trim().isEmpty()) { + personMap.put("nation", Integer.parseInt(newNation.trim())); + } else { + personMap.put("nation", 1); + } + } catch (Exception e) { + log.warn("民族编码转Integer失败: {}, 用默认值1(汉族)", newNation); + personMap.put("nation", 1); + } + personMap.put("service", "evo-thirdParty"); + + Integer age = null; + Integer sex = null; + if (plainIdCard != null && plainIdCard.length() == 18) { + try { + String birthStr = plainIdCard.substring(6, 14); + LocalDate birthDate = LocalDate.parse(birthStr, DateTimeFormatter.ofPattern("yyyyMMdd")); + age = Period.between(birthDate, LocalDate.now()).getYears(); + String sexChar = plainIdCard.substring(16, 17); + int sexNum = Integer.parseInt(sexChar); + sex = sexNum % 2 == 0 ? 2 : 1; + } catch (Exception e) { + log.warn("从身份证号解析年龄/性别失败, idCard={}", plainIdCard); + } + } + personMap.put("age", age); + personMap.put("sex", sex); + + Object faceFile = newFaceFile; + java.util.function.Function extractThumbFromSingle = (singleObj) -> { + if (singleObj == null) { + return null; + } + if (singleObj instanceof Map) { + Map faceFileMap = (Map) singleObj; + Object thumbUrlObj = faceFileMap.get("thumbUrl"); + if (thumbUrlObj != null && !thumbUrlObj.toString().trim().isEmpty()) { + return thumbUrlObj.toString(); + } + Object urlObj = faceFileMap.get("url"); + if (urlObj != null && !urlObj.toString().trim().isEmpty()) { + return urlObj.toString().trim().replaceAll("^`+|`+$", ""); + } + } else if (singleObj instanceof cn.hutool.json.JSONObject) { + cn.hutool.json.JSONObject faceJson = (cn.hutool.json.JSONObject) singleObj; + Object thumbUrlObj = faceJson.get("thumbUrl"); + if (thumbUrlObj != null && !thumbUrlObj.toString().trim().isEmpty()) { + return thumbUrlObj.toString(); + } + Object urlObj = faceJson.get("url"); + if (urlObj != null && !urlObj.toString().trim().isEmpty()) { + return urlObj.toString().trim().replaceAll("^`+|`+$", ""); + } + } else { + try { + cn.hutool.json.JSONObject faceJson = cn.hutool.json.JSONUtil.parseObj(singleObj); + Object thumbUrlObj = faceJson.get("thumbUrl"); + if (thumbUrlObj != null && !thumbUrlObj.toString().trim().isEmpty()) { + return thumbUrlObj.toString(); + } + Object urlObj = faceJson.get("url"); + if (urlObj != null && !urlObj.toString().trim().isEmpty()) { + return urlObj.toString().trim().replaceAll("^`+|`+$", ""); + } + } catch (Exception e) { + log.warn("解析单个图片对象为JSON失败: {}", e.getMessage()); + } + } + return null; + }; + + String thumbUrl = null; + if (faceFile != null) { + boolean isArray = false; + Object firstElement = null; + if (faceFile instanceof java.util.Collection) { + isArray = true; + java.util.Collection coll = (java.util.Collection) faceFile; + if (!coll.isEmpty()) { + firstElement = coll.iterator().next(); + } + } else if (faceFile.getClass().isArray()) { + isArray = true; + Object[] arr = (Object[]) faceFile; + if (arr.length > 0) { + firstElement = arr[0]; + } + } else if (faceFile instanceof cn.hutool.json.JSONArray) { + isArray = true; + cn.hutool.json.JSONArray jsonArr = (cn.hutool.json.JSONArray) faceFile; + if (!jsonArr.isEmpty()) { + firstElement = jsonArr.get(0); + } + } + if (isArray) { + if (firstElement != null) { + thumbUrl = extractThumbFromSingle.apply(firstElement); + } + } else { + thumbUrl = extractThumbFromSingle.apply(faceFile); + } + } + + if (thumbUrl != null && !thumbUrl.trim().isEmpty()) { + String trimmedThumb = thumbUrl.trim(); + boolean startsWithHttp = trimmedThumb.startsWith("http://") || trimmedThumb.startsWith("https://"); + String cleanBase64 = null; + + if (startsWithHttp) { + log.info("同步大华人员(新增):人脸为HTTP URL,开始下载转base64, url={}", trimmedThumb); + cleanBase64 = downloadImageToBase64(trimmedThumb); + log.info("同步大华人员(新增):URL下载转base64完成, base64长度={}", cleanBase64 == null ? 0 : cleanBase64.length()); + } else { + boolean startsWithDataImage = trimmedThumb.startsWith("data:image"); + if (trimmedThumb.contains(",") && startsWithDataImage) { + String prefixPart = trimmedThumb.substring(0, trimmedThumb.indexOf(",") + 1); + String bodyPart = trimmedThumb.substring(trimmedThumb.indexOf(",") + 1); + String cleanBody = bodyPart.replaceAll("\\s+", ""); + cleanBase64 = prefixPart + cleanBody; + } else { + String pureBody = trimmedThumb.replaceAll("\\s+", ""); + cleanBase64 = "data:image/jpeg;base64," + pureBody; + } + } + + int commaIdx = cleanBase64.indexOf(","); + String base64BodyOnly = commaIdx >= 0 ? cleanBase64.substring(commaIdx + 1) : cleanBase64; + boolean isLikelyBase64 = base64BodyOnly.matches("^[A-Za-z0-9+/=]+$"); + int bodyLen = base64BodyOnly.length(); + boolean finalHasPrefix = cleanBase64.startsWith("data:image"); + + if (finalHasPrefix && isLikelyBase64 && bodyLen >= 1000) { + List personBiosignatures = new ArrayList<>(); + DaHuaPersonSyncCmd.PersonBiosignature biosignature = new DaHuaPersonSyncCmd.PersonBiosignature(); + biosignature.setType(3); + biosignature.setIndex(1); + biosignature.setBase64Data(cleanBase64); + personBiosignatures.add(biosignature); + personMap.put("personBiosignatures", personBiosignatures); + } else { + log.warn("同步大华人员(新增):人脸图片校验不通过,放弃传人脸, finalHasPrefix={}, isLikelyBase64={}, bodyLen={}", + finalHasPrefix, isLikelyBase64, bodyLen); + } + } + + DaHuaPersonSyncCmd cmd = cn.hutool.core.bean.BeanUtil.toBean(personMap, DaHuaPersonSyncCmd.class); + Object biosObj = personMap.get("personBiosignatures"); + if (biosObj instanceof List) { + @SuppressWarnings("unchecked") + List bioList = (List) biosObj; + cmd.setPersonBiosignatures(bioList); + } + String maskedIdCard = plainIdCard; + if (plainIdCard != null && plainIdCard.length() >= 14) { + maskedIdCard = plainIdCard.substring(0, 6) + "********" + plainIdCard.substring(plainIdCard.length() - 4); + } + log.info("Dubbo调用primeport.zcloudDaHuaFacade.syncPerson(新增), name={}, departmentId(corpId)={}, idCardMask={}", + cmd.getName(), cmd.getDepartmentId(), maskedIdCard); + if (cmd.getPersonBiosignatures() != null) { + log.info("Dubbo调用新增大华人员携带人脸生物特征, size={}", cmd.getPersonBiosignatures().size()); + } + try { + SingleResponse syncResp = zcloudDaHuaFacade.syncPerson(cmd); + if (syncResp == null || !syncResp.isSuccess()) { + String errCode = syncResp == null ? "" : syncResp.getErrCode(); + String errMessage = syncResp == null ? "大华Dubbo返回为空" : syncResp.getErrMessage(); + if (errMessage == null || errMessage.trim().isEmpty()) errMessage = "大华人员新增失败"; + log.warn("新增大华人员失败(不影响本地修改), errCode={}, errMessage={}, name={}, idCard={}", errCode, errMessage, newName, maskedIdCard); + return null; + } + log.info("新增大华人员成功, name={}, idCard={}", newName, maskedIdCard); + Object data = syncResp.getData(); + if (data != null) { + Integer dahuaId = null; + if (data instanceof Map) { + Map dataMap = (Map) data; + Object idObj = dataMap.get("personId"); + if (idObj == null) { + idObj = dataMap.get("id"); + } + if (idObj != null) { + dahuaId = Integer.valueOf(idObj.toString()); + } + } else if (data instanceof cn.hutool.json.JSONObject) { + cn.hutool.json.JSONObject dataJson = (cn.hutool.json.JSONObject) data; + Object idObj = dataJson.get("personId"); + if (idObj == null) { + idObj = dataJson.get("id"); + } + if (idObj != null) { + dahuaId = Integer.valueOf(idObj.toString()); + } + } + if (dahuaId != null) { + String dahuaCode = plainIdCard; + userRepository.updateDahuaInfo(userDO.getId(), dahuaId, dahuaCode); + userDO.setDahuaId(dahuaId); + userDO.setDahuaCode(dahuaCode); + userE.setDahuaId(dahuaId); + userE.setDahuaCode(dahuaCode); + log.info("新增大华人员后写回dahuaId/dahuaCode成功, userId={}, dahuaId={}, dahuaCode={}", + userDO.getId(), dahuaId, dahuaCode); + return dahuaId; + } else { + log.warn("新增大华人员成功但未获取到dahuaId, data={}", data); + } + } + return null; + } catch (Exception e) { + log.error("新增大华人员异常(不影响本地修改), name={}, idCard={}", newName, maskedIdCard, e); + return null; + } + } + @Transactional(rollbackFor = Exception.class) public void editData(UserQryCmd userUpdateCmd) { List userDOList = new ArrayList<>(); @@ -1115,4 +1447,69 @@ public class UserUpdateExe { } + private static String downloadImageToBase64(String urlStr) { + if (urlStr == null || urlStr.trim().isEmpty()) { + throw new BizException("同步大华平台失败:人脸图片URL为空"); + } + String cleanUrl = urlStr.trim().replaceAll("^`+|`+$", ""); + HttpURLConnection conn = null; + InputStream in = null; + try { + URL url = new URL(cleanUrl); + conn = (HttpURLConnection) url.openConnection(); + conn.setConnectTimeout(15000); + conn.setReadTimeout(30000); + conn.setRequestMethod("GET"); + conn.setRequestProperty("User-Agent", "Mozilla/5.0 ZCloudGBS"); + int respCode = conn.getResponseCode(); + if (respCode < 200 || respCode >= 300) { + log.error("下载人脸图片失败, HTTP状态码={}, url={}", respCode, cleanUrl); + throw new BizException("同步大华平台失败:人脸图片下载失败(HTTP " + respCode + ")"); + } + String contentType = conn.getContentType(); + in = conn.getInputStream(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buf = new byte[8192]; + int len; + while ((len = in.read(buf)) != -1) { + baos.write(buf, 0, len); + } + byte[] imgBytes = baos.toByteArray(); + if (imgBytes == null || imgBytes.length < 1000) { + log.error("下载人脸图片失败, 文件过小({}字节), url={}", imgBytes == null ? 0 : imgBytes.length, cleanUrl); + throw new BizException("同步大华平台失败:人脸图片下载不完整"); + } + String imgType = "jpeg"; + if (contentType != null) { + String ct = contentType.toLowerCase(); + if (ct.contains("image/png")) { + imgType = "png"; + } else if (ct.contains("image/jpg") || ct.contains("image/jpeg")) { + imgType = "jpeg"; + } else if (ct.contains("image/gif")) { + imgType = "gif"; + } else if (ct.contains("image/webp")) { + imgType = "webp"; + } else if (ct.contains("image/bmp")) { + imgType = "bmp"; + } + } + String base64Body = Base64.getEncoder().encodeToString(imgBytes); + return "data:image/" + imgType + ";base64," + base64Body; + } catch (BizException e) { + throw e; + } catch (Exception e) { + log.error("下载人脸图片异常, url={}", cleanUrl, e); + throw new BizException("同步大华平台失败:人脸图片下载异常(" + e.getMessage() + ")"); + } finally { + if (in != null) { + try { in.close(); } catch (Exception ignore) {} + } + if (conn != null) { + try { conn.disconnect(); } catch (Exception ignore) {} + } + } + } + + } \ No newline at end of file diff --git a/web-app/src/main/java/com/zcloud/basic/info/service/UserServiceImpl.java b/web-app/src/main/java/com/zcloud/basic/info/service/UserServiceImpl.java index 12c089f4..3c97c73a 100644 --- a/web-app/src/main/java/com/zcloud/basic/info/service/UserServiceImpl.java +++ b/web-app/src/main/java/com/zcloud/basic/info/service/UserServiceImpl.java @@ -12,6 +12,7 @@ import com.zcloud.basic.info.dto.clientobject.*; import com.zcloud.basic.info.persistence.dataobject.UserImgDO; import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; @@ -41,6 +42,7 @@ public class UserServiceImpl implements UserServiceI { } @Override + @Transactional(rollbackFor = Exception.class) public SingleResponse add(UserAddCmd cmd) { userAddExe.execute(cmd);