Merge remote-tracking branch 'origin/feature/1.4.0'
# Conflicts: # README.md
This commit is contained in:
@@ -1,7 +1,14 @@
|
||||
# 1、简介
|
||||
中微子代理(neutrino-proxy)是一个基于netty的、开源的java内网穿透项目。遵循MIT许可,因此您可以对它进行复制、修改、传播并用于任何个人或商业行为。
|
||||
|
||||
# 2、项目结构
|
||||
# 2、运行示例
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
# 3、项目结构
|
||||
- neutrino-proxy
|
||||
- neutrino-core 与代理无关的基础封装
|
||||
- neutrino-proxy-core 与代理相关的公共常量、编解码器
|
||||
@@ -9,13 +16,13 @@
|
||||
- neutrino-proxy-server 代理服务端项目
|
||||
- neutrino-proxy-admin 代理监控项目(基于vue-element-admin开发)
|
||||
|
||||
# 3、运行
|
||||
## 3.1、使用keytool工具生成ssl证书, 若不需要ssl加密可跳过
|
||||
# 4、运行
|
||||
## 4.1、使用keytool工具生成ssl证书, 若不需要ssl加密可跳过
|
||||
```shell
|
||||
keytool -genkey -alias test1 -keyalg RSA -keysize 1024 -validity 3650 -keypass 123456 -storepass 123456 -keystore "./test.jks"
|
||||
```
|
||||
|
||||
## 3.2、修改服务端配置(application.yml)
|
||||
## 4.2、修改服务端配置(application.yml)
|
||||
```yml
|
||||
application:
|
||||
name: neutrino-proxy-server
|
||||
@@ -39,18 +46,18 @@ proxy:
|
||||
key-store-password: 123456
|
||||
key-manager-password: 123456
|
||||
# 证书存放路径,若不想打进jar包,可不带classpath:前缀
|
||||
jks-path: classpath:/test.jks
|
||||
# license配置, 客户端连接时需要用这个进行校验
|
||||
license:
|
||||
# license数为3表示用该license连接的客户端最多可代理3个端口,-1为不限
|
||||
79419a1a8691413aa5e845b9e3e90051: 3
|
||||
9352b1c25f564c81a5677131d7769876: 2
|
||||
jks-path: classpath:/test.jks
|
||||
data:
|
||||
# 数据库配置(不用动,项目自动会自动初始化)
|
||||
sqlite:
|
||||
url: jdbc:sqlite:data.db
|
||||
driver-class: org.sqlite.JDBC
|
||||
```
|
||||
|
||||
## 3.3、启动服务端
|
||||
## 4.3、启动服务端
|
||||
> fun.asgc.neutrino.proxy.server.ProxyServer
|
||||
|
||||
## 3.4、修改客户端配置
|
||||
## 4.4、修改客户端配置
|
||||
```yml
|
||||
application:
|
||||
name: neutrino-proxy-client
|
||||
@@ -76,35 +83,16 @@ proxy:
|
||||
server-port: 9000
|
||||
# 是否启用ssl,启用则必须配置ssl相关参数
|
||||
ssl-enable: false
|
||||
# 获取license提示间隔(秒)
|
||||
obtain-license-interval: 5
|
||||
```
|
||||
|
||||
## 3.5、准备代理信息配置文件 config.json
|
||||
```
|
||||
{
|
||||
"environment": "我的Mac",
|
||||
"clientKey": "79419a1a8691413aa5e845b9e3e90051", # 对应服务端配置license中的key
|
||||
"proxy": [
|
||||
{
|
||||
"serverPort": 9100, # 外网服务器对外暴露的端口
|
||||
"clientInfo": "127.0.0.1:3306" # 需要代理的本地端口(mysql)
|
||||
},
|
||||
{
|
||||
"serverPort": 9101, # 外网服务器对外暴露的端口
|
||||
"clientInfo": "rm-xxxx.mysql.rds.aliyuncs.com:3306" # 代理外网端口本身无意义,仅供测试
|
||||
},
|
||||
{
|
||||
"serverPort": 9102, # 外网服务器对外暴露的端口
|
||||
"clientInfo": "127.0.0.1:8080" # 需要代理的本地端口(http)
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 3.6、启动客户端
|
||||
## 4.6、启动客户端
|
||||
> fun.asgc.neutrino.proxy.client.ProxyClient
|
||||
默认情况下,客户端会加载当前目录下的config.json文件作为代理配置,可通过命令行参数指定,如:java -jar neutrino-proxy-client.jar /xxx/proxy.json
|
||||
默认情况下,客户端会加载当前目录下的.neutrino-proxy.license里的license,可通过命令行参数指定,如:java -jar neutrino-proxy-client.jar license=xxx
|
||||
若启动参数未指定license,且是首次启动(当前目录下未缓存license),则需要根据命令行提示输入正确的license, 输入完成后完成连接,可在服务端管理页面控制端口转发,参见[2、运行示例](#2)
|
||||
|
||||
# 4、未来迭代方向
|
||||
# 5、未来迭代方向
|
||||
- 优化代码、增强稳定性
|
||||
- 服务端增加管理页面,提供报表、授权、限流等功能
|
||||
- 从项目中分离、孵化出另一个开源项目(neutrino-framework)
|
||||
@@ -112,12 +100,12 @@ proxy:
|
||||
# 5、技术文档
|
||||
- [Aop](./docs/Aop.MD)
|
||||
|
||||
# 6、联系我们
|
||||
# 7、联系我们
|
||||
- 微信: yuyunshize
|
||||
- Gitee(主更): https://gitee.com/asgc/neutrino-proxy
|
||||
- Github: https://github.com/aoshiguchen/neutrino-proxy
|
||||
|
||||
# 7、特别鸣谢
|
||||
* [JetBrains](https://www.jetbrains.com?from=neutrino-proxy)
|
||||
# 8、特别鸣谢
|
||||
* [JetBrains](https://www.jetbrains.com?from=RedisFront)
|
||||
|
||||

|
||||
Binary file not shown.
|
After Width: | Height: | Size: 521 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 276 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 216 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 425 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 202 KiB |
@@ -24,10 +24,14 @@ package fun.asgc.neutrino.core.aop.compiler;
|
||||
import com.google.common.collect.Lists;
|
||||
import fun.asgc.neutrino.core.base.GlobalConfig;
|
||||
import fun.asgc.neutrino.core.util.CollectionUtil;
|
||||
import fun.asgc.neutrino.core.util.FileUtil;
|
||||
import fun.asgc.neutrino.core.util.SystemUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.tools.*;
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -43,6 +47,7 @@ public class AsgcCompiler {
|
||||
private final DiagnosticCollector<JavaFileObject> collector;
|
||||
private final StandardJavaFileManager standardJavaFileManager;
|
||||
private final List<String> options = new ArrayList<>();
|
||||
private final List<String> defaultClassPathList = new ArrayList<>();
|
||||
private final List<String> classpathList = new ArrayList<>();
|
||||
private final Collection<JavaFileObject> compilationUnits = new ArrayList<JavaFileObject>();
|
||||
private boolean isSaveSourceCodeFile;
|
||||
@@ -65,7 +70,7 @@ public class AsgcCompiler {
|
||||
this.standardJavaFileManager = javaCompiler.getStandardFileManager(collector, null, null);
|
||||
this.isSaveClassFile = false;
|
||||
this.generatorCodeSavePath = GlobalConfig.getGeneratorCodeSavePath();
|
||||
this.dynamicClassLoader = new DynamicClassLoader(classLoader);
|
||||
this.dynamicClassLoader = new DynamicClassLoader(classLoader, this);
|
||||
|
||||
addOption("-Xlint:unchecked");
|
||||
addOption("-implicit:class");
|
||||
@@ -74,6 +79,9 @@ public class AsgcCompiler {
|
||||
}
|
||||
|
||||
public void addClasspath(String classpath) {
|
||||
if (this.classpathList.contains(classpath)) {
|
||||
return;
|
||||
}
|
||||
this.classpathList.add(classpath);
|
||||
}
|
||||
|
||||
@@ -91,9 +99,10 @@ public class AsgcCompiler {
|
||||
|
||||
private List<String> getOptions() {
|
||||
List<String> list = Lists.newArrayList(options);
|
||||
if (!CollectionUtil.isEmpty(classpathList)) {
|
||||
List<String> cp = getClasspathList();
|
||||
if (!CollectionUtil.isEmpty(cp)) {
|
||||
list.add("-classpath");
|
||||
list.add(classpathList.stream().collect(Collectors.joining(File.pathSeparator)));
|
||||
list.add(cp.stream().collect(Collectors.joining(File.pathSeparator)));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
@@ -123,7 +132,15 @@ public class AsgcCompiler {
|
||||
public Class<?> compile(String pkg, String className, String sourceCode) throws ClassNotFoundException {
|
||||
log.info("options:" + getOptions());
|
||||
JavaFileManager javaFileManager = new DynamicJavaFileManager(standardJavaFileManager, dynamicClassLoader);
|
||||
Boolean result = javaCompiler.getTask(null, javaFileManager, collector, getOptions(), null, Lists.newArrayList(new StringSource(className, sourceCode))).call();
|
||||
Iterable<? extends JavaFileObject> compilationUnits = Lists.newArrayList(new StringSource(className, sourceCode));
|
||||
if (GlobalConfig.isSaveGeneratorCode()) {
|
||||
javaFileManager = standardJavaFileManager;
|
||||
File file = FileUtil.save(GlobalConfig.getGeneratorCodeSavePath() + pkg.replaceAll("\\.", "/"), className + ".java", sourceCode);
|
||||
compilationUnits = standardJavaFileManager.getJavaFileObjects(file);
|
||||
addClasspath(GlobalConfig.getGeneratorCodeSavePath());
|
||||
}
|
||||
|
||||
Boolean result = javaCompiler.getTask(null, javaFileManager, collector, getOptions(), null, compilationUnits).call();
|
||||
if (!result || collector.getDiagnostics().size() > 0) {
|
||||
if (!result || collector.getDiagnostics().size() > 0) {
|
||||
for (Diagnostic<? extends JavaFileObject> diagnostic : collector.getDiagnostics()) {
|
||||
@@ -172,11 +189,61 @@ public class AsgcCompiler {
|
||||
private void log() {
|
||||
List<String> warnings = getWarnings();
|
||||
List<String> errors = getErrors();
|
||||
if (!CollectionUtil.isEmpty(warnings)) {
|
||||
log.warn(warnings.stream().collect(Collectors.joining()));
|
||||
}
|
||||
// if (!CollectionUtil.isEmpty(warnings)) {
|
||||
// log.warn(warnings.stream().collect(Collectors.joining()));
|
||||
// }
|
||||
if (!CollectionUtil.isEmpty(errors)) {
|
||||
log.warn(errors.stream().collect(Collectors.joining()));
|
||||
}
|
||||
}
|
||||
|
||||
private URLClassLoader getURLClassLoader() {
|
||||
ClassLoader ret = Thread.currentThread().getContextClassLoader();
|
||||
if (null == ret) {
|
||||
ret = AsgcCompiler.class.getClassLoader();
|
||||
}
|
||||
return (ret instanceof URLClassLoader) ? (URLClassLoader)ret : null;
|
||||
}
|
||||
|
||||
public List<String> getClasspathList() {
|
||||
List<String> classpathList = new ArrayList<>();
|
||||
List<String> defaultClasspathList = getDefaultClasspathList();
|
||||
List<String> customClasspathList = this.classpathList;
|
||||
if (!CollectionUtil.isEmpty(defaultClasspathList)) {
|
||||
classpathList.addAll(defaultClasspathList);
|
||||
}
|
||||
if (!CollectionUtil.isEmpty(customClasspathList)) {
|
||||
classpathList.addAll(customClasspathList);
|
||||
}
|
||||
return classpathList;
|
||||
}
|
||||
|
||||
private synchronized List<String> getDefaultClasspathList() {
|
||||
if (!CollectionUtil.isEmpty(defaultClassPathList)) {
|
||||
return defaultClassPathList;
|
||||
}
|
||||
URLClassLoader classLoader = getURLClassLoader();
|
||||
if (null == classLoader) {
|
||||
return defaultClassPathList;
|
||||
}
|
||||
|
||||
boolean isWindows = SystemUtil.isWindows();
|
||||
for (URL url : classLoader.getURLs()) {
|
||||
String path = url.getFile();
|
||||
|
||||
// 如果是 windows 系统,去除前缀字符 '/'
|
||||
if (isWindows && path.startsWith("/")) {
|
||||
path = path.substring(1);
|
||||
}
|
||||
|
||||
// 去除后缀字符 '/'
|
||||
if (path.length() > 1 && (path.endsWith("/") || path.endsWith(File.separator))) {
|
||||
path = path.substring(0, path.length() - 1);
|
||||
}
|
||||
|
||||
defaultClassPathList.add(path);
|
||||
}
|
||||
|
||||
return defaultClassPathList;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ import java.net.URI;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: wen.y
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/8/25
|
||||
*/
|
||||
public class CustomJavaFileObject implements JavaFileObject {
|
||||
|
||||
+131
-6
@@ -21,33 +21,158 @@
|
||||
*/
|
||||
package fun.asgc.neutrino.core.aop.compiler;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import fun.asgc.neutrino.core.util.ArrayUtil;
|
||||
import fun.asgc.neutrino.core.util.CollectionUtil;
|
||||
import fun.asgc.neutrino.core.util.FileUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.IOException;
|
||||
import java.net.JarURLConnection;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.*;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: wen.y
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/8/25
|
||||
*/
|
||||
public class DynamicClassLoader extends ClassLoader {
|
||||
private final Map<String, MemoryByteCode> byteCodes = new HashMap<>();
|
||||
private AsgcCompiler compiler;
|
||||
private Map<String, Class<?>> classMap = new HashMap<>();
|
||||
|
||||
public DynamicClassLoader(ClassLoader classLoader) {
|
||||
super(classLoader);
|
||||
}
|
||||
|
||||
public DynamicClassLoader(ClassLoader classLoader, AsgcCompiler compiler) {
|
||||
super(classLoader);
|
||||
this.compiler = compiler;
|
||||
}
|
||||
|
||||
public void registerCompiledSource(MemoryByteCode byteCode) {
|
||||
byteCodes.put(byteCode.getClassName(), byteCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||
if (classMap.containsKey(name)) {
|
||||
return classMap.get(name);
|
||||
}
|
||||
MemoryByteCode byteCode = byteCodes.get(name);
|
||||
if (null == byteCode) {
|
||||
return super.findClass(name);
|
||||
if (null != byteCode) {
|
||||
return super.defineClass(name, byteCode.getByteCode(), 0, byteCode.getByteCode().length);
|
||||
}
|
||||
if (null != this.compiler) {
|
||||
Class<?> ret = doFindClass(name, compiler.getClasspathList());
|
||||
if (null != ret) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return super.findClass(name);
|
||||
}
|
||||
|
||||
private Class<?> doFindClass(String name, List<String> classpathList) throws ClassNotFoundException {
|
||||
if (CollectionUtil.isEmpty(classpathList)) {
|
||||
return null;
|
||||
}
|
||||
String packageName = "";
|
||||
if (name.lastIndexOf(".") != -1) {
|
||||
packageName = name.substring(0, name.lastIndexOf("."));
|
||||
}
|
||||
for (String path : classpathList) {
|
||||
try {
|
||||
URL url = new URL("file:" + path);
|
||||
if (path.endsWith(".jar")) {
|
||||
url = new URL("jar:file:" + path + "!/");
|
||||
}
|
||||
Set<Class<?>> classSet = scan(packageName, url);
|
||||
if (CollectionUtil.isEmpty(classSet)) {
|
||||
continue;
|
||||
}
|
||||
Optional<Class<?>> classOptional = classSet.stream().filter(c -> c.getName().equals(name)).findFirst();
|
||||
if (classOptional.isPresent()) {
|
||||
return classOptional.get();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Set<Class<?>> scan(String packageName, URL url) throws IOException, ClassNotFoundException, URISyntaxException {
|
||||
Set<Class<?>> result = new HashSet<>();
|
||||
if (null == url) {
|
||||
return result;
|
||||
}
|
||||
String packagePath = packageName.replace(".", "/");
|
||||
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url}, Thread.currentThread().getContextClassLoader());
|
||||
String protocol = url.getProtocol();
|
||||
if ("jar".equals(protocol)) {
|
||||
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
|
||||
JarFile jarFile = jarURLConnection.getJarFile();
|
||||
Enumeration<JarEntry> entries = jarFile.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry jarEntry = entries.nextElement();
|
||||
String name = jarEntry.getName();
|
||||
int index = name.indexOf(packagePath);
|
||||
if (index != -1 && name.endsWith(".class")) {
|
||||
String replace = name.substring(index, name.length() - 6).replace("/", ".");
|
||||
Class clazz = urlClassLoader.loadClass(replace);
|
||||
result.add(clazz);
|
||||
}
|
||||
}
|
||||
} else if ("file".endsWith(protocol)) {
|
||||
String path = url.getPath();
|
||||
String targetPath = path + "/" + packagePath;
|
||||
addClasses(targetPath, result, packageName);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private synchronized void addClasses(String path, Set<Class<?>> classes, String packageName) throws ClassNotFoundException, URISyntaxException {
|
||||
File[] files = new File(path).listFiles(new FileFilter() {
|
||||
@Override
|
||||
public boolean accept(File file) {
|
||||
return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
|
||||
}
|
||||
});
|
||||
if (ArrayUtil.isEmpty(files)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return super.defineClass(name, byteCode.getByteCode(), 0, byteCode.getByteCode().length);
|
||||
for (File file : files) {
|
||||
String fileName = file.getName();
|
||||
if (file.isFile()) {
|
||||
String className = fileName.substring(0, fileName.lastIndexOf("."));
|
||||
String fullClassName = packageName + "." + className;
|
||||
Class clazz = null;
|
||||
try {
|
||||
clazz = Thread.currentThread().getContextClassLoader().loadClass(fullClassName);
|
||||
} catch (ClassNotFoundException e) {
|
||||
if (this.classMap.containsKey(fullClassName)) {
|
||||
clazz = this.classMap.get(fileName);
|
||||
} else {
|
||||
byte[] byteCode = FileUtil.readBytes(file);
|
||||
clazz = super.defineClass(fullClassName, byteCode, 0, byteCode.length);
|
||||
this.classMap.put(fullClassName, clazz);
|
||||
}
|
||||
}
|
||||
if (null != clazz) {
|
||||
classes.add(clazz);
|
||||
}
|
||||
} else {
|
||||
String subPackagePath = path + "/" + fileName;
|
||||
String subPackageName = packageName + "." + fileName;
|
||||
addClasses(subPackagePath, classes, subPackageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Class<?>> getClasses() throws ClassNotFoundException {
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.core.aop.compiler;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/8/26
|
||||
*/
|
||||
public @interface DynamicCompile {
|
||||
|
||||
}
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.core.aop.compiler;
|
||||
|
||||
import javax.annotation.processing.*;
|
||||
import javax.lang.model.SourceVersion;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.tools.Diagnostic;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/8/26
|
||||
*/
|
||||
@SupportedAnnotationTypes("DynamicCompile")
|
||||
@SupportedSourceVersion(SourceVersion.RELEASE_8)
|
||||
public class DynamicCompileProcess extends AbstractProcessor {
|
||||
|
||||
// @Override
|
||||
// public synchronized void init(ProcessingEnvironment processingEnv) {
|
||||
// super.init(processingEnv);
|
||||
// }
|
||||
|
||||
@Override
|
||||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
||||
processingEnv.getMessager().printMessage( Diagnostic.Kind.ERROR, "---------Hello World!");
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -39,6 +39,7 @@ public class MemoryByteCode extends SimpleJavaFileObject {
|
||||
private static final String CLASS_FILE_SUFFIX = ".class";
|
||||
|
||||
private ByteArrayOutputStream byteArrayOutputStream;
|
||||
private byte[] byteCode;
|
||||
|
||||
public MemoryByteCode(String className) {
|
||||
super(URI.create("byte:///" + className.replace(PKG_SEPARATOR, DIR_SEPARATOR)
|
||||
@@ -51,6 +52,12 @@ public class MemoryByteCode extends SimpleJavaFileObject {
|
||||
this.byteArrayOutputStream = byteArrayOutputStream;
|
||||
}
|
||||
|
||||
public MemoryByteCode(String className, byte[] byteCode)
|
||||
throws URISyntaxException {
|
||||
this(className);
|
||||
this.byteCode = byteCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream openOutputStream() throws IOException {
|
||||
if (byteArrayOutputStream == null) {
|
||||
@@ -60,7 +67,7 @@ public class MemoryByteCode extends SimpleJavaFileObject {
|
||||
}
|
||||
|
||||
public byte[] getByteCode() {
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
return null == byteCode ? byteArrayOutputStream.toByteArray() : byteCode;
|
||||
}
|
||||
|
||||
public String getClassName() {
|
||||
|
||||
-2
@@ -21,8 +21,6 @@
|
||||
*/
|
||||
package fun.asgc.neutrino.core.aop.compiler;
|
||||
|
||||
import fun.asgc.neutrino.core.util.CollectionUtil;
|
||||
|
||||
import javax.tools.JavaFileObject;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
*/
|
||||
package fun.asgc.neutrino.core.aop.proxy;
|
||||
|
||||
import fun.asgc.neutrino.core.aop.compiler.AsgcCompiler;
|
||||
import fun.asgc.neutrino.core.base.GlobalConfig;
|
||||
import fun.asgc.neutrino.core.util.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -38,7 +39,7 @@ public class AsgcProxyFactory implements ProxyFactory {
|
||||
private static final String SYMBOLIC = "AsgcProxy$$";
|
||||
private static final String classNameTemplate = "%s" + SYMBOLIC + "%s";
|
||||
private static AtomicLong proxyClassCounter = new AtomicLong();
|
||||
private ProxyCompiler compiler = new ProxyCompiler();
|
||||
private AsgcCompiler compiler = new AsgcCompiler();
|
||||
private ProxyClassLoader classLoader = new ProxyClassLoader();
|
||||
|
||||
@Override
|
||||
@@ -85,20 +86,11 @@ public class AsgcProxyFactory implements ProxyFactory {
|
||||
if (GlobalConfig.isPrintGeneratorCode()) {
|
||||
log.debug("类:{} 的代理类源码:\n{}", targetType.getName(), sourceCode);
|
||||
}
|
||||
Class<P> retClass = compile(proxyClass);
|
||||
Class<P> retClass = (Class<P>)compiler.compile(proxyClass.getPkg(), proxyClass.getName(), proxyClass.getSourceCode());
|
||||
P obj = retClass.newInstance();
|
||||
return obj;
|
||||
}
|
||||
|
||||
private <T> Class<T> compile(ProxyClass proxyClass) throws ClassNotFoundException {
|
||||
if (SystemUtil.isStartupFromJar() || GlobalConfig.isSaveGeneratorCode()) {
|
||||
compiler.compileToFile(proxyClass);
|
||||
return (Class<T>)classLoader.loadProxyClass(proxyClass);
|
||||
}
|
||||
compiler.compile(proxyClass);
|
||||
return (Class<T>)classLoader.loadProxyClass(proxyClass);
|
||||
}
|
||||
|
||||
private String generateClassName(Class<?> clazz) {
|
||||
return String.format(classNameTemplate, clazz.getSimpleName(), proxyClassCounter.incrementAndGet());
|
||||
}
|
||||
|
||||
@@ -1,294 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.core.aop.proxy;
|
||||
|
||||
import fun.asgc.neutrino.core.base.GlobalConfig;
|
||||
import fun.asgc.neutrino.core.util.FileUtil;
|
||||
import fun.asgc.neutrino.core.util.SystemUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.tools.*;
|
||||
import java.io.*;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/24
|
||||
*/
|
||||
@Slf4j
|
||||
public class ProxyCompiler {
|
||||
/**
|
||||
* 收集编译过程信息
|
||||
*/
|
||||
private static DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
|
||||
|
||||
protected volatile List<String> options = null;
|
||||
protected volatile boolean isUnpack = false;
|
||||
|
||||
|
||||
protected List<String> getOptions() {
|
||||
|
||||
|
||||
if (options != null) {
|
||||
return options;
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
if (options != null) {
|
||||
return options;
|
||||
}
|
||||
|
||||
List<String> ret = new ArrayList<>();
|
||||
ret.add("-target");
|
||||
ret.add("1.8");
|
||||
|
||||
String cp = getClassPath();
|
||||
if (cp != null && cp.trim().length() != 0) {
|
||||
ret.add("-classpath");
|
||||
ret.add(cp);
|
||||
}
|
||||
|
||||
options = ret;
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容 tomcat 丢失 class path,否则无法编译
|
||||
*/
|
||||
protected String getClassPath() {
|
||||
if (!SystemUtil.isStartupFromJar()) {
|
||||
URLClassLoader classLoader = getURLClassLoader();
|
||||
if (classLoader == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
boolean isWindows = SystemUtil.isWindows();
|
||||
StringBuilder ret = new StringBuilder();
|
||||
for (URL url : classLoader.getURLs()) {
|
||||
if (index++ > 0) {
|
||||
ret.append(File.pathSeparator);
|
||||
}
|
||||
|
||||
String path = url.getFile();
|
||||
|
||||
// 如果是 windows 系统,去除前缀字符 '/'
|
||||
if (isWindows && path.startsWith("/")) {
|
||||
path = path.substring(1);
|
||||
}
|
||||
|
||||
// 去除后缀字符 '/'
|
||||
if (path.length() > 1 && (path.endsWith("/") || path.endsWith(File.separator))) {
|
||||
path = path.substring(0, path.length() - 1);
|
||||
}
|
||||
ret.append(path);
|
||||
}
|
||||
|
||||
return ret.toString();
|
||||
}
|
||||
List<String> list = new ArrayList<>();
|
||||
File file = new File(GlobalConfig.getGeneratorCodeSavePath() + "BOOT-INF/lib/");
|
||||
if (file.exists()) {
|
||||
File[] files = file.listFiles();
|
||||
for (File f : files) {
|
||||
list.add(f.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
list.add(GlobalConfig.getGeneratorCodeSavePath() + "BOOT-INF/classes/");
|
||||
return list.stream().collect(Collectors.joining(File.pathSeparator));
|
||||
}
|
||||
|
||||
protected URLClassLoader getURLClassLoader() {
|
||||
ClassLoader ret = Thread.currentThread().getContextClassLoader();
|
||||
if (ret == null) {
|
||||
ret = ProxyCompiler.class.getClassLoader();
|
||||
}
|
||||
return (ret instanceof URLClassLoader) ? (URLClassLoader)ret : null;
|
||||
}
|
||||
|
||||
public void compile(ProxyClass proxyClass) {
|
||||
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
|
||||
if (compiler == null) {
|
||||
throw new RuntimeException("Can not get javax.tools.JavaCompiler, check whether \"tools.jar\" is in the environment variable CLASSPATH \n" +
|
||||
"Visit https://jfinal.com/doc/4-8 for details \n");
|
||||
}
|
||||
|
||||
DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();
|
||||
try (MyJavaFileManager javaFileManager = new MyJavaFileManager(compiler.getStandardFileManager(collector, null, null))) {
|
||||
|
||||
MyJavaFileObject javaFileObject = new MyJavaFileObject(proxyClass.getName(), proxyClass.getSourceCode());
|
||||
Boolean result = compiler.getTask(null, javaFileManager, collector, getOptions(), null, Arrays.asList(javaFileObject)).call();
|
||||
outputCompileError(result, collector);
|
||||
|
||||
Map<String, byte[]> ret = new HashMap<>();
|
||||
for (Map.Entry<String, MyJavaFileObject> e : javaFileManager.fileObjects.entrySet()) {
|
||||
ret.put(e.getKey(), e.getValue().getByteCode());
|
||||
}
|
||||
|
||||
proxyClass.setByteCode(ret);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void compileToFile(ProxyClass proxyClass) {
|
||||
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
|
||||
if (compiler == null) {
|
||||
throw new RuntimeException("Can not get javax.tools.JavaCompiler, check whether \"tools.jar\" is in the environment variable CLASSPATH \n" +
|
||||
"Visit https://jfinal.com/doc/4-8 for details \n");
|
||||
}
|
||||
unpack();
|
||||
|
||||
File file = FileUtil.save(GlobalConfig.getGeneratorCodeSavePath() + proxyClass.getPkg().replaceAll("\\.", "/"), proxyClass.getName() + ".java", proxyClass.getSourceCode());
|
||||
StandardJavaFileManager standardFileManager = compiler.getStandardFileManager(diagnostics, null, null);
|
||||
|
||||
Iterable<? extends JavaFileObject> iterable = standardFileManager.getJavaFileObjects(file);
|
||||
// 创建一个编译任务
|
||||
JavaCompiler.CompilationTask task = compiler.getTask(null, standardFileManager, diagnostics, getOptions(), null, iterable);
|
||||
//JavaCompiler.CompilationTask 实现了 Callable 接口
|
||||
Boolean result = task.call();
|
||||
printLog(result, file);
|
||||
}
|
||||
|
||||
private synchronized void unpack() {
|
||||
if (!SystemUtil.isStartupFromJar()) {
|
||||
return;
|
||||
}
|
||||
if (isUnpack) {
|
||||
return;
|
||||
}
|
||||
isUnpack = true;
|
||||
try {
|
||||
File file1 = new File(GlobalConfig.getGeneratorCodeSavePath() + "BOOT-INF/lib/");
|
||||
File file2 = new File(GlobalConfig.getGeneratorCodeSavePath() + "BOOT-INF/classes/");
|
||||
if (file1.exists() && file2.exists()) {
|
||||
return;
|
||||
}
|
||||
log.info("解压jar:{} 到:{}", SystemUtil.getCurrentJarFilePath(), GlobalConfig.getGeneratorCodeSavePath());
|
||||
FileUtil.unzipJar(GlobalConfig.getGeneratorCodeSavePath(), SystemUtil.getCurrentJarFilePath());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void printLog(Boolean result, File ...files){
|
||||
if (!result) {
|
||||
StringJoiner rs = new StringJoiner(System.getProperty("line.separator"));
|
||||
for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
|
||||
rs.add(String.format("%s:%s[line %d column %d]-->%s%n", diagnostic.getKind(), diagnostic.getSource(), diagnostic.getLineNumber(),
|
||||
diagnostic.getColumnNumber(),
|
||||
diagnostic.getMessage(null)));
|
||||
}
|
||||
log.error("编译失败,原因:{}", rs.toString());
|
||||
} else {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Arrays.stream(files).forEach(file -> {
|
||||
sb.append(file.getName());
|
||||
sb.append(";");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected void outputCompileError(Boolean result, DiagnosticCollector<JavaFileObject> collector) {
|
||||
if (! result) {
|
||||
collector.getDiagnostics().forEach(item -> log.error(item.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
public ProxyCompiler setCompileOptions(List<String> options) {
|
||||
Objects.requireNonNull(options, "options can not be null");
|
||||
this.options = options;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProxyCompiler addCompileOption(String option) {
|
||||
Objects.requireNonNull(option, "option can not be null");
|
||||
options.add(option);
|
||||
return this;
|
||||
}
|
||||
|
||||
public static class MyJavaFileObject extends SimpleJavaFileObject {
|
||||
|
||||
private String source;
|
||||
private ByteArrayOutputStream outPutStream;
|
||||
|
||||
public MyJavaFileObject(String name, String source) {
|
||||
super(URI.create("String:///" + name + JavaFileObject.Kind.SOURCE.extension), JavaFileObject.Kind.SOURCE);
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public MyJavaFileObject(String name, JavaFileObject.Kind kind) {
|
||||
super(URI.create("String:///" + name + kind.extension), kind);
|
||||
source = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
|
||||
if (source == null) {
|
||||
throw new IllegalStateException("source field can not be null");
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream openOutputStream() throws IOException {
|
||||
outPutStream = new ByteArrayOutputStream();
|
||||
return outPutStream;
|
||||
}
|
||||
|
||||
public byte[] getByteCode() {
|
||||
return outPutStream.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
public static class MyJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
|
||||
|
||||
public Map<String, MyJavaFileObject> fileObjects = new HashMap<>();
|
||||
|
||||
public MyJavaFileManager(JavaFileManager fileManager) {
|
||||
super(fileManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String qualifiedClassName, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
|
||||
MyJavaFileObject javaFileObject = new MyJavaFileObject(qualifiedClassName, kind);
|
||||
fileObjects.put(qualifiedClassName, javaFileObject);
|
||||
return javaFileObject;
|
||||
}
|
||||
|
||||
// 是否在编译时依赖另一个类的情况下用到本方法 ?
|
||||
@Override
|
||||
public JavaFileObject getJavaFileForInput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind) throws IOException {
|
||||
JavaFileObject javaFileObject = fileObjects.get(className);
|
||||
if (javaFileObject == null) {
|
||||
javaFileObject = super.getJavaFileForInput(location, className, kind);
|
||||
}
|
||||
return javaFileObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,39 +128,50 @@ public class ClassUtil {
|
||||
}
|
||||
log.info("开始进行类扫描,扫描包:{}", packageName);
|
||||
Set<Class<?>> classes = new HashSet<>();
|
||||
String packagePath = packageName.replace(".", "/");
|
||||
Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources("");
|
||||
while (resources.hasMoreElements()) {
|
||||
URL url = resources.nextElement();
|
||||
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url}, Thread.currentThread().getContextClassLoader());
|
||||
log.info(url.toString());
|
||||
String protocol = url.getProtocol();
|
||||
if ("jar".equals(protocol)) {
|
||||
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
|
||||
JarFile jarFile = jarURLConnection.getJarFile();
|
||||
Enumeration<JarEntry> entries = jarFile.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry jarEntry = entries.nextElement();
|
||||
String name = jarEntry.getName();
|
||||
int index = name.indexOf(packagePath);
|
||||
if (index != -1 && name.endsWith(".class")) {
|
||||
String replace = name.substring(index, name.length() - 6).replace("/", ".");
|
||||
Class clazz = urlClassLoader.loadClass(replace);
|
||||
log.debug("scan class {}", clazz.getName());
|
||||
classes.add(clazz);
|
||||
}
|
||||
}
|
||||
} else if ("file".endsWith(protocol)) {
|
||||
String path = url.getPath();
|
||||
String targetPath = path + "/" + packagePath;
|
||||
addClasses(targetPath, classes, packageName);
|
||||
}
|
||||
classes.addAll(scan(packageName, resources.nextElement()));
|
||||
}
|
||||
if (SystemUtil.isStartupFromJar()) {
|
||||
classes.addAll(scan(packageName, new URL("jar:file:" + SystemUtil.getCurrentJarFilePath() + "!/")));
|
||||
}
|
||||
log.info("扫描完毕,包:{}下一共有:{}个类", packageName, classes.size());
|
||||
classesCache.set(packageName, classes);
|
||||
return classes;
|
||||
}
|
||||
|
||||
public static Set<Class<?>> scan(String packageName, URL url) throws IOException, ClassNotFoundException {
|
||||
Set<Class<?>> result = new HashSet<>();
|
||||
if (null == url) {
|
||||
return result;
|
||||
}
|
||||
String packagePath = packageName.replace(".", "/");
|
||||
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url}, Thread.currentThread().getContextClassLoader());
|
||||
log.info(url.toString());
|
||||
String protocol = url.getProtocol();
|
||||
if ("jar".equals(protocol)) {
|
||||
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
|
||||
JarFile jarFile = jarURLConnection.getJarFile();
|
||||
Enumeration<JarEntry> entries = jarFile.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry jarEntry = entries.nextElement();
|
||||
String name = jarEntry.getName();
|
||||
int index = name.indexOf(packagePath);
|
||||
if (index != -1 && name.endsWith(".class")) {
|
||||
String replace = name.substring(index, name.length() - 6).replace("/", ".");
|
||||
Class clazz = urlClassLoader.loadClass(replace);
|
||||
log.debug("scan class {}", clazz.getName());
|
||||
result.add(clazz);
|
||||
}
|
||||
}
|
||||
} else if ("file".endsWith(protocol)) {
|
||||
String path = url.getPath();
|
||||
String targetPath = path + "/" + packagePath;
|
||||
addClasses(targetPath, result, packageName);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void addClasses(String path, Set<Class<?>> classes, String packageName) throws ClassNotFoundException {
|
||||
File[] files = new File(path).listFiles(new FileFilter() {
|
||||
@Override
|
||||
|
||||
@@ -111,18 +111,30 @@ public class FileUtil {
|
||||
*/
|
||||
public static byte[] readBytes(String path) {
|
||||
try (InputStream in = getInputStream(path)){
|
||||
byte[] bytes = new byte[1024];
|
||||
int length = 0;
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
while ((length = in.read(bytes)) != -1) {
|
||||
baos.write(bytes, 0, length);
|
||||
}
|
||||
return baos.toByteArray();
|
||||
return readBytes(in);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] readBytes(File file) {
|
||||
try (InputStream in = new FileInputStream(file)){
|
||||
return readBytes(in);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] readBytes(InputStream in) throws IOException {
|
||||
byte[] bytes = new byte[1024];
|
||||
int length = 0;
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
while ((length = in.read(bytes)) != -1) {
|
||||
baos.write(bytes, 0, length);
|
||||
}
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
public static void write(String path, String content) {
|
||||
// try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(path), StandardCharsets.UTF_8)){
|
||||
// writer.write(content);
|
||||
|
||||
+4
-3
@@ -88,15 +88,16 @@ public class AsgcCompilerTest {
|
||||
compiler.addClasspath("/Users/yangwen/my/tmp/java");
|
||||
compiler.addClasspath("/Users/yangwen/my/tmp/java/asgc-package-lab1-1.0-SNAPSHOT.jar");
|
||||
String code = "package a.b;\n" +
|
||||
"import fun.asgc.lab.pkg.lab1.Dog1;\n" +
|
||||
"import fun.asgc.lab.pkg.lab1.Dog;\n" +
|
||||
"import fun.asgc.cptest.Player;\n" +
|
||||
"public class RadioPlayer implements Player {\n" +
|
||||
"\tpublic void play() {\n" +
|
||||
// "\t\tSystem.out.println(Dog.class);\n" +
|
||||
// "\t\tSystem.out.println(new Dog(\"大黄\").eat(\"骨头\"));\n" +
|
||||
"\t\tSystem.out.println(Dog.class);\n" +
|
||||
"\t\tSystem.out.println(new Dog(\"大黄\").eat(\"骨头\"));\n" +
|
||||
"\t\tSystem.out.println(\"收音机播放\");\n" +
|
||||
"\t}\n" +
|
||||
"}\n";
|
||||
// GlobalConfig.setIsSaveGeneratorCode(true);
|
||||
Class clazz = compiler.compile("a.b","RadioPlayer", code);
|
||||
Method method = ReflectUtil.getMethods(clazz).stream().filter(m -> m.getName().equals("play")).findFirst().get();
|
||||
Object instance = clazz.newInstance();
|
||||
|
||||
@@ -25,6 +25,8 @@ import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -71,4 +73,12 @@ public class ClassUtilTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scan() throws Exception {
|
||||
String path = "jar:file:/Users/yangwen/my/tmp/java/neutrino-proxy-server-1.0-SNAPSHOT-jar-with-dependencies.jar!/";
|
||||
URL url = new URL(path);
|
||||
Set<Class<?>> c = ClassUtil.scan("fun.asgc.neutrino.proxy.server", url);
|
||||
System.out.println(c);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
<template>
|
||||
<div class="drop-down-table">
|
||||
<div class="input-tag-box">
|
||||
<div id="tag-list-box">
|
||||
<el-tag class="elTag" v-show="tags.length > 0" v-for="(tag,index) in tags" closable type="info" size="mini" @close="handeCloseTag(index,tag)">{{tag.name}}</el-tag>
|
||||
<el-input v-model="inputValue"
|
||||
:placeholder="tags.length > 0 ? '':'请选择'"
|
||||
@focus="editor = true"
|
||||
@keyup.backspace.native="keyup"
|
||||
:style="{width:inputWidth}"/>
|
||||
</div>
|
||||
<i :class="editor ? iconUp : iconDown" @click="editor = !editor" style="color: #C0C4CC;"/>
|
||||
</div>
|
||||
<div class="popup-class" :style="{'width':width}" v-show="editor">
|
||||
<el-table
|
||||
border
|
||||
:columns="columns"
|
||||
:data="getData"
|
||||
@row-click="handeRowClick"
|
||||
max-height="500"
|
||||
:row-class-name="tableRowClassName"
|
||||
header-cell-class-name="table_header_class"
|
||||
>
|
||||
<el-table-column v-for="item in columns" align="center" :prop="item.key" :label="item.title" :min-width="item.minWidth"/>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '400px'
|
||||
},
|
||||
data: {
|
||||
type: Array,
|
||||
default: []
|
||||
// required: true
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
default: []
|
||||
// required: true
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getData() {
|
||||
if (!this.multiple) {
|
||||
return this.data
|
||||
} else {
|
||||
return this.data.filter(item => !this.inputValue || item.name.toLowerCase().includes(this.inputValue.toLowerCase()))
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editor: false,
|
||||
inputValue: '',
|
||||
iconUp: 'el-select__caret el-input__icon el-icon-arrow-up',
|
||||
iconDown: 'el-select__caret el-input__icon el-icon-arrow-down',
|
||||
tags: [],
|
||||
inputWidth: '100%'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.inputValue = this.value
|
||||
},
|
||||
watch: {
|
||||
editor() {
|
||||
if (this.editor) {
|
||||
document.getElementsByClassName('input-tag-box')[0].style.borderColor = '#409EFF'
|
||||
} else {
|
||||
this.inputValue = ''
|
||||
document.getElementsByClassName('input-tag-box')[0].style.borderColor = '#909399'
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
tableRowClassName({ row, rowIndex }) {
|
||||
row.index = rowIndex
|
||||
let className = 'success-row'
|
||||
if (row.disabled) {
|
||||
className = 'disabled-row'
|
||||
}
|
||||
const _index = this.tags.findIndex(item => item === row)
|
||||
if (_index > -1) {
|
||||
return className + ' select-row'
|
||||
}
|
||||
return className
|
||||
},
|
||||
handeRowClick(row, column, event) {
|
||||
if (row.disabled) {
|
||||
return
|
||||
}
|
||||
if (this.multiple) {
|
||||
const _index = this.tags.findIndex(item => item === row)
|
||||
if (_index > -1) {
|
||||
this.tags.splice(_index, 1)
|
||||
} else {
|
||||
this.tags.push(row)
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.getInputWidth()
|
||||
})
|
||||
} else {
|
||||
this.editor = false
|
||||
this.inputValue = row.name
|
||||
this.tags = [row]
|
||||
}
|
||||
this.$emit('rowClick', row, this.tags, column, event)
|
||||
},
|
||||
handeCloseTag(index, tag) {
|
||||
this.tags.splice(index, 1)
|
||||
this.inputValue = ''
|
||||
},
|
||||
keyup() {
|
||||
if (this.inputValue.length <= 0 && this.tags.length > 0) {
|
||||
const list = document.getElementsByClassName('elTag')
|
||||
if (list[list.length - 1].style.borderColor !== '') {
|
||||
this.handeCloseTag(this.tags.length - 1)
|
||||
} else {
|
||||
list[list.length - 1].style.borderColor = '#909399'
|
||||
}
|
||||
}
|
||||
},
|
||||
getInputWidth() {
|
||||
let tagMaxWidth = 0
|
||||
const offsetWidth = document.getElementById('tag-list-box').offsetWidth
|
||||
const elTagName = document.getElementsByClassName('elTag')
|
||||
elTagName.forEach(item => {
|
||||
const width = item.offsetWidth + 5.5
|
||||
if (width + tagMaxWidth < offsetWidth) {
|
||||
tagMaxWidth = width + tagMaxWidth
|
||||
} else {
|
||||
tagMaxWidth = width
|
||||
}
|
||||
})
|
||||
this.inputWidth = (offsetWidth - tagMaxWidth) + 'px'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.drop-down-table{
|
||||
.input-tag-box{
|
||||
display: flex;
|
||||
background: #fff;
|
||||
align-items: center;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #DCDFE6;
|
||||
#tag-list-box{
|
||||
padding: 0;
|
||||
width: ~"calc(100% - 30px)";;
|
||||
.elTag{
|
||||
margin-left: 5px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
/deep/.el-input{
|
||||
display: inline-block;
|
||||
}
|
||||
/deep/.el-input__inner{
|
||||
border: 0px!important;
|
||||
padding-right: 0;
|
||||
}
|
||||
/deep/.el-input__icon{
|
||||
line-height: 34px!important;
|
||||
}
|
||||
}
|
||||
.popup-class{
|
||||
padding: 8px;
|
||||
margin-top: 5px;
|
||||
width: 100%;
|
||||
background: white;
|
||||
border: 1px solid #f0f2f5;
|
||||
box-shadow: 1px 1px 10px #f0f2f5;
|
||||
position: absolute;
|
||||
z-index: 9;
|
||||
/deep/.ivu-table-tip{
|
||||
overflow-x: hidden!important;
|
||||
}
|
||||
}
|
||||
/deep/.table_header_class{
|
||||
font-weight: bolder;
|
||||
color: black;
|
||||
background-color: #ebeef5!important;
|
||||
}
|
||||
/deep/.gutter{
|
||||
background-color: #ebeef5!important;
|
||||
}
|
||||
/deep/.el-table .success-row {
|
||||
&:hover{
|
||||
cursor: pointer!important;
|
||||
}
|
||||
}
|
||||
/deep/.el-table .disabled-row {
|
||||
color: #c0c4cc !important;
|
||||
&:hover{
|
||||
cursor: not-allowed!important;
|
||||
}
|
||||
&:hover>td{
|
||||
background-color: #FFF!important;
|
||||
}
|
||||
}
|
||||
/deep/.el-table .select-row {
|
||||
color: #409eff;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -89,7 +89,16 @@
|
||||
:width="280"
|
||||
:disabled="dialogStatus==='update'"
|
||||
/>
|
||||
|
||||
<!-- <DropdownTable
|
||||
:columns="countryColumns"
|
||||
:data="licenseList"
|
||||
v-model="temp.licenseId"
|
||||
:value="temp.licenseName"
|
||||
@rowClick="selectedFeeItem"
|
||||
disabled
|
||||
:disabled="dialogStatus==='update'"
|
||||
style="width: 280px"
|
||||
/>-->
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('服务端端口')" prop="serverPort">
|
||||
<el-select style="width: 280px;" class="filter-item" v-model="temp.serverPort" placeholder="请选择">
|
||||
@@ -197,7 +206,11 @@
|
||||
clientIp: [{ required: true, message: '请输入客户端IP', trigger: 'blur' }],
|
||||
clientPort: [{ required: true, message: '请输入客户端端口', trigger: 'blur' }]
|
||||
},
|
||||
downloadLoading: false
|
||||
downloadLoading: false,
|
||||
countryColumns: [
|
||||
{ prop: 'userName', label: '用户名', align: 'center' },
|
||||
{ prop: 'name', label: 'License', align: 'center' }
|
||||
]
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
@@ -342,10 +355,9 @@
|
||||
}
|
||||
})
|
||||
},
|
||||
selectedFeeItem(list) {
|
||||
console.log(list)
|
||||
this.temp.licenseId = list.id
|
||||
this.temp.licenseName = list.name
|
||||
selectedFeeItem(row, list) {
|
||||
this.temp.licenseId = row.id
|
||||
this.temp.licenseName = row.name
|
||||
},
|
||||
handleDelete(row) {
|
||||
this.$confirm('确定要删除吗?', '提示', {
|
||||
|
||||
+1
@@ -98,6 +98,7 @@ public class LicenseObtainService implements ApplicationRunner {
|
||||
for (String s : args) {
|
||||
if (s.startsWith("license=") && s.length() > 8) {
|
||||
license = s.substring(8).trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,20 +32,50 @@
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<!-- <build>-->
|
||||
<!-- <finalName>${artifactId}</finalName>-->
|
||||
<!-- <plugins>-->
|
||||
<!-- <plugin>-->
|
||||
<!-- <groupId>org.springframework.boot</groupId>-->
|
||||
<!-- <artifactId>spring-boot-maven-plugin</artifactId>-->
|
||||
<!-- <version>2.1.3.RELEASE</version>-->
|
||||
<!-- <configuration>-->
|
||||
<!-- <mainClass>fun.asgc.neutrino.proxy.server.ProxyServer</mainClass>-->
|
||||
<!-- </configuration>-->
|
||||
<!-- <executions>-->
|
||||
<!-- <execution>-->
|
||||
<!-- <goals>-->
|
||||
<!-- <goal>repackage</goal>-->
|
||||
<!-- </goals>-->
|
||||
<!-- </execution>-->
|
||||
<!-- </executions>-->
|
||||
<!-- </plugin>-->
|
||||
<!-- </plugins>-->
|
||||
<!-- </build>-->
|
||||
|
||||
<build>
|
||||
<finalName>${artifactId}</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>2.1.3.RELEASE</version>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<configuration>
|
||||
<mainClass>fun.asgc.neutrino.proxy.server.ProxyServer</mainClass>
|
||||
<appendAssemblyId>false</appendAssemblyId>
|
||||
<finalName>${artifactId}</finalName>
|
||||
<archive>
|
||||
<manifest>
|
||||
<!--这里指定要运行的main类-->
|
||||
<mainClass>fun.asgc.neutrino.proxy.server.ProxyServer</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
<descriptorRefs>
|
||||
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||
</descriptorRefs>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-assembly</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
|
||||
@@ -24,6 +24,7 @@ package fun.asgc.neutrino.proxy.server;
|
||||
|
||||
import fun.asgc.neutrino.core.annotation.EnableJob;
|
||||
import fun.asgc.neutrino.core.annotation.NeutrinoApplication;
|
||||
import fun.asgc.neutrino.core.base.GlobalConfig;
|
||||
import fun.asgc.neutrino.core.context.NeutrinoLauncher;
|
||||
|
||||
/**
|
||||
@@ -36,6 +37,7 @@ import fun.asgc.neutrino.core.context.NeutrinoLauncher;
|
||||
public class ProxyServer {
|
||||
|
||||
public static void main(String[] args) {
|
||||
GlobalConfig.setIsSaveGeneratorCode(true);
|
||||
NeutrinoLauncher.run(ProxyServer.class, args).sync();
|
||||
}
|
||||
|
||||
|
||||
+7
-1
@@ -29,14 +29,20 @@ import lombok.Data;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
* 服务端代理配置
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
@Data
|
||||
@Configuration(prefix = "neutrino.proxy")
|
||||
public class ProxyConfig {
|
||||
/**
|
||||
* 传输协议相关配置
|
||||
*/
|
||||
private Protocol protocol;
|
||||
/**
|
||||
* 服务端配置
|
||||
*/
|
||||
private Server server;
|
||||
|
||||
@Data
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
*
|
||||
* 鉴权注解
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/7/31
|
||||
*/
|
||||
|
||||
+3
-3
@@ -33,14 +33,14 @@ import java.sql.DriverManager;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: 初始化数据库
|
||||
* 初始化数据库
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/7/31
|
||||
*/
|
||||
@Slf4j
|
||||
@PreLoad("init")
|
||||
public class DBInitialize {
|
||||
private static List<String> initDataTableNameList = Lists.newArrayList("user", "license", "port_pool", "port_mapping", "job_qrtz_trigger_info");
|
||||
private static List<String> initDataTableNameList = Lists.newArrayList("user", "license", "port_pool", "port_mapping", "job_info");
|
||||
private static SqliteConfig sqliteConfig;
|
||||
private static JdbcTemplate jdbcTemplate;
|
||||
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
*
|
||||
* 响应体
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/7/31
|
||||
*/
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ import lombok.Getter;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* 服务异常
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/7/31
|
||||
*/
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ import lombok.experimental.Accessors;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
*
|
||||
* 系统上下文
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/8/2
|
||||
*/
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ package fun.asgc.neutrino.proxy.server.base.rest;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
|
||||
|
||||
/**
|
||||
*
|
||||
* 系统上下文持有者
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/8/2
|
||||
*/
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
*
|
||||
* 定时任务配置
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/9/4
|
||||
*/
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ import fun.asgc.neutrino.core.annotation.Value;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
*
|
||||
* sqlite数据库配置
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/7/31
|
||||
*/
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ import fun.asgc.neutrino.core.web.interceptor.RestControllerAdviceHandler;
|
||||
import fun.asgc.neutrino.proxy.server.base.rest.interceptor.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* web配置
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/7/30
|
||||
*/
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ import fun.asgc.neutrino.proxy.server.base.rest.ServiceException;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* 全局异常处理
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/7/31
|
||||
*/
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
*
|
||||
* 连接类型枚举
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/8/31
|
||||
*/
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
*
|
||||
* 异常常量枚举
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/7/31
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user