Initial project

This commit is contained in:
Timi
2025-07-08 14:34:32 +08:00
parent 271e2ae673
commit c27146aa91
56 changed files with 3050 additions and 80 deletions

View File

@@ -0,0 +1,17 @@
package com.imyeyu.spring.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* AOP 切面日志注解,应用在 Controller 的接口上
*
* @author 夜雨
* @version 2021-07-21 23:34
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AOPLog {
}

View File

@@ -0,0 +1,110 @@
package com.imyeyu.spring.annotation;
import com.imyeyu.java.TimiJava;
import com.imyeyu.spring.TimiSpring;
import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.entity.IDEntity;
import com.imyeyu.utils.Text;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* AOP 切面日志
*
* @author 夜雨
* @version 2021-08-17 16:26
*/
@Aspect
@Component
public class AOPLogInterceptor {
public static final String REQUEST_ID = "TIMI_SPRING_REQUEST_ID";
private static final Logger log = LoggerFactory.getLogger(AOPLogInterceptor.class);
/** 注入注解 */
@Pointcut("@annotation(annotation.com.imyeyu.spring.AOPLog)")
public void logPointCut() {
}
/**
* 执行前
*
* @param joinPoint 切入点
*/
@Before("logPointCut()")
public void doBefore(JoinPoint joinPoint) {
String uuid = Text.tempUUID();
TimiSpring.setSessionAttr(REQUEST_ID, uuid);
log.info("ID: {} Request -> IP: {}, URI: {}", uuid, TimiSpring.getRequestIP(), TimiSpring.getRequest().getRequestURI());
}
/**
* 执行后
*
* @param response 返回内容
* @throws Throwable 异常
*/
@AfterReturning(returning = "response", pointcut = "logPointCut()")
public void doAfterReturning(Object response) throws Throwable {
String msg = "ID: {} Response <- Return.";
if (response instanceof IDEntity<?> entity) {
// 返回实体
msg += entity.getClass().getSimpleName() + "." + entity.getId();
} else if (response instanceof PageResult<?> pageResult) {
// 返回数组
if (pageResult.getList().isEmpty()) {
msg += "PageResult<?> Empty";
} else {
if (pageResult.getList().get(0) == null) {
msg += "PageResult<?>." + pageResult.getList().size();
} else {
msg += "PageResult<" + pageResult.getList().get(0).getClass().getSimpleName() + ">[" + pageResult.getList().size() + "]";
}
}
// 返回数据页
} else if (response instanceof String string) {
// 返回字符串
if (string.length() < 64) {
msg += string;
} else {
msg += string.substring(0, 64) + "..";
}
msg = msg.replaceAll("[\\r\\n]+", "");
} else if (response instanceof Boolean bool) {
// 返回布尔值
msg += bool;
} else if (response instanceof Number number) {
// 返回数字
msg += response.getClass().getSimpleName() + ".[" + number.doubleValue() + "]";
} else {
// 其他对象
if (TimiJava.isNotEmpty(response)) {
msg += response.getClass().getSimpleName();
} else {
msg += "NULL";
}
}
log.info(msg, TimiSpring.getSessionAttr(REQUEST_ID));
}
/**
* 环绕
*
* @param pjp 切入点
* @return 执行返回
* @throws Throwable 异常
*/
@Around("logPointCut()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
return pjp.proceed();
}
}

View File

@@ -0,0 +1,46 @@
package com.imyeyu.spring.annotation;
import org.springframework.stereotype.Component;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 实体注解Component 别名,只是为了在实体类注入服务接口(如果实体需要注入服务,需要这个类注解)
* <pre>
*
* &#64;Entity
* &#64;NoArgsConstructor // 需要个空的构造方法让 MyBatis 正常实例化
* public class Entity {
*
* &#64;Transient
* private transient static Service service;
*
* // 通过构造方法注入
* &#64;Autowired
* public Entity(Service service) {
* Entity.service = service;
* }
* }
*
* </pre>
*
* @author 夜雨
* @version 2021-08-18 16:31
*/
@Component
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Entity {
/**
* 设置控制反转 ID
*
* @return 控制反转 ID
*/
String value() default "";
}

