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,35 @@
package com.imyeyu.spring.util;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import com.imyeyu.java.TimiJava;
import java.lang.annotation.Annotation;
/**
* 数据验证动态消息返回抽象类
*
* @author 夜雨
* @version 2023-05-07 00:08
*/
public abstract class AbstractValidator<A extends Annotation, T> implements ConstraintValidator<A, T> {
/**
* 验证处理器,入参验证数据,返回错误消息语言映射,返回 null 时表示通过验证
*
* @param t 验证数据
* @return 验证消息回调
*/
protected abstract String inspector(T t);
@Override
public boolean isValid(T value, ConstraintValidatorContext context) {
String msgKey = inspector(value);
if (TimiJava.isNotEmpty(msgKey)) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(msgKey).addConstraintViolation();
return false;
}
return true;
}
}

View File

@@ -0,0 +1,103 @@
package com.imyeyu.spring.util;
import jakarta.servlet.ServletException;
import jakarta.validation.ValidationException;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.java.bean.timi.TimiResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局异常处理器
*
* @author 夜雨
* @version 2023-05-06 16:28
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
private static final String DEV_LANG_CONFIG = "dev.lang";
@Value("${spring.profiles.active}")
private String env;
/**
* @param e
* @return
*/
@ExceptionHandler(HttpMessageConversionException.class)
public TimiResponse<?> conversionException(HttpMessageConversionException e) {
log.warn(e.getMessage());
if (env.contains("dev") || log.isDebugEnabled()) {
log.error("conversion error", e);
}
return new TimiResponse<>(TimiCode.ARG_BAD);
}
/**
* 请求异常
*
* @param e 异常
* @return 异常返回
*/
@ExceptionHandler(ServletException.class)
public TimiResponse<?> headerException(ServletException e) {
log.warn(e.getMessage());
if (env.contains("dev") || log.isDebugEnabled()) {
log.error("header error", e);
}
return new TimiResponse<>(TimiCode.REQUEST_BAD);
}
/**
* 接口入参基本校验异常
*
* @param e 异常
* @return 异常返回
*/
@ExceptionHandler({BindException.class, ValidationException.class, MethodArgumentNotValidException.class, TypeMismatchException.class})
public TimiResponse<?> paramsException(Exception e) {
if (e instanceof MethodArgumentNotValidException subE) {
log.warn("request error", e);
FieldError error = subE.getBindingResult().getFieldError();
if (error != null) {
return new TimiResponse<>(TimiCode.ARG_BAD, error.getDefaultMessage());
}
}
if (env.startsWith("dev") || log.isDebugEnabled()) {
log.error("request error", e);
}
return new TimiResponse<>(TimiCode.REQUEST_BAD);
}
/**
* 全局异常
*
* @param e 异常
* @return 异常返回
*/
@ExceptionHandler(Throwable.class)
public TimiResponse<?> error(Throwable e) {
if (e instanceof TimiException timiE) {
// TODO 400 以下即使是开发环境也不算异常
if (env.startsWith("dev") || log.isDebugEnabled()) {
log.error(timiE.getMessage(), e);
}
// 一般异常
return timiE.toResponse();
}
// 致命异常
log.error("fatal error", e);
return new TimiResponse<>(TimiCode.ERROR);
}
}

View File

@@ -0,0 +1,75 @@
package com.imyeyu.spring.util;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.CallbackArgReturn;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiResponse;
import com.imyeyu.spring.TimiSpring;
import com.imyeyu.spring.annotation.AOPLogInterceptor;
import com.imyeyu.spring.annotation.IgnoreGlobalReturn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.Objects;
/**
* 全局返回处理器,包装 TimiResponse
*
* @author 夜雨
* @version 2023-04-30 00:59
*/
@RestControllerAdvice
public class GlobalReturnHandler implements ResponseBodyAdvice<Object> {
private static final Logger log = LoggerFactory.getLogger(GlobalReturnHandler.class);
private CallbackArgReturn<String, String> multilingualHeader;
@Override
public boolean supports(@NonNull MethodParameter returnType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
return Objects.requireNonNull(returnType.getMethod()).getAnnotation(IgnoreGlobalReturn.class) == null;
}
@Override
public Object beforeBodyWrite(
Object body,
@NonNull MethodParameter returnType,
@NonNull MediaType selectedContentType,
@NonNull Class<? extends HttpMessageConverter<?>> selectedConverterType,
@NonNull ServerHttpRequest request,
@NonNull ServerHttpResponse response)
{
TimiResponse<?> result;
if (body instanceof TimiResponse<?> timiResponse) {
// 可能已被全局异常包装
result = timiResponse;
} else {
result = new TimiResponse<>(TimiCode.SUCCESS, body);
}
if (multilingualHeader != null && TimiJava.isNotEmpty(result.getMsgKey())) {
result.setMsg(multilingualHeader.handler(result.getMsgKey()));
} else if (TimiJava.isEmpty(result.getMsg())) {
result.setMsg(TimiCode.fromCode(result.getCode()).toString());
}
if (30000 < result.getCode()) {
log.warn("ID: {} Response -> Exception.{}.{}", TimiSpring.getSessionAttr(AOPLogInterceptor.REQUEST_ID), result.getCode(), result.getMsg());
}
return result;
}
public CallbackArgReturn<String, String> getMultilingualHeader() {
return multilingualHeader;
}
public void setMultilingualHeader(CallbackArgReturn<String, String> multilingualHeader) {
this.multilingualHeader = multilingualHeader;
}
}

