Initial commit

This commit is contained in:
Eric
2026-01-16 18:51:16 +08:00
commit 98c057de11
280 changed files with 16665 additions and 0 deletions

View File

@@ -0,0 +1,85 @@
<?xml version="1.0"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.lingniu.framework</groupId>
<artifactId>lingniu-framework-dependencies</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../lingniu-framework-dependencies/pom.xml</relativePath>
</parent>
<artifactId>lingniu-framework-plugin-core</artifactId>
<name>${project.artifactId}</name>
<dependencies>
<dependency>
<groupId>cn.lingniu.framework</groupId>
<artifactId>lingniu-framework-plugin-util</artifactId>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
<dependency>
<groupId>org.db4j</groupId>
<artifactId>reflectasm</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>one.util</groupId>
<artifactId>streamex</artifactId>
</dependency>
<dependency>
<groupId>org.jooq</groupId>
<artifactId>jool</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<scope>compile</scope>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,121 @@
package cn.lingniu.framework.plugin.core.base;
import cn.hutool.core.lang.Assert;
import cn.lingniu.framework.plugin.core.exception.ErrorCode;
import cn.lingniu.framework.plugin.core.exception.ServiceException;
import cn.lingniu.framework.plugin.core.exception.enums.GlobalErrorCodeConstants;
import cn.lingniu.framework.plugin.core.exception.util.ServiceExceptionUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import java.io.Serializable;
import java.util.Objects;
/**
* 通用返回
*
* @param <T> 数据泛型
*/
@Data
public class CommonResult<T> implements Serializable {
/**
* 错误码
*
* @see ErrorCode#getCode()
*/
private Integer code;
/**
* 错误提示,用户可阅读
*
* @see ErrorCode#getMsg() ()
*/
private String msg;
/**
* 返回数据
*/
private T data;
/**
* 将传入的 result 对象,转换成另外一个泛型结果的对象
*
* 因为 A 方法返回的 CommonResult 对象,不满足调用其的 B 方法的返回,所以需要进行转换。
*
* @param result 传入的 result 对象
* @param <T> 返回的泛型
* @return 新的 CommonResult 对象
*/
public static <T> CommonResult<T> error(CommonResult<?> result) {
return error(result.getCode(), result.getMsg());
}
public static <T> CommonResult<T> error(Integer code, String message) {
Assert.notEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), code, "code 必须是错误的!");
CommonResult<T> result = new CommonResult<>();
result.code = code;
result.msg = message;
return result;
}
public static <T> CommonResult<T> error(ErrorCode errorCode, Object... params) {
Assert.notEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), errorCode.getCode(), "code 必须是错误的!");
CommonResult<T> result = new CommonResult<>();
result.code = errorCode.getCode();
result.msg = ServiceExceptionUtil.doFormat(errorCode.getCode(), errorCode.getMsg(), params);
return result;
}
public static <T> CommonResult<T> error(ErrorCode errorCode) {
return error(errorCode.getCode(), errorCode.getMsg());
}
public static <T> CommonResult<T> success(T data) {
CommonResult<T> result = new CommonResult<>();
result.code = GlobalErrorCodeConstants.SUCCESS.getCode();
result.data = data;
result.msg = "";
return result;
}
public static boolean isSuccess(Integer code) {
return Objects.equals(code, GlobalErrorCodeConstants.SUCCESS.getCode());
}
@JsonIgnore // 避免 jackson 序列化
public boolean isSuccess() {
return isSuccess(code);
}
@JsonIgnore // 避免 jackson 序列化
public boolean isError() {
return !isSuccess();
}
// ========= 和 Exception 异常体系集成 =========
/**
* 判断是否有异常。如果有,则抛出 {@link ServiceException} 异常
*/
public void checkError() throws ServiceException {
if (isSuccess()) {
return;
}
// 业务异常
throw new ServiceException(code, msg);
}
/**
* 判断是否有异常。如果有,则抛出 {@link ServiceException} 异常
* 如果没有,则返回 {@link #data} 数据
*/
@JsonIgnore // 避免 jackson 序列化
public T getCheckedData() {
checkError();
return data;
}
public static <T> CommonResult<T> error(ServiceException serviceException) {
return error(serviceException.getCode(), serviceException.getMessage());
}
}

View File

@@ -0,0 +1,38 @@
package cn.lingniu.framework.plugin.core.base;
import cn.lingniu.framework.plugin.util.core.ArrayValuable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
/**
* 终端的枚举
*/
@RequiredArgsConstructor
@Getter
public enum TerminalEnum implements ArrayValuable<Integer> {
UNKNOWN(0, "未知"), // 目的:在无法解析到 terminal 时,使用它
WECHAT_MINI_PROGRAM(10, "微信小程序"),
WECHAT_WAP(11, "微信公众号"),
H5(20, "H5 网页"),
APP(31, "手机 App"),
;
public static final Integer[] ARRAYS = Arrays.stream(values()).map(TerminalEnum::getTerminal).toArray(Integer[]::new);
/**
* 终端
*/
private final Integer terminal;
/**
* 终端名
*/
private final String name;
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@@ -0,0 +1,35 @@
package cn.lingniu.framework.plugin.core.base;
/**
* Web 过滤器顺序的枚举类,保证过滤器按照符合我们的预期
*
* 考虑到每个 starter 都需要用到该工具类,所以放到 common 模块下的 enums 包下
*
*/
public interface WebFilterOrderEnum {
int CORS_FILTER = Integer.MIN_VALUE;
int TRACE_FILTER = CORS_FILTER + 1;
int REQUEST_BODY_CACHE_FILTER = Integer.MIN_VALUE + 500;
int API_ENCRYPT_FILTER = REQUEST_BODY_CACHE_FILTER + 1;
// OrderedRequestContextFilter 默认为 -105用于国际化上下文等等
int TENANT_CONTEXT_FILTER = - 104; // 需要保证在 ApiAccessLogFilter 前面
int API_ACCESS_LOG_FILTER = -103; // 需要保证在 RequestBodyCacheFilter 后面
int XSS_FILTER = -102; // 需要保证在 RequestBodyCacheFilter 后面
// Spring Security Filter 默认为 -100可见 org.springframework.boot.autoconfigure.security.SecurityProperties 配置属性类
int TENANT_SECURITY_FILTER = -99; // 需要保证在 Spring Security 过滤器后面
int FLOWABLE_FILTER = -98; // 需要保证在 Spring Security 过滤后面
int DEMO_FILTER = Integer.MAX_VALUE;
}

View File

@@ -0,0 +1,16 @@
package cn.lingniu.framework.plugin.core.config;
import lombok.experimental.UtilityClass;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@UtilityClass
public class CommonConstant {
//Spring 应用名
public static final String SPRING_APP_NAME_KEY = "spring.application.name";
//active profiles
public static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active";
}

View File

@@ -0,0 +1,15 @@
package cn.lingniu.framework.plugin.core.context;
public abstract class ApplicationNameContext {
private static String applicationName;
public static String getApplicationName() {
return applicationName;
}
public static void setApplicationName(String applicationName) {
ApplicationNameContext.applicationName = applicationName;
}
}

View File

@@ -0,0 +1,60 @@
package cn.lingniu.framework.plugin.core.context;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectProvider;import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.SpringBootVersion;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import java.util.Map;
/**
* spring bean 的所有bean存储
*/
@Slf4j
public class SpringBeanApplicationContext implements ApplicationContextAware {
private static ApplicationContext applicationContext = null;
private static final String SPRING_BOOT_VERSION = SpringBootVersion.getVersion();
private static String applicationName;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringBeanApplicationContext.applicationContext = applicationContext;
}
public static Object getBean(String beanName) throws BeansException {
return applicationContext.getBean(beanName);
}
public static <T> T getBean(Class<T> clazz) throws BeansException {
return applicationContext.getBean(clazz);
}
public static String getSpringBootVersion() {
return SPRING_BOOT_VERSION;
}
public static <T> ObjectProvider<T> getBeanProvider(Class<T> clazz) throws BeansException {
return applicationContext.getBeanProvider(clazz);
}
public static <T> T getBean(String beanName, Class<T> clazz) throws BeansException {
return applicationContext.getBean(beanName, clazz);
}
public static <T> Map<String, T> getBeans(Class<T> clazz) throws BeansException {
return applicationContext.getBeansOfType(clazz);
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
}

View File

@@ -0,0 +1,30 @@
package cn.lingniu.framework.plugin.core.exception;
import cn.lingniu.framework.plugin.core.exception.enums.GlobalErrorCodeConstants;
import cn.lingniu.framework.plugin.core.exception.enums.ServiceErrorCodeRange;
import lombok.Data;
/**
* 错误码对象
*
* 全局错误码,占用 [0, 999], 参见 {@link GlobalErrorCodeConstants}
* 业务异常错误码,占用 [1 000 000 000, +∞),参见 {@link ServiceErrorCodeRange}
*/
@Data
public class ErrorCode {
/**
* 错误码
*/
private final Integer code;
/**
* 错误提示
*/
private final String msg;
public ErrorCode(Integer code, String message) {
this.code = code;
this.msg = message;
}
}

View File

@@ -0,0 +1,60 @@
package cn.lingniu.framework.plugin.core.exception;
import cn.lingniu.framework.plugin.core.exception.enums.GlobalErrorCodeConstants;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 服务器异常 Exception
*/
@Data
@EqualsAndHashCode(callSuper = true)
public final class ServerException extends RuntimeException {
/**
* 全局错误码
*
* @see GlobalErrorCodeConstants
*/
private Integer code;
/**
* 错误提示
*/
private String message;
/**
* 空构造方法,避免反序列化问题
*/
public ServerException() {
}
public ServerException(ErrorCode errorCode) {
this.code = errorCode.getCode();
this.message = errorCode.getMsg();
}
public ServerException(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public ServerException setCode(Integer code) {
this.code = code;
return this;
}
@Override
public String getMessage() {
return message;
}
public ServerException setMessage(String message) {
this.message = message;
return this;
}
}

View File

@@ -0,0 +1,60 @@
package cn.lingniu.framework.plugin.core.exception;
import cn.lingniu.framework.plugin.core.exception.enums.ServiceErrorCodeRange;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 业务逻辑异常 Exception
*/
@Data
@EqualsAndHashCode(callSuper = true)
public final class ServiceException extends RuntimeException {
/**
* 业务错误码
*
* @see ServiceErrorCodeRange
*/
private Integer code;
/**
* 错误提示
*/
private String message;
/**
* 空构造方法,避免反序列化问题
*/
public ServiceException() {
}
public ServiceException(ErrorCode errorCode) {
this.code = errorCode.getCode();
this.message = errorCode.getMsg();
}
public ServiceException(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public ServiceException setCode(Integer code) {
this.code = code;
return this;
}
@Override
public String getMessage() {
return message;
}
public ServiceException setMessage(String message) {
this.message = message;
return this;
}
}

View File

@@ -0,0 +1,39 @@
package cn.lingniu.framework.plugin.core.exception.enums;
import cn.lingniu.framework.plugin.core.exception.ErrorCode;
/**
* 全局错误码枚举
* 0-999 系统异常编码保留
*
* 一般情况下,使用 HTTP 响应状态码 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status
* 虽然说HTTP 响应状态码作为业务使用表达能力偏弱,但是使用在系统层面还是非常不错的
* 比较特殊的是,因为之前一直使用 0 作为成功,就不使用 200 啦。
*/
public interface GlobalErrorCodeConstants {
ErrorCode SUCCESS = new ErrorCode(0, "成功");
// ========== 客户端错误段 ==========
ErrorCode BAD_REQUEST = new ErrorCode(400, "请求参数不正确");
ErrorCode UNAUTHORIZED = new ErrorCode(401, "账号未登录");
ErrorCode FORBIDDEN = new ErrorCode(403, "没有该操作权限");
ErrorCode NOT_FOUND = new ErrorCode(404, "请求未找到");
ErrorCode METHOD_NOT_ALLOWED = new ErrorCode(405, "请求方法不正确");
ErrorCode LOCKED = new ErrorCode(423, "请求失败,请稍后重试"); // 并发请求,不允许
ErrorCode TOO_MANY_REQUESTS = new ErrorCode(429, "请求过于频繁,请稍后重试");
ErrorCode TIME_OUT_ERROR = new ErrorCode(430, "请求超时!");
// ========== 服务端错误段 ==========
ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, "系统异常");
ErrorCode BAD_GATE_WAY_ERROR = new ErrorCode(502, "服务端Bad Gateway异常");
// ========== 自定义错误段 ==========
ErrorCode UNKNOWN = new ErrorCode(999, "未知错误");
ErrorCode FEIGN_DECODE_ERROR = new ErrorCode(900, "Feign异常解码错误");
}

View File

@@ -0,0 +1,11 @@
package cn.lingniu.framework.plugin.core.exception.enums;
/**
* todo 业务异常的错误码区间,解决:解决各模块错误码定义,避免重复,在此只声明不做实际使用
* 抽象类,业务可自定义继承
*/
public abstract class ServiceErrorCodeRange {
}

View File

@@ -0,0 +1,76 @@
package cn.lingniu.framework.plugin.core.exception.util;
import cn.lingniu.framework.plugin.core.exception.ErrorCode;
import cn.lingniu.framework.plugin.core.exception.ServiceException;
import cn.lingniu.framework.plugin.core.exception.enums.GlobalErrorCodeConstants;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
/**
* {@link ServiceException} 工具类
*
* 目的在于,格式化异常信息提示。
* 考虑到 String.format 在参数不正确时会报错,因此使用 {} 作为占位符,并使用 {@link #doFormat(int, String, Object...)} 方法来格式化
*/
@Slf4j
public class ServiceExceptionUtil {
// ========== 和 ServiceException 的集成 ==========
public static ServiceException exception(ErrorCode errorCode) {
return exception0(errorCode.getCode(), errorCode.getMsg());
}
public static ServiceException exception(ErrorCode errorCode, Object... params) {
return exception0(errorCode.getCode(), errorCode.getMsg(), params);
}
public static ServiceException exception0(Integer code, String messagePattern, Object... params) {
String message = doFormat(code, messagePattern, params);
return new ServiceException(code, message);
}
public static ServiceException invalidParamException(String messagePattern, Object... params) {
return exception0(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), messagePattern, params);
}
// ========== 格式化方法 ==========
/**
* 将错误编号对应的消息使用 params 进行格式化。
*
* @param code 错误编号
* @param messagePattern 消息模版
* @param params 参数
* @return 格式化后的提示
*/
@VisibleForTesting
public static String doFormat(int code, String messagePattern, Object... params) {
StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);
int i = 0;
int j;
int l;
for (l = 0; l < params.length; l++) {
j = messagePattern.indexOf("{}", i);
if (j == -1) {
log.error("[doFormat][参数过多:错误码({})|错误内容({})|参数({})", code, messagePattern, params);
if (i == 0) {
return messagePattern;
} else {
sbuf.append(messagePattern.substring(i));
return sbuf.toString();
}
} else {
sbuf.append(messagePattern, i, j);
sbuf.append(params[l]);
i = j + 2;
}
}
if (messagePattern.indexOf("{}", i) != -1) {
log.error("[doFormat][参数过少:错误码({})|错误内容({})|参数({})", code, messagePattern, params);
}
sbuf.append(messagePattern.substring(i));
return sbuf.toString();
}
}

View File

@@ -0,0 +1,43 @@
package cn.lingniu.framework.plugin.core.init;
import cn.lingniu.framework.plugin.core.config.CommonConstant;
import cn.lingniu.framework.plugin.core.context.ApplicationNameContext;
import cn.lingniu.framework.plugin.util.validation.ObjectEmptyUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
/**
* 优先级最高
*/
@Configuration
@Slf4j
public class FrameworkSystemInitContextInitializer implements EnvironmentPostProcessor, Ordered {
private static boolean initialized = false;
private final static String APPLICATION_NAME = CommonConstant.SPRING_APP_NAME_KEY;
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
if (initialized) {
return;
}
initialized = true;
String applicationName = environment.getProperty(APPLICATION_NAME, "");
if (ObjectEmptyUtils.isEmpty(applicationName)) {
throw new IllegalArgumentException("[框架异常][" + CommonConstant.SPRING_APP_NAME_KEY + "] is not set-必须配置!");
} else {
ApplicationNameContext.setApplicationName(applicationName);
}
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE - 100000;
}
}

View File

@@ -0,0 +1,16 @@
package cn.lingniu.framework.plugin.core.init.applicationContext;
import cn.lingniu.framework.plugin.core.context.SpringBeanApplicationContext;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class SpringApplicationContextCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return SpringBeanApplicationContext.getApplicationContext() == null;
}
}

View File

@@ -0,0 +1,25 @@
package cn.lingniu.framework.plugin.core.init.applicationContext;
import cn.lingniu.framework.plugin.core.context.SpringBeanApplicationContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
@Configuration
public class SpringApplicationContextConfiguration implements EnvironmentAware {
@Bean
@Conditional(SpringApplicationContextCondition.class)
public SpringBeanApplicationContext springApplicationContext(ApplicationContext applicationContext) {
SpringBeanApplicationContext context = new SpringBeanApplicationContext();
context.setApplicationContext(applicationContext);
return context;
}
@Override
public void setEnvironment(Environment event) {
}
}

View File

@@ -0,0 +1,6 @@
org.springframework.boot.env.EnvironmentPostProcessor=\
cn.lingniu.framework.plugin.core.init.FrameworkSystemInitContextInitializer
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.lingniu.framework.plugin.core.init.applicationContext.SpringApplicationContextConfiguration

View File

@@ -0,0 +1,7 @@
# 框架核心模块
## ApplicationNameContext 初始化
## 异常定义
## 等