View File

@@ -0,0 +1,17 @@
package com.imyeyu.spring.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 忽略全局返回处理器注解
*
* @author 夜雨
* @version 2023-07-16 10:58
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreGlobalReturn {
}

View File

@@ -0,0 +1,36 @@
package com.imyeyu.spring.annotation;
import org.springframework.stereotype.Component;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 应用在 Controller 的接口上
*
* <p>{@link #lifeCycle()} 生命周期内(秒)限制访问 {@link #value()} 次
*
* @author 夜雨
* @version 2021-08-16 17:57
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface RequestRateLimit {
/**
* 生命周期内访问限制,默认 10 秒内 10 次
*
* @return 生命周期内限制访问次数
*/
int value() default 10;
/**
* 生命周期
*
* @return 生命周期秒数
*/
int lifeCycle() default 10;
}

View File

@@ -0,0 +1,56 @@
package com.imyeyu.spring.annotation;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.lang.NonNull;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
/**
* 抽象访问频率限制,具体子类实现
*
* @author 夜雨
* @version 2021-08-16 18:07
*/
public abstract class RequestRateLimitAbstractInterceptor implements HandlerInterceptor {
protected String buildId(HandlerMethod handlerMethod) {
return handlerMethod.getMethod().getDeclaringClass().getSimpleName() + "." + handlerMethod.getMethod().getName();
}
/**
* 处理前
*
* @param req 请求
* @param resp 返回
* @param handler 处理方法
* @return true 为通过
*/
@Override
public boolean preHandle(@NonNull HttpServletRequest req,
@NonNull HttpServletResponse resp,
@NonNull Object handler) {
// 方法注解
if (handler instanceof HandlerMethod handlerMethod) {
RequestRateLimit requestRateLimit = handlerMethod.getMethodAnnotation(RequestRateLimit.class);
if (requestRateLimit == null) {
return true;
}
// 频率限制
return beforeRun(req, resp, buildId(handlerMethod), requestRateLimit.lifeCycle(), requestRateLimit.value());
}
return true;
}
/**
* 接口访问前触发
*
* @param req 请求
* @param resp 返回
* @param id 方法
* @param cycle 生命周期
* @param limit 访问限制
* @return true 为通过
*/
public abstract boolean beforeRun(HttpServletRequest req, HttpServletResponse resp, String id, int cycle, int limit);
}

View File

@@ -0,0 +1,20 @@
package com.imyeyu.spring.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 单字段 Json 数据体
*
* @author 夜雨
* @version 2023-08-09 10:36
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestSingleParam {
}

View File

@@ -0,0 +1,53 @@
package com.imyeyu.spring.annotation;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import jakarta.annotation.Nonnull;
import jakarta.servlet.http.HttpServletRequest;
import com.imyeyu.io.IO;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
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;
@Component
public class RequestSingleParamResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestSingleParam.class);
}
@Override
public Object resolveArgument(@Nonnull MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request == null) {
throw new TimiException(TimiCode.REQUEST_BAD, "request illegal");
}
JsonElement element = JsonParser.parseString(IO.toString(request.getInputStream()));
if (!element.isJsonObject()) {
throw new TimiException(TimiCode.ARG_BAD, "not json object");
}
JsonObject object = element.getAsJsonObject();
String parameterName = parameter.getParameterName();
if (!object.has(parameterName)) {
throw new TimiException(TimiCode.ARG_MISS, "not found " + parameterName + " param");
}
JsonElement el = object.get(parameterName);
if (parameter.getParameterType().isAssignableFrom(Long.class)) {
return el.getAsLong();
}
if (parameter.getParameterType().isAssignableFrom(Integer.class)) {
return el.getAsInt();
}
if (parameter.getParameterType().isAssignableFrom(String.class)) {
return el.getAsString();
}
throw new TimiException(TimiCode.ERROR, "not support parameter type");
}
}

View File

