Compare commits
7 Commits
08aab8d5a9
...
511b519925
| Author | SHA1 | Date | |
|---|---|---|---|
| 511b519925 | |||
| 595ca407b3 | |||
| 7aadec7306 | |||
| 745b3acfef | |||
| 23598242f0 | |||
| 113af72208 | |||
| 17b20f38e6 |
109
CLAUDE.md
Normal file
109
CLAUDE.md
Normal file
@ -0,0 +1,109 @@
|
||||
# 角色描述:
|
||||
|
||||
你是世界上最优秀和全能的程序开发工程师, 你写的代码因其简单高效而受到称赞
|
||||
|
||||
你精通 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
|
||||
|
||||
不做
|
||||
|
||||
- 在没有充分理由的情况下添加外部库
|
||||
- 用繁重的计算阻塞主线程, 卸载或推迟
|
||||
@ -0,0 +1,32 @@
|
||||
package com.imyeyu.spring.annotation.table;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author 夜雨
|
||||
* @since 2025-12-01 10:56
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface DeleteColumn {
|
||||
|
||||
Type value() default Type.UNIX;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2025-12-01 10:57
|
||||
*/
|
||||
enum Type {
|
||||
|
||||
UNIX,
|
||||
|
||||
DATE,
|
||||
|
||||
DATE_TIME
|
||||
}
|
||||
}
|
||||
@ -17,6 +17,8 @@ public class Page extends BasePage {
|
||||
|
||||
protected LinkedHashMap<String, BaseMapper.OrderType> orderMap;
|
||||
|
||||
protected LinkedHashMap<String, String> likeMap;
|
||||
|
||||
public Page() {
|
||||
}
|
||||
|
||||
@ -45,9 +47,11 @@ public class Page extends BasePage {
|
||||
orderMap.put(Text.camelCase2underscore(field), orderType);
|
||||
}
|
||||
|
||||
public static <T, P extends Page, R extends PageResult<T>> R toResult(BaseMapper<T, ?> pageMapper, P page, R result) {
|
||||
result.setList(pageMapper.listOrder(page.getOffset(), page.getLimit(), page.getOrderMap()));
|
||||
result.setTotal(pageMapper.count());
|
||||
return result;
|
||||
public LinkedHashMap<String, String> getLikeMap() {
|
||||
return likeMap;
|
||||
}
|
||||
|
||||
public void setLikeMap(LinkedHashMap<String, String> likeMap) {
|
||||
this.likeMap = likeMap;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.imyeyu.spring.entity;
|
||||
|
||||
import com.imyeyu.spring.annotation.table.DeleteColumn;
|
||||
import com.imyeyu.utils.Time;
|
||||
|
||||
import java.io.Serializable;
|
||||
@ -19,6 +20,7 @@ public class BaseEntity implements Serializable, Creatable, Updatable, Deletable
|
||||
protected Long updatedAt;
|
||||
|
||||
/** 删除时间 */
|
||||
@DeleteColumn
|
||||
protected Long deletedAt;
|
||||
|
||||
/**
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package com.imyeyu.spring.entity;
|
||||
|
||||
import com.imyeyu.utils.Time;
|
||||
|
||||
/**
|
||||
* 可软删除实体
|
||||
*
|
||||
@ -27,5 +29,7 @@ public interface Deletable {
|
||||
*
|
||||
* @return true 为已删除
|
||||
*/
|
||||
boolean isDeleted();
|
||||
default boolean isDeleted() {
|
||||
return getDeletedAt() != null && getDeletedAt() < Time.now();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package com.imyeyu.spring.mapper;
|
||||
|
||||
import com.imyeyu.spring.bean.Page;
|
||||
import com.imyeyu.spring.bean.PageResult;
|
||||
import com.imyeyu.spring.util.SQLProvider;
|
||||
import org.apache.ibatis.annotations.DeleteProvider;
|
||||
import org.apache.ibatis.annotations.InsertProvider;
|
||||
@ -8,7 +10,6 @@ import org.apache.ibatis.annotations.SelectProvider;
|
||||
import org.apache.ibatis.annotations.UpdateProvider;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 基本 SQL 映射,子接口可以不实现
|
||||
@ -31,35 +32,44 @@ public interface BaseMapper<T, P> {
|
||||
DESC
|
||||
}
|
||||
|
||||
static final String NOT_DELETE = " AND `deleted_at` IS NULL ";
|
||||
String NOT_DELETE = " AND `deleted_at` IS NULL ";
|
||||
|
||||
static final String LIMIT_1 = " LIMIT 1";
|
||||
String LIMIT_1 = " LIMIT 1";
|
||||
|
||||
static final String UNIX_TIME = " FLOOR(UNIX_TIMESTAMP(NOW(3)) * 1000) ";
|
||||
String UNIX_TIME = " FLOOR(UNIX_TIMESTAMP(NOW(3)) * 1000) ";
|
||||
|
||||
static final String PAGE = NOT_DELETE + " LIMIT #{offset}, #{limit}";
|
||||
String PAGE = NOT_DELETE + " LIMIT #{offset}, #{limit}";
|
||||
|
||||
/**
|
||||
* 统计数据量
|
||||
* 根据 Page 对象查询数据列表
|
||||
*
|
||||
* @return 数据量
|
||||
*/
|
||||
@SelectProvider(type = SQLProvider.class, method = "count")
|
||||
long count();
|
||||
|
||||
default List<T> list(long offset, int limit) {
|
||||
return listOrder(offset, limit, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部分数据
|
||||
*
|
||||
* @param offset 偏移
|
||||
* @param limit 数据量
|
||||
* @param page 分页参数
|
||||
* @return 数据列表
|
||||
*/
|
||||
@SelectProvider(type = SQLProvider.class, method = "listOrder")
|
||||
List<T> listOrder(long offset, int limit, Map<String, OrderType> orderMap);
|
||||
@SelectProvider(type = SQLProvider.class, method = "listByPage")
|
||||
List<T> listByPage(Page page);
|
||||
|
||||
/**
|
||||
* 根据 Page 对象统计数据量
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @return 数据量
|
||||
*/
|
||||
@SelectProvider(type = SQLProvider.class, method = "countByPage")
|
||||
long countByPage(Page page);
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @return 分页结果
|
||||
*/
|
||||
default PageResult<T> page(Page page) {
|
||||
PageResult<T> result = new PageResult<>();
|
||||
result.setTotal(countByPage(page));
|
||||
result.setList(listByPage(page));
|
||||
return result;
|
||||
}
|
||||
|
||||
@SelectProvider(type = SQLProvider.class, method = "listAll")
|
||||
List<T> listAll();
|
||||
@ -107,6 +117,9 @@ public interface BaseMapper<T, P> {
|
||||
@UpdateProvider(type = SQLProvider.class, method = "delete")
|
||||
void delete(P id);
|
||||
|
||||
@UpdateProvider(type = SQLProvider.class, method = "deleteAllByExample")
|
||||
void deleteAllByExample(T t);
|
||||
|
||||
/**
|
||||
* 销毁(物理删除)
|
||||
*
|
||||
|
||||
@ -32,7 +32,7 @@ public abstract class AbstractEntityService<T, P> implements BaseService<T, P> {
|
||||
@Override
|
||||
public PageResult<T> page(Page page) {
|
||||
checkMapper();
|
||||
return Page.toResult(baseMapper, page, new PageResult<>());
|
||||
return baseMapper.page(page);
|
||||
}
|
||||
|
||||
public void create(T t) {
|
||||
|
||||
@ -6,10 +6,11 @@ import com.imyeyu.java.bean.timi.TimiException;
|
||||
import com.imyeyu.java.ref.Ref;
|
||||
import com.imyeyu.spring.annotation.table.AutoUUID;
|
||||
import com.imyeyu.spring.annotation.table.Column;
|
||||
import com.imyeyu.spring.annotation.table.DeleteColumn;
|
||||
import com.imyeyu.spring.annotation.table.Id;
|
||||
import com.imyeyu.spring.annotation.table.Table;
|
||||
import com.imyeyu.spring.annotation.table.Transient;
|
||||
import com.imyeyu.spring.entity.BaseEntity;
|
||||
import com.imyeyu.spring.bean.Page;
|
||||
import com.imyeyu.spring.entity.Creatable;
|
||||
import com.imyeyu.spring.entity.Deletable;
|
||||
import com.imyeyu.spring.entity.Destroyable;
|
||||
@ -24,6 +25,7 @@ import java.lang.reflect.Field;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
@ -41,39 +43,80 @@ public class SQLProvider {
|
||||
/** 反射缓存 */
|
||||
private static final Map<Class<?>, EntityMeta> ENTITY_META_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
public String count(ProviderContext context) {
|
||||
EntityMeta meta = getEntityMeta(context);
|
||||
StringBuilder sql = new StringBuilder();
|
||||
sql.append("SELECT COUNT(*) FROM `%s` WHERE 1 = 1".formatted(meta.table));
|
||||
if (meta.canDelete) {
|
||||
sql.append(" AND (`deleted_at` IS NULL OR %s < `deleted_at`)".formatted(Time.now()));
|
||||
}
|
||||
return sql.toString();
|
||||
}
|
||||
|
||||
public String listOrder(ProviderContext context, @Param("offset") Long offset, @Param("limit") Integer limit, @Param("orderMap") Map<String, BaseMapper.OrderType> orderMap) {
|
||||
/**
|
||||
* 根据 Page 对象查询数据列表
|
||||
*
|
||||
* @param context 代理器上下文
|
||||
* @param page 分页参数
|
||||
* @return SQL
|
||||
*/
|
||||
public String listByPage(ProviderContext context, @Param("page") Page page) {
|
||||
EntityMeta meta = getEntityMeta(context);
|
||||
StringBuilder sql = new StringBuilder();
|
||||
sql.append("SELECT %s FROM `%s` WHERE 1 = 1".formatted(meta.selectAllClause, meta.table));
|
||||
|
||||
// 处理软删除
|
||||
if (meta.canDelete) {
|
||||
sql.append(" AND (`deleted_at` IS NULL OR %s < `deleted_at`)".formatted(Time.now()));
|
||||
}
|
||||
if (TimiJava.isNotEmpty(orderMap)) {
|
||||
// 处理模糊查询
|
||||
if (TimiJava.isNotEmpty(page.getLikeMap())) {
|
||||
for (Map.Entry<String, String> item : page.getLikeMap().entrySet()) {
|
||||
sql.append(" AND `%s` LIKE CONCAT('%%', #{likeMap.%s}, '%%')".formatted(
|
||||
Text.camelCase2underscore(item.getKey()),
|
||||
item.getKey()
|
||||
));
|
||||
}
|
||||
}
|
||||
// 处理排序
|
||||
if (TimiJava.isNotEmpty(page.getOrderMap())) {
|
||||
sql.append(" ORDER BY ");
|
||||
for (Map.Entry<String, BaseMapper.OrderType> item : orderMap.entrySet()) {
|
||||
sql.append(Text.camelCase2underscore(item.getKey())).append(' ').append(item.getValue().toString());
|
||||
sql.append(", ");
|
||||
for (Map.Entry<String, BaseMapper.OrderType> item : page.getOrderMap().entrySet()) {
|
||||
sql.append("`%s` %s, ".formatted(
|
||||
Text.camelCase2underscore(item.getKey()),
|
||||
item.getValue().toString()
|
||||
));
|
||||
}
|
||||
sql.deleteCharAt(sql.length() - 2);
|
||||
} else {
|
||||
// 默认排序
|
||||
if (meta.canCreate && !meta.canUpdate) {
|
||||
sql.append(" ORDER BY created_at DESC");
|
||||
sql.append(" ORDER BY `created_at` DESC");
|
||||
}
|
||||
if (meta.canCreate && meta.canUpdate) {
|
||||
sql.append(" ORDER BY COALESCE(updated_at, created_at) DESC");
|
||||
sql.append(" ORDER BY COALESCE(`updated_at`, `created_at`) DESC");
|
||||
}
|
||||
}
|
||||
return sql.append(" LIMIT %s, %s".formatted(offset, limit)).toString();
|
||||
// 分页
|
||||
sql.append(" LIMIT #{offset}, #{limit}");
|
||||
return sql.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 Page 对象统计数据量
|
||||
*
|
||||
* @param context 代理器上下文
|
||||
* @param page 分页参数
|
||||
* @return SQL
|
||||
*/
|
||||
public String countByPage(ProviderContext context, @Param("page") com.imyeyu.spring.bean.Page page) {
|
||||
EntityMeta meta = getEntityMeta(context);
|
||||
StringBuilder sql = new StringBuilder();
|
||||
sql.append("SELECT COUNT(*) FROM `%s` WHERE 1 = 1".formatted(meta.table));
|
||||
// 处理软删除
|
||||
if (meta.canDelete) {
|
||||
sql.append(" AND (`deleted_at` IS NULL OR %s < `deleted_at`)".formatted(Time.now()));
|
||||
}
|
||||
// 处理模糊查询
|
||||
if (TimiJava.isNotEmpty(page.getLikeMap())) {
|
||||
for (Map.Entry<String, String> item : page.getLikeMap().entrySet()) {
|
||||
sql.append(" AND `%s` LIKE CONCAT('%%', #{likeMap.%s}, '%%')".formatted(
|
||||
Text.camelCase2underscore(item.getKey()),
|
||||
item.getKey()
|
||||
));
|
||||
}
|
||||
}
|
||||
return sql.toString();
|
||||
}
|
||||
|
||||
public String listAll(ProviderContext context) {
|
||||
@ -154,16 +197,8 @@ public class SQLProvider {
|
||||
public String selectAllByExample(Object entity) {
|
||||
EntityMeta meta = getEntityMeta(entity.getClass());
|
||||
String conditionClause = meta.fieldColumnList.stream()
|
||||
.filter(fc -> {
|
||||
try {
|
||||
return Ref.getFieldValue(entity, fc.field, Object.class) != null;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
})
|
||||
.map(fc -> {
|
||||
return "`%s` = #{%s}".formatted(fc.columnName, fc.fieldName);
|
||||
})
|
||||
.filter(fc -> fc.isNotNull(entity))
|
||||
.map(fc -> "`%s` = #{%s}".formatted(fc.columnName, fc.fieldName))
|
||||
.collect(Collectors.joining(" AND "));
|
||||
|
||||
StringBuilder sql = new StringBuilder();
|
||||
@ -188,14 +223,12 @@ public class SQLProvider {
|
||||
TimiException.required(meta.idFieldColumn, "not found id field in %s".formatted(meta.entityClass));
|
||||
TimiException.required(meta.canUpdate, "not allow update for %s".formatted(meta.entityClass));
|
||||
|
||||
if (entity instanceof Updatable updatable) {
|
||||
updatable.setUpdatedAt(Time.now());
|
||||
}
|
||||
String setClause = meta.fieldColumnList.stream()
|
||||
.filter(fc -> !fc.isId)
|
||||
.map(fc -> {
|
||||
if (entity instanceof Updatable updatableEntity) {
|
||||
updatableEntity.setUpdatedAt(Time.now());
|
||||
}
|
||||
return "`%s` = #{%s}".formatted(fc.columnName, fc.fieldName);
|
||||
})
|
||||
.filter(FieldColumn::isNotId)
|
||||
.map(fc -> "`%s` = #{%s}".formatted(fc.columnName, fc.fieldName))
|
||||
.collect(Collectors.joining(", "));
|
||||
return "UPDATE `%s` SET %s WHERE `%s` = #{%s}".formatted(meta.table, setClause, meta.idFieldColumn.columnName, meta.idFieldColumn.fieldName);
|
||||
}
|
||||
@ -211,27 +244,19 @@ public class SQLProvider {
|
||||
TimiException.required(meta.idFieldColumn, "not found id field in %s".formatted(meta.entityClass));
|
||||
TimiException.required(meta.canUpdate, "not allow update for %s".formatted(meta.entityClass));
|
||||
|
||||
if (entity instanceof BaseEntity baseEntity) {
|
||||
baseEntity.setCreatedAt(null);
|
||||
baseEntity.setDeletedAt(null);
|
||||
if (entity instanceof Creatable creatable) {
|
||||
creatable.setCreatedAt(null);
|
||||
}
|
||||
if (entity instanceof Updatable updatable) {
|
||||
updatable.setUpdatedAt(Time.now());
|
||||
}
|
||||
if (entity instanceof Deletable deletable) {
|
||||
deletable.setDeletedAt(null);
|
||||
}
|
||||
String setClause = meta.fieldColumnList.stream()
|
||||
.filter(fc -> {
|
||||
if (fc.isId) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return Ref.getFieldValue(entity, fc.field, Object.class) != null;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
})
|
||||
.map(fc -> {
|
||||
if (entity instanceof Updatable updatableEntity) {
|
||||
updatableEntity.setUpdatedAt(Time.now());
|
||||
}
|
||||
return "`%s` = #{%s}".formatted(fc.columnName, fc.fieldName);
|
||||
})
|
||||
.filter(FieldColumn::isNotId)
|
||||
.filter(fc -> fc.isNotNull(entity))
|
||||
.map(fc -> "`%s` = #{%s}".formatted(fc.columnName, fc.fieldName))
|
||||
.collect(Collectors.joining(", "));
|
||||
return "UPDATE `%s` SET %s WHERE `%s` = #{%s}".formatted(meta.table, setClause, meta.idFieldColumn.columnName, meta.idFieldColumn.fieldName);
|
||||
}
|
||||
@ -251,6 +276,29 @@ public class SQLProvider {
|
||||
return "UPDATE `%s` SET `deleted_at` = %s WHERE `%s` = #{id}".formatted(meta.table, Time.now(), meta.idFieldColumn.columnName);
|
||||
}
|
||||
|
||||
public String deleteAllByExample(Object entity) {
|
||||
EntityMeta meta = getEntityMeta(entity.getClass());
|
||||
TimiException.required(meta.canDelete, "not allow delete for %s".formatted(meta.entityClass));
|
||||
|
||||
FieldColumn deleteColumn = meta.getFieldColumnList().stream().filter(fc -> fc.isDeleteColumn).findFirst().orElse(null);
|
||||
TimiException.required(deleteColumn, "unknown delete column, use com.imyeyu.spring.annotation.table.DeleteColumn annotation on field");
|
||||
assert deleteColumn != null;
|
||||
assert deleteColumn.deleteColumnType != null;
|
||||
|
||||
String delClause = meta.fieldColumnList.stream()
|
||||
.filter(FieldColumn::isNotId)
|
||||
.filter(fc -> fc.isNotNull(entity))
|
||||
.map(fc -> "`%s` = #{%s}".formatted(fc.columnName, fc.fieldName))
|
||||
.collect(Collectors.joining(" AND "));
|
||||
StringBuilder sql = new StringBuilder("UPDATE `%s` SET `%s` = ".formatted(meta.table, deleteColumn.getColumnName()));
|
||||
sql.append("'").append(switch (deleteColumn.deleteColumnType) {
|
||||
case UNIX -> Time.now();
|
||||
case DATE, DATE_TIME -> new Date();
|
||||
}).append("'");
|
||||
sql.append(" WHERE ").append(delClause);
|
||||
return sql.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 硬删除,需要实体实现 {@link Destroyable}
|
||||
*
|
||||
@ -438,6 +486,10 @@ public class SQLProvider {
|
||||
|
||||
final boolean isAutoUpperUUID;
|
||||
|
||||
final boolean isDeleteColumn;
|
||||
|
||||
final DeleteColumn.Type deleteColumnType;
|
||||
|
||||
public FieldColumn(Field field) {
|
||||
this.field = field;
|
||||
|
||||
@ -456,6 +508,24 @@ public class SQLProvider {
|
||||
} else {
|
||||
isAutoUpperUUID = false;
|
||||
}
|
||||
isDeleteColumn = field.isAnnotationPresent(DeleteColumn.class);
|
||||
if (isDeleteColumn) {
|
||||
deleteColumnType = field.getAnnotation(DeleteColumn.class).value();
|
||||
} else {
|
||||
deleteColumnType = null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isNull(Object entity) {
|
||||
try {
|
||||
return Ref.getFieldValue(entity, field, Object.class) == null;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isNotNull(Object entity) {
|
||||
return !isNull(entity);
|
||||
}
|
||||
|
||||
public Field getField() {
|
||||
@ -474,6 +544,10 @@ public class SQLProvider {
|
||||
return isId;
|
||||
}
|
||||
|
||||
public boolean isNotId() {
|
||||
return !isId();
|
||||
}
|
||||
|
||||
public boolean isAutoUUID() {
|
||||
return isAutoUUID;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user