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