This commit is contained in:
2025-11-28 16:23:32 +08:00
commit a9e0e16c29
826 changed files with 89805 additions and 0 deletions

21
.editorconfig Normal file
View File

@@ -0,0 +1,21 @@
# http://editorconfig.org
root = true
# 空格替代Tab缩进在各种编辑工具下效果一致
[*]
indent_style = space
indent_size = 4
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
[*.java]
indent_style = tab
[*.{json,yml}]
indent_size = 2
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

31
.gitignore vendored Normal file
View File

@@ -0,0 +1,31 @@
# maven #
target
logs
# windows #
Thumbs.db
# Mac #
.DS_Store
# eclipse #
.settings
.project
.classpath
.log
*.class
# idea #
.idea
*.iml
# Package Files #
*.jar
*.war
*.ear
/target
# Flattened pom
.flattened-pom.xml
/**/.flattened-pom.xml

35
LICENSE Normal file
View File

@@ -0,0 +1,35 @@
BladeX商业授权许可协议
一、 知识产权:
BladeX系列产品知识产权归上海布雷德科技有限公司独立所有
二、 许可:
1. 在您完全接受并遵守本协议的基础上本协议授予您使用BladeX的某些权利和非独占性许可。
2. 本协议中,将本产品使用用途分为"专业版用途"和"企业版用途"。
3. "专业版用途"定义:指个人在非团体机构中出于任何合法目的使用本产品(任何目的包括商业目的或非盈利目的)。
4. "企业版用途"定义:指拥有合法执照的团体机构(例如公司企业、政府、学校、军队、医院、社会团体等各类组织)(不包含集团,若集团使用则需为各个子公司分别购买企业授权)出于任何合法目的使用本产品(任何目的包括商业目的或非盈利目的)。
5. 若您不能以拥有合法执照的团体机构名义购买企业版,则视为个人名义购买,仅可行使专业版用途。在遵守此协议的前提下,后续有一次机会将企业版授权免费绑定至法人为购买人的新公司,并从专业版用途转为企业版用途。
三、 约束和限制:
1. 本产品只能由您为本协议许可的目的而使用,您不得透露给任何第三方;
2. 从本产品取得的任何信息、软件、产品或服务,您不得对其进行修改、改编或基于以上内容创建同种类别的衍生产品并售卖。
3. 您不得对本产品以及与之关联的商业授权进行发布、出租、销售、分销、抵押、转让、许可或发放子许可证。
4. 本产品商业授权版可能包含一些独立功能或特性,这些功能只有在您购买商业授权后才可以使用。在未取得商业授权的情况下,您不得使用、尝试使用或复制这些授权版独立功能。
5. 若您的客户要求以源码方式交付软件,需缴纳企业版授权费用,否则本产品部分不得提供源码。
四、 不得用于非法或禁止的用途:
您在使用本产品或服务时,不得将本产品产品或服务用于任何非法用途或本协议条款、条件和声明禁止的用途。
五、 免责说明:
1. 本产品按"现状"授予许可您须自行承担使用本产品的风险。BladeX团队不对此提供任何明示、暗示或任何其它形式的担保和表示。在任何情况下对于因使用或无法使用本软件而导致的任何损失包括但不仅限于商业利润损失、业务中断或业务信息丢失BladeX团队无需向您或任何第三方负责即使BladeX团队已被告知可能会造成此类损失。在任何情况下 BladeX团队均不就任何直接的、间接的、附带的、后果性的、特别的、惩戒性的和处罚性的损害赔偿承担任何责任无论该主张是基于保证、合同、侵权包括疏忽或是基于其他原因作出。
2. 本产品可能内置有第三方服务,您应自行评估使用这些第三方服务的风险,由使用此类第三方服务而产生的纠纷,全部责任由您自行承担。
3. BladeX团队不对使用本产品构建的网站中任何信息内容以及导致的任何版权纠纷、法律争议和后果承担任何责任全部责任由您自行承担。
4. BladeX团队可能会经常提供产品更新或升级但BladeX团队没有为根据本协议许可的产品提供维护或更新的责任。
5. BladeX团队可能会按照官方制定的答疑规则为您进行答疑但BladeX团队没有为根据本协议许可的产品提供技术支持的义务或责任。
六、 权利和所有权的保留:
BladeX团队保留所有未在本协议中明确授予您的所有权利。BladeX团队保留随时更新本协议的权利并只需公示于对应产品项目的LICENSE文件无需征得您的事先同意且无需另行通知更新后的内容应于公示即时生效。您可以随时访问产品地址并查阅最新版许可条款在更新生效后您继续使用本产品则被视作您已接受了新的条款。
七、 协议终止
1. 您一旦开始复制、下载、安装或者使用本产品,即被视为完全理解并接受本协议的各项条款,在享有上述条款授予的许可权力同时,也受到相关的约束和限制,本协议许可范围以外的行为,将直接违反本协议并构成侵权。
2. 一旦您违反本协议的条款BladeX团队随时可能终止本协议、收回许可和授权并要求您承担相应法律和经济责任。

43
README.md Normal file
View File

@@ -0,0 +1,43 @@
## 版权声明
* BladeX是一个商业化软件系列产品知识产权归**上海布雷德科技有限公司**独立所有
* 您一旦开始复制、下载、安装或者使用本产品,即被视为完全理解并接受本协议的各项条款
* 更多详情请看:[BladeX商业授权许可协议](https://license.bladex.cn)
## 答疑流程
>1. 遇到问题或Bug
>2. 业务型问题打断点调试尝试找出问题所在
>3. 系统型问题通过百度、谷歌、社区查找解决方案
>4. 未解决问题则进入技术社区进行发帖提问:[https://sns.bladex.cn](https://sns.bladex.cn)
>5. 将帖子地址发至商业群,特别简单三言两语就能描述清楚的也可在答疑时间内发至商业群提问
>6. 发帖的时候一定要描述清楚,详细描述遇到问题的**重现步骤**、**报错详细信息**、**相关代码与逻辑**、**使用软件版本**以及**操作系统版本**,否则随意发帖提问将会提高我们的答疑难度。
## 答疑时间
* 工作日9:00 ~ 17:00 提供答疑,周末、节假日休息,暂停答疑
* 请勿**私聊提问**,以免被其他用户的消息覆盖从而无法获得答疑
* 答疑时间外遇到问题可以将问题发帖至[技术社区](https://sns.bladex.cn),我们后续会逐个回复
## 授权范围
* 专业版:只可用于**个人学习**及**个人私活**项目,不可用于公司或团队,不可泄露给任何第三方
* 企业版:可用于**企业名下**的任何项目,企业版员工在**未购买**专业版授权前,只授权开发**所在授权企业名下**的项目,**不得将BladeX用于个人私活**
* 共同遵守若甲方需要您提供项目源码则需代为甲方购买BladeX企业授权甲方购买后续的所有项目都无需再次购买授权
## 商用权益
* ✔️ 遵守[商业协议](https://license.bladex.cn)的前提下将BladeX系列产品用于授权范围内的商用项目并上线运营
* ✔️ 遵守[商业协议](https://license.bladex.cn)的前提下,不限制项目数,不限制服务器数
* ✔️ 遵守[商业协议](https://license.bladex.cn)的前提下,将自行编写的业务代码申请软件著作权
## 何为侵权
* ❌ 不遵守商业协议,私自销售商业源码
* ❌ 以任何理由将BladeX源码用于申请软件著作权
* ❌ 将商业源码以任何途径任何理由泄露给未授权的单位或个人
* ❌ 开发完毕项目没有为甲方购买企业授权向甲方提供了BladeX代码
* ❌ 基于BladeX拓展研发与BladeX有竞争关系的衍生框架并将其开源或销售
## 侵权后果
* 情节较轻:第一次发现警告处理
* 情节较重:封禁账号,踢出商业群,并保留追究法律责任的权利
* 情节严重:与本地律师事务所合作,以公司名义起诉侵犯计算机软件著作权
## 举报有奖
* 向官方提供有用线索并成功捣毁盗版个人或窝点,将会看成果给予 50010000 不等的现金奖励
* 官方唯一指定QQ1272154962

38
blade-bom/pom.xml Normal file
View File

@@ -0,0 +1,38 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>BladeX-Tool</artifactId>
<groupId>org.springblade</groupId>
<version>${revision}</version>
</parent>
<groupId>org.springblade.platform</groupId>
<artifactId>blade-bom</artifactId>
<packaging>pom</packaging>
<description>bladex统一版本配置</description>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>${maven.flatten.version}</version>
<configuration>
<updatePomFile>true</updatePomFile>
<flattenMode>oss</flattenMode>
<pomElements>
<dependencyManagement>expand</dependencyManagement>
<pluginManagement>remove</pluginManagement>
<dependencies>remove</dependencies>
<properties>remove</properties>
<repositories>remove</repositories>
</pomElements>
</configuration>
</plugin>
</plugins>
</build>
</project>

12
blade-core-auto/README.md Normal file
View File

@@ -0,0 +1,12 @@
# auto 代码自动生成
## 规划
1. 生成 java spi
2. 用来生成 `spring.factories`
3. 考虑生成 `spring-devtools.properties`
4. 考虑?生成 `swagger` 注解
## 参考
Google Auto: https://github.com/google/auto
Spring 5 - spring-context-indexerhttps://github.com/spring-projects/spring-framework/tree/master/spring-context-indexer

36
blade-core-auto/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">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>BladeX-Tool</artifactId>
<groupId>org.springblade</groupId>
<version>${revision}</version>
</parent>
<artifactId>blade-core-auto</artifactId>
<name>${project.artifactId}</name>
<version>${project.parent.version}</version>
<packaging>jar</packaging>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.plugin.version}</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>6.0.13.Final</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,44 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.auto.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.SOURCE;
/**
* ApplicationContextInitializer 处理
*
* @author L.cm
*/
@Documented
@Retention(SOURCE)
@Target(TYPE)
public @interface AutoContextInitializer {
}

View File

@@ -0,0 +1,44 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.auto.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.SOURCE;
/**
* EnvironmentPostProcessor 处理
*
* @author L.cm
*/
@Documented
@Retention(SOURCE)
@Target(TYPE)
public @interface AutoEnvPostProcessor {
}

View File

@@ -0,0 +1,44 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.auto.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.SOURCE;
/**
* FailureAnalyzer 处理
*
* @author L.cm
*/
@Documented
@Retention(SOURCE)
@Target(TYPE)
public @interface AutoFailureAnalyzer {
}

View File

@@ -0,0 +1,44 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.auto.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.SOURCE;
/**
* AutoIgnore 处理
*
* @author L.cm
*/
@Documented
@Retention(SOURCE)
@Target(TYPE)
public @interface AutoIgnore {
}

View File

@@ -0,0 +1,44 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.auto.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.SOURCE;
/**
* ApplicationListener 处理
*
* @author L.cm
*/
@Documented
@Retention(SOURCE)
@Target(TYPE)
public @interface AutoListener {
}

View File

@@ -0,0 +1,44 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.auto.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.SOURCE;
/**
* SpringApplicationRunListener 处理
*
* @author L.cm
*/
@Documented
@Retention(SOURCE)
@Target(TYPE)
public @interface AutoRunListener {
}

View File

@@ -0,0 +1,90 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.auto.common;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Set;
/**
* 抽象 处理器
*
* @author L.cm
*/
public abstract class AbstractBladeProcessor extends AbstractProcessor {
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
/**
* AutoService 注解处理器
* @param annotations 注解 getSupportedAnnotationTypes
* @param roundEnv 扫描到的 注解新
* @return 是否完成
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
try {
return processImpl(annotations, roundEnv);
} catch (Exception e) {
fatalError(e);
return false;
}
}
protected abstract boolean processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv);
protected void log(String msg) {
if (processingEnv.getOptions().containsKey("debug")) {
processingEnv.getMessager().printMessage(Kind.NOTE, msg);
}
}
protected void error(String msg, Element element, AnnotationMirror annotation) {
processingEnv.getMessager().printMessage(Kind.ERROR, msg, element, annotation);
}
protected void fatalError(Exception e) {
// We don't allow exceptions of any kind to propagate to the compiler
StringWriter writer = new StringWriter();
e.printStackTrace(new PrintWriter(writer));
fatalError(writer.toString());
}
protected void fatalError(String msg) {
processingEnv.getMessager().printMessage(Kind.ERROR, "FATAL ERROR: " + msg);
}
}

View File

@@ -0,0 +1,78 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.auto.common;
import org.springblade.core.auto.annotation.*;
/**
* 注解类型
*
* @author L.cm
*/
public enum BootAutoType {
/**
* Component组合注解添加到 spring.factories
*/
COMPONENT(BootAutoType.COMPONENT_ANNOTATION, "org.springframework.boot.autoconfigure.EnableAutoConfiguration"),
/**
* ApplicationContextInitializer 添加到 spring.factories
*/
CONTEXT_INITIALIZER(AutoContextInitializer.class.getName(), "org.springframework.context.ApplicationContextInitializer"),
/**
* ApplicationListener 添加到 spring.factories
*/
LISTENER(AutoListener.class.getName(), "org.springframework.context.ApplicationListener"),
/**
* SpringApplicationRunListener 添加到 spring.factories
*/
RUN_LISTENER(AutoRunListener.class.getName(), "org.springframework.boot.SpringApplicationRunListener"),
/**
* FailureAnalyzer 添加到 spring.factories
*/
FAILURE_ANALYZER(AutoFailureAnalyzer.class.getName(), "org.springframework.boot.diagnostics.FailureAnalyzer"),
/**
* EnvironmentPostProcessor 添加到 spring.factories
*/
ENV_POST_PROCESSOR(AutoEnvPostProcessor.class.getName(), "org.springframework.boot.env.EnvironmentPostProcessor");
private final String annotationName;
private final String configureKey;
BootAutoType(String annotationName, String configureKey) {
this.annotationName = annotationName;
this.configureKey = configureKey;
}
public final String getAnnotationName() {
return annotationName;
}
public final String getConfigureKey() {
return configureKey;
}
public static final String COMPONENT_ANNOTATION = "org.springframework.stereotype.Component";
}

View File

@@ -0,0 +1,158 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.auto.common;
import java.util.*;
/**
* MultiSetMap
*
* @author L.cm
*/
public class MultiSetMap<K, V> {
private transient final Map<K, Set<V>> map;
public MultiSetMap() {
map = new HashMap<>();
}
private Set<V> createSet() {
return new HashSet<>();
}
/**
* put to MultiSetMap
*
* @param key 键
* @param value 值
* @return boolean
*/
public boolean put(K key, V value) {
Set<V> set = map.get(key);
if (set == null) {
set = createSet();
if (set.add(value)) {
map.put(key, set);
return true;
} else {
throw new AssertionError("New set violated the set spec");
}
} else if (set.add(value)) {
return true;
} else {
return false;
}
}
/**
* 是否包含某个key
*
* @param key key
* @return 结果
*/
public boolean containsKey(K key) {
return map.containsKey(key);
}
/**
* 是否包含 value 中的某个值
*
* @param value value
* @return 是否包含
*/
public boolean containsVal(V value) {
Collection<Set<V>> values = map.values();
return values.stream().anyMatch(vs -> vs.contains(value));
}
/**
* key 集合
*
* @return keys
*/
public Set<K> keySet() {
return map.keySet();
}
/**
* put list to MultiSetMap
*
* @param key 键
* @param set 值列表
* @return boolean
*/
public boolean putAll(K key, Set<V> set) {
if (set == null) {
return false;
} else {
map.put(key, set);
return true;
}
}
/**
* put MultiSetMap to MultiSetMap
*
* @param data MultiSetMap
* @return boolean
*/
public boolean putAll(MultiSetMap<K, V> data) {
if (data == null || data.isEmpty()) {
return false;
} else {
for (K k : data.keySet()) {
this.putAll(k, data.get(k));
}
return true;
}
}
/**
* get List by key
*
* @param key 键
* @return List
*/
public Set<V> get(K key) {
return map.get(key);
}
/**
* clear MultiSetMap
*/
public void clear() {
map.clear();
}
/**
* isEmpty
*
* @return isEmpty
*/
public boolean isEmpty() {
return map.isEmpty();
}
}

View File

@@ -0,0 +1,52 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.auto.common;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 集合 工具类
*
* @author L.cm
*/
public class Sets {
/**
* 不可变 集合
*
* @param es 对象
* @param <E> 泛型
* @return 集合
*/
@SafeVarargs
public static <E> Set<E> ofImmutableSet(E... es) {
Objects.requireNonNull(es);
return Stream.of(es).collect(Collectors.toSet());
}
}

View File

@@ -0,0 +1,132 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.auto.common;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.QualifiedNameable;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import java.util.ArrayList;
import java.util.List;
/**
* Type utilities.
*
* @author Stephane Nicoll
* @since 5.0
*/
public class TypeHelper {
private final ProcessingEnvironment env;
private final Types types;
public TypeHelper(ProcessingEnvironment env) {
this.env = env;
this.types = env.getTypeUtils();
}
public String getType(Element element) {
return getType(element != null ? element.asType() : null);
}
public String getType(AnnotationMirror annotation) {
return getType(annotation != null ? annotation.getAnnotationType() : null);
}
public String getType(TypeMirror type) {
if (type == null) {
return null;
}
if (type instanceof DeclaredType) {
DeclaredType declaredType = (DeclaredType) type;
Element enclosingElement = declaredType.asElement().getEnclosingElement();
if (enclosingElement != null && enclosingElement instanceof TypeElement) {
return getQualifiedName(enclosingElement) + "$" + declaredType.asElement().getSimpleName().toString();
} else {
return getQualifiedName(declaredType.asElement());
}
}
return type.toString();
}
private String getQualifiedName(Element element) {
if (element instanceof QualifiedNameable) {
return ((QualifiedNameable) element).getQualifiedName().toString();
}
return element.toString();
}
/**
* Return the super class of the specified {@link Element} or null if this
* {@code element} represents {@link Object}.
*
* @param element Element
* @return Element
*/
public Element getSuperClass(Element element) {
List<? extends TypeMirror> superTypes = this.types.directSupertypes(element.asType());
if (superTypes.isEmpty()) {
// reached java.lang.Object
return null;
}
return this.types.asElement(superTypes.get(0));
}
/**
* Return the interfaces that are <strong>directly</strong> implemented by the
* specified {@link Element} or an empty list if this {@code element} does not
* implement any interface.
*
* @param element Element
* @return Element list
*/
public List<Element> getDirectInterfaces(Element element) {
List<? extends TypeMirror> superTypes = this.types.directSupertypes(element.asType());
List<Element> directInterfaces = new ArrayList<>();
// index 0 is the super class
if (superTypes.size() > 1) {
for (int i = 1; i < superTypes.size(); i++) {
Element e = this.types.asElement(superTypes.get(i));
if (e != null) {
directInterfaces.add(e);
}
}
}
return directInterfaces;
}
public List<? extends AnnotationMirror> getAllAnnotationMirrors(Element e) {
return this.env.getElementUtils().getAllAnnotationMirrors(e);
}
}

View File

@@ -0,0 +1,305 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.auto.factories;
import org.springblade.core.auto.annotation.AutoIgnore;
import org.springblade.core.auto.common.AbstractBladeProcessor;
import org.springblade.core.auto.common.BootAutoType;
import org.springblade.core.auto.common.MultiSetMap;
import org.springblade.core.auto.service.AutoService;
import javax.annotation.processing.*;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* spring boot 自动配置处理器
*
* @author L.cm
*/
@AutoService(Processor.class)
@SupportedAnnotationTypes("*")
@SupportedOptions("debug")
public class AutoFactoriesProcessor extends AbstractBladeProcessor {
/**
* 处理的注解 @FeignClient
*/
private static final String FEIGN_CLIENT_ANNOTATION = "org.springframework.cloud.openfeign.FeignClient";
/**
* Feign 自动配置
*/
private static final String FEIGN_AUTO_CONFIGURE_KEY = "org.springblade.core.cloud.feign.BladeFeignAutoConfiguration";
/**
* The location to look for factories.
* <p>Can be present in multiple JAR files.
*/
private static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
/**
* devtools有 Configuration 注解的 jar 一般需要 devtools 配置文件
*/
private static final String DEVTOOLS_RESOURCE_LOCATION = "META-INF/spring-devtools.properties";
/**
* AutoConfiguration 注解
*/
private static final String AUTO_CONFIGURATION = "org.springframework.boot.autoconfigure.AutoConfiguration";
/**
* AutoConfiguration imports out put
*/
private static final String AUTO_CONFIGURATION_IMPORTS_LOCATION = "META-INF/spring/" + AUTO_CONFIGURATION + ".imports";
/**
* 数据承载
*/
private final MultiSetMap<String, String> factories = new MultiSetMap<>();
/**
* spring boot 2.7 @AutoConfiguration
*/
private final Set<String> autoConfigurationImportsSet = new LinkedHashSet<>();
/**
* 元素辅助类
*/
private Elements elementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
elementUtils = processingEnv.getElementUtils();
}
@Override
protected boolean processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (roundEnv.processingOver()) {
// 1. 生成 spring boot 2.7.x @AutoConfiguration
generateAutoConfigurationImportsFiles();
// 2. 生成 spring.factories
generateFactoriesFiles();
} else {
processAnnotations(annotations, roundEnv);
}
return false;
}
private void processAnnotations(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 日志 打印信息 gradle build --debug
log(annotations.toString());
Set<? extends Element> elementSet = roundEnv.getRootElements();
log("All Element set: " + elementSet.toString());
// 过滤 TypeElement
Set<TypeElement> typeElementSet = elementSet.stream()
.filter(this::isClassOrInterface)
.filter(TypeElement.class::isInstance)
.map(e -> (TypeElement) e)
.collect(Collectors.toSet());
// 如果为空直接跳出
if (typeElementSet.isEmpty()) {
log("Annotations elementSet is isEmpty");
return;
}
for (TypeElement typeElement : typeElementSet) {
if (isAnnotation(elementUtils, typeElement, AutoIgnore.class.getName())) {
log("Found @AutoIgnore annotationignore Element: " + typeElement.toString());
} else if (isAnnotation(elementUtils, typeElement, FEIGN_CLIENT_ANNOTATION)) {
log("Found @FeignClient Element: " + typeElement.toString());
ElementKind elementKind = typeElement.getKind();
// Feign Client 只处理 接口
if (ElementKind.INTERFACE != elementKind) {
fatalError("@FeignClient Element " + typeElement + " 不是接口。");
continue;
}
String factoryName = typeElement.getQualifiedName().toString();
if (factories.containsVal(factoryName)) {
continue;
}
log("读取到新配置 spring.factories factoryName" + factoryName);
factories.put(FEIGN_AUTO_CONFIGURE_KEY, factoryName);
} else {
// 1. 生成 2.7.x 的 spi
if (isAnnotation(elementUtils, typeElement, BootAutoType.COMPONENT_ANNOTATION)) {
String autoConfigurationBeanName = typeElement.getQualifiedName().toString();
autoConfigurationImportsSet.add(autoConfigurationBeanName);
log("读取到自动配置 @AutoConfiguration" + autoConfigurationBeanName);
}
// 2. 老的 spring.factories
for (BootAutoType autoType : BootAutoType.values()) {
String annotation = autoType.getAnnotationName();
if (isAnnotation(elementUtils, typeElement, annotation)) {
log("Found @" + annotation + " Element: " + typeElement.toString());
String factoryName = typeElement.getQualifiedName().toString();
if (factories.containsVal(factoryName)) {
continue;
}
log("读取到新配置 spring.factories factoryName" + factoryName);
factories.put(autoType.getConfigureKey(), factoryName);
}
}
}
}
}
private void generateFactoriesFiles() {
if (factories.isEmpty()) {
return;
}
Filer filer = processingEnv.getFiler();
try {
// spring.factories 配置
MultiSetMap<String, String> allFactories = new MultiSetMap<>();
// 1. 用户手动配置项目下的 spring.factories 文件
try {
FileObject existingFactoriesFile = filer.getResource(StandardLocation.SOURCE_OUTPUT, "", FACTORIES_RESOURCE_LOCATION);
// 查找是否已经存在 spring.factories
log("Looking for existing spring.factories file at " + existingFactoriesFile.toUri());
MultiSetMap<String, String> existingFactories = FactoriesFiles.readFactoriesFile(existingFactoriesFile, elementUtils);
log("Existing spring.factories entries: " + existingFactories);
allFactories.putAll(existingFactories);
} catch (IOException e) {
log("spring.factories resource file not found.");
}
// 2. 增量编译,已经存在的 spring.factories 文件
try {
FileObject existingFactoriesFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "", FACTORIES_RESOURCE_LOCATION);
// 查找是否已经存在 spring.factories
log("Looking for existing spring.factories file at " + existingFactoriesFile.toUri());
MultiSetMap<String, String> existingFactories = FactoriesFiles.readFactoriesFile(existingFactoriesFile, elementUtils);
log("Existing spring.factories entries: " + existingFactories);
allFactories.putAll(existingFactories);
} catch (IOException e) {
log("spring.factories resource file did not already exist.");
}
// 3. 处理器扫描出来的新的配置
allFactories.putAll(factories);
log("New spring.factories file contents: " + allFactories);
FileObject factoriesFile = filer.createResource(StandardLocation.CLASS_OUTPUT, "", FACTORIES_RESOURCE_LOCATION);
try (OutputStream out = factoriesFile.openOutputStream()) {
FactoriesFiles.writeFactoriesFile(allFactories, out);
}
// 4. devtools 配置,因为有 @Configuration 注解的需要 devtools
String classesPath = factoriesFile.toUri().toString().split("classes")[0];
Path projectPath = Paths.get(new URI(classesPath)).getParent();
String projectName = projectPath.getFileName().toString();
FileObject devToolsFile = filer.createResource(StandardLocation.CLASS_OUTPUT, "", DEVTOOLS_RESOURCE_LOCATION);
try (OutputStream out = devToolsFile.openOutputStream()) {
FactoriesFiles.writeDevToolsFile(projectName, out);
}
} catch (IOException | URISyntaxException e) {
fatalError(e);
}
}
private void generateAutoConfigurationImportsFiles() {
if (autoConfigurationImportsSet.isEmpty()) {
return;
}
Filer filer = processingEnv.getFiler();
try {
// AutoConfiguration 配置
Set<String> allAutoConfigurationImports = new LinkedHashSet<>();
// 1. 用户手动配置项目下的 AutoConfiguration 文件
try {
FileObject existingFactoriesFile = filer.getResource(StandardLocation.SOURCE_OUTPUT, "", AUTO_CONFIGURATION_IMPORTS_LOCATION);
// 查找是否已经存在 spring.factories
log("Looking for existing AutoConfiguration imports file at " + existingFactoriesFile.toUri());
Set<String> existingSet = FactoriesFiles.readAutoConfigurationImports(existingFactoriesFile);
log("Existing AutoConfiguration imports entries: " + existingSet);
allAutoConfigurationImports.addAll(existingSet);
} catch (IOException e) {
log("AutoConfiguration imports resource file not found.");
}
// 2. 增量编译,已经存在的配置文件
try {
FileObject existingFactoriesFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "", AUTO_CONFIGURATION_IMPORTS_LOCATION);
// 查找是否已经存在 spring.factories
log("Looking for existing AutoConfiguration imports file at " + existingFactoriesFile.toUri());
Set<String> existingSet = FactoriesFiles.readAutoConfigurationImports(existingFactoriesFile);
log("Existing AutoConfiguration imports entries: " + existingSet);
allAutoConfigurationImports.addAll(existingSet);
} catch (IOException e) {
log("AutoConfiguration imports resource file did not already exist.");
}
// 3. 处理器扫描出来的新的配置
allAutoConfigurationImports.addAll(autoConfigurationImportsSet);
log("New AutoConfiguration imports file contents: " + allAutoConfigurationImports);
FileObject autoConfigurationImportsFile = filer.createResource(StandardLocation.CLASS_OUTPUT, "", AUTO_CONFIGURATION_IMPORTS_LOCATION);
try (OutputStream out = autoConfigurationImportsFile.openOutputStream()) {
FactoriesFiles.writeAutoConfigurationImportsFile(allAutoConfigurationImports, out);
}
} catch (IOException e) {
fatalError(e);
}
}
private boolean isClassOrInterface(Element e) {
ElementKind kind = e.getKind();
return kind == ElementKind.CLASS || kind == ElementKind.INTERFACE;
}
private boolean isAnnotation(Elements elementUtils, Element e, String annotationFullName) {
List<? extends AnnotationMirror> annotationList = elementUtils.getAllAnnotationMirrors(e);
for (AnnotationMirror annotation : annotationList) {
// 如果是对于的注解
if (isAnnotation(annotationFullName, annotation)) {
return true;
}
// 处理组合注解
Element element = annotation.getAnnotationType().asElement();
String elementStr = element.toString();
// 如果是 java 元注解,继续循环
if (elementStr.startsWith("java.lang") || elementStr.startsWith("kotlin.")) {
continue;
}
// 递归处理 组合注解
if (isAnnotation(elementUtils, element, annotationFullName)) {
return true;
}
}
return false;
}
private boolean isAnnotation(String annotationFullName, AnnotationMirror annotation) {
return annotationFullName.equals(annotation.getAnnotationType().toString());
}
}

View File

@@ -0,0 +1,163 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.auto.factories;
import org.springblade.core.auto.common.MultiSetMap;
import javax.lang.model.util.Elements;
import javax.tools.FileObject;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
/**
* spring boot 自动化配置工具类
*
* @author L.cm
*/
class FactoriesFiles {
private static final Charset UTF_8 = StandardCharsets.UTF_8;
/**
* 读取 spring.factories 文件
*
* @param fileObject FileObject
* @return MultiSetMap
* @throws IOException 异常信息
*/
protected static MultiSetMap<String, String> readFactoriesFile(FileObject fileObject, Elements elementUtils) throws IOException {
// 读取 spring.factories 内容
Properties properties = new Properties();
try (InputStream input = fileObject.openInputStream()) {
properties.load(input);
}
MultiSetMap<String, String> multiSetMap = new MultiSetMap<>();
Set<Map.Entry<Object, Object>> entrySet = properties.entrySet();
for (Map.Entry<Object, Object> objectEntry : entrySet) {
String key = (String) objectEntry.getKey();
String value = (String) objectEntry.getValue();
if (value == null || value.trim().isEmpty()) {
continue;
}
// 解析 spring.factories
String[] values = value.split(",");
Set<String> valueSet = Arrays.stream(values)
.filter(v -> !v.isEmpty())
.map(String::trim)
// 校验是否删除文件
.filter((v) -> Objects.nonNull(elementUtils.getTypeElement(v)))
.collect(Collectors.toSet());
multiSetMap.putAll(key.trim(), valueSet);
}
return multiSetMap;
}
/**
* 读取已经存在的 AutoConfiguration imports
*
* @param fileObject FileObject
* @return Set
* @throws IOException IOException
*/
protected static Set<String> readAutoConfigurationImports(FileObject fileObject) throws IOException {
Set<String> set = new HashSet<>();
try (
InputStream input = fileObject.openInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(input))
) {
reader.lines()
.map(String::trim)
.filter(line -> !line.startsWith("#"))
.forEach(set::add);
}
return set;
}
/**
* 写出 spring.factories 文件
*
* @param factories factories 信息
* @param output 输出流
* @throws IOException 异常信息
*/
protected static void writeFactoriesFile(MultiSetMap<String, String> factories,
OutputStream output) throws IOException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, UTF_8));
Set<String> keySet = factories.keySet();
for (String key : keySet) {
Set<String> values = factories.get(key);
if (values == null || values.isEmpty()) {
continue;
}
writer.write(key);
writer.write("=\\\n ");
StringJoiner joiner = new StringJoiner(",\\\n ");
for (String value : values) {
joiner.add(value);
}
writer.write(joiner.toString());
writer.newLine();
}
writer.flush();
output.close();
}
/**
* 写出 spring-devtools.properties
*
* @param projectName 项目名
* @param output 输出流
* @throws IOException 异常信息
*/
protected static void writeDevToolsFile(String projectName,
OutputStream output) throws IOException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, UTF_8));
String format = "restart.include.%s=/%s[\\\\w-]+\\.jar";
writer.write(String.format(format, projectName, projectName));
writer.flush();
output.close();
}
/**
* 写出 AutoConfiguration imports
*
* @param allAutoConfigurationImports allAutoConfigurationImports
* @param output OutputStream
* @throws IOException IOException
*/
protected static void writeAutoConfigurationImportsFile(Set<String> allAutoConfigurationImports, OutputStream output) throws IOException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, UTF_8));
StringJoiner joiner = new StringJoiner("\n");
for (String configurationImport : allAutoConfigurationImports) {
joiner.add(configurationImport);
}
writer.write(joiner.toString());
writer.flush();
}
}

