运维页面

This commit is contained in:
2026-08-21 00:49:38 +08:00
parent 0d82bd4c76
commit ec306f0a98
11 changed files with 464 additions and 190 deletions
@@ -7,6 +7,7 @@ import com.yunhai.bschclassmanage.enums.SyncDataType;
import com.yunhai.bschclassmanage.service.ISyncLogService;
import com.yunhai.bschclassmanage.utils.OtherUtil;
import com.yunhai.bschclassmanage.utils.SensitiveDataUtil;
import com.yunhai.bschclassmanage.utils.SyncLogRetryContext;
import com.yunhai.bschclassmanage.utils.UserContext;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
@@ -47,7 +48,12 @@ public class SyncLogAspect {
SyncLog entity = new SyncLog();
// 枚举类型 key/name
entity.setSyncDataTypeKey(dataType.getKey());
entity.setSyncDataTypeName(dataType.getName());
String typeName = dataType.getName();
if (SyncLogRetryContext.isRetry()) {
// 重试重放时,在同步类型名上追加"(重试)"标记,便于与首次调用区分
typeName = (typeName == null ? "" : typeName) + "(重试)";
}
entity.setSyncDataTypeName(typeName);
// 大类:Send / Sync(由 key 前缀推导)
boolean isSend = dataType.getKey() != null && dataType.getKey().startsWith("Send");
entity.setSyncType(isSend ? "Send" : "Sync");
@@ -7,6 +7,7 @@ import com.yunhai.bschclassmanage.utils.GzipUtil;
import com.yunhai.bschclassmanage.utils.OperationLogResolver;
import com.yunhai.bschclassmanage.utils.OtherUtil;
import com.yunhai.bschclassmanage.utils.SensitiveDataUtil;
import com.yunhai.bschclassmanage.utils.SyncLogRetryContext;
import com.yunhai.bschclassmanage.utils.UserContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
@@ -98,7 +99,12 @@ public class SysOperationLogAspect {
e.setRequestMethod(httpMethod);
e.setClassMethod(point.getTarget().getClass().getName() + "." + methodName);
e.setLogLevel(OperationLogResolver.resolveLevel(httpMethod, uri, methodName).getCode());
e.setOperationType(OperationLogResolver.resolveOperationType(httpMethod, uri, methodName));
String opType = OperationLogResolver.resolveOperationType(httpMethod, uri, methodName);
if (SyncLogRetryContext.isRetry()) {
// 重试重放时,在操作类型上追加"(重试)"标记,便于与首次调用区分
opType = (opType == null ? "" : opType) + "(重试)";
}
e.setOperationType(opType);
if (request != null) {
e.setUserAgent(request.getHeader("User-Agent"));
e.setRequestParams(SensitiveDataUtil.mask(buildRequestParams(point, request)));
@@ -13,7 +13,9 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.Enumeration;
import java.util.List;
import java.util.Optional;
@@ -37,6 +39,21 @@ public class LoginUserController {
return authUserWithDept;
}
/**
* 判断是否登录:已登录则重定向到运维工作台 /work-view.html(兼容 context-path);
* 未登录返回 401(通常 CAS 拦截器会先行 302 到 CAS 登录页)。
*/
@GetMapping("checkLogin")
public void checkLogin(BoaUser authUserWithDept, HttpServletRequest request, HttpServletResponse response) throws IOException {
if (authUserWithDept != null) {
response.sendRedirect(request.getContextPath() + "/work-view.html");
} else {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":401,\"msg\":\"未登录\"}");
}
}
@GetMapping("Logout")
protected void logout(HttpServletRequest request) {
HttpSession session = request.getSession();
@@ -79,7 +79,7 @@ public class SyncLogController extends BaseController {
Object result = syncLogService.retry(id);
Map<String, Object> m = new LinkedHashMap<>();
m.put("success", true);
m.put("message", "重试已提交,执行结果见新增的同步日志");
m.put("message", "重试成功");
if (result != null) m.put("data", result);
return m;
}
@@ -80,7 +80,7 @@ public class SysOperationLogController extends BaseController {
Object result = sysOperationLogService.retry(id);
Map<String, Object> m = new LinkedHashMap<>();
m.put("success", true);
m.put("message", "重试已提交,执行结果见新增的审计日志");
m.put("message", "重试成功");
if (result != null) m.put("data", result);
return m;
}
@@ -74,6 +74,18 @@ public class SyncLog implements Serializable {
@Column(name = "status")
private Integer status;
/** 重试次数 */
@Column(name = "retry_count", columnDefinition = "INT DEFAULT 0")
private Integer retryCount;
/** 上次重试状态:1 成功 / 0 失败 */
@Column(name = "last_retry_status")
private Integer lastRetryStatus;
/** 上次重试时间 */
@Column(name = "last_retry_time", columnDefinition = "DATETIME(3)")
private LocalDateTime lastRetryTime;
@CreationTimestamp
@Column(name = "create_time", nullable = false, updatable = false, columnDefinition = "DATETIME(3)")
private LocalDateTime createTime;
@@ -71,6 +71,18 @@ public class SysOperationLog implements Serializable {
@Column(name = "error_msg", columnDefinition = "TEXT")
private String errorMsg; // 异常信息,截断 2000
/** 重试次数 */
@Column(name = "retry_count", columnDefinition = "INT DEFAULT 0")
private Integer retryCount;
/** 上次重试状态:1 成功 / 0 失败 */
@Column(name = "last_retry_status")
private Integer lastRetryStatus;
/** 上次重试时间 */
@Column(name = "last_retry_time", columnDefinition = "DATETIME(3)")
private LocalDateTime lastRetryTime;
@CreationTimestamp
@Column(name = "create_time", nullable = false, updatable = false, columnDefinition = "DATETIME(3)")
private LocalDateTime createTime;
@@ -6,6 +6,7 @@ import com.yunhai.bschclassmanage.entity.SyncLog;
import com.yunhai.bschclassmanage.model.vo.PageVO;
import com.yunhai.bschclassmanage.service.ISyncLogService;
import com.yunhai.bschclassmanage.utils.ReflectRetryUtil;
import com.yunhai.bschclassmanage.utils.SyncLogRetryContext;
import com.yunhai.platform.utils.exception.DException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@@ -23,6 +24,7 @@ import javax.persistence.criteria.Predicate;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
@@ -104,6 +106,10 @@ public class SyncLogServiceImpl implements ISyncLogService {
if (StringUtils.isBlank(e.getClassMethod())) throw new DException("该日志缺少调用信息(classMethod),无法重试");
if (StringUtils.isBlank(e.getJsonData())) throw new DException("该日志缺少入参(jsonData),无法重试");
// 记录本次重试元数据(重试次数 +1、重试时间)
e.setRetryCount((e.getRetryCount() == null ? 0 : e.getRetryCount()) + 1);
e.setLastRetryTime(LocalDateTime.now());
try {
@SuppressWarnings("unchecked")
Map<String, Object> argMap = JSON.parseObject(e.getJsonData(), LinkedHashMap.class);
@@ -133,11 +139,26 @@ public class SyncLogServiceImpl implements ISyncLogService {
}
Method invokeMethod = ReflectRetryUtil.getInvokeMethod(resolved.bean, target);
return invokeMethod.invoke(resolved.bean, args);
SyncLogRetryContext.mark(); // 标记为重试,切面生成新日志时在同步类型名追加"(重试)"
Object result = invokeMethod.invoke(resolved.bean, args);
e.setLastRetryStatus(1); // 本次重试成功
return result;
} catch (DException de) {
e.setLastRetryStatus(0); // 本次重试失败
throw de;
} catch (InvocationTargetException | IllegalAccessException ex) {
e.setLastRetryStatus(0); // 本次重试失败
throw ReflectRetryUtil.rethrow(ex);
} catch (RuntimeException re) {
e.setLastRetryStatus(0); // 本次重试失败(入参转换 / 定位等)
throw re;
} finally {
SyncLogRetryContext.clear(); // 清除重试标记,避免线程池复用泄漏
try {
repository.save(e); // 同步落库重试元数据(次数 / 上次状态 / 时间)
} catch (Exception saveEx) {
log.error("重试元数据落库失败, id={}", e.getId(), saveEx);
}
}
}
}
@@ -7,6 +7,7 @@ import com.yunhai.bschclassmanage.model.vo.PageVO;
import com.yunhai.bschclassmanage.service.ISysOperationLogService;
import com.yunhai.bschclassmanage.utils.GzipUtil;
import com.yunhai.bschclassmanage.utils.ReflectRetryUtil;
import com.yunhai.bschclassmanage.utils.SyncLogRetryContext;
import com.yunhai.bschclassmanage.utils.UserContext;
import com.yunhai.platform.utils.exception.DException;
import lombok.extern.slf4j.Slf4j;
@@ -139,43 +140,63 @@ public class SysOperationLogServiceImpl implements ISysOperationLogService {
throw new DException("非 JSON 数组入参(如 GET query)无法可靠重放,请手动重试");
}
JSONArray arr;
try {
arr = JSONArray.parseArray(e.getRequestParams());
} catch (Exception ex) {
throw new DException("入参解析失败(requestParams),无法重试");
}
if (arr == null) throw new DException("入参解析失败(requestParams),无法重试");
ReflectRetryUtil.Resolved resolved = ReflectRetryUtil.resolveBeanAndMethod(
applicationContext, e.getClassMethod(), arr.size());
Method target = resolved.method;
Parameter[] params = target.getParameters();
Object[] args = new Object[params.length];
int jsonIdx = 0;
for (int i = 0; i < params.length; i++) {
if (ReflectRetryUtil.isSkippable(params[i].getType())) {
args[i] = null; // 容器注入参数,无法重建
} else {
Object raw = (jsonIdx < arr.size()) ? arr.get(jsonIdx++) : null;
args[i] = ReflectRetryUtil.convert(raw, params[i]);
}
}
// 记录本次重试元数据(重试次数 +1、重试时间)
e.setRetryCount((e.getRetryCount() == null ? 0 : e.getRetryCount()) + 1);
e.setLastRetryTime(LocalDateTime.now());
// 还原原操作人上下文,使重放触发的审计切面记录正确操作人;结束后恢复本次请求上下文
UserContext.UserInfo snapshot = UserContext.getCurrentUser();
try {
JSONArray arr;
try {
arr = JSONArray.parseArray(e.getRequestParams());
} catch (Exception ex) {
throw new DException("入参解析失败(requestParams),无法重试");
}
if (arr == null) throw new DException("入参解析失败(requestParams),无法重试");
ReflectRetryUtil.Resolved resolved = ReflectRetryUtil.resolveBeanAndMethod(
applicationContext, e.getClassMethod(), arr.size());
Method target = resolved.method;
Parameter[] params = target.getParameters();
Object[] args = new Object[params.length];
int jsonIdx = 0;
for (int i = 0; i < params.length; i++) {
if (ReflectRetryUtil.isSkippable(params[i].getType())) {
args[i] = null; // 容器注入参数,无法重建
} else {
Object raw = (jsonIdx < arr.size()) ? arr.get(jsonIdx++) : null;
args[i] = ReflectRetryUtil.convert(raw, params[i]);
}
}
UserContext.setUserId(e.getUserId());
UserContext.setUserName(e.getUserName());
UserContext.setSchId(e.getSchId());
UserContext.setIp(e.getRequestIp());
Method invokeMethod = ReflectRetryUtil.getInvokeMethod(resolved.bean, target);
return invokeMethod.invoke(resolved.bean, args);
SyncLogRetryContext.mark(); // 标记为重试,审计切面生成新日志时在操作类型追加"(重试)"
Object result = invokeMethod.invoke(resolved.bean, args);
e.setLastRetryStatus(1); // 本次重试成功
return result;
} catch (DException de) {
e.setLastRetryStatus(0); // 本次重试失败
throw de;
} catch (InvocationTargetException | IllegalAccessException ex) {
e.setLastRetryStatus(0); // 本次重试失败
throw ReflectRetryUtil.rethrow(ex);
} catch (RuntimeException re) {
e.setLastRetryStatus(0); // 本次重试失败(入参转换 / 定位等)
throw re;
} finally {
UserContext.setUserInfo(snapshot);
SyncLogRetryContext.clear(); // 清除重试标记,避免线程池复用泄漏
try {
repository.save(e); // 同步落库重试元数据(次数 / 上次状态 / 时间)
} catch (Exception saveEx) {
log.error("审计日志重试元数据落库失败, id={}", e.getId(), saveEx);
}
}
}
}