Merge pull request #3 from chillzhuang/master

up
This commit is contained in:
k 2020-11-09 10:09:02 +08:00 committed by GitHub
commit f61eaf9c8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
100 changed files with 2676 additions and 511 deletions

View File

@ -1,7 +1,7 @@
<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/Spring%20Cloud-Greenwich.SR3-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%20Cloud-Hoxton.SR8-blue.svg" alt="Coverage Status">
<img src="https://img.shields.io/badge/Spring%20Boot-2.2.11.RELEASE-blue.svg" alt="Downloads">
</p>
## SpringBlade微服务开发平台
@ -33,19 +33,24 @@ blade-tool
├── blade-core-secure -- 安全模块
├── blade-core-swagger -- swagger拓展封装模块
├── blade-core-test -- 单元测试封装模块
└── blade-core-tool -- 工具包模块
├── blade-core-tool -- 单元测试封装模块
└── blade-core-transaction -- 分布式事物封装模块
```
## 官网
* 官网地址:[https://bladex.vip](https://bladex.vip)
* 问答社区:[https://sns.bladex.vip](https://sns.bladex.vip)
* 会员计划:[SpringBlade会员计划](https://gitee.com/smallc/SpringBlade/wikis/SpringBlade会员计划)
* 交流群:`477853168`
* 交流一群:`477853168`(满)
* 交流二群:`751253339`(满)
* 交流三群:`784729540`(满)
* 交流四群:`1034621754`(满)
* 交流五群:`946350912`
## 在线演示
* Sword演示地址[https://sword.bladex.vip](https://sword.bladex.vip)
* Saber演示地址[https://saber.bladex.vip](https://saber.bladex.vip)
* 数据大屏演示地址:[https://data.avuejs.com](https://data.avuejs.com)
## 项目地址
* 后端Gitee地址[https://gitee.com/smallc/SpringBlade](https://gitee.com/smallc/SpringBlade)

View File

@ -5,7 +5,7 @@
<parent>
<groupId>org.springblade</groupId>
<artifactId>blade-tool</artifactId>
<version>2.5.4</version>
<version>2.8.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -62,33 +62,6 @@
<artifactId>blade-core-swagger</artifactId>
<version>${blade.tool.version}</version>
</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-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--MyBatis-->
<dependency>
<groupId>org.springblade</groupId>
@ -115,13 +88,13 @@
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.18</version>
<version>1.1.22</version>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
<version>8.0.16</version>
</dependency>
<!-- PostgreSql -->
<!--<dependency>

View File

@ -26,6 +26,7 @@ import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* 配置类
*
* @author Chill
*/
@Slf4j
@ -41,6 +42,8 @@ public class BladeBootAutoConfiguration {
/**
* 全局变量定义
*
* @return SystemConstant
*/
@Bean
public SystemConstant fileConst() {

View File

@ -15,14 +15,18 @@
*/
package org.springblade.core.boot.config;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor;
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import lombok.AllArgsConstructor;
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.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
/**
* mybatisplus 配置
@ -30,24 +34,43 @@ import org.springframework.context.annotation.Profile;
* @author Chill
*/
@Configuration
@AllArgsConstructor
@MapperScan("org.springblade.**.mapper.**")
public class MybatisPlusConfiguration {
private final TenantLineHandler tenantLineHandler;
/**
* mybatis-plus 拦截器集合
*/
@Bean
@ConditionalOnMissingBean(PaginationInterceptor.class)
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
@ConditionalOnMissingBean(MybatisPlusInterceptor.class)
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 配置租户拦截器
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(tenantLineHandler));
// 配置分页拦截器
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
/**
* SQL执行效率插件
*
* @return PerformanceInterceptor
* mybatis-plus自3.4.0起采用新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
*/
@Bean
@Profile({AppConstant.DEV_CODE, AppConstant.TEST_CODE})
public PerformanceInterceptor performanceInterceptor() {
return new PerformanceInterceptor();
public ConfigurationCustomizer configurationCustomizer() {
return configuration -> configuration.setUseDeprecatedExecutor(false);
}
/**
* sql 日志
*
* @return SqlLogInterceptor
*/
@Bean
@ConditionalOnProperty(value = "blade.mybatis-plus.sql-log", matchIfMissing = true)
public SqlLogInterceptor sqlLogInterceptor() {
return new SqlLogInterceptor();
}
}

View File

@ -99,8 +99,8 @@ public class BladeFileUtil {
/**
* 文本排序转换成枚举
*
* @param sort
* @return
* @param sort 排序
* @return FileSort
*/
public static FileSort of(String sort) {
try {
@ -228,6 +228,7 @@ public class BladeFileUtil {
* 获取BladeFile封装类
*
* @param files 文件集合
* @param dir 路径
* @param path 路径
* @param virtualPath 虚拟路径
* @return BladeFile

View File

@ -29,7 +29,7 @@ public interface IFileProxy {
*
* @param file 文件
* @param dir 目录
* @return
* @return String
*/
String[] path(File file, String dir);
@ -38,7 +38,7 @@ public interface IFileProxy {
*
* @param file 文件
* @param path 路径
* @return
* @return File
*/
File rename(File file, String path);

View File

@ -27,6 +27,7 @@ import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Spring boot 控制器 请求日志方便代码调试
@ -66,6 +67,7 @@ public class RequestLogAspect {
continue;
}
RequestBody requestBody = methodParam.getParameterAnnotation(RequestBody.class);
String parameterName = methodParam.getParameterName();
Object value = args[i];
// 如果是body的json则是对象
if (requestBody != null && value != null) {
@ -89,6 +91,19 @@ public class RequestLogAspect {
} else if (value instanceof HttpServletResponse) {
} else if (value instanceof InputStream) {
} else if (value instanceof InputStreamSource) {
} else if (value instanceof List) {
List<?> list = (List<?>) value;
AtomicBoolean isSkip = new AtomicBoolean(false);
for (Object o : list) {
if ("StandardMultipartFile".equalsIgnoreCase(o.getClass().getSimpleName())) {
isSkip.set(true);
break;
}
}
if (isSkip.get()) {
paraMap.put(parameterName, "此参数不能序列化为json");
continue;
}
} else {
// 参数名
RequestParam requestParam = methodParam.getParameterAnnotation(RequestParam.class);

View File

@ -15,7 +15,7 @@
*/
package org.springblade.core.boot.tenant;
import com.baomidou.mybatisplus.extension.plugins.tenant.TenantHandler;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
@ -31,7 +31,7 @@ import org.springblade.core.tool.utils.StringUtil;
*/
@Slf4j
@AllArgsConstructor
public class BladeTenantHandler implements TenantHandler {
public class BladeTenantHandler implements TenantLineHandler {
private final BladeTenantProperties properties;
@ -59,10 +59,10 @@ public class BladeTenantHandler implements TenantHandler {
* 过滤租户表
*
* @param tableName 表名
* @return 是否进行过滤
* @return 是否忽略, true:表示忽略false:需要解析并拼接多租户条件
*/
@Override
public boolean doTableFilter(String tableName) {
public boolean ignoreTable(String tableName) {
return !(
(
(properties.getTables().size() > 0 && properties.getTables().contains(tableName))

View File

@ -46,5 +46,5 @@ public class BladeTenantProperties {
/**
* 多租户系统数据表
*/
private List<String> bladeTables = Arrays.asList("blade_notice", "blade_log_api", "blade_log_error", "blade_log_usual");
private List<String> bladeTables = Arrays.asList("blade_notice", "blade_post", "blade_log_api", "blade_log_error", "blade_log_usual");
}

View File

@ -15,21 +15,15 @@
*/
package org.springblade.core.boot.tenant;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.parser.ISqlParser;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.tenant.TenantHandler;
import com.baomidou.mybatisplus.extension.plugins.tenant.TenantSqlParser;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import lombok.AllArgsConstructor;
import org.springblade.core.boot.config.MybatisPlusConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
/**
* 多租户配置类
*
@ -37,7 +31,7 @@ import java.util.List;
*/
@Configuration
@AllArgsConstructor
@AutoConfigureBefore(MybatisConfiguration.class)
@AutoConfigureBefore(MybatisPlusConfiguration.class)
@EnableConfigurationProperties(BladeTenantProperties.class)
public class TenantConfiguration {
@ -52,8 +46,8 @@ public class TenantConfiguration {
* @return TenantHandler
*/
@Bean
@ConditionalOnMissingBean(TenantHandler.class)
public TenantHandler bladeTenantHandler() {
@ConditionalOnMissingBean(TenantLineHandler.class)
public TenantLineHandler bladeTenantHandler() {
return new BladeTenantHandler(properties);
}
@ -68,21 +62,4 @@ public class TenantConfiguration {
return new BladeTenantId();
}
/**
* 分页插件
*
* @param tenantHandler 自定义租户处理器
* @return PaginationInterceptor
*/
@Bean
public PaginationInterceptor paginationInterceptor(TenantHandler tenantHandler) {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
List<ISqlParser> sqlParserList = new ArrayList<>();
TenantSqlParser tenantSqlParser = new TenantSqlParser();
tenantSqlParser.setTenantHandler(tenantHandler);
sqlParserList.add(tenantSqlParser);
paginationInterceptor.setSqlParserList(sqlParserList);
return paginationInterceptor;
}
}

View File

@ -60,9 +60,6 @@ spring:
#maximum-pool-size: 5
#max-lifetime: 1800000
#minimum-idle: 1
devtools:
restart:
log-condition-evaluation-delta: false
#配置日志地址
@ -80,7 +77,7 @@ mybatis-plus:
banner: false
db-config:
#主键类型 0:"数据库ID自增", 1:"不操作", 2:"用户输入ID",3:"数字型snowflake", 4:"全局唯一ID UUID", 5:"字符串型snowflake";
id-type: id_worker
id-type: assign_id
#字段策略
insert-strategy: not_null
update-strategy: not_null
@ -100,7 +97,7 @@ mybatis-plus:
swagger:
title: SpringBlade 接口文档系统
description: SpringBlade 接口文档系统
version: 2.5.4
version: 2.8.0
license: Powered By SpringBlade
licenseUrl: https://bladex.vip
terms-of-service-url: https://bladex.vip

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>2.5.4</version>
<version>2.8.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -27,11 +27,6 @@
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<!--Feign-->
<dependency>
<groupId>io.github.openfeign</groupId>
@ -87,6 +82,12 @@
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>${alibaba.cloud.version}</version>
</dependency>
<!-- Alibaba依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.74</version>
</dependency>
</dependencies>
</project>

View File

@ -55,7 +55,7 @@ import java.util.ArrayList;
public class BladeFeignAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnMissingBean(Targeter.class)
public Targeter bladeFeignTargeter() {
return new BladeHystrixTargeter();
}

View File

@ -1,32 +0,0 @@
package org.springblade.core.cloud.stream;
import org.springframework.cloud.stream.annotation.Input;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;
/**
* 服务异常 Streams
*
* @author L.cm
*/
public interface ServiceErrorStreams {
String INPUT = "service-error-in";
String OUTPUT = "service-error-out";
/**
* input
*
* @return SubscribableChannel
*/
@Input(INPUT)
SubscribableChannel subscribablebChannel();
/**
* output
*
* @return MessageChannel
*/
@Output(OUTPUT)
MessageChannel messageChannel();
}

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>2.5.4</version>
<version>2.8.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -210,6 +210,7 @@ public class BladeCodeGenerator {
map.put("codeName", codeName);
map.put("serviceName", serviceName);
map.put("servicePackage", servicePackage);
map.put("servicePackageLowerCase", servicePackage.toLowerCase());
map.put("tenantColumn", tenantColumn);
map.put("hasWrapper", hasWrapper);
this.setMap(map);

View File

@ -16,9 +16,9 @@
package $!{package.Controller};
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperationSupport;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import lombok.AllArgsConstructor;
import javax.validation.Valid;
@ -160,7 +160,7 @@ public class $!{table.controllerName} {
@ApiOperationSupport(order = 7)
@ApiOperation(value = "逻辑删除", notes = "传入ids")
public R remove(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
return R.status($!{table.entityPath}Service.deleteLogic(Func.toIntList(ids)));
return R.status($!{table.entityPath}Service.deleteLogic(Func.toLongList(ids)));
}
#else
@ -172,7 +172,7 @@ public class $!{table.controllerName} {
@ApiOperationSupport(order = 8)
@ApiOperation(value = "删除", notes = "传入ids")
public R remove(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
return R.status($!{table.entityPath}Service.removeByIds(Func.toIntList(ids)));
return R.status($!{table.entityPath}Service.removeByIds(Func.toLongList(ids)));
}
#end

View File

@ -47,6 +47,10 @@
},
selectionList: [],
option: {
height: 'auto',
calcHeight: 210,
searchShow: true,
searchMenuSpan: 6,
tip: false,
border: true,
index: true,
@ -90,30 +94,30 @@
}
},
methods: {
rowSave(row, loading, done) {
rowSave(row, done, loading) {
add(row).then(() => {
loading();
done();
this.onLoad(this.page);
this.$message({
type: "success",
message: "操作成功!"
});
}, error => {
done();
console.log(error);
window.console.log(error);
loading();
});
},
rowUpdate(row, index, loading, done) {
rowUpdate(row, index, done, loading) {
update(row).then(() => {
loading();
done();
this.onLoad(this.page);
this.$message({
type: "success",
message: "操作成功!"
});
}, error => {
done();
console.log(error);
window.console.log(error);
loading();
});
},
rowDel(row) {
@ -167,9 +171,11 @@
this.query = {};
this.onLoad(this.page);
},
searchChange(params) {
searchChange(params, done) {
this.query = params;
this.page.currentPage = 1;
this.onLoad(this.page, params);
done();
},
selectionChange(list) {
this.selectionList = list;

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>2.5.4</version>
<version>2.8.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -25,7 +25,7 @@ public interface AppConstant {
/**
* 应用版本
*/
String APPLICATION_VERSION = "2.5.4";
String APPLICATION_VERSION = "2.8.0";
/**
* 基础包
@ -76,6 +76,10 @@ public interface AppConstant {
* 链路追踪模块名称
*/
String APPLICATION_ZIPKIN_NAME = APPLICATION_NAME_PREFIX + "zipkin";
/**
* 报表系统名称
*/
String APPLICATION_REPORT_NAME = APPLICATION_NAME_PREFIX + "report";
/**
* 测试模块名称
*/

View File

@ -22,7 +22,7 @@ package org.springblade.core.launch.constant;
*/
public interface TokenConstant {
String SIGN_KEY = "Blade";
String SIGN_KEY = "bladexisapowerfulmicroservicearchitectureupgradedandoptimizedfromacommercialproject";
String AVATAR = "avatar";
String HEADER = "blade-auth";
String BEARER = "bearer";
@ -36,6 +36,7 @@ public interface TokenConstant {
String USER_NAME = "user_name";
String ROLE_NAME = "role_name";
String TENANT_ID = "tenant_id";
String OAUTH_ID = "oauth_id";
String CLIENT_ID = "client_id";
String LICENSE = "license";
String LICENSE_NAME = "powered by blade";

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>2.5.4</version>
<version>2.8.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -20,10 +20,7 @@ import org.springblade.core.launch.props.BladeProperties;
import org.springblade.core.launch.server.ServerInfo;
import org.springblade.core.log.model.LogAbstract;
import org.springblade.core.secure.utils.SecureUtil;
import org.springblade.core.tool.utils.DateUtil;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.UrlUtil;
import org.springblade.core.tool.utils.WebUtil;
import org.springblade.core.tool.utils.*;
import javax.servlet.http.HttpServletRequest;
@ -41,12 +38,14 @@ public class LogAbstractUtil {
* @param logAbstract 日志基础类
*/
public static void addRequestInfoToLog(HttpServletRequest request, LogAbstract logAbstract) {
logAbstract.setRemoteIp(WebUtil.getIP(request));
logAbstract.setUserAgent(request.getHeader(WebUtil.USER_AGENT_HEADER));
logAbstract.setRequestUri(UrlUtil.getPath(request.getRequestURI()));
logAbstract.setMethod(request.getMethod());
logAbstract.setParams(WebUtil.getRequestParamString(request));
logAbstract.setCreateBy(SecureUtil.getUserAccount(request));
if (ObjectUtil.isNotEmpty(request)) {
logAbstract.setRemoteIp(WebUtil.getIP(request));
logAbstract.setUserAgent(request.getHeader(WebUtil.USER_AGENT_HEADER));
logAbstract.setRequestUri(UrlUtil.getPath(request.getRequestURI()));
logAbstract.setMethod(request.getMethod());
logAbstract.setParams(WebUtil.getRequestParamString(request));
logAbstract.setCreateBy(SecureUtil.getUserAccount(request));
}
}
/**

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>2.5.4</version>
<version>2.8.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -18,6 +18,8 @@ package org.springblade.core.mp.base;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springblade.core.tool.utils.DateUtil;
@ -37,8 +39,9 @@ public class BaseEntity implements Serializable {
/**
* 创建人
*/
@JsonSerialize(using = ToStringSerializer.class)
@ApiModelProperty(value = "创建人")
private Integer createUser;
private Long createUser;
/**
* 创建时间
@ -51,8 +54,9 @@ public class BaseEntity implements Serializable {
/**
* 更新人
*/
@JsonSerialize(using = ToStringSerializer.class)
@ApiModelProperty(value = "更新人")
private Integer updateUser;
private Long updateUser;
/**
* 更新时间

View File

@ -32,8 +32,8 @@ public interface BaseService<T> extends IService<T> {
* 逻辑删除
*
* @param ids id集合(逗号分隔)
* @return
* @return boolean
*/
boolean deleteLogic(@NotEmpty List<Integer> ids);
boolean deleteLogic(@NotEmpty List<Long> ids);
}

View File

@ -24,8 +24,6 @@ import org.springblade.core.tool.utils.DateUtil;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotEmpty;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Date;
import java.util.List;
@ -39,14 +37,6 @@ import java.util.List;
@Validated
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
public boolean save(T entity) {
BladeUser user = SecureUtil.getUser();
@ -75,7 +65,7 @@ public class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseEntity> exte
}
@Override
public boolean deleteLogic(@NotEmpty List<Integer> ids) {
public boolean deleteLogic(@NotEmpty List<Long> ids) {
return super.removeByIds(ids);
}

View File

@ -18,6 +18,7 @@ package org.springblade.core.mp.base;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 租户基础实体类
@ -25,6 +26,7 @@ import lombok.Data;
* @author Chill
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class TenantEntity extends BaseEntity {
/**

View File

@ -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);
}
}

View File

@ -30,15 +30,17 @@ public abstract class BaseEntityWrapper<E, V> {
/**
* 单个实体类包装
* @param entity
* @return
*
* @param entity 实体类
* @return V
*/
public abstract V entityVO(E entity);
/**
* 实体类集合包装
* @param list
* @return
*
* @param list 猎豹
* @return List V
*/
public List<V> listVO(List<E> list) {
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) {
List<V> records = listVO(pages.getRecords());

View File

@ -18,6 +18,8 @@ package org.springblade.core.mp.support;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springblade.core.launch.constant.TokenConstant;
import org.springblade.core.tool.support.Kv;
import org.springblade.core.tool.utils.BeanUtil;
import org.springblade.core.tool.utils.Func;
@ -33,8 +35,8 @@ public class Condition {
/**
* 转化成mybatis plus中的Page
*
* @param query
* @return
* @param query 查询条件
* @return IPage
*/
public static <T> IPage<T> getPage(Query query) {
Page<T> page = new Page<>(Func.toInt(query.getCurrent(), 1), Func.toInt(query.getSize(), 10));
@ -46,9 +48,9 @@ public class Condition {
/**
* 获取mybatis plus中的QueryWrapper
*
* @param entity
* @param <T>
* @return
* @param entity 实体
* @param <T> 类型
* @return QueryWrapper
*/
public static <T> QueryWrapper<T> getQueryWrapper(T entity) {
return new QueryWrapper<>(entity);
@ -57,16 +59,28 @@ public class Condition {
/**
* 获取mybatis plus中的QueryWrapper
*
* @param query
* @param clazz
* @param <T>
* @return
* @param query 查询条件
* @param clazz 实体类
* @param <T> 类型
* @return QueryWrapper
*/
public static <T> QueryWrapper<T> getQueryWrapper(Map<String, Object> query, Class<T> clazz) {
query.remove("current");
query.remove("size");
query.remove("ascs");
query.remove("descs");
Kv exclude = Kv.init().set(TokenConstant.HEADER, TokenConstant.HEADER)
.set("current", "current").set("size", "size").set("ascs", "ascs").set("descs", "descs");
return getQueryWrapper(query, exclude, clazz);
}
/**
* 获取mybatis plus中的QueryWrapper
*
* @param query 查询条件
* @param exclude 排除的查询条件
* @param clazz 实体类
* @param <T> 类型
* @return QueryWrapper
*/
public static <T> QueryWrapper<T> getQueryWrapper(Map<String, Object> query, Map<String, Object> exclude, Class<T> clazz) {
exclude.forEach((k, v) -> query.remove(k));
QueryWrapper<T> qw = new QueryWrapper<>();
qw.setEntity(BeanUtil.newInstance(clazz));
SqlKeyword.buildCondition(query, qw);

View File

@ -88,7 +88,7 @@ public class SqlKeyword {
*
* @param column 字段名
* @param keyword 关键字
* @return
* @return String
*/
private static String getColumn(String column, String keyword) {
return StringUtil.humpToUnderline(StringUtil.removeSuffix(column, keyword));

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>2.5.4</version>
<version>2.8.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -27,6 +27,12 @@
<artifactId>qiniu-java-sdk</artifactId>
<version>7.2.18</version>
</dependency>
<!--alioss-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.1.0</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,283 @@
/**
* 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.oss;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PolicyConditions;
import com.aliyun.oss.model.PutObjectResult;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.springblade.core.oss.model.BladeFile;
import org.springblade.core.oss.model.OssFile;
import org.springblade.core.oss.props.OssProperties;
import org.springblade.core.oss.rule.OssRule;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springblade.core.tool.utils.StringPool;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* AliossTemplate
*
* @author Chill
*/
@AllArgsConstructor
public class AliossTemplate {
private OSSClient ossClient;
private OssProperties ossProperties;
private OssRule ossRule;
@SneakyThrows
public void makeBucket(String bucketName) {
if (!bucketExists(bucketName)) {
ossClient.createBucket(getBucketName(bucketName));
}
}
@SneakyThrows
public void removeBucket(String bucketName) {
ossClient.deleteBucket(getBucketName(bucketName));
}
@SneakyThrows
public boolean bucketExists(String bucketName) {
return ossClient.doesBucketExist(getBucketName(bucketName));
}
@SneakyThrows
public void copyFile(String bucketName, String fileName, String destBucketName) {
ossClient.copyObject(getBucketName(bucketName), fileName, getBucketName(destBucketName), fileName);
}
@SneakyThrows
public void copyFile(String bucketName, String fileName, String destBucketName, String destFileName) {
ossClient.copyObject(getBucketName(bucketName), fileName, getBucketName(destBucketName), destFileName);
}
@SneakyThrows
public OssFile statFile(String fileName) {
return statFile(ossProperties.getBucketName(), fileName);
}
@SneakyThrows
public OssFile statFile(String bucketName, String fileName) {
ObjectMetadata stat = ossClient.getObjectMetadata(getBucketName(bucketName), fileName);
OssFile ossFile = new OssFile();
ossFile.setName(fileName);
ossFile.setLink(fileLink(ossFile.getName()));
ossFile.setHash(stat.getContentMD5());
ossFile.setLength(stat.getContentLength());
ossFile.setPutTime(stat.getLastModified());
ossFile.setContentType(stat.getContentType());
return ossFile;
}
@SneakyThrows
public String filePath(String fileName) {
return getOssHost().concat(StringPool.SLASH).concat(fileName);
}
@SneakyThrows
public String filePath(String bucketName, String fileName) {
return getOssHost(bucketName).concat(StringPool.SLASH).concat(fileName);
}
@SneakyThrows
public String fileLink(String fileName) {
return getOssHost().concat(StringPool.SLASH).concat(fileName);
}
@SneakyThrows
public String fileLink(String bucketName, String fileName) {
return getOssHost(bucketName).concat(StringPool.SLASH).concat(fileName);
}
/**
* 文件对象
*
* @param file 上传文件类
* @return
*/
@SneakyThrows
public BladeFile putFile(MultipartFile file) {
return putFile(ossProperties.getBucketName(), file.getOriginalFilename(), file);
}
/**
* @param fileName 上传文件名
* @param file 上传文件类
* @return
*/
@SneakyThrows
public BladeFile putFile(String fileName, MultipartFile file) {
return putFile(ossProperties.getBucketName(), fileName, file);
}
@SneakyThrows
public BladeFile putFile(String bucketName, String fileName, MultipartFile file) {
return putFile(bucketName, fileName, file.getInputStream());
}
@SneakyThrows
public BladeFile putFile(String fileName, InputStream stream) {
return putFile(ossProperties.getBucketName(), fileName, stream);
}
@SneakyThrows
public BladeFile putFile(String bucketName, String fileName, InputStream stream) {
return put(bucketName, stream, fileName, false);
}
@SneakyThrows
public BladeFile put(String bucketName, InputStream stream, String key, boolean cover) {
makeBucket(bucketName);
String originalName = key;
key = getFileName(key);
// 覆盖上传
if (cover) {
ossClient.putObject(getBucketName(bucketName), key, stream);
} else {
PutObjectResult response = ossClient.putObject(getBucketName(bucketName), key, stream);
int retry = 0;
int retryCount = 5;
while (StringUtils.isEmpty(response.getETag()) && retry < retryCount) {
response = ossClient.putObject(getBucketName(bucketName), key, stream);
retry++;
}
}
BladeFile file = new BladeFile();
file.setOriginalName(originalName);
file.setName(key);
file.setLink(fileLink(bucketName, key));
return file;
}
@SneakyThrows
public void removeFile(String fileName) {
ossClient.deleteObject(getBucketName(), fileName);
}
@SneakyThrows
public void removeFile(String bucketName, String fileName) {
ossClient.deleteObject(getBucketName(bucketName), fileName);
}
@SneakyThrows
public void removeFiles(List<String> fileNames) {
fileNames.forEach(this::removeFile);
}
@SneakyThrows
public void removeFiles(String bucketName, List<String> fileNames) {
fileNames.forEach(fileName -> removeFile(getBucketName(bucketName), fileName));
}
/**
* 根据规则生成存储桶名称规则
*
* @return String
*/
private String getBucketName() {
return getBucketName(ossProperties.getBucketName());
}
/**
* 根据规则生成存储桶名称规则
*
* @param bucketName 存储桶名称
* @return String
*/
private String getBucketName(String bucketName) {
return ossRule.bucketName(bucketName);
}
/**
* 根据规则生成文件名称规则
*
* @param originalFilename 原始文件名
* @return string
*/
private String getFileName(String originalFilename) {
return ossRule.fileName(originalFilename);
}
public String getUploadToken() {
return getUploadToken(ossProperties.getBucketName());
}
/**
* TODO 过期时间
* <p>
* 获取上传凭证普通上传
*/
public String getUploadToken(String bucketName) {
// 默认过期时间2小时
return getUploadToken(bucketName, ossProperties.getArgs().get("expireTime", 3600L));
}
/**
* TODO 上传大小限制基础路径
* <p>
* 获取上传凭证普通上传
*/
public String getUploadToken(String bucketName, long expireTime) {
String baseDir = "upload";
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
Date expiration = new Date(expireEndTime);
PolicyConditions policyConds = new PolicyConditions();
// 默认大小限制10M
policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, ossProperties.getArgs().get("contentLengthRange", 10485760));
policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, baseDir);
String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
byte[] binaryData = postPolicy.getBytes(StandardCharsets.UTF_8);
String encodedPolicy = BinaryUtil.toBase64String(binaryData);
String postSignature = ossClient.calculatePostSignature(postPolicy);
Map<String, String> respMap = new LinkedHashMap<>(16);
respMap.put("accessid", ossProperties.getAccessKey());
respMap.put("policy", encodedPolicy);
respMap.put("signature", postSignature);
respMap.put("dir", baseDir);
respMap.put("host", getOssHost(bucketName));
respMap.put("expire", String.valueOf(expireEndTime / 1000));
return JsonUtil.toJson(respMap);
}
public String getOssHost(String bucketName) {
String prefix = ossProperties.getEndpoint().contains("https://") ? "https://" : "http://";
return prefix + getBucketName(bucketName) + StringPool.DOT + ossProperties.getEndpoint().replaceFirst(prefix, StringPool.EMPTY);
}
public String getOssHost() {
return getOssHost(ossProperties.getBucketName());
}
}

View File

@ -0,0 +1,83 @@
/**
* 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.oss.config;
import com.aliyun.oss.ClientConfiguration;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.common.auth.CredentialsProvider;
import com.aliyun.oss.common.auth.DefaultCredentialProvider;
import lombok.AllArgsConstructor;
import org.springblade.core.oss.AliossTemplate;
import org.springblade.core.oss.props.OssProperties;
import org.springblade.core.oss.rule.BladeOssRule;
import org.springblade.core.oss.rule.OssRule;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Alioss配置类
*
* @author Chill
*/
@Configuration
@AllArgsConstructor
@AutoConfigureAfter(QiniuConfiguration.class)
@EnableConfigurationProperties(OssProperties.class)
@ConditionalOnProperty(value = "oss.name", havingValue = "alioss")
public class AliossConfiguration {
private OssProperties ossProperties;
@Bean
@ConditionalOnMissingBean(OssRule.class)
public OssRule ossRule() {
return new BladeOssRule();
}
@Bean
@ConditionalOnMissingBean(OSSClient.class)
public OSSClient ossClient() {
// 创建ClientConfigurationClientConfiguration是OSSClient的配置类可配置代理连接超时最大连接数等参数
ClientConfiguration conf = new ClientConfiguration();
// 设置OSSClient允许打开的最大HTTP连接数默认为1024个
conf.setMaxConnections(1024);
// 设置Socket层传输数据的超时时间默认为50000毫秒
conf.setSocketTimeout(50000);
// 设置建立连接的超时时间默认为50000毫秒
conf.setConnectionTimeout(50000);
// 设置从连接池中获取连接的超时时间单位毫秒默认不超时
conf.setConnectionRequestTimeout(1000);
// 设置连接空闲超时时间超时则关闭连接默认为60000毫秒
conf.setIdleConnectionTime(60000);
// 设置失败请求重试次数默认为3次
conf.setMaxErrorRetry(5);
CredentialsProvider credentialsProvider = new DefaultCredentialProvider(ossProperties.getAccessKey(), ossProperties.getSecretKey());
return new OSSClient(ossProperties.getEndpoint(), credentialsProvider, conf);
}
@Bean
@ConditionalOnMissingBean(AliossTemplate.class)
@ConditionalOnBean({OSSClient.class, OssRule.class})
public AliossTemplate aliossTemplate(OSSClient ossClient, OssRule ossRule) {
return new AliossTemplate(ossClient, ossProperties, ossRule);
}
}

View File

@ -39,8 +39,8 @@ import org.springframework.context.annotation.Configuration;
@Configuration
@AllArgsConstructor
@EnableConfigurationProperties(OssProperties.class)
@ConditionalOnProperty(value = "oss.enable", havingValue = "true")
public class OssConfiguration {
@ConditionalOnProperty(value = "oss.name", havingValue = "qiniu")
public class QiniuConfiguration {
private OssProperties ossProperties;

View File

@ -32,4 +32,8 @@ public class BladeFile {
* 文件名
*/
private String name;
/**
* 原始文件名
*/
private String originalName;
}

View File

@ -16,6 +16,7 @@
package org.springblade.core.oss.props;
import lombok.Data;
import org.springblade.core.tool.support.Kv;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
@ -30,7 +31,7 @@ public class OssProperties {
/**
* 是否启用
*/
private Boolean enable;
private Boolean enabled;
/**
* 对象存储名称
@ -62,4 +63,9 @@ public class OssProperties {
*/
private String bucketName = "bladex";
/**
* 自定义属性
*/
private Kv args;
}

30
blade-core-report/pom.xml Normal file
View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>2.8.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>blade-core-report</artifactId>
<name>${project.artifactId}</name>
<version>${blade.tool.version}</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-mybatis</artifactId>
<version>${blade.tool.version}</version>
</dependency>
<dependency>
<groupId>com.bstek.ureport</groupId>
<artifactId>ureport2-console</artifactId>
<version>2.2.9</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,65 @@
/**
* 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.report.config;
import com.bstek.ureport.UReportPropertyPlaceholderConfigurer;
import com.bstek.ureport.console.UReportServlet;
import com.bstek.ureport.provider.report.ReportProvider;
import org.springblade.core.report.props.ReportDatabaseProperties;
import org.springblade.core.report.props.ReportProperties;
import org.springblade.core.report.provider.DatabaseProvider;
import org.springblade.core.report.provider.ReportPlaceholderProvider;
import org.springblade.core.report.service.IReportFileService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.core.annotation.Order;
import javax.servlet.Servlet;
/**
* UReport配置类
*
* @author Chill
*/
@Order
@Configuration
@ConditionalOnProperty(value = "report.enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties({ReportProperties.class, ReportDatabaseProperties.class})
@ImportResource("classpath:ureport-console-context.xml")
public class ReportConfiguration {
@Bean
public ServletRegistrationBean<Servlet> registrationBean() {
return new ServletRegistrationBean<>(new UReportServlet(), "/ureport/*");
}
@Bean
public UReportPropertyPlaceholderConfigurer uReportPropertyPlaceholderConfigurer(ReportProperties properties) {
return new ReportPlaceholderProvider(properties);
}
@Bean
@ConditionalOnMissingBean
public ReportProvider reportProvider(ReportDatabaseProperties properties, IReportFileService service) {
return new DatabaseProvider(properties, service);
}
}

View File

@ -0,0 +1,54 @@
/**
* 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.report.datasource;
import com.bstek.ureport.definition.datasource.BuildinDatasource;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* UReport数据源配置
*
* @author Chill
*/
@Slf4j
@AllArgsConstructor
public class ReportDataSource implements BuildinDatasource {
private static final String NAME = "ReportDataSource";
private final DataSource dataSource;
@Override
public String name() {
return NAME;
}
@Override
public Connection getConnection() {
try {
return dataSource.getConnection();
} catch (SQLException e) {
log.error("report数据源链接失败");
e.printStackTrace();
}
return null;
}
}

View File

@ -0,0 +1,38 @@
/**
* 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.report.endpoint;
import org.springblade.core.launch.constant.AppConstant;
import org.springblade.core.report.service.IReportFileService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.annotations.ApiIgnore;
/**
* UReport Boot版 API端点
*
* @author Chill
*/
@ApiIgnore
@RestController
@RequestMapping(AppConstant.APPLICATION_REPORT_NAME + "/report/rest")
public class ReportBootEndpoint extends ReportEndpoint {
public ReportBootEndpoint(IReportFileService service) {
super(service);
}
}

View File

@ -0,0 +1,71 @@
/**
* 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.report.endpoint;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.AllArgsConstructor;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.report.entity.ReportFileEntity;
import org.springblade.core.report.service.IReportFileService;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.Func;
import org.springframework.web.bind.annotation.*;
import springfox.documentation.annotations.ApiIgnore;
import java.util.Map;
/**
* UReport API端点
*
* @author Chill
*/
@ApiIgnore
@RestController
@AllArgsConstructor
@RequestMapping("/report/rest")
public class ReportEndpoint {
private final IReportFileService service;
/**
* 详情
*/
@GetMapping("/detail")
public R<ReportFileEntity> detail(ReportFileEntity file) {
ReportFileEntity detail = service.getOne(Condition.getQueryWrapper(file));
return R.data(detail);
}
/**
* 分页
*/
@GetMapping("/list")
public R<IPage<ReportFileEntity>> list(@RequestParam Map<String, Object> file, Query query) {
IPage<ReportFileEntity> pages = service.page(Condition.getPage(query), Condition.getQueryWrapper(file, ReportFileEntity.class));
return R.data(pages);
}
/**
* 删除
*/
@PostMapping("/remove")
public R remove(@RequestParam String ids) {
boolean temp = service.removeByIds(Func.toLongList(ids));
return R.status(temp);
}
}

View File

@ -0,0 +1,65 @@
/**
* 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.report.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* UReport实体类
*
* @author Chill
*/
@Data
@TableName("blade_report_file")
public class ReportFileEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**
* 文件名
*/
private String name;
/**
* 文件内容
*/
private byte[] content;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
/**
* 是否已删除
*/
@TableLogic
private Integer isDeleted;
}

View File

@ -0,0 +1,27 @@
/**
* 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.report.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springblade.core.report.entity.ReportFileEntity;
/**
* UReport Mapper
*
* @author Chill
*/
public interface ReportFileMapper extends BaseMapper<ReportFileEntity> {
}

View File

@ -0,0 +1,32 @@
/**
* 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.report.props;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* UReport配置类
*
* @author Chill
*/
@Data
@ConfigurationProperties(prefix = "report.database.provider")
public class ReportDatabaseProperties {
private String name = "数据库文件系统";
private String prefix = "blade-";
private boolean disabled = false;
}

View File

@ -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.report.props;
import lombok.Data;
import org.springblade.core.tool.utils.StringPool;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* UReport配置类
*
* @author Chill
*/
@Data
@ConfigurationProperties(prefix = "report")
public class ReportProperties {
private Boolean enabled = true;
private Boolean disableHttpSessionReportCache = false;
private Boolean disableFileProvider = true;
private String fileStoreDir = StringPool.EMPTY;
private Boolean debug = false;
}

View File

@ -0,0 +1,110 @@
/**
* 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.report.provider;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.bstek.ureport.provider.report.ReportFile;
import com.bstek.ureport.provider.report.ReportProvider;
import lombok.AllArgsConstructor;
import org.springblade.core.report.entity.ReportFileEntity;
import org.springblade.core.report.props.ReportDatabaseProperties;
import org.springblade.core.report.service.IReportFileService;
import org.springblade.core.tool.constant.BladeConstant;
import org.springblade.core.tool.utils.DateUtil;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 数据库文件处理
*
* @author Chill
*/
@AllArgsConstructor
public class DatabaseProvider implements ReportProvider {
private final ReportDatabaseProperties properties;
private final IReportFileService service;
@Override
public InputStream loadReport(String file) {
ReportFileEntity reportFileEntity = service.getOne(Wrappers.<ReportFileEntity>lambdaQuery().eq(ReportFileEntity::getName, getFileName(file)));
byte[] content = reportFileEntity.getContent();
return new ByteArrayInputStream(content);
}
@Override
public void deleteReport(String file) {
service.remove(Wrappers.<ReportFileEntity>lambdaUpdate().eq(ReportFileEntity::getName, getFileName(file)));
}
@Override
public List<ReportFile> getReportFiles() {
List<ReportFileEntity> list = service.list();
List<ReportFile> reportFiles = new ArrayList<>();
list.forEach(reportFileEntity -> reportFiles.add(new ReportFile(reportFileEntity.getName(), reportFileEntity.getUpdateTime())));
return reportFiles;
}
@Override
public void saveReport(String file, String content) {
String fileName = getFileName(file);
ReportFileEntity reportFileEntity = service.getOne(Wrappers.<ReportFileEntity>lambdaQuery().eq(ReportFileEntity::getName, fileName));
Date now = DateUtil.now();
if (reportFileEntity == null) {
reportFileEntity = new ReportFileEntity();
reportFileEntity.setName(fileName);
reportFileEntity.setContent(content.getBytes());
reportFileEntity.setCreateTime(now);
reportFileEntity.setIsDeleted(BladeConstant.DB_NOT_DELETED);
} else {
reportFileEntity.setContent(content.getBytes());
}
reportFileEntity.setUpdateTime(now);
service.saveOrUpdate(reportFileEntity);
}
@Override
public String getName() {
return properties.getName();
}
@Override
public boolean disabled() {
return properties.isDisabled();
}
@Override
public String getPrefix() {
return properties.getPrefix();
}
/**
* 获取标准格式文件名
*
* @param name 原文件名
*/
private String getFileName(String name) {
if (name.startsWith(getPrefix())) {
name = name.substring(getPrefix().length());
}
return name;
}
}

View File

@ -0,0 +1,39 @@
/**
* 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.report.provider;
import com.bstek.ureport.UReportPropertyPlaceholderConfigurer;
import org.springblade.core.report.props.ReportProperties;
import java.util.Properties;
/**
* UReport自定义配置
*
* @author Chill
*/
public class ReportPlaceholderProvider extends UReportPropertyPlaceholderConfigurer {
public ReportPlaceholderProvider(ReportProperties properties) {
Properties props = new Properties();
props.setProperty("ureport.disableHttpSessionReportCache", properties.getDisableHttpSessionReportCache().toString());
props.setProperty("ureport.disableFileProvider", properties.getDisableFileProvider().toString());
props.setProperty("ureport.fileStoreDir", properties.getFileStoreDir());
props.setProperty("ureport.debug", properties.getDebug().toString());
this.setProperties(props);
}
}

View File

@ -0,0 +1,27 @@
/**
* 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.report.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springblade.core.report.entity.ReportFileEntity;
/**
* UReport Service
*
* @author Chill
*/
public interface IReportFileService extends IService<ReportFileEntity> {
}

View File

@ -0,0 +1,31 @@
/**
* 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.report.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springblade.core.report.entity.ReportFileEntity;
import org.springblade.core.report.mapper.ReportFileMapper;
import org.springblade.core.report.service.IReportFileService;
import org.springframework.stereotype.Service;
/**
* UReport Service
*
* @author Chill
*/
@Service
public class ReportFileServiceImpl extends ServiceImpl<ReportFileMapper, ReportFileEntity> implements IReportFileService {
}

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>2.5.4</version>
<version>2.8.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -19,8 +19,13 @@
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.2</version>
</dependency>
<!--Blade-->
<dependency>

View File

@ -1,37 +0,0 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 io.jsonwebtoken.impl;
import java.util.Base64;
/**
* Base64Codec
*
* @author jjwt
*/
public class Base64Codec extends AbstractTextCodec {
@Override
public String encode(byte[] data) {
return Base64.getEncoder().encodeToString(data);
}
@Override
public byte[] decode(String encoded) {
return Base64.getDecoder().decode(encoded);
}
}

View File

@ -15,6 +15,8 @@
*/
package org.springblade.core.secure;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ -33,6 +35,13 @@ public class AuthInfo {
private String tokenType;
@ApiModelProperty(value = "刷新令牌")
private String refreshToken;
@ApiModelProperty(value = "用户ID")
@JsonSerialize(using = ToStringSerializer.class)
private Long userId;
@ApiModelProperty(value = "租户ID")
private String tenantId;
@ApiModelProperty(value = "第三方系统ID")
private String oauthId;
@ApiModelProperty(value = "头像")
private String avatar = "https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png";
@ApiModelProperty(value = "角色名")

View File

@ -39,7 +39,7 @@ public class BladeUser implements Serializable {
* 用户id
*/
@ApiModelProperty(hidden = true)
private Integer userId;
private Long userId;
/**
* 租户ID
*/

View File

@ -20,7 +20,6 @@ import lombok.AllArgsConstructor;
import org.springblade.core.secure.aspect.AuthAspect;
import org.springblade.core.secure.interceptor.ClientInterceptor;
import org.springblade.core.secure.interceptor.SecureInterceptor;
import org.springblade.core.secure.props.BladeClientProperties;
import org.springblade.core.secure.props.BladeSecureProperties;
import org.springblade.core.secure.provider.ClientDetailsServiceImpl;
import org.springblade.core.secure.provider.IClientDetailsService;
@ -42,26 +41,24 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Order
@Configuration
@AllArgsConstructor
@EnableConfigurationProperties({BladeSecureProperties.class, BladeClientProperties.class})
@EnableConfigurationProperties({BladeSecureProperties.class})
public class SecureConfiguration implements WebMvcConfigurer {
private final SecureRegistry secureRegistry;
private final BladeSecureProperties secureProperties;
private final BladeClientProperties clientProperties;
private final JdbcTemplate jdbcTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
clientProperties.getClient().forEach(cs -> registry.addInterceptor(new ClientInterceptor(cs.getClientId())).addPathPatterns(cs.getPathPatterns()));
secureProperties.getClient().forEach(cs -> registry.addInterceptor(new ClientInterceptor(cs.getClientId())).addPathPatterns(cs.getPathPatterns()));
if (secureRegistry.isEnable()) {
if (secureRegistry.isEnabled()) {
registry.addInterceptor(new SecureInterceptor())
.excludePathPatterns(secureRegistry.getExcludePatterns())
.excludePathPatterns(secureRegistry.getDefaultExcludePatterns())
.excludePathPatterns(secureProperties.getExcludePatterns());
.excludePathPatterns(secureProperties.getSkipUrl());
}
}

View File

@ -52,7 +52,7 @@ public class ClientInterceptor extends HandlerInterceptorAdapter {
} else {
log.warn("客户端认证失败,请求接口:{}请求IP{},请求参数:{}", request.getRequestURI(), WebUtil.getIP(request), JsonUtil.toJson(request.getParameterMap()));
R result = R.fail(ResultCode.UN_AUTHORIZED);
response.setHeader(BladeConstant.CONTENT_TYPE_NAME, MediaType.APPLICATION_JSON_UTF8_VALUE);
response.setHeader(BladeConstant.CONTENT_TYPE_NAME, MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(BladeConstant.UTF_8);
response.setStatus(HttpServletResponse.SC_OK);
try {

View File

@ -48,7 +48,7 @@ public class SecureInterceptor extends HandlerInterceptorAdapter {
log.warn("签名认证失败,请求接口:{}请求IP{},请求参数:{}", request.getRequestURI(), WebUtil.getIP(request), JsonUtil.toJson(request.getParameterMap()));
R result = R.fail(ResultCode.UN_AUTHORIZED);
response.setCharacterEncoding(BladeConstant.UTF_8);
response.setHeader(BladeConstant.CONTENT_TYPE_NAME, MediaType.APPLICATION_JSON_UTF8_VALUE);
response.setHeader(BladeConstant.CONTENT_TYPE_NAME, MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_OK);
try {
response.getWriter().write(Objects.requireNonNull(JsonUtil.toJson(result)));

View File

@ -27,9 +27,11 @@ import java.util.List;
* @author Chill
*/
@Data
@ConfigurationProperties("blade.secure.url")
@ConfigurationProperties("blade.secure")
public class BladeSecureProperties {
private final List<String> excludePatterns = new ArrayList<>();
private final List<ClientSecure> client = new ArrayList<>();
private final List<String> skipUrl = new ArrayList<>();
}

View File

@ -29,7 +29,7 @@ import java.util.List;
@Data
public class SecureRegistry {
private boolean enable = false;
private boolean enabled = false;
private final List<String> defaultExcludePatterns = new ArrayList<>();
@ -38,7 +38,6 @@ public class SecureRegistry {
public SecureRegistry() {
this.defaultExcludePatterns.add("/actuator/health/**");
this.defaultExcludePatterns.add("/v2/api-docs/**");
this.defaultExcludePatterns.add("/v2/api-docs-ext/**");
this.defaultExcludePatterns.add("/auth/**");
this.defaultExcludePatterns.add("/token/**");
this.defaultExcludePatterns.add("/log/**");

View File

@ -27,6 +27,7 @@ import org.springblade.core.secure.constant.SecureConstant;
import org.springblade.core.secure.exception.SecureException;
import org.springblade.core.secure.provider.IClientDetails;
import org.springblade.core.secure.provider.IClientDetailsService;
import org.springblade.core.tool.constant.RoleConstant;
import org.springblade.core.tool.utils.*;
import javax.crypto.spec.SecretKeySpec;
@ -52,9 +53,9 @@ public class SecureUtil {
private final static String TENANT_ID = TokenConstant.TENANT_ID;
private final static String CLIENT_ID = TokenConstant.CLIENT_ID;
private final static Integer AUTH_LENGTH = TokenConstant.AUTH_LENGTH;
private static String BASE64_SECURITY = Base64.getEncoder().encodeToString(TokenConstant.SIGN_KEY.getBytes(Charsets.UTF_8));
private static final String BASE64_SECURITY = Base64.getEncoder().encodeToString(TokenConstant.SIGN_KEY.getBytes(Charsets.UTF_8));
private static IClientDetailsService clientDetailsService;
private static final IClientDetailsService clientDetailsService;
static {
clientDetailsService = SpringUtil.getBean(IClientDetailsService.class);
@ -94,7 +95,7 @@ public class SecureUtil {
return null;
}
String clientId = Func.toStr(claims.get(SecureUtil.CLIENT_ID));
Integer userId = Func.toInt(claims.get(SecureUtil.USER_ID));
Long userId = Func.toLong(claims.get(SecureUtil.USER_ID));
String tenantId = Func.toStr(claims.get(SecureUtil.TENANT_ID));
String roleId = Func.toStr(claims.get(SecureUtil.ROLE_ID));
String account = Func.toStr(claims.get(SecureUtil.ACCOUNT));
@ -111,13 +112,21 @@ public class SecureUtil {
return bladeUser;
}
/**
* 是否为超管
*
* @return boolean
*/
public static boolean isAdministrator() {
return StringUtil.containsAny(getUserRole(), RoleConstant.ADMIN);
}
/**
* 获取用户id
*
* @return userId
*/
public static Integer getUserId() {
public static Long getUserId() {
BladeUser user = getUser();
return (null == user) ? -1 : user.getUserId();
}
@ -128,7 +137,7 @@ public class SecureUtil {
* @param request request
* @return userId
*/
public static Integer getUserId(HttpServletRequest request) {
public static Long getUserId(HttpServletRequest request) {
BladeUser user = getUser(request);
return (null == user) ? -1 : user.getUserId();
}
@ -246,12 +255,17 @@ public class SecureUtil {
*/
public static Claims getClaims(HttpServletRequest request) {
String auth = request.getHeader(SecureUtil.HEADER);
if ((auth != null) && (auth.length() > AUTH_LENGTH)) {
if (StringUtil.isNotBlank(auth) && auth.length() > AUTH_LENGTH) {
String headStr = auth.substring(0, 6).toLowerCase();
if (headStr.compareTo(SecureUtil.BEARER) == 0) {
auth = auth.substring(7);
return SecureUtil.parseJWT(auth);
}
} else {
String parameter = request.getParameter(SecureUtil.HEADER);
if (StringUtil.isNotBlank(parameter)) {
return SecureUtil.parseJWT(parameter);
}
}
return null;
}
@ -283,8 +297,8 @@ public class SecureUtil {
*/
public static Claims parseJWT(String jsonWebToken) {
try {
return Jwts.parser()
.setSigningKey(Base64.getDecoder().decode(BASE64_SECURITY))
return Jwts.parserBuilder()
.setSigningKey(Base64.getDecoder().decode(BASE64_SECURITY)).build()
.parseClaimsJws(jsonWebToken).getBody();
} catch (Exception ex) {
return null;
@ -325,10 +339,10 @@ public class SecureUtil {
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
//添加构成JWT的类
JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JsonWebToken")
JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT")
.setIssuer(issuer)
.setAudience(audience)
.signWith(signatureAlgorithm, signingKey);
.signWith(signingKey);
//设置JWT参数
user.forEach(builder::claim);

36
blade-core-social/pom.xml Normal file
View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>2.8.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>blade-core-social</artifactId>
<name>${project.artifactId}</name>
<version>${blade.tool.version}</version>
<packaging>jar</packaging>
<dependencies>
<!--Blade-->
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-tool</artifactId>
<version>${blade.tool.version}</version>
</dependency>
<!-- 第三方登陆 -->
<dependency>
<groupId>me.zhyd.oauth</groupId>
<artifactId>JustAuth</artifactId>
<version>1.15.8</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,44 @@
/**
* 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.social.config;
import com.xkcoding.http.HttpUtil;
import com.xkcoding.http.support.Http;
import com.xkcoding.http.support.httpclient.HttpClientImpl;
import org.springblade.core.social.props.SocialProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* SocialConfiguration
*
* @author Chill
*/
@Configuration
@EnableConfigurationProperties(SocialProperties.class)
public class SocialConfiguration {
@Bean
@ConditionalOnMissingBean(Http.class)
public Http simpleHttp() {
HttpClientImpl httpClient = new HttpClientImpl();
HttpUtil.setHttp(httpClient);
return httpClient;
}
}

View File

@ -0,0 +1,57 @@
/**
* 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.social.props;
import com.google.common.collect.Maps;
import lombok.Getter;
import lombok.Setter;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthDefaultSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.Map;
/**
* SocialProperties
*
* @author Chill
*/
@Getter
@Setter
@ConfigurationProperties(prefix = "social")
public class SocialProperties {
/**
* 启用
*/
private Boolean enabled = false;
/**
* 域名地址
*/
private String domain;
/**
* 类型
*/
private Map<AuthDefaultSource, AuthConfig> oauth = Maps.newHashMap();
/**
* 别名
*/
private Map<String, String> alias = Maps.newHashMap();
}

View File

@ -0,0 +1,150 @@
/**
* 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.social.utils;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthDefaultSource;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.request.*;
import org.springblade.core.social.props.SocialProperties;
import java.util.Objects;
/**
* SocialUtil
*
* @author Chill
*/
public class SocialUtil {
/**
* 根据具体的授权来源获取授权请求工具类
*
* @param source 授权来源
* @return AuthRequest
*/
public static AuthRequest getAuthRequest(String source, SocialProperties socialProperties) {
AuthDefaultSource authSource = Objects.requireNonNull(AuthDefaultSource.valueOf(source.toUpperCase()));
AuthConfig authConfig = socialProperties.getOauth().get(authSource);
if (authConfig == null) {
throw new AuthException("未获取到有效的Auth配置");
}
AuthRequest authRequest = null;
switch (authSource) {
case GITHUB:
authRequest = new AuthGithubRequest(authConfig);
break;
case GITEE:
authRequest = new AuthGiteeRequest(authConfig);
break;
case OSCHINA:
authRequest = new AuthOschinaRequest(authConfig);
break;
case QQ:
authRequest = new AuthQqRequest(authConfig);
break;
case WECHAT_OPEN:
authRequest = new AuthWeChatOpenRequest(authConfig);
break;
case WECHAT_ENTERPRISE:
authRequest = new AuthWeChatEnterpriseRequest(authConfig);
break;
case WECHAT_MP:
authRequest = new AuthWeChatMpRequest(authConfig);
break;
case DINGTALK:
authRequest = new AuthDingTalkRequest(authConfig);
break;
case ALIPAY:
// 支付宝在创建回调地址时不允许使用localhost或者127.0.0.1所以这儿的回调地址使用的局域网内的ip
authRequest = new AuthAlipayRequest(authConfig);
break;
case BAIDU:
authRequest = new AuthBaiduRequest(authConfig);
break;
case WEIBO:
authRequest = new AuthWeiboRequest(authConfig);
break;
case CODING:
authRequest = new AuthCodingRequest(authConfig);
break;
case CSDN:
authRequest = new AuthCsdnRequest(authConfig);
break;
case TAOBAO:
authRequest = new AuthTaobaoRequest(authConfig);
break;
case GOOGLE:
authRequest = new AuthGoogleRequest(authConfig);
break;
case FACEBOOK:
authRequest = new AuthFacebookRequest(authConfig);
break;
case DOUYIN:
authRequest = new AuthDouyinRequest(authConfig);
break;
case LINKEDIN:
authRequest = new AuthLinkedinRequest(authConfig);
break;
case MICROSOFT:
authRequest = new AuthMicrosoftRequest(authConfig);
break;
case MI:
authRequest = new AuthMiRequest(authConfig);
break;
case TOUTIAO:
authRequest = new AuthToutiaoRequest(authConfig);
break;
case TEAMBITION:
authRequest = new AuthTeambitionRequest(authConfig);
break;
case PINTEREST:
authRequest = new AuthPinterestRequest(authConfig);
break;
case RENREN:
authRequest = new AuthRenrenRequest(authConfig);
break;
case STACK_OVERFLOW:
authRequest = new AuthStackOverflowRequest(authConfig);
break;
case HUAWEI:
authRequest = new AuthHuaweiRequest(authConfig);
break;
case KUJIALE:
authRequest = new AuthKujialeRequest(authConfig);
break;
case GITLAB:
authRequest = new AuthGitlabRequest(authConfig);
break;
case MEITUAN:
authRequest = new AuthMeituanRequest(authConfig);
break;
case ELEME:
authRequest = new AuthElemeRequest(authConfig);
break;
case TWITTER:
authRequest = new AuthTwitterRequest(authConfig);
break;
default:
break;
}
if (null == authRequest) {
throw new AuthException("未获取到有效的Auth配置");
}
return authRequest;
}
}

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>2.5.4</version>
<version>2.8.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -23,26 +23,10 @@
<version>${blade.tool.version}</version>
</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>
<artifactId>knife4j-micro-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
</dependencies>

View File

@ -0,0 +1,32 @@
/**
* 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.swagger;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
import java.lang.annotation.*;
/**
* Swagger配置开关
*
* @author Chill
*/
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@EnableSwagger2WebMvc
public @interface EnableSwagger {
}

View File

@ -16,23 +16,23 @@
package org.springblade.core.swagger;
import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Lists;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.Import;
import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.ApiSelectorBuilder;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@ -42,14 +42,14 @@ import java.util.List;
* @author Chill
*/
@Configuration
@EnableSwagger2
@EnableSwaggerBootstrapUI
@Profile({"dev", "test"})
@EnableSwagger
@EnableConfigurationProperties(SwaggerProperties.class)
@Import(BeanValidatorPluginsConfiguration.class)
public class SwaggerAutoConfiguration {
private static final String DEFAULT_EXCLUDE_PATH = "/error";
private static final String BASE_PATH = "/**";
private static final String DEFAULT_MAPPING_PATH = "/";
private static final String DEFAULT_BASE_PATH = "/**";
private static final List<String> DEFAULT_EXCLUDE_PATH = Arrays.asList("/error", "/actuator/**");
@Bean
@ConditionalOnMissingBean
@ -61,29 +61,27 @@ public class SwaggerAutoConfiguration {
public Docket api(SwaggerProperties swaggerProperties) {
// base-path处理
if (swaggerProperties.getBasePath().size() == 0) {
swaggerProperties.getBasePath().add(BASE_PATH);
swaggerProperties.getBasePath().add(DEFAULT_BASE_PATH);
}
//noinspection unchecked
List<Predicate<String>> basePath = new ArrayList();
swaggerProperties.getBasePath().forEach(path -> basePath.add(PathSelectors.ant(path)));
// exclude-path处理
if (swaggerProperties.getExcludePath().size() == 0) {
swaggerProperties.getExcludePath().add(DEFAULT_EXCLUDE_PATH);
swaggerProperties.getExcludePath().addAll(DEFAULT_EXCLUDE_PATH);
}
List<Predicate<String>> excludePath = new ArrayList<>();
swaggerProperties.getExcludePath().forEach(path -> excludePath.add(PathSelectors.ant(path)));
//noinspection Guava
return new Docket(DocumentationType.SWAGGER_2)
ApiSelectorBuilder apis = new Docket(DocumentationType.SWAGGER_2)
.host(swaggerProperties.getHost())
.apiInfo(apiInfo(swaggerProperties)).select()
.apis(SwaggerUtil.basePackages(swaggerProperties.getBasePackages()))
.paths(Predicates.and(Predicates.not(Predicates.or(excludePath)), Predicates.or(basePath)))
.build()
.apis(SwaggerUtil.basePackages(swaggerProperties.getBasePackages()));
swaggerProperties.getBasePath().forEach(p -> apis.paths(PathSelectors.ant(p)));
swaggerProperties.getExcludePath().forEach(p -> apis.paths(PathSelectors.ant(p).negate()));
return apis.build()
.securitySchemes(Collections.singletonList(securitySchema()))
.securityContexts(Collections.singletonList(securityContext()))
.pathMapping("/");
.securityContexts(Lists.newArrayList(securityContext()))
.securitySchemes(Collections.singletonList(securitySchema()))
.pathMapping(DEFAULT_MAPPING_PATH);
}
/**

View File

@ -0,0 +1,43 @@
/**
* 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.swagger;
import org.springblade.core.launch.constant.AppConstant;
import org.springblade.core.launch.service.LauncherService;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.core.Ordered;
import java.util.Properties;
/**
* 初始化Swagger配置
*
* @author Chill
*/
public class SwaggerLauncherServiceImpl implements LauncherService {
@Override
public void launcher(SpringApplicationBuilder builder, String appName, String profile) {
Properties props = System.getProperties();
if (profile.equals(AppConstant.PROD_CODE)) {
props.setProperty("knife4j.production", "true");
}
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}

View File

@ -55,7 +55,7 @@ public class SwaggerProperties {
/**
* 版本
**/
private String version = "2.5.4";
private String version = "2.8.0";
/**
* 许可证
**/

View File

@ -17,10 +17,10 @@ package org.springblade.core.swagger;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import springfox.documentation.RequestHandler;
import java.util.List;
import java.util.function.Predicate;
/**
* Swagger工具类

View File

@ -0,0 +1 @@
org.springblade.core.swagger.SwaggerLauncherServiceImpl

View File

@ -5,7 +5,7 @@
<parent>
<groupId>org.springblade</groupId>
<artifactId>blade-tool</artifactId>
<version>2.5.4</version>
<version>2.8.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>org.springblade</groupId>
<artifactId>blade-tool</artifactId>
<version>2.5.4</version>
<version>2.8.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -31,7 +31,7 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>26.0-jre</version>
<version>30.0-jre</version>
</dependency>
<!-- okhttp -->
<dependency>
@ -67,6 +67,11 @@
<artifactId>protostuff-runtime</artifactId>
<version>${protostuff.version}</version>
</dependency>
<!--Redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -16,9 +16,11 @@
package org.springblade.core.tool.config;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import lombok.AllArgsConstructor;
import org.springblade.core.tool.jackson.BladeJavaTimeModule;
import org.springblade.core.tool.utils.DateUtil;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
@ -40,6 +42,7 @@ import java.util.TimeZone;
* @author Chill
*/
@Configuration
@AllArgsConstructor
@ConditionalOnClass(ObjectMapper.class)
@AutoConfigureBefore(JacksonAutoConfiguration.class)
public class JacksonConfiguration {
@ -59,8 +62,8 @@ public class JacksonConfiguration {
//序列化时日期的统一格式
objectMapper.setDateFormat(new SimpleDateFormat(DateUtil.PATTERN_DATETIME, Locale.CHINA));
//序列化处理
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
objectMapper.configure(JsonParser.Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, true);
objectMapper.configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), true);
objectMapper.configure(JsonReadFeature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER.mappedFeature(), true);
objectMapper.findAndRegisterModules();
//失败处理
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);

View File

@ -19,11 +19,7 @@ package org.springblade.core.tool.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
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.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
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.web.servlet.config.annotation.WebMvcConfigurer;
import javax.servlet.DispatcherType;
import java.util.List;
/**
@ -46,8 +41,6 @@ public class MessageConfiguration implements WebMvcConfigurer {
private final ObjectMapper objectMapper;
private final XssProperties xssProperties;
/**
* 使用 JACKSON 作为JSON MessageConverter
*/
@ -61,20 +54,4 @@ public class MessageConfiguration implements WebMvcConfigurer {
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;
}
}

View File

@ -13,10 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.boot.config;
package org.springblade.core.tool.config;
import org.springblade.core.boot.redis.RedisKeySerializer;
import org.springblade.core.tool.redis.RedisKeySerializer;
import org.springblade.core.tool.utils.RedisUtil;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.cache.CacheManager;
@ -78,4 +80,10 @@ public class RedisTemplateConfiguration {
.cacheDefaults(redisCacheConfiguration).build();
}
@Bean(name = "redisUtil")
@ConditionalOnBean(RedisTemplate.class)
public RedisUtil redisUtils(RedisTemplate<String, Object> redisTemplate) {
return new RedisUtil(redisTemplate);
}
}

View File

@ -16,9 +16,7 @@
package org.springblade.core.tool.config;
import org.springblade.core.tool.support.xss.XssProperties;
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.Configuration;
import org.springframework.core.Ordered;
@ -32,7 +30,6 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
*/
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
@EnableConfigurationProperties(XssProperties.class)
public class ToolConfiguration implements WebMvcConfigurer {
/**
@ -41,7 +38,7 @@ public class ToolConfiguration implements WebMvcConfigurer {
* @return SpringUtil
*/
@Bean
public SpringUtil springUtils() {
public SpringUtil springUtil() {
return new SpringUtil();
}

View File

@ -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.enabled", 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;
}
}

View File

@ -17,6 +17,7 @@ package org.springblade.core.tool.jackson;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
@ -97,7 +98,7 @@ public class JsonUtil {
* @param <T> T 泛型标记
* @return Bean
*/
public static <T> T parse(String content, TypeReference<?> typeReference) {
public static <T> T parse(String content, TypeReference<T> typeReference) {
try {
return getInstance().readValue(content, typeReference);
} catch (IOException e) {
@ -130,7 +131,7 @@ public class JsonUtil {
* @param <T> T 泛型标记
* @return Bean
*/
public static <T> T parse(byte[] bytes, TypeReference<?> typeReference) {
public static <T> T parse(byte[] bytes, TypeReference<T> typeReference) {
try {
return getInstance().readValue(bytes, typeReference);
} catch (IOException e) {
@ -162,7 +163,7 @@ public class JsonUtil {
* @param <T> T 泛型标记
* @return Bean
*/
public static <T> T parse(InputStream in, TypeReference<?> typeReference) {
public static <T> T parse(InputStream in, TypeReference<T> typeReference) {
try {
return getInstance().readValue(in, typeReference);
} catch (IOException e) {
@ -184,7 +185,7 @@ public class JsonUtil {
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<>();
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) {
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);
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.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
super.configure(JsonParser.Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, true);
super.configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), true);
super.configure(JsonReadFeature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER.mappedFeature(), true);
super.findAndRegisterModules();
//失败处理
super.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);

View File

@ -16,6 +16,8 @@
package org.springblade.core.tool.node;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.util.ArrayList;
@ -29,15 +31,19 @@ import java.util.List;
@Data
public class BaseNode implements INode {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
protected Integer id;
@JsonSerialize(using = ToStringSerializer.class)
protected Long id;
/**
* 父节点ID
*/
protected Integer parentId;
@JsonSerialize(using = ToStringSerializer.class)
protected Long parentId;
/**
* 子孙节点
@ -45,4 +51,22 @@ public class BaseNode implements INode {
@JsonInclude(JsonInclude.Include.NON_EMPTY)
protected List<INode> children = new ArrayList<>();
/**
* 是否有子孙节点
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private Boolean hasChildren;
/**
* 是否有子孙节点
*/
@Override
public Boolean getHasChildren() {
if (children.size() > 0) {
return true;
} else {
return this.hasChildren;
}
}
}

View File

@ -28,12 +28,14 @@ import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = false)
public class ForestNode extends BaseNode {
private static final long serialVersionUID = 1L;
/**
* 节点内容
*/
private Object content;
public ForestNode(Integer id, Integer parentId, Object content) {
public ForestNode(Long id, Long parentId, Object content) {
this.id = id;
this.parentId = parentId;
this.content = content;

View File

@ -33,7 +33,7 @@ public class ForestNodeManager<T extends INode> {
/**
* 森林的父节点ID
*/
private List<Integer> parentIds = new ArrayList<>();
private List<Long> parentIds = new ArrayList<>();
public ForestNodeManager(List<T> items) {
list = items;
@ -45,9 +45,9 @@ public class ForestNodeManager<T extends INode> {
* @param id 节点ID
* @return 对应的节点对象
*/
public INode getTreeNodeAT(int id) {
public INode getTreeNodeAT(Long id) {
for (INode forestNode : list) {
if (forestNode.getId() == id) {
if (forestNode.getId().longValue() == id) {
return forestNode;
}
}
@ -59,7 +59,7 @@ public class ForestNodeManager<T extends INode> {
*
* @param parentId 父节点ID
*/
public void addParentId(Integer parentId) {
public void addParentId(Long parentId) {
parentIds.add(parentId);
}

View File

@ -15,6 +15,7 @@
*/
package org.springblade.core.tool.node;
import java.io.Serializable;
import java.util.List;
/**
@ -22,21 +23,21 @@ import java.util.List;
*
* @author Chill
*/
public interface INode {
public interface INode extends Serializable {
/**
* 主键
*
* @return Integer
*/
Integer getId();
Long getId();
/**
* 父主键
*
* @return Integer
*/
Integer getParentId();
Long getParentId();
/**
* 子孙节点
@ -45,4 +46,13 @@ public interface INode {
*/
List<INode> getChildren();
/**
* 是否有子孙节点
*
* @return Boolean
*/
default Boolean getHasChildren() {
return false;
}
}

View File

@ -14,16 +14,16 @@ public class NodeTest {
public static void main(String[] args) {
List<ForestNode> list = new ArrayList<>();
list.add(new ForestNode(1, 0, "1"));
list.add(new ForestNode(2, 0, "2"));
list.add(new ForestNode(3, 1, "3"));
list.add(new ForestNode(4, 2, "4"));
list.add(new ForestNode(5, 3, "5"));
list.add(new ForestNode(6, 4, "6"));
list.add(new ForestNode(7, 3, "7"));
list.add(new ForestNode(8, 5, "8"));
list.add(new ForestNode(9, 6, "9"));
list.add(new ForestNode(10, 9, "10"));
list.add(new ForestNode(1L, 0L, "1"));
list.add(new ForestNode(2L, 0L, "2"));
list.add(new ForestNode(3L, 1L, "3"));
list.add(new ForestNode(4L, 2L, "4"));
list.add(new ForestNode(5L, 3L, "5"));
list.add(new ForestNode(6L, 4L, "6"));
list.add(new ForestNode(7L, 3L, "7"));
list.add(new ForestNode(8L, 5L, "8"));
list.add(new ForestNode(9L, 6L, "9"));
list.add(new ForestNode(10L, 9L, "10"));
List<ForestNode> tns = ForestNodeMerger.merge(list);
tns.forEach(node ->
System.out.println(JsonUtil.toJson(node))

View File

@ -15,6 +15,8 @@
*/
package org.springblade.core.tool.node;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -27,10 +29,14 @@ import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = false)
public class TreeNode extends BaseNode {
private static final long serialVersionUID = 1L;
private String title;
private Integer key;
@JsonSerialize(using = ToStringSerializer.class)
private Long key;
private Integer value;
@JsonSerialize(using = ToStringSerializer.class)
private Long value;
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.boot.redis;
package org.springblade.core.tool.redis;
import org.springframework.cache.interceptor.SimpleKey;
import org.springframework.core.convert.ConversionService;

View File

@ -30,9 +30,8 @@ import java.util.HashMap;
*/
public class Kv extends LinkedCaseInsensitiveMap<Object> {
private Kv() {
super();
}
/**
@ -44,8 +43,8 @@ public class Kv extends LinkedCaseInsensitiveMap<Object> {
return new Kv();
}
public static HashMap newMap() {
return new HashMap(16);
public static <K, V> HashMap<K, V> newMap() {
return new HashMap<>(16);
}
/**
@ -198,7 +197,9 @@ public class Kv extends LinkedCaseInsensitiveMap<Object> {
@Override
public Kv clone() {
return (Kv) super.clone();
Kv clone = new Kv();
clone.putAll(this);
return clone;
}
}

View File

@ -16,6 +16,7 @@
package org.springblade.core.tool.support.xss;
import lombok.AllArgsConstructor;
import org.springblade.core.tool.utils.StringPool;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
@ -30,6 +31,7 @@ import java.io.IOException;
public class XssFilter implements Filter {
private XssProperties xssProperties;
private XssUrlProperties xssUrlProperties;
@Override
public void init(FilterConfig config) {
@ -39,7 +41,7 @@ public class XssFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String path = ((HttpServletRequest) request).getServletPath();
if (xssProperties.getExcludePatterns().stream().anyMatch(path::contains)) {
if (isSkip(path)) {
chain.doFilter(request, response);
} else {
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
public void destroy() {

View File

@ -15,9 +15,7 @@
*/
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.WebUtil;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
@ -29,6 +27,7 @@ import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Map;
@ -49,15 +48,9 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
*/
private final static HtmlFilter HTML_FILTER = new HtmlFilter();
/**
* 缓存报文,支持多次读取流
*/
private final byte[] body;
public XssHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
orgRequest = request;
body = WebUtil.getRequestBytes(request);
}
@Override
@ -67,51 +60,67 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
@Override
public ServletInputStream getInputStream() throws IOException {
//为空直接返回
if (null == super.getHeader(HttpHeaders.CONTENT_TYPE)) {
return super.getInputStream();
}
//非json类型直接返回
if (!super.getHeader(HttpHeaders.CONTENT_TYPE).equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)
&& !super.getHeader(HttpHeaders.CONTENT_TYPE).equalsIgnoreCase(MediaType.APPLICATION_JSON_UTF8_VALUE)) {
if (super.getHeader(HttpHeaders.CONTENT_TYPE).startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)) {
return super.getInputStream();
}
//为空直接返回
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));
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(inputHandlers(super.getInputStream()).getBytes());
return new ServletInputStream() {
@Override
public int read() {
return byteArrayInputStream.read();
}
@Override
public boolean isFinished() {
return true;
return false;
}
@Override
public boolean isReady() {
return true;
return false;
}
@Override
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
@ -129,7 +138,6 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
if (parameters == null || parameters.length == 0) {
return null;
}
for (int i = 0; i < parameters.length; i++) {
parameters[i] = xssEncode(parameters[i]);
}
@ -165,6 +173,7 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
/**
* 获取最原始的request
*
* @return HttpServletRequest
*/
public HttpServletRequest getOrgRequest() {
@ -173,6 +182,7 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
/**
* 获取最原始的request
*
* @param request request
* @return HttpServletRequest
*/
@ -180,7 +190,6 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
if (request instanceof XssHttpServletRequestWrapper) {
return ((XssHttpServletRequestWrapper) request).getOrgRequest();
}
return request;
}

View File

@ -27,9 +27,17 @@ import java.util.List;
* @author Chill
*/
@Data
@ConfigurationProperties("blade.xss.url")
@ConfigurationProperties("blade.xss")
public class XssProperties {
private final List<String> excludePatterns = new ArrayList<>();
/**
* 开启xss
*/
private Boolean enabled = true;
/**
* 放行url
*/
private List<String> skipUrl = new ArrayList<>();
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.secure.props;
package org.springblade.core.tool.support.xss;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ -22,14 +22,14 @@ import java.util.ArrayList;
import java.util.List;
/**
* 客户端校验配置
* Xss配置类
*
* @author Chill
*/
@Data
@ConfigurationProperties("blade.secure")
public class BladeClientProperties {
@ConfigurationProperties("blade.xss.url")
public class XssUrlProperties {
private final List<ClientSecure> client = new ArrayList<>();
private final List<String> excludePatterns = new ArrayList<>();
}

View File

@ -1124,7 +1124,7 @@ public class Func {
* @param <T> T 泛型标记
* @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);
}
@ -1136,7 +1136,7 @@ public class Func {
* @param <T> T 泛型标记
* @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);
}
@ -1148,7 +1148,7 @@ public class Func {
* @param <T> T 泛型标记
* @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);
}

View File

@ -0,0 +1,595 @@
package org.springblade.core.tool.utils;
import lombok.AllArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Redis工具类
*
* @author Chill
*/
@AllArgsConstructor
public class RedisUtil {
private RedisTemplate<String, Object> redisTemplate;
//=============================common============================
/**
* 指定缓存失效时间
*
* @param key
* @param time 时间()
* @return boolean
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
*
* @param key 不能为null
* @return 时间() 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
*
* @param key
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtil.arrayToList(key));
}
}
}
//============================String=============================
/**
* 普通缓存获取
*
* @param key
* @return
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
*
* @param key
* @param value
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
*
* @param key
* @param value
* @param time 时间() time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
*
* @param key
* @param value
* @param time 时间
* @param timeUnit 时间单位
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time, TimeUnit timeUnit) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, timeUnit);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
*
* @param key
* @param delta 要增加几(大于0)
* @return long
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
*
* @param key
* @param delta 要减少几(小于0)
* @return long
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
//================================Map=================================
/**
* HashGet
*
* @param key 不能为null
* @param item 不能为null
* @return
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
*
* @param key
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
*
* @param key
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
*
* @param key
* @param map 对应多个键值
* @param time 时间()
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key
* @param item
* @param value
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key
* @param item
* @param value
* @param time 时间() 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
*
* @param key 不能为null
* @param item 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 不能为null
* @param item 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key
* @param item
* @param by 要增加几(大于0)
* @return double
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key
* @param item
* @param by 要减少记(小于0)
* @return double
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
//============================set=============================
/**
* 根据key获取Set中的所有值
*
* @param key
* @return Set
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key
* @param value
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key
* @param values 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
*
* @param key
* @param time 时间()
* @param values 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0) {
expire(key, time);
}
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
*
* @param key
* @return long
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
*
* @param key
* @param values 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
//===============================list=================================
/**
* 获取list缓存的内容
*
* @param key
* @param start 开始
* @param end 结束 0 -1代表所有值
* @return List
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
*
* @param key
* @return long
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
*
* @param key
* @param index 索引
* @return Object
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
*
* @param key
* @param value
* @return boolean
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key
* @param value
* @param time 时间()
* @return boolean
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key
* @param value
* @return boolean
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key
* @param value
* @param time 时间()
* @return boolean
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
*
* @param key
* @param index 索引
* @param value
* @return boolean
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
*
* @param key
* @param count 移除多少个
* @param value
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}

View File

@ -186,7 +186,7 @@ public class StringUtil extends org.springframework.util.StringUtils {
* @return {String}
*/
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对象
*
* @param sb 初始StringBuilder
* @param sb 初始StringBuilder
* @param strs 初始字符串列表
* @return StringBuilder对象
*/

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>blade-tool</artifactId>
<groupId>org.springblade</groupId>
<version>2.5.4</version>
<version>2.8.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -28,16 +28,14 @@
<!-- Seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata</artifactId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>${alibaba.cloud.version}</version>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>${alibaba.seata.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -15,19 +15,7 @@
*/
package org.springblade.core.transaction.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import javax.sql.DataSource;
/**
* 分布式事务数据源配置
@ -37,43 +25,4 @@ import javax.sql.DataSource;
@Configuration
public class DataSourceConfiguration {
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
bean.setDataSource(dataSourceProxy);
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
bean.setMapperLocations(resolver.getResources("classpath:org/springblade/**/mapper/*Mapper.xml"));
SqlSessionFactory factory = null;
try {
factory = bean.getObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
return factory;
}
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
/**
* 从配置文件获取属性构造datasource
*/
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DruidDataSource druidDataSource() {
return new DruidDataSource();
}
/**
* 构造datasource代理对象
*/
@Primary
@Bean("dataSource")
public DataSourceProxy dataSourceProxy(DataSource druidDataSource) {
return new DataSourceProxy(druidDataSource);
}
}

View File

@ -0,0 +1,3 @@
service {
disableGlobalTransaction = false
}

View File

@ -1,20 +0,0 @@
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
serverAddr = "localhost"
namespace = ""
cluster = "default"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "nacos"
nacos {
serverAddr = "localhost"
namespace = ""
}
}

42
pom.xml
View File

@ -5,7 +5,7 @@
<groupId>org.springblade</groupId>
<artifactId>blade-tool</artifactId>
<version>2.5.4</version>
<version>2.8.0</version>
<packaging>pom</packaging>
<name>blade-tool</name>
<description>
@ -36,24 +36,24 @@
</scm>
<properties>
<blade.tool.version>2.5.4</blade.tool.version>
<blade.tool.version>2.8.0</blade.tool.version>
<java.version>1.8</java.version>
<maven.plugin.version>3.8.0</maven.plugin.version>
<swagger.version>2.9.2</swagger.version>
<swagger.models.version>1.5.21</swagger.models.version>
<swagger.bootstrapui.version>1.9.6</swagger.bootstrapui.version>
<mybatis.plus.version>3.1.2</mybatis.plus.version>
<curator.framework.version>4.0.1</curator.framework.version>
<swagger.version>2.10.5</swagger.version>
<swagger.models.version>1.6.2</swagger.models.version>
<knife4j.version>2.0.6</knife4j.version>
<mybatis.plus.version>3.4.0</mybatis.plus.version>
<protostuff.version>1.6.0</protostuff.version>
<disruptor.version>3.4.2</disruptor.version>
<spring.boot.admin.version>2.1.5</spring.boot.admin.version>
<mica.auto.version>1.1.0</mica.auto.version>
<alibaba.cloud.version>2.1.0.RELEASE</alibaba.cloud.version>
<alibaba.seata.version>0.9.0</alibaba.seata.version>
<spring.boot.admin.version>2.3.0</spring.boot.admin.version>
<mica.auto.version>1.2.5</mica.auto.version>
<alibaba.cloud.version>2.2.3.RELEASE</alibaba.cloud.version>
<alibaba.seata.version>1.3.0</alibaba.seata.version>
<spring.plugin.version>2.0.0.RELEASE</spring.plugin.version>
<spring.boot.version>2.1.9.RELEASE</spring.boot.version>
<spring.cloud.version>Greenwich.SR3</spring.cloud.version>
<spring.boot.version>2.2.11.RELEASE</spring.boot.version>
<spring.cloud.version>Hoxton.SR8</spring.cloud.version>
<spring.platform.version>Cairo-SR8</spring.platform.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@ -68,15 +68,27 @@
<module>blade-core-log</module>
<module>blade-core-mybatis</module>
<module>blade-core-secure</module>
<module>blade-core-social</module>
<module>blade-core-swagger</module>
<module>blade-core-test</module>
<module>blade-core-tool</module>
<module>blade-core-oss</module>
<module>blade-core-transaction</module>
<module>blade-core-report</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.plugin</groupId>
<artifactId>spring-plugin-core</artifactId>
<version>${spring.plugin.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.plugin</groupId>
<artifactId>spring-plugin-metadata</artifactId>
<version>${spring.plugin.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
@ -183,7 +195,7 @@
<repositories>
<repository>
<id>aliyun-repos</id>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<url>https://maven.aliyun.com/nexus/content/groups/public/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
@ -193,7 +205,7 @@
<pluginRepositories>
<pluginRepository>
<id>aliyun-plugin</id>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<url>https://maven.aliyun.com/nexus/content/groups/public/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>