This commit is contained in:
clay
2024-03-06 17:44:09 +08:00
commit adaec0eadd
1493 changed files with 219939 additions and 0 deletions

25
log/log-api/pom.xml Normal file
View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>log</artifactId>
<groupId>cn.fateverse</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>log-api</artifactId>
<dependencies>
<dependency>
<groupId>cn.fateverse</groupId>
<artifactId>common-core</artifactId>
</dependency>
<dependency>
<groupId>cn.fateverse</groupId>
<artifactId>common-swagger</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,28 @@
package cn.fateverse.log.dubbo;
import cn.fateverse.log.entity.LoginInfo;
import cn.fateverse.log.entity.OperationLog;
import java.util.List;
/**
* @author Clay
* @date 2023-02-20
*/
public interface DubboLogService {
/**
* 批量保存日志
*
* @param list 需要保存的日志
*/
void batchSaveLog(List<OperationLog> list);
/**
* 保存登录信息
*
* @param info 登录日志信息
*/
void saveLoginInfo(LoginInfo info);
}

View File

@@ -0,0 +1,61 @@
package cn.fateverse.log.entity;
import cn.fateverse.common.core.annotaion.EnableAutoField;
import cn.fateverse.common.core.annotaion.GenerateId;
import cn.fateverse.common.core.enums.GenIdEnum;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 用户登录信息
*
* @author Clay
* @date 2022/11/2
*/
@Data
@EnableAutoField
public class LoginInfo implements Serializable {
/**
* 访问Id
*/
@GenerateId(idType = GenIdEnum.SNOWFLAKE)
private Long infoId;
private String uuid;
/**
* 用户名
*/
private String userName;
/**
* 登录ip
*/
private String ipddr;
/**
* 登录地点
*/
private String loginLocation;
/**
* 浏览器类型
*/
private String browser;
/**
* 操作系统
*/
private String os;
/**
* 登录状态
*/
private Integer state;
/**
* 登录信息
*/
private String msg;
/**
* 登录时间
*/
private Date loginTime;
}

View File

@@ -0,0 +1,115 @@
package cn.fateverse.log.entity;
import cn.fateverse.common.core.annotaion.EnableAutoField;
import cn.fateverse.common.core.annotaion.GenerateId;
import cn.fateverse.common.core.enums.GenIdEnum;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* @author Clay
* @date 2022/11/1
*/
@Data
@EnableAutoField
public class OperationLog implements Serializable {
/**
* 日志主键
*/
@GenerateId(idType = GenIdEnum.SNOWFLAKE)
private Long operId;
/**
* 用户id
*/
private Long userId;
/**
* 操作模块
*/
private String title;
/**
* 业务类型0其它 1新增 2修改 3删除
*/
private Integer businessType;
/**
* 请求方法
*/
private String method;
/**
* 请求方式
*/
private String requestMethod;
/**
* 操作类别0其它 1后台用户 2手机端用户
*/
private Integer operatorType;
/**
* 操作人员
*/
private String operName;
/**
* 部门名称
*/
private String deptName;
/**
* 请求url
*/
private String operUrl;
/**
* 操作地址
*/
private String operIp;
/**
* 操作地点
*/
private String operLocation;
/**
* 请求参数
*/
private String operParam;
/**
* 返回参数
*/
private String jsonResult;
/**
* 操作状态0正常 1异常
*/
private Integer state;
/**
* 错误消息
*/
private String errorMsg;
/**
* 异常栈信息
*/
private String errorStackTrace;
/**
* 操作时间
*/
private Date operTime;
/**
* 消耗时间
*/
private Long consumeTime;
}

View File

@@ -0,0 +1,40 @@
package cn.fateverse.log.query;
import cn.fateverse.common.core.entity.QueryTime;
import io.swagger.annotations.ApiModel;
import lombok.Data;
/**
* @Description: 日志管理->登录日志查询实体类
* @Author: Gary
* @DateTime:2022/11/15 20:24
* @Version: V2.0
*/
@Data
@ApiModel("登录日志查询实体")
public class LoginLogQuery extends QueryTime {
/**
* 登录地址
*/
private String ipAddr;
/**
* 用户名称
*/
private String userName;
/**
* 登录状态
*/
private String state;
}

View File

@@ -0,0 +1,39 @@
package cn.fateverse.log.query;
import cn.fateverse.common.core.entity.QueryTime;
import io.swagger.annotations.ApiModel;
import lombok.Data;
/**
* @Description: 日志管理->操作日志查询实体类
* @Author: Gary
* @DateTime:2022/11/14 20:24
* @Version: V2.0
*/
@Data
@ApiModel("日志查询实体")
public class OperationLogQuery extends QueryTime {
/**
* 系统模块
*/
private String title;
/**
* 操作人员
*/
private String operName;
/**
* 操作类型
*/
private Integer businessType;
/**
* 操作状态
*/
private String state;
}

View File

