mirror of
https://github.com/chillzhuang/blade-tool
synced 2025-01-06 04:55:33 +08:00
🎉 2.6.0.RELEASE 升级Hoxton.SR1 适配最新架构
This commit is contained in:
parent
457ab4088f
commit
181063c231
@ -1,7 +1,7 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://img.shields.io/badge/license-LGPL%20v3-blue.svg" alt="Build Status">
|
<img src="https://img.shields.io/badge/license-LGPL%20v3-blue.svg" alt="Build Status">
|
||||||
<img src="https://img.shields.io/badge/Spring%20Cloud-Greenwich.SR3-blue.svg" alt="Coverage Status">
|
<img src="https://img.shields.io/badge/Spring%20Cloud-Hoxton.SR1-blue.svg" alt="Coverage Status">
|
||||||
<img src="https://img.shields.io/badge/Spring%20Boot-2.1.9.RELEASE-blue.svg" alt="Downloads">
|
<img src="https://img.shields.io/badge/Spring%20Boot-2.2.2.RELEASE-blue.svg" alt="Downloads">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## SpringBlade微服务开发平台
|
## SpringBlade微服务开发平台
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springblade</groupId>
|
<groupId>org.springblade</groupId>
|
||||||
<artifactId>blade-tool</artifactId>
|
<artifactId>blade-tool</artifactId>
|
||||||
<version>2.5.4</version>
|
<version>2.6.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
@ -62,28 +62,6 @@
|
|||||||
<artifactId>blade-core-swagger</artifactId>
|
<artifactId>blade-core-swagger</artifactId>
|
||||||
<version>${blade.tool.version}</version>
|
<version>${blade.tool.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!--Swagger-->
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.springfox</groupId>
|
|
||||||
<artifactId>springfox-swagger2</artifactId>
|
|
||||||
<version>${swagger.version}</version>
|
|
||||||
<exclusions>
|
|
||||||
<exclusion>
|
|
||||||
<groupId>io.swagger</groupId>
|
|
||||||
<artifactId>swagger-models</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
</exclusions>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.swagger</groupId>
|
|
||||||
<artifactId>swagger-models</artifactId>
|
|
||||||
<version>${swagger.models.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.github.xiaoymin</groupId>
|
|
||||||
<artifactId>swagger-bootstrap-ui</artifactId>
|
|
||||||
<version>${swagger.bootstrapui.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<!--Redis-->
|
<!--Redis-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
@ -16,13 +16,12 @@
|
|||||||
package org.springblade.core.boot.config;
|
package org.springblade.core.boot.config;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
|
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor;
|
|
||||||
import org.mybatis.spring.annotation.MapperScan;
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
import org.springblade.core.launch.constant.AppConstant;
|
import org.springblade.core.mp.plugins.SqlLogInterceptor;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Profile;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mybatisplus 配置
|
* mybatisplus 配置
|
||||||
@ -40,14 +39,12 @@ public class MybatisPlusConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SQL执行效率插件
|
* sql 日志
|
||||||
*
|
|
||||||
* @return PerformanceInterceptor
|
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
@Profile({AppConstant.DEV_CODE, AppConstant.TEST_CODE})
|
@ConditionalOnProperty(value = "blade.mybatis-plus.sql-log.enable", matchIfMissing = true)
|
||||||
public PerformanceInterceptor performanceInterceptor() {
|
public SqlLogInterceptor sqlLogInterceptor() {
|
||||||
return new PerformanceInterceptor();
|
return new SqlLogInterceptor();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ public class BladeTenantHandler implements TenantHandler {
|
|||||||
* @return 租户ID
|
* @return 租户ID
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Expression getTenantId() {
|
public Expression getTenantId(boolean where) {
|
||||||
return new StringValue(Func.toStr(SecureUtil.getTenantId(), TenantConstant.DEFAULT_TENANT_ID));
|
return new StringValue(Func.toStr(SecureUtil.getTenantId(), TenantConstant.DEFAULT_TENANT_ID));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ mybatis-plus:
|
|||||||
swagger:
|
swagger:
|
||||||
title: SpringBlade 接口文档系统
|
title: SpringBlade 接口文档系统
|
||||||
description: SpringBlade 接口文档系统
|
description: SpringBlade 接口文档系统
|
||||||
version: 2.5.4
|
version: 2.6.0
|
||||||
license: Powered By SpringBlade
|
license: Powered By SpringBlade
|
||||||
licenseUrl: https://bladex.vip
|
licenseUrl: https://bladex.vip
|
||||||
terms-of-service-url: https://bladex.vip
|
terms-of-service-url: https://bladex.vip
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>blade-tool</artifactId>
|
<artifactId>blade-tool</artifactId>
|
||||||
<groupId>org.springblade</groupId>
|
<groupId>org.springblade</groupId>
|
||||||
<version>2.5.4</version>
|
<version>2.6.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>blade-tool</artifactId>
|
<artifactId>blade-tool</artifactId>
|
||||||
<groupId>org.springblade</groupId>
|
<groupId>org.springblade</groupId>
|
||||||
<version>2.5.4</version>
|
<version>2.6.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
@ -16,9 +16,9 @@
|
|||||||
package $!{package.Controller};
|
package $!{package.Controller};
|
||||||
|
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiOperationSupport;
|
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
import io.swagger.annotations.ApiParam;
|
import io.swagger.annotations.ApiParam;
|
||||||
|
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>blade-tool</artifactId>
|
<artifactId>blade-tool</artifactId>
|
||||||
<groupId>org.springblade</groupId>
|
<groupId>org.springblade</groupId>
|
||||||
<version>2.5.4</version>
|
<version>2.6.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
@ -25,7 +25,7 @@ public interface AppConstant {
|
|||||||
/**
|
/**
|
||||||
* 应用版本
|
* 应用版本
|
||||||
*/
|
*/
|
||||||
String APPLICATION_VERSION = "2.5.4";
|
String APPLICATION_VERSION = "2.6.0";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基础包
|
* 基础包
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>blade-tool</artifactId>
|
<artifactId>blade-tool</artifactId>
|
||||||
<groupId>org.springblade</groupId>
|
<groupId>org.springblade</groupId>
|
||||||
<version>2.5.4</version>
|
<version>2.6.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>blade-tool</artifactId>
|
<artifactId>blade-tool</artifactId>
|
||||||
<groupId>org.springblade</groupId>
|
<groupId>org.springblade</groupId>
|
||||||
<version>2.5.4</version>
|
<version>2.6.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
@ -32,7 +32,7 @@ public interface BaseService<T> extends IService<T> {
|
|||||||
* 逻辑删除
|
* 逻辑删除
|
||||||
*
|
*
|
||||||
* @param ids id集合(逗号分隔)
|
* @param ids id集合(逗号分隔)
|
||||||
* @return
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
boolean deleteLogic(@NotEmpty List<Integer> ids);
|
boolean deleteLogic(@NotEmpty List<Integer> ids);
|
||||||
|
|
||||||
|
@ -24,8 +24,6 @@ import org.springblade.core.tool.utils.DateUtil;
|
|||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
import javax.validation.constraints.NotEmpty;
|
import javax.validation.constraints.NotEmpty;
|
||||||
import java.lang.reflect.ParameterizedType;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -39,14 +37,6 @@ import java.util.List;
|
|||||||
@Validated
|
@Validated
|
||||||
public class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseEntity> extends ServiceImpl<M, T> implements BaseService<T> {
|
public class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseEntity> extends ServiceImpl<M, T> implements BaseService<T> {
|
||||||
|
|
||||||
private Class<T> modelClass;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public BaseServiceImpl() {
|
|
||||||
Type type = this.getClass().getGenericSuperclass();
|
|
||||||
this.modelClass = (Class<T>) ((ParameterizedType) type).getActualTypeArguments()[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean save(T entity) {
|
public boolean save(T entity) {
|
||||||
BladeUser user = SecureUtil.getUser();
|
BladeUser user = SecureUtil.getUser();
|
||||||
|
@ -18,6 +18,7 @@ package org.springblade.core.mp.base;
|
|||||||
|
|
||||||
import io.swagger.annotations.ApiModelProperty;
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 租户基础实体类
|
* 租户基础实体类
|
||||||
@ -25,6 +26,7 @@ import lombok.Data;
|
|||||||
* @author Chill
|
* @author Chill
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public class TenantEntity extends BaseEntity {
|
public class TenantEntity extends BaseEntity {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,185 @@
|
|||||||
|
package org.springblade.core.mp.plugins;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.StringPool;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.SystemClock;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.ibatis.executor.statement.StatementHandler;
|
||||||
|
import org.apache.ibatis.mapping.MappedStatement;
|
||||||
|
import org.apache.ibatis.plugin.*;
|
||||||
|
import org.apache.ibatis.reflection.MetaObject;
|
||||||
|
import org.apache.ibatis.reflection.SystemMetaObject;
|
||||||
|
import org.apache.ibatis.session.ResultHandler;
|
||||||
|
import org.springblade.core.tool.utils.StringUtil;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于输出每条 SQL 语句及其执行时间
|
||||||
|
*
|
||||||
|
* @author hubin nieqiurong TaoYu
|
||||||
|
* @since 2016-07-07
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Intercepts({
|
||||||
|
@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
|
||||||
|
@Signature(type = StatementHandler.class, method = "update", args = Statement.class),
|
||||||
|
@Signature(type = StatementHandler.class, method = "batch", args = Statement.class)
|
||||||
|
})
|
||||||
|
public class SqlLogInterceptor implements Interceptor {
|
||||||
|
private static final String DRUID_POOLED_PREPARED_STATEMENT = "com.alibaba.druid.pool.DruidPooledPreparedStatement";
|
||||||
|
private static final String T4C_PREPARED_STATEMENT = "oracle.jdbc.driver.T4CPreparedStatement";
|
||||||
|
private static final String ORACLE_PREPARED_STATEMENT_WRAPPER = "oracle.jdbc.driver.OraclePreparedStatementWrapper";
|
||||||
|
|
||||||
|
private Method oracleGetOriginalSqlMethod;
|
||||||
|
private Method druidGetSqlMethod;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object intercept(Invocation invocation) throws Throwable {
|
||||||
|
Statement statement;
|
||||||
|
Object firstArg = invocation.getArgs()[0];
|
||||||
|
if (Proxy.isProxyClass(firstArg.getClass())) {
|
||||||
|
statement = (Statement) SystemMetaObject.forObject(firstArg).getValue("h.statement");
|
||||||
|
} else {
|
||||||
|
statement = (Statement) firstArg;
|
||||||
|
}
|
||||||
|
MetaObject stmtMetaObj = SystemMetaObject.forObject(statement);
|
||||||
|
try {
|
||||||
|
statement = (Statement) stmtMetaObj.getValue("stmt.statement");
|
||||||
|
} catch (Exception e) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
if (stmtMetaObj.hasGetter("delegate")) {
|
||||||
|
//Hikari
|
||||||
|
try {
|
||||||
|
statement = (Statement) stmtMetaObj.getValue("delegate");
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String originalSql = null;
|
||||||
|
String stmtClassName = statement.getClass().getName();
|
||||||
|
if (DRUID_POOLED_PREPARED_STATEMENT.equals(stmtClassName)) {
|
||||||
|
try {
|
||||||
|
if (druidGetSqlMethod == null) {
|
||||||
|
Class<?> clazz = Class.forName(DRUID_POOLED_PREPARED_STATEMENT);
|
||||||
|
druidGetSqlMethod = clazz.getMethod("getSql");
|
||||||
|
}
|
||||||
|
Object stmtSql = druidGetSqlMethod.invoke(statement);
|
||||||
|
if (stmtSql instanceof String) {
|
||||||
|
originalSql = (String) stmtSql;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
} else if (T4C_PREPARED_STATEMENT.equals(stmtClassName)
|
||||||
|
|| ORACLE_PREPARED_STATEMENT_WRAPPER.equals(stmtClassName)) {
|
||||||
|
try {
|
||||||
|
if (oracleGetOriginalSqlMethod != null) {
|
||||||
|
Object stmtSql = oracleGetOriginalSqlMethod.invoke(statement);
|
||||||
|
if (stmtSql instanceof String) {
|
||||||
|
originalSql = (String) stmtSql;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Class<?> clazz = Class.forName(stmtClassName);
|
||||||
|
oracleGetOriginalSqlMethod = getMethodRegular(clazz, "getOriginalSql");
|
||||||
|
if (oracleGetOriginalSqlMethod != null) {
|
||||||
|
//OraclePreparedStatementWrapper is not a public class, need set this.
|
||||||
|
oracleGetOriginalSqlMethod.setAccessible(true);
|
||||||
|
if (null != oracleGetOriginalSqlMethod) {
|
||||||
|
Object stmtSql = oracleGetOriginalSqlMethod.invoke(statement);
|
||||||
|
if (stmtSql instanceof String) {
|
||||||
|
originalSql = (String) stmtSql;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
//ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (originalSql == null) {
|
||||||
|
originalSql = statement.toString();
|
||||||
|
}
|
||||||
|
originalSql = originalSql.replaceAll("[\\s]+", StringPool.SPACE);
|
||||||
|
int index = indexOfSqlStart(originalSql);
|
||||||
|
if (index > 0) {
|
||||||
|
originalSql = originalSql.substring(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算执行 SQL 耗时
|
||||||
|
long start = SystemClock.now();
|
||||||
|
Object result = invocation.proceed();
|
||||||
|
long timing = SystemClock.now() - start;
|
||||||
|
|
||||||
|
// SQL 打印执行结果
|
||||||
|
Object target = PluginUtils.realTarget(invocation.getTarget());
|
||||||
|
MetaObject metaObject = SystemMetaObject.forObject(target);
|
||||||
|
MappedStatement ms = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
|
||||||
|
// 打印 sql
|
||||||
|
System.err.println(
|
||||||
|
StringUtil.format(
|
||||||
|
"\n============== Sql Start ==============" +
|
||||||
|
"\nExecute ID :{}" +
|
||||||
|
"\nExecute SQL :{}" +
|
||||||
|
"\nExecute Time:{} ms" +
|
||||||
|
"\n============== Sql End ==============\n",
|
||||||
|
ms.getId(), originalSql, timing));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object plugin(Object target) {
|
||||||
|
if (target instanceof StatementHandler) {
|
||||||
|
return Plugin.wrap(target, this);
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取此方法名的具体 Method
|
||||||
|
*
|
||||||
|
* @param clazz class 对象
|
||||||
|
* @param methodName 方法名
|
||||||
|
* @return 方法
|
||||||
|
*/
|
||||||
|
private Method getMethodRegular(Class<?> clazz, String methodName) {
|
||||||
|
if (Object.class.equals(clazz)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (Method method : clazz.getDeclaredMethods()) {
|
||||||
|
if (method.getName().equals(methodName)) {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getMethodRegular(clazz.getSuperclass(), methodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取sql语句开头部分
|
||||||
|
*
|
||||||
|
* @param sql ignore
|
||||||
|
* @return ignore
|
||||||
|
*/
|
||||||
|
private int indexOfSqlStart(String sql) {
|
||||||
|
String upperCaseSql = sql.toUpperCase();
|
||||||
|
Set<Integer> set = new HashSet<>();
|
||||||
|
set.add(upperCaseSql.indexOf("SELECT "));
|
||||||
|
set.add(upperCaseSql.indexOf("UPDATE "));
|
||||||
|
set.add(upperCaseSql.indexOf("INSERT "));
|
||||||
|
set.add(upperCaseSql.indexOf("DELETE "));
|
||||||
|
set.remove(-1);
|
||||||
|
if (CollectionUtils.isEmpty(set)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
List<Integer> list = new ArrayList<>(set);
|
||||||
|
list.sort(Comparator.naturalOrder());
|
||||||
|
return list.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -30,15 +30,17 @@ public abstract class BaseEntityWrapper<E, V> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 单个实体类包装
|
* 单个实体类包装
|
||||||
* @param entity
|
*
|
||||||
* @return
|
* @param entity 实体类
|
||||||
|
* @return V
|
||||||
*/
|
*/
|
||||||
public abstract V entityVO(E entity);
|
public abstract V entityVO(E entity);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 实体类集合包装
|
* 实体类集合包装
|
||||||
* @param list
|
*
|
||||||
* @return
|
* @param list 猎豹
|
||||||
|
* @return List V
|
||||||
*/
|
*/
|
||||||
public List<V> listVO(List<E> list) {
|
public List<V> listVO(List<E> list) {
|
||||||
return list.stream().map(this::entityVO).collect(Collectors.toList());
|
return list.stream().map(this::entityVO).collect(Collectors.toList());
|
||||||
@ -46,8 +48,9 @@ public abstract class BaseEntityWrapper<E, V> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 分页实体类集合包装
|
* 分页实体类集合包装
|
||||||
* @param pages
|
*
|
||||||
* @return
|
* @param pages 分页
|
||||||
|
* @return Page V
|
||||||
*/
|
*/
|
||||||
public IPage<V> pageVO(IPage<E> pages) {
|
public IPage<V> pageVO(IPage<E> pages) {
|
||||||
List<V> records = listVO(pages.getRecords());
|
List<V> records = listVO(pages.getRecords());
|
||||||
|
@ -33,8 +33,8 @@ public class Condition {
|
|||||||
/**
|
/**
|
||||||
* 转化成mybatis plus中的Page
|
* 转化成mybatis plus中的Page
|
||||||
*
|
*
|
||||||
* @param query
|
* @param query 查询包装类
|
||||||
* @return
|
* @return Page T
|
||||||
*/
|
*/
|
||||||
public static <T> IPage<T> getPage(Query query) {
|
public static <T> IPage<T> getPage(Query query) {
|
||||||
Page<T> page = new Page<>(Func.toInt(query.getCurrent(), 1), Func.toInt(query.getSize(), 10));
|
Page<T> page = new Page<>(Func.toInt(query.getCurrent(), 1), Func.toInt(query.getSize(), 10));
|
||||||
@ -46,9 +46,9 @@ public class Condition {
|
|||||||
/**
|
/**
|
||||||
* 获取mybatis plus中的QueryWrapper
|
* 获取mybatis plus中的QueryWrapper
|
||||||
*
|
*
|
||||||
* @param entity
|
* @param entity 实体类
|
||||||
* @param <T>
|
* @param <T> 泛型
|
||||||
* @return
|
* @return QueryWrapper
|
||||||
*/
|
*/
|
||||||
public static <T> QueryWrapper<T> getQueryWrapper(T entity) {
|
public static <T> QueryWrapper<T> getQueryWrapper(T entity) {
|
||||||
return new QueryWrapper<>(entity);
|
return new QueryWrapper<>(entity);
|
||||||
@ -57,10 +57,10 @@ public class Condition {
|
|||||||
/**
|
/**
|
||||||
* 获取mybatis plus中的QueryWrapper
|
* 获取mybatis plus中的QueryWrapper
|
||||||
*
|
*
|
||||||
* @param query
|
* @param query 查询包装类
|
||||||
* @param clazz
|
* @param clazz 实体类
|
||||||
* @param <T>
|
* @param <T> 泛型
|
||||||
* @return
|
* @return QueryWrapper
|
||||||
*/
|
*/
|
||||||
public static <T> QueryWrapper<T> getQueryWrapper(Map<String, Object> query, Class<T> clazz) {
|
public static <T> QueryWrapper<T> getQueryWrapper(Map<String, Object> query, Class<T> clazz) {
|
||||||
query.remove("current");
|
query.remove("current");
|
||||||
|
@ -88,7 +88,7 @@ public class SqlKeyword {
|
|||||||
*
|
*
|
||||||
* @param column 字段名
|
* @param column 字段名
|
||||||
* @param keyword 关键字
|
* @param keyword 关键字
|
||||||
* @return
|
* @return String
|
||||||
*/
|
*/
|
||||||
private static String getColumn(String column, String keyword) {
|
private static String getColumn(String column, String keyword) {
|
||||||
return StringUtil.humpToUnderline(StringUtil.removeSuffix(column, keyword));
|
return StringUtil.humpToUnderline(StringUtil.removeSuffix(column, keyword));
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>blade-tool</artifactId>
|
<artifactId>blade-tool</artifactId>
|
||||||
<groupId>org.springblade</groupId>
|
<groupId>org.springblade</groupId>
|
||||||
<version>2.5.4</version>
|
<version>2.6.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>blade-tool</artifactId>
|
<artifactId>blade-tool</artifactId>
|
||||||
<groupId>org.springblade</groupId>
|
<groupId>org.springblade</groupId>
|
||||||
<version>2.5.4</version>
|
<version>2.6.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>blade-tool</artifactId>
|
<artifactId>blade-tool</artifactId>
|
||||||
<groupId>org.springblade</groupId>
|
<groupId>org.springblade</groupId>
|
||||||
<version>2.5.4</version>
|
<version>2.6.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
@ -23,26 +23,10 @@
|
|||||||
<version>${blade.tool.version}</version>
|
<version>${blade.tool.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!--Swagger-->
|
<!--Swagger-->
|
||||||
<dependency>
|
|
||||||
<groupId>io.springfox</groupId>
|
|
||||||
<artifactId>springfox-swagger2</artifactId>
|
|
||||||
<version>${swagger.version}</version>
|
|
||||||
<exclusions>
|
|
||||||
<exclusion>
|
|
||||||
<groupId>io.swagger</groupId>
|
|
||||||
<artifactId>swagger-models</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
</exclusions>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.swagger</groupId>
|
|
||||||
<artifactId>swagger-models</artifactId>
|
|
||||||
<version>${swagger.models.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.xiaoymin</groupId>
|
<groupId>com.github.xiaoymin</groupId>
|
||||||
<artifactId>swagger-bootstrap-ui</artifactId>
|
<artifactId>knife4j-micro-spring-boot-starter</artifactId>
|
||||||
<version>${swagger.bootstrapui.version}</version>
|
<version>${knife4j.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
@ -16,14 +16,16 @@
|
|||||||
package org.springblade.core.swagger;
|
package org.springblade.core.swagger;
|
||||||
|
|
||||||
|
|
||||||
import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI;
|
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
import org.springframework.context.annotation.Profile;
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
|
||||||
import springfox.documentation.builders.ApiInfoBuilder;
|
import springfox.documentation.builders.ApiInfoBuilder;
|
||||||
import springfox.documentation.builders.PathSelectors;
|
import springfox.documentation.builders.PathSelectors;
|
||||||
import springfox.documentation.service.*;
|
import springfox.documentation.service.*;
|
||||||
@ -42,10 +44,11 @@ import java.util.List;
|
|||||||
* @author Chill
|
* @author Chill
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
|
@EnableKnife4j
|
||||||
@EnableSwagger2
|
@EnableSwagger2
|
||||||
@EnableSwaggerBootstrapUI
|
|
||||||
@Profile({"dev", "test"})
|
@Profile({"dev", "test"})
|
||||||
@EnableConfigurationProperties(SwaggerProperties.class)
|
@EnableConfigurationProperties(SwaggerProperties.class)
|
||||||
|
@Import(BeanValidatorPluginsConfiguration.class)
|
||||||
public class SwaggerAutoConfiguration {
|
public class SwaggerAutoConfiguration {
|
||||||
|
|
||||||
private static final String DEFAULT_EXCLUDE_PATH = "/error";
|
private static final String DEFAULT_EXCLUDE_PATH = "/error";
|
||||||
|
@ -55,7 +55,7 @@ public class SwaggerProperties {
|
|||||||
/**
|
/**
|
||||||
* 版本
|
* 版本
|
||||||
**/
|
**/
|
||||||
private String version = "2.5.4";
|
private String version = "2.6.0";
|
||||||
/**
|
/**
|
||||||
* 许可证
|
* 许可证
|
||||||
**/
|
**/
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springblade</groupId>
|
<groupId>org.springblade</groupId>
|
||||||
<artifactId>blade-tool</artifactId>
|
<artifactId>blade-tool</artifactId>
|
||||||
<version>2.5.4</version>
|
<version>2.6.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springblade</groupId>
|
<groupId>org.springblade</groupId>
|
||||||
<artifactId>blade-tool</artifactId>
|
<artifactId>blade-tool</artifactId>
|
||||||
<version>2.5.4</version>
|
<version>2.6.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
@ -16,9 +16,11 @@
|
|||||||
package org.springblade.core.tool.config;
|
package org.springblade.core.tool.config;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonParser;
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
import com.fasterxml.jackson.core.json.JsonReadFeature;
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
import org.springblade.core.tool.jackson.BladeJavaTimeModule;
|
import org.springblade.core.tool.jackson.BladeJavaTimeModule;
|
||||||
import org.springblade.core.tool.utils.DateUtil;
|
import org.springblade.core.tool.utils.DateUtil;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||||
@ -40,6 +42,7 @@ import java.util.TimeZone;
|
|||||||
* @author Chill
|
* @author Chill
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
|
@AllArgsConstructor
|
||||||
@ConditionalOnClass(ObjectMapper.class)
|
@ConditionalOnClass(ObjectMapper.class)
|
||||||
@AutoConfigureBefore(JacksonAutoConfiguration.class)
|
@AutoConfigureBefore(JacksonAutoConfiguration.class)
|
||||||
public class JacksonConfiguration {
|
public class JacksonConfiguration {
|
||||||
@ -59,8 +62,8 @@ public class JacksonConfiguration {
|
|||||||
//序列化时,日期的统一格式
|
//序列化时,日期的统一格式
|
||||||
objectMapper.setDateFormat(new SimpleDateFormat(DateUtil.PATTERN_DATETIME, Locale.CHINA));
|
objectMapper.setDateFormat(new SimpleDateFormat(DateUtil.PATTERN_DATETIME, Locale.CHINA));
|
||||||
//序列化处理
|
//序列化处理
|
||||||
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
|
objectMapper.configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), true);
|
||||||
objectMapper.configure(JsonParser.Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, true);
|
objectMapper.configure(JsonReadFeature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER.mappedFeature(), true);
|
||||||
objectMapper.findAndRegisterModules();
|
objectMapper.findAndRegisterModules();
|
||||||
//失败处理
|
//失败处理
|
||||||
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
|
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
|
||||||
|
@ -19,11 +19,7 @@ package org.springblade.core.tool.config;
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import org.springblade.core.tool.jackson.MappingApiJackson2HttpMessageConverter;
|
import org.springblade.core.tool.jackson.MappingApiJackson2HttpMessageConverter;
|
||||||
import org.springblade.core.tool.support.xss.XssFilter;
|
|
||||||
import org.springblade.core.tool.support.xss.XssProperties;
|
|
||||||
import org.springblade.core.tool.utils.Charsets;
|
import org.springblade.core.tool.utils.Charsets;
|
||||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
@ -31,7 +27,6 @@ import org.springframework.http.converter.*;
|
|||||||
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
|
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
import javax.servlet.DispatcherType;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,8 +41,6 @@ public class MessageConfiguration implements WebMvcConfigurer {
|
|||||||
|
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
private final XssProperties xssProperties;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 使用 JACKSON 作为JSON MessageConverter
|
* 使用 JACKSON 作为JSON MessageConverter
|
||||||
*/
|
*/
|
||||||
@ -61,20 +54,4 @@ public class MessageConfiguration implements WebMvcConfigurer {
|
|||||||
converters.add(new MappingApiJackson2HttpMessageConverter(objectMapper));
|
converters.add(new MappingApiJackson2HttpMessageConverter(objectMapper));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 防XSS注入
|
|
||||||
*
|
|
||||||
* @return FilterRegistrationBean
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
public FilterRegistrationBean xssFilterRegistration() {
|
|
||||||
FilterRegistrationBean registration = new FilterRegistrationBean();
|
|
||||||
registration.setDispatcherTypes(DispatcherType.REQUEST);
|
|
||||||
registration.setFilter(new XssFilter(xssProperties));
|
|
||||||
registration.addUrlPatterns("/*");
|
|
||||||
registration.setName("xssFilter");
|
|
||||||
registration.setOrder(Ordered.LOWEST_PRECEDENCE);
|
|
||||||
return registration;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,7 @@
|
|||||||
package org.springblade.core.tool.config;
|
package org.springblade.core.tool.config;
|
||||||
|
|
||||||
|
|
||||||
import org.springblade.core.tool.support.xss.XssProperties;
|
|
||||||
import org.springblade.core.tool.utils.SpringUtil;
|
import org.springblade.core.tool.utils.SpringUtil;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
@ -32,7 +30,6 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||||
@EnableConfigurationProperties(XssProperties.class)
|
|
||||||
public class ToolConfiguration implements WebMvcConfigurer {
|
public class ToolConfiguration implements WebMvcConfigurer {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
|
||||||
|
* <p>
|
||||||
|
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* <p>
|
||||||
|
* http://www.gnu.org/licenses/lgpl.html
|
||||||
|
* <p>
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springblade.core.tool.config;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springblade.core.tool.support.xss.XssFilter;
|
||||||
|
import org.springblade.core.tool.support.xss.XssProperties;
|
||||||
|
import org.springblade.core.tool.support.xss.XssUrlProperties;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
|
||||||
|
import javax.servlet.DispatcherType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Xss配置类
|
||||||
|
*
|
||||||
|
* @author Chill
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@AllArgsConstructor
|
||||||
|
@ConditionalOnProperty(value = "blade.xss.enable", havingValue = "true")
|
||||||
|
@EnableConfigurationProperties({XssProperties.class, XssUrlProperties.class})
|
||||||
|
public class XssConfiguration {
|
||||||
|
|
||||||
|
private final XssProperties xssProperties;
|
||||||
|
private final XssUrlProperties xssUrlProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 防XSS注入
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public FilterRegistrationBean<XssFilter> xssFilterRegistration() {
|
||||||
|
FilterRegistrationBean<XssFilter> registration = new FilterRegistrationBean<>();
|
||||||
|
registration.setDispatcherTypes(DispatcherType.REQUEST);
|
||||||
|
registration.setFilter(new XssFilter(xssProperties, xssUrlProperties));
|
||||||
|
registration.addUrlPatterns("/*");
|
||||||
|
registration.setName("xssFilter");
|
||||||
|
registration.setOrder(Ordered.LOWEST_PRECEDENCE);
|
||||||
|
return registration;
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,7 @@ package org.springblade.core.tool.jackson;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonParser;
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.core.json.JsonReadFeature;
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
@ -97,7 +98,7 @@ public class JsonUtil {
|
|||||||
* @param <T> T 泛型标记
|
* @param <T> T 泛型标记
|
||||||
* @return Bean
|
* @return Bean
|
||||||
*/
|
*/
|
||||||
public static <T> T parse(String content, TypeReference<?> typeReference) {
|
public static <T> T parse(String content, TypeReference<T> typeReference) {
|
||||||
try {
|
try {
|
||||||
return getInstance().readValue(content, typeReference);
|
return getInstance().readValue(content, typeReference);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@ -130,7 +131,7 @@ public class JsonUtil {
|
|||||||
* @param <T> T 泛型标记
|
* @param <T> T 泛型标记
|
||||||
* @return Bean
|
* @return Bean
|
||||||
*/
|
*/
|
||||||
public static <T> T parse(byte[] bytes, TypeReference<?> typeReference) {
|
public static <T> T parse(byte[] bytes, TypeReference<T> typeReference) {
|
||||||
try {
|
try {
|
||||||
return getInstance().readValue(bytes, typeReference);
|
return getInstance().readValue(bytes, typeReference);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@ -162,7 +163,7 @@ public class JsonUtil {
|
|||||||
* @param <T> T 泛型标记
|
* @param <T> T 泛型标记
|
||||||
* @return Bean
|
* @return Bean
|
||||||
*/
|
*/
|
||||||
public static <T> T parse(InputStream in, TypeReference<?> typeReference) {
|
public static <T> T parse(InputStream in, TypeReference<T> typeReference) {
|
||||||
try {
|
try {
|
||||||
return getInstance().readValue(in, typeReference);
|
return getInstance().readValue(in, typeReference);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@ -184,7 +185,7 @@ public class JsonUtil {
|
|||||||
content = StringPool.LEFT_SQ_BRACKET + content + StringPool.RIGHT_SQ_BRACKET;
|
content = StringPool.LEFT_SQ_BRACKET + content + StringPool.RIGHT_SQ_BRACKET;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Map<String, Object>> list = getInstance().readValue(content, new TypeReference<List<T>>() {
|
List<Map<String, Object>> list = getInstance().readValue(content, new TypeReference<List<Map<String, Object>>>() {
|
||||||
});
|
});
|
||||||
List<T> result = new ArrayList<>();
|
List<T> result = new ArrayList<>();
|
||||||
for (Map<String, Object> map : list) {
|
for (Map<String, Object> map : list) {
|
||||||
@ -208,7 +209,7 @@ public class JsonUtil {
|
|||||||
|
|
||||||
public static <T> Map<String, T> toMap(String content, Class<T> valueTypeRef) {
|
public static <T> Map<String, T> toMap(String content, Class<T> valueTypeRef) {
|
||||||
try {
|
try {
|
||||||
Map<String, Map<String, Object>> map = getInstance().readValue(content, new TypeReference<Map<String, T>>() {
|
Map<String, Map<String, Object>> map = getInstance().readValue(content, new TypeReference<Map<String, Map<String, Object>>>() {
|
||||||
});
|
});
|
||||||
Map<String, T> result = new HashMap<>(16);
|
Map<String, T> result = new HashMap<>(16);
|
||||||
for (Map.Entry<String, Map<String, Object>> entry : map.entrySet()) {
|
for (Map.Entry<String, Map<String, Object>> entry : map.entrySet()) {
|
||||||
@ -305,8 +306,8 @@ public class JsonUtil {
|
|||||||
//序列化时,日期的统一格式
|
//序列化时,日期的统一格式
|
||||||
super.setDateFormat(new SimpleDateFormat(DateUtil.PATTERN_DATETIME, Locale.CHINA));
|
super.setDateFormat(new SimpleDateFormat(DateUtil.PATTERN_DATETIME, Locale.CHINA));
|
||||||
//序列化处理
|
//序列化处理
|
||||||
super.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
|
super.configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), true);
|
||||||
super.configure(JsonParser.Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, true);
|
super.configure(JsonReadFeature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER.mappedFeature(), true);
|
||||||
super.findAndRegisterModules();
|
super.findAndRegisterModules();
|
||||||
//失败处理
|
//失败处理
|
||||||
super.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
|
super.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package org.springblade.core.tool.support.xss;
|
package org.springblade.core.tool.support.xss;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springblade.core.tool.utils.StringPool;
|
||||||
|
|
||||||
import javax.servlet.*;
|
import javax.servlet.*;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
@ -30,6 +31,7 @@ import java.io.IOException;
|
|||||||
public class XssFilter implements Filter {
|
public class XssFilter implements Filter {
|
||||||
|
|
||||||
private XssProperties xssProperties;
|
private XssProperties xssProperties;
|
||||||
|
private XssUrlProperties xssUrlProperties;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(FilterConfig config) {
|
public void init(FilterConfig config) {
|
||||||
@ -39,7 +41,7 @@ public class XssFilter implements Filter {
|
|||||||
@Override
|
@Override
|
||||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||||
String path = ((HttpServletRequest) request).getServletPath();
|
String path = ((HttpServletRequest) request).getServletPath();
|
||||||
if (xssProperties.getExcludePatterns().stream().anyMatch(path::contains)) {
|
if (isSkip(path)) {
|
||||||
chain.doFilter(request, response);
|
chain.doFilter(request, response);
|
||||||
} else {
|
} else {
|
||||||
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);
|
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);
|
||||||
@ -47,6 +49,11 @@ public class XssFilter implements Filter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isSkip(String path) {
|
||||||
|
return (xssUrlProperties.getExcludePatterns().stream().anyMatch(path::startsWith))
|
||||||
|
|| (xssProperties.getSkipUrl().stream().map(url -> url.replace("/**", StringPool.EMPTY)).anyMatch(path::startsWith));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void destroy() {
|
public void destroy() {
|
||||||
|
|
||||||
|
@ -15,9 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.springblade.core.tool.support.xss;
|
package org.springblade.core.tool.support.xss;
|
||||||
|
|
||||||
import org.springblade.core.tool.utils.Charsets;
|
|
||||||
import org.springblade.core.tool.utils.StringUtil;
|
import org.springblade.core.tool.utils.StringUtil;
|
||||||
import org.springblade.core.tool.utils.WebUtil;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
|
||||||
@ -29,6 +27,7 @@ import java.io.BufferedReader;
|
|||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -49,15 +48,9 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
|
|||||||
*/
|
*/
|
||||||
private final static HtmlFilter HTML_FILTER = new HtmlFilter();
|
private final static HtmlFilter HTML_FILTER = new HtmlFilter();
|
||||||
|
|
||||||
/**
|
public XssHttpServletRequestWrapper(HttpServletRequest request) {
|
||||||
* 缓存报文,支持多次读取流
|
|
||||||
*/
|
|
||||||
private final byte[] body;
|
|
||||||
|
|
||||||
public XssHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
|
|
||||||
super(request);
|
super(request);
|
||||||
orgRequest = request;
|
orgRequest = request;
|
||||||
body = WebUtil.getRequestBytes(request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -67,51 +60,67 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ServletInputStream getInputStream() throws IOException {
|
public ServletInputStream getInputStream() throws IOException {
|
||||||
|
|
||||||
//为空,直接返回
|
|
||||||
if (null == super.getHeader(HttpHeaders.CONTENT_TYPE)) {
|
if (null == super.getHeader(HttpHeaders.CONTENT_TYPE)) {
|
||||||
return super.getInputStream();
|
return super.getInputStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
//非json类型,直接返回
|
if (super.getHeader(HttpHeaders.CONTENT_TYPE).startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)) {
|
||||||
if (!super.getHeader(HttpHeaders.CONTENT_TYPE).equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)
|
|
||||||
&& !super.getHeader(HttpHeaders.CONTENT_TYPE).equalsIgnoreCase(MediaType.APPLICATION_JSON_UTF8_VALUE)) {
|
|
||||||
return super.getInputStream();
|
return super.getInputStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
//为空,直接返回
|
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(inputHandlers(super.getInputStream()).getBytes());
|
||||||
String requestStr = WebUtil.getRequestStr(orgRequest, body);
|
|
||||||
if (StringUtil.isBlank(requestStr)) {
|
|
||||||
return super.getInputStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
requestStr = xssEncode(requestStr);
|
|
||||||
|
|
||||||
final ByteArrayInputStream bis = new ByteArrayInputStream(requestStr.getBytes(Charsets.UTF_8));
|
|
||||||
|
|
||||||
return new ServletInputStream() {
|
return new ServletInputStream() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() {
|
||||||
|
return byteArrayInputStream.read();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFinished() {
|
public boolean isFinished() {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isReady() {
|
public boolean isReady() {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setReadListener(ReadListener readListener) {
|
public void setReadListener(ReadListener readListener) {
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read() throws IOException {
|
|
||||||
return bis.read();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private String inputHandlers(ServletInputStream servletInputStream) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
BufferedReader reader = null;
|
||||||
|
try {
|
||||||
|
reader = new BufferedReader(new InputStreamReader(servletInputStream, StandardCharsets.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 xssEncode(sb.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -129,7 +138,6 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
|
|||||||
if (parameters == null || parameters.length == 0) {
|
if (parameters == null || parameters.length == 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < parameters.length; i++) {
|
for (int i = 0; i < parameters.length; i++) {
|
||||||
parameters[i] = xssEncode(parameters[i]);
|
parameters[i] = xssEncode(parameters[i]);
|
||||||
}
|
}
|
||||||
@ -165,6 +173,7 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取最原始的request
|
* 获取最原始的request
|
||||||
|
*
|
||||||
* @return HttpServletRequest
|
* @return HttpServletRequest
|
||||||
*/
|
*/
|
||||||
public HttpServletRequest getOrgRequest() {
|
public HttpServletRequest getOrgRequest() {
|
||||||
@ -173,6 +182,7 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取最原始的request
|
* 获取最原始的request
|
||||||
|
*
|
||||||
* @param request request
|
* @param request request
|
||||||
* @return HttpServletRequest
|
* @return HttpServletRequest
|
||||||
*/
|
*/
|
||||||
@ -180,7 +190,6 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
|
|||||||
if (request instanceof XssHttpServletRequestWrapper) {
|
if (request instanceof XssHttpServletRequestWrapper) {
|
||||||
return ((XssHttpServletRequestWrapper) request).getOrgRequest();
|
return ((XssHttpServletRequestWrapper) request).getOrgRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,9 +27,17 @@ import java.util.List;
|
|||||||
* @author Chill
|
* @author Chill
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@ConfigurationProperties("blade.xss.url")
|
@ConfigurationProperties("blade.xss")
|
||||||
public class XssProperties {
|
public class XssProperties {
|
||||||
|
|
||||||
private final List<String> excludePatterns = new ArrayList<>();
|
/**
|
||||||
|
* 开启xss
|
||||||
|
*/
|
||||||
|
private Boolean enable = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 放行url
|
||||||
|
*/
|
||||||
|
private List<String> skipUrl = new ArrayList<>();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
|
||||||
|
* <p>
|
||||||
|
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* <p>
|
||||||
|
* http://www.gnu.org/licenses/lgpl.html
|
||||||
|
* <p>
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springblade.core.tool.support.xss;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Xss配置类
|
||||||
|
*
|
||||||
|
* @author Chill
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ConfigurationProperties("blade.xss.url")
|
||||||
|
public class XssUrlProperties {
|
||||||
|
|
||||||
|
private final List<String> excludePatterns = new ArrayList<>();
|
||||||
|
|
||||||
|
}
|
@ -1124,7 +1124,7 @@ public class Func {
|
|||||||
* @param <T> T 泛型标记
|
* @param <T> T 泛型标记
|
||||||
* @return Bean
|
* @return Bean
|
||||||
*/
|
*/
|
||||||
public static <T> T parse(byte[] bytes, TypeReference<?> typeReference) {
|
public static <T> T parse(byte[] bytes, TypeReference<T> typeReference) {
|
||||||
return JsonUtil.parse(bytes, typeReference);
|
return JsonUtil.parse(bytes, typeReference);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1136,7 +1136,7 @@ public class Func {
|
|||||||
* @param <T> T 泛型标记
|
* @param <T> T 泛型标记
|
||||||
* @return Bean
|
* @return Bean
|
||||||
*/
|
*/
|
||||||
public static <T> T parse(String jsonString, TypeReference<?> typeReference) {
|
public static <T> T parse(String jsonString, TypeReference<T> typeReference) {
|
||||||
return JsonUtil.parse(jsonString, typeReference);
|
return JsonUtil.parse(jsonString, typeReference);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1148,7 +1148,7 @@ public class Func {
|
|||||||
* @param <T> T 泛型标记
|
* @param <T> T 泛型标记
|
||||||
* @return Bean
|
* @return Bean
|
||||||
*/
|
*/
|
||||||
public static <T> T parse(InputStream in, TypeReference<?> typeReference) {
|
public static <T> T parse(InputStream in, TypeReference<T> typeReference) {
|
||||||
return JsonUtil.parse(in, typeReference);
|
return JsonUtil.parse(in, typeReference);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,7 +186,7 @@ public class StringUtil extends org.springframework.util.StringUtils {
|
|||||||
* @return {String}
|
* @return {String}
|
||||||
*/
|
*/
|
||||||
public static String escapeHtml(String html) {
|
public static String escapeHtml(String html) {
|
||||||
return HtmlUtils.htmlEscape(html);
|
return StringUtil.isBlank(html) ? StringPool.EMPTY : HtmlUtils.htmlEscape(html);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1250,7 +1250,7 @@ public class StringUtil extends org.springframework.util.StringUtils {
|
|||||||
/**
|
/**
|
||||||
* 创建StringBuilder对象
|
* 创建StringBuilder对象
|
||||||
*
|
*
|
||||||
* @param sb 初始StringBuilder
|
* @param sb 初始StringBuilder
|
||||||
* @param strs 初始字符串列表
|
* @param strs 初始字符串列表
|
||||||
* @return StringBuilder对象
|
* @return StringBuilder对象
|
||||||
*/
|
*/
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>blade-tool</artifactId>
|
<artifactId>blade-tool</artifactId>
|
||||||
<groupId>org.springblade</groupId>
|
<groupId>org.springblade</groupId>
|
||||||
<version>2.5.4</version>
|
<version>2.6.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
18
pom.xml
18
pom.xml
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<groupId>org.springblade</groupId>
|
<groupId>org.springblade</groupId>
|
||||||
<artifactId>blade-tool</artifactId>
|
<artifactId>blade-tool</artifactId>
|
||||||
<version>2.5.4</version>
|
<version>2.6.0</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<name>blade-tool</name>
|
<name>blade-tool</name>
|
||||||
<description>
|
<description>
|
||||||
@ -36,24 +36,24 @@
|
|||||||
</scm>
|
</scm>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<blade.tool.version>2.5.4</blade.tool.version>
|
<blade.tool.version>2.6.0</blade.tool.version>
|
||||||
|
|
||||||
<java.version>1.8</java.version>
|
<java.version>1.8</java.version>
|
||||||
<maven.plugin.version>3.8.0</maven.plugin.version>
|
<maven.plugin.version>3.8.0</maven.plugin.version>
|
||||||
<swagger.version>2.9.2</swagger.version>
|
<swagger.version>2.9.2</swagger.version>
|
||||||
<swagger.models.version>1.5.21</swagger.models.version>
|
<swagger.models.version>1.5.21</swagger.models.version>
|
||||||
<swagger.bootstrapui.version>1.9.6</swagger.bootstrapui.version>
|
<knife4j.version>2.0.1</knife4j.version>
|
||||||
<mybatis.plus.version>3.1.2</mybatis.plus.version>
|
<mybatis.plus.version>3.2.0</mybatis.plus.version>
|
||||||
<curator.framework.version>4.0.1</curator.framework.version>
|
<curator.framework.version>4.0.1</curator.framework.version>
|
||||||
<protostuff.version>1.6.0</protostuff.version>
|
<protostuff.version>1.6.0</protostuff.version>
|
||||||
<disruptor.version>3.4.2</disruptor.version>
|
<disruptor.version>3.4.2</disruptor.version>
|
||||||
<spring.boot.admin.version>2.1.5</spring.boot.admin.version>
|
<spring.boot.admin.version>2.2.0</spring.boot.admin.version>
|
||||||
<mica.auto.version>1.1.0</mica.auto.version>
|
<mica.auto.version>1.1.0</mica.auto.version>
|
||||||
<alibaba.cloud.version>2.1.0.RELEASE</alibaba.cloud.version>
|
<alibaba.cloud.version>2.1.1.RELEASE</alibaba.cloud.version>
|
||||||
<alibaba.seata.version>0.9.0</alibaba.seata.version>
|
<alibaba.seata.version>1.0.0</alibaba.seata.version>
|
||||||
|
|
||||||
<spring.boot.version>2.1.9.RELEASE</spring.boot.version>
|
<spring.boot.version>2.2.2.RELEASE</spring.boot.version>
|
||||||
<spring.cloud.version>Greenwich.SR3</spring.cloud.version>
|
<spring.cloud.version>Hoxton.SR1</spring.cloud.version>
|
||||||
<spring.platform.version>Cairo-SR8</spring.platform.version>
|
<spring.platform.version>Cairo-SR8</spring.platform.version>
|
||||||
|
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
Loading…
Reference in New Issue
Block a user