大华口门对接

komen
zhangxiongfeng 2026-07-28 15:29:07 +08:00
parent 65ad2b538e
commit 8beed9d73d
8 changed files with 380 additions and 135 deletions

View File

@ -100,31 +100,22 @@ public class UserAddExe {
// if (!b) { // if (!b) {
// throw new BizException("请先完善企业信息"); // 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 { try {
ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attrs != null) { if (attrs != null) {
HttpServletRequest req = attrs.getRequest(); HttpServletRequest req = attrs.getRequest();
Enumeration<String> headerNames = req.getHeaderNames();
Map<String, String> 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"); String orgidHeader = req.getHeader("orgid");
log.info("========== 从请求头获取 orgid: {}", orgidHeader);
if (orgidHeader != null && cmd.getCorpinfoId() == null) { if (orgidHeader != null && cmd.getCorpinfoId() == null) {
try { try {
cmd.setCorpinfoId(Long.parseLong(orgidHeader.trim())); cmd.setCorpinfoId(Long.parseLong(orgidHeader.trim()));
log.info("========== 用请求头 orgid 设置 cmd.corpinfoId: {}", cmd.getCorpinfoId());
} catch (Exception e) { } catch (Exception e) {
log.warn("========== orgid 转 Long 失败: {}", orgidHeader); log.warn("orgid 转 Long 失败: {}", orgidHeader);
} }
} }
} }
} catch (Exception e) { } catch (Exception e) {
log.warn("========== 获取请求头异常: {}", e.getMessage()); log.warn("获取请求头异常: {}", e.getMessage());
} }
Long tenantId = null; Long tenantId = null;
SSOUser ssoUser = AuthContext.getCurrentUser(); SSOUser ssoUser = AuthContext.getCurrentUser();
@ -185,34 +176,39 @@ public class UserAddExe {
userE.setRoleId(roleId); userE.setRoleId(roleId);
} }
try { try {
log.info("========== 企业类型 type: {}", corpInfoDO.getType());
userE.setUserType(CorpTypeEnum.getUserTypeByCode(corpInfoDO.getType())); userE.setUserType(CorpTypeEnum.getUserTypeByCode(corpInfoDO.getType()));
userE.resetPassword(corpInfoDO.getType()); userE.resetPassword(corpInfoDO.getType());
//先同步大华人脸平台,成功后再保存本地 //先同步大华人脸平台,成功后再保存本地
log.info("========== 开始调用大华同步接口");
syncDaHuaPerson(userE); syncDaHuaPerson(userE);
log.info("========== 大华同步成功,开始保存本地用户");
res = userGateway.add(userE); res = userGateway.add(userE);
log.info("========== 本地保存结果: {}", res);
if (corpInfoDO != null && !ObjectUtils.isEmpty(corpInfoDO.getCorpName())) { if (corpInfoDO != null && !ObjectUtils.isEmpty(corpInfoDO.getCorpName())) {
corpName = corpInfoDO.getCorpName(); corpName = corpInfoDO.getCorpName();
} }
userEmploymentLogE.initAdd(userEmploymentLogE, corpName, userE.getId()); userEmploymentLogE.initAdd(userEmploymentLogE, corpName, userE.getId());
try {
userEmploymentLogGateway.add(userEmploymentLogE); userEmploymentLogGateway.add(userEmploymentLogE);
} catch (Exception e) {
log.error("用户就业日志保存失败(不影响用户主体), userId: {}, error: {}", userE.getId(), e.getMessage(), e);
}
try {
addUserChangeRecordForSave(userE, corpInfoDO); addUserChangeRecordForSave(userE, corpInfoDO);
log.info("========== 用户就业日志保存成功"); } catch (Exception e) {
log.error("用户变更记录保存失败(不影响用户主体), userId: {}, error: {}", userE.getId(), e.getMessage(), e);
}
} catch (BizException e) { } catch (BizException e) {
log.error("========== 业务异常: {}", e.getMessage(), e); log.error("新增用户业务异常: {}", e.getMessage(), e);
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
log.error("========== 系统异常: {}", e.getMessage(), e); log.error("新增用户系统异常: {}", e.getMessage(), e);
throw new RuntimeException(e); throw new RuntimeException(e);
} }
if (!res) { if (!res) {
throw new BizException("保存失败"); throw new BizException("保存失败");
} }
log.info("========== 新增用户完成"); cmd.setId(userE.getId());
cmd.setUsername(userE.getUsername());
log.info("新增用户完成, userId: {}, username: {}", userE.getId(), userE.getUsername());
return true; return true;
} }
@ -241,67 +237,34 @@ public class UserAddExe {
personMap.put("service", "evo-thirdParty"); personMap.put("service", "evo-thirdParty");
Object faceFile = userE.getFaceFile(); 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<Object, String> extractThumbFromSingle = (singleObj) -> { java.util.function.Function<Object, String> extractThumbFromSingle = (singleObj) -> {
if (singleObj == null) { if (singleObj == null) {
log.debug("单个图片对象为null");
return null; return null;
} }
log.info("从单个对象提取thumbUrl对象类型: {}", singleObj.getClass().getName());
if (singleObj instanceof Map) { if (singleObj instanceof Map) {
Map<String, Object> faceFileMap = (Map<String, Object>) singleObj; Map<String, Object> faceFileMap = (Map<String, Object>) singleObj;
log.info("单个对象是Map类型所有key: {}", faceFileMap.keySet());
for (Map.Entry<String, Object> 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"); Object thumbUrlObj = faceFileMap.get("thumbUrl");
if (thumbUrlObj != null) { if (thumbUrlObj != null) {
String res = thumbUrlObj.toString(); return thumbUrlObj.toString();
log.info("从Map提取到thumbUrl长度={}", res.length());
return res;
} }
} else if (singleObj instanceof cn.hutool.json.JSONObject) { } else if (singleObj instanceof cn.hutool.json.JSONObject) {
cn.hutool.json.JSONObject faceJson = (cn.hutool.json.JSONObject) singleObj; 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"); Object thumbUrlObj = faceJson.get("thumbUrl");
if (thumbUrlObj != null) { if (thumbUrlObj != null) {
String res = thumbUrlObj.toString(); return thumbUrlObj.toString();
log.info("从JSONObject提取到thumbUrl长度={}", res.length());
return res;
} }
} else { } else {
try { try {
cn.hutool.json.JSONObject faceJson = cn.hutool.json.JSONUtil.parseObj(singleObj); 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"); Object thumbUrlObj = faceJson.get("thumbUrl");
if (thumbUrlObj != null) { if (thumbUrlObj != null) {
String res = thumbUrlObj.toString(); return thumbUrlObj.toString();
log.info("从解析后的JSONObject提取到thumbUrl长度={}", res.length());
return res;
} }
} catch (Exception e) { } catch (Exception e) {
log.warn("解析单个图片对象为JSON失败: {}, 错误: {}", singleObj, e.getMessage()); log.warn("解析单个图片对象为JSON失败: {}", e.getMessage());
} }
} }
log.warn("单个对象中未找到thumbUrl字段");
return null; return null;
}; };
@ -312,56 +275,39 @@ public class UserAddExe {
if (faceFile instanceof java.util.Collection) { if (faceFile instanceof java.util.Collection) {
isArray = true; isArray = true;
java.util.Collection<?> coll = (java.util.Collection<?>) faceFile; java.util.Collection<?> coll = (java.util.Collection<?>) faceFile;
log.info("faceFile是Collection/List类型size={}", coll.size());
if (!coll.isEmpty()) { if (!coll.isEmpty()) {
firstElement = coll.iterator().next(); firstElement = coll.iterator().next();
log.info("取Collection第一个元素类型: {}", firstElement == null ? "null" : firstElement.getClass().getName());
} else {
log.warn("faceFile是空数组无元素可取");
} }
} else if (faceFile.getClass().isArray()) { } else if (faceFile.getClass().isArray()) {
isArray = true; isArray = true;
Object[] arr = (Object[]) faceFile; Object[] arr = (Object[]) faceFile;
log.info("faceFile是原生数组类型length={}", arr.length);
if (arr.length > 0) { if (arr.length > 0) {
firstElement = arr[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) { } else if (faceFile instanceof cn.hutool.json.JSONArray) {
isArray = true; isArray = true;
cn.hutool.json.JSONArray jsonArr = (cn.hutool.json.JSONArray) faceFile; cn.hutool.json.JSONArray jsonArr = (cn.hutool.json.JSONArray) faceFile;
log.info("faceFile是JSONArray类型size={}", jsonArr.size());
if (!jsonArr.isEmpty()) { if (!jsonArr.isEmpty()) {
firstElement = jsonArr.get(0); firstElement = jsonArr.get(0);
log.info("取JSONArray第0个元素类型: {}", firstElement == null ? "null" : firstElement.getClass().getName());
} else {
log.warn("faceFile是空JSONArray无元素可取");
} }
} }
if (isArray) { if (isArray) {
log.info("faceFile是数组/集合类型从第一个元素中提取thumbUrl");
if (firstElement != null) { if (firstElement != null) {
thumbUrl = extractThumbFromSingle.apply(firstElement); thumbUrl = extractThumbFromSingle.apply(firstElement);
} else {
log.warn("数组第一个元素是null无法提取thumbUrl");
} }
} else { } else {
log.info("faceFile是单个对象类型直接提取thumbUrl");
thumbUrl = extractThumbFromSingle.apply(faceFile); 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()) { if (thumbUrl != null && !thumbUrl.trim().isEmpty()) {
String trimmedThumb = thumbUrl.trim(); String trimmedThumb = thumbUrl.trim();
boolean startsWithDataImage = trimmedThumb.startsWith("data:image");
boolean startsWithHttp = trimmedThumb.startsWith("http://") || trimmedThumb.startsWith("https://"); boolean startsWithHttp = trimmedThumb.startsWith("http://") || trimmedThumb.startsWith("https://");
boolean containsComma = trimmedThumb.contains(","); boolean containsComma = trimmedThumb.contains(",");
boolean startsWithDataImage = trimmedThumb.startsWith("data:image");
String cleanBase64; String cleanBase64;
if (containsComma && startsWithDataImage) { if (containsComma && startsWithDataImage) {
@ -369,46 +315,25 @@ public class UserAddExe {
String bodyPart = trimmedThumb.substring(trimmedThumb.indexOf(",") + 1); String bodyPart = trimmedThumb.substring(trimmedThumb.indexOf(",") + 1);
String cleanBody = bodyPart.replaceAll("\\s+", ""); String cleanBody = bodyPart.replaceAll("\\s+", "");
cleanBase64 = prefixPart + cleanBody; cleanBase64 = prefixPart + cleanBody;
log.info("已保留data:image前缀(严格对齐大华文档示例), 前缀={}, 仅清洗主体中的空白字符", prefixPart.length() > 80 ? prefixPart.substring(0, 80) + "..." : prefixPart);
} else { } else {
String pureBody = trimmedThumb.replaceAll("\\s+", ""); String pureBody = trimmedThumb.replaceAll("\\s+", "");
cleanBase64 = "data:image/jpeg;base64," + pureBody; cleanBase64 = "data:image/jpeg;base64," + pureBody;
log.info("原始thumbUrl无标准data:image前缀已自动补齐(data:image/jpeg;base64,)以对齐大华文档示例");
} }
int commaIdx = cleanBase64.indexOf(","); int commaIdx = cleanBase64.indexOf(",");
String base64BodyOnly = commaIdx >= 0 ? cleanBase64.substring(commaIdx + 1) : cleanBase64; String base64BodyOnly = commaIdx >= 0 ? cleanBase64.substring(commaIdx + 1) : cleanBase64;
boolean isLikelyBase64 = base64BodyOnly.matches("^[A-Za-z0-9+/=]+$"); boolean isLikelyBase64 = base64BodyOnly.matches("^[A-Za-z0-9+/=]+$");
int totalLen = cleanBase64.length();
int bodyLen = base64BodyOnly.length(); int bodyLen = base64BodyOnly.length();
boolean finalHasPrefix = cleanBase64.startsWith("data:image"); 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) { if (startsWithHttp) {
log.error("===== 严重错误传的是HTTP/HTTPS URL(或文件路径)大华文档personBiosignatures要求必须传【带data:image前缀的base64图片】不是URL ====="); log.error("同步大华人员失败人脸图片传的是HTTP/HTTPS URL要求必须传带data:image前缀的base64图片");
System.out.println("===== 大华图片错误HTTP URL无效, 前200: " + (trimmedThumb.length() > 200 ? trimmedThumb.substring(0, 200) : trimmedThumb));
} else if (!finalHasPrefix) { } else if (!finalHasPrefix) {
log.error("===== 最终结果竟然没有data:image前缀(代码补齐逻辑失效) ====="); log.error("同步大华人员失败人脸图片最终无data:image前缀代码补齐逻辑失效");
System.out.println("===== 大华图片错误无data:image前缀, 前100: " + (cleanBase64.length() > 100 ? cleanBase64.substring(0, 100) : cleanBase64));
} else if (!isLikelyBase64) { } else if (!isLikelyBase64) {
log.error("===== base64主体部分含非base64非法字符前200: {}", base64BodyOnly.length() > 200 ? base64BodyOnly.substring(0, 200) : base64BodyOnly); log.error("同步大华人员失败人脸图片base64主体含非base64非法字符");
System.out.println("===== 大华图片错误主体非base64字符, 前200: " + (base64BodyOnly.length() > 200 ? base64BodyOnly.substring(0, 200) : base64BodyOnly));
} else if (bodyLen < 1000) { } else if (bodyLen < 1000) {
log.error("===== base64主体长度过短({}),缩略图太小/损坏正常300KB JPEG→主体约40万字符 =====", bodyLen); log.error("同步大华人员失败人脸图片base64主体长度过短({}),缩略图可能损坏", bodyLen);
System.out.println("===== 大华图片错误:主体过短=" + bodyLen);
} else {
log.info("===== ✅ 完全符合大华文档personBiosignatures格式带data:image前缀 + 纯base64主体 + 长度正常 =====");
} }
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<Map<String, Object>> personBiosignatures = new ArrayList<>(); List<Map<String, Object>> personBiosignatures = new ArrayList<>();
Map<String, Object> biosignature = new java.util.LinkedHashMap<>(); Map<String, Object> biosignature = new java.util.LinkedHashMap<>();
@ -417,15 +342,9 @@ public class UserAddExe {
biosignature.put("base64Data", cleanBase64); biosignature.put("base64Data", cleanBase64);
personBiosignatures.add(biosignature); personBiosignatures.add(biosignature);
personMap.put("personBiosignatures", personBiosignatures); 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 { } else {
log.warn("未获取到人脸图片thumbUrlpersonBiosignatures将不传递"); log.warn("未获取到人脸图片thumbUrlpersonBiosignatures将不传递");
System.out.println("===== 警告未从faceFile中获取到有效的thumbUrlpersonBiosignatures不会传给大华");
} }
log.info("========== 大华图片调试结束 ==========");
Integer age = null; Integer age = null;
Integer sex = null; Integer sex = null;
@ -490,7 +409,6 @@ public class UserAddExe {
if (tenantId != null && !tenantId.trim().isEmpty()) { if (tenantId != null && !tenantId.trim().isEmpty()) {
extraHeaders.put("tenantId", tenantId); extraHeaders.put("tenantId", tenantId);
} }
log.info("收集到需要透传的请求头: {}", extraHeaders);
} }
} catch (Exception e) { } catch (Exception e) {
log.warn("获取当前请求头异常: {}", e.getMessage()); log.warn("获取当前请求头异常: {}", e.getMessage());
@ -501,14 +419,15 @@ public class UserAddExe {
if (skipped != null && skipped) { if (skipped != null && skipped) {
return; return;
} }
String maskedIdCard = plainIdCard;
if (plainIdCard != null && plainIdCard.length() >= 14) {
maskedIdCard = plainIdCard.substring(0, 6) + "********" + plainIdCard.substring(plainIdCard.length() - 4);
}
if (success == null || !success) { if (success == null || !success) {
String errCode = (String) syncResult.getOrDefault("errCode", ""); String errCode = (String) syncResult.getOrDefault("errCode", "");
String errMessage = (String) syncResult.getOrDefault("errMessage", "大华人员同步失败"); String errMessage = (String) syncResult.getOrDefault("errMessage", "大华人员同步失败");
log.error("同步大华人员失败, errCode={}, errMessage={}, name={}, idCard={}", errCode, errMessage, userE.getName(), plainIdCard);
throw new BizException("同步大华平台失败:" + errMessage); throw new BizException("同步大华平台失败:" + errMessage);
} }
log.info("同步大华人员成功, name={}, idCard={}", userE.getName(), plainIdCard);
System.out.println("同步大华人员成功, name=" + userE.getName() + ", idCard=" + plainIdCard);
Object data = syncResult.get("data"); Object data = syncResult.get("data");
if (data != null) { if (data != null) {
if (data instanceof Map) { if (data instanceof Map) {
@ -538,7 +457,6 @@ public class UserAddExe {
} }
} }
userE.setDahuaCode(plainIdCard); userE.setDahuaCode(plainIdCard);
log.info("设置大华dahuaCode成功, dahuaCode={}", plainIdCard);
} }
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@ -898,7 +816,7 @@ public class UserAddExe {
sendParams.put("code", phoneCode); sendParams.put("code", phoneCode);
messageSendCmd.setParams(sendParams); messageSendCmd.setParams(sendParams);
SingleResponse<Boolean> d = messageFacade.send(messageSendCmd); SingleResponse<Boolean> d = messageFacade.send(messageSendCmd);
System.out.println( d.toString()); log.info("短信发送结果: {}, phone: {}", d, phone);
return d == null ? false : d.getData(); return d == null ? false : d.getData();
} }

View File

@ -14,6 +14,7 @@ import com.jjb.saas.system.client.user.request.UserUpdateQuitCmd;
import com.jjb.saas.system.client.user.response.UserDetailCO; import com.jjb.saas.system.client.user.response.UserDetailCO;
import com.sun.xml.bind.v2.TODO; import com.sun.xml.bind.v2.TODO;
import com.zcloud.basic.info.constant.RedisConstant; 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.enums.*;
import com.zcloud.basic.info.domain.gateway.*; import com.zcloud.basic.info.domain.gateway.*;
import com.zcloud.basic.info.domain.model.*; import com.zcloud.basic.info.domain.model.*;
@ -34,13 +35,19 @@ import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils; 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.LocalDateTime;
import java.time.Period;
import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
import java.util.Base64;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static dm.jdbc.util.DriverUtil.log;
/** /**
* web-app * web-app
@ -70,17 +77,25 @@ public class UserUpdateExe {
private ZcloudHiddenFacade zcloudHiddenFacade; private ZcloudHiddenFacade zcloudHiddenFacade;
@DubboReference @DubboReference
private ZcloudRiskFacade zcloudRiskFacade; private ZcloudRiskFacade zcloudRiskFacade;
private final DaHuaConfig daHuaConfig;
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void execute(UserUpdateCmd userUpdateCmd) { public void execute(UserUpdateCmd userUpdateCmd) {
//pc端不允许修改固定和流动状态 log.info("【用户编辑execute入口】开始执行, id={}, name={}, phone={}",
userUpdateCmd.getId(), userUpdateCmd.getName(), userUpdateCmd.getPhone());
userUpdateCmd.setFlowFlag(null); userUpdateCmd.setFlowFlag(null);
UserE userE = new UserE(); UserE userE = new UserE();
BeanUtils.copyProperties(userUpdateCmd, userE); BeanUtils.copyProperties(userUpdateCmd, userE);
// 对比用户老数据与要修改数据,查看是否涉及调岗及入职
UserDO userDO = userRepository.getInfoById(userUpdateCmd.getId()); 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(); UserE oldUserE = new UserE();
BeanUtils.copyProperties(userDO, oldUserE); BeanUtils.copyProperties(userDO, oldUserE);
// boolean transferPositionFlag = userE.verifyTransferPosition(oldUserE, userE); // boolean transferPositionFlag = userE.verifyTransferPosition(oldUserE, userE);
@ -232,6 +247,230 @@ public class UserUpdateExe {
userChangeRecordGateway.add(userChangeRecordE); 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<String, Object> 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<Object, String> extractThumbFromSingle = (singleObj) -> {
if (singleObj == null) {
return null;
}
if (singleObj instanceof Map) {
Map<String, Object> faceFileMap = (Map<String, Object>) 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<Map<String, Object>> personBiosignatures = new ArrayList<>();
Map<String, Object> 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<String, String> 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<String, Object> 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) @Transactional(rollbackFor = Exception.class)
public void editData(UserQryCmd userUpdateCmd) { public void editData(UserQryCmd userUpdateCmd) {
List<UserDO> userDOList = new ArrayList<>(); List<UserDO> userDOList = new ArrayList<>();

View File

@ -41,10 +41,15 @@ public class UserServiceImpl implements UserServiceI {
} }
@Override @Override
public SingleResponse add(UserAddCmd cmd) { public SingleResponse<UserCO> add(UserAddCmd cmd) {
userAddExe.execute(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 @Override
@ -279,4 +284,3 @@ public class UserServiceImpl implements UserServiceI {
return userQueryExe.pageByNopermissionAll(qry); return userQueryExe.pageByNopermissionAll(qry);
} }
} }

View File

@ -23,6 +23,9 @@ import java.time.LocalDate;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class UserAddCmd extends Command { public class UserAddCmd extends Command {
@ApiModelProperty(value = "主键id新增时不传保存成功后返回", name = "id")
private Long id;
@ApiModelProperty(value = "业务主键id老系统id", name = "userId") @ApiModelProperty(value = "业务主键id老系统id", name = "userId")
private String userId; private String userId;

View File

@ -63,6 +63,8 @@ public class UserUpdateCmd extends Command {
private String userIdCard; private String userIdCard;
@ApiModelProperty(value = "人脸头像url", name = "userAvatarUrl") @ApiModelProperty(value = "人脸头像url", name = "userAvatarUrl")
private String userAvatarUrl; private String userAvatarUrl;
@ApiModelProperty(value = "人脸图片文件(取thumbUrl字段作为base64传大华)")
private Object faceFile;
@ApiModelProperty(value = "现住址", name = "currentAddress") @ApiModelProperty(value = "现住址", name = "currentAddress")
private String currentAddress; private String currentAddress;
@ApiModelProperty(value = "户口所在地", name = "locationAddress") @ApiModelProperty(value = "户口所在地", name = "locationAddress")
@ -127,4 +129,3 @@ public class UserUpdateCmd extends Command {
@ApiModelProperty(value = "是否缴纳其他人身伤害保险") @ApiModelProperty(value = "是否缴纳其他人身伤害保险")
private Integer isBf; private Integer isBf;
} }

View File

@ -11,6 +11,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
@ -67,9 +68,7 @@ public class DaHuaConfig {
String gw = (primeportGateway == null || primeportGateway.trim().isEmpty()) ? "basicInfo" : primeportGateway.trim(); String gw = (primeportGateway == null || primeportGateway.trim().isEmpty()) ? "basicInfo" : primeportGateway.trim();
String url = baseUrl + "/" + gw + "/dahua/person/sync"; String url = baseUrl + "/" + gw + "/dahua/person/sync";
String reqJson = JSONUtil.toJsonStr(personMap); String reqJson = JSONUtil.toJsonStr(personMap);
log.info("调用大华人员同步接口, url={}, 请求参数={}", url, reqJson); log.info("调用大华人员同步接口, url={}", url);
System.out.println("调用大华人员同步接口, url=" + url);
System.out.println("请求参数: " + reqJson);
try { try {
HttpRequest request = HttpRequest.post(url) HttpRequest request = HttpRequest.post(url)
.header("Content-Type", "application/json;charset=UTF-8") .header("Content-Type", "application/json;charset=UTF-8")
@ -79,14 +78,12 @@ public class DaHuaConfig {
extraHeaders.forEach((k, v) -> { extraHeaders.forEach((k, v) -> {
if (k != null && v != null) { if (k != null && v != null) {
request.header(k, v); request.header(k, v);
System.out.println("透传请求头: " + k + "=" + v);
} }
}); });
log.info("透传请求头: {}", extraHeaders); log.info("透传请求头: {}", extraHeaders);
} }
String respBody = request.execute().body(); String respBody = request.execute().body();
log.info("大华人员同步接口返回: {}", respBody); log.info("大华人员同步接口返回: {}", respBody);
System.out.println("大华人员同步接口返回: " + respBody);
if (respBody == null || respBody.trim().isEmpty()) { if (respBody == null || respBody.trim().isEmpty()) {
resultMap.put("success", false); resultMap.put("success", false);
resultMap.put("errCode", "EMPTY_RESPONSE"); resultMap.put("errCode", "EMPTY_RESPONSE");
@ -111,4 +108,84 @@ public class DaHuaConfig {
return resultMap; return resultMap;
} }
} }
public Map<String, Object> updatePerson(Map<String, Object> personMap) {
return updatePerson(personMap, null);
}
public Map<String, Object> updatePerson(Map<String, Object> personMap, Map<String, String> extraHeaders) {
Map<String, Object> 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;
}
}
} }

View File

@ -39,6 +39,7 @@ public class UserGatewayImpl implements UserGateway {
private UserFacade userFacade; private UserFacade userFacade;
@Override @Override
@Transactional(rollbackFor = Exception.class, propagation = org.springframework.transaction.annotation.Propagation.REQUIRES_NEW)
public Boolean add(UserE userE) { public Boolean add(UserE userE) {
UserDO d = new UserDO(); UserDO d = new UserDO();
BeanUtils.copyProperties(userE, d); BeanUtils.copyProperties(userE, d);
@ -48,6 +49,7 @@ public class UserGatewayImpl implements UserGateway {
} }
@Override @Override
@Transactional(rollbackFor = Exception.class, propagation = org.springframework.transaction.annotation.Propagation.REQUIRES_NEW)
public Boolean addYanshi(UserE userE) { public Boolean addYanshi(UserE userE) {
UserDO d = new UserDO(); UserDO d = new UserDO();
BeanUtils.copyProperties(userE, d); BeanUtils.copyProperties(userE, d);
@ -66,6 +68,7 @@ public class UserGatewayImpl implements UserGateway {
} }
@Override @Override
@Transactional(rollbackFor = Exception.class, propagation = org.springframework.transaction.annotation.Propagation.REQUIRES_NEW)
public boolean addXgf(UserE userE) { public boolean addXgf(UserE userE) {
UserDO d = new UserDO(); UserDO d = new UserDO();
// 新增用户默认在职 // 新增用户默认在职
@ -264,4 +267,3 @@ public class UserGatewayImpl implements UserGateway {
userRepository.deletedUserByPhone(phone); userRepository.deletedUserByPhone(phone);
} }
} }

View File

@ -340,6 +340,8 @@
u.is_injuries_pay_time, u.is_injuries_pay_time,
u.pay_number, u.pay_number,
u.is_bf, u.is_bf,
u.dahua_id,
u.dahua_code,
CASE CASE
WHEN LENGTH(FROM_BASE64(u.user_id_card)) > 0 WHEN LENGTH(FROM_BASE64(u.user_id_card)) > 0
@ -682,4 +684,3 @@
${ew.customSqlSegment} ${ew.customSqlSegment}
</select> </select>
</mapper> </mapper>