Commit 02897568 authored by liang.tang's avatar liang.tang
Browse files

magic-api

parents
Pipeline #222 failed with stages
in 0 seconds
package org.ssssssss.magicapi.core.resource;
import org.ssssssss.magicapi.utils.IoUtils;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* 文件存储实现
*
* @author mxd
*/
public class FileResource implements Resource {
private final boolean readonly;
protected File file;
protected String rootPath;
public FileResource(File file, boolean readonly, String rootPath) {
this.file = file;
this.readonly = readonly;
this.rootPath = rootPath;
}
@Override
public boolean readonly() {
return this.readonly;
}
@Override
public boolean exists() {
return this.file.exists();
}
@Override
public boolean delete() {
return !readonly() && IoUtils.delete(this.file);
}
@Override
public boolean isDirectory() {
return this.file.isDirectory();
}
@Override
public boolean mkdir() {
return !readonly() && this.file.mkdirs();
}
@Override
public byte[] read() {
return IoUtils.bytes(this.file);
}
@Override
public boolean renameTo(Resource resource) {
if (!this.readonly()) {
File target = ((FileResource) resource).file;
if (this.file.renameTo(target)) {
this.file = target;
return true;
}
}
return false;
}
@Override
public Resource getResource(String name) {
return new FileResource(new File(this.file, name), this.readonly, this.rootPath);
}
@Override
public String name() {
return this.file.getName();
}
@Override
public List<Resource> resources() {
File[] files = this.file.listFiles();
return files == null ? Collections.emptyList() : Arrays.stream(files).map(it -> new FileResource(it, this.readonly, this.rootPath)).collect(Collectors.toList());
}
@Override
public Resource parent() {
return this.rootPath.equals(this.file.getAbsolutePath()) ? null : new FileResource(this.file.getParentFile(), this.readonly, this.rootPath);
}
@Override
public List<Resource> dirs() {
return IoUtils.dirs(this.file).stream().map(it -> new FileResource(it, this.readonly, this.rootPath)).collect(Collectors.toList());
}
@Override
public boolean write(byte[] bytes) {
return !readonly() && IoUtils.write(this.file, bytes);
}
@Override
public boolean write(String content) {
return !readonly() && IoUtils.write(this.file, content);
}
@Override
public List<Resource> files(String suffix) {
return IoUtils.files(this.file, suffix).stream().map(it -> new FileResource(it, this.readonly, this.rootPath)).collect(Collectors.toList());
}
@Override
public String getAbsolutePath() {
return this.file.getAbsolutePath();
}
@Override
public String toString() {
return String.format("file://%s", this.file.getAbsolutePath());
}
@Override
public void processExport(ZipOutputStream zos, String path, Resource directory, List<Resource> resources, List<String> excludes) throws IOException {
for (Resource resource : resources) {
if (resource.parent().getAbsolutePath().equals(directory.getAbsolutePath()) && !excludes.contains(resource.name())) {
if (resource.isDirectory()) {
String newPath = path + resource.name() + "/";
zos.putNextEntry(new ZipEntry(newPath));
zos.closeEntry();
processExport(zos, newPath, resource, resource.resources(), excludes);
} else {
zos.putNextEntry(new ZipEntry(path + resource.name()));
zos.write(resource.read());
zos.closeEntry();
}
}
}
}
@Override
public String getFilePath() {
Resource parent = parent();
while (parent.parent() != null) {
parent = parent.parent();
}
String path = this.getAbsolutePath()
.replace(parent.getAbsolutePath(), "")
.replace("\\", "/");
if (isDirectory() && !path.endsWith("/")) {
path += "/";
}
return path.startsWith("/") ? path.substring(1) : path;
}
}
package org.ssssssss.magicapi.core.resource;
import org.ssssssss.magicapi.utils.IoUtils;
import org.ssssssss.magicapi.utils.PathUtils;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
/**
* Jar存储实现
*
* @author mxd
*/
public class JarResource implements Resource {
private final JarFile jarFile;
private final ZipEntry entry;
private final List<JarEntry> entries;
private final String entryName;
private final boolean inSpringBoot;
private final String rootName;
private JarResource parent = null;
public JarResource(JarFile jarFile, String entryName, List<JarEntry> entries, boolean inSpringBoot) {
this.jarFile = jarFile;
this.entryName = entryName;
this.rootName = entryName;
this.inSpringBoot = inSpringBoot;
this.entry = getEntry(this.entryName);
this.entries = entries;
}
public JarResource(JarFile jarFile, String entryName, List<JarEntry> entries, JarResource parent, boolean inSpringBoot) {
this(jarFile, entryName, entries, inSpringBoot);
this.parent = parent;
}
@Override
public String separator() {
return "/";
}
@Override
public boolean readonly() {
return true;
}
@Override
public byte[] read() {
try {
return IoUtils.bytes(this.jarFile.getInputStream(entry));
} catch (IOException e) {
return new byte[0];
}
}
@Override
public boolean isDirectory() {
return this.entry.isDirectory();
}
@Override
public boolean exists() {
return this.entry != null;
}
protected ZipEntry getEntry(String name) {
if (inSpringBoot && name.startsWith(ResourceAdapter.SPRING_BOOT_CLASS_PATH)) {
name = name.substring(ResourceAdapter.SPRING_BOOT_CLASS_PATH.length());
}
return this.jarFile.getEntry(name);
}
@Override
public Resource getResource(String name) {
String entryName = PathUtils.replaceSlash(this.entryName + "/" + name);
String prefix = PathUtils.replaceSlash(entryName + "/");
return new JarResource(this.jarFile, entryName, entries.stream()
.filter(it -> it.getName().startsWith(prefix))
.collect(Collectors.toList()), this, this.inSpringBoot);
}
@Override
public String name() {
String name = this.entryName;
if (isDirectory()) {
name = name.substring(0, name.length() - 1);
}
int index = name.lastIndexOf("/");
return index > -1 ? name.substring(index + 1) : name;
}
@Override
public Resource parent() {
return this.parent;
}
@Override
public List<Resource> dirs() {
return resources().stream().filter(Resource::isDirectory).collect(Collectors.toList());
}
@Override
public List<Resource> files(String suffix) {
return this.entries.stream().filter(it -> it.getName().endsWith(suffix))
.map(entry -> new JarResource(jarFile, entry.getName(), Collections.emptyList(), this, this.inSpringBoot))
.collect(Collectors.toList());
}
@Override
public List<Resource> resources() {
String prefix = PathUtils.replaceSlash(this.entryName + "/");
return entries.stream()
.filter(it -> it.getName().startsWith(prefix))
.map(entry -> new JarResource(jarFile, entry.getName(), entries.stream()
.filter(item -> item.getName().startsWith(PathUtils.replaceSlash(entry.getName() + "/")))
.collect(Collectors.toList()), this, this.inSpringBoot)
)
.collect(Collectors.toList());
}
@Override
public String getAbsolutePath() {
return this.jarFile.getName() + "/" + this.entryName;
}
@Override
public String toString() {
return String.format("jar://%s", this.entryName);
}
@Override
public String getFilePath() {
JarResource root = this;
while (root.parent != null) {
root = root.parent;
}
String path = this.entryName.substring(root.rootName.length());
return path.startsWith("/") ? path.substring(1) : path;
}
}
package org.ssssssss.magicapi.core.resource;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* Key-Value形式的存储
*
* @author mxd
*/
public abstract class KeyValueResource implements Resource {
protected String separator;
protected String path;
protected KeyValueResource parent;
protected boolean readonly = false;
public KeyValueResource(String separator, String path, KeyValueResource parent) {
this.separator = separator;
this.path = path;
this.parent = parent;
}
public KeyValueResource(String separator, String path, boolean readonly, KeyValueResource parent) {
this.separator = separator;
this.path = path;
this.parent = parent;
this.readonly = readonly;
}
@Override
public String separator() {
return this.separator;
}
@Override
public boolean isDirectory() {
return this.path.endsWith(separator);
}
@Override
public boolean readonly() {
return this.readonly;
}
@Override
public final boolean renameTo(Resource resource) {
if (readonly()) {
return false;
}
if (resource.getAbsolutePath().equalsIgnoreCase(this.getAbsolutePath())) {
return true;
}
if (!(resource instanceof KeyValueResource)) {
throw new IllegalArgumentException("无法将" + this.getAbsolutePath() + "重命名为:" + resource.getAbsolutePath());
}
KeyValueResource targetResource = (KeyValueResource) resource;
// 判断移动的是否是文件夹
if (resource.isDirectory()) {
Set<String> oldKeys = this.keys();
Map<String, String> mappings = new HashMap<>(oldKeys.size());
int keyLen = this.path.length();
oldKeys.forEach(oldKey -> mappings.put(oldKey, targetResource.path + oldKey.substring(keyLen)));
return renameTo(mappings);
} else {
return renameTo(Collections.singletonMap(this.path, targetResource.path));
}
}
@Override
public boolean delete() {
if (readonly()) {
return false;
}
if (isDirectory()) {
return this.keys().stream().allMatch(this::deleteByKey);
}
return deleteByKey(getAbsolutePath());
}
protected boolean deleteByKey(String key) {
return false;
}
/**
* 需要做修改的key,原key: 新key
*
* @param renameKeys 需重命名的key
* @return 是否修改成功
*/
protected abstract boolean renameTo(Map<String, String> renameKeys);
@Override
public String name() {
String name = this.path;
if (isDirectory()) {
name = this.path.substring(0, name.length() - 1);
}
int index = name.lastIndexOf(separator);
return index > -1 ? name.substring(index + 1) : name;
}
@Override
public Resource getResource(String name) {
name = (isDirectory() ? this.path : this.path + separator) + name;
return mappedFunction().apply(name);
}
@Override
public Resource getDirectory(String name) {
return getResource(name + separator);
}
@Override
public boolean mkdir() {
if (!isDirectory()) {
this.path += separator;
}
return write("this is directory");
}
@Override
public Resource parent() {
return this.parent;
}
@Override
public boolean write(byte[] bytes) {
return !readonly() && write(new String(bytes, StandardCharsets.UTF_8));
}
@Override
public List<Resource> resources() {
return keys().stream().map(mappedFunction()).collect(Collectors.toList());
}
/**
* mapped函数,用于根据路径创建资源对象
*
* @return mapped函数
*/
protected abstract Function<String, Resource> mappedFunction();
/**
* 该资源下的keys
*
* @return 返回该资源下的keys
*/
protected abstract Set<String> keys();
@Override
public List<Resource> dirs() {
return resources().stream().filter(Resource::isDirectory).collect(Collectors.toList());
}
@Override
public List<Resource> files(String suffix) {
return resources().stream().filter(it -> it.name().endsWith(suffix)).collect(Collectors.toList());
}
@Override
public String getAbsolutePath() {
return this.path;
}
@Override
public String getFilePath() {
Resource parent = parent();
while (parent.parent() != null) {
parent = parent.parent();
}
String path = this.getAbsolutePath()
.replace(parent.getAbsolutePath(), "")
.replace("\\", "/")
.replace(this.separator, "/");
return path.startsWith("/") ? path.substring(1) : path;
}
}
package org.ssssssss.magicapi.core.resource;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* 资源对象接口
*
* @author mxd
*/
public interface Resource {
/**
* 判断是否是只读
*
* @return 返回资源是否是只读
*/
default boolean readonly() {
return false;
}
/**
* 判断是否存在
*
* @return 返回资源是否存在
*/
default boolean exists() {
return false;
}
/**
* 判断是否是目录
*
* @return 返回资源是否是目录
*/
default boolean isDirectory() {
return false;
}
/**
* 删除
*
* @return 返回是否删除成功
*/
default boolean delete() {
return false;
}
/**
* 创建目录
*
* @return 返回是否创建成功
*/
default boolean mkdir() {
return false;
}
/**
* 重命名
*
* @param resource 目标资源
* @return 是否重命名成功
*/
default boolean renameTo(Resource resource) {
return false;
}
/**
* 写入
*
* @param content 写入的内容
* @return 是否写入成功
*/
default boolean write(String content) {
return false;
}
/**
* 写入
*
* @param bytes 写入的内容
* @return 是否写入成功
*/
default boolean write(byte[] bytes) {
return false;
}
/**
* 获取分隔符
*
* @return 返回分隔符
*/
default String separator() {
return null;
}
/**
* 处理导出
*
* @param zos zip 输出流
* @param path 路径
* @param directory 目录资源对象
* @param resources 资源集合
* @param excludes 排除的目录
* @throws IOException 处理过程中抛出的异常
*/
default void processExport(ZipOutputStream zos, String path, Resource directory, List<Resource> resources, List<String> excludes) throws IOException {
for (Resource resource : resources) {
String fullName = directory.getAbsolutePath();
if (!fullName.endsWith(separator())) {
fullName += separator();
}
fullName += resource.name();
if (resource.isDirectory()) {
fullName += separator();
}
if (fullName.equals(resource.getAbsolutePath()) && !excludes.contains(resource.name())) {
if (resource.isDirectory()) {
String newPath = path + resource.name() + "/";
zos.putNextEntry(new ZipEntry(newPath));
zos.closeEntry();
processExport(zos, newPath, resource, resources, excludes);
} else {
zos.putNextEntry(new ZipEntry(path + resource.name()));
zos.write(resource.read());
zos.closeEntry();
}
}
}
}
/**
* 处理导出
*
* @param os 输出流
* @param excludes 排除的目录
* @throws IOException 处理过程中抛出的异常
*/
default void export(OutputStream os, String... excludes) throws IOException {
ZipOutputStream zos = new ZipOutputStream(os);
processExport(zos, "", this, resources(), Arrays.asList(excludes == null ? new String[0] : excludes));
zos.close();
}
/**
* 读取
*
* @return 读取的资源内容
*/
byte[] read();
/**
* 读取当前资源下的所有内容,主要是缓存作用。
*/
default void readAll() {
}
/**
* 获取子目录
*
* @param name 目录名称
* @return 返回资源对象
*/
default Resource getDirectory(String name) {
return getResource(name);
}
/**
* 获取子资源
*
* @param name 文件名称
* @return 返回资源对象
*/
Resource getResource(String name);
/**
* 获取资源名
*
* @return 返回资源名称
*/
String name();
/**
* 获取子资源集合
*
* @return 返回资源集合
*/
List<Resource> resources();
/**
* 父级资源
*
* @return 返回父级资源
*/
Resource parent();
/**
* 目录
*
* @return 返回当前资源下的目录
*/
List<Resource> dirs();
/**
* 遍历文件
*
* @param suffix 文件名后缀
* @return 返回当前资源下的文件
*/
List<Resource> files(String suffix);
/**
* 获取所在位置
*
* @return 获取绝对路径
*/
String getAbsolutePath();
/**
* 获取文件路径
*
* @return 返回文件路径
*/
String getFilePath();
}
package org.ssssssss.magicapi.core.resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.ResourceUtils;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
/**
* 资源适配器
*
* @author mxd
*/
public class ResourceAdapter {
public static final String SPRING_BOOT_CLASS_PATH = "BOOT-INF/classes/";
private static PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
public static Resource getResource(String location, boolean readonly) throws IOException {
if (location == null) {
return null;
}
org.springframework.core.io.Resource resource;
if (location.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX)) {
resource = resolver.getResource(location);
if (resource.exists()) {
return resolveResource(resource, true);
} else {
throw new FileNotFoundException(String.format("%s not found", resource.getDescription()));
}
} else {
resource = resolver.getResource(location);
if (!resource.exists()) {
resource = resolver.getResource(ResourceUtils.FILE_URL_PREFIX + location);
}
}
return resolveResource(resource, readonly);
}
private static Resource resolveResource(org.springframework.core.io.Resource resource, boolean readonly) throws IOException {
URL url = resource.getURI().toURL();
if (url.getProtocol().equals(ResourceUtils.URL_PROTOCOL_JAR)) {
JarURLConnection connection = (JarURLConnection) url.openConnection();
boolean springBootClassPath = "org.springframework.boot.loader.jar.JarURLConnection".equals(connection.getClass().getName());
String entryName = (springBootClassPath ? SPRING_BOOT_CLASS_PATH : "") + connection.getEntryName();
JarFile jarFile = connection.getJarFile();
List<JarEntry> entries = jarFile.stream().filter(it -> it.getName().startsWith(entryName)).collect(Collectors.toList());
if (entries.isEmpty()) {
entries = jarFile.stream().filter(it -> it.getName().startsWith(connection.getEntryName())).collect(Collectors.toList());
return new JarResource(jarFile, connection.getEntryName(), entries, springBootClassPath);
}
return new JarResource(jarFile, entryName, entries, springBootClassPath);
} else {
return new FileResource(resource.getFile(), readonly, resource.getFile().getAbsolutePath());
}
}
}
package org.ssssssss.magicapi.core.resource;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
/**
* Zip 存储实现
*
* @author mxd
*/
public class ZipResource implements Resource {
private final Map<String, byte[]> cachedContent;
private String path = "";
private Resource parent;
public ZipResource(InputStream is) throws IOException {
cachedContent = new TreeMap<>();
try (ZipArchiveInputStream zis = new ZipArchiveInputStream(is)) {
ArchiveEntry entry;
byte[] buf = new byte[4096];
int len = -1;
while ((entry = zis.getNextEntry()) != null) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
while ((len = zis.read(buf, 0, buf.length)) != -1) {
os.write(buf, 0, len);
}
String charset = ((ZipArchiveEntry) entry).getGeneralPurposeBit().usesUTF8ForNames() ? StandardCharsets.UTF_8.name() : "GBK";
cachedContent.put(new String(((ZipArchiveEntry) entry).getRawName(), charset), os.toByteArray());
}
}
}
ZipResource(String name, Map<String, byte[]> cachedContent, Resource parent) {
this.path = name;
this.cachedContent = cachedContent;
this.parent = parent;
}
@Override
public boolean readonly() {
return true;
}
@Override
public boolean exists() {
return this.cachedContent.containsKey(this.path);
}
@Override
public byte[] read() {
return cachedContent.getOrDefault(this.path, new byte[0]);
}
@Override
public Resource getResource(String name) {
return new ZipResource(this.path + name, this.cachedContent, this);
}
@Override
public Resource getDirectory(String name) {
return new ZipResource(this.path + name + "/", this.cachedContent, this);
}
@Override
public boolean isDirectory() {
return this.path.isEmpty() || this.path.endsWith("/");
}
@Override
public String name() {
String name = this.path;
if (isDirectory()) {
name = this.path.length() > 0 ? this.path.substring(0, name.length() - 1) : "";
}
int index = name.lastIndexOf("/");
return index > -1 ? name.substring(index + 1) : name;
}
@Override
public List<Resource> resources() {
throw new UnsupportedOperationException();
}
@Override
public Resource parent() {
return this.parent;
}
@Override
public List<Resource> dirs() {
int len = this.path.length();
return this.cachedContent.keySet().stream()
.filter(it -> it.endsWith("/") && it.startsWith(this.path) && it.indexOf("/", len + 1) == it.length() - 1)
.map(it -> this.getDirectory(it.substring(len, it.length() - 1)))
.collect(Collectors.toList());
}
@Override
public List<Resource> files(String suffix) {
if (isDirectory()) {
int len = this.path.length();
return this.cachedContent.keySet().stream()
.filter(it -> it.startsWith(this.path) && it.endsWith(suffix) && it.indexOf("/", len) == -1)
.map(it -> this.getResource(it.substring(len)))
.collect(Collectors.toList());
}
return Collections.emptyList();
}
@Override
public String getAbsolutePath() {
return this.path;
}
@Override
public String getFilePath() {
throw new UnsupportedOperationException();
}
}
package org.ssssssss.magicapi.core.service;
import org.springframework.context.event.EventListener;
import org.ssssssss.magicapi.core.event.EventAction;
import org.ssssssss.magicapi.core.event.FileEvent;
import org.ssssssss.magicapi.core.event.GroupEvent;
import org.ssssssss.magicapi.core.event.MagicEvent;
import org.ssssssss.magicapi.core.exception.MagicAPIException;
import org.ssssssss.magicapi.core.model.MagicEntity;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
public abstract class AbstractMagicDynamicRegistry<T extends MagicEntity> implements MagicDynamicRegistry<T> {
/**
* 已缓存的映射信息
*/
private final Map<String, MappingNode<T>> mappings = new ConcurrentHashMap<>();
protected final MagicResourceStorage<T> magicResourceStorage;
public AbstractMagicDynamicRegistry(MagicResourceStorage<T> magicResourceStorage) {
this.magicResourceStorage = magicResourceStorage;
}
@Override
public boolean register(T entity) {
MappingNode<T> mappingNode = buildMappingNode(entity);
String newMappingKey = mappingNode.getMappingKey();
MappingNode<T> oldMappingNode = mappings.get(entity.getId());
if (oldMappingNode != null) {
String oldMappingKey = oldMappingNode.getMappingKey();
// mappingKey一致时 刷新即可
if (Objects.equals(oldMappingKey, newMappingKey)) {
if (!entity.equals(oldMappingNode.getEntity())) {
// 刷新
oldMappingNode.setEntity(entity);
}
return true;
}
// 不一致时,需要取消注册旧的,重新注册当前的
mappings.remove(oldMappingKey);
mappings.remove(entity.getId());
unregister(oldMappingNode);
} else if (mappings.containsKey(newMappingKey)) {
throw new MagicAPIException(newMappingKey + " 已注册,请更换名称或路径");
}
if (register(mappingNode)) {
mappings.put(entity.getId(), mappingNode);
mappings.put(newMappingKey, mappingNode);
return true;
}
return false;
}
@EventListener(condition = "#event.action == T(org.ssssssss.magicapi.core.event.EventAction).CLEAR")
public void clear(MagicEvent event) {
Iterator<Map.Entry<String, MappingNode<T>>> iterator = mappings.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, MappingNode<T>> entry = iterator.next();
if (Objects.equals(entry.getKey(), entry.getValue().getEntity().getId())) {
unregister(entry.getValue());
}
iterator.remove();
}
}
protected void processEvent(FileEvent event) {
T info = (T) event.getEntity();
if (event.getAction() == EventAction.DELETE) {
unregister(info);
} else {
register(info);
}
}
protected void processEvent(GroupEvent event) {
if (event.getAction() == EventAction.DELETE) {
event.getEntities().forEach(entity -> unregister((T) entity));
} else {
event.getEntities().forEach(entity -> register((T) entity));
}
}
@Override
public T getMapping(String mappingKey) {
MappingNode<T> node = mappings.get(mappingKey);
return node == null ? null : node.getEntity();
}
@Override
public boolean unregister(T entity) {
MappingNode<T> mappingNode = mappings.remove(entity.getId());
if (mappingNode != null) {
mappings.remove(mappingNode.getMappingKey());
unregister(mappingNode);
return true;
}
return false;
}
@Override
public MagicResourceStorage<T> getMagicResourceStorage() {
return this.magicResourceStorage;
}
protected boolean register(MappingNode<T> mappingNode) {
return true;
}
protected void unregister(MappingNode<T> mappingNode) {
}
public List<T> mappings(){
return this.mappings.values().stream().map(MappingNode::getEntity).collect(Collectors.toList());
}
protected MappingNode<T> buildMappingNode(T entity) {
MappingNode<T> mappingNode = new MappingNode<>(entity);
mappingNode.setMappingKey(this.magicResourceStorage.buildKey(entity));
return mappingNode;
}
protected static class MappingNode<T extends MagicEntity> {
private T entity;
private String mappingKey = "";
private Object mappingData;
public MappingNode(T entity) {
this.entity = entity;
}
public T getEntity() {
return entity;
}
public void setEntity(T entity) {
this.entity = entity;
}
public String getMappingKey() {
return mappingKey;
}
public void setMappingKey(String mappingKey) {
this.mappingKey = mappingKey;
}
public Object getMappingData() {
return mappingData;
}
public void setMappingData(Object mappingData) {
this.mappingData = mappingData;
}
}
}
package org.ssssssss.magicapi.core.service;
import org.ssssssss.magicapi.core.config.JsonCodeConstants;
import org.ssssssss.magicapi.core.model.PathMagicEntity;
import org.ssssssss.magicapi.utils.PathUtils;
import java.util.Objects;
public abstract class AbstractPathMagicResourceStorage<T extends PathMagicEntity> implements MagicResourceStorage<T>, JsonCodeConstants {
protected MagicResourceService magicResourceService;
@Override
public String suffix() {
return ".ms";
}
@Override
public boolean requirePath() {
return true;
}
@Override
public void setMagicResourceService(MagicResourceService magicResourceService) {
this.magicResourceService = magicResourceService;
}
public String buildMappingKey(T entity, String path) {
return PathUtils.replaceSlash("/" + Objects.toString(path, "") + "/"+ Objects.toString(entity.getPath(), ""));
}
@Override
public void validate(T entity) {
notBlank(entity.getPath(), REQUEST_PATH_REQUIRED);
notBlank(entity.getScript(), SCRIPT_REQUIRED);
}
}
package org.ssssssss.magicapi.core.service;
import org.ssssssss.magicapi.core.annotation.MagicModule;
import org.ssssssss.magicapi.core.model.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
/**
* API调用接口
*/
public interface MagicAPIService {
/**
* 执行MagicAPI中的接口,原始内容,不包含code以及message信息
*
* @param method 请求方法
* @param path 请求路径
* @param context 变量信息
*/
<T> T execute(String method, String path, Map<String, Object> context);
/**
* 执行MagicAPI中的接口,带code和message信息
*
* @param method 请求方法
* @param path 请求路径
* @param context 变量信息
*/
<T> T call(String method, String path, Map<String, Object> context);
/**
* 执行MagicAPI中的函数
*
* @param path 函数路径
* @param context 变量信息
*/
<T> T invoke(String path, Map<String, Object> context);
/**
* 上传
*/
boolean upload(InputStream inputStream, String mode) throws IOException;
/**
* 下载
*/
void download(String groupId, List<SelectedResource> resources, OutputStream os) throws IOException;
JsonBean<?> push(String target, String secretKey, String mode, List<SelectedResource> resources);
/**
* 处理刷新通知
*/
boolean processNotify(MagicNotify magicNotify);
}
package org.ssssssss.magicapi.core.service;
import org.ssssssss.magicapi.core.model.MagicEntity;
import java.util.Collections;
import java.util.List;
public interface MagicDynamicRegistry<T extends MagicEntity> {
/**
* 注册
*/
boolean register(T entity);
/**
* 取消注册
*/
boolean unregister(T entity);
T getMapping(String mappingKey);
/**
* 资源存储器
*/
MagicResourceStorage<T> getMagicResourceStorage();
default List<T> defaultMappings() {
return Collections.emptyList();
}
}
package org.ssssssss.magicapi.core.service;
import org.ssssssss.magicapi.core.model.MagicNotify;
/**
* 接口通知发送处理接口
*
* @author mxd
*/
public interface MagicNotifyService {
/**
* 发送通知
*
* @param magicNotify 通知对象
*/
void sendNotify(MagicNotify magicNotify);
}
package org.ssssssss.magicapi.core.service;
import org.ssssssss.magicapi.core.resource.Resource;
import org.ssssssss.magicapi.core.model.*;
import org.ssssssss.magicapi.utils.PathUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
/**
* 资源存储服务
*/
public interface MagicResourceService {
/**
* 刷新缓存
*/
void refresh();
Resource getResource();
boolean processNotify(MagicNotify magicNotify);
/**
* 保存分组
*/
boolean saveGroup(Group group);
/**
* 移动
*
* @param src 源ID
* @param groupId 目标分组
*/
boolean move(String src, String groupId);
/**
* 复制分组
*
* @param src 源ID
* @param target 目标分组
*/
String copyGroup(String src, String target);
/**
* 全部分组
*/
TreeNode<Group> tree(String type);
/**
* 全部分组
*/
Map<String, TreeNode<Group>> tree();
List<Group> getGroupsByFileId(String id);
/**
* 获取分组 Resource
*
* @param id 分组ID
*/
Resource getGroupResource(String id);
/**
* 保存文件
*
* @param entity 文件内容
*/
<T extends MagicEntity> boolean saveFile(T entity);
/**
* 删除文件或文件夹
*
* @param id 文件或分组ID
*/
boolean delete(String id);
/**
* 获取目录下的所有文件
*
* @param groupId 分组ID
*/
<T extends MagicEntity> List<T> listFiles(String groupId);
/**
* 获取所有文件
*
* @param type 类型
*/
<T extends MagicEntity> List<T> files(String type);
/**
* 获取文件详情
*/
<T extends MagicEntity> T file(String id);
/**
* 获取分组详情
*/
Group getGroup(String id);
void export(String groupId, List<SelectedResource> resources, OutputStream os) throws IOException;
/**
* 锁定资源
*/
boolean lock(String id);
/**
* 解锁资源
*/
boolean unlock(String id);
boolean upload(InputStream inputStream, boolean full) throws IOException;
/**
* 获取完整分组路径
*/
String getGroupPath(String groupId);
/**
* 获取完整分组名称
*/
String getGroupName(String groupId);
default String getScriptName(MagicEntity entity){
String fullName;
if(entity instanceof PathMagicEntity){
PathMagicEntity pme = (PathMagicEntity) entity;
fullName = String.format("/%s/%s(/%s/%s)", getGroupName(pme.getGroupId()), pme.getName(), getGroupPath(pme.getGroupId()), pme.getPath());
} else {
fullName = String.format("/%s/%s", getGroupName(entity.getGroupId()), entity.getName());
}
return PathUtils.replaceSlash(fullName);
}
}
package org.ssssssss.magicapi.core.service;
import org.ssssssss.magicapi.core.resource.Resource;
import org.ssssssss.magicapi.core.model.MagicEntity;
import org.ssssssss.magicapi.core.service.MagicResourceService;
import org.ssssssss.magicapi.utils.JsonUtils;
import java.nio.charset.StandardCharsets;
public interface MagicResourceStorage<T extends MagicEntity> {
String separatorWithCRLF = "\r\n================================\r\n";
String separatorWithLF = "\n================================\n";
/**
* 文件夹名
*/
String folder();
/**
* 允许的后缀,为空则不限制
*/
String suffix();
Class<T> magicClass();
/**
* 是否支持path
*/
boolean requirePath();
default boolean requiredScript() {
return true;
}
default boolean allowRoot() {
return false;
}
default T read(byte[] bytes) {
String content = new String(bytes, StandardCharsets.UTF_8);
if (requiredScript()) {
String separator = separatorWithCRLF;
int index = content.indexOf(separator);
if (index == -1) {
separator = separatorWithLF;
index = content.indexOf(separatorWithLF);
}
if (index > -1) {
T info = JsonUtils.readValue(content.substring(0, index), magicClass());
info.setScript(content.substring(index + separator.length()));
return info;
}
}
return JsonUtils.readValue(content, magicClass());
}
default byte[] write(MagicEntity entity) {
entity = entity.copy();
String script = entity.getScript();
entity.setScript(null);
return (JsonUtils.toJsonString(entity) + separatorWithCRLF + script).getBytes(StandardCharsets.UTF_8);
}
default T readResource(Resource resource) {
return read(resource.read());
}
String buildMappingKey(T entity);
default String buildKey(MagicEntity entity) {
return buildMappingKey((T) entity);
}
/**
* 校验参数
*/
default void validate(T entity) {
}
void setMagicResourceService(MagicResourceService magicResourceService);
}
package org.ssssssss.magicapi.core.service.impl;
import org.apache.commons.lang3.StringUtils;
import org.ssssssss.magicapi.core.model.ApiInfo;
import org.ssssssss.magicapi.core.service.AbstractPathMagicResourceStorage;
import org.ssssssss.magicapi.utils.PathUtils;
import java.util.Objects;
public class ApiInfoMagicResourceStorage extends AbstractPathMagicResourceStorage<ApiInfo> {
private final String prefix;
public ApiInfoMagicResourceStorage(String prefix) {
this.prefix = StringUtils.defaultIfBlank(prefix, "") + "/";
}
@Override
public String folder() {
return "api";
}
@Override
public Class<ApiInfo> magicClass() {
return ApiInfo.class;
}
@Override
public String buildMappingKey(ApiInfo info, String path) {
return info.getMethod().toUpperCase() + ":" + super.buildMappingKey(info, path);
}
@Override
public String buildMappingKey(ApiInfo info) {
return PathUtils.replaceSlash(buildMappingKey(info, this.prefix + Objects.toString(magicResourceService.getGroupPath(info.getGroupId()), "")));
}
@Override
public void validate(ApiInfo entity) {
notBlank(entity.getMethod(), REQUEST_METHOD_REQUIRED);
super.validate(entity);
}
}
package org.ssssssss.magicapi.core.service.impl;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.ssssssss.magicapi.core.annotation.MagicModule;
import org.ssssssss.magicapi.core.config.Constants;
import org.ssssssss.magicapi.core.config.JsonCodeConstants;
import org.ssssssss.magicapi.core.config.WebSocketSessionManager;
import org.ssssssss.magicapi.core.context.RequestEntity;
import org.ssssssss.magicapi.core.event.EventAction;
import org.ssssssss.magicapi.core.event.MagicEvent;
import org.ssssssss.magicapi.core.exception.MagicAPIException;
import org.ssssssss.magicapi.core.handler.MagicWebSocketDispatcher;
import org.ssssssss.magicapi.core.interceptor.ResultProvider;
import org.ssssssss.magicapi.core.model.*;
import org.ssssssss.magicapi.core.service.MagicAPIService;
import org.ssssssss.magicapi.core.service.MagicResourceService;
import org.ssssssss.magicapi.core.servlet.MagicRequestContextHolder;
import org.ssssssss.magicapi.function.model.FunctionInfo;
import org.ssssssss.magicapi.function.service.FunctionMagicDynamicRegistry;
import org.ssssssss.magicapi.utils.PathUtils;
import org.ssssssss.magicapi.utils.ScriptManager;
import org.ssssssss.magicapi.utils.SignUtils;
import org.ssssssss.script.MagicScriptContext;
import java.io.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@MagicModule("magic")
public class DefaultMagicAPIService implements MagicAPIService, JsonCodeConstants {
private final static Logger logger = LoggerFactory.getLogger(DefaultMagicAPIService.class);
private final boolean throwException;
private final ResultProvider resultProvider;
private final String instanceId;
private final MagicResourceService resourceService;
private final ApplicationEventPublisher publisher;
private final RequestMagicDynamicRegistry requestMagicDynamicRegistry;
private final FunctionMagicDynamicRegistry functionMagicDynamicRegistry;
private final String prefix;
private final MagicRequestContextHolder magicRequestHolder;
public DefaultMagicAPIService(ResultProvider resultProvider,
String instanceId,
MagicResourceService resourceService,
RequestMagicDynamicRegistry requestMagicDynamicRegistry,
FunctionMagicDynamicRegistry functionMagicDynamicRegistry,
boolean throwException,
String prefix,
MagicRequestContextHolder magicRequestHolder,
ApplicationEventPublisher publisher) {
this.resultProvider = resultProvider;
this.requestMagicDynamicRegistry = requestMagicDynamicRegistry;
this.functionMagicDynamicRegistry = functionMagicDynamicRegistry;
this.throwException = throwException;
this.resourceService = resourceService;
this.instanceId = instanceId;
this.prefix = StringUtils.defaultIfBlank(prefix, "");
this.magicRequestHolder = magicRequestHolder;
this.publisher = publisher;
}
@SuppressWarnings({"unchecked"})
private <T> T execute(RequestEntity requestEntity, PathMagicEntity info, Map<String, Object> context) {
MagicScriptContext scriptContext = new MagicScriptContext();
String fullGroupName = resourceService.getGroupName(info.getGroupId());
String fullGroupPath = resourceService.getGroupPath(info.getGroupId());
String scriptName = PathUtils.replaceSlash(String.format("/%s/%s(/%s/%s)", fullGroupName, info.getName(), fullGroupPath, info.getPath()));
scriptContext.setScriptName(scriptName);
scriptContext.putMapIntoContext(context);
if (requestEntity != null) {
requestEntity.setMagicScriptContext(scriptContext);
}
return (T) ScriptManager.executeScript(info.getScript(), scriptContext);
}
@Override
public <T> T execute(String method, String path, Map<String, Object> context) {
return execute(null, method, path, context);
}
private <T> T execute(RequestEntity requestEntity, String method, String path, Map<String, Object> context) {
String mappingKey = Objects.toString(method, "GET").toUpperCase() + ":" + PathUtils.replaceSlash(this.prefix + "/" + Objects.toString(path, ""));
ApiInfo info = requestMagicDynamicRegistry.getMapping(mappingKey);
if (info == null) {
throw new MagicAPIException(String.format("找不到对应接口 [%s:%s]", method, path));
}
if (context == null) {
context = new HashMap<>();
}
context.put("apiInfo", info);
return execute(requestEntity, info, context);
}
@SuppressWarnings({"unchecked"})
@Override
public <T> T call(String method, String path, Map<String, Object> context) {
RequestEntity requestEntity = RequestEntity.create();
try {
requestEntity.request(magicRequestHolder.getRequest()).response(magicRequestHolder.getResponse());
return (T) resultProvider.buildResult(requestEntity, (Object) execute(requestEntity, method, path, context));
} catch (Throwable root) {
if (throwException) {
throw root;
}
return (T) resultProvider.buildResult(requestEntity, root);
}
}
@SuppressWarnings({"unchecked"})
@Override
public <T> T invoke(String path, Map<String, Object> context) {
FunctionInfo functionInfo = functionMagicDynamicRegistry.getMapping(path);
if (functionInfo == null) {
throw new MagicAPIException(String.format("找不到对应函数 [%s]", path));
}
return (T) execute(null, functionInfo, context);
}
@Override
public boolean upload(InputStream inputStream, String mode) throws IOException {
return resourceService.upload(inputStream, Constants.UPLOAD_MODE_FULL.equals(mode));
}
@Override
public void download(String groupId, List<SelectedResource> resources, OutputStream os) throws IOException {
resourceService.export(groupId, resources, os);
}
@Override
public JsonBean<?> push(String target, String secretKey, String mode, List<SelectedResource> resources) {
notBlank(target, TARGET_IS_REQUIRED);
notBlank(secretKey, SECRET_KEY_IS_REQUIRED);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
download(null, resources, baos);
} catch (IOException e) {
return new JsonBean<>(-1, e.getMessage());
}
byte[] bytes = baos.toByteArray();
long timestamp = System.currentTimeMillis();
RestTemplate restTemplate = new RestTemplate();
MultiValueMap<String, Object> param = new LinkedMultiValueMap<>();
param.add("timestamp", timestamp);
param.add("mode", mode);
param.add("sign", SignUtils.sign(timestamp, secretKey, mode, bytes));
param.add("file", new InputStreamResource(new ByteArrayInputStream(bytes)) {
@Override
public String getFilename() {
return "magic-api.zip";
}
@Override
public long contentLength() {
return bytes.length;
}
});
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
return restTemplate.postForObject(target, new HttpEntity<>(param, headers), JsonBean.class);
}
@Override
public boolean processNotify(MagicNotify magicNotify) {
if (magicNotify == null || instanceId.equals(magicNotify.getFrom())) {
return false;
}
logger.debug("收到通知消息:{}", magicNotify);
switch (magicNotify.getAction()) {
case WS_C_S:
return processWebSocketMessageReceived(magicNotify.getClientId(), magicNotify.getContent());
case WS_S_C:
return processWebSocketSendMessage(magicNotify.getClientId(), magicNotify.getContent());
case WS_S_S:
return processWebSocketEventMessage(magicNotify.getContent());
case CLEAR:
publisher.publishEvent(new MagicEvent("clear", EventAction.CLEAR, Constants.EVENT_SOURCE_NOTIFY));
}
return resourceService.processNotify(magicNotify);
}
private boolean processWebSocketSendMessage(String clientId, String content) {
WebSocketSessionManager.sendByClientId(clientId, content);
return true;
}
private boolean processWebSocketMessageReceived(String clientId, String content) {
MagicWebSocketDispatcher.processMessageReceived(clientId, content);
return true;
}
private boolean processWebSocketEventMessage(String content) {
MagicWebSocketDispatcher.processWebSocketEventMessage(content);
return true;
}
}
package org.ssssssss.magicapi.core.service.impl;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationListener;
import org.ssssssss.magicapi.core.config.Constants;
import org.ssssssss.magicapi.core.config.JsonCodeConstants;
import org.ssssssss.magicapi.core.resource.Resource;
import org.ssssssss.magicapi.core.resource.ZipResource;
import org.ssssssss.magicapi.core.event.EventAction;
import org.ssssssss.magicapi.core.event.FileEvent;
import org.ssssssss.magicapi.core.event.GroupEvent;
import org.ssssssss.magicapi.core.event.MagicEvent;
import org.ssssssss.magicapi.core.exception.InvalidArgumentException;
import org.ssssssss.magicapi.core.model.*;
import org.ssssssss.magicapi.core.service.AbstractPathMagicResourceStorage;
import org.ssssssss.magicapi.core.service.MagicResourceService;
import org.ssssssss.magicapi.core.service.MagicResourceStorage;
import org.ssssssss.magicapi.utils.IoUtils;
import org.ssssssss.magicapi.utils.JsonUtils;
import org.ssssssss.magicapi.utils.WebUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class DefaultMagicResourceService implements MagicResourceService, JsonCodeConstants, ApplicationListener<ApplicationStartedEvent> {
private final Resource root;
private final Map<String, Resource> groupMappings = new HashMap<>(16);
private final Map<String, Group> groupCache = new HashMap<>(16);
private final Map<String, Resource> fileMappings = new HashMap<>(32);
private final Map<String, MagicEntity> fileCache = new HashMap<>(32);
private final Map<String, Map<String, String>> pathCache = new HashMap<>(16);
private final Map<String, MagicResourceStorage<? extends MagicEntity>> storages;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final ApplicationEventPublisher publisher;
private final Logger logger = LoggerFactory.getLogger(DefaultMagicResourceService.class);
public DefaultMagicResourceService(Resource resource, List<MagicResourceStorage<? extends MagicEntity>> storages, ApplicationEventPublisher publisher) {
this.root = resource;
this.storages = storages.stream()
.peek(it -> it.setMagicResourceService(this))
.collect(Collectors.toMap(MagicResourceStorage::folder, it -> it));
this.publisher = publisher;
}
public boolean processNotify(MagicNotify notify) {
if (Constants.EVENT_TYPE_FILE.equals(notify.getType())) {
return processFileNotify(notify.getId(), notify.getAction());
}
if (notify.getAction() == EventAction.CLEAR) {
this.read(false);
return true;
}
return processGroupNotify(notify.getId(), notify.getAction());
}
private boolean processGroupNotify(String id, EventAction action) {
Group group = groupCache.get(id);
if (group == null) {
// create
this.readAll();
group = groupCache.get(id);
}
TreeNode<Group> treeNode = tree(group.getType()).findTreeNode(it -> it.getId().equals(id));
if (treeNode != null) {
GroupEvent event = new GroupEvent(group.getType(), action, group);
event.setSource(Constants.EVENT_SOURCE_NOTIFY);
if (event.getAction() == EventAction.DELETE) {
event.setEntities(deleteGroup(id));
} else if (action != EventAction.CREATE) {
Resource folder = groupMappings.get(id);
folder.readAll();
if (folder.exists()) {
// 刷新分组缓存
refreshGroup(folder, storages.get(group.getType()));
} else {
this.readAll();
treeNode = tree(group.getType()).findTreeNode(it -> it.getId().equals(id));
}
event.setGroup(groupCache.get(id));
event.setEntities(treeNode
.flat()
.stream()
.flatMap(g -> listFiles(g.getId()).stream())
.collect(Collectors.toList()));
}
publisher.publishEvent(event);
return true;
}
return false;
}
private boolean processFileNotify(String id, EventAction action) {
MagicEntity entity = fileCache.get(id);
if (entity == null) { // create
this.readAll();
entity = fileCache.get(id);
}
if (entity != null) {
Group group = groupCache.get(entity.getGroupId());
if (group != null) {
MagicResourceStorage<? extends MagicEntity> storage = storages.get(group.getType());
Map<String, String> pathCacheMap = storage.requirePath() ? pathCache.get(storage.folder()) : null;
if (action == EventAction.DELETE) {
fileMappings.remove(id);
entity = fileCache.remove(id);
if (pathCacheMap != null) {
pathCacheMap.remove(id);
}
} else {
Resource resource = fileMappings.get(id);
resource.readAll();
if (resource.exists()) {
entity = storage.read(resource.read());
putFile(storage, entity, resource);
} else {
this.readAll();
entity = fileCache.get(id);
}
}
publisher.publishEvent(new FileEvent(group.getType(), action, entity, Constants.EVENT_SOURCE_NOTIFY));
}
}
return false;
}
private void init() {
groupMappings.clear();
groupCache.clear();
fileMappings.clear();
fileCache.clear();
pathCache.clear();
storages.forEach((key, registry) -> {
if (registry.requirePath()) {
pathCache.put(registry.folder(), new HashMap<>(32));
}
Resource folder = root.getDirectory(key);
if (registry.allowRoot()) {
String rootId = key + ":0";
Group group = new Group();
group.setId(rootId);
group.setType(key);
group.setParentId("0");
putGroup(group, folder);
}
if (!folder.exists()) {
folder.mkdir();
}
});
}
private void read(boolean triggerEvent) {
writeLock(() -> {
if (triggerEvent) {
publisher.publishEvent(new MagicEvent("clear", EventAction.CLEAR));
}
this.readAll();
fileCache.values().forEach(entity -> {
Group group = groupCache.get(entity.getGroupId());
publisher.publishEvent(new FileEvent(group.getType(), EventAction.LOAD, entity));
});
return null;
});
}
private void readAll() {
writeLock(() -> {
this.init();
this.root.readAll();
storages.forEach((key, registry) -> refreshGroup(root.getDirectory(key), registry));
return null;
});
}
@Override
public void refresh() {
this.read(true);
}
@Override
public Resource getResource() {
return root;
}
private void refreshGroup(Resource folder, MagicResourceStorage<? extends MagicEntity> storage) {
if (storage.allowRoot()) {
folder.files(storage.suffix()).forEach(file -> putFile(storage, storage.readResource(file), file));
} else {
folder.dirs().forEach(dir -> {
Resource meta = dir.getResource(Constants.GROUP_METABASE);
if (meta.exists()) {
putGroup(JsonUtils.readValue(meta.read(), Group.class), dir);
dir.files(storage.suffix()).forEach(file -> putFile(storage, storage.readResource(file), file));
}
});
}
}
@Override
public boolean saveGroup(Group group) {
isTrue(!root.readonly(), IS_READ_ONLY);
// 类型校验
isTrue(storages.containsKey(group.getType()), NOT_SUPPORTED_GROUP_TYPE);
// 名字校验
notNull(group.getName(), NAME_REQUIRED);
notNull(IoUtils.validateFileName(group.getName()), NAME_INVALID);
// 需要填写parentId
notNull(group.getParentId(), GROUP_ID_REQUIRED);
MagicResourceStorage<? extends MagicEntity> storage = storages.get(group.getType());
return writeLock(() -> {
Resource resource;
// 判断是否要保存到根节点下
if (Constants.ROOT_ID.equals(group.getParentId())) {
resource = root.getDirectory(group.getType());
} else {
// 找到上级分组
resource = getGroupResource(group.getParentId());
// 上级分组需要存在
isTrue(resource != null && resource.exists(), GROUP_NOT_FOUND);
}
Resource groupResource;
GroupEvent event = new GroupEvent(group.getType(), group.getId() == null ? EventAction.CREATE : EventAction.SAVE, group);
if (group.getId() == null || !groupCache.containsKey(group.getId())) {
// 添加分组
if (group.getId() == null) {
group.setId(UUID.randomUUID().toString().replace("-", ""));
}
group.setCreateTime(System.currentTimeMillis());
group.setCreateBy(WebUtils.currentUserName());
groupResource = resource.getDirectory(group.getName());
// 判断分组文件夹需要不存在
isTrue(!groupResource.exists(), FILE_SAVE_FAILURE);
// 创建文件夹
groupResource.mkdir();
} else {
Group oldGroup = groupCache.get(group.getId());
if (storage.requirePath() && !Objects.equals(oldGroup.getPath(), group.getPath())) {
TreeNode<Group> treeNode = tree(group.getType());
String oldPath = oldGroup.getPath();
oldGroup.setPath(group.getPath());
// 递归找出该组下的文件
List<MagicEntity> entities = treeNode.findTreeNode(it -> it.getId().equals(group.getId()))
.flat()
.stream()
.flatMap(it -> fileCache.values().stream().filter(f -> f.getGroupId().equals(it.getId())))
.collect(Collectors.toList());
for (MagicEntity entity : entities) {
String newMappingKey = storage.buildKey(entity);
if (pathCache.get(group.getType()).entrySet().stream().anyMatch(entry -> entry.getValue().equals(newMappingKey) && !entry.getKey().equals(entity.getId()))) {
// 还原path
oldGroup.setPath(oldPath);
throw new InvalidArgumentException(SAVE_GROUP_PATH_CONFLICT);
}
}
}
Resource oldResource = getGroupResource(group.getId());
groupResource = resource.getDirectory(group.getName());
isTrue(oldResource != null && oldResource.exists(), GROUP_NOT_FOUND);
// 设置修改时间,修改人
group.setUpdateBy(WebUtils.currentUserName());
group.setUpdateTime(System.currentTimeMillis());
// 名字不一样时重命名
if (!Objects.equals(oldGroup.getName(), group.getName())) {
// 判断分组文件夹需要不存在
isTrue(!groupResource.exists(), FILE_SAVE_FAILURE);
isTrue(oldResource.renameTo(groupResource), FILE_SAVE_FAILURE);
}
}
// 写入分组信息
if (groupResource.getResource(Constants.GROUP_METABASE).write(JsonUtils.toJsonString(group))) {
putGroup(group, groupResource);
TreeNode<Group> treeNode = tree(group.getType()).findTreeNode(it -> it.getId().equals(group.getId()));
// 刷新分组缓存
refreshGroup(groupResource, storage);
if (event.getAction() != EventAction.CREATE) {
event.setEntities(treeNode
.flat()
.stream()
.flatMap(g -> listFiles(g.getId()).stream())
.collect(Collectors.toList()));
}
publisher.publishEvent(event);
return true;
}
return false;
});
}
@Override
public boolean move(String src, String groupId) {
isTrue(!root.readonly(), IS_READ_ONLY);
Group group = groupCache.get(groupId);
isTrue("0".equals(groupId) || group != null, GROUP_NOT_FOUND);
isTrue(!Objects.equals(src, groupId), MOVE_NAME_CONFLICT);
return writeLock(() -> {
Group srcGroup = groupCache.get(src);
if (srcGroup != null) {
// 移动分组
return moveGroup(srcGroup, groupId);
} else {
// 不能将文件移动至根节点下
notNull(group, GROUP_NOT_FOUND);
MagicEntity entity = fileCache.get(src);
notNull(entity, FILE_NOT_FOUND);
// 移动文件
return moveFile(entity.copy(), group);
}
});
}
@Override
public String copyGroup(String src, String groupId) {
isTrue(!root.readonly(), IS_READ_ONLY);
Group group = groupCache.get(groupId);
isTrue("0".equals(groupId) || group != null, GROUP_NOT_FOUND);
isTrue(!Objects.equals(src, groupId), SRC_GROUP_CONFLICT);
Group srcGroup = groupCache.get(src);
notNull(srcGroup, GROUP_NOT_FOUND);
Group newGroup = new Group();
newGroup.setType(srcGroup.getType());
newGroup.setParentId(groupId);
newGroup.setName(srcGroup.getName() + "(Copy)");
newGroup.setPath(Objects.toString(srcGroup.getPath(), "") + "_copy");
newGroup.setOptions(srcGroup.getOptions());
newGroup.setPaths(srcGroup.getPaths());
newGroup.setProperties(srcGroup.getProperties());
saveGroup(newGroup);
listFiles(src).stream()
.map(MagicEntity::copy)
.peek(it -> it.setGroupId(newGroup.getId()))
.peek(it -> it.setId(null))
.forEach(this::saveFile);
return newGroup.getId();
}
/**
* 移动分组
*
* @param src 分组信息
* @param target 目标分组ID
*/
private boolean moveGroup(Group src, String target) {
isTrue(!root.readonly(), IS_READ_ONLY);
MagicResourceStorage<?> storage = storages.get(src.getType());
Resource targetResource = Constants.ROOT_ID.equals(target) ? this.root.getDirectory(storage.folder()) : groupMappings.get(target);
// 校验分组名称是否有冲突
isTrue(!targetResource.getDirectory(src.getName()).exists(), MOVE_NAME_CONFLICT);
targetResource = targetResource.getDirectory(src.getName());
TreeNode<Group> treeNode = tree(storage.folder());
String oldParentId = src.getParentId();
src.setParentId(target);
// 递归找出要移动的文件
List<MagicEntity> entities = treeNode.findTreeNode(it -> it.getId().equals(src.getId()))
.flat()
.stream()
.flatMap(it -> fileCache.values().stream().filter(f -> f.getGroupId().equals(it.getId())))
.collect(Collectors.toList());
if (storage.requirePath()) {
for (MagicEntity entity : entities) {
String newMappingKey = storage.buildKey(entity);
if (pathCache.get(src.getType()).entrySet().stream().anyMatch(entry -> entry.getValue().equals(newMappingKey) && !entry.getKey().equals(entity.getId()))) {
// 还原parentId
src.setParentId(oldParentId);
throw new InvalidArgumentException(MOVE_PATH_CONFLICT);
}
}
}
// 设置修改时间,修改人
src.setUpdateBy(WebUtils.currentUserName());
src.setUpdateTime(System.currentTimeMillis());
Resource oldResource = groupMappings.get(src.getId());
if (oldResource.renameTo(targetResource)) {
Resource resource = targetResource.getResource(Constants.GROUP_METABASE);
if (resource.write(JsonUtils.toJsonString(src))) {
// 更新group缓存
putGroup(src, targetResource);
// 更新mapping缓存
if (storage.requirePath()) {
Map<String, String> selfPathCache = pathCache.get(storage.folder());
entities.forEach(entity -> selfPathCache.put(entity.getId(), storage.buildKey(entity)));
}
// 刷新缓存
refreshGroup(targetResource, storage);
publisher.publishEvent(new GroupEvent(src.getType(), EventAction.MOVE, src, entities));
return true;
}
}
return false;
}
/**
* 移动文件
*
* @param entity 文件信息
* @param group 目标分组
*/
private <T extends MagicEntity> boolean moveFile(T entity, Group group) {
isTrue(!root.readonly(), IS_READ_ONLY);
// 判断是否被锁定
isTrue(!Constants.LOCK.equals(entity.getLock()), RESOURCE_LOCKED);
// 设置新的分组ID
entity.setGroupId(group.getId());
MagicResourceStorage<?> storage = storages.get(group.getType());
// 计算mappingKey
String newMappingKey = storage.buildKey(entity);
Resource resource = groupMappings.get(group.getId());
// 判断名字和路径是否有冲突
Resource newResource = resource.getResource(entity.getName() + storage.suffix());
isTrue(!newResource.exists(), MOVE_NAME_CONFLICT);
isTrue(!storage.requirePath() || pathCache.get(storage.folder()).entrySet().stream().noneMatch(entry -> entry.getValue().equals(newMappingKey) && !entry.getKey().equals(entity.getId())), MOVE_PATH_CONFLICT);
// 设置修改时间,修改人
entity.setUpdateBy(WebUtils.currentUserName());
entity.setUpdateTime(System.currentTimeMillis());
// 写入新文件
if (newResource.write(storage.write(entity))) {
// 删除旧文件
fileMappings.remove(entity.getId()).delete();
// 写入缓存
putFile(storage, entity, newResource);
publisher.publishEvent(new FileEvent(group.getType(), EventAction.MOVE, entity));
return true;
}
return false;
}
@Override
public TreeNode<Group> tree(String type) {
return readLock(() -> groupCache.values().stream().filter(it -> type.equals(it.getType())).collect(Collectors.collectingAndThen(Collectors.toList(), this::convertToTree)));
}
@Override
public Map<String, TreeNode<Group>> tree() {
return readLock(() -> groupCache.values().stream().collect(Collectors.groupingBy(Group::getType, Collectors.collectingAndThen(Collectors.toList(), this::convertToTree))));
}
@Override
public List<Group> getGroupsByFileId(String id) {
return readLock(() -> {
List<Group> groups = new ArrayList<>();
MagicEntity entity = fileCache.get(id);
if (entity != null) {
Group group = groupCache.get(entity.getGroupId());
while (group != null) {
groups.add(group);
group = groupCache.get(group.getParentId());
}
}
return groups;
});
}
private TreeNode<Group> convertToTree(List<Group> groups) {
TreeNode<Group> root = new TreeNode<>();
root.setNode(new Group("0", "root"));
convertToTree(groups, root);
return root;
}
private void convertToTree(List<Group> remains, TreeNode<Group> current) {
Group temp;
List<TreeNode<Group>> childNodes = new LinkedList<>();
Iterator<Group> iterator = remains.iterator();
while (iterator.hasNext()) {
temp = iterator.next();
if (current.getNode().getId().equals(temp.getParentId())) {
childNodes.add(new TreeNode<>(temp));
iterator.remove();
}
}
current.setChildren(childNodes);
childNodes.forEach(it -> convertToTree(remains, it));
}
@Override
public Resource getGroupResource(String id) {
return groupMappings.get(id);
}
@Override
public <T extends MagicEntity> boolean saveFile(T entity) {
isTrue(!root.readonly(), IS_READ_ONLY);
// 校验必填信息
notNull(entity.getGroupId(), GROUP_ID_REQUIRED);
// 校验名字
notBlank(entity.getName(), NAME_REQUIRED);
isTrue(IoUtils.validateFileName(entity.getName()), NAME_INVALID);
return writeLock(() -> {
EventAction action = entity.getId() == null || !fileCache.containsKey(entity.getId()) ? EventAction.CREATE : EventAction.SAVE;
// 获取所在分组
Resource groupResource = getGroupResource(entity.getGroupId());
// 分组需要存在
notNull(groupResource, GROUP_NOT_FOUND);
MagicResourceStorage<T> storage;
if (entity.getGroupId().contains(":")) {
storage = (MagicResourceStorage<T>) this.storages.get(entity.getGroupId().split(":")[0]);
} else {
// 读取分组信息
Group group = groupCache.get(entity.getGroupId());
storage = (MagicResourceStorage<T>) this.storages.get(group.getType());
}
// 检查脚本
if (storage.requiredScript()) {
notBlank(entity.getScript(), SCRIPT_REQUIRED);
}
// 路径检查
if (storage.requirePath()) {
notBlank(((PathMagicEntity) entity).getPath(), PATH_REQUIRED);
String newMappingKey = storage.buildKey(entity);
isTrue(pathCache.get(storage.folder()).entrySet().stream().noneMatch(entry -> entry.getValue().equals(newMappingKey) && !entry.getKey().equals(entity.getId())), PATH_CONFLICT);
}
storage.validate(entity);
// 拼接文件名
String filename = entity.getName() + storage.suffix();
// 获取修改前的信息
Resource fileResource = groupResource.getResource(filename);
if (action == EventAction.CREATE) {
if (entity.getId() == null) {
isTrue(!fileResource.exists(), FILE_SAVE_FAILURE);
// 新增操作赋值
entity.setId(UUID.randomUUID().toString().replace("-", ""));
}
entity.setCreateTime(System.currentTimeMillis());
entity.setCreateBy(WebUtils.currentUserName());
} else {
// 修改操作赋值
entity.setUpdateTime(System.currentTimeMillis());
entity.setUpdateBy(WebUtils.currentUserName());
isTrue(!Constants.LOCK.equals(fileCache.get(entity.getId()).getLock()), RESOURCE_LOCKED);
Resource oldFileResource = fileMappings.get(entity.getId());
if (!oldFileResource.name().equals(fileResource.name())) {
// 重命名
isTrue(oldFileResource.renameTo(fileResource), FILE_SAVE_FAILURE);
}
}
boolean flag = fileResource.write(storage.write(entity));
if (flag) {
publisher.publishEvent(new FileEvent(storage.folder(), action, entity));
putFile(storage, entity, fileResource);
}
return flag;
});
}
private List<MagicEntity> deleteGroup(String id) {
isTrue(!root.readonly(), IS_READ_ONLY);
Group group = groupCache.get(id);
List<MagicEntity> entities = new ArrayList<>();
// 递归删除分组和文件
tree(group.getType())
.findTreeNode(it -> it.getId().equals(id))
.flat()
.forEach(g -> {
groupCache.remove(g.getId());
groupMappings.remove(g.getId());
fileCache.values().stream()
.filter(f -> f.getGroupId().equals(g.getId())).peek(entities::add)
.collect(Collectors.toList())
.forEach(file -> {
fileCache.remove(file.getId());
fileMappings.remove(file.getId());
Map<String, String> map = pathCache.get(g.getType());
if (map != null) {
map.remove(file.getId());
}
});
});
groupMappings.remove(id);
groupCache.remove(id);
return entities;
}
@Override
public boolean delete(String id) {
isTrue(!root.readonly(), IS_READ_ONLY);
return writeLock(() -> {
Resource resource = getGroupResource(id);
if (resource != null) {
// 删除分组
if (resource.exists() && resource.delete()) {
Group group = groupCache.get(id);
GroupEvent event = new GroupEvent(groupCache.get(group.getId()).getType(), EventAction.DELETE, group);
event.setEntities(deleteGroup(id));
publisher.publishEvent(event);
return true;
}
}
resource = fileMappings.get(id);
// 删除文件
if (resource != null && resource.exists() && resource.delete()) {
MagicEntity entity = fileCache.remove(id);
String type = groupCache.get(entity.getGroupId()).getType();
publisher.publishEvent(new FileEvent(type, EventAction.DELETE, entity));
fileMappings.remove(id);
fileCache.remove(id);
Map<String, String> map = pathCache.get(type);
if (map != null) {
map.remove(id);
}
}
return true;
});
}
@Override
public <T extends MagicEntity> List<T> listFiles(String groupId) {
return readLock(() -> {
Group group = groupCache.get(groupId);
notNull(group, GROUP_NOT_FOUND);
return fileCache.values().stream()
.filter(it -> it.getGroupId().equals(groupId))
.map(it -> (T) it)
.collect(Collectors.toList());
});
}
@Override
public <T extends MagicEntity> List<T> files(String type) {
MagicResourceStorage<? extends MagicEntity> storage = storages.get(type);
Resource directory = root.getDirectory(type);
if (directory.exists()) {
return directory.files(storage.suffix()).stream()
.map(storage::readResource)
.map(it -> (T) it)
.collect(Collectors.toList());
}
return Collections.emptyList();
}
@Override
public <T extends MagicEntity> T file(String id) {
return (T) fileCache.get(id);
}
@Override
public Group getGroup(String id) {
return groupCache.get(id);
}
@Override
public void export(String groupId, List<SelectedResource> resources, OutputStream os) throws IOException {
if (StringUtils.isNotBlank(groupId)) {
Resource resource = getGroupResource(groupId);
notNull(resource, GROUP_NOT_FOUND);
resource.export(os);
} else if (resources == null || resources.isEmpty()) {
root.export(os);
} else {
ZipOutputStream zos = new ZipOutputStream(os);
for (SelectedResource item : resources) {
if ("root".equals(item.getType())) {
zos.putNextEntry(new ZipEntry(item.getId() + "/"));
} else if ("group".equals(item.getType())) {
Resource resource = getGroupResource(item.getId());
notNull(resource, GROUP_NOT_FOUND);
zos.putNextEntry(new ZipEntry(resource.getFilePath()));
zos.closeEntry();
resource = resource.getResource(Constants.GROUP_METABASE);
zos.putNextEntry(new ZipEntry(resource.getFilePath()));
zos.write(resource.read());
zos.closeEntry();
} else {
Resource resource = fileMappings.get(item.getId());
MagicEntity entity = fileCache.get(item.getId());
notNull(entity, FILE_NOT_FOUND);
Resource groupResource = groupMappings.get(entity.getGroupId());
Group group = groupCache.get(entity.getGroupId());
MagicResourceStorage<? extends MagicEntity> storage = storages.get(group.getType());
zos.putNextEntry(new ZipEntry(groupResource.getFilePath() + entity.getName() + storage.suffix()));
zos.write(resource.read());
zos.closeEntry();
}
}
zos.flush();
zos.close();
}
}
@Override
public boolean lock(String id) {
return doLockResource(id, Constants.LOCK);
}
private boolean doLockResource(String id, String lockState) {
isTrue(!root.readonly(), IS_READ_ONLY);
return writeLock(() -> {
MagicEntity entity = fileCache.get(id);
Resource resource = fileMappings.get(id);
notNull(entity, FILE_NOT_FOUND);
notNull(resource, FILE_NOT_FOUND);
Group group = groupCache.get(entity.getGroupId());
notNull(group, GROUP_NOT_FOUND);
entity.setLock(lockState);
entity.setUpdateTime(System.currentTimeMillis());
entity.setUpdateBy(WebUtils.currentUserName());
MagicResourceStorage<? extends MagicEntity> storage = storages.get(group.getType());
boolean flag = resource.write(storage.write(entity));
if (flag) {
putFile(storage, entity, resource);
}
return flag;
});
}
@Override
public boolean unlock(String id) {
return doLockResource(id, Constants.UNLOCK);
}
@Override
public boolean upload(InputStream inputStream, boolean full) throws IOException {
isTrue(!root.readonly(), IS_READ_ONLY);
try {
ZipResource zipResource = new ZipResource(inputStream);
Set<Group> groups = new LinkedHashSet<>();
Set<MagicEntity> entities = new LinkedHashSet<>();
return writeLock(() -> {
readAllResource(zipResource, groups, entities, !full);
if (full) {
// 全量模式先删除处理。
root.delete();
this.init();
publisher.publishEvent(new MagicEvent("clear", EventAction.CLEAR));
}
for (Group group : groups) {
saveGroup(group);
}
for (MagicEntity entity : entities) {
saveFile(entity);
}
return true;
});
} finally {
IoUtils.close(inputStream);
}
}
private void readAllResource(Resource root, Set<Group> groups, Set<MagicEntity> entities, boolean checked) {
Resource resource = root.getResource(Constants.GROUP_METABASE);
MagicResourceStorage<? extends MagicEntity> storage = null;
if (resource.exists()) {
Group group = JsonUtils.readValue(resource.read(), Group.class);
group.setType(mappingV1Type(group.getType()));
storage = storages.get(group.getType());
notNull(storage, NOT_SUPPORTED_GROUP_TYPE);
}
readAllResource(root, storage, groups, entities, null, "/", checked);
}
private void readAllResource(Resource root, MagicResourceStorage<? extends MagicEntity> storage, Set<Group> groups, Set<MagicEntity> entities, Set<String> mappingKeys, String path, boolean checked) {
storage = storage == null ? storages.get(root.name()) : storage;
if (storage != null) {
mappingKeys = mappingKeys == null ? new HashSet<>() : mappingKeys;
if (!storage.allowRoot()) {
Resource resource = root.getResource(Constants.GROUP_METABASE);
// 分组信息不存在时,直接返回不处理
if (resource.exists()) {
// 读取分组信息
Group group = JsonUtils.readValue(resource.read(), Group.class);
group.setType(mappingV1Type(group.getType()));
groups.add(group);
if (storage.requirePath()) {
path = path + Objects.toString(group.getPath(), "") + "/";
}
}
}
for (Resource file : root.files(storage.suffix())) {
MagicEntity entity = storage.read(file.read());
if (storage.allowRoot()) {
entity.setGroupId(storage.folder() + ":0");
}
String mappingKey;
if (storage instanceof AbstractPathMagicResourceStorage) {
mappingKey = ((AbstractPathMagicResourceStorage) storage).buildMappingKey((PathMagicEntity) entity, path);
} else {
mappingKey = storage.buildKey(entity);
}
if (checked) {
String groupId = entity.getGroupId();
// 名字和路径冲突检查
fileCache.values().stream()
// 同一分组
.filter(it -> Objects.equals(it.getGroupId(), groupId))
// 非自身
.filter(it -> !it.getId().equals(entity.getId()))
.forEach(it -> isTrue(!Objects.equals(it.getName(), entity.getName()), RESOURCE_PATH_CONFLICT.format(entity.getName())));
}
// 自检
isTrue(mappingKeys.add(mappingKey), RESOURCE_PATH_CONFLICT.format(mappingKey));
entities.add(entity);
}
}
for (Resource directory : root.dirs()) {
readAllResource(directory, storage, groups, entities, mappingKeys, path, checked);
}
}
/**
* 兼容1.x版本
*/
private String mappingV1Type(String type) {
if ("1".equals(type)) {
return "api";
} else if ("2".equals(type)) {
return "function";
}
return type;
}
@Override
public String getGroupName(String groupId) {
return findGroups(groupId).stream()
.map(Group::getName)
.collect(Collectors.joining("/"));
}
@Override
public String getGroupPath(String groupId) {
return findGroups(groupId).stream()
.map(Group::getPath)
.filter(StringUtils::isNotBlank)
.collect(Collectors.joining("/"));
}
private List<Group> findGroups(String groupId) {
return readLock(() -> {
List<Group> groups = new ArrayList<>();
String key = groupId;
while (groupCache.containsKey(key)) {
Group group = groupCache.get(key);
groups.add(0, group);
key = group.getParentId();
}
return groups;
});
}
private void putGroup(Group group, Resource resource) {
groupMappings.put(group.getId(), resource);
groupCache.put(group.getId(), group);
}
private void putFile(MagicResourceStorage<?> storage, MagicEntity entity, Resource resource) {
fileMappings.put(entity.getId(), resource);
fileCache.put(entity.getId(), entity);
if (storage.requirePath()) {
pathCache.get(storage.folder()).put(entity.getId(), storage.buildKey(entity));
}
}
private <R> R readLock(Supplier<R> supplier) {
try {
lock.readLock().lock();
return supplier.get();
} finally {
lock.readLock().unlock();
}
}
private <R> R writeLock(Supplier<R> supplier) {
try {
lock.writeLock().lock();
return supplier.get();
} finally {
lock.writeLock().unlock();
}
}
@Override
public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent) {
try {
this.read(false);
} catch (Exception e) {
logger.error("启动过程中发生异常", e);
}
}
}
package org.ssssssss.magicapi.core.service.impl;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.event.EventListener;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.ssssssss.magicapi.core.config.MagicConfiguration;
import org.ssssssss.magicapi.core.event.FileEvent;
import org.ssssssss.magicapi.core.event.GroupEvent;
import org.ssssssss.magicapi.core.exception.InvalidArgumentException;
import org.ssssssss.magicapi.core.model.ApiInfo;
import org.ssssssss.magicapi.core.service.AbstractMagicDynamicRegistry;
import org.ssssssss.magicapi.core.service.MagicResourceStorage;
import org.ssssssss.magicapi.core.servlet.MagicHttpServletRequest;
import org.ssssssss.magicapi.core.servlet.MagicHttpServletResponse;
import org.ssssssss.magicapi.core.web.RequestHandler;
import org.ssssssss.magicapi.utils.Mapping;
import org.ssssssss.magicapi.utils.PathUtils;
import org.ssssssss.magicapi.utils.ScriptManager;
import org.ssssssss.script.MagicResourceLoader;
import org.ssssssss.script.MagicScriptContext;
import org.ssssssss.script.exception.MagicExitException;
import org.ssssssss.script.runtime.ExitValue;
import org.ssssssss.script.runtime.function.MagicScriptLambdaFunction;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import static org.ssssssss.magicapi.core.config.JsonCodeConstants.REQUEST_PATH_CONFLICT;
public class RequestMagicDynamicRegistry extends AbstractMagicDynamicRegistry<ApiInfo> {
private final Mapping mapping;
private Object handler;
private final Method method = RequestHandler.class.getDeclaredMethod("invoke", MagicHttpServletRequest.class, MagicHttpServletResponse.class, Map.class, Map.class, Map.class);
private static final Logger logger = LoggerFactory.getLogger(RequestMagicDynamicRegistry.class);
private final boolean allowOverride;
private final String prefix;
public RequestMagicDynamicRegistry(MagicResourceStorage<ApiInfo> magicResourceStorage, Mapping mapping, boolean allowOverride, String prefix) throws NoSuchMethodException {
super(magicResourceStorage);
this.mapping = mapping;
this.allowOverride = allowOverride;
this.prefix = StringUtils.defaultIfBlank(prefix, "") + "/";
MagicResourceLoader.addFunctionLoader(this::lookupLambdaFunction);
}
private Object lookupLambdaFunction(MagicScriptContext context, String name) {
int index = name.indexOf(":");
if (index > -1) {
String method = name.substring(0, index);
String path = name.substring(index + 1);
ApiInfo info = getMapping(method.toUpperCase() + ":" + PathUtils.replaceSlash(this.prefix + path));
if (info != null) {
String scriptName = MagicConfiguration.getMagicResourceService().getScriptName(info);
return (MagicScriptLambdaFunction) (variables, args) -> {
MagicScriptContext newContext = new MagicScriptContext();
Map<String, Object> varMap = new LinkedHashMap<>(context.getRootVariables());
varMap.putAll(variables.getVariables(context));
newContext.setScriptName(scriptName);
newContext.putMapIntoContext(varMap);
Object value = ScriptManager.executeScript(info.getScript(), newContext);
if (value instanceof ExitValue) {
throw new MagicExitException((ExitValue) value);
}
return value;
};
}
}
return null;
}
public void setHandler(Object handler) {
this.handler = handler;
}
@EventListener(condition = "#event.type == 'api'")
public void onFileEvent(FileEvent event) {
processEvent(event);
}
@EventListener(condition = "#event.type == 'api'")
public void onGroupEvent(GroupEvent event) {
processEvent(event);
}
public ApiInfo getApiInfoFromRequest(MagicHttpServletRequest request) {
String mappingKey = Objects.toString(request.getMethod(), "GET").toUpperCase() + ":" + request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
return getMapping(mappingKey);
}
@Override
public boolean register(MappingNode<ApiInfo> mappingNode) {
String mappingKey = mappingNode.getMappingKey();
int index = mappingKey.indexOf(":");
String requestMethod = mappingKey.substring(0, index);
String path = mappingKey.substring(index + 1);
RequestMappingInfo requestMappingInfo = mapping.paths(path).methods(RequestMethod.valueOf(requestMethod.toUpperCase())).build();
if (mapping.getHandlerMethods().containsKey(requestMappingInfo)) {
if (!allowOverride) {
logger.error("接口[{}({})]与应用冲突,无法注册", mappingNode.getEntity().getName(), mappingKey);
throw new InvalidArgumentException(REQUEST_PATH_CONFLICT.format(mappingNode.getEntity().getName(),mappingKey));
}
logger.warn("取消注册应用接口:{}", requestMappingInfo);
// 取消注册原接口
mapping.unregister(requestMappingInfo);
}
logger.debug("注册接口[{}({})]", mappingNode.getEntity().getName(), mappingKey);
mapping.register(requestMappingInfo, handler, method);
mappingNode.setMappingData(requestMappingInfo);
return true;
}
@Override
protected void unregister(MappingNode<ApiInfo> mappingNode) {
logger.debug("取消注册接口[{}({})]", mappingNode.getEntity().getName(), mappingNode.getMappingKey());
mapping.unregister((RequestMappingInfo) mappingNode.getMappingData());
}
}
package org.ssssssss.magicapi.core.servlet;
public interface MagicCookie {
String getName();
String getValue();
<T> T getCookie();
}
package org.ssssssss.magicapi.core.servlet;
import org.springframework.http.HttpInputMessage;
import org.springframework.web.multipart.MultipartRequest;
import java.io.IOException;
import java.io.InputStream;
import java.security.Principal;
import java.util.Enumeration;
public interface MagicHttpServletRequest {
String getHeader(String name);
Enumeration<String> getHeaders(String name);
String getRequestURI();
String getMethod();
void setAttribute(String key, Object value);
String[] getParameterValues(String name);
Object getAttribute(String name);
HttpInputMessage getHttpInputMessage();
String getContentType();
MagicHttpSession getSession();
MagicCookie[] getCookies();
InputStream getInputStream() throws IOException;
boolean isMultipart();
String getRemoteAddr();
MultipartRequest resolveMultipart();
Principal getUserPrincipal();
<T> T getRequest();
}
package org.ssssssss.magicapi.core.servlet;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collection;
public interface MagicHttpServletResponse {
public void setHeader(String name, String value);
public void addHeader(String name, String value);
public void sendRedirect(String location) throws IOException;
public void addCookie(MagicCookie cookie);
public void setContentType(String contentType);
public void setCharacterEncoding(String characterEncoding);
public OutputStream getOutputStream() throws IOException;
public Collection<String> getHeaderNames();
public <T> T getResponse();
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment