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 6328e2c7..94685572 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 @@ -100,31 +100,22 @@ public class UserAddExe { // if (!b) { // throw new BizException("请先完善企业信息"); // } - log.info("========== 开始新增用户,请求参数 name: {}, corpinfoId: {}, phone: {}, 请求体 JSON: {}", cmd.getName(), cmd.getCorpinfoId(), cmd.getPhone(), JSONUtil.toJsonStr(cmd)); + log.info("开始新增用户,name: {}, corpinfoId: {}, phone: {}", cmd.getName(), cmd.getCorpinfoId(), cmd.getPhone()); try { ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attrs != null) { HttpServletRequest req = attrs.getRequest(); - Enumeration headerNames = req.getHeaderNames(); - Map headerMap = new HashMap<>(); - while (headerNames.hasMoreElements()) { - String h = headerNames.nextElement(); - headerMap.put(h, req.getHeader(h)); - } - log.info("========== 请求头信息: {}", JSONUtil.toJsonStr(headerMap)); String orgidHeader = req.getHeader("orgid"); - log.info("========== 从请求头获取 orgid: {}", orgidHeader); if (orgidHeader != null && cmd.getCorpinfoId() == null) { try { cmd.setCorpinfoId(Long.parseLong(orgidHeader.trim())); - log.info("========== 用请求头 orgid 设置 cmd.corpinfoId: {}", cmd.getCorpinfoId()); } catch (Exception e) { - log.warn("========== orgid 转 Long 失败: {}", orgidHeader); + log.warn("orgid 转 Long 失败: {}", orgidHeader); } } } } catch (Exception e) { - log.warn("========== 获取请求头异常: {}", e.getMessage()); + log.warn("获取请求头异常: {}", e.getMessage()); } Long tenantId = null; SSOUser ssoUser = AuthContext.getCurrentUser(); @@ -185,34 +176,39 @@ public class UserAddExe { userE.setRoleId(roleId); } try { - log.info("========== 企业类型 type: {}", corpInfoDO.getType()); userE.setUserType(CorpTypeEnum.getUserTypeByCode(corpInfoDO.getType())); userE.resetPassword(corpInfoDO.getType()); //先同步大华人脸平台,成功后再保存本地 - log.info("========== 开始调用大华同步接口"); syncDaHuaPerson(userE); - log.info("========== 大华同步成功,开始保存本地用户"); res = userGateway.add(userE); - log.info("========== 本地保存结果: {}", res); if (corpInfoDO != null && !ObjectUtils.isEmpty(corpInfoDO.getCorpName())) { corpName = corpInfoDO.getCorpName(); } userEmploymentLogE.initAdd(userEmploymentLogE, corpName, userE.getId()); - userEmploymentLogGateway.add(userEmploymentLogE); - addUserChangeRecordForSave(userE, corpInfoDO); - log.info("========== 用户就业日志保存成功"); + try { + userEmploymentLogGateway.add(userEmploymentLogE); + } catch (Exception e) { + log.error("用户就业日志保存失败(不影响用户主体), userId: {}, error: {}", userE.getId(), e.getMessage(), e); + } + try { + addUserChangeRecordForSave(userE, corpInfoDO); + } catch (Exception e) { + log.error("用户变更记录保存失败(不影响用户主体), userId: {}, error: {}", userE.getId(), e.getMessage(), e); + } } catch (BizException e) { - log.error("========== 业务异常: {}", e.getMessage(), e); + log.error("新增用户业务异常: {}", e.getMessage(), e); throw e; } catch (Exception e) { - log.error("========== 系统异常: {}", e.getMessage(), e); + log.error("新增用户系统异常: {}", e.getMessage(), e); throw new RuntimeException(e); } if (!res) { throw new BizException("保存失败"); } - log.info("========== 新增用户完成"); + cmd.setId(userE.getId()); + cmd.setUsername(userE.getUsername()); + log.info("新增用户完成, userId: {}, username: {}", userE.getId(), userE.getUsername()); return true; } @@ -241,67 +237,34 @@ public class UserAddExe { personMap.put("service", "evo-thirdParty"); Object faceFile = userE.getFaceFile(); - log.info("========== 大华图片调试开始 =========="); - log.info("faceFile原始类型: {}, faceFile原始值(截取前500字符): {}", - faceFile == null ? "null" : faceFile.getClass().getName(), - faceFile == null ? "null" : (faceFile.toString().length() > 500 ? faceFile.toString().substring(0, 500) + "..." : faceFile.toString())); - log.info("userE.getUserAvatarUrl()值(截取前500字符): {}", - userE.getUserAvatarUrl() == null ? "null" : (userE.getUserAvatarUrl().length() > 500 ? userE.getUserAvatarUrl().substring(0, 500) + "..." : userE.getUserAvatarUrl())); java.util.function.Function extractThumbFromSingle = (singleObj) -> { if (singleObj == null) { - log.debug("单个图片对象为null"); return null; } - log.info("从单个对象提取thumbUrl,对象类型: {}", singleObj.getClass().getName()); if (singleObj instanceof Map) { Map faceFileMap = (Map) singleObj; - log.info("单个对象是Map类型,所有key: {}", faceFileMap.keySet()); - for (Map.Entry entry : faceFileMap.entrySet()) { - Object v = entry.getValue(); - String vStr = v == null ? "null" : (v.toString().length() > 200 ? v.toString().substring(0, 200) + "..." : v.toString()); - log.info("单个对象 key={}, value类型={}, value={}", entry.getKey(), v == null ? "null" : v.getClass().getName(), vStr); - } Object thumbUrlObj = faceFileMap.get("thumbUrl"); if (thumbUrlObj != null) { - String res = thumbUrlObj.toString(); - log.info("从Map提取到thumbUrl,长度={}", res.length()); - return res; + return thumbUrlObj.toString(); } } else if (singleObj instanceof cn.hutool.json.JSONObject) { cn.hutool.json.JSONObject faceJson = (cn.hutool.json.JSONObject) singleObj; - log.info("单个对象是JSONObject类型,所有key: {}", faceJson.keySet()); - for (String key : faceJson.keySet()) { - Object v = faceJson.get(key); - String vStr = v == null ? "null" : (v.toString().length() > 200 ? v.toString().substring(0, 200) + "..." : v.toString()); - log.info("单个对象 key={}, value类型={}, value={}", key, v == null ? "null" : v.getClass().getName(), vStr); - } Object thumbUrlObj = faceJson.get("thumbUrl"); if (thumbUrlObj != null) { - String res = thumbUrlObj.toString(); - log.info("从JSONObject提取到thumbUrl,长度={}", res.length()); - return res; + return thumbUrlObj.toString(); } } else { try { cn.hutool.json.JSONObject faceJson = cn.hutool.json.JSONUtil.parseObj(singleObj); - log.info("单个对象解析为JSONObject后,所有key: {}", faceJson.keySet()); - for (String key : faceJson.keySet()) { - Object v = faceJson.get(key); - String vStr = v == null ? "null" : (v.toString().length() > 200 ? v.toString().substring(0, 200) + "..." : v.toString()); - log.info("单个对象 key={}, value类型={}, value={}", key, v == null ? "null" : v.getClass().getName(), vStr); - } Object thumbUrlObj = faceJson.get("thumbUrl"); if (thumbUrlObj != null) { - String res = thumbUrlObj.toString(); - log.info("从解析后的JSONObject提取到thumbUrl,长度={}", res.length()); - return res; + return thumbUrlObj.toString(); } } catch (Exception e) { - log.warn("解析单个图片对象为JSON失败: {}, 错误: {}", singleObj, e.getMessage()); + log.warn("解析单个图片对象为JSON失败: {}", e.getMessage()); } } - log.warn("单个对象中未找到thumbUrl字段"); return null; }; @@ -312,56 +275,39 @@ public class UserAddExe { if (faceFile instanceof java.util.Collection) { isArray = true; java.util.Collection coll = (java.util.Collection) faceFile; - log.info("faceFile是Collection/List类型,size={}", coll.size()); if (!coll.isEmpty()) { firstElement = coll.iterator().next(); - log.info("取Collection第一个元素,类型: {}", firstElement == null ? "null" : firstElement.getClass().getName()); - } else { - log.warn("faceFile是空数组,无元素可取"); } } else if (faceFile.getClass().isArray()) { isArray = true; Object[] arr = (Object[]) faceFile; - log.info("faceFile是原生数组类型,length={}", arr.length); if (arr.length > 0) { firstElement = arr[0]; - log.info("取数组第0个元素,类型: {}", firstElement == null ? "null" : firstElement.getClass().getName()); - } else { - log.warn("faceFile是空原生数组,无元素可取"); } } else if (faceFile instanceof cn.hutool.json.JSONArray) { isArray = true; cn.hutool.json.JSONArray jsonArr = (cn.hutool.json.JSONArray) faceFile; - log.info("faceFile是JSONArray类型,size={}", jsonArr.size()); if (!jsonArr.isEmpty()) { firstElement = jsonArr.get(0); - log.info("取JSONArray第0个元素,类型: {}", firstElement == null ? "null" : firstElement.getClass().getName()); - } else { - log.warn("faceFile是空JSONArray,无元素可取"); } } if (isArray) { - log.info("faceFile是数组/集合类型,从第一个元素中提取thumbUrl"); if (firstElement != null) { thumbUrl = extractThumbFromSingle.apply(firstElement); - } else { - log.warn("数组第一个元素是null,无法提取thumbUrl"); } } else { - log.info("faceFile是单个对象类型,直接提取thumbUrl"); thumbUrl = extractThumbFromSingle.apply(faceFile); } } - log.info("从faceFile提取thumbUrl结果(截取前500字符): {}", - thumbUrl == null ? "null" : (thumbUrl.length() > 500 ? thumbUrl.substring(0, 500) + "..." : thumbUrl)); + if (thumbUrl != null && !thumbUrl.trim().isEmpty()) { String trimmedThumb = thumbUrl.trim(); - boolean startsWithDataImage = trimmedThumb.startsWith("data:image"); boolean startsWithHttp = trimmedThumb.startsWith("http://") || trimmedThumb.startsWith("https://"); boolean containsComma = trimmedThumb.contains(","); + boolean startsWithDataImage = trimmedThumb.startsWith("data:image"); String cleanBase64; if (containsComma && startsWithDataImage) { @@ -369,46 +315,25 @@ public class UserAddExe { String bodyPart = trimmedThumb.substring(trimmedThumb.indexOf(",") + 1); String cleanBody = bodyPart.replaceAll("\\s+", ""); cleanBase64 = prefixPart + cleanBody; - log.info("已保留data:image前缀(严格对齐大华文档示例), 前缀={}, 仅清洗主体中的空白字符", prefixPart.length() > 80 ? prefixPart.substring(0, 80) + "..." : prefixPart); } else { String pureBody = trimmedThumb.replaceAll("\\s+", ""); cleanBase64 = "data:image/jpeg;base64," + pureBody; - log.info("原始thumbUrl无标准data:image前缀,已自动补齐(data:image/jpeg;base64,)以对齐大华文档示例"); } int commaIdx = cleanBase64.indexOf(","); String base64BodyOnly = commaIdx >= 0 ? cleanBase64.substring(commaIdx + 1) : cleanBase64; boolean isLikelyBase64 = base64BodyOnly.matches("^[A-Za-z0-9+/=]+$"); - int totalLen = cleanBase64.length(); int bodyLen = base64BodyOnly.length(); boolean finalHasPrefix = cleanBase64.startsWith("data:image"); - log.info("========== 图片格式判断结果(严格对齐大华文档示例) =========="); - log.info("原始thumbUrl总长度: {}", trimmedThumb.length()); - log.info("最终结果是否带data:image前缀(文档示例:true): {}", finalHasPrefix); - log.info("是否是http/https URL(文档要求:false): {}", startsWithHttp); - log.info("最终整体长度(含前缀): {}", totalLen); - log.info("base64主体长度(去掉前缀逗号后): {}", bodyLen); - log.info("base64主体是否匹配纯base64字符集(A-Za-z0-9+/=): {}", isLikelyBase64); if (startsWithHttp) { - log.error("===== 严重错误:传的是HTTP/HTTPS URL(或文件路径)!大华文档personBiosignatures要求必须传【带data:image前缀的base64图片】,不是URL ====="); - System.out.println("===== 大华图片错误:HTTP URL无效, 前200: " + (trimmedThumb.length() > 200 ? trimmedThumb.substring(0, 200) : trimmedThumb)); + log.error("同步大华人员失败:人脸图片传的是HTTP/HTTPS URL,要求必须传带data:image前缀的base64图片"); } else if (!finalHasPrefix) { - log.error("===== 最终结果竟然没有data:image前缀(代码补齐逻辑失效) ====="); - System.out.println("===== 大华图片错误:无data:image前缀, 前100: " + (cleanBase64.length() > 100 ? cleanBase64.substring(0, 100) : cleanBase64)); + log.error("同步大华人员失败:人脸图片最终无data:image前缀,代码补齐逻辑失效"); } else if (!isLikelyBase64) { - log.error("===== base64主体部分含非base64非法字符!前200: {}", base64BodyOnly.length() > 200 ? base64BodyOnly.substring(0, 200) : base64BodyOnly); - System.out.println("===== 大华图片错误:主体非base64字符, 前200: " + (base64BodyOnly.length() > 200 ? base64BodyOnly.substring(0, 200) : base64BodyOnly)); + log.error("同步大华人员失败:人脸图片base64主体含非base64非法字符"); } else if (bodyLen < 1000) { - log.error("===== base64主体长度过短({}),缩略图太小/损坏!正常300KB JPEG→主体约40万字符 =====", bodyLen); - System.out.println("===== 大华图片错误:主体过短=" + bodyLen); - } else { - log.info("===== ✅ 完全符合大华文档personBiosignatures格式:带data:image前缀 + 纯base64主体 + 长度正常 ====="); + log.error("同步大华人员失败:人脸图片base64主体长度过短({}),缩略图可能损坏", bodyLen); } - log.info("【最终传给大华base64Data(严格对齐文档)】前150字符: {}", cleanBase64.length() > 150 ? cleanBase64.substring(0, 150) : cleanBase64); - log.info("【最终传给大华base64Data】后150字符: {}", cleanBase64.length() > 150 ? cleanBase64.substring(cleanBase64.length() - 150) : cleanBase64); - System.out.println("===== 最终带前缀完整base64Data 前200: " + (cleanBase64.length() > 200 ? cleanBase64.substring(0, 200) : cleanBase64)); - System.out.println("===== 最终带前缀完整base64Data 后200: " + (cleanBase64.length() > 200 ? cleanBase64.substring(cleanBase64.length() - 200) : cleanBase64)); - System.out.println("===== 是否按文档带data:image前缀=" + finalHasPrefix + " / 最终总长度=" + totalLen + " / 主体长度=" + bodyLen); List> personBiosignatures = new ArrayList<>(); Map biosignature = new java.util.LinkedHashMap<>(); @@ -417,15 +342,9 @@ public class UserAddExe { biosignature.put("base64Data", cleanBase64); personBiosignatures.add(biosignature); personMap.put("personBiosignatures", personBiosignatures); - log.info("组装personBiosignatures成功(严格按文档顺序type→index→base64Data,LinkedHashMap有序)"); - String biosigJson = JSONUtil.toJsonStr(personBiosignatures); - log.info("personBiosignatures JSON(截取前500字符): {}", biosigJson.length() > 500 ? biosigJson.substring(0, 500) + "..." : biosigJson); - System.out.println("===== 传给大华personBiosignatures完整JSON前500字符: " + (biosigJson.length() > 500 ? biosigJson.substring(0, 500) + "..." : biosigJson)); } else { log.warn("未获取到人脸图片thumbUrl,personBiosignatures将不传递"); - System.out.println("===== 警告:未从faceFile中获取到有效的thumbUrl,personBiosignatures不会传给大华!"); } - log.info("========== 大华图片调试结束 =========="); Integer age = null; Integer sex = null; @@ -490,7 +409,6 @@ public class UserAddExe { if (tenantId != null && !tenantId.trim().isEmpty()) { extraHeaders.put("tenantId", tenantId); } - log.info("收集到需要透传的请求头: {}", extraHeaders); } } catch (Exception e) { log.warn("获取当前请求头异常: {}", e.getMessage()); @@ -501,14 +419,15 @@ public class UserAddExe { if (skipped != null && skipped) { return; } + String maskedIdCard = plainIdCard; + if (plainIdCard != null && plainIdCard.length() >= 14) { + maskedIdCard = plainIdCard.substring(0, 6) + "********" + plainIdCard.substring(plainIdCard.length() - 4); + } if (success == null || !success) { String errCode = (String) syncResult.getOrDefault("errCode", ""); String errMessage = (String) syncResult.getOrDefault("errMessage", "大华人员同步失败"); - log.error("同步大华人员失败, errCode={}, errMessage={}, name={}, idCard={}", errCode, errMessage, userE.getName(), plainIdCard); throw new BizException("同步大华平台失败:" + errMessage); } - log.info("同步大华人员成功, name={}, idCard={}", userE.getName(), plainIdCard); - System.out.println("同步大华人员成功, name=" + userE.getName() + ", idCard=" + plainIdCard); Object data = syncResult.get("data"); if (data != null) { if (data instanceof Map) { @@ -538,7 +457,6 @@ public class UserAddExe { } } userE.setDahuaCode(plainIdCard); - log.info("设置大华dahuaCode成功, dahuaCode={}", plainIdCard); } @Transactional(rollbackFor = Exception.class) @@ -898,7 +816,7 @@ public class UserAddExe { sendParams.put("code", phoneCode); messageSendCmd.setParams(sendParams); SingleResponse d = messageFacade.send(messageSendCmd); - System.out.println( d.toString()); + log.info("短信发送结果: {}, phone: {}", d, phone); return d == null ? false : d.getData(); } 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 2287c9d4..133fe6f1 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 @@ -14,6 +14,7 @@ import com.jjb.saas.system.client.user.request.UserUpdateQuitCmd; import com.jjb.saas.system.client.user.response.UserDetailCO; import com.sun.xml.bind.v2.TODO; import com.zcloud.basic.info.constant.RedisConstant; +import com.zcloud.basic.info.domain.config.DaHuaConfig; import com.zcloud.basic.info.domain.enums.*; import com.zcloud.basic.info.domain.gateway.*; import com.zcloud.basic.info.domain.model.*; @@ -34,13 +35,19 @@ import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.ObjectUtils; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import javax.servlet.http.HttpServletRequest; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.Period; +import java.time.format.DateTimeFormatter; import java.util.*; +import java.util.Base64; import java.util.stream.Collectors; -import static dm.jdbc.util.DriverUtil.log; - /** * web-app @@ -70,17 +77,25 @@ public class UserUpdateExe { private ZcloudHiddenFacade zcloudHiddenFacade; @DubboReference private ZcloudRiskFacade zcloudRiskFacade; + private final DaHuaConfig daHuaConfig; @Transactional(rollbackFor = Exception.class) public void execute(UserUpdateCmd userUpdateCmd) { - //pc端不允许修改固定和流动状态 + log.info("【用户编辑execute入口】开始执行, id={}, name={}, phone={}", + userUpdateCmd.getId(), userUpdateCmd.getName(), userUpdateCmd.getPhone()); userUpdateCmd.setFlowFlag(null); UserE userE = new UserE(); BeanUtils.copyProperties(userUpdateCmd, userE); - // 对比用户老数据与要修改数据,查看是否涉及调岗及入职 UserDO userDO = userRepository.getInfoById(userUpdateCmd.getId()); + if (userDO == null) { + throw new BizException("用户不存在,用户id:" + userUpdateCmd.getId()); + } + log.info("【getInfoById查询结果】dahuaId={}, dahuaCode={}, name={}, userIdCard={}", + userDO.getDahuaId(), userDO.getDahuaCode(), userDO.getName(), userDO.getUserIdCard()); + syncDaHuaPersonUpdate(userUpdateCmd, userDO); + UserE oldUserE = new UserE(); BeanUtils.copyProperties(userDO, oldUserE); // boolean transferPositionFlag = userE.verifyTransferPosition(oldUserE, userE); @@ -232,6 +247,230 @@ public class UserUpdateExe { userChangeRecordGateway.add(userChangeRecordE); } + private void syncDaHuaPersonUpdate(UserUpdateCmd userUpdateCmd, UserDO userDO) { + log.info("========== 大华人员(更新)检查开始 =========="); + log.info("用户主键userDO.id={}, name={}, userIdCard={}, dahuaId={}, dahuaCode={}", + userDO.getId(), userDO.getName(), userDO.getUserIdCard(), userDO.getDahuaId(), userDO.getDahuaCode()); + Integer dahuaId = userDO.getDahuaId(); + String dahuaCode = userDO.getDahuaCode(); + if (dahuaId == null && (dahuaCode == null || dahuaCode.trim().isEmpty())) { + log.info("【跳过同步大华】该用户user表中dahuaId和dahuaCode都为空,说明从未同步过大华,本次跳过不调更新接口"); + return; + } + + 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(); + Object newFaceFile = userUpdateCmd.getFaceFile(); + log.info("【本次请求入参】name是否传={}, phone是否传={}, userIdCard是否传={}, faceFile是否传={}", + userUpdateCmd.getName() != null, userUpdateCmd.getPhone() != null, userUpdateCmd.getUserIdCard() != null, newFaceFile != null); + + boolean nameChanged = !Objects.equals(newName, userDO.getName()); + boolean idCardChanged = !Objects.equals(newIdCard, userDO.getUserIdCard()); + boolean phoneChanged = !Objects.equals(newPhone, userDO.getPhone()); + boolean faceChanged = newFaceFile != null; + log.info("【变化检测】nameChanged={}({}→{}), idCardChanged={}({}→{}), phoneChanged={}({}→{}), faceChanged={}", + nameChanged, userDO.getName(), newName, + idCardChanged, userDO.getUserIdCard(), newIdCard, + phoneChanged, userDO.getPhone(), newPhone, + faceChanged); + if (!nameChanged && !idCardChanged && !phoneChanged && !faceChanged) { + log.info("【跳过同步大华】姓名/身份证/手机号/人脸图 4项都没变化,按优化逻辑不调更新接口"); + return; + } + + String plainIdCard = newIdCard; + if (newIdCard != null && !newIdCard.trim().isEmpty()) { + try { + byte[] decodedBytes = Base64.getDecoder().decode(newIdCard.trim()); + plainIdCard = new String(decodedBytes, StandardCharsets.UTF_8); + log.info("身份证号Base64解密成功, 密文前20={}, 明文前10={}", + newIdCard.substring(0, Math.min(20, newIdCard.length())) + (newIdCard.length() > 20 ? "..." : ""), + plainIdCard.substring(0, Math.min(10, plainIdCard.length())) + (plainIdCard.length() > 10 ? "..." : "")); + } catch (Exception e) { + log.warn("身份证号非Base64格式,按原值处理: {}", newIdCard); + } + } + + Map personMap = new HashMap<>(); + if (dahuaId != null) { + personMap.put("id", dahuaId); + } + personMap.put("name", newName); + personMap.put("code", dahuaCode != null ? dahuaCode : plainIdCard); + personMap.put("paperType", 111); + personMap.put("paperNumber", plainIdCard); + personMap.put("phone", newPhone); + personMap.put("departmentId", 1); + personMap.put("nationName", "汉族"); + 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) { + return thumbUrlObj.toString(); + } + } 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) { + return thumbUrlObj.toString(); + } + } else { + try { + cn.hutool.json.JSONObject faceJson = cn.hutool.json.JSONUtil.parseObj(singleObj); + Object thumbUrlObj = faceJson.get("thumbUrl"); + if (thumbUrlObj != null) { + return thumbUrlObj.toString(); + } + } 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://"); + boolean containsComma = trimmedThumb.contains(","); + boolean startsWithDataImage = trimmedThumb.startsWith("data:image"); + + 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; + } 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) { + log.error("同步大华人员(更新)失败:人脸图片最终无data:image前缀,代码补齐逻辑失效"); + throw new BizException("同步大华平台失败:人脸图片格式异常"); + } else if (!isLikelyBase64) { + log.error("同步大华人员(更新)失败:人脸图片base64主体含非base64非法字符"); + throw new BizException("同步大华平台失败:人脸图片格式错误"); + } else if (bodyLen < 1000) { + log.error("同步大华人员(更新)失败:人脸图片base64主体长度过短({}),缩略图可能损坏", bodyLen); + throw new BizException("同步大华平台失败:人脸图片不完整"); + } + + List> personBiosignatures = new ArrayList<>(); + Map biosignature = new java.util.LinkedHashMap<>(); + biosignature.put("type", 3); + biosignature.put("index", 1); + biosignature.put("base64Data", cleanBase64); + personBiosignatures.add(biosignature); + personMap.put("personBiosignatures", personBiosignatures); + } + + Map extraHeaders = new HashMap<>(); + try { + ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attrs != null) { + HttpServletRequest req = attrs.getRequest(); + String token = req.getHeader("token"); + String orgid = req.getHeader("orgid"); + String tenantId = req.getHeader("tenantId"); + if (token != null && !token.trim().isEmpty()) { + extraHeaders.put("token", token); + } + if (orgid != null && !orgid.trim().isEmpty()) { + extraHeaders.put("orgid", orgid); + } + if (tenantId != null && !tenantId.trim().isEmpty()) { + extraHeaders.put("tenantId", tenantId); + } + } + } catch (Exception e) { + log.warn("获取当前请求头异常: {}", e.getMessage()); + } + + Map syncResult = daHuaConfig.updatePerson(personMap, extraHeaders); + Boolean success = (Boolean) syncResult.get("success"); + Boolean skipped = (Boolean) syncResult.get("skipped"); + if (skipped != null && skipped) { + return; + } + String maskedIdCard = plainIdCard; + if (plainIdCard != null && plainIdCard.length() >= 14) { + maskedIdCard = plainIdCard.substring(0, 6) + "********" + plainIdCard.substring(plainIdCard.length() - 4); + } + if (success == null || !success) { + String errCode = (String) syncResult.getOrDefault("errCode", ""); + String errMessage = (String) syncResult.getOrDefault("errMessage", "大华人员更新失败"); + log.error("同步大华人员(更新)失败, errCode={}, errMessage={}, name={}, idCard={}", errCode, errMessage, newName, maskedIdCard); + throw new BizException("同步大华平台失败:" + errMessage); + } + log.info("同步大华人员(更新)成功, name={}, idCard={}", newName, maskedIdCard); + } + @Transactional(rollbackFor = Exception.class) public void editData(UserQryCmd userUpdateCmd) { List userDOList = new ArrayList<>(); @@ -830,4 +1069,4 @@ public class UserUpdateExe { } -} +} \ 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 6872fa19..12c089f4 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 @@ -41,10 +41,15 @@ public class UserServiceImpl implements UserServiceI { } @Override - public SingleResponse add(UserAddCmd cmd) { + public SingleResponse add(UserAddCmd cmd) { userAddExe.execute(cmd); - return SingleResponse.buildSuccess(); + UserCO userCO = new UserCO(); + userCO.setId(cmd.getId()); + userCO.setUsername(cmd.getUsername()); + userCO.setName(cmd.getName()); + userCO.setPhone(cmd.getPhone()); + return SingleResponse.of(userCO); } @Override @@ -278,5 +283,4 @@ public class UserServiceImpl implements UserServiceI { public PageResponse pageByNopermissionAll(UserPageQry qry) { return userQueryExe.pageByNopermissionAll(qry); } -} - +} \ No newline at end of file diff --git a/web-client/src/main/java/com/zcloud/basic/info/dto/UserAddCmd.java b/web-client/src/main/java/com/zcloud/basic/info/dto/UserAddCmd.java index a3e8bafd..57d21363 100644 --- a/web-client/src/main/java/com/zcloud/basic/info/dto/UserAddCmd.java +++ b/web-client/src/main/java/com/zcloud/basic/info/dto/UserAddCmd.java @@ -23,6 +23,9 @@ import java.time.LocalDate; @NoArgsConstructor @AllArgsConstructor public class UserAddCmd extends Command { + @ApiModelProperty(value = "主键id(新增时不传,保存成功后返回)", name = "id") + private Long id; + @ApiModelProperty(value = "业务主键id老系统id", name = "userId") private String userId; diff --git a/web-client/src/main/java/com/zcloud/basic/info/dto/UserUpdateCmd.java b/web-client/src/main/java/com/zcloud/basic/info/dto/UserUpdateCmd.java index 3ce256f5..5ffdea03 100644 --- a/web-client/src/main/java/com/zcloud/basic/info/dto/UserUpdateCmd.java +++ b/web-client/src/main/java/com/zcloud/basic/info/dto/UserUpdateCmd.java @@ -63,6 +63,8 @@ public class UserUpdateCmd extends Command { private String userIdCard; @ApiModelProperty(value = "人脸头像url", name = "userAvatarUrl") private String userAvatarUrl; + @ApiModelProperty(value = "人脸图片文件(取thumbUrl字段作为base64传大华)") + private Object faceFile; @ApiModelProperty(value = "现住址", name = "currentAddress") private String currentAddress; @ApiModelProperty(value = "户口所在地", name = "locationAddress") @@ -126,5 +128,4 @@ public class UserUpdateCmd extends Command { private String payNumber; @ApiModelProperty(value = "是否缴纳其他人身伤害保险") private Integer isBf; -} - +} \ No newline at end of file diff --git a/web-domain/src/main/java/com/zcloud/basic/info/domain/config/DaHuaConfig.java b/web-domain/src/main/java/com/zcloud/basic/info/domain/config/DaHuaConfig.java index 2fd661e5..d5aad7a2 100644 --- a/web-domain/src/main/java/com/zcloud/basic/info/domain/config/DaHuaConfig.java +++ b/web-domain/src/main/java/com/zcloud/basic/info/domain/config/DaHuaConfig.java @@ -11,6 +11,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -67,9 +68,7 @@ public class DaHuaConfig { String gw = (primeportGateway == null || primeportGateway.trim().isEmpty()) ? "basicInfo" : primeportGateway.trim(); String url = baseUrl + "/" + gw + "/dahua/person/sync"; String reqJson = JSONUtil.toJsonStr(personMap); - log.info("调用大华人员同步接口, url={}, 请求参数={}", url, reqJson); - System.out.println("调用大华人员同步接口, url=" + url); - System.out.println("请求参数: " + reqJson); + log.info("调用大华人员同步接口, url={}", url); try { HttpRequest request = HttpRequest.post(url) .header("Content-Type", "application/json;charset=UTF-8") @@ -79,14 +78,12 @@ public class DaHuaConfig { extraHeaders.forEach((k, v) -> { if (k != null && v != null) { request.header(k, v); - System.out.println("透传请求头: " + k + "=" + v); } }); log.info("透传请求头: {}", extraHeaders); } String respBody = request.execute().body(); log.info("大华人员同步接口返回: {}", respBody); - System.out.println("大华人员同步接口返回: " + respBody); if (respBody == null || respBody.trim().isEmpty()) { resultMap.put("success", false); resultMap.put("errCode", "EMPTY_RESPONSE"); @@ -111,4 +108,84 @@ public class DaHuaConfig { return resultMap; } } + + public Map updatePerson(Map personMap) { + return updatePerson(personMap, null); + } + + public Map updatePerson(Map personMap, Map extraHeaders) { + Map resultMap = new HashMap<>(); + log.info("========== 大华人员(更新)调用开始 =========="); + log.info("dockFlag配置值={}, primeportUrl={}, primeportGateway={}", dockFlag, primeportUrl, primeportGateway); + if (dockFlag == null || dockFlag != 1) { + log.info("大华对接未开启(dockFlag={}), 跳过更新大华人员", dockFlag); + resultMap.put("success", true); + resultMap.put("skipped", true); + return resultMap; + } + String baseUrl = primeportUrl; + if (baseUrl == null || baseUrl.trim().isEmpty()) { + throw new RuntimeException("大华对接网关地址未配置(dahua.config.primeportUrl)"); + } + baseUrl = baseUrl.trim(); + if (baseUrl.endsWith("/")) { + baseUrl = baseUrl.substring(0, baseUrl.length() - 1); + } + String gw = (primeportGateway == null || primeportGateway.trim().isEmpty()) ? "basicInfo" : primeportGateway.trim(); + + String url = baseUrl + "/" + gw + "/dahua/person/update"; + log.info("【实际请求URL】method=PUT, url={}", url); + log.info("【对比-新增sync的URL】method=POST, {}/{}/dahua/person/sync", baseUrl, gw); + + String reqJson = JSONUtil.toJsonStr(personMap); + log.info("【传给大华的请求体(JSON)】{}", reqJson); + log.info("【personMap关键字段】id={}, code={}, name={}, paperNumber={}, phone={}, departmentId={}, paperType={}, service={}, personBiosignatures是否有值={}", + personMap.get("id"), personMap.get("code"), personMap.get("name"), + personMap.get("paperNumber"), personMap.get("phone"), + personMap.get("departmentId"), personMap.get("paperType"), + personMap.get("service"), + personMap.get("personBiosignatures") == null ? "null" : + (personMap.get("personBiosignatures") instanceof List + ? "List-size=" + ((List) personMap.get("personBiosignatures")).size() + : personMap.get("personBiosignatures").getClass().getName())); + try { + HttpRequest request = HttpRequest.put(url) + .header("Content-Type", "application/json;charset=UTF-8") + .timeout(30000) + .body(reqJson); + if (extraHeaders != null && !extraHeaders.isEmpty()) { + extraHeaders.forEach((k, v) -> { + if (k != null && v != null) { + request.header(k, v); + } + }); + log.info("【透传请求头】{}", extraHeaders); + } + String respBody = request.execute().body(); + log.info("【大华接口原始返回】{}", respBody); + if (respBody == null || respBody.trim().isEmpty()) { + resultMap.put("success", false); + resultMap.put("errCode", "EMPTY_RESPONSE"); + resultMap.put("errMessage", "大华人员更新接口返回为空"); + return resultMap; + } + JSONObject respJson = JSONUtil.parseObj(respBody); + Boolean success = respJson.getBool("success", false); + String errCode = respJson.getStr("errCode", ""); + String errMessage = respJson.getStr("errMessage", ""); + Object data = respJson.get("data"); + resultMap.put("success", success); + resultMap.put("errCode", errCode); + resultMap.put("errMessage", errMessage); + resultMap.put("data", data); + log.info("【解析后返回】success={}, errCode={}, errMessage={}, data={}", success, errCode, errMessage, data); + return resultMap; + } catch (Exception e) { + log.error("调用大华人员更新接口异常: {}", e.getMessage(), e); + resultMap.put("success", false); + resultMap.put("errCode", "HTTP_EXCEPTION"); + resultMap.put("errMessage", "调用大华人员更新接口异常: " + e.getMessage()); + return resultMap; + } + } } \ No newline at end of file diff --git a/web-infrastructure/src/main/java/com/zcloud/basic/info/gatewayimpl/UserGatewayImpl.java b/web-infrastructure/src/main/java/com/zcloud/basic/info/gatewayimpl/UserGatewayImpl.java index 63f96413..1ecfecd4 100644 --- a/web-infrastructure/src/main/java/com/zcloud/basic/info/gatewayimpl/UserGatewayImpl.java +++ b/web-infrastructure/src/main/java/com/zcloud/basic/info/gatewayimpl/UserGatewayImpl.java @@ -39,6 +39,7 @@ public class UserGatewayImpl implements UserGateway { private UserFacade userFacade; @Override + @Transactional(rollbackFor = Exception.class, propagation = org.springframework.transaction.annotation.Propagation.REQUIRES_NEW) public Boolean add(UserE userE) { UserDO d = new UserDO(); BeanUtils.copyProperties(userE, d); @@ -48,6 +49,7 @@ public class UserGatewayImpl implements UserGateway { } @Override + @Transactional(rollbackFor = Exception.class, propagation = org.springframework.transaction.annotation.Propagation.REQUIRES_NEW) public Boolean addYanshi(UserE userE) { UserDO d = new UserDO(); BeanUtils.copyProperties(userE, d); @@ -66,6 +68,7 @@ public class UserGatewayImpl implements UserGateway { } @Override + @Transactional(rollbackFor = Exception.class, propagation = org.springframework.transaction.annotation.Propagation.REQUIRES_NEW) public boolean addXgf(UserE userE) { UserDO d = new UserDO(); // 新增用户默认在职 @@ -263,5 +266,4 @@ public class UserGatewayImpl implements UserGateway { userRepository.deletedUserByPhone(phone); } -} - +} \ No newline at end of file diff --git a/web-infrastructure/src/main/resources/mapper/UserMapper.xml b/web-infrastructure/src/main/resources/mapper/UserMapper.xml index 9456a6c2..198223bb 100644 --- a/web-infrastructure/src/main/resources/mapper/UserMapper.xml +++ b/web-infrastructure/src/main/resources/mapper/UserMapper.xml @@ -340,6 +340,8 @@ u.is_injuries_pay_time, u.pay_number, u.is_bf, + u.dahua_id, + u.dahua_code, CASE WHEN LENGTH(FROM_BASE64(u.user_id_card)) > 0 @@ -681,5 +683,4 @@ left join post p on p.id = u.post_id ${ew.customSqlSegment} - - + \ No newline at end of file