synclog
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
package com.yunhai.bschclassmanage.Repository;
|
||||
|
||||
import com.yunhai.bschclassmanage.entity.SyncLog;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface SyncLogRepository extends JpaRepository<SyncLog, Long> {
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.yunhai.bschclassmanage.annotation;
|
||||
|
||||
import com.yunhai.bschclassmanage.enums.SyncDataType;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 标记一个方法需要记录"用户同步调用日志"。
|
||||
* 由 SyncLogAspect 拦截,按 dataType 写入 sync_log 表。
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface SyncOpLog {
|
||||
/** 该操作对应的同步数据类型枚举(作为默认的 value 元素,可用 @SyncOpLog(SyncDataType.X) 简写) */
|
||||
SyncDataType value();
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.yunhai.bschclassmanage.aspect;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.yunhai.bschclassmanage.annotation.SyncOpLog;
|
||||
import com.yunhai.bschclassmanage.entity.SyncLog;
|
||||
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.UserContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 同步调用日志切面。
|
||||
* 拦截所有标注 @SyncLog 的方法(当前仅 SyncUserBaseService),
|
||||
* 统一采集:枚举类型 / 大类(Sync-Send) / 类名.方法名 / 操作人 / 学校 / IP / 脱敏入参 / 异常,
|
||||
* 并在 finally 中异步落库(不影响主流程、异常照常抛出)。
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@Component
|
||||
@Order(10)
|
||||
public class SyncLogAspect {
|
||||
|
||||
private static final int MAX_ERROR_MSG = 2000;
|
||||
|
||||
@Autowired
|
||||
private ISyncLogService syncLogService;
|
||||
|
||||
@Around("@annotation(syncLog)")
|
||||
public Object around(ProceedingJoinPoint pjp,SyncOpLog syncLog) throws Throwable {
|
||||
SyncDataType dataType = syncLog.value();
|
||||
String classMethod = pjp.getTarget().getClass().getName() + "." + pjp.getSignature().getName();
|
||||
String jsonData = buildJsonData(pjp);
|
||||
|
||||
SyncLog entity = new SyncLog();
|
||||
// 枚举类型 key/name
|
||||
entity.setSyncDataTypeKey(dataType.getKey());
|
||||
entity.setSyncDataTypeName(dataType.getName());
|
||||
// 大类:Send / Sync(由 key 前缀推导)
|
||||
boolean isSend = dataType.getKey() != null && dataType.getKey().startsWith("Send");
|
||||
entity.setSyncType(isSend ? "Send" : "Sync");
|
||||
entity.setSyncTypeKey(isSend ? "Send" : "Sync");
|
||||
entity.setSyncTypeName(isSend ? "推送" : "同步");
|
||||
|
||||
entity.setClassMethod(classMethod);
|
||||
entity.setUserId(UserContext.getUserId());
|
||||
entity.setUserName(UserContext.getUserName());
|
||||
entity.setSchId(UserContext.getSchId());
|
||||
entity.setRequestIp(StringUtils.hasText(UserContext.getIp())
|
||||
? UserContext.getIp() : OtherUtil.getClientIp());
|
||||
entity.setJsonData(jsonData);
|
||||
|
||||
Object result;
|
||||
try {
|
||||
result = pjp.proceed();
|
||||
} catch (Throwable t) {
|
||||
entity.setErrorMsg(truncate(t.getMessage(), MAX_ERROR_MSG));
|
||||
syncLogService.saveAsync(entity);
|
||||
throw t;
|
||||
}
|
||||
syncLogService.saveAsync(entity);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将方法参数按"参数名->值"序列化为 JSON,并用 SensitiveDataUtil 脱敏 pwd/idCard/phone 等。
|
||||
*/
|
||||
private String buildJsonData(ProceedingJoinPoint pjp) {
|
||||
try {
|
||||
MethodSignature ms = (MethodSignature) pjp.getSignature();
|
||||
String[] names = ms.getParameterNames();
|
||||
Object[] args = pjp.getArgs();
|
||||
if (names == null || args == null) return "";
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
for (int i = 0; i < args.length && i < names.length; i++) {
|
||||
map.put(names[i], args[i]);
|
||||
}
|
||||
return SensitiveDataUtil.mask(JSON.toJSONString(map));
|
||||
} catch (Exception e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private String truncate(String s, int max) {
|
||||
return s != null && s.length() > max ? s.substring(0, max) : s;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.yunhai.bschclassmanage.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 用户同步调用日志实体。
|
||||
* 专用于记录 SyncUserBaseService 对基础平台 OpenAPI 的同步/推送调用,
|
||||
* 与 operation_log(业务日志)、sys_operation_log(审计日志)物理隔离。
|
||||
* 索引由 sqlscript/sync_log.sql 负责创建,本实体不含 @Table(indexes),
|
||||
* 避免 ddl-auto=update 启动时重复建索引告警。
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@Entity
|
||||
@Table(name = "sync_log")
|
||||
public class SyncLog implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
/** SyncDataType.key,如 Sync-AddGroup */
|
||||
@Column(name = "sync_data_type_key", length = 64)
|
||||
private String syncDataTypeKey;
|
||||
|
||||
/** SyncDataType.name,如 添加组 */
|
||||
@Column(name = "sync_data_type_name", length = 64)
|
||||
private String syncDataTypeName;
|
||||
|
||||
/** 大类:Sync / Send */
|
||||
@Column(name = "sync_type", nullable = false, length = 16)
|
||||
private String syncType;
|
||||
|
||||
/** 大类 key:Sync / Send */
|
||||
@Column(name = "sync_type_key", nullable = false, length = 16)
|
||||
private String syncTypeKey;
|
||||
|
||||
/** 大类名称:同步 / 推送 */
|
||||
@Column(name = "sync_type_name", length = 32)
|
||||
private String syncTypeName;
|
||||
|
||||
/** 类名.方法名 */
|
||||
@Column(name = "class_method", nullable = false, length = 512)
|
||||
private String classMethod;
|
||||
|
||||
/** 操作人ID(来自 UserContext) */
|
||||
@Column(name = "user_id")
|
||||
private Integer userId;
|
||||
|
||||
/** 操作人姓名(来自 UserContext) */
|
||||
@Column(name = "user_name", length = 64)
|
||||
private String userName;
|
||||
|
||||
/** 学校ID(多校隔离,来自 UserContext) */
|
||||
@Column(name = "sch_id")
|
||||
private Integer schId;
|
||||
|
||||
/** 客户端IP */
|
||||
@Column(name = "request_ip", length = 64)
|
||||
private String requestIp;
|
||||
|
||||
/** 请求入参(参数名->值,已脱敏),TEXT */
|
||||
@Column(name = "json_data", columnDefinition = "TEXT")
|
||||
private String jsonData;
|
||||
|
||||
/** 异常信息,成功为 NULL */
|
||||
@Column(name = "error_msg", columnDefinition = "TEXT")
|
||||
private String errorMsg;
|
||||
|
||||
@CreationTimestamp
|
||||
@Column(name = "create_time", nullable = false, updatable = false, columnDefinition = "DATETIME(3)")
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.yunhai.bschclassmanage.service;
|
||||
|
||||
import com.yunhai.bschclassmanage.entity.SyncLog;
|
||||
|
||||
public interface ISyncLogService {
|
||||
/**
|
||||
* 异步保存一条同步调用日志。
|
||||
* 由 SyncLogAspect 在 finally 中调用,失败仅记日志、不抛异常、不阻塞主流程。
|
||||
*/
|
||||
void saveAsync(SyncLog entity);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.yunhai.bschclassmanage.service.impl;
|
||||
|
||||
import com.yunhai.bschclassmanage.Repository.SyncLogRepository;
|
||||
import com.yunhai.bschclassmanage.entity.SyncLog;
|
||||
import com.yunhai.bschclassmanage.service.ISyncLogService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SyncLogServiceImpl implements ISyncLogService {
|
||||
|
||||
@Autowired
|
||||
private SyncLogRepository repository;
|
||||
|
||||
@Async("logExecutor")
|
||||
@Override
|
||||
public void saveAsync(SyncLog entity) {
|
||||
try {
|
||||
repository.save(entity);
|
||||
} catch (Exception e) {
|
||||
log.error("同步日志异步落库失败, classMethod={}", entity.getClassMethod(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user