View File

@@ -0,0 +1,295 @@
package com.imyeyu.spring.util;
import com.imyeyu.spring.config.AbstractRedisConfig;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.serializer.RedisSerializer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
* RedisTemplate 功能封装,简化 Redis 操作
* <p>serializer 为该 RedisTemplate 的键的序列化操作,序列化解析器由 {@link AbstractRedisConfig} 提供
*
* @author 夜雨
* @version 2021-11-21 09:58
*/
public class Redis<K, V> {
private final RedisSerializer<K> serializer;
private final RedisTemplate<K, V> redis;
public Redis(RedisTemplate<K, V> redis, RedisSerializer<K> serializer) {
this.redis = redis;
this.serializer = serializer;
}
/**
* 获取 Redis 模板对象
*
* @return Redis 模板对象
*/
public RedisTemplate<?, ?> getRedis() {
return redis;
}
/**
* 加锁
*
* @param key
* @param value
* @param timeoutMS
* @return true 为加锁成功
*/
public boolean lock(K key, V value, long timeoutMS) {
Boolean lock = redis.opsForValue().setIfAbsent(key, value, timeoutMS, TimeUnit.MILLISECONDS);
return lock != null && lock;
}
public void releaseLock(K key) {
destroy(key);
}
/**
* 设置存活时间
*
* @param key 键
* @param ms 毫秒 TTL
*/
public void setExpire(K key, long ms) {
redis.expire(key, Duration.ofMillis(ms));
}
/**
* 获取该数据 TTL
*
* @param key 键
* @return 毫秒 TTL
*/
public long getExpire(K key) {
return Objects.requireNonNullElse(redis.getExpire(key, TimeUnit.MILLISECONDS), -1L);
}
/**
* 设置数据并保持 TTL
*
* @param key 键
* @param value 值
*/
public void setAndKeepTTL(K key, V value) {
Long expire = redis.getExpire(key, TimeUnit.MILLISECONDS);
if (expire == null || expire <= 0) {
// 判死
destroy(key);
} else {
redis.opsForValue().set(key, value, Duration.ofMillis(expire));
}
}
/**
* 设置数据
*
* @param key 键
* @param value 值
* @param ms 毫秒 TTL
*/
public void set(K key, V value, long ms) {
redis.opsForValue().set(key, value, Duration.ofMillis(ms));
}
/**
* 获取值
*
* @param key 键
* @return 值
*/
public V get(K key) {
return redis.opsForValue().get(key);
}
/**
* 获取值,强转为 String
*
* @param key 键
* @return 值
*/
public String getString(K key) {
return get(key).toString();
}
/**
* 获取值,强转为 Boolean
*
* @param key 键
* @return 值
*/
public Boolean is(K key) {
return Boolean.parseBoolean(getString(key));
}
/**
* 获取值,强转为 Boolean 并取反
*
* @param key 键
* @return 值
*/
public Boolean not(K key) {
return !is(key);
}
/**
* 是否存在
*
* @param key 键
* @return true 为存在
*/
public boolean has(K key) {
return get(key) != null;
}
/**
* 对列表添加值
*
* @param key 键
* @param value 值
*/
public void add(K key, V value) {
redis.opsForList().leftPush(key, value);
}
/**
* 对列表批量添加值
*
* @param key 键
* @param values 值
*/
@SafeVarargs
public final void addAll(K key, V... values) {
redis.opsForList().leftPushAll(key, values);
}
/**
* 获取为列表
*
* @param key 键
* @return 列表
*/
public List<V> getList(K key) {
return redis.opsForList().range(key, 0, -1);
}
/**
* 获取所有数据列表
*
* @return 所有数据列表
*/
public Map<K, List<V>> getAllList() {
Map<K, List<V>> r = new HashMap<>();
List<K> ks = keys("*");
for (int i = 0; i < ks.size(); i++) {
r.put(ks.get(i), getList(ks.get(i)));
}
return r;
}
/**
* 值为列表时查找是否存在某值
*
* @param key 键
* @param value 值
* @return true 为存在
*/
public boolean contains(K key, V value) {
return getList(key).contains(value);
}
/**
* 获取所有值
*
* @return 所有值
* @throws TimiException 异常
*/
public List<V> values() {
List<V> r = new ArrayList<>();
List<K> keys = keys("*");
for (K key : keys) {
r.add(get(key));
}
return r;
}
/**
* 获取所有数据(包括键)
*
* @return 所有数据(包括键)
*/
public Map<K, V> map() {
Map<K, V> r = new HashMap<>();
List<K> ks = keys("*");
for (int i = 0; i < ks.size(); i++) {
r.put(ks.get(i), get(ks.get(i)));
}
return r;
}
/**
* 获取符合条件的 key
*
* @param pattern 表达式
* @return keys
*/
public List<K> keys(String pattern) {
List<K> keys = new ArrayList<>();
scan(pattern, item -> {
if (item != null) {
keys.add(serializer.deserialize(item));
}
});
return keys;
}
/**
* 销毁对象
*
* @param key 键
* @return true 为成功
*/
public boolean destroy(K key) {
if (TimiJava.isNotEmpty(key) && has(key)) {
Boolean isSucceed = redis.delete(key);
return isSucceed != null && isSucceed;
}
return false;
}
/** 删库 */
public void flushAll() {
Objects.requireNonNull(redis.getConnectionFactory()).getConnection().serverCommands().flushAll();
}
/**
* scan 实现
*
* @param pattern 表达式
* @param consumer 对迭代到的 key 进行操作
*/
private void scan(String pattern, Consumer<byte[]> consumer) {
redis.execute((RedisConnection connection) -> {
try (Cursor<byte[]> cursor = connection.keyCommands().scan(ScanOptions.scanOptions().count(Long.MAX_VALUE).match(pattern).build())) {
cursor.forEachRemaining(consumer);
return null;
}
});
}
}