View File

@@ -0,0 +1,56 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.auto.service;
import java.lang.annotation.*;
/**
* An annotation for service providers as described in {@link java.util.ServiceLoader}. The {@link
* AutoServiceProcessor} generates the configuration files which
* allows service providers to be loaded with {@link java.util.ServiceLoader#load(Class)}.
*
* <p>Service providers assert that they conform to the service provider specification.
* Specifically, they must:
*
* <ul>
* <li>be a non-inner, non-anonymous, concrete class
* <li>have a publicly accessible no-arg constructor
* <li>implement the interface type returned by {@code value()}
* </ul>
*
* @author google
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface AutoService {
/**
* Returns the interfaces implemented by this service provider.
*
* @return interface array
*/
Class<?>[] value();
}

View File

@@ -0,0 +1,263 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.auto.service;
import org.springblade.core.auto.common.AbstractBladeProcessor;
import org.springblade.core.auto.common.MultiSetMap;
import org.springblade.core.auto.common.Sets;
import org.springblade.core.auto.common.TypeHelper;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedOptions;
import javax.lang.model.element.*;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.SimpleAnnotationValueVisitor8;
import javax.lang.model.util.Types;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.util.*;
import java.util.stream.Collectors;
/**
* java spi 服务自动处理器 参考google auto
*
* @author L.cm
*/
@SupportedOptions("debug")
public class AutoServiceProcessor extends AbstractBladeProcessor {
/**
* spi 服务集合key 接口 -> value 实现列表
*/
private final MultiSetMap<String, String> providers = new MultiSetMap<>();
private TypeHelper typeHelper;
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
this.typeHelper = new TypeHelper(env);
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return Sets.ofImmutableSet(AutoService.class.getName());
}
@Override
protected boolean processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (roundEnv.processingOver()) {
generateConfigFiles();
} else {
processAnnotations(annotations, roundEnv);
}
return true;
}
private void processAnnotations(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(AutoService.class);
log(annotations.toString());
log(elements.toString());
for (Element e : elements) {
TypeElement providerImplementer = (TypeElement) e;
AnnotationMirror annotationMirror = getAnnotationMirror(e, AutoService.class);
if (annotationMirror == null) {
continue;
}
Set<TypeMirror> typeMirrors = getValueFieldOfClasses(annotationMirror);
if (typeMirrors.isEmpty()) {
error("No service interfaces provided for element!", e, annotationMirror);
continue;
}
for (TypeMirror typeMirror : typeMirrors) {
String providerInterfaceName = typeHelper.getType(typeMirror);
Name providerImplementerName = providerImplementer.getQualifiedName();
log("provider interface: " + providerInterfaceName);
log("provider implementer: " + providerImplementerName);
if (checkImplementer(providerImplementer, typeMirror)) {
providers.put(providerInterfaceName, typeHelper.getType(providerImplementer));
} else {
String message = "ServiceProviders must implement their service provider interface. "
+ providerImplementerName + " does not implement " + providerInterfaceName;
error(message, e, annotationMirror);
}
}
}
}
private void generateConfigFiles() {
Filer filer = processingEnv.getFiler();
for (String providerInterface : providers.keySet()) {
String resourceFile = "META-INF/services/" + providerInterface;
log("Working on resource file: " + resourceFile);
try {
SortedSet<String> allServices = new TreeSet<>();
try {
FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "", resourceFile);
log("Looking for existing resource file at " + existingFile.toUri());
Set<String> oldServices = ServicesFiles.readServiceFile(existingFile.openInputStream());
log("Existing service entries: " + oldServices);
allServices.addAll(oldServices);
} catch (IOException e) {
log("Resource file did not already exist.");
}
Set<String> newServices = new HashSet<>(providers.get(providerInterface));
if (allServices.containsAll(newServices)) {
log("No new service entries being added.");
return;
}
allServices.addAll(newServices);
log("New service file contents: " + allServices);
FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "", resourceFile);
OutputStream out = fileObject.openOutputStream();
ServicesFiles.writeServiceFile(allServices, out);
out.close();
log("Wrote to: " + fileObject.toUri());
} catch (IOException e) {
fatalError("Unable to create " + resourceFile + ", " + e);
return;
}
}
}
/**
* Verifies {@link java.util.spi.LocaleServiceProvider} constraints on the concrete provider class.
* Note that these constraints are enforced at runtime via the ServiceLoader,
* we're just checking them at compile time to be extra nice to our users.
*/
private boolean checkImplementer(TypeElement providerImplementer, TypeMirror providerType) {
// TODO: We're currently only enforcing the subtype relationship
// constraint. It would be nice to enforce them all.
Types types = processingEnv.getTypeUtils();
return types.isSubtype(providerImplementer.asType(), providerType);
}
/**
* 读取 AutoService 上的 value 值
*
* @param annotationMirror AnnotationMirror
* @return value 集合
*/
private Set<TypeMirror> getValueFieldOfClasses(AnnotationMirror annotationMirror) {
return getAnnotationValue(annotationMirror, "value")
.accept(new SimpleAnnotationValueVisitor8<Set<TypeMirror>, Void>() {
@Override
public Set<TypeMirror> visitType(TypeMirror typeMirror, Void v) {
Set<TypeMirror> declaredTypeSet = new HashSet<>(1);
declaredTypeSet.add(typeMirror);
return Collections.unmodifiableSet(declaredTypeSet);
}
@Override
public Set<TypeMirror> visitArray(
List<? extends AnnotationValue> values, Void v) {
return values
.stream()
.flatMap(value -> value.accept(this, null).stream())
.collect(Collectors.toSet());
}
}, null);
}
/**
* Returns a {@link ExecutableElement} and its associated {@link AnnotationValue} if such
* an element was either declared in the usage represented by the provided
* {@link AnnotationMirror}, or if such an element was defined with a default.
*
* @param annotationMirror AnnotationMirror
* @param elementName elementName
* @return AnnotationValue map
* @throws IllegalArgumentException if no element is defined with the given elementName.
*/
public AnnotationValue getAnnotationValue(AnnotationMirror annotationMirror, String elementName) {
Objects.requireNonNull(annotationMirror);
Objects.requireNonNull(elementName);
for (Map.Entry<ExecutableElement, AnnotationValue> entry : getAnnotationValuesWithDefaults(annotationMirror).entrySet()) {
if (entry.getKey().getSimpleName().contentEquals(elementName)) {
return entry.getValue();
}
}
String name = typeHelper.getType(annotationMirror);
throw new IllegalArgumentException(String.format("@%s does not define an element %s()", name, elementName));
}
/**
* Returns the {@link AnnotationMirror}'s map of {@link AnnotationValue} indexed by {@link
* ExecutableElement}, supplying default values from the annotation if the annotation property has
* not been set. This is equivalent to {@link
* Elements#getElementValuesWithDefaults(AnnotationMirror)} but can be called statically without
* an {@link Elements} instance.
*
* <p>The iteration order of elements of the returned map will be the order in which the {@link
* ExecutableElement}s are defined in {@code annotation}'s {@linkplain
* AnnotationMirror#getAnnotationType() type}.
*
* @param annotation AnnotationMirror
* @return AnnotationValue Map
*/
public Map<ExecutableElement, AnnotationValue> getAnnotationValuesWithDefaults(AnnotationMirror annotation) {
Map<ExecutableElement, AnnotationValue> values = new HashMap<>(32);
Map<? extends ExecutableElement, ? extends AnnotationValue> declaredValues = annotation.getElementValues();
for (ExecutableElement method : ElementFilter.methodsIn(annotation.getAnnotationType().asElement().getEnclosedElements())) {
// Must iterate and put in this order, to ensure consistency in generated code.
if (declaredValues.containsKey(method)) {
values.put(method, declaredValues.get(method));
} else if (method.getDefaultValue() != null) {
values.put(method, method.getDefaultValue());
} else {
String name = typeHelper.getType(method);
throw new IllegalStateException(
"Unset annotation value without default should never happen: " + name + '.' + method.getSimpleName() + "()");
}
}
return Collections.unmodifiableMap(values);
}
public AnnotationMirror getAnnotationMirror(Element element, Class<? extends Annotation> annotationClass) {
String annotationClassName = annotationClass.getCanonicalName();
for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
String name = typeHelper.getType(annotationMirror);
if (name.contentEquals(annotationClassName)) {
return annotationMirror;
}
}
return null;
}
}

View File

@@ -0,0 +1,86 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.auto.service;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* A helper class for reading and writing Services files.
*
* @author L.cm
*/
class ServicesFiles {
private static final Charset UTF_8 = StandardCharsets.UTF_8;
/**
* Reads the set of service classes from a service file.
*
* @param input not {@code null}. Closed after use.
* @return a not {@code null Set} of service class names.
* @throws IOException
*/
static Set<String> readServiceFile(InputStream input) throws IOException {
HashSet<String> serviceClasses = new HashSet<>();
try (
InputStreamReader isr = new InputStreamReader(input, UTF_8);
BufferedReader r = new BufferedReader(isr)
) {
String line;
while ((line = r.readLine()) != null) {
int commentStart = line.indexOf('#');
if (commentStart >= 0) {
line = line.substring(0, commentStart);
}
line = line.trim();
if (!line.isEmpty()) {
serviceClasses.add(line);
}
}
return serviceClasses;
}
}
/**
* Writes the set of service class names to a service file.
*
* @param output not {@code null}. Not closed after use.
* @param services a not {@code null Collection} of service class names.
* @throws IOException
*/
static void writeServiceFile(Collection<String> services, OutputStream output) throws IOException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, UTF_8));
for (String service : services) {
writer.write(service);
writer.newLine();
}
writer.flush();
}
}

View File

@@ -0,0 +1,2 @@
org.springblade.core.auto.service.AutoServiceProcessor
org.springblade.core.auto.factories.AutoFactoriesProcessor

59
blade-core-boot/pom.xml Normal file
View File

@@ -0,0 +1,59 @@
<?xml version="1.0"?>
<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">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>BladeX-Tool</artifactId>
<groupId>org.springblade</groupId>
<version>${revision}</version>
</parent>
<artifactId>blade-core-boot</artifactId>
<name>${project.artifactId}</name>
<version>${project.parent.version}</version>
<packaging>jar</packaging>
<dependencies>
<!-- Blade -->
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-context</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-db</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-secure</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-cloud</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-log</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-mybatis</artifactId>
</dependency>
<!-- Auto -->
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-auto</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,46 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.boot.config;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.launch.props.BladePropertySource;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* blade自动配置类
*
* @author Chill
*/
@Slf4j
@AutoConfiguration
@AllArgsConstructor
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
@BladePropertySource(value = "classpath:/blade-boot.yml")
public class BladeBootAutoConfiguration {
}

View File

@@ -0,0 +1,116 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.boot.config;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.boot.error.ErrorType;
import org.springblade.core.boot.error.ErrorUtil;
import org.springblade.core.context.BladeContext;
import org.springblade.core.context.BladeRunnableWrapper;
import org.springblade.core.launch.props.BladeProperties;
import org.springblade.core.log.constant.EventConstant;
import org.springblade.core.log.event.ErrorLogEvent;
import org.springblade.core.log.model.LogError;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.boot.task.ThreadPoolTaskExecutorCustomizer;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;
import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 异步处理
*
* @author Chill
*/
@Slf4j
@Configuration
@EnableAsync
@EnableScheduling
@AllArgsConstructor
public class BladeExecutorConfiguration extends AsyncConfigurerSupport {
private final BladeContext bladeContext;
private final BladeProperties bladeProperties;
private final ApplicationEventPublisher publisher;
@Bean
public ThreadPoolTaskExecutorCustomizer taskExecutorCustomizer() {
return taskExecutor -> {
taskExecutor.setThreadNamePrefix("async-task-");
taskExecutor.setTaskDecorator(BladeRunnableWrapper::new);
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
};
}
@Bean
public ThreadPoolTaskExecutorCustomizer taskSchedulerCustomizer() {
return taskExecutor -> {
taskExecutor.setThreadNamePrefix("async-scheduler");
taskExecutor.setTaskDecorator(BladeRunnableWrapper::new);
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
};
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new BladeAsyncUncaughtExceptionHandler(bladeContext, bladeProperties, publisher);
}
@RequiredArgsConstructor
private static class BladeAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
private final BladeContext bladeContext;
private final BladeProperties bladeProperties;
private final ApplicationEventPublisher eventPublisher;
@Override
public void handleUncaughtException(@NonNull Throwable error, @NonNull Method method, @NonNull Object... params) {
log.error("Unexpected exception occurred invoking async method: {}", method, error);
LogError logError = new LogError();
// 服务信息、环境、异常类型
logError.setParams(ErrorType.ASYNC.getType());
logError.setEnv(bladeProperties.getEnv());
logError.setServiceId(bladeProperties.getName());
logError.setRequestUri(bladeContext.getRequestId());
// 堆栈信息
ErrorUtil.initErrorInfo(error, logError);
Map<String, Object> event = new HashMap<>(16);
event.put(EventConstant.EVENT_LOG, logError);
eventPublisher.publishEvent(new ErrorLogEvent(event));
}
}
}

View File

@@ -0,0 +1,58 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.boot.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.retry.interceptor.RetryInterceptorBuilder;
import org.springframework.retry.interceptor.RetryOperationsInterceptor;
/**
* 重试机制
*
* @author Chill
*/
@Slf4j
@AutoConfiguration
public class BladeRetryConfiguration {
@Bean
@ConditionalOnMissingBean(name = "configServerRetryInterceptor")
public RetryOperationsInterceptor configServerRetryInterceptor() {
log.info(String.format(
"configServerRetryInterceptor: Changing backOffOptions " +
"to initial: %s, multiplier: %s, maxInterval: %s",
1000, 1.2, 5000));
return RetryInterceptorBuilder
.stateless()
.backOffOptions(1000, 1.2, 5000)
.maxAttempts(10)
.build();
}
}

View File

@@ -0,0 +1,71 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.boot.config;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.boot.props.BladeFileProperties;
import org.springblade.core.boot.props.BladeUploadProperties;
import org.springblade.core.boot.resolver.TokenArgumentResolver;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
/**
* WEB配置
*
* @author Chill
*/
@Slf4j
@AutoConfiguration
@Order(Ordered.HIGHEST_PRECEDENCE)
@AllArgsConstructor
@EnableConfigurationProperties({
BladeUploadProperties.class, BladeFileProperties.class
})
public class BladeWebMvcConfiguration implements WebMvcConfigurer {
private final BladeUploadProperties bladeUploadProperties;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
String path = bladeUploadProperties.getSavePath();
registry.addResourceHandler("/upload/**")
.addResourceLocations("file:" + path + "/upload/");
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new TokenArgumentResolver());
}
}

View File

@@ -0,0 +1,66 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.boot.config;
import lombok.AllArgsConstructor;
import org.springblade.core.boot.request.BladeRequestFilter;
import org.springblade.core.boot.request.RequestProperties;
import org.springblade.core.boot.request.XssProperties;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import jakarta.servlet.DispatcherType;
/**
* 过滤器配置类
*
* @author Chill
*/
@AutoConfiguration
@AllArgsConstructor
@EnableConfigurationProperties({RequestProperties.class, XssProperties.class})
public class RequestConfiguration {
private final RequestProperties requestProperties;
private final XssProperties xssProperties;
/**
* 全局过滤器
*/
@Bean
public FilterRegistrationBean<BladeRequestFilter> bladeFilterRegistration() {
FilterRegistrationBean<BladeRequestFilter> registration = new FilterRegistrationBean<>();
registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.setFilter(new BladeRequestFilter(requestProperties, xssProperties));
registration.addUrlPatterns("/*");
registration.setName("bladeRequestFilter");
registration.setOrder(Ordered.LOWEST_PRECEDENCE);
return registration;
}
}

View File

@@ -0,0 +1,288 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.boot.ctrl;
import org.springblade.core.boot.file.LocalFile;
import org.springblade.core.boot.file.BladeFileUtil;
import org.springblade.core.secure.BladeUser;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.Charsets;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.WebUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourceRegion;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.UriUtils;
import jakarta.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.util.List;
/**
* Blade控制器封装类
*
* @author Chill
*/
public class BladeController {
/**
* ============================ REQUEST =================================================
*/
@Autowired
private HttpServletRequest request;
/**
* 获取request
*/
public HttpServletRequest getRequest() {
return this.request;
}
/**
* 获取当前用户
*
* @return
*/
public BladeUser getUser() {
return AuthUtil.getUser();
}
/** ============================ API_RESULT ================================================= */
/**
* 返回ApiResult
*
* @param data
* @return R
*/
public <T> R<T> data(T data) {
return R.data(data);
}
/**
* 返回ApiResult
*
* @param data
* @param message
* @return R
*/
public <T> R<T> data(T data, String message) {
return R.data(data, message);
}
/**
* 返回ApiResult
*
* @param data
* @param message
* @param code
* @return R
*/
public <T> R<T> data(T data, String message, int code) {
return R.data(code, data, message);
}
/**
* 返回ApiResult
*
* @param message
* @return R
*/
public R success(String message) {
return R.success(message);
}
/**
* 返回ApiResult
*
* @param message
* @return R
*/
public R fail(String message) {
return R.fail(message);
}
/**
* 返回ApiResult
*
* @param flag
* @return R
*/
public R status(boolean flag) {
return R.status(flag);
}
/**============================ FILE ================================================= */
/**
* 获取BladeFile封装类
*
* @param file
* @return
*/
public LocalFile getFile(MultipartFile file) {
return BladeFileUtil.getFile(file);
}
/**
* 获取BladeFile封装类
*
* @param file
* @param dir
* @return
*/
public LocalFile getFile(MultipartFile file, String dir) {
return BladeFileUtil.getFile(file, dir);
}
/**
* 获取BladeFile封装类
*
* @param file
* @param dir
* @param path
* @param virtualPath
* @return
*/
public LocalFile getFile(MultipartFile file, String dir, String path, String virtualPath) {
return BladeFileUtil.getFile(file, dir, path, virtualPath);
}
/**
* 获取BladeFile封装类
*
* @param files
* @return
*/
public List<LocalFile> getFiles(List<MultipartFile> files) {
return BladeFileUtil.getFiles(files);
}
/**
* 获取BladeFile封装类
*
* @param files
* @param dir
* @return
*/
public List<LocalFile> getFiles(List<MultipartFile> files, String dir) {
return BladeFileUtil.getFiles(files, dir);
}
/**
* 获取BladeFile封装类
*
* @param files
* @param path
* @param virtualPath
* @return
*/
public List<LocalFile> getFiles(List<MultipartFile> files, String dir, String path, String virtualPath) {
return BladeFileUtil.getFiles(files, dir, path, virtualPath);
}
/**
* 下载文件
*
* @param file 文件
* @return {ResponseEntity}
* @throws IOException io异常
*/
protected ResponseEntity<ResourceRegion> download(File file) throws IOException {
String fileName = file.getName();
return download(file, fileName);
}
/**
* 下载
*
* @param file 文件
* @param fileName 生成的文件名
* @return {ResponseEntity}
* @throws IOException io异常
*/
protected ResponseEntity<ResourceRegion> download(File file, String fileName) throws IOException {
Resource resource = new FileSystemResource(file);
return download(resource, fileName);
}
/**
* 下载
*
* @param resource 资源
* @param fileName 生成的文件名
* @return {ResponseEntity}
* @throws IOException io异常
*/
protected ResponseEntity<ResourceRegion> download(Resource resource, String fileName) throws IOException {
HttpServletRequest request = WebUtil.getRequest();
String header = request.getHeader(HttpHeaders.USER_AGENT);
// 避免空指针
header = header == null ? StringPool.EMPTY : header.toUpperCase();
HttpStatus status;
String msie= "MSIE";
String trident= "TRIDENT";
String edge= "EDGE";
if (header.contains(msie) || header.contains(trident) || header.contains(edge)) {
status = HttpStatus.OK;
} else {
status = HttpStatus.CREATED;
}
// 断点续传
long position = 0;
long count = resource.contentLength();
String range = request.getHeader(HttpHeaders.RANGE);
if (null != range) {
status = HttpStatus.PARTIAL_CONTENT;
String[] rangeRange = range.replace("bytes=", StringPool.EMPTY).split(StringPool.DASH);
position = Long.parseLong(rangeRange[0]);
if (rangeRange.length > 1) {
long end = Long.parseLong(rangeRange[1]);
count = end - position + 1;
}
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
String encodeFileName = UriUtils.encode(fileName, Charsets.UTF_8);
// 兼容各种浏览器下载:
// https://blog.robotshell.org/2012/deal-with-http-header-encoding-for-file-download/
String disposition = "attachment;" +
"filename=\"" + encodeFileName + "\";" +
"filename*=utf-8''" + encodeFileName;
headers.set(HttpHeaders.CONTENT_DISPOSITION, disposition);
return new ResponseEntity<>(new ResourceRegion(resource, position, count), headers, status);
}
}

View File

@@ -0,0 +1,66 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.boot.error;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.lang.Nullable;
/**
* 异常类型
*
* @author L.cm
*/
@Getter
@RequiredArgsConstructor
public enum ErrorType {
/**
* 异常类型
*/
REQUEST("request"),
ASYNC("async"),
SCHEDULER("scheduler"),
WEB_SOCKET("websocket"),
OTHER("other");
@JsonValue
private final String type;
@Nullable
@JsonCreator
public static ErrorType of(String type) {
ErrorType[] values = ErrorType.values();
for (ErrorType errorType : values) {
if (errorType.type.equals(type)) {
return errorType;
}
}
return null;
}
}

View File

@@ -0,0 +1,62 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.boot.error;
import org.springblade.core.log.model.LogError;
import org.springblade.core.tool.utils.DateUtil;
import org.springblade.core.tool.utils.Exceptions;
import org.springblade.core.tool.utils.ObjectUtil;
/**
* 异常工具类
*
* @author L.cm
*/
public class ErrorUtil {
/**
* 初始化异常信息
*
* @param error 异常
* @param event 异常事件封装
*/
public static void initErrorInfo(Throwable error, LogError event) {
// 堆栈信息
event.setStackTrace(Exceptions.getStackTraceAsString(error));
event.setExceptionName(error.getClass().getName());
event.setMessage(error.getMessage());
event.setCreateTime(DateUtil.now());
StackTraceElement[] elements = error.getStackTrace();
if (ObjectUtil.isNotEmpty(elements)) {
// 报错的类信息
StackTraceElement element = elements[0];
event.setMethodClass(element.getClassName());
event.setFileName(element.getFileName());
event.setMethodName(element.getMethodName());
event.setLineNumber(element.getLineNumber());
}
}
}

View File

@@ -0,0 +1,249 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.boot.file;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.web.multipart.MultipartFile;
import java.util.*;
/**
* 文件工具类
*
* @author Chill
*/
public class BladeFileUtil {
/**
* 定义允许上传的文件扩展名
*/
private static final HashMap<String, String> EXT_MAP = new HashMap<>();
private static final String IS_DIR = "is_dir";
private static final String FILE_NAME = "filename";
private static final String FILE_SIZE = "filesize";
/**
* 图片扩展名
*/
private static final String[] FILE_TYPES = new String[]{"gif", "jpg", "jpeg", "png", "bmp"};
static {
EXT_MAP.put("image", ".gif,.jpg,.jpeg,.png,.bmp,.JPG,.JPEG,.PNG");
EXT_MAP.put("flash", ".swf,.flv");
EXT_MAP.put("media", ".swf,.flv,.mp3,.mp4,.wav,.wma,.wmv,.mid,.avi,.mpg,.asf,.rm,.rmvb");
EXT_MAP.put("file", ".doc,.docx,.xls,.xlsx,.ppt,.htm,.html,.txt,.zip,.rar,.gz,.bz2");
EXT_MAP.put("allfile", ".gif,.jpg,.jpeg,.png,.bmp,.swf,.flv,.mp3,.mp4,.wav,.wma,.wmv,.mid,.avi,.mpg,.asf,.rm,.rmvb,.doc,.docx,.xls,.xlsx,.ppt,.htm,.html,.txt,.zip,.rar,.gz,.bz2");
}
/**
* 获取文件后缀
*
* @param fileName 文件名
* @return String 返回后缀
*/
public static String getFileExt(String fileName) {
return fileName.substring(fileName.lastIndexOf(StringPool.DOT));
}
/**
* 测试文件后缀 只让指定后缀的文件上传像jsp,war,sh等危险的后缀禁止
*
* @param dir 目录
* @param fileName 文件名
* @return 返回成功与否
*/
public static boolean testExt(String dir, String fileName) {
String fileExt = getFileExt(fileName);
String ext = EXT_MAP.get(dir);
return StringUtil.isNotBlank(ext) && (ext.contains(fileExt.toLowerCase()) || ext.contains(fileExt.toUpperCase()));
}
/**
* 文件管理排序
*/
public enum FileSort {
/**
* 大小
*/
size,
/**
* 类型
*/
type,
/**
* 名称
*/
name;
/**
* 文本排序转换成枚举
*
* @param sort
* @return
*/
public static FileSort of(String sort) {
try {
return FileSort.valueOf(sort);
} catch (Exception e) {
return FileSort.name;
}
}
}
public static class NameComparator implements Comparator {
@Override
public int compare(Object a, Object b) {
Hashtable hashA = (Hashtable) a;
Hashtable hashB = (Hashtable) b;
if (((Boolean) hashA.get(IS_DIR)) && !((Boolean) hashB.get(IS_DIR))) {
return -1;
} else if (!((Boolean) hashA.get(IS_DIR)) && ((Boolean) hashB.get(IS_DIR))) {
return 1;
} else {
return ((String) hashA.get(FILE_NAME)).compareTo((String) hashB.get(FILE_NAME));
}
}
}
public static class SizeComparator implements Comparator {
@Override
public int compare(Object a, Object b) {
Hashtable hashA = (Hashtable) a;
Hashtable hashB = (Hashtable) b;
if (((Boolean) hashA.get(IS_DIR)) && !((Boolean) hashB.get(IS_DIR))) {
return -1;
} else if (!((Boolean) hashA.get(IS_DIR)) && ((Boolean) hashB.get(IS_DIR))) {
return 1;
} else {
if (((Long) hashA.get(FILE_SIZE)) > ((Long) hashB.get(FILE_SIZE))) {
return 1;
} else if (((Long) hashA.get(FILE_SIZE)) < ((Long) hashB.get(FILE_SIZE))) {
return -1;
} else {
return 0;
}
}
}
}
public static class TypeComparator implements Comparator {
@Override
public int compare(Object a, Object b) {
Hashtable hashA = (Hashtable) a;
Hashtable hashB = (Hashtable) b;
if (((Boolean) hashA.get(IS_DIR)) && !((Boolean) hashB.get(IS_DIR))) {
return -1;
} else if (!((Boolean) hashA.get(IS_DIR)) && ((Boolean) hashB.get(IS_DIR))) {
return 1;
} else {
return ((String) hashA.get("filetype")).compareTo((String) hashB.get("filetype"));
}
}
}
public static String formatUrl(String url) {
return url.replaceAll("\\\\", "/");
}
/********************************BladeFile封装********************************************************/
/**
* 获取BladeFile封装类
*
* @param file 文件
* @return BladeFile
*/
public static LocalFile getFile(MultipartFile file) {
return getFile(file, "image", null, null);
}
/**
* 获取BladeFile封装类
*
* @param file 文件
* @param dir 目录
* @return BladeFile
*/
public static LocalFile getFile(MultipartFile file, String dir) {
return getFile(file, dir, null, null);
}
/**
* 获取BladeFile封装类
*
* @param file 文件
* @param dir 目录
* @param path 路径
* @param virtualPath 虚拟路径
* @return BladeFile
*/
public static LocalFile getFile(MultipartFile file, String dir, String path, String virtualPath) {
return new LocalFile(file, dir, path, virtualPath);
}
/**
* 获取BladeFile封装类
*
* @param files 文件集合
* @return BladeFile
*/
public static List<LocalFile> getFiles(List<MultipartFile> files) {
return getFiles(files, "image", null, null);
}
/**
* 获取BladeFile封装类
*
* @param files 文件集合
* @param dir 目录
* @return BladeFile
*/
public static List<LocalFile> getFiles(List<MultipartFile> files, String dir) {
return getFiles(files, dir, null, null);
}
/**
* 获取BladeFile封装类
*
* @param files 文件集合
* @param path 路径
* @param virtualPath 虚拟路径
* @return BladeFile
*/
public static List<LocalFile> getFiles(List<MultipartFile> files, String dir, String path, String virtualPath) {
List<LocalFile> list = new ArrayList<>();
for (MultipartFile file : files) {
list.add(new LocalFile(file, dir, path, virtualPath));
}
return list;
}
}

View File

@@ -0,0 +1,60 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.boot.file;
import java.io.File;
/**
* 文件管理类
*
* @author Chill
*/
public class FileProxyManager {
private IFileProxy defaultFileProxyFactory = new LocalFileProxyFactory();
private static final FileProxyManager ME = new FileProxyManager();
public static FileProxyManager me() {
return ME;
}
public IFileProxy getDefaultFileProxyFactory() {
return defaultFileProxyFactory;
}
public void setDefaultFileProxyFactory(IFileProxy defaultFileProxyFactory) {
this.defaultFileProxyFactory = defaultFileProxyFactory;
}
public String[] path(File file, String dir) {
return defaultFileProxyFactory.path(file, dir);
}
public File rename(File file, String path) {
return defaultFileProxyFactory.rename(file, path);
}
}

View File

@@ -0,0 +1,62 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.boot.file;
import java.io.File;
/**
* 文件代理接口
*
* @author Chill
*/
public interface IFileProxy {
/**
* 返回路径[物理路径][虚拟路径]
*
* @param file 文件
* @param dir 目录
* @return
*/
String[] path(File file, String dir);
/**
* 文件重命名策略
*
* @param file 文件
* @param path 路径
* @return
*/
File rename(File file, String path);
/**
* 图片压缩
*
* @param path 路径
*/
void compress(String path);
}

View File

@@ -0,0 +1,166 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.boot.file;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.springblade.core.boot.props.BladeFileProperties;
import org.springblade.core.tool.utils.DateUtil;
import org.springblade.core.tool.utils.SpringUtil;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
/**
* 上传文件封装
*
* @author Chill
*/
@Data
public class LocalFile {
/**
* 上传文件在附件表中的id
*/
private Object fileId;
/**
* 上传文件
*/
@JsonIgnore
private MultipartFile file;
/**
* 文件外网地址
*/
private String domain;
/**
* 上传分类文件夹
*/
private String dir;
/**
* 上传物理路径
*/
private String uploadPath;
/**
* 上传虚拟路径
*/
private String uploadVirtualPath;
/**
* 文件名
*/
private String fileName;
/**
* 真实文件名
*/
private String originalFileName;
/**
* 文件配置
*/
private static BladeFileProperties fileProperties;
private static BladeFileProperties getBladeFileProperties() {
if (fileProperties == null) {
fileProperties = SpringUtil.getBean(BladeFileProperties.class);
}
return fileProperties;
}
public LocalFile(MultipartFile file, String dir) {
this.dir = dir;
this.file = file;
this.fileName = file.getName();
this.originalFileName = file.getOriginalFilename();
this.domain = getBladeFileProperties().getUploadDomain();
this.uploadPath = BladeFileUtil.formatUrl(File.separator + getBladeFileProperties().getUploadRealPath() + File.separator + dir + File.separator + DateUtil.format(DateUtil.now(), "yyyyMMdd") + File.separator + this.originalFileName);
this.uploadVirtualPath = BladeFileUtil.formatUrl(getBladeFileProperties().getUploadCtxPath().replace(getBladeFileProperties().getContextPath(), "") + File.separator + dir + File.separator + DateUtil.format(DateUtil.now(), "yyyyMMdd") + File.separator + this.originalFileName);
}
public LocalFile(MultipartFile file, String dir, String uploadPath, String uploadVirtualPath) {
this(file, dir);
if (null != uploadPath) {
this.uploadPath = BladeFileUtil.formatUrl(uploadPath);
this.uploadVirtualPath = BladeFileUtil.formatUrl(uploadVirtualPath);
}
}
/**
* 图片上传
*/
public void transfer() {
transfer(getBladeFileProperties().getCompress());
}
/**
* 图片上传
*
* @param compress 是否压缩
*/
public void transfer(boolean compress) {
IFileProxy fileFactory = FileProxyManager.me().getDefaultFileProxyFactory();
this.transfer(fileFactory, compress);
}
/**
* 图片上传
*
* @param fileFactory 文件上传工厂类
* @param compress 是否压缩
*/
public void transfer(IFileProxy fileFactory, boolean compress) {
try {
File file = new File(uploadPath);
if (null != fileFactory) {
String[] path = fileFactory.path(file, dir);
this.uploadPath = path[0];
this.uploadVirtualPath = path[1];
file = fileFactory.rename(file, path[0]);
}
File pfile = file.getParentFile();
if (!pfile.exists()) {
pfile.mkdirs();
}
this.file.transferTo(file);
if (compress) {
fileFactory.compress(this.uploadPath);
}
} catch (IllegalStateException | IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,126 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.boot.file;
import org.springblade.core.boot.props.BladeFileProperties;
import org.springblade.core.tool.utils.DateUtil;
import org.springblade.core.tool.utils.ImageUtil;
import org.springblade.core.tool.utils.SpringUtil;
import org.springblade.core.tool.utils.StringPool;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
/**
* 文件代理类
*
* @author Chill
*/
public class LocalFileProxyFactory implements IFileProxy {
/**
* 文件配置
*/
private static BladeFileProperties fileProperties;
private static BladeFileProperties getBladeFileProperties() {
if (fileProperties == null) {
fileProperties = SpringUtil.getBean(BladeFileProperties.class);
}
return fileProperties;
}
@Override
public File rename(File f, String path) {
File dest = new File(path);
f.renameTo(dest);
return dest;
}
@Override
public String[] path(File f, String dir) {
//避免网络延迟导致时间不同步
long time = System.nanoTime();
StringBuilder uploadPath = new StringBuilder()
.append(getFileDir(dir, getBladeFileProperties().getUploadRealPath()))
.append(time)
.append(getFileExt(f.getName()));
StringBuilder virtualPath = new StringBuilder()
.append(getFileDir(dir, getBladeFileProperties().getUploadCtxPath()))
.append(time)
.append(getFileExt(f.getName()));
return new String[]{BladeFileUtil.formatUrl(uploadPath.toString()), BladeFileUtil.formatUrl(virtualPath.toString())};
}
/**
* 获取文件后缀
*
* @param fileName 文件名
* @return 文件后缀
*/
public static String getFileExt(String fileName) {
if (!fileName.contains(StringPool.DOT)) {
return ".jpg";
} else {
return fileName.substring(fileName.lastIndexOf(StringPool.DOT));
}
}
/**
* 获取文件保存地址
*
* @param dir 目录
* @param saveDir 保存目录
* @return 地址
*/
public static String getFileDir(String dir, String saveDir) {
StringBuilder newFileDir = new StringBuilder();
newFileDir.append(saveDir)
.append(File.separator).append(dir).append(File.separator).append(DateUtil.format(DateUtil.now(), "yyyyMMdd"))
.append(File.separator);
return newFileDir.toString();
}
/**
* 图片压缩
*
* @param path 文件地址
*/
@Override
public void compress(String path) {
try {
ImageUtil.zoomScale(ImageUtil.readImage(path), new FileOutputStream(new File(path)), null, getBladeFileProperties().getCompressScale(), getBladeFileProperties().getCompressFlag());
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,101 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.boot.props;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* BladeFileProperties
*
* @author Chill
*/
@Getter
@Setter
@ConfigurationProperties("blade.file")
public class BladeFileProperties {
/**
* 远程上传模式
*/
private boolean remoteMode = false;
/**
* 外网地址
*/
private String uploadDomain = "http://127.0.0.1:8999";
/**
* 上传下载路径(物理路径)
*/
private String remotePath = System.getProperty("user.dir") + "/target/blade";
/**
* 上传路径(相对路径)
*/
private String uploadPath = "/upload";
/**
* 下载路径
*/
private String downloadPath = "/download";
/**
* 图片压缩
*/
private Boolean compress = false;
/**
* 图片压缩比例
*/
private Double compressScale = 2.00;
/**
* 图片缩放选择:true放大;false缩小
*/
private Boolean compressFlag = false;
/**
* 项目物理路径
*/
private String realPath = System.getProperty("user.dir");
/**
* 项目相对路径
*/
private String contextPath = "/";
public String getUploadRealPath() {
return (remoteMode ? remotePath : realPath) + uploadPath;
}
public String getUploadCtxPath() {
return contextPath + uploadPath;
}
}

View File

@@ -0,0 +1,52 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.boot.props;
import lombok.Getter;
import lombok.Setter;
import org.springblade.core.tool.utils.PathUtil;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.lang.Nullable;
/**
* 文件上传配置
*
* @author Chill
*/
@Getter
@Setter
@RefreshScope
@ConfigurationProperties("blade.upload")
public class BladeUploadProperties {
/**
* 文件保存目录默认jar 包同级目录
*/
@Nullable
private String savePath = PathUtil.getJarPath();
}

View File

@@ -0,0 +1,129 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.boot.request;
import org.springblade.core.tool.utils.WebUtil;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* 全局Request包装
*
* @author Chill
*/
public class BladeHttpServletRequestWrapper extends HttpServletRequestWrapper {
/**
* 没被包装过的HttpServletRequest特殊场景,需要自己过滤)
*/
private final HttpServletRequest orgRequest;
/**
* 缓存报文,支持多次读取流
*/
private byte[] body;
public BladeHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
orgRequest = request;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
if (super.getHeader(HttpHeaders.CONTENT_TYPE) == null) {
return super.getInputStream();
}
if (super.getHeader(HttpHeaders.CONTENT_TYPE).startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)) {
return super.getInputStream();
}
if (body == null) {
body = WebUtil.getRequestBody(super.getInputStream()).getBytes();
}
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() {
return byteArrayInputStream.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
/**
* 获取初始request
*
* @return HttpServletRequest
*/
public HttpServletRequest getOrgRequest() {
return orgRequest;
}
/**
* 获取初始request
*
* @param request request
* @return HttpServletRequest
*/
public static HttpServletRequest getOrgRequest(HttpServletRequest request) {
if (request instanceof BladeHttpServletRequestWrapper) {
return ((BladeHttpServletRequestWrapper) request).getOrgRequest();
}
return request;
}
}

View File

@@ -0,0 +1,84 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.boot.request;
import lombok.AllArgsConstructor;
import org.springframework.util.AntPathMatcher;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* Request全局过滤
*
* @author Chill
*/
@AllArgsConstructor
public class BladeRequestFilter implements Filter {
private final RequestProperties requestProperties;
private final XssProperties xssProperties;
private final AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public void init(FilterConfig config) {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String path = ((HttpServletRequest) request).getServletPath();
// 跳过 Request 包装
if (!requestProperties.getEnabled() || isRequestSkip(path)) {
chain.doFilter(request, response);
}
// 默认 Request 包装
else if (!xssProperties.getEnabled() || isXssSkip(path)) {
BladeHttpServletRequestWrapper bladeRequest = new BladeHttpServletRequestWrapper((HttpServletRequest) request);
chain.doFilter(bladeRequest, response);
}
// Xss Request 包装
else {
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);
chain.doFilter(xssRequest, response);
}
}
private boolean isRequestSkip(String path) {
return requestProperties.getSkipUrl().stream().anyMatch(pattern -> antPathMatcher.match(pattern, path));
}
private boolean isXssSkip(String path) {
return xssProperties.getSkipUrl().stream().anyMatch(pattern -> antPathMatcher.match(pattern, path));
}
@Override
public void destroy() {
}
}

View File

@@ -0,0 +1,53 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.boot.request;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.ArrayList;
import java.util.List;
/**
* Request配置类
*
* @author Chill
*/
@Data
@ConfigurationProperties("blade.request")
public class RequestProperties {
/**
* 开启自定义request
*/
private Boolean enabled = true;
/**
* 放行url
*/
private List<String> skipUrl = new ArrayList<>();
}

View File

@@ -0,0 +1,536 @@
package org.springblade.core.boot.request;
import org.springblade.core.tool.utils.StringPool;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* HTML filtering utility for protecting against XSS (Cross Site Scripting).
* <p>
* This code is licensed LGPLv3
* <p>
* This code is a Java port of the original work in PHP by Cal Hendersen.
* http://code.iamcal.com/php/lib_filter/
* <p>
* The trickiest part of the translation was handling the differences in regex handling
* between PHP and Java. These resources were helpful in the process:
* <p>
* http://java.sun.com/j2se/1.4.2/docs/api/java/util/regex/Pattern.html
* http://us2.php.net/manual/en/reference.pcre.pattern.modifiers.php
* http://www.regular-expressions.info/modifiers.html
* <p>
* A note on naming conventions: instance variables are prefixed with a "v"; global
* constants are in all caps.
* <p>
* Sample use:
* String input = ...
* String clean = new HtmlFilter().filter( input );
* <p>
* The class is not thread safe. Create a new instance if in doubt.
* <p>
* If you find bugs or have suggestions on improvement (especially regarding
* performance), please contact us. The latest version of this
* source, and our contact details, can be found at http://xss-html-filter.sf.net
*
* @author Joseph O'Connell
* @author Cal Hendersen
* @author Michael Semb Wever
*/
public final class XssHtmlFilter {
/**
* regex flag union representing /si modifiers in php
**/
private static final int REGEX_FLAGS_SI = Pattern.CASE_INSENSITIVE | Pattern.DOTALL;
private static final Pattern P_COMMENTS = Pattern.compile("<!--(.*?)-->", Pattern.DOTALL);
private static final Pattern P_COMMENT = Pattern.compile("^!--(.*)--$", REGEX_FLAGS_SI);
private static final Pattern P_TAGS = Pattern.compile("<(.*?)>", Pattern.DOTALL);
private static final Pattern P_END_TAG = Pattern.compile("^/([a-z0-9]+)", REGEX_FLAGS_SI);
private static final Pattern P_START_TAG = Pattern.compile("^([a-z0-9]+)(.*?)(/?)$", REGEX_FLAGS_SI);
private static final Pattern P_QUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)=([\"'])(.*?)\\2", REGEX_FLAGS_SI);
private static final Pattern P_UNQUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)(=)([^\"\\s']+)", REGEX_FLAGS_SI);
private static final Pattern P_PROTOCOL = Pattern.compile("^([^:]+):", REGEX_FLAGS_SI);
private static final Pattern P_ENTITY = Pattern.compile("&#(\\d+);?");
private static final Pattern P_ENTITY_UNICODE = Pattern.compile("&#x([0-9a-f]+);?");
private static final Pattern P_ENCODE = Pattern.compile("%([0-9a-f]{2});?");
private static final Pattern P_VALID_ENTITIES = Pattern.compile("&([^&;]*)(?=(;|&|$))");
private static final Pattern P_VALID_QUOTES = Pattern.compile("(>|^)([^<]+?)(<|$)", Pattern.DOTALL);
private static final Pattern P_END_ARROW = Pattern.compile("^>");
private static final Pattern P_BODY_TO_END = Pattern.compile("<([^>]*?)(?=<|$)");
private static final Pattern P_XML_CONTENT = Pattern.compile("(^|>)([^<]*?)(?=>)");
private static final Pattern P_STRAY_LEFT_ARROW = Pattern.compile("<([^>]*?)(?=<|$)");
private static final Pattern P_STRAY_RIGHT_ARROW = Pattern.compile("(^|>)([^<]*?)(?=>)");
private static final Pattern P_AMP = Pattern.compile("&");
private static final Pattern P_QUOTE = Pattern.compile("");
private static final Pattern P_LEFT_ARROW = Pattern.compile("<");
private static final Pattern P_RIGHT_ARROW = Pattern.compile(">");
private static final Pattern P_BOTH_ARROWS = Pattern.compile("<>");
private static final ConcurrentMap<String, Pattern> P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap<String, Pattern>();
private static final ConcurrentMap<String, Pattern> P_REMOVE_SELF_BLANKS = new ConcurrentHashMap<String, Pattern>();
/**
* set of allowed html elements, along with allowed attributes for each element
**/
private final Map<String, List<String>> vAllowed;
/**
* counts of open tags for each (allowable) html element
**/
private final Map<String, Integer> vTagCounts = new HashMap<String, Integer>();
/**
* html elements which must always be self-closing (e.g. "<img />")
**/
private final String[] vSelfClosingTags;
/**
* html elements which must always have separate opening and closing tags (e.g. "<b></b>")
**/
private final String[] vNeedClosingTags;
/**
* set of disallowed html elements
**/
private final String[] vDisallowed;
/**
* attributes which should be checked for valid protocols
**/
private final String[] vProtocolAtts;
/**
* allowed protocols
**/
private final String[] vAllowedProtocols;
/**
* tags which should be removed if they contain no content (e.g. "<b></b>" or "<b />")
**/
private final String[] vRemoveBlanks;
/**
* entities allowed within html markup
**/
private final String[] vAllowedEntities;
/**
* flag determining whether comments are allowed in input String.
*/
private final boolean stripComment;
private final boolean encodeQuotes;
private boolean vDebug = false;
/**
* flag determining whether to try to make tags when presented with "unbalanced"
* angle brackets (e.g. "<b text </b>" becomes "<b> text </b>"). If set to false,
* unbalanced angle brackets will be html escaped.
*/
private final boolean alwaysMakeTags;
/**
* Default constructor.
*/
public XssHtmlFilter() {
vAllowed = new HashMap<>();
final ArrayList<String> aAtts = new ArrayList<String>();
aAtts.add("href");
aAtts.add("target");
vAllowed.put("a", aAtts);
final ArrayList<String> imgAtts = new ArrayList<String>();
imgAtts.add("src");
imgAtts.add("width");
imgAtts.add("height");
imgAtts.add("alt");
vAllowed.put("img", imgAtts);
final ArrayList<String> noAtts = new ArrayList<String>();
vAllowed.put("b", noAtts);
vAllowed.put("strong", noAtts);
vAllowed.put("i", noAtts);
vAllowed.put("em", noAtts);
vSelfClosingTags = new String[]{"img"};
vNeedClosingTags = new String[]{"a", "b", "strong", "i", "em"};
vDisallowed = new String[]{};
vAllowedProtocols = new String[]{"http", "mailto", "https"};
vProtocolAtts = new String[]{"src", "href"};
vRemoveBlanks = new String[]{"a", "b", "strong", "i", "em"};
vAllowedEntities = new String[]{"amp", "gt", "lt", "quot"};
stripComment = true;
encodeQuotes = true;
alwaysMakeTags = false;
}
/**
* Set debug flag to true. Otherwise use default settings. See the default constructor.
*
* @param debug turn debug on with a true argument
*/
public XssHtmlFilter(final boolean debug) {
this();
vDebug = debug;
}
/**
* Map-parameter configurable constructor.
*
* @param conf map containing configuration. keys match field names.
*/
public XssHtmlFilter(final Map<String, Object> conf) {
assert conf.containsKey("vAllowed") : "configuration requires vAllowed";
assert conf.containsKey("vSelfClosingTags") : "configuration requires vSelfClosingTags";
assert conf.containsKey("vNeedClosingTags") : "configuration requires vNeedClosingTags";
assert conf.containsKey("vDisallowed") : "configuration requires vDisallowed";
assert conf.containsKey("vAllowedProtocols") : "configuration requires vAllowedProtocols";
assert conf.containsKey("vProtocolAtts") : "configuration requires vProtocolAtts";
assert conf.containsKey("vRemoveBlanks") : "configuration requires vRemoveBlanks";
assert conf.containsKey("vAllowedEntities") : "configuration requires vAllowedEntities";
vAllowed = Collections.unmodifiableMap((HashMap<String, List<String>>) conf.get("vAllowed"));
vSelfClosingTags = (String[]) conf.get("vSelfClosingTags");
vNeedClosingTags = (String[]) conf.get("vNeedClosingTags");
vDisallowed = (String[]) conf.get("vDisallowed");
vAllowedProtocols = (String[]) conf.get("vAllowedProtocols");
vProtocolAtts = (String[]) conf.get("vProtocolAtts");
vRemoveBlanks = (String[]) conf.get("vRemoveBlanks");
vAllowedEntities = (String[]) conf.get("vAllowedEntities");
stripComment = conf.containsKey("stripComment") ? (Boolean) conf.get("stripComment") : true;
encodeQuotes = conf.containsKey("encodeQuotes") ? (Boolean) conf.get("encodeQuotes") : true;
alwaysMakeTags = conf.containsKey("alwaysMakeTags") ? (Boolean) conf.get("alwaysMakeTags") : true;
}
private void reset() {
vTagCounts.clear();
}
private void debug(final String msg) {
if (vDebug) {
Logger.getAnonymousLogger().info(msg);
}
}
public static String chr(final int decimal) {
return String.valueOf((char) decimal);
}
public static String htmlSpecialChars(final String s) {
String result = s;
result = regexReplace(P_AMP, "&amp;", result);
result = regexReplace(P_QUOTE, "&quot;", result);
result = regexReplace(P_LEFT_ARROW, "&lt;", result);
result = regexReplace(P_RIGHT_ARROW, "&gt;", result);
return result;
}
//---------------------------------------------------------------
/**
* given a user submitted input String, filter out any invalid or restricted
* html.
*
* @param input text (i.e. submitted by a user) than may contain html
* @return "clean" version of input, with only valid, whitelisted html elements allowed
*/
public String filter(final String input) {
reset();
String s = input;
debug("************************************************");
debug(" INPUT: " + input);
s = escapeComments(s);
debug(" escapeComments: " + s);
s = balanceHtml(s);
debug(" balanceHtml: " + s);
s = checkTags(s);
debug(" checkTags: " + s);
s = processRemoveBlanks(s);
debug("processRemoveBlanks: " + s);
s = validateEntities(s);
debug(" validateEntites: " + s);
debug("************************************************\n\n");
return s;
}
public boolean isAlwaysMakeTags() {
return alwaysMakeTags;
}
public boolean isStripComments() {
return stripComment;
}
private String escapeComments(final String s) {
final Matcher m = P_COMMENTS.matcher(s);
final StringBuffer buf = new StringBuffer();
if (m.find()) {
final String match = m.group(1);
m.appendReplacement(buf, Matcher.quoteReplacement("<!--" + htmlSpecialChars(match) + "-->"));
}
m.appendTail(buf);
return buf.toString();
}
private String balanceHtml(String s) {
if (alwaysMakeTags) {
//
// try and form html
//
s = regexReplace(P_END_ARROW, "", s);
s = regexReplace(P_BODY_TO_END, "<$1>", s);
s = regexReplace(P_XML_CONTENT, "$1<$2", s);
} else {
//
// escape stray brackets
//
s = regexReplace(P_STRAY_LEFT_ARROW, "&lt;$1", s);
s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2&gt;<", s);
//
// the last regexp causes '<>' entities to appear
// (we need to do a lookahead assertion so that the last bracket can
// be used in the next pass of the regexp)
//
s = regexReplace(P_BOTH_ARROWS, "", s);
}
return s;
}
private String checkTags(String s) {
Matcher m = P_TAGS.matcher(s);
final StringBuffer buf = new StringBuffer();
while (m.find()) {
String replaceStr = m.group(1);
replaceStr = processTag(replaceStr);
m.appendReplacement(buf, Matcher.quoteReplacement(replaceStr));
}
m.appendTail(buf);
s = buf.toString();
// these get tallied in processTag
// (remember to reset before subsequent calls to filter method)
for (String key : vTagCounts.keySet()) {
for (int ii = 0; ii < vTagCounts.get(key); ii++) {
s += "</" + key + ">";
}
}
return s;
}
private String processRemoveBlanks(final String s) {
String result = s;
for (String tag : vRemoveBlanks) {
if (!P_REMOVE_PAIR_BLANKS.containsKey(tag)) {
P_REMOVE_PAIR_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?></" + tag + ">"));
}
result = regexReplace(P_REMOVE_PAIR_BLANKS.get(tag), "", result);
if (!P_REMOVE_SELF_BLANKS.containsKey(tag)) {
P_REMOVE_SELF_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?/>"));
}
result = regexReplace(P_REMOVE_SELF_BLANKS.get(tag), "", result);
}
return result;
}
private static String regexReplace(final Pattern regexPattern, final String replacement, final String s) {
Matcher m = regexPattern.matcher(s);
return m.replaceAll(replacement);
}
private String processTag(final String s) {
Matcher m = P_END_TAG.matcher(s);
if (m.find()) {
final String name = m.group(1).toLowerCase();
if (allowed(name)) {
if (!inArray(name, vSelfClosingTags)) {
if (vTagCounts.containsKey(name)) {
vTagCounts.put(name, vTagCounts.get(name) - 1);
return "</" + name + ">";
}
}
}
}
m = P_START_TAG.matcher(s);
if (m.find()) {
final String name = m.group(1).toLowerCase();
final String body = m.group(2);
String ending = m.group(3);
if (allowed(name)) {
String params = "";
final Matcher m2 = P_QUOTED_ATTRIBUTES.matcher(body);
final Matcher m3 = P_UNQUOTED_ATTRIBUTES.matcher(body);
final List<String> paramNames = new ArrayList<String>();
final List<String> paramValues = new ArrayList<String>();
while (m2.find()) {
paramNames.add(m2.group(1));
paramValues.add(m2.group(3));
}
while (m3.find()) {
paramNames.add(m3.group(1));
paramValues.add(m3.group(3));
}
String paramName, paramValue;
for (int ii = 0; ii < paramNames.size(); ii++) {
paramName = paramNames.get(ii).toLowerCase();
paramValue = paramValues.get(ii);
if (allowedAttribute(name, paramName)) {
if (inArray(paramName, vProtocolAtts)) {
paramValue = processParamProtocol(paramValue);
}
params += " " + paramName + "=\"" + paramValue + "\"";
}
}
if (inArray(name, vSelfClosingTags)) {
ending = " /";
}
if (inArray(name, vNeedClosingTags)) {
ending = "";
}
if (ending == null || ending.length() < 1) {
if (vTagCounts.containsKey(name)) {
vTagCounts.put(name, vTagCounts.get(name) + 1);
} else {
vTagCounts.put(name, 1);
}
} else {
ending = " /";
}
return "<" + name + params + ending + ">";
} else {
return "";
}
}
m = P_COMMENT.matcher(s);
if (!stripComment && m.find()) {
return "<" + m.group() + ">";
}
return "";
}
private String processParamProtocol(String s) {
s = decodeEntities(s);
final Matcher m = P_PROTOCOL.matcher(s);
if (m.find()) {
final String protocol = m.group(1);
if (!inArray(protocol, vAllowedProtocols)) {
// bad protocol, turn into local anchor link instead
s = "#" + s.substring(protocol.length() + 1);
if (s.startsWith(StringPool.DOUBLE_SLASH)) {
s = "#" + s.substring(3);
}
}
}
return s;
}
private String decodeEntities(String s) {
StringBuffer buf = new StringBuffer();
Matcher m = P_ENTITY.matcher(s);
while (m.find()) {
final String match = m.group(1);
final int decimal = Integer.decode(match);
m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
}
m.appendTail(buf);
s = buf.toString();
buf = new StringBuffer();
m = P_ENTITY_UNICODE.matcher(s);
while (m.find()) {
final String match = m.group(1);
final int decimal = Integer.valueOf(match, 16).intValue();
m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
}
m.appendTail(buf);
s = buf.toString();
buf = new StringBuffer();
m = P_ENCODE.matcher(s);
while (m.find()) {
final String match = m.group(1);
final int decimal = Integer.valueOf(match, 16).intValue();
m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
}
m.appendTail(buf);
s = buf.toString();
s = validateEntities(s);
return s;
}
private String validateEntities(final String s) {
StringBuffer buf = new StringBuffer();
// validate entities throughout the string
Matcher m = P_VALID_ENTITIES.matcher(s);
while (m.find()) {
final String one = m.group(1);
final String two = m.group(2);
m.appendReplacement(buf, Matcher.quoteReplacement(checkEntity(one, two)));
}
m.appendTail(buf);
return encodeQuotes(buf.toString());
}
private String encodeQuotes(final String s) {
if (encodeQuotes) {
StringBuffer buf = new StringBuffer();
Matcher m = P_VALID_QUOTES.matcher(s);
while (m.find()) {
final String one = m.group(1);
final String two = m.group(2);
final String three = m.group(3);
m.appendReplacement(buf, Matcher.quoteReplacement(one + regexReplace(P_QUOTE, "&quot;", two) + three));
}
m.appendTail(buf);
return buf.toString();
} else {
return s;
}
}
private String checkEntity(final String preamble, final String term) {
return ";".equals(term) && isValidEntity(preamble)
? '&' + preamble
: "&amp;" + preamble;
}
private boolean isValidEntity(final String entity) {
return inArray(entity, vAllowedEntities);
}
private static boolean inArray(final String s, final String[] array) {
for (String item : array) {
if (item != null && item.equals(s)) {
return true;
}
}
return false;
}
private boolean allowed(final String name) {
return (vAllowed.isEmpty() || vAllowed.containsKey(name)) && !inArray(name, vDisallowed);
}
private boolean allowedAttribute(final String name, final String paramName) {
return allowed(name) && (vAllowed.isEmpty() || vAllowed.get(name).contains(paramName));
}
}

View File

@@ -0,0 +1,184 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.boot.request;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.core.tool.utils.WebUtil;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* XSS过滤
*
* @author Chill
*/
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
/**
* 没被包装过的HttpServletRequest特殊场景,需要自己过滤)
*/
private final HttpServletRequest orgRequest;
/**
* 缓存报文,支持多次读取流
*/
private byte[] body;
/**
* html过滤
*/
private final static XssHtmlFilter HTML_FILTER = new XssHtmlFilter();
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
orgRequest = request;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
if (super.getHeader(HttpHeaders.CONTENT_TYPE) == null) {
return super.getInputStream();
}
if (super.getHeader(HttpHeaders.CONTENT_TYPE).startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)) {
return super.getInputStream();
}
if (body == null) {
body = xssEncode(WebUtil.getRequestBody(super.getInputStream())).getBytes();
}
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() {
return byteArrayInputStream.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
@Override
public String getParameter(String name) {
String value = super.getParameter(xssEncode(name));
if (StringUtil.isNotBlank(value)) {
value = xssEncode(value);
}
return value;
}
@Override
public String[] getParameterValues(String name) {
String[] parameters = super.getParameterValues(name);
if (parameters == null || parameters.length == 0) {
return null;
}
for (int i = 0; i < parameters.length; i++) {
parameters[i] = xssEncode(parameters[i]);
}
return parameters;
}
@Override
public Map<String, String[]> getParameterMap() {
Map<String, String[]> map = new LinkedHashMap<>();
Map<String, String[]> parameters = super.getParameterMap();
for (String key : parameters.keySet()) {
String[] values = parameters.get(key);
for (int i = 0; i < values.length; i++) {
values[i] = xssEncode(values[i]);
}
map.put(key, values);
}
return map;
}
@Override
public String getHeader(String name) {
String value = super.getHeader(xssEncode(name));
if (StringUtil.isNotBlank(value)) {
value = xssEncode(value);
}
return value;
}
private String xssEncode(String input) {
return HTML_FILTER.filter(input);
}
/**
* 获取初始request
*
* @return HttpServletRequest
*/
public HttpServletRequest getOrgRequest() {
return orgRequest;
}
/**
* 获取初始request
*
* @param request request
* @return HttpServletRequest
*/
public static HttpServletRequest getOrgRequest(HttpServletRequest request) {
if (request instanceof XssHttpServletRequestWrapper) {
return ((XssHttpServletRequestWrapper) request).getOrgRequest();
}
return request;
}
}

View File

@@ -0,0 +1,53 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.boot.request;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.ArrayList;
import java.util.List;
/**
* Xss配置类
*
* @author Chill
*/
@Data
@ConfigurationProperties("blade.xss")
public class XssProperties {
/**
* 开启xss
*/
private Boolean enabled = true;
/**
* 放行url
*/
private List<String> skipUrl = new ArrayList<>();
}

View File

@@ -0,0 +1,73 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.boot.resolver;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.secure.BladeUser;
import org.springblade.core.secure.utils.AuthUtil;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* Token转化BladeUser
*
* @author Chill
*/
@Slf4j
public class TokenArgumentResolver implements HandlerMethodArgumentResolver {
/**
* 入参筛选
*
* @param methodParameter 参数集合
* @return 格式化后的参数
*/
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterType().equals(BladeUser.class);
}
/**
* 出参设置
*
* @param methodParameter 入参集合
* @param modelAndViewContainer model 和 view
* @param nativeWebRequest web相关
* @param webDataBinderFactory 入参解析
* @return 包装对象
*/
@Override
public Object resolveArgument(MethodParameter methodParameter,
ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory) {
return AuthUtil.getUser();
}
}

View File

@@ -0,0 +1,8 @@
${AnsiColor.BLUE} ______ _ _ ___ ___
${AnsiColor.BLUE} | ___ \| | | | \ \ / /
${AnsiColor.BLUE} | |_/ /| | __ _ __| | ___ \ V /
${AnsiColor.BLUE} | ___ \| | / _` | / _` | / _ \ > <
${AnsiColor.BLUE} | |_/ /| || (_| || (_| || __/ / . \
${AnsiColor.BLUE} \____/ |_| \__,_| \__,_| \___|/__/ \__\
${AnsiColor.BLUE}:: BladeX ${blade.service.version} :: ${spring.application.name}:${AnsiColor.RED}${blade.env}${AnsiColor.BLUE} :: Running SpringBoot ${spring-boot.version} :: ${AnsiColor.BRIGHT_BLACK}

View File

@@ -0,0 +1,35 @@
#服务器配置
server:
undertow:
# 线程配置
threads:
# 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程
io: 16
# 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载
worker: 400
# 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
buffer-size: 1024
# 是否分配的直接内存
direct-buffers: true
servlet:
# 编码配置
encoding:
charset: UTF-8
force: true
#spring配置
spring:
servlet:
multipart:
enabled: true
max-file-size: 1024MB
max-request-size: 1024MB
mvc:
throw-exception-if-no-handler-found: true
web:
resources:
add-mappings: false
devtools:
restart:
log-condition-evaluation-delta: false

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

54
blade-core-cloud/pom.xml Normal file
View File

@@ -0,0 +1,54 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>BladeX-Tool</artifactId>
<groupId>org.springblade</groupId>
<version>${revision}</version>
</parent>
<artifactId>blade-core-cloud</artifactId>
<name>${project.artifactId}</name>
<version>${project.parent.version}</version>
<packaging>jar</packaging>
<dependencies>
<!-- Blade -->
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-launch</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-context</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-auth</artifactId>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-loadbalancer</artifactId>
</dependency>
<!-- Admin -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
</dependency>
<!-- Sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- Auto -->
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-auto</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,48 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.annotation;
import java.lang.annotation.*;
/**
* header 版本 处理
*
* @author L.cm
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface ApiVersion {
/**
* header 路径中的版本
*
* @return 版本号
*/
String value() default "";
}

View File

@@ -0,0 +1,47 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.annotation;
import java.lang.annotation.*;
/**
* 注解用于生成 requestMappingInfo 时候直接拼接路径规则,自动放置于方法路径开始部分
*
* @author L.cm
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface UrlVersion {
/**
* url 路径中的版本
*
* @return 版本号
*/
String value() default "";
}

View File

@@ -0,0 +1,116 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.annotation;
import org.springframework.core.annotation.AliasFor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import java.lang.annotation.*;
/**
* 版本号处理
*
* <p>
* 1. url 版本号:添加到 url 前
* 2. Accept 版本application/vnd.blade.VERSION+json
* </p>
*
* @author L.cm
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping
@UrlVersion
@ApiVersion
@Validated
public @interface VersionMapping {
/**
* Alias for {@link RequestMapping#name}.
* @return {String[]}
*/
@AliasFor(annotation = RequestMapping.class)
String name() default "";
/**
* Alias for {@link RequestMapping#value}.
* @return {String[]}
*/
@AliasFor(annotation = RequestMapping.class)
String[] value() default {};
/**
* Alias for {@link RequestMapping#path}.
* @return {String[]}
*/
@AliasFor(annotation = RequestMapping.class)
String[] path() default {};
/**
* Alias for {@link RequestMapping#params}.
* @return {String[]}
*/
@AliasFor(annotation = RequestMapping.class)
String[] params() default {};
/**
* Alias for {@link RequestMapping#headers}.
* @return {String[]}
*/
@AliasFor(annotation = RequestMapping.class)
String[] headers() default {};
/**
* Alias for {@link RequestMapping#consumes}.
* @return {String[]}
*/
@AliasFor(annotation = RequestMapping.class)
String[] consumes() default {};
/**
* Alias for {@link RequestMapping#produces}.
* default json utf-8
* @return {String[]}
*/
@AliasFor(annotation = RequestMapping.class)
String[] produces() default {};
/**
* Alias for {@link UrlVersion#value}.
* @return {String}
*/
@AliasFor(annotation = UrlVersion.class, attribute = "value")
String urlVersion() default "";
/**
* Alias for {@link ApiVersion#value}.
* @return {String}
*/
@AliasFor(annotation = ApiVersion.class, attribute = "value")
String apiVersion() default "";
}

View File

@@ -0,0 +1,49 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.cloud.client;
import org.springblade.core.launch.constant.AppConstant;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import java.lang.annotation.*;
/**
* Cloud启动注解配置
*
* @author Chill
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@EnableDiscoveryClient
@EnableFeignClients(AppConstant.BASE_PACKAGES)
@SpringBootApplication
public @interface BladeCloudApplication {
}

View File

@@ -0,0 +1,54 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.feign;
import feign.Target;
import org.springframework.cloud.openfeign.FallbackFactory;
import lombok.AllArgsConstructor;
import org.springframework.cglib.proxy.Enhancer;
/**
* 默认 Fallback避免写过多fallback类
*
* @param <T> 泛型标记
* @author L.cm
*/
@AllArgsConstructor
public class BladeFallbackFactory<T> implements FallbackFactory<T> {
private final Target<T> target;
@Override
@SuppressWarnings("unchecked")
public T create(Throwable cause) {
final Class<T> targetType = target.type();
final String targetName = target.name();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetType);
enhancer.setUseCache(true);
enhancer.setCallback(new BladeFeignFallback<>(targetType, targetName, cause));
return (T) enhancer.create();
}
}

View File

@@ -0,0 +1,111 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.feign;
import com.fasterxml.jackson.databind.JsonNode;
import feign.FeignException;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.api.ResultCode;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springblade.core.tool.utils.ObjectUtil;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.lang.Nullable;
import java.lang.reflect.Method;
import java.util.*;
/**
* blade fallBack 代理处理
*
* @author L.cm
*/
@Slf4j
@AllArgsConstructor
public class BladeFeignFallback<T> implements MethodInterceptor {
private final Class<T> targetType;
private final String targetName;
private final Throwable cause;
private final static String CODE = "code";
@Nullable
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
String errorMessage = cause.getMessage();
log.error("BladeFeignFallback:[{}.{}] serviceId:[{}] message:[{}]", targetType.getName(), method.getName(), targetName, errorMessage);
Class<?> returnType = method.getReturnType();
// 集合类型反馈空集合
if (List.class == returnType || Collection.class == returnType) {
return Collections.emptyList();
}
if (Set.class == returnType) {
return Collections.emptySet();
}
if (Map.class == returnType) {
return Collections.emptyMap();
}
// 暂时不支持 fluxrx异步等返回值不是 R直接返回 null。
if (R.class != returnType) {
return null;
}
// 非 FeignException
if (!(cause instanceof FeignException)) {
return R.fail(ResultCode.INTERNAL_SERVER_ERROR, errorMessage);
}
FeignException exception = (FeignException) cause;
byte[] content = exception.content();
// 如果返回的数据为空
if (ObjectUtil.isEmpty(content)) {
return R.fail(ResultCode.INTERNAL_SERVER_ERROR, errorMessage);
}
// 转换成 jsonNode 读取,因为直接转换,可能 对方放回的并 不是 R 的格式。
JsonNode resultNode = JsonUtil.readTree(content);
// 判断是否 R 格式 返回体
if (resultNode.has(CODE)) {
return JsonUtil.getInstance().convertValue(resultNode, R.class);
}
return R.fail(resultNode.toString());
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
BladeFeignFallback<?> that = (BladeFeignFallback<?>) o;
return targetType.equals(that.targetType);
}
@Override
public int hashCode() {
return Objects.hash(targetType);
}
}

View File

@@ -0,0 +1,57 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.feign;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springblade.core.tool.constant.BladeConstant;
import org.springblade.core.tool.utils.ThreadLocalUtil;
import org.springframework.http.HttpHeaders;
/**
* feign 传递Request header
*
* <p>
* https://blog.csdn.net/u014519194/article/details/77160958
* http://tietang.wang/2016/02/25/hystrix/Hystrix%E5%8F%82%E6%95%B0%E8%AF%A6%E8%A7%A3/
* https://github.com/Netflix/Hystrix/issues/92#issuecomment-260548068
* </p>
*
* @author L.cm
*/
public class BladeFeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
HttpHeaders headers = ThreadLocalUtil.get(BladeConstant.CONTEXT_KEY);
if (headers != null && !headers.isEmpty()) {
headers.forEach((key, values) ->
values.forEach(value -> requestTemplate.header(key, value))
);
}
}
}

View File

@@ -0,0 +1,90 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.cloud.feign;
import org.springblade.core.launch.constant.AppConstant;
import org.springframework.cloud.openfeign.EnableFeignClients;
import java.lang.annotation.*;
/**
* 开启Feign注解
*
* @author Chill
*/
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@EnableFeignClients(AppConstant.BASE_PACKAGES)
public @interface EnableBladeFeign {
/**
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
* declarations e.g.: {@code @ComponentScan("org.my.pkg")} instead of
* {@code @ComponentScan(basePackages="org.my.pkg")}.
*
* @return the array of 'basePackages'.
*/
String[] value() default {};
/**
* Base packages to scan for annotated components.
* <p>
* {@link #value()} is an alias for (and mutually exclusive with) this attribute.
* <p>
* Use {@link #basePackageClasses()} for a type-safe alternative to String-based
* package names.
*
* @return the array of 'basePackages'.
*/
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages()} for specifying the packages to
* scan for annotated components. The package of each class specified will be scanned.
* <p>
* Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute.
*
* @return the array of 'basePackageClasses'.
*/
Class<?>[] basePackageClasses() default {};
/**
* A custom <code>@Configuration</code> for all feign clients. Can contain override
* <code>@Bean</code> definition for the pieces that make up the client, for instance
* {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
*/
Class<?>[] defaultConfiguration() default {};
/**
* List of classes annotated with @FeignClient. If not empty, disables classpath scanning.
*
* @return
*/
Class<?>[] clients() default {};
}

View File

@@ -0,0 +1,39 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.http;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
/**
* http 配置
*
* @author L.cm
*/
@AutoConfiguration
@EnableConfigurationProperties(BladeHttpProperties.class)
public class BladeHttpConfiguration {
}

View File

@@ -0,0 +1,74 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.http;
import lombok.Getter;
import lombok.Setter;
import org.springblade.core.launch.log.BladeLogLevel;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import java.util.concurrent.TimeUnit;
/**
* http 配置
*
* @author L.cm
*/
@Getter
@Setter
@RefreshScope
@ConfigurationProperties("blade.http")
public class BladeHttpProperties {
/**
* 最大连接数默认200
*/
private int maxConnections = 200;
/**
* 连接存活时间默认900L
*/
private long timeToLive = 900L;
/**
* 连接池存活时间单位,默认:秒
*/
private TimeUnit timeUnit = TimeUnit.SECONDS;
/**
* 链接超时默认2000毫秒
*/
private int connectionTimeout = 2000;
/**
* 是否支持重定向默认true
*/
private boolean followRedirects = true;
/**
* 关闭证书校验
*/
private boolean disableSslValidation = true;
/**
* 日志级别
*/
private BladeLogLevel level = BladeLogLevel.NONE;
}

View File

@@ -0,0 +1,27 @@
package org.springblade.core.cloud.http;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import java.util.List;
/**
* Loadbalancer RestTemplate
*
* @author L.cm
*/
public class LbRestTemplate extends RestTemplate {
public LbRestTemplate() {
super();
}
public LbRestTemplate(ClientHttpRequestFactory requestFactory) {
super(requestFactory);
}
public LbRestTemplate(List<HttpMessageConverter<?>> messageConverters) {
super(messageConverters);
}
}

View File

@@ -0,0 +1,200 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.http;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import org.springblade.core.cloud.http.client.OkHttp3ClientHttpRequestFactory;
import org.springblade.core.cloud.http.logger.HttpLoggingInterceptor;
import org.springblade.core.cloud.http.logger.OkHttpSlf4jLogger;
import org.springblade.core.tool.ssl.DisableValidationTrustManager;
import org.springblade.core.tool.ssl.TrustAllHostNames;
import org.springblade.core.tool.utils.Charsets;
import org.springblade.core.tool.utils.Holder;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Http RestTemplateHeaderInterceptor 配置
*
* @author L.cm
*/
@Slf4j
@RequiredArgsConstructor
@AutoConfiguration
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty(value = "blade.http.enabled", matchIfMissing = true)
public class RestTemplateConfiguration {
private final BladeHttpProperties properties;
/**
* okhttp3 请求日志拦截器
*
* @return HttpLoggingInterceptor
*/
@Bean
public HttpLoggingInterceptor loggingInterceptor() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(new OkHttpSlf4jLogger());
interceptor.setLevel(properties.getLevel());
return interceptor;
}
/**
* okhttp3 链接池配置
*
* @return okhttp3.ConnectionPool
*/
@Bean
@ConditionalOnMissingBean
public ConnectionPool httpClientConnectionPool() {
int maxTotalConnections = properties.getMaxConnections();
long timeToLive = properties.getTimeToLive();
TimeUnit ttlUnit = properties.getTimeUnit();
return new ConnectionPool(maxTotalConnections, timeToLive, ttlUnit);
}
/**
* 配置OkHttpClient
*
* @param connectionPool 链接池配置
* @param interceptor 拦截器
* @return OkHttpClient
*/
@Bean
@ConditionalOnMissingBean
public OkHttpClient okHttpClient(ConnectionPool connectionPool, HttpLoggingInterceptor interceptor) {
boolean followRedirects = properties.isFollowRedirects();
int connectTimeout = properties.getConnectionTimeout();
return this.createBuilder(properties.isDisableSslValidation())
.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.followRedirects(followRedirects)
.connectionPool(connectionPool)
.addInterceptor(interceptor)
.build();
}
private OkHttpClient.Builder createBuilder(boolean disableSslValidation) {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
if (disableSslValidation) {
try {
X509TrustManager disabledTrustManager = DisableValidationTrustManager.INSTANCE;
TrustManager[] trustManagers = new TrustManager[]{disabledTrustManager};
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustManagers, Holder.SECURE_RANDOM);
SSLSocketFactory disabledSslSocketFactory = sslContext.getSocketFactory();
builder.sslSocketFactory(disabledSslSocketFactory, disabledTrustManager);
builder.hostnameVerifier(TrustAllHostNames.INSTANCE);
} catch (NoSuchAlgorithmException | KeyManagementException e) {
log.warn("Error setting SSLSocketFactory in OKHttpClient", e);
}
}
return builder;
}
@Bean
public RestTemplateHeaderInterceptor requestHeaderInterceptor() {
return new RestTemplateHeaderInterceptor();
}
@AutoConfiguration
@RequiredArgsConstructor
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty(value = "blade.http.rest-template.enable")
public static class RestTemplateAutoConfiguration {
private final ApplicationContext context;
/**
* 普通的 RestTemplate不透传请求头一般只做外部 http 调用
*
* @param okHttpClient OkHttpClient
* @return RestTemplate
*/
@Bean
@ConditionalOnMissingBean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder, OkHttpClient okHttpClient) {
restTemplateBuilder.requestFactory(() -> new OkHttp3ClientHttpRequestFactory(okHttpClient));
RestTemplate restTemplate = restTemplateBuilder.build();
configMessageConverters(context, restTemplate.getMessageConverters());
return restTemplate;
}
}
@AutoConfiguration
@RequiredArgsConstructor
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty(value = "blade.http.lb-rest-template.enable")
public static class LbRestTemplateAutoConfiguration {
private final ApplicationContext context;
/**
* 支持负载均衡的 LbRestTemplate
*
* @param okHttpClient OkHttpClient
* @return LbRestTemplate
*/
@Bean
@LoadBalanced
@ConditionalOnMissingBean
public LbRestTemplate lbRestTemplate(RestTemplateBuilder restTemplateBuilder, OkHttpClient okHttpClient) {
restTemplateBuilder.requestFactory(() -> new OkHttp3ClientHttpRequestFactory(okHttpClient));
LbRestTemplate restTemplate = restTemplateBuilder.build(LbRestTemplate.class);
restTemplate.getInterceptors().add(context.getBean(RestTemplateHeaderInterceptor.class));
configMessageConverters(context, restTemplate.getMessageConverters());
return restTemplate;
}
}
private static void configMessageConverters(ApplicationContext context, List<HttpMessageConverter<?>> converters) {
converters.removeIf(x -> x instanceof StringHttpMessageConverter || x instanceof MappingJackson2HttpMessageConverter);
converters.add(new StringHttpMessageConverter(Charsets.UTF_8));
converters.add(new MappingJackson2HttpMessageConverter(context.getBean(ObjectMapper.class)));
}
}

View File

@@ -0,0 +1,57 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.http;
import lombok.AllArgsConstructor;
import org.springblade.core.tool.constant.BladeConstant;
import org.springblade.core.tool.utils.ThreadLocalUtil;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.lang.NonNull;
import java.io.IOException;
/**
* RestTemplateHeaderInterceptor 传递Request header
*
* @author L.cm
*/
@AllArgsConstructor
public class RestTemplateHeaderInterceptor implements ClientHttpRequestInterceptor {
@NonNull
@Override
public ClientHttpResponse intercept(@NonNull HttpRequest request, @NonNull byte[] bytes, @NonNull ClientHttpRequestExecution execution) throws IOException {
HttpHeaders headers = ThreadLocalUtil.get(BladeConstant.CONTEXT_KEY);
if (headers != null && !headers.isEmpty()) {
HttpHeaders httpHeaders = request.getHeaders();
headers.forEach((key, values) -> values.forEach(value -> httpHeaders.add(key, value)));
}
return execution.execute(request, bytes);
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* 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
*
* https://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 org.springblade.core.cloud.http.client;
import org.springframework.http.HttpHeaders;
import org.springframework.http.StreamingHttpOutputMessage;
import org.springframework.http.client.AbstractClientHttpRequest;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.FastByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* Abstract base for {@link ClientHttpRequest} that also implement
* {@link StreamingHttpOutputMessage}. Ensures that headers and
* body are not written multiple times.
*
* @author Arjen Poutsma
* @since 6.1
*/
public abstract class AbstractStreamingClientHttpRequest extends AbstractClientHttpRequest
implements StreamingHttpOutputMessage {
@Nullable
private Body body;
@Nullable
private FastByteArrayOutputStream bodyStream;
@Override
protected final OutputStream getBodyInternal(HttpHeaders headers) {
Assert.state(this.body == null, "Invoke either getBody or setBody; not both");
if (this.bodyStream == null) {
this.bodyStream = new FastByteArrayOutputStream(1024);
}
return this.bodyStream;
}
@Override
public final void setBody(Body body) {
Assert.notNull(body, "Body must not be null");
assertNotExecuted();
Assert.state(this.bodyStream == null, "Invoke either getBody or setBody; not both");
this.body = body;
}
@Override
protected final ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException {
if (this.body == null && this.bodyStream != null) {
this.body = outputStream -> this.bodyStream.writeTo(outputStream);
}
return executeInternal(headers, this.body);
}
/**
* Abstract template method that writes the given headers and content to the HTTP request.
*
* @param headers the HTTP headers
* @param body the HTTP body, may be {@code null} if no body was {@linkplain #setBody(Body) set}
* @return the response object for the executed request
* @since 6.1
*/
protected abstract ClientHttpResponse executeInternal(HttpHeaders headers, @Nullable Body body) throws IOException;
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* 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
*
* https://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 org.springblade.core.cloud.http.client;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okio.BufferedSink;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.net.URI;
/**
* {@link ClientHttpRequest} implementation based on OkHttp 3.x.
*
* <p>Created via the {@link OkHttp3ClientHttpRequestFactory}.
*
* @author Luciano Leggieri
* @author Arjen Poutsma
* @author Roy Clarkson
* @since 4.3
*/
public class OkHttp3ClientHttpRequest extends AbstractStreamingClientHttpRequest {
private final OkHttpClient client;
private final URI uri;
private final HttpMethod method;
public OkHttp3ClientHttpRequest(OkHttpClient client, URI uri, HttpMethod method) {
this.client = client;
this.uri = uri;
this.method = method;
}
@Override
public HttpMethod getMethod() {
return this.method;
}
@Override
public URI getURI() {
return this.uri;
}
@Override
@SuppressWarnings("removal")
protected ClientHttpResponse executeInternal(HttpHeaders headers, @Nullable Body body) throws IOException {
RequestBody requestBody;
if (body != null) {
requestBody = new BodyRequestBody(headers, body);
} else if (okhttp3.internal.http.HttpMethod.requiresRequestBody(getMethod().name())) {
String header = headers.getFirst(HttpHeaders.CONTENT_TYPE);
MediaType contentType = (header != null) ? MediaType.parse(header) : null;
requestBody = RequestBody.create(contentType, new byte[0]);
} else {
requestBody = null;
}
Request.Builder builder = new Request.Builder()
.url(this.uri.toURL());
builder.method(this.method.name(), requestBody);
headers.forEach((headerName, headerValues) -> {
for (String headerValue : headerValues) {
builder.addHeader(headerName, headerValue);
}
});
Request request = builder.build();
return new OkHttp3ClientHttpResponse(this.client.newCall(request).execute());
}
private static class BodyRequestBody extends RequestBody {
private final HttpHeaders headers;
private final Body body;
public BodyRequestBody(HttpHeaders headers, Body body) {
this.headers = headers;
this.body = body;
}
@Override
public long contentLength() {
return this.headers.getContentLength();
}
@Nullable
@Override
public MediaType contentType() {
String contentType = this.headers.getFirst(HttpHeaders.CONTENT_TYPE);
if (StringUtils.hasText(contentType)) {
return MediaType.parse(contentType);
} else {
return null;
}
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
this.body.writeTo(sink.outputStream());
}
@Override
public boolean isOneShot() {
return !this.body.repeatable();
}
}
}

View File

@@ -0,0 +1,156 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* 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
*
* https://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 org.springblade.core.cloud.http.client;
import okhttp3.Cache;
import okhttp3.OkHttpClient;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.util.Assert;
import java.io.IOException;
import java.net.URI;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
/**
* {@link ClientHttpRequestFactory} implementation that uses
* <a href="https://square.github.io/okhttp/">OkHttp</a> 3.x to create requests.
*
* @author Luciano Leggieri
* @author Arjen Poutsma
* @author Roy Clarkson
* @since 4.3
*/
public class OkHttp3ClientHttpRequestFactory implements ClientHttpRequestFactory, DisposableBean {
private OkHttpClient client;
private final boolean defaultClient;
/**
* Create a factory with a default {@link OkHttpClient} instance.
*/
public OkHttp3ClientHttpRequestFactory() {
this.client = new OkHttpClient();
this.defaultClient = true;
}
/**
* Create a factory with the given {@link OkHttpClient} instance.
*
* @param client the client to use
*/
public OkHttp3ClientHttpRequestFactory(OkHttpClient client) {
Assert.notNull(client, "OkHttpClient must not be null");
this.client = client;
this.defaultClient = false;
}
/**
* Set the underlying read timeout in milliseconds.
* A value of 0 specifies an infinite timeout.
*/
public void setReadTimeout(int readTimeout) {
this.client = this.client.newBuilder()
.readTimeout(readTimeout, TimeUnit.MILLISECONDS)
.build();
}
/**
* Set the underlying read timeout in milliseconds.
* A value of 0 specifies an infinite timeout.
*
* @since 6.1
*/
public void setReadTimeout(Duration readTimeout) {
this.client = this.client.newBuilder()
.readTimeout(readTimeout)
.build();
}
/**
* Set the underlying write timeout in milliseconds.
* A value of 0 specifies an infinite timeout.
*/
public void setWriteTimeout(int writeTimeout) {
this.client = this.client.newBuilder()
.writeTimeout(writeTimeout, TimeUnit.MILLISECONDS)
.build();
}
/**
* Set the underlying write timeout in milliseconds.
* A value of 0 specifies an infinite timeout.
*
* @since 6.1
*/
public void setWriteTimeout(Duration writeTimeout) {
this.client = this.client.newBuilder()
.writeTimeout(writeTimeout)
.build();
}
/**
* Set the underlying connect timeout in milliseconds.
* A value of 0 specifies an infinite timeout.
*/
public void setConnectTimeout(int connectTimeout) {
this.client = this.client.newBuilder()
.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
.build();
}
/**
* Set the underlying connect timeout in milliseconds.
* A value of 0 specifies an infinite timeout.
*
* @since 6.1
*/
public void setConnectTimeout(Duration connectTimeout) {
this.client = this.client.newBuilder()
.connectTimeout(connectTimeout)
.build();
}
@NotNull
@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) {
return new OkHttp3ClientHttpRequest(this.client, uri, httpMethod);
}
@Override
public void destroy() throws IOException {
if (this.defaultClient) {
// Clean up the client if we created it in the constructor
Cache cache = this.client.cache();
if (cache != null) {
cache.close();
}
this.client.dispatcher().executorService().shutdown();
this.client.connectionPool().evictAll();
}
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* 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
*
* https://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 org.springblade.core.cloud.http.client;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import java.io.IOException;
import java.io.InputStream;
/**
* {@link ClientHttpResponse} implementation based on OkHttp 3.x.
*
* @author Luciano Leggieri
* @author Arjen Poutsma
* @author Roy Clarkson
* @since 4.3
*/
public class OkHttp3ClientHttpResponse implements ClientHttpResponse {
private final Response response;
@Nullable
private volatile HttpHeaders headers;
public OkHttp3ClientHttpResponse(Response response) {
Assert.notNull(response, "Response must not be null");
this.response = response;
}
@Override
public HttpStatusCode getStatusCode() throws IOException {
return HttpStatusCode.valueOf(this.response.code());
}
@Override
public String getStatusText() {
return this.response.message();
}
@Override
public InputStream getBody() throws IOException {
ResponseBody body = this.response.body();
return (body != null ? body.byteStream() : InputStream.nullInputStream());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = this.headers;
if (headers == null) {
headers = new HttpHeaders();
for (String headerName : this.response.headers().names()) {
for (String headerValue : this.response.headers(headerName)) {
headers.add(headerName, headerValue);
}
}
this.headers = headers;
}
return headers;
}
@Override
public void close() {
ResponseBody body = this.response.body();
if (body != null) {
body.close();
}
}
}

View File

@@ -0,0 +1,266 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.http.logger;
import okhttp3.*;
import okhttp3.internal.http.HttpHeaders;
import okio.Buffer;
import okio.BufferedSource;
import okio.GzipSource;
import org.springblade.core.launch.log.BladeLogLevel;
import java.io.EOFException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* An OkHttp interceptor which logs request and response information. Can be applied as an
* {@linkplain OkHttpClient#interceptors() application interceptor} or as a {@linkplain
* OkHttpClient#networkInterceptors() network interceptor}. <p> The format of the logs created by
* this class should not be considered stable and may change slightly between releases. If you need
* a stable logging format, use your own interceptor.
*
* @author L.cm
*/
public final class HttpLoggingInterceptor implements Interceptor {
private static final Charset UTF8 = StandardCharsets.UTF_8;
private final Logger logger;
private volatile BladeLogLevel level = BladeLogLevel.NONE;
public interface Logger {
/**
* log
* @param message message
*/
void log(String message);
}
public HttpLoggingInterceptor(Logger logger) {
this.logger = logger;
}
/**
* Change the level at which this interceptor logs.
* @param level log Level
* @return HttpLoggingInterceptor
*/
public HttpLoggingInterceptor setLevel(BladeLogLevel level) {
this.level = Objects.requireNonNull(level, "level == null. Use Level.NONE instead.");
return this;
}
public BladeLogLevel getLevel() {
return level;
}
@Override
public Response intercept(Chain chain) throws IOException {
BladeLogLevel level = this.level;
Request request = chain.request();
if (level == BladeLogLevel.NONE) {
return chain.proceed(request);
}
boolean logBody = level == BladeLogLevel.BODY;
boolean logHeaders = logBody || level == BladeLogLevel.HEADERS;
RequestBody requestBody = request.body();
boolean hasRequestBody = requestBody != null;
Connection connection = chain.connection();
String requestStartMessage = "--> "
+ request.method()
+ ' ' + request.url()
+ (connection != null ? " " + connection.protocol() : "");
if (!logHeaders && hasRequestBody) {
requestStartMessage += " (" + requestBody.contentLength() + "-byte body)";
}
logger.log(requestStartMessage);
if (logHeaders) {
if (hasRequestBody) {
// Request body headers are only present when installed as a network interceptor. Force
// them to be included (when available) so there values are known.
if (requestBody.contentType() != null) {
logger.log("Content-Type: " + requestBody.contentType());
}
if (requestBody.contentLength() != -1) {
logger.log("Content-Length: " + requestBody.contentLength());
}
}
Headers headers = request.headers();
for (int i = 0, count = headers.size(); i < count; i++) {
String name = headers.name(i);
// Skip headers from the request body as they are explicitly logged above.
if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) {
logger.log(name + ": " + headers.value(i));
}
}
if (!logBody || !hasRequestBody) {
logger.log("--> END " + request.method());
} else if (bodyHasUnknownEncoding(request.headers())) {
logger.log("--> END " + request.method() + " (encoded body omitted)");
} else {
Buffer buffer = new Buffer();
requestBody.writeTo(buffer);
Charset charset = UTF8;
MediaType contentType = requestBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF8);
}
logger.log("");
if (isPlaintext(buffer)) {
logger.log(buffer.readString(charset));
logger.log("--> END " + request.method()
+ " (" + requestBody.contentLength() + "-byte body)");
} else {
logger.log("--> END " + request.method() + " (binary "
+ requestBody.contentLength() + "-byte body omitted)");
}
}
}
long startNs = System.nanoTime();
Response response;
try {
response = chain.proceed(request);
} catch (Exception e) {
logger.log("<-- HTTP FAILED: " + e);
throw e;
}
long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
ResponseBody responseBody = response.body();
long contentLength = responseBody.contentLength();
String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";
logger.log("<-- "
+ response.code()
+ (response.message().isEmpty() ? "" : ' ' + response.message())
+ ' ' + response.request().url()
+ " (" + tookMs + "ms" + (!logHeaders ? ", " + bodySize + " body" : "") + ')');
if (logHeaders) {
Headers headers = response.headers();
int count = headers.size();
for (int i = 0; i < count; i++) {
logger.log(headers.name(i) + ": " + headers.value(i));
}
if (!logBody || !HttpHeaders.hasBody(response)) {
logger.log("<-- END HTTP");
} else if (bodyHasUnknownEncoding(response.headers())) {
logger.log("<-- END HTTP (encoded body omitted)");
} else {
BufferedSource source = responseBody.source();
// Buffer the entire body.
source.request(Long.MAX_VALUE);
Buffer buffer = source.getBuffer();
Long gzippedLength = null;
if ("gzip".equalsIgnoreCase(headers.get("Content-Encoding"))) {
gzippedLength = buffer.size();
GzipSource gzippedResponseBody = null;
try {
gzippedResponseBody = new GzipSource(buffer.clone());
buffer = new Buffer();
buffer.writeAll(gzippedResponseBody);
} finally {
if (gzippedResponseBody != null) {
gzippedResponseBody.close();
}
}
}
Charset charset = UTF8;
MediaType contentType = responseBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF8);
}
if (!isPlaintext(buffer)) {
logger.log("");
logger.log("<-- END HTTP (binary " + buffer.size() + "-byte body omitted)");
return response;
}
if (contentLength != 0) {
logger.log("");
logger.log(buffer.clone().readString(charset));
}
if (gzippedLength != null) {
logger.log("<-- END HTTP (" + buffer.size() + "-byte, "
+ gzippedLength + "-gzipped-byte body)");
} else {
logger.log("<-- END HTTP (" + buffer.size() + "-byte body)");
}
}
}
return response;
}
/**
* Returns true if the body in question probably contains human readable text. Uses a small sample
* of code points to detect unicode control characters commonly used in binary file signatures.
*/
private static boolean isPlaintext(Buffer buffer) {
try {
Buffer prefix = new Buffer();
long byteCount = buffer.size() < 64 ? buffer.size() : 64;
buffer.copyTo(prefix, 0, byteCount);
for (int i = 0; i < 16; i++) {
if (prefix.exhausted()) {
break;
}
int codePoint = prefix.readUtf8CodePoint();
if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
return false;
}
}
return true;
} catch (EOFException e) {
// Truncated UTF-8 sequence.
return false;
}
}
private boolean bodyHasUnknownEncoding(Headers headers) {
String contentEncoding = headers.get("Content-Encoding");
return contentEncoding != null
&& !"identity".equalsIgnoreCase(contentEncoding)
&& !"gzip".equalsIgnoreCase(contentEncoding);
}
}

View File

@@ -0,0 +1,42 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.http.logger;
import lombok.extern.slf4j.Slf4j;
/**
* OkHttp Slf4j logger
*
* @author L.cm
*/
@Slf4j
public class OkHttpSlf4jLogger implements HttpLoggingInterceptor.Logger {
@Override
public void log(String message) {
log.info(message);
}
}

View File

@@ -0,0 +1,26 @@
package org.springblade.core.cloud.sentinel;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* Sentinel统一限流策略
*
* @author Chill
*/
public class BladeBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
// Return 429 (Too Many Requests) by default.
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.getWriter().print(JsonUtil.toJson(R.fail(e.getMessage())));
}
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* 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
*
* https://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 org.springblade.core.cloud.sentinel;
import com.alibaba.cloud.sentinel.feign.SentinelContractHolder;
import feign.Contract;
import feign.Feign;
import feign.InvocationHandlerFactory;
import feign.Target;
import lombok.SneakyThrows;
import org.springblade.core.cloud.feign.BladeFallbackFactory;
import org.springframework.beans.BeansException;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.FeignClientFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.StringUtils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
/**
* feign集成sentinel自动配置
* 重写 {@link com.alibaba.cloud.sentinel.feign.SentinelFeign} 适配最新API
*
* @author Chill
*/
public class BladeFeignSentinel {
public static Builder builder() {
return new Builder();
}
public static final class Builder extends Feign.Builder implements ApplicationContextAware {
private Contract contract = new Contract.Default();
private ApplicationContext applicationContext;
private FeignClientFactory feignContext;
@Override
public Feign.Builder invocationHandlerFactory(InvocationHandlerFactory invocationHandlerFactory) {
throw new UnsupportedOperationException();
}
@Override
public Builder contract(Contract contract) {
this.contract = contract;
return this;
}
@Override
public Feign internalBuild() {
super.invocationHandlerFactory(new InvocationHandlerFactory() {
@SneakyThrows
@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
// 注解取值以避免循环依赖的问题
FeignClient feignClient = AnnotationUtils.findAnnotation(target.type(), FeignClient.class);
Class fallback = feignClient.fallback();
Class fallbackFactory = feignClient.fallbackFactory();
String contextId = feignClient.contextId();
if (!StringUtils.hasText(contextId)) {
contextId = feignClient.name();
}
Object fallbackInstance;
FallbackFactory fallbackFactoryInstance;
// 判断fallback类型
if (void.class != fallback) {
fallbackInstance = getFromContext(contextId, "fallback", fallback, target.type());
return new BladeSentinelInvocationHandler(target, dispatch, new FallbackFactory.Default(fallbackInstance));
}
if (void.class != fallbackFactory) {
fallbackFactoryInstance = (FallbackFactory) getFromContext(contextId, "fallbackFactory", fallbackFactory, FallbackFactory.class);
return new BladeSentinelInvocationHandler(target, dispatch, fallbackFactoryInstance);
}
// 默认fallbackFactory
BladeFallbackFactory bladeFallbackFactory = new BladeFallbackFactory(target);
return new BladeSentinelInvocationHandler(target, dispatch, bladeFallbackFactory);
}
private Object getFromContext(String name, String type, Class fallbackType, Class targetType) {
Object fallbackInstance = feignContext.getInstance(name, fallbackType);
if (fallbackInstance == null) {
throw new IllegalStateException(
String.format("No %s instance of type %s found for feign client %s",
type, fallbackType, name)
);
}
if (!targetType.isAssignableFrom(fallbackType)) {
throw new IllegalStateException(
String.format("Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
type, fallbackType, targetType, name)
);
}
return fallbackInstance;
}
});
super.contract(new SentinelContractHolder(contract));
return super.internalBuild();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
feignContext = this.applicationContext.getBean(FeignClientFactory.class);
}
}
}

View File

@@ -0,0 +1,71 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.cloud.sentinel;
import com.alibaba.cloud.sentinel.feign.SentinelFeignAutoConfiguration;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import feign.Feign;
import feign.RequestInterceptor;
import lombok.AllArgsConstructor;
import org.springblade.core.cloud.feign.BladeFeignRequestInterceptor;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
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.Primary;
import org.springframework.context.annotation.Scope;
/**
* Sentinel配置类
*
* @author Chill
*/
@AllArgsConstructor
@AutoConfiguration(before = SentinelFeignAutoConfiguration.class)
@ConditionalOnProperty(name = "feign.sentinel.enabled")
public class BladeSentinelAutoConfiguration {
@Bean
@Primary
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Feign.Builder feignSentinelBuilder(RequestInterceptor requestInterceptor) {
return BladeFeignSentinel.builder().requestInterceptor(requestInterceptor);
}
@Bean
@ConditionalOnMissingBean(name = "bladeFeignRequestInterceptor")
public RequestInterceptor requestInterceptor() {
return new BladeFeignRequestInterceptor();
}
@Bean
@ConditionalOnMissingBean
public BlockExceptionHandler blockExceptionHandler() {
return new BladeBlockExceptionHandler();
}
}

View File

@@ -0,0 +1,82 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.cloud.sentinel;
import com.alibaba.cloud.sentinel.SentinelProperties;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.SentinelWebInterceptor;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.DefaultBlockExceptionHandler;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.UrlCleaner;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.config.SentinelWebMvcConfig;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.util.StringUtils;
import java.util.Optional;
/**
* 处理sentinel2021兼容问题
*
* @author Chill
*/
@RequiredArgsConstructor
@Import(BladeSentinelFilterConfiguration.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class BladeSentinelFilterConfiguration {
@Bean
public SentinelWebInterceptor sentinelWebInterceptor(SentinelWebMvcConfig sentinelWebMvcConfig) {
return new SentinelWebInterceptor(sentinelWebMvcConfig);
}
@Bean
public SentinelWebMvcConfig sentinelWebMvcConfig(SentinelProperties properties,
Optional<UrlCleaner> urlCleanerOptional, Optional<BlockExceptionHandler> blockExceptionHandlerOptional,
Optional<RequestOriginParser> requestOriginParserOptional) {
SentinelWebMvcConfig sentinelWebMvcConfig = new SentinelWebMvcConfig();
sentinelWebMvcConfig.setHttpMethodSpecify(properties.getHttpMethodSpecify());
sentinelWebMvcConfig.setWebContextUnify(properties.getWebContextUnify());
if (blockExceptionHandlerOptional.isPresent()) {
blockExceptionHandlerOptional.ifPresent(sentinelWebMvcConfig::setBlockExceptionHandler);
} else {
if (StringUtils.hasText(properties.getBlockPage())) {
sentinelWebMvcConfig.setBlockExceptionHandler(
((request, response, e) -> response.sendRedirect(properties.getBlockPage())));
} else {
sentinelWebMvcConfig.setBlockExceptionHandler(new DefaultBlockExceptionHandler());
}
}
urlCleanerOptional.ifPresent(sentinelWebMvcConfig::setUrlCleaner);
requestOriginParserOptional.ifPresent(sentinelWebMvcConfig::setOriginParser);
return sentinelWebMvcConfig;
}
}

View File

@@ -0,0 +1,178 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.cloud.sentinel;
import com.alibaba.cloud.sentinel.feign.SentinelContractHolder;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import feign.Feign;
import feign.InvocationHandlerFactory;
import feign.MethodMetadata;
import feign.Target;
import org.springframework.cloud.openfeign.FallbackFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.LinkedHashMap;
import java.util.Map;
import static feign.Util.checkNotNull;
/**
* 重写 {@link com.alibaba.cloud.sentinel.feign.SentinelInvocationHandler} 适配最新API
*
* @author Chill
*/
public class BladeSentinelInvocationHandler implements InvocationHandler {
private final Target<?> target;
private final Map<Method, InvocationHandlerFactory.MethodHandler> dispatch;
private FallbackFactory fallbackFactory;
private Map<Method, Method> fallbackMethodMap;
public BladeSentinelInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch,
FallbackFactory fallbackFactory) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch");
this.fallbackFactory = fallbackFactory;
this.fallbackMethodMap = toFallbackMethod(dispatch);
}
public BladeSentinelInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch");
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
if ("equals".equals(method.getName())) {
try {
Object otherHandler = args.length > 0 && args[0] != null
? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
Object result;
InvocationHandlerFactory.MethodHandler methodHandler = this.dispatch.get(method);
// only handle by HardCodedTarget
if (target instanceof Target.HardCodedTarget) {
Target.HardCodedTarget hardCodedTarget = (Target.HardCodedTarget) target;
MethodMetadata methodMetadata = SentinelContractHolder.METADATA_MAP
.get(hardCodedTarget.type().getName()
+ Feign.configKey(hardCodedTarget.type(), method));
// resource default is HttpMethod:protocol://url
if (methodMetadata == null) {
result = methodHandler.invoke(args);
} else {
String resourceName = methodMetadata.template().method().toUpperCase()
+ ":" + hardCodedTarget.url() + methodMetadata.template().path();
Entry entry = null;
try {
ContextUtil.enter(resourceName);
entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
result = methodHandler.invoke(args);
} catch (Throwable ex) {
// fallback handle
if (!BlockException.isBlockException(ex)) {
Tracer.trace(ex);
}
if (fallbackFactory != null) {
try {
Object fallbackResult = fallbackMethodMap.get(method)
.invoke(fallbackFactory.create(ex), args);
return fallbackResult;
} catch (IllegalAccessException e) {
// shouldn't happen as method is public due to being an
// interface
throw new AssertionError(e);
} catch (InvocationTargetException e) {
throw new AssertionError(e.getCause());
}
} else {
// throw exception if fallbackFactory is null
throw ex;
}
} finally {
if (entry != null) {
entry.exit(1, args);
}
ContextUtil.exit();
}
}
} else {
// other target type using default strategy
result = methodHandler.invoke(args);
}
return result;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof BladeSentinelInvocationHandler) {
BladeSentinelInvocationHandler other = (BladeSentinelInvocationHandler) obj;
return target.equals(other.target);
}
return false;
}
@Override
public int hashCode() {
return target.hashCode();
}
@Override
public String toString() {
return target.toString();
}
static Map<Method, Method> toFallbackMethod(Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
Map<Method, Method> result = new LinkedHashMap<>();
for (Method method : dispatch.keySet()) {
method.setAccessible(true);
result.put(method, method);
}
return result;
}
}

View File

@@ -0,0 +1,53 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.server;
import io.undertow.Undertow;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import static io.undertow.UndertowOptions.ENABLE_HTTP2;
/**
* Undertow http2 h2c 配置,对 servlet 开启
*
* @author L.cm
*/
@AutoConfiguration(before = ServletWebServerFactoryAutoConfiguration.class)
@ConditionalOnClass(Undertow.class)
public class UndertowHttp2Configuration {
@Bean
public WebServerFactoryCustomizer<UndertowServletWebServerFactory> undertowHttp2WebServerFactoryCustomizer() {
return factory -> factory.addBuilderCustomizers(builder -> builder.setServerOption(ENABLE_HTTP2, true));
}
}

View File

@@ -0,0 +1,58 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.version;
import lombok.Getter;
import org.springframework.http.MediaType;
/**
* blade Media Typesapplication/vnd.github.VERSION+json
*
* <p>
* https://developer.github.com/v3/media/
* </p>
*
* @author L.cm
*/
@Getter
public class BladeMediaType {
private static final String MEDIA_TYPE_TEMP = "application/vnd.%s.%s+json";
private final String appName = "blade";
private final String version;
private final MediaType mediaType;
public BladeMediaType(String version) {
this.version = version;
this.mediaType = MediaType.valueOf(String.format(MEDIA_TYPE_TEMP, appName, version));
}
@Override
public String toString() {
return mediaType.toString();
}
}

View File

@@ -0,0 +1,113 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.version;
import org.springblade.core.cloud.annotation.ApiVersion;
import org.springblade.core.cloud.annotation.UrlVersion;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.lang.reflect.Method;
import java.util.Map;
/**
* url版本号处理 和 header 版本处理
*
* <p>
* url: /v1/user/{id}
* header: Accept application/vnd.blade.VERSION+json
* </p>
*
* 注意c 代表客户端版本
*
* @author L.cm
*/
public class BladeRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
@Nullable
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo mappingInfo = super.getMappingForMethod(method, handlerType);
if (mappingInfo != null) {
RequestMappingInfo apiVersionMappingInfo = getApiVersionMappingInfo(method, handlerType);
return apiVersionMappingInfo == null ? mappingInfo : apiVersionMappingInfo.combine(mappingInfo);
}
return null;
}
@Nullable
private RequestMappingInfo getApiVersionMappingInfo(Method method, Class<?> handlerType) {
// url 上的版本,优先获取方法上的版本
UrlVersion urlVersion = AnnotatedElementUtils.findMergedAnnotation(method, UrlVersion.class);
// 再次尝试类上的版本
if (urlVersion == null || StringUtil.isBlank(urlVersion.value())) {
urlVersion = AnnotatedElementUtils.findMergedAnnotation(handlerType, UrlVersion.class);
}
// Media Types 版本信息
ApiVersion apiVersion = AnnotatedElementUtils.findMergedAnnotation(method, ApiVersion.class);
// 再次尝试类上的版本
if (apiVersion == null || StringUtil.isBlank(apiVersion.value())) {
apiVersion = AnnotatedElementUtils.findMergedAnnotation(handlerType, ApiVersion.class);
}
boolean nonUrlVersion = urlVersion == null || StringUtil.isBlank(urlVersion.value());
boolean nonApiVersion = apiVersion == null || StringUtil.isBlank(apiVersion.value());
// 先判断同时不纯在
if (nonUrlVersion && nonApiVersion) {
return null;
}
// 如果 header 版本不存在
RequestMappingInfo.Builder mappingInfoBuilder = null;
if (nonApiVersion) {
mappingInfoBuilder = RequestMappingInfo.paths(urlVersion.value());
} else {
mappingInfoBuilder = RequestMappingInfo.paths(StringPool.EMPTY);
}
// 如果url版本不存在
if (nonUrlVersion) {
String versionMediaTypes = new BladeMediaType(apiVersion.value()).toString();
mappingInfoBuilder.produces(versionMediaTypes);
}
return mappingInfoBuilder.options(super.getBuilderConfiguration()).build();
}
@Override
protected void handlerMethodsInitialized(Map<RequestMappingInfo, HandlerMethod> handlerMethods) {
// 打印路由信息 spring boot 2.1 去掉了这个 日志的打印
if (logger.isInfoEnabled()) {
for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethods.entrySet()) {
RequestMappingInfo mapping = entry.getKey();
HandlerMethod handlerMethod = entry.getValue();
logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
}
}
super.handlerMethodsInitialized(handlerMethods);
}
}

View File

@@ -0,0 +1,110 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.version;
import feign.MethodMetadata;
import org.springblade.core.cloud.annotation.ApiVersion;
import org.springblade.core.cloud.annotation.UrlVersion;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
import org.springframework.cloud.openfeign.support.SpringMvcContract;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.convert.ConversionService;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.List;
/**
* 支持 blade-boot 的 版本 处理
*
* @see org.springblade.core.cloud.annotation.UrlVersion
* @see org.springblade.core.cloud.annotation.ApiVersion
* @author L.cm
*/
public class BladeSpringMvcContract extends SpringMvcContract {
public BladeSpringMvcContract(List<AnnotatedParameterProcessor> annotatedParameterProcessors, ConversionService conversionService) {
super(annotatedParameterProcessors, conversionService);
}
@Override
protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {
if (RequestMapping.class.isInstance(methodAnnotation) || methodAnnotation.annotationType().isAnnotationPresent(RequestMapping.class)) {
Class<?> targetType = method.getDeclaringClass();
// url 上的版本,优先获取方法上的版本
UrlVersion urlVersion = AnnotatedElementUtils.findMergedAnnotation(method, UrlVersion.class);
// 再次尝试类上的版本
if (urlVersion == null || StringUtil.isBlank(urlVersion.value())) {
urlVersion = AnnotatedElementUtils.findMergedAnnotation(targetType, UrlVersion.class);
}
if (urlVersion != null && StringUtil.isNotBlank(urlVersion.value())) {
String versionUrl = "/" + urlVersion.value();
data.template().uri(versionUrl);
}
// 注意:在父类之前 添加 url版本在父类之后处理 Media Types 版本
super.processAnnotationOnMethod(data, methodAnnotation, method);
// 处理 Media Types 版本信息
ApiVersion apiVersion = AnnotatedElementUtils.findMergedAnnotation(method, ApiVersion.class);
// 再次尝试类上的版本
if (apiVersion == null || StringUtil.isBlank(apiVersion.value())) {
apiVersion = AnnotatedElementUtils.findMergedAnnotation(targetType, ApiVersion.class);
}
if (apiVersion != null && StringUtil.isNotBlank(apiVersion.value())) {
BladeMediaType bladeMediaType = new BladeMediaType(apiVersion.value());
data.template().header(HttpHeaders.ACCEPT, bladeMediaType.toString());
}
}
}
/**
* 参考https://gist.github.com/rmfish/0ed59a9af6c05157be2a60c9acea2a10
* @param annotations 注解
* @param paramIndex 参数索引
* @return 是否 http 注解
*/
@Override
protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {
boolean httpAnnotation = super.processAnnotationsOnParameter(data, annotations, paramIndex);
// 在 springMvc 中如果是 Get 请求且参数中是对象 没有声明为@RequestBody 则默认为 Param
if (!httpAnnotation && StringPool.GET.equals(data.template().method().toUpperCase())) {
for (Annotation parameterAnnotation : annotations) {
if (!(parameterAnnotation instanceof RequestBody)) {
return false;
}
}
data.queryMapIndex(paramIndex);
return true;
}
return httpAnnotation;
}
}

View File

@@ -0,0 +1,53 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.version;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
/**
* url版本号处理
*
* @author L.cm
*/
public class BladeWebMvcRegistrations implements WebMvcRegistrations {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new BladeRequestMappingHandlerMapping();
}
@Override
public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
return null;
}
@Override
public ExceptionHandlerExceptionResolver getExceptionHandlerExceptionResolver() {
return null;
}
}

View File

@@ -0,0 +1,47 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.cloud.version;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.context.annotation.Bean;
/**
* url版本号处理
*
* 参考https://gitee.com/lianqu1990/spring-boot-starter-version-mapping
*
* @author L.cm
*/
@AutoConfiguration
@ConditionalOnWebApplication
public class VersionMappingAutoConfiguration {
@Bean
public WebMvcRegistrations bladeWebMvcRegistrations() {
return new BladeWebMvcRegistrations();
}
}

View File

@@ -0,0 +1,31 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>BladeX-Tool</artifactId>
<groupId>org.springblade</groupId>
<version>${revision}</version>
</parent>
<artifactId>blade-core-context</artifactId>
<name>${project.artifactId}</name>
<version>${project.parent.version}</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-tool</artifactId>
</dependency>
<!-- Auto -->
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-auto</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,74 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.context;
import org.slf4j.MDC;
import org.springblade.core.tool.utils.ThreadLocalUtil;
import org.springframework.lang.Nullable;
import java.util.Map;
import java.util.concurrent.Callable;
/**
* 多线程中传递 context 和 mdc
*
* @author L.cm
*/
public class BladeCallableWrapper<V> implements Callable<V> {
private final Callable<V> delegate;
private final Map<String, Object> tlMap;
/**
* logback 下有可能为 null
*/
@Nullable
private final Map<String, String> mdcMap;
public BladeCallableWrapper(Callable<V> callable) {
this.delegate = callable;
this.tlMap = ThreadLocalUtil.getAll();
this.mdcMap = MDC.getCopyOfContextMap();
}
@Override
public V call() throws Exception {
if (!tlMap.isEmpty()) {
ThreadLocalUtil.put(tlMap);
}
if (mdcMap != null && !mdcMap.isEmpty()) {
MDC.setContextMap(mdcMap);
}
try {
return delegate.call();
} finally {
tlMap.clear();
if (mdcMap != null) {
mdcMap.clear();
}
ThreadLocalUtil.clear();
MDC.clear();
}
}
}

View File

@@ -0,0 +1,82 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.context;
import org.springframework.lang.Nullable;
import java.util.function.Function;
/**
* Blade微服务上下文
*
* @author L.cm
*/
public interface BladeContext {
/**
* 获取 请求 id
*
* @return 请求id
*/
@Nullable
String getRequestId();
/**
* 账号id
*
* @return 账号id
*/
@Nullable
String getAccountId();
/**
* 获取租户id
*
* @return 租户id
*/
@Nullable
String getTenantId();
/**
* 获取上下文中的数据
*
* @param ctxKey 上下文中的key
* @return 返回对象
*/
@Nullable
String get(String ctxKey);
/**
* 获取上下文中的数据
*
* @param ctxKey 上下文中的key
* @param function 函数式
* @param <T> 泛型对象
* @return 返回对象
*/
@Nullable
<T> T get(String ctxKey, Function<String, T> function);
}

View File

@@ -0,0 +1,59 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.context;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import jakarta.servlet.http.HttpServletRequest;
/**
* HttpHeaders 获取器,用于跨服务和线程的传递,
* <p>
* 暂时不支持 webflux。
*
* @author L.cm
*/
public interface BladeHttpHeadersGetter {
/**
* 获取 HttpHeaders
*
* @return HttpHeaders
*/
@Nullable
HttpHeaders get();
/**
* 获取 HttpHeaders
*
* @param request 请求
* @return HttpHeaders
*/
@Nullable
HttpHeaders get(HttpServletRequest request);
}

View File

