2 Commits
12ee14a62a
...
6383d3d9b5
Author | SHA1 | Message | Date |
---|---|---|---|
|
6383d3d9b5 |
Merge remote-tracking branch 'origin/milestone-20250727-金币重构三期' into milestone-20250727-金币重构三期
|
3 days ago |
|
a013bce6c1 |
8.19操作日志写入数据库完毕
|
3 days ago |
12 changed files with 310 additions and 35 deletions
-
108src/main/java/com/example/demo/RabbitMQ/LogAspect.java
-
39src/main/java/com/example/demo/RabbitMQ/LogConsumer.java
-
28src/main/java/com/example/demo/Util/SecurityUtils.java
-
48src/main/java/com/example/demo/config/RabbitMQConfig.java
-
15src/main/java/com/example/demo/config/interfac/Log.java
-
2src/main/java/com/example/demo/controller/coin/GoldDetailController.java
-
21src/main/java/com/example/demo/domain/DTO/OperationLogDTO.java
-
30src/main/java/com/example/demo/domain/entity/Log.java
-
25src/main/java/com/example/demo/domain/entity/OperationLog.java
-
1src/main/java/com/example/demo/domain/vo/coin/Page.java
-
13src/main/java/com/example/demo/mapper/coin/OperationLogMapper.java
-
15src/main/resources/application-test.yml
@ -0,0 +1,108 @@ |
|||||
|
package com.example.demo.RabbitMQ; |
||||
|
|
||||
|
import com.example.demo.Util.SecurityUtils; |
||||
|
import com.example.demo.config.RabbitMQConfig; |
||||
|
import com.example.demo.config.interfac.Log; |
||||
|
import com.example.demo.domain.DTO.OperationLogDTO; |
||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
import jakarta.servlet.http.HttpServletRequest; |
||||
|
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.amqp.rabbit.core.RabbitTemplate; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.springframework.web.context.request.RequestContextHolder; |
||||
|
import org.springframework.web.context.request.ServletRequestAttributes; |
||||
|
|
||||
|
import java.time.LocalDateTime; |
||||
|
|
||||
|
// com.example.demo.aspect.LogAspect.java |
||||
|
@Aspect |
||||
|
@Component |
||||
|
@Slf4j |
||||
|
public class LogAspect { |
||||
|
|
||||
|
@Autowired |
||||
|
private RabbitTemplate rabbitTemplate; |
||||
|
|
||||
|
@Around("@annotation(com.example.demo.config.interfac.Log)") |
||||
|
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { |
||||
|
long startTime = System.currentTimeMillis(); |
||||
|
|
||||
|
MethodSignature signature = (MethodSignature) joinPoint.getSignature(); |
||||
|
String methodName = signature.getName(); |
||||
|
String className = signature.getDeclaringTypeName(); |
||||
|
Object[] args = joinPoint.getArgs(); |
||||
|
|
||||
|
Log logAnnotation = signature.getMethod().getAnnotation(Log.class); |
||||
|
String action = logAnnotation.value(); |
||||
|
|
||||
|
// ✅ 使用 Spring Security 获取真实用户 |
||||
|
String username = SecurityUtils.getCurrentUsername(); |
||||
|
Integer userId = SecurityUtils.getCurrentUserId(); |
||||
|
|
||||
|
String ip = getClientIp(); |
||||
|
|
||||
|
ObjectMapper mapper = new ObjectMapper(); |
||||
|
String argsJson = "[]"; |
||||
|
try { |
||||
|
argsJson = mapper.writeValueAsString(args); |
||||
|
} catch (Exception e) { |
||||
|
argsJson = "serialize failed"; |
||||
|
} |
||||
|
|
||||
|
Object result; |
||||
|
try { |
||||
|
result = joinPoint.proceed(); |
||||
|
} catch (Exception e) { |
||||
|
log.error("方法执行异常: {}", e.getMessage()); |
||||
|
throw e; |
||||
|
} |
||||
|
|
||||
|
long duration = System.currentTimeMillis() - startTime; |
||||
|
|
||||
|
// ✅ 构造日志消息 DTO |
||||
|
OperationLogDTO logDTO = new OperationLogDTO(); |
||||
|
logDTO.setUserId(userId); |
||||
|
logDTO.setUsername(username); |
||||
|
logDTO.setAction(action); |
||||
|
logDTO.setIp(ip); |
||||
|
logDTO.setMethod(className + "." + methodName); |
||||
|
logDTO.setArgs(argsJson); |
||||
|
logDTO.setCreateTime(LocalDateTime.now()); |
||||
|
System.out.println(logDTO); |
||||
|
|
||||
|
// ✅ 发送消息到 RabbitMQ(不等待) |
||||
|
try { |
||||
|
rabbitTemplate.convertAndSend(RabbitMQConfig.LOG_EXCHANGE, "log.save", logDTO); |
||||
|
log.info("📩 日志消息已发送到 RabbitMQ: {}", action); |
||||
|
} catch (Exception e) { |
||||
|
log.error("发送日志消息到 RabbitMQ 失败", e); |
||||
|
} |
||||
|
|
||||
|
log.info("✅ AOP 拦截完成: {} 执行 [{}] 耗时 {}ms", username, action, duration); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
private String getClientIp() { |
||||
|
try { |
||||
|
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); |
||||
|
String ip = request.getHeader("X-Forwarded-For"); |
||||
|
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { |
||||
|
ip = request.getHeader("Proxy-Client-IP"); |
||||
|
} |
||||
|
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { |
||||
|
ip = request.getHeader("WL-Proxy-Client-IP"); |
||||
|
} |
||||
|
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { |
||||
|
ip = request.getRemoteAddr(); |
||||
|
} |
||||
|
return ip.split(",")[0].trim(); // 多代理时取第一个 |
||||
|
} catch (Exception e) { |
||||
|
return "unknown"; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,39 @@ |
|||||
|
package com.example.demo.RabbitMQ; |
||||
|
|
||||
|
import com.example.demo.config.RabbitMQConfig; |
||||
|
import com.example.demo.domain.DTO.OperationLogDTO; |
||||
|
import com.example.demo.domain.entity.OperationLog; |
||||
|
import com.example.demo.mapper.coin.OperationLogMapper; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.amqp.rabbit.annotation.RabbitListener; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
// com.example.demo.consumer.LogConsumer.java |
||||
|
@Component |
||||
|
@Slf4j |
||||
|
public class LogConsumer { |
||||
|
|
||||
|
@Autowired |
||||
|
private OperationLogMapper operationLogMapper; |
||||
|
|
||||
|
@RabbitListener(queues = RabbitMQConfig.LOG_QUEUE) |
||||
|
public void consumeLog(OperationLogDTO logDTO) { |
||||
|
try { |
||||
|
OperationLog log = new OperationLog(); |
||||
|
log.setUserId(logDTO.getUserId()); |
||||
|
log.setUsername(logDTO.getUsername()); |
||||
|
log.setAction(logDTO.getAction()); |
||||
|
log.setIp(logDTO.getIp()); |
||||
|
log.setMethod(logDTO.getMethod()); |
||||
|
log.setArgs(logDTO.getArgs()); |
||||
|
log.setCreateTime(logDTO.getCreateTime()); |
||||
|
System.out.println("consumer" + log); |
||||
|
operationLogMapper.insertLog(log); |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
log.error("持久化日志失败", e); |
||||
|
// 可以重试或记录到文件 |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,28 @@ |
|||||
|
package com.example.demo.Util; |
||||
|
|
||||
|
import com.example.demo.domain.entity.Admin; |
||||
|
import org.springframework.security.core.Authentication; |
||||
|
import org.springframework.security.core.context.SecurityContextHolder; |
||||
|
|
||||
|
// com.example.demo.utils.SecurityUtils.java |
||||
|
public class SecurityUtils { |
||||
|
|
||||
|
public static String getCurrentUsername() { |
||||
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); |
||||
|
if (authentication == null || !authentication.isAuthenticated()) { |
||||
|
return "anonymous"; |
||||
|
} |
||||
|
return authentication.getName(); |
||||
|
} |
||||
|
|
||||
|
public static Integer getCurrentUserId() { |
||||
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); |
||||
|
if (authentication == null || !authentication.isAuthenticated()) { |
||||
|
return null; |
||||
|
} |
||||
|
if (authentication.getPrincipal() instanceof Admin userDetails) { |
||||
|
return userDetails.getId(); |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
} |
@ -0,0 +1,48 @@ |
|||||
|
package com.example.demo.config; |
||||
|
|
||||
|
|
||||
|
import org.springframework.amqp.core.Binding; |
||||
|
import org.springframework.amqp.core.BindingBuilder; |
||||
|
import org.springframework.amqp.core.Queue; |
||||
|
import org.springframework.amqp.core.TopicExchange; |
||||
|
import org.springframework.amqp.rabbit.connection.ConnectionFactory; |
||||
|
import org.springframework.amqp.rabbit.core.RabbitTemplate; |
||||
|
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; |
||||
|
import org.springframework.amqp.support.converter.MessageConverter; |
||||
|
import org.springframework.context.annotation.Bean; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
|
||||
|
@Configuration |
||||
|
public class RabbitMQConfig { |
||||
|
|
||||
|
public static final String LOG_QUEUE = "operation_log_queue"; |
||||
|
public static final String LOG_EXCHANGE = "operation_log_exchange"; |
||||
|
|
||||
|
@Bean |
||||
|
public Queue logQueue() { |
||||
|
return new Queue(LOG_QUEUE, true); |
||||
|
} |
||||
|
|
||||
|
@Bean |
||||
|
public TopicExchange logExchange() { |
||||
|
return new TopicExchange(LOG_EXCHANGE); |
||||
|
} |
||||
|
|
||||
|
@Bean |
||||
|
public Binding binding() { |
||||
|
return BindingBuilder.bind(logQueue()) |
||||
|
.to(logExchange()) |
||||
|
.with("log.*"); |
||||
|
} |
||||
|
@Bean |
||||
|
public MessageConverter messageConverter() { |
||||
|
return new Jackson2JsonMessageConverter(); |
||||
|
} |
||||
|
|
||||
|
@Bean |
||||
|
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { |
||||
|
RabbitTemplate template = new RabbitTemplate(connectionFactory); |
||||
|
template.setMessageConverter(messageConverter()); |
||||
|
return template; |
||||
|
} |
||||
|
} |
@ -0,0 +1,15 @@ |
|||||
|
package com.example.demo.config.interfac; |
||||
|
|
||||
|
import java.lang.annotation.ElementType; |
||||
|
import java.lang.annotation.Retention; |
||||
|
import java.lang.annotation.RetentionPolicy; |
||||
|
import java.lang.annotation.Target; |
||||
|
import java.lang.annotation.Documented; |
||||
|
|
||||
|
// com.example.demo.annotation.Log.java |
||||
|
@Target(ElementType.METHOD) |
||||
|
@Retention(RetentionPolicy.RUNTIME) |
||||
|
@Documented |
||||
|
public @interface Log { |
||||
|
String value() default ""; // 描述操作,如 "修改用户" |
||||
|
} |
@ -0,0 +1,21 @@ |
|||||
|
package com.example.demo.domain.DTO; |
||||
|
|
||||
|
import lombok.AllArgsConstructor; |
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
|
||||
|
import java.io.Serializable; |
||||
|
import java.time.LocalDateTime; |
||||
|
|
||||
|
@Data |
||||
|
@AllArgsConstructor |
||||
|
@NoArgsConstructor |
||||
|
public class OperationLogDTO{ |
||||
|
private Integer userId; |
||||
|
private String username; |
||||
|
private String action; |
||||
|
private String ip; |
||||
|
private String method; |
||||
|
private String args; |
||||
|
private LocalDateTime createTime; |
||||
|
} |
@ -1,30 +0,0 @@ |
|||||
package com.example.demo.domain.entity; |
|
||||
|
|
||||
import com.fasterxml.jackson.annotation.JsonFormat; |
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
|
||||
import lombok.Data; |
|
||||
import lombok.NoArgsConstructor; |
|
||||
|
|
||||
import java.io.Serializable; |
|
||||
import java.util.Date; |
|
||||
|
|
||||
@Data |
|
||||
@NoArgsConstructor |
|
||||
@JsonIgnoreProperties(ignoreUnknown = true) |
|
||||
public class Log implements Serializable { |
|
||||
private static final long serialVersionUID = 1L; |
|
||||
|
|
||||
private Integer id; // 日志id |
|
||||
private String ip; // ip地址 |
|
||||
private Integer adminId; // 用户id |
|
||||
private String method; // 方法名 |
|
||||
private String operation; // 操作 |
|
||||
private Object params; // 参数 |
|
||||
private String path; // 请求url |
|
||||
|
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") |
|
||||
private Date createTime; // 创建时间(操作时间) |
|
||||
|
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") |
|
||||
private Date updateTime; // 更新时间 |
|
||||
} |
|
@ -0,0 +1,25 @@ |
|||||
|
package com.example.demo.domain.entity; |
||||
|
|
||||
|
import lombok.AllArgsConstructor; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
import org.springframework.data.annotation.Id; |
||||
|
|
||||
|
import java.time.LocalDateTime; |
||||
|
|
||||
|
@Data |
||||
|
@NoArgsConstructor |
||||
|
public class OperationLog { |
||||
|
@Id |
||||
|
private Integer id; |
||||
|
|
||||
|
private Integer userId; |
||||
|
private String username; |
||||
|
private String action; // 操作描述 |
||||
|
private String ip; |
||||
|
private String method; // 调用的方法名 |
||||
|
private String args; // 参数(JSON字符串) |
||||
|
private LocalDateTime createTime = LocalDateTime.now(); |
||||
|
|
||||
|
} |
@ -0,0 +1,13 @@ |
|||||
|
package com.example.demo.mapper.coin;// com.example.demo.mapper.OperationLogMapper.java |
||||
|
import com.example.demo.domain.entity.OperationLog; |
||||
|
import org.apache.ibatis.annotations.Insert; |
||||
|
import org.apache.ibatis.annotations.Mapper; |
||||
|
import org.apache.ibatis.annotations.Param; |
||||
|
|
||||
|
@Mapper |
||||
|
public interface OperationLogMapper { |
||||
|
|
||||
|
@Insert("INSERT INTO operation_log (user_id, username, action, ip, method, args, create_time) " + |
||||
|
"VALUES (#{userId}, #{username}, #{action}, #{ip}, #{method}, #{args}, NOW())") |
||||
|
void insertLog(OperationLog log); |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue