Commit 77c18bba by henry

修改日志记录

1 parent c246eec2
Showing with 1901 additions and 0 deletions
...@@ -16,5 +16,29 @@ ...@@ -16,5 +16,29 @@
<maven.compiler.target>8</maven.compiler.target> <maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- mybatis-plus-extension -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
</dependencies>
</project> </project>
\ No newline at end of file
/**
* Copyright (C) 2018-2020
* All rights reserved, Designed By www.yixiang.co
* 注意:
* 本软件为www.yixiang.co开发研制
*/
package org.arch.modules.sysmanage.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.awt.*;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.List;
import java.util.Set;
/**
* @author hupeng
* @date 2020-05-14
*/
@Data
@ApiModel(value="用户详细信息", description="用户详细信息")
public class UserDto implements Serializable {
@ApiModelProperty(value = "用户主键",hidden = true)
private Long id;
@ApiModelProperty(value = "用户名称/昵称")
private String name;
@ApiModelProperty(value = "用户编码")
private String userCode;
@ApiModelProperty(value = "用户isc编码")
private String iscUserId;
/**
* 员工类型
*/
@ApiModelProperty(value = "员工类型",notes = "1:正式员工 0 临时员工")
private Integer userType;
/**
* 生日
*/
@ApiModelProperty(value = "生日")
private String birthday;
/**
* 工作年限
*/
@ApiModelProperty(value = "工作年限")
private Double workYear;
/**
* 入职时间
*/
@ApiModelProperty(value = "入职时间")
private String entryTime;
@ApiModelProperty(value = "用户性别",notes = "0:未知 1:男 2:女")
private String sex;
@ApiModelProperty(value = "手机号")
private String phone;
@ApiModelProperty(value = "启用/禁用状态",notes = "0:禁用,1:启用")
private Integer enabled;
@ApiModelProperty(value = "菜单集合 说明:用户可以操作的菜单或者按钮",notes = "用户可访问的菜单或者按钮")
private Set<Menu> menus;
@ApiModelProperty(value = "数据权限范围信息")
private String dataPermission;
@ApiModelProperty(value = "创建时间")
private Timestamp createTime;
@ApiModelProperty(value = "菜单id集合",notes = "菜单id集合")
private List<Long> menuIds;
@ApiModelProperty(value = "按钮id集合",notes = "按钮id集合")
private List<Long> buttionIds;
@ApiModelProperty(value = "权限标识集合",notes = "权限标识集合")
private Set<String> perms;
@ApiModelProperty(value = "isctoken")
private String token;
@ApiModelProperty(value = "部门路径",notes = "部门路径")
private String orgSimpleName;
}
package org.arch.utils;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
/**
*
* 功能说明:JSON转换工具类
*/
@Slf4j
public class JsonUtils {
//单例
private static ObjectMapper mapper = new ObjectMapper();
/**
* 对象转换成JSON
*/
public static String object2Json(Object obj){
String json = null;
try {
json = mapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
log.error("对象转换JSON失败...");
e.printStackTrace();
}
return json;
}
/**
* json格式数据转换成对象(对象包括Bean对象,Map和List等)
*/
public static Object json2Object(String json,Class<? extends Object> clazz){
Object object = null;
try {
object = mapper.readValue(json, clazz);
} catch (JsonParseException e) {
log.error("JsonParseException:JSON转换成对象失败...");
e.printStackTrace();
} catch (JsonMappingException e) {
log.error("JsonMappingException:JSON转换成对象失败...");
e.printStackTrace();
} catch (IOException e) {
log.error("IOException:JSON转换成对象失败...");
e.printStackTrace();
}
return object;
}
}
package org.arch.utils;
import io.jsonwebtoken.*;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
public class JwtUitls {
/**
* 过期时间5分钟
*/
private static final long EXPIRE_TIME=1000*5*60*1000;
/**
* 加密密钥
*/
private static final String KEY = "D30CCEEC367C55F0E4C7B3BE8207C5DD";
/**
* 生成token
* @param userName 用户id
* @param userName 用户名
* @return
*/
public String createToken(String userName){
Map<String,Object> header = new HashMap();
header.put("typ","JWT");
header.put("alg","HS256");
//setID:用户ID
//setExpiration:token过期时间 当前时间+有效时间
//setSubject:用户名
//setIssuedAt:token创建时间
//signWith:加密方式
JwtBuilder builder = Jwts.builder().setHeader(header)
.setId(userName)
.setExpiration(new Date(System.currentTimeMillis()+EXPIRE_TIME))
.setSubject(userName)
.setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256,KEY);
return builder.compact();
}
/**
* 验证token是否有效
* @param token 请求头中携带的token
* @return token验证结果 2-token过期;1-token认证通过;0-token认证失败
*/
public int verify(String token){
Claims claims = null;
try {
//token过期后,会抛出ExpiredJwtException 异常,通过这个来判定token过期,
claims = Jwts.parser().setSigningKey(KEY).parseClaimsJws(token).getBody();
}catch (ExpiredJwtException e){
return 2;
}
//从token中获取用户id,查询该Id的用户是否存在,存在则token验证通过
String id = claims.getId();
// UserStudent userStudent = userStudentMapper.selectById(id);
// UserLoginLog userLoginLog =userLoginLogService.getToKen(token);
/* if(userLoginLog != null){
return 1;
}else{
return 0;
}*/
return 0;
}
}
/**
* Copyright (C) 2018-2020
* All rights reserved, Designed By www.yixiang.co
* 注意:
* 本软件为www.yixiang.co开发研制
*/
package org.arch.utils;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Calendar;
import java.util.Date;
/**
* @author Zheng Jie
* 字符串工具类, 继承org.apache.commons.lang3.StringUtils类
*/
public class StringUtils extends org.apache.commons.lang3.StringUtils {
private static final char SEPARATOR = '_';
private static final String UNKNOWN = "unknown";
/**
* 驼峰命名法工具
*
* @return toCamelCase(" hello_world ") == "helloWorld"
* toCapitalizeCamelCase("hello_world") == "HelloWorld"
* toUnderScoreCase("helloWorld") = "hello_world"
*/
public static String toCamelCase(String s) {
if (s == null) {
return null;
}
s = s.toLowerCase();
StringBuilder sb = new StringBuilder(s.length());
boolean upperCase = false;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == SEPARATOR) {
upperCase = true;
} else if (upperCase) {
sb.append(Character.toUpperCase(c));
upperCase = false;
} else {
sb.append(c);
}
}
return sb.toString();
}
/**
* 驼峰命名法工具
*
* @return toCamelCase(" hello_world ") == "helloWorld"
* toCapitalizeCamelCase("hello_world") == "HelloWorld"
* toUnderScoreCase("helloWorld") = "hello_world"
*/
public static String toCapitalizeCamelCase(String s) {
if (s == null) {
return null;
}
s = toCamelCase(s);
return s.substring(0, 1).toUpperCase() + s.substring(1);
}
/**
* 驼峰命名法工具
*
* @return toCamelCase(" hello_world ") == "helloWorld"
* toCapitalizeCamelCase("hello_world") == "HelloWorld"
* toUnderScoreCase("helloWorld") = "hello_world"
*/
static String toUnderScoreCase(String s) {
if (s == null) {
return null;
}
StringBuilder sb = new StringBuilder();
boolean upperCase = false;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
boolean nextUpperCase = true;
if (i < (s.length() - 1)) {
nextUpperCase = Character.isUpperCase(s.charAt(i + 1));
}
if ((i > 0) && Character.isUpperCase(c)) {
if (!upperCase || !nextUpperCase) {
sb.append(SEPARATOR);
}
upperCase = true;
} else {
upperCase = false;
}
sb.append(Character.toLowerCase(c));
}
return sb.toString();
}
/**
* 获取ip地址
*/
public static String getIp(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
String comma = ",";
String localhost = "127.0.0.1";
if (ip.contains(comma)) {
ip = ip.split(",")[0];
}
if (localhost.equals(ip)) {
// 获取本机真正的ip地址
try {
ip = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
return ip;
}
/**
* 获得当天是周几
*/
public static String getWeekDay(){
String[] weekDays = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
Calendar cal = Calendar.getInstance();
cal.setTime(new Date());
int w = cal.get(Calendar.DAY_OF_WEEK) - 1;
if (w < 0){
w = 0;
}
return weekDays[w];
}
}
...@@ -22,5 +22,12 @@ ...@@ -22,5 +22,12 @@
<maven.compiler.target>8</maven.compiler.target> <maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
<dependencies>
<dependency>
<groupId>org.arch</groupId>
<artifactId>arch-base</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project> </project>
\ No newline at end of file
...@@ -17,5 +17,47 @@ ...@@ -17,5 +17,47 @@
<maven.compiler.target>8</maven.compiler.target> <maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
<dependencies>
<dependency>
<groupId>org.arch</groupId>
<artifactId>mybatis</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- druid数据源驱动 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
</dependency>
<dependency>
<groupId>org.arch</groupId>
<artifactId>arch-base</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
</dependencies>
</project> </project>
\ No newline at end of file
package org.arch.annotation;
import org.arch.logenum.LogEventTypeEnum;
import org.arch.logenum.LogLevEnum;
import org.arch.logenum.LogTypeEnum;
import java.lang.annotation.*;
/**
* @Date 2023/3/9 13:03
* @description: 日志配置
* @Title: LogConfig
* @Package com.xiaobai.aspect.annotation
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AutoLog {
/**
* 功能描述
*/
LogEventTypeEnum value();
LogLevEnum LogLevValue();
LogTypeEnum LogTypeValue();
/**
* 是否保存请求的参数
*/
boolean isSaveRequestData() default true;
/**
* 是否保存响应的参数
*/
boolean isSaveResponseData() default true;
//操作描述
String description() default "";
}
package org.arch.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String value() default "";
}
package org.arch.annotation;
import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperaLogModel {
//模块名
String value();
}
package org.arch.config;
import org.arch.filter.LoginFilter;
import org.arch.filter.SqlFilter;
import org.arch.filter.XssFilter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import javax.servlet.DispatcherType;
import org.springframework.beans.factory.annotation.Value;
@Slf4j
@Configuration
@ComponentScan("org.arch")
public class LogAutoConfiguration {
@Value("${log.enable.flag}")
private boolean logFlag;
@Value("${sql.enable.flag}")
private boolean sqlFlag;
@Value("${xss.enable.flag}")
private boolean xssFlag;
@Autowired // 一定要注入,才会让 DemoFilter中的 @Autowrie生效
private LoginFilter loginFilter;
public LogAutoConfiguration() {
log.info("加载logging配置...");
}
@Bean
public FilterRegistrationBean xssFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.setFilter(new XssFilter());
registration.addUrlPatterns("/*");
registration.setName("xssFilter");
//false则关闭改过滤器
registration.setEnabled(xssFlag);
registration.setOrder(Integer.MAX_VALUE);
return registration;
}
@Bean
public FilterRegistrationBean sqlFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.setFilter(new SqlFilter());
registration.addUrlPatterns("/*");
registration.setName("sqlFilter");
//false则关闭改过滤器
registration.setEnabled(sqlFlag);
registration.setOrder(Integer.MAX_VALUE);
return registration;
}
@Bean
public FilterRegistrationBean loginFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.setFilter(loginFilter);
registration.addUrlPatterns("/*");
registration.setName("loginFilter");
//false则关闭改过滤器
registration.setEnabled(logFlag);
registration.setOrder(Integer.MAX_VALUE);
/*JwtUitls.userLoginLogService = userLoginLogService;//重新赋值!!!!!!!!!!!!!,防止mapper还没加载的时候,@Autowired*/
return registration;
}
}
package org.arch.entity;
/**
* 描述内容
*
* @author xh13k
* {@code @date} 2024/06/12
*/
public class DescContent {
public final static String QUERY_PAGINATED_LIST_ELEMENTS = "查询元素列表";
public final static String QUERY_LIST_ELEMENT_RELATIONSHIPS = "查询元素关系列表";
public final static String QUERY_LIST_VIEWS = "查询视图列表";
public final static String QUERY_OVERALL_ASSET_LIST = "查询总体资产列表";
public final static String QUERY_LIST_PROJECTS = "查询项目列表";
}
package org.arch.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
/**
* 系统操作日志(SysEventLog)实体类
*
* @author makejava
* @since 2024-01-25 09:50:17
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@TableName("sys_event_log")
@ApiModel(value = "sysEventLog对象", description = "系统日志对象")
public class SysEventLog {
/**
* 主键ID
*/
@ApiModelProperty(value = "主键ID")
// @TableId(value = "log_id", type = IdType.AUTO)
private String logId;
/**
* 用户iscID
*/
@ApiModelProperty(value = "用户iscID")
private String userId;
/**
* 用户名
*/
@ApiModelProperty(value = "用户名")
private String username;
/**
* ip地址
*/
@ApiModelProperty(value = "ip地址")
private String ipAddr;
/**
* 创建时间
*/
@ApiModelProperty(value = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 事件类型
*/
@ApiModelProperty(value = "事件类型 0 其他 1 新增 2 修改 3 删除 4 查询 5 授权 6 导出 7 导入 8 登录 9退出")
private String eventType;
/**
* 事件内容
*/
@ApiModelProperty(value = "事件内容 1 日志统计 2 日志数据")
private String eventContent;
/**
* 日志类型
*/
@ApiModelProperty(value = "日志类型 0 系统日志 1 业务日志")
private String logType;
/**
* 日志级别
*/
@ApiModelProperty(value = "日志级别 0 低 1 中 2 高")
private String logLev;
/**
* 是否为异常 0 否 1 是
*/
@ApiModelProperty(value = "是否为异常 0 否 1 是")
private Integer isWarn;
/**
* 是否成功 0 失败 1 成功
*/
@ApiModelProperty(value = "是否成功 0 失败 1 成功")
private String result;
/**
* 账号
*/
@ApiModelProperty(value = "账号")
private String userCode;
/**
* 菜单ID
*/
@ApiModelProperty(value = "菜单ID")
private String menuId;
/**
* 菜单全路径
*/
@ApiModelProperty(value = "菜单全路径")
private String menuUri;
/**
* 是否已读
*/
@ApiModelProperty(value = "是否已读")
private Integer isRead;
/**
* 请求时间
*/
@ApiModelProperty(value = "请求时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date requestTime;
/**
* 请求参数
*/
@ApiModelProperty(value = "请求参数")
private String requestParams;
@ApiModelProperty(value = "请求路径")
@TableField(exist = false)
private String operUrl;
@ApiModelProperty(value = "错误信息")
@TableField(exist = false)
private String errorMsg;
@ApiModelProperty(value = "请求方式")
@TableField(exist = false)
private String requestMethod;
@ApiModelProperty(value = "响应结果")
@TableField(exist = false)
private String jsonResult;
@ApiModelProperty(value = "接口方法")
@TableField(exist = false)
private String method;
@ApiModelProperty(value = "接口响应时间")
private Long costTime;
@ApiModelProperty(value = "查询条件时间")
@TableField(exist = false)
private String time;
public static final String LOG_ID = "log_id";
public static final String USER_ID = "user_id";
public static final String USER_NAME = "username";
public static final String IP_ADDR = "ip_addr";
public static final String CREATE_TIME = "create_time";
public static final String ENENT_TYPE = "event_type";
public static final String EVENT_CONTENT = "event_content";
public static final String LOG_TYPE = "log_type";
public static final String LOG_LEV = "log_lev";
public static final String IS_WARN = "is_warn";
public static final String RESULT = "result";
public static final String USER_CODE = "user_code";
public static final String MENU_ID = "menu_id";
public static final String MENU_URI = "menu_uri";
public static final String IS_READ = "is_read";
public static final String REQUEST_TIME = "request_time";
public static final String REQUEST_PARAMS = "request_params";
}
package org.arch.filter;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import net.minidev.json.JSONObject;
import org.arch.utils.JwtUitls;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
@Component
public class LoginFilter implements Filter
{
@Override
public void init(FilterConfig filterConfig) throws ServletException
{
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException
{
JwtUitls jwtUitls = new JwtUitls();
Map<String, String> map = new HashMap<>();
String url = ((HttpServletRequest) servletRequest).getRequestURI();
if (url != null)
{
//登录请求直接放行
if ("/login".equals(url) || "/register".equals(url) ||"/i6000/adToKen".equals(url)|| "/i6000/adUserLoginLog".equals(url) )
{
filterChain.doFilter(servletRequest, servletResponse);
return;
} else
{
//其他请求验证token
String token = ((HttpServletRequest) servletRequest).getHeader("Authorization");
if (StringUtils.isNotBlank(token))
{
//token验证结果
int verify = jwtUitls.verify(token);
if (verify != 1)
{
//验证失败
if (verify == 2)
{
map.put("errorMsg", "token已过期");
} else if (verify == 0)
{
map.put("errorMsg", "用户信息验证失败");
}
} else if (verify == 1)
{
//验证成功,放行
filterChain.doFilter(servletRequest, servletResponse);
return;
}
} else
{
//token为空的返回
map.put("errorMsg", "未携带token信息");
}
}
JSONObject jsonObject = new JSONObject(map);
servletResponse.setContentType("application/json");
//设置响应的编码
servletResponse.setCharacterEncoding("utf-8");
//响应
PrintWriter pw = servletResponse.getWriter();
pw.write(jsonObject.toString());
pw.flush();
pw.close();
}
}
@Override
public void destroy()
{
}
}
\ No newline at end of file
package org.arch.filter;
import org.apache.commons.lang3.StringUtils;
import org.arch.Result;
import org.arch.utils.JsonUtils;
import org.springframework.util.CollectionUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
/**
* SQL过滤
*/
public class SqlFilter implements Filter{
/**
* SQL注入过滤
* @param str 待验证的字符串
*/
public static String sqlInject(String str){
if(StringUtils.isBlank(str)){
return null;
}
//去掉'|"|;|\字符
str = StringUtils.replace(str, "'", "");
str = StringUtils.replace(str, "\"", "");
str = StringUtils.replace(str, ";", "");
str = StringUtils.replace(str, "\\", "");
//转换成小写
str = str.toLowerCase();
//非法字符
String[] keywords = {"master", "truncate", "insert", "select", "delete", "update", "declare", "alter", "drop","%"};
//判断是否包含非法字符
for(String keyword : keywords){
if(str.indexOf(keyword) != -1){
throw new IllegalArgumentException("包含非法字符");
}
}
return str;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
//response.setContentType("text/html;charset=utf-8");
// 检查查询参数
Map<String, String[]> params = req.getParameterMap();
if (!CollectionUtils.isEmpty(params)) {
for (Map.Entry<String, String[]> entry : params.entrySet()) {
if (entry.getValue() == null) {
continue;
}
for (String value : entry.getValue()) {
if (validate(value)) {
errorResponse(resp);
return;
}
}
}
}
// 检查请求Body中的参数(使用 RequestBodyWrapper 规避流被读取后,后续无法再次读取问题)
SqlHttpServletRequestWrapper rw = new SqlHttpServletRequestWrapper(req);
// 整个JSON作为一个值来检查(出于性能考虑,但会增加误判风险)
if (validate(rw.getBody())) {
errorResponse(resp);
return;
}
chain.doFilter(rw, resp);
}
private void errorResponse(HttpServletResponse response) throws IOException {
Result res = new Result();
response.setCharacterEncoding("UTF-8");
//response.setContentType("text/html");
response.setContentType("application/json");
res.isFailed();
res.setMsg("非法参数,服务器拒绝请求");
res.setData("");
String json = JsonUtils.object2Json(res);
response.setStatus(200);
//response.set
response.getWriter().write(json);
}
/**
* 检查参数是否合法
*
* @param value
* @return
*/
/* private boolean validate(String value) {
// 空值一定不包含敏感字符,所以认为合法
if (StringUtils.isEmpty(value)) {
return true;
}
// 处理是否包含SQL注释字符
if (sqlCommentPattern.matcher(value).find()) {
return false;
}
// 检查是否包含SQL注入敏感字符
if (sqlSyntaxPattern.matcher(value).find()) {
return false;
}
return true;
}*/
public boolean validate(String str) {
if (!StringUtils.isEmpty(str)){
//效验
str = str.toLowerCase();//统一转为小写
//过滤掉的sql关键字,可以手动添加
String badStr = "'|and|exec|execute|insert|select | update |drop|" +
"char |declare|sitename|net user|xp_cmdshell|;| - | + |and|exec|execute|insert|create |" +
"table|from|grant| use |group_concat|column_name|" +
"information_schema.columns|table_schema|union|where| delete| update|order | by | count |*|" +
"alert|alter | \\ |asc |substring|restore|backup|net +user|net +localgroup +administrators|" +
"%|netlocalgroup administrators|like|join|into|substr|ascii|having|`|@ |~|!|$|"+
"^|&|=|<|>|?|!|#|¥|……|&|——|【|】|‘|;|:|”|“| 、|‘|,|。|. | }|\\|、|"+
"chr| mid |master|truncate|;| or | or|or |--| + | // | / | # ";//过滤掉的sql关键字,可以手动添加
String[] badStrs = badStr.split("\\|");
for (int i = 0; i < badStrs.length; i++) {
if (str.indexOf(badStrs[i]) >= 0) {
System.out.println(str+"存在非法字符:"+badStrs[i]);
return true;
}
}
return false;
} else {
return false;
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
package org.arch.filter;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* sql注入
* @author henry
*
*/
public class SqlHttpServletRequestWrapper extends HttpServletRequestWrapper {
/**
* JSON 字符串
*/
private String body;
public SqlHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
// 获取JSON请求Body
if (isJsonContentType()) {
try {
this.body = getJsonRequestBody(request.getInputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 检查Content-Type是否为application/json
*
* @return
*/
private boolean isJsonContentType() {
String contentType = this.getHeader("Content-Type");
return contentType != null && contentType.toLowerCase().startsWith("application/json");
}
@Override
public ServletInputStream getInputStream() throws IOException {
if (!isJsonContentType()) {
return super.getInputStream();
}
return new ServletInputStream() {
ByteArrayInputStream input = null;
{
if (body != null) {
input = new ByteArrayInputStream(body.getBytes());
}
}
@Override
public int read() throws IOException {
if (input == null) {
return -1;
} else {
return input.read();
}
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener listener) {
}
};
}
/**
* 解析JSON请求Body,并重新封装ServletInputStream,使之可以被重复读取
*
* @param input
* @return
*/
private String getJsonRequestBody(ServletInputStream input) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(input))) {
StringBuilder builder = new StringBuilder();
reader.lines().forEach(builder::append);
return builder.toString();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
public String getBody() {
return this.body;
}
}
package org.arch.filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class XssFilter implements Filter {
Logger log = LoggerFactory.getLogger(this.getClass());
// 忽略权限检查的url地址
private final String[] excludeUrls = new String[]{
"null"
};
public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) arg0;
HttpServletResponse response = (HttpServletResponse) arg1;
String pathInfo = req.getPathInfo() == null ? "" : req.getPathInfo();
//获取请求url的后两层
String url = req.getServletPath() + pathInfo;
//获取请求你ip后的全部路径
String uri = req.getRequestURI();
//注入xss过滤器实例
XssHttpServletRequestWrapper reqW = new XssHttpServletRequestWrapper(req);
//过滤掉不需要的Xss校验的地址
for (String str : excludeUrls) {
if (uri.indexOf(str) >= 0) {
arg2.doFilter(arg0, response);
return;
}
}
//过滤
arg2.doFilter(reqW, response);
}
public void destroy() {
}
public void init(FilterConfig filterconfig1) throws ServletException {
}
}
package org.arch.filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
Logger log = LoggerFactory.getLogger(this.getClass());
public XssHttpServletRequestWrapper() {
super(null);
}
public XssHttpServletRequestWrapper(HttpServletRequest httpservletrequest) {
super(httpservletrequest);
}
//过滤springmvc中的 @RequestParam 注解中的参数
public String[] getParameterValues(String s) {
String str[] = super.getParameterValues(s);
if (str == null) {
return null;
}
int i = str.length;
String as1[] = new String[i];
for (int j = 0; j < i; j++) {
//System.out.println("getParameterValues:"+str[j]);
as1[j] = cleanXSS(cleanSQLInject(str[j]));
}
log.info("XssHttpServletRequestWraper净化后的请求为:==========" + as1);
return as1;
}
//过滤request.getParameter的参数
public String getParameter(String s) {
String s1 = super.getParameter(s);
if (s1 == null) {
return null;
} else {
String s2 = cleanXSS(cleanSQLInject(s1));
log.info("XssHttpServletRequestWraper净化后的请求为:==========" + s2);
return s2;
}
}
//过滤请求体 json 格式的
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(inputHandlers(super.getInputStream ()).getBytes ());
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) { }
};
}
public String inputHandlers(ServletInputStream servletInputStream){
StringBuilder sb = new StringBuilder();
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(servletInputStream, Charset.forName("UTF-8")));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (servletInputStream != null) {
try {
servletInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return cleanXSS(sb.toString ());
}
public String cleanXSS(String src) {
String temp = src;
src = src.replaceAll("<", "<").replaceAll(">", ">");
src = src.replaceAll("\\(", "(").replaceAll("\\)", ")");
src = src.replaceAll("'", "'");
src = src.replaceAll(";", ";");
//bgh 2018/05/30 新增
/**-----------------------start--------------------------*/
src = src.replaceAll("<", "& lt;").replaceAll(">", "& gt;");
src = src.replaceAll("\\(", "& #40;").replaceAll("\\)", "& #41");
src = src.replaceAll("eval\\((.*)\\)", "");
src = src.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
src = src.replaceAll("script", "");
src = src.replaceAll("link", "");
src = src.replaceAll("frame", "");
/**-----------------------end--------------------------*/
Pattern pattern = Pattern.compile("(eval\\((.*)\\)|script)",
Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(src);
src = matcher.replaceAll("");
pattern = Pattern.compile("[\\\"\\'][\\s]*javascript:(.*)[\\\"\\']",
Pattern.CASE_INSENSITIVE);
matcher = pattern.matcher(src);
src = matcher.replaceAll("\"\"");
// 增加脚本
src = src.replaceAll("script", "").replaceAll(";", "")
/*.replaceAll("\"", "").replaceAll("@", "")*/
.replaceAll("0x0d", "").replaceAll("0x0a", "");
if (!temp.equals(src)) {
// System.out.println("输入信息存在xss攻击!");
// System.out.println("原始输入信息-->" + temp);
// System.out.println("处理后信息-->" + src);
log.error("xss攻击检查:参数含有非法攻击字符,已禁止继续访问!!");
log.error("原始输入信息-->" + temp);
// throw new CustomerException("xss攻击检查:参数含有非法攻击字符,已禁止继续访问!!");
}
return src;
}
//输出
public void outputMsgByOutputStream(HttpServletResponse response, String msg) throws IOException {
ServletOutputStream outputStream = response.getOutputStream(); //获取输出流
response.setHeader("content-type", "text/html;charset=UTF-8"); //通过设置响应头控制浏览器以UTF-8的编码显示数据,如果不加这句话,那么浏览器显示的将是乱码
byte[] dataByteArr = msg.getBytes("UTF-8");// 将字符转换成字节数组,指定以UTF-8编码进行转换
outputStream.write(dataByteArr);// 使用OutputStream流向客户端输出字节数组
}
// 需要增加通配,过滤大小写组合
public String cleanSQLInject(String src) {
String lowSrc = src.toLowerCase();
String temp = src;
String lowSrcAfter = lowSrc.replaceAll("insert", "forbidI")
.replaceAll("select", "forbidS")
.replaceAll("update", "forbidU")
.replaceAll("delete", "forbidD").replaceAll("and", "forbidA")
.replaceAll("or", "forbidO");
if (!lowSrcAfter.equals(lowSrc)) {
log.error("sql注入检查:输入信息存在SQL攻击!");
log.error("原始输入信息-->" + temp);
log.error("处理后信息-->" + lowSrc);
// throw new CustomerException("sql注入检查:参数含有非法攻击字符,已禁止继续访问!!");
}
return src;
}
}
package org.arch.logenum;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum LogEventContentEnum {
LOG_STAT("1","日志统计"),
LOG_DATA("2","日志数据"),
;
/**
* 操作菜单编码
*/
private String code;
/**
* 操作时间内容
*/
private String eventContent;
}
package org.arch.logenum;
/**
* 操作事件类型枚举类
*/
public enum LogEventTypeEnum {
/**
* 其它
*/
OTHER,
/**
* 新增
*/
INSERT,
/**
* 修改
*/
UPDATE,
/**
* 删除
*/
DELETE,
/**
* 查询
*/
SEARCH,
/**
* 授权
*/
GRANT,
/**
* 导出
*/
EXPORT,
/**
* 导入
*/
IMPORT,
/**
* 登录
*/
LOGIN,
/**
* 模版下载
*/
DOWNLOAD
}
package org.arch.logenum;
/**
* 日志级别枚举类
*/
public enum LogLevEnum {
LOW,
MIDDLE,
HIGH
}
package org.arch.logenum;
/**
* 日志类型枚举类
*/
public enum LogTypeEnum {
SYS_LOG,
BUSI_LOG
}
package org.arch.mapper;
import com.github.yulichang.base.MPJBaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.arch.entity.SysEventLog;
import org.springframework.data.domain.Pageable;
import java.util.List;
import java.util.Map;
/**
* 系统操作日志(SysEventLog)表数据库访问层
*
* @author makejava
* @since 2024-01-25 09:50:15
*/
@Mapper
public interface SysEventLogMapper extends MPJBaseMapper<SysEventLog> {
@Select("<script>" +
"SELECT\n" +
"\ta.label AS name,\n" +
"\tCOUNT(c.log_id) AS `value`\n" +
"FROM\n" +
"\tsys_dict_detail a\n" +
"LEFT JOIN sys_dict b ON a.dict_id = b.id\n" +
"LEFT JOIN sys_event_log c ON a.`value` = c.${column}\n" +
"WHERE\n" +
"\tb.type_value = #{typeValue}\n" +
"GROUP BY\n" +
"\ta.`label`" +
"</script>")
List<Map<String, Object>> getGroupData(@Param("typeValue") String value, @Param("column") String column);
/**
* 通过ID查询单条数据
*
* @param logId 主键
* @return 实例对象
*/
SysEventLog queryById(String logId);
/**
* 查询指定行数据
*
* @param sysEventLog 查询条件
* @param pageable 分页对象
* @return 对象列表
*/
List<SysEventLog> queryAllByLimit(SysEventLog sysEventLog, @Param("pageable") Pageable pageable);
/**
* 统计总行数
*
* @param sysEventLog 查询条件
* @return 总行数
*/
long count(SysEventLog sysEventLog);
/**
* 新增数据
*
* @param sysEventLog 实例对象
* @return 影响行数
*/
int insert(SysEventLog sysEventLog);
/**
* 批量新增数据(MyBatis原生foreach方法)
*
* @param entities List<SysEventLog> 实例对象列表
* @return 影响行数
*/
int insertBatch(@Param("entities") List<SysEventLog> entities);
/**
* 批量新增或按主键更新数据(MyBatis原生foreach方法)
*
* @param entities List<SysEventLog> 实例对象列表
* @return 影响行数
* @throws org.springframework.jdbc.BadSqlGrammarException 入参是空List的时候会抛SQL语句错误的异常,请自行校验入参
*/
int insertOrUpdateBatch(@Param("entities") List<SysEventLog> entities);
/**
* 修改数据
*
* @param sysEventLog 实例对象
* @return 影响行数
*/
int update(SysEventLog sysEventLog);
/**
* 通过主键删除数据
*
* @param logId 主键
* @return 影响行数
*/
int deleteById(String logId);
}
package org.arch.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.arch.modules.sysmanage.dto.UserDto;
import org.springframework.data.domain.Page;
import org.arch.entity.SysEventLog;
import org.springframework.data.domain.PageRequest;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
* 系统操作日志(SysEventLog)表服务接口
*
* @author makejava
* @since 2024-01-25 09:50:19
*/
public interface SysEventLogService extends IService<SysEventLog> {
/**
* 通过ID查询单条数据
*
* @param logId 主键
* @return 实例对象
*/
SysEventLog queryById(String logId);
/**
* 分页查询
*
* @param sysEventLog 筛选条件
* @param pageRequest 分页对象
* @return 查询结果
*/
Page<SysEventLog> queryByPage(SysEventLog sysEventLog, PageRequest pageRequest);
/**
* 新增数据
*
* @param sysEventLog 实例对象
* @return 实例对象
*/
SysEventLog insert(SysEventLog sysEventLog);
/**
* 修改数据
*
* @param sysEventLog 实例对象
* @return 实例对象
*/
SysEventLog update(SysEventLog sysEventLog);
/**
* 通过主键删除数据
*
* @param logId 主键
* @return 是否成功
*/
boolean deleteById(String logId);
boolean addLog(HttpServletRequest request, SysEventLog params, UserDto userDto);
Map<String, Object> getCount();
}
package org.arch.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import org.arch.entity.SysEventLog;
import org.arch.mapper.SysEventLogMapper;
import org.arch.modules.sysmanage.dto.UserDto;
import org.arch.service.SysEventLogService;
import org.arch.utils.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 系统操作日志(SysEventLog)表服务实现类
*
* @author makejava
* @since 2024-01-25 09:50:21
*/
@Service("sysEventLogService")
public class SysEventLogServiceImpl extends ServiceImpl<SysEventLogMapper, SysEventLog> implements SysEventLogService {
@Resource
private SysEventLogMapper sysEventLogDao;
/**
* 通过ID查询单条数据
*
* @param logId 主键
* @return 实例对象
*/
@Override
public SysEventLog queryById(String logId) {
return this.sysEventLogDao.queryById(logId);
}
/**
* 分页查询
*
* @param sysEventLog 筛选条件
* @param pageRequest 分页对象
* @return 查询结果
*/
@Override
public Page<SysEventLog> queryByPage(SysEventLog sysEventLog, PageRequest pageRequest) {
long total = this.sysEventLogDao.count(sysEventLog);
return new PageImpl<>(this.sysEventLogDao.queryAllByLimit(sysEventLog, pageRequest), pageRequest, total);
}
/**
* 新增数据
*
* @param sysEventLog 实例对象
* @return 实例对象
*/
@Override
public SysEventLog insert(SysEventLog sysEventLog) {
this.sysEventLogDao.insert(sysEventLog);
return sysEventLog;
}
/**
* 修改数据
*
* @param sysEventLog 实例对象
* @return 实例对象
*/
@Override
public SysEventLog update(SysEventLog sysEventLog) {
this.sysEventLogDao.update(sysEventLog);
return this.queryById(sysEventLog.getLogId());
}
/**
* 通过主键删除数据
*
* @param logId 主键
* @return 是否成功
*/
@Override
public boolean deleteById(String logId) {
return this.sysEventLogDao.deleteById(logId) > 0;
}
@Override
public boolean addLog(HttpServletRequest request, SysEventLog params, UserDto userDto) {
String ipaddr = StringUtils.getIp(request);
params.setIpAddr(ipaddr);
params.setIsRead(0);
params.setCreateTime(DateTime.now());
params.setLogId(IdUtil.fastSimpleUUID());
params.setIsWarn(0);
if(params.getResult()==null || "".equals(params.getResult())){
params.setResult("1");
}
params.setUserId(userDto.getIscUserId());
params.setUserCode(userDto.getUserCode());
params.setUsername(userDto.getName());
QueryWrapper<SysEventLog> queryWrapper = Wrappers.query();
queryWrapper.eq("user_id", userDto.getIscUserId());
queryWrapper.orderByDesc("create_time");
queryWrapper.last("limit 1");
List<SysEventLog> list = getBaseMapper().selectList(queryWrapper);
if (CollectionUtil.isNotEmpty(list)){
String lastIp = list.get(0).getIpAddr();
if(lastIp!=null&&!lastIp.equals(ipaddr)){
SysEventLog eventLog2 = new SysEventLog();
eventLog2=params;
// eventLog2.setEventtype("IP地址异常");
eventLog2.setEventType("8");
eventLog2.setEventContent("IP地址异常登录");
eventLog2.setLogType("0");
eventLog2.setLogLev("2");
eventLog2.setIsWarn(1);
}
}
return SqlHelper.retBool(getBaseMapper().insert(params));
}
@Override
public Map<String, Object> getCount() {
Map<String, Object> result = new HashMap<>();
List<Map<String, Object>> log_event_type = getBaseMapper().getGroupData("log_event_type", SysEventLog.ENENT_TYPE);
List<Map<String, Object>> log_lev = getBaseMapper().getGroupData("log_lev", SysEventLog.LOG_LEV);
result.put("eventTypeData", log_event_type);
result.put("logLevData", log_lev);
return result;
}
}
package org.arch.util;
public class CustomerException extends RuntimeException{
private String msg;
public CustomerException(String msg) {
//关键代码,
super(msg);
this.msg = msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
package org.arch.util;
import java.util.*;
public class EmptyUtils {
/**
* 判断collection是否为空
* @param collection
* @return
*/
public static Boolean IsEmptyCollection(Collection collection){
if(null == collection || collection.isEmpty()){
return true;
}else{
return false;
}
}
/**
* 判断map 是否为空
* @param map
* @return
*/
public static Boolean IsEmptyMap(Map map){
if(null == map || map.isEmpty()){
return true;
}else{
return false;
}
}
/**
* 判断 list 是否为空
* @param list
*/
public static Boolean IsEmptyList(List list){
if(null == list || list.isEmpty()){
return true;
}else{
return false;
}
}
/**
* 判断set是否为空
* @param set
*/
public static Boolean IsEmptySet(Set set){
if(null == set || set.isEmpty()){
return true;
}else{
return false;
}
}
/**
* 判断字符串是否为空
* @param str
* @return
*/
public static Boolean isEmptyString(String str){
if(null == str || "".equals(str)){
return true;
}else{
return false;
}
}
public static void main(String[] args) {
Map<String,Object> map = new HashMap<>();
Boolean a = IsEmptyMap(map);
System.out.println(a);
}
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.eadc.config.LogAutoConfiguration
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!