mirror of
https://gitee.com/ssssssss-team/magic-api.git
synced 2026-06-10 02:42:25 +08:00
'迁移目录'
This commit is contained in:
215
magic-api/pom.xml
Normal file
215
magic-api/pom.xml
Normal file
@@ -0,0 +1,215 @@
|
||||
<?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>
|
||||
<groupId>org.ssssssss</groupId>
|
||||
<artifactId>magic-api</artifactId>
|
||||
<version>1.1.3</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>magic-api</name>
|
||||
<description>auto generate http api</description>
|
||||
<url>https://ssssssss.org/</url>
|
||||
<inceptionYear>2020</inceptionYear>
|
||||
<licenses>
|
||||
<license>
|
||||
<name>The MIT License (MIT)</name>
|
||||
<url>https://github.com/ssssssss-team/magic-api/blob/master/LICENSE</url>
|
||||
</license>
|
||||
</licenses>
|
||||
<developers>
|
||||
<developer>
|
||||
<name>jmxd</name>
|
||||
<email>838425805@qq.com</email>
|
||||
</developer>
|
||||
</developers>
|
||||
<scm>
|
||||
<url>https://gitee.com/ssssssss-team/magic-api</url>
|
||||
<connection>scm:git:https://gitee.com/ssssssss-team/magic-api.git</connection>
|
||||
<developerConnection>scm:git:https://gitee.com/ssssssss-team/magic-api.git</developerConnection>
|
||||
</scm>
|
||||
<properties>
|
||||
<spring-boot.version>2.4.5</spring-boot.version>
|
||||
<magic-script.version>1.4.5</magic-script.version>
|
||||
<commons-compress.version>1.20</commons-compress.version>
|
||||
<commons-io.version>2.7</commons-io.version>
|
||||
<commons-text.version>1.6</commons-text.version>
|
||||
<commons-beanutils.version>1.9.4</commons-beanutils.version>
|
||||
<spring-boot-starter-log4j.version>1.3.8.RELEASE</spring-boot-starter-log4j.version>
|
||||
<java.version>1.8</java.version>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.ssssssss</groupId>
|
||||
<artifactId>magic-script</artifactId>
|
||||
<version>${magic-script.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-text</artifactId>
|
||||
<version>${commons-text.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-log4j2</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-log4j</artifactId>
|
||||
<version>${spring-boot-starter-log4j.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-mongodb</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-beanutils</groupId>
|
||||
<artifactId>commons-beanutils</artifactId>
|
||||
<version>${commons-beanutils.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-jdbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>${commons-io.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
<version>${commons-compress.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>release</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<!--Compiler-->
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- Source -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>2.2.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>jar-no-fork</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- Java Doc -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<configuration>
|
||||
<additionalOptions>
|
||||
<additionalOption>-Xdoclint:none</additionalOption>
|
||||
</additionalOptions>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<!-- GPG -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>1.5</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<distributionManagement>
|
||||
<snapshotRepository>
|
||||
<id>oss</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
</snapshotRepository>
|
||||
<repository>
|
||||
<id>oss</id>
|
||||
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url>
|
||||
</repository>
|
||||
</distributionManagement>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
@@ -0,0 +1,60 @@
|
||||
package org.ssssssss.magicapi.adapter;
|
||||
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.ssssssss.magicapi.provider.ColumnMapperProvider;
|
||||
import org.ssssssss.magicapi.provider.impl.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class ColumnMapperAdapter {
|
||||
|
||||
private final Map<String, RowMapper<Map<String, Object>>> columnMapRowMappers = new HashMap<>();
|
||||
|
||||
private final Map<String, Function<String, String>> rowMapColumnMappers = new HashMap<>();
|
||||
|
||||
private RowMapper<Map<String, Object>> mapRowColumnMapper;
|
||||
|
||||
private Function<String, String> rowMapColumnMapper;
|
||||
|
||||
public ColumnMapperAdapter() {
|
||||
setDefault(new DefaultColumnMapperProvider());
|
||||
add(new CamelColumnMapperProvider());
|
||||
add(new PascalColumnMapperProvider());
|
||||
add(new LowerColumnMapperProvider());
|
||||
add(new UpperColumnMapperProvider());
|
||||
}
|
||||
|
||||
public void add(ColumnMapperProvider columnMapperProvider) {
|
||||
columnMapRowMappers.put(columnMapperProvider.name(), columnMapperProvider.getColumnMapRowMapper());
|
||||
rowMapColumnMappers.put(columnMapperProvider.name(), columnMapperProvider.getRowMapColumnMapper());
|
||||
}
|
||||
|
||||
public void setDefault(ColumnMapperProvider columnMapperProvider) {
|
||||
this.mapRowColumnMapper = columnMapperProvider.getColumnMapRowMapper();
|
||||
this.rowMapColumnMapper = columnMapperProvider.getRowMapColumnMapper();
|
||||
add(columnMapperProvider);
|
||||
}
|
||||
|
||||
public void setDefault(String name) {
|
||||
this.mapRowColumnMapper = getColumnMapRowMapper(name);
|
||||
this.rowMapColumnMapper = getRowMapColumnMapper(name);
|
||||
}
|
||||
|
||||
public RowMapper<Map<String, Object>> getDefaultColumnMapRowMapper() {
|
||||
return this.mapRowColumnMapper;
|
||||
}
|
||||
|
||||
public Function<String, String> getDefaultRowMapColumnMapper() {
|
||||
return this.rowMapColumnMapper;
|
||||
}
|
||||
|
||||
public RowMapper<Map<String, Object>> getColumnMapRowMapper(String name) {
|
||||
return columnMapRowMappers.getOrDefault(name, mapRowColumnMapper);
|
||||
}
|
||||
|
||||
public Function<String, String> getRowMapColumnMapper(String name) {
|
||||
return rowMapColumnMappers.getOrDefault(name, rowMapColumnMapper);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.ssssssss.magicapi.adapter;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.ssssssss.magicapi.dialect.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class DialectAdapter {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DialectAdapter.class);
|
||||
|
||||
private final List<Dialect> dialectList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 缓存已解析的方言
|
||||
*/
|
||||
private final Map<String, Dialect> dialectMap = new ConcurrentHashMap<>();
|
||||
|
||||
public DialectAdapter() {
|
||||
add(new MySQLDialect());
|
||||
add(new OracleDialect());
|
||||
add(new PostgreSQLDialect());
|
||||
add(new ClickhouseDialect());
|
||||
add(new DB2Dialect());
|
||||
add(new SQLServerDialect());
|
||||
add(new SQLServer2005Dialect());
|
||||
add(new DmDialect());
|
||||
}
|
||||
|
||||
public void add(Dialect dialect) {
|
||||
this.dialectList.add(dialect);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库方言
|
||||
*/
|
||||
public Dialect getDialectFromUrl(String fromUrl) {
|
||||
Dialect cached = dialectMap.get(fromUrl);
|
||||
if (cached == null && !dialectMap.containsKey(fromUrl)) {
|
||||
for (Dialect dialect : dialectList) {
|
||||
if (dialect.match(fromUrl)) {
|
||||
cached = dialect;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cached == null) {
|
||||
logger.warn(String.format("magic-api在%s中无法获取dialect", fromUrl));
|
||||
}
|
||||
dialectMap.put(fromUrl, cached);
|
||||
}
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package org.ssssssss.magicapi.adapter;
|
||||
|
||||
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;
|
||||
|
||||
public interface Resource {
|
||||
|
||||
/**
|
||||
* 是否是只读
|
||||
*/
|
||||
default boolean readonly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否存在
|
||||
*/
|
||||
default boolean exists() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是目录
|
||||
*/
|
||||
default boolean isDirectory() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
default boolean delete() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建目录
|
||||
*/
|
||||
default boolean mkdir() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重命名
|
||||
*/
|
||||
default boolean renameTo(Resource resource) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入
|
||||
*/
|
||||
default boolean write(String content) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入
|
||||
*/
|
||||
default boolean write(byte[] bytes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default String separator() {
|
||||
return null;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取
|
||||
*/
|
||||
byte[] read();
|
||||
|
||||
/**
|
||||
* 读取当前资源下的所有内容,主要是缓存作用。
|
||||
*/
|
||||
default void readAll() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取子目录
|
||||
*/
|
||||
default Resource getDirectory(String name) {
|
||||
return getResource(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取子资源
|
||||
*/
|
||||
Resource getResource(String name);
|
||||
|
||||
/**
|
||||
* 获取资源名
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* 获取子资源集合
|
||||
*/
|
||||
List<Resource> resources();
|
||||
|
||||
/**
|
||||
* 父级资源
|
||||
*/
|
||||
Resource parent();
|
||||
|
||||
/**
|
||||
* 目录
|
||||
*/
|
||||
List<Resource> dirs();
|
||||
|
||||
/**
|
||||
* 遍历文件
|
||||
*/
|
||||
List<Resource> files(String suffix);
|
||||
|
||||
/**
|
||||
* 获取所在位置
|
||||
*/
|
||||
String getAbsolutePath();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package org.ssssssss.magicapi.adapter;
|
||||
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
import org.ssssssss.magicapi.adapter.resource.FileResource;
|
||||
import org.ssssssss.magicapi.adapter.resource.JarResource;
|
||||
|
||||
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;
|
||||
|
||||
public abstract 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 = connection.getClass().getName().equals("org.springframework.boot.loader.jar.JarURLConnection");
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package org.ssssssss.magicapi.adapter.resource;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.support.rowset.SqlRowSet;
|
||||
import org.ssssssss.magicapi.adapter.Resource;
|
||||
import org.ssssssss.magicapi.utils.Assert;
|
||||
import org.ssssssss.magicapi.utils.IoUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.Blob;
|
||||
import java.sql.Clob;
|
||||
import java.sql.SQLException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class DatabaseResource extends KeyValueResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DatabaseResource.class);
|
||||
private final JdbcTemplate template;
|
||||
private final String tableName;
|
||||
private Map<String, String> cachedContent = new ConcurrentHashMap<>();
|
||||
|
||||
public DatabaseResource(JdbcTemplate template, String tableName) {
|
||||
this(template, tableName, false);
|
||||
}
|
||||
|
||||
public DatabaseResource(JdbcTemplate template, String tableName, boolean readonly) {
|
||||
this(template, tableName, "/magic-api", readonly);
|
||||
}
|
||||
|
||||
public DatabaseResource(JdbcTemplate template, String tableName, String path, boolean readonly) {
|
||||
this(template, tableName, path, readonly, null);
|
||||
}
|
||||
|
||||
public DatabaseResource(JdbcTemplate template, String tableName, String path, boolean readonly, KeyValueResource parent) {
|
||||
super("/", path, readonly, parent);
|
||||
this.template = template;
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
public DatabaseResource(JdbcTemplate template, String tableName, String path, boolean readonly, Map<String, String> cachedContent, KeyValueResource parent) {
|
||||
this(template, tableName, path, readonly, parent);
|
||||
this.cachedContent = cachedContent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] read() {
|
||||
String value = this.cachedContent.get(path);
|
||||
if (value == null) {
|
||||
String sql = String.format("select file_content from %s where file_path = ?", tableName);
|
||||
value = template.queryForObject(sql, String.class, this.path);
|
||||
}
|
||||
return value == null ? new byte[0] : value.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readAll() {
|
||||
this.cachedContent.clear();
|
||||
String sql = String.format("select file_path, file_content from %s where file_path like '%s%%'", tableName, this.path);
|
||||
SqlRowSet sqlRowSet = template.queryForRowSet(sql);
|
||||
while (sqlRowSet.next()) {
|
||||
Object object = sqlRowSet.getObject(2);
|
||||
String content = null;
|
||||
if (object instanceof String) {
|
||||
content = object.toString();
|
||||
} else if (object instanceof byte[]) {
|
||||
content = new String((byte[]) object, StandardCharsets.UTF_8);
|
||||
} else if (object instanceof Blob) {
|
||||
Blob blob = (Blob) object;
|
||||
try (InputStream is = blob.getBinaryStream()) {
|
||||
content = new String(IoUtils.bytes(is), StandardCharsets.UTF_8);
|
||||
} catch (SQLException | IOException ex) {
|
||||
logger.error("读取content失败", ex);
|
||||
}
|
||||
} else if (object instanceof Clob) {
|
||||
Clob clob = (Clob) object;
|
||||
try {
|
||||
content = clob.getSubString(1, (int) clob.length());
|
||||
} catch (SQLException ex) {
|
||||
logger.error("读取content失败", ex);
|
||||
}
|
||||
}
|
||||
Assert.isNotNull(content, "读取content失败,请检查列类型是否正确");
|
||||
this.cachedContent.put(sqlRowSet.getString(1), content);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
if (this.cachedContent.containsKey(this.path)) {
|
||||
return true;
|
||||
}
|
||||
String sql = String.format("select count(*) from %s where file_path = ?", tableName);
|
||||
Long value = template.queryForObject(sql, Long.class, this.path);
|
||||
return value != null && value > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean write(String content) {
|
||||
String sql = String.format("update %s set file_content = ? where file_path = ?", tableName);
|
||||
if (exists()) {
|
||||
if (template.update(sql, content, this.path) > 0) {
|
||||
this.cachedContent.put(this.path, content);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
sql = String.format("insert into %s (file_path,file_content) values(?,?)", tableName);
|
||||
if (template.update(sql, this.path, content) > 0) {
|
||||
this.cachedContent.put(this.path, content);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> keys() {
|
||||
String sql = String.format("select file_path from %s where file_path like '%s%%'", tableName, isDirectory() ? this.path : (this.path + separator));
|
||||
return new HashSet<>(template.queryForList(sql, String.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean renameTo(Map<String, String> renameKeys) {
|
||||
List<Object[]> args = renameKeys.entrySet().stream().map(entry -> new Object[]{entry.getValue(), entry.getKey()}).collect(Collectors.toList());
|
||||
String sql = String.format("update %s set file_path = ? where file_path = ?", tableName);
|
||||
if (Arrays.stream(template.batchUpdate(sql, args)).sum() > 0) {
|
||||
renameKeys.forEach((oldKey, newKey) -> this.cachedContent.put(newKey, this.cachedContent.remove(oldKey)));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete() {
|
||||
String sql = String.format("delete from %s where file_path = ? or file_path like '%s%%'", tableName, isDirectory() ? this.path : this.path + separator);
|
||||
if (template.update(sql, this.path) > 0) {
|
||||
this.cachedContent.remove(this.path);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<String, Resource> mappedFunction() {
|
||||
return it -> new DatabaseResource(template, tableName, it, readonly, this.cachedContent, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("db://%s/%s", tableName, Objects.toString(this.path, ""));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package org.ssssssss.magicapi.adapter.resource;
|
||||
|
||||
import org.ssssssss.magicapi.adapter.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;
|
||||
|
||||
public class FileResource implements Resource {
|
||||
|
||||
private final boolean readonly;
|
||||
private File file;
|
||||
|
||||
public FileResource(File file, boolean readonly) {
|
||||
this.file = file;
|
||||
this.readonly = readonly;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@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)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource parent() {
|
||||
return new FileResource(this.file.getParentFile(), this.readonly);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Resource> dirs() {
|
||||
return IoUtils.dirs(this.file).stream().map(it -> new FileResource(it, this.readonly)).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)).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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package org.ssssssss.magicapi.adapter.resource;
|
||||
|
||||
import org.ssssssss.magicapi.adapter.Resource;
|
||||
import org.ssssssss.magicapi.adapter.ResourceAdapter;
|
||||
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;
|
||||
|
||||
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 JarResource parent = this;
|
||||
|
||||
public JarResource(JarFile jarFile, String entryName, List<JarEntry> entries, boolean inSpringBoot) {
|
||||
this.jarFile = jarFile;
|
||||
this.entryName = 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.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.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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package org.ssssssss.magicapi.adapter.resource;
|
||||
|
||||
import org.ssssssss.magicapi.adapter.Resource;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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
|
||||
*/
|
||||
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());
|
||||
}
|
||||
|
||||
protected abstract Function<String, Resource> mappedFunction();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package org.ssssssss.magicapi.adapter.resource;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.redis.core.Cursor;
|
||||
import org.springframework.data.redis.core.RedisCallback;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.ssssssss.magicapi.adapter.Resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class RedisResource extends KeyValueResource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RedisResource.class);
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
private final Map<String, String> cachedContent = new ConcurrentHashMap<>();
|
||||
|
||||
public RedisResource(StringRedisTemplate redisTemplate, String path, boolean readonly, RedisResource parent) {
|
||||
super(":", path, readonly, parent);
|
||||
this.redisTemplate = redisTemplate;
|
||||
}
|
||||
|
||||
public RedisResource(StringRedisTemplate redisTemplate, String path, boolean readonly) {
|
||||
this(redisTemplate, path, readonly, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readAll() {
|
||||
this.cachedContent.clear();
|
||||
List<String> keys = new ArrayList<>(keys());
|
||||
List<String> values = redisTemplate.opsForValue().multiGet(keys);
|
||||
if (values != null) {
|
||||
for (int i = 0, size = keys.size(); i < size; i++) {
|
||||
this.cachedContent.put(keys.get(i), values.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] read() {
|
||||
String value = this.cachedContent.get(path);
|
||||
if (value == null) {
|
||||
value = redisTemplate.opsForValue().get(path);
|
||||
}
|
||||
return value == null ? new byte[0] : value.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean write(String content) {
|
||||
this.redisTemplate.opsForValue().set(this.path, content);
|
||||
this.cachedContent.put(this.path, content);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean renameTo(Map<String, String> renameKeys) {
|
||||
renameKeys.forEach(this.redisTemplate::rename);
|
||||
renameKeys.forEach((oldKey, newKey) -> this.cachedContent.put(newKey, this.cachedContent.remove(oldKey)));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
if (this.cachedContent.containsKey(this.path)) {
|
||||
return true;
|
||||
}
|
||||
return Boolean.TRUE.equals(this.redisTemplate.hasKey(this.path));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean deleteByKey(String key) {
|
||||
if (Boolean.TRUE.equals(this.redisTemplate.delete(key))) {
|
||||
this.cachedContent.remove(key);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<String, Resource> mappedFunction() {
|
||||
return (it) -> new RedisResource(this.redisTemplate, it, readonly, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> keys() {
|
||||
Set<String> keys = this.redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
|
||||
ScanOptions options = new ScanOptions.ScanOptionsBuilder()
|
||||
.count(Long.MAX_VALUE)
|
||||
.match((isDirectory() ? this.path : (this.path + separator)) + "*")
|
||||
.build();
|
||||
Set<String> returnKeys = new HashSet<>();
|
||||
try (Cursor<byte[]> cursor = connection.scan(options)) {
|
||||
while (cursor.hasNext()) {
|
||||
returnKeys.add(new String(cursor.next()));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("扫描key出错", e);
|
||||
}
|
||||
return returnKeys;
|
||||
});
|
||||
return keys == null ? Collections.emptySet() : keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("redis://%s", getAbsolutePath());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package org.ssssssss.magicapi.adapter.resource;
|
||||
|
||||
import org.apache.commons.compress.archivers.ArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
|
||||
import org.ssssssss.magicapi.adapter.Resource;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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 HashMap<>();
|
||||
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);
|
||||
}
|
||||
cachedContent.put(entry.getName(), 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.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;
|
||||
}
|
||||
}
|
||||
133
magic-api/src/main/java/org/ssssssss/magicapi/cache/DefaultSqlCache.java
vendored
Normal file
133
magic-api/src/main/java/org/ssssssss/magicapi/cache/DefaultSqlCache.java
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
package org.ssssssss.magicapi.cache;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
public class DefaultSqlCache extends LinkedHashMap<String, DefaultSqlCache.ExpireNode<Object>> implements SqlCache {
|
||||
|
||||
private final String separator = ":";
|
||||
|
||||
private final int capacity;
|
||||
|
||||
private final long expire;
|
||||
|
||||
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
||||
|
||||
public DefaultSqlCache(int capacity, long expire) {
|
||||
super((int) Math.ceil(capacity / 0.75) + 1, 0.75f, true);
|
||||
// 容量
|
||||
this.capacity = capacity;
|
||||
// 固定过期时间
|
||||
this.expire = expire;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(String name, String key, Object value) {
|
||||
// 封装成过期时间节点
|
||||
put(name, key, value, this.expire);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(String name, String key, Object value, long ttl) {
|
||||
long expireTime = ttl > 0 ? (System.currentTimeMillis() + ttl) :
|
||||
(this.expire > -1 ? System.currentTimeMillis() + this.expire : Long.MAX_VALUE);
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
// 封装成过期时间节点
|
||||
put(name + separator + key, new ExpireNode<>(expireTime, value));
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(String name, String key) {
|
||||
key = name + separator + key;
|
||||
lock.readLock().lock();
|
||||
ExpireNode<Object> expireNode;
|
||||
try {
|
||||
expireNode = super.get(key);
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
if (expireNode == null) {
|
||||
return null;
|
||||
}
|
||||
// 惰性删除过期的
|
||||
// if (this.expire > -1L && expireNode.expire < System.currentTimeMillis()) {
|
||||
if (expireNode.expire < System.currentTimeMillis()) {
|
||||
try {
|
||||
lock.writeLock().lock();
|
||||
super.remove(key);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return expireNode.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String name) {
|
||||
try {
|
||||
lock.writeLock().lock();
|
||||
Iterator<Map.Entry<String, ExpireNode<Object>>> iterator = super.entrySet().iterator();
|
||||
String prefix = name + separator;
|
||||
// 清除所有key前缀为name + separator的缓存
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, ExpireNode<Object>> entry = iterator.next();
|
||||
if (entry.getKey().startsWith(prefix)) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry<String, ExpireNode<Object>> eldest) {
|
||||
if (this.expire > -1L && size() > capacity) {
|
||||
clean();
|
||||
}
|
||||
// lru淘汰
|
||||
return size() > this.capacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理已过期的数据
|
||||
*/
|
||||
private void clean() {
|
||||
try {
|
||||
lock.writeLock().lock();
|
||||
Iterator<Map.Entry<String, ExpireNode<Object>>> iterator = super.entrySet().iterator();
|
||||
long now = System.currentTimeMillis();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, ExpireNode<Object>> next = iterator.next();
|
||||
// 判断是否过期
|
||||
if (next.getValue().expire < now) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 过期时间节点
|
||||
*/
|
||||
static class ExpireNode<V> {
|
||||
long expire;
|
||||
V value;
|
||||
|
||||
ExpireNode(long expire, V value) {
|
||||
this.expire = expire;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
54
magic-api/src/main/java/org/ssssssss/magicapi/cache/SqlCache.java
vendored
Normal file
54
magic-api/src/main/java/org/ssssssss/magicapi/cache/SqlCache.java
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
package org.ssssssss.magicapi.cache;
|
||||
|
||||
import org.ssssssss.magicapi.utils.MD5Utils;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* SQL缓存接口
|
||||
*/
|
||||
public interface SqlCache {
|
||||
|
||||
/**
|
||||
* 计算key
|
||||
*/
|
||||
default String buildSqlCacheKey(String sql, Object[] params) {
|
||||
return MD5Utils.encrypt(sql + ":" + Arrays.toString(params));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 存入缓存
|
||||
*
|
||||
* @param name 名字
|
||||
* @param key key
|
||||
* @param value 值
|
||||
*/
|
||||
void put(String name, String key, Object value);
|
||||
|
||||
/**
|
||||
* 存入缓存
|
||||
*
|
||||
* @param name 名字
|
||||
* @param key key
|
||||
* @param value 值
|
||||
* @param ttl 有效期
|
||||
*/
|
||||
void put(String name, String key, Object value, long ttl);
|
||||
|
||||
/**
|
||||
* 获取缓存
|
||||
*
|
||||
* @param name 名字
|
||||
* @param key key
|
||||
*/
|
||||
<T> T get(String name, String key);
|
||||
|
||||
/**
|
||||
* 删除缓存
|
||||
*
|
||||
* @param name 名字
|
||||
*/
|
||||
void delete(String name);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
package org.ssssssss.magicapi.config;
|
||||
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.ssssssss.magicapi.adapter.Resource;
|
||||
import org.ssssssss.magicapi.controller.RequestHandler;
|
||||
import org.ssssssss.magicapi.interceptor.AuthorizationInterceptor;
|
||||
import org.ssssssss.magicapi.interceptor.RequestInterceptor;
|
||||
import org.ssssssss.magicapi.provider.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class MagicConfiguration {
|
||||
|
||||
/**
|
||||
* 拦截器
|
||||
*/
|
||||
private final List<RequestInterceptor> requestInterceptors = new ArrayList<>();
|
||||
/**
|
||||
* 接口映射
|
||||
*/
|
||||
private MappingHandlerMapping mappingHandlerMapping;
|
||||
/**
|
||||
* 函数管理
|
||||
*/
|
||||
private MagicFunctionManager magicFunctionManager;
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
private String password;
|
||||
/**
|
||||
* 编辑器配置文件
|
||||
*/
|
||||
private String editorConfig;
|
||||
/**
|
||||
* 接口查询Service
|
||||
*/
|
||||
private ApiServiceProvider apiServiceProvider;
|
||||
|
||||
/**
|
||||
* 分组查询Service
|
||||
*/
|
||||
private GroupServiceProvider groupServiceProvider;
|
||||
|
||||
/**
|
||||
* 函数查询Service
|
||||
*/
|
||||
private FunctionServiceProvider functionServiceProvider;
|
||||
|
||||
/**
|
||||
* 动态数据源
|
||||
*/
|
||||
private MagicDynamicDataSource magicDynamicDataSource;
|
||||
|
||||
private MagicAPIService magicAPIService;
|
||||
|
||||
/**
|
||||
* 请求出错时,是否抛出异常
|
||||
*/
|
||||
private boolean throwException = false;
|
||||
|
||||
/**
|
||||
* 结果处理器
|
||||
*/
|
||||
private ResultProvider resultProvider;
|
||||
|
||||
private Resource workspace;
|
||||
|
||||
private List<HttpMessageConverter<?>> httpMessageConverters = new ArrayList<>();
|
||||
|
||||
private AuthorizationInterceptor authorizationInterceptor;
|
||||
|
||||
/**
|
||||
* debug 超时时间
|
||||
*/
|
||||
private int debugTimeout;
|
||||
|
||||
private boolean enableWeb = false;
|
||||
|
||||
public void addRequestInterceptor(RequestInterceptor requestInterceptor) {
|
||||
this.requestInterceptors.add(requestInterceptor);
|
||||
}
|
||||
|
||||
public MappingHandlerMapping getMappingHandlerMapping() {
|
||||
return mappingHandlerMapping;
|
||||
}
|
||||
|
||||
public void setMappingHandlerMapping(MappingHandlerMapping mappingHandlerMapping) {
|
||||
this.mappingHandlerMapping = mappingHandlerMapping;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public AuthorizationInterceptor getAuthorizationInterceptor() {
|
||||
return authorizationInterceptor;
|
||||
}
|
||||
|
||||
public void setAuthorizationInterceptor(AuthorizationInterceptor authorizationInterceptor) {
|
||||
this.authorizationInterceptor = authorizationInterceptor;
|
||||
}
|
||||
|
||||
public List<RequestInterceptor> getRequestInterceptors() {
|
||||
return requestInterceptors;
|
||||
}
|
||||
|
||||
public ApiServiceProvider getApiServiceProvider() {
|
||||
return apiServiceProvider;
|
||||
}
|
||||
|
||||
public void setApiServiceProvider(ApiServiceProvider apiServiceProvider) {
|
||||
this.apiServiceProvider = apiServiceProvider;
|
||||
}
|
||||
|
||||
public GroupServiceProvider getGroupServiceProvider() {
|
||||
return groupServiceProvider;
|
||||
}
|
||||
|
||||
public void setGroupServiceProvider(GroupServiceProvider groupServiceProvider) {
|
||||
this.groupServiceProvider = groupServiceProvider;
|
||||
}
|
||||
|
||||
public MagicDynamicDataSource getMagicDynamicDataSource() {
|
||||
return magicDynamicDataSource;
|
||||
}
|
||||
|
||||
public void setMagicDynamicDataSource(MagicDynamicDataSource magicDynamicDataSource) {
|
||||
this.magicDynamicDataSource = magicDynamicDataSource;
|
||||
}
|
||||
|
||||
public boolean isThrowException() {
|
||||
return throwException;
|
||||
}
|
||||
|
||||
public void setThrowException(boolean throwException) {
|
||||
this.throwException = throwException;
|
||||
}
|
||||
|
||||
public ResultProvider getResultProvider() {
|
||||
return resultProvider;
|
||||
}
|
||||
|
||||
public void setResultProvider(ResultProvider resultProvider) {
|
||||
this.resultProvider = resultProvider;
|
||||
}
|
||||
|
||||
public List<HttpMessageConverter<?>> getHttpMessageConverters() {
|
||||
return httpMessageConverters;
|
||||
}
|
||||
|
||||
public void setHttpMessageConverters(List<HttpMessageConverter<?>> httpMessageConverters) {
|
||||
this.httpMessageConverters = httpMessageConverters;
|
||||
}
|
||||
|
||||
public int getDebugTimeout() {
|
||||
return debugTimeout;
|
||||
}
|
||||
|
||||
public void setDebugTimeout(int debugTimeout) {
|
||||
this.debugTimeout = debugTimeout;
|
||||
}
|
||||
|
||||
public boolean isEnableWeb() {
|
||||
return enableWeb;
|
||||
}
|
||||
|
||||
public void setEnableWeb(boolean enableWeb) {
|
||||
this.enableWeb = enableWeb;
|
||||
}
|
||||
|
||||
public FunctionServiceProvider getFunctionServiceProvider() {
|
||||
return functionServiceProvider;
|
||||
}
|
||||
|
||||
public void setFunctionServiceProvider(FunctionServiceProvider functionServiceProvider) {
|
||||
this.functionServiceProvider = functionServiceProvider;
|
||||
}
|
||||
|
||||
public MagicFunctionManager getMagicFunctionManager() {
|
||||
return magicFunctionManager;
|
||||
}
|
||||
|
||||
public void setMagicFunctionManager(MagicFunctionManager magicFunctionManager) {
|
||||
this.magicFunctionManager = magicFunctionManager;
|
||||
}
|
||||
|
||||
public String getEditorConfig() {
|
||||
return editorConfig;
|
||||
}
|
||||
|
||||
public void setEditorConfig(String editorConfig) {
|
||||
this.editorConfig = editorConfig;
|
||||
}
|
||||
|
||||
public Resource getWorkspace() {
|
||||
return workspace;
|
||||
}
|
||||
|
||||
public void setWorkspace(Resource workspace) {
|
||||
this.workspace = workspace;
|
||||
}
|
||||
|
||||
public MagicAPIService getMagicAPIService() {
|
||||
return magicAPIService;
|
||||
}
|
||||
|
||||
public void setMagicAPIService(MagicAPIService magicAPIService) {
|
||||
this.magicAPIService = magicAPIService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印banner
|
||||
*/
|
||||
public void printBanner() {
|
||||
System.out.println(" __ __ _ _ ____ ___ ");
|
||||
System.out.println(" | \\/ | __ _ __ _ (_) ___ / \\ | _ \\|_ _|");
|
||||
System.out.println(" | |\\/| | / _` | / _` || | / __| / _ \\ | |_) || | ");
|
||||
System.out.println(" | | | || (_| || (_| || || (__ / ___ \\ | __/ | | ");
|
||||
System.out.println(" |_| |_| \\__,_| \\__, ||_| \\___|/_/ \\_\\|_| |___|");
|
||||
System.out.println(" |___/ " + RequestHandler.class.getPackage().getImplementationVersion());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.ssssssss.magicapi.config;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.ssssssss.magicapi.model.Constants;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
public class MagicCorsFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
|
||||
}
|
||||
|
||||
public void process(HttpServletRequest request, HttpServletResponse response) {
|
||||
String value = request.getHeader("Origin");
|
||||
response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, StringUtils.isBlank(value) ? "*" : value);
|
||||
response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
|
||||
value = request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS);
|
||||
if (StringUtils.isNotBlank(value)) {
|
||||
response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, value);
|
||||
}
|
||||
value = request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD);
|
||||
response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, StringUtils.isBlank(value) ? "GET,POST,OPTIONS,PUT,DELETE" : value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
|
||||
HttpServletRequest request = (HttpServletRequest) req;
|
||||
if (StringUtils.isNotBlank(Constants.HEADER_REQUEST_SESSION)) {
|
||||
process(request, (HttpServletResponse) resp);
|
||||
}
|
||||
chain.doFilter(req, resp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
package org.ssssssss.magicapi.config;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||
import org.springframework.jdbc.datasource.DataSourceUtils;
|
||||
import org.ssssssss.magicapi.adapter.DialectAdapter;
|
||||
import org.ssssssss.magicapi.dialect.Dialect;
|
||||
import org.ssssssss.magicapi.exception.MagicAPIException;
|
||||
import org.ssssssss.magicapi.utils.Assert;
|
||||
import org.ssssssss.magicapi.utils.IoUtils;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.util.*;
|
||||
|
||||
public class MagicDynamicDataSource {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MagicDynamicDataSource.class);
|
||||
|
||||
private final Map<String, MagicDynamicDataSource.DataSourceNode> dataSourceMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 注册默认数据源
|
||||
*/
|
||||
public void put(DataSource dataSource) {
|
||||
put(null, dataSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册数据源(可以运行时注册)
|
||||
*
|
||||
* @param dataSourceKey 数据源Key
|
||||
*/
|
||||
public void put(String dataSourceKey, DataSource dataSource) {
|
||||
put(dataSourceKey, dataSource, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册数据源(可以运行时注册)
|
||||
*
|
||||
* @param dataSourceKey 数据源Key
|
||||
* @param maxRows 最大返回行数
|
||||
*/
|
||||
public void put(String dataSourceKey, DataSource dataSource, int maxRows) {
|
||||
put(null, dataSourceKey, dataSourceKey, dataSource, maxRows);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册数据源(可以运行时注册)
|
||||
*
|
||||
* @param id 数据源ID
|
||||
* @param dataSourceKey 数据源Key
|
||||
* @param datasourceName 数据源名称
|
||||
*/
|
||||
public void put(String id, String dataSourceKey, String datasourceName, DataSource dataSource, int maxRows) {
|
||||
if (dataSourceKey == null) {
|
||||
dataSourceKey = "";
|
||||
}
|
||||
logger.info("注册数据源:{}", StringUtils.isNotBlank(dataSourceKey) ? dataSourceKey : "default");
|
||||
DataSourceNode node = this.dataSourceMap.put(dataSourceKey, new DataSourceNode(dataSource, dataSourceKey, datasourceName, id, maxRows));
|
||||
if (node != null) {
|
||||
node.close();
|
||||
}
|
||||
if (id != null) {
|
||||
String finalDataSourceKey = dataSourceKey;
|
||||
this.dataSourceMap.entrySet().stream()
|
||||
.filter(it -> id.equals(it.getValue().getId()) && !finalDataSourceKey.equals(it.getValue().getKey()))
|
||||
.findFirst()
|
||||
.ifPresent(it -> {
|
||||
logger.info("移除旧数据源:{}", it.getValue().getKey());
|
||||
this.dataSourceMap.remove(it.getValue().getKey()).close();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全部数据源
|
||||
*/
|
||||
public List<String> datasources() {
|
||||
return new ArrayList<>(this.dataSourceMap.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全部数据源
|
||||
*/
|
||||
public Collection<DataSourceNode> datasourceNodes() {
|
||||
return this.dataSourceMap.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数据源
|
||||
*
|
||||
* @param datasourceKey 数据源Key
|
||||
*/
|
||||
public boolean delete(String datasourceKey) {
|
||||
boolean result = false;
|
||||
// 检查参数是否合法
|
||||
if (datasourceKey != null && !datasourceKey.isEmpty()) {
|
||||
DataSourceNode node = this.dataSourceMap.remove(datasourceKey);
|
||||
result = node != null;
|
||||
if (result) {
|
||||
node.close();
|
||||
}
|
||||
}
|
||||
logger.info("删除数据源:{}:{}", datasourceKey, result ? "成功" : "失败");
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认数据源
|
||||
*/
|
||||
public MagicDynamicDataSource.DataSourceNode getDataSource() {
|
||||
return getDataSource(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据源
|
||||
*
|
||||
* @param datasourceKey 数据源Key
|
||||
*/
|
||||
public MagicDynamicDataSource.DataSourceNode getDataSource(String datasourceKey) {
|
||||
if (datasourceKey == null) {
|
||||
datasourceKey = "";
|
||||
}
|
||||
MagicDynamicDataSource.DataSourceNode dataSourceNode = dataSourceMap.get(datasourceKey);
|
||||
Assert.isNotNull(dataSourceNode, String.format("找不到数据源%s", datasourceKey));
|
||||
return dataSourceNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认数据源
|
||||
*/
|
||||
public void setDefault(DataSource dataSource) {
|
||||
put(dataSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认数据源
|
||||
*
|
||||
* @param maxRows 最大返回行数
|
||||
*/
|
||||
public void setDefault(DataSource dataSource, int maxRows) {
|
||||
put(null, null, null, dataSource, maxRows);
|
||||
}
|
||||
|
||||
|
||||
public void add(String dataSourceKey, DataSource dataSource) {
|
||||
put(dataSourceKey, dataSource);
|
||||
}
|
||||
|
||||
public void add(String dataSourceKey, DataSource dataSource, int maxRows) {
|
||||
put(null, dataSourceKey, dataSourceKey, dataSource, maxRows);
|
||||
}
|
||||
|
||||
public static class DataSourceNode {
|
||||
|
||||
private final String id;
|
||||
|
||||
private final String key;
|
||||
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* 事务管理器
|
||||
*/
|
||||
private final DataSourceTransactionManager dataSourceTransactionManager;
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
private final DataSource dataSource;
|
||||
|
||||
private Dialect dialect;
|
||||
|
||||
DataSourceNode(DataSource dataSource, String key, String name, String id, int maxRows) {
|
||||
this.dataSource = dataSource;
|
||||
this.key = key;
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
this.dataSourceTransactionManager = new DataSourceTransactionManager(this.dataSource);
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
this.jdbcTemplate.setMaxRows(maxRows);
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public JdbcTemplate getJdbcTemplate() {
|
||||
return this.jdbcTemplate;
|
||||
}
|
||||
|
||||
public DataSourceTransactionManager getDataSourceTransactionManager() {
|
||||
return dataSourceTransactionManager;
|
||||
}
|
||||
|
||||
public Dialect getDialect(DialectAdapter dialectAdapter) {
|
||||
if (this.dialect == null) {
|
||||
Connection connection = null;
|
||||
try {
|
||||
connection = this.dataSource.getConnection();
|
||||
this.dialect = dialectAdapter.getDialectFromUrl(connection.getMetaData().getURL());
|
||||
if (this.dialect == null) {
|
||||
throw new MagicAPIException("自动获取数据库方言失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new MagicAPIException("自动获取数据库方言失败", e);
|
||||
} finally {
|
||||
DataSourceUtils.releaseConnection(connection, this.dataSource);
|
||||
}
|
||||
}
|
||||
return dialect;
|
||||
}
|
||||
|
||||
public DataSource getDataSource() {
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
IoUtils.closeDataSource(this.dataSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.ssssssss.magicapi.config;
|
||||
|
||||
/**
|
||||
* 函数,主要用于脚本中直接可使用的函数,如 now();
|
||||
*/
|
||||
public interface MagicFunction {
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
package org.ssssssss.magicapi.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.ssssssss.magicapi.model.FunctionInfo;
|
||||
import org.ssssssss.magicapi.model.Group;
|
||||
import org.ssssssss.magicapi.model.Parameter;
|
||||
import org.ssssssss.magicapi.model.TreeNode;
|
||||
import org.ssssssss.magicapi.provider.FunctionServiceProvider;
|
||||
import org.ssssssss.magicapi.provider.GroupServiceProvider;
|
||||
import org.ssssssss.magicapi.script.ScriptManager;
|
||||
import org.ssssssss.magicapi.utils.PathUtils;
|
||||
import org.ssssssss.script.MagicResourceLoader;
|
||||
import org.ssssssss.script.MagicScriptContext;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MagicFunctionManager {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MagicFunctionManager.class);
|
||||
private static Map<String, FunctionInfo> mappings = new ConcurrentHashMap<>();
|
||||
private GroupServiceProvider groupServiceProvider;
|
||||
private FunctionServiceProvider functionServiceProvider;
|
||||
private TreeNode<Group> groups;
|
||||
|
||||
public MagicFunctionManager(GroupServiceProvider groupServiceProvider, FunctionServiceProvider functionServiceProvider) {
|
||||
this.groupServiceProvider = groupServiceProvider;
|
||||
this.functionServiceProvider = functionServiceProvider;
|
||||
}
|
||||
|
||||
public void registerFunctionLoader() {
|
||||
MagicResourceLoader.addFunctionLoader((path) -> {
|
||||
FunctionInfo info = mappings.get(path);
|
||||
if (info != null) {
|
||||
List<Parameter> parameters = info.getParameters();
|
||||
return (Function<Object[], Object>) objects -> {
|
||||
MagicScriptContext context = MagicScriptContext.get();
|
||||
try {
|
||||
MagicScriptContext functionContext = new MagicScriptContext(context.getRootVariables());
|
||||
MagicScriptContext.set(functionContext);
|
||||
if (objects != null) {
|
||||
for (int i = 0, len = objects.length, size = parameters.size(); i < len && i < size; i++) {
|
||||
functionContext.set(parameters.get(i).getName(), objects[i]);
|
||||
}
|
||||
}
|
||||
return ScriptManager.executeScript(info.getScript(), functionContext);
|
||||
} finally {
|
||||
MagicScriptContext.set(context);
|
||||
}
|
||||
};
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载所有分组
|
||||
*/
|
||||
public synchronized void loadGroup() {
|
||||
groups = groupServiceProvider.functionGroupTree();
|
||||
}
|
||||
|
||||
public void registerAllFunction() {
|
||||
loadGroup();
|
||||
functionServiceProvider.listWithScript().stream()
|
||||
.filter(it -> groupServiceProvider.getFullPath(it.getGroupId()) != null)
|
||||
.forEach(this::register);
|
||||
}
|
||||
|
||||
public boolean hasRegister(FunctionInfo info) {
|
||||
String path = PathUtils.replaceSlash(Objects.toString(groupServiceProvider.getFullPath(info.getGroupId()), "") + "/" + info.getPath());
|
||||
FunctionInfo functionInfo = mappings.get(path);
|
||||
return functionInfo != null && !Objects.equals(info.getId(), functionInfo.getId());
|
||||
}
|
||||
|
||||
public boolean hasRegister(Set<String> paths) {
|
||||
return paths.stream().anyMatch(mappings::containsKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 函数移动
|
||||
*/
|
||||
public boolean move(String id, String groupId) {
|
||||
FunctionInfo info = mappings.get(id);
|
||||
if (info == null) {
|
||||
return false;
|
||||
}
|
||||
String path = Objects.toString(groupServiceProvider.getFullPath(groupId), "");
|
||||
FunctionInfo functionInfo = mappings.get(PathUtils.replaceSlash(path + "/" + info.getPath()));
|
||||
if (functionInfo != null && !Objects.equals(functionInfo.getId(), id)) {
|
||||
return false;
|
||||
}
|
||||
unregister(id);
|
||||
info.setGroupId(groupId);
|
||||
register(info);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public void register(FunctionInfo functionInfo) {
|
||||
if (functionInfo == null) {
|
||||
return;
|
||||
}
|
||||
FunctionInfo oldFunctionInfo = mappings.get(functionInfo.getId());
|
||||
if (oldFunctionInfo != null) {
|
||||
// 完全一致时不用注册
|
||||
if (functionInfo.equals(oldFunctionInfo)) {
|
||||
return;
|
||||
}
|
||||
// 如果路径不一致,则需要取消注册
|
||||
if (!Objects.equals(functionInfo.getPath(), oldFunctionInfo.getPath())) {
|
||||
unregister(functionInfo.getId());
|
||||
}
|
||||
}
|
||||
String path = Objects.toString(groupServiceProvider.getFullPath(functionInfo.getGroupId()), "");
|
||||
mappings.put(functionInfo.getId(), functionInfo);
|
||||
path = PathUtils.replaceSlash(path + "/" + functionInfo.getPath());
|
||||
functionInfo.setMappingPath(path);
|
||||
mappings.put(path, functionInfo);
|
||||
logger.info("注册函数:[{}:{}]", functionInfo.getName(), path);
|
||||
}
|
||||
|
||||
public Collection<FunctionInfo> getFunctionInfos() {
|
||||
return mappings.values();
|
||||
}
|
||||
|
||||
private boolean hasConflict(TreeNode<Group> group, String newPath) {
|
||||
// 获取要移动的接口
|
||||
List<FunctionInfo> infos = mappings.values().stream()
|
||||
.filter(info -> Objects.equals(info.getGroupId(), group.getNode().getId()))
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
// 判断是否有冲突
|
||||
for (FunctionInfo info : infos) {
|
||||
if (mappings.containsKey(PathUtils.replaceSlash(newPath + "/" + info.getPath()))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (TreeNode<Group> child : group.getChildren()) {
|
||||
if (hasConflict(child, newPath + "/" + Objects.toString(child.getNode().getPath(), ""))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean checkGroup(Group group) {
|
||||
TreeNode<Group> oldTree = groups.findTreeNode((item) -> item.getId().equals(group.getId()));
|
||||
// 如果只改了名字,则不做任何操作
|
||||
if (Objects.equals(oldTree.getNode().getParentId(), group.getParentId()) &&
|
||||
Objects.equals(oldTree.getNode().getPath(), group.getPath())) {
|
||||
return true;
|
||||
}
|
||||
// 新的接口分组路径
|
||||
String newPath = Objects.toString(groupServiceProvider.getFullPath(group.getParentId()), "");
|
||||
// 检测冲突
|
||||
return !hasConflict(oldTree, newPath + "/" + Objects.toString(group.getPath(), ""));
|
||||
}
|
||||
|
||||
private void recurseUpdateGroup(TreeNode<Group> node, boolean updateGroupId) {
|
||||
mappings.values().stream()
|
||||
.filter(info -> Objects.equals(info.getGroupId(), node.getNode().getId()))
|
||||
.distinct()
|
||||
.collect(Collectors.toList())
|
||||
.forEach(info -> {
|
||||
unregister(info.getId());
|
||||
if (updateGroupId) {
|
||||
info.setGroupId(node.getNode().getId());
|
||||
}
|
||||
register(info);
|
||||
});
|
||||
for (TreeNode<Group> child : node.getChildren()) {
|
||||
recurseUpdateGroup(child, false);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean updateGroup(String groupId) {
|
||||
loadGroup(); // 重新加载分组
|
||||
TreeNode<Group> groupTreeNode = groups.findTreeNode((item) -> item.getId().equals(groupId));
|
||||
recurseUpdateGroup(groupTreeNode, true);
|
||||
return functionServiceProvider.reload(groupId);
|
||||
}
|
||||
|
||||
public void deleteGroup(List<String> groupIds) {
|
||||
mappings.values().stream()
|
||||
.filter(info -> groupIds.contains(info.getGroupId()))
|
||||
.distinct()
|
||||
.collect(Collectors.toList())
|
||||
.forEach(info -> unregister(info.getId()));
|
||||
// 刷新分组缓存
|
||||
loadGroup();
|
||||
}
|
||||
|
||||
public void unregister(String id) {
|
||||
FunctionInfo functionInfo = mappings.remove(id);
|
||||
if (functionInfo != null) {
|
||||
mappings.remove(functionInfo.getMappingPath());
|
||||
logger.info("取消注册函数:[{},{}]", functionInfo.getName(), functionInfo.getMappingPath());
|
||||
}
|
||||
}
|
||||
|
||||
public void enableRefresh(int interval) {
|
||||
if (interval > 0) {
|
||||
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(this::registerAllFunction, interval, interval, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.ssssssss.magicapi.config;
|
||||
|
||||
import org.ssssssss.script.annotation.UnableCall;
|
||||
|
||||
/**
|
||||
* 模块,主要用于import指令,import时根据模块名获取当前类如:import assert;
|
||||
*/
|
||||
public interface MagicModule {
|
||||
|
||||
/**
|
||||
* 获取模块名
|
||||
*/
|
||||
@UnableCall
|
||||
String getModuleName();
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.ssssssss.magicapi.config;
|
||||
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.ssssssss.magicapi.controller.MagicController;
|
||||
import org.ssssssss.magicapi.exception.MagicLoginException;
|
||||
import org.ssssssss.magicapi.interceptor.AuthorizationInterceptor;
|
||||
import org.ssssssss.magicapi.model.Constants;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* /magic/web相关接口的拦截器
|
||||
*/
|
||||
public class MagicWebRequestInterceptor implements HandlerInterceptor {
|
||||
|
||||
private final MagicCorsFilter magicCorsFilter;
|
||||
|
||||
private AuthorizationInterceptor authorizationInterceptor;
|
||||
|
||||
public MagicWebRequestInterceptor(MagicCorsFilter magicCorsFilter, AuthorizationInterceptor authorizationInterceptor) {
|
||||
this.magicCorsFilter = magicCorsFilter;
|
||||
this.authorizationInterceptor = authorizationInterceptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws MagicLoginException {
|
||||
HandlerMethod handlerMethod;
|
||||
if (handler instanceof HandlerMethod) {
|
||||
handlerMethod = (HandlerMethod) handler;
|
||||
handler = handlerMethod.getBean();
|
||||
if (handler instanceof MagicController) {
|
||||
if (magicCorsFilter != null) {
|
||||
magicCorsFilter.process(request, response);
|
||||
}
|
||||
Valid valid = handlerMethod.getMethodAnnotation(Valid.class);
|
||||
if (authorizationInterceptor.requireLogin() && (valid == null || valid.requireLogin())) {
|
||||
request.setAttribute(Constants.ATTRIBUTE_MAGIC_USER, authorizationInterceptor.getUserByToken(request.getHeader(Constants.MAGIC_TOKEN_HEADER)));
|
||||
}
|
||||
((MagicController) handler).doValid(request, valid);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,527 @@
|
||||
package org.ssssssss.magicapi.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
import org.ssssssss.magicapi.controller.RequestHandler;
|
||||
import org.ssssssss.magicapi.model.ApiInfo;
|
||||
import org.ssssssss.magicapi.model.Group;
|
||||
import org.ssssssss.magicapi.model.TreeNode;
|
||||
import org.ssssssss.magicapi.provider.ApiServiceProvider;
|
||||
import org.ssssssss.magicapi.provider.GroupServiceProvider;
|
||||
import org.ssssssss.magicapi.utils.Mapping;
|
||||
import org.ssssssss.magicapi.utils.PathUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 请求映射
|
||||
*/
|
||||
public class MappingHandlerMapping {
|
||||
|
||||
/**
|
||||
* 已缓存的映射信息
|
||||
*/
|
||||
private static final Map<String, MappingNode> mappings = new ConcurrentHashMap<>();
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MappingHandlerMapping.class);
|
||||
/**
|
||||
* 接口分组
|
||||
*/
|
||||
private static TreeNode<Group> groups;
|
||||
/**
|
||||
* 请求到达时处理的方法
|
||||
*/
|
||||
private final Method method = RequestHandler.class.getDeclaredMethod("invoke", HttpServletRequest.class, HttpServletResponse.class, Map.class, Map.class);
|
||||
/**
|
||||
* 统一接口前缀
|
||||
*/
|
||||
private final String prefix;
|
||||
/**
|
||||
* 是否覆盖应用接口
|
||||
*/
|
||||
private final boolean allowOverride;
|
||||
/**
|
||||
* 缓存已映射的接口信息
|
||||
*/
|
||||
private final List<ApiInfo> apiInfos = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
private Mapping mappingHelper;
|
||||
/**
|
||||
* 请求处理器
|
||||
*/
|
||||
private Object handler;
|
||||
/**
|
||||
* 接口信息读取
|
||||
*/
|
||||
private ApiServiceProvider magicApiService;
|
||||
/**
|
||||
* 分组信息读取
|
||||
*/
|
||||
private GroupServiceProvider groupServiceProvider;
|
||||
|
||||
public MappingHandlerMapping(String prefix, boolean allowOverride) throws NoSuchMethodException {
|
||||
this.prefix = prefix;
|
||||
this.allowOverride = allowOverride;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据request获取对应的接口信息
|
||||
*/
|
||||
public static ApiInfo getMappingApiInfo(HttpServletRequest request) {
|
||||
NativeWebRequest webRequest = new ServletWebRequest(request);
|
||||
// 找到注册的路径
|
||||
String requestMapping = (String) webRequest.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
|
||||
// 根据请求方法和路径获取接口信息
|
||||
return getMappingApiInfo(buildMappingKey(request.getMethod(), requestMapping));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据绑定的key获取接口信息
|
||||
*/
|
||||
private static ApiInfo getMappingApiInfo(String key) {
|
||||
return mappings.get(key).getInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建缓存map的key
|
||||
*
|
||||
* @param requestMethod 请求方法
|
||||
* @param requestMapping 请求路径
|
||||
*/
|
||||
private static String buildMappingKey(String requestMethod, String requestMapping) {
|
||||
if (!StringUtils.isEmpty(requestMapping) && !requestMapping.startsWith("/")) {
|
||||
requestMapping = "/" + requestMapping;
|
||||
}
|
||||
return Objects.toString(requestMethod, "GET").toUpperCase() + ":" + requestMapping;
|
||||
}
|
||||
|
||||
public static Group findGroup(String groupId) {
|
||||
TreeNode<Group> node = groups.findTreeNode(it -> it.getId().equals(groupId));
|
||||
return node != null ? node.getNode() : null;
|
||||
}
|
||||
|
||||
public static List<Group> findGroups(String groupId) {
|
||||
List<Group> groups = new ArrayList<>();
|
||||
Group group;
|
||||
while (!"0".equals(groupId) && (group = MappingHandlerMapping.findGroup(groupId)) != null) {
|
||||
groups.add(group);
|
||||
groupId = group.getParentId();
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
public void setRequestMappingHandlerMapping(RequestMappingHandlerMapping requestMappingHandlerMapping) {
|
||||
this.mappingHelper = Mapping.create(requestMappingHandlerMapping);
|
||||
}
|
||||
|
||||
public void setHandler(Object handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
public void setMagicApiService(ApiServiceProvider magicApiService) {
|
||||
this.magicApiService = magicApiService;
|
||||
}
|
||||
|
||||
public void setGroupServiceProvider(GroupServiceProvider groupServiceProvider) {
|
||||
this.groupServiceProvider = groupServiceProvider;
|
||||
}
|
||||
|
||||
public List<ApiInfo> getApiInfos() {
|
||||
return apiInfos;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载所有分组
|
||||
*/
|
||||
public synchronized void loadGroup() {
|
||||
groups = groupServiceProvider.apiGroupTree();
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册请求
|
||||
*/
|
||||
public void registerAllMapping() {
|
||||
try {
|
||||
loadGroup();
|
||||
List<ApiInfo> list = magicApiService.listWithScript();
|
||||
if (list != null) {
|
||||
list = list.stream().filter(it -> groupServiceProvider.getFullPath(it.getGroupId()) != null).collect(Collectors.toList());
|
||||
for (ApiInfo info : list) {
|
||||
try {
|
||||
// 当接口存在时,刷新缓存
|
||||
registerMapping(info, true);
|
||||
} catch (Exception e) {
|
||||
logger.error("接口:{}注册失败", info.getName(), e);
|
||||
}
|
||||
}
|
||||
List<String> resistedList = list.stream().map(ApiInfo::getId).collect(Collectors.toList());
|
||||
Iterator<ApiInfo> iterator = apiInfos.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
String oldId = iterator.next().getId();
|
||||
// 当接口不存在时,取消注册接口
|
||||
if (!resistedList.contains(oldId)) {
|
||||
unregisterMapping(oldId, false);
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.info("注册接口映射失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据请求方法和路径获取接口信息
|
||||
*
|
||||
* @param method 请求方法
|
||||
* @param requestMapping 请求路径
|
||||
*/
|
||||
public ApiInfo getApiInfo(String method, String requestMapping) {
|
||||
MappingNode mappingNode = mappings.get(buildMappingKey(method, concatPath("", requestMapping)));
|
||||
return mappingNode == null ? null : mappingNode.getInfo();
|
||||
}
|
||||
|
||||
private boolean hasConflict(TreeNode<Group> group, String newPath) {
|
||||
// 获取要移动的接口
|
||||
List<ApiInfo> infos = apiInfos.stream().filter(info -> Objects.equals(info.getGroupId(), group.getNode().getId())).collect(Collectors.toList());
|
||||
// 判断是否有冲突
|
||||
for (ApiInfo info : infos) {
|
||||
String path = concatPath(newPath, "/" + info.getPath());
|
||||
String mappingKey = buildMappingKey(info.getMethod(), path);
|
||||
MappingNode mappingNode = mappings.get(mappingKey);
|
||||
if (mappingNode != null) {
|
||||
if (mappingNode.getInfo().equals(info)) {
|
||||
continue;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!allowOverride) {
|
||||
Map<RequestMappingInfo, HandlerMethod> handlerMethods = this.mappingHelper.getHandlerMethods();
|
||||
if (handlerMethods.get(getRequestMapping(info.getMethod(), path)) != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (TreeNode<Group> child : group.getChildren()) {
|
||||
if (hasConflict(child, newPath + "/" + Objects.toString(child.getNode().getPath(), ""))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否允许修改
|
||||
*/
|
||||
public boolean checkGroup(Group group) {
|
||||
TreeNode<Group> oldTree = groups.findTreeNode((item) -> item.getId().equals(group.getId()));
|
||||
// 如果没移动目录且没改路径,则只需要判断名字是否冲突
|
||||
boolean parentIdEquals = Objects.equals(oldTree.getNode().getParentId(), group.getParentId());
|
||||
boolean nameEquals = Objects.equals(oldTree.getNode().getName(), group.getName());
|
||||
if (parentIdEquals && Objects.equals(oldTree.getNode().getPath(), group.getPath())) {
|
||||
return nameEquals || !groupServiceProvider.exists(group);
|
||||
}
|
||||
// 检测名字是否冲突
|
||||
if ((!parentIdEquals || !nameEquals) && groupServiceProvider.exists(group)) {
|
||||
return false;
|
||||
}
|
||||
// 新的接口分组路径
|
||||
String newPath = groupServiceProvider.getFullPath(group.getParentId());
|
||||
// 检测冲突
|
||||
return !hasConflict(oldTree, newPath + "/" + Objects.toString(group.getPath(), ""));
|
||||
}
|
||||
|
||||
public boolean hasRegister(Set<String> paths) {
|
||||
return paths.stream().anyMatch(mappings::containsKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除分组
|
||||
*/
|
||||
public void deleteGroup(List<String> groupIds) {
|
||||
// 找到对应的所有接口
|
||||
List<ApiInfo> deleteInfos = apiInfos.stream().filter(info -> groupIds.contains(info.getGroupId())).collect(Collectors.toList());
|
||||
for (ApiInfo info : deleteInfos) {
|
||||
unregisterMapping(info.getId(), true);
|
||||
}
|
||||
// 全部删除
|
||||
apiInfos.removeAll(deleteInfos);
|
||||
// 刷新分组缓存
|
||||
loadGroup();
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改分组
|
||||
*/
|
||||
public boolean updateGroup(String groupId) {
|
||||
loadGroup(); // 重新加载分组
|
||||
TreeNode<Group> groupTreeNode = groups.findTreeNode((item) -> item.getId().equals(groupId));
|
||||
recurseUpdateGroup(groupTreeNode, true);
|
||||
return magicApiService.reload(groupId);
|
||||
}
|
||||
|
||||
private void recurseUpdateGroup(TreeNode<Group> node, boolean updateGroupId) {
|
||||
apiInfos.stream().filter(info -> Objects.equals(info.getGroupId(), node.getNode().getId())).forEach(info -> {
|
||||
unregisterMapping(info.getId(), false);
|
||||
if (updateGroupId) {
|
||||
info.setGroupId(node.getNode().getId());
|
||||
}
|
||||
registerMapping(info, false);
|
||||
});
|
||||
for (TreeNode<Group> child : node.getChildren()) {
|
||||
recurseUpdateGroup(child, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否已注册
|
||||
*/
|
||||
public boolean hasRegisterMapping(ApiInfo info) {
|
||||
if (info.getId() != null) {
|
||||
MappingNode mappingNode = mappings.get(info.getId());
|
||||
ApiInfo oldInfo = mappingNode == null ? null : mappingNode.getInfo();
|
||||
if (oldInfo != null
|
||||
&& Objects.equals(oldInfo.getGroupId(), info.getGroupId())
|
||||
&& Objects.equals(oldInfo.getMethod(), info.getMethod())
|
||||
&& Objects.equals(oldInfo.getPath(), info.getPath())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
String mappingKey = getMappingKey(info);
|
||||
if (mappings.containsKey(mappingKey)) {
|
||||
return !mappings.get(mappingKey).getInfo().getId().equals(info.getId());
|
||||
}
|
||||
if (!allowOverride) {
|
||||
Map<RequestMappingInfo, HandlerMethod> handlerMethods = this.mappingHelper.getHandlerMethods();
|
||||
return handlerMethods.get(getRequestMapping(info)) != null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 接口移动
|
||||
*/
|
||||
public boolean move(String id, String groupId) {
|
||||
MappingNode mappingNode = mappings.get(id);
|
||||
if (mappingNode == null) {
|
||||
return false;
|
||||
}
|
||||
ApiInfo copy = mappingNode.getInfo().copy();
|
||||
copy.setGroupId(groupId);
|
||||
if (hasRegisterMapping(copy)) {
|
||||
return false;
|
||||
}
|
||||
unregisterMapping(id, true);
|
||||
registerMapping(copy, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册请求映射
|
||||
*/
|
||||
public void registerMapping(ApiInfo info, boolean delete) {
|
||||
if(info == null){
|
||||
return;
|
||||
}
|
||||
// 先判断是否已注册,如果已注册,则先取消注册在进行注册。
|
||||
MappingNode mappingNode = mappings.get(info.getId());
|
||||
String newMappingKey = getMappingKey(info);
|
||||
if (mappingNode != null) {
|
||||
ApiInfo oldInfo = mappingNode.getInfo();
|
||||
String oldMappingKey = mappingNode.getMappingKey();
|
||||
// URL 路径一致时,刷新脚本内容即可
|
||||
if (Objects.equals(oldMappingKey, newMappingKey)) {
|
||||
if (!info.equals(oldInfo)) {
|
||||
mappingNode.setInfo(info);
|
||||
mappings.get(newMappingKey).setInfo(info);
|
||||
if (delete) {
|
||||
refreshCache(info);
|
||||
}
|
||||
logger.info("刷新接口:{},{}", info.getName(), newMappingKey);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// URL不一致时,需要取消注册旧接口,重新注册新接口
|
||||
logger.info("取消注册接口:{},{}", oldInfo.getName(), oldMappingKey);
|
||||
// 取消注册
|
||||
mappings.remove(oldMappingKey);
|
||||
mappingHelper.unregister(getRequestMapping(oldInfo));
|
||||
}
|
||||
mappingNode = new MappingNode(info);
|
||||
mappingNode.setMappingKey(newMappingKey);
|
||||
// 注册
|
||||
RequestMappingInfo requestMapping = getRequestMapping(info);
|
||||
mappingNode.setRequestMappingInfo(requestMapping);
|
||||
mappingNode.setInfo(info);
|
||||
// 如果与应用冲突
|
||||
if (!overrideApplicationMapping(requestMapping)) {
|
||||
logger.error("接口{},{}与应用冲突,无法注册", info.getName(), newMappingKey);
|
||||
return;
|
||||
}
|
||||
logger.info("注册接口:{},{}", info.getName(), newMappingKey);
|
||||
mappings.put(info.getId(), mappingNode);
|
||||
mappings.put(newMappingKey, mappingNode);
|
||||
registerMapping(requestMapping, handler, method);
|
||||
if (delete) { // 刷新缓存
|
||||
refreshCache(info);
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshCache(ApiInfo info) {
|
||||
apiInfos.removeIf(i -> i.getId().equalsIgnoreCase(info.getId()));
|
||||
apiInfos.add(info);
|
||||
}
|
||||
|
||||
private void registerMapping(RequestMappingInfo requestMapping, Object handler, Method method) {
|
||||
mappingHelper.register(requestMapping, handler, method);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消注册请求映射
|
||||
*/
|
||||
public void unregisterMapping(String id, boolean delete) {
|
||||
MappingNode mappingNode = mappings.remove(id);
|
||||
if (mappingNode != null) {
|
||||
ApiInfo info = mappingNode.getInfo();
|
||||
logger.info("取消注册接口:{}", info.getName());
|
||||
mappings.remove(mappingNode.getMappingKey());
|
||||
mappingHelper.unregister(mappingNode.getRequestMappingInfo());
|
||||
if (delete) { //刷新缓存
|
||||
apiInfos.removeIf(i -> i.getId().equalsIgnoreCase(info.getId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据接口信息获取绑定map的key
|
||||
*/
|
||||
private String getMappingKey(ApiInfo info) {
|
||||
return buildMappingKey(info.getMethod(), getRequestPath(info.getGroupId(), info.getPath()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理前缀
|
||||
*
|
||||
* @param groupId 分组ID
|
||||
* @param path 请求路径
|
||||
*/
|
||||
public String getRequestPath(String groupId, String path) {
|
||||
return concatPath(groupServiceProvider.getFullPath(groupId), path);
|
||||
|
||||
}
|
||||
|
||||
public void registerController(Object target, String base) {
|
||||
Method[] methods = target.getClass().getDeclaredMethods();
|
||||
for (Method method : methods) {
|
||||
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
|
||||
if (requestMapping != null) {
|
||||
String[] paths = Stream.of(requestMapping.value()).map(value -> base + value).toArray(String[]::new);
|
||||
mappingHelper.register(RequestMappingInfo.paths(paths).build(), target, method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String concatPath(String groupPath, String path) {
|
||||
path = groupPath + "/" + path;
|
||||
if (prefix != null) {
|
||||
path = prefix + "/" + path;
|
||||
}
|
||||
path = PathUtils.replaceSlash(path);
|
||||
if (path.startsWith("/")) {
|
||||
return path.substring(1);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* 覆盖应用接口
|
||||
*/
|
||||
private boolean overrideApplicationMapping(RequestMappingInfo requestMapping) {
|
||||
if (mappingHelper.getHandlerMethods().containsKey(requestMapping)) {
|
||||
if (!allowOverride) {
|
||||
// 不允许覆盖
|
||||
return false;
|
||||
}
|
||||
logger.warn("取消注册应用接口:{}", requestMapping);
|
||||
// 取消注册原接口
|
||||
mappingHelper.unregister(requestMapping);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据接口信息构建 RequestMappingInfo
|
||||
*/
|
||||
private RequestMappingInfo getRequestMapping(ApiInfo info) {
|
||||
return RequestMappingInfo.paths(getRequestPath(info.getGroupId(), info.getPath())).methods(RequestMethod.valueOf(info.getMethod().toUpperCase())).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据接口信息构建 RequestMappingInfo
|
||||
*/
|
||||
private RequestMappingInfo getRequestMapping(String method, String path) {
|
||||
return RequestMappingInfo.paths(path).methods(RequestMethod.valueOf(method.toUpperCase())).build();
|
||||
}
|
||||
|
||||
public void enableRefresh(int interval) {
|
||||
if (interval > 0) {
|
||||
logger.info("启动自动刷新magic-api");
|
||||
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(this::registerAllMapping, interval, interval, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
static class MappingNode {
|
||||
|
||||
private ApiInfo info;
|
||||
|
||||
private String mappingKey;
|
||||
|
||||
private RequestMappingInfo requestMappingInfo;
|
||||
|
||||
public MappingNode(ApiInfo info) {
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public ApiInfo getInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
public void setInfo(ApiInfo info) {
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public String getMappingKey() {
|
||||
return mappingKey;
|
||||
}
|
||||
|
||||
public void setMappingKey(String mappingKey) {
|
||||
this.mappingKey = mappingKey;
|
||||
}
|
||||
|
||||
public RequestMappingInfo getRequestMappingInfo() {
|
||||
return requestMappingInfo;
|
||||
}
|
||||
|
||||
public void setRequestMappingInfo(RequestMappingInfo requestMappingInfo) {
|
||||
this.requestMappingInfo = requestMappingInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.ssssssss.magicapi.config;
|
||||
|
||||
import org.ssssssss.magicapi.interceptor.Authorization;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface Valid {
|
||||
|
||||
/**
|
||||
* 验证是否有该权限
|
||||
*/
|
||||
Authorization authorization() default Authorization.NONE;
|
||||
|
||||
/**
|
||||
* 验证是否是只读模式
|
||||
*/
|
||||
boolean readonly() default true;
|
||||
|
||||
/**
|
||||
* 验证是否需要登录
|
||||
*/
|
||||
boolean requireLogin() default true;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.ssssssss.magicapi.context;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Cookie Context 用于脚本中获取cookie信息
|
||||
*/
|
||||
public class CookieContext extends HashMap<String, String> {
|
||||
|
||||
private final Cookie[] cookies;
|
||||
|
||||
public CookieContext(HttpServletRequest request) {
|
||||
this.cookies = request.getCookies();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String get(Object key) {
|
||||
for (Cookie cookie : cookies) {
|
||||
if (cookie.getName().equalsIgnoreCase("" + key)) {
|
||||
return cookie.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.ssssssss.magicapi.context;
|
||||
|
||||
import org.ssssssss.magicapi.model.RequestEntity;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
public class RequestContext {
|
||||
|
||||
private static final ThreadLocal<RequestEntity> REQUEST_ENTITY_THREAD_LOCAL = new InheritableThreadLocal<>();
|
||||
|
||||
public static HttpServletRequest getHttpServletRequest() {
|
||||
RequestEntity requestEntity = REQUEST_ENTITY_THREAD_LOCAL.get();
|
||||
return requestEntity == null ? null : requestEntity.getRequest();
|
||||
}
|
||||
|
||||
public static RequestEntity getRequestEntity() {
|
||||
return REQUEST_ENTITY_THREAD_LOCAL.get();
|
||||
}
|
||||
|
||||
public static void setRequestEntity(RequestEntity requestEntity) {
|
||||
REQUEST_ENTITY_THREAD_LOCAL.set(requestEntity);
|
||||
}
|
||||
|
||||
public static void remove() {
|
||||
REQUEST_ENTITY_THREAD_LOCAL.remove();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.ssssssss.magicapi.context;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Session Context 用于脚本中获取Session信息
|
||||
*/
|
||||
public class SessionContext extends HashMap<String, Object> {
|
||||
|
||||
private final HttpSession session;
|
||||
|
||||
public SessionContext(HttpSession session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(Object key) {
|
||||
return session != null ? session.getAttribute(key.toString()) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object put(String key, Object value) {
|
||||
Object oldValue = session.getAttribute(key);
|
||||
session.setAttribute(key, value);
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package org.ssssssss.magicapi.controller;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.ssssssss.magicapi.config.MagicConfiguration;
|
||||
import org.ssssssss.magicapi.config.Valid;
|
||||
import org.ssssssss.magicapi.interceptor.Authorization;
|
||||
import org.ssssssss.magicapi.model.ApiInfo;
|
||||
import org.ssssssss.magicapi.model.JsonBean;
|
||||
import org.ssssssss.magicapi.provider.ApiServiceProvider;
|
||||
import org.ssssssss.magicapi.provider.MagicAPIService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 接口相关操作
|
||||
*/
|
||||
public class MagicAPIController extends MagicController implements MagicExceptionHandler {
|
||||
|
||||
private final ApiServiceProvider apiServiceProvider;
|
||||
|
||||
private final MagicAPIService magicAPIService;
|
||||
|
||||
public MagicAPIController(MagicConfiguration configuration) {
|
||||
super(configuration);
|
||||
this.apiServiceProvider = configuration.getApiServiceProvider();
|
||||
this.magicAPIService = configuration.getMagicAPIService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除接口
|
||||
*
|
||||
* @param id 接口ID
|
||||
*/
|
||||
@RequestMapping("/delete")
|
||||
@ResponseBody
|
||||
@Valid(readonly = false, authorization = Authorization.DELETE)
|
||||
public JsonBean<Boolean> delete(String id) {
|
||||
return new JsonBean<>(magicAPIService.deleteApi(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有接口
|
||||
*/
|
||||
@RequestMapping("/list")
|
||||
@ResponseBody
|
||||
@Valid(authorization = Authorization.VIEW)
|
||||
public JsonBean<List<ApiInfo>> list() {
|
||||
return new JsonBean<>(magicAPIService.apiList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询接口详情
|
||||
*
|
||||
* @param id 接口ID
|
||||
*/
|
||||
@RequestMapping("/get")
|
||||
@ResponseBody
|
||||
@Valid(authorization = Authorization.VIEW)
|
||||
public JsonBean<ApiInfo> get(String id) {
|
||||
return new JsonBean<>(magicAPIService.getApiInfo(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询历史记录
|
||||
*
|
||||
* @param id 接口ID
|
||||
*/
|
||||
@RequestMapping("/backups")
|
||||
@ResponseBody
|
||||
@Valid(authorization = Authorization.VIEW)
|
||||
public JsonBean<List<Long>> backups(String id) {
|
||||
return new JsonBean<>(apiServiceProvider.backupList(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取历史记录
|
||||
*
|
||||
* @param id 接口ID
|
||||
* @param timestamp 时间点
|
||||
*/
|
||||
@RequestMapping("/backup/get")
|
||||
@ResponseBody
|
||||
@Valid(authorization = Authorization.VIEW)
|
||||
public JsonBean<ApiInfo> backups(String id, Long timestamp) {
|
||||
return new JsonBean<>(apiServiceProvider.backupInfo(id, timestamp));
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动接口
|
||||
*/
|
||||
@RequestMapping("/api/move")
|
||||
@ResponseBody
|
||||
@Valid(readonly = false, authorization = Authorization.SAVE)
|
||||
public JsonBean<Boolean> apiMove(String id, String groupId) {
|
||||
return new JsonBean<>(magicAPIService.moveApi(id, groupId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存接口
|
||||
*
|
||||
* @param info 接口信息
|
||||
*/
|
||||
@RequestMapping("/save")
|
||||
@ResponseBody
|
||||
@Valid(readonly = false, authorization = Authorization.SAVE)
|
||||
public JsonBean<String> save(@RequestBody ApiInfo info) {
|
||||
return new JsonBean<>(magicAPIService.saveApi(info));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.ssssssss.magicapi.controller;
|
||||
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.ssssssss.magicapi.config.MagicConfiguration;
|
||||
import org.ssssssss.magicapi.config.Valid;
|
||||
import org.ssssssss.magicapi.exception.InvalidArgumentException;
|
||||
import org.ssssssss.magicapi.exception.MagicLoginException;
|
||||
import org.ssssssss.magicapi.interceptor.Authorization;
|
||||
import org.ssssssss.magicapi.interceptor.MagicUser;
|
||||
import org.ssssssss.magicapi.model.Constants;
|
||||
import org.ssssssss.magicapi.model.JsonBean;
|
||||
import org.ssssssss.magicapi.model.JsonCodeConstants;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
public class MagicController implements JsonCodeConstants {
|
||||
|
||||
MagicConfiguration configuration;
|
||||
|
||||
MagicController(MagicConfiguration configuration) {
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
public void doValid(HttpServletRequest request, Valid valid) {
|
||||
if (valid != null) {
|
||||
if (!valid.readonly() && configuration.getWorkspace().readonly()) {
|
||||
throw new InvalidArgumentException(IS_READ_ONLY);
|
||||
}
|
||||
if (valid.authorization() != Authorization.NONE && !allowVisit(request, valid.authorization())) {
|
||||
throw new InvalidArgumentException(PERMISSION_INVALID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否有权限访问按钮
|
||||
*/
|
||||
boolean allowVisit(HttpServletRequest request, Authorization authorization) {
|
||||
if (authorization == null) {
|
||||
return true;
|
||||
}
|
||||
MagicUser magicUser = (MagicUser) request.getAttribute(Constants.ATTRIBUTE_MAGIC_USER);
|
||||
return configuration.getAuthorizationInterceptor().allowVisit(magicUser, request, authorization);
|
||||
}
|
||||
|
||||
@ExceptionHandler(MagicLoginException.class)
|
||||
@ResponseBody
|
||||
public JsonBean<Void> invalidLogin(MagicLoginException exception) {
|
||||
return new JsonBean<>(-1, exception.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
package org.ssssssss.magicapi.controller;
|
||||
|
||||
import com.fasterxml.jackson.databind.type.TypeFactory;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertyNameAliases;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
|
||||
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
|
||||
import org.springframework.boot.jdbc.DatabaseDriver;
|
||||
import org.springframework.jdbc.datasource.DataSourceUtils;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.ssssssss.magicapi.adapter.Resource;
|
||||
import org.ssssssss.magicapi.config.MagicConfiguration;
|
||||
import org.ssssssss.magicapi.config.MagicDynamicDataSource;
|
||||
import org.ssssssss.magicapi.config.Valid;
|
||||
import org.ssssssss.magicapi.exception.InvalidArgumentException;
|
||||
import org.ssssssss.magicapi.interceptor.Authorization;
|
||||
import org.ssssssss.magicapi.model.Constants;
|
||||
import org.ssssssss.magicapi.model.JsonBean;
|
||||
import org.ssssssss.magicapi.utils.IoUtils;
|
||||
import org.ssssssss.magicapi.utils.JsonUtils;
|
||||
import org.ssssssss.script.functions.ObjectConvertExtension;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class MagicDataSourceController extends MagicController implements MagicExceptionHandler {
|
||||
|
||||
private static final ClassLoader classLoader = MagicDataSourceController.class.getClassLoader();
|
||||
|
||||
// copy from DataSourceBuilder
|
||||
private static final String[] DATA_SOURCE_TYPE_NAMES = new String[]{
|
||||
"com.zaxxer.hikari.HikariDataSource",
|
||||
"org.apache.tomcat.jdbc.pool.DataSource",
|
||||
"org.apache.commons.dbcp2.BasicDataSource"};
|
||||
|
||||
private final Resource resource;
|
||||
|
||||
public MagicDataSourceController(MagicConfiguration configuration) {
|
||||
super(configuration);
|
||||
resource = configuration.getWorkspace().getDirectory(Constants.PATH_DATASOURCE);
|
||||
if (!resource.exists()) {
|
||||
resource.mkdir();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询数据源列表
|
||||
*/
|
||||
@RequestMapping("/datasource/list")
|
||||
@ResponseBody
|
||||
@Valid(authorization = Authorization.VIEW)
|
||||
public JsonBean<List<Map<String, Object>>> list() {
|
||||
List<Map<String, Object>> list = configuration.getMagicDynamicDataSource().datasourceNodes().stream().map(it -> {
|
||||
Map<String, Object> row = new HashMap<>();
|
||||
row.put("id", it.getId()); // id为空的则认为是不可修改的
|
||||
row.put("key", it.getKey()); // 如果为null 说明是主数据源
|
||||
row.put("name", it.getName());
|
||||
return row;
|
||||
}).collect(Collectors.toList());
|
||||
return new JsonBean<>(list);
|
||||
}
|
||||
|
||||
@RequestMapping("/datasource/test")
|
||||
@ResponseBody
|
||||
public JsonBean<String> test(@RequestBody Map<String, String> properties) {
|
||||
DataSource dataSource = null;
|
||||
try {
|
||||
dataSource = createDataSource(properties);
|
||||
Connection connection = dataSource.getConnection();
|
||||
DataSourceUtils.doCloseConnection(connection, dataSource);
|
||||
} catch (Exception e) {
|
||||
return new JsonBean<>(e.getMessage());
|
||||
} finally {
|
||||
IoUtils.closeDataSource(dataSource);
|
||||
}
|
||||
return new JsonBean<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存数据源
|
||||
*
|
||||
* @param properties 数据源配置信息
|
||||
*/
|
||||
@RequestMapping("/datasource/save")
|
||||
@Valid(readonly = false, authorization = Authorization.DATASOURCE_SAVE)
|
||||
@ResponseBody
|
||||
public JsonBean<String> save(@RequestBody Map<String, String> properties) {
|
||||
String key = properties.get("key");
|
||||
// 校验key是否符合规则
|
||||
notBlank(key, DATASOURCE_KEY_REQUIRED);
|
||||
isTrue(IoUtils.validateFileName(key), DATASOURCE_KEY_INVALID);
|
||||
String name = properties.getOrDefault("name", key);
|
||||
String id = properties.get("id");
|
||||
Stream<String> keyStream;
|
||||
if (StringUtils.isBlank(id)) {
|
||||
keyStream = configuration.getMagicDynamicDataSource().datasources().stream();
|
||||
} else {
|
||||
keyStream = configuration.getMagicDynamicDataSource().datasourceNodes().stream()
|
||||
.filter(it -> !id.equals(it.getId()))
|
||||
.map(MagicDynamicDataSource.DataSourceNode::getKey);
|
||||
}
|
||||
String dsId = StringUtils.isBlank(id) ? UUID.randomUUID().toString().replace("-", "") : id;
|
||||
// 验证是否有冲突
|
||||
isTrue(keyStream.noneMatch(key::equals), DATASOURCE_KEY_EXISTS);
|
||||
|
||||
int maxRows = ObjectConvertExtension.asInt(properties.get("maxRows"), -1);
|
||||
properties.remove("id");
|
||||
// 注册数据源
|
||||
configuration.getMagicDynamicDataSource().put(dsId, key, name, createDataSource(properties), maxRows);
|
||||
properties.put("id", dsId);
|
||||
resource.getResource(dsId + ".json").write(JsonUtils.toJsonString(properties));
|
||||
return new JsonBean<>(dsId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数据源
|
||||
*
|
||||
* @param id 数据源ID
|
||||
*/
|
||||
@RequestMapping("/datasource/delete")
|
||||
@Valid(readonly = false, authorization = Authorization.DATASOURCE_DELETE)
|
||||
@ResponseBody
|
||||
public JsonBean<Boolean> delete(String id) {
|
||||
// 查询数据源是否存在
|
||||
Optional<MagicDynamicDataSource.DataSourceNode> dataSourceNode = configuration.getMagicDynamicDataSource().datasourceNodes().stream()
|
||||
.filter(it -> id.equals(it.getId()))
|
||||
.findFirst();
|
||||
isTrue(dataSourceNode.isPresent(), DATASOURCE_NOT_FOUND);
|
||||
Resource resource = this.resource.getResource(id + ".json");
|
||||
// 删除数据源
|
||||
isTrue(resource.delete(), DATASOURCE_NOT_FOUND);
|
||||
// 取消注册数据源
|
||||
dataSourceNode.ifPresent(it -> configuration.getMagicDynamicDataSource().delete(it.getKey()));
|
||||
return new JsonBean<>(true);
|
||||
|
||||
}
|
||||
|
||||
@RequestMapping("/datasource/detail")
|
||||
@Valid(authorization = Authorization.DATASOURCE_VIEW)
|
||||
@ResponseBody
|
||||
public JsonBean<Object> detail(String id) {
|
||||
Resource resource = this.resource.getResource(id + ".json");
|
||||
byte[] bytes = resource.read();
|
||||
isTrue(bytes != null && bytes.length > 0, DATASOURCE_NOT_FOUND);
|
||||
return new JsonBean<>(JsonUtils.readValue(bytes, LinkedHashMap.class));
|
||||
}
|
||||
|
||||
// 启动之后注册数据源
|
||||
public void registerDataSource() {
|
||||
resource.readAll();
|
||||
List<Resource> resources = resource.files(".json");
|
||||
// 删除旧的数据源
|
||||
configuration.getMagicDynamicDataSource().datasourceNodes().stream()
|
||||
.filter(it -> it.getId() != null)
|
||||
.map(MagicDynamicDataSource.DataSourceNode::getKey)
|
||||
.collect(Collectors.toList())
|
||||
.forEach(it -> configuration.getMagicDynamicDataSource().delete(it));
|
||||
TypeFactory factory = TypeFactory.defaultInstance();
|
||||
for (Resource item : resources) {
|
||||
Map<String, String> properties = JsonUtils.readValue(item.read(), factory.constructMapType(HashMap.class, String.class, String.class));
|
||||
if (properties != null) {
|
||||
String key = properties.get("key");
|
||||
String name = properties.getOrDefault("name", key);
|
||||
String dsId = properties.remove("id");
|
||||
int maxRows = ObjectConvertExtension.asInt(properties.get("maxRows"), -1);
|
||||
configuration.getMagicDynamicDataSource().put(dsId, key, name, createDataSource(properties), maxRows);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// copy from DataSourceBuilder
|
||||
private DataSource createDataSource(Map<String, String> properties) {
|
||||
Class<? extends DataSource> dataSourceType = getDataSourceType(properties.get("type"));
|
||||
if (!properties.containsKey("driverClassName")
|
||||
&& properties.containsKey("url")) {
|
||||
String url = properties.get("url");
|
||||
String driverClass = DatabaseDriver.fromJdbcUrl(url).getDriverClassName();
|
||||
properties.put("driverClassName", driverClass);
|
||||
}
|
||||
DataSource dataSource = BeanUtils.instantiateClass(dataSourceType);
|
||||
ConfigurationPropertySource source = new MapConfigurationPropertySource(properties);
|
||||
ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
|
||||
aliases.addAliases("url", "jdbc-url");
|
||||
aliases.addAliases("username", "user");
|
||||
Binder binder = new Binder(source.withAliases(aliases));
|
||||
binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(dataSource));
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Class<? extends DataSource> getDataSourceType(String datasourceType) {
|
||||
if (StringUtils.isNotBlank(datasourceType)) {
|
||||
try {
|
||||
return (Class<? extends DataSource>) ClassUtils.forName(datasourceType, classLoader);
|
||||
} catch (Exception e) {
|
||||
throw new InvalidArgumentException(DATASOURCE_TYPE_NOT_FOUND.format(datasourceType));
|
||||
}
|
||||
}
|
||||
for (String name : DATA_SOURCE_TYPE_NAMES) {
|
||||
try {
|
||||
return (Class<? extends DataSource>) ClassUtils.forName(name, classLoader);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
throw new InvalidArgumentException(DATASOURCE_TYPE_NOT_SET);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.ssssssss.magicapi.controller;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.ssssssss.magicapi.exception.InvalidArgumentException;
|
||||
import org.ssssssss.magicapi.model.JsonBean;
|
||||
|
||||
public interface MagicExceptionHandler {
|
||||
|
||||
Logger logger = LoggerFactory.getLogger(MagicExceptionHandler.class);
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
@ResponseBody
|
||||
default Object exceptionHandler(Exception e) {
|
||||
logger.error("magic-api调用接口出错", e);
|
||||
return new JsonBean<>(-1, e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(InvalidArgumentException.class)
|
||||
@ResponseBody
|
||||
default Object exceptionHandler(InvalidArgumentException e) {
|
||||
return new JsonBean<>(e.getCode(), e.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package org.ssssssss.magicapi.controller;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.ssssssss.magicapi.config.MagicConfiguration;
|
||||
import org.ssssssss.magicapi.config.Valid;
|
||||
import org.ssssssss.magicapi.interceptor.Authorization;
|
||||
import org.ssssssss.magicapi.model.FunctionInfo;
|
||||
import org.ssssssss.magicapi.model.JsonBean;
|
||||
import org.ssssssss.magicapi.provider.FunctionServiceProvider;
|
||||
import org.ssssssss.magicapi.provider.MagicAPIService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MagicFunctionController extends MagicController implements MagicExceptionHandler {
|
||||
|
||||
private final FunctionServiceProvider functionService;
|
||||
|
||||
private final MagicAPIService magicAPIService;
|
||||
|
||||
public MagicFunctionController(MagicConfiguration configuration) {
|
||||
super(configuration);
|
||||
this.functionService = configuration.getFunctionServiceProvider();
|
||||
this.magicAPIService = configuration.getMagicAPIService();
|
||||
}
|
||||
|
||||
@RequestMapping("/function/list")
|
||||
@ResponseBody
|
||||
@Valid(authorization = Authorization.VIEW)
|
||||
public JsonBean<List<FunctionInfo>> list() {
|
||||
return new JsonBean<>(magicAPIService.functionList());
|
||||
}
|
||||
|
||||
@RequestMapping("/function/get")
|
||||
@ResponseBody
|
||||
@Valid(authorization = Authorization.VIEW)
|
||||
public JsonBean<FunctionInfo> get(String id) {
|
||||
return new JsonBean<>(magicAPIService.getFunctionInfo(id));
|
||||
}
|
||||
|
||||
@RequestMapping("/function/backup/get")
|
||||
@ResponseBody
|
||||
@Valid(authorization = Authorization.VIEW)
|
||||
public JsonBean<FunctionInfo> backups(String id, Long timestamp) {
|
||||
return new JsonBean<>(functionService.backupInfo(id, timestamp));
|
||||
}
|
||||
|
||||
@RequestMapping("/function/backups")
|
||||
@ResponseBody
|
||||
@Valid(authorization = Authorization.VIEW)
|
||||
public JsonBean<List<Long>> backups(String id) {
|
||||
return new JsonBean<>(functionService.backupList(id));
|
||||
}
|
||||
|
||||
@RequestMapping("/function/move")
|
||||
@ResponseBody
|
||||
@Valid(readonly = false, authorization = Authorization.SAVE)
|
||||
public JsonBean<Boolean> move(String id, String groupId) {
|
||||
return new JsonBean<>(magicAPIService.moveFunction(id, groupId));
|
||||
}
|
||||
|
||||
|
||||
@RequestMapping("/function/save")
|
||||
@ResponseBody
|
||||
@Valid(readonly = false, authorization = Authorization.SAVE)
|
||||
public JsonBean<String> save(@RequestBody FunctionInfo functionInfo) {
|
||||
return new JsonBean<>(magicAPIService.saveFunction(functionInfo));
|
||||
}
|
||||
|
||||
@RequestMapping("/function/delete")
|
||||
@ResponseBody
|
||||
@Valid(readonly = false, authorization = Authorization.DELETE)
|
||||
public JsonBean<Boolean> delete(String id) {
|
||||
return new JsonBean<>(magicAPIService.deleteFunction(id));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.ssssssss.magicapi.controller;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.ssssssss.magicapi.config.MagicConfiguration;
|
||||
import org.ssssssss.magicapi.config.Valid;
|
||||
import org.ssssssss.magicapi.interceptor.Authorization;
|
||||
import org.ssssssss.magicapi.model.Group;
|
||||
import org.ssssssss.magicapi.model.JsonBean;
|
||||
import org.ssssssss.magicapi.provider.MagicAPIService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MagicGroupController extends MagicController implements MagicExceptionHandler {
|
||||
|
||||
private final MagicAPIService magicAPIService;
|
||||
|
||||
public MagicGroupController(MagicConfiguration configuration) {
|
||||
super(configuration);
|
||||
this.magicAPIService = configuration.getMagicAPIService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除分组
|
||||
*/
|
||||
@RequestMapping("/group/delete")
|
||||
@ResponseBody
|
||||
@Valid(readonly = false, authorization = Authorization.DELETE)
|
||||
public JsonBean<Boolean> deleteGroup(String groupId) {
|
||||
return new JsonBean<>(magicAPIService.deleteGroup(groupId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改分组
|
||||
*/
|
||||
@RequestMapping("/group/update")
|
||||
@ResponseBody
|
||||
@Valid(readonly = false, authorization = Authorization.SAVE)
|
||||
public synchronized JsonBean<Boolean> groupUpdate(@RequestBody Group group) {
|
||||
if (magicAPIService.updateGroup(group)) {
|
||||
return new JsonBean<>(true);
|
||||
}
|
||||
return new JsonBean<>(GROUP_CONFLICT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有分组
|
||||
*/
|
||||
@RequestMapping("/group/list")
|
||||
@ResponseBody
|
||||
@Valid(authorization = Authorization.VIEW)
|
||||
public JsonBean<List<Group>> groupList(String type) {
|
||||
return new JsonBean<>(magicAPIService.groupList(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建分组
|
||||
*/
|
||||
@RequestMapping("/group/create")
|
||||
@ResponseBody
|
||||
@Valid(readonly = false, authorization = Authorization.SAVE)
|
||||
public JsonBean<String> createGroup(@RequestBody Group group) {
|
||||
return new JsonBean<>(magicAPIService.createGroup(group));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
package org.ssssssss.magicapi.controller;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
import org.ssssssss.magicapi.adapter.Resource;
|
||||
import org.ssssssss.magicapi.adapter.resource.ZipResource;
|
||||
import org.ssssssss.magicapi.config.MagicConfiguration;
|
||||
import org.ssssssss.magicapi.config.Valid;
|
||||
import org.ssssssss.magicapi.exception.MagicLoginException;
|
||||
import org.ssssssss.magicapi.interceptor.Authorization;
|
||||
import org.ssssssss.magicapi.interceptor.MagicUser;
|
||||
import org.ssssssss.magicapi.logging.MagicLoggerContext;
|
||||
import org.ssssssss.magicapi.model.*;
|
||||
import org.ssssssss.magicapi.modules.ResponseModule;
|
||||
import org.ssssssss.magicapi.modules.SQLModule;
|
||||
import org.ssssssss.magicapi.provider.GroupServiceProvider;
|
||||
import org.ssssssss.magicapi.provider.MagicAPIService;
|
||||
import org.ssssssss.magicapi.provider.StoreServiceProvider;
|
||||
import org.ssssssss.magicapi.utils.JsonUtils;
|
||||
import org.ssssssss.magicapi.utils.PathUtils;
|
||||
import org.ssssssss.script.MagicResourceLoader;
|
||||
import org.ssssssss.script.MagicScriptEngine;
|
||||
import org.ssssssss.script.ScriptClass;
|
||||
import org.ssssssss.script.parsing.Span;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class MagicWorkbenchController extends MagicController implements MagicExceptionHandler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MagicWorkbenchController.class);
|
||||
|
||||
public MagicWorkbenchController(MagicConfiguration configuration) {
|
||||
super(configuration);
|
||||
// 给前端添加代码提示
|
||||
MagicScriptEngine.addScriptClass(SQLModule.class);
|
||||
MagicScriptEngine.addScriptClass(MagicAPIService.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有class
|
||||
*/
|
||||
@RequestMapping("/classes")
|
||||
@ResponseBody
|
||||
@Valid(requireLogin = false)
|
||||
public JsonBean<Map<String, Object>> classes() {
|
||||
Map<String, ScriptClass> classMap = MagicScriptEngine.getScriptClassMap();
|
||||
classMap.putAll(MagicResourceLoader.getModules());
|
||||
Map<String, Object> values = new HashMap<>();
|
||||
values.put("classes", classMap);
|
||||
values.put("extensions", MagicScriptEngine.getExtensionScriptClass());
|
||||
values.put("functions", MagicScriptEngine.getFunctions());
|
||||
return new JsonBean<>(values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个class
|
||||
*
|
||||
* @param className 类名
|
||||
*/
|
||||
@RequestMapping("/class")
|
||||
@ResponseBody
|
||||
public JsonBean<List<ScriptClass>> clazz(String className) {
|
||||
return new JsonBean<>(MagicScriptEngine.getScriptClass(className));
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录
|
||||
*/
|
||||
@RequestMapping("/login")
|
||||
@ResponseBody
|
||||
@Valid(requireLogin = false)
|
||||
public JsonBean<Boolean> login(String username, String password, HttpServletRequest request, HttpServletResponse response) throws MagicLoginException {
|
||||
if (configuration.getAuthorizationInterceptor().requireLogin()) {
|
||||
if (StringUtils.isBlank(username) && StringUtils.isBlank(password)) {
|
||||
try {
|
||||
configuration.getAuthorizationInterceptor().getUserByToken(request.getHeader(Constants.MAGIC_TOKEN_HEADER));
|
||||
} catch (MagicLoginException ignored) {
|
||||
return new JsonBean<>(false);
|
||||
}
|
||||
} else {
|
||||
MagicUser user = configuration.getAuthorizationInterceptor().login(username, password);
|
||||
response.setHeader(Constants.MAGIC_TOKEN_HEADER, user.getToken());
|
||||
response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, Constants.MAGIC_TOKEN_HEADER);
|
||||
}
|
||||
}
|
||||
return new JsonBean<>(true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建控制台输出
|
||||
*/
|
||||
@RequestMapping("/console")
|
||||
@Valid(requireLogin = false)
|
||||
public SseEmitter console() throws IOException {
|
||||
String sessionId = UUID.randomUUID().toString().replace("-", "");
|
||||
SseEmitter emitter = MagicLoggerContext.createEmitter(sessionId);
|
||||
emitter.send(SseEmitter.event().data(sessionId).name("create"));
|
||||
return emitter;
|
||||
}
|
||||
|
||||
@RequestMapping("/options")
|
||||
@ResponseBody
|
||||
@Valid(requireLogin = false)
|
||||
public JsonBean<List<List<String>>> options() {
|
||||
return new JsonBean<>(Stream.of(Options.values()).map(item -> Arrays.asList(item.getValue(), item.getName(), item.getDefaultValue())).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@RequestMapping("/search")
|
||||
@ResponseBody
|
||||
public JsonBean<List<Map<String, Object>>> search(String keyword, String type) {
|
||||
if (StringUtils.isBlank(keyword)) {
|
||||
return new JsonBean<>(Collections.emptyList());
|
||||
}
|
||||
List<MagicEntity> entities = new ArrayList<>();
|
||||
if (!"2".equals(type)) {
|
||||
entities.addAll(configuration.getMappingHandlerMapping().getApiInfos());
|
||||
}
|
||||
if (!"1".equals(type)) {
|
||||
entities.addAll(configuration.getMagicFunctionManager().getFunctionInfos());
|
||||
}
|
||||
return new JsonBean<>(entities.stream().filter(it -> it.getScript().contains(keyword)).map(it -> {
|
||||
String script = it.getScript();
|
||||
int index = script.indexOf(keyword);
|
||||
int endIndex = script.indexOf("\n", index + keyword.length());
|
||||
Span span = new Span(script, index, endIndex == -1 ? script.length() : endIndex);
|
||||
return new HashMap<String, Object>() {
|
||||
{
|
||||
put("id", it.getId());
|
||||
put("text", span.getText().trim());
|
||||
put("line", span.getLine().getLineNumber());
|
||||
put("type", it instanceof ApiInfo ? 1 : 2);
|
||||
}
|
||||
};
|
||||
}).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/config-js")
|
||||
@ResponseBody
|
||||
@Valid(requireLogin = false)
|
||||
public ResponseEntity<?> configJs() {
|
||||
ResponseEntity.BodyBuilder responseBuilder = ResponseEntity.ok().contentType(MediaType.parseMediaType("application/javascript"));
|
||||
if (configuration.getEditorConfig() != null) {
|
||||
try {
|
||||
String path = configuration.getEditorConfig();
|
||||
if (path.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX)) {
|
||||
path = path.substring(ResourceUtils.CLASSPATH_URL_PREFIX.length());
|
||||
return responseBuilder.body(new InputStreamResource(new ClassPathResource(path).getInputStream()));
|
||||
}
|
||||
File file = ResourceUtils.getFile(configuration.getEditorConfig());
|
||||
return responseBuilder.body(Files.readAllBytes(Paths.get(file.toURI())));
|
||||
} catch (IOException e) {
|
||||
logger.warn("读取编辑器配置文件{}失败", configuration.getEditorConfig());
|
||||
}
|
||||
}
|
||||
return responseBuilder.body("var MAGIC_EDITOR_CONFIG = {}".getBytes());
|
||||
}
|
||||
|
||||
@RequestMapping("/download")
|
||||
@Valid(authorization = Authorization.DOWNLOAD)
|
||||
@ResponseBody
|
||||
public ResponseEntity<?> download(String groupId) throws IOException {
|
||||
if (StringUtils.isBlank(groupId)) {
|
||||
return download(configuration.getWorkspace(), "magic-api-all.zip");
|
||||
} else {
|
||||
Resource resource = configuration.getGroupServiceProvider().getGroupResource(groupId);
|
||||
notNull(resource, GROUP_NOT_FOUND);
|
||||
return download(resource, "magic-api-group.zip");
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping("/upload")
|
||||
@Valid(readonly = false, authorization = Authorization.UPLOAD)
|
||||
@ResponseBody
|
||||
public JsonBean<Boolean> upload(MultipartFile file, String mode) throws IOException {
|
||||
notNull(file, FILE_IS_REQUIRED);
|
||||
ZipResource root = new ZipResource(file.getInputStream());
|
||||
Set<String> apiPaths = new HashSet<>();
|
||||
Set<String> functionPaths = new HashSet<>();
|
||||
Set<Group> groups = new HashSet<>();
|
||||
Set<ApiInfo> apiInfos = new HashSet<>();
|
||||
Set<FunctionInfo> functionInfos = new HashSet<>();
|
||||
// 检查上传资源中是否有冲突
|
||||
isTrue(readPaths(groups, apiPaths, functionPaths, apiInfos, functionInfos, "/", root), UPLOAD_PATH_CONFLICT);
|
||||
// 判断是否是强制上传
|
||||
if (!"force".equals(mode)) {
|
||||
// 检测与已注册的接口和函数是否有冲突
|
||||
isTrue(!configuration.getMappingHandlerMapping().hasRegister(apiPaths), UPLOAD_PATH_CONFLICT.format("接口"));
|
||||
isTrue(!configuration.getMagicFunctionManager().hasRegister(apiPaths), UPLOAD_PATH_CONFLICT.format("函数"));
|
||||
}
|
||||
Resource item = root.getResource("group.json");
|
||||
GroupServiceProvider groupServiceProvider = configuration.getGroupServiceProvider();
|
||||
if (item.exists()) {
|
||||
Group group = groupServiceProvider.readGroup(item);
|
||||
// 检查分组是否存在
|
||||
isTrue("0".equals(group.getParentId()) || groupServiceProvider.getGroupResource(group.getParentId()).exists(), GROUP_NOT_FOUND);
|
||||
groups.removeIf(it -> it.getId().equalsIgnoreCase(group.getId()));
|
||||
}
|
||||
for (Group group : groups) {
|
||||
Resource groupResource = groupServiceProvider.getGroupResource(group.getId());
|
||||
if (groupResource != null && groupResource.exists()) {
|
||||
groupServiceProvider.update(group);
|
||||
} else {
|
||||
groupServiceProvider.insert(group);
|
||||
}
|
||||
}
|
||||
Resource backups = configuration.getWorkspace().getDirectory(Constants.PATH_BACKUPS);
|
||||
// 保存
|
||||
write(configuration.getApiServiceProvider(), backups, apiInfos);
|
||||
write(configuration.getFunctionServiceProvider(), backups, functionInfos);
|
||||
// 重新注册
|
||||
configuration.getMappingHandlerMapping().registerAllMapping();
|
||||
configuration.getMagicFunctionManager().registerAllFunction();
|
||||
return new JsonBean<>(SUCCESS, true);
|
||||
}
|
||||
|
||||
private <T extends MagicEntity> void write(StoreServiceProvider<T> provider, Resource backups, Set<T> infos) {
|
||||
for (T info : infos) {
|
||||
Resource resource = configuration.getGroupServiceProvider().getGroupResource(info.getGroupId());
|
||||
resource = resource.getResource(info.getName() + ".ms");
|
||||
byte[] content = provider.serialize(info);
|
||||
resource.write(content);
|
||||
Resource directory = backups.getDirectory(info.getId());
|
||||
if (!directory.exists()) {
|
||||
directory.mkdir();
|
||||
}
|
||||
directory.getResource(System.currentTimeMillis() + ".ms").write(content);
|
||||
resource.write(content);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean readPaths(Set<Group> groups, Set<String> apiPaths, Set<String> functionPaths, Set<ApiInfo> apiInfos, Set<FunctionInfo> functionInfos, String parentPath, Resource root) {
|
||||
Resource resource = root.getResource("group.json");
|
||||
String path = "";
|
||||
if (resource.exists()) {
|
||||
Group group = JsonUtils.readValue(resource.read(), Group.class);
|
||||
groups.add(group);
|
||||
path = Objects.toString(group.getPath(), "");
|
||||
boolean isApi = Constants.GROUP_TYPE_API.equals(group.getType());
|
||||
for (Resource file : root.files(".ms")) {
|
||||
boolean conflict;
|
||||
if (isApi) {
|
||||
ApiInfo info = configuration.getApiServiceProvider().deserialize(file.read());
|
||||
apiInfos.add(info);
|
||||
conflict = !apiPaths.add(Objects.toString(info.getMethod(), "GET") + ":" + PathUtils.replaceSlash(parentPath + "/" + path + "/" + info.getPath()));
|
||||
} else {
|
||||
FunctionInfo info = configuration.getFunctionServiceProvider().deserialize(file.read());
|
||||
functionInfos.add(info);
|
||||
conflict = !functionPaths.add(PathUtils.replaceSlash(parentPath + "/" + path + "/" + info.getPath()));
|
||||
}
|
||||
if (conflict) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Resource directory : root.dirs()) {
|
||||
if (!readPaths(groups, apiPaths, functionPaths, apiInfos, functionInfos, PathUtils.replaceSlash(parentPath + "/" + path), directory)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private ResponseEntity<?> download(Resource resource, String filename) throws IOException {
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
resource.export(os, Constants.PATH_BACKUPS);
|
||||
return ResponseModule.download(os.toByteArray(), filename);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,503 @@
|
||||
package org.ssssssss.magicapi.controller;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.slf4j.event.Level;
|
||||
import org.springframework.core.io.InputStreamSource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.ssssssss.magicapi.config.MagicConfiguration;
|
||||
import org.ssssssss.magicapi.config.MappingHandlerMapping;
|
||||
import org.ssssssss.magicapi.config.Valid;
|
||||
import org.ssssssss.magicapi.context.CookieContext;
|
||||
import org.ssssssss.magicapi.context.RequestContext;
|
||||
import org.ssssssss.magicapi.context.SessionContext;
|
||||
import org.ssssssss.magicapi.interceptor.RequestInterceptor;
|
||||
import org.ssssssss.magicapi.logging.LogInfo;
|
||||
import org.ssssssss.magicapi.logging.MagicLoggerContext;
|
||||
import org.ssssssss.magicapi.model.*;
|
||||
import org.ssssssss.magicapi.modules.ResponseModule;
|
||||
import org.ssssssss.magicapi.provider.ResultProvider;
|
||||
import org.ssssssss.magicapi.script.ScriptManager;
|
||||
import org.ssssssss.magicapi.utils.PatternUtils;
|
||||
import org.ssssssss.script.MagicScriptContext;
|
||||
import org.ssssssss.script.MagicScriptDebugContext;
|
||||
import org.ssssssss.script.exception.MagicScriptAssertException;
|
||||
import org.ssssssss.script.exception.MagicScriptException;
|
||||
import org.ssssssss.script.functions.ObjectConvertExtension;
|
||||
import org.ssssssss.script.parsing.Span;
|
||||
import org.ssssssss.script.parsing.ast.literal.BooleanLiteral;
|
||||
import org.ssssssss.script.reflection.JavaInvoker;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.ssssssss.magicapi.model.Constants.*;
|
||||
|
||||
public class RequestHandler extends MagicController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RequestHandler.class);
|
||||
|
||||
private final ResultProvider resultProvider;
|
||||
|
||||
public RequestHandler(MagicConfiguration configuration) {
|
||||
super(configuration);
|
||||
this.resultProvider = configuration.getResultProvider();
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试入口、实际请求入口
|
||||
*/
|
||||
@ResponseBody
|
||||
@Valid(requireLogin = false) // 无需验证是否要登录
|
||||
public Object invoke(HttpServletRequest request, HttpServletResponse response,
|
||||
@PathVariable(required = false) Map<String, Object> pathVariables,
|
||||
@RequestParam(required = false) Map<String, Object> parameters) throws Throwable {
|
||||
RequestEntity requestEntity = new RequestEntity(request, response, isRequestedFromTest(request), parameters, pathVariables);
|
||||
if (requestEntity.isRequestedFromTest()) {
|
||||
response.setHeader(HEADER_RESPONSE_WITH_MAGIC_API, CONST_STRING_TRUE);
|
||||
response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HEADER_RESPONSE_WITH_MAGIC_API);
|
||||
}
|
||||
if (requestEntity.getApiInfo() == null) {
|
||||
logger.error("{}找不到对应接口", request.getRequestURI());
|
||||
return buildResult(requestEntity, API_NOT_FOUND, "接口不存在");
|
||||
}
|
||||
// 验证
|
||||
Object value = doValidate(requestEntity, "参数", requestEntity.getApiInfo().getParameters(), parameters);
|
||||
if (value != null) {
|
||||
return requestEntity.isRequestedFromTest() ? new JsonBean<>(PARAMETER_INVALID, value) : value;
|
||||
}
|
||||
Map<String, Object> headers = new HashMap<String, Object>() {
|
||||
@Override
|
||||
public Object get(Object key) {
|
||||
return request.getHeader(key.toString());
|
||||
}
|
||||
};
|
||||
requestEntity.setHeaders(headers);
|
||||
// 验证 header
|
||||
value = doValidate(requestEntity, "header", requestEntity.getApiInfo().getHeaders(), headers);
|
||||
if (value != null) {
|
||||
return requestEntity.isRequestedFromTest() ? new JsonBean<>(HEADER_INVALID, value) : value;
|
||||
}
|
||||
List<Path> paths = new ArrayList<>(requestEntity.getApiInfo().getPaths());
|
||||
MappingHandlerMapping.findGroups(requestEntity.getApiInfo().getGroupId())
|
||||
.stream()
|
||||
.flatMap(it -> it.getPaths().stream())
|
||||
.filter(it -> !paths.contains(it))
|
||||
.forEach(paths::add);
|
||||
// 验证 path
|
||||
value = doValidate(requestEntity, "path", paths, requestEntity.getPathVariables());
|
||||
if (value != null) {
|
||||
return requestEntity.isRequestedFromTest() ? new JsonBean<>(PATH_VARIABLE_INVALID, value) : value;
|
||||
}
|
||||
MagicScriptContext context = createMagicScriptContext(requestEntity);
|
||||
requestEntity.setMagicScriptContext(context);
|
||||
RequestContext.setRequestEntity(requestEntity);
|
||||
// 执行前置拦截器
|
||||
if ((value = doPreHandle(requestEntity)) != null) {
|
||||
if (requestEntity.isRequestedFromTest()) {
|
||||
// 修正前端显示,当拦截器返回时,原样输出显示
|
||||
response.setHeader(HEADER_RESPONSE_WITH_MAGIC_API, CONST_STRING_FALSE);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
if (requestEntity.isRequestedFromTest()) {
|
||||
return isRequestedFromContinue(request) ? invokeContinueRequest(requestEntity) : invokeTestRequest(requestEntity);
|
||||
}
|
||||
return invokeRequest(requestEntity);
|
||||
}
|
||||
|
||||
private Object buildResult(RequestEntity requestEntity, JsonCode code, Object data) {
|
||||
return resultProvider.buildResult(requestEntity, code.getCode(), code.getMessage(), data);
|
||||
}
|
||||
|
||||
private <T extends BaseDefinition> Object doValidate(RequestEntity requestEntity, String comment, List<T> validateParameters, Map<String, Object> parameters) {
|
||||
for (BaseDefinition parameter : validateParameters) {
|
||||
if (StringUtils.isNotBlank(parameter.getName())) {
|
||||
String requestValue = StringUtils.defaultIfBlank(Objects.toString(parameters.get(parameter.getName()), EMPTY), Objects.toString(parameter.getDefaultValue(), EMPTY));
|
||||
if (StringUtils.isBlank(requestValue)) {
|
||||
if (!parameter.isRequired()) {
|
||||
continue;
|
||||
}
|
||||
return resultProvider.buildResult(requestEntity, RESPONSE_CODE_INVALID, StringUtils.defaultIfBlank(parameter.getError(), String.format("%s[%s]为必填项", comment, parameter.getName())));
|
||||
}
|
||||
try {
|
||||
Object value = convertValue(parameter.getDataType(), parameter.getName(), requestValue);
|
||||
if (VALIDATE_TYPE_PATTERN.equals(parameter.getValidateType())) { // 正则验证
|
||||
String expression = parameter.getExpression();
|
||||
if (StringUtils.isNotBlank(expression) && !PatternUtils.match(Objects.toString(value, EMPTY), expression)) {
|
||||
return resultProvider.buildResult(requestEntity, RESPONSE_CODE_INVALID, StringUtils.defaultIfBlank(parameter.getError(), String.format("%s[%s]不满足正则表达式", comment, parameter.getName())));
|
||||
}
|
||||
}
|
||||
parameters.put(parameter.getName(), value);
|
||||
} catch (Exception e) {
|
||||
return resultProvider.buildResult(requestEntity, RESPONSE_CODE_INVALID, StringUtils.defaultIfBlank(parameter.getError(), String.format("%s[%s]不合法", comment, parameter.getName())));
|
||||
}
|
||||
}
|
||||
}
|
||||
// 取出表达式验证的参数
|
||||
List<BaseDefinition> validates = validateParameters.stream().filter(it -> VALIDATE_TYPE_EXPRESSION.equals(it.getValidateType()) && StringUtils.isNotBlank(it.getExpression())).collect(Collectors.toList());
|
||||
for (BaseDefinition parameter : validates) {
|
||||
MagicScriptContext context = new MagicScriptContext();
|
||||
// 将其他参数也放置脚本中,以实现“依赖”的情况
|
||||
context.putMapIntoContext(parameters);
|
||||
// 设置自身变量
|
||||
context.set(EXPRESSION_DEFAULT_VAR_NAME, parameters.get(parameter.getName()));
|
||||
if (!BooleanLiteral.isTrue(ScriptManager.executeExpression(parameter.getExpression(), context))) {
|
||||
return resultProvider.buildResult(requestEntity, RESPONSE_CODE_INVALID, StringUtils.defaultIfBlank(parameter.getError(), String.format("%s[%s]不满足表达式", comment, parameter.getName())));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换参数类型
|
||||
*/
|
||||
private Object convertValue(DataType dataType, String name, String value) {
|
||||
if (dataType == null) {
|
||||
return value;
|
||||
}
|
||||
try {
|
||||
if (dataType.isNumber()) {
|
||||
BigDecimal decimal = ObjectConvertExtension.asDecimal(value, null);
|
||||
if (decimal == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
return dataType.getInvoker().invoke0(decimal, null);
|
||||
} else {
|
||||
JavaInvoker<Method> invoker = dataType.getInvoker();
|
||||
if (invoker != null) {
|
||||
List<Object> params = new ArrayList<>();
|
||||
if (dataType.isNeedName()) {
|
||||
params.add(name);
|
||||
}
|
||||
if (dataType.isNeedValue()) {
|
||||
params.add(value);
|
||||
}
|
||||
return invoker.invoke0(null, null, params.toArray());
|
||||
}
|
||||
}
|
||||
return value;
|
||||
} catch (Throwable throwable) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
private Object invokeContinueRequest(RequestEntity requestEntity) throws Exception {
|
||||
HttpServletRequest request = requestEntity.getRequest();
|
||||
String sessionId = getRequestedSessionId(request);
|
||||
MagicScriptDebugContext context = MagicScriptDebugContext.getDebugContext(sessionId);
|
||||
if (context == null) {
|
||||
return new JsonBean<>(DEBUG_SESSION_NOT_FOUND, buildResult(requestEntity, DEBUG_SESSION_NOT_FOUND, null));
|
||||
}
|
||||
// 重置断点
|
||||
context.setBreakpoints(getRequestedBreakpoints(request));
|
||||
// 步进
|
||||
context.setStepInto(CONST_STRING_TRUE.equalsIgnoreCase(request.getHeader(HEADER_REQUEST_STEP_INTO)));
|
||||
try {
|
||||
context.singal(); //等待语句执行到断点或执行完毕
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (context.isRunning()) { //判断是否执行完毕
|
||||
return new JsonBodyBean<>(1000, context.getId(), resultProvider.buildResult(requestEntity, 1000, context.getId()), context.getDebugInfo());
|
||||
} else if (context.isException()) {
|
||||
return resolveThrowable(requestEntity, (Throwable) context.getReturnValue());
|
||||
}
|
||||
Object value = context.getReturnValue();
|
||||
// 执行后置拦截器
|
||||
if ((value = doPostHandle(requestEntity, value)) != null) {
|
||||
// 修正前端显示,当拦截器返回时,原样输出显示
|
||||
requestEntity.getResponse().setHeader(HEADER_RESPONSE_WITH_MAGIC_API, CONST_STRING_FALSE);
|
||||
// 后置拦截器不包裹
|
||||
return value;
|
||||
}
|
||||
return convertResult(requestEntity, context.getReturnValue());
|
||||
}
|
||||
|
||||
private Object invokeTestRequest(RequestEntity requestEntity) {
|
||||
try {
|
||||
// 初始化debug操作
|
||||
MagicScriptDebugContext context = initializeDebug(requestEntity);
|
||||
Object result = ScriptManager.executeScript(requestEntity.getApiInfo().getScript(), requestEntity.getMagicScriptContext());
|
||||
if (context.isRunning()) {
|
||||
return new JsonBodyBean<>(1000, context.getId(), resultProvider.buildResult(requestEntity, 1000, context.getId(), result), result);
|
||||
} else if (context.isException()) { //判断是否出现异常
|
||||
return resolveThrowable(requestEntity, (Throwable) context.getReturnValue());
|
||||
}
|
||||
Object value = result;
|
||||
// 执行后置拦截器
|
||||
if ((value = doPostHandle(requestEntity, value)) != null) {
|
||||
// 修正前端显示,当拦截器返回时,原样输出显示
|
||||
requestEntity.getResponse().setHeader(HEADER_RESPONSE_WITH_MAGIC_API, CONST_STRING_FALSE);
|
||||
// 后置拦截器不包裹
|
||||
return value;
|
||||
}
|
||||
return convertResult(requestEntity, result);
|
||||
} catch (Exception e) {
|
||||
return resolveThrowable(requestEntity, e);
|
||||
}
|
||||
}
|
||||
|
||||
private Object invokeRequest(RequestEntity requestEntity) throws Throwable {
|
||||
HttpServletRequest request = requestEntity.getRequest();
|
||||
try {
|
||||
Object result = ScriptManager.executeScript(requestEntity.getApiInfo().getScript(), requestEntity.getMagicScriptContext());
|
||||
Object value = result;
|
||||
// 执行后置拦截器
|
||||
if ((value = doPostHandle(requestEntity, value)) != null) {
|
||||
return value;
|
||||
}
|
||||
// 对返回结果包装处理
|
||||
return response(requestEntity, result);
|
||||
} catch (Throwable root) {
|
||||
Throwable parent = root;
|
||||
do {
|
||||
if (parent instanceof MagicScriptAssertException) {
|
||||
MagicScriptAssertException sae = (MagicScriptAssertException) parent;
|
||||
return resultProvider.buildResult(requestEntity, sae.getCode(), sae.getMessage());
|
||||
}
|
||||
} while ((parent = parent.getCause()) != null);
|
||||
if (configuration.isThrowException()) {
|
||||
throw root;
|
||||
}
|
||||
logger.error("接口{}请求出错", request.getRequestURI(), root);
|
||||
return resultProvider.buildResult(requestEntity, RESPONSE_CODE_EXCEPTION, "系统内部出现错误");
|
||||
} finally {
|
||||
RequestContext.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换请求结果
|
||||
*/
|
||||
private Object convertResult(RequestEntity requestEntity, Object result) throws IOException {
|
||||
if (result instanceof ResponseEntity) {
|
||||
ResponseEntity<?> entity = (ResponseEntity<?>) result;
|
||||
List<String> headers = new ArrayList<>();
|
||||
for (Map.Entry<String, List<String>> entry : entity.getHeaders().entrySet()) {
|
||||
String key = entry.getKey();
|
||||
for (String value : entry.getValue()) {
|
||||
headers.add(HEADER_PREFIX_FOR_TEST + key);
|
||||
requestEntity.getResponse().addHeader(HEADER_PREFIX_FOR_TEST + key, value);
|
||||
}
|
||||
}
|
||||
headers.add(HEADER_RESPONSE_WITH_MAGIC_API);
|
||||
// 允许前端读取自定义的header(跨域情况)。
|
||||
requestEntity.getResponse().setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, String.join(",", headers));
|
||||
if (entity.getHeaders().isEmpty()) {
|
||||
return ResponseEntity.ok(new JsonBean<>(entity.getBody()));
|
||||
}
|
||||
return ResponseEntity.ok(new JsonBean<>(convertToBase64(entity.getBody())));
|
||||
} else if (result instanceof ResponseModule.NullValue) {
|
||||
// 对于return response.end() 的特殊处理
|
||||
return new JsonBean<>(RESPONSE_CODE_SUCCESS, "empty.");
|
||||
}
|
||||
return new JsonBean<>(resultProvider.buildResult(requestEntity, result));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将结果转为base64
|
||||
*/
|
||||
private String convertToBase64(Object value) throws IOException {
|
||||
if (value instanceof String || value instanceof Number) {
|
||||
return convertToBase64(value.toString().getBytes());
|
||||
} else if (value instanceof byte[]) {
|
||||
return Base64.getEncoder().encodeToString((byte[]) value);
|
||||
} else if (value instanceof InputStream) {
|
||||
return convertToBase64(IOUtils.toByteArray((InputStream) value));
|
||||
} else if (value instanceof InputStreamSource) {
|
||||
InputStreamSource iss = (InputStreamSource) value;
|
||||
return convertToBase64(iss.getInputStream());
|
||||
} else {
|
||||
return convertToBase64(new ObjectMapper().writeValueAsString(value));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解决异常
|
||||
*/
|
||||
private JsonBean<Object> resolveThrowable(RequestEntity requestEntity, Throwable root) {
|
||||
MagicScriptException se = null;
|
||||
Throwable parent = root;
|
||||
do {
|
||||
if (parent instanceof MagicScriptAssertException) {
|
||||
MagicScriptAssertException sae = (MagicScriptAssertException) parent;
|
||||
return new JsonBean<>(resultProvider.buildResult(requestEntity, sae.getCode(), sae.getMessage()));
|
||||
}
|
||||
if (parent instanceof MagicScriptException) {
|
||||
se = (MagicScriptException) parent;
|
||||
}
|
||||
} while ((parent = parent.getCause()) != null);
|
||||
logger.error("测试脚本出错", root);
|
||||
if (se != null) {
|
||||
Span.Line line = se.getLine();
|
||||
return new JsonBodyBean<>(-1000, se.getSimpleMessage(), resultProvider.buildResult(requestEntity, -1000, se.getSimpleMessage()), line == null ? null : Arrays.asList(line.getLineNumber(), line.getEndLineNumber(), line.getStartCol(), line.getEndCol()));
|
||||
}
|
||||
return new JsonBean<>(-1, root.getMessage(), resultProvider.buildResult(requestEntity, RESPONSE_CODE_EXCEPTION, root.getMessage()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化DEBUG
|
||||
*/
|
||||
private MagicScriptDebugContext initializeDebug(RequestEntity requestEntity) {
|
||||
MagicScriptDebugContext context = (MagicScriptDebugContext) requestEntity.getMagicScriptContext();
|
||||
HttpServletRequest request = requestEntity.getRequest();
|
||||
// 由于debug是开启一个新线程,为了防止在子线程中无法获取request对象,所以将request放在InheritableThreadLocal中。
|
||||
RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true);
|
||||
|
||||
String sessionId = getRequestedSessionId(request);
|
||||
// 设置断点
|
||||
context.setBreakpoints(getRequestedBreakpoints(request));
|
||||
context.setTimeout(configuration.getDebugTimeout());
|
||||
context.setId(sessionId);
|
||||
// 设置相关回调,打印日志,回收资源
|
||||
context.onComplete(() -> {
|
||||
if (context.isException()) {
|
||||
MagicLoggerContext.println(new LogInfo(Level.ERROR.name().toLowerCase(), "执行脚本出错", (Throwable) context.getReturnValue()));
|
||||
}
|
||||
logger.info("Close Console Session : {}", sessionId);
|
||||
RequestContext.remove();
|
||||
MagicLoggerContext.remove(sessionId);
|
||||
});
|
||||
context.onStart(() -> {
|
||||
RequestContext.setRequestEntity(requestEntity);
|
||||
MagicLoggerContext.SESSION.set(sessionId);
|
||||
logger.info("Create Console Session : {}", sessionId);
|
||||
});
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是测试请求
|
||||
*/
|
||||
private boolean isRequestedFromTest(HttpServletRequest request) {
|
||||
return configuration.isEnableWeb() && request.getHeader(HEADER_REQUEST_SESSION) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是恢复断点
|
||||
*/
|
||||
private boolean isRequestedFromContinue(HttpServletRequest request) {
|
||||
return request.getHeader(HEADER_REQUEST_CONTINUE) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取测试sessionId
|
||||
*/
|
||||
private String getRequestedSessionId(HttpServletRequest request) {
|
||||
return request.getHeader(HEADER_REQUEST_SESSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得断点
|
||||
*/
|
||||
private List<Integer> getRequestedBreakpoints(HttpServletRequest request) {
|
||||
String breakpoints = request.getHeader(HEADER_REQUEST_BREAKPOINTS);
|
||||
if (breakpoints != null) {
|
||||
return Arrays.stream(breakpoints.split(","))
|
||||
.map(val -> ObjectConvertExtension.asInt(val, -1))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取RequestBody
|
||||
*/
|
||||
private Object readRequestBody(HttpServletRequest request) throws IOException {
|
||||
if (configuration.getHttpMessageConverters() != null && request.getContentType() != null) {
|
||||
MediaType mediaType = MediaType.valueOf(request.getContentType());
|
||||
Class clazz = Object.class;
|
||||
try {
|
||||
for (HttpMessageConverter<?> converter : configuration.getHttpMessageConverters()) {
|
||||
if (converter.canRead(clazz, mediaType)) {
|
||||
return converter.read(clazz, new ServletServerHttpRequest(request));
|
||||
}
|
||||
}
|
||||
} catch (HttpMessageNotReadableException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 MagicScriptContext
|
||||
*/
|
||||
private MagicScriptContext createMagicScriptContext(RequestEntity requestEntity) throws IOException {
|
||||
// 构建脚本上下文
|
||||
MagicScriptContext context = requestEntity.isRequestedFromTest() ? new MagicScriptDebugContext() : new MagicScriptContext();
|
||||
Object wrap = requestEntity.getApiInfo().getOptionValue(Options.WRAP_REQUEST_PARAMETERS.getValue());
|
||||
if (wrap != null && StringUtils.isNotBlank(wrap.toString())) {
|
||||
context.set(wrap.toString(), requestEntity.getParameters());
|
||||
}
|
||||
context.putMapIntoContext(requestEntity.getParameters());
|
||||
context.putMapIntoContext(requestEntity.getPathVariables());
|
||||
context.set(VAR_NAME_COOKIE, new CookieContext(requestEntity.getRequest()));
|
||||
context.set(VAR_NAME_HEADER, requestEntity.getHeaders());
|
||||
context.set(VAR_NAME_SESSION, new SessionContext(requestEntity.getRequest().getSession()));
|
||||
context.set(VAR_NAME_PATH_VARIABLE, requestEntity.getPathVariables());
|
||||
Object requestBody = readRequestBody(requestEntity.getRequest());
|
||||
if (requestBody != null) {
|
||||
context.set(VAR_NAME_REQUEST_BODY, requestBody);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* 包装返回结果
|
||||
*/
|
||||
private Object response(RequestEntity requestEntity, Object value) {
|
||||
if (value instanceof ResponseEntity) {
|
||||
return value;
|
||||
} else if (value instanceof ResponseModule.NullValue) {
|
||||
return null;
|
||||
}
|
||||
return resultProvider.buildResult(requestEntity, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行后置拦截器
|
||||
*/
|
||||
private Object doPostHandle(RequestEntity requestEntity, Object value) throws Exception {
|
||||
for (RequestInterceptor requestInterceptor : configuration.getRequestInterceptors()) {
|
||||
Object target = requestInterceptor.postHandle(requestEntity, value);
|
||||
if (target != null) {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行前置拦截器
|
||||
*/
|
||||
private Object doPreHandle(RequestEntity requestEntity) throws Exception {
|
||||
for (RequestInterceptor requestInterceptor : configuration.getRequestInterceptors()) {
|
||||
Object value = requestInterceptor.preHandle(requestEntity);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.ssssssss.magicapi.dialect;
|
||||
|
||||
public class ClickhouseDialect extends MySQLDialect {
|
||||
|
||||
@Override
|
||||
public boolean match(String jdbcUrl) {
|
||||
return jdbcUrl.contains(":clickhouse:");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.ssssssss.magicapi.dialect;
|
||||
|
||||
|
||||
import org.ssssssss.magicapi.modules.BoundSql;
|
||||
|
||||
public class DB2Dialect implements Dialect {
|
||||
@Override
|
||||
public boolean match(String jdbcUrl) {
|
||||
return jdbcUrl.contains(":db2:");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageSql(String sql, BoundSql boundSql, long offset, long limit) {
|
||||
boundSql.addParameter(offset + 1);
|
||||
boundSql.addParameter(offset + limit);
|
||||
return "SELECT * FROM (SELECT TMP_PAGE.*,ROWNUMBER() OVER() AS ROW_ID FROM ( " + sql +
|
||||
" ) AS TMP_PAGE) TMP_PAGE WHERE ROW_ID BETWEEN ? AND ?";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.ssssssss.magicapi.dialect;
|
||||
|
||||
|
||||
import org.ssssssss.magicapi.modules.BoundSql;
|
||||
|
||||
public interface Dialect {
|
||||
|
||||
/**
|
||||
* 根据jdbcUrl匹配
|
||||
*/
|
||||
boolean match(String jdbcUrl);
|
||||
|
||||
/**
|
||||
* 获取查总数的sql
|
||||
*/
|
||||
default String getCountSql(String sql) {
|
||||
return "select count(1) from (" + sql + ") count_";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分页sql
|
||||
*/
|
||||
String getPageSql(String sql, BoundSql boundSql, long offset, long limit);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.ssssssss.magicapi.dialect;
|
||||
|
||||
import org.ssssssss.magicapi.modules.BoundSql;
|
||||
|
||||
/**
|
||||
* @description: 达梦数据库方言
|
||||
* @author: qijiantuoluowang
|
||||
* @create: 2020-12-09 19:33
|
||||
**/
|
||||
public class DmDialect implements Dialect {
|
||||
|
||||
@Override
|
||||
public boolean match(String jdbcUrl) {
|
||||
return jdbcUrl.contains(":dm:");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageSql(String sql, BoundSql boundSql, long offset, long limit) {
|
||||
limit = (offset >= 1) ? (offset + limit) : limit;
|
||||
boundSql.addParameter(limit);
|
||||
boundSql.addParameter(offset);
|
||||
return "SELECT * FROM ( SELECT TMP.*, ROWNUM ROW_ID FROM ( " +
|
||||
sql + " ) TMP WHERE ROWNUM <= ? ) WHERE ROW_ID > ?";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.ssssssss.magicapi.dialect;
|
||||
|
||||
|
||||
import org.ssssssss.magicapi.modules.BoundSql;
|
||||
|
||||
public class MySQLDialect implements Dialect {
|
||||
|
||||
@Override
|
||||
public boolean match(String jdbcUrl) {
|
||||
return jdbcUrl.contains(":mysql:") || jdbcUrl.contains(":mariadb:") || jdbcUrl.contains(":cobar:");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageSql(String sql, BoundSql boundSql, long offset, long limit) {
|
||||
boundSql.addParameter(offset);
|
||||
boundSql.addParameter(limit);
|
||||
return sql + " limit ?,?";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.ssssssss.magicapi.dialect;
|
||||
|
||||
|
||||
import org.ssssssss.magicapi.modules.BoundSql;
|
||||
|
||||
public class OracleDialect implements Dialect {
|
||||
|
||||
@Override
|
||||
public boolean match(String jdbcUrl) {
|
||||
return jdbcUrl.contains(":oracle:");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageSql(String sql, BoundSql boundSql, long offset, long limit) {
|
||||
limit = (offset >= 1) ? (offset + limit) : limit;
|
||||
boundSql.addParameter(limit);
|
||||
boundSql.addParameter(offset);
|
||||
return "SELECT * FROM ( SELECT TMP.*, ROWNUM ROW_ID FROM ( " +
|
||||
sql + " ) TMP WHERE ROWNUM <= ? ) WHERE ROW_ID > ?";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.ssssssss.magicapi.dialect;
|
||||
|
||||
|
||||
import org.ssssssss.magicapi.modules.BoundSql;
|
||||
|
||||
public class PostgreSQLDialect implements Dialect {
|
||||
@Override
|
||||
public boolean match(String jdbcUrl) {
|
||||
return jdbcUrl.contains(":postgresql:");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageSql(String sql, BoundSql boundSql, long offset, long limit) {
|
||||
boundSql.addParameter(limit);
|
||||
boundSql.addParameter(offset);
|
||||
return sql + " limit ? offset ?";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package org.ssssssss.magicapi.dialect;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.ssssssss.magicapi.modules.BoundSql;
|
||||
|
||||
public class SQLServer2005Dialect implements Dialect {
|
||||
@Override
|
||||
public boolean match(String jdbcUrl) {
|
||||
return jdbcUrl.contains(":sqlserver:");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageSql(String sql, BoundSql boundSql, long offset, long limit) {
|
||||
StringBuilder pagingBuilder = new StringBuilder();
|
||||
String orderby = getOrderByPart(sql);
|
||||
String distinctStr = "";
|
||||
|
||||
String loweredString = sql.toLowerCase();
|
||||
String sqlPartString = sql;
|
||||
if (loweredString.trim().startsWith("select")) {
|
||||
int index = 6;
|
||||
if (loweredString.startsWith("select distinct")) {
|
||||
distinctStr = "DISTINCT ";
|
||||
index = 15;
|
||||
}
|
||||
sqlPartString = sqlPartString.substring(index);
|
||||
}
|
||||
pagingBuilder.append(sqlPartString);
|
||||
|
||||
// if no ORDER BY is specified use fake ORDER BY field to avoid errors
|
||||
if (StringUtils.isEmpty(orderby)) {
|
||||
orderby = "ORDER BY CURRENT_TIMESTAMP";
|
||||
}
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append("WITH query AS (SELECT ")
|
||||
.append(distinctStr)
|
||||
.append("TOP 100 PERCENT ")
|
||||
.append(" ROW_NUMBER() OVER (")
|
||||
.append(orderby)
|
||||
.append(") as __row_number__, ")
|
||||
.append(pagingBuilder)
|
||||
.append(") SELECT * FROM query WHERE __row_number__ BETWEEN ? AND ?")
|
||||
.append(" ORDER BY __row_number__");
|
||||
boundSql.addParameter(offset + 1);
|
||||
boundSql.addParameter(offset + limit);
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private String getOrderByPart(String sql) {
|
||||
String loweredString = sql.toLowerCase();
|
||||
int orderByIndex = loweredString.indexOf("order by");
|
||||
if (orderByIndex != -1) {
|
||||
// if we find a new "order by" then we need to ignore
|
||||
// the previous one since it was probably used for a subquery
|
||||
return sql.substring(orderByIndex);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.ssssssss.magicapi.dialect;
|
||||
|
||||
|
||||
import org.ssssssss.magicapi.modules.BoundSql;
|
||||
|
||||
public class SQLServerDialect implements Dialect {
|
||||
@Override
|
||||
public boolean match(String jdbcUrl) {
|
||||
return jdbcUrl.contains(":sqlserver2012:");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageSql(String sql, BoundSql boundSql, long offset, long limit) {
|
||||
boundSql.addParameter(offset);
|
||||
boundSql.addParameter(limit);
|
||||
return sql + " OFFSET ? ROWS FETCH NEXT ? ROWS ONLY";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.ssssssss.magicapi.exception;
|
||||
|
||||
import org.ssssssss.magicapi.model.JsonCode;
|
||||
|
||||
public class InvalidArgumentException extends RuntimeException {
|
||||
|
||||
private final JsonCode jsonCode;
|
||||
|
||||
public InvalidArgumentException(JsonCode jsonCode) {
|
||||
super(jsonCode.getMessage());
|
||||
this.jsonCode = jsonCode;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return jsonCode.getCode();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.ssssssss.magicapi.exception;
|
||||
|
||||
public class MagicAPIException extends RuntimeException {
|
||||
|
||||
public MagicAPIException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public MagicAPIException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.ssssssss.magicapi.exception;
|
||||
|
||||
public class MagicLoginException extends Exception {
|
||||
|
||||
public MagicLoginException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.ssssssss.magicapi.exception;
|
||||
|
||||
public class MagicServiceException extends RuntimeException {
|
||||
|
||||
public MagicServiceException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package org.ssssssss.magicapi.interceptor;
|
||||
|
||||
public enum Authorization {
|
||||
NONE, SAVE, VIEW, DELETE, DOWNLOAD, UPLOAD, DATASOURCE_SAVE, DATASOURCE_VIEW, DATASOURCE_DELETE
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.ssssssss.magicapi.interceptor;
|
||||
|
||||
import org.ssssssss.magicapi.exception.MagicLoginException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
public interface AuthorizationInterceptor {
|
||||
|
||||
|
||||
/**
|
||||
* 是否需要登录
|
||||
*/
|
||||
default boolean requireLogin() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据Token获取User对象
|
||||
*/
|
||||
default MagicUser getUserByToken(String token) throws MagicLoginException {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户名,密码登录
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param password 密码
|
||||
*/
|
||||
default MagicUser login(String username, String password) throws MagicLoginException {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否拥有页面按钮的权限
|
||||
*/
|
||||
default boolean allowVisit(MagicUser magicUser, HttpServletRequest request, Authorization authorization) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.ssssssss.magicapi.interceptor;
|
||||
|
||||
import org.ssssssss.magicapi.exception.MagicLoginException;
|
||||
import org.ssssssss.magicapi.utils.MD5Utils;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class DefaultAuthorizationInterceptor implements AuthorizationInterceptor {
|
||||
|
||||
private final boolean requireLogin;
|
||||
private String validToken;
|
||||
private MagicUser configMagicUser;
|
||||
|
||||
public DefaultAuthorizationInterceptor(String username, String password) {
|
||||
if (this.requireLogin = username != null && password != null) {
|
||||
this.validToken = MD5Utils.encrypt(String.format("%s||%s", username, password));
|
||||
this.configMagicUser = new MagicUser(username, username, this.validToken);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requireLogin() {
|
||||
return this.requireLogin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MagicUser getUserByToken(String token) throws MagicLoginException {
|
||||
if (requireLogin && Objects.equals(validToken, token)) {
|
||||
return configMagicUser;
|
||||
}
|
||||
throw new MagicLoginException("token无效");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MagicUser login(String username, String password) throws MagicLoginException {
|
||||
if (requireLogin && Objects.equals(MD5Utils.encrypt(String.format("%s||%s", username, password)), this.validToken)) {
|
||||
return configMagicUser;
|
||||
}
|
||||
throw new MagicLoginException("用户名或密码不正确");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.ssssssss.magicapi.interceptor;
|
||||
|
||||
public class MagicUser {
|
||||
|
||||
private String id;
|
||||
|
||||
private String username;
|
||||
|
||||
private String token;
|
||||
|
||||
public MagicUser(String id, String username, String token) {
|
||||
this.id = id;
|
||||
this.username = username;
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.ssssssss.magicapi.interceptor;
|
||||
|
||||
import org.ssssssss.magicapi.model.ApiInfo;
|
||||
import org.ssssssss.magicapi.model.RequestEntity;
|
||||
import org.ssssssss.script.MagicScriptContext;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 请求拦截器
|
||||
*/
|
||||
public interface RequestInterceptor {
|
||||
|
||||
/**
|
||||
* 请求之前执行
|
||||
*
|
||||
* @return 当返回对象时,直接将此对象返回到页面,返回null时,继续执行后续操作
|
||||
*/
|
||||
default Object preHandle(RequestEntity requestEntity) throws Exception {
|
||||
return preHandle(requestEntity.getApiInfo(), requestEntity.getMagicScriptContext(), requestEntity.getRequest(), requestEntity.getResponse());
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求之前执行
|
||||
*
|
||||
* @return 当返回对象时,直接将此对象返回到页面,返回null时,继续执行后续操作
|
||||
*/
|
||||
default Object preHandle(ApiInfo info, MagicScriptContext context, HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 执行完毕之后执行
|
||||
*
|
||||
* @param value 即将要返回到页面的值
|
||||
* @return 返回到页面的对象, 当返回null时执行后续拦截器,否则直接返回该值,不执行后续拦截器
|
||||
*/
|
||||
default Object postHandle(ApiInfo info, MagicScriptContext context, Object value, HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行完毕之后执行
|
||||
*
|
||||
* @param value 即将要返回到页面的值
|
||||
* @return 返回到页面的对象, 当返回null时执行后续拦截器,否则直接返回该值,不执行后续拦截器
|
||||
*/
|
||||
default Object postHandle(RequestEntity requestEntity, Object value) throws Exception {
|
||||
return postHandle(requestEntity.getApiInfo(), requestEntity.getMagicScriptContext(), value, requestEntity.getRequest(), requestEntity.getResponse());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.ssssssss.magicapi.interceptor;
|
||||
|
||||
import org.ssssssss.magicapi.model.RequestEntity;
|
||||
import org.ssssssss.magicapi.modules.BoundSql;
|
||||
|
||||
/**
|
||||
* SQL 拦截器
|
||||
*/
|
||||
public interface SQLInterceptor {
|
||||
|
||||
/**
|
||||
* 1.1.1 新增
|
||||
*/
|
||||
default void preHandle(BoundSql boundSql, RequestEntity requestEntity) {
|
||||
preHandle(boundSql);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @see SQLInterceptor#preHandle(BoundSql, RequestEntity)
|
||||
*/
|
||||
@Deprecated
|
||||
default void preHandle(BoundSql boundSql) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.ssssssss.magicapi.logging;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.core.Filter;
|
||||
import org.apache.logging.log4j.core.Layout;
|
||||
import org.apache.logging.log4j.core.LogEvent;
|
||||
import org.apache.logging.log4j.core.LoggerContext;
|
||||
import org.apache.logging.log4j.core.appender.AbstractAppender;
|
||||
import org.apache.logging.log4j.core.config.Configuration;
|
||||
import org.apache.logging.log4j.core.config.LoggerConfig;
|
||||
import org.apache.logging.log4j.core.config.Property;
|
||||
import org.apache.logging.log4j.core.impl.ThrowableProxy;
|
||||
import org.apache.logging.log4j.core.layout.PatternLayout;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 对接Log4j2
|
||||
*/
|
||||
public class Log4j2LoggerContext implements MagicLoggerContext {
|
||||
|
||||
@Override
|
||||
public void generateAppender() {
|
||||
LoggerContext context = (LoggerContext) LogManager.getContext(false);
|
||||
Configuration configuration = context.getConfiguration();
|
||||
LoggerConfig logger = configuration.getRootLogger();
|
||||
PatternLayout layout = PatternLayout.newBuilder()
|
||||
.withCharset(StandardCharsets.UTF_8)
|
||||
.withConfiguration(configuration)
|
||||
.withPattern("%d %t %p %X{TracingMsg} %c - %m%n")
|
||||
.build();
|
||||
MagicLog4j2Appender appender = new MagicLog4j2Appender("Magic", logger.getFilter(), layout);
|
||||
appender.start();
|
||||
configuration.addAppender(appender);
|
||||
logger.addAppender(appender, logger.getLevel(), logger.getFilter());
|
||||
context.updateLoggers(configuration);
|
||||
}
|
||||
|
||||
static class MagicLog4j2Appender extends AbstractAppender {
|
||||
|
||||
MagicLog4j2Appender(String name, Filter filter, Layout<? extends Serializable> layout) {
|
||||
super(name, filter, layout, true, Property.EMPTY_ARRAY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void append(LogEvent event) {
|
||||
LogInfo logInfo = new LogInfo();
|
||||
logInfo.setLevel(event.getLevel().name().toLowerCase());
|
||||
logInfo.setMessage(event.getMessage().getFormattedMessage());
|
||||
ThrowableProxy throwableProxy = event.getThrownProxy();
|
||||
if (throwableProxy != null) {
|
||||
logInfo.setThrowable(throwableProxy.getThrowable());
|
||||
}
|
||||
MagicLoggerContext.println(logInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.ssssssss.magicapi.logging;
|
||||
|
||||
import org.apache.log4j.AppenderSkeleton;
|
||||
import org.apache.log4j.LogManager;
|
||||
import org.apache.log4j.PatternLayout;
|
||||
import org.apache.log4j.spi.LoggingEvent;
|
||||
import org.apache.log4j.spi.RootLogger;
|
||||
import org.apache.log4j.spi.ThrowableInformation;
|
||||
|
||||
|
||||
/**
|
||||
* 对接Log4j
|
||||
*/
|
||||
public class Log4jLoggerContext implements MagicLoggerContext {
|
||||
@Override
|
||||
public void generateAppender() {
|
||||
RootLogger logger = (RootLogger) LogManager.getRootLogger();
|
||||
PatternLayout patternLayout = new PatternLayout("%d %p [%c] - %m%n");
|
||||
MagicLog4jAppender magicLog4jAppender = new MagicLog4jAppender();
|
||||
magicLog4jAppender.setLayout(patternLayout);
|
||||
logger.addAppender(magicLog4jAppender);
|
||||
}
|
||||
|
||||
static class MagicLog4jAppender extends AppenderSkeleton {
|
||||
|
||||
|
||||
@Override
|
||||
protected void append(LoggingEvent event) {
|
||||
LogInfo logInfo = new LogInfo();
|
||||
logInfo.setLevel(event.getLevel().toString().toLowerCase());
|
||||
logInfo.setMessage(String.valueOf(event.getMessage()));
|
||||
ThrowableInformation throwableInformation = event.getThrowableInformation();
|
||||
if (throwableInformation != null) {
|
||||
logInfo.setThrowable(throwableInformation.getThrowable());
|
||||
}
|
||||
MagicLoggerContext.println(logInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresLayout() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.ssssssss.magicapi.logging;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
/**
|
||||
* 打印的日志信息
|
||||
*/
|
||||
public class LogInfo {
|
||||
|
||||
private String level;
|
||||
|
||||
private String message;
|
||||
|
||||
private String throwable;
|
||||
|
||||
public LogInfo() {
|
||||
}
|
||||
|
||||
public LogInfo(String level, String message, Throwable throwable) {
|
||||
this.level = level;
|
||||
this.message = message;
|
||||
this.setThrowable(throwable);
|
||||
}
|
||||
|
||||
public String getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
public void setLevel(String level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getThrowable() {
|
||||
return throwable;
|
||||
}
|
||||
|
||||
public void setThrowable(Throwable throwable) {
|
||||
if (throwable != null) {
|
||||
try (StringWriter writer = new StringWriter();
|
||||
PrintWriter printWriter = new PrintWriter(writer)) {
|
||||
throwable.printStackTrace(printWriter);
|
||||
this.throwable = writer.toString();
|
||||
} catch (IOException ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.ssssssss.magicapi.logging;
|
||||
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.classic.spi.ThrowableProxy;
|
||||
import ch.qos.logback.core.UnsynchronizedAppenderBase;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* 对接Logback
|
||||
*/
|
||||
public class LogbackLoggerContext implements MagicLoggerContext {
|
||||
|
||||
@Override
|
||||
public void generateAppender() {
|
||||
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||
ch.qos.logback.classic.Logger logger = context.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||
MagicLogbackAppender appender = new MagicLogbackAppender();
|
||||
appender.setContext(context);
|
||||
appender.setName(LOGGER_NAME);
|
||||
appender.start();
|
||||
logger.addAppender(appender);
|
||||
}
|
||||
|
||||
static class MagicLogbackAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
|
||||
|
||||
@Override
|
||||
protected void append(ILoggingEvent event) {
|
||||
LogInfo logInfo = new LogInfo();
|
||||
logInfo.setLevel(event.getLevel().levelStr.toLowerCase());
|
||||
logInfo.setMessage(event.getFormattedMessage());
|
||||
ThrowableProxy throwableProxy = (ThrowableProxy) event.getThrowableProxy();
|
||||
if (throwableProxy != null) {
|
||||
logInfo.setThrowable(throwableProxy.getThrowable());
|
||||
}
|
||||
MagicLoggerContext.println(logInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.ssssssss.magicapi.logging;
|
||||
|
||||
import org.slf4j.ILoggerFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* 日志管理
|
||||
*/
|
||||
public class LoggerManager {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(LoggerManager.class);
|
||||
|
||||
/**
|
||||
* 创建一个新的appender至项目中,用于UI界面
|
||||
*/
|
||||
public static MagicLoggerContext createMagicAppender() {
|
||||
ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
|
||||
String loggerFactoryClassName = loggerFactory.getClass().getName();
|
||||
MagicLoggerContext magicLoggerContext = null;
|
||||
if ("ch.qos.logback.classic.LoggerContext".equalsIgnoreCase(loggerFactoryClassName)) { //logback
|
||||
magicLoggerContext = new LogbackLoggerContext();
|
||||
} else if ("org.apache.logging.slf4j.Log4jLoggerFactory".equalsIgnoreCase(loggerFactoryClassName)) { //log4j2
|
||||
magicLoggerContext = new Log4j2LoggerContext();
|
||||
} else if ("org.slf4j.impl.Log4jLoggerFactory".equalsIgnoreCase(loggerFactoryClassName)) { //log4j 1
|
||||
magicLoggerContext = new Log4jLoggerContext();
|
||||
}
|
||||
if (magicLoggerContext == null) {
|
||||
logger.error("无法识别LoggerContext:{}", loggerFactoryClassName);
|
||||
} else {
|
||||
magicLoggerContext.generateAppender();
|
||||
}
|
||||
return magicLoggerContext;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package org.ssssssss.magicapi.logging;
|
||||
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
import org.ssssssss.script.MagicScriptContext;
|
||||
import org.ssssssss.script.MagicScriptDebugContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public interface MagicLoggerContext {
|
||||
|
||||
String LOGGER_NAME = "MagicAPI";
|
||||
|
||||
Map<String, SseEmitter> emitterMap = new ConcurrentHashMap<>();
|
||||
|
||||
ThreadLocal<String> SESSION = new InheritableThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 创建sseEmitter推送
|
||||
*
|
||||
* @param sessionId 会话id
|
||||
*/
|
||||
static SseEmitter createEmitter(String sessionId) {
|
||||
SseEmitter sseEmitter = new SseEmitter(0L);
|
||||
emitterMap.put(sessionId, sseEmitter);
|
||||
return sseEmitter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除会话
|
||||
*
|
||||
* @param sessionId 会话id
|
||||
*/
|
||||
static void remove(String sessionId) {
|
||||
SseEmitter sseEmitter = emitterMap.remove(sessionId);
|
||||
SESSION.remove();
|
||||
if (sseEmitter != null) {
|
||||
try {
|
||||
sseEmitter.send(SseEmitter.event().data(sessionId).name("close"));
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 打印日志
|
||||
*
|
||||
* @param logInfo 日志信息
|
||||
*/
|
||||
static void println(LogInfo logInfo) {
|
||||
// 获取SessionId
|
||||
MagicScriptContext context = MagicScriptContext.get();
|
||||
String sessionId;
|
||||
if (context instanceof MagicScriptDebugContext) {
|
||||
sessionId = ((MagicScriptDebugContext) context).getId();
|
||||
} else {
|
||||
sessionId = SESSION.get();
|
||||
}
|
||||
if (sessionId != null) {
|
||||
SseEmitter sseEmitter = emitterMap.get(sessionId);
|
||||
if (sseEmitter != null) {
|
||||
try {
|
||||
// 推送日志事件
|
||||
sseEmitter.send(SseEmitter.event().data(logInfo).name("log"));
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成appender
|
||||
*/
|
||||
void generateAppender();
|
||||
}
|
||||
277
magic-api/src/main/java/org/ssssssss/magicapi/model/ApiInfo.java
Normal file
277
magic-api/src/main/java/org/ssssssss/magicapi/model/ApiInfo.java
Normal file
@@ -0,0 +1,277 @@
|
||||
package org.ssssssss.magicapi.model;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.ssssssss.magicapi.config.MappingHandlerMapping;
|
||||
import org.ssssssss.magicapi.utils.JsonUtils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 接口信息
|
||||
*/
|
||||
public class ApiInfo extends MagicEntity {
|
||||
|
||||
/**
|
||||
* 请求方法
|
||||
*/
|
||||
private String method = "GET";
|
||||
|
||||
/**
|
||||
* 请求路径
|
||||
*/
|
||||
private String path;
|
||||
|
||||
/**
|
||||
* 设置的请求参数
|
||||
*/
|
||||
private List<Parameter> parameters = Collections.emptyList();
|
||||
|
||||
/**
|
||||
* 设置的接口选项
|
||||
*/
|
||||
private String option;
|
||||
|
||||
/**
|
||||
* 请求体
|
||||
*/
|
||||
private String requestBody;
|
||||
|
||||
/**
|
||||
* 请求头
|
||||
*/
|
||||
private List<Header> headers = Collections.emptyList();
|
||||
|
||||
/**
|
||||
* 路径变量
|
||||
*/
|
||||
private List<Path> paths = Collections.emptyList();
|
||||
|
||||
/**
|
||||
* 输出结果
|
||||
*/
|
||||
private String responseBody;
|
||||
|
||||
/**
|
||||
* 接口描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 接口选项json
|
||||
*/
|
||||
private transient JsonNode jsonNode;
|
||||
|
||||
public String getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public void setMethod(String method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public void setParameter(String parameter) {
|
||||
if (parameter != null) {
|
||||
parameter = parameter.trim();
|
||||
if (parameter.startsWith("[")) { // v0.5.0+
|
||||
this.parameters = JsonUtils.readValue(Objects.toString(parameter, "[]"), new TypeReference<List<Parameter>>() {
|
||||
});
|
||||
} else {
|
||||
Map map = JsonUtils.readValue(Objects.toString(parameter, "{}"), Map.class);
|
||||
Object request = map.get("request");
|
||||
if (request instanceof Map) {
|
||||
Map requestMap = (Map) request;
|
||||
Set keys = requestMap.keySet();
|
||||
this.parameters = new ArrayList<>();
|
||||
for (Object key : keys) {
|
||||
this.parameters.add(new Parameter(key.toString(), Objects.toString(requestMap.get(key), "")));
|
||||
}
|
||||
}
|
||||
Object header = map.get("header");
|
||||
if (header instanceof Map) {
|
||||
Map headers = (Map) header;
|
||||
Set keys = headers.keySet();
|
||||
this.headers = new ArrayList<>();
|
||||
for (Object key : keys) {
|
||||
this.headers.add(new Header(key.toString(), Objects.toString(headers.get(key), "")));
|
||||
}
|
||||
}
|
||||
if (map.containsKey("body")) {
|
||||
this.requestBody = Objects.toString(map.get("body"), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getResponseBody() {
|
||||
return responseBody;
|
||||
}
|
||||
|
||||
public void setResponseBody(String responseBody) {
|
||||
this.responseBody = responseBody;
|
||||
}
|
||||
|
||||
public String getRequestBody() {
|
||||
return requestBody;
|
||||
}
|
||||
|
||||
public void setRequestBody(String requestBody) {
|
||||
this.requestBody = requestBody;
|
||||
}
|
||||
|
||||
public List<Path> getPaths() {
|
||||
return paths;
|
||||
}
|
||||
|
||||
public void setPaths(List<Path> paths) {
|
||||
this.paths = paths;
|
||||
}
|
||||
|
||||
public Map<String, String> getOptionMap() {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
if (this.jsonNode == null) {
|
||||
return null;
|
||||
} else if (this.jsonNode.isArray()) {
|
||||
for (JsonNode node : this.jsonNode) {
|
||||
map.put(node.get("name").asText(), node.get("value").asText());
|
||||
}
|
||||
} else {
|
||||
this.jsonNode.fieldNames().forEachRemaining(it -> map.put(it, this.jsonNode.get(it).asText()));
|
||||
}
|
||||
MappingHandlerMapping.findGroups(this.groupId)
|
||||
.stream()
|
||||
.flatMap(it -> it.getOptions().stream())
|
||||
.forEach(option -> {
|
||||
if (!map.containsKey(option.getName())) {
|
||||
map.put(option.getName(), option.getValue());
|
||||
}
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getOption() {
|
||||
return option;
|
||||
}
|
||||
|
||||
public void setOption(String option) {
|
||||
this.option = option;
|
||||
try {
|
||||
this.jsonNode = new ObjectMapper().readTree(option);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public List<Parameter> getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public void setParameters(List<Parameter> parameters) {
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
public void setRequestHeader(String requestHeader) {
|
||||
this.headers = JsonUtils.readValue(Objects.toString(requestHeader, "[]"), new TypeReference<List<Header>>() {
|
||||
});
|
||||
}
|
||||
|
||||
public List<Header> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
public void setHeaders(List<Header> headers) {
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public void setOptionValue(String optionValue) {
|
||||
this.setOption(optionValue);
|
||||
}
|
||||
|
||||
public String getOptionValue(Options options) {
|
||||
return getOptionValue(options.getValue());
|
||||
}
|
||||
|
||||
public String getOptionValue(String key) {
|
||||
if (this.jsonNode == null) {
|
||||
return null;
|
||||
}
|
||||
if (this.jsonNode.isArray()) {
|
||||
for (JsonNode node : this.jsonNode) {
|
||||
if (node.isObject() && Objects.equals(key, node.get("name").asText())) {
|
||||
return node.get("value").asText();
|
||||
}
|
||||
}
|
||||
} else if (this.jsonNode.isObject()) {
|
||||
JsonNode node = this.jsonNode.get(key);
|
||||
if (node != null) {
|
||||
return node.asText();
|
||||
}
|
||||
}
|
||||
return MappingHandlerMapping.findGroups(this.groupId)
|
||||
.stream()
|
||||
.flatMap(it -> it.getOptions().stream())
|
||||
.filter(it -> key.equals(it.getName()))
|
||||
.findFirst()
|
||||
.map(BaseDefinition::getValue)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ApiInfo apiInfo = (ApiInfo) o;
|
||||
return Objects.equals(id, apiInfo.id) &&
|
||||
Objects.equals(method, apiInfo.method) &&
|
||||
Objects.equals(path, apiInfo.path) &&
|
||||
Objects.equals(script, apiInfo.script) &&
|
||||
Objects.equals(name, apiInfo.name) &&
|
||||
Objects.equals(paths, apiInfo.paths) &&
|
||||
Objects.equals(groupId, apiInfo.groupId) &&
|
||||
Objects.equals(parameters, apiInfo.parameters) &&
|
||||
Objects.equals(option, apiInfo.option) &&
|
||||
Objects.equals(requestBody, apiInfo.requestBody) &&
|
||||
Objects.equals(headers, apiInfo.headers) &&
|
||||
Objects.equals(description, apiInfo.description);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, method, path, script, name, groupId, parameters, option, requestBody, headers, responseBody, description);
|
||||
}
|
||||
|
||||
public ApiInfo copy() {
|
||||
ApiInfo info = new ApiInfo();
|
||||
info.setId(this.id);
|
||||
info.setMethod(this.method);
|
||||
info.setName(this.name);
|
||||
info.setPath(this.path);
|
||||
info.setScript(this.script);
|
||||
info.setGroupId(this.groupId);
|
||||
info.setParameters(this.parameters);
|
||||
info.jsonNode = this.jsonNode;
|
||||
info.setRequestBody(this.requestBody);
|
||||
info.setHeaders(this.headers);
|
||||
info.setResponseBody(this.responseBody);
|
||||
info.setDescription(this.description);
|
||||
info.setPaths(this.paths);
|
||||
return info;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package org.ssssssss.magicapi.model;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class BaseDefinition {
|
||||
|
||||
/**
|
||||
* 名
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 值
|
||||
*/
|
||||
private String value;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 是否必填
|
||||
*/
|
||||
private boolean required;
|
||||
|
||||
/**
|
||||
* 数据类型
|
||||
*/
|
||||
private DataType dataType;
|
||||
|
||||
/**
|
||||
* 类型,函数专用
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 默认值
|
||||
*/
|
||||
private String defaultValue;
|
||||
|
||||
/**
|
||||
* 验证类型
|
||||
*/
|
||||
private String validateType;
|
||||
|
||||
/**
|
||||
* 验证说明
|
||||
*/
|
||||
private String error;
|
||||
|
||||
/**
|
||||
* 验证表达式
|
||||
*/
|
||||
private String expression;
|
||||
|
||||
public BaseDefinition() {
|
||||
}
|
||||
|
||||
public BaseDefinition(String name, String value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public boolean isRequired() {
|
||||
return required;
|
||||
}
|
||||
|
||||
public void setRequired(boolean required) {
|
||||
this.required = required;
|
||||
}
|
||||
|
||||
public DataType getDataType() {
|
||||
return dataType == null ? DataType.String : dataType;
|
||||
}
|
||||
|
||||
public void setDataType(DataType dataType) {
|
||||
this.dataType = dataType;
|
||||
}
|
||||
|
||||
public String getDefaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public void setDefaultValue(String defaultValue) {
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
public String getValidateType() {
|
||||
return validateType;
|
||||
}
|
||||
|
||||
public void setValidateType(String validateType) {
|
||||
this.validateType = validateType;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public void setError(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public String getExpression() {
|
||||
return expression;
|
||||
}
|
||||
|
||||
public void setExpression(String expression) {
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof BaseDefinition)) return false;
|
||||
BaseDefinition that = (BaseDefinition) o;
|
||||
return required == that.required && Objects.equals(name, that.name) && Objects.equals(value, that.value) && Objects.equals(description, that.description) && dataType == that.dataType && Objects.equals(defaultValue, that.defaultValue) && Objects.equals(validateType, that.validateType) && Objects.equals(error, that.error) && Objects.equals(expression, that.expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, value, description, required, dataType, defaultValue, validateType, error, expression);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package org.ssssssss.magicapi.model;
|
||||
|
||||
public class Constants {
|
||||
|
||||
/**
|
||||
* true 常量
|
||||
*/
|
||||
public static final String CONST_STRING_TRUE = "true";
|
||||
|
||||
/**
|
||||
* false 常量
|
||||
*/
|
||||
public static final String CONST_STRING_FALSE = "false";
|
||||
|
||||
|
||||
/**
|
||||
* 分组类型: 接口
|
||||
*/
|
||||
public static final String GROUP_TYPE_API = "1";
|
||||
|
||||
/**
|
||||
* 分组类型: 函数
|
||||
*/
|
||||
public static final String GROUP_TYPE_FUNCTION = "2";
|
||||
|
||||
/**
|
||||
* 接口文件夹名
|
||||
*/
|
||||
public static final String PATH_API = "api";
|
||||
|
||||
/**
|
||||
* 函数文件夹名
|
||||
*/
|
||||
public static final String PATH_FUNCTION = "function";
|
||||
|
||||
/**
|
||||
* 数据源文件夹名
|
||||
*/
|
||||
public static final String PATH_DATASOURCE = "datasource";
|
||||
|
||||
/**
|
||||
* 备份文件夹名
|
||||
*/
|
||||
public static final String PATH_BACKUPS = "backups";
|
||||
|
||||
/**
|
||||
* 空值
|
||||
*/
|
||||
public static final String EMPTY = "";
|
||||
|
||||
/**
|
||||
* 表达式验证
|
||||
*/
|
||||
public static final String VALIDATE_TYPE_EXPRESSION = "expression";
|
||||
|
||||
/**
|
||||
* 正则验证
|
||||
*/
|
||||
public static final String VALIDATE_TYPE_PATTERN = "pattern";
|
||||
|
||||
/**
|
||||
* 表达式验证中变量的默认名称
|
||||
*/
|
||||
public static final String EXPRESSION_DEFAULT_VAR_NAME = "value";
|
||||
|
||||
/**
|
||||
* 脚本中session的变量名
|
||||
*/
|
||||
public static final String VAR_NAME_SESSION = "session";
|
||||
|
||||
/**
|
||||
* 脚本中cookie的变量名
|
||||
*/
|
||||
public static final String VAR_NAME_COOKIE = "cookie";
|
||||
|
||||
/**
|
||||
* 脚本中路径变量的变量名
|
||||
*/
|
||||
public static final String VAR_NAME_PATH_VARIABLE = "path";
|
||||
|
||||
/**
|
||||
* 脚本中header的变量名
|
||||
*/
|
||||
public static final String VAR_NAME_HEADER = "header";
|
||||
|
||||
/**
|
||||
* 脚本中RequestBody的变量名
|
||||
*/
|
||||
public static final String VAR_NAME_REQUEST_BODY = "body";
|
||||
|
||||
/**
|
||||
* 脚本中RequestBody的变量名
|
||||
*/
|
||||
public static final String HEADER_PREFIX_FOR_TEST = "MA-";
|
||||
|
||||
public static final String HEADER_REQUEST_SESSION = "Magic-Request-Session";
|
||||
|
||||
public static final String HEADER_REQUEST_BREAKPOINTS = "Magic-Request-Breakpoints";
|
||||
|
||||
public static final String HEADER_REQUEST_CONTINUE = "Magic-Request-Continue";
|
||||
|
||||
public static final String HEADER_REQUEST_STEP_INTO = "Magic-Request-Step-Into";
|
||||
|
||||
public static final String HEADER_RESPONSE_WITH_MAGIC_API = "Response-With-Magic-API";
|
||||
|
||||
public static final String ATTRIBUTE_MAGIC_USER = "MAGIC_API_ATTRIBUTE_USER";
|
||||
|
||||
public static final String MAGIC_TOKEN_HEADER = "Magic-Token";
|
||||
|
||||
/**
|
||||
* 执行成功的code值
|
||||
*/
|
||||
public static int RESPONSE_CODE_SUCCESS = 1;
|
||||
|
||||
/**
|
||||
* 执行出现异常的code值
|
||||
*/
|
||||
public static int RESPONSE_CODE_EXCEPTION = -1;
|
||||
|
||||
/**
|
||||
* 参数验证未通过的code值
|
||||
*/
|
||||
public static int RESPONSE_CODE_INVALID = 0;
|
||||
|
||||
/**
|
||||
* 通知新增
|
||||
*/
|
||||
public static final int NOTIFY_ACTION_ADD = 1;
|
||||
|
||||
/**
|
||||
* 通知修改
|
||||
*/
|
||||
public static final int NOTIFY_ACTION_UPDATE = 2;
|
||||
|
||||
/**
|
||||
* 通知删除
|
||||
*/
|
||||
public static final int NOTIFY_ACTION_DELETE = 3;
|
||||
|
||||
/**
|
||||
* 通知更新全部
|
||||
*/
|
||||
public static final int NOTIFY_ACTION_ALL = 4;
|
||||
|
||||
/**
|
||||
* 通知接口刷新
|
||||
*/
|
||||
public static final int NOTIFY_ACTION_API = 1;
|
||||
|
||||
/**
|
||||
* 通知分组刷新
|
||||
*/
|
||||
public static final int NOTIFY_ACTION_GROUP = 2;
|
||||
|
||||
/**
|
||||
* 通知函数刷新
|
||||
*/
|
||||
public static final int NOTIFY_ACTION_FUNCTION = 3;
|
||||
|
||||
/**
|
||||
* 通知数据源刷新
|
||||
*/
|
||||
public static final int NOTIFY_ACTION_DATASOURCE = 4;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package org.ssssssss.magicapi.model;
|
||||
|
||||
import org.ssssssss.magicapi.modules.RequestModule;
|
||||
import org.ssssssss.script.reflection.JavaInvoker;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static org.ssssssss.script.reflection.JavaReflection.findInvoker;
|
||||
|
||||
public enum DataType {
|
||||
String("string"),
|
||||
Integer(true, findInvoker(BigDecimal.class, "intValue"), "number"),
|
||||
Double(true, findInvoker(BigDecimal.class, "doubleValue"), "number"),
|
||||
Long(true, findInvoker(BigDecimal.class, "longValue"), "number"),
|
||||
Float(true, findInvoker(BigDecimal.class, "floatValue"), "number"),
|
||||
Byte(true, findInvoker(BigDecimal.class, "byteValue"), "number"),
|
||||
Short(true, findInvoker(BigDecimal.class, "shortValue"), "number"),
|
||||
MultipartFile(findInvoker(RequestModule.class, "getFile", new Class<?>[]{String.class}), true, false, "file"),
|
||||
MultipartFiles(findInvoker(RequestModule.class, "getFiles", new Class<?>[]{String.class}), true, false, "file");
|
||||
|
||||
private boolean isNumber;
|
||||
|
||||
private JavaInvoker<Method> invoker;
|
||||
|
||||
private boolean needName;
|
||||
|
||||
private boolean needValue;
|
||||
|
||||
private String javascriptType;
|
||||
|
||||
DataType(boolean isNumber, JavaInvoker<Method> invoker, boolean needName, boolean needValue, String javascriptType) {
|
||||
this.isNumber = isNumber;
|
||||
this.invoker = invoker;
|
||||
this.needName = needName;
|
||||
this.needValue = needValue;
|
||||
this.javascriptType = javascriptType;
|
||||
}
|
||||
|
||||
DataType(JavaInvoker<Method> invoker, boolean needName, boolean needValue, String javascriptType) {
|
||||
this(false, invoker, needName, needValue, javascriptType);
|
||||
}
|
||||
|
||||
DataType(boolean isNumber, JavaInvoker<Method> invoker, String javascriptType) {
|
||||
this(invoker, false, false, javascriptType);
|
||||
this.isNumber = isNumber;
|
||||
}
|
||||
|
||||
DataType(String javascriptType) {
|
||||
this.javascriptType = javascriptType;
|
||||
}
|
||||
|
||||
public boolean isNumber() {
|
||||
return isNumber;
|
||||
}
|
||||
|
||||
public JavaInvoker<Method> getInvoker() {
|
||||
return invoker;
|
||||
}
|
||||
|
||||
public boolean isNeedName() {
|
||||
return needName;
|
||||
}
|
||||
|
||||
public boolean isNeedValue() {
|
||||
return needValue;
|
||||
}
|
||||
|
||||
public java.lang.String getJavascriptType() {
|
||||
return javascriptType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package org.ssssssss.magicapi.model;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import org.ssssssss.magicapi.utils.JsonUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class FunctionInfo extends MagicEntity {
|
||||
|
||||
private String path;
|
||||
|
||||
private String description;
|
||||
|
||||
private String returnType;
|
||||
|
||||
private String mappingPath;
|
||||
|
||||
private List<Parameter> parameters = Collections.emptyList();
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public void setParameter(String parameter) {
|
||||
try {
|
||||
this.parameters = JsonUtils.readValue(Objects.toString(parameter, "[]"), new TypeReference<List<Parameter>>() {
|
||||
});
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public String getMappingPath() {
|
||||
return mappingPath;
|
||||
}
|
||||
|
||||
public void setMappingPath(String mappingPath) {
|
||||
this.mappingPath = mappingPath;
|
||||
}
|
||||
|
||||
public String getReturnType() {
|
||||
return returnType;
|
||||
}
|
||||
|
||||
public void setReturnType(String returnType) {
|
||||
this.returnType = returnType;
|
||||
}
|
||||
|
||||
public List<Parameter> getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public void setParameters(List<Parameter> parameters) {
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
FunctionInfo functionInfo = (FunctionInfo) o;
|
||||
return Objects.equals(id, functionInfo.id) &&
|
||||
Objects.equals(path, functionInfo.path) &&
|
||||
Objects.equals(script, functionInfo.script) &&
|
||||
Objects.equals(name, functionInfo.name) &&
|
||||
Objects.equals(groupId, functionInfo.groupId) &&
|
||||
Objects.equals(description, functionInfo.description) &&
|
||||
Objects.equals(parameters, functionInfo.parameters) &&
|
||||
Objects.equals(returnType, functionInfo.returnType);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, path, script, name, groupId, parameters, description, returnType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package org.ssssssss.magicapi.model;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class Group {
|
||||
|
||||
private String id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String type;
|
||||
|
||||
private String parentId;
|
||||
|
||||
private String path;
|
||||
|
||||
/**
|
||||
* 路径变量
|
||||
*/
|
||||
private List<Path> paths = Collections.emptyList();
|
||||
|
||||
/**
|
||||
* 分组选项
|
||||
*/
|
||||
private List<BaseDefinition> options = Collections.emptyList();
|
||||
|
||||
public Group(String id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Group() {
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getParentId() {
|
||||
return parentId;
|
||||
}
|
||||
|
||||
public void setParentId(String parentId) {
|
||||
this.parentId = parentId;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public List<Path> getPaths() {
|
||||
return paths;
|
||||
}
|
||||
|
||||
public void setPaths(List<Path> paths) {
|
||||
this.paths = paths;
|
||||
}
|
||||
|
||||
public List<BaseDefinition> getOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
public void setOptions(List<BaseDefinition> options) {
|
||||
this.options = options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.ssssssss.magicapi.model;
|
||||
|
||||
public class Header extends BaseDefinition {
|
||||
|
||||
public Header() {
|
||||
}
|
||||
|
||||
public Header(String name, String value) {
|
||||
super(name, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package org.ssssssss.magicapi.model;
|
||||
|
||||
/**
|
||||
* 统一返回值对象
|
||||
*/
|
||||
public class JsonBean<T> {
|
||||
|
||||
/**
|
||||
* 状态码
|
||||
*/
|
||||
private int code = 1;
|
||||
|
||||
/**
|
||||
* 状态说明
|
||||
*/
|
||||
private String message = "success";
|
||||
|
||||
/**
|
||||
* 实际数据
|
||||
*/
|
||||
private T data;
|
||||
|
||||
/**
|
||||
* 服务器时间
|
||||
*/
|
||||
private long timestamp = System.currentTimeMillis();
|
||||
|
||||
private Integer executeTime;
|
||||
|
||||
public JsonBean(int code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public JsonBean(int code, String message, T data, Integer executeTime) {
|
||||
this(code, message, data);
|
||||
this.executeTime = executeTime;
|
||||
}
|
||||
|
||||
public JsonBean(int code, String message, T data) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public JsonBean() {
|
||||
}
|
||||
|
||||
public JsonBean(JsonCode jsonCode) {
|
||||
this(jsonCode, null);
|
||||
}
|
||||
|
||||
public JsonBean(JsonCode jsonCode, T data) {
|
||||
this(jsonCode.getCode(), jsonCode.getMessage(), data);
|
||||
}
|
||||
|
||||
public JsonBean(T data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(T data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public Integer getExecuteTime() {
|
||||
return executeTime;
|
||||
}
|
||||
|
||||
public void setExecuteTime(Integer executeTime) {
|
||||
this.executeTime = executeTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.ssssssss.magicapi.model;
|
||||
|
||||
public class JsonBodyBean<T> extends JsonBean<T> {
|
||||
|
||||
private Object body;
|
||||
|
||||
public JsonBodyBean(int code, String message, T data, Object body) {
|
||||
super(code, message, data);
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public JsonBodyBean(T data, Object body) {
|
||||
super(data);
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public Object getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public void setBody(Object body) {
|
||||
this.body = body;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.ssssssss.magicapi.model;
|
||||
|
||||
public class JsonCode {
|
||||
|
||||
private int code;
|
||||
|
||||
private String message;
|
||||
|
||||
public JsonCode(int code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public JsonCode format(Object... args) {
|
||||
return new JsonCode(this.code, String.format(this.message, args));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package org.ssssssss.magicapi.model;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.ssssssss.magicapi.exception.InvalidArgumentException;
|
||||
|
||||
public interface JsonCodeConstants {
|
||||
|
||||
JsonCode SUCCESS = new JsonCode(1, "success");
|
||||
|
||||
JsonCode IS_READ_ONLY = new JsonCode(-2, "当前为只读模式,无法操作");
|
||||
|
||||
JsonCode PERMISSION_INVALID = new JsonCode(-10, "无权限操作.");
|
||||
|
||||
JsonCode GROUP_NOT_FOUND = new JsonCode(0, "找不到分组信息");
|
||||
|
||||
JsonCode NAME_CONFLICT = new JsonCode(0, "移动后名称会重复,请修改名称后在试。");
|
||||
|
||||
JsonCode REQUEST_PATH_CONFLICT = new JsonCode(0, "该路径已被映射,请换一个请求方法或路径");
|
||||
|
||||
JsonCode FUNCTION_PATH_CONFLICT = new JsonCode(0, "该路径已被映射,请换一个请求方法或路径");
|
||||
|
||||
JsonCode REQUEST_METHOD_REQUIRED = new JsonCode(0, "请求方法不能为空");
|
||||
|
||||
JsonCode REQUEST_PATH_REQUIRED = new JsonCode(0, "请求路径不能为空");
|
||||
|
||||
JsonCode FUNCTION_PATH_REQUIRED = new JsonCode(0, "函数路径不能为空");
|
||||
|
||||
JsonCode SCRIPT_REQUIRED = new JsonCode(0, "脚本内容不能为空");
|
||||
|
||||
JsonCode API_NAME_REQUIRED = new JsonCode(0, "接口名称不能为空");
|
||||
|
||||
JsonCode GROUP_NAME_REQUIRED = new JsonCode(0, "分组名称不能为空");
|
||||
|
||||
JsonCode GROUP_TYPE_REQUIRED = new JsonCode(0, "分组类型不能为空");
|
||||
|
||||
JsonCode FUNCTION_NAME_REQUIRED = new JsonCode(0, "函数名称不能为空");
|
||||
|
||||
JsonCode NAME_INVALID = new JsonCode(0, "名称不能包含特殊字符,只允许中文、数字、字母以及_组合");
|
||||
|
||||
JsonCode DATASOURCE_KEY_INVALID = new JsonCode(0, "数据源Key不能包含特殊字符,只允许中文、数字、字母以及_组合");
|
||||
|
||||
JsonCode API_ALREADY_EXISTS = new JsonCode(0, "接口%s:%s已存在或接口名称重复");
|
||||
|
||||
JsonCode FUNCTION_ALREADY_EXISTS = new JsonCode(0, "函数%s已存在或名称重复");
|
||||
|
||||
JsonCode API_SAVE_FAILURE = new JsonCode(0, "保存失败,请检查接口名称是否重复且不能包含特殊字符。");
|
||||
|
||||
JsonCode FUNCTION_SAVE_FAILURE = new JsonCode(0, "保存失败,请检查函数名称是否重复且不能包含特殊字符。");
|
||||
|
||||
JsonCode GROUP_SAVE_FAILURE = new JsonCode(0, "保存失败,同一组下分组名称不能重复且不能包含特殊字符。");
|
||||
|
||||
JsonCode GROUP_CONFLICT = new JsonCode(-20, "修改分组后,名称或路径会有冲突,请检查!");
|
||||
|
||||
JsonCode PARAMETER_INVALID = new JsonCode(0, "参数验证失败");
|
||||
|
||||
JsonCode HEADER_INVALID = new JsonCode(0, "header验证失败");
|
||||
|
||||
JsonCode PATH_VARIABLE_INVALID = new JsonCode(0, "路径变量验证失败");
|
||||
|
||||
JsonCode FILE_IS_REQUIRED = new JsonCode(0, "请上传文件");
|
||||
|
||||
JsonCode UPLOAD_PATH_CONFLICT = new JsonCode(0, "上传后%s路径会有冲突,请检查");
|
||||
|
||||
JsonCode DEBUG_SESSION_NOT_FOUND = new JsonCode(0, "debug session not found!");
|
||||
|
||||
JsonCode API_NOT_FOUND = new JsonCode(1001, "api not found");
|
||||
|
||||
JsonCode DATASOURCE_KEY_REQUIRED = new JsonCode(0, "数据源Key不能为空");
|
||||
|
||||
JsonCode DATASOURCE_KEY_EXISTS = new JsonCode(0, "数据源%s已存在或名称重复");
|
||||
|
||||
JsonCode DATASOURCE_TYPE_NOT_FOUND = new JsonCode(0, "%s not found");
|
||||
JsonCode DATASOURCE_NOT_FOUND = new JsonCode(0, "找不到对应的数据源");
|
||||
|
||||
JsonCode DATASOURCE_TYPE_NOT_SET = new JsonCode(0, "请设置数据源类型");
|
||||
|
||||
default void notNull(Object value, JsonCode jsonCode) {
|
||||
if (value == null) {
|
||||
throw new InvalidArgumentException(jsonCode);
|
||||
}
|
||||
}
|
||||
|
||||
default void isTrue(boolean value, JsonCode jsonCode) {
|
||||
if (!value) {
|
||||
throw new InvalidArgumentException(jsonCode);
|
||||
}
|
||||
}
|
||||
|
||||
default void notBlank(String value, JsonCode jsonCode) {
|
||||
isTrue(StringUtils.isNotBlank(value), jsonCode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package org.ssssssss.magicapi.model;
|
||||
|
||||
public class MagicEntity implements Cloneable {
|
||||
|
||||
protected String id;
|
||||
|
||||
protected String script;
|
||||
|
||||
protected String groupId;
|
||||
|
||||
protected String name;
|
||||
|
||||
protected Long createTime;
|
||||
|
||||
protected Long updateTime;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getScript() {
|
||||
return script;
|
||||
}
|
||||
|
||||
public void setScript(String script) {
|
||||
this.script = script;
|
||||
}
|
||||
|
||||
public Long getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(Long createTime) {
|
||||
this.createTime = createTime;
|
||||
}
|
||||
|
||||
public Long getUpdateTime() {
|
||||
return updateTime;
|
||||
}
|
||||
|
||||
public void setUpdateTime(Long updateTime) {
|
||||
this.updateTime = updateTime;
|
||||
}
|
||||
|
||||
public String getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public void setGroupId(String groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public MagicEntity clone() {
|
||||
try {
|
||||
return (MagicEntity) super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.ssssssss.magicapi.model;
|
||||
|
||||
public class MagicNotify {
|
||||
|
||||
/**
|
||||
* 消息来源
|
||||
*/
|
||||
private String from;
|
||||
|
||||
/**
|
||||
* 对应的id,如接口id、函数id,分组id、数据源id
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 动作
|
||||
*/
|
||||
private int action = -1;
|
||||
|
||||
/**
|
||||
* 操作对象,如接口、函数、分组、数据源
|
||||
*/
|
||||
private int type = -1;
|
||||
|
||||
public MagicNotify() {
|
||||
}
|
||||
|
||||
public MagicNotify(String from) {
|
||||
this(from, null, Constants.NOTIFY_ACTION_ALL, -1);
|
||||
}
|
||||
|
||||
public MagicNotify(String from, String id, int action, int type) {
|
||||
this.from = from;
|
||||
this.id = id;
|
||||
this.action = action;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getFrom() {
|
||||
return from;
|
||||
}
|
||||
|
||||
public void setFrom(String from) {
|
||||
this.from = from;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setAction(int action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(int type) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.ssssssss.magicapi.model;
|
||||
|
||||
public enum Options {
|
||||
|
||||
WRAP_REQUEST_PARAMETERS("包装请求参数到一个变量中", "wrap_request_parameter"),
|
||||
PERMISSION("允许拥有该权限的访问", "permission"),
|
||||
ROLE("允许拥有该角色的访问", "role"),
|
||||
REQUIRE_LOGIN("该接口需要登录才允许访问", "require_login", "true"),
|
||||
ANONYMOUS("该接口需要不登录也可访问", "anonymous", "true");
|
||||
|
||||
private final String name;
|
||||
private final String value;
|
||||
private final String defaultValue;
|
||||
|
||||
Options(String name, String value) {
|
||||
this(name, value, null);
|
||||
}
|
||||
|
||||
Options(String name, String value, String defaultValue) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String getDefaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.ssssssss.magicapi.model;
|
||||
|
||||
/**
|
||||
* 分页对象
|
||||
*/
|
||||
public class Page {
|
||||
|
||||
private long limit;
|
||||
|
||||
private long offset;
|
||||
|
||||
public Page() {
|
||||
}
|
||||
|
||||
public Page(long limit, long offset) {
|
||||
this.limit = limit;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public long getLimit() {
|
||||
return limit;
|
||||
}
|
||||
|
||||
public void setLimit(long limit) {
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
public long getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void setOffset(long offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.ssssssss.magicapi.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分页执行结果
|
||||
*/
|
||||
public class PageResult<T> {
|
||||
|
||||
/**
|
||||
* 总条数
|
||||
*/
|
||||
private long total;
|
||||
|
||||
/**
|
||||
* 数据项
|
||||
*/
|
||||
private List<T> list;
|
||||
|
||||
public PageResult(long total, List<T> list) {
|
||||
this.total = total;
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
public PageResult() {
|
||||
}
|
||||
|
||||
public long getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public void setTotal(long total) {
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
public List<T> getList() {
|
||||
return list;
|
||||
}
|
||||
|
||||
public void setList(List<T> list) {
|
||||
this.list = list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.ssssssss.magicapi.model;
|
||||
|
||||
public class Parameter extends BaseDefinition {
|
||||
|
||||
public Parameter() {
|
||||
}
|
||||
|
||||
public Parameter(String name, String value) {
|
||||
super(name, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.ssssssss.magicapi.model;
|
||||
|
||||
public class Path extends BaseDefinition {
|
||||
|
||||
public Path() {
|
||||
}
|
||||
|
||||
public Path(String name, String value) {
|
||||
super(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRequired() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package org.ssssssss.magicapi.model;
|
||||
|
||||
import org.ssssssss.magicapi.config.MappingHandlerMapping;
|
||||
import org.ssssssss.script.MagicScriptContext;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Map;
|
||||
|
||||
public class RequestEntity {
|
||||
|
||||
private ApiInfo apiInfo;
|
||||
|
||||
private HttpServletRequest request;
|
||||
|
||||
private HttpServletResponse response;
|
||||
|
||||
private boolean requestedFromTest;
|
||||
|
||||
private Map<String, Object> parameters;
|
||||
|
||||
private Map<String, Object> pathVariables;
|
||||
private Long requestTime = System.currentTimeMillis();
|
||||
private MagicScriptContext magicScriptContext;
|
||||
private Map<String, Object> headers;
|
||||
|
||||
private RequestEntity() {
|
||||
|
||||
}
|
||||
|
||||
public RequestEntity(HttpServletRequest request, HttpServletResponse response, boolean requestedFromTest, Map<String, Object> parameters, Map<String, Object> pathVariables) {
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
this.requestedFromTest = requestedFromTest;
|
||||
this.parameters = parameters;
|
||||
this.pathVariables = pathVariables;
|
||||
ApiInfo info = MappingHandlerMapping.getMappingApiInfo(request);
|
||||
this.apiInfo = info != null ? info.copy() : null;
|
||||
}
|
||||
|
||||
public RequestEntity(ApiInfo apiInfo, HttpServletRequest request, HttpServletResponse response, boolean requestedFromTest, Map<String, Object> parameters, Map<String, Object> pathVariables) {
|
||||
this.apiInfo = apiInfo;
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
this.requestedFromTest = requestedFromTest;
|
||||
this.parameters = parameters;
|
||||
this.pathVariables = pathVariables;
|
||||
}
|
||||
|
||||
public RequestEntity(HttpServletRequest request, HttpServletResponse response, boolean requestedFromTest, Map<String, Object> parameters, Map<String, Object> pathVariables, MagicScriptContext magicScriptContext, Map<String, Object> headers) {
|
||||
ApiInfo info = MappingHandlerMapping.getMappingApiInfo(request);
|
||||
this.apiInfo = info != null ? info.copy() : null;
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
this.requestedFromTest = requestedFromTest;
|
||||
this.parameters = parameters;
|
||||
this.pathVariables = pathVariables;
|
||||
this.magicScriptContext = magicScriptContext;
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public static RequestEntity empty() {
|
||||
return new RequestEntity();
|
||||
}
|
||||
|
||||
public ApiInfo getApiInfo() {
|
||||
return apiInfo;
|
||||
}
|
||||
|
||||
public void setApiInfo(ApiInfo apiInfo) {
|
||||
this.apiInfo = apiInfo;
|
||||
}
|
||||
|
||||
public HttpServletRequest getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
public void setRequest(HttpServletRequest request) {
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
public HttpServletResponse getResponse() {
|
||||
return response;
|
||||
}
|
||||
|
||||
public void setResponse(HttpServletResponse response) {
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
public boolean isRequestedFromTest() {
|
||||
return requestedFromTest;
|
||||
}
|
||||
|
||||
public void setRequestedFromTest(boolean requestedFromTest) {
|
||||
this.requestedFromTest = requestedFromTest;
|
||||
}
|
||||
|
||||
public Map<String, Object> getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public void setParameters(Map<String, Object> parameters) {
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
public Map<String, Object> getPathVariables() {
|
||||
return pathVariables;
|
||||
}
|
||||
|
||||
public void setPathVariables(Map<String, Object> pathVariables) {
|
||||
this.pathVariables = pathVariables;
|
||||
}
|
||||
|
||||
public Long getRequestTime() {
|
||||
return requestTime;
|
||||
}
|
||||
|
||||
public MagicScriptContext getMagicScriptContext() {
|
||||
return magicScriptContext;
|
||||
}
|
||||
|
||||
public void setMagicScriptContext(MagicScriptContext magicScriptContext) {
|
||||
this.magicScriptContext = magicScriptContext;
|
||||
}
|
||||
|
||||
public Map<String, Object> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
public void setHeaders(Map<String, Object> headers) {
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.ssssssss.magicapi.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class TreeNode<T> {
|
||||
|
||||
private T node;
|
||||
|
||||
private List<TreeNode<T>> children = new ArrayList<>();
|
||||
|
||||
public TreeNode() {
|
||||
}
|
||||
|
||||
public TreeNode(T node) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
public T getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
public void setNode(T node) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
public List<TreeNode<T>> getChildren() {
|
||||
return children;
|
||||
}
|
||||
|
||||
public void setChildren(List<TreeNode<T>> children) {
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
|
||||
public TreeNode<T> findTreeNode(Function<T, Boolean> mapping) {
|
||||
return findTreeNode(this.children, mapping);
|
||||
}
|
||||
|
||||
private TreeNode<T> findTreeNode(List<TreeNode<T>> childs, Function<T, Boolean> mapping) {
|
||||
for (TreeNode<T> child : childs) {
|
||||
if (mapping.apply(child.getNode())) {
|
||||
return child;
|
||||
}
|
||||
TreeNode<T> node = findTreeNode(child.children, mapping);
|
||||
if (node != null) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void moveTo(TreeNode<T> node) {
|
||||
node.children.add(this);
|
||||
}
|
||||
|
||||
public List<T> flat() {
|
||||
return flat(this);
|
||||
}
|
||||
|
||||
private List<T> flat(TreeNode<T> node) {
|
||||
List<T> result = new ArrayList<>();
|
||||
result.add(node.getNode());
|
||||
for (TreeNode<T> item : node.getChildren()) {
|
||||
result.addAll(flat(item));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package org.ssssssss.magicapi.modules;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.ssssssss.magicapi.config.MagicModule;
|
||||
import org.ssssssss.script.annotation.Comment;
|
||||
import org.ssssssss.script.exception.MagicScriptAssertException;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 断言模块
|
||||
*/
|
||||
public class AssertModule implements MagicModule {
|
||||
|
||||
/**
|
||||
* 判断值不能为null
|
||||
*
|
||||
* @param value 值
|
||||
* @param code 状态码
|
||||
* @param message 状态说明
|
||||
*/
|
||||
@Comment("判断值不能为空")
|
||||
public void notNull(@Comment("值") Object value, @Comment("判断失败时的code") int code, @Comment("判断失败时的说明") String message) {
|
||||
if (value == null) {
|
||||
throw new MagicScriptAssertException(code, message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断值不能为empty
|
||||
*
|
||||
* @param value 值
|
||||
* @param code 状态码
|
||||
* @param message 状态说明
|
||||
*/
|
||||
@Comment("判断值不能为Empty")
|
||||
public void notEmpty(@Comment("值") String value, @Comment("判断失败时的code") int code, @Comment("判断失败时的说明") String message) {
|
||||
if (StringUtils.isEmpty(value)) {
|
||||
throw new MagicScriptAssertException(code, message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断值不能为blank
|
||||
*
|
||||
* @param value 值
|
||||
* @param code 状态码
|
||||
* @param message 状态说明
|
||||
*/
|
||||
@Comment("判断值不能为Blank")
|
||||
public void notBlank(@Comment("值") String value, @Comment("判断失败时的code") int code, @Comment("判断失败时的说明") String message) {
|
||||
if (StringUtils.isBlank(value)) {
|
||||
throw new MagicScriptAssertException(code, message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 正则验证值
|
||||
*
|
||||
* @param value 值
|
||||
* @param code 状态码
|
||||
* @param message 状态说明
|
||||
*/
|
||||
@Comment("正则判断")
|
||||
public void regx(@Comment("值") String value, String pattern, @Comment("判断失败时的code") int code, @Comment("判断失败时的说明") String message) {
|
||||
if (value == null || !Pattern.compile(pattern).matcher(value).matches()) {
|
||||
throw new MagicScriptAssertException(code, message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断值值是否为true
|
||||
*
|
||||
* @param value 值
|
||||
* @param code 状态码
|
||||
* @param message 状态说明
|
||||
*/
|
||||
@Comment("判断值是否为true")
|
||||
public void isTrue(@Comment("值") boolean value, @Comment("判断失败时的code") int code, @Comment("判断失败时的说明") String message) {
|
||||
if (!value) {
|
||||
throw new MagicScriptAssertException(code, message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getModuleName() {
|
||||
return "assert";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package org.ssssssss.magicapi.modules;
|
||||
|
||||
import org.ssssssss.magicapi.cache.SqlCache;
|
||||
import org.ssssssss.magicapi.context.RequestContext;
|
||||
import org.ssssssss.magicapi.interceptor.SQLInterceptor;
|
||||
import org.ssssssss.script.MagicScriptContext;
|
||||
import org.ssssssss.script.functions.StreamExtension;
|
||||
import org.ssssssss.script.parsing.GenericTokenParser;
|
||||
import org.ssssssss.script.parsing.ast.literal.BooleanLiteral;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
public class BoundSql {
|
||||
|
||||
private static final GenericTokenParser concatTokenParser = new GenericTokenParser("${", "}", false);
|
||||
|
||||
private static final GenericTokenParser replaceTokenParser = new GenericTokenParser("#{", "}", true);
|
||||
|
||||
private static final GenericTokenParser ifTokenParser = new GenericTokenParser("?{", "}", true);
|
||||
|
||||
private static final GenericTokenParser ifParamTokenParser = new GenericTokenParser("?{", ",", true);
|
||||
|
||||
private static final Pattern REPLACE_MULTI_WHITE_LINE = Pattern.compile("(\r?\n(\\s*\r?\n)+)");
|
||||
|
||||
private String sql;
|
||||
|
||||
private List<Object> parameters = new ArrayList<>();
|
||||
|
||||
private SqlCache sqlCache;
|
||||
|
||||
private String cacheName;
|
||||
|
||||
private long ttl;
|
||||
|
||||
public BoundSql(String sql, List<Object> parameters, SQLModule sqlModule) {
|
||||
this.sql = sql;
|
||||
this.parameters = parameters;
|
||||
this.sqlCache = sqlModule.getSqlCache();
|
||||
this.cacheName = sqlModule.getCacheName();
|
||||
this.ttl = sqlModule.getTtl();
|
||||
}
|
||||
|
||||
BoundSql(String sql) {
|
||||
MagicScriptContext context = MagicScriptContext.get();
|
||||
// 处理?{}参数
|
||||
this.sql = ifTokenParser.parse(sql.trim(), text -> {
|
||||
AtomicBoolean ifTrue = new AtomicBoolean(false);
|
||||
String val = ifParamTokenParser.parse("?{" + text, param -> {
|
||||
ifTrue.set(BooleanLiteral.isTrue(context.eval(param)));
|
||||
return null;
|
||||
});
|
||||
return ifTrue.get() ? val : "";
|
||||
});
|
||||
// 处理${}参数
|
||||
this.sql = concatTokenParser.parse(this.sql, text -> String.valueOf(context.eval(text)));
|
||||
// 处理#{}参数
|
||||
this.sql = replaceTokenParser.parse(this.sql, text -> {
|
||||
Object value = context.eval(text);
|
||||
if (value == null) {
|
||||
parameters.add(null);
|
||||
return "?";
|
||||
}
|
||||
try {
|
||||
//对集合自动展开
|
||||
List<Object> objects = StreamExtension.arrayLikeToList(value);
|
||||
parameters.addAll(objects);
|
||||
return IntStream.range(0, objects.size()).mapToObj(t -> "?").collect(Collectors.joining(","));
|
||||
} catch (Exception e) {
|
||||
parameters.add(value);
|
||||
return "?";
|
||||
}
|
||||
});
|
||||
this.sql = this.sql == null ? null : REPLACE_MULTI_WHITE_LINE.matcher(this.sql.trim()).replaceAll("\r\n");
|
||||
}
|
||||
|
||||
BoundSql(String sql, SQLModule sqlModule) {
|
||||
this(sql);
|
||||
this.sqlCache = sqlModule.getSqlCache();
|
||||
this.cacheName = sqlModule.getCacheName();
|
||||
this.ttl = sqlModule.getTtl();
|
||||
}
|
||||
|
||||
private BoundSql() {
|
||||
|
||||
}
|
||||
|
||||
BoundSql copy(String newSql) {
|
||||
BoundSql boundSql = new BoundSql();
|
||||
boundSql.setParameters(new ArrayList<>(this.parameters));
|
||||
boundSql.setSql(this.sql);
|
||||
boundSql.ttl = this.ttl;
|
||||
boundSql.cacheName = this.cacheName;
|
||||
boundSql.sqlCache = this.sqlCache;
|
||||
boundSql.sql = newSql;
|
||||
return boundSql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加SQL参数
|
||||
*/
|
||||
public void addParameter(Object value) {
|
||||
parameters.add(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取要执行的SQL
|
||||
*/
|
||||
public String getSql() {
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置要执行的SQL
|
||||
*/
|
||||
public void setSql(String sql) {
|
||||
this.sql = sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取要执行的参数
|
||||
*/
|
||||
public Object[] getParameters() {
|
||||
return parameters.toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置要执行的参数
|
||||
*/
|
||||
public void setParameters(List<Object> parameters) {
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存值
|
||||
*/
|
||||
private <T> T getCacheValue(String sql, Object[] params, Supplier<T> supplier) {
|
||||
if (cacheName == null) {
|
||||
return supplier.get();
|
||||
}
|
||||
String cacheKey = sqlCache.buildSqlCacheKey(sql, params);
|
||||
Object cacheValue = sqlCache.get(cacheName, cacheKey);
|
||||
if (cacheValue != null) {
|
||||
return (T) cacheValue;
|
||||
}
|
||||
T value = supplier.get();
|
||||
sqlCache.put(cacheName, cacheKey, value, ttl);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存值
|
||||
*/
|
||||
<T> T getCacheValue(List<SQLInterceptor> interceptors, Supplier<T> supplier) {
|
||||
interceptors.forEach(interceptor -> interceptor.preHandle(this, RequestContext.getRequestEntity()));
|
||||
return getCacheValue(this.getSql(), this.getParameters(), supplier);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.ssssssss.magicapi.modules;
|
||||
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.ssssssss.magicapi.config.MagicModule;
|
||||
import org.ssssssss.script.annotation.Comment;
|
||||
|
||||
public class EnvModule implements MagicModule {
|
||||
|
||||
private final Environment environment;
|
||||
|
||||
public EnvModule(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModuleName() {
|
||||
return "env";
|
||||
}
|
||||
|
||||
@Comment("获取配置")
|
||||
public String get(@Comment("配置项") String key) {
|
||||
return environment.getProperty(key);
|
||||
}
|
||||
|
||||
@Comment("获取配置")
|
||||
public String get(@Comment("配置项") String key, @Comment("未配置时的默认值") String defaultValue) {
|
||||
return environment.getProperty(key, defaultValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package org.ssssssss.magicapi.modules;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.ssssssss.magicapi.config.MagicModule;
|
||||
import org.ssssssss.script.annotation.Comment;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* http 模块
|
||||
*
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class HttpModule implements MagicModule {
|
||||
|
||||
private final RestTemplate template;
|
||||
private final HttpHeaders httpHeaders = new HttpHeaders();
|
||||
private final Class<?> responseType = Object.class;
|
||||
private final MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
|
||||
private final MultiValueMap<String, Object> data = new LinkedMultiValueMap<>();
|
||||
private final Map<String, ?> variables = new HashMap<>();
|
||||
private String url;
|
||||
private HttpMethod method = HttpMethod.GET;
|
||||
private HttpEntity<Object> entity = null;
|
||||
private Object requestBody;
|
||||
|
||||
public HttpModule(RestTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
public HttpModule(RestTemplate template, String url) {
|
||||
this.template = template;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModuleName() {
|
||||
return "http";
|
||||
}
|
||||
|
||||
@Comment("创建连接")
|
||||
public HttpModule connect(@Comment("目标URL") String url) {
|
||||
return new HttpModule(template, url);
|
||||
}
|
||||
|
||||
@Comment("设置URL参数")
|
||||
public HttpModule param(@Comment("参数名") String key, @Comment("参数值") Object... values) {
|
||||
if (values != null) {
|
||||
for (Object value : values) {
|
||||
this.params.add(key, value);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("批量设置URL参数")
|
||||
public HttpModule param(@Comment("参数值") Map<String, Object> values) {
|
||||
values.forEach((key, value) -> param(key, Objects.toString(value, "")));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("设置form参数")
|
||||
public HttpModule data(@Comment("参数名") String key, @Comment("参数值") Object... values) {
|
||||
if (values != null) {
|
||||
for (Object value : values) {
|
||||
this.data.add(key, value);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("批量设置form参数")
|
||||
public HttpModule data(@Comment("参数值") Map<String, Object> values) {
|
||||
values.forEach((key, value) -> data(key, Objects.toString(value, "")));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("设置header")
|
||||
public HttpModule header(@Comment("header名") String key, @Comment("header值") String value) {
|
||||
httpHeaders.add(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("批量设置header")
|
||||
public HttpModule header(@Comment("header值") Map<String, Object> values) {
|
||||
values.entrySet()
|
||||
.stream()
|
||||
.filter(it -> it.getValue() != null)
|
||||
.forEach(entry -> header(entry.getKey(), entry.getValue().toString()));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("设置请求方法,默认GET")
|
||||
public HttpModule method(@Comment("请求方法") HttpMethod method) {
|
||||
this.method = method;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("设置`RequestBody`")
|
||||
public HttpModule body(@Comment("`RequestBody`") Object requestBody) {
|
||||
this.requestBody = requestBody;
|
||||
this.contentType(MediaType.APPLICATION_JSON);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("自定义`HttpEntity`")
|
||||
public HttpModule entity(@Comment("`HttpEntity`") HttpEntity<Object> entity) {
|
||||
this.entity = entity;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("设置`ContentType`")
|
||||
public HttpModule contentType(@Comment("Content-Type值") String contentType) {
|
||||
return contentType(MediaType.parseMediaType(contentType));
|
||||
}
|
||||
|
||||
@Comment("设置`ContentType`")
|
||||
public HttpModule contentType(@Comment("Content-Type值") MediaType mediaType) {
|
||||
this.httpHeaders.setContentType(mediaType);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("发送`POST`请求")
|
||||
public ResponseEntity<Object> post() {
|
||||
this.method(HttpMethod.POST);
|
||||
return this.execute();
|
||||
}
|
||||
|
||||
@Comment("发送`GET`请求")
|
||||
public ResponseEntity<Object> get() {
|
||||
this.method(HttpMethod.GET);
|
||||
return this.execute();
|
||||
}
|
||||
|
||||
@Comment("发送`PUT`请求")
|
||||
public ResponseEntity<Object> put() {
|
||||
this.method(HttpMethod.PUT);
|
||||
return this.execute();
|
||||
}
|
||||
|
||||
@Comment("发送`DELETE`请求")
|
||||
public ResponseEntity<Object> delete() {
|
||||
this.method(HttpMethod.DELETE);
|
||||
return this.execute();
|
||||
}
|
||||
|
||||
@Comment("执行请求")
|
||||
public ResponseEntity<Object> execute() {
|
||||
if (!this.params.isEmpty()) {
|
||||
String params = this.params.entrySet().stream()
|
||||
.map(it -> it.getValue().stream()
|
||||
.map(value -> it.getKey() + "=" + value)
|
||||
.collect(Collectors.joining("&"))
|
||||
).collect(Collectors.joining("&"));
|
||||
if (StringUtils.isNotBlank(params)) {
|
||||
this.url += (this.url.contains("?") ? "&" : "?") + params;
|
||||
}
|
||||
}
|
||||
if (!this.data.isEmpty()) {
|
||||
this.entity = new HttpEntity<>(this.data, this.httpHeaders);
|
||||
} else if (this.entity == null && this.requestBody != null) {
|
||||
this.entity = new HttpEntity<>(this.requestBody, this.httpHeaders);
|
||||
} else {
|
||||
this.entity = new HttpEntity<>(null, this.httpHeaders);
|
||||
}
|
||||
return template.exchange(url, this.method, entity, Object.class, responseType, variables);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package org.ssssssss.magicapi.modules;
|
||||
|
||||
import com.mongodb.client.FindIterable;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.model.UpdateOptions;
|
||||
import org.bson.Document;
|
||||
import org.ssssssss.script.annotation.Comment;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* MongoCollection方法扩展
|
||||
*/
|
||||
public class MongoCollectionExtension {
|
||||
|
||||
@Comment("执行批量插入操作")
|
||||
public void insert(MongoCollection<Document> collection, @Comment("要插入的集合") List<Map<String, Object>> maps) {
|
||||
collection.insertMany(maps.stream().map(Document::new).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Comment("执行单条插入操作")
|
||||
public void insert(MongoCollection<Document> collection, @Comment("执行插入数据") Map<String, Object> map) {
|
||||
insert(collection, Collections.singletonList(map));
|
||||
}
|
||||
|
||||
@Comment("执行查询操作")
|
||||
public FindIterable<Document> find(MongoCollection<Document> collection, @Comment("查询条件") Map<String, Object> query) {
|
||||
return collection.find(new Document(query));
|
||||
}
|
||||
|
||||
@Comment("修改操作,返回修改数量")
|
||||
public long update(MongoCollection<Document> collection, @Comment("查询条件") Map<String, Object> query, @Comment("修改值") Map<String, Object> update) {
|
||||
return collection.updateOne(new Document(query), new Document(update)).getModifiedCount();
|
||||
}
|
||||
|
||||
@Comment("批量修改,返回修改数量")
|
||||
public long updateMany(MongoCollection<Document> collection, @Comment("修改条件") Map<String, Object> query, @Comment("修改值") Map<String, Object> update) {
|
||||
return collection.updateMany(new Document(query), new Document(update)).getModifiedCount();
|
||||
}
|
||||
|
||||
@Comment("批量修改,返回修改数量")
|
||||
public long updateMany(MongoCollection<Document> collection, @Comment("查询条件") Map<String, Object> query, @Comment("修改值") Map<String, Object> update, Map<String, Object> filters) {
|
||||
UpdateOptions updateOptions = new UpdateOptions();
|
||||
if (filters != null && !filters.isEmpty()) {
|
||||
Object upsert = filters.get("upsert");
|
||||
if (upsert != null) {
|
||||
filters.remove("upsert");
|
||||
updateOptions.upsert(Boolean.parseBoolean(upsert.toString()));
|
||||
}
|
||||
Object bypassDocumentValidation = filters.get("bypassDocumentValidation");
|
||||
if (bypassDocumentValidation != null) {
|
||||
filters.remove("bypassDocumentValidation");
|
||||
updateOptions.bypassDocumentValidation(Boolean.parseBoolean(bypassDocumentValidation.toString()));
|
||||
}
|
||||
List<Document> arrayFilters = filters.entrySet().stream().map(entry -> new Document(entry.getKey(), entry.getValue())).collect(Collectors.toList());
|
||||
updateOptions.arrayFilters(arrayFilters);
|
||||
}
|
||||
return collection.updateMany(new Document(query), new Document(update), updateOptions).getModifiedCount();
|
||||
}
|
||||
|
||||
@Comment("查询数量")
|
||||
public long count(MongoCollection<Document> collection, @Comment("查询") Map<String, Object> query) {
|
||||
return collection.countDocuments(new Document(query));
|
||||
}
|
||||
|
||||
@Comment("批量删除,返回删除条数")
|
||||
public long remove(MongoCollection<Document> collection, @Comment("删除条件") Map<String, Object> query) {
|
||||
return collection.deleteMany(new Document(query)).getDeletedCount();
|
||||
}
|
||||
|
||||
@Comment("删除一条,返回删除条数")
|
||||
public long removeOne(MongoCollection<Document> collection, @Comment("删除条件") Map<String, Object> query) {
|
||||
return collection.deleteOne(new Document(query)).getDeletedCount();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.ssssssss.magicapi.modules;
|
||||
|
||||
import com.mongodb.client.FindIterable;
|
||||
import com.mongodb.client.MongoCursor;
|
||||
import org.bson.Document;
|
||||
import org.ssssssss.script.annotation.Comment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Mongo FindIterable 方法扩展
|
||||
*/
|
||||
public class MongoFindIterableExtension {
|
||||
|
||||
@Comment("结果转为List")
|
||||
public List<Map<String, Object>> list(FindIterable<Document> iterable) {
|
||||
MongoCursor<Document> cursor = iterable.iterator();
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
while (cursor.hasNext()) {
|
||||
result.add(cursor.next());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.ssssssss.magicapi.modules;
|
||||
|
||||
import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bson.Document;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.ssssssss.magicapi.config.MagicModule;
|
||||
import org.ssssssss.script.reflection.AbstractReflection;
|
||||
import org.ssssssss.script.reflection.JavaInvoker;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* mongo模块
|
||||
*/
|
||||
public class MongoModule extends HashMap<String, Object> implements MagicModule {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MongoModule.class);
|
||||
|
||||
private final MongoTemplate mongoTemplate;
|
||||
private final JavaInvoker<Method> mongoDbFactoryInvoker;
|
||||
private JavaInvoker<Method> invoker;
|
||||
|
||||
public MongoModule(MongoTemplate mongoTemplate) {
|
||||
this.mongoTemplate = mongoTemplate;
|
||||
AbstractReflection reflection = AbstractReflection.getInstance();
|
||||
mongoDbFactoryInvoker = reflection.getMethod(this.mongoTemplate, "getMongoDbFactory");
|
||||
if (mongoDbFactoryInvoker != null) {
|
||||
try {
|
||||
Object factory = mongoDbFactoryInvoker.invoke0(this.mongoTemplate, null);
|
||||
invoker = reflection.getMethod(factory, "getDb", StringUtils.EMPTY);
|
||||
if (invoker == null) {
|
||||
invoker = reflection.getMethod(factory, "getMongoDatabase", StringUtils.EMPTY);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
logger.error("mongo模块初始化失败", e);
|
||||
}
|
||||
} else {
|
||||
logger.error("mongo模块初始化失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(Object databaseName) {
|
||||
return databaseName == null ? null : new HashMap<String, MongoCollection<Document>>() {
|
||||
@Override
|
||||
public MongoCollection<Document> get(Object collection) {
|
||||
if (collection == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Object factory = mongoDbFactoryInvoker.invoke0(mongoTemplate, null);
|
||||
MongoDatabase database = (MongoDatabase) invoker.invoke0(factory, null, databaseName.toString());
|
||||
return database.getCollection(collection.toString());
|
||||
} catch (Throwable throwable) {
|
||||
throw new RuntimeException(throwable);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModuleName() {
|
||||
return "mongo";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.ssssssss.magicapi.modules;
|
||||
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisCallback;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.ssssssss.magicapi.config.MagicModule;
|
||||
import org.ssssssss.script.functions.DynamicMethod;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* redis模块
|
||||
*/
|
||||
public class RedisModule implements MagicModule, DynamicMethod {
|
||||
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
|
||||
public RedisModule(RedisConnectionFactory connectionFactory) {
|
||||
this.redisTemplate = new StringRedisTemplate(connectionFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModuleName() {
|
||||
return "redis";
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化
|
||||
*/
|
||||
private byte[] serializer(Object value) {
|
||||
if (value == null || value instanceof String) {
|
||||
return redisTemplate.getStringSerializer().serialize((String) value);
|
||||
}
|
||||
return serializer(value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 反序列化
|
||||
*/
|
||||
private Object deserialize(Object value) {
|
||||
if (value != null) {
|
||||
if (value instanceof byte[]) {
|
||||
return this.redisTemplate.getStringSerializer().deserialize((byte[]) value);
|
||||
}
|
||||
if (value instanceof List) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Object> valueList = (List<Object>) value;
|
||||
List<Object> resultList = new ArrayList<>(valueList.size());
|
||||
for (Object val : valueList) {
|
||||
resultList.add(deserialize(val));
|
||||
}
|
||||
return resultList;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行命令
|
||||
*
|
||||
* @param methodName 命令名称
|
||||
* @param parameters 命令参数
|
||||
*/
|
||||
@Override
|
||||
public Object execute(String methodName, List<Object> parameters) {
|
||||
return this.redisTemplate.execute((RedisCallback<Object>) connection -> {
|
||||
byte[][] params = new byte[parameters.size()][];
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
params[i] = serializer(parameters.get(i));
|
||||
}
|
||||
return deserialize(connection.execute(methodName, params));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package org.ssssssss.magicapi.modules;
|
||||
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.multipart.MultipartRequest;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
import org.ssssssss.magicapi.context.RequestContext;
|
||||
import org.ssssssss.script.annotation.Comment;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* request 模块
|
||||
*/
|
||||
public class RequestModule {
|
||||
|
||||
/**
|
||||
* 获取文件信息
|
||||
*
|
||||
* @param name 参数名
|
||||
*/
|
||||
@Comment("获取文件")
|
||||
public static MultipartFile getFile(@Comment("参数名") String name) {
|
||||
MultipartRequest request = getMultipartHttpServletRequest();
|
||||
return request == null ? null : request.getFile(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件信息
|
||||
*
|
||||
* @param name 参数名
|
||||
*/
|
||||
@Comment("获取多个文件")
|
||||
public static List<MultipartFile> getFiles(@Comment("参数名") String name) {
|
||||
MultipartRequest request = getMultipartHttpServletRequest();
|
||||
return request == null ? null : request.getFiles(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取原生HttpServletRequest对象
|
||||
*/
|
||||
public static HttpServletRequest get() {
|
||||
return RequestContext.getHttpServletRequest();
|
||||
}
|
||||
|
||||
private static MultipartRequest getMultipartHttpServletRequest() {
|
||||
HttpServletRequest request = get();
|
||||
if (request != null && request.getContentType() != null && request.getContentType().toLowerCase().startsWith("multipart/")) {
|
||||
return WebUtils.getNativeRequest(request, MultipartRequest.class);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据参数名获取参数值集合
|
||||
*
|
||||
* @param name 参数名
|
||||
*/
|
||||
@Comment("根据请求参数名获取值")
|
||||
public List<String> getValues(@Comment("参数名") String name) {
|
||||
HttpServletRequest request = get();
|
||||
if (request != null) {
|
||||
String[] values = request.getParameterValues(name);
|
||||
return values == null ? null : Arrays.asList(values);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据header名获取header集合
|
||||
*
|
||||
* @param name 参数名
|
||||
*/
|
||||
@Comment("根据header名获取值")
|
||||
public List<String> getHeaders(@Comment("header名") String name) {
|
||||
HttpServletRequest request = get();
|
||||
if (request != null) {
|
||||
Enumeration<String> headers = request.getHeaders(name);
|
||||
return headers == null ? null : Collections.list(headers);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
package org.ssssssss.magicapi.modules;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.ssssssss.magicapi.provider.ResultProvider;
|
||||
import org.ssssssss.script.annotation.Comment;
|
||||
import org.ssssssss.script.functions.ObjectConvertExtension;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* response模块
|
||||
*/
|
||||
public class ResponseModule {
|
||||
|
||||
private final ResultProvider resultProvider;
|
||||
|
||||
public ResponseModule(ResultProvider resultProvider) {
|
||||
this.resultProvider = resultProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件下载
|
||||
*
|
||||
* @param value 文件内容
|
||||
* @param filename 文件名
|
||||
*/
|
||||
@Comment("文件下载")
|
||||
public static ResponseEntity<?> download(@Comment("文件内容,如`byte[]`") Object value, @Comment("文件名") String filename) throws UnsupportedEncodingException {
|
||||
return ResponseEntity.ok().contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + URLEncoder.encode(filename, "UTF-8"))
|
||||
.body(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自行构建分页结果
|
||||
*
|
||||
* @param total 条数
|
||||
* @param values 数据内容
|
||||
*/
|
||||
@Comment("返回自定义分页结果")
|
||||
public Object page(@Comment("总条数") long total, @Comment("当前结果集") List<Map<String, Object>> values) {
|
||||
return resultProvider.buildPageResult(total, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义json结果
|
||||
*
|
||||
* @param value json内容
|
||||
*/
|
||||
@Comment("自定义返回json内容")
|
||||
public ResponseEntity<Object> json(@Comment("返回对象") Object value) {
|
||||
return ResponseEntity.ok(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加Header
|
||||
*/
|
||||
@Comment("添加response header")
|
||||
public ResponseModule addHeader(@Comment("header名") String key, @Comment("header值") String value) {
|
||||
if (StringUtils.isNotBlank(key)) {
|
||||
HttpServletResponse response = getResponse();
|
||||
if (response != null) {
|
||||
response.addHeader(key, value);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置header
|
||||
*/
|
||||
@Comment("设置response header")
|
||||
public ResponseModule setHeader(@Comment("header名") String key, @Comment("header值") String value) {
|
||||
if (StringUtils.isNotBlank(key)) {
|
||||
HttpServletResponse response = getResponse();
|
||||
if (response != null) {
|
||||
response.setHeader(key, value);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加cookie
|
||||
*/
|
||||
@Comment("添加Cookie")
|
||||
public ResponseModule addCookie(@Comment("cookie名") String name, @Comment("cookie值") String value) {
|
||||
if (StringUtils.isNotBlank(name)) {
|
||||
addCookie(new Cookie(name, value));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加cookie
|
||||
*/
|
||||
@Comment("批量添加Cookie")
|
||||
public ResponseModule addCookies(@Comment("Cookies") Map<String, String> cookies, @Comment("Cookie选项,如`path`、`httpOnly`、`domain`、`maxAge`") Map<String, Object> options) {
|
||||
if (cookies != null) {
|
||||
for (Map.Entry<String, String> entry : cookies.entrySet()) {
|
||||
addCookie(entry.getKey(), entry.getValue(), options);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加cookie
|
||||
*/
|
||||
@Comment("批量添加Cookie")
|
||||
public ResponseModule addCookies(@Comment("Cookies") Map<String, String> cookies) {
|
||||
return addCookies(cookies, null);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加cookie
|
||||
*/
|
||||
@Comment("添加Cookie")
|
||||
public ResponseModule addCookie(@Comment("Cookie名") String name, @Comment("Cookie值") String value,
|
||||
@Comment("Cookie选项,如`path`、`httpOnly`、`domain`、`maxAge`") Map<String, Object> options) {
|
||||
if (StringUtils.isNotBlank(name)) {
|
||||
Cookie cookie = new Cookie(name, value);
|
||||
if (options != null) {
|
||||
Object path = options.get("path");
|
||||
if (path != null) {
|
||||
cookie.setPath(path.toString());
|
||||
}
|
||||
Object httpOnly = options.get("httpOnly");
|
||||
if (httpOnly != null) {
|
||||
cookie.setHttpOnly("true".equalsIgnoreCase(httpOnly.toString()));
|
||||
}
|
||||
Object domain = options.get("domain");
|
||||
if (domain != null) {
|
||||
cookie.setDomain(domain.toString());
|
||||
}
|
||||
Object maxAge = options.get("maxAge");
|
||||
int age;
|
||||
if (maxAge != null && (age = ObjectConvertExtension.asInt(maxAge, Integer.MIN_VALUE)) != Integer.MIN_VALUE) {
|
||||
cookie.setMaxAge(age);
|
||||
}
|
||||
}
|
||||
addCookie(cookie);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("终止输出,执行此方法后不会对结果进行任何输出及处理")
|
||||
public NullValue end() {
|
||||
return NullValue.INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加cookie
|
||||
*/
|
||||
@Comment("添加Cookie")
|
||||
public ResponseModule addCookie(@Comment("Cookie对象") Cookie cookie) {
|
||||
if (cookie != null) {
|
||||
HttpServletResponse response = getResponse();
|
||||
if (response != null) {
|
||||
response.addCookie(cookie);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private HttpServletResponse getResponse() {
|
||||
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
|
||||
if (requestAttributes instanceof ServletRequestAttributes) {
|
||||
return ((ServletRequestAttributes) requestAttributes).getResponse();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 展示图片
|
||||
*
|
||||
* @param value 图片内容
|
||||
* @param mime 图片类型,image/png,image/jpeg,image/gif
|
||||
*/
|
||||
@Comment("输出图片")
|
||||
public ResponseEntity image(@Comment("图片内容,如`byte[]`") Object value, @Comment("图片类型,如`image/png`、`image/jpeg`、`image/gif`") String mime) {
|
||||
return ResponseEntity.ok().header(HttpHeaders.CONTENT_TYPE, mime).body(value);
|
||||
}
|
||||
|
||||
public static class NullValue {
|
||||
static final NullValue INSTANCE = new NullValue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,463 @@
|
||||
package org.ssssssss.magicapi.modules;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.support.GeneratedKeyHolder;
|
||||
import org.ssssssss.magicapi.adapter.ColumnMapperAdapter;
|
||||
import org.ssssssss.magicapi.adapter.DialectAdapter;
|
||||
import org.ssssssss.magicapi.cache.SqlCache;
|
||||
import org.ssssssss.magicapi.config.MagicDynamicDataSource;
|
||||
import org.ssssssss.magicapi.config.MagicDynamicDataSource.DataSourceNode;
|
||||
import org.ssssssss.magicapi.config.MagicModule;
|
||||
import org.ssssssss.magicapi.context.RequestContext;
|
||||
import org.ssssssss.magicapi.dialect.Dialect;
|
||||
import org.ssssssss.magicapi.interceptor.SQLInterceptor;
|
||||
import org.ssssssss.magicapi.model.Page;
|
||||
import org.ssssssss.magicapi.model.RequestEntity;
|
||||
import org.ssssssss.magicapi.modules.table.NamedTable;
|
||||
import org.ssssssss.magicapi.provider.PageProvider;
|
||||
import org.ssssssss.magicapi.provider.ResultProvider;
|
||||
import org.ssssssss.script.MagicScriptContext;
|
||||
import org.ssssssss.script.annotation.Comment;
|
||||
import org.ssssssss.script.annotation.UnableCall;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 数据库查询模块
|
||||
*/
|
||||
public class SQLModule extends HashMap<String, SQLModule> implements MagicModule {
|
||||
|
||||
private MagicDynamicDataSource dynamicDataSource;
|
||||
|
||||
private DataSourceNode dataSourceNode;
|
||||
|
||||
private PageProvider pageProvider;
|
||||
|
||||
private ResultProvider resultProvider;
|
||||
|
||||
private ColumnMapperAdapter columnMapperAdapter;
|
||||
|
||||
private DialectAdapter dialectAdapter;
|
||||
|
||||
private RowMapper<Map<String, Object>> columnMapRowMapper;
|
||||
|
||||
private Function<String, String> rowMapColumnMapper;
|
||||
|
||||
private SqlCache sqlCache;
|
||||
|
||||
private String cacheName;
|
||||
|
||||
private List<SQLInterceptor> sqlInterceptors;
|
||||
|
||||
private long ttl;
|
||||
|
||||
public SQLModule() {
|
||||
|
||||
}
|
||||
|
||||
public SQLModule(MagicDynamicDataSource dynamicDataSource) {
|
||||
this.dynamicDataSource = dynamicDataSource;
|
||||
this.dataSourceNode = dynamicDataSource.getDataSource();
|
||||
}
|
||||
|
||||
@UnableCall
|
||||
public void setPageProvider(PageProvider pageProvider) {
|
||||
this.pageProvider = pageProvider;
|
||||
}
|
||||
|
||||
@UnableCall
|
||||
public void setResultProvider(ResultProvider resultProvider) {
|
||||
this.resultProvider = resultProvider;
|
||||
}
|
||||
|
||||
@UnableCall
|
||||
public void setColumnMapperProvider(ColumnMapperAdapter columnMapperAdapter) {
|
||||
this.columnMapperAdapter = columnMapperAdapter;
|
||||
}
|
||||
|
||||
@UnableCall
|
||||
public void setDialectAdapter(DialectAdapter dialectAdapter) {
|
||||
this.dialectAdapter = dialectAdapter;
|
||||
}
|
||||
|
||||
@UnableCall
|
||||
public void setColumnMapRowMapper(RowMapper<Map<String, Object>> columnMapRowMapper) {
|
||||
this.columnMapRowMapper = columnMapRowMapper;
|
||||
}
|
||||
|
||||
@UnableCall
|
||||
public void setRowMapColumnMapper(Function<String, String> rowMapColumnMapper) {
|
||||
this.rowMapColumnMapper = rowMapColumnMapper;
|
||||
}
|
||||
|
||||
private void setDynamicDataSource(MagicDynamicDataSource dynamicDataSource) {
|
||||
this.dynamicDataSource = dynamicDataSource;
|
||||
}
|
||||
|
||||
@UnableCall
|
||||
public void setSqlInterceptors(List<SQLInterceptor> sqlInterceptors) {
|
||||
this.sqlInterceptors = sqlInterceptors;
|
||||
}
|
||||
|
||||
private void setDataSourceNode(DataSourceNode dataSourceNode) {
|
||||
this.dataSourceNode = dataSourceNode;
|
||||
}
|
||||
|
||||
protected String getCacheName() {
|
||||
return cacheName;
|
||||
}
|
||||
|
||||
private void setCacheName(String cacheName) {
|
||||
this.cacheName = cacheName;
|
||||
}
|
||||
|
||||
protected long getTtl() {
|
||||
return ttl;
|
||||
}
|
||||
|
||||
private void setTtl(long ttl) {
|
||||
this.ttl = ttl;
|
||||
}
|
||||
|
||||
protected SqlCache getSqlCache() {
|
||||
return sqlCache;
|
||||
}
|
||||
|
||||
@UnableCall
|
||||
public void setSqlCache(SqlCache sqlCache) {
|
||||
this.sqlCache = sqlCache;
|
||||
}
|
||||
|
||||
@UnableCall
|
||||
private SQLModule cloneSQLModule() {
|
||||
SQLModule sqlModule = new SQLModule();
|
||||
sqlModule.setDynamicDataSource(this.dynamicDataSource);
|
||||
sqlModule.setDataSourceNode(this.dataSourceNode);
|
||||
sqlModule.setPageProvider(this.pageProvider);
|
||||
sqlModule.setColumnMapperProvider(this.columnMapperAdapter);
|
||||
sqlModule.setColumnMapRowMapper(this.columnMapRowMapper);
|
||||
sqlModule.setRowMapColumnMapper(this.rowMapColumnMapper);
|
||||
sqlModule.setSqlCache(this.sqlCache);
|
||||
sqlModule.setTtl(this.ttl);
|
||||
sqlModule.setResultProvider(this.resultProvider);
|
||||
sqlModule.setDialectAdapter(this.dialectAdapter);
|
||||
sqlModule.setSqlInterceptors(this.sqlInterceptors);
|
||||
return sqlModule;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开启事务,在一个回调中进行操作
|
||||
*
|
||||
* @param function 回调函数
|
||||
*/
|
||||
@Comment("开启事务,并在回调中处理")
|
||||
public Object transaction(@Comment("回调函数,如:()=>{....}") Function<?, ?> function) {
|
||||
Transaction transaction = transaction(); //创建事务
|
||||
try {
|
||||
Object val = function.apply(null);
|
||||
transaction.commit(); //提交事务
|
||||
return val;
|
||||
} catch (Throwable throwable) {
|
||||
transaction.rollback(); //回滚事务
|
||||
throw throwable;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开启事务,手动提交和回滚
|
||||
*/
|
||||
@Comment("开启事务,返回事务对象")
|
||||
public Transaction transaction() {
|
||||
return new Transaction(this.dataSourceNode.getDataSourceTransactionManager());
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用缓存
|
||||
*
|
||||
* @param cacheName 缓存名
|
||||
* @param ttl 过期时间
|
||||
*/
|
||||
@Comment("使用缓存")
|
||||
public SQLModule cache(@Comment("缓存名") String cacheName, @Comment("过期时间") long ttl) {
|
||||
if (cacheName == null) {
|
||||
return this;
|
||||
}
|
||||
SQLModule sqlModule = cloneSQLModule();
|
||||
sqlModule.setCacheName(cacheName);
|
||||
sqlModule.setTtl(ttl);
|
||||
return sqlModule;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用缓存(采用默认缓存时间)
|
||||
*
|
||||
* @param cacheName 缓冲名
|
||||
*/
|
||||
@Comment("使用缓存,过期时间采用默认配置")
|
||||
public SQLModule cache(@Comment("缓存名") String cacheName) {
|
||||
return cache(cacheName, 0);
|
||||
}
|
||||
|
||||
@Comment("采用驼峰列名")
|
||||
public SQLModule camel() {
|
||||
return columnCase("camel");
|
||||
}
|
||||
|
||||
@Comment("采用帕斯卡列名")
|
||||
public SQLModule pascal() {
|
||||
return columnCase("pascal");
|
||||
}
|
||||
|
||||
@Comment("采用全小写列名")
|
||||
public SQLModule lower() {
|
||||
return columnCase("lower");
|
||||
}
|
||||
|
||||
@Comment("采用全大写列名")
|
||||
public SQLModule upper() {
|
||||
return columnCase("upper");
|
||||
}
|
||||
|
||||
@Comment("列名保持原样")
|
||||
public SQLModule normal() {
|
||||
return columnCase("default");
|
||||
}
|
||||
|
||||
@Comment("指定列名转换")
|
||||
public SQLModule columnCase(String name) {
|
||||
SQLModule sqlModule = cloneSQLModule();
|
||||
sqlModule.setColumnMapRowMapper(this.columnMapperAdapter.getColumnMapRowMapper(name));
|
||||
sqlModule.setRowMapColumnMapper(this.columnMapperAdapter.getRowMapColumnMapper(name));
|
||||
return sqlModule;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据源切换
|
||||
*/
|
||||
@Override
|
||||
public SQLModule get(Object key) {
|
||||
SQLModule sqlModule = cloneSQLModule();
|
||||
if (key == null) {
|
||||
sqlModule.setDataSourceNode(dynamicDataSource.getDataSource());
|
||||
} else {
|
||||
sqlModule.setDataSourceNode(dynamicDataSource.getDataSource(key.toString()));
|
||||
}
|
||||
return sqlModule;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询List
|
||||
*/
|
||||
@Comment("查询SQL,返回List类型结果")
|
||||
public List<Map<String, Object>> select(@Comment("`SQL`语句") String sql) {
|
||||
return select(new BoundSql(sql, this));
|
||||
}
|
||||
|
||||
@UnableCall
|
||||
public List<Map<String, Object>> select(BoundSql boundSql) {
|
||||
return boundSql.getCacheValue(this.sqlInterceptors, () -> dataSourceNode.getJdbcTemplate().query(boundSql.getSql(), this.columnMapRowMapper, boundSql.getParameters()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行update
|
||||
*/
|
||||
@Comment("执行update操作,返回受影响行数")
|
||||
public int update(@Comment("`SQL`语句") String sql) {
|
||||
return update(new BoundSql(sql));
|
||||
}
|
||||
|
||||
@UnableCall
|
||||
public int update(BoundSql boundSql) {
|
||||
sqlInterceptors.forEach(sqlInterceptor -> sqlInterceptor.preHandle(boundSql));
|
||||
int value = dataSourceNode.getJdbcTemplate().update(boundSql.getSql(), boundSql.getParameters());
|
||||
if (this.cacheName != null) {
|
||||
this.sqlCache.delete(this.cacheName);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入并返回主键
|
||||
*/
|
||||
@Comment("执行insert操作,返回插入主键")
|
||||
public long insert(@Comment("`SQL`语句") String sql) {
|
||||
MagicKeyHolder magicKeyHolder = new MagicKeyHolder();
|
||||
insert(new BoundSql(sql), magicKeyHolder);
|
||||
return magicKeyHolder.getLongKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入并返回主键
|
||||
*/
|
||||
@Comment("执行insert操作,返回插入主键")
|
||||
public Object insert(@Comment("`SQL`语句") String sql, @Comment("主键列") String primary) {
|
||||
return insert(new BoundSql(sql), primary);
|
||||
}
|
||||
|
||||
void insert(BoundSql boundSql, MagicKeyHolder keyHolder) {
|
||||
sqlInterceptors.forEach(sqlInterceptor -> sqlInterceptor.preHandle(boundSql));
|
||||
dataSourceNode.getJdbcTemplate().update(con -> {
|
||||
PreparedStatement ps = keyHolder.createPrepareStatement(con, boundSql.getSql());
|
||||
new ArgumentPreparedStatementSetter(boundSql.getParameters()).setValues(ps);
|
||||
return ps;
|
||||
}, keyHolder);
|
||||
if (this.cacheName != null) {
|
||||
this.sqlCache.delete(this.cacheName);
|
||||
}
|
||||
}
|
||||
|
||||
@UnableCall
|
||||
public Object insert(BoundSql boundSql, String primary) {
|
||||
MagicKeyHolder keyHolder = new MagicKeyHolder(primary);
|
||||
insert(boundSql, keyHolder);
|
||||
return keyHolder.getObjectKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*/
|
||||
@Comment("执行分页查询,分页条件自动获取")
|
||||
public Object page(@Comment("`SQL`语句") String sql) {
|
||||
return page(new BoundSql(sql, this));
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询(手动传入limit和offset参数)
|
||||
*/
|
||||
@Comment("执行分页查询,分页条件手动传入")
|
||||
public Object page(@Comment("`SQL`语句") String sql, @Comment("限制条数") long limit, @Comment("跳过条数") long offset) {
|
||||
BoundSql boundSql = new BoundSql(sql, this);
|
||||
return page(boundSql, new Page(limit, offset));
|
||||
}
|
||||
|
||||
@UnableCall
|
||||
public Object page(BoundSql boundSql) {
|
||||
Page page = pageProvider.getPage(MagicScriptContext.get());
|
||||
return page(boundSql, page);
|
||||
}
|
||||
|
||||
private Object page(BoundSql boundSql, Page page) {
|
||||
Dialect dialect = dataSourceNode.getDialect(dialectAdapter);
|
||||
BoundSql countBoundSql = boundSql.copy(dialect.getCountSql(boundSql.getSql()));
|
||||
int count = countBoundSql.getCacheValue(this.sqlInterceptors, () -> dataSourceNode.getJdbcTemplate().query(countBoundSql.getSql(), new SingleRowResultSetExtractor<>(Integer.class), countBoundSql.getParameters()));
|
||||
List<Map<String, Object>> list = null;
|
||||
if (count > 0) {
|
||||
BoundSql pageBoundSql = buildPageBoundSql(dialect, boundSql, page.getOffset(), page.getLimit());
|
||||
list = pageBoundSql.getCacheValue(this.sqlInterceptors, () -> dataSourceNode.getJdbcTemplate().query(pageBoundSql.getSql(), this.columnMapRowMapper, pageBoundSql.getParameters()));
|
||||
}
|
||||
RequestEntity requestEntity = RequestContext.getRequestEntity();
|
||||
return resultProvider.buildPageResult(requestEntity, page, count, list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询int值
|
||||
*/
|
||||
@Comment("查询int值,适合单行单列int的结果")
|
||||
public Integer selectInt(@Comment("`SQL`语句") String sql) {
|
||||
BoundSql boundSql = new BoundSql(sql, this);
|
||||
return boundSql.getCacheValue(this.sqlInterceptors, () -> dataSourceNode.getJdbcTemplate().query(boundSql.getSql(),new SingleRowResultSetExtractor<>(Integer.class), boundSql.getParameters()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询Map
|
||||
*/
|
||||
@Comment("查询单条结果,查不到返回null")
|
||||
public Map<String, Object> selectOne(@Comment("`SQL`语句") String sql) {
|
||||
return selectOne(new BoundSql(sql, this));
|
||||
}
|
||||
|
||||
@UnableCall
|
||||
public Map<String, Object> selectOne(BoundSql boundSql) {
|
||||
return boundSql.getCacheValue(this.sqlInterceptors, () -> {
|
||||
Dialect dialect = dataSourceNode.getDialect(dialectAdapter);
|
||||
BoundSql pageBoundSql = buildPageBoundSql(dialect, boundSql, 0, 1);
|
||||
return dataSourceNode.getJdbcTemplate().query(pageBoundSql.getSql(), new SingleRowResultSetExtractor<>(this.columnMapRowMapper), pageBoundSql.getParameters());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询单行单列的值
|
||||
*/
|
||||
@Comment("查询单行单列的值")
|
||||
public Object selectValue(@Comment("`SQL`语句") String sql) {
|
||||
BoundSql boundSql = new BoundSql(sql, this);
|
||||
return boundSql.getCacheValue(this.sqlInterceptors, () -> {
|
||||
Dialect dialect = dataSourceNode.getDialect(dialectAdapter);
|
||||
BoundSql pageBoundSql = buildPageBoundSql(dialect, boundSql, 0, 1);
|
||||
return dataSourceNode.getJdbcTemplate().query(pageBoundSql.getSql(), new SingleRowResultSetExtractor<>(Object.class), pageBoundSql.getParameters());
|
||||
});
|
||||
}
|
||||
|
||||
@Comment("指定table,进行单表操作")
|
||||
public NamedTable table(String tableName) {
|
||||
return new NamedTable(tableName, this, rowMapColumnMapper);
|
||||
}
|
||||
|
||||
private BoundSql buildPageBoundSql(Dialect dialect, BoundSql boundSql, long offset, long limit) {
|
||||
String pageSql = dialect.getPageSql(boundSql.getSql(), boundSql, offset, limit);
|
||||
return boundSql.copy(pageSql);
|
||||
}
|
||||
|
||||
@UnableCall
|
||||
@Override
|
||||
public String getModuleName() {
|
||||
return "db";
|
||||
}
|
||||
|
||||
class MagicKeyHolder extends GeneratedKeyHolder {
|
||||
|
||||
private final boolean useGeneratedKeys;
|
||||
|
||||
private final String primary;
|
||||
|
||||
public MagicKeyHolder() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public MagicKeyHolder(String primary) {
|
||||
this.primary = primary;
|
||||
this.useGeneratedKeys = StringUtils.isBlank(primary);
|
||||
}
|
||||
|
||||
PreparedStatement createPrepareStatement(Connection connection, String sql) throws SQLException {
|
||||
if (useGeneratedKeys) {
|
||||
return connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
|
||||
}
|
||||
return connection.prepareStatement(sql, new String[]{primary});
|
||||
}
|
||||
|
||||
public long getLongKey() throws InvalidDataAccessApiUsageException, DataRetrievalFailureException {
|
||||
Number key = super.getKey();
|
||||
if (key == null) {
|
||||
return -1;
|
||||
}
|
||||
return key.longValue();
|
||||
}
|
||||
|
||||
public Object getObjectKey() {
|
||||
if (useGeneratedKeys) {
|
||||
return getLongKey();
|
||||
}
|
||||
List<Map<String, Object>> keyList = getKeyList();
|
||||
if (keyList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Iterator<Object> keyIterator = keyList.get(0).values().iterator();
|
||||
return keyIterator.hasNext() ? keyIterator.next() : null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.ssssssss.magicapi.modules;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.support.JdbcUtils;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class SingleRowResultSetExtractor<T> implements ResultSetExtractor<T>{
|
||||
|
||||
|
||||
private final boolean singleColumn;
|
||||
|
||||
private final RowMapper<T> mapper;
|
||||
|
||||
private final Class<T> requiredType;
|
||||
|
||||
public SingleRowResultSetExtractor(RowMapper<T> mapper) {
|
||||
this(mapper, null, false);
|
||||
}
|
||||
|
||||
public SingleRowResultSetExtractor(Class<T> requiredType) {
|
||||
this(null, requiredType, true);
|
||||
}
|
||||
|
||||
private SingleRowResultSetExtractor(RowMapper<T> mapper, Class<T> requiredType, boolean singleColumn) {
|
||||
this.mapper = mapper;
|
||||
this.requiredType = requiredType;
|
||||
this.singleColumn = singleColumn;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public T extractData(ResultSet rs) throws SQLException, DataAccessException {
|
||||
if(rs.next()){
|
||||
if(singleColumn){
|
||||
return (T) JdbcUtils.getResultSetValue(rs, 1, requiredType);
|
||||
}
|
||||
return mapper.mapRow(rs, 0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.ssssssss.magicapi.modules;
|
||||
|
||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.support.DefaultTransactionDefinition;
|
||||
import org.ssssssss.script.annotation.Comment;
|
||||
|
||||
/**
|
||||
* 事务模块
|
||||
*/
|
||||
public class Transaction {
|
||||
|
||||
private static final TransactionDefinition TRANSACTION_DEFINITION = new DefaultTransactionDefinition();
|
||||
private final DataSourceTransactionManager dataSourceTransactionManager;
|
||||
private final TransactionStatus transactionStatus;
|
||||
|
||||
public Transaction(DataSourceTransactionManager dataSourceTransactionManager) {
|
||||
this.dataSourceTransactionManager = dataSourceTransactionManager;
|
||||
this.transactionStatus = dataSourceTransactionManager.getTransaction(TRANSACTION_DEFINITION);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚事务
|
||||
*/
|
||||
@Comment("回滚事务")
|
||||
public void rollback() {
|
||||
this.dataSourceTransactionManager.rollback(this.transactionStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交事务
|
||||
*/
|
||||
@Comment("提交事务")
|
||||
public void commit() {
|
||||
this.dataSourceTransactionManager.commit(this.transactionStatus);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
package org.ssssssss.magicapi.modules.table;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.ssssssss.magicapi.exception.MagicAPIException;
|
||||
import org.ssssssss.magicapi.modules.BoundSql;
|
||||
import org.ssssssss.magicapi.modules.SQLModule;
|
||||
import org.ssssssss.script.annotation.Comment;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class NamedTable {
|
||||
|
||||
String tableName;
|
||||
|
||||
SQLModule sqlModule;
|
||||
|
||||
String primary;
|
||||
|
||||
Map<String, Object> columns = new HashMap<>();
|
||||
|
||||
List<String> fields = new ArrayList<>();
|
||||
|
||||
List<String> groups = new ArrayList<>();
|
||||
|
||||
List<String> orders = new ArrayList<>();
|
||||
|
||||
Function<String, String> rowMapColumnMapper;
|
||||
|
||||
Object defaultPrimaryValue;
|
||||
|
||||
Where where = new Where(this);
|
||||
|
||||
public NamedTable(String tableName, SQLModule sqlModule, Function<String, String> rowMapColumnMapper) {
|
||||
this.tableName = tableName;
|
||||
this.sqlModule = sqlModule;
|
||||
this.rowMapColumnMapper = rowMapColumnMapper;
|
||||
}
|
||||
|
||||
@Comment("设置主键名,update时使用")
|
||||
public NamedTable primary(String primary) {
|
||||
return primary(primary, null);
|
||||
}
|
||||
|
||||
@Comment("设置主键名,并设置默认主键值(主要用于insert)")
|
||||
public NamedTable primary(String primary, Object defaultPrimaryValue) {
|
||||
this.primary = rowMapColumnMapper.apply(primary);
|
||||
this.defaultPrimaryValue = defaultPrimaryValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("拼接where")
|
||||
public Where where() {
|
||||
return where;
|
||||
}
|
||||
|
||||
@Comment("设置单列的值")
|
||||
public NamedTable column(@Comment("列名") String key, @Comment("值") Object value) {
|
||||
this.columns.put(rowMapColumnMapper.apply(key), value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("设置查询的列,如`columns('a','b','c')`")
|
||||
public NamedTable columns(@Comment("各项列") String... columns) {
|
||||
if (columns != null) {
|
||||
for (String column : columns) {
|
||||
column(column);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("设置查询的列,如`columns(['a','b','c'])`")
|
||||
public NamedTable columns(Collection<String> columns) {
|
||||
if (columns != null) {
|
||||
columns.stream().filter(StringUtils::isNotBlank).map(rowMapColumnMapper).forEach(this.fields::add);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("设置查询的列,如`column('a')`")
|
||||
public NamedTable column(String column) {
|
||||
if (StringUtils.isNotBlank(column)) {
|
||||
this.fields.add(this.rowMapColumnMapper.apply(column));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("拼接`order by xxx asc/desc`")
|
||||
public NamedTable orderBy(@Comment("要排序的列") String column, @Comment("`asc`或`desc`") String sort) {
|
||||
this.orders.add(column + " " + sort);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("拼接`order by xxx asc`")
|
||||
public NamedTable orderBy(@Comment("要排序的列") String column) {
|
||||
return orderBy(column, "asc");
|
||||
}
|
||||
|
||||
@Comment("拼接`order by xxx desc`")
|
||||
public NamedTable orderByDesc(@Comment("要排序的列") String column) {
|
||||
return orderBy(column, "desc");
|
||||
}
|
||||
|
||||
@Comment("拼接`group by`")
|
||||
public NamedTable groupBy(@Comment("要分组的列") String... columns) {
|
||||
this.groups.addAll(Arrays.asList(columns));
|
||||
return this;
|
||||
}
|
||||
|
||||
private List<Map.Entry<String, Object>> filterNotBlanks() {
|
||||
return this.columns.entrySet().stream()
|
||||
.filter(it -> StringUtils.isNotBlank(Objects.toString(it.getValue(), "")))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Comment("执行插入,返回主键")
|
||||
public Object insert() {
|
||||
return insert(null);
|
||||
}
|
||||
|
||||
@Comment("执行插入,返回主键")
|
||||
public Object insert(@Comment("各项列和值") Map<String, Object> data) {
|
||||
if (data != null) {
|
||||
data.forEach((key, value) -> this.columns.put(rowMapColumnMapper.apply(key), value));
|
||||
}
|
||||
if (this.defaultPrimaryValue != null && StringUtils.isBlank(Objects.toString(this.columns.getOrDefault(this.primary, "")))) {
|
||||
this.columns.put(this.primary, this.defaultPrimaryValue);
|
||||
}
|
||||
List<Map.Entry<String, Object>> entries = filterNotBlanks();
|
||||
if (entries.isEmpty()) {
|
||||
throw new MagicAPIException("参数不能为空");
|
||||
}
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("insert into ");
|
||||
builder.append(tableName);
|
||||
builder.append("(");
|
||||
builder.append(StringUtils.join(entries.stream().map(Map.Entry::getKey).toArray(), ","));
|
||||
builder.append(") values (");
|
||||
builder.append(StringUtils.join(Collections.nCopies(entries.size(), "?"), ","));
|
||||
builder.append(")");
|
||||
return sqlModule.insert(new BoundSql(builder.toString(), entries.stream().map(Map.Entry::getValue).collect(Collectors.toList()), sqlModule), this.primary);
|
||||
}
|
||||
|
||||
@Comment("执行delete语句(物理删除)")
|
||||
public int delete() {
|
||||
if (where.isEmpty()) {
|
||||
throw new MagicAPIException("delete语句不能没有条件");
|
||||
}
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("delete from ");
|
||||
builder.append(tableName);
|
||||
builder.append(where.getSql());
|
||||
return sqlModule.update(new BoundSql(builder.toString(), where.getParams(), sqlModule));
|
||||
}
|
||||
|
||||
@Comment("保存到表中,当主键有值时则修改,否则插入")
|
||||
public Object save() {
|
||||
return this.save(null);
|
||||
}
|
||||
|
||||
@Comment("保存到表中,当主键有值时则修改,否则插入")
|
||||
public Object save(@Comment("各项列和值") Map<String, Object> data) {
|
||||
if (StringUtils.isBlank(this.primary)) {
|
||||
throw new MagicAPIException("请设置主键");
|
||||
}
|
||||
if (StringUtils.isNotBlank(Objects.toString(this.columns.get(this.primary), "")) || (data != null && StringUtils.isNotBlank(Objects.toString(data.get(this.primary), "")))) {
|
||||
return update(data);
|
||||
}
|
||||
return insert(data);
|
||||
}
|
||||
|
||||
|
||||
@Comment("执行`select`查询")
|
||||
public List<Map<String, Object>> select() {
|
||||
return sqlModule.select(buildSelect());
|
||||
}
|
||||
|
||||
@Comment("执行`selectOne`查询")
|
||||
public Map<String, Object> selectOne() {
|
||||
return sqlModule.selectOne(buildSelect());
|
||||
}
|
||||
|
||||
private BoundSql buildSelect() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("select ");
|
||||
if (this.fields.isEmpty()) {
|
||||
builder.append("*");
|
||||
} else {
|
||||
builder.append(StringUtils.join(this.fields, ","));
|
||||
}
|
||||
builder.append(" from ").append(tableName);
|
||||
List<Object> params = new ArrayList<>();
|
||||
if (!where.isEmpty()) {
|
||||
builder.append(where.getSql());
|
||||
params.addAll(where.getParams());
|
||||
}
|
||||
if (!orders.isEmpty()) {
|
||||
builder.append(" order by ");
|
||||
builder.append(String.join(",", orders));
|
||||
}
|
||||
if (!groups.isEmpty()) {
|
||||
builder.append(" group by ");
|
||||
builder.append(String.join(",", groups));
|
||||
}
|
||||
return new BoundSql(builder.toString(), params, sqlModule);
|
||||
}
|
||||
|
||||
@Comment("执行分页查询")
|
||||
public Object page() {
|
||||
return sqlModule.page(buildSelect());
|
||||
}
|
||||
|
||||
@Comment("执行update语句")
|
||||
public int update() {
|
||||
return update(null);
|
||||
}
|
||||
|
||||
@Comment("执行update语句")
|
||||
public int update(@Comment("各项列和值") Map<String, Object> data) {
|
||||
if (null != data) {
|
||||
data.forEach((key, value) -> this.columns.put(rowMapColumnMapper.apply(key), value));
|
||||
}
|
||||
Object primaryValue = null;
|
||||
if (StringUtils.isNotBlank(this.primary)) {
|
||||
primaryValue = this.columns.remove(this.primary);
|
||||
}
|
||||
List<Map.Entry<String, Object>> entries = filterNotBlanks();
|
||||
if (entries.isEmpty()) {
|
||||
throw new MagicAPIException("要修改的列不能为空");
|
||||
}
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("update ");
|
||||
builder.append(tableName);
|
||||
builder.append(" set ");
|
||||
List<Object> params = new ArrayList<>();
|
||||
for (int i = 0, size = entries.size(); i < size; i++) {
|
||||
Map.Entry<String, Object> entry = entries.get(i);
|
||||
builder.append(entry.getKey()).append(" = ?");
|
||||
params.add(entry.getValue());
|
||||
if (i + 1 < size) {
|
||||
builder.append(",");
|
||||
}
|
||||
}
|
||||
if (!where.isEmpty()) {
|
||||
builder.append(where.getSql());
|
||||
params.addAll(where.getParams());
|
||||
} else if (primaryValue != null) {
|
||||
builder.append(" where ").append(this.primary).append(" = ?");
|
||||
params.add(primaryValue);
|
||||
} else {
|
||||
throw new MagicAPIException("主键值不能为空");
|
||||
}
|
||||
return sqlModule.update(new BoundSql(builder.toString(), params, sqlModule));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
package org.ssssssss.magicapi.modules.table;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.ssssssss.script.annotation.Comment;
|
||||
import org.ssssssss.script.functions.StreamExtension;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class Where {
|
||||
|
||||
private final List<String> tokens = new ArrayList<>();
|
||||
|
||||
private final List<Object> params = new ArrayList<>();
|
||||
|
||||
private final NamedTable namedTable;
|
||||
|
||||
private final boolean needWhere;
|
||||
|
||||
private boolean notNull = false;
|
||||
|
||||
private boolean notBlank = false;
|
||||
|
||||
public Where(NamedTable namedTable) {
|
||||
this(namedTable, true);
|
||||
}
|
||||
|
||||
public Where(NamedTable namedTable, boolean needWhere) {
|
||||
this.namedTable = namedTable;
|
||||
this.needWhere = needWhere;
|
||||
}
|
||||
|
||||
void appendAnd() {
|
||||
remove();
|
||||
tokens.add("and");
|
||||
}
|
||||
|
||||
void appendOr() {
|
||||
remove();
|
||||
tokens.add("or");
|
||||
}
|
||||
|
||||
List<Object> getParams() {
|
||||
return params;
|
||||
}
|
||||
|
||||
void remove() {
|
||||
int size = tokens.size();
|
||||
while (size > 0) {
|
||||
String token = tokens.get(size - 1);
|
||||
if ("and".equalsIgnoreCase(token) || "or".equalsIgnoreCase(token)) {
|
||||
tokens.remove(size - 1);
|
||||
size--;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (size > 0) {
|
||||
String token = tokens.get(0);
|
||||
if ("and".equalsIgnoreCase(token) || "or".equalsIgnoreCase(token)) {
|
||||
tokens.remove(0);
|
||||
size--;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return tokens.isEmpty();
|
||||
}
|
||||
|
||||
void append(String value) {
|
||||
tokens.add(value);
|
||||
}
|
||||
|
||||
String getSql() {
|
||||
remove();
|
||||
if (isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
return (needWhere ? " where " : "") + StringUtils.join(tokens, " ");
|
||||
}
|
||||
|
||||
@Comment("过滤`null`的参数")
|
||||
public Where notNull() {
|
||||
return notNull(true);
|
||||
}
|
||||
|
||||
@Comment("过滤`blank`的参数")
|
||||
public Where notBlank() {
|
||||
return notBlank(true);
|
||||
}
|
||||
|
||||
@Comment("是否过滤`null`的参数")
|
||||
public Where notNull(boolean flag) {
|
||||
this.notNull = flag;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("是否过滤`blank`的参数")
|
||||
public Where notBlank(boolean flag) {
|
||||
this.notNull = flag;
|
||||
return this;
|
||||
}
|
||||
|
||||
boolean filterNullAndBlank(Object value) {
|
||||
if (notNull && value == null) {
|
||||
return false;
|
||||
}
|
||||
return !notBlank || !StringUtils.isEmpty(Objects.toString(value, ""));
|
||||
}
|
||||
|
||||
@Comment("等于`=`,如:`eq('name', '老王') ---> name = '老王'`")
|
||||
public Where eq(@Comment("数据库中的列名") String column, @Comment("值") Object value) {
|
||||
return eq(true, column, value);
|
||||
}
|
||||
|
||||
@Comment("等于`=`,如:`eq('name', '老王') ---> name = '老王'`")
|
||||
public Where eq(@Comment("判断表达式,当为true时拼接条件") boolean condition, @Comment("数据库中的列名") String column, @Comment("值") Object value) {
|
||||
if (condition && filterNullAndBlank(value)) {
|
||||
tokens.add(column);
|
||||
if (value == null) {
|
||||
append(" is null");
|
||||
} else {
|
||||
params.add(value);
|
||||
append(" = ?");
|
||||
}
|
||||
appendAnd();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("不等于`<>`,如:`ne('name', '老王') ---> name <> '老王'`")
|
||||
public Where ne(@Comment("数据库中的列名") String column, @Comment("值") Object value) {
|
||||
return ne(true, column, value);
|
||||
}
|
||||
|
||||
@Comment("不等于`<>`,如:`ne('name', '老王') ---> name <> '老王'`")
|
||||
public Where ne(@Comment("判断表达式,当为true时拼接条件") boolean condition, @Comment("数据库中的列名") String column, @Comment("值") Object value) {
|
||||
if (condition && filterNullAndBlank(value)) {
|
||||
append(column);
|
||||
if (value == null) {
|
||||
append("is not null");
|
||||
} else {
|
||||
params.add(value);
|
||||
append("<> ?");
|
||||
}
|
||||
appendAnd();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private Where append(boolean append, String column, String condition, Object value) {
|
||||
if (append && filterNullAndBlank(value)) {
|
||||
append(column);
|
||||
append(condition);
|
||||
appendAnd();
|
||||
params.add(value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("小于`<`,如:`lt('age', 18) ---> age < 18")
|
||||
public Where lt(@Comment("数据库中的列名") String column, @Comment("值") Object value) {
|
||||
return lt(true, column, value);
|
||||
}
|
||||
|
||||
@Comment("小于`<`,如:`lt('age', 18) ---> age < 18")
|
||||
public Where lt(@Comment("判断表达式,当为true时拼接条件") boolean condition, @Comment("数据库中的列名") String column, @Comment("值") Object value) {
|
||||
return append(condition, column, " < ?", value);
|
||||
}
|
||||
|
||||
@Comment("小于等于`<=`,如:`lte('age', 18) ---> age <= 18")
|
||||
public Where lte(@Comment("数据库中的列名") String column, @Comment("值") Object value) {
|
||||
return lte(true, column, value);
|
||||
}
|
||||
|
||||
@Comment("小于等于`<=`,如:`lte('age', 18) ---> age <= 18")
|
||||
public Where lte(@Comment("判断表达式,当为true时拼接条件") boolean condition, @Comment("数据库中的列名") String column, @Comment("值") Object value) {
|
||||
return append(condition, column, " <= ?", value);
|
||||
}
|
||||
|
||||
@Comment("大于`>`,如:`get('age', 18) ---> age > 18")
|
||||
public Where gt(@Comment("数据库中的列名") String column, @Comment("值") Object value) {
|
||||
return gt(true, column, value);
|
||||
}
|
||||
|
||||
@Comment("大于`>`,如:`get('age', 18) ---> age > 18")
|
||||
public Where gt(@Comment("判断表达式,当为true时拼接条件") boolean condition, @Comment("数据库中的列名") String column, @Comment("值") Object value) {
|
||||
return append(condition, column, " > ?", value);
|
||||
}
|
||||
|
||||
@Comment("大于等于`>=`,如:`get('age', 18) ---> age >= 18")
|
||||
public Where gte(@Comment("数据库中的列名") String column, @Comment("值") Object value) {
|
||||
return gte(true, column, value);
|
||||
}
|
||||
|
||||
@Comment("大于等于`>=`,如:`get('age', 18) ---> age >= 18")
|
||||
public Where gte(@Comment("判断表达式,当为true时拼接条件") boolean condition, @Comment("数据库中的列名") String column, @Comment("值") Object value) {
|
||||
return append(condition, column, " >= ?", value);
|
||||
}
|
||||
|
||||
@Comment("`in`,如:`in('age', [1,2,3]) ---> age in (1,2,3)")
|
||||
public Where in(@Comment("数据库中的列名") String column, @Comment("值") Object value) {
|
||||
return in(true, column, value);
|
||||
}
|
||||
|
||||
@Comment("`in`,如:`in('age', [1,2,3]) ---> age in (1,2,3)")
|
||||
public Where in(@Comment("判断表达式,当为true时拼接条件") boolean condition, @Comment("数据库中的列名") String column, @Comment("值") Object value) {
|
||||
if (condition && value != null) {
|
||||
List<Object> objects = StreamExtension.arrayLikeToList(value);
|
||||
if (objects.size() > 0) {
|
||||
append(column);
|
||||
append(" in (");
|
||||
append(StringUtils.join(",", Collections.nCopies(objects.size(), "?")));
|
||||
append(")");
|
||||
appendAnd();
|
||||
params.addAll(objects);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("`not in`,如:`notIn('age', [1,2,3]) ---> age not in (1,2,3)")
|
||||
public Where notIn(@Comment("数据库中的列名") String column, @Comment("值") Object value) {
|
||||
return notIn(true, column, value);
|
||||
}
|
||||
|
||||
@Comment("`not in`,如:`notIn('age', [1,2,3]) ---> age not in (1,2,3)")
|
||||
public Where notIn(@Comment("判断表达式,当为true时拼接条件") boolean condition, @Comment("数据库中的列名") String column, @Comment("值") Object value) {
|
||||
if (condition && value != null) {
|
||||
List<Object> objects = StreamExtension.arrayLikeToList(value);
|
||||
if (objects.size() > 0) {
|
||||
append(column);
|
||||
append("not in (");
|
||||
append(StringUtils.join(",", Collections.nCopies(objects.size(), "?")));
|
||||
append(")");
|
||||
appendAnd();
|
||||
params.addAll(objects);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("`like`,如:`like('name', '%王%') ---> name like '%王%'")
|
||||
public Where like(@Comment("数据库中的列名") String column, @Comment("值") Object value) {
|
||||
return like(true, column, value);
|
||||
}
|
||||
|
||||
@Comment("`like`,如:`like('name', '%王%') ---> name like '%王%'")
|
||||
public Where like(@Comment("判断表达式,当为true时拼接条件") boolean condition, @Comment("数据库中的列名") String column, @Comment("值") Object value) {
|
||||
return append(condition, column, "like ?", value);
|
||||
}
|
||||
|
||||
@Comment("`not like`,如:`notLike('name', '%王%') ---> name not like '%王%'")
|
||||
public Where notLike(@Comment("数据库中的列名") String column, @Comment("值") Object value) {
|
||||
return notLike(true, column, value);
|
||||
}
|
||||
|
||||
@Comment("`not like` ,如:`notLike('name', '%王%') ---> name not like '%王%'")
|
||||
public Where notLike(@Comment("判断表达式,当为true时拼接条件") boolean condition, @Comment("数据库中的列名") String column, @Comment("值") Object value) {
|
||||
return append(condition, column, "not like ?", value);
|
||||
}
|
||||
|
||||
@Comment("`is null`,如:`isNull('name') ---> name is null")
|
||||
public Where isNull(@Comment("数据库中的列名") String column) {
|
||||
return isNull(true, column);
|
||||
}
|
||||
|
||||
@Comment("`is null`,如:`isNull('name') ---> name is null")
|
||||
public Where isNull(@Comment("判断表达式,当为true时拼接条件") boolean condition, @Comment("数据库中的列名") String column) {
|
||||
if (condition) {
|
||||
append(column);
|
||||
append("is null");
|
||||
appendAnd();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("`is not null`,如:`isNotNull('name') ---> name is not null")
|
||||
public Where isNotNull(@Comment("数据库中的列名") String column) {
|
||||
return isNotNull(true, column);
|
||||
}
|
||||
|
||||
@Comment("`is not null`,如:`isNotNull('name') ---> name is not null")
|
||||
public Where isNotNull(@Comment("判断表达式,当为true时拼接条件") boolean condition, @Comment("数据库中的列名") String column) {
|
||||
if (condition) {
|
||||
append(column);
|
||||
append("is not null");
|
||||
appendAnd();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("拼接`or`")
|
||||
public Where or() {
|
||||
appendOr();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("拼接`and`")
|
||||
public Where and() {
|
||||
appendAnd();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("`and`嵌套,如and(it => it.eq('name','李白').ne('status','正常') --> and (name = '李白' and status <> '正常')")
|
||||
public Where and(Function<Object[], Where> function) {
|
||||
return and(true, function);
|
||||
}
|
||||
|
||||
@Comment("`and`嵌套,如and(it => it.eq('name','李白').ne('status','正常') --> and (name = '李白' and status <> '正常')")
|
||||
public Where and(@Comment("判断表达式,当为true时拼接条件") boolean condition, Function<Object[], Where> function) {
|
||||
if (condition) {
|
||||
Where expr = function.apply(new Object[]{new Where(this.namedTable, false)});
|
||||
this.params.addAll(expr.params);
|
||||
append("(");
|
||||
append(expr.getSql());
|
||||
append(")");
|
||||
appendAnd();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("拼接`order by xxx asc/desc`")
|
||||
public Where orderBy(@Comment("要排序的列") String column, @Comment("`asc`或`desc`") String sort) {
|
||||
this.namedTable.orderBy(column, sort);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("拼接`order by xxx asc`")
|
||||
public Where orderBy(@Comment("要排序的列") String column) {
|
||||
return orderBy(column, "asc");
|
||||
}
|
||||
|
||||
@Comment("拼接`order by xxx desc`")
|
||||
public Where orderByDesc(@Comment("要排序的列") String column) {
|
||||
return orderBy(column, "desc");
|
||||
}
|
||||
|
||||
@Comment("拼接`group by`")
|
||||
public Where groupBy(@Comment("要分组的列") String... columns) {
|
||||
this.namedTable.groupBy(columns);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Comment("保存到表中,当主键有值时则修改,否则插入")
|
||||
public Object save() {
|
||||
return namedTable.save();
|
||||
}
|
||||
|
||||
@Comment("保存到表中,当主键有值时则修改,否则插入")
|
||||
public Object save(@Comment("各项列和值") Map<String, Object> data) {
|
||||
return namedTable.save(data);
|
||||
}
|
||||
|
||||
@Comment("执行插入语句,返回主键")
|
||||
public Object insert() {
|
||||
return namedTable.insert();
|
||||
}
|
||||
|
||||
@Comment("执行插入语句,返回主键")
|
||||
public Object insert(@Comment("各项列和值") Map<String, Object> data) {
|
||||
return namedTable.insert(data);
|
||||
}
|
||||
|
||||
@Comment("执行update语句")
|
||||
public int update() {
|
||||
return namedTable.update();
|
||||
}
|
||||
|
||||
@Comment("执行update语句")
|
||||
public int update(@Comment("各项列和值") Map<String, Object> data) {
|
||||
return namedTable.update(data);
|
||||
}
|
||||
|
||||
@Comment("执行分页查询")
|
||||
public Object page() {
|
||||
return namedTable.page();
|
||||
}
|
||||
|
||||
@Comment("执行select查询")
|
||||
public List<Map<String, Object>> select() {
|
||||
return namedTable.select();
|
||||
}
|
||||
|
||||
@Comment("执行selectOne查询")
|
||||
public Map<String, Object> selectOne() {
|
||||
return namedTable.selectOne();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.ssssssss.magicapi.provider;
|
||||
|
||||
|
||||
import org.ssssssss.magicapi.adapter.Resource;
|
||||
import org.ssssssss.magicapi.model.ApiInfo;
|
||||
|
||||
/**
|
||||
* API存储接口
|
||||
*/
|
||||
public abstract class ApiServiceProvider extends StoreServiceProvider<ApiInfo> {
|
||||
|
||||
|
||||
public ApiServiceProvider(Resource workspace, GroupServiceProvider groupServiceProvider) {
|
||||
super(ApiInfo.class, workspace, groupServiceProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断接口是否存在
|
||||
*/
|
||||
public boolean exists(ApiInfo info) {
|
||||
return infos.values().stream()
|
||||
.anyMatch(it -> info.getGroupId().equals(it.getGroupId()) && (info.getName().equals(it.getName()) || (info.getMethod().equals(it.getMethod()) && info.getPath().equals(it.getPath()))));
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断接口是否存在
|
||||
*/
|
||||
public boolean existsWithoutId(ApiInfo info) {
|
||||
return infos.values().stream()
|
||||
.anyMatch(it -> !info.getId().equals(it.getId()) && info.getGroupId().equals(it.getGroupId()) && (info.getName().equals(it.getName()) || (info.getMethod().equals(it.getMethod()) && info.getPath().equals(it.getPath()))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize(ApiInfo info) {
|
||||
return super.serialize(info);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.ssssssss.magicapi.provider;
|
||||
|
||||
import org.springframework.jdbc.core.ColumnMapRowMapper;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public interface ColumnMapperProvider {
|
||||
|
||||
String name();
|
||||
|
||||
String mapping(String columnName);
|
||||
|
||||
default String unmapping(String name) {
|
||||
return name;
|
||||
}
|
||||
|
||||
default Function<String, String> getRowMapColumnMapper() {
|
||||
return this::unmapping;
|
||||
}
|
||||
|
||||
default ColumnMapRowMapper getColumnMapRowMapper() {
|
||||
return new ColumnMapRowMapper() {
|
||||
@Override
|
||||
protected Map<String, Object> createColumnMap(int columnCount) {
|
||||
return new LinkedHashMap<>(columnCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getColumnKey(String columnName) {
|
||||
return mapping(columnName);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.ssssssss.magicapi.provider;
|
||||
|
||||
import org.ssssssss.magicapi.adapter.Resource;
|
||||
import org.ssssssss.magicapi.model.FunctionInfo;
|
||||
|
||||
public abstract class FunctionServiceProvider extends StoreServiceProvider<FunctionInfo> {
|
||||
|
||||
public FunctionServiceProvider(Resource workspace, GroupServiceProvider groupServiceProvider) {
|
||||
super(FunctionInfo.class, workspace, groupServiceProvider);
|
||||
}
|
||||
|
||||
public boolean exists(FunctionInfo info) {
|
||||
return infos.values().stream()
|
||||
.anyMatch(it -> info.getGroupId().equals(it.getGroupId()) && (info.getName().equals(it.getName()) || info.getPath().equals(it.getPath())));
|
||||
}
|
||||
|
||||
public boolean existsWithoutId(FunctionInfo info) {
|
||||
return infos.values().stream()
|
||||
.anyMatch(it -> !info.getId().equals(it.getId()) && info.getGroupId().equals(it.getGroupId()) && (info.getName().equals(it.getName()) || info.getPath().equals(it.getPath())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize(FunctionInfo info) {
|
||||
return super.serialize(info);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.ssssssss.magicapi.provider;
|
||||
|
||||
import org.ssssssss.magicapi.adapter.Resource;
|
||||
import org.ssssssss.magicapi.model.Group;
|
||||
import org.ssssssss.magicapi.model.TreeNode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public interface GroupServiceProvider {
|
||||
|
||||
/**
|
||||
* 添加分组
|
||||
*/
|
||||
boolean insert(Group group);
|
||||
|
||||
/**
|
||||
* 修改分组
|
||||
*/
|
||||
boolean update(Group group);
|
||||
|
||||
/**
|
||||
* 删除分组
|
||||
*/
|
||||
boolean delete(String groupId);
|
||||
|
||||
/**
|
||||
* 分组是否存在
|
||||
*/
|
||||
boolean exists(Group group);
|
||||
|
||||
/**
|
||||
* 是否有该分组
|
||||
*/
|
||||
boolean containsApiGroup(String groupId);
|
||||
|
||||
Group readGroup(Resource resource);
|
||||
|
||||
/**
|
||||
* 接口分组列表
|
||||
*/
|
||||
TreeNode<Group> apiGroupTree();
|
||||
|
||||
/**
|
||||
* 函数分组列表
|
||||
*/
|
||||
TreeNode<Group> functionGroupTree();
|
||||
|
||||
/**
|
||||
* 分组列表
|
||||
*/
|
||||
List<Group> groupList(String type);
|
||||
|
||||
/**
|
||||
* 根据分组Id获取分组路径
|
||||
*/
|
||||
String getFullPath(String groupId);
|
||||
|
||||
/**
|
||||
* 根据分组Id获取分组名称
|
||||
*/
|
||||
String getFullName(String groupId);
|
||||
|
||||
Resource getGroupResource(String groupId);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.ssssssss.magicapi.provider;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface LanguageProvider {
|
||||
|
||||
/**
|
||||
* 是否支持该语言
|
||||
*/
|
||||
boolean support(String languageName);
|
||||
|
||||
/**
|
||||
* 执行具体脚本
|
||||
*
|
||||
* @param languageName 语言类型
|
||||
* @param script 脚本内容
|
||||
* @param context 当前环境中的变量信息
|
||||
*/
|
||||
Object execute(String languageName, String script, Map<String, Object> context) throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package org.ssssssss.magicapi.provider;
|
||||
|
||||
import org.ssssssss.magicapi.config.MagicModule;
|
||||
import org.ssssssss.magicapi.model.ApiInfo;
|
||||
import org.ssssssss.magicapi.model.FunctionInfo;
|
||||
import org.ssssssss.magicapi.model.Group;
|
||||
import org.ssssssss.magicapi.model.MagicNotify;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* API调用接口
|
||||
*/
|
||||
public interface MagicAPIService extends MagicModule {
|
||||
|
||||
/**
|
||||
* 执行MagicAPI中的接口,原始内容,不包含code以及message信息
|
||||
*
|
||||
* @param method 请求方法
|
||||
* @param path 请求路径
|
||||
* @param context 请求上下文,主要给脚本中使用
|
||||
*/
|
||||
Object execute(String method, String path, Map<String, Object> context);
|
||||
|
||||
/**
|
||||
* 执行MagicAPI中的接口,带code和message信息
|
||||
*
|
||||
* @param method 请求方法
|
||||
* @param path 请求路径
|
||||
* @param context 请求上下文,主要给脚本中使用
|
||||
*/
|
||||
Object call(String method, String path, Map<String, Object> context);
|
||||
|
||||
/**
|
||||
* 保存接口
|
||||
*
|
||||
* @return 保存成功后,返回Id
|
||||
*/
|
||||
String saveApi(ApiInfo apiInfo);
|
||||
|
||||
/**
|
||||
* 获取接口详情
|
||||
* @param id 接口id
|
||||
* @return
|
||||
*/
|
||||
ApiInfo getApiInfo(String id);
|
||||
|
||||
/**
|
||||
* 获取接口列表
|
||||
*/
|
||||
List<ApiInfo> apiList();
|
||||
|
||||
/**
|
||||
* 删除接口
|
||||
*
|
||||
* @param id 接口id
|
||||
*/
|
||||
boolean deleteApi(String id);
|
||||
|
||||
/**
|
||||
* 移动接口
|
||||
*
|
||||
* @param id 接口id
|
||||
* @param groupId 分组id
|
||||
*/
|
||||
boolean moveApi(String id, String groupId);
|
||||
|
||||
/**
|
||||
* 保存函数
|
||||
*
|
||||
* @return 保存成功后,返回Id
|
||||
*/
|
||||
String saveFunction(FunctionInfo functionInfo);
|
||||
|
||||
/**
|
||||
* 获取函数详情
|
||||
* @param id 函数id
|
||||
*/
|
||||
FunctionInfo getFunctionInfo(String id);
|
||||
|
||||
/**
|
||||
* 获取函数列表
|
||||
*/
|
||||
List<FunctionInfo> functionList();
|
||||
|
||||
/**
|
||||
* 删除函数
|
||||
*
|
||||
* @param id 函数id
|
||||
*/
|
||||
boolean deleteFunction(String id);
|
||||
|
||||
/**
|
||||
* 移动函数
|
||||
*
|
||||
* @param id 函数id
|
||||
* @param groupId 分组id
|
||||
*/
|
||||
boolean moveFunction(String id, String groupId);
|
||||
|
||||
|
||||
/**
|
||||
* 创建分组
|
||||
*
|
||||
* @param group 分组信息
|
||||
* @return 创建成功后返回分组id
|
||||
*/
|
||||
String createGroup(Group group);
|
||||
|
||||
/**
|
||||
* 修改分组信息
|
||||
*
|
||||
* @param group 分组信息
|
||||
*/
|
||||
boolean updateGroup(Group group);
|
||||
|
||||
/**
|
||||
* 删除分组
|
||||
*
|
||||
* @param id 分组id
|
||||
*/
|
||||
boolean deleteGroup(String id);
|
||||
|
||||
/**
|
||||
* 查询分组列表
|
||||
*
|
||||
* @param type 分组类型,1 接口分组,2 函数分组
|
||||
*/
|
||||
List<Group> groupList(String type);
|
||||
|
||||
/**
|
||||
* 处理刷新通知
|
||||
*/
|
||||
boolean processNotify(MagicNotify magicNotify);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.ssssssss.magicapi.provider;
|
||||
|
||||
import org.ssssssss.magicapi.model.MagicNotify;
|
||||
|
||||
public interface MagicNotifyService {
|
||||
|
||||
void sendNotify(MagicNotify magicNotify);
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user