View File

@@ -0,0 +1,99 @@
package com.imyeyu.spring.util;
import com.google.gson.Gson;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.nio.charset.StandardCharsets;
/**
* @author 夜雨
* @version 2023-07-17 16:20
*/
public class RedisSerializers {
/** 字符串序列化 */
public static final StringRedisSerializer STRING = new StringRedisSerializer();
/** 长整型序列化 */
public static final RedisSerializer<Integer> INTEGER = new RedisSerializer<>() {
@Override
public byte[] serialize(Integer value) throws SerializationException {
if (value == null) {
return null;
}
byte[] result = new byte[Integer.BYTES];
for (int i = Integer.BYTES - 1; 0 <= i; i--) {
result[i] = (byte) (value & 0xFF);
value >>= Byte.SIZE;
}
return result;
}
@Override
public Integer deserialize(byte[] bytes) throws SerializationException {
if (bytes == null) {
return null;
}
int result = 0;
for (int i = 0; i < Integer.BYTES; i++) {
result <<= Byte.SIZE;
result |= (bytes[i] & 0xFF);
}
return result;
}
};
/** 长整型序列化 */
public static final RedisSerializer<Long> LONG = new RedisSerializer<>() {
@Override
public byte[] serialize(Long value) throws SerializationException {
if (value == null) {
return null;
}
byte[] result = new byte[Long.BYTES];
for (int i = Long.BYTES - 1; 0 <= i; i--) {
result[i] = (byte) (value & 0xFF);
value >>= Byte.SIZE;
}
return result;
}
@Override
public Long deserialize(byte[] bytes) throws SerializationException {
if (bytes == null) {
return null;
}
long result = 0;
for (int i = 0; i < Long.BYTES; i++) {
result <<= Byte.SIZE;
result |= (bytes[i] & 0xFF);
}
return result;
}
};
/** Gson 序列化 */
public static <T> RedisSerializer<T> gsonSerializer(Class<T> clazz) {
return new RedisSerializer<>() {
private static final Gson GSON = new Gson();
@Override
public byte[] serialize(T object) throws SerializationException {
return GSON.toJson(object).getBytes(StandardCharsets.UTF_8);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (bytes == null) {
return null;
}
return GSON.fromJson(new String(bytes, StandardCharsets.UTF_8), clazz);
}
};
}
}

View File

