Compare commits

...

37 Commits

Author SHA1 Message Date
wangyu
1670aed4cd feat: 完善代码 2024-07-01 10:59:37 +08:00
wangyu
d191ec10e3 feat: 完善全局脚本 2024-06-30 16:21:47 +08:00
wangyu
fe1416cc81 feat: 测试通过 2024-06-30 15:11:11 +08:00
wangyu
27ec8bf22d feat: 使用设计模式优化代码 2024-06-30 14:50:09 +08:00
wangyu
7758cede09 feat: 调整内置bean 2024-06-29 14:18:55 +08:00
wangyu
7f48c55203 feat: 实现各种关联条件 2024-06-29 14:08:02 +08:00
wangyu
89eaae41d5 feat: 实现多对多查询 2024-06-29 13:50:37 +08:00
wangyu
e448280290 feat: excel代码位置调整 2024-06-28 11:10:50 +08:00
wangyu
d84030494c feat: 完成非常牛逼的动态类型和关联感知,尽可能使用原生注解实现 2024-06-28 11:06:53 +08:00
wangyu
f6c2997c45 feat: 重大进展,关联查询可用 2024-06-28 00:06:54 +08:00
wangyu
0f27376a5f feat: 增加关联信息 2024-06-27 18:03:05 +08:00
wangyu
873635d6a9 feat: 最终使用回调+反射解决 2024-06-27 00:10:40 +08:00
wangyu
9ab6918432 feat: 增加填充器 2024-06-26 17:51:08 +08:00
wangyu
1d67d20416 feat: 规范化包,避免重复扫描仓库 2024-06-25 23:19:32 +08:00
wangyu
0ec52fd90a feat: 完善r2dbc场景 2024-06-25 18:11:37 +08:00
wangyu
2bf84f0c27 feat: 完善r2dbc场景 2024-06-25 16:17:05 +08:00
wangyu
4dfe06ffb2 feat: 升级Date 2024-06-25 10:49:11 +08:00
wangyu
c662fa1c65 feat: 完善核心逻辑 2024-06-24 23:48:29 +08:00
wangyu
4cceda5734 feat: 代码暂存,合并master分支 2024-06-24 17:49:58 +08:00
wangyu
ded964bb1e feat: 解决npe 2024-03-19 10:57:48 +08:00
wangyu
119e9b67e9 feat: 尽可能解耦reflections 2023-09-18 22:12:00 +08:00
wangyu
ec0c1afef7 feat: 使用串行编译,以兼容java17 2023-09-18 17:33:53 +08:00
wangyu
96aa81d9b9 feat: 优化编译逻辑 2023-09-13 14:10:35 +08:00
wangyu
38811b4f93 feat: 核心业务修复 2023-05-17 10:13:32 +08:00
wangyu
5c1e7d885f feat: 升级最新版本依赖 2023-05-08 16:33:54 +08:00
wangyu
b41f0f5ae8 feat: 优化编译,更加稳定 2022-10-04 22:51:10 +08:00
wangyu
040452e066 feat:优化仓库注册,动态导入 2022-10-03 22:21:14 +08:00
wangyu
eb23176b78 feat:避免反复查询,加快速度 2022-10-02 21:00:34 +08:00
wangyu
db2ff0519a feat:清理无用导入 2022-10-01 23:15:22 +08:00
wangyu
d13807e793 feat:完美解决http basic的问题 2022-10-01 21:42:24 +08:00
wangyu
89ac2c3fcf feat: 暂存 2022-08-05 17:31:37 +08:00
wangyu
36ba7dbe40 feat: 将灵感进行实现,后续待考虑 2022-08-05 17:22:05 +08:00
wangyu
dd2f780249 feat: 使用抽象泛型工厂模式解决问题,增强稳定性,可无限复用 2022-08-05 16:12:47 +08:00
wangyu
4742c1d50c feat: 保证整体结构稳定 2022-08-05 15:56:40 +08:00
wangyu
208ca062c6 feat: 暂存一下 2022-08-04 17:35:02 +08:00
wangyu
ff6929ed6d feat: 升级版本 2022-08-03 17:35:15 +08:00
wangyu
45940fc5e8 feat: 下一代大版本更新 2022-08-03 16:39:25 +08:00
300 changed files with 7271 additions and 1214 deletions

2
.gitignore vendored
View File

