Compare commits

..

10 Commits

Author SHA1 Message Date
黄斌峰 95e4f5ad4b style(log): 更新日志配置文件以显示行号 2026-08-10 17:15:52 +08:00
黄斌峰 52ebda0a42 fix(feign): 修复FR返回类型未反序列化,丢失错误消息的问题 2026-08-10 17:15:16 +08:00
黄斌峰 868376fc42 refactor(db): 临时注释掉报错的数据库驱动依赖 2026-08-10 12:56:46 +08:00
黄斌峰 2a1b90c6cb feat(log): 添加traceId 2026-08-10 07:31:46 +08:00
黄斌峰 667bf66489 feat(redis): 添加 RedissonClient 获取方法 2026-08-10 07:31:11 +08:00
黄斌峰 6a965e1618 feat(auth): 新增MK OAuth认证模式支持 2026-08-07 17:55:41 +08:00
黄斌峰 deb4029f01 feat(launch): 添加 Nacos 配置导入控制功能 2026-08-07 16:03:42 +08:00
黄斌峰 dc22a9b669 chore(build): 更新版本号为快照并禁用BladeX仓库配置 2026-08-07 15:56:43 +08:00
aguanleishen 57bea22e44 feat:添加wps相关代码 2026-07-28 17:28:31 +08:00
aguanleishen 15854b40ac feat:添加APP版本管理相关代码 2026-07-28 08:32:50 +08:00
36 changed files with 1498 additions and 46 deletions
@@ -77,4 +77,8 @@ public interface OAuth2GranterConstant {
*/ */
String REGISTER = "register"; String REGISTER = "register";
/**
* mk oauth模式
*/
String MK = "mk";
} }
@@ -0,0 +1,134 @@
package org.springblade.core.oauth2.endpoint;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.oauth2.constant.OAuth2TokenConstant;
import org.springblade.core.oauth2.exception.ExceptionCode;
import org.springblade.core.oauth2.granter.TokenGranter;
import org.springblade.core.oauth2.granter.TokenGranterFactory;
import org.springblade.core.oauth2.handler.TokenHandler;
import org.springblade.core.oauth2.provider.OAuth2Request;
import org.springblade.core.oauth2.provider.OAuth2Token;
import org.springblade.core.oauth2.service.OAuth2User;
import org.springblade.core.oauth2.utils.OAuth2ExceptionUtil;
import org.springblade.core.tool.support.Kv;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import static org.springblade.core.oauth2.constant.OAuth2GranterConstant.MK;
import static org.springblade.core.oauth2.constant.OAuth2ParameterConstant.USERNAME;
/**
* mk oauth认证端点
* @author bfhuange
* @date 2024/9/13
*/
@Slf4j
@RequiredArgsConstructor
@Tag(name = "跳转mk认证", description = "跳转mk认证端点")
public abstract class AbstractOAuth2MKEndpoint {
private final TokenGranterFactory granterFactory;
private final TokenHandler tokenHandler;
/**
* 返回登录页面
* @param request
* @return
*/
@GetMapping("/oauth/mk/login")
public ResponseEntity<String> login(HttpServletRequest request, HttpServletResponse response) {
String referer = request.getHeader("referer");
if (!checkRefererUrl(referer)) {
// 校验来源地址
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body("非法来源地址");
}
// 返回登录页面
return ResponseEntity.ok(generateLoginUrl(referer));
}
/**
* 通过mk授权码换取本系统token
* @param mkCode
* @return
*/
@GetMapping("/oauth/mk/token")
public ResponseEntity<Kv> token(String mkCode) {
log.info("mk 登录回调 mkCode{}", mkCode);
if (!StringUtils.hasText(mkCode)) {
// 根据无效的mk code的错误代码抛出异常
OAuth2ExceptionUtil.throwFromCode(ExceptionCode.INVALID_MK_CODE.getCode());
}
// 将mk code 转换成erp的账号/手机号
String account = getAccountByMkCode(mkCode);
if (account == null) {
OAuth2ExceptionUtil.throwFromCode(ExceptionCode.MK_ACCOUNT_NOT_FIND.getCode());
}
// 创建 OAuth2 请求对象并构建参数
OAuth2Request request = OAuth2Request.create();
// 默认管理组
request.setTenantId(OAuth2TokenConstant.DEFAULT_TENANT_ID);
// 只设置账号
request.getParameterArgs().set(USERNAME, account);
// 根据MK oauth的授权类型创建对应的 TokenGranter
TokenGranter tokenGranter = granterFactory.create(MK);
// 使用 TokenGranter 获取用户信息
OAuth2User user = tokenGranter.user(request);
// 创建令牌
OAuth2Token token = tokenGranter.token(user, request);
// 对令牌进行增强处理
OAuth2Token enhanceToken = tokenHandler.enhance(user, token, request);
// 返回增强后的令牌
return ResponseEntity.ok(enhanceToken.getArgs());
}
/**
* 输出消息
* @param response
* @param message
*/
protected void writeMessage(HttpServletResponse response, String message) {
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
try(PrintWriter writer = response.getWriter()) {
log.warn(message);
writer.println(message);
} catch (IOException e) {
log.error(message + "异常", e);
}
}
/**
* 生成登录地址
* @param refererUrl
* @return
*/
protected abstract String generateLoginUrl(String refererUrl);
/**
* 根据mk授权码获取账号
* @param mkCode
* @return
*/
protected abstract String getAccountByMkCode(String mkCode);
/**
* 校验来源url,返回false,不跳转登录页面
* @param refererUrl
* @return
*/
protected abstract boolean checkRefererUrl(String refererUrl);
}
@@ -74,6 +74,11 @@ public enum ExceptionCode implements Oauth2ExceptionCode {
*/ */
UNAUTHORIZED_USER(OAuth2ErrorCode.UNAUTHORIZED_USER, "认证信息错误或无效"), UNAUTHORIZED_USER(OAuth2ErrorCode.UNAUTHORIZED_USER, "认证信息错误或无效"),
/**
* 用户未授权 - 账号或密码不正确。
*/
ACCOUNT_OR_PASSWORD_ERROR(OAuth2ErrorCode.ACCOUNT_OR_PASSWORD_ERROR, "账号或密码不正确"),
/** /**
* 用户租户未授权 - 指定的用户租户未授权。 * 用户租户未授权 - 指定的用户租户未授权。
*/ */
@@ -84,6 +89,21 @@ public enum ExceptionCode implements Oauth2ExceptionCode {
*/ */
INVALID_REFRESH_TOKEN(OAuth2ErrorCode.INVALID_REFRESH_TOKEN, "令牌刷新错误或无效"), INVALID_REFRESH_TOKEN(OAuth2ErrorCode.INVALID_REFRESH_TOKEN, "令牌刷新错误或无效"),
/**
* 用户未授权 - 无效的mk code
*/
INVALID_MK_CODE(OAuth2ErrorCode.INVALID_MK_CODE, "无效的mk code"),
/**
* 用户未授权 - 获取mk账号错误
*/
MK_ACCOUNT_NOT_FIND(OAuth2ErrorCode.MK_ACCOUNT_FETCH_ERROR, "获取MK账号错误"),
/**
* 用户未授权 - mk账号未同步本系统
*/
MK_ACCOUNT_NOT_SYNC_SYSTEM(OAuth2ErrorCode.MK_ACCOUNT_NOT_SYNC_SYSTEM, "MK账号【%s】未同步到本系统"),
/** /**
* 客户端不存在 - 指定的客户端ID不存在或无效。 * 客户端不存在 - 指定的客户端ID不存在或无效。
*/ */
@@ -59,10 +59,26 @@ public interface OAuth2ErrorCode {
* 用户租户未授权 - 指定的用户租户未授权。 * 用户租户未授权 - 指定的用户租户未授权。
*/ */
int UNAUTHORIZED_USER_TENANT = 2006; int UNAUTHORIZED_USER_TENANT = 2006;
/**
* 账号或密码不正确
*/
int ACCOUNT_OR_PASSWORD_ERROR = 2007;
/** /**
* 令牌刷新错误或无效 - 刷新令牌认证信息错误或无效。 * 令牌刷新错误或无效 - 刷新令牌认证信息错误或无效。
*/ */
int INVALID_REFRESH_TOKEN = 2010; int INVALID_REFRESH_TOKEN = 2010;
/**
* 无效的mk code
*/
int INVALID_MK_CODE = 2020;
/**
* 获取mk账号错误
*/
int MK_ACCOUNT_FETCH_ERROR = 2021;
/**
* mk账号未同步本系统
*/
int MK_ACCOUNT_NOT_SYNC_SYSTEM = 2022;
/** /**
* 客户端不存在 - 指定的客户端ID不存在或无效。 * 客户端不存在 - 指定的客户端ID不存在或无效。
*/ */
@@ -25,6 +25,7 @@
*/ */
package org.springblade.core.oauth2.granter; package org.springblade.core.oauth2.granter;
import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springblade.core.launch.constant.TokenConstant; import org.springblade.core.launch.constant.TokenConstant;
@@ -66,8 +67,9 @@ public abstract class AbstractTokenGranter implements TokenGranter {
* 认证失败原因:密码匹配失败 * 认证失败原因:密码匹配失败
*/ */
private static final String REASON_PASSWORD_MISMATCH = "密码匹配失败"; private static final String REASON_PASSWORD_MISMATCH = "密码匹配失败";
@Getter
private final OAuth2ClientService clientService; private final OAuth2ClientService clientService;
@Getter
private final OAuth2UserService userService; private final OAuth2UserService userService;
private final PasswordHandler passwordHandler; private final PasswordHandler passwordHandler;
@@ -0,0 +1,81 @@
package org.springblade.core.oauth2.granter;
import org.springblade.core.oauth2.exception.ExceptionCode;
import org.springblade.core.oauth2.exception.OAuth2ErrorCode;
import org.springblade.core.oauth2.exception.OAuth2Exception;
import org.springblade.core.oauth2.handler.PasswordHandler;
import org.springblade.core.oauth2.provider.OAuth2Request;
import org.springblade.core.oauth2.service.OAuth2Client;
import org.springblade.core.oauth2.service.OAuth2ClientService;
import org.springblade.core.oauth2.service.OAuth2User;
import org.springblade.core.oauth2.service.OAuth2UserService;
import org.springblade.core.oauth2.utils.OAuth2ExceptionUtil;
import org.springframework.stereotype.Component;
import java.util.Objects;
import java.util.Optional;
/**
* PasswordTokenGranter
*
* @author BladeX
*/
@Component
public class MKTokenGranter extends AbstractTokenGranter {
/**
* MK client id
*/
private static final String MK_CLIENT_ID = "mk-oauth";
public MKTokenGranter(OAuth2ClientService clientService, OAuth2UserService userService, PasswordHandler passwordHandler) {
super(clientService, userService, passwordHandler);
}
@Override
public String type() {
return MK;
}
@Override
public OAuth2User user(OAuth2Request request) {
OAuth2User user = this.getUser(request);
return Optional.ofNullable(this.enhancer)
.map(enhancer -> enhancer.enhance(user, request))
.orElse(user);
}
@Override
public OAuth2Client client(OAuth2Request request) {
// 直接使用固定的client id
return getClientService().loadByClientId(MK_CLIENT_ID);
}
private OAuth2User getUser(OAuth2Request request) {
// 获取用户信息
OAuth2User user = getUserService().loadByUsername(request.getUsername(), request);
// 用户不存在
if (user == null) {
String message = String.format(ExceptionCode.MK_ACCOUNT_NOT_SYNC_SYSTEM.getMessage(), request.getUsername());
throw new OAuth2Exception(ExceptionCode.MK_ACCOUNT_NOT_SYNC_SYSTEM, message);
}
// 校验用户信息
if (!getUserService().validateUser(Objects.requireNonNull(user))) {
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_USER);
}
// 无需校验用户密码
//MK登进来检查是否是默认密码
//MK登录跳过强制修改密码
// user.setDefaultPwdFlag(false);
// 设置客户端信息
user.setClient(client(request));
// 返回用户信息
return user;
}
}
@@ -115,6 +115,7 @@ public class TokenGranterFactory {
case WECHAT_APPLET -> properties.getGranter().getWechatApplet(); case WECHAT_APPLET -> properties.getGranter().getWechatApplet();
case SOCIAL -> properties.getGranter().getSocial(); case SOCIAL -> properties.getGranter().getSocial();
case REGISTER -> properties.getGranter().getRegister(); case REGISTER -> properties.getGranter().getRegister();
case MK -> properties.getGranter().getMk();
default -> true; default -> true;
}; };
} }
@@ -76,7 +76,7 @@ public abstract class AbstractAuthorizationHandler implements AuthorizationHandl
*/ */
@Override @Override
public OAuth2Validation authValidation(OAuth2User user, OAuth2Request request) { public OAuth2Validation authValidation(OAuth2User user, OAuth2Request request) {
if (request.isClientCredentials() || request.isImplicit() || request.isSocial()) { if (request.isClientCredentials() || request.isImplicit() || request.isSocial() || request.isMk()) {
return new OAuth2Validation(); return new OAuth2Validation();
} }
if (Func.hasEmpty(user, user.getUserId())) { if (Func.hasEmpty(user, user.getUserId())) {
@@ -61,7 +61,7 @@ public class OAuth2AuthorizationHandler extends AbstractAuthorizationHandler {
*/ */
@Override @Override
public void authFailure(OAuth2User user, OAuth2Request request, OAuth2Validation validation) { public void authFailure(OAuth2User user, OAuth2Request request, OAuth2Validation validation) {
log.error("用户:{},认证失败,失败原因:{}", user.getAccount(), validation.getMessage());
} }
} }
@@ -65,6 +65,11 @@ public class OAuth2Properties {
*/ */
private String privateKey; private String privateKey;
/**
* sm4密钥
*/
private String sm4SecretKey;
/** /**
* 授权模式 * 授权模式
*/ */
@@ -117,6 +122,10 @@ public class OAuth2Properties {
* 是否开启注册模式 * 是否开启注册模式
*/ */
private Boolean register = true; private Boolean register = true;
/**
* 是否开启 mk oauth模式
*/
private Boolean mk = true;
} }
} }
@@ -410,6 +410,14 @@ public class OAuth2Request {
return SOCIAL.equals(getGrantType()); return SOCIAL.equals(getGrantType());
} }
/**
* 是否mk oauth模式
* @return
*/
public Boolean isMk() {
return MK.equals(getGrantType());
}
/** /**
* 设置租户ID * 设置租户ID
* *
@@ -56,6 +56,9 @@ public class OAuth2ExceptionUtil {
OAUTH2_EXCEPTION.put( OAUTH2_EXCEPTION.put(
ExceptionCode.INVALID_USER, () -> new UserInvalidException(ExceptionCode.INVALID_USER.getMessage(), new Throwable()) ExceptionCode.INVALID_USER, () -> new UserInvalidException(ExceptionCode.INVALID_USER.getMessage(), new Throwable())
); );
OAUTH2_EXCEPTION.put(
ExceptionCode.ACCOUNT_OR_PASSWORD_ERROR, () -> new UserInvalidException(ExceptionCode.ACCOUNT_OR_PASSWORD_ERROR.getMessage())
);
OAUTH2_EXCEPTION.put( OAUTH2_EXCEPTION.put(
ExceptionCode.UNAUTHORIZED_USER, () -> new UserUnauthorizedException(ExceptionCode.UNAUTHORIZED_USER.getMessage(), new Throwable()) ExceptionCode.UNAUTHORIZED_USER, () -> new UserUnauthorizedException(ExceptionCode.UNAUTHORIZED_USER.getMessage(), new Throwable())
); );
@@ -95,6 +98,13 @@ public class OAuth2ExceptionUtil {
OAUTH2_EXCEPTION.put( OAUTH2_EXCEPTION.put(
ExceptionCode.TEMPORARILY_UNAVAILABLE, () -> new OAuth2Exception(ExceptionCode.TEMPORARILY_UNAVAILABLE, ExceptionCode.TEMPORARILY_UNAVAILABLE.getMessage(), new Throwable()) ExceptionCode.TEMPORARILY_UNAVAILABLE, () -> new OAuth2Exception(ExceptionCode.TEMPORARILY_UNAVAILABLE, ExceptionCode.TEMPORARILY_UNAVAILABLE.getMessage(), new Throwable())
); );
put(ExceptionCode.INVALID_MK_CODE);
put(ExceptionCode.MK_ACCOUNT_NOT_FIND);
put(ExceptionCode.MK_ACCOUNT_NOT_SYNC_SYSTEM);
}
private static void put(ExceptionCode exceptionCode) {
OAUTH2_EXCEPTION.put(exceptionCode, () -> new OAuth2Exception(exceptionCode, exceptionCode.getMessage()));
} }
/** /**
@@ -25,6 +25,7 @@
*/ */
package org.springblade.core.cloud.feign; package org.springblade.core.cloud.feign;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import feign.FeignException; import feign.FeignException;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
@@ -85,8 +86,8 @@ public class BladeFeignFallback<T> implements MethodInterceptor {
return JSON.parseObject(body, returnType); return JSON.parseObject(body, returnType);
} }
} }
// 暂时不支持 flux,rx,异步等,返回值不是 R,直接返回 null。 // 暂时不支持 flux,rx,异步等,返回值不是 R(或其子类,如 FR,直接返回 null。
if (R.class != returnType) { if (!R.class.isAssignableFrom(returnType)) {
return null; return null;
} }
// 非 FeignException // 非 FeignException
@@ -103,7 +104,9 @@ public class BladeFeignFallback<T> implements MethodInterceptor {
JsonNode resultNode = JsonUtil.readTree(content); JsonNode resultNode = JsonUtil.readTree(content);
// 判断是否 R 格式 返回体 // 判断是否 R 格式 返回体
if (resultNode.has(CODE)) { if (resultNode.has(CODE)) {
return JsonUtil.getInstance().convertValue(resultNode, R.class); // 支持 R 及其子类(如 FR)的泛型反序列化,保留服务端失败原因
JavaType javaType = JsonUtil.getInstance().getTypeFactory().constructType(method.getGenericReturnType());
return JsonUtil.getInstance().convertValue(resultNode, javaType);
} }
return R.fail(resultNode.toString()); return R.fail(resultNode.toString());
} }
@@ -25,10 +25,12 @@
*/ */
package org.springblade.core.cloud.feign; package org.springblade.core.cloud.feign;
import org.slf4j.MDC;
import org.springblade.core.secure.BladeUser; import org.springblade.core.secure.BladeUser;
import org.springblade.core.secure.utils.AuthUtil; import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.constant.BladeConstant; import org.springblade.core.tool.constant.BladeConstant;
import org.springblade.core.tool.utils.Func; import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.core.tool.utils.ThreadLocalUtil; import org.springblade.core.tool.utils.ThreadLocalUtil;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
@@ -57,6 +59,13 @@ public class BladeFeignRequestInterceptor implements RequestInterceptor {
values.forEach(value -> requestTemplate.header(key, value)) values.forEach(value -> requestTemplate.header(key, value))
); );
} }
if (!requestTemplate.headers().containsKey(BladeConstant.TRACE_ID_HEADER)) {
// 不包含traceId请求头,且traceId不为空,则添加
String traceId = MDC.get(BladeConstant.MDC_TRACE_ID_KEY);
if (StringUtil.isNotBlank(traceId)) {
requestTemplate.header(BladeConstant.TRACE_ID_HEADER, traceId);
}
}
// 如果是 API Key 认证则跳过设置 // 如果是 API Key 认证则跳过设置
if (AuthUtil.isApiKeyRequest()) { if (AuthUtil.isApiKeyRequest()) {
return; return;
@@ -25,6 +25,7 @@
*/ */
package org.springblade.core.context; package org.springblade.core.context;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC; import org.slf4j.MDC;
import org.springblade.core.tool.utils.ThreadLocalUtil; import org.springblade.core.tool.utils.ThreadLocalUtil;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
@@ -37,6 +38,7 @@ import java.util.concurrent.Callable;
* *
* @author L.cm * @author L.cm
*/ */
@Slf4j
public class BladeCallableWrapper<V> implements Callable<V> { public class BladeCallableWrapper<V> implements Callable<V> {
private final Callable<V> delegate; private final Callable<V> delegate;
private final Map<String, Object> tlMap; private final Map<String, Object> tlMap;
@@ -62,6 +64,9 @@ public class BladeCallableWrapper<V> implements Callable<V> {
} }
try { try {
return delegate.call(); return delegate.call();
} catch (Exception e) {
log.error("异步任务执行异常", e);
throw e;
} finally { } finally {
tlMap.clear(); tlMap.clear();
if (mdcMap != null) { if (mdcMap != null) {
@@ -25,6 +25,7 @@
*/ */
package org.springblade.core.context; package org.springblade.core.context;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC; import org.slf4j.MDC;
import org.springblade.core.tool.utils.ThreadLocalUtil; import org.springblade.core.tool.utils.ThreadLocalUtil;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
@@ -36,6 +37,7 @@ import java.util.Map;
* *
* @author L.cm * @author L.cm
*/ */
@Slf4j
public class BladeRunnableWrapper implements Runnable { public class BladeRunnableWrapper implements Runnable {
private final Runnable delegate; private final Runnable delegate;
private final Map<String, Object> tlMap; private final Map<String, Object> tlMap;
@@ -61,6 +63,9 @@ public class BladeRunnableWrapper implements Runnable {
} }
try { try {
delegate.run(); delegate.run();
} catch (Exception e) {
log.error("异步任务执行异常", e);
throw e;
} finally { } finally {
tlMap.clear(); tlMap.clear();
if (mdcMap != null) { if (mdcMap != null) {
+4 -4
View File
@@ -51,11 +51,11 @@
<artifactId>mysql-connector-j</artifactId> <artifactId>mysql-connector-j</artifactId>
</dependency> </dependency>
<!-- Oracle --> <!-- Oracle -->
<dependency> <!-- <dependency>
<groupId>com.oracle</groupId> <groupId>com.oracle</groupId>
<artifactId>ojdbc7</artifactId> <artifactId>ojdbc7</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>-->
<dependency> <dependency>
<groupId>com.oracle.database.jdbc</groupId> <groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc17</artifactId> <artifactId>ojdbc17</artifactId>
@@ -80,11 +80,11 @@
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<!--YashanDB--> <!--YashanDB-->
<dependency> <!-- <dependency>
<groupId>com.yashandb.jdbc</groupId> <groupId>com.yashandb.jdbc</groupId>
<artifactId>yasdb-jdbc</artifactId> <artifactId>yasdb-jdbc</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>-->
<!--KingbaseES--> <!--KingbaseES-->
<dependency> <dependency>
<groupId>cn.com.kingbase</groupId> <groupId>cn.com.kingbase</groupId>
@@ -46,6 +46,20 @@ import java.util.*;
*/ */
public class BladeApplication { public class BladeApplication {
/**
* 是否注入 Blade 默认的 Nacos spring.config.import。
*/
public static final String NACOS_IMPORT_ENABLED_PROPERTY = "blade.launcher.nacos.import.enabled";
/**
* 是否注入 LauncherService 中的 Nacos 系统属性配置。
*/
public static final String NACOS_CONFIG_ENABLED_PROPERTY = "blade.launcher.nacos.config.enabled";
private static final String NACOS_IMPORT_ENABLED_ENV = "BLADE_LAUNCHER_NACOS_IMPORT_ENABLED";
private static final String NACOS_CONFIG_ENABLED_ENV = "BLADE_LAUNCHER_NACOS_CONFIG_ENABLED";
/** /**
* 启动SpringBoot应用,不使用自定义SpringApplicationBuilder * 启动SpringBoot应用,不使用自定义SpringApplicationBuilder
* *
@@ -120,6 +134,40 @@ public class BladeApplication {
return StringUtils.hasText(osName) && !(AppConstant.OS_NAME_LINUX.equalsIgnoreCase(osName)); return StringUtils.hasText(osName) && !(AppConstant.OS_NAME_LINUX.equalsIgnoreCase(osName));
} }
/**
* 是否启用 Blade 默认的 Nacos 配置导入。
*
* @return true 表示启用
*/
public static boolean isNacosImportEnabled() {
return getBoolean(NACOS_IMPORT_ENABLED_PROPERTY, NACOS_IMPORT_ENABLED_ENV, true);
}
/**
* 是否启用 LauncherService 中的 Nacos 系统属性配置。
*
* @return true 表示启用
*/
public static boolean isNacosConfigEnabled() {
return getBoolean(NACOS_CONFIG_ENABLED_PROPERTY, NACOS_CONFIG_ENABLED_ENV, true);
}
/**
* 关闭 Blade 启动阶段的 Nacos 默认配置注入,改由应用自身配置文件声明。
*/
public static void disableNacosLaunchConfig() {
System.setProperty(NACOS_IMPORT_ENABLED_PROPERTY, Boolean.FALSE.toString());
System.setProperty(NACOS_CONFIG_ENABLED_PROPERTY, Boolean.FALSE.toString());
}
private static boolean getBoolean(String propertyName, String envName, boolean defaultValue) {
String value = System.getProperty(propertyName);
if (!StringUtils.hasText(value)) {
value = System.getenv(envName);
}
return StringUtils.hasText(value) ? Boolean.parseBoolean(value) : defaultValue;
}
/** /**
* 配置环境变量 * 配置环境变量
@@ -199,7 +247,9 @@ public class BladeApplication {
defaultProperties.setProperty("spring.sleuth.sampler.percentage", "1.0"); 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.alibaba.seata.tx-service-group", appName.concat(NacosConstant.NACOS_GROUP_SUFFIX));
defaultProperties.setProperty("nacos.logging.default.config.enabled", "false"); defaultProperties.setProperty("nacos.logging.default.config.enabled", "false");
defaultProperties.setProperty("spring.config.import", String.join(",", NacosConstant.dataId(), NacosConstant.dataId(profile), NacosConstant.dataId(appName, profile))); if (isNacosImportEnabled()) {
defaultProperties.setProperty("spring.config.import", String.join(",", NacosConstant.dataId(), NacosConstant.dataId(profile), NacosConstant.dataId(appName, profile)));
}
return defaultProperties; return defaultProperties;
} }
@@ -127,6 +127,17 @@ public interface AppConstant {
*/ */
String APPLICATION_DEMO_NAME = APPLICATION_NAME_PREFIX + "demo"; String APPLICATION_DEMO_NAME = APPLICATION_NAME_PREFIX + "demo";
/**
* 文件模块模块名称
*/
String APPLICATION_FILE_NAME =APPLICATION_NAME_PREFIX +"file";
/**
* 三方服务模块名称
*/
String APPLICATION_OPENAPI_NAME =APPLICATION_NAME_PREFIX +"openapi";
/** /**
* 开发环境 * 开发环境
*/ */
@@ -0,0 +1,221 @@
package org.springblade.core.tool.api;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.NullSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.servlet.http.HttpServletResponse;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.springblade.core.tool.constant.BladeConstant;
import org.springblade.core.tool.utils.ObjectUtil;
import org.springframework.lang.Nullable;
import java.io.Serial;
import java.util.Optional;
/**
* feign 统一API响应结果封装
* @author bfhuange
* @since 2024/11/28
*/
@Getter
@Setter
@ToString
@Schema(description = "返回信息")
@NoArgsConstructor
public class FR<T> extends R<T> {
@Serial
private static final long serialVersionUID = 1L;
@JsonSerialize(nullsUsing = NullSerializer.class)
@Schema(description = "承载数据")
private T data;
private FR(IResultCode resultCode) {
this(resultCode, null, resultCode.getMessage());
}
private FR(IResultCode resultCode, String msg) {
this(resultCode, null, msg);
}
private FR(IResultCode resultCode, T data) {
this(resultCode, data, resultCode.getMessage());
}
private FR(IResultCode resultCode, T data, String msg) {
this(resultCode.getCode(), data, msg);
}
private FR(int code, T data, String msg) {
super(code, data, msg);
this.data = data;
}
public void setData(T data) {
this.data = data;
super.setData(data);
}
/**
* 判断返回是否为成功
*
* @param result Result
* @return 是否成功
*/
public static boolean isSuccess(@Nullable FR<?> result) {
return Optional.ofNullable(result)
.map(x -> ObjectUtil.nullSafeEquals(ResultCode.SUCCESS.getCode(), x.getCode()))
.orElse(Boolean.FALSE);
}
/**
* 判断返回是否为成功
*
* @param result Result
* @return 是否成功
*/
public static boolean isNotSuccess(@Nullable FR<?> result) {
return !FR.isSuccess(result);
}
/**
* 返回R
*
* @param data 数据
* @param <T> T 泛型标记
* @return R
*/
public static <T> FR<T> data(T data) {
return data(data, BladeConstant.DEFAULT_SUCCESS_MESSAGE);
}
/**
* 返回R
*
* @param data 数据
* @param msg 消息
* @param <T> T 泛型标记
* @return R
*/
public static <T> FR<T> data(T data, String msg) {
return data(HttpServletResponse.SC_OK, data, msg);
}
/**
* 返回R
*
* @param code 状态码
* @param data 数据
* @param msg 消息
* @param <T> T 泛型标记
* @return R
*/
public static <T> FR<T> data(int code, T data, String msg) {
return new FR<>(code, data, data == null ? BladeConstant.DEFAULT_NULL_MESSAGE : msg);
}
/**
* 返回R
*
* @param msg 消息
* @param <T> T 泛型标记
* @return R
*/
public static <T> FR<T> success(String msg) {
return new FR<>(ResultCode.SUCCESS, msg);
}
/**
* 返回R
*
* @param resultCode 业务代码
* @param <T> T 泛型标记
* @return R
*/
public static <T> FR<T> success(IResultCode resultCode) {
return new FR<>(resultCode);
}
/**
* 返回R
*
* @param resultCode 业务代码
* @param msg 消息
* @param <T> T 泛型标记
* @return R
*/
public static <T> FR<T> success(IResultCode resultCode, String msg) {
return new FR<>(resultCode, msg);
}
/**
* 返回R
*
* @param msg 消息
* @param <T> T 泛型标记
* @return R
*/
public static <T> FR<T> fail(String msg) {
return new FR<>(ResultCode.FAILURE, msg);
}
/**
* 返回R
*
* @param code 状态码
* @param msg 消息
* @param <T> T 泛型标记
* @return R
*/
public static <T> FR<T> fail(int code, String msg) {
return new FR<>(code, null, msg);
}
/**
* 返回R
*
* @param resultCode 业务代码
* @param <T> T 泛型标记
* @return R
*/
public static <T> FR<T> fail(IResultCode resultCode) {
return new FR<>(resultCode);
}
/**
* 返回R
*
* @param resultCode 业务代码
* @param msg 消息
* @param <T> T 泛型标记
* @return R
*/
public static <T> FR<T> fail(IResultCode resultCode, String msg) {
return new FR<>(resultCode, msg);
}
/**
* 返回R
*
* @param flag 成功状态
* @return R
*/
public static <T> FR<T> status(boolean flag) {
return flag ? success(BladeConstant.DEFAULT_SUCCESS_MESSAGE) : fail(BladeConstant.DEFAULT_FAILURE_MESSAGE);
}
/**
* 从r复制
* @param result
* @return
* @param <T>
*/
public static <T> FR<T> copy(R<T> result) {
return new FR<>(result.getCode(), result.getData(), result.getMsg());
}
}
@@ -79,7 +79,7 @@ public class R<T> implements Serializable {
this(resultCode.getCode(), data, msg); this(resultCode.getCode(), data, msg);
} }
private R(int code, T data, String msg) { protected R(int code, T data, String msg) {
this.code = code; this.code = code;
this.data = data; this.data = data;
this.msg = msg; this.msg = msg;
@@ -171,4 +171,18 @@ public interface BladeConstant {
*/ */
String DEFAULT_UNAUTHORIZED_MESSAGE = "签名认证失败"; String DEFAULT_UNAUTHORIZED_MESSAGE = "签名认证失败";
/**
* mdc trace id key
*/
String MDC_TRACE_ID_KEY = "traceId";
/**
* trace id header
*/
String TRACE_ID_HEADER = "bs-trace-id";
/**
* 默认线程池名称
*/
String DEFAULT_THREAD_EXECUTOR_NAME = "dtpExecutor1";
} }
@@ -30,6 +30,7 @@ import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.annotation.Pointcut;
import org.springblade.core.log.utils.LogTraceUtil; import org.springblade.core.log.utils.LogTraceUtil;
import org.springblade.core.tool.utils.StringUtil;
/** /**
* 为异步方法添加traceId * 为异步方法添加traceId
@@ -45,11 +46,17 @@ public class LogTraceAspect {
@Around("logPointCut()") @Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable { public Object around(ProceedingJoinPoint point) throws Throwable {
boolean clean = false;
try { try {
LogTraceUtil.insert(); if (StringUtil.isBlank(LogTraceUtil.getTraceIdFromMDC())) {
LogTraceUtil.insert();
clean = true;
}
return point.proceed(); return point.proceed();
} finally { } finally {
LogTraceUtil.remove(); if (clean) {
LogTraceUtil.remove();
}
} }
} }
} }
@@ -25,9 +25,13 @@
*/ */
package org.springblade.core.log.filter; package org.springblade.core.log.filter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springblade.core.log.utils.LogTraceUtil; import org.springblade.core.log.utils.LogTraceUtil;
import jakarta.servlet.*; import jakarta.servlet.*;
import org.springblade.core.tool.constant.BladeConstant;
import java.io.IOException; import java.io.IOException;
/** /**
@@ -43,13 +47,18 @@ public class LogTraceFilter implements Filter {
@Override @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
boolean flag = LogTraceUtil.insert(); String traceId = null;
if (request instanceof HttpServletRequest httpServletRequest) {
traceId = httpServletRequest.getHeader(BladeConstant.TRACE_ID_HEADER);
}
traceId = LogTraceUtil.insert(traceId);
try { try {
if (response instanceof HttpServletResponse httpServletResponse) {
httpServletResponse.addHeader(BladeConstant.TRACE_ID_HEADER, traceId);
}
chain.doFilter(request, response); chain.doFilter(request, response);
} finally { } finally {
if (flag) { LogTraceUtil.remove();
LogTraceUtil.remove();
}
} }
} }
@@ -0,0 +1,471 @@
package org.springblade.core.log.utils;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.tool.api.IResultCode;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author bfhuange
* @date 2024/8/27
**/
public class AssertUtils {
/**
* 对象非空
* @param object
* @param message
*/
public static void notNull(Object object, String message) throws ServiceException {
if (object == null) {
throw new ServiceException(message);
}
}
/**
* 对象非空
* @param object
* @param resultCode
*/
public static void notNull(Object object, IResultCode resultCode) throws ServiceException {
if (object == null) {
throw new ServiceException(resultCode);
}
}
/**
* Map非空
* @param map
* @param message
*/
public static void notEmpty(Map<?, ?> map, String message) throws ServiceException {
if (CollectionUtils.isEmpty(map)) {
throw new ServiceException(message);
}
}
/**
* Map非空
* @param map
* @param resultCode
*/
public static void notEmpty(Map<?, ?> map, IResultCode resultCode) throws ServiceException {
if (CollectionUtils.isEmpty(map)) {
throw new ServiceException(resultCode);
}
}
/**
* 列表非空
* @param collection
* @param message
*/
public static void notEmpty(Collection<?> collection, String message) throws ServiceException {
if (CollectionUtils.isEmpty(collection)) {
throw new ServiceException(message);
}
}
/**
* 列表非空
* @param collection
* @param resultCode
*/
public static void notEmpty(Collection<?> collection, IResultCode resultCode) throws ServiceException {
if (CollectionUtils.isEmpty(collection)) {
throw new ServiceException(resultCode);
}
}
/**
* 数组非空
* @param array
* @param message
*/
public static void notEmpty(Object[] array, String message) throws ServiceException {
if (ObjectUtils.isEmpty(array)) {
throw new ServiceException(message);
}
}
/**
* 数组非空
* @param array
* @param resultCode
*/
public static void notEmpty(Object[] array, IResultCode resultCode) throws ServiceException {
if (ObjectUtils.isEmpty(array)) {
throw new ServiceException(resultCode);
}
}
/**
* 字符串非空
* @param value
* @param message
*/
public static void notEmpty(String value, String message) throws ServiceException {
if (StringUtil.hasLength(value)) {
throw new ServiceException(message);
}
}
/**
* 字符串非空
* @param value
* @param resultCode
*/
public static void notEmpty(String value, IResultCode resultCode) throws ServiceException {
if (StringUtil.hasLength(value)) {
throw new ServiceException(resultCode);
}
}
/**
* 字符串去除空格非空
* @param value
* @param message
*/
public static void notBlank(String value, String message) throws ServiceException {
if (StringUtil.isBlank(value)) {
throw new ServiceException(message);
}
}
/**
* 字符串去除空格非空
* @param value
* @param resultCode
*/
public static void notBlank(String value, IResultCode resultCode) throws ServiceException {
if (StringUtil.isBlank(value)) {
throw new ServiceException(resultCode);
}
}
/**
* 获取重复数据
* @param dataList
* @return
* @throws ServiceException
*/
public static List<String> getRepeatData(List<String> dataList) throws ServiceException {
if(CollectionUtils.isEmpty(dataList)) {
return null;
}
//判断是否有重复数据
List<String> distinctDataList = dataList.stream().distinct().toList();
if(dataList.size() == distinctDataList.size()) {
return null;
}
List<String> repeatList = new ArrayList<>();
Map<String,Long> dataMap = dataList.stream().collect(Collectors.groupingBy(p -> p,Collectors.counting()));
//处理重复数据
for(Map.Entry<String,Long> entry:dataMap.entrySet()) {
if(entry.getValue() > 1) {
repeatList.add(entry.getKey());
}
}
return repeatList;
}
/**
* 数据不允许重复
* @param dataList
* @param message
* @throws ServiceException
*/
public static void notRepeat(List<String> dataList,String message) throws ServiceException {
//获取重复数据
List<String> repeatList = getRepeatData(dataList);
//若有重复数据,则校验不通过处理
if(!CollectionUtils.isEmpty(repeatList)) {
String repeatData = String.join(",",repeatList);
throw new ServiceException(String.format(message,repeatData));
}
}
/**
* 字符串相等校验
* @param str1
* @param str2
* @param message
* @throws ServiceException
*/
public static void equals(String str1, String str2, String message) throws ServiceException {
if (!StringUtil.equals(str1,str2)) {
throw new ServiceException(message);
}
}
/**
* 字符串相等校验
* @param str1
* @param str2
* @param resultCode
* @throws ServiceException
*/
public static void equals(String str1, String str2, IResultCode resultCode) throws ServiceException {
if (!StringUtil.equals(str1,str2)) {
throw new ServiceException(resultCode);
}
}
/**
* 数字相等校验
* @param num1
* @param num2
* @param message
* @throws ServiceException
*/
public static void equals(BigDecimal num1, BigDecimal num2, String message) throws ServiceException {
if (num1.compareTo(num2) != 0) {
throw new ServiceException(message);
}
}
/**
* 数字相等校验
* @param num1
* @param num2
* @param resultCode
* @throws ServiceException
*/
public static void equals(BigDecimal num1, BigDecimal num2, IResultCode resultCode) throws ServiceException {
if (num1.compareTo(num2) != 0) {
throw new ServiceException(resultCode);
}
}
/**
* 数字不大于校验
* @param num1
* @param num2
* @param message
* @throws ServiceException
*/
public static void notGreaterThan(BigDecimal num1, BigDecimal num2, String message) throws ServiceException {
if(num1.compareTo(num2) > 0) {
throw new ServiceException(message);
}
}
/**
* 数字不大于校验
* @param num1
* @param num2
* @param resultCode
* @throws ServiceException
*/
public static void notGreaterThan(BigDecimal num1, BigDecimal num2, IResultCode resultCode) throws ServiceException {
if(num1.compareTo(num2) > 0) {
throw new ServiceException(resultCode);
}
}
/**
* 数字不大于校验
* @param num1
* @param num2
* @param message
* @throws ServiceException
*/
public static void notGreaterThan(Integer num1, Integer num2, String message) throws ServiceException {
if(num1 > num2) {
throw new ServiceException(message);
}
}
/**
* 数字不大于校验
* @param num1
* @param num2
* @param resultCode
* @throws ServiceException
*/
public static void notGreaterThan(Integer num1, Integer num2, IResultCode resultCode) throws ServiceException {
if(num1 > num2) {
throw new ServiceException(resultCode);
}
}
/**
* 数字不小于校验
* @param num1
* @param num2
* @param message
* @throws ServiceException
*/
public static void notLessThan(BigDecimal num1, BigDecimal num2, String message) throws ServiceException {
if(num1.compareTo(num2) < 0) {
throw new ServiceException(message);
}
}
/**
* 数字不小于校验
* @param num1
* @param num2
* @param resultCode
* @throws ServiceException
*/
public static void notLessThan(BigDecimal num1, BigDecimal num2, IResultCode resultCode) throws ServiceException {
if(num1.compareTo(num2) < 0) {
throw new ServiceException(resultCode);
}
}
/**
* 表达式为真判断校验
* @param expression
* @param message
* @throws ServiceException
*/
public static void isTrue(boolean expression, String message) throws ServiceException {
if(!expression) {
throw new ServiceException(message);
}
}
/**
* 表达式为真判断校验
* @param expression
* @param resultCode
* @throws ServiceException
*/
public static void isTrue(boolean expression, IResultCode resultCode) throws ServiceException {
if(!expression) {
throw new ServiceException(resultCode);
}
}
/**
* 表达式判断为假校验
* @param expression
* @param message
* @throws ServiceException
*/
public static void isFalse(boolean expression, String message) throws ServiceException {
if(expression) {
throw new ServiceException(message);
}
}
/**
* 表达式判断为假校验
* @param expression
* @param resultCode
* @throws ServiceException
*/
public static void isFalse(boolean expression, IResultCode resultCode) throws ServiceException {
if(expression) {
throw new ServiceException(resultCode);
}
}
/**
* 字符串不相等校验
* @param str1
* @param str2
* @param message
* @throws ServiceException
*/
public static void notEquals(String str1, String str2, String message) throws ServiceException {
if (StringUtil.equals(str1,str2)) {
throw new ServiceException(message);
}
}
/**
* 字符串不相等校验
* @param str1
* @param str2
* @param resultCode
* @throws ServiceException
*/
public static void notEquals(String str1, String str2, IResultCode resultCode) throws ServiceException {
if (StringUtil.equals(str1,str2)) {
throw new ServiceException(resultCode);
}
}
/**
* 对象为空
* @param object
* @param message
*/
public static void isNull(Object object, String message) throws ServiceException {
if (object != null) {
throw new ServiceException(message);
}
}
/**
* 对象为空
* @param object
* @param resultCode
*/
public static void isNull(Object object, IResultCode resultCode) throws ServiceException {
if (object != null) {
throw new ServiceException(resultCode);
}
}
/**
* 对象为数值
* @param value
* @param message
* @throws ServiceException
*/
public static void isInteger(String value,String message) throws ServiceException {
try {
Integer.valueOf(value);
} catch (NumberFormatException e) {
throw new ServiceException(message);
}
}
/**
* 对象为数值
* @param value
* @param resultCode
* @throws ServiceException
*/
public static void isInteger(String value,IResultCode resultCode) throws ServiceException {
try {
Integer.valueOf(value);
} catch (NumberFormatException e) {
throw new ServiceException(resultCode);
}
}
/**
* 列表为空
* @param collection
* @param message
*/
public static void isEmpty(Collection<?> collection, String message) throws ServiceException {
if (!CollectionUtils.isEmpty(collection)) {
throw new ServiceException(message);
}
}
/**
* 列表为空
* @param collection
* @param resultCode
*/
public static void isEmpty(Collection<?> collection, IResultCode resultCode) throws ServiceException {
if (!CollectionUtils.isEmpty(collection)) {
throw new ServiceException(resultCode);
}
}
}
@@ -26,6 +26,7 @@
package org.springblade.core.log.utils; package org.springblade.core.log.utils;
import org.slf4j.MDC; import org.slf4j.MDC;
import org.springblade.core.tool.constant.BladeConstant;
import org.springblade.core.tool.utils.StringUtil; import org.springblade.core.tool.utils.StringUtil;
/** /**
@@ -34,7 +35,6 @@ import org.springblade.core.tool.utils.StringUtil;
* @author Chill * @author Chill
*/ */
public class LogTraceUtil { public class LogTraceUtil {
private static final String UNIQUE_ID = "traceId";
/** /**
* 获取日志追踪id格式 * 获取日志追踪id格式
@@ -43,23 +43,39 @@ public class LogTraceUtil {
return StringUtil.randomUUID(); return StringUtil.randomUUID();
} }
/**
* 从mdc获取日志追踪id
* @return
*/
public static String getTraceIdFromMDC() {
return MDC.get(BladeConstant.MDC_TRACE_ID_KEY);
}
/** /**
* 插入traceId * 插入traceId
*/ */
public static boolean insert() { public static String insert() {
MDC.put(UNIQUE_ID, getTraceId()); return LogTraceUtil.insert(null);
return true;
} }
/** /**
* 移除traceId * 移除traceId
*/ */
public static boolean remove() { public static boolean remove() {
MDC.remove(UNIQUE_ID); MDC.remove(BladeConstant.MDC_TRACE_ID_KEY);
return true; return true;
} }
public static String getTraceIdFromMDC() { /**
return MDC.get(UNIQUE_ID); * 插入traceId
* @param traceId
* @return
*/
public static String insert(String traceId) {
if (traceId == null) {
traceId = getTraceId();
}
MDC.put(BladeConstant.MDC_TRACE_ID_KEY, traceId);
return traceId;
} }
} }
@@ -10,7 +10,7 @@
<conversionRule conversionWord="wEx" class="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/> <conversionRule conversionWord="wEx" class="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
<!-- 彩色日志格式 --> <!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN" <property name="CONSOLE_LOG_PATTERN"
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/> value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr([%X{traceId:-N/A}]){yellow} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %clr([%line]){green} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!-- 控制台输出 --> <!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
@@ -10,7 +10,7 @@
<conversionRule conversionWord="wEx" class="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/> <conversionRule conversionWord="wEx" class="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
<!-- 彩色日志格式 --> <!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN" <property name="CONSOLE_LOG_PATTERN"
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/> value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr([%X{traceId:-N/A}]){yellow} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %clr([%line]){green} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!-- 控制台输出 --> <!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
@@ -10,7 +10,7 @@
<conversionRule conversionWord="wEx" class="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/> <conversionRule conversionWord="wEx" class="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
<!-- 彩色日志格式 --> <!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN" <property name="CONSOLE_LOG_PATTERN"
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/> value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr([%X{traceId:-N/A}]){yellow} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %clr([%line]){green} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!-- 控制台输出 --> <!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
@@ -26,6 +26,7 @@
package org.springblade.core.redis.lock; package org.springblade.core.redis.lock;
import org.redisson.api.RedissonClient;
import org.springblade.core.tool.function.CheckedSupplier; import org.springblade.core.tool.function.CheckedSupplier;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -97,4 +98,10 @@ public interface RedisLockClient {
return lock(lockName, LockType.REENTRANT, waitTime, leaseTime, TimeUnit.SECONDS, supplier); return lock(lockName, LockType.REENTRANT, waitTime, leaseTime, TimeUnit.SECONDS, supplier);
} }
/**
* 获取 RedissonClient
*
* @return RedissonClient
*/
RedissonClient getRedissonClient();
} }
@@ -85,4 +85,9 @@ public class RedisLockClientImpl implements RedisLockClient {
return null; return null;
} }
@Override
public RedissonClient getRedissonClient() {
return redissonClient;
}
} }
+30
View File
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springblade</groupId>
<artifactId>BladeX-Tool</artifactId>
<version>${revision}</version>
</parent>
<artifactId>blade-starter-threadpool</artifactId>
<name>${project.artifactId}</name>
<version>${project.parent.version}</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.dromara.dynamictp</groupId>
<artifactId>dynamic-tp-spring-cloud-starter-nacos</artifactId>
<version>1.1.9.1-3.x</version>
</dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-auto</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,145 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.threadpool.config;
import org.dromara.dynamictp.core.executor.DtpExecutor;
import org.dromara.dynamictp.core.executor.OrderedDtpExecutor;
import org.dromara.dynamictp.core.support.DynamicTp;
import org.dromara.dynamictp.core.support.ThreadPoolBuilder;
import org.dromara.dynamictp.core.support.ThreadPoolCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import static org.dromara.dynamictp.common.em.QueueTypeEnum.MEMORY_SAFE_LINKED_BLOCKING_QUEUE;
import static org.dromara.dynamictp.common.em.RejectedTypeEnum.CALLER_RUNS_POLICY;
/**
* @author Redick01
*/
// @Configuration
public class ThreadPoolConfiguration {
/**
* 通过{@link DynamicTp} 注解定义普通juc线程池,会享受到该框架增强能力,注解名称优先级高于方法名
*
* @return 线程池实例
*/
@DynamicTp("jucThreadPoolExecutor")
@Bean
public ThreadPoolExecutor jucThreadPoolExecutor() {
return (ThreadPoolExecutor) Executors.newFixedThreadPool(1);
}
/**
* 通过{@link DynamicTp} 注解定义spring线程池,会享受到该框架增强能力,注解名称优先级高于方法名
*
* @return 线程池实例
*/
@DynamicTp("threadPoolTaskExecutor")
@Bean
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
return new ThreadPoolTaskExecutor();
}
/**
* 通过{@link ThreadPoolCreator} 快速创建一些简单配置的线程池,使用默认参数
* tips: 建议直接在配置中心配置就行,不用@Bean声明
*
* @return 线程池实例
*/
@Bean
public DtpExecutor dtpExecutor0() {
return ThreadPoolCreator.createDynamicFast("dtpExecutor0");
}
/**
* 通过{@link ThreadPoolBuilder} 设置详细参数创建动态线程池
* tips: 建议直接在配置中心配置就行,不用@Bean声明
* @return 线程池实例
*/
@Bean
public ThreadPoolExecutor dtpExecutor1() {
return ThreadPoolBuilder.newBuilder()
.threadPoolName("dtpExecutor1")
.threadFactory("test-dtp-common")
.corePoolSize(10)
.maximumPoolSize(15)
.keepAliveTime(40)
.timeUnit(TimeUnit.SECONDS)
.workQueue(MEMORY_SAFE_LINKED_BLOCKING_QUEUE.getName(), 2000)
.buildDynamic();
}
/**
* 通过{@link ThreadPoolBuilder} 设置详细参数创建动态线程池
* eager,参考tomcat线程池设计,适用于处理io密集型任务场景,具体参数可以看代码注释
* tips: 建议直接在配置中心配置就行,不用@Bean声明
* @return 线程池实例
*/
@Bean
public DtpExecutor eagerDtpExecutor() {
return ThreadPoolBuilder.newBuilder()
.threadPoolName("eagerDtpExecutor")
.threadFactory("test-eager")
.corePoolSize(2)
.maximumPoolSize(4)
.queueCapacity(2000)
.eager()
.buildDynamic();
}
/**
* 通过{@link ThreadPoolBuilder} 设置详细参数创建动态线程池
* ordered,适用于处理有序任务场景,任务要实现Ordered接口,具体参数可以看代码注释
* tips: 建议直接在配置中心配置就行,不用@Bean声明
* @return 线程池实例
*/
@Bean
public OrderedDtpExecutor orderedDtpExecutor() {
return ThreadPoolBuilder.newBuilder()
.threadPoolName("orderedDtpExecutor")
.threadFactory("test-ordered")
.corePoolSize(4)
.maximumPoolSize(4)
.queueCapacity(2000)
.buildOrdered();
}
/**
* 通过{@link ThreadPoolBuilder} 设置详细参数创建线程池
* scheduled,适用于处理定时任务场景,具体参数可以看代码注释
* tips: 建议直接在配置中心配置就行,不用@Bean声明
* @return 线程池实例
*/
@Bean
public ScheduledExecutorService scheduledDtpExecutor() {
return ThreadPoolBuilder.newBuilder()
.threadPoolName("scheduledDtpExecutor")
.corePoolSize(2)
.threadFactory("test-scheduled")
.rejectedExecutionHandler(CALLER_RUNS_POLICY.getName())
.buildScheduled();
}
}
@@ -0,0 +1,59 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springblade.core.threadpool.wrapper;
import lombok.extern.slf4j.Slf4j;
import org.dromara.dynamictp.core.support.task.wrapper.TaskWrapper;
import org.springblade.core.auto.service.AutoService;
/**
* CustomTaskWrapper related
*
* @author yanhom
* @since 1.1.0
*/
@AutoService(TaskWrapper.class)
@Slf4j
public class CustomTaskWrapper implements TaskWrapper {
@Override
public String name() {
return "custom";
}
@Override
public Runnable wrap(Runnable runnable) {
return new MyRunnable(runnable);
}
public static class MyRunnable implements Runnable {
private final Runnable runnable;
public MyRunnable(Runnable runnable) {
this.runnable = runnable;
}
@Override
public void run() {
log.info("before run");
runnable.run();
log.info("after run");
}
}
}
@@ -0,0 +1,94 @@
# 动态线程池配置文件,建议单独开一个文件放到配置中心,字段详解看readme介绍
spring:
dynamic:
tp:
enabled: true
enabledBanner: true # 是否开启banner打印,默认true
enabledCollect: false # 是否开启监控指标采集,默认false
collectorTypes: micrometer,logging # 监控数据采集器类型(logging | micrometer | internal_logging),默认micrometer
logPath: ${user.home}/dynamic-tp/logs # 监控日志数据路径,默认 ${user.home}/logs
monitorInterval: 5 # 监控时间间隔(报警判断、指标采集),默认5s
platforms: # 通知报警平台配置
- platform: wechat
urlKey: 3a7500-1287-4bd-a798-c5c3d8b69c # 替换
receivers: test1,test2 # 接受人企微名称
- platform: ding
urlKey: f80dad441fcd655438f4a08dcd6a # 替换
secret: SECb5441fa6f375d5b9d21 # 替换,非sign模式可以没有此值
receivers: 15810119805 # 钉钉账号手机号
- platform: lark
urlKey: 0d944ae7-b24a-40 # 替换
receivers: test1,test2 # 接受人飞书名称/openid
tomcatTp: # tomcat web server线程池配置
corePoolSize: 100
maximumPoolSize: 400
keepAliveTime: 60
jettyTp: # jetty web server线程池配置
corePoolSize: 100
maximumPoolSize: 400
undertowTp: # undertow web server线程池配置
corePoolSize: 100
maximumPoolSize: 400
keepAliveTime: 60
hystrixTp: # hystrix 线程池配置
- threadPoolName: hystrix1
corePoolSize: 100
maximumPoolSize: 400
keepAliveTime: 60
dubboTp: # dubbo 线程池配置
- threadPoolName: dubboTp#20880
corePoolSize: 100
maximumPoolSize: 400
keepAliveTime: 60
rocketMqTp: # rocketmq 线程池配置
- threadPoolName: group1#topic1
corePoolSize: 200
maximumPoolSize: 400
keepAliveTime: 60
executors: # 动态线程池配置,都有默认值,采用默认值的可以不配置该项,减少配置量
- threadPoolName: dtpExecutor1
executorType: common # 线程池类型common、eager:适用于io密集型
corePoolSize: 6
maximumPoolSize: 8
queueCapacity: 200
queueType: VariableLinkedBlockingQueue # 任务队列,查看源码QueueTypeEnum枚举类
rejectedHandlerType: CallerRunsPolicy # 拒绝策略,查看RejectedTypeEnum枚举类
keepAliveTime: 50
allowCoreThreadTimeOut: false # 是否允许核心线程池超时
threadNamePrefix: test # 线程名前缀
waitForTasksToCompleteOnShutdown: false # 参考spring线程池设计,优雅关闭线程池
awaitTerminationSeconds: 5 # 单位(s
preStartAllCoreThreads: false # 是否预热所有核心线程,默认false
runTimeout: 200 # 任务执行超时阈值,目前只做告警用,单位(ms)
queueTimeout: 100 # 任务在队列等待超时阈值,目前只做告警用,单位(ms)
taskWrapperNames: ["ttl"] # 任务包装器名称,集成TaskWrapper接口
notifyItems: # 报警项,不配置自动会按默认值配置(变更通知、容量报警、活性报警、拒绝报警、任务超时报警)
- type: capacity # 报警项类型,查看源码 NotifyTypeEnum枚举类
enabled: true
threshold: 80 # 报警阈值
platforms: [ding,wechat] # 可选配置,不配置默认拿上层platforms配置的所以平台
interval: 120 # 报警间隔(单位:s
- type: change
enabled: true
- type: liveness
enabled: true
threshold: 80
- type: reject
enabled: true
threshold: 1
- type: run_timeout
enabled: true
threshold: 1
- type: queue_timeout
enabled: true
threshold: 1
- threadPoolName: orderedDtpExecutor
executorType: ordered
corePoolSize: 4
maximumPoolSize: 6
queueCapacity: 2000
queueType: VariableLinkedBlockingQueue
rejectedHandlerType: CallerRunsPolicy
keepAliveTime: 50
allowCoreThreadTimeOut: false
threadNamePrefix: test
+24 -18
View File
@@ -10,7 +10,7 @@
<properties> <properties>
<!-- BladeX-Tool Version --> <!-- BladeX-Tool Version -->
<revision>4.10.0.RELEASE</revision> <revision>4.10.0.BASE-SNAPSHOT</revision>
<java.version>17</java.version> <java.version>17</java.version>
<maven.plugin.version>3.14.1</maven.plugin.version> <maven.plugin.version>3.14.1</maven.plugin.version>
@@ -120,6 +120,7 @@
<module>blade-starter-trace</module> <module>blade-starter-trace</module>
<module>blade-starter-transaction</module> <module>blade-starter-transaction</module>
<module>blade-starter-xss</module> <module>blade-starter-xss</module>
<module>blade-starter-threadpool</module>
</modules> </modules>
<dependencyManagement> <dependencyManagement>
@@ -941,6 +942,11 @@
<artifactId>hutool-all</artifactId> <artifactId>hutool-all</artifactId>
<version>5.8.27</version> <version>5.8.27</version>
</dependency> </dependency>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-starter-threadpool</artifactId>
<version>${revision}</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
@@ -1059,11 +1065,11 @@
<enabled>false</enabled> <enabled>false</enabled>
</snapshots> </snapshots>
</repository> </repository>
<repository> <!-- <repository>-->
<id>bladex</id> <!-- <id>bladex</id>-->
<name>BladeX Release Repository</name> <!-- <name>BladeX Release Repository</name>-->
<url>https://center.javablade.com/api/packages/blade/maven</url> <!-- <url>https://center.javablade.com/api/packages/blade/maven</url>-->
</repository> <!-- </repository>-->
</repositories> </repositories>
<pluginRepositories> <pluginRepositories>
<pluginRepository> <pluginRepository>
@@ -1076,18 +1082,18 @@
</pluginRepository> </pluginRepository>
</pluginRepositories> </pluginRepositories>
<distributionManagement> <!-- <distributionManagement>-->
<repository> <!-- <repository>-->
<id>bladex</id> <!-- <id>bladex</id>-->
<name>BladeX Release Repository</name> <!-- <name>BladeX Release Repository</name>-->
<url>https://center.javablade.com/api/packages/blade/maven</url> <!-- <url>https://center.javablade.com/api/packages/blade/maven</url>-->
</repository> <!-- </repository>-->
<snapshotRepository> <!-- <snapshotRepository>-->
<id>bladex</id> <!-- <id>bladex</id>-->
<name>BladeX Snapshot Repository</name> <!-- <name>BladeX Snapshot Repository</name>-->
<url>https://center.javablade.com/api/packages/blade/maven</url> <!-- <url>https://center.javablade.com/api/packages/blade/maven</url>-->
</snapshotRepository> <!-- </snapshotRepository>-->
</distributionManagement> <!-- </distributionManagement>-->
<profiles> <profiles>
<profile> <profile>