@@ -0,0 +1,322 @@
package com.imyeyu.spring.util;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode;
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.Id;
import com.imyeyu.spring.annotation.table.Table;
import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.Creatable;
import com.imyeyu.spring.entity.Deletable;
import com.imyeyu.spring.entity.Destroyable;
import com.imyeyu.spring.entity.Updatable;
import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.utils.Text;
import com.imyeyu.utils.Time;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.builder.annotation.ProviderContext;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* 通用 Mapper SQL 代理器
*
* @author 夜雨
* @since 2025-02-05 23:34
*/
public class SQLProvider {
/** 反射缓存 */
private static final Map<Class<?>, EntityMeta> ENTITY_META_CACHE = new ConcurrentHashMap<>();
/**
* 插入
* <p><i>不实现 {@link Creatable} 也允许调用是合理的,某些数据属于关联数据,不参与主创建过程</i></p>
*
* @param entity 实体
* @return SQL
*/
public String insert(ProviderContext context, Object entity) {
EntityMeta meta = getEntityMeta(entity.getClass());
String columns = meta.fieldColumnList.stream().map(fc -> "`%s`".formatted(fc.columnName)).collect(Collectors.joining(", "));
String values = meta.fieldColumnList.stream().map(fc -> {
try {
if (fc.isAutoUUID && TimiJava.isEmpty(Ref.getFieldValue(entity, fc.field, String.class))) {
String uuid = UUID.randomUUID().toString();
if (fc.isAutoUpperUUID) {
uuid = uuid.toUpperCase();
}
Ref.setFieldValue(entity, fc.field, uuid);
}
} catch (IllegalAccessException e) {
throw new TimiException(TimiCode.ERROR).msgKey("auto set field:%s value error".formatted(fc.fieldName));
}
if (entity instanceof Creatable creatableEntity && creatableEntity.getCreatedAt() == null) {
creatableEntity.setCreatedAt(Time.now());
}
return "#{%s}".formatted(fc.fieldName);
}).collect(Collectors.joining(", "));
return "INSERT INTO `%s` (%s) VALUES (%s)".formatted(meta.table, columns, values);
}
/**
* 根据 ID 查询
*
* @param context 代理器上下文
* @param id ID
* @return SQL
*/
public String select(ProviderContext context, @Param("id") Object id) {
EntityMeta meta = getEntityMeta(context);
TimiException.required(meta.idFieldColumn, "not found id field in %s".formatted(meta.entityClass));
StringBuilder sql = new StringBuilder();
sql.append("SELECT * FROM `%s` WHERE `%s` = #{%s}".formatted(meta.table, meta.idFieldColumn.columnName, id));
if (meta.canDelete) {
sql.append(" AND (`deleted_at` IS NULL OR %s < `deleted_at`)".formatted(Time.now()));
}
return sql.append(" LIMIT 1").toString();
}
/**
* 根据实体非空字段使用等号查询
*
* @param entity 实体
* @return SQL
*/
public String selectByExample(Object entity) {
return selectAllByExample(entity) + BaseMapper.LIMIT_1;
}
/**
* 根据实体非空字段使用等号查询
*
* @param entity 实体
* @return SQL
*/
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);
})
.collect(Collectors.joining(" AND "));
StringBuilder sql = new StringBuilder();
sql.append("SELECT * FROM `%s` WHERE %s".formatted(meta.table, conditionClause));
if (meta.canDelete) {
if (TimiJava.isNotEmpty(conditionClause)) {
sql.append(" AND ");
}
sql.append("(`deleted_at` IS NULL OR %s < `deleted_at`)".formatted(Time.now()));
}
return sql.toString();
}
/**
* 根据 ID 更新,需要实体实现 {@link Updatable}
*
* @param entity 实体
* @return SQL
*/
public String update(Object entity) {
EntityMeta meta = getEntityMeta(entity.getClass());
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));
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);
})
.collect(Collectors.joining(", "));
return "UPDATE `%s` SET `%s` WHERE `%s` = #{%s}".formatted(meta.table, setClause, meta.idFieldColumn.columnName, meta.idFieldColumn.fieldName);
}
/**
* 根据 ID 软删除,需要实体实现 {@link Deletable}
*
* @param context 代理器上下文
* @param id ID
* @return SQL
*/
public String delete(ProviderContext context, @Param("id") Object id) {
EntityMeta meta = getEntityMeta(context);
TimiException.required(meta.idFieldColumn, "not found id field in %s".formatted(meta.entityClass));
TimiException.requiredTrue(meta.canDelete, "not allow soft delete for %s".formatted(meta.entityClass));
return "UPDATE `%s` SET `deleted_at` = %s WHERE `%s` = #{id}".formatted(meta.table, Time.now(), meta.idFieldColumn.columnName);
}
/**
* 硬删除,需要实体实现 {@link Destroyable}
*
* @param context 代理器上下文
* @param id ID
* @return SQL
*/
public String destroy(ProviderContext context, @Param("id") Object id) {
EntityMeta meta = getEntityMeta(context);
TimiException.required(meta.idFieldColumn, "not found id field in %s".formatted(meta.entityClass));
TimiException.requiredTrue(meta.canDestroy, "not allow destroy for %s".formatted(meta.entityClass));
return "DELETE FROM `%s` WHERE `%s` = #{id}".formatted(meta.table, meta.idFieldColumn.columnName);
}
/**
* 根据代理器上下文获取 Mapper 实体类元数据
*
* @param context 代理器上下文
* @return 实体类元数据
*/
private EntityMeta getEntityMeta(ProviderContext context) {
Type[] types = context.getMapperType().getGenericInterfaces();
ParameterizedType type = (ParameterizedType) types[0];
Class<?> entityClass = (Class<?>) type.getActualTypeArguments()[0];
return getEntityMeta(entityClass);
}
/**
* 获取实体类元数据
*
* @param entityClass 实体类
* @return 元数据
*/
private EntityMeta getEntityMeta(Class<?> entityClass) {
return ENTITY_META_CACHE.computeIfAbsent(entityClass, EntityMeta::new);
}
/**
* 实体元数据
*
* @author 夜雨
* @since 2025-02-05 23:47
*/
private static class EntityMeta {
/** 实体类 */
final Class<?> entityClass;
/** 表名 */
final String table;
/** ID 字段 */
final FieldColumn idFieldColumn;
/** 只读的列名字段名映射Map&lt;列名,字段名&gt; */
final List<FieldColumn> fieldColumnList;
/** true 为可更新 */
final boolean canUpdate;
/** true 为可删除(软删除) */
final boolean canDelete;
/** true 为可销毁(硬删除) */
final boolean canDestroy;
public EntityMeta(Class<?> entityClass) {
this.entityClass = entityClass;
// 表名
while (entityClass.isAnnotationPresent(Transient.class)) {
entityClass = entityClass.getSuperclass();
}
Table table = entityClass.getAnnotation(Table.class);
if (table == null) {
this.table = Text.camelCase2underscore(entityClass.getSimpleName());
} else {
this.table = table.value();
TimiException.required(this.table, String.format("empty table annotation value for %s entity", entityClass.getName()));
}
List<Field> allFieldList = Ref.listAllFields(entityClass);
FieldColumn idFieldColumn = null;
List<FieldColumn> fieldColumnList = new ArrayList<>();
for (int i = 0; i < allFieldList.size(); i++) {
Field field = allFieldList.get(i);
if (field.isAnnotationPresent(Transient.class)) {
continue;
}
FieldColumn fieldColumn = new FieldColumn(field);
if (fieldColumn.isId) {
TimiException.requiredNull(idFieldColumn, String.format("multi id field for %s entity", entityClass.getName()));
idFieldColumn = fieldColumn;
}
fieldColumnList.add(fieldColumn);
}
this.idFieldColumn = idFieldColumn;
this.fieldColumnList = List.of(fieldColumnList.toArray(new FieldColumn[0])); // 转为只读
canUpdate = Updatable.class.isAssignableFrom(entityClass);
canDelete = Deletable.class.isAssignableFrom(entityClass);
canDestroy = Destroyable.class.isAssignableFrom(entityClass);
}
}
/**
* 实体字段属性
*
* @author 夜雨
* @since 2025-02-07 09:54
*/
private static class FieldColumn {
/** 字段 */
final Field field;
/** 字段名 */
final String fieldName;
/** 列名 */
final String columnName;
/** true 为 ID */
final boolean isId;
/** true 为自动生成 UUID */
final boolean isAutoUUID;
final boolean isAutoUpperUUID;
public FieldColumn(Field field) {
this.field = field;
fieldName = field.getName();
Column column = field.getAnnotation(Column.class);
if (column == null) {
columnName = Text.camelCase2underscore(field.getName());
} else {
columnName = column.value();
TimiException.required(columnName, "empty field:%s column annotation value for %s entity".formatted(field.getName(), field.getDeclaringClass()));
}
isId = field.isAnnotationPresent(Id.class);
isAutoUUID = field.isAnnotationPresent(AutoUUID.class);
if (isAutoUUID) {
isAutoUpperUUID = field.getAnnotation(AutoUUID.class).upper();
} else {
isAutoUpperUUID = false;
}
}
}
}

View File

@@ -0,0 +1,2 @@
/** 其他工具 */
package com.imyeyu.spring.util;