@@ -0,0 +1,72 @@
package cn.fateverse.log.vo;
import cn.fateverse.log.entity.LoginInfo;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.BeanUtils;
import java.io.Serializable;
import java.util.Date;
/**
* 用户登录信息
*
* @author Clay
* @date 2022/11/2
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class LoginInfoVo implements Serializable {
/**
* 访问Id
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long infoId;
/**
* 用户名
*/
private String userName;
/**
* 登录ip
*/
private String ipddr;
/**
* 登录地点
*/
private String loginLocation;
/**
* 浏览器类型
*/
private String browser;
/**
* 操作系统
*/
private String os;
/**
* 登录状态
*/
private Integer state;
/**
* 登录信息
*/
private String msg;
/**
* 登录时间
*/
private Date loginTime;
public static LoginInfoVo toLoginInfoVo(LoginInfo info) {
LoginInfoVo vo = new LoginInfoVo();
BeanUtils.copyProperties(info, vo);
return vo;
}
}

View File

@@ -0,0 +1,131 @@
package cn.fateverse.log.vo;
import cn.fateverse.log.entity.OperationLog;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/**
* @author Clay
* @date 2022/11/1
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class OperationLogVo implements Serializable {
/**
* 日志主键
*/
@JsonSerialize(using = ToStringSerializer.class)
private Long operId;
/**
* 操作模块
*/
private String title;
/**
* 业务类型0其它 1新增 2修改 3删除
*/
private Integer businessType;
/**
* 请求方法
*/
private String method;
/**
* 请求方式
*/
private String requestMethod;
/**
* 操作类别0其它 1后台用户 2手机端用户
*/
private Integer operatorType;
/**
* 操作人员
*/
private String operName;
/**
* 请求url
*/
private String operUrl;
/**
* 操作地址
*/
private String operIp;
/**
* 操作地点
*/
private String operLocation;
/**
* 请求参数
*/
private String operParam;
/**
* 返回参数
*/
private String jsonResult;
/**
* 操作状态0正常 1异常
*/
private Integer state;
/**
* 错误消息
*/
private String errorMsg;
/**
* 异常栈信息
*/
private String errorStackTrace;
/**
* 操作时间
*/
private Date operTime;
/**
* 消耗时间
*/
private Long consumeTime;
public static OperationLogVo toOperationLogVo(OperationLog operationLog) {
return OperationLogVo.builder()
.operId(operationLog.getOperId())
.title(operationLog.getTitle())
.businessType(operationLog.getBusinessType())
.method(operationLog.getMethod())
.requestMethod(operationLog.getRequestMethod())
.operatorType(operationLog.getOperatorType())
.operName(operationLog.getOperName())
.operUrl(operationLog.getOperUrl())
.operIp(operationLog.getOperIp())
.operLocation(operationLog.getOperLocation())
.operParam(operationLog.getOperParam())
.jsonResult(operationLog.getJsonResult())
.state(operationLog.getState())
.errorMsg(operationLog.getErrorMsg())
.operTime(operationLog.getOperTime())
.consumeTime(operationLog.getConsumeTime())
.build();
}
}

93
log/log-biz/pom.xml Normal file
View File

@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>log</artifactId>
<groupId>cn.fateverse</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>log-biz</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<sharding.version>4.0.0-RC1</sharding.version>
<maven.deploy.skip>true</maven.deploy.skip>
</properties>
<dependencies>
<dependency>
<groupId>cn.fateverse</groupId>
<artifactId>common-mybatis</artifactId>
</dependency>
<dependency>
<groupId>cn.fateverse</groupId>
<artifactId>common-security</artifactId>
</dependency>
<dependency>
<groupId>cn.fateverse</groupId>
<artifactId>log-api</artifactId>
</dependency>
<dependency>
<groupId>cn.fateverse</groupId>
<artifactId>common-decrypt</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
<!-- Sharding-JDBC -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>${sharding.version}</version>
</dependency>
<!-- ip2region -->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>2.7.0</version>
</dependency>
<!--rabbitmq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1-jre</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.3</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,22 @@
package cn.fateverse.log;
import cn.fateverse.common.security.annotation.EnableSecurity;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* @author Clay
* @date 2023-05-24
*/
@EnableSecurity
@EnableDiscoveryClient
@SpringBootApplication
public class LogApplication {
public static void main(String[] args) {
SpringApplication.run(LogApplication.class, args);
System.out.println("日志服务启动成功");
}
}

View File

