单点日志
This commit is contained in:
1 parent
17015621e1
commit
e260c015ab
6 files changed
+169
-78
No files matched your search
+2
@@ -15,6 +15,8 @@ public enum UserTypeEnum {
|
||||
ZZD_OPEN_GETTOKEN("10", "浙政钉开放平台-gettoken"),
|
||||
ZZD_OPEN_APP_USER("11", "浙政钉开放平台-dingtalk_app_user"),
|
||||
ZZD_SSO("12", "浙政钉单点-/zzdsso"),
|
||||
SH_OPEN_USER("20", "诗画浦江-openapi用户详情"),
|
||||
SH_SSO("21", "诗画单点-/shjnsso"),
|
||||
;
|
||||
private final String type;
|
||||
private final String description;
|
||||
|
||||
+60
-45
@@ -183,9 +183,9 @@ public class ThirdPartyOAth2Cotroller {
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录 /zzdsso 业务结果(成功发 token / 需短信 / 业务失败),与开放平台 SDK 调用日志分开查。
|
||||
* 记录单点业务结果(浙政钉 / 诗画),与开放平台/诗画 openapi 调用日志分开查。
|
||||
*/
|
||||
private void recordZzdSsoControllerLog(String requestUrl, String reqParam, R result, long startMs, String stage) {
|
||||
private void recordSsoControllerLog(String apiNamePrefix, String requestUrl, String reqParam, R result, long startMs, String stage) {
|
||||
boolean ok = result != null && result.getCode() == 0;
|
||||
HashMap<String, Object> body = new HashMap<>();
|
||||
body.put("stage", stage);
|
||||
@@ -196,7 +196,7 @@ public class ThirdPartyOAth2Cotroller {
|
||||
body.put("data", result.getData());
|
||||
}
|
||||
thirdPartyOAuth2Service.recordSsoLog(
|
||||
UserTypeEnum.ZZD_SSO.getDescription() + (ok ? "-成功" : "-失败"),
|
||||
apiNamePrefix + (ok ? "-成功" : "-失败"),
|
||||
requestUrl,
|
||||
reqParam,
|
||||
JSON.toJSONString(body),
|
||||
@@ -205,6 +205,10 @@ public class ThirdPartyOAth2Cotroller {
|
||||
ok ? null : (result != null ? result.getMsg() : "unknown"));
|
||||
}
|
||||
|
||||
private void recordZzdSsoControllerLog(String requestUrl, String reqParam, R result, long startMs, String stage) {
|
||||
recordSsoControllerLog(UserTypeEnum.ZZD_SSO.getDescription(), requestUrl, reqParam, result, startMs, stage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 浙政钉短信核验:校验验证码、写入绑定表、与本系统用户手机号+员工编码比对后返回 token。
|
||||
*/
|
||||
@@ -320,50 +324,61 @@ public class ThirdPartyOAth2Cotroller {
|
||||
@Transactional
|
||||
@GlobalTransactional
|
||||
public R shjnsso(@RequestBody @Valid SHJNSSODTO shjnsso, HttpServletRequest httpServletRequest) {
|
||||
//检查设备指纹
|
||||
ZZDUserDTO zzdUserDTO = thirdPartyOAuth2Service.shjnsso(shjnsso.getAccountId(), shjnsso.getSessionId());
|
||||
if (null == zzdUserDTO) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
ZZDUserDTO.Ext ext = zzdUserDTO.getExt();
|
||||
String mobile = zzdUserDTO.getMobile();
|
||||
R<List<SysUser>> userByMobileOrEmployeeCode = remoteUserService.getUserByMobileOrEmployeeCode(null, mobile);
|
||||
List<SysUser> sysUserList = userByMobileOrEmployeeCode.getData();
|
||||
if (!CollectionUtils.isEmpty(sysUserList)) {
|
||||
SysUser sysUser = sysUserList.get(0);
|
||||
//判断当前用户设备是否信任
|
||||
String md5 = DeviceFingerprintUtil.generate(httpServletRequest);
|
||||
R<UserTrustedDeviceEntity> userTrustedDeviceR = remoteUserTrustedDeviceService.getUserTrustedDeviceByIdAndMD5(sysUser.getPhone(), md5, SH_DEVICE_NAME);
|
||||
UserTrustedDeviceEntity userTrustedDevice = userTrustedDeviceR.getData();
|
||||
if (userTrustedDevice == null || userTrustedDevice.getExpireTime().isBefore(LocalDateTime.now())) {
|
||||
return R.failed(-1, "请先信任设备");
|
||||
long startMs = System.currentTimeMillis();
|
||||
String accountId = shjnsso != null ? shjnsso.getAccountId() : null;
|
||||
String sessionId = shjnsso != null ? shjnsso.getSessionId() : null;
|
||||
String reqParam = "accountId=" + StringUtils.defaultString(accountId)
|
||||
+ ",sessionId=" + StringUtils.defaultString(sessionId)
|
||||
+ ",startMs=" + startMs;
|
||||
try {
|
||||
ZZDUserDTO zzdUserDTO = thirdPartyOAuth2Service.shjnsso(accountId, sessionId);
|
||||
if (null == zzdUserDTO) {
|
||||
R failed = R.failed("诗画用户信息为空");
|
||||
recordSsoControllerLog(UserTypeEnum.SH_SSO.getDescription(), "/thirdPartyOAth2/shjnsso",
|
||||
reqParam, failed, startMs, "emptyUser");
|
||||
return failed;
|
||||
}
|
||||
//登录成功生成token
|
||||
UserTokenDTO userTokenDTO = new UserTokenDTO();
|
||||
userTokenDTO.setSysUser(sysUser);
|
||||
|
||||
return remoteTokenService.generateToken(userTokenDTO);
|
||||
} else {
|
||||
//自动注册
|
||||
// UserDTO userDTO = new UserDTO();
|
||||
//// userDTO.setDeptId(1865990541445263361L);
|
||||
// userDTO.setName(zzdUserDTO.getUsername());
|
||||
// userDTO.setNickname(zzdUserDTO.getUsername());
|
||||
// userDTO.setSystemType("2");
|
||||
// userDTO.setPhone(mobile);
|
||||
// userDTO.setCreateBy("诗画自动创建");
|
||||
// //赋予随机密码
|
||||
// userDTO.setPassword("pj*"+ UUID.randomUUID());
|
||||
// R<Boolean> booleanR = remoteUserService.registerUser(userDTO);
|
||||
// R<List<SysUser>> userByMobile=remoteUserService.getUserByMobile(mobile);
|
||||
// SysUser sysUser = userByMobile.getData().get(0);
|
||||
// UserTokenDTO userTokenDTO = new UserTokenDTO();
|
||||
// userTokenDTO.setSysUser(sysUser);
|
||||
//// //登录成功,根据新注册的用户信息返回token
|
||||
// r=remoteTokenService.generateToken(userTokenDTO);
|
||||
return R.failed("系统中没有当前用户信息,请联系管理员进行申请!");
|
||||
String mobile = zzdUserDTO.getMobile();
|
||||
reqParam = reqParam + ",mobile=" + StringUtils.defaultString(mobile)
|
||||
+ ",nickName=" + StringUtils.defaultString(zzdUserDTO.getNickName());
|
||||
R<List<SysUser>> userByMobileOrEmployeeCode = remoteUserService.getUserByMobileOrEmployeeCode(null, mobile);
|
||||
List<SysUser> sysUserList = userByMobileOrEmployeeCode.getData();
|
||||
if (!CollectionUtils.isEmpty(sysUserList)) {
|
||||
SysUser sysUser = sysUserList.get(0);
|
||||
String md5 = DeviceFingerprintUtil.generate(httpServletRequest);
|
||||
R<UserTrustedDeviceEntity> userTrustedDeviceR = remoteUserTrustedDeviceService.getUserTrustedDeviceByIdAndMD5(sysUser.getPhone(), md5, SH_DEVICE_NAME);
|
||||
UserTrustedDeviceEntity userTrustedDevice = userTrustedDeviceR.getData();
|
||||
if (userTrustedDevice == null || userTrustedDevice.getExpireTime().isBefore(LocalDateTime.now())) {
|
||||
R failed = R.failed(-1, "请先信任设备");
|
||||
recordSsoControllerLog(UserTypeEnum.SH_SSO.getDescription(), "/thirdPartyOAth2/shjnsso",
|
||||
reqParam + ",deviceMd5=" + md5, failed, startMs, "needTrustDevice");
|
||||
return failed;
|
||||
}
|
||||
UserTokenDTO userTokenDTO = new UserTokenDTO();
|
||||
userTokenDTO.setSysUser(sysUser);
|
||||
R result = remoteTokenService.generateToken(userTokenDTO);
|
||||
recordSsoControllerLog(UserTypeEnum.SH_SSO.getDescription(), "/thirdPartyOAth2/shjnsso",
|
||||
reqParam + ",sysUserId=" + sysUser.getUserId(), result, startMs, "token");
|
||||
return result;
|
||||
}
|
||||
R failed = R.failed("系统中没有当前用户信息,请联系管理员进行申请!");
|
||||
recordSsoControllerLog(UserTypeEnum.SH_SSO.getDescription(), "/thirdPartyOAth2/shjnsso",
|
||||
reqParam, failed, startMs, "sysUserNotFound");
|
||||
return failed;
|
||||
} catch (ThirdPartyException e) {
|
||||
thirdPartyOAuth2Service.recordSsoLog(UserTypeEnum.SH_SSO.getDescription() + "-失败",
|
||||
"/thirdPartyOAth2/shjnsso", reqParam,
|
||||
"costMs=" + (System.currentTimeMillis() - startMs),
|
||||
"FAIL", e.getCode(), e.getMessage());
|
||||
return R.failed(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.error("[shjnsso] 未预期异常", e);
|
||||
thirdPartyOAuth2Service.recordSsoLog(UserTypeEnum.SH_SSO.getDescription() + "-失败",
|
||||
"/thirdPartyOAth2/shjnsso", reqParam,
|
||||
"costMs=" + (System.currentTimeMillis() - startMs),
|
||||
"FAIL", "SH_SSO_UNEXPECTED", e.getMessage());
|
||||
return R.failed("诗画单点登录失败");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Inner(value = false)
|
||||
|
||||
+6
-3
@@ -5,8 +5,11 @@ import com.bsd.pjcloud.thirdparty.entity.ThirdPartyLogEntity;
|
||||
|
||||
public interface ThirdPartyLogService extends IService<ThirdPartyLogEntity> {
|
||||
|
||||
|
||||
|
||||
|
||||
void saveLog(ThirdPartyLogEntity thirdPartyLogEntity);
|
||||
|
||||
/**
|
||||
* 浙政钉 / 诗画单点日志统一入口,写入 third_party_log。
|
||||
*/
|
||||
void recordSsoLog(String apiName, String requestUrl, String requestParameter,
|
||||
String responseBody, String httpStatus, String errorCode, String errorMsg);
|
||||
}
|
||||
+18
-27
@@ -406,34 +406,11 @@ public class ThirdPartyOAuth2Service {
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入单点日志表 third_party_log(异步),成功/失败都记;后台分析用,不做脱敏。
|
||||
* 写入单点日志表 third_party_log,成功/失败都记;后台「单点日志」菜单查看,不做脱敏。
|
||||
*/
|
||||
public void recordSsoLog(String apiName, String requestUrl, String requestParameter,
|
||||
String responseBody, String httpStatus, String errorCode, String errorMsg) {
|
||||
try {
|
||||
ThirdPartyLogEntity entity = new ThirdPartyLogEntity();
|
||||
entity.setApiName(apiName);
|
||||
entity.setRequestUrl(requestUrl);
|
||||
entity.setRequestParameter(truncate(requestParameter, 8000));
|
||||
entity.setResponseBody(truncate(responseBody, 16000));
|
||||
entity.setHttpStatus(httpStatus);
|
||||
entity.setErrorCode(errorCode);
|
||||
entity.setErrorMsg(truncate(errorMsg, 2000));
|
||||
entity.setCreateBy("zzd-openapi");
|
||||
thirdPartyLogService.saveLog(entity);
|
||||
} catch (Exception e) {
|
||||
log.warn("写入单点日志失败 apiName={}", apiName, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String truncate(String s, int max) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
if (s.length() <= max) {
|
||||
return s;
|
||||
}
|
||||
return s.substring(0, max) + "...(truncated)";
|
||||
thirdPartyLogService.recordSsoLog(apiName, requestUrl, requestParameter, responseBody, httpStatus, errorCode, errorMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -868,17 +845,31 @@ public class ThirdPartyOAuth2Service {
|
||||
}
|
||||
|
||||
/**
|
||||
* 诗画浦江单点:直连诗画开放接口获取用户,不再经对方 /login/shpj 中转
|
||||
* 诗画浦江单点:直连诗画开放接口获取用户,不再经对方 /login/shpj 中转。
|
||||
* HTTP 原始请求/响应由 {@link com.bsd.pjcloud.thirdparty.shpj.ShpjUserClient} 写入单点日志。
|
||||
*/
|
||||
public ZZDUserDTO shjnsso(String accountId, String sessionId) {
|
||||
String reqLog = "accountId=" + accountId + ",sessionId=" + sessionId + ",time=" + System.currentTimeMillis();
|
||||
ShpjOpenapiUser openapiUser = shpjUserClient.getUserInfo(accountId, sessionId);
|
||||
if (openapiUser == null || openapiUser.getIsLogin() != 1) {
|
||||
recordSsoLog(UserTypeEnum.SH_OPEN_USER.getDescription() + "-失败",
|
||||
"/openapi/v2/{accountId}/detail", reqLog, JSON.toJSONString(openapiUser),
|
||||
"FAIL", "SHPJ_NOT_LOGIN", "请先登录诗画客户端");
|
||||
throw new ThirdPartyException("请先登录诗画客户端", "SHPJ_NOT_LOGIN");
|
||||
}
|
||||
if (!StringUtils.hasText(openapiUser.getMobile())) {
|
||||
recordSsoLog(UserTypeEnum.SH_OPEN_USER.getDescription() + "-失败",
|
||||
"/openapi/v2/{accountId}/detail", reqLog, JSON.toJSONString(openapiUser),
|
||||
"FAIL", "SHPJ_MOBILE_EMPTY", "诗画用户手机号为空");
|
||||
throw new ThirdPartyException("诗画用户手机号为空", "SHPJ_MOBILE_EMPTY");
|
||||
}
|
||||
return mapShpjOpenapiUserToZzdDto(openapiUser);
|
||||
ZZDUserDTO dto = mapShpjOpenapiUserToZzdDto(openapiUser);
|
||||
com.alibaba.fastjson.JSONObject okBody = new com.alibaba.fastjson.JSONObject();
|
||||
okBody.put("mappedUser", JSON.toJSON(dto));
|
||||
recordSsoLog(UserTypeEnum.SH_SSO.getDescription() + "-换用户成功",
|
||||
"/thirdPartyOAth2/shjnsso", reqLog, okBody.toJSONString(),
|
||||
"OK", "0", null);
|
||||
return dto;
|
||||
}
|
||||
|
||||
private static ZZDUserDTO mapShpjOpenapiUserToZzdDto(ShpjOpenapiUser user) {
|
||||
|
||||
+65
-3
@@ -1,26 +1,88 @@
|
||||
package com.bsd.pjcloud.thirdparty.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.bsd.pjcloud.common.core.constant.CommonConstants;
|
||||
import com.bsd.pjcloud.common.data.tenant.TenantContextHolder;
|
||||
import com.bsd.pjcloud.thirdparty.entity.ThirdPartyLogEntity;
|
||||
import com.bsd.pjcloud.thirdparty.mapper.ThirdPartyLogMapper;
|
||||
import com.bsd.pjcloud.thirdparty.service.ThirdPartyLogService;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 单点日志记录
|
||||
*
|
||||
* @author zjf
|
||||
* @date 2025-03-31 13:48:37
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ThirdPartyLogServiceImpl extends ServiceImpl<ThirdPartyLogMapper, ThirdPartyLogEntity> implements ThirdPartyLogService {
|
||||
/**
|
||||
* 同步写入(REQUIRES_NEW)。不再 @Async:异步线程常丢租户上下文,导致 tenant_id 为空,
|
||||
* 后台按租户查询时看不到日志。
|
||||
*/
|
||||
@Override
|
||||
@Async
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
public void saveLog(ThirdPartyLogEntity thirdPartyLogEntity) {
|
||||
baseMapper.insert(thirdPartyLogEntity);
|
||||
Long prevTenant = TenantContextHolder.getTenantId();
|
||||
try {
|
||||
if (thirdPartyLogEntity.getTenantId() == null) {
|
||||
thirdPartyLogEntity.setTenantId(prevTenant != null ? prevTenant : CommonConstants.TENANT_ID_1);
|
||||
}
|
||||
if (thirdPartyLogEntity.getCreateTime() == null) {
|
||||
thirdPartyLogEntity.setCreateTime(LocalDateTime.now());
|
||||
}
|
||||
if (thirdPartyLogEntity.getDeleted() == null) {
|
||||
thirdPartyLogEntity.setDeleted(Boolean.FALSE);
|
||||
}
|
||||
TenantContextHolder.setTenantId(thirdPartyLogEntity.getTenantId());
|
||||
baseMapper.insert(thirdPartyLogEntity);
|
||||
} catch (Exception e) {
|
||||
log.error("写入 third_party_log 失败 apiName={}", thirdPartyLogEntity.getApiName(), e);
|
||||
throw e;
|
||||
} finally {
|
||||
if (prevTenant != null) {
|
||||
TenantContextHolder.setTenantId(prevTenant);
|
||||
} else {
|
||||
TenantContextHolder.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordSsoLog(String apiName, String requestUrl, String requestParameter,
|
||||
String responseBody, String httpStatus, String errorCode, String errorMsg) {
|
||||
try {
|
||||
ThirdPartyLogEntity entity = new ThirdPartyLogEntity();
|
||||
entity.setApiName(apiName);
|
||||
entity.setRequestUrl(requestUrl);
|
||||
entity.setRequestParameter(truncate(requestParameter, 8000));
|
||||
entity.setResponseBody(truncate(responseBody, 16000));
|
||||
entity.setHttpStatus(httpStatus);
|
||||
entity.setErrorCode(errorCode);
|
||||
entity.setErrorMsg(truncate(errorMsg, 2000));
|
||||
entity.setCreateBy("sso-log");
|
||||
entity.setCreateTime(LocalDateTime.now());
|
||||
entity.setDeleted(Boolean.FALSE);
|
||||
entity.setTenantId(CommonConstants.TENANT_ID_1);
|
||||
saveLog(entity);
|
||||
} catch (Exception e) {
|
||||
log.warn("写入单点日志失败 apiName={}", apiName, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String truncate(String s, int max) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
if (s.length() <= max) {
|
||||
return s;
|
||||
}
|
||||
return s.substring(0, max) + "...(truncated)";
|
||||
}
|
||||
}
|
||||
+18
@@ -2,7 +2,9 @@ package com.bsd.pjcloud.thirdparty.shpj;
|
||||
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import com.bsd.pjcloud.thirdparty.constant.UserTypeEnum;
|
||||
import com.bsd.pjcloud.thirdparty.exception.ThirdPartyException;
|
||||
import com.bsd.pjcloud.thirdparty.service.ThirdPartyLogService;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -24,6 +26,8 @@ public class ShpjUserClient {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private final ThirdPartyLogService thirdPartyLogService;
|
||||
|
||||
/**
|
||||
* 按 accountId + sessionId 查询诗画用户详情
|
||||
*/
|
||||
@@ -55,6 +59,10 @@ public class ShpjUserClient {
|
||||
|
||||
String apiUrl = "/openapi/v2/" + accountId + "/detail";
|
||||
String fullUrl = properties.getBaseUrl() + apiUrl;
|
||||
String reqLog = "accountId=" + accountId + ",sessionId=" + sessionId
|
||||
+ ",connectNo=" + properties.getConnectNo()
|
||||
+ ",appkey=" + properties.getAppKey()
|
||||
+ ",url=" + fullUrl;
|
||||
log.info("调用诗画浦江接口[{}]开始, accountId={}, sessionId={}", apiUrl, accountId, sessionId);
|
||||
|
||||
try (HttpResponse httpResponse = HttpRequest.get(fullUrl)
|
||||
@@ -64,6 +72,9 @@ public class ShpjUserClient {
|
||||
String body = httpResponse.body();
|
||||
if (!httpResponse.isOk()) {
|
||||
log.error("诗画浦江接口[{}]HTTP错误:{} - {}", apiUrl, httpResponse.getStatus(), body);
|
||||
thirdPartyLogService.recordSsoLog(UserTypeEnum.SH_OPEN_USER.getDescription() + "-失败",
|
||||
apiUrl, reqLog, body, String.valueOf(httpResponse.getStatus()),
|
||||
"SHPJ_HTTP", "诗画浦江接口HTTP错误");
|
||||
throw new ThirdPartyException("诗画浦江接口调用失败", "SHPJ_HTTP");
|
||||
}
|
||||
log.info("诗画浦江接口[{}]完成, 返回:{}", apiUrl, body);
|
||||
@@ -72,13 +83,20 @@ public class ShpjUserClient {
|
||||
objectMapper.getTypeFactory().constructParametricType(ShpjApiWrapper.class, ShpjOpenapiUser.class));
|
||||
if (!resp.isSuccessful()) {
|
||||
log.error("诗画浦江接口[{}]业务失败: [{}-{}]", apiUrl, resp.getCode(), resp.getMessage());
|
||||
thirdPartyLogService.recordSsoLog(UserTypeEnum.SH_OPEN_USER.getDescription() + "-失败",
|
||||
apiUrl, reqLog, body, "OK",
|
||||
"SHPJ_BIZ_" + resp.getCode(), resp.getMessage());
|
||||
throw new ThirdPartyException("诗画浦江接口调用失败:" + resp.getMessage(), "SHPJ_BIZ_" + resp.getCode());
|
||||
}
|
||||
thirdPartyLogService.recordSsoLog(UserTypeEnum.SH_OPEN_USER.getDescription() + "-成功",
|
||||
apiUrl, reqLog, body, "OK", "0", null);
|
||||
return resp.getData();
|
||||
} catch (ThirdPartyException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("诗画浦江接口[{}]异常", apiUrl, e);
|
||||
thirdPartyLogService.recordSsoLog(UserTypeEnum.SH_OPEN_USER.getDescription() + "-失败",
|
||||
apiUrl, reqLog, null, "FAIL", "SHPJ_ERROR", e.getMessage());
|
||||
throw new ThirdPartyException("诗画浦江服务暂时不可用", "SHPJ_ERROR");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user