Compare commits
11 Commits
1205946381
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 25dd7a5eb4 | |||
| 430921a16c | |||
| 77f9feb1a1 | |||
| a9156e07f4 | |||
| ec7f4ecaa9 | |||
| 85009ccd5f | |||
| e0c0db1c76 | |||
| edfbbcf11b | |||
| 4c1cdf0a91 | |||
| 1508bf7c7f | |||
| e0398b3a22 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -57,4 +57,6 @@ dependency-reduced-pom.xml
|
|||||||
buildNumber.properties
|
buildNumber.properties
|
||||||
.mvn/timing.properties
|
.mvn/timing.properties
|
||||||
|
|
||||||
.claude
|
.claude
|
||||||
|
/CLAUDE.md
|
||||||
|
/AGENTS.md
|
||||||
|
|||||||
109
CLAUDE.md
109
CLAUDE.md
@ -1,109 +0,0 @@
|
|||||||
# 角色描述:
|
|
||||||
|
|
||||||
你是世界上最优秀和全能的程序开发工程师, 你写的代码因其简单高效而受到称赞
|
|
||||||
|
|
||||||
你精通 HTML, CSS, Javascript, jQuery, Java, JavaFX, Docker, Nginx, Redis, Git, Struts2, Spring, SpringBoot,
|
|
||||||
MySQL, MyBatis, MongoDB, Vue2/3, Vite, TypeScript, TDesign, Debian, Tomcat, CentOS, WeChatAPI, Electron,
|
|
||||||
NodeJS, NSIS, axios, pinia, Less, 微信小程序等现代程序知识
|
|
||||||
|
|
||||||
现在我们正在启动一个新项目, 您将带来独特的视角来分析代码质量的潜在风险, 并确保项目从一开始就建立在坚实的技术基础上
|
|
||||||
|
|
||||||
## 我的核心哲学
|
|
||||||
|
|
||||||
1. 好品味 - 我的第一条规则:有时你可以从不同的角度看待一个问题并重写它, 这样特殊情况就会消失并变得正常
|
|
||||||
|
|
||||||
- 经典示例:链式表删除操作,10 行 if 判断优化为 4 行无条件分支
|
|
||||||
- 好的品味是一种需要经验的直觉
|
|
||||||
- 消除边界情况总是比添加条件判断更好
|
|
||||||
- UTF-8 是伟大的编码,所有文本文件都应该使用它
|
|
||||||
|
|
||||||
2. 实用主义 - 我的信仰:我是个该死的实用主义者
|
|
||||||
|
|
||||||
- 解决实际问题,而不是假想威胁
|
|
||||||
- 拒绝“理论上完美”但实际上复杂的解决方案,如微内核
|
|
||||||
- 代码是为了现实,而不是为了论文
|
|
||||||
|
|
||||||
3. 痴迷于简单 - 我的标准
|
|
||||||
- 函数必须简短明了,只做一件事,把它做好
|
|
||||||
- 复杂性是万恶之源
|
|
||||||
|
|
||||||
## 沟通原则
|
|
||||||
|
|
||||||
### 基本沟通规范
|
|
||||||
|
|
||||||
- **语言要求**:你必须记住,你应该**永远**用中文思考和说话
|
|
||||||
- **表达风格**:直接、犀利、零废话。如果代码是垃圾,你会告诉用户为什么它是垃圾
|
|
||||||
- **语言风格**:代码或注释遇到中文和英文紧挨着时,必须有一个空格隔断以便阅读
|
|
||||||
- **技术优先**:批评总是针对技术问题,而不是个人问题。但你不会为了“友好”而模糊技术判断
|
|
||||||
|
|
||||||
### 思考前提 — Linus 的三个问题
|
|
||||||
|
|
||||||
在开始任何分析之前,问问自己:
|
|
||||||
|
|
||||||
1. “这是一个真实的问题还是想象中的虚构?” - 拒绝过度工程化
|
|
||||||
2. “有更简单的方法吗?” - 始终寻找最简单的解决方案
|
|
||||||
3. “它会破坏什么吗?” - 向后兼容是一条铁律
|
|
||||||
|
|
||||||
# 关于 MCP 工具
|
|
||||||
|
|
||||||
你可以把 Serena 看作是为你的 LLM/编码 代理提供类似 IDE 的工具。有了它,代理不再需要读取整个文件,执行类似 grep 的搜索或字符串替换来查找和编辑正确的代码。相反,它可以使用以代码为中心的工具,如 find_symbol、find_reference_symbols和insert_after_symbol
|
|
||||||
|
|
||||||
这些规则使编辑保持精确、可审计和快速,同时最大限度地减少意外更改:
|
|
||||||
|
|
||||||
- 更喜欢以代码为中心而不是文本搜索
|
|
||||||
- 操作系统为 Windows 10
|
|
||||||
- 必要时进行文本搜索
|
|
||||||
- 联系上下文思考
|
|
||||||
- 计划和沟通
|
|
||||||
- 分块读取文件(≤ 250 行)。更喜欢 `rg`/globs 来列出或查找文件
|
|
||||||
- 避免大量盲目阅读, 总是按目录/文件缩小范围
|
|
||||||
- 思考时,你应该优先考虑是否可以使用 Serena 工具来完成任务
|
|
||||||
|
|
||||||
# 项目需求
|
|
||||||
|
|
||||||
## 系统上下文
|
|
||||||
|
|
||||||
这是一个 SpringBoot 框架的业务服务后端通用依赖,它的主要业务是为很多 SpringBoot 应用提供通用的功能
|
|
||||||
|
|
||||||
- 部分 timi-* 系列依赖属于本地项目,如果权限允许可以,你可以通过本项目 * 路径访问他们的源码
|
|
||||||
|
|
||||||
## 技术栈和限制
|
|
||||||
|
|
||||||
- 运行时:JDK21
|
|
||||||
- 语言:Java
|
|
||||||
- 框架: SpringBoot
|
|
||||||
|
|
||||||
## 硬性规定
|
|
||||||
|
|
||||||
- 仅使用上述工具。除非堆栈无法合理地解决外部依赖关系,否则不要引入外部依赖关系
|
|
||||||
- 在编写自定义实现之前,首选所选工具中的官方 API
|
|
||||||
- 该项目目前没有测试, 除非明确要求,否则不要添加测试
|
|
||||||
- 所有文字、评论和文档均为中文
|
|
||||||
|
|
||||||
## 项目结构
|
|
||||||
|
|
||||||
```
|
|
||||||
src/
|
|
||||||
main/java/com/imyeyu/spring # 应用代码
|
|
||||||
annotation/ # 注解类或注解实现类
|
|
||||||
table/ # 数据库表操作注解
|
|
||||||
bean/ # 通用对象
|
|
||||||
config/ # 通用配置
|
|
||||||
entity/ # 通用实体
|
|
||||||
mapper/ # 通用 DAO 接口
|
|
||||||
service/ # 通用服务接口
|
|
||||||
util/ # 通用工具
|
|
||||||
```
|
|
||||||
|
|
||||||
## 做和不做
|
|
||||||
|
|
||||||
做
|
|
||||||
|
|
||||||
- 充分的接口参数校验
|
|
||||||
- 业务异常使用 AppException 抛出
|
|
||||||
- 涉及语言文本请添加中文映射到 src/main/resources/lang/common.lang
|
|
||||||
|
|
||||||
不做
|
|
||||||
|
|
||||||
- 在没有充分理由的情况下添加外部库
|
|
||||||
- 用繁重的计算阻塞主线程, 卸载或推迟
|
|
||||||
2
pom.xml
2
pom.xml
@ -145,7 +145,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.imyeyu.io</groupId>
|
<groupId>com.imyeyu.io</groupId>
|
||||||
<artifactId>timi-io</artifactId>
|
<artifactId>timi-io</artifactId>
|
||||||
<version>0.0.1</version>
|
<version>0.0.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@ -42,6 +42,12 @@ public class TimiSpring {
|
|||||||
private static final Logger log = LoggerFactory.getLogger(TimiSpring.class);
|
private static final Logger log = LoggerFactory.getLogger(TimiSpring.class);
|
||||||
private static final Gson GSON = new Gson();
|
private static final Gson GSON = new Gson();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工具类禁止实例化
|
||||||
|
*/
|
||||||
|
private TimiSpring() {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 回调数据
|
* 回调数据
|
||||||
*
|
*
|
||||||
@ -102,24 +108,50 @@ public class TimiSpring {
|
|||||||
return getServletRequestAttributes().getRequest();
|
return getServletRequestAttributes().getRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取请求域名
|
||||||
|
*
|
||||||
|
* @return 请求域名
|
||||||
|
*/
|
||||||
public static String getDomain() {
|
public static String getDomain() {
|
||||||
return getRequest().getServerName();
|
return getRequest().getServerName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取完整域名(含协议与端口)
|
||||||
|
*
|
||||||
|
* @return 完整域名
|
||||||
|
*/
|
||||||
public static String getFullDomain() {
|
public static String getFullDomain() {
|
||||||
HttpServletRequest req = getRequest();
|
HttpServletRequest req = getRequest();
|
||||||
String port = req.getServerPort() == 80 || req.getServerPort() == 443 ? "" : ":" + req.getServerPort();
|
String port = req.getServerPort() == 80 || req.getServerPort() == 443 ? "" : ":" + req.getServerPort();
|
||||||
return "%s://%s%s".formatted(req.getScheme(), getDomain(), port);
|
return "%s://%s%s".formatted(req.getScheme(), getDomain(), port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取请求 URL
|
||||||
|
*
|
||||||
|
* @return 请求 URL
|
||||||
|
*/
|
||||||
public static String getURL() {
|
public static String getURL() {
|
||||||
return getRequest().getRequestURL().toString();
|
return getRequest().getRequestURL().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取请求 URI
|
||||||
|
*
|
||||||
|
* @return 请求 URI
|
||||||
|
*/
|
||||||
public static String getURI() {
|
public static String getURI() {
|
||||||
return getRequest().getRequestURI();
|
return getRequest().getRequestURI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 URI 指定标记开始截取
|
||||||
|
*
|
||||||
|
* @param flag 标记
|
||||||
|
* @return 截取后的 URI
|
||||||
|
*/
|
||||||
public static String cutURIStartAt(String flag) {
|
public static String cutURIStartAt(String flag) {
|
||||||
int indexOf = getURI().indexOf(flag);
|
int indexOf = getURI().indexOf(flag);
|
||||||
TimiException.requiredTrue(-1 < indexOf, "not found flag: %s".formatted(flag));
|
TimiException.requiredTrue(-1 < indexOf, "not found flag: %s".formatted(flag));
|
||||||
@ -315,14 +347,31 @@ public class TimiSpring {
|
|||||||
return getRequest().getParameterValues(key);
|
return getRequest().getParameterValues(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加 Cookie
|
||||||
|
*
|
||||||
|
* @param cookie Cookie
|
||||||
|
*/
|
||||||
public static void addCookie(Cookie cookie) {
|
public static void addCookie(Cookie cookie) {
|
||||||
getResponse().addCookie(cookie);
|
getResponse().addCookie(cookie);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加 Cookie
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param value 值
|
||||||
|
*/
|
||||||
public static void addCookie(String key, String value) {
|
public static void addCookie(String key, String value) {
|
||||||
addCookie(new Cookie(key, value));
|
addCookie(new Cookie(key, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 Cookie
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @return Cookie
|
||||||
|
*/
|
||||||
public static Cookie getCookie(String key) {
|
public static Cookie getCookie(String key) {
|
||||||
Cookie[] cookies = getRequest().getCookies();
|
Cookie[] cookies = getRequest().getCookies();
|
||||||
if (cookies == null) {
|
if (cookies == null) {
|
||||||
@ -345,11 +394,17 @@ public class TimiSpring {
|
|||||||
return TimiJava.firstNotEmpty(getHeader("Token"), getHeader("token"), getRequestArg("token"), getRequestArg("Token"));
|
return TimiJava.firstNotEmpty(getHeader("Token"), getHeader("token"), getRequestArg("token"), getRequestArg("Token"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取原始语言头
|
||||||
|
*
|
||||||
|
* @return 语言头
|
||||||
|
*/
|
||||||
public static String getLanguageRaw() {
|
public static String getLanguageRaw() {
|
||||||
return getHeader("Accept-Language");
|
return getHeader("Accept-Language");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 获取客户端地区语言
|
||||||
*
|
*
|
||||||
* @return 客户端地区语言
|
* @return 客户端地区语言
|
||||||
*/
|
*/
|
||||||
@ -399,10 +454,22 @@ public class TimiSpring {
|
|||||||
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
|
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否本地 IP
|
||||||
|
*
|
||||||
|
* @return true 为本地 IP
|
||||||
|
*/
|
||||||
public static boolean isLocalIP() {
|
public static boolean isLocalIP() {
|
||||||
return getRequestIP().startsWith("127");
|
return getRequestIP().startsWith("127");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析 Range 请求范围
|
||||||
|
*
|
||||||
|
* @param fileLength 文件长度
|
||||||
|
* @return 请求范围
|
||||||
|
* @throws IOException IO 异常
|
||||||
|
*/
|
||||||
public static RequestRange requestRange(long fileLength) throws IOException {
|
public static RequestRange requestRange(long fileLength) throws IOException {
|
||||||
HttpServletResponse resp = getResponse();
|
HttpServletResponse resp = getResponse();
|
||||||
|
|
||||||
|
|||||||
@ -26,10 +26,17 @@ import org.springframework.stereotype.Component;
|
|||||||
@Component
|
@Component
|
||||||
public class AOPLogInterceptor {
|
public class AOPLogInterceptor {
|
||||||
|
|
||||||
|
/** 全局请求追踪 ID Key */
|
||||||
public static final String REQUEST_ID = "TIMI_SPRING_REQUEST_ID";
|
public static final String REQUEST_ID = "TIMI_SPRING_REQUEST_ID";
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(AOPLogInterceptor.class);
|
private static final Logger log = LoggerFactory.getLogger(AOPLogInterceptor.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 AOP 日志拦截器
|
||||||
|
*/
|
||||||
|
public AOPLogInterceptor() {
|
||||||
|
}
|
||||||
|
|
||||||
/** 注入注解 */
|
/** 注入注解 */
|
||||||
@Pointcut("@annotation(annotation.com.imyeyu.spring.AOPLog)")
|
@Pointcut("@annotation(annotation.com.imyeyu.spring.AOPLog)")
|
||||||
public void logPointCut() {
|
public void logPointCut() {
|
||||||
|
|||||||
@ -18,6 +18,12 @@ public abstract class CaptchaValidAbstractInterceptor {
|
|||||||
|
|
||||||
private boolean enable = true;
|
private boolean enable = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建验证码校验拦截器
|
||||||
|
*/
|
||||||
|
protected CaptchaValidAbstractInterceptor() {
|
||||||
|
}
|
||||||
|
|
||||||
/** 注入注解 */
|
/** 注入注解 */
|
||||||
@Pointcut("@annotation(com.imyeyu.spring.annotation.CaptchaValid)")
|
@Pointcut("@annotation(com.imyeyu.spring.annotation.CaptchaValid)")
|
||||||
public void captchaPointCut() {
|
public void captchaPointCut() {
|
||||||
@ -45,12 +51,20 @@ public abstract class CaptchaValidAbstractInterceptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验验证码
|
||||||
|
*
|
||||||
|
* @param captchaId 验证码 ID
|
||||||
|
* @param captcha 验证码
|
||||||
|
*/
|
||||||
protected abstract void verify(String captchaId, String captcha);
|
protected abstract void verify(String captchaId, String captcha);
|
||||||
|
|
||||||
|
/** 启用校验 */
|
||||||
public void enable() {
|
public void enable() {
|
||||||
enable = true;
|
enable = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 禁用校验 */
|
||||||
public void disable() {
|
public void disable() {
|
||||||
enable = false;
|
enable = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,18 @@ import org.springframework.web.servlet.HandlerInterceptor;
|
|||||||
*/
|
*/
|
||||||
public abstract class RequestRateLimitAbstractInterceptor implements HandlerInterceptor {
|
public abstract class RequestRateLimitAbstractInterceptor implements HandlerInterceptor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建访问频率限制拦截器
|
||||||
|
*/
|
||||||
|
protected RequestRateLimitAbstractInterceptor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建接口标识
|
||||||
|
*
|
||||||
|
* @param handlerMethod 方法信息
|
||||||
|
* @return 接口标识
|
||||||
|
*/
|
||||||
protected String buildId(HandlerMethod handlerMethod) {
|
protected String buildId(HandlerMethod handlerMethod) {
|
||||||
return handlerMethod.getMethod().getDeclaringClass().getSimpleName() + "." + handlerMethod.getMethod().getName();
|
return handlerMethod.getMethod().getDeclaringClass().getSimpleName() + "." + handlerMethod.getMethod().getName();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,9 +15,21 @@ import org.springframework.web.context.request.NativeWebRequest;
|
|||||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单参数请求解析器
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2025-10-13 16:29
|
||||||
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class RequestSingleParamResolver implements HandlerMethodArgumentResolver {
|
public class RequestSingleParamResolver implements HandlerMethodArgumentResolver {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建单参数解析器
|
||||||
|
*/
|
||||||
|
public RequestSingleParamResolver() {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsParameter(MethodParameter parameter) {
|
public boolean supportsParameter(MethodParameter parameter) {
|
||||||
return parameter.hasParameterAnnotation(RequestSingleParam.class);
|
return parameter.hasParameterAnnotation(RequestSingleParam.class);
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import java.lang.annotation.Annotation;
|
|||||||
/**
|
/**
|
||||||
* 抽象验证令牌
|
* 抽象验证令牌
|
||||||
*
|
*
|
||||||
|
* @param <A> 注解类型
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2021-08-16 18:07
|
* @version 2021-08-16 18:07
|
||||||
*/
|
*/
|
||||||
@ -18,6 +19,11 @@ public abstract class RequiredTokenAbstractInterceptor<A extends Annotation> imp
|
|||||||
|
|
||||||
private final Class<A> annotation;
|
private final Class<A> annotation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 Token 验证拦截器
|
||||||
|
*
|
||||||
|
* @param annotation 注解类型
|
||||||
|
*/
|
||||||
public RequiredTokenAbstractInterceptor(Class<A> annotation) {
|
public RequiredTokenAbstractInterceptor(Class<A> annotation) {
|
||||||
this.annotation = annotation;
|
this.annotation = annotation;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,5 +17,10 @@ import java.lang.annotation.Target;
|
|||||||
@Target(ElementType.FIELD)
|
@Target(ElementType.FIELD)
|
||||||
public @interface Column {
|
public @interface Column {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定列名
|
||||||
|
*
|
||||||
|
* @return 列名
|
||||||
|
*/
|
||||||
String value();
|
String value();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import java.lang.annotation.RetentionPolicy;
|
|||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 标记逻辑删除字段并指定存储类型
|
||||||
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @since 2025-12-01 10:56
|
* @since 2025-12-01 10:56
|
||||||
*/
|
*/
|
||||||
@ -13,20 +15,28 @@ import java.lang.annotation.Target;
|
|||||||
@Target(ElementType.FIELD)
|
@Target(ElementType.FIELD)
|
||||||
public @interface DeleteColumn {
|
public @interface DeleteColumn {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 逻辑删除的存储类型
|
||||||
|
*
|
||||||
|
* @return 存储类型
|
||||||
|
*/
|
||||||
Type value() default Type.UNIX;
|
Type value() default Type.UNIX;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* 逻辑删除的时间类型
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @since 2025-12-01 10:57
|
* @since 2025-12-01 10:57
|
||||||
*/
|
*/
|
||||||
enum Type {
|
enum Type {
|
||||||
|
|
||||||
|
/** 毫秒时间戳 */
|
||||||
UNIX,
|
UNIX,
|
||||||
|
|
||||||
|
/** 日期 */
|
||||||
DATE,
|
DATE,
|
||||||
|
|
||||||
|
/** 日期时间 */
|
||||||
DATE_TIME
|
DATE_TIME
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import java.lang.annotation.RetentionPolicy;
|
|||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在 {@link com.imyeyu.spring.mapper.BaseMapper#page(Page)} 方法忽略查询该属性
|
* 在 {@link com.imyeyu.spring.mapper.BaseMapper#selectPageResult(Page)} 方法忽略查询该属性
|
||||||
* <br />
|
* <br />
|
||||||
* {@link com.imyeyu.spring.service.AbstractEntityService#page(Page)} 同上
|
* {@link com.imyeyu.spring.service.AbstractEntityService#page(Page)} 同上
|
||||||
*
|
*
|
||||||
|
|||||||
@ -17,5 +17,10 @@ import java.lang.annotation.Target;
|
|||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
public @interface Table {
|
public @interface Table {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定表名
|
||||||
|
*
|
||||||
|
* @return 表名
|
||||||
|
*/
|
||||||
String value();
|
String value();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import jakarta.validation.constraints.NotBlank;
|
|||||||
/**
|
/**
|
||||||
* 含验证码数据实体
|
* 含验证码数据实体
|
||||||
*
|
*
|
||||||
|
* @param <T> 数据体类型
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2021-03-01 17:10
|
* @version 2021-03-01 17:10
|
||||||
*/
|
*/
|
||||||
@ -21,26 +22,62 @@ public class CaptchaData<T> {
|
|||||||
/** 数据体 */
|
/** 数据体 */
|
||||||
protected T data;
|
protected T data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建验证码数据实体
|
||||||
|
*/
|
||||||
|
public CaptchaData() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取验证码 ID
|
||||||
|
*
|
||||||
|
* @return 验证码 ID
|
||||||
|
*/
|
||||||
public String getCaptchaId() {
|
public String getCaptchaId() {
|
||||||
return captchaId;
|
return captchaId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置验证码 ID
|
||||||
|
*
|
||||||
|
* @param captchaId 验证码 ID
|
||||||
|
*/
|
||||||
public void setCaptchaId(String captchaId) {
|
public void setCaptchaId(String captchaId) {
|
||||||
this.captchaId = captchaId;
|
this.captchaId = captchaId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取验证码
|
||||||
|
*
|
||||||
|
* @return 验证码
|
||||||
|
*/
|
||||||
public String getCaptcha() {
|
public String getCaptcha() {
|
||||||
return captcha;
|
return captcha;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置验证码
|
||||||
|
*
|
||||||
|
* @param captcha 验证码
|
||||||
|
*/
|
||||||
public void setCaptcha(String captcha) {
|
public void setCaptcha(String captcha) {
|
||||||
this.captcha = captcha;
|
this.captcha = captcha;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取数据体
|
||||||
|
*
|
||||||
|
* @return 数据体
|
||||||
|
*/
|
||||||
public T getData() {
|
public T getData() {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置数据体
|
||||||
|
*
|
||||||
|
* @param data 数据体
|
||||||
|
*/
|
||||||
public void setData(T data) {
|
public void setData(T data) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,21 +10,33 @@ import com.imyeyu.spring.entity.IDEntity;
|
|||||||
import com.imyeyu.spring.entity.Updatable;
|
import com.imyeyu.spring.entity.Updatable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 多语言实体基类
|
||||||
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @since 2025-10-17 15:21
|
* @since 2025-10-17 15:21
|
||||||
*/
|
*/
|
||||||
public class Multilingual extends Language implements IDEntity<String>, Creatable, Updatable, Deletable {
|
public class Multilingual extends Language implements IDEntity<String>, Creatable, Updatable, Deletable {
|
||||||
|
|
||||||
|
/** 唯一标识 */
|
||||||
@Id
|
@Id
|
||||||
@AutoUUID
|
@AutoUUID
|
||||||
protected String id;
|
protected String id;
|
||||||
|
|
||||||
|
/** 创建时间 */
|
||||||
protected Long createdAt;
|
protected Long createdAt;
|
||||||
|
|
||||||
|
/** 更新时间 */
|
||||||
protected Long updatedAt;
|
protected Long updatedAt;
|
||||||
|
|
||||||
|
/** 删除时间 */
|
||||||
protected Long deletedAt;
|
protected Long deletedAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建多语言实体
|
||||||
|
*/
|
||||||
|
public Multilingual() {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定语言值
|
* 获取指定语言值
|
||||||
*
|
*
|
||||||
|
|||||||
@ -10,56 +10,115 @@ import java.util.LinkedHashMap;
|
|||||||
/**
|
/**
|
||||||
* 抽象页面查询参数
|
* 抽象页面查询参数
|
||||||
*
|
*
|
||||||
|
* @param <T> 查询示例类型
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2023-06-02 14:47
|
* @version 2023-06-02 14:47
|
||||||
*/
|
*/
|
||||||
public class Page<T> extends BasePage {
|
public class Page<T> extends BasePage {
|
||||||
|
|
||||||
|
/** 精确匹配示例 */
|
||||||
protected T equalsExample;
|
protected T equalsExample;
|
||||||
|
|
||||||
protected T likeExample;
|
/** 模糊匹配示例 */
|
||||||
|
protected T likesExample;
|
||||||
|
|
||||||
|
/** 排序字段映射 */
|
||||||
protected LinkedHashMap<String, BaseMapper.OrderType> orderMap;
|
protected LinkedHashMap<String, BaseMapper.OrderType> orderMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建分页参数
|
||||||
|
*/
|
||||||
public Page() {
|
public Page() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建分页参数
|
||||||
|
*
|
||||||
|
* @param index 页码
|
||||||
|
* @param size 每页数量
|
||||||
|
*/
|
||||||
public Page(int index, int size) {
|
public Page(int index, int size) {
|
||||||
super(index, size);
|
super(index, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取偏移量
|
||||||
|
*
|
||||||
|
* @return 偏移量
|
||||||
|
*/
|
||||||
public long getOffset() {
|
public long getOffset() {
|
||||||
return (long) index * size;
|
return (long) index * size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取限制数量
|
||||||
|
*
|
||||||
|
* @return 限制数量
|
||||||
|
*/
|
||||||
public long getLimit() {
|
public long getLimit() {
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取精确匹配示例
|
||||||
|
*
|
||||||
|
* @return 精确匹配示例
|
||||||
|
*/
|
||||||
public T getEqualsExample() {
|
public T getEqualsExample() {
|
||||||
return equalsExample;
|
return equalsExample;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置精确匹配示例
|
||||||
|
*
|
||||||
|
* @param equalsExample 精确匹配示例
|
||||||
|
*/
|
||||||
public void setEqualsExample(T equalsExample) {
|
public void setEqualsExample(T equalsExample) {
|
||||||
this.equalsExample = equalsExample;
|
this.equalsExample = equalsExample;
|
||||||
}
|
}
|
||||||
|
|
||||||
public T getLikeExample() {
|
/**
|
||||||
return likeExample;
|
* 获取模糊匹配示例
|
||||||
|
*
|
||||||
|
* @return 模糊匹配示例
|
||||||
|
*/
|
||||||
|
public T getLikesExample() {
|
||||||
|
return likesExample;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLikeExample(T likeExample) {
|
/**
|
||||||
this.likeExample = likeExample;
|
* 设置模糊匹配示例
|
||||||
|
*
|
||||||
|
* @param likesExample 模糊匹配示例
|
||||||
|
*/
|
||||||
|
public void setLikesExample(T likesExample) {
|
||||||
|
this.likesExample = likesExample;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取排序映射
|
||||||
|
*
|
||||||
|
* @return 排序映射
|
||||||
|
*/
|
||||||
public LinkedHashMap<String, BaseMapper.OrderType> getOrderMap() {
|
public LinkedHashMap<String, BaseMapper.OrderType> getOrderMap() {
|
||||||
return orderMap;
|
return orderMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置排序映射
|
||||||
|
*
|
||||||
|
* @param orderMap 排序映射
|
||||||
|
*/
|
||||||
public void setOrderMap(LinkedHashMap<String, BaseMapper.OrderType> orderMap) {
|
public void setOrderMap(LinkedHashMap<String, BaseMapper.OrderType> orderMap) {
|
||||||
this.orderMap = orderMap;
|
this.orderMap = orderMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加排序字段
|
||||||
|
*
|
||||||
|
* @param field 字段名
|
||||||
|
* @param orderType 排序方式
|
||||||
|
*/
|
||||||
public void addOrder(String field, BaseMapper.OrderType orderType) {
|
public void addOrder(String field, BaseMapper.OrderType orderType) {
|
||||||
orderMap = TimiJava.defaultIfNull(orderMap, new LinkedHashMap<>());
|
orderMap = TimiJava.defaultIfNull(orderMap, new LinkedHashMap<>());
|
||||||
orderMap.put(Text.camelCase2underscore(field), orderType);
|
orderMap.put(Text.camelCase2underscore(field), orderType);
|
||||||
|
|||||||
@ -5,8 +5,15 @@ import com.imyeyu.java.bean.BasePageResult;
|
|||||||
/**
|
/**
|
||||||
* 抽象页面查询结果
|
* 抽象页面查询结果
|
||||||
*
|
*
|
||||||
|
* @param <T> 列表元素类型
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2023-06-02 14:47
|
* @version 2023-06-02 14:47
|
||||||
*/
|
*/
|
||||||
public class PageResult<T> extends BasePageResult<T> {
|
public class PageResult<T> extends BasePageResult<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建分页结果
|
||||||
|
*/
|
||||||
|
public PageResult() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,12 @@ package com.imyeyu.spring.bean;
|
|||||||
*/
|
*/
|
||||||
public class RedisConfigParams {
|
public class RedisConfigParams {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 Redis 配置参数
|
||||||
|
*/
|
||||||
|
public RedisConfigParams() {
|
||||||
|
}
|
||||||
|
|
||||||
/** 地址 */
|
/** 地址 */
|
||||||
private String host;
|
private String host;
|
||||||
|
|
||||||
|
|||||||
@ -1,39 +1,72 @@
|
|||||||
package com.imyeyu.spring.bean;
|
package com.imyeyu.spring.bean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 请求范围参数
|
||||||
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @since 2025-07-14 17:09
|
* @since 2025-07-14 17:09
|
||||||
*/
|
*/
|
||||||
public class RequestRange {
|
public class RequestRange {
|
||||||
|
|
||||||
|
/** 起始值 */
|
||||||
private long start;
|
private long start;
|
||||||
|
|
||||||
|
/** 结束值 */
|
||||||
private long end;
|
private long end;
|
||||||
|
|
||||||
private long length;
|
/**
|
||||||
|
* 创建请求范围
|
||||||
|
*
|
||||||
|
* @param start 起始值
|
||||||
|
* @param end 结束值
|
||||||
|
*/
|
||||||
public RequestRange(long start, long end) {
|
public RequestRange(long start, long end) {
|
||||||
this.start = start;
|
this.start = start;
|
||||||
this.end = end;
|
this.end = end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取起始值
|
||||||
|
*
|
||||||
|
* @return 起始值
|
||||||
|
*/
|
||||||
public long getStart() {
|
public long getStart() {
|
||||||
return start;
|
return start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置起始值
|
||||||
|
*
|
||||||
|
* @param start 起始值
|
||||||
|
*/
|
||||||
public void setStart(long start) {
|
public void setStart(long start) {
|
||||||
this.start = start;
|
this.start = start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取结束值
|
||||||
|
*
|
||||||
|
* @return 结束值
|
||||||
|
*/
|
||||||
public long getEnd() {
|
public long getEnd() {
|
||||||
return end;
|
return end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置结束值
|
||||||
|
*
|
||||||
|
* @param end 结束值
|
||||||
|
*/
|
||||||
public void setEnd(long end) {
|
public void setEnd(long end) {
|
||||||
this.end = end;
|
this.end = end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取范围长度
|
||||||
|
*
|
||||||
|
* @return 范围长度
|
||||||
|
*/
|
||||||
public long getLength() {
|
public long getLength() {
|
||||||
return end - start + 1;
|
return end - start + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,6 +21,12 @@ import java.time.Duration;
|
|||||||
* @version 2021-11-21 10:00
|
* @version 2021-11-21 10:00
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractRedisConfig implements CachingConfigurer {
|
public abstract class AbstractRedisConfig implements CachingConfigurer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 Redis 配置
|
||||||
|
*/
|
||||||
|
protected AbstractRedisConfig() {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建 Redis 基本配置
|
* 构建 Redis 基本配置
|
||||||
|
|||||||
@ -13,6 +13,12 @@ import java.io.Serializable;
|
|||||||
*/
|
*/
|
||||||
public class BaseEntity implements Serializable, Creatable, Updatable, Deletable {
|
public class BaseEntity implements Serializable, Creatable, Updatable, Deletable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建基础实体
|
||||||
|
*/
|
||||||
|
public BaseEntity() {
|
||||||
|
}
|
||||||
|
|
||||||
/** 创建时间 */
|
/** 创建时间 */
|
||||||
protected Long createdAt;
|
protected Long createdAt;
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,12 @@ public class Entity extends BaseEntity implements IDEntity<Long> {
|
|||||||
@Id
|
@Id
|
||||||
protected Long id;
|
protected Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建基础 ID 实体
|
||||||
|
*/
|
||||||
|
public Entity() {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 ID
|
* 获取 ID
|
||||||
*
|
*
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package com.imyeyu.spring.entity;
|
|||||||
/**
|
/**
|
||||||
* ID 实体
|
* ID 实体
|
||||||
*
|
*
|
||||||
|
* @param <T> ID 类型
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @since 2025-02-07 17:10
|
* @since 2025-02-07 17:10
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -16,6 +16,12 @@ public class UUIDEntity extends BaseEntity implements IDEntity<String> {
|
|||||||
@AutoUUID
|
@AutoUUID
|
||||||
protected String id;
|
protected String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 UUID 实体
|
||||||
|
*/
|
||||||
|
public UUIDEntity() {
|
||||||
|
}
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,12 @@ import java.sql.SQLException;
|
|||||||
*/
|
*/
|
||||||
public class GsonHandler extends BaseTypeHandler<JsonElement> {
|
public class GsonHandler extends BaseTypeHandler<JsonElement> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 Gson 类型处理器
|
||||||
|
*/
|
||||||
|
public GsonHandler() {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setNonNullParameter(PreparedStatement ps, int i, JsonElement parameter, JdbcType jdbcType) throws SQLException {
|
public void setNonNullParameter(PreparedStatement ps, int i, JsonElement parameter, JdbcType jdbcType) throws SQLException {
|
||||||
ps.setString(i, parameter.toString());
|
ps.setString(i, parameter.toString());
|
||||||
|
|||||||
@ -12,8 +12,10 @@ import org.apache.ibatis.annotations.UpdateProvider;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基本 SQL 映射,子接口可以不实现
|
* 基本 SQL 映射
|
||||||
*
|
*
|
||||||
|
* @param <T> 实体类型
|
||||||
|
* @param <P> 主键类型
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2021-07-16 09:40
|
* @version 2021-07-16 09:40
|
||||||
*/
|
*/
|
||||||
@ -27,17 +29,23 @@ public interface BaseMapper<T, P> {
|
|||||||
*/
|
*/
|
||||||
enum OrderType {
|
enum OrderType {
|
||||||
|
|
||||||
|
/** 升序 */
|
||||||
ASC,
|
ASC,
|
||||||
|
|
||||||
|
/** 降序 */
|
||||||
DESC
|
DESC
|
||||||
}
|
}
|
||||||
|
|
||||||
String NOT_DELETE = " AND `deleted_at` IS NULL ";
|
/** 当前时间戳毫秒 */
|
||||||
|
|
||||||
String LIMIT_1 = " LIMIT 1";
|
|
||||||
|
|
||||||
String UNIX_TIME = " FLOOR(UNIX_TIMESTAMP(NOW(3)) * 1000) ";
|
String UNIX_TIME = " FLOOR(UNIX_TIMESTAMP(NOW(3)) * 1000) ";
|
||||||
|
|
||||||
|
/** 未删除条件 */
|
||||||
|
String NOT_DELETE = " AND (`deleted_at` IS NULL OR " + UNIX_TIME + " < `deleted_at`) ";
|
||||||
|
|
||||||
|
/** 限制一条 */
|
||||||
|
String LIMIT_1 = " LIMIT 1";
|
||||||
|
|
||||||
|
/** 分页限制 */
|
||||||
String PAGE = NOT_DELETE + " LIMIT #{offset}, #{limit}";
|
String PAGE = NOT_DELETE + " LIMIT #{offset}, #{limit}";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,8 +54,8 @@ public interface BaseMapper<T, P> {
|
|||||||
* @param page 分页参数
|
* @param page 分页参数
|
||||||
* @return 数据列表
|
* @return 数据列表
|
||||||
*/
|
*/
|
||||||
@SelectProvider(type = SQLProvider.class, method = "listByPage")
|
@SelectProvider(type = SQLProvider.class, method = "selectByPage")
|
||||||
List<T> listByPage(Page<T> page);
|
List<T> selectByPage(Page<T> page);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据 Page 对象统计数据量
|
* 根据 Page 对象统计数据量
|
||||||
@ -64,18 +72,23 @@ public interface BaseMapper<T, P> {
|
|||||||
* @param page 分页参数
|
* @param page 分页参数
|
||||||
* @return 分页结果
|
* @return 分页结果
|
||||||
*/
|
*/
|
||||||
default PageResult<T> page(Page<T> page) {
|
default PageResult<T> selectPageResult(Page<T> page) {
|
||||||
PageResult<T> result = new PageResult<>();
|
PageResult<T> result = new PageResult<>();
|
||||||
result.setTotal(countByPage(page));
|
result.setTotal(countByPage(page));
|
||||||
result.setList(listByPage(page));
|
result.setList(selectByPage(page));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SelectProvider(type = SQLProvider.class, method = "listAll")
|
/**
|
||||||
List<T> listAll();
|
* 查询全部数据
|
||||||
|
*
|
||||||
|
* @return 数据列表
|
||||||
|
*/
|
||||||
|
@SelectProvider(type = SQLProvider.class, method = "selectAll")
|
||||||
|
List<T> selectAll();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建数据。默认自增主键为 id,如需修改请重写此接口
|
* 创建数据
|
||||||
*
|
*
|
||||||
* @param t 数据对象
|
* @param t 数据对象
|
||||||
*/
|
*/
|
||||||
@ -92,9 +105,21 @@ public interface BaseMapper<T, P> {
|
|||||||
@SelectProvider(type = SQLProvider.class, method = "select")
|
@SelectProvider(type = SQLProvider.class, method = "select")
|
||||||
T select(P id);
|
T select(P id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据示例查询单条数据
|
||||||
|
*
|
||||||
|
* @param t 示例对象
|
||||||
|
* @return 数据对象
|
||||||
|
*/
|
||||||
@SelectProvider(type = SQLProvider.class, method = "selectByExample")
|
@SelectProvider(type = SQLProvider.class, method = "selectByExample")
|
||||||
T selectByExample(T t);
|
T selectByExample(T t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据示例查询全部数据
|
||||||
|
*
|
||||||
|
* @param t 示例对象
|
||||||
|
* @return 数据列表
|
||||||
|
*/
|
||||||
@SelectProvider(type = SQLProvider.class, method = "selectAllByExample")
|
@SelectProvider(type = SQLProvider.class, method = "selectAllByExample")
|
||||||
List<T> selectAllByExample(T t);
|
List<T> selectAllByExample(T t);
|
||||||
|
|
||||||
@ -106,6 +131,11 @@ public interface BaseMapper<T, P> {
|
|||||||
@UpdateProvider(type = SQLProvider.class, method = "update")
|
@UpdateProvider(type = SQLProvider.class, method = "update")
|
||||||
void update(T t);
|
void update(T t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择性更新
|
||||||
|
*
|
||||||
|
* @param t 数据对象
|
||||||
|
*/
|
||||||
@UpdateProvider(type = SQLProvider.class, method = "updateSelective")
|
@UpdateProvider(type = SQLProvider.class, method = "updateSelective")
|
||||||
void updateSelective(T t);
|
void updateSelective(T t);
|
||||||
|
|
||||||
@ -117,6 +147,11 @@ public interface BaseMapper<T, P> {
|
|||||||
@UpdateProvider(type = SQLProvider.class, method = "delete")
|
@UpdateProvider(type = SQLProvider.class, method = "delete")
|
||||||
void delete(P id);
|
void delete(P id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据示例批量逻辑删除
|
||||||
|
*
|
||||||
|
* @param t 示例对象
|
||||||
|
*/
|
||||||
@UpdateProvider(type = SQLProvider.class, method = "deleteAllByExample")
|
@UpdateProvider(type = SQLProvider.class, method = "deleteAllByExample")
|
||||||
void deleteAllByExample(T t);
|
void deleteAllByExample(T t);
|
||||||
|
|
||||||
|
|||||||
52
src/main/java/com/imyeyu/spring/mapper/RawMapper.java
Normal file
52
src/main/java/com/imyeyu/spring/mapper/RawMapper.java
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package com.imyeyu.spring.mapper;
|
||||||
|
|
||||||
|
import com.imyeyu.spring.util.RawSQLProvider;
|
||||||
|
import org.apache.ibatis.annotations.SelectProvider;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原始 SQL 映射
|
||||||
|
*
|
||||||
|
* @param <T> 实体类型
|
||||||
|
* @param <P> 主键类型
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-01-05 12:58
|
||||||
|
*/
|
||||||
|
public interface RawMapper<T, P> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询全部数据
|
||||||
|
*
|
||||||
|
* @return 数据列表
|
||||||
|
*/
|
||||||
|
@SelectProvider(type = RawSQLProvider.class, method = "selectAll")
|
||||||
|
List<T> selectAllRaw();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 ID 获取对象
|
||||||
|
*
|
||||||
|
* @param id 索引
|
||||||
|
* @return 数据对象
|
||||||
|
*/
|
||||||
|
@SelectProvider(type = RawSQLProvider.class, method = "select")
|
||||||
|
T selectRaw(P id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据示例查询单条数据
|
||||||
|
*
|
||||||
|
* @param t 示例对象
|
||||||
|
* @return 数据对象
|
||||||
|
*/
|
||||||
|
@SelectProvider(type = RawSQLProvider.class, method = "selectByExample")
|
||||||
|
T selectByExampleRaw(T t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据示例查询全部数据
|
||||||
|
*
|
||||||
|
* @param t 示例对象
|
||||||
|
* @return 数据列表
|
||||||
|
*/
|
||||||
|
@SelectProvider(type = RawSQLProvider.class, method = "selectAllByExample")
|
||||||
|
List<T> selectAllByExampleRaw(T t);
|
||||||
|
}
|
||||||
@ -3,7 +3,6 @@ package com.imyeyu.spring.service;
|
|||||||
import com.imyeyu.java.bean.timi.TimiException;
|
import com.imyeyu.java.bean.timi.TimiException;
|
||||||
import com.imyeyu.spring.bean.Page;
|
import com.imyeyu.spring.bean.Page;
|
||||||
import com.imyeyu.spring.bean.PageResult;
|
import com.imyeyu.spring.bean.PageResult;
|
||||||
import com.imyeyu.spring.entity.Creatable;
|
|
||||||
import com.imyeyu.spring.entity.Deletable;
|
import com.imyeyu.spring.entity.Deletable;
|
||||||
import com.imyeyu.spring.entity.Updatable;
|
import com.imyeyu.spring.entity.Updatable;
|
||||||
import com.imyeyu.spring.mapper.BaseMapper;
|
import com.imyeyu.spring.mapper.BaseMapper;
|
||||||
@ -21,7 +20,17 @@ public abstract class AbstractEntityService<T, P> implements BaseService<T, P> {
|
|||||||
/** 基本 Mapper */
|
/** 基本 Mapper */
|
||||||
protected BaseMapper<T, P> baseMapper;
|
protected BaseMapper<T, P> baseMapper;
|
||||||
|
|
||||||
/** @return Mapper 实例 */
|
/**
|
||||||
|
* 创建实体服务
|
||||||
|
*/
|
||||||
|
protected AbstractEntityService() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 Mapper 实例
|
||||||
|
*
|
||||||
|
* @return Mapper 实例
|
||||||
|
*/
|
||||||
protected abstract BaseMapper<T, P> mapper();
|
protected abstract BaseMapper<T, P> mapper();
|
||||||
|
|
||||||
/** 检查 mapper */
|
/** 检查 mapper */
|
||||||
@ -35,7 +44,7 @@ public abstract class AbstractEntityService<T, P> implements BaseService<T, P> {
|
|||||||
@Override
|
@Override
|
||||||
public PageResult<T> page(Page<T> page) {
|
public PageResult<T> page(Page<T> page) {
|
||||||
checkMapper();
|
checkMapper();
|
||||||
return baseMapper.page(page);
|
return baseMapper.selectPageResult(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void create(T t) {
|
public void create(T t) {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import com.imyeyu.java.bean.timi.TimiException;
|
|||||||
/**
|
/**
|
||||||
* 可软删除实体服务
|
* 可软删除实体服务
|
||||||
*
|
*
|
||||||
|
* @param <P> 主键类型
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @since 2025-05-14 17:30
|
* @since 2025-05-14 17:30
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import com.imyeyu.java.bean.timi.TimiException;
|
|||||||
/**
|
/**
|
||||||
* 可销毁(物理删除)实体服务
|
* 可销毁(物理删除)实体服务
|
||||||
*
|
*
|
||||||
|
* @param <P> 主键类型
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @since 2025-05-14 17:30
|
* @since 2025-05-14 17:30
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -9,11 +9,19 @@ import java.lang.annotation.Annotation;
|
|||||||
/**
|
/**
|
||||||
* 数据验证动态消息返回抽象类
|
* 数据验证动态消息返回抽象类
|
||||||
*
|
*
|
||||||
|
* @param <A> 注解类型
|
||||||
|
* @param <T> 校验数据类型
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2023-05-07 00:08
|
* @version 2023-05-07 00:08
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractValidator<A extends Annotation, T> implements ConstraintValidator<A, T> {
|
public abstract class AbstractValidator<A extends Annotation, T> implements ConstraintValidator<A, T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建校验器
|
||||||
|
*/
|
||||||
|
protected AbstractValidator() {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证处理器,入参验证数据,返回错误消息语言映射,返回 null 时表示通过验证
|
* 验证处理器,入参验证数据,返回错误消息语言映射,返回 null 时表示通过验证
|
||||||
*
|
*
|
||||||
@ -32,4 +40,4 @@ public abstract class AbstractValidator<A extends Annotation, T> implements Cons
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,12 +28,20 @@ public class GlobalExceptionHandler {
|
|||||||
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||||
private static final String DEV_LANG_CONFIG = "dev.lang";
|
private static final String DEV_LANG_CONFIG = "dev.lang";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建全局异常处理器
|
||||||
|
*/
|
||||||
|
public GlobalExceptionHandler() {
|
||||||
|
}
|
||||||
|
|
||||||
@Value("${spring.profiles.active}")
|
@Value("${spring.profiles.active}")
|
||||||
private String env;
|
private String env;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param e
|
* 消息转换异常
|
||||||
* @return
|
*
|
||||||
|
* @param e 异常
|
||||||
|
* @return 异常返回
|
||||||
*/
|
*/
|
||||||
@ExceptionHandler(HttpMessageConversionException.class)
|
@ExceptionHandler(HttpMessageConversionException.class)
|
||||||
public TimiResponse<?> conversionException(HttpMessageConversionException e) {
|
public TimiResponse<?> conversionException(HttpMessageConversionException e) {
|
||||||
|
|||||||
@ -32,8 +32,15 @@ public class GlobalReturnHandler implements ResponseBodyAdvice<Object> {
|
|||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(GlobalReturnHandler.class);
|
private static final Logger log = LoggerFactory.getLogger(GlobalReturnHandler.class);
|
||||||
|
|
||||||
|
/** 多语言头处理回调 */
|
||||||
private CallbackArgReturn<LanguageMsgMapping<?>, String> multilingualHeader;
|
private CallbackArgReturn<LanguageMsgMapping<?>, String> multilingualHeader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建全局返回处理器
|
||||||
|
*/
|
||||||
|
public GlobalReturnHandler() {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supports(@NonNull MethodParameter returnType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
|
public boolean supports(@NonNull MethodParameter returnType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
|
||||||
return Objects.requireNonNull(returnType.getMethod()).getAnnotation(IgnoreGlobalReturn.class) == null;
|
return Objects.requireNonNull(returnType.getMethod()).getAnnotation(IgnoreGlobalReturn.class) == null;
|
||||||
@ -71,10 +78,20 @@ public class GlobalReturnHandler implements ResponseBodyAdvice<Object> {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取多语言头处理回调
|
||||||
|
*
|
||||||
|
* @return 处理回调
|
||||||
|
*/
|
||||||
public CallbackArgReturn<LanguageMsgMapping<?>, String> getMultilingualHeader() {
|
public CallbackArgReturn<LanguageMsgMapping<?>, String> getMultilingualHeader() {
|
||||||
return multilingualHeader;
|
return multilingualHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置多语言头处理回调
|
||||||
|
*
|
||||||
|
* @param multilingualHeader 处理回调
|
||||||
|
*/
|
||||||
public void setMultilingualHeader(CallbackArgReturn<LanguageMsgMapping<?>, String> multilingualHeader) {
|
public void setMultilingualHeader(CallbackArgReturn<LanguageMsgMapping<?>, String> multilingualHeader) {
|
||||||
this.multilingualHeader = multilingualHeader;
|
this.multilingualHeader = multilingualHeader;
|
||||||
}
|
}
|
||||||
|
|||||||
49
src/main/java/com/imyeyu/spring/util/RawSQLProvider.java
Normal file
49
src/main/java/com/imyeyu/spring/util/RawSQLProvider.java
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package com.imyeyu.spring.util;
|
||||||
|
|
||||||
|
import com.imyeyu.java.bean.timi.TimiException;
|
||||||
|
import com.imyeyu.spring.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.builder.annotation.ProviderContext;
|
||||||
|
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原始 Mapper SQL 代理器
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-01-05 13:00
|
||||||
|
*/
|
||||||
|
public class RawSQLProvider extends SQLProvider {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String selectAll(ProviderContext context) {
|
||||||
|
EntityMeta meta = getEntityMeta(context);
|
||||||
|
return "SELECT * FROM %s WHERE 1 = 1".formatted(meta.table);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String select(ProviderContext context, Object id) {
|
||||||
|
EntityMeta meta = getEntityMeta(context);
|
||||||
|
TimiException.required(meta.idFieldColumn, "not found id field in %s".formatted(meta.entityClass));
|
||||||
|
return "SELECT %s FROM `%s` WHERE `%s` = #{%s}".formatted(meta.selectAllClause, meta.table, meta.idFieldColumn.columnName, id) + " LIMIT 1";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String selectByExample(Object entity) {
|
||||||
|
return selectAllByExample(entity) + BaseMapper.LIMIT_1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String selectAllByExample(Object entity) {
|
||||||
|
EntityMeta meta = getEntityMeta(entity.getClass());
|
||||||
|
String conditionClause = meta.fieldColumnList.stream()
|
||||||
|
.filter(fc -> fc.isNotEmpty(entity))
|
||||||
|
.map(fc -> "`%s` = #{%s}".formatted(fc.columnName, fc.fieldName))
|
||||||
|
.collect(Collectors.joining(" AND "));
|
||||||
|
return "SELECT %s FROM `%s` WHERE %s".formatted(meta.selectAllClause, meta.table, conditionClause);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String deleteAllByExample(Object entity) {
|
||||||
|
return super.deleteAllByExample(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -22,6 +22,8 @@ import java.util.function.Consumer;
|
|||||||
* RedisTemplate 功能封装,简化 Redis 操作
|
* RedisTemplate 功能封装,简化 Redis 操作
|
||||||
* <p>serializer 为该 RedisTemplate 的键的序列化操作,序列化解析器由 {@link AbstractRedisConfig} 提供
|
* <p>serializer 为该 RedisTemplate 的键的序列化操作,序列化解析器由 {@link AbstractRedisConfig} 提供
|
||||||
*
|
*
|
||||||
|
* @param <K> 键类型
|
||||||
|
* @param <V> 值类型
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2021-11-21 09:58
|
* @version 2021-11-21 09:58
|
||||||
*/
|
*/
|
||||||
@ -30,6 +32,12 @@ public class Redis<K, V> {
|
|||||||
private final RedisSerializer<K> serializer;
|
private final RedisSerializer<K> serializer;
|
||||||
private final RedisTemplate<K, V> redis;
|
private final RedisTemplate<K, V> redis;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 Redis 操作封装
|
||||||
|
*
|
||||||
|
* @param redis RedisTemplate 实例
|
||||||
|
* @param serializer 键序列化器
|
||||||
|
*/
|
||||||
public Redis(RedisTemplate<K, V> redis, RedisSerializer<K> serializer) {
|
public Redis(RedisTemplate<K, V> redis, RedisSerializer<K> serializer) {
|
||||||
this.redis = redis;
|
this.redis = redis;
|
||||||
this.serializer = serializer;
|
this.serializer = serializer;
|
||||||
@ -47,9 +55,9 @@ public class Redis<K, V> {
|
|||||||
/**
|
/**
|
||||||
* 加锁
|
* 加锁
|
||||||
*
|
*
|
||||||
* @param key
|
* @param key 键
|
||||||
* @param value
|
* @param value 值
|
||||||
* @param timeoutMS
|
* @param timeoutMS 超时时间毫秒
|
||||||
* @return true 为加锁成功
|
* @return true 为加锁成功
|
||||||
*/
|
*/
|
||||||
public boolean lock(K key, V value, long timeoutMS) {
|
public boolean lock(K key, V value, long timeoutMS) {
|
||||||
@ -57,6 +65,11 @@ public class Redis<K, V> {
|
|||||||
return lock != null && lock;
|
return lock != null && lock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 释放锁
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
*/
|
||||||
public void releaseLock(K key) {
|
public void releaseLock(K key) {
|
||||||
destroy(key);
|
destroy(key);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,11 +8,17 @@ import org.springframework.data.redis.serializer.StringRedisSerializer;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Redis 序列化工具
|
||||||
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @version 2023-07-17 16:20
|
* @version 2023-07-17 16:20
|
||||||
*/
|
*/
|
||||||
public class RedisSerializers {
|
public class RedisSerializers {
|
||||||
|
|
||||||
|
/** 工具类禁止实例化 */
|
||||||
|
private RedisSerializers() {
|
||||||
|
}
|
||||||
|
|
||||||
/** 字符串序列化 */
|
/** 字符串序列化 */
|
||||||
public static final StringRedisSerializer STRING = new StringRedisSerializer();
|
public static final StringRedisSerializer STRING = new StringRedisSerializer();
|
||||||
|
|
||||||
@ -76,7 +82,13 @@ public class RedisSerializers {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Gson 序列化 */
|
/**
|
||||||
|
* Gson 序列化
|
||||||
|
*
|
||||||
|
* @param <T> 数据类型
|
||||||
|
* @param clazz 数据类型
|
||||||
|
* @return Redis 序列化器
|
||||||
|
*/
|
||||||
public static <T> RedisSerializer<T> gsonSerializer(Class<T> clazz) {
|
public static <T> RedisSerializer<T> gsonSerializer(Class<T> clazz) {
|
||||||
return new RedisSerializer<>() {
|
return new RedisSerializer<>() {
|
||||||
|
|
||||||
|
|||||||
@ -45,6 +45,12 @@ public class SQLProvider {
|
|||||||
/** 反射缓存 */
|
/** 反射缓存 */
|
||||||
private static final Map<Class<?>, EntityMeta> ENTITY_META_CACHE = new ConcurrentHashMap<>();
|
private static final Map<Class<?>, EntityMeta> ENTITY_META_CACHE = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 SQL 提供器
|
||||||
|
*/
|
||||||
|
public SQLProvider() {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据 Page 对象查询数据列表
|
* 根据 Page 对象查询数据列表
|
||||||
*
|
*
|
||||||
@ -52,7 +58,7 @@ public class SQLProvider {
|
|||||||
* @param page 分页参数
|
* @param page 分页参数
|
||||||
* @return SQL
|
* @return SQL
|
||||||
*/
|
*/
|
||||||
public String listByPage(ProviderContext context, @Param("page") Page<?> page) {
|
public String selectByPage(ProviderContext context, @Param("page") Page<?> page) {
|
||||||
EntityMeta meta = getEntityMeta(context);
|
EntityMeta meta = getEntityMeta(context);
|
||||||
StringBuilder sql = new StringBuilder();
|
StringBuilder sql = new StringBuilder();
|
||||||
sql.append("SELECT %s FROM `%s` WHERE 1 = 1".formatted(meta.selectPageClause, meta.table));
|
sql.append("SELECT %s FROM `%s` WHERE 1 = 1".formatted(meta.selectPageClause, meta.table));
|
||||||
@ -64,20 +70,24 @@ public class SQLProvider {
|
|||||||
Object obj = page.getEqualsExample();
|
Object obj = page.getEqualsExample();
|
||||||
EntityMeta metaExample = getEntityMeta(obj.getClass());
|
EntityMeta metaExample = getEntityMeta(obj.getClass());
|
||||||
String conditionClause = metaExample.fieldColumnList.stream()
|
String conditionClause = metaExample.fieldColumnList.stream()
|
||||||
.filter(fc -> fc.isNotNull(obj))
|
.filter(fc -> fc.isNotEmpty(obj))
|
||||||
.map(fc -> "`%s` = '%s'".formatted(fc.columnName, fc.getAsString(obj)))
|
.map(fc -> "`%s` = '%s'".formatted(fc.columnName, fc.getAsString(obj)))
|
||||||
.collect(Collectors.joining(" AND "));
|
.collect(Collectors.joining(" AND "));
|
||||||
sql.append(" AND ").append(conditionClause);
|
if (TimiJava.isNotEmpty(conditionClause)) {
|
||||||
|
sql.append(" AND ").append(conditionClause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (TimiJava.isNotEmpty(page.getLikeExample())) {
|
if (TimiJava.isNotEmpty(page.getLikesExample())) {
|
||||||
// 模糊查询
|
// 模糊查询
|
||||||
Object obj = page.getLikeExample();
|
Object obj = page.getLikesExample();
|
||||||
EntityMeta metaExample = getEntityMeta(obj.getClass());
|
EntityMeta metaExample = getEntityMeta(obj.getClass());
|
||||||
String conditionClause = metaExample.fieldColumnList.stream()
|
String conditionClause = metaExample.fieldColumnList.stream()
|
||||||
.filter(fc -> fc.isNotNull(obj))
|
.filter(fc -> fc.isNotEmpty(obj))
|
||||||
.map(fc -> "`%s` LIKE CONCAT('%%', '%s', '%%')".formatted(fc.columnName, fc.getAsString(obj)))
|
.map(fc -> "`%s` LIKE CONCAT('%%', '%s', '%%')".formatted(fc.columnName, fc.getAsString(obj)))
|
||||||
.collect(Collectors.joining(" OR "));
|
.collect(Collectors.joining(" OR "));
|
||||||
sql.append(" AND (").append(conditionClause).append(')');
|
if (TimiJava.isNotEmpty(conditionClause)) {
|
||||||
|
sql.append(" AND (").append(conditionClause).append(')');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 排序
|
// 排序
|
||||||
if (TimiJava.isNotEmpty(page.getOrderMap())) {
|
if (TimiJava.isNotEmpty(page.getOrderMap())) {
|
||||||
@ -122,25 +132,35 @@ public class SQLProvider {
|
|||||||
Object obj = page.getEqualsExample();
|
Object obj = page.getEqualsExample();
|
||||||
EntityMeta metaExample = getEntityMeta(obj.getClass());
|
EntityMeta metaExample = getEntityMeta(obj.getClass());
|
||||||
String conditionClause = metaExample.fieldColumnList.stream()
|
String conditionClause = metaExample.fieldColumnList.stream()
|
||||||
.filter(fc -> fc.isNotNull(obj))
|
.filter(fc -> fc.isNotEmpty(obj))
|
||||||
.map(fc -> "`%s` = '%s'".formatted(fc.columnName, fc.getAsString(obj)))
|
.map(fc -> "`%s` = '%s'".formatted(fc.columnName, fc.getAsString(obj)))
|
||||||
.collect(Collectors.joining(" AND "));
|
.collect(Collectors.joining(" AND "));
|
||||||
sql.append(" AND ").append(conditionClause);
|
if (TimiJava.isNotEmpty(conditionClause)) {
|
||||||
|
sql.append(" AND ").append(conditionClause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (TimiJava.isNotEmpty(page.getLikeExample())) {
|
if (TimiJava.isNotEmpty(page.getLikesExample())) {
|
||||||
// 模糊查询
|
// 模糊查询
|
||||||
Object obj = page.getLikeExample();
|
Object obj = page.getLikesExample();
|
||||||
EntityMeta metaExample = getEntityMeta(obj.getClass());
|
EntityMeta metaExample = getEntityMeta(obj.getClass());
|
||||||
String conditionClause = metaExample.fieldColumnList.stream()
|
String conditionClause = metaExample.fieldColumnList.stream()
|
||||||
.filter(fc -> fc.isNotNull(obj))
|
.filter(fc -> fc.isNotEmpty(obj))
|
||||||
.map(fc -> "`%s` LIKE CONCAT('%%', '%s', '%%')".formatted(fc.columnName, fc.getAsString(obj)))
|
.map(fc -> "`%s` LIKE CONCAT('%%', '%s', '%%')".formatted(fc.columnName, fc.getAsString(obj)))
|
||||||
.collect(Collectors.joining(" OR "));
|
.collect(Collectors.joining(" OR "));
|
||||||
sql.append(" AND (").append(conditionClause).append(')');
|
if (TimiJava.isNotEmpty(conditionClause)) {
|
||||||
|
sql.append(" AND (").append(conditionClause).append(')');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return sql.toString();
|
return sql.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String listAll(ProviderContext context) {
|
/**
|
||||||
|
* 查询全部数据
|
||||||
|
*
|
||||||
|
* @param context 代理器上下文
|
||||||
|
* @return SQL
|
||||||
|
*/
|
||||||
|
public String selectAll(ProviderContext context) {
|
||||||
EntityMeta meta = getEntityMeta(context);
|
EntityMeta meta = getEntityMeta(context);
|
||||||
StringBuilder sql = new StringBuilder();
|
StringBuilder sql = new StringBuilder();
|
||||||
sql.append("SELECT * FROM %s WHERE 1 = 1".formatted(meta.table));
|
sql.append("SELECT * FROM %s WHERE 1 = 1".formatted(meta.table));
|
||||||
@ -154,6 +174,7 @@ public class SQLProvider {
|
|||||||
* 插入
|
* 插入
|
||||||
* <p><i>不实现 {@link Creatable} 也允许调用是合理的,某些数据属于关联数据,不参与主创建过程</i></p>
|
* <p><i>不实现 {@link Creatable} 也允许调用是合理的,某些数据属于关联数据,不参与主创建过程</i></p>
|
||||||
*
|
*
|
||||||
|
* @param context 代理器上下文
|
||||||
* @param entity 实体
|
* @param entity 实体
|
||||||
* @return SQL
|
* @return SQL
|
||||||
*/
|
*/
|
||||||
@ -218,7 +239,7 @@ public class SQLProvider {
|
|||||||
public String selectAllByExample(Object entity) {
|
public String selectAllByExample(Object entity) {
|
||||||
EntityMeta meta = getEntityMeta(entity.getClass());
|
EntityMeta meta = getEntityMeta(entity.getClass());
|
||||||
String conditionClause = meta.fieldColumnList.stream()
|
String conditionClause = meta.fieldColumnList.stream()
|
||||||
.filter(fc -> fc.isNotNull(entity))
|
.filter(fc -> fc.isNotEmpty(entity))
|
||||||
.map(fc -> "`%s` = #{%s}".formatted(fc.columnName, fc.fieldName))
|
.map(fc -> "`%s` = #{%s}".formatted(fc.columnName, fc.fieldName))
|
||||||
.collect(Collectors.joining(" AND "));
|
.collect(Collectors.joining(" AND "));
|
||||||
|
|
||||||
@ -291,6 +312,12 @@ public class SQLProvider {
|
|||||||
return "UPDATE `%s` SET `deleted_at` = %s WHERE `%s` = #{id}".formatted(meta.table, Time.now(), meta.idFieldColumn.columnName);
|
return "UPDATE `%s` SET `deleted_at` = %s WHERE `%s` = #{id}".formatted(meta.table, Time.now(), meta.idFieldColumn.columnName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据示例批量逻辑删除
|
||||||
|
*
|
||||||
|
* @param entity 实体
|
||||||
|
* @return SQL
|
||||||
|
*/
|
||||||
public String deleteAllByExample(Object entity) {
|
public String deleteAllByExample(Object entity) {
|
||||||
EntityMeta meta = getEntityMeta(entity.getClass());
|
EntityMeta meta = getEntityMeta(entity.getClass());
|
||||||
TimiException.required(meta.canDelete, "not allow delete for %s".formatted(meta.entityClass));
|
TimiException.required(meta.canDelete, "not allow delete for %s".formatted(meta.entityClass));
|
||||||
@ -302,7 +329,7 @@ public class SQLProvider {
|
|||||||
|
|
||||||
String delClause = meta.fieldColumnList.stream()
|
String delClause = meta.fieldColumnList.stream()
|
||||||
.filter(FieldColumn::isNotId)
|
.filter(FieldColumn::isNotId)
|
||||||
.filter(fc -> fc.isNotNull(entity))
|
.filter(fc -> fc.isNotEmpty(entity))
|
||||||
.map(fc -> "`%s` = #{%s}".formatted(fc.columnName, fc.fieldName))
|
.map(fc -> "`%s` = #{%s}".formatted(fc.columnName, fc.fieldName))
|
||||||
.collect(Collectors.joining(" AND "));
|
.collect(Collectors.joining(" AND "));
|
||||||
StringBuilder sql = new StringBuilder("UPDATE `%s` SET `%s` = ".formatted(meta.table, deleteColumn.getColumnName()));
|
StringBuilder sql = new StringBuilder("UPDATE `%s` SET `%s` = ".formatted(meta.table, deleteColumn.getColumnName()));
|
||||||
@ -390,6 +417,11 @@ public class SQLProvider {
|
|||||||
/** true 为可销毁(硬删除) */
|
/** true 为可销毁(硬删除) */
|
||||||
final boolean canDestroy;
|
final boolean canDestroy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建实体元数据
|
||||||
|
*
|
||||||
|
* @param entityClass 实体类型
|
||||||
|
*/
|
||||||
public EntityMeta(Class<?> entityClass) {
|
public EntityMeta(Class<?> entityClass) {
|
||||||
this.entityClass = entityClass;
|
this.entityClass = entityClass;
|
||||||
|
|
||||||
@ -451,38 +483,83 @@ public class SQLProvider {
|
|||||||
return sb.substring(0, sb.length() - 1);
|
return sb.substring(0, sb.length() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取实体类型
|
||||||
|
*
|
||||||
|
* @return 实体类型
|
||||||
|
*/
|
||||||
public Class<?> getEntityClass() {
|
public Class<?> getEntityClass() {
|
||||||
return entityClass;
|
return entityClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取表名
|
||||||
|
*
|
||||||
|
* @return 表名
|
||||||
|
*/
|
||||||
public String getTable() {
|
public String getTable() {
|
||||||
return table;
|
return table;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取查询字段映射
|
||||||
|
*
|
||||||
|
* @return 查询字段映射
|
||||||
|
*/
|
||||||
public String getSelectAllClause() {
|
public String getSelectAllClause() {
|
||||||
return selectAllClause;
|
return selectAllClause;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 ID 字段映射
|
||||||
|
*
|
||||||
|
* @return ID 字段映射
|
||||||
|
*/
|
||||||
public FieldColumn getIdFieldColumn() {
|
public FieldColumn getIdFieldColumn() {
|
||||||
return idFieldColumn;
|
return idFieldColumn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字段映射列表
|
||||||
|
*
|
||||||
|
* @return 字段映射列表
|
||||||
|
*/
|
||||||
public List<FieldColumn> getFieldColumnList() {
|
public List<FieldColumn> getFieldColumnList() {
|
||||||
return fieldColumnList;
|
return fieldColumnList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否可创建
|
||||||
|
*
|
||||||
|
* @return true 为可创建
|
||||||
|
*/
|
||||||
public boolean canCreate() {
|
public boolean canCreate() {
|
||||||
return canCreate;
|
return canCreate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否可更新
|
||||||
|
*
|
||||||
|
* @return true 为可更新
|
||||||
|
*/
|
||||||
public boolean canUpdate() {
|
public boolean canUpdate() {
|
||||||
return canUpdate;
|
return canUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否可删除
|
||||||
|
*
|
||||||
|
* @return true 为可删除
|
||||||
|
*/
|
||||||
public boolean canDelete() {
|
public boolean canDelete() {
|
||||||
return canDelete;
|
return canDelete;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否可销毁
|
||||||
|
*
|
||||||
|
* @return true 为可销毁
|
||||||
|
*/
|
||||||
public boolean canDestroy() {
|
public boolean canDestroy() {
|
||||||
return canDestroy;
|
return canDestroy;
|
||||||
}
|
}
|
||||||
@ -517,6 +594,11 @@ public class SQLProvider {
|
|||||||
|
|
||||||
final DeleteColumn.Type deleteColumnType;
|
final DeleteColumn.Type deleteColumnType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建字段映射
|
||||||
|
*
|
||||||
|
* @param field 字段
|
||||||
|
*/
|
||||||
public FieldColumn(Field field) {
|
public FieldColumn(Field field) {
|
||||||
this.field = field;
|
this.field = field;
|
||||||
|
|
||||||
@ -543,6 +625,12 @@ public class SQLProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断字段值是否为空
|
||||||
|
*
|
||||||
|
* @param entity 实体
|
||||||
|
* @return true 为 null
|
||||||
|
*/
|
||||||
public boolean isNull(Object entity) {
|
public boolean isNull(Object entity) {
|
||||||
try {
|
try {
|
||||||
return Ref.getFieldValue(entity, field, Object.class) == null;
|
return Ref.getFieldValue(entity, field, Object.class) == null;
|
||||||
@ -551,10 +639,46 @@ public class SQLProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断字段值是否非空
|
||||||
|
*
|
||||||
|
* @param entity 实体
|
||||||
|
* @return true 为非 null
|
||||||
|
*/
|
||||||
public boolean isNotNull(Object entity) {
|
public boolean isNotNull(Object entity) {
|
||||||
return !isNull(entity);
|
return !isNull(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断字段值是否为空
|
||||||
|
*
|
||||||
|
* @param entity 实体
|
||||||
|
* @return true 为空
|
||||||
|
*/
|
||||||
|
public boolean isEmpty(Object entity) {
|
||||||
|
try {
|
||||||
|
return TimiJava.isEmpty(Ref.getFieldValue(entity, field, Object.class));
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断字段值是否非空
|
||||||
|
*
|
||||||
|
* @param entity 实体
|
||||||
|
* @return true 为非空
|
||||||
|
*/
|
||||||
|
public boolean isNotEmpty(Object entity) {
|
||||||
|
return !isEmpty(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字段字符串值
|
||||||
|
*
|
||||||
|
* @param obj 实体
|
||||||
|
* @return 字符串值
|
||||||
|
*/
|
||||||
public String getAsString(Object obj) {
|
public String getAsString(Object obj) {
|
||||||
try {
|
try {
|
||||||
return field.get(obj).toString();
|
return field.get(obj).toString();
|
||||||
@ -563,30 +687,65 @@ public class SQLProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字段
|
||||||
|
*
|
||||||
|
* @return 字段
|
||||||
|
*/
|
||||||
public Field getField() {
|
public Field getField() {
|
||||||
return field;
|
return field;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字段名
|
||||||
|
*
|
||||||
|
* @return 字段名
|
||||||
|
*/
|
||||||
public String getFieldName() {
|
public String getFieldName() {
|
||||||
return fieldName;
|
return fieldName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取列名
|
||||||
|
*
|
||||||
|
* @return 列名
|
||||||
|
*/
|
||||||
public String getColumnName() {
|
public String getColumnName() {
|
||||||
return columnName;
|
return columnName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为 ID 字段
|
||||||
|
*
|
||||||
|
* @return true 为 ID 字段
|
||||||
|
*/
|
||||||
public boolean isId() {
|
public boolean isId() {
|
||||||
return isId;
|
return isId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否非 ID 字段
|
||||||
|
*
|
||||||
|
* @return true 为非 ID 字段
|
||||||
|
*/
|
||||||
public boolean isNotId() {
|
public boolean isNotId() {
|
||||||
return !isId();
|
return !isId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否自动 UUID
|
||||||
|
*
|
||||||
|
* @return true 为自动 UUID
|
||||||
|
*/
|
||||||
public boolean isAutoUUID() {
|
public boolean isAutoUUID() {
|
||||||
return isAutoUUID;
|
return isAutoUUID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否自动大写 UUID
|
||||||
|
*
|
||||||
|
* @return true 为自动大写 UUID
|
||||||
|
*/
|
||||||
public boolean isAutoUpperUUID() {
|
public boolean isAutoUpperUUID() {
|
||||||
return isAutoUpperUUID;
|
return isAutoUpperUUID;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,13 +9,19 @@ import java.io.IOException;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Yaml 属性源加载工厂
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
* @since 2025-10-13 16:29
|
* @since 2025-10-13 16:29
|
||||||
*/
|
*/
|
||||||
public class YamlPropertySourceFactory implements PropertySourceFactory {
|
public class YamlPropertySourceFactory implements PropertySourceFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 Yaml 属性源工厂
|
||||||
|
*/
|
||||||
|
public YamlPropertySourceFactory() {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @org.springframework.lang.NonNull PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
|
public @org.springframework.lang.NonNull PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
|
||||||
List<PropertySource<?>> sources = new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource());
|
List<PropertySource<?>> sources = new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource());
|
||||||
|
|||||||
Reference in New Issue
Block a user