@@ -0,0 +1,68 @@
package cn.fateverse.log.configuration;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadata;
import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider;
import org.springframework.boot.jdbc.metadata.HikariDataSourcePoolMetadata;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Clay
* @date 2023-05-25
*/
@Configuration
public class DataSourceHealthConfig {
/**
* 配置数据源池元数据提供者
*
* @return 数据源池元数据提供者
*/
@Bean
DataSourcePoolMetadataProvider dataSourcePoolMetadataProvider() {
return dataSource -> dataSource instanceof HikariDataSource
// 如果使用的数据源没有对应的 DataSourcePoolMetadata 实现的话也可以全部使用 NotAvailableDataSourcePoolMetadata
? new HikariDataSourcePoolMetadata((HikariDataSource) dataSource)
: new NotAvailableDataSourcePoolMetadata();
}
/**
* 不可用的数据源池元数据.
*/
private static class NotAvailableDataSourcePoolMetadata implements DataSourcePoolMetadata {
@Override
public Float getUsage() {
return null;
}
@Override
public Integer getActive() {
return null;
}
@Override
public Integer getMax() {
return null;
}
@Override
public Integer getMin() {
return null;
}
@Override
public String getValidationQuery() {
// 该字符串是适用于MySQL的简单查询语句,用于检查检查,其他数据库可能需要更换
return "select 1";
}
@Override
public Boolean getDefaultAutoCommit() {
return null;
}
}
}

View File

@@ -0,0 +1,54 @@
package cn.fateverse.log.configuration;
import cn.fateverse.common.core.utils.ObjectUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "log")
public class RabbitProperties {
/**
* 交换机名称
*/
public String exchangeLog = "exchange.log";
/**
* 队列名称
*/
private String queueLog = "queue.log";
/**
* 路由
*/
public String routingKey = "log.routing";
public String getExchangeLog() {
return exchangeLog;
}
public void setExchangeLog(String exchangeLog) {
if (!ObjectUtils.isEmpty(exchangeLog)) {
this.exchangeLog = exchangeLog;
}
}
public String getQueueLog() {
return queueLog;
}
public void setQueueLog(String queueLog) {
if (!ObjectUtils.isEmpty(queueLog)) {
this.queueLog = queueLog;
}
}
public String getRoutingKey() {
return routingKey;
}
public void setRoutingKey(String routingKey) {
if (!ObjectUtils.isEmpty(routingKey)) {
this.routingKey = routingKey;
}
}
}

View File

@@ -0,0 +1,35 @@
package cn.fateverse.log.configuration;
import cn.hutool.core.convert.Convert;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
/**
* @author Clay
* @date 2023-05-25
*/
public class TablePreciseShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
/**
* 执行分片操作
*
* @param tableNames 表名集合
* @param preciseShardingValue 精确分片值
* @return 分片后的表名
*/
@Override
public String doSharding(Collection<String> tableNames, PreciseShardingValue<Long> preciseShardingValue) {
BigDecimal bigDecimal = new BigDecimal(preciseShardingValue.getValue());
long value = bigDecimal.divide(new BigDecimal(10), 0, RoundingMode.DOWN).longValue();
int index = Convert.toInt(value & 1);
List<String> list = new ArrayList<>(tableNames);
return list.get(index);
}
}

View File

@@ -0,0 +1,78 @@
package cn.fateverse.log.controller;
import cn.fateverse.common.core.result.Result;
import cn.fateverse.common.core.result.page.TableDataInfo;
import cn.fateverse.common.decrypt.annotation.Encrypt;
import cn.fateverse.common.security.annotation.Anonymity;
import cn.fateverse.log.query.LoginLogQuery;
import cn.fateverse.log.service.LoginInfoService;
import cn.fateverse.log.vo.LoginInfoVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
/**
* @author Clay
* @date 2022/11/2
*/
@Api(tags = "登录日志管理")
@Anonymity
@RestController
@RequestMapping("/login-info")
public class LoginInfoController {
private final LoginInfoService loginInfoService;
public LoginInfoController(LoginInfoService loginInfoService) {
this.loginInfoService = loginInfoService;
}
/**
* 查询登录日志信息
* @param loginLogQuery
* @return
*/
@GetMapping("/list")
@ApiOperation("查询日志信息")
@PreAuthorize("@ss.hasPermission('admin:log:list')")
public Result<TableDataInfo<LoginInfoVo>> loginSearch(LoginLogQuery loginLogQuery){
TableDataInfo<LoginInfoVo> dataTable = loginInfoService.search(loginLogQuery);
return Result.ok(dataTable);
}
/**
* 删除登录日志 单个/批量
* @param infoIds
* @return
*/
@DeleteMapping("/{infoIds}")
@ApiOperation("登录日志删除")
@PreAuthorize("@ss.hasPermission('admin:log:del')")
public Result<Void> loginRemove(@PathVariable Long[] infoIds){
if (infoIds.length==0){
return Result.error("id不能为空");
}
loginInfoService.delete(infoIds);
return Result.ok();
}
/**
* 根据id 查询日志详细信息
* @param infoId
* @return
*/
@Encrypt
@GetMapping("/{infoId}")
@ApiOperation("查询登录日志")
@PreAuthorize("@ss.hasPermission('admin:log:query')")
public Result<LoginInfoVo> getInfo(@PathVariable("infoId") Long infoId){
LoginInfoVo loginInfo = loginInfoService.select(infoId);
return Result.ok(loginInfo);
}
}

View File