@ -4,6 +4,8 @@
**/*.iml
**/.idea/**
.flattened-pom.xml
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>flyfish-framework</artifactId>
<groupId>com.flyfish.framework</groupId>
<version>0.0.1-SNAPSHOT</version>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -17,14 +17,20 @@
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-relational</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.flyfish.framework</groupId>
<artifactId>flyfish-web</artifactId>
<version>${project.version}</version>
<version>${revision}</version>
</dependency>
</dependencies>
</project>

View File

@ -4,8 +4,8 @@ import com.flyfish.framework.annotations.EnumValue;
import com.flyfish.framework.annotations.Order;
import com.flyfish.framework.annotations.Property;
import com.flyfish.framework.approval.enums.ApproveStatus;
import com.flyfish.framework.builder.CriteriaBuilder;
import com.flyfish.framework.domain.authorized.AuthorizedQo;
import com.flyfish.framework.query.QueryDefinition;
import lombok.Getter;
import lombok.Setter;
@ -24,7 +24,7 @@ public class ApprovalDomainQo<T extends ApprovalDomain> extends AuthorizedQo<T>
private String status;
@Override
public CriteriaBuilder<T> criteriaBuilder() {
return super.criteriaBuilder().with("status", "approveStatus");
public QueryDefinition queryBuilder() {
return super.queryBuilder().mutate().and(T::getApproveStatus).eq(status);
}
}

View File

@ -5,6 +5,8 @@ import com.flyfish.framework.domain.base.AuditDomain;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;
/**
* 审批记录
@ -12,6 +14,7 @@ import org.springframework.data.mongodb.core.mapping.Document;
* @author wangyu
*/
@Document(collection = "approve-records")
@Table("t_approve_records")
@Getter
@Setter
public class ApproveRecord extends AuditDomain {
@ -23,9 +26,11 @@ public class ApproveRecord extends AuditDomain {
private String module;
// 模块名称
@Column("module_name")
private String moduleName;
// 数据id
@Column("data_id")
private String dataId;
// 审批人

View File

@ -5,6 +5,7 @@ import com.flyfish.framework.approval.enums.ApproveAction;
import com.flyfish.framework.domain.base.Vo;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.Optional;
@ -23,7 +24,7 @@ public class ApproveRecordListVo implements Vo<ApproveRecord> {
private String approver;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
private Date approveTime;
private LocalDateTime approveTime;
private String action;

View File

@ -1,7 +1,7 @@
package com.flyfish.framework.approval.domain.record;
import com.flyfish.framework.builder.CriteriaBuilder;
import com.flyfish.framework.domain.base.NameLikeQo;
import com.flyfish.framework.query.QueryDefinition;
import lombok.Getter;
import lombok.Setter;
@ -27,7 +27,12 @@ public class ApproveRecordQo extends NameLikeQo<ApproveRecord> {
private String approver;
@Override
public CriteriaBuilder<ApproveRecord> criteriaBuilder() {
return super.criteriaBuilder().with("module", "dataId", "approved", "approver");
public QueryDefinition queryBuilder() {
return super.queryBuilder()
.mutate()
.and(ApproveRecord::getModule).eq(module)
.and(ApproveRecord::getDataId).eq(dataId)
.and(ApproveRecord::getApproved).eq(approved)
.and(ApproveRecord::getApprover).eq(approver);
}
}

View File

@ -5,6 +5,7 @@ import com.flyfish.framework.approval.domain.record.ApproveRecord;
import com.flyfish.framework.domain.base.Vo;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.Date;
/**
@ -24,7 +25,7 @@ public class ApprovalListVo implements Vo<ApproveRecord> {
private String dataId;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
private Date createTime;
private LocalDateTime createTime;
private String creator;

View File

@ -39,6 +39,11 @@ public class ModuleDelegateService {
// 审批的服务们
private Map<String, BaseReactiveService<ApprovalDomain>> approvalServices;
private String getModuleName(BaseReactiveService<ApprovalDomain> service) {
return ReflectionUtils.getGenericType(service.getClass()).map(this::moduleName)
.orElseThrow(() -> new InvalidBusinessException("系统模块名称抽取失败!"));
}
@Autowired
@SuppressWarnings("all")
public void setApprovalServices(ObjectProvider<BaseReactiveService> services) {
@ -47,7 +52,7 @@ public class ModuleDelegateService {
.map(clazz -> ClassUtils.isAssignable(clazz, ApprovalDomain.class))
.orElse(false))
.map(service -> (BaseReactiveService<ApprovalDomain>) service)
.collect(Collectors.toMap(s -> s.getEntityInformation().getCollectionName(), s -> s));
.collect(Collectors.toMap(this::getModuleName, s -> s));
}
/**

View File

@ -0,0 +1,21 @@
CREATE TABLE IF NOT EXISTS `t_approve_records`
(
`id` VARCHAR(36) NOT NULL COMMENT '主键',
`code` VARCHAR(32) NOT NULL COMMENT '编码',
`name` VARCHAR(100) NOT NULL COMMENT '名称',
`approved` BIT(1) NULL DEFAULT b'0' COMMENT '是否已审批',
`module` VARCHAR(50) NOT NULL COMMENT '模块',
`module_name` VARCHAR(50) NULL COMMENT '模块名称',
`data_id` VARCHAR(36) NULL COMMENT '数据id',
`approver` VARCHAR(36) NULL COMMENT '审批人',
`action` VARCHAR(24) NULL COMMENT '审批动作',
`opinion` VARCHAR(500) NULL COMMENT '审批意见',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`modify_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
`creator` VARCHAR(36) NULL COMMENT '创建人名称',
`creator_id` VARCHAR(36) NULL COMMENT '创建人id',
`modifier` VARCHAR(36) NULL COMMENT '修改人名称',
`modifier_id` VARCHAR(36) NULL COMMENT '修改人id',
`delete` BIT(1) NOT NULL DEFAULT b'0',
PRIMARY KEY (`id`)
) COMMENT '审批记录';

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>flyfish-framework</artifactId>
<groupId>com.flyfish.framework</groupId>
<version>0.0.1-SNAPSHOT</version>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -17,10 +17,20 @@
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-relational</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.flyfish.framework</groupId>
<artifactId>flyfish-web</artifactId>
<version>${project.version}</version>
<version>${revision}</version>
</dependency>
</dependencies>
</project>

View File

@ -6,6 +6,7 @@ import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.relational.core.mapping.Table;
/**
* 系统备份
@ -15,6 +16,7 @@ import org.springframework.data.mongodb.core.mapping.Document;
@Getter
@Setter
@Document(collection = "backups")
@Table("t_backup")
public class Backup extends AuditDomain {
// 文件路径

View File

@ -3,10 +3,11 @@ package com.flyfish.framework.backup.domain;
import com.flyfish.framework.annotations.Property;
import lombok.Data;
import java.util.Date;
import java.time.LocalDateTime;
/**
* 版本号实体
*
* @author wangyu
*/
@Data
@ -22,5 +23,5 @@ public class Version {
private String script;
@Property("创建时间")
private Date time;
private LocalDateTime time;
}

View File

@ -2,7 +2,10 @@ package com.flyfish.framework.backup.scheduler;
import com.alibaba.fastjson.JSON;
import com.flyfish.framework.backup.domain.Backup;
import com.flyfish.framework.context.DateContext;
import com.flyfish.framework.domain.base.DomainService;
import com.flyfish.framework.query.Query;
import com.flyfish.framework.repository.ReactiveEntityOperations;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@ -12,8 +15,8 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.data.util.CastUtils;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.unit.DataSize;
@ -22,12 +25,11 @@ import reactor.core.publisher.Mono;
import java.io.IOException;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Date;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
@ -45,9 +47,9 @@ public class BackupScheduler {
// data buffer 工厂
private final DataBufferFactory factory = new DefaultDataBufferFactory();
// 用于注入所有集合名称
private List<MongoEntityInformation<?, String>> collections;
private List<EntityInformation<?, String>> collections;
// 异步的mongo操作可以快速备份
private ReactiveMongoOperations operations;
private ReactiveEntityOperations operations;
// 备份路径
private String backupPath = "/opt/flyfish/backup";
@ -57,8 +59,8 @@ public class BackupScheduler {
}
@Autowired
public void setOperations(ReactiveMongoOperations reactiveMongoOperations) {
this.operations = reactiveMongoOperations;
public void setOperations(ReactiveEntityOperations reactiveEntityOperations) {
this.operations = reactiveEntityOperations;
}
@Autowired
@ -81,7 +83,7 @@ public class BackupScheduler {
String parent = createIfNotExists(backupPath + "/" + code);
// 开始备份先构造一个指示器
Backup backup = new Backup();
backup.setCreateTime(new Date());
backup.setCreateTime(LocalDateTime.now());
backup.setCreator("系统");
backup.setCode(code);
backup.setStatus(Backup.Status.RUNNING);
@ -92,8 +94,8 @@ public class BackupScheduler {
operations.save(backup)
.thenMany(Flux.fromIterable(this.collections))
.flatMap(info -> operations
.findAll(info.getJavaType(), info.getCollectionName()).collectList()
.map(list -> new BackupIndex.BackupContent(info.getCollectionName(), JSON.toJSONBytes(list)))
.findAll(Query.empty(), CastUtils.cast(info)).collectList()
.map(list -> new BackupIndex.BackupContent(info.getJavaType().getSimpleName(), JSON.toJSONBytes(list)))
)
.flatMap(content -> writeContents(content, parent))
.collectList()
@ -115,7 +117,7 @@ public class BackupScheduler {
})
.then(Mono.defer(() -> {
backup.setLog("成功备份");
backup.setPeriod(new Date().getTime() - backup.getCreateTime().getTime());
backup.setPeriod(DateContext.distance(backup.getCreateTime(), LocalDateTime.now()));
backup.setStatus(Backup.Status.SUCCESS);
backup.setSize(DataSize.ofBytes(sizeCounter.get()).toKilobytes() + "KB");
return operations.save(backup);
@ -125,7 +127,7 @@ public class BackupScheduler {
}, e -> {
backup.setStatus(Backup.Status.FAILED);
backup.setLog(e.getMessage());
backup.setPeriod(new Date().getTime() - backup.getCreateTime().getTime());
backup.setPeriod(DateContext.distance(backup.getCreateTime(), LocalDateTime.now()));
operations.save(backup).subscribe();
});

View File

@ -0,0 +1,19 @@
CREATE TABLE IF NOT EXISTS `t_backup`
(
`id` VARCHAR(36) NOT NULL COMMENT '主键',
`code` VARCHAR(32) NOT NULL COMMENT '编码',
`name` VARCHAR(100) NOT NULL COMMENT '名称',
`filepath` VARCHAR(1000) NULL COMMENT '文件路径',
`log` TEXT NULL COMMENT '备份日志',
`status` VARCHAR(24) NULL COMMENT '备份状态',
`period` BIGINT NULL COMMENT '备份耗时',
`size` VARCHAR(64) NULL COMMENT '备份大小',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`modify_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
`creator` VARCHAR(36) NULL COMMENT '创建人名称',
`creator_id` VARCHAR(36) NULL COMMENT '创建人id',
`modifier` VARCHAR(36) NULL COMMENT '修改人名称',
`modifier_id` VARCHAR(36) NULL COMMENT '修改人id',
`delete` BIT(1) NOT NULL DEFAULT b'0',
PRIMARY KEY (`id`)
) COMMENT '备份记录表';

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>flyfish-framework</artifactId>
<groupId>com.flyfish.framework</groupId>
<version>0.0.1-SNAPSHOT</version>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -59,10 +59,6 @@
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
</dependency>
</dependencies>
</project>
</project>

View File

@ -2,6 +2,7 @@ package com.flyfish.framework.compiler;
import com.flyfish.framework.compiler.support.DelegateJavaCompiler;
import com.flyfish.framework.compiler.support.SimpleJavaCompiler;
import org.springframework.lang.Nullable;
import java.io.Closeable;
import java.io.IOException;
@ -37,7 +38,18 @@ public interface DynamicJavaCompiler extends Closeable {
*
* @param name 文件名
* @param source 源码
* @return 完整类名
*/
@Nullable
String compile(String name, String source) throws IOException;
/**
* 加载类
* 用于全部编译后再加载
*
* @param name 名称
* @return 结果
*/
Class<?> compile(String name, String source) throws ClassNotFoundException, IOException;
@Nullable
Class<?> load(String name);
}

View File

@ -7,9 +7,9 @@ package com.flyfish.framework.compiler.core;
*/
public interface ClassLoaders {
MemoryClassLoader CLASS_LOADER = new MemoryClassLoader();
DynamicClassLoader CLASS_LOADER = new MemoryClassLoader();
static MemoryClassLoader memory() {
static DynamicClassLoader memory() {
return CLASS_LOADER;
}
}

View File

@ -0,0 +1,24 @@
package com.flyfish.framework.compiler.core;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Map;
/**
* 动态类加载器
*
* @author wangyu
*/
public abstract class DynamicClassLoader extends URLClassLoader {
DynamicClassLoader() {
super(new URL[0], DynamicClassLoader.class.getClassLoader());
}
/**
* 获取类的字节码
*
* @return 结果
*/
public abstract Map<String, byte[]> getClassBytes();
}

View File

@ -1,30 +1,31 @@
package com.flyfish.framework.compiler.core;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.Closeable;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
/**
* In-memory compile Java source code as String.
*
* @author michael
*/
public class JavaStringCompiler implements Closeable {
@Slf4j
class JavaStringCompiler implements VendorJavaCompiler {
private final JavaCompiler compiler;
private final MemoryJavaFileManager manager;
private final MemoryClassLoader classLoader;
public JavaStringCompiler(MemoryClassLoader classLoader) {
JavaStringCompiler(DynamicClassLoader classLoader) {
this.compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager stdManager = compiler.getStandardFileManager(null, null, null);
this.manager = new MemoryJavaFileManager(stdManager, classLoader);
this.classLoader = classLoader;
}
/**
@ -34,27 +35,27 @@ public class JavaStringCompiler implements Closeable {
* @param source The source code as String.
* @return The compiled results as Map that contains class name as key,
* class binary as value.
* @throws IOException If compile error.
*/
public Map<String, byte[]> compile(String fileName, String source) {
@Override
public String compile(String fileName, String source) {
JavaFileObject javaFileObject = manager.makeStringSource(fileName, source);
JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, Collections.singletonList(javaFileObject));
Boolean result = task.call();
if (result == null || !result) {
throw new RuntimeException("Compilation failed.");
String className = StringUtils.substringBeforeLast(fileName, ".");
if (BooleanUtils.isTrue(task.call())) {
return manager.getClassBytes().keySet().stream().filter(key -> key.endsWith(className))
.findFirst().orElse(null);
}
return manager.getClassBytes();
return null;
}
/**
* Load class from compiled classes.
* 获取动态classLoader
*
* @param name Full class name.
* @return The Class instance.
* @throws ClassNotFoundException If class not found.
* @return 结果
*/
public Class<?> loadClass(String name) throws ClassNotFoundException {
return classLoader.loadClass(name);
@Override
public DynamicClassLoader getClassLoader() {
return ClassLoaders.memory();
}
@Override
@ -63,11 +64,9 @@ public class JavaStringCompiler implements Closeable {
if (null != manager) {
manager.close();
}
if (null != classLoader) {
classLoader.close();
}
getClassLoader().close();
} catch (IOException e) {
e.printStackTrace();
log.error("关闭类编译器失败!", e);
}
}
}

View File

@ -2,8 +2,6 @@ package com.flyfish.framework.compiler.core;
import org.apache.commons.lang3.StringUtils;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ -12,15 +10,11 @@ import java.util.concurrent.ConcurrentHashMap;
*
* @author michael
*/
class MemoryClassLoader extends URLClassLoader {
class MemoryClassLoader extends DynamicClassLoader {
// class name to class bytes:
private final Map<String, byte[]> classBytes = new ConcurrentHashMap<>();
public MemoryClassLoader() {
super(new URL[0], MemoryClassLoader.class.getClassLoader());
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] buf = classBytes.get(name);
@ -31,6 +25,7 @@ class MemoryClassLoader extends URLClassLoader {
return defineClass(name, buf);
}
@Override
public Map<String, byte[]> getClassBytes() {
return classBytes;
}

View File

@ -1,5 +1,7 @@
package com.flyfish.framework.compiler.core;
import lombok.Getter;
import javax.tools.*;
import java.io.ByteArrayOutputStream;
import java.io.FilterOutputStream;
@ -7,87 +9,82 @@ import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.nio.CharBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* In-memory java file manager.
*
* @author michael
*/
@Getter
class MemoryJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
// compiled classes in bytes:
private final Map<String, byte[]> classBytes;
// compiled classes in bytes:
private final Map<String, byte[]> classBytes;
MemoryJavaFileManager(JavaFileManager fileManager, MemoryClassLoader classLoader) {
super(fileManager);
classBytes = classLoader.getClassBytes();
}
MemoryJavaFileManager(JavaFileManager fileManager, DynamicClassLoader classLoader) {
super(fileManager);
classBytes = classLoader.getClassBytes();
}
public Map<String, byte[]> getClassBytes() {
return this.classBytes;
}
@Override
public void flush() {
}
@Override
public void flush() {
}
@Override
public void close() throws IOException {
classBytes.clear();
}
@Override
public void close() throws IOException {
classBytes.clear();
}
@Override
public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind,
FileObject sibling) throws IOException {
if (kind == JavaFileObject.Kind.CLASS) {
return new MemoryOutputJavaFileObject(className);
} else {
return super.getJavaFileForOutput(location, className, kind, sibling);
}
}
@Override
public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind,
FileObject sibling) throws IOException {
if (kind == JavaFileObject.Kind.CLASS) {
return new MemoryOutputJavaFileObject(className);
} else {
return super.getJavaFileForOutput(location, className, kind, sibling);
}
}
JavaFileObject makeStringSource(String name, String code) {
return new MemoryInputJavaFileObject(name, code);
}
JavaFileObject makeStringSource(String name, String code) {
return new MemoryInputJavaFileObject(name, code);
}
static class MemoryInputJavaFileObject extends SimpleJavaFileObject {
static class MemoryInputJavaFileObject extends SimpleJavaFileObject {
final String code;
final String code;
MemoryInputJavaFileObject(String name, String code) {
super(URI.create("string:///" + name), Kind.SOURCE);
this.code = code;
}
MemoryInputJavaFileObject(String name, String code) {
super(URI.create("string:///" + name), Kind.SOURCE);
this.code = code;
}
@Override
public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
return CharBuffer.wrap(code);
}
}
@Override
public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
return CharBuffer.wrap(code);
}
}
class MemoryOutputJavaFileObject extends SimpleJavaFileObject {
final String name;
class MemoryOutputJavaFileObject extends SimpleJavaFileObject {
final String name;
MemoryOutputJavaFileObject(String name) {
super(URI.create("string:///" + name), Kind.CLASS);
this.name = name;
}
MemoryOutputJavaFileObject(String name) {
super(URI.create("string:///" + name), Kind.CLASS);
this.name = name;
}
@Override
public OutputStream openOutputStream() {
return new FilterOutputStream(new ByteArrayOutputStream()) {
@Override
public void close() throws IOException {
out.close();
ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
classBytes.put(name, bos.toByteArray());
}
};
}
@Override
public OutputStream openOutputStream() {
return new FilterOutputStream(new ByteArrayOutputStream()) {
@Override
public void close() throws IOException {
out.close();
ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
classBytes.put(name, bos.toByteArray());
}
};
}
}
}
}

View File

@ -0,0 +1,31 @@
package com.flyfish.framework.compiler.core;
import java.io.Closeable;
/**
* 第三方java编译器
*
* @author wangyu
*/
public interface VendorJavaCompiler extends Closeable {
static VendorJavaCompiler string() {
return new JavaStringCompiler(ClassLoaders.memory());
}
/**
* 编译并返回编译结果
*
* @param name 文件名
* @param source 源码
* @return 完整类名
*/
String compile(String name, String source);
/**
* 获取动态classLoader
*
* @return 结果
*/
DynamicClassLoader getClassLoader();
}

View File

@ -1,21 +1,21 @@
package com.flyfish.framework.compiler.support;
import com.flyfish.framework.compiler.DynamicJavaCompiler;
import com.flyfish.framework.compiler.core.ClassLoaders;
import com.flyfish.framework.compiler.core.JavaStringCompiler;
import org.apache.commons.lang3.StringUtils;
import com.flyfish.framework.compiler.core.VendorJavaCompiler;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.io.IOException;
/**
* 使用第三方成熟框架
*
* @author wangyu
*/
@Slf4j
public class DelegateJavaCompiler implements DynamicJavaCompiler {
// 第三方编译器
private final JavaStringCompiler compiler = new JavaStringCompiler(ClassLoaders.memory());
private final VendorJavaCompiler compiler = VendorJavaCompiler.string();
private DelegateJavaCompiler() {
}
@ -29,22 +29,32 @@ public class DelegateJavaCompiler implements DynamicJavaCompiler {
*
* @param name 文件名
* @param source 源码
* @return 完整类名
*/
@Override
public String compile(String name, String source) {
return compiler.compile(name, source);
}
/**
* 加载类
* 用于全部编译后再加载
*
* @param name 名称
* @return 结果
*/
@Override
public Class<?> compile(String name, String source) throws ClassNotFoundException {
Map<String, byte[]> byteMap = compiler.compile(name, source);
String className = StringUtils.substringBeforeLast(name, ".");
for (String key : byteMap.keySet()) {
if (key.contains(className)) {
return compiler.loadClass(key);
}
public Class<?> load(String name) {
try {
return compiler.getClassLoader().loadClass(name);
} catch (ClassNotFoundException e) {
log.warn("类不存在!{}", name);
return null;
}
throw new RuntimeException("【java编译器】源码中没有可以编译的类");
}
@Override
public void close() {
public void close() throws IOException {
compiler.close();
}
@ -52,12 +62,4 @@ public class DelegateJavaCompiler implements DynamicJavaCompiler {
private static final DelegateJavaCompiler INSTANCE = new DelegateJavaCompiler();
}
// public static void main(String[] args) throws ClassNotFoundException, IOException {
// long time = System.currentTimeMillis();
// System.out.println(StreamUtils.copyToString(DelegateJavaCompiler.class.getResourceAsStream("./"), StandardCharsets.UTF_8));
// Class<?> clazz = getInstance().compile("Fuck.java", "package com.flyfish.project; public class Fuck {}");
// System.out.println(clazz + "耗时:" + (System.currentTimeMillis() - time) / 1000.0 + "");
// }
}

View File

@ -1,7 +1,8 @@
package com.flyfish.framework.compiler.support;
import com.flyfish.framework.compiler.DynamicJavaCompiler;
import org.springframework.util.StreamUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
@ -44,10 +45,10 @@ public class SimpleJavaCompiler implements DynamicJavaCompiler {
* @return 结果
*/
@Override
public Class<?> compile(String name, String source) throws ClassNotFoundException {
public String compile(String name, String source) {
// 存在缓存写入
if (classMap.containsKey(name)) {
return classMap.get(name);
return classMap.get(name).getName();
}
// 构建java文件对象
JavaFileObject fileObject = new StringJavaObject(name, source);
@ -55,12 +56,32 @@ public class SimpleJavaCompiler implements DynamicJavaCompiler {
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null,
options, null, Collections.singletonList(fileObject));
// 阻塞编译
if (task.call()) {
Class<?> clazz = Class.forName("com.flyfish.project." + name);
classMap.put(name, clazz);
return clazz;
if (BooleanUtils.isTrue(task.call())) {
return "com.flyfish.project." + name;
}
return null;
}
/**
* 加载类
* 用于全部编译后再加载
*
* @param name 名称
* @return 结果
*/
@Override
public Class<?> load(String name) {
String simpleName = StringUtils.substringAfterLast(name, ".");
if (classMap.containsKey(simpleName)) {
return classMap.get(simpleName);
}
try {
Class<?> clazz = Class.forName(name);
classMap.put(clazz.getSimpleName(), clazz);
return clazz;
} catch (ClassNotFoundException e) {
return null;
}
throw new RuntimeException("【java编译器】尝试编译源代码出现异常");
}
@Override

View File

@ -12,6 +12,7 @@ import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -22,26 +23,28 @@ import java.util.stream.Collectors;
*/
public class TemplateCompiler {
// 变量池缓存
private static final Map<Class<?>, Map<String, Method>> POOL_CACHE = new ConcurrentHashMap<>();
/**
* 编译模板文件和源码数据为源代码字符串
*
* @param dataClass 数据class
* @param filename 文件名
* @param filename 文件名
* @return 结果
*/
public static Function<JavaSource, String> compile(Class<?> dataClass, String filename) {
public static <T> Function<T, String> compile(Class<T> dataClass, String filename) {
// 获取文件内容
InputStream fis = TemplateCompiler.class.getResourceAsStream(filename);
try {
// 获取模板内容
String template = StreamUtils.copyToString(fis, StandardCharsets.UTF_8);
// 准备变量池
Map<String, PropertyDescriptor> pool = Arrays.stream(BeanUtils.getPropertyDescriptors(dataClass))
.collect(Collectors.toMap(descriptor -> "#{" + descriptor.getName() + "}", a -> a));
Map<String, Method> pool = methodPool(dataClass);
// 编译
return cfg -> pool.keySet().stream().reduce(template, (result, key) -> {
// 获取读取方法
Method method = pool.get(key).getReadMethod();
Method method = pool.get(key);
Object value = null;
try {
// 取得结果
@ -57,6 +60,17 @@ public class TemplateCompiler {
}
}
/**
* 数据类方法池
*
* @param dataClass 数据类
* @return 结果
*/
private static Map<String, Method> methodPool(Class<?> dataClass) {
return POOL_CACHE.computeIfAbsent(dataClass, k -> Arrays.stream(BeanUtils.getPropertyDescriptors(k))
.collect(Collectors.toMap(descriptor -> "#{" + descriptor.getName() + "}", PropertyDescriptor::getReadMethod)));
}
/**
* 解析单个值
*

View File

@ -5,6 +5,9 @@ import org.springframework.stereotype.Component;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@ -20,6 +23,7 @@ public class DateContext {
private static DateContext instance;
private ThreadLocal<DateFormat> formatThreadLocal = new ThreadLocal<>();
private ThreadLocal<Map<String, DateFormat>> mapThreadLocal = new ThreadLocal<>();
private static final ZoneId zoneId = ZoneId.of("Asia/Shanghai");
public DateContext() {
instance = this;
@ -29,6 +33,19 @@ public class DateContext {
return instance;
}
public static LocalDateTime ofMillis(long millis) {
Instant instant = Instant.ofEpochMilli(millis);
return LocalDateTime.ofInstant(instant, zoneId);
}
public static long toMillis(LocalDateTime localDateTime) {
return localDateTime.atZone(zoneId).toInstant().toEpochMilli();
}
public static long distance(LocalDateTime start, LocalDateTime end) {
return toMillis(end) - toMillis(start);
}
public static DateFormat simpleFormat() {
DateFormat format = instance.formatThreadLocal.get();
if (format == null) {

View File

@ -0,0 +1,15 @@
package com.flyfish.framework.utils;
/**
* 构建器表示
*/
@FunctionalInterface
public interface Builder<T> {
/**
* 构建对象
*
* @return 构建结果
*/
T build();
}

View File

@ -0,0 +1,109 @@
package com.flyfish.framework.utils;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.lang.NonNull;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.*;
/**
* 内部的包扫描器提供特定注解扫描
*
* @author wangyu
*/
public class ClassPathResourceScanner extends ClassPathScanningCandidateComponentProvider {
private final TypeFilter filter;
/**
* 扫描某个特定注解
*
* @param annoType 注解类型
* @return 结果
*/
public static ClassPathResourceScanner forAnnotation(Class<? extends Annotation> annoType) {
return new ClassPathResourceScanner(new AnnotationTypeFilter(annoType));
}
/**
* 扫描子类
*
* @param superType 父类型
* @return 结果
*/
public static ClassPathResourceScanner forSuperType(Class<?> superType) {
return new ClassPathResourceScanner(new AssignableTypeFilter(superType));
}
private ClassPathResourceScanner(TypeFilter filter) {
super(false);
this.filter = filter;
resetFilters(false);
addIncludeFilter(filter);
}
@Override
protected boolean isCandidateComponent(@NonNull MetadataReader metadataReader) throws IOException {
return filter.match(metadataReader, getMetadataReaderFactory());
}
@Override
protected boolean isCandidateComponent(@NonNull AnnotatedBeanDefinition beanDefinition) {
return true;
}
private Class<?> resolveType(BeanDefinition bf, ClassLoader cl) {
if (null != bf.getBeanClassName()) {
try {
return ClassUtils.forName(bf.getBeanClassName(), cl);
} catch (ClassNotFoundException e) {
return null;
}
}
return null;
}
/**
* 扫描类
*
* @param packageNames 包名
* @return 结果
*/
public Set<Class<?>> scan(Collection<String> packageNames) {
// 获取扫描器的ClassLoader保证同源
ClassLoader cl = this.getClass().getClassLoader();
Set<Class<?>> scanned = new HashSet<>();
for (String packageName : packageNames) {
Set<BeanDefinition> bfs = findCandidateComponents(packageName);
// 不存在不要浪费性能
if (CollectionUtils.isEmpty(bfs)) continue;
// 代理并生成子类并注册到ioc容器
for (BeanDefinition bf : bfs) {
Class<?> resolved = resolveType(bf, cl);
if (null != resolved) {
scanned.add(resolved);
}
}
}
return scanned;
}
/**
* 扫描类
*
* @param packageNames 包名
* @return 结果
*/
public Set<Class<?>> scan(String... packageNames) {
return scan(Arrays.asList(packageNames));
}
}

View File

@ -0,0 +1,36 @@
package com.flyfish.framework.utils;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* LRU (least recently used)最近最久未使用缓存<br>
* 根据使用时间来判定对象是否被持续缓存<br>
* 当对象被访问时放入缓存当缓存满了最久未被使用的对象将被移除<br>
* 此缓存基于LinkedHashMap因此当被缓存的对象每被访问一次这个对象的key就到链表头部<br>
* 这个算法简单并且非常快他比FIFO有一个显著优势是经常使用的对象不太可能被移除缓存<br>
* 缺点是当缓存满时不能被很快的访问
*
* @param <K> 键类型
* @param <V> 值类型
* @author wangyu
*/
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private static final long serialVersionUID = 1L;
private final int maxSize;
public LRUCache(int maxSize) {
this(maxSize, 16, 0.75f, false);
}
public LRUCache(int maxSize, int initialCapacity, float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor, accessOrder);
this.maxSize = maxSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return this.size() > this.maxSize;
}
}

View File

@ -0,0 +1,12 @@
package com.flyfish.framework.utils;
public interface Supportable<T> {
/**
* 判断是否支持实体
*
* @param entity 实体信息
* @return 结果
*/
boolean supports(T entity);
}

16
flyfish-data/README.md Normal file
View File

@ -0,0 +1,16 @@
# 核心数据框架
本模块是flyfish framework的最核心能力
其提供无感知的多种数据源支持并完整适配了mongodb和rdbms的查询表现
用户只需要用一套api即可完成数据的查询或修改无需为了底层实现而大费周章重新开发。
## 核心架构
1. 基于Spring SPI模式声明式注入查询实现工厂动态替换查询构建逻辑
2. 按需引入不浪费任何依赖节省空间。使用maven的按需引入模式让打包后的结果不必因为重量级的框架而变得冗余
3. 指哪打哪稳定快速。集成最新版本的spring mongo引擎和spring data r2dbc
4. 核心框架广泛使用,保证"0bug"。
## 无限扩展
1. 根据实际业务需求采用mongo、jpa、r2dbc的方式动态选择实现而业务和底层存储实现无关完全解耦
2. 健壮的架构能够支撑99%的业务
3. 完善的关联机制,多表关联做到全自动化

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>flyfish-data</artifactId>
<groupId>com.flyfish.framework</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>flyfish-data-common</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.flyfish.framework</groupId>
<artifactId>flyfish-common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>com.flyfish.framework</groupId>
<artifactId>flyfish-data-domain</artifactId>
<version>${revision}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
</dependency>
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -9,6 +9,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration;
import org.springframework.context.annotation.Bean;
/**
@ -17,7 +18,7 @@ import org.springframework.context.annotation.Bean;
*
* @author wangyu
*/
@AutoConfigureBefore(MongoDataAutoConfiguration.class)
@AutoConfigureBefore({R2dbcDataAutoConfiguration.class, MongoDataAutoConfiguration.class})
@ConditionalOnProperty("flyfish.jasypt.key")
@EnableEncryptableProperties
public class DecryptConfig {
@ -34,7 +35,6 @@ public class DecryptConfig {
return new AESEncryptablePropertyResolver(password);
}
@Bean(name = "encryptablePropertyFilter")
public EncryptablePropertyFilter encryptablePropertyFilter() {
return (source, name) -> StringUtils.endsWithIgnoreCase(name, PASSWORD_KEY);

View File

@ -2,9 +2,9 @@ package com.flyfish.framework.config;
import com.flyfish.framework.bean.EnumValue;
import com.flyfish.framework.enums.NamedEnum;
import com.flyfish.framework.utils.ClassPathResourceScanner;
import com.flyfish.framework.utils.StringFormats;
import org.apache.commons.lang3.ClassUtils;
import org.reflections.Reflections;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
@ -13,6 +13,7 @@ import java.util.stream.Collectors;
/**
* 枚举相关专门处理枚举
*
* @author wangyu
*/
public class EnumConfig implements InitializingBean, ImportBeanDefinitionRegistrar {
@ -23,14 +24,13 @@ public class EnumConfig implements InitializingBean, ImportBeanDefinitionRegistr
private final Map<String, List<EnumValue>> values = new HashMap<>();
public EnumConfig() {
Reflections reflections = new Reflections("com.flyfish.project");
// 得到Resource注解的类
Set<Class<? extends NamedEnum>> classSet = reflections.getSubTypesOf(NamedEnum.class);
// 得到枚举类
Set<Class<?>> classSet = ClassPathResourceScanner.forSuperType(NamedEnum.class).scan("com.flyfish.project");
// 注入
classSet.stream().filter(clazz -> ClassUtils.isAssignable(clazz, Enum.class)).forEach(clazz -> {
String name = StringFormats.camel2Line(clazz.getSimpleName());
List<EnumValue> values = Arrays.stream(clazz.getEnumConstants()).reduce(new ArrayList<>(), (result, item) -> {
result.add(new EnumValue(((Enum<?>) item).name(), item.getName()));
result.add(new EnumValue(((Enum<?>) item).name(), ((NamedEnum) item).getName()));
return result;
}, (a, b) -> a);
this.mapper.put(name, values.stream().collect(Collectors.toMap(EnumValue::getValue, EnumValue::getText)));

View File

@ -36,4 +36,11 @@ public class AESEncryptablePropertyResolver implements EncryptablePropertyResolv
}
return value;
}
public static void main(String[] args) {
String test = "$-dcfe1dbfdf51e1b007c68c6c0b7008c774ed7c05d8aca2797562159ac1098bcc5a9fdcb42e6acb6562944c21969d34a2";
AESEncryptablePropertyResolver resolver = new AESEncryptablePropertyResolver("sxu-service-2020");
String value = resolver.resolvePropertyValue(test);
System.out.println(value);
}
}

View File

@ -1,11 +1,11 @@
package com.flyfish.framework.domain.authorized;
import com.flyfish.framework.builder.CriteriaBuilder;
import com.flyfish.framework.domain.base.NameLikeQo;
import com.flyfish.framework.domain.po.Department;
import com.flyfish.framework.query.LambdaQueryChain;
import com.flyfish.framework.query.Queries;
import lombok.Setter;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.data.mongodb.core.query.Criteria;
import java.util.Collections;
import java.util.Set;
@ -38,20 +38,17 @@ public abstract class AbstractAuthorizedQo<T extends AuthorizedDomain> extends N
*
* @return 结果
*/
protected Criteria withPublished() {
protected LambdaQueryChain<T> withPublished() {
if (null != published) {
if (BooleanUtils.isTrue(published)) {
return Criteria.where("published").is(true);
return Queries.where(T::getPublished).eq(true);
}
return Criteria.where("$or").is(
CriteriaBuilder.createCriteriaList(
Criteria.where("published").isNull(),
Criteria.where("published").is(false)
)
return Queries.within(
Queries.where(T::getPublished).isNull().or(T::getPublished).eq(false)
);
} else {
// 未指定发布状态查询已发布内容
return Criteria.where("published").is(true);
return Queries.where(T::getPublished).eq(true);
}
}

View File

@ -0,0 +1,42 @@
package com.flyfish.framework.domain.authorized;
import com.flyfish.framework.enums.UserType;
import com.flyfish.framework.query.QueryDefinition;
import com.flyfish.framework.query.Queries;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.BooleanUtils;
/**
* 带鉴权的查询实体主要以部门隔绝
*
* @param <T>
*/
@Getter
@Setter
public class AuthorizedQo<T extends AuthorizedDomain> extends AbstractAuthorizedQo<T> {
@Override
public QueryDefinition queryBuilder() {
// 超级管理员拥有查看所有草稿的权限
if (user.getType() == UserType.SUPER_ADMIN) {
return super.queryBuilder().mutate().and(this.withPublished());
}
// 查询草稿只查询自己的
if (BooleanUtils.isFalse(published)) {
return super.queryBuilder()
.mutate()
.and(this.withPublished())
.and(Queries.where(T::getCreatorId).eq(user.getId()));
}
// 普通查询根据权限配置查询
return super.queryBuilder()
.mutate()
.and(this.withPublished())
.and(Queries.where(T::getAuthorizeId).in(authorizeIds())
.or(Queries.where(T::getCreatorId).eq(user.getId())
.and(T::getAuthorizeId).in(((AuthorizedUserDetails) user).getVisibleDeparts()))
);
}
}

View File

@ -0,0 +1,49 @@
package com.flyfish.framework.domain.authorized.advanced;
import com.flyfish.framework.domain.authorized.AbstractAuthorizedQo;
import com.flyfish.framework.domain.authorized.AuthorizedUserDetails;
import com.flyfish.framework.enums.UserType;
import com.flyfish.framework.query.QueryDefinition;
import com.flyfish.framework.query.Queries;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.BooleanUtils;
/**
* 拥有归属权的授权查询
*
* @param <T> 泛型
* @author wangyu
* 将会额外查询归属者的内容
*/
@Getter
@Setter
public class OwnedAuthorizedQo<T extends OwnedAuthorizedDomain> extends AbstractAuthorizedQo<T> {
@Override
public QueryDefinition queryBuilder() {
// 超级管理员拥有查看所有草稿的权限
if (user.getType() == UserType.SUPER_ADMIN) {
return super.queryBuilder().mutate().and(this.withPublished());
}
// 查询草稿只查询自己的
if (BooleanUtils.isFalse(published)) {
return super.queryBuilder()
.mutate()
.and(this.withPublished())
// 此处原意为使用用户id匹配owners列中的id修改后框架会自动判定并解包存在耦合性
.and(Queries
.where(T::getCreatorId).eq(user.getId())
.or(T::getOwners).hasId(user.getId()));
}
// 普通查询根据权限配置查询
return super.queryBuilder()
.mutate()
.and(this.withPublished())
.and(() -> Queries.where(T::getAuthorizeId).in(authorizeIds())
.or(Queries.where(T::getCreatorId).eq(user.getId())
.and(T::getAuthorizeId).in(((AuthorizedUserDetails) user).getVisibleDeparts()))
.or(Queries.where(T::getOwners).hasId(user.getId()))
);
}
}

View File

@ -1,20 +1,30 @@
package com.flyfish.framework.domain.base;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.flyfish.framework.builder.CriteriaBuilder;
import com.flyfish.framework.query.Queries;
import com.flyfish.framework.query.Query;
import com.flyfish.framework.query.QueryDefinition;
import com.flyfish.framework.utils.CopyUtils;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.repository.core.EntityInformation;
import java.lang.reflect.ParameterizedType;
import java.util.List;
import java.util.Optional;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
/**
* 基本的查询实体
*
* @author Mr.Wang
*/
@Slf4j
public class BaseQo<T extends Domain> implements Qo<T> {
protected Pageable pageable;
@ -25,6 +35,10 @@ public class BaseQo<T extends Domain> implements Qo<T> {
protected List<String> fields;
@Getter
@Setter
protected boolean fetchRef;
public Qo<T> accept(List<T> result, Pageable pageable) {
this.pageable = pageable;
this.result = result;
@ -100,12 +114,20 @@ public class BaseQo<T extends Domain> implements Qo<T> {
* @return 结果
*/
@Override
public Criteria getCriteria() {
CriteriaBuilder<T> criteriaBuilder = criteriaBuilder();
if (null != criteriaBuilder) {
return criteriaBuilder.build();
public <C> Optional<C> getQuery(EntityInformation<T, String> entityInformation) {
Query query = Query.empty();
QueryDefinition definition = queryBuilder();
if (null != definition) {
// 查询定义存在时使用查询定义
query.setDefinition(definition);
} else {
// 不存在查询定义取得example若不存在example则使用probe
query.setExample(defaultIfNull(getExample(), getProbe()));
}
return null;
query.setSort(sorts());
query.setPageable(getPageable());
query.setFields(getFields());
return Optional.of(Queries.convert(query, entityInformation));
}
/**
@ -115,7 +137,7 @@ public class BaseQo<T extends Domain> implements Qo<T> {
*/
@Override
public List<String> getFields() {
return null;
return fields;
}
/**
@ -123,7 +145,7 @@ public class BaseQo<T extends Domain> implements Qo<T> {
*
* @return 结果
*/
public CriteriaBuilder<T> criteriaBuilder() {
public QueryDefinition queryBuilder() {
return null;
}
@ -144,4 +166,29 @@ public class BaseQo<T extends Domain> implements Qo<T> {
public Sort sorts() {
return Sort.by(Sort.Order.desc("modifyTime"));
}
/**
* 判断当前查询是否为空
*
* @return 结果
*/
@Override
public boolean isEmpty() {
QueryDefinition definition = queryBuilder();
Example<T> example = getExample();
return null == example && (null == definition || definition.isEmpty());
}
private Example<T> getProbe() {
Class<T> type = pojoType();
if (null != type && !Object.class.equals(type)) {
try {
T pojo = CopyUtils.copyQueryProps(this, type.newInstance());
return Example.of(pojo);
} catch (InstantiationException | IllegalAccessException e) {
log.warn("尝试实例化bean失败", e);
}
}
return null;
}
}

View File

@ -1,10 +1,10 @@
package com.flyfish.framework.domain.base;
import com.flyfish.framework.builder.CriteriaBuilder;
import com.flyfish.framework.query.QueryDefinition;
import com.flyfish.framework.query.Queries;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.util.StringUtils;
import java.util.Collection;
@ -46,22 +46,24 @@ public class NameLikeQo<T extends Domain> extends BaseQo<T> {
private String distinct;
@Override
public CriteriaBuilder<T> criteriaBuilder() {
return CriteriaBuilder.accept(this)
.with("name", CriteriaBuilder.Builders.LIKE)
.with("enable", "code", "creatorId", "modifierId")
.with("ids", "id", CriteriaBuilder.Builders.IN)
.with("excludeId", "id", Criteria::ne)
.with("createTimeRange", "createTime", CriteriaBuilder.Builders.DATE_RANGE)
.with("modifyTimeRange", "modifyTime", CriteriaBuilder.Builders.DATE_RANGE);
public QueryDefinition queryBuilder() {
return Queries.where("name").like(name)
.and("enable").eq(enable)
.and("code").eq(code)
.and("creatorId").eq(creatorId)
.and("modifierId").eq(modifierId)
.and("id").in(ids)
.and("id").ne(excludeId)
.and("createTime").between(createTimeRange)
.and("modifyTime").between(modifyTimeRange);
}
@Override
public Sort sorts() {
if (StringUtils.isEmpty(sort)) {
return super.sorts();
if (StringUtils.hasText(sort)) {
return Sort.by(Sort.Order.desc(sort));
}
return Sort.by(Sort.Order.desc(sort));
return super.sorts();
}
}

View File

@ -0,0 +1,26 @@
package com.flyfish.framework.domain.base;
import com.flyfish.framework.query.QueryDefinition;
import lombok.RequiredArgsConstructor;
/**
* 基本查询qo
*
* @param <T> 泛型
*/
@RequiredArgsConstructor
public class SimpleQo<T extends Domain> extends BaseQo<T> {
private final QueryDefinition query;
public static <T extends Domain> Qo<T> of(QueryDefinition query) {
return new SimpleQo<>(query);
}
@Override
public QueryDefinition queryBuilder() {
return query;
}
}

View File

@ -0,0 +1,4 @@
package com.flyfish.framework.domain;
/**
* 提供应用级别的实体
*/

View File

@ -0,0 +1,37 @@
package com.flyfish.framework.domain.tree;
import com.flyfish.framework.domain.base.NameLikeQo;
import com.flyfish.framework.query.QueryDefinition;
import com.flyfish.framework.query.LambdaQueryChain;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.BooleanUtils;
import java.util.List;
/**
* 属性菜单的qo
*/
@Getter
@Setter
public class TreeQo<T extends TreeDomain<T>> extends NameLikeQo<T> {
private Integer depth;
private String parentId;
private List<String> parentIds;
private Boolean recursive;
@Override
public QueryDefinition queryBuilder() {
LambdaQueryChain<T> chain = super.queryBuilder().mutate().and(T::getDepth).eq(depth);
if (BooleanUtils.isTrue(recursive)) {
chain.and(T::getParentIds).has(parentId).and(T::getParentIds).in(parentId);
} else {
chain.and(T::getParentId).eq(parentId).and(T::getParentId).in(parentIds);
}
return chain;
}
}

View File

@ -0,0 +1,101 @@
package com.flyfish.framework.query;
import com.flyfish.framework.domain.base.Domain;
import com.flyfish.framework.query.Queries.Combinator;
import com.flyfish.framework.query.support.DomainFunction;
import java.util.List;
import java.util.function.Supplier;
/**
* lambda查询条件
*
* @author wangyu
*/
public interface LambdaQueryChain<T extends Domain> extends QueryChain<LambdaQueryChain<T>, DomainFunction<T>> {
/**
* 以且连接下一个字段
*
* @param column
* @return 查询条件
*/
@Override
QueryCondition<LambdaQueryChain<T>> and(DomainFunction<T> column);
/**
* 直接拼接提供者此处懒加载最终build才会执行
*
* @param supplier 提供者
* @return 结果
*/
@Override
<V extends QueryChain<V, ?>> LambdaQueryChain<T> and(Supplier<QueryChain<V, ?>> supplier);
/**
* 条件列表
*
* @param chain 多个条件们
* @return 结果
*/
@Override
<V extends QueryChain<V, ?>> LambdaQueryChain<T> and(QueryChain<V, ?> chain);
/**
* 多个嵌套子条件列表
*
* @param chains 多条链
* @return 结果
*/
@Override
default <V extends QueryChain<V, ?>> LambdaQueryChain<T> and(List<QueryChain<V, ?>> chains) {
return QueryChain.super.and(chains);
}
/**
* 多个嵌套子条件列表
*
* @param chains 多条链
* @param combinator 各个链条之间的连接方式
* @return 结果
*/
@Override
<V extends QueryChain<V, ?>> LambdaQueryChain<T> and(List<QueryChain<V, ?>> chains, Combinator combinator);
/**
* 以或连接下一个字段
*
* @param column
* @return 查询条件
*/
@Override
QueryCondition<LambdaQueryChain<T>> or(DomainFunction<T> column);
/**
* 以或连接条件列表
*
* @param chain 多个条件们
* @return 结果
*/
@Override
<V extends QueryChain<V, ?>> LambdaQueryChain<T> or(QueryChain<V, ?> chain);
/**
* 以或连接嵌套多个子条件列表
*
* @param chains 多条链
* @param combinator 各个链条之间的连接方式
* @return 结果
*/
@Override
<V extends QueryChain<V, ?>> LambdaQueryChain<T> or(List<QueryChain<V, ?>> chains, Combinator combinator);
/**
* 直接拼接提供者此处懒加载最终build才会执行
*
* @param supplier 提供者
* @return 结果
*/
@Override
<V extends QueryChain<V, ?>> LambdaQueryChain<T> or(Supplier<QueryChain<V, ?>> supplier);
}

View File

@ -0,0 +1,100 @@
package com.flyfish.framework.query;
import com.flyfish.framework.query.Queries.Combinator;
import java.util.List;
import java.util.function.Supplier;
/**
* 查询链条
*
* @author wangyu
*/
public interface NamedQueryChain extends QueryChain<NamedQueryChain, String> {
/**
* 以且连接下一个字段
*
* @param column
* @return 查询条件
*/
@Override
QueryCondition<NamedQueryChain> and(String column);
/**
* 直接拼接提供者此处懒加载最终build才会执行
*
* @param supplier 提供者
* @return 结果
*/
@Override
<V extends QueryChain<V, ?>> NamedQueryChain and(Supplier<QueryChain<V, ?>> supplier);
/**
* 条件列表
*
* @param chain 多个条件们
* @return 结果
*/
@Override
<V extends QueryChain<V, ?>> NamedQueryChain and(QueryChain<V, ?> chain);
/**
* 多个嵌套子条件列表
*
* @param chains 多条链
* @return 结果
*/
@Override
default <V extends QueryChain<V, ?>> NamedQueryChain and(List<QueryChain<V, ?>> chains) {
return QueryChain.super.and(chains);
}
/**
* 多个嵌套子条件列表
*
* @param chains 多条链
* @param combinator 各个链条之间的连接方式
* @return 结果
*/
@Override
<V extends QueryChain<V, ?>> NamedQueryChain and(List<QueryChain<V, ?>> chains, Combinator combinator);
/**
* 以或连接下一个字段
*
* @param column
* @return 查询条件
*/
@Override
QueryCondition<NamedQueryChain> or(String column);
/**
* 以或连接条件列表
*
* @param chain 多个条件们
* @return 结果
*/
@Override
<V extends QueryChain<V, ?>> NamedQueryChain or(QueryChain<V, ?> chain);
/**
* 直接拼接提供者此处懒加载最终build才会执行
*
* @param supplier 提供者
* @return 结果
*/
@Override
<V extends QueryChain<V, ?>> NamedQueryChain or(Supplier<QueryChain<V, ?>> supplier);
/**
* 以或连接嵌套多个子条件列表
*
* @param chains 多条链
* @param combinator 各个链条之间的连接方式
* @return 结果
*/
@Override
<V extends QueryChain<V, ?>> NamedQueryChain or(List<QueryChain<V, ?>> chains, Combinator combinator);
}

View File

@ -0,0 +1,93 @@
package com.flyfish.framework.query;
import com.flyfish.framework.domain.base.Domain;
import com.flyfish.framework.query.chain.DefaultLambdaQueryChain;
import com.flyfish.framework.query.chain.DefaultNamedQueryChain;
import com.flyfish.framework.query.support.DomainFunction;
import org.springframework.data.repository.core.EntityInformation;
/**
* 查询工具类
* 基于Fluent API风格实现
* 底层基于适配器兼容各种数据库,包括关系型数据库和
* 自动判定空值空值默认情况不会进行查询
*
* @author wangyu
*/
public final class Queries {
/**
* 查询转换
*
* @param query 通用查询
* @param entityInformation 实体信息
* @param <T> 具体查询泛型
* @return 转换结果
*/
@SuppressWarnings("unchecked")
public static <T> T convert(Query query, EntityInformation<? extends Domain, String> entityInformation) {
return QueryFactories.getFactory(entityInformation)
.map(factory -> (T) factory.getConverter().convert(query))
.orElse(null);
}
/**
* 创建基于字符串字段名的查询
*
* @param column 列名
* @return 结果
*/
public static QueryCondition<NamedQueryChain> where(String column) {
NamedQueryChain chain = new DefaultNamedQueryChain();
return chain.and(column);
}
/**
* 创建基于lambda字段引用的查询
*
* @param getter 列引用
* @return 结果
*/
public static <T extends Domain> QueryCondition<LambdaQueryChain<T>> where(DomainFunction<T> getter) {
LambdaQueryChain<T> chain = new DefaultLambdaQueryChain<>();
return chain.and(getter);
}
/**
* 以嵌套条件开始
*
* @param chain 要嵌套的条件
* @return 结果
*/
public static NamedQueryChain within(NamedQueryChain chain) {
NamedQueryChain created = new DefaultNamedQueryChain();
return created.and(chain);
}
/**
* 以嵌套条件开始
*
* @param chain 要嵌套的条件
* @return 结果
*/
public static <T extends Domain> LambdaQueryChain<T> within(LambdaQueryChain<T> chain) {
LambdaQueryChain<T> created = new DefaultLambdaQueryChain<>();
return created.and(chain);
}
/**
* 传入多个查询列表时的连接方式
*/
public enum Combinator {
AND, OR
}
/**
* 方向用于描述查询和部分语法
*/
public enum Direction {
LEFT, RIGHT, ALL
}
}

View File

@ -0,0 +1,71 @@
package com.flyfish.framework.query;
import com.flyfish.framework.domain.base.Domain;
import lombok.Data;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import java.util.List;
import java.util.function.Function;
/**
* 抽象查询实体
*
* @author wangyu
*/
@Data
public class Query {
// 查询定义
private QueryDefinition definition;
// 查询用例
private Example<?> example;
// 排序
private Sort sort;
// 分页
private Pageable pageable;
// 字段列表
private List<String> fields;
/**
* 初始化一个查询
*
* @param definition 查询定义
* @return 结果
*/
public static Query query(QueryDefinition definition) {
Query query = new Query();
query.setDefinition(definition);
return query;
}
/**
* 创建一个空查询
*
* @return 结果
*/
public static Query empty() {
return new Query();
}
/**
* 指定选择的字段
*
* @param columns 字段们
* @return 当前自身
*/
public Query select(String... columns) {
return this;
}
/**
* 指定选择的字段
*
* @param getters 字段们
* @return 当前自身
*/
public <T extends Domain> Query select(Function<T, ?>... getters) {
return this;
}
}

View File

@ -0,0 +1,106 @@
package com.flyfish.framework.query;
import com.flyfish.framework.query.Queries.Combinator;
import java.util.List;
import java.util.function.Supplier;
/**
* 查询链
* 此处为高度抽象便于上层调用
* 下层重写方法实现
*
* @param <C> 本类型泛型
* @param <P> 参数泛型
* @author wangyu
*/
public interface QueryChain<C extends QueryChain<C, P>, P> extends QueryDefinition {
/**
* 以且连接下一个字段
*
* @param column
* @return 查询条件
*/
QueryCondition<C> and(P column);
/**
* 直接拼接提供者此处懒加载最终build才会执行
*
* @param supplier 提供者
* @param <V> 泛型支持其他类型的链
* @return 结果
*/
<V extends QueryChain<V, ?>> C and(Supplier<QueryChain<V, ?>> supplier);
/**
* 条件列表
*
* @param chain 多个条件们
* @return 结果
*/
<V extends QueryChain<V, ?>> C and(QueryChain<V, ?> chain);
/**
* 多个嵌套子条件列表
*
* @param chains 多条链
* @return 结果
*/
default <V extends QueryChain<V, ?>> C and(List<QueryChain<V, ?>> chains) {
return and(chains, Combinator.AND);
}
/**
* 多个嵌套子条件列表
*
* @param chains 多条链
* @param combinator 各个链条之间的连接方式
* @return 结果
*/
<V extends QueryChain<V, ?>> C and(List<QueryChain<V, ?>> chains, Combinator combinator);
/**
* 以或连接下一个字段
*
* @param column
* @return 查询条件
*/
QueryCondition<C> or(P column);
/**
* 以或连接条件列表
*
* @param chain 多个条件们
* @return 结果
*/
<V extends QueryChain<V, ?>> C or(QueryChain<V, ?> chain);
/**
* 以或连接嵌套多个子条件列表
*
* @param chains 多条链
* @return 结果
*/
default <V extends QueryChain<V, ?>> C or(List<QueryChain<V, ?>> chains) {
return and(chains, Combinator.AND);
}
/**
* 以或连接嵌套多个子条件列表
*
* @param chains 多条链
* @param combinator 各个链条之间的连接方式
* @return 结果
*/
<V extends QueryChain<V, ?>> C or(List<QueryChain<V, ?>> chains, Combinator combinator);
/**
* 直接拼接提供者此处懒加载最终build才会执行
*
* @param supplier 提供者
* @param <V> 泛型支持其他类型的链
* @return 结果
*/
<V extends QueryChain<V, ?>> C or(Supplier<QueryChain<V, ?>> supplier);
}

View File

@ -0,0 +1,152 @@
package com.flyfish.framework.query;
import java.util.Collection;
import java.util.List;
/**
* 查询条件
*
* @param <C> 条件本类型
* @author wangyu
*/
public interface QueryCondition<C extends QueryChain<C, ?>> {
/**
* 相等判定
*
* @param value
* @return 结果
*/
C eq(Object value);
/**
* 判定某列的值中存在指定值特指json array数据类型且子类型中带有id的场景
* 用于兼容mongodb查询mysql查询使用JSON_CONTAINS进行判定
* 当且仅当mongodb会拼接.$idmysql一律匹配id字段
* <p>
* 等价于 .eq(value)
*
* @param value
* @return 结果
*/
C hasId(Object value);
/**
* 对于mongodb自动处理对于关系型数据库代表json array中是否包含对应值值仅支持基本数据类型
* <p>
* 等价于 .eq(value)
*
* @param value 基本数据类型的值
* @return 结果
*/
C has(Object value);
/**
* 不等判定
*
* @param value
* @return 结果
*/
C ne(Object value);
/**
* 值介于两者之间
*
* @param items 双值列表
* @return 结果
*/
C between(List<?> items);
/**
* 模糊匹配这里是全模糊
*
* @param keyword 查询关键字
* @return 结果
*/
C like(String keyword);
/**
* 根据指定的方向进行模糊查询
*
* @param keyword 关键字
* @param direction 方向可以匹配开头和结尾
* @return 结果
*/
C like(String keyword, Queries.Direction direction);
/**
* 判定为空
*
* @return 结果
*/
C isNull();
/**
* 包含在内
*
* @param values 集合
* @return 结果
*/
C in(Collection<?> values);
/**
* 包含在内
* 注意需要根据字段类型推断
* 如果是json数组需要进行双向匹配
*
* @param values 值们
* @return 结果
*/
C in(Object... values);
/**
* 不包含在内
* 注意需要根据字段类型推断
* 如果是json数组需要进行双向匹配
*
* @param values 值们
* @return 结果
*/
C nin(Object... values);
/**
* 不包含在内
*
* @param values 集合
* @return 结果
*/
C nin(Collection<?> values);
/**
* 小于指定的值
*
* @param value 类型
* @return 结果
*/
C lt(Object value);
/**
* 小于等于指定的值
*
* @param value 类型
* @return 结果
*/
C lte(Object value);
/**
* 大于指定的值
*
* @param value 类型
* @return 结果
*/
C gt(Object value);
/**
* 大于等于指定的值
*
* @param value 类型
* @return 结果
*/
C gte(Object value);
}

View File

@ -0,0 +1,54 @@
package com.flyfish.framework.query;
import com.flyfish.framework.domain.base.Domain;
import com.flyfish.framework.domain.base.Qo;
import com.flyfish.framework.domain.base.SimpleQo;
import com.flyfish.framework.query.spi.adaptor.CriteriaAdaptor;
/**
* 可构建的
*
* @author wangyu
*/
public interface QueryDefinition {
/**
* 构建结果
*
* @param adaptor 查询适配器
* @param <T> 泛型
* @return 构建结果
*/
<T> T build(CriteriaAdaptor<T> adaptor);
/**
* 包装快捷方法
*
* @param <D> 实体类型泛型
* @return 结果
*/
default <D extends Domain> Qo<D> wrap() {
return new SimpleQo<>(this);
}
/**
* 修改此修改会直接接着查询条件进行拼接
*
* @return 修改句柄
*/
QueryMutation mutate();
/**
* 会直接添加嵌套查询到当前查询末尾
*
* @return 修改句柄
*/
QueryMutation within();
/**
* 判断查询条件是否为空
*
* @return 结果
*/
boolean isEmpty();
}

View File

@ -0,0 +1,33 @@
package com.flyfish.framework.query;
import com.flyfish.framework.domain.base.Domain;
import com.flyfish.framework.query.spi.QueryFactory;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.data.repository.core.EntityInformation;
import java.util.List;
import java.util.Optional;
/**
* 查询工厂持有者
*
* @author wangyu
*/
class QueryFactories {
@SuppressWarnings("rawtypes")
private static final List<QueryFactory> FACTORIES =
SpringFactoriesLoader.loadFactories(QueryFactory.class, null);
/**
* 获取目标查询工厂
*
* @param entityInformation 实体信息
* @return 结果
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public static Optional<QueryFactory> getFactory(EntityInformation<? extends Domain, String> entityInformation) {
return FACTORIES.stream().filter(factory -> factory.supports(entityInformation))
.findFirst();
}
}

View File

@ -0,0 +1,80 @@
package com.flyfish.framework.query;
import com.flyfish.framework.domain.base.Domain;
import com.flyfish.framework.query.support.DomainFunction;
/**
* 查询修改逻辑
*
* @author wangyu
*/
public interface QueryMutation {
/**
* 以lambda的形式修改
* 注意如果之前用过lambda的形式此处应该校验类型
*
* @param getter 方法引用
* @param <T> 实体泛型
* @return 结果
*/
<T extends Domain> QueryCondition<LambdaQueryChain<T>> and(DomainFunction<T> getter);
/**
* 以column的形式修改
*
* @param column 列名
* @return 结果
*/
QueryCondition<NamedQueryChain> and(String column);
/**
* 直接以and拼接其他的所有条件
*
* @param chain 传入的查询
* @return 结果
*/
<T extends Domain> LambdaQueryChain<T> and(LambdaQueryChain<T> chain);
/**
* 直接以and拼接其他的所有条件
*
* @param chain 传入的查询
* @return 结果
*/
NamedQueryChain and(NamedQueryChain chain);
/**
* 以lambda的形式修改
* 注意如果之前用过lambda的形式此处应该校验类型
*
* @param getter 方法引用
* @param <T> 实体泛型
* @return 结果
*/
<T extends Domain> QueryCondition<LambdaQueryChain<T>> or(DomainFunction<T> getter);
/**
* 以column的形式修改
*
* @param column 列名
* @return 结果
*/
QueryCondition<NamedQueryChain> or(String column);
/**
* 直接以or拼接其他的所有条件
*
* @param chain 传入的查询
* @return 结果
*/
<T extends Domain> LambdaQueryChain<T> or(LambdaQueryChain<T> chain);
/**
* 直接以or拼接其他的所有条件
*
* @param chain 传入的查询
* @return 结果
*/
NamedQueryChain or(NamedQueryChain chain);
}

View File

@ -0,0 +1,4 @@
# 查询构建Fluent Api适配器
同时适配关系型数据库mysql和非关系型数据库mongodb
未来将会支持更多,采用覆盖的方式进行对象组合

View File

@ -0,0 +1,42 @@
package com.flyfish.framework.query.chain;
import com.flyfish.framework.domain.base.Domain;
import com.flyfish.framework.query.LambdaQueryChain;
import com.flyfish.framework.query.QueryCondition;
import com.flyfish.framework.query.support.DomainFunction;
/**
* 默认按lambda的查询链实现
*
* @author wangyu
*/
public class DefaultLambdaQueryChain<T extends Domain> extends DefaultQueryChain<LambdaQueryChain<T>, DomainFunction<T>> implements LambdaQueryChain<T> {
private QueryCondition<LambdaQueryChain<T>> createCondition(DomainFunction<T> column) {
return createCondition(column.getName());
}
/**
* 以且连接下一个字段
*
* @param column
* @return 查询条件
*/
@Override
public QueryCondition<LambdaQueryChain<T>> and(DomainFunction<T> column) {
and();
return createCondition(column);
}
/**
* 以或连接下一个字段
*
* @param column
* @return 查询条件
*/
@Override
public QueryCondition<LambdaQueryChain<T>> or(DomainFunction<T> column) {
or();
return createCondition(column);
}
}

View File

@ -0,0 +1,37 @@
package com.flyfish.framework.query.chain;
import com.flyfish.framework.query.NamedQueryChain;
import com.flyfish.framework.query.QueryCondition;
/**
* 默认按名称的查询链实现
*
* @author wangyu
*/
public class DefaultNamedQueryChain extends DefaultQueryChain<NamedQueryChain, String> implements NamedQueryChain {
/**
* 以且连接下一个字段
*
* @param column
* @return 查询条件
*/
@Override
public QueryCondition<NamedQueryChain> and(String column) {
and();
return createCondition(column);
}
/**
* 以或连接下一个字段
*
* @param column
* @return 查询条件
*/
@Override
public QueryCondition<NamedQueryChain> or(String column) {
or();
return createCondition(column);
}
}

View File

@ -0,0 +1,190 @@
package com.flyfish.framework.query.chain;
import com.flyfish.framework.query.Queries;
import com.flyfish.framework.query.QueryChain;
import com.flyfish.framework.query.QueryCondition;
import com.flyfish.framework.query.QueryMutation;
import com.flyfish.framework.query.holder.QueryChainHolder;
import com.flyfish.framework.query.spi.adaptor.CriteriaAdaptor;
import lombok.Getter;
import org.springframework.data.util.CastUtils;
import java.util.List;
import java.util.function.Supplier;
/**
* 默认的查询定义交给holder提供构建
* 在执行时我们并不知道adaptor是谁只有最后构建阶段才能明确
*
* @author wangyu
*/
@Getter
abstract class DefaultQueryChain<C extends QueryChain<C, P>, P> implements QueryChain<C, P> {
protected final DefaultQueryChainHolder<?> holder = new DefaultQueryChainHolder<>();
/**
* 以且的关系进行拼接
*
* @return 结果
*/
protected QueryChainHolder<?> and() {
return holder.link(adaptor -> adaptor::and);
}
/**
* 以或的关系进行拼接
*
* @return 结果
*/
protected QueryChainHolder<?> or() {
return holder.link(adaptor -> adaptor::or);
}
/**
* 创建条件使用默认实现的条件
*
* @param column 列名
* @return 结果
*/
protected QueryCondition<C> createCondition(String column) {
return new DefaultQueryCondition<>(self(), holder, column);
}
/**
* 返回自身真实类型
*
* @return 结果
*/
private C self() {
return CastUtils.cast(this);
}
/**
* 构建结果
*
* @param adaptor 查询适配器
* @return 构建结果
*/
@Override
@SuppressWarnings("unchecked")
public <T> T build(CriteriaAdaptor<T> adaptor) {
QueryChainHolder<T> casted = (QueryChainHolder<T>) holder;
return casted.get(adaptor);
}
/**
* 修改此修改会直接接着查询条件进行拼接
*
* @return 修改句柄
*/
@Override
public QueryMutation mutate() {
return new DefaultQueryMutation(this, false);
}
/**
* 会直接添加嵌套查询到当前查询末尾
*
* @return 修改句柄
*/
@Override
public QueryMutation within() {
return new DefaultQueryMutation(this, true);
}
/**
* 判断查询条件是否为空
*
* @return 结果
*/
@Override
public boolean isEmpty() {
return holder.isEmpty();
}
/**
* 直接拼接提供者此处懒加载最终build才会执行
*
* @param supplier 提供者
* @return 结果
*/
@Override
public <V extends QueryChain<V, ?>> C and(Supplier<QueryChain<V, ?>> supplier) {
and().with(adaptor -> supplier.get().build(adaptor));
return self();
}
/**
* 条件列表
*
* @param chain 多个条件们
* @return 结果
*/
@Override
public <V extends QueryChain<V, ?>> C and(QueryChain<V, ?> chain) {
and().with(chain::build);
return self();
}
/**
* 多个嵌套子条件列表
*
* @param chains 多条链
* @param combinator 各个链条之间的连接方式
* @return 结果
*/
@Override
public <V extends QueryChain<V, ?>> C and(List<QueryChain<V, ?>> chains, Queries.Combinator combinator) {
return and(combine(chains, combinator));
}
/**
* 以或连接条件列表
*
* @param chain 多个条件们
* @return 结果
*/
@Override
public <V extends QueryChain<V, ?>> C or(QueryChain<V, ?> chain) {
or().with(chain::build);
return self();
}
/**
* 以或连接嵌套多个子条件列表
*
* @param chains 多条链
* @param combinator 各个链条之间的连接方式
* @return 结果
*/
@Override
public <V extends QueryChain<V, ?>> C or(List<QueryChain<V, ?>> chains, Queries.Combinator combinator) {
return or(combine(chains, combinator));
}
/**
* 直接拼接提供者此处懒加载最终build才会执行
*
* @param supplier 提供者
* @return 结果
*/
@Override
public <V extends QueryChain<V, ?>> C or(Supplier<QueryChain<V, ?>> supplier) {
or().with(adaptor -> supplier.get().build(adaptor));
return self();
}
/**
* 结合多个查询链并根据组合方式进行处理
*
* @param chains 查询链集合
* @param combinator 组合方式
* @return 结果
*/
private <V extends QueryChain<V, ?>> C combine(List<QueryChain<V, ?>> chains, Queries.Combinator combinator) {
return chains.stream().reduce((left, right) -> combinator == Queries.Combinator.OR ? left.or(right) : left.and(right))
.map(this::and)
.orElse(self());
}
}

View File

@ -0,0 +1,83 @@
package com.flyfish.framework.query.chain;
import com.flyfish.framework.query.holder.QueryChainHolder;
import com.flyfish.framework.query.spi.adaptor.CriteriaAdaptor;
import com.flyfish.framework.utils.Assert;
import org.apache.commons.collections4.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Function;
/**
* 默认的查询保持器
*
* @param <C> 泛型交给适配器处理
*/
class DefaultQueryChainHolder<C> implements QueryChainHolder<C> {
private Function<CriteriaAdaptor<C>, BinaryOperator<C>> operator;
private final List<BiFunction<C, CriteriaAdaptor<C>, C>> builders = new ArrayList<>();
/**
* 等待下一步消费
* 如果不调用with该状态将一直保持可被其他link调用替换
* 在调用with后自动消费并完成拼接
*
* @param operator 操作方法
* @return 本身
*/
@Override
public QueryChainHolder<C> link(Function<CriteriaAdaptor<C>, BinaryOperator<C>> operator) {
this.operator = operator;
return this;
}
/**
* 拼接条件
*
* @param criteria 条件
*/
@Override
public void with(Function<CriteriaAdaptor<C>, C> criteria) {
Assert.notNull(operator, "连接操作不可为空");
Function<CriteriaAdaptor<C>, BinaryOperator<C>> finalOperator = operator;
this.operator = null;
BiFunction<C, CriteriaAdaptor<C>, C> builder = (previous, adaptor) -> {
// 连接逻辑
BinaryOperator<C> linker = finalOperator.apply(adaptor);
// 具体条件
C next = criteria.apply(adaptor);
// 之前的条件
return linker.apply(previous, next);
};
builders.add(builder);
}
/**
* 触发动作得到最终的
*
* @return 本身
*/
@Override
public C get(CriteriaAdaptor<C> adaptor) {
if (CollectionUtils.isNotEmpty(builders)) {
return builders.stream()
.reduce(adaptor.empty(), (result, builder) -> builder.apply(result, adaptor), (a, b) -> a);
}
return adaptor.empty();
}
/**
* 判断条件是否为空
*
* @return 结果
*/
@Override
public boolean isEmpty() {
return builders.isEmpty();
}
}

View File

@ -0,0 +1,258 @@
package com.flyfish.framework.query.chain;
import com.flyfish.framework.query.Queries;
import com.flyfish.framework.query.QueryChain;
import com.flyfish.framework.query.QueryCondition;
import com.flyfish.framework.query.holder.QueryChainHolder;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.Collection;
import java.util.List;
/**
* 条件操作实现
*
* @author wangyu
*/
@RequiredArgsConstructor
class DefaultQueryCondition<C extends QueryChain<C, ?>> implements QueryCondition<C> {
private final C chain;
private final QueryChainHolder<?> holder;
private final String column;
/**
* 相等判定
*
* @param value
* @return 结果
*/
@Override
public C eq(Object value) {
if (null != value) {
holder.with(adaptor -> adaptor.eq(column, value));
}
return chain;
}
/**
* 判定某列的值中存在指定值特指json array数据类型且子类型中带有id的场景
* 用于兼容mongodb查询mysql查询使用JSON_CONTAINS进行判定
* 当且仅当mongodb会拼接.$idmysql一律匹配id字段
* <p>
* 等价于 .eq(value)
*
* @param value
* @return 结果
*/
@Override
public C hasId(Object value) {
if (null != value) {
holder.with(adaptor -> adaptor.hasId(column, value));
}
return chain;
}
/**
* 对于mongodb自动处理对于关系型数据库代表json array中是否包含对应值值仅支持基本数据类型
* <p>
* 等价于 .eq(value)
*
* @param value 基本数据类型的值
* @return 结果
*/
@Override
public C has(Object value) {
if (null != value) {
holder.with(adaptor -> adaptor.has(column, value));
}
return chain;
}
/**
* 不等判定
*
* @param value
* @return 结果
*/
@Override
public C ne(Object value) {
if (null != value) {
holder.with(adaptor -> adaptor.ne(column, value));
}
return chain;
}
/**
* 值介于两者之间
*
* @param items 双值列表
* @return 结果
*/
@Override
public C between(List<?> items) {
if (CollectionUtils.size(items) == 2) {
holder.with(adaptor -> adaptor.between(column, items));
}
return chain;
}
/**
* 模糊匹配这里是全模糊
*
* @param keyword 查询关键字
* @return 结果
*/
@Override
public C like(String keyword) {
if (StringUtils.isNotBlank(keyword)) {
holder.with(adaptor -> adaptor.like(column, keyword, Queries.Direction.ALL));
}
return chain;
}
/**
* 根据指定的方向进行模糊查询
*
* @param keyword 关键字
* @param direction 方向可以匹配开头和结尾
* @return 结果
*/
@Override
public C like(String keyword, Queries.Direction direction) {
if (StringUtils.isNotBlank(keyword)) {
holder.with(adaptor -> adaptor.like(column, keyword, direction));
}
return chain;
}
/**
* 判定为空
*
* @return 结果
*/
@Override
public C isNull() {
holder.with(adaptor -> adaptor.isNull(column));
return chain;
}
/**
* 包含在内
*
* @param list 集合
* @return 结果
*/
@Override
public C in(Collection<?> list) {
if (CollectionUtils.isNotEmpty(list)) {
holder.with(adaptor -> adaptor.in(column, list));
}
return chain;
}
/**
* 包含在内
* 注意需要根据字段类型推断
* 如果是json数组需要进行双向匹配
*
* @param values 值们
* @return 结果
*/
@Override
public C in(Object... values) {
if (ArrayUtils.isNotEmpty(values)) {
holder.with(adaptor -> adaptor.in(column, values));
}
return chain;
}
/**
* 不包含在内
* 注意需要根据字段类型推断
* 如果是json数组需要进行双向匹配
*
* @param values 值们
* @return 结果
*/
@Override
public C nin(Object... values) {
if (ArrayUtils.isNotEmpty(values)) {
holder.with(adaptor -> adaptor.nin(column, values));
}
return chain;
}
/**
* 不包含在内
*
* @param values 集合
* @return 结果
*/
@Override
public C nin(Collection<?> values) {
if (CollectionUtils.isNotEmpty(values)) {
holder.with(adaptor -> adaptor.nin(column, values));
}
return chain;
}
/**
* 小于指定的值
*
* @param value 类型
* @return 结果
*/
@Override
public C lt(Object value) {
if (null != value) {
holder.with(adaptor -> adaptor.lt(column, value));
}
return chain;
}
/**
* 小于等于指定的值
*
* @param value 类型
* @return 结果
*/
@Override
public C lte(Object value) {
if (null != value) {
holder.with(adaptor -> adaptor.lte(column, value));
}
return chain;
}
/**
* 大于指定的值
*
* @param value 类型
* @return 结果
*/
@Override
public C gt(Object value) {
if (null != value) {
holder.with(adaptor -> adaptor.gt(column, value));
}
return chain;
}
/**
* 大于等于指定的值
*
* @param value 类型
* @return 结果
*/
@Override
public C gte(Object value) {
if (null != value) {
holder.with(adaptor -> adaptor.gte(column, value));
}
return chain;
}
}

View File

@ -0,0 +1,134 @@
package com.flyfish.framework.query.chain;
import com.flyfish.framework.domain.base.Domain;
import com.flyfish.framework.query.*;
import com.flyfish.framework.query.support.DomainFunction;
import lombok.RequiredArgsConstructor;
import java.util.Collections;
/**
* 默认的查询修改逻辑
*
* @author wangyu
* <p>
* 不修改原条件而是在此基础上新增
* 理论上查询不允许直接修改而是通过该类做一个桥接
* 也可以修改链的形式
*/
@RequiredArgsConstructor
public class DefaultQueryMutation implements QueryMutation {
private final QueryChain<?, ?> previous;
private final boolean within;
/**
* 根据实际的参数合并查询链
*
* @param newChain 新链
* @param <P> 参数类型
* @param <C> 查询链类型
* @return 结果
*/
private <P, C extends QueryChain<C, P>> C merge(C newChain) {
if (within) {
// 内联模式旧条件不修改
newChain.and(Collections.singletonList(previous));
} else {
// 修改模式直接修改
previous.and(newChain);
}
return newChain;
}
/**
* 以lambda的形式修改
* 注意如果之前用过lambda的形式此处应该校验类型
*
* @param getter 方法引用
* @return 结果
*/
@Override
public <T extends Domain> QueryCondition<LambdaQueryChain<T>> and(DomainFunction<T> getter) {
return merge(new DefaultLambdaQueryChain<T>()).and(getter);
}
/**
* 以column的形式修改
*
* @param column 列名
* @return 结果
*/
@Override
public QueryCondition<NamedQueryChain> and(String column) {
return merge(new DefaultNamedQueryChain()).and(column);
}
/**
* 直接以and拼接其他的所有条件
*
* @param chain 传入的查询
* @return 结果
*/
@Override
public <T extends Domain> LambdaQueryChain<T> and(LambdaQueryChain<T> chain) {
return merge(chain);
}
/**
* 直接以and拼接其他的所有条件
*
* @param chain 传入的查询
* @return 结果
*/
@Override
public NamedQueryChain and(NamedQueryChain chain) {
return merge(chain);
}
/**
* 以lambda的形式修改
* 注意如果之前用过lambda的形式此处应该校验类型
*
* @param getter 方法引用
* @return 结果
*/
@Override
public <T extends Domain> QueryCondition<LambdaQueryChain<T>> or(DomainFunction<T> getter) {
return merge(new DefaultLambdaQueryChain<T>()).or(getter);
}
/**
* 以column的形式修改
*
* @param column 列名
* @return 结果
*/
@Override
public QueryCondition<NamedQueryChain> or(String column) {
return merge(new DefaultNamedQueryChain()).or(column);
}
/**
* 直接以or拼接其他的所有条件
*
* @param chain 传入的查询
* @return 结果
*/
@Override
public <T extends Domain> LambdaQueryChain<T> or(LambdaQueryChain<T> chain) {
return merge(chain);
}
/**
* 直接以or拼接其他的所有条件
*
* @param chain 传入的查询
* @return 结果
*/
@Override
public NamedQueryChain or(NamedQueryChain chain) {
return merge(chain);
}
}

View File

@ -0,0 +1,45 @@
package com.flyfish.framework.query.holder;
import com.flyfish.framework.query.spi.adaptor.CriteriaAdaptor;
import java.util.function.BinaryOperator;
import java.util.function.Function;
/**
* 查询链保持器将所有拼接工作后置可以做检查和处理
*
* @author wangyu
*/
public interface QueryChainHolder<C> {
/**
* 等待下一步消费
* 如果不调用with该状态将一直保持可被其他link调用替换
* 在调用with后自动消费并完成拼接
*
* @param operator 操作方法
* @return 本身
*/
QueryChainHolder<C> link(Function<CriteriaAdaptor<C>, BinaryOperator<C>> operator);
/**
* 拼接条件
*
* @param criteria 条件
*/
void with(Function<CriteriaAdaptor<C>, C> criteria);
/**
* 触发动作得到最终的
*
* @return 本身
*/
C get(CriteriaAdaptor<C> adaptor);
/**
* 判断条件是否为空
*
* @return 结果
*/
boolean isEmpty();
}

View File

@ -0,0 +1,31 @@
package com.flyfish.framework.query.spi;
import com.flyfish.framework.domain.base.Domain;
import com.flyfish.framework.query.spi.adaptor.CriteriaAdaptor;
import com.flyfish.framework.query.spi.converter.QueryConverter;
import com.flyfish.framework.utils.Supportable;
import org.springframework.data.repository.core.EntityInformation;
/**
* 查询工厂提供统一的实例
*
* @param <C> 查询条件对象泛型
* @param <Q> 查询对象泛型
* @author wangyu
*/
public interface QueryFactory<C, Q> extends Supportable<EntityInformation<? extends Domain, String>> {
/**
* 获取查询转换器
*
* @return 结果
*/
QueryConverter<Q> getConverter();
/**
* 获取查询适配器
*
* @return 结果
*/
CriteriaAdaptor<C> getAdaptor();
}

View File

@ -0,0 +1,187 @@
package com.flyfish.framework.query.spi.adaptor;
import com.flyfish.framework.query.Queries;
import java.util.Collection;
import java.util.List;
/**
* 查询适配器
* 直接根据条件操作得到查询条件片段
* 适配不同的数据库抽取通用操作
*
* @author wangyu
* 由工厂进行实例化并输出
*/
public interface CriteriaAdaptor<C> {
/**
* 一个空的查询用于起手
*
* @return 结果
*/
C empty();
/**
* 且的关系进行拼接
*
* @param first 起始条件
* @param other 其他条件
* @return 结果
*/
C and(C first, C other);
/**
* 或的关系进行拼接
*
* @param first 起始条件
* @param other 其他条件
* @return 结果
*/
C or(C first, C other);
/**
* 相等判定
*
* @param column 列名
* @param value
* @return 结果
*/
C eq(String column, Object value);
/**
* 判定某列的值中存在指定值特指json array数据类型且子类型中带有id的场景
* 用于兼容mongodb查询mysql查询使用JSON_CONTAINS进行判定
* 当且仅当mongodb会拼接.$idmysql一律匹配id字段
* <p>
* 等价于 .eq(value)
*
* @param column 列名
* @param value
* @return 结果
*/
C hasId(String column, Object value);
/**
* 对于mongodb自动处理对于关系型数据库代表json array中是否包含对应值值仅支持基本数据类型
* <p>
* 等价于 .eq(value)
*
* @param column 列名
* @param value 基本数据类型的值
* @return 结果
*/
C has(String column, Object value);
/**
* 不等判定
*
* @param column 列名
* @param value
* @return 结果
*/
C ne(String column, Object value);
/**
* 值介于两者之间
*
* @param column 列名
* @param items 双值列表
* @return 结果
*/
C between(String column, List<?> items);
/**
* 根据指定的方向进行模糊查询
*
* @param column 列名
* @param keyword 关键字
* @param direction 方向可以匹配开头和结尾
* @return 结果
*/
C like(String column, String keyword, Queries.Direction direction);
/**
* 判定为空
*
* @param column 列名
* @return 结果
*/
C isNull(String column);
/**
* 小于指定的值
*
* @param column 列名
* @param value 类型
* @return 结果
*/
C lt(String column, Object value);
/**
* 小于等于指定的值
*
* @param column 列名
* @param value 类型
* @return 结果
*/
C lte(String column, Object value);
/**
* 大于指定的值
*
* @param column 列名
* @param value 类型
* @return 结果
*/
C gt(String column, Object value);
/**
* 大于等于指定的值
*
* @param column 列名
* @param value 类型
* @return 结果
*/
C gte(String column, Object value);
/**
* 包含在内
*
* @param column 列名
* @param values 集合
* @return 结果
*/
C in(String column, Collection<?> values);
/**
* 包含在内
* 注意需要根据字段类型推断
* 如果是json数组需要进行双向匹配
*
* @param column 列名
* @param values 值们
* @return 结果
*/
C in(String column, Object... values);
/**
* 不包含在内
* 注意需要根据字段类型推断
* 如果是json数组需要进行双向匹配
*
* @param column 列名
* @param values 值们
* @return 结果
*/
C nin(String column, Object... values);
/**
* 不包含在内
*
* @param column 列名
* @param values 集合
* @return 结果
*/
C nin(String column, Collection<?> values);
}

View File

@ -0,0 +1,19 @@
package com.flyfish.framework.query.spi.converter;
import com.flyfish.framework.query.Query;
/**
* 原生查询转换器
*
* @author wangyu
*/
public interface QueryConverter<T> {
/**
* 转换查询
*
* @param query 封装的查询
* @return 本地查询
*/
T convert(Query query);
}

View File

@ -0,0 +1,21 @@
package com.flyfish.framework.query.support;
import com.flyfish.framework.domain.base.Domain;
/**
* 支持序列化的实体方法
*
* @param <T> 泛型父类为实体
*/
@FunctionalInterface
public interface DomainFunction<T extends Domain> extends SFunction<T, Object> {
/**
* 快速获取名称
*
* @return 序列化后的名称
*/
default String getName() {
return EntityNameUtils.getName(this);
}
}

View File

@ -0,0 +1,170 @@
package com.flyfish.framework.query.support;
import com.flyfish.framework.annotations.Property;
import com.flyfish.framework.utils.LRUCache;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.security.InvalidParameterException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
public class EntityNameUtils {
// SerializedLambda 反序列化缓存
private static final Map<String, WeakReference<SerializedLambda>> FUNC_CACHE = new ConcurrentHashMap<>();
// 列别名缓存
private static final Map<Class<?>, Map<String, String>> COLUMN_CACHE = new LRUCache<>(5);
private static final String MONGO_FIELD = "org.springframework.data.mongodb.core.mapping.Field";
private static final String RELATIONAL_COLUMN = "org.springframework.data.relational.core.mapping.Column";
/**
* 方法转为属性
*
* @param name 方法名称
* @return 最终属性
*/
public static String methodToProperty(String name) {
if (name.startsWith("is")) {
name = name.substring(2);
} else {
if (!name.startsWith("get") && !name.startsWith("set")) {
throw new InvalidParameterException("不是正确的getter或setter");
}
name = name.substring(3);
}
if (name.length() == 1 || name.length() > 1 && !Character.isUpperCase(name.charAt(1))) {
name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
}
return name;
}
/**
* 将驼峰命名转化成下划线
*
* @param param 参数
* @return 结果
*/
public static String camelToUnderline(String param) {
if (param.length() < 3) {
return param.toLowerCase();
}
StringBuilder sb = new StringBuilder(param);
int temp = 0;//定位
//从第三个字符开始 避免命名不规范
for (int i = 2; i < param.length(); i++) {
if (Character.isUpperCase(param.charAt(i))) {
sb.insert(i + temp, "_");
temp += 1;
}
}
return sb.toString().toLowerCase();
}
/**
* 解析列并使用处理函数处理
*
* @param func 方法引用
* @param <T> 实体泛型
* @return 处理结果
*/
public static <T> String getName(SFunction<T, ?> func) {
SerializedLambda lambda = resolve(func);
String property = methodToProperty(lambda.getImplMethodName());
Class<?> beanClass = resolveEntityClass(lambda);
return tryCache(beanClass).getOrDefault(property, property);
}
/**
* 解析获得实体类
*
* @param lambda 序列化的lambda
* @return 最终获取的类
*/
private static Class<?> resolveEntityClass(SerializedLambda lambda) {
Class<?> type = lambda.getInstantiatedType();
tryCache(type);
return type;
}
/**
* 尝试缓存
*
* @param entityClass bean的类型
*/
private static Map<String, String> tryCache(Class<?> entityClass) {
return COLUMN_CACHE.computeIfAbsent(entityClass, EntityNameUtils::buildFieldsCache);
}
/**
* 构建字段缓存
*
* @param type 类型
* @return 构建后的缓存
*/
private static Map<String, String> buildFieldsCache(Class<?> type) {
Map<String, String> fields = new HashMap<>();
ReflectionUtils.doWithFields(type, field -> fields.put(field.getName(), resolveFinalName(field)));
return fields;
}
/**
* 解析字段注解或直接取用下划线逻辑
*
* @return 解析结果
*/
private static String resolveFinalName(Field field) {
MergedAnnotations annotations = MergedAnnotations.from(field);
if (annotations.isPresent(Property.class)) {
String key = annotations.get(Property.class).getString("key");
if (StringUtils.hasText(key)) {
return key;
}
}
if (annotations.isPresent(MONGO_FIELD)) {
String name = annotations.get(MONGO_FIELD).getString("name");
if (StringUtils.hasText(name)) {
return name;
}
}
if (annotations.isPresent(RELATIONAL_COLUMN)) {
String name = annotations.get(RELATIONAL_COLUMN).getString("value");
if (StringUtils.hasText(name)) {
return name;
}
}
return field.getName();
}
/**
* 解析方法引用为序列化lambda实例
* 该方式会使用缓存
*
* @param func 方法引用
* @param <T> 泛型
* @return 解析结果
*/
private static <T> SerializedLambda resolve(SFunction<T, ?> func) {
Class<?> clazz = func.getClass();
String name = clazz.getName();
return Optional.ofNullable(FUNC_CACHE.get(name))
.map(WeakReference::get)
.orElseGet(() -> {
SerializedLambda lambda = SerializedLambda.resolve(func);
FUNC_CACHE.put(name, new WeakReference<>(lambda));
return lambda;
});
}
}

View File

@ -0,0 +1,14 @@
package com.flyfish.framework.query.support;
import java.io.Serializable;
import java.util.function.Function;
/**
* 支持序列化的function
*
* @param <T> 泛型入参
* @param <R> 泛型返回值
*/
@FunctionalInterface
public interface SFunction<T, R> extends Function<T, R>, Serializable {
}

View File

@ -0,0 +1,155 @@
/*
* Copyright (c) 2011-2021, baomidou (jobob@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.flyfish.framework.query.support;
import lombok.Getter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.SerializationUtils;
import java.io.*;
/**
* 这个类是从 {@link java.lang.invoke.SerializedLambda} 里面 copy 过来的
* 字段信息完全一样
* <p>负责将一个支持序列的 Function 序列化为 SerializedLambda</p>
*
* @author mbp
*/
@SuppressWarnings("unused")
class SerializedLambda implements Serializable {
private static final long serialVersionUID = 8025925345765570181L;
private Class<?> capturingClass;
private String functionalInterfaceClass;
private String functionalInterfaceMethodName;
private String functionalInterfaceMethodSignature;
private String implClass;
/**
* -- GETTER --
* 获取实现者的方法名称
*
* @return 方法名称
*/
@Getter
private String implMethodName;
private String implMethodSignature;
private int implMethodKind;
private String instantiatedMethodType;
private Object[] capturedArgs;
/**
* 通过反序列化转换 lambda 表达式该方法只能序列化 lambda 表达式不能序列化接口实现或者正常非 lambda 写法的对象
*
* @param lambda lambda对象
* @return 返回解析后的 SerializedLambda
*/
public static SerializedLambda resolve(SFunction<?, ?> lambda) {
Assert.isTrue(lambda.getClass().isSynthetic(), "该方法仅能传入 lambda 表达式产生的合成类");
byte[] stream = SerializationUtils.serialize(lambda);
Assert.notNull(stream, "序列化类失败!");
try (ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(stream)) {
@Override
protected Class<?> resolveClass(ObjectStreamClass objectStreamClass) throws IOException, ClassNotFoundException {
Class<?> clazz;
try {
clazz = forName(objectStreamClass.getName());
} catch (Exception ex) {
clazz = super.resolveClass(objectStreamClass);
}
return clazz == java.lang.invoke.SerializedLambda.class ? SerializedLambda.class : clazz;
}
}) {
return (SerializedLambda) objIn.readObject();
} catch (ClassNotFoundException | IOException e) {
throw new NullPointerException("不应该发生这件事");
}
}
/**
* 以类名实例化类会隐藏报错需要在确定有该类的情况下调用
*
* @param name 类型
* @return 实例
*/
private static Class<?> forName(String name) {
try {
return ClassUtils.forName(name, null);
} catch (ClassNotFoundException e) {
return null;
}
}
/**
* 获取接口 class
*
* @return 返回 class 名称
*/
public String getFunctionalInterfaceClassName() {
return normalizedName(functionalInterfaceClass);
}
/**
* 获取实现的 class
*
* @return 实现类
*/
public Class<?> getImplClass() {
return forName(getImplClassName());
}
/**
* 获取 class 的名称
*
* @return 类名
*/
public String getImplClassName() {
return normalizedName(implClass);
}
/**
* 正常化类名称将类名称中的 / 替换为 .
*
* @param name 名称
* @return 正常的类名
*/
private String normalizedName(String name) {
return name.replace('/', '.');
}
/**
* @return 获取实例化方法的类型
*/
public Class<?> getInstantiatedType() {
String instantiatedTypeName = normalizedName(instantiatedMethodType.substring(2, instantiatedMethodType.indexOf(';')));
return forName(instantiatedTypeName);
}
/**
* @return 字符串形式
*/
@Override
public String toString() {
String interfaceName = getFunctionalInterfaceClassName();
String implName = getImplClassName();
return String.format("%s -> %s::%s",
interfaceName.substring(interfaceName.lastIndexOf('.') + 1),
implName.substring(implName.lastIndexOf('.') + 1),
implMethodName);
}
}

View File

@ -0,0 +1,50 @@
package com.flyfish.framework.repository;
import com.flyfish.framework.repository.base.DomainRepository;
import org.reactivestreams.Publisher;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.lang.NonNull;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 默认的持久层dao
*
* @param <T> 泛型
*/
@NoRepositoryBean
public interface DefaultReactiveRepository<T> extends ReactiveCrudRepository<T, String>, ReactiveQueryModelExecutor<T>, DomainRepository<T> {
/**
* 插入给定的实体. 假设该实体是新的以便应用插入优化
* 使用返回的实例进行后续操作因为保存操作可能会完全改变实体实例
* 建议使用 {@link #save(Object)} 来避免使用特定存储的 API
*
* @param entity 要插入的实体不能为空
* @return 保存后的实体
*/
@NonNull
<S extends T> Mono<S> insert(@NonNull S entity);
/**
* 插入给定的实体列表. 假设这些实体是新的以便应用插入优化
* 使用返回的实例进行后续操作因为保存操作可能会完全改变实体实例
* 建议使用 {@link #save(Object)} 来避免使用特定存储的 API
*
* @param entities 实体列表不能为空
* @return 保存后的实体列表
*/
<S extends T> Flux<S> insert(Iterable<S> entities);
/**
* 插入给定的实体发布者发布的实体. 假设这些实体是新的以便应用插入优化
* 使用返回的实例进行后续操作因为保存操作可能会完全改变实体实例
* 建议使用 {@link #save(Object)} 来避免使用特定存储的 API
*
* @param entities 发布的实体列表不能为空
* @return 保存后的实体列表
*/
<S extends T> Flux<S> insert(Publisher<S> entities);
}

View File

@ -0,0 +1,53 @@
package com.flyfish.framework.repository;
import com.flyfish.framework.domain.base.Domain;
import com.flyfish.framework.repository.base.DomainRepository;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;
import java.util.List;
/**
* 默认的仓库
*
* @author wangyu
*/
@NoRepositoryBean
public interface DefaultRepository<T extends Domain> extends CrudRepository<T, String>, PagingAndSortingRepository<T, String>,
QueryModelExecutor<T>,
DomainRepository<T> {
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#saveAll(java.lang.Iterable)
*/
@Override
<S extends T> List<S> saveAll(Iterable<S> entities);
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#findAll()
*/
@Override
List<T> findAll();
/**
* 插入新对象
*
* @param entity 实体
* @param <S> 泛型
* @return 插入的实体带id
*/
<S extends T> S insert(S entity);
/**
* 批量插入对象
*
* @param entities 对象列表
* @param <S> 泛型
* @return 结果列表
*/
<S extends T> List<S> insert(Iterable<S> entities);
}

View File

@ -0,0 +1,60 @@
package com.flyfish.framework.repository;
import com.flyfish.framework.domain.base.Domain;
import com.flyfish.framework.query.Queries;
import com.flyfish.framework.query.Query;
import org.springframework.data.repository.core.EntityInformation;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 高层封装的模型操作
*
* @author wangyu
*/
public interface ReactiveEntityOperations {
/**
* 查询通过查询条件和类进行精确查询
*
* @param query 查询
* @param entityInformation 实体信息
* @param <T> 实体类的实例化结果
* @param <E> 实体信息具体实现类
* @return 查询结果
*/
<T extends Domain, E extends EntityInformation<T, String>> Mono<T> find(Query query, E entityInformation);
/**
* 查询全部符合条件的实体
*
* @param query 查询
* @param entityInformation 实体信息
* @param <T> 实体类的实例化结果
* @param <E> 实体信息具体实现类
* @return 结果
*/
<T extends Domain, E extends EntityInformation<T, String>> Flux<T> findAll(Query query, E entityInformation);
/**
* 保存实体
*
* @param bean 实体
* @param <T> 实体类的实例化结果
* @return 结果
*/
<T extends Domain> Mono<T> save(T bean);
/**
* 转换为原生查询
*
* @param query 查询
* @param entityInformation 实体信息
* @param <R> 泛型原生查询类型
* @param <T> 泛型实体类型
* @return 结果
*/
default <R, T extends Domain> R toNative(Query query, EntityInformation<T, String> entityInformation) {
return Queries.convert(query, entityInformation);
}
}

View File

@ -31,8 +31,7 @@ public interface ReactiveQueryModelExecutor<T> {
*
* @param query must not be {@literal null}.
* @return a single entity matching the given {@link Qo} or {@link Optional#empty()} if none was found.
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if the Qo yields more than one
* result.
* result.
*/
Mono<T> findOne(Qo<T> query);
@ -95,8 +94,8 @@ public interface ReactiveQueryModelExecutor<T> {
/**
* 删除全部
*
* @param qo 查询实体
* @param query 查询实体
* @return 结果
*/
Mono<Void> deleteAll(Qo<T> qo);
Mono<Void> deleteAll(Qo<T> query);
}

View File

@ -1,6 +1,6 @@
package com.flyfish.framework.repository.base;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.repository.core.EntityInformation;
/**
* 标记实体的仓库
@ -14,5 +14,5 @@ public interface DomainRepository<T> {
*
* @return 结果
*/
MongoEntityInformation<T, String> getEntityInformation();
EntityInformation<T, String> getEntityInformation();
}

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.flyfish.framework</groupId>
<artifactId>flyfish-data</artifactId>
<version>${revision}</version>
</parent>
<artifactId>flyfish-data-domain</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.flyfish.framework</groupId>
<artifactId>flyfish-common</artifactId>
</dependency>
<dependency>
<groupId>com.flyfish.framework</groupId>
<artifactId>flyfish-data-relational</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
</project>

Some files were not shown because too many files have changed in this diff Show More