@@ -0,0 +1,73 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.context;
import org.slf4j.MDC;
import org.springblade.core.tool.utils.ThreadLocalUtil;
import org.springframework.lang.Nullable;
import java.util.Map;
/**
* 多线程中传递 context 和 mdc
*
* @author L.cm
*/
public class BladeRunnableWrapper implements Runnable {
private final Runnable delegate;
private final Map<String, Object> tlMap;
/**
* logback 下有可能为 null
*/
@Nullable
private final Map<String, String> mdcMap;
public BladeRunnableWrapper(Runnable runnable) {
this.delegate = runnable;
this.tlMap = ThreadLocalUtil.getAll();
this.mdcMap = MDC.getCopyOfContextMap();
}
@Override
public void run() {
if (!tlMap.isEmpty()) {
ThreadLocalUtil.put(tlMap);
}
if (mdcMap != null && !mdcMap.isEmpty()) {
MDC.setContextMap(mdcMap);
}
try {
delegate.run();
} finally {
tlMap.clear();
if (mdcMap != null) {
mdcMap.clear();
}
ThreadLocalUtil.clear();
MDC.clear();
}
}
}

View File

@@ -0,0 +1,89 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.context;
import lombok.RequiredArgsConstructor;
import org.springblade.core.context.props.BladeContextProperties;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.core.tool.utils.ThreadLocalUtil;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import java.util.function.Function;
import static org.springblade.core.tool.constant.BladeConstant.CONTEXT_KEY;
/**
* blade servlet 上下文,跨线程失效
*
* @author L.cm
*/
@RequiredArgsConstructor
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class BladeServletContext implements BladeContext {
private final BladeContextProperties contextProperties;
private final BladeHttpHeadersGetter httpHeadersGetter;
@Nullable
@Override
public String getRequestId() {
return get(contextProperties.getHeaders().getRequestId());
}
@Nullable
@Override
public String getAccountId() {
return get(contextProperties.getHeaders().getAccountId());
}
@Nullable
@Override
public String getTenantId() {
return get(contextProperties.getHeaders().getTenantId());
}
@Nullable
@Override
public String get(String ctxKey) {
HttpHeaders headers = ThreadLocalUtil.getIfAbsent(CONTEXT_KEY, httpHeadersGetter::get);
if (headers == null || headers.isEmpty()) {
return null;
}
return headers.getFirst(ctxKey);
}
@Nullable
@Override
public <T> T get(String ctxKey, Function<String, T> function) {
String ctxValue = get(ctxKey);
if (StringUtil.isBlank(ctxValue)) {
return null;
}
return function.apply(ctxKey);
}
}

View File

@@ -0,0 +1,84 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.context;
import lombok.RequiredArgsConstructor;
import org.springblade.core.context.props.BladeContextProperties;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.core.tool.utils.WebUtil;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.List;
/**
* HttpHeaders 获取器
*
* @author L.cm
*/
@RequiredArgsConstructor
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class ServletHttpHeadersGetter implements BladeHttpHeadersGetter {
private final BladeContextProperties properties;
@Nullable
@Override
public HttpHeaders get() {
HttpServletRequest request = WebUtil.getRequest();
if (request == null) {
return null;
}
return get(request);
}
@Nullable
@Override
public HttpHeaders get(HttpServletRequest request) {
HttpHeaders headers = new HttpHeaders();
List<String> crossHeaders = properties.getCrossHeaders();
// 传递请求头
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
List<String> allowed = properties.getHeaders().getAllowed();
while (headerNames.hasMoreElements()) {
String key = headerNames.nextElement();
// 只支持配置的 header
if (crossHeaders.contains(key) || allowed.contains(key)) {
String values = request.getHeader(key);
// header value 不为空的 传递
if (StringUtil.isNotBlank(values)) {
headers.add(key, values);
}
}
}
}
return headers;
}
}

View File

@@ -0,0 +1,62 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.context.config;
import org.springblade.core.context.BladeContext;
import org.springblade.core.context.BladeHttpHeadersGetter;
import org.springblade.core.context.BladeServletContext;
import org.springblade.core.context.ServletHttpHeadersGetter;
import org.springblade.core.context.props.BladeContextProperties;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
/**
* blade 服务上下文配置
*
* @author L.cm
*/
@AutoConfiguration
@Order(Ordered.HIGHEST_PRECEDENCE)
@EnableConfigurationProperties(BladeContextProperties.class)
public class BladeContextAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public BladeHttpHeadersGetter bladeHttpHeadersGetter(BladeContextProperties contextProperties) {
return new ServletHttpHeadersGetter(contextProperties);
}
@Bean
@ConditionalOnMissingBean
public BladeContext bladeContext(BladeContextProperties contextProperties, BladeHttpHeadersGetter httpHeadersGetter) {
return new BladeServletContext(contextProperties, httpHeadersGetter);
}
}

View File

@@ -0,0 +1,51 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.context.config;
import org.springblade.core.context.BladeHttpHeadersGetter;
import org.springblade.core.context.listener.BladeServletRequestListener;
import org.springblade.core.context.props.BladeContextProperties;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
/**
* Servlet 监听器自动配置
*
* @author L.cm
*/
@AutoConfiguration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class BladeServletListenerConfiguration {
@Bean
public ServletListenerRegistrationBean<?> registerCustomListener(BladeContextProperties properties,
BladeHttpHeadersGetter httpHeadersGetter) {
return new ServletListenerRegistrationBean<>(new BladeServletRequestListener(properties, httpHeadersGetter));
}
}

View File

@@ -0,0 +1,83 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.context.listener;
import lombok.RequiredArgsConstructor;
import org.slf4j.MDC;
import org.springblade.core.context.BladeHttpHeadersGetter;
import org.springblade.core.context.props.BladeContextProperties;
import org.springblade.core.tool.constant.BladeConstant;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.core.tool.utils.ThreadLocalUtil;
import org.springframework.http.HttpHeaders;
import jakarta.servlet.ServletRequestEvent;
import jakarta.servlet.ServletRequestListener;
import jakarta.servlet.http.HttpServletRequest;
/**
* Servlet 请求监听器
*
* @author L.cm
*/
@RequiredArgsConstructor
public class BladeServletRequestListener implements ServletRequestListener {
private final BladeContextProperties contextProperties;
private final BladeHttpHeadersGetter httpHeadersGetter;
@Override
public void requestInitialized(ServletRequestEvent event) {
HttpServletRequest request = (HttpServletRequest) event.getServletRequest();
// MDC 获取透传的 变量
BladeContextProperties.Headers headers = contextProperties.getHeaders();
String requestId = request.getHeader(headers.getRequestId());
if (StringUtil.isNotBlank(requestId)) {
MDC.put(BladeConstant.MDC_REQUEST_ID_KEY, requestId);
}
String accountId = request.getHeader(headers.getAccountId());
if (StringUtil.isNotBlank(accountId)) {
MDC.put(BladeConstant.MDC_ACCOUNT_ID_KEY, accountId);
}
String tenantId = request.getHeader(headers.getTenantId());
if (StringUtil.isNotBlank(tenantId)) {
MDC.put(BladeConstant.MDC_TENANT_ID_KEY, tenantId);
}
// 处理 context直接传递 request因为 spring 中的尚未初始化完成
HttpHeaders httpHeaders = httpHeadersGetter.get(request);
ThreadLocalUtil.put(BladeConstant.CONTEXT_KEY, httpHeaders);
}
@Override
public void requestDestroyed(ServletRequestEvent event) {
// 会话销毁时,清除上下文
ThreadLocalUtil.clear();
// 会话销毁时,清除 mdc
MDC.remove(BladeConstant.MDC_REQUEST_ID_KEY);
MDC.remove(BladeConstant.MDC_ACCOUNT_ID_KEY);
MDC.remove(BladeConstant.MDC_TENANT_ID_KEY);
}
}

View File

@@ -0,0 +1,90 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: DreamLu (596392912@qq.com)
*/
package org.springblade.core.context.props;
import lombok.Getter;
import lombok.Setter;
import org.springblade.core.launch.constant.TokenConstant;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Headers 配置
*
* @author L.cm
*/
@Getter
@Setter
@ConfigurationProperties(BladeContextProperties.PREFIX)
public class BladeContextProperties {
/**
* 配置前缀
*/
public static final String PREFIX = "blade.context";
/**
* 上下文传递的 headers 信息
*/
private Headers headers = new Headers();
@Getter
@Setter
public static class Headers {
/**
* 请求id默认Blade-RequestId
*/
private String requestId = "Blade-RequestId";
/**
* 用于 聚合层 向调用层传递用户信息 的请求头默认Blade-AccountId
*/
private String accountId = "Blade-AccountId";
/**
* 用于 聚合层 向调用层传递租户id 的请求头默认Blade-TenantId
*/
private String tenantId = "Blade-TenantId";
/**
* 自定义 RestTemplate 和 Feign 透传到下层的 Headers 名称列表
*/
private List<String> allowed = Arrays.asList("X-Real-IP", "x-forwarded-for", "version", "VERSION", "authorization", "Authorization", TokenConstant.HEADER.toLowerCase(), TokenConstant.HEADER);
}
/**
* 获取跨服务的请求头
*
* @return 请求头列表
*/
public List<String> getCrossHeaders() {
List<String> headerList = new ArrayList<>();
headerList.add(headers.getRequestId());
headerList.add(headers.getAccountId());
headerList.add(headers.getTenantId());
headerList.addAll(headers.getAllowed());
return headerList;
}
}

87
blade-core-db/pom.xml Normal file
View File

@@ -0,0 +1,87 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>BladeX-Tool</artifactId>
<groupId>org.springblade</groupId>
<version>${revision}</version>
</parent>
<artifactId>blade-core-db</artifactId>
<name>${project.artifactId}</name>
<version>${project.parent.version}</version>
<packaging>jar</packaging>
<dependencies>
<!--Blade-->
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-tool</artifactId>
</dependency>
<!--Spring-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<exclusion>
<artifactId>tomcat-jdbc</artifactId>
<groupId>org.apache.tomcat</groupId>
</exclusion>
</exclusions>
</dependency>
<!--Mybatis-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency>
<!-- Druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
</dependency>
<!-- MySql -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- Oracle -->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc7</artifactId>
<optional>true</optional>
</dependency>
<!-- PostgreSql -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<optional>true</optional>
</dependency>
<!-- SqlServer -->
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<optional>true</optional>
</dependency>
<!-- DaMeng -->
<dependency>
<groupId>com.dameng</groupId>
<artifactId>DmJdbcDriver18</artifactId>
<optional>true</optional>
</dependency>
<!--YashanDB-->
<dependency>
<groupId>com.yashandb.jdbc</groupId>
<artifactId>yasdb-jdbc</artifactId>
<optional>true</optional>
</dependency>
<!-- Auto -->
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-auto</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,40 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.db.config;
import org.springblade.core.launch.props.BladePropertySource;
import org.springframework.boot.autoconfigure.AutoConfiguration;
/**
* 数据源配置类
*
* @author Chill
*/
@AutoConfiguration
@BladePropertySource(value = "classpath:/blade-db.yml")
public class DbConfiguration {
}

View File

@@ -0,0 +1,6 @@
/**
* Created by Blade.
*
* @author zhuangqian
*/
package org.springblade.core.db;

View File

@@ -0,0 +1,39 @@
#spring-datasource配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
druid:
initial-size: 5
max-active: 20
min-idle: 5
max-wait: 60000
# MySql、PostgreSQL校验
validation-query: select 1
# Oracle校验
#validation-query: select 1 from dual
validation-query-timeout: 2000
test-on-borrow: false
test-on-return: false
test-while-idle: true
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
stat-view-servlet:
enabled: true
login-username: blade
login-password: 1qaz@WSX
web-stat-filter:
enabled: true
url-pattern: /*
exclusions: '*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*'
session-stat-enable: true
session-stat-max-count: 10
#hikari:
#connection-test-query: SELECT 1 FROM DUAL
#connection-timeout: 30000
#maximum-pool-size: 5
#max-lifetime: 1800000
#minimum-idle: 1

49
blade-core-launch/pom.xml Normal file
View File

@@ -0,0 +1,49 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>BladeX-Tool</artifactId>
<groupId>org.springblade</groupId>
<version>${revision}</version>
</parent>
<artifactId>blade-core-launch</artifactId>
<name>${project.artifactId}</name>
<version>${project.parent.version}</version>
<packaging>jar</packaging>
<dependencies>
<!--Spring-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</dependency>
<!-- Auto -->
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-auto</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,223 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.launch;
import org.springblade.core.launch.constant.AppConstant;
import org.springblade.core.launch.constant.NacosConstant;
import org.springblade.core.launch.service.LauncherService;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.*;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
* BladeX 应用启动类
* <p>
* 该类主要封装启动逻辑,包括环境变量处理、配置属性设置、以及自定义组件的加载。
*
* @author BladeX
*/
public class BladeApplication {
/**
* 启动SpringBoot应用不使用自定义SpringApplicationBuilder
*
* @param appName 应用名称
* @param source Spring Boot应用的主类
* @param args 命令行参数
* @return 配置完成的应用上下文
*/
public static ConfigurableApplicationContext run(String appName, Class<?> source, String... args) {
SpringApplicationBuilder springApplicationBuilder = createSpringApplicationBuilder(appName, source, args);
return springApplicationBuilder.run(args);
}
/**
* 启动SpringBoot应用使用自定义SpringApplicationBuilder
*
* @param appName 应用名称
* @param source Spring Boot应用的主类
* @param builder 自定义的SpringApplicationBuilder
* @param args 命令行参数
* @return 配置完成的应用上下文
*/
public static ConfigurableApplicationContext run(String appName, Class<?> source, SpringApplicationBuilder builder, String... args) {
SpringApplicationBuilder springApplicationBuilder = createSpringApplicationBuilder(appName, source, builder, args);
return springApplicationBuilder.run(args);
}
/**
* 创建SpringApplicationBuilder实例包括环境配置、系统属性设置、默认属性设置和自定义组件加载
*
* @param appName 应用名称
* @param source Spring Boot应用的主类
* @param args 命令行参数
* @return 配置完成的SpringApplicationBuilder实例
*/
public static SpringApplicationBuilder createSpringApplicationBuilder(String appName, Class<?> source, String... args) {
return createSpringApplicationBuilder(appName, source, null, args);
}
/**
* 创建SpringApplicationBuilder实例包括环境配置、系统属性设置、默认属性设置和自定义组件加载
*
* @param appName 应用名称
* @param source Spring Boot应用的主类
* @param builder 自定义的SpringApplicationBuilder
* @param args 命令行参数
* @return 配置完成的SpringApplicationBuilder实例
*/
public static SpringApplicationBuilder createSpringApplicationBuilder(String appName, Class<?> source, SpringApplicationBuilder builder, String... args) {
Assert.hasText(appName, "[appName]服务名不能为空"); // 确保应用名称不为空
ConfigurableEnvironment environment = configureEnvironment(args); // 配置环境变量,包括命令行参数、系统属性和系统环境变量
List<String> activeProfileList = getActiveProfiles(environment); // 获取当前激活的Spring配置文件列表
String profile = determineActiveProfile(activeProfileList); // 确定要激活的配置文件,如果存在多个则抛出异常
if (builder == null) { // 如果没有提供自定义的SpringApplicationBuilder则创建一个新的
builder = new SpringApplicationBuilder(source); // 使用Spring Boot应用的主类初始化SpringApplicationBuilder
}
builder.profiles(profile); // 设置SpringApplicationBuilder要激活的配置文件
setSystemProperties(appName, profile); // 设置系统属性,包括应用名称、激活的配置文件等
Properties defaultProperties = setDefaultProperties(appName, profile); // 设置默认的一些属性
builder.properties(defaultProperties); // 将这些默认属性添加到SpringApplicationBuilder中
loadCustomComponents(builder, appName, profile); // 加载自定义组件,如各种启动器服务
return builder; // 返回配置好的SpringApplicationBuilder实例
}
/**
* 判断是否为本地开发环境
*
* @return true如果是本地开发环境否则为false
*/
public static boolean isLocalDev() {
String osName = System.getProperty("os.name");
return StringUtils.hasText(osName) && !(AppConstant.OS_NAME_LINUX.equalsIgnoreCase(osName));
}
/**
* 配置环境变量
*
* @param args 命令行参数
* @return 配置完成的环境
*/
private static ConfigurableEnvironment configureEnvironment(String... args) {
ConfigurableEnvironment environment = new StandardEnvironment();
MutablePropertySources propertySources = environment.getPropertySources();
propertySources.addFirst(new SimpleCommandLinePropertySource(args));
propertySources.addLast(new MapPropertySource(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, environment.getSystemProperties()));
propertySources.addLast(new SystemEnvironmentPropertySource(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, environment.getSystemEnvironment()));
return environment;
}
/**
* 获取激活的配置文件列表
*
* @param environment 环境配置
* @return 激活的配置文件列表
*/
private static List<String> getActiveProfiles(ConfigurableEnvironment environment) {
String[] activeProfiles = environment.getActiveProfiles();
return new ArrayList<>(Arrays.asList(activeProfiles));
}
/**
* 确定激活的配置文件
*
* @param activeProfileList 激活的配置文件列表
* @return 确定的激活配置文件
*/
private static String determineActiveProfile(List<String> activeProfileList) {
if (activeProfileList.isEmpty()) {
String defaultProfile = AppConstant.DEV_CODE;
activeProfileList.add(defaultProfile);
return defaultProfile;
} else if (activeProfileList.size() == 1) {
return activeProfileList.get(0);
} else {
throw new IllegalStateException("不可同时存在多个环境变量: " + String.join(", ", activeProfileList));
}
}
/**
* 设置系统属性
*
* @param appName 应用名称
* @param profile 激活的配置文件
*/
private static void setSystemProperties(String appName, String profile) {
Properties props = System.getProperties();
props.setProperty("spring.application.name", appName);
props.setProperty("spring.profiles.active", profile);
props.setProperty("info.version", AppConstant.APPLICATION_VERSION);
props.setProperty("info.desc", appName);
props.setProperty("file.encoding", StandardCharsets.UTF_8.name());
props.setProperty("blade.env", profile);
props.setProperty("blade.name", appName);
props.setProperty("blade.is-local", String.valueOf(isLocalDev()));
props.setProperty("blade.dev-mode", profile.equals(AppConstant.PROD_CODE) ? "false" : "true");
props.setProperty("blade.service.version", AppConstant.APPLICATION_VERSION);
props.setProperty("loadbalancer.client.name", appName);
}
/**
* 设置默认属性
*
* @param appName 应用名称
* @param profile 激活的配置文件
* @return 默认属性集
*/
private static Properties setDefaultProperties(String appName, String profile) {
Properties defaultProperties = new Properties();
defaultProperties.setProperty("nacos.logging.default.config.enabled", "false");
defaultProperties.setProperty("spring.main.allow-bean-definition-overriding", "true");
defaultProperties.setProperty("spring.sleuth.sampler.percentage", "1.0");
defaultProperties.setProperty("spring.cloud.alibaba.seata.tx-service-group", appName.concat(NacosConstant.NACOS_GROUP_SUFFIX));
defaultProperties.setProperty("spring.cloud.nacos.config.file-extension", NacosConstant.NACOS_CONFIG_FORMAT);
defaultProperties.setProperty("spring.cloud.nacos.config.shared-configs[0].data-id", NacosConstant.sharedDataId());
defaultProperties.setProperty("spring.cloud.nacos.config.shared-configs[0].group", NacosConstant.NACOS_CONFIG_GROUP);
defaultProperties.setProperty("spring.cloud.nacos.config.shared-configs[0].refresh", NacosConstant.NACOS_CONFIG_REFRESH);
defaultProperties.setProperty("spring.cloud.nacos.config.shared-configs[1].data-id", NacosConstant.sharedDataId(profile));
defaultProperties.setProperty("spring.cloud.nacos.config.shared-configs[1].group", NacosConstant.NACOS_CONFIG_GROUP);
defaultProperties.setProperty("spring.cloud.nacos.config.shared-configs[1].refresh", NacosConstant.NACOS_CONFIG_REFRESH);
return defaultProperties;
}
/**
* 加载自定义组件
*
* @param builder SpringApplicationBuilder实例
* @param appName 应用名称
* @param profile 激活的配置文件
*/
private static void loadCustomComponents(SpringApplicationBuilder builder, String appName, String profile) {
ServiceLoader<LauncherService> serviceLoader = ServiceLoader.load(LauncherService.class);
serviceLoader.forEach(launcherService -> launcherService.launcher(builder, appName, profile, isLocalDev()));
}
}

View File

@@ -0,0 +1,58 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.launch;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.scheduling.annotation.Async;
import org.springframework.util.StringUtils;
import java.util.Objects;
/**
* 项目启动事件通知
*
* @author Chill
*/
@Slf4j
@AutoConfiguration
public class StartEventListener {
@Async
@Order
@EventListener(WebServerInitializedEvent.class)
public void afterStart(WebServerInitializedEvent event) {
Environment environment = event.getApplicationContext().getEnvironment();
String appName = Objects.requireNonNull(environment.getProperty("spring.application.name")).toUpperCase();
int localPort = event.getWebServer().getPort();
String profile = StringUtils.arrayToCommaDelimitedString(environment.getActiveProfiles());
log.info("---[{}]---启动完成,当前使用的端口:[{}],环境变量:[{}]---", appName, localPort, profile);
}
}

View File

@@ -0,0 +1,43 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.core.launch.config;
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
/**
* 配置类
*
* @author Chill
*/
@AutoConfiguration
@AllArgsConstructor
@Order(Ordered.HIGHEST_PRECEDENCE)
public class BladeLaunchConfiguration {
}

Some files were not shown because too many files have changed in this diff Show More