@@ -0,0 +1,67 @@
package cn.fateverse.log.controller;
import cn.fateverse.common.core.result.Result;
import cn.fateverse.common.core.result.page.TableDataInfo;
import cn.fateverse.common.security.annotation.Anonymity;
import cn.fateverse.log.query.OperationLogQuery;
import cn.fateverse.log.service.OperationService;
import cn.fateverse.log.vo.OperationLogVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
/**
* 操作日志控制器
*
* @author Clay
* @date 2022/11/1
*/
@Api(tags = "操作日志管理")
@Slf4j
@RestController
@RequestMapping("/log")
public class OperationLogController {
private final OperationService operationService;
public OperationLogController(OperationService operationService) {
this.operationService = operationService;
}
/**
* @param operationLogQuery
* @return
*/
@GetMapping("/list")
@Anonymity
@ApiOperation("查询日志信息")
@PreAuthorize("@ss.hasPermission('admin:log:list')")
public Result<TableDataInfo<OperationLogVo>> SearchLog(OperationLogQuery operationLogQuery) {
TableDataInfo<OperationLogVo> dataTable = operationService.search(operationLogQuery);
return Result.ok(dataTable);
}
@GetMapping("/{operId}")
@ApiOperation("查询日志信息")
@PreAuthorize("@ss.hasPermission('admin:log:list')")
public Result<OperationLogVo> SearchLog(@PathVariable Long operId) {
OperationLogVo operationLogVo = operationService.select(operId);
return Result.ok(operationLogVo);
}
@DeleteMapping("/{operIds}")
@ApiOperation("操作日志删除")
@PreAuthorize("@ss.hasPermission('admin:log:del')")
public Result<Integer> OperationInfoRemove(@PathVariable Long[] operIds) {
if (operIds.length == 0) {
return Result.error("id不能为空");
}
operationService.delete(operIds);
return Result.ok();
}
}

View File

@@ -0,0 +1,55 @@
package cn.fateverse.log.dubbo;
import cn.fateverse.log.configuration.RabbitProperties;
import cn.fateverse.log.service.LoginInfoService;
import cn.fateverse.log.service.OperationService;
import cn.fateverse.log.entity.LoginInfo;
import cn.fateverse.log.entity.OperationLog;
import cn.fateverse.log.mq.RabbitConfig;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.Executor;
/**
* @author Clay
* @date 2023-02-20
*/
@DubboService
public class DubboLogServiceImpl implements DubboLogService {
private final LoginInfoService loginInfoService;
@Resource
private RabbitTemplate rabbitTemplate;
@Resource
private RabbitProperties properties;
private final OperationService operationService;
private final Executor executor;
public DubboLogServiceImpl(LoginInfoService loginInfoService, OperationService operationService, Executor executor) {
this.loginInfoService = loginInfoService;
this.operationService = operationService;
this.executor = executor;
}
@Override
public void batchSaveLog(List<OperationLog> list) {
executor.execute(()->{
rabbitTemplate.invoke(operations -> {
rabbitTemplate.convertAndSend(properties.getExchangeLog(), properties.getRoutingKey(), list);
return rabbitTemplate.waitForConfirms(5000);
});
});
}
@Override
public void saveLoginInfo(LoginInfo info) {
loginInfoService.save(info);
}
}

View File

@@ -0,0 +1,46 @@
package cn.fateverse.log.mapper;
import cn.fateverse.log.entity.LoginInfo;
import cn.fateverse.log.query.LoginLogQuery;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* @author Clay
* @date 2022/11/2
*/
public interface LoginInfoMapper {
/**
* 保存登录日志信息
*
* @param loginInfo
* @return
*/
int save(LoginInfo loginInfo);
/**
* 查询登录日志
*
* @param loginLogQuery
* @return
*/
List<LoginInfo> search(LoginLogQuery loginLogQuery);
/**
* 删除登录日志
*
* @param infoIds 日志id
*/
int delete(Long[] infoIds);
/**
* 查询登录日志详情
*
* @param infoId 日志id
* @return 日志详情
*/
LoginInfo selectById(@Param("infoId") Long infoId);
}

View File

@@ -0,0 +1,51 @@
package cn.fateverse.log.mapper;
import cn.fateverse.common.core.entity.PageInfo;
import cn.fateverse.log.entity.OperationLog;
import cn.fateverse.log.query.OperationLogQuery;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* @author Clay
* @date 2022/11/1
*/
public interface OperationMapper {
/**
* 批量保存日志信息
*
* @param operationLogList 日志保存信息
*/
int batchSave(List<OperationLog> operationLogList);
/**
* 获取操作日志详情
*
* @param operId 操作日志id
* @return 操作日志返回对象
*/
OperationLog selectById(@Param("operId") Long operId);
/**
* 查询操作日志
*
* @param operationLogQuery
* @return
*/
List<OperationLog> search(@Param("operation") OperationLogQuery operationLogQuery);
List<OperationLog> searchSubQuery(@Param("operation") OperationLogQuery operationLogQuery, @Param("start") Integer start, @Param("size") Integer size);
Long searchCount(@Param("operation") OperationLogQuery operationLogQuery, @Param("start") Integer start, @Param("size") Integer size);
/**
* 删除日志
*
* @param operIds 操作日志id
*/
int delete(Long[] operIds);
}

View File

@@ -0,0 +1,83 @@
package cn.fateverse.log.mq;
import cn.fateverse.log.service.OperationService;
import cn.fateverse.log.entity.OperationLog;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author Clay
* @date 2023-08-02
*/
@Slf4j
@Component
public class RabbiListener {
private final ThreadPoolExecutor executor;
private final OperationService operationService;
/**
* 最大重试次数
*/
private static final int MAX_RETRIES = 3;
public RabbiListener(OperationService operationService) {
this.operationService = operationService;
executor = new ThreadPoolExecutor(2,
4,
60,
TimeUnit.SECONDS,new LinkedBlockingDeque<>(128),
new ThreadFactoryBuilder().setNameFormat("rabbit_%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy());
}
@RabbitListener(queues = "#{queueLog.name}")
public void consumeLog(List<OperationLog> operationLogList, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) {
executor.submit(() -> saveLog(operationLogList, channel, tag));
}
public void saveLog(List<OperationLog> operationLogList, Channel channel, long tag) {
int retryCount = 0;
boolean consumeStart = false;
while (retryCount < MAX_RETRIES) {
retryCount++;
log.info("消费业务!");
try {
operationService.batchSave(operationLogList);
consumeStart = true;
break;
} catch (Exception e) {
e.printStackTrace();
retryCount++;
log.error("操作日志失败,次数:" + retryCount);
log.error("异常信息", e);
}
}
try {
if (consumeStart) {
channel.basicAck(tag, false);
} else {
channel.basicNack(tag, false, false);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,63 @@
package cn.fateverse.log.mq;
import cn.fateverse.log.configuration.RabbitProperties;
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.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
/**
* @author Clay
* @date 2023-08-02
*/
@Configuration
@EnableConfigurationProperties(RabbitProperties.class)
public class RabbitConfig {
@Resource
private RabbitProperties properties;
/**
* 交换机
*
* @return topic交换机
*/
@Bean
public TopicExchange exchangeLog() {
return new TopicExchange(properties.getExchangeLog());
}
/**
* 队列
*
* @return 队列
*/
@Bean
public Queue queueLog() {
Map<String, Object> args = new HashMap<>(8);
args.put("x-expires", 60000);
return new Queue(properties.getQueueLog(), true, false, false, args);
}
/**
* 当前节点绑定的mq
*
* @param queueLog 队列
* @param exchangeLog 交换机
* @return 绑定结果
*/
@Bean
public Binding binding(Queue queueLog, TopicExchange exchangeLog) {
return BindingBuilder.bind(queueLog).to(exchangeLog).with(properties.getRoutingKey());
}
}

View File

@@ -0,0 +1,43 @@
package cn.fateverse.log.service;
import cn.fateverse.common.core.result.page.TableDataInfo;
import cn.fateverse.log.entity.LoginInfo;
import cn.fateverse.log.query.LoginLogQuery;
import cn.fateverse.log.vo.LoginInfoVo;
/**
* @author Clay
* @date 2022/11/2
*/
public interface LoginInfoService {
/**
* 保存登录日志信息
*
* @param info
* @return
*/
int save(LoginInfo info);
/**
* 登录日志查询
*
* @param loginLogQuery 日志查询条件
* @return 表格数据
*/
TableDataInfo<LoginInfoVo> search(LoginLogQuery loginLogQuery);
/**
* 删除登录日志
*
* @param infoIds 日志id
*/
void delete(Long[] infoIds);
/**
* 查询登录日志详情
*
* @param infoId 日志id
* @return 日志详情
*/
LoginInfoVo select(Long infoId);
}

View File

@@ -0,0 +1,48 @@
package cn.fateverse.log.service;
import cn.fateverse.common.core.result.page.TableDataInfo;
import cn.fateverse.log.entity.OperationLog;
import cn.fateverse.log.query.OperationLogQuery;
import cn.fateverse.log.vo.OperationLogVo;
import java.util.List;
/**
* 操作日志service服务
*
* @author Clay
* @date 2022/11/1
*/
public interface OperationService {
/**
* 批量保存日志信息
*
* @param operationLogList 需要保存的日志信息
*/
void batchSave(List<OperationLog> operationLogList);
/**
* 获取操作日志详情
*
* @param operId 操作日志id
* @return 操作日志返回对象
*/
OperationLogVo select(Long operId);
/**
* 获取操作日志
*
* @param operationLogQuery 操作日志查询
* @return 操作日志列表
*/
TableDataInfo<OperationLogVo> search(OperationLogQuery operationLogQuery);
/**
* 删除日志
*
* @param operIds 操作日志id
*/
void delete(Long[] operIds);
}

View File

@@ -0,0 +1,71 @@
package cn.fateverse.log.service.impl;
import cn.fateverse.log.entity.LoginInfo;
import cn.fateverse.log.mapper.LoginInfoMapper;
import cn.fateverse.log.query.LoginLogQuery;
import cn.fateverse.log.service.LoginInfoService;
import cn.fateverse.log.utils.IpLocation;
import cn.fateverse.log.vo.LoginInfoVo;
import cn.hutool.core.util.StrUtil;
import cn.fateverse.common.core.result.page.TableDataInfo;
import cn.fateverse.common.mybatis.utils.PageUtils;
import cn.fateverse.common.security.entity.LoginUser;
import cn.fateverse.common.security.service.TokenService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author Clay
* @date 2022/11/2
*/
@Slf4j
@Service
public class LoginInfoServiceImpl implements LoginInfoService {
private final LoginInfoMapper loginInfoMapper;
private final TokenService tokenService;
public LoginInfoServiceImpl(LoginInfoMapper loginInfoMapper,
TokenService tokenService) {
this.loginInfoMapper = loginInfoMapper;
this.tokenService = tokenService;
}
@Override
public int save(LoginInfo info) {
String ipddr = info.getIpddr();
if (!StrUtil.isEmpty(ipddr)) {
info.setLoginLocation(IpLocation.getRegion(ipddr.split(",")[0]));
}
if (!StrUtil.isEmpty(info.getUuid())) {
LoginUser loginUser = tokenService.getLoginUserUUid(info.getUuid());
if (StrUtil.isEmpty(loginUser.getLoginLocation())) {
loginUser.setLoginLocation(info.getLoginLocation());
tokenService.setLoginUser(loginUser);
}
}
return loginInfoMapper.save(info);
}
@Override
public TableDataInfo<LoginInfoVo> search(LoginLogQuery loginLogQuery) {
PageUtils.startPage();
List<LoginInfo> loginInfos = loginInfoMapper.search(loginLogQuery);
return PageUtils.convertDataTable(loginInfos, LoginInfoVo::toLoginInfoVo);
}
@Override
public void delete(Long[] infoIds) {
loginInfoMapper.delete(infoIds);
}
@Override
public LoginInfoVo select(Long infoId) {
LoginInfo loginInfo = loginInfoMapper.selectById(infoId);
return LoginInfoVo.toLoginInfoVo(loginInfo);
}
}

View File

@@ -0,0 +1,72 @@
package cn.fateverse.log.service.impl;
import cn.fateverse.common.security.utils.SecurityUtils;
import cn.hutool.core.util.StrUtil;
import cn.fateverse.common.core.result.page.TableDataInfo;
import cn.fateverse.common.mybatis.utils.PageUtils;
import cn.fateverse.log.entity.OperationLog;
import cn.fateverse.log.mapper.OperationMapper;
import cn.fateverse.log.query.OperationLogQuery;
import cn.fateverse.log.service.OperationService;
import cn.fateverse.log.utils.IpLocation;
import cn.fateverse.log.vo.OperationLogVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* @author Clay
* @date 2022/11/1
*/
@Slf4j
@Service
public class OperationServiceImpl implements OperationService {
private final OperationMapper operationMapper;
public OperationServiceImpl(OperationMapper operationMapper) {
this.operationMapper = operationMapper;
}
@Override
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void batchSave(List<OperationLog> operationLogList) {
if (null == operationLogList || operationLogList.isEmpty()) {
return;
}
operationLogList.forEach(operationLog -> {
if (!StrUtil.isEmpty(operationLog.getOperIp())) {
operationLog.setOperLocation(IpLocation.getRegion(operationLog.getOperIp().split(",")[0]));
}
});
operationMapper.batchSave(operationLogList);
}
@Override
public OperationLogVo select(Long operId) {
OperationLog operationLog = operationMapper.selectById(operId);
OperationLogVo operationLogVo = OperationLogVo.toOperationLogVo(operationLog);
if (SecurityUtils.isAdmin()) {
operationLogVo.setErrorStackTrace(operationLog.getErrorStackTrace());
}
return operationLogVo;
}
@Override
public TableDataInfo<OperationLogVo> search(OperationLogQuery operationLogQuery) {
PageUtils.startPage();
List<OperationLog> operationLogs = operationMapper.search(operationLogQuery);
return PageUtils.convertDataTable(operationLogs, OperationLogVo::toOperationLogVo);
}
@Override
public void delete(Long[] operIds) {
operationMapper.delete(operIds);
}
}

View File

@@ -0,0 +1,55 @@
package cn.fateverse.log.utils;
import org.apache.commons.io.IOUtils;
import org.lionsoul.ip2region.xdb.Searcher;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.stream.Collectors;
/**
* @author Clay
* @date 2023-06-02
*/
public class IpLocation {
private static volatile Searcher searcher = null;
private static Searcher getDbSearcher() {
if (null == searcher) {
synchronized (IpLocation.class) {
if (null == searcher) {
InputStream inputStream = IpLocation.class.getResourceAsStream("/ip2region.xdb");
try {
searcher = Searcher.newWithBuffer(IOUtils.toByteArray(inputStream));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
return searcher;
}
public static String getRegion(String ip) {
String region;
try {
Searcher searcher = getDbSearcher();
if (null == searcher) {
return "";
}
region = searcher.search(ip);
region = region.replaceAll("0\\|", "");
region = Arrays.stream(region.split("\\|")).distinct().collect(Collectors.joining(" "));
} catch (Exception e) {
e.printStackTrace();
return "";
}
return region;
}
}

View File

@@ -0,0 +1,13 @@
# Spring
spring:
cloud:
nacos:
discovery:
# 服务注册地址
server-addr: 10.7.127.189:38848
namespace: clay
dubbo:
registry:
parameters:
namespace: dubbo-clay

View File

@@ -0,0 +1,11 @@
# Spring
spring:
cloud:
nacos:
discovery:
# 服务注册地址
server-addr: nacos.fateverse.svc.cluster.local:8848
management:
server:
port: 9595

View File

@@ -0,0 +1,42 @@
# Tomcat
server:
port: 10005
# Spring
spring:
application:
# 应用名称
name: log
profiles:
# 环境配置
active: dev
main:
allow-bean-definition-overriding: true
cloud:
nacos:
discovery:
# 服务注册地址
server-addr: 192.168.101.108:8848
username: nacos
password: nacos
namespace: ${spring.profiles.active}
config:
# 配置中心地址
server-addr: ${spring.cloud.nacos.discovery.server-addr}
file-extension: yaml
namespace: ${spring.profiles.active}
shared-configs:
- data-id: application-${spring.profiles.active}.yaml
refresh: true
dubbo:
application:
name: dubbo-${spring.application.name}
protocol:
name: dubbo
port: -1
registry:
address: nacos://${spring.cloud.nacos.discovery.server-addr}
username: ${spring.cloud.nacos.discovery.username}
password: ${spring.cloud.nacos.discovery.password}
parameters:
namespace: dubbo-${spring.profiles.active}

Binary file not shown.

View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.fateverse.log.mapper.LoginInfoMapper">
<resultMap type="cn.fateverse.log.entity.LoginInfo" id="LoginInfoResult">
<id property="infoId" column="info_id"/>
<result property="userName" column="user_name"/>
<result property="ipddr" column="ipaddr"/>
<result property="loginLocation" column="login_location"/>
<result property="browser" column="browser"/>
<result property="os" column="os"/>
<result property="state" column="state"/>
<result property="msg" column="msg"/>
<result property="loginTime" column="login_time"/>
</resultMap>
<insert id="save" parameterType="cn.fateverse.log.entity.LoginInfo">
insert into sys_login_infor
(info_id, user_name, ipaddr, login_location, browser, os, state, msg, login_time)
values
(#{infoId}, #{userName}, #{ipddr}, #{loginLocation}, #{browser}, #{os}, #{state}, #{msg}, #{loginTime})
</insert>
<select id="search" resultMap="LoginInfoResult"
parameterType="cn.fateverse.log.query.LoginLogQuery">
select info_id, user_name, ipaddr, login_location, browser, os, state, msg, login_time
from sys_login_infor
<where>
<if test="ipAddr !=null and ipAddr !=''">
and ipaddr like concat('%',#{ipAddr},'%')
</if>
<if test="userName !=null and userName !=''">
and user_name like concat('%',#{userName},'%')
</if>
<if test="state !=null and state !=''">
and state = #{state}
</if>
<if test="startTime !=null">
and login_time between #{startTime} and #{endTime}
</if>
</where>
order by login_time desc
</select>
<delete id="delete" parameterType="String">
delete from sys_login_infor where info_id in
<foreach collection="array" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<select id="selectById" resultMap="LoginInfoResult">
select info_id, user_name, ipaddr, login_location, browser, os, state, msg, login_time
from sys_login_infor
where info_id =#{infoId}
</select>
</mapper>

View File

@@ -0,0 +1,176 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.fateverse.log.mapper.OperationMapper">
<resultMap type="cn.fateverse.log.entity.OperationLog" id="OperationLogResult">
<id property="operId" column="oper_id"/>
<result property="title" column="title"/>
<result property="businessType" column="business_type"/>
<result property="method" column="method"/>
<result property="requestMethod" column="request_method"/>
<result property="operatorType" column="operator_type"/>
<result property="operName" column="oper_name"/>
<result property="deptName" column="dept_name"/>
<result property="operUrl" column="oper_url"/>
<result property="operIp" column="oper_ip"/>
<result property="operLocation" column="oper_location"/>
<result property="operParam" column="oper_param"/>
<result property="jsonResult" column="json_result"/>
<result property="state" column="state"/>
<result property="errorMsg" column="error_msg"/>
<result property="operTime" column="oper_time"/>
<result property="consumeTime" column="consume_time"/>
</resultMap>
<insert id="batchSave">
insert into sys_operation_log
(oper_id, title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip,
oper_location, oper_param, json_result, state, error_msg, error_stack_trace, oper_time, consume_time)
values
<foreach collection="list" item="log" separator=",">
(#{log.operId}, #{log.title}, #{log.businessType}, #{log.method}, #{log.requestMethod}, #{log.operatorType},
#{log.operName},
#{log.deptName}, #{log.operUrl}, #{log.operIp}, #{log.operLocation}, #{log.operParam}, #{log.jsonResult},
#{log.state}, #{log.errorMsg},
#{log.errorStackTrace}, #{log.operTime}, #{log.consumeTime})
</foreach>
</insert>
<select id="searchSubQuery" resultMap="OperationLogResult"
parameterType="cn.fateverse.log.query.OperationLogQuery">
select oper_id, title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url,
oper_ip, oper_location, state, oper_time, consume_time
from sys_operation_log
<where>
oper_id >= (select oper_id from sys_operation_log
<where>
<if test="operation.title !=null and operation.title !=''">
and title like concat('%',#{operation.title},'%')
</if>
<if test="operation.operName !=null and operation.operName !=''">
and oper_name like concat('%',#{operation.operName},'%')
</if>
<if test="operation.businessType !=null ">
and business_type like concat('%',#{operation.businessType},'%')
</if>
<if test="operation.state !=null and operation.state !=''">
and state =#{operation.state}
</if>
<if test="operation.startTime !=null ">
and oper_time &gt;=#{operation.startTime}
</if>
<if test="operation.endTime !=null ">
and oper_time &lt;=#{operation.endTime}
</if>
</where>
limit #{start},1
)
<if test="operation.title !=null and operation.title !=''">
and title like concat('%',#{operation.title},'%')
</if>
<if test="operation.operName !=null and operation.operName !=''">
and oper_name like concat('%',#{operation.operName},'%')
</if>
<if test="operation.businessType !=null ">
and business_type like concat('%',#{operation.businessType},'%')
</if>
<if test="operation.state !=null and operation.state !=''">
and state =#{operation.state}
</if>
<if test="operation.startTime !=null ">
and oper_time &gt;=#{operation.startTime}
</if>
<if test="operation.endTime !=null ">
and oper_time &lt;=#{operation.endTime}
</if>
</where>
order by oper_time desc limit #{size}
</select>
<select id="search" resultMap="OperationLogResult"
parameterType="cn.fateverse.log.query.OperationLogQuery">
select oper_id, title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url,
oper_ip, oper_location, state, oper_time, consume_time
from sys_operation_log
<where>
<if test="operation.title !=null and operation.title !=''">
and title like concat('%',#{operation.title},'%')
</if>
<if test="operation.operName !=null and operation.operName !=''">
and oper_name like concat('%',#{operation.operName},'%')
</if>
<if test="operation.businessType !=null ">
and business_type like concat('%',#{operation.businessType},'%')
</if>
<if test="operation.state !=null and operation.state !=''">
and state =#{operation.state}
</if>
<if test="operation.startTime !=null ">
and oper_time &gt;=#{operation.startTime}
</if>
<if test="operation.endTime !=null ">
and oper_time &lt;=#{operation.endTime}
</if>
</where>
order by oper_time desc
</select>
<select id="searchCount" resultType="java.lang.Long">
select count(*) from sys_operation_log
<where>
<if test="operation.title !=null and operation.title !=''">
and title like concat('%',#{operation.title},'%')
</if>
<if test="operation.operName !=null and operation.operName !=''">
and oper_name like concat('%',#{operation.operName},'%')
</if>
<if test="operation.businessType !=null ">
and business_type like concat('%',#{operation.businessType},'%')
</if>
<if test="operation.state !=null and operation.state !=''">
and state =#{operation.state}
</if>
<if test="operation.startTime !=null ">
and oper_time &gt;=#{operation.startTime}
</if>
<if test="operation.endTime !=null ">
and oper_time &lt;=#{operation.endTime}
</if>
</where>
</select>
<delete id="delete" parameterType="String">
delete from sys_operation_log where oper_id in
<foreach collection="array" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<select id="selectById" resultMap="OperationLogResult">
select oper_id,
title,
business_type,
method,
request_method,
operator_type,
oper_name,
dept_name,
oper_url,
oper_ip,
oper_location,
oper_param,
json_result,
state,
error_msg,
error_stack_trace,
oper_time,
consume_time
from sys_operation_log
where oper_id = #{operId}
</select>
</mapper>

24
log/pom.xml Normal file
View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>fateverse</artifactId>
<groupId>cn.fateverse</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>log</artifactId>
<packaging>pom</packaging>
<modules>
<module>log-api</module>
<module>log-biz</module>
</modules>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
</project>