@@ -0,0 +1,22 @@
package com.imyeyu.spring.annotation;
import org.springframework.stereotype.Component;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 应用在 Controller 接口上
*
* <p>需要验证令牌,只验证该令牌是否有效,不验证数据和令牌的关系
*
* @author 夜雨
* @version 2021-08-16 17:58
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface RequiredToken {
}

View File

@@ -0,0 +1,56 @@
package com.imyeyu.spring.annotation;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.lang.NonNull;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import java.lang.annotation.Annotation;
/**
* 抽象验证令牌
*
* @author 夜雨
* @version 2021-08-16 18:07
*/
public abstract class RequiredTokenAbstractInterceptor<A extends Annotation> implements HandlerInterceptor {
private final Class<A> annotation;
public RequiredTokenAbstractInterceptor(Class<A> annotation) {
this.annotation = annotation;
}
/**
* 处理前
*
* @param req 请求
* @param resp 返回
* @param handler 处理方法
* @return true 为通过
*/
@Override
public boolean preHandle(@NonNull HttpServletRequest req,
@NonNull HttpServletResponse resp,
@NonNull Object handler) {
// 方法注解
if (handler instanceof HandlerMethod handlerMethod) {
A requiredTokenAnnotation = handlerMethod.getMethodAnnotation(annotation);
if (requiredTokenAnnotation == null) {
return true;
}
return beforeRun(req, resp);
}
return true;
}
/**
* 访问前(通过 Token 限制)
*
* @param req 请求
* @param resp 返回
* @return true 为通过
*/
protected abstract boolean beforeRun(HttpServletRequest req, HttpServletResponse resp);
}

View File

@@ -0,0 +1,2 @@
/** 注解和注解实现 */
package com.imyeyu.spring.annotation;

View File

@@ -0,0 +1,26 @@
package com.imyeyu.spring.annotation.table;
import com.imyeyu.spring.util.SQLProvider;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自动生成 UUID在 {@link SQLProvider} 代理器中的方法会对此注解字段自动生成 UUID
*
* @author 夜雨
* @since 2025-02-05 23:44
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface AutoUUID {
/**
* 是否全大写 UUID
*
* @return true 为使用全大写 UUID
*/
boolean upper() default false;
}

View File

@@ -0,0 +1,21 @@
package com.imyeyu.spring.annotation.table;
import com.imyeyu.spring.util.SQLProvider;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 指定列名,在 {@link SQLProvider} 代理器中的方法无法简单将字段转为数据库列名时,使用此注解指定
*
* @author 夜雨
* @since 2025-01-29 09:53
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
String value();
}

View File

@@ -0,0 +1,25 @@
package com.imyeyu.spring.annotation.table;
import com.imyeyu.spring.util.SQLProvider;
import org.apache.ibatis.builder.annotation.ProviderContext;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 标记为 ID 字段,在 {@link SQLProvider} 代理器中的方法使用此字段进行以下操作
* <ul>
* <li>{@link SQLProvider#select(ProviderContext, Object)}</li>
* <li>{@link SQLProvider#delete(ProviderContext, Object)}</li>
* <li>{@link SQLProvider#destroy(ProviderContext, Object)}</li>
* </ul>
*
* @author 夜雨
* @since 2025-01-29 09:54
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Id {
}

View File

@@ -0,0 +1,21 @@
package com.imyeyu.spring.annotation.table;
import com.imyeyu.spring.util.SQLProvider;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 指定表名,在 {@link SQLProvider} 代理器中的方法无法简单将实体类名转为数据库表名时,使用此注解指定
*
* @author 夜雨
* @since 2025-01-29 09:53
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Table {
String value();
}

View File

@@ -0,0 +1,16 @@
package com.imyeyu.spring.annotation.table;
import com.imyeyu.spring.util.SQLProvider;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* 忽略字段或表示非表实体,在 {@link SQLProvider} 代理器中的方法无需处理该字段时,使用此注解标记,不标记的字段均视为映射数据库列
*
* @author 夜雨
* @since 2025-02-06 17:46
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface Transient {
}