Compare commits

..
Author SHA1 Message Date
傲世孤尘 5f2d67937d !45 更新测试报告
Merge pull request !45 from NichenFly/dev
2023-11-16 11:53:57 +00:00
= 45f9756b68 去掉一些日志输出 2023-11-16 10:52:11 +08:00
az 0548a9b13e 更新不加密、SM2+AES加密方式的性能对比报告 2023-11-15 20:35:46 +08:00
傲世孤尘 6c2873e453 !44 添加SM2+AES加密与SSL加密的测试报告
Merge pull request !44 from NichenFly/dev
2023-11-15 07:09:44 +00:00
= de243149a9 报告中添加测试代码 2023-11-15 14:57:40 +08:00
= dfb22fa148 添加SM2+AES、SSL的测试内容 2023-11-15 14:53:58 +08:00
= 3b77ecddb9 取消使用hutool的工具进行sm2和aes的加解密 2023-11-15 12:11:22 +08:00
傲世孤尘 5b7962d727 !43 添加配置项sm2-encrypt-enable
Merge pull request !43 from NichenFly/dev
2023-11-15 01:55:49 +00:00
az 9c5ad7aa72 添加配置项目,修改日志信息,不输出公钥信息 2023-11-14 22:04:00 +08:00
傲世孤尘 c7c58a806d !42 解决加密链路解码异常问题
Merge pull request !42 from NichenFly/dev
2023-11-14 13:38:17 +00:00
az 8c14727fba 修复加密过程中的通道编码异常问题 2023-11-14 21:31:08 +08:00
= f2005b3e74 修改参数 2023-11-14 17:04:10 +08:00
= 4569165993 加密算法改为AES 2023-11-10 16:37:30 +08:00
= fc1ba4eb1a Encoder执行完后进行空间释放 2023-11-09 09:00:52 +08:00
傲世孤尘 5a7ac41911 !41 解决加密链路解码异常问题
Merge pull request !41 from NichenFly/dev
2023-11-08 13:23:13 +00:00
az 6c6506c677 解决加密链路解码异常问题 2023-11-08 21:19:29 +08:00
傲世孤尘 4afd300c92 !40 链路使用国密算法对链路进行加密,保证传输过程中信息的机密性
Merge pull request !40 from NichenFly/dev
2023-11-08 06:40:50 +00:00
= 55f85861cf 添加输出信息 2023-11-08 14:08:42 +08:00
= 1698e944ee 修改运行参数 2023-11-08 09:31:39 +08:00
az 8842ae2899 添加非安全判断 2023-11-07 22:33:39 +08:00
= 4eb04e6f0f 修改encoder和decoder适配加密过程 2023-11-07 16:46:18 +08:00
= a16fba4b03 密码协商过程 2023-11-07 15:25:41 +08:00
= 726f38c332 添加SM2、SM4的生成密钥和加解密方法 2023-11-07 10:43:26 +08:00
aoshiguchen 09f934bdd0 更新打包脚本 2023-11-02 17:32:10 +08:00
aoshiguchen d54c4644cf 新增专用于打包复制的配置文件,更新Dockerfile 2023-11-02 17:15:16 +08:00
aoshiguchen 4232be546a 更新Dockerfile 2023-11-02 16:54:29 +08:00
aoshiguchen b5b82e0b80 修正官网客户端配置错误 2023-11-02 16:18:52 +08:00
aoshiguchen 0eaad35379 jdk版本升级为21 2023-11-02 15:56:22 +08:00
aoshiguchen b754dd5456 jdk版本升级为21. 2023-11-02 14:59:49 +08:00
aoshiguchen 035db05e70 jdk版本升级为21 2023-11-02 14:54:24 +08:00
aoshiguchen f7c3e5b76b 新增文章[从Neutrino-Proxy(中微子代理)到Solon Native] 2023-11-02 13:36:33 +08:00
aoshiguchen 85d86e15c2 更新solon版本为2.5.12 2023-11-01 13:41:17 +08:00
aoshiguchen eea06908f9 更新赞赏公示 2023-11-01 11:58:42 +08:00
aoshiguchen 7fc339e6c1 更新todolist 2023-11-01 11:56:57 +08:00
songyinyin 97714e02c5 支持应用中获取当前的版本号 2023-10-31 12:02:44 +08:00
aoshiguchen d8d5cc61ab aot阶段操作数据库逻辑跳过 2023-10-30 18:19:56 +08:00
aoshiguchen 67bcbb4383 .. 2023-10-30 17:17:59 +08:00
aoshiguchen c8207097c1 日志输出内容调整. 2023-10-30 12:40:42 +08:00
songyinyin 8a89786a41 原生编译:增加三方包 高版本jdk 反射的支持 2023-10-29 21:43:29 +08:00
songyinyin c9b501f334 打包时把 lombok 排除掉 2023-10-29 21:40:57 +08:00
aoshiguchen e90bc0f10a 修正官网错别字 2023-10-28 10:47:03 +08:00
aoshiguchen 66d98711bf 更新官网使用须知 2023-10-28 10:42:12 +08:00
aoshiguchen de0ac5b500 更新公众号官宣文章. 2023-10-27 23:32:15 +08:00
aoshiguchen 1dffa1d921 更新官网文档. 2023-10-27 23:24:13 +08:00
aoshiguchen 42dedf03e1 升级solon版本. 2023-10-27 21:12:02 +08:00
aoshiguchen f1ebc47f51 服务端、客户端native编译打包脚本调整 2023-10-27 18:12:29 +08:00
aoshiguchen 4ca43572a0 服务端、客户端编译打包脚本调整 2023-10-27 17:36:44 +08:00
aoshiguchen 116f3d7b36 workflow去掉mac构建 2023-10-27 15:52:07 +08:00
aoshiguchen 265699c3dc aot阶段放开数据库初始化 2023-10-27 15:30:47 +08:00
aoshiguchen c644128552 升级solon版本为2.5.12.-SNAPSHOT 2023-10-27 12:55:17 +08:00
songyinyin fa63ec71a5 原生镜像赋予执行权限 2023-10-26 23:15:26 +08:00
songyinyin 07d5c9f5e4 解决远程编译windows乱码问题 2023-10-26 23:11:10 +08:00
aoshiguchen cb9aa5da81 1、增加LOG_LEVEL环境变量处理逻辑
2、aot处理阶段不执行初始化数据库的逻辑
2023-10-26 21:24:43 +08:00
aoshiguchen 03a9d2e971 修正官网错别字 2023-10-26 20:32:40 +08:00
aoshiguchen 52d96157a4 更新官网赞助列表 2023-10-26 20:24:19 +08:00
aoshiguchen c998512549 官网新增企业登记案例展示 2023-10-26 20:19:20 +08:00
aoshiguchen ac0487432d 官网导航栏新增更新记录入口,维护版本记录、里程碑 2023-10-26 17:54:01 +08:00
aoshiguchen 530b0e2ef9 新增2.0版本宣传文章. 2023-10-26 16:44:50 +08:00
aoshiguchen 311b4a266d 更新服务端、客户端默认配置 2023-10-26 16:06:04 +08:00
aoshiguchen 94fea242e1 更新官网安装部署文档 2023-10-26 15:20:48 +08:00
aoshiguchen 65f6d23eda 更新官网安装部署文档. 2023-10-26 15:18:36 +08:00
aoshiguchen 635544b66d 更新官网安装部署文档 2023-10-26 14:36:34 +08:00
aoshiguchen 52cc45901e 更新个人赞赏说明 2023-10-26 13:41:02 +08:00
songyinyin 66898bded2 增加 Github Action 以支持多平台打包 2023-10-25 21:46:50 +08:00
aoshiguchen 914813c904 官网文档结构调整 2023-10-25 18:31:06 +08:00
aoshiguchen 1dd00f09a8 Merge remote-tracking branch 'origin/dev' into dev 2023-10-25 18:20:53 +08:00
aoshiguchen 8b564cb2f5 官网文档结构调整. 2023-10-25 18:20:43 +08:00
songyinyin d9dfd05180 优化原生编译;日志文件默认开启 2023-10-25 18:19:44 +08:00
aoshiguchen 066b63d38a 1、官网常见问题汇总文档拆分
2、官网去掉演示环境说明
3、官网新增支持栏目
2023-10-25 18:01:50 +08:00
aoshiguchen 78c5014cb0 更新客户端Dockerfile 2023-10-25 12:14:09 +08:00
aoshiguchen 768422d498 更新服务端Dockerfile 2023-10-25 11:58:19 +08:00
aoshiguchen 54d81092a0 更新.editconfig 2023-10-25 11:37:55 +08:00
aoshiguchen 49b4006f1b 更新服务端h2数据库默认存放路径 2023-10-25 11:28:35 +08:00
aoshiguchen 4dd23134e4 1、更新服务端、客户端编译/启动脚本
2、客户端启动参数处理逻辑调整
2023-10-25 11:23:38 +08:00
aoshiguchen 9257605be5 更新vlog 2023-10-25 10:24:33 +08:00
aoshiguchen cd471c1eb4 更新vlog、里程碑文件格式 2023-10-25 10:17:44 +08:00
aoshiguchen 8b358ccadf 更新里程碑 2023-10-24 23:35:27 +08:00
aoshiguchen 35f9f7756e 更新README.MD 2023-10-24 23:16:09 +08:00
aoshiguchen 2c2b169460 新增VLog记录 2023-10-24 23:03:28 +08:00
aoshiguchen f15eec426d 升级mysql jdbc驱动版本 2023-10-24 21:17:15 +08:00
aoshiguchen 798a61e905 更新native测试记录、升级solon版本 2023-10-24 17:37:53 +08:00
songyinyin f8e93a7eaa 原生编译:手动注册 SelectorProvider 及其子类;增加 add-opens 参数 2023-10-24 16:40:46 +08:00
aoshiguchen 2716addc77 更新native测试记录 2023-10-23 22:25:47 +08:00
songyinyin 833bfe94a9 原生编译:netty spi 接口注册 2023-10-23 22:10:41 +08:00
aoshiguchen f07912a189 解决客户端native编译时启动udp端口的问题. 2023-10-23 21:26:22 +08:00
aoshiguchen d3eb245db1 解决客户端native编译时启动udp端口的问题 2023-10-23 21:24:48 +08:00
songyinyin d06af96102 原生编译:修复首页报表数据一直为空 2023-10-22 23:42:15 +08:00
aoshiguchen 3bbc9a1193 去掉演示环境 2023-10-22 13:40:37 +08:00
aoshiguchen f193dfc42c 更新贡献者列表. 2023-10-22 13:38:41 +08:00
aoshiguchen 6f129cad30 更新贡献者列表 2023-10-22 13:35:54 +08:00
aoshiguchen 6412c4c081 新增native测试.MD 2023-10-22 13:28:29 +08:00
aoshiguchen 05c709e33a 新增客户端native编译脚本 2023-10-21 23:54:00 +08:00
songyinyin d32cfa5d51 原生编译:兼容 client 2023-10-21 23:36:19 +08:00
songyinyin 7baf60e8a7 原生编译:兼容接口参数和返回值 2023-10-21 20:55:47 +08:00
aoshiguchen c40142321f 新增服务端native编译脚本 2023-10-21 06:29:47 +08:00
aoshiguchen 18cbc3dced 更新pom 2023-10-20 21:26:10 +08:00
傲世孤尘 6de1bbc74a !39 原生编译第一阶段:项目成功启动
Merge pull request !39 from 读钓/feature/native
2023-10-20 11:56:14 +00:00
songyinyin 7dce301bef 原生编译第一阶段:项目成功启动 2023-10-20 19:29:38 +08:00
aoshiguchen 543ea25fb0 升级solon以来版本 2023-10-19 22:22:25 +08:00
傲世孤尘 8587241714 !38 PageHelper 修改为 mybatis-plus 分页插件
Merge pull request !38 from 读钓/feature/native
2023-10-19 11:04:54 +00:00
songyinyin 38847902c8 PageHelper 修改为 mybatis-plus 分页 2023-10-19 18:37:36 +08:00
aoshiguchen 4685a15d5a 下载文件优化. 2023-10-18 10:54:42 +08:00
aoshiguchen a9608a449d 解决上传文件卡住的问题 2023-10-17 15:06:43 +08:00
aoshiguchen 9c0b3f3086 Merge remote-tracking branch 'origin/master' into dev 2023-10-12 12:05:54 +08:00
aoshiguchen 446910fd44 更新官网 2023-10-12 12:03:50 +08:00
aoshiguchen c4e05aad9e 更新官网. 2023-10-12 11:56:41 +08:00
qiuyue 4db971aabb feat: 常见问题汇总 3,4 2023-10-11 18:24:56 +08:00
aoshiguchen 4c975798d7 Merge remote-tracking branch 'origin/dev' 2023-09-24 19:49:58 +08:00
aoshiguchen ddc52af4bb 更新UDP说明 2023-09-24 19:43:04 +08:00
aoshiguchen 0cc4608983 Merge remote-tracking branch 'origin/dev' 2023-09-24 13:39:10 +08:00
aoshiguchen 99bfce9a31 新增限速测试, 2023-09-24 13:29:53 +08:00
aoshiguchen cb0cbee532 Merge remote-tracking branch 'origin/dev' 2023-09-24 12:01:53 +08:00
aoshiguchen 268e17273e 代理优化,解决背压下载大文件中途断开的问题 2023-09-23 23:21:13 +08:00
aoshiguchen 840dd2d344 新增1.9.0发布文章 2023-09-23 18:03:41 +08:00
aoshiguchen e50c303437 Merge remote-tracking branch 'origin/dev'
# Conflicts:
#	neutrino-proxy-vuepress/docs/01.使用教程/01.开始使用/01.快速上手.md
#	todolist.MD
2023-09-23 11:13:30 +08:00
aoshiguchen 3cf64596c6 udp代理优化. 2023-09-23 10:56:45 +08:00
aoshiguchen 35327cc9a5 更新官网文档. 2023-09-22 23:24:02 +08:00
aoshiguchen 4d8e4190ba 更新官网文档. 2023-09-22 23:06:37 +08:00
aoshiguchen 2da08e1b55 调通UDP代理功能,更新SQL脚本 2023-09-22 22:42:08 +08:00
aoshiguchen 8d9fc4cff6 调通UDP代理功能,更新SQL脚本 2023-09-22 22:41:01 +08:00
aoshiguchen 4d380dbdf3 新增/编辑端口映射时端口占用检测功能屏蔽,解决docker下使用的各种问题 2023-09-22 20:15:41 +08:00
aoshiguchen d5abf90954 端口映射打开网页,优先使用域名 2023-09-22 13:41:50 +08:00
aoshiguchen e416c76ea8 调通UDP代理 2023-09-21 23:40:00 +08:00
aoshiguchen 33704b1694 客户端udp配置调整. 2023-09-21 16:30:48 +08:00
aoshiguchen 491f042b2f UDP代理隧道连接逻辑调整 2023-09-20 22:22:36 +08:00
aoshiguchen fd306ce8c3 服务端收到UDP数据包转发至客户端调通 2023-09-20 11:57:00 +08:00
aoshiguchen 6880898321 客户端新增UDP隧道消息处理器 2023-09-19 13:16:43 +08:00
aoshiguchen 5578874df7 中文日志改为英文 2023-09-19 12:56:42 +08:00
aoshiguchen 67c362b423 客户端代码优化 2023-09-19 12:09:32 +08:00
aoshiguchen b026b46647 服务端收到来自访问者的udp数据,转发到客户端 2023-09-18 18:53:45 +08:00
aoshiguchen bfe880a6c6 新增端口映射支持选择UDP协议 2023-09-17 10:59:20 +08:00
aoshiguchen f46332db4a 客户端适应udp配置调整 2023-09-16 18:10:26 +08:00
aoshiguchen 5b2fe447b2 更新todolist,服务端适应udp配置调整 2023-09-16 17:47:27 +08:00
aoshiguchen 0301f15628 更新文档 2023-09-13 11:11:32 +08:00
aoshiguchen 4b278216e5 更新todolist 2023-09-12 14:07:28 +08:00
aoshiguchen 5fbf3684a6 更新文档 2023-09-07 10:05:41 +08:00
aoshiguchen 85d3ed914f 更新dromara组织项目logo. 2023-09-01 12:41:29 +08:00
aoshiguchen bf5028e056 更新dromara组织项目logo 2023-09-01 12:36:11 +08:00
aoshiguchen 3bd011b58d Merge remote-tracking branch 'origin/dev' 2023-08-31 21:04:01 +08:00
aoshiguchen 3610de97db 客户端优化,收到DISCONNECT消息时断开代理隧道连接 2023-08-31 16:55:34 +08:00
aoshiguchen 40a03c1630 更新文档. 2023-08-31 16:46:45 +08:00
aoshiguchen f1ea585cec 更新赞赏记录 2023-08-31 16:42:00 +08:00
aoshiguchen 9647006ed1 更新TODOLIST 2023-08-31 16:39:29 +08:00
aoshiguchen 8c5a6b2f0d 解决端口映射编辑时,如果端口号没有变动,则不验证。避免出现端口映射正在使用时,无法更新端口映射其他信息的问题 2023-08-31 16:29:16 +08:00
aoshiguchen 3991fcb91c 端口映射页面增加快捷打开网页按钮 2023-08-30 18:13:01 +08:00
傲世孤尘 93e8879bf5 !37 客户端断开连接时,记录日志空指针异常和捕获异常错误
Merge pull request !37 from Metal/dev
2023-08-28 07:32:31 +00:00
MnMainTao 0466710f73 1、修复客户端断开连接时,记录日志空指针异常
2、修复客户端通道异常时,捕获异常抛到父类会造成异常的问题
2023-08-28 15:21:11 +08:00
aoshiguchen ef8cab0a36 更新README.MD 2023-08-10 19:44:23 +08:00
傲世孤尘 7a9e81c27a !36 新增一键访问穿透 url 的按钮
Merge pull request !36 from 孔明/dev
2023-08-10 11:19:47 +00:00
click33 3f42b58471 新增一键访问穿透url的按钮 2023-08-10 19:01:27 +08:00
aoshiguchen 323d86b49d 新增icon 2023-08-10 16:45:50 +08:00
aoshiguchen e2226f7d0b Merge branch 'dev' of https://gitee.com/asgc/neutrino-proxy into dev 2023-08-04 13:55:44 +08:00
傲世孤尘 5094700b66 !35 修复删除license时未做端口映射检查BUG
Merge pull request !35 from tan90/dev
2023-07-29 08:07:10 +00:00
陈浩杰 b737535b0f fix: 修复删除license时未做端口映射检查BUG 2023-07-29 15:52:33 +08:00
傲世孤尘 7a95fbddbc !34 修复日志异常打印msg为空
Merge pull request !34 from Yohanes/dev
2023-07-26 05:46:11 +00:00
Yohanes f00d8d1479 修复日志打印msg为null 2023-07-26 13:43:13 +08:00
aoshiguchen 4550a677db 更新客户端镜像build脚本 2023-07-26 10:11:36 +08:00
Yohanes f604e8b011 Merge remote-tracking branch 'upstream/dev' into dev 2023-07-26 08:48:15 +08:00
aoshiguchen a400fccf77 更新TODOLIST 2023-07-25 17:16:38 +08:00
傲世孤尘 87e4af5ba5 !33 端口映射、license页面下拉框课增加可输入搜索
Merge pull request !33 from Yohanes/dev
2023-07-25 09:09:59 +00:00
aoshiguchen 1d11dd3f85 脚本变量命名修正 2023-07-25 16:11:35 +08:00
Yohanes b3e85b6a65 Merge remote-tracking branch 'upstream/dev' into dev 2023-07-25 14:36:18 +08:00
aoshiguchen c76c4d8257 新增客户端docker镜像build脚本 2023-07-25 14:34:04 +08:00
Yohanes 64fcd43be5 端口映射、license页面下拉框课增加可输入搜索 2023-07-24 17:01:27 +08:00
aoshiguchen a7b077af8c 更新赞赏记录 2023-07-21 20:49:02 +08:00
aoshiguchen a4c2addb33 更新todolist. 2023-07-18 10:19:19 +08:00
傲世孤尘 dd6493cc66 !32 检查端口是否被占用
Merge pull request !32 from Metal/dev
2023-07-18 01:59:00 +00:00
Metal 52ce6d265a 代理配置-端口映射,配置服务端端口时,检查端口是否被占用,无法保存的问题 2023-07-18 09:51:06 +08:00
aoshiguchen eacc98d18f 更新todolist 2023-07-17 21:54:14 +08:00
傲世孤尘 65af658be5 !31 检查端口是否被占用
Merge pull request !31 from Metal/dev
2023-07-17 13:39:33 +00:00
Metal a7b2694204 代理配置-端口映射,配置服务端端口时,检查端口是否被占用 2023-07-17 21:06:51 +08:00
aoshiguchen abab45785b 更新todolist 2023-07-16 11:10:37 +08:00
aoshiguchen 9da00e47bd 更新前端打包脚本. 2023-07-07 12:11:00 +08:00
aoshiguchen 4eea46fa46 官网首页跳转链接增加来源参数 2023-07-07 11:31:44 +08:00
傲世孤尘 b7518bfbeb !30 新增选择器组件支持滚动加载
Merge pull request !30 from Metal/dev
2023-07-06 13:27:54 +00:00
Metal a95b534701 1、添加前端组件:选择器支持滚动加载
2、代理配置-端口映射,新增和修改功能服务端端口字段选择器修改为支持滚动加载
3、代理配置-端口映射,后台接口getAvailablePortList修改参数内容并支持滚动加载模式也即是分页加载
2023-07-06 21:22:42 +08:00
aoshiguchen 04747296c5 Merge remote-tracking branch 'origin/dev' 2023-07-04 21:48:05 +08:00
aoshiguchen f78a74cd0b Merge remote-tracking branch 'origin/dev' 2023-07-01 14:05:44 +08:00
aoshiguchen e715976726 Merge remote-tracking branch 'origin/dev' 2023-05-27 12:09:01 +08:00
aoshiguchen f66791678c . 2023-05-27 12:08:55 +08:00
287 changed files with 5785 additions and 2370 deletions
+8
View File
@@ -0,0 +1,8 @@
root=true
[*.{groovy,java,kt,xml}]
indent_style = space
indent_size = 4
[*.sh]
end_of_line=lf
+87
View File
@@ -0,0 +1,87 @@
name: Build linux and mac native image
on:
workflow_dispatch:
push:
tags:
- 'v*'
jobs:
build:
strategy:
fail-fast: false
matrix:
# see: https://docs.github.com/zh/actions/using-jobs/choosing-the-runner-for-a-job
# os: ['ubuntu-20.04', 'macos-12']
os: ['ubuntu-20.04']
name: build - ${{ matrix.os }}
runs-on: ${{ matrix.os }}
# https://github.com/softprops/action-gh-release/issues/236#issuecomment-1150530128
permissions:
contents: write
steps:
- uses: actions/checkout@v3
- name: setup node v13.12.0
uses: actions/setup-node@v3
with:
node-version: 13.12.0
# cache: 'npm' # 使用缓存需要把 package-lock.json 也上传到 git 上
- name: build neutrino-proxy-admin
run: |
cd neutrino-proxy-admin
npm install
npm run build:docker·
cp -rf ./dist/ ./../neutrino-proxy-server/src/main/resources/static/
- name: GitHub Action for GraalVM JDK 17
uses: graalvm/setup-graalvm@v1
with:
java-version: '21.0.1' # for a specific JDK 17; or '17' for the latest JDK 17
distribution: 'graalvm' # New 'distribution' option
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Cache local Maven repository
uses: actions/cache@v2
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- name: Build with Maven (neutrino-proxy-server)
run: |
mvn clean install -pl neutrino-proxy-core -am -DskipTests --no-transfer-progress
cd neutrino-proxy-server
mvn clean native:compile -P native --file pom.xml --no-transfer-progress
chmod +x target/neutrino-proxy-server
cp target/neutrino-proxy-server ./../neutrino-proxy-server-${{ github.ref_name }}
cp target/classes/app-copy.yml ./../neutrino-proxy-server-${{ github.ref_name }}/app.yml
- name: Archive zip (neutrino-proxy-server)
uses: thedoctor0/zip-release@master
with:
type: 'zip'
path: 'neutrino-proxy-server-${{ github.ref_name }}'
filename: neutrino-proxy-server-${{ matrix.os }}-${{ github.ref_name }}.zip
- name: Build with Maven (neutrino-proxy-client)
run: |
cd neutrino-proxy-client
mvn clean package --file pom.xml --no-transfer-progress
mvn clean native:compile -P native --file pom.xml --no-transfer-progress
chmod +x target/neutrino-proxy-client
cp target/neutrino-proxy-client ./../neutrino-proxy-client-${{ github.ref_name }}
cp target/classes/app-copy.yml ./../neutrino-proxy-client-${{ github.ref_name }}/app.yml
- name: Archive zip (neutrino-proxy-client)
uses: thedoctor0/zip-release@master
with:
type: 'zip'
path: 'neutrino-proxy-client-${{ github.ref_name }}'
filename: neutrino-proxy-client-${{ matrix.os }}-${{ github.ref_name }}.zip
# GitHub 上创建 release
- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
files: |
neutrino-proxy-server/target/neutrino-proxy-server.jar
neutrino-proxy-client/target/neutrino-proxy-client.jar
neutrino-proxy-server-${{ matrix.os }}-${{ github.ref_name }}.zip
neutrino-proxy-client-${{ matrix.os }}-${{ github.ref_name }}.zip
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
+84
View File
@@ -0,0 +1,84 @@
name: Build windows native image
on:
workflow_dispatch:
push:
tags:
- 'v*'
jobs:
build:
strategy:
fail-fast: false
matrix:
# see: https://docs.github.com/zh/actions/using-jobs/choosing-the-runner-for-a-job
os: ['windows-2022']
name: build - ${{ matrix.os }}
runs-on: ${{ matrix.os }}
# https://github.com/softprops/action-gh-release/issues/236#issuecomment-1150530128
permissions:
contents: write
steps:
- uses: actions/checkout@v3
- name: setup node v13.12.0
uses: actions/setup-node@v3
with:
node-version: 13.12.0
# cache: 'npm' # 使用缓存需要把 package-lock.json 也上传到 git 上
- name: build neutrino-proxy-admin
run: |
cd neutrino-proxy-admin
npm install
npm run build:docker
cp -r ./dist/ ./../neutrino-proxy-server/src/main/resources/static/
- name: GitHub Action for GraalVM JDK 17
uses: graalvm/setup-graalvm@v1
with:
java-version: '21.0.1' # for a specific JDK 17; or '17' for the latest JDK 17
distribution: 'graalvm' # New 'distribution' option
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Cache local Maven repository
uses: actions/cache@v2
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- name: Build with Maven (neutrino-proxy-server)
run: |
set MAVEN_OPTS="-Duser.language=UTF-8 -Dfile.encoding=UTF-8"
mvn clean install -pl neutrino-proxy-core -am -DskipTests --no-transfer-progress
cd neutrino-proxy-server
mvn clean native:compile -P native --file pom.xml --no-transfer-progress
cp target/neutrino-proxy-server.exe ./../neutrino-proxy-server.exe
- name: Archive zip (neutrino-proxy-server)
uses: thedoctor0/zip-release@master
with:
type: 'zip'
path: 'neutrino-proxy-server.exe'
filename: neutrino-proxy-server-${{ matrix.os }}-${{ github.ref_name }}.zip
- name: Build with Maven (neutrino-proxy-client)
run: |
set MAVEN_OPTS="-Duser.language=UTF-8 -Dfile.encoding=UTF-8"
cd neutrino-proxy-client
mvn clean package --file pom.xml --no-transfer-progress
mvn clean native:compile -P native --file pom.xml --no-transfer-progress
cp target/neutrino-proxy-client.exe ./../neutrino-proxy-client.exe
- name: Archive zip (neutrino-proxy-client)
uses: thedoctor0/zip-release@master
with:
type: 'zip'
path: 'neutrino-proxy-client.exe'
filename: neutrino-proxy-client-${{ matrix.os }}-${{ github.ref_name }}.zip
# GitHub 上创建 release
- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
files: |
neutrino-proxy-server/target/neutrino-proxy-server.jar
neutrino-proxy-client/target/neutrino-proxy-client.jar
neutrino-proxy-server-${{ matrix.os }}-${{ github.ref_name }}.zip
neutrino-proxy-client-${{ matrix.os }}-${{ github.ref_name }}.zip
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
+3
View File
@@ -57,3 +57,6 @@ hs_err_pid*
neutrino-proxy-vuepress/deploy.sh
.NEUTRINO_PROXY_CLIENT_ID
logs
data
**/cert/**
neutrino-proxy-server/src/main/resources/static/**
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+16
View File
@@ -0,0 +1,16 @@
- 2022-06-16 1.0.0版本发布,基于自研应用框架、纯配置无管理后台
- 2022-10-12 1.5.0版本发布,底层框架优化,新增管理后台
- 2022-11-23 1.6.0版本发布,新增日志管理模块,新增首页流量统计
- 2023-01-08 中微子代理Gitee Star突破100
- 2023-01-16 加入Dromara组织
- 2023-02-23 1.6.4版本发布,管理后台增、删、改、禁用实时生效,启动参数、GZip优化
- 2023-03-12 1.7.0版本发布,底层框架更换为Solon + MybatisPlus
- 2023-03-26 1.7.1版本发布,新增端口池分组、新增报表管理模块、首页图表优化
- 2023-04-03 1.8.0版本发布,端口映射支持选择协议,HTTP映射支持子域名
- 2023-05-27 1.8.2版本发布,增加HTTPS支持
- 2023-06-03 1.8.3版本发布,客户端重连优化、服务端验证客户端身份逻辑优化
- 2023-06-08 1.8.4版本发布,增加对mariadb的支持、配置文件支持指定日志级别、重连参数
- 2023-07-04 1.8.5版本发布,后台若干细节优化、配置文件优化
- 2023-09-07 1.8.6版本发布,后台若干细节优化
- 2023-09-22 1.9.0版本发布,端口映射支持UDP协议
- 2023-09-25 中微子代理Gitee Star数突破1000
+22 -21
View File
@@ -4,8 +4,8 @@
<p align="center">
<a href='https://gitee.com/dromara/neutrino-proxy/stargazers'><img src='https://gitee.com/dromara/neutrino-proxy/badge/star.svg?theme=dark' alt='star'></img></a>
<a href='https://gitee.com/dromara/neutrino-proxy/members'><img src='https://gitee.com/dromara/neutrino-proxy/badge/fork.svg?theme=dark' alt='fork'></img></a>
<a target="_blank" href="https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html">
<img src="https://img.shields.io/badge/JDK-8+-red.svg" />
<a target="_blank" href="https://www.oracle.com/java/technologies/javase/javase-jdk17-downloads.html">
<img src="https://img.shields.io/badge/JDK-17+-red.svg" />
</a>
<a href="./LICENSE">
<img src="https://img.shields.io/badge/license-MIT-red" alt="license MIT">
@@ -37,55 +37,56 @@
# 4、代理示意图
![代理流程](assets/design/neutrino-proxy-process.jpg)
# 5、演示环境
> 可使用分配好的游客license试用。服务器带宽较低,仅供学习使用!
- 管理后台地址:http://103.163.47.16:9527
- 游客账号:visitor/123456
# 6、联系我们
# 5、联系我们
- 微信: yuyunshize
- Gitee: https://gitee.com/asgc/neutrino-proxy
# 7、贡献者列表
# 6、贡献者列表
<p>
<a href="https://gitee.com/zcans" target="_blank">
<img src="assets/developer/zcans.png" width="12%">
<img src="assets/developer/zcans.png" width="11%">
</a>
<a href="https://gitee.com/bmlt" target="_blank">
<img src="assets/developer/jiuye.png" width="12%">
<img src="assets/developer/jiuye.png" width="11%">
</a>
<a href="https://gitee.com/wangke666" target="_blank">
<img src="assets/developer/keke.png" width="12%">
<img src="assets/developer/keke.png" width="11%">
</a>
<a href="https://gitee.com/xue-fangming" target="_blank">
<img src="assets/developer/fangming.png" width="12%">
<img src="assets/developer/fangming.png" width="11%">
</a>
<a href="https://gitee.com/y0hanes" target="_blank">
<img src="assets/developer/yohanes.png" width="12%">
<img src="assets/developer/yohanes.png" width="11%">
</a>
<a href="https://gitee.com/noear_admin" target="_blank">
<img src="assets/developer/near.png" width="12%">
<img src="assets/developer/near.png" width="11%">
</a>
<a href="https://gitee.com/westboy" target="_blank">
<img src="assets/developer/westboy.jpg" width="12%">
<img src="assets/developer/westboy.jpg" width="11%">
</a>
<a href="https://gitee.com/liugddx" target="_blank">
<img src="assets/developer/liugddx.png" width="12%">
<img src="assets/developer/liugddx.png" width="11%">
</a>
<a href="https://gitee.com/DianZiFaPiao" target="_blank">
<img src="assets/developer/tan90.png" width="12%">
</a>
<a href="https://gitee.com/dearsny" target="_blank">
<img src="assets/developer/pingon.png" width="12%">
<img src="assets/developer/pingon.png" width="11%">
</a>
<a href="https://gitee.com/ixxxk" target="_blank">
<img src="assets/developer/tany.png" width="12%">
<img src="assets/developer/tany.png" width="11%">
</a>
<a href="https://gitee.com/zoo-plus" target="_blank">
<img src="assets/developer/zoo-plus.png" width="12%">
<img src="assets/developer/zoo-plus.png" width="11%">
</a>
<a href="https://gitee.com/MetalXingxing" target="_blank">
<img src="assets/developer/metal.png" width="12%">
<img src="assets/developer/metal.png" width="11%">
</a>
<a href="https://gitee.com/click33" target="_blank">
<img src="assets/developer/click33.png" width="11%">
</a>
<a href="https://gitee.com/songyinyin" target="_blank">
<img src="assets/developer/songyinyin.png" width="11%">
</a>
</p>
+74
View File
@@ -0,0 +1,74 @@
# 1.x
## 1.0.0
> 第一个基本可用版本
- 基于自研底层应用框架、Netty
- 纯配置实现、无管理后台
- 支持TCP代理
## 1.5.0
- 管理后台新增用户管理、License管理、端口映射、调度管理、客户端连接日志、调度日志
- 支持默认sqlite数据库
- 底层框架Aop、Banner、SqlMapper、Ioc、@Value注解、Yml配置加载、静态资源服务等优化
- 底层框架新增@Async@Singleton等注解
- 新增AsgcCompiler、ApplicationEvent机制封装
## 1.6.0
- 新增日志管理模块,调度日志迁移至日志管理
- 管理后台新增登录日志
- 新增首页流量统计
## 1.6.4
- 支持代理服务端用户(删除/禁用)、端口池(删除/禁用/启用)、License(删除/禁用/启用)、端口映射(新增/删除/禁用/启用)实时生效
- 启动参数优化
- 服务端静态资源服务支持缓存、gzip压缩,提升响应速度
## 1.7.0
- 底层框架更换为Solon + MybatisPlus
## 1.7.1
- 针对管理后台各页面的排序、搜索条件的优化
- 增加端口池分组、相关的端口映射优化
- 增加了报表管理模块:用户流量报表、license流量报表、用户流量月度明细、license流量月度明细
- 完成首页图表展示:license统计、端口映射统计、今日流量统计、汇总流量统计、最近15日流量折线图
## 1.8.0
- 端口映射支持选择协议
- HTTP协议下映射支持配置子域名(前提是server端配置了域名)
## 1.8.2
- 增加HTTPS的支持。
## 1.8.3
- 客户端重连优化
- 服务端验证客户端身份逻辑优化
## 1.8.4
- 增加对mariadb的支持
- 服务端/客户端,支持配置文件、启动参数指定日志级别
- 客户端支持配置文件指定重连间隔,是否开启无限重连
## 1.8.5
- 后台端口池管理支持批量删除
- 端口映射下拉选择端口支持搜索
- 客户端/服务端配置增加心跳日志开关,有需要时开启,方便排查问题
- 客户端/服务端读写超时逻辑微调
## 1.8.6
- 端口映射选择端口支持分页
- 新增/更新端口映射,增加端口占用检测(端口映射编辑时,如果端口号没有变动,则不验证。避免出现端口映射正在使用时,无法更新端口映射其他信息的问题)
- 下拉搜索license,支持模糊搜索
- license下拉用户搜索,支持模糊搜索
- 端口映射HTTP(S)新增打开网页按钮
- 客户端断开连接时,记录日志空指针异常问题修复
## 1.9.0
- 端口映射支持UDP协议
- 端口映射HTTP(S)新增打开网页按钮,优先使用域名打开
- 新增/编辑端口映射时端口占用检测功能屏蔽,解决docker下使用的各种问题
# 2.x
## 2.0.0
- solon版本升级为`2.5.11`
- jdk版本升级为17
- 支持原生编译改造
- 默认支持的数据库由sqlite改为h2
Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 KiB

+137
View File
@@ -0,0 +1,137 @@
# 内网穿透神器NeutrinoProxy 1.9.0版本发布
## 更新内容
> 与1.8.0版本对比,更新内容如下:
- 核心功能
- 域名映射支持HTTPS
- 支持UDP协议代理
- 客户端断开连接时,记录日志空指针异常问题修复
- 基础优化
- 客户端重连逻辑优化,支持配置文件指定重连间隔,是否开启无限重连
- 增加对mariadb的支持
- 服务端/客户端,支持配置文件、启动参数指定日志级别
- 后台端口池管理支持批量删除
- 后台下拉选择license、用户支持模糊搜索
- 端口映射下拉选择端口支持搜索、分野
- 客户端/服务端配置增加心跳日志开关,有需要时开启,方便排查问题
- 端口映射HTTP(S)新增打开网页快捷操作,优先使用绑定域名打开
## 升级须知
> 从1.9.0之前的版本升级需要注意:
- 在项目`neutrino-proxy-server/src/main/resources/sql`目录下找到mysql/sqlite的增量sql文件`UPDATE-20230922.SQL`,执行更新自己的中微子代理数据库
- 参照[官网服务端配置](https://dromara.gitee.io/neutrino-proxy/pages/f2d0f1)调整现有的服务端配置
- 参照[官网客户端配置](https://dromara.gitee.io/neutrino-proxy/pages/50ce10)吊证现有的客户端配置
- 从[仓库发行版](https://gitee.com/dromara/neutrino-proxy/releases)下载最新版本的客户端(neutrino-proxy-client.jar)、服务端(neutrino-proxy-server.jar、 neutrino-proxy-admin.zip)部署文件。若为服务端是docker镜像部署,则更新镜像重启即可
## 项目简介
- [中微子代理(neutrino-proxy)](https://gitee.com/dromara/neutrino-proxy) 是一款基于netty的内网穿透神器。该项目采用最为宽松的MIT协议,因此您可以对它进行复制、修改、传播并用于任何个人或商业行为。
- Gitee地址:https://gitee.com/dromara/neutrino-proxy
- 官网地址:http://neutrino-proxy.dromara.org
- 服务端管理后台截图:
<img src="./home.png" width="100%"/>
## 主要特点:
- 1、流量监控:首页图表、报表管理多维度流量监控。全方位掌握实时、历史代理数据。
- 2、用户/License:支持多用户、多客户端使用。后台禁用实时生效。
- 3、端口池:对外端口统一管理,支持用户、License独占端口。
- 4、端口映射:新增、编辑、删除、禁用实时生效。
- 5、Docker:服务端支持Docker一键部署。
- 6、SSL证书:支持SSL,保护您的信息安全。
- 7、域名映射:支持绑定子域名,方便本地调试三方回调
- 8、多协议:支持代理TCP、HTTP(S)、UDP多种协议
- 9、采用最为宽松的MIT协议,免去你的后顾之忧
## 快速使用
> 更多使用姿势、细节请通过官网或结尾微信二维码加我备注"中微子代理"入群交流。
### 1、 部署服务端
#### 1.1、 Docker一键部署
> 当前最新版本为1.9.0,下面的脚本中,可以使用:`registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:1.9.0` 指定版本安装,推荐使用`latest`直接安装最新版。
##### 使用默认sqlite数据库
```shell
docker run -it -p 9000-9200:9000-9200/tcp -p 8888:8888 \
-d --restart=always --name neutrino-proxy \
registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:latest
```
##### 指定自己的mysql数据库
- 在服务器上创建目录:/root/neutrino-proxy/config
- 在该目录下创建`app.yml`文本文件,并配置如下内容:
```yml
neutrino:
data:
db:
type: mysql
# 自己的数据库实例,创建一个空的名为'neutrino-proxy'的数据库即可,首次启动服务端会自动初始化
url: jdbc:mysql://xxxx:3306/neutrino-proxy?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useAffectedRows=true&useSSL=false
driver-class: com.mysql.jdbc.Driver
# 数据库帐号
username: xxx
# 数据库密码
password: xxx
```
- 然后执行如下命令:
```shell
docker run -it -p 9000-9200:9000-9200/tcp -p 8888:8888 \
-v /root/neutrino-proxy/config:/root/neutrino-proxy/config \
-d --restart=always --name neutrino \
registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:latest
```
#### 1.2、使用jar包自行部署
- 首先确保服务器上已安装java8运行环境
- 打开[发行版页面](https://gitee.com/dromara/neutrino-proxy/releases),下载最新的release包:`neutrino-proxy-server.jar``neutrino-proxy-admin.zip`
- 在服务器上新建部署目录:`/work/projects/neutrino-proxy-server`
-` neutrino-proxy-server.jar``neutrino-proxy-admin.zip`上传至服务器部署目录。
- 解压`neutrino-proxy-admin.zip`文件
- 执行命令`java -jar neutrino-proxy-server.jar`启动服务端完成部署,默认使用sqlite数据库。
- 若需要指定自己的mysql数据库,同样的需要在当前目录下新建`app.yml`文件,文件内容同上。执行命令`java -jar neutrino-proxy-server.jar config=app.yml`启动服务端完成部署
- 可参照 https://gitee.com/dromara/neutrino-proxy/blob/master/bin/server_start.sh 使用shell脚本启动服务端。
### 2、管理后台配置
- 服务端部署成功后,访问`http://{服务端IP}:8888`打开后台管理页面。
- 使用默认的管理员帐号登录:admin/123456
- 打开`代理配置>License管理`页面,可以看到系统已经自动为管理员初始化了一条License记录,复制该`LicenseKey`备用,后续客户端配置需要。
- 打开`代理配置>端口映射`页面,可以看到系统已经自动为初始化了几条端口映射。可根据需要自行添加、修改。这里我们以`9101 -> 127.0.0.1:8080`映射为例
### 3、启动客户端
- 首先确保本地已安装java8运行环境
- 打开[发行版页面](https://gitee.com/dromara/neutrino-proxy/releases),下载最新的release包:`neutrino-proxy-client.jar`
- 在本地`neutrino-proxy-client.jar`同级别目录下新建`app.yml`文件,并配置如下内容:
```yml
neutrino:
proxy:
tunnel:
# ssl证书密钥(使用jjar包内自带的证书,则此处无需修改)
key-store-password: 123456
# ssl证书管理密钥(使用jjar包内自带的证书,则此处无需修改。自定义证书,则此处配置对应的路径)
jks-path: classpath:/test.jks
# 代理服务端IP
server-ip: localhost
# 代理服务端IP, 若是非ssl端口,则ssl-enable需要配置为false
server-port: 9002
# 是否启用ssl
ssl-enable: true
# licenseKey,客户端凭证。此处需要配置刚刚从管理后台复制的LicenseKey
license-key: xxxx
```
- 执行命令`java -jar neutrino-proxy-client.jar`启动客户端
- 查看服务端License管理,刷新页面,对应的License在线状态为`在线`,则表明客户端已正常连接。
### 4、代理验证
- 本地启动被代理服务,如:redis、本地web项目、本地mysql等等
- 先确保本地能正常访问被代理服务,如果本地都不能访问,不用想代理更不可能!!!
- 通过服务端IP+9101(上面License配置的端口映射重的服务端端口)访问本地被代理服务
## 联系我们
笔者时间、能力有限,且开源项目非一朝一夕之事,存在众多问题亦在所难免。使用、学习过程中有任何问题欢迎大家与我联系。
对项目有什么想法或者建议,可以加我微信拉交流群,或者创建[issues](https://gitee.com/dromara/neutrino-proxy/issues),一起完善项目
- 微信号:yuyunshize
- Email: aoshiguchen@dromara.org
- 中微子代理官网:http://neutrino-proxy.dromara.org
- 中微子代理仓库:https://gitee.com/dromara/neutrino-proxy
- 微信二维码:
<img src="MyWeChatQRCode.jpeg" width="100%"/>
Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 KiB

+60
View File
@@ -0,0 +1,60 @@
# 中微子代理(NeutrinoProxy)2.0.0重磅发布
## 项目简介
- [中微子代理(neutrino-proxy)](https://gitee.com/asgc/neutrino-proxy) 是一款基于netty的内网穿透神器。该项目采用最为宽松的MIT协议,因此您可以对它进行复制、修改、传播并用于任何个人或商业行为。
- 市面上基于内网穿透的常见产品有:花生壳、TeamView、cpolar等。
- 常见的使用场景:
- 本地开发调试第三方回调
- 本地开发异地接口连调
- 远程登录内网windows机器
- 将本地服务映射到外网,用于演示
- Gitee仓库:https://gitee.com/dromara/neutrino-proxy
- Github仓库:https://github.com/dromara/neutrino-proxy
- 官网地址1: https://neutrino-proxy.dromara.org
- 官网地址2: https://dromara.gitee.io/neutrino-proxy
## 主要特点:
- 1、流量监控:首页图表、报表管理多维度流量监控。全方位掌握实时、历史代理数据。
- 2、用户/License:支持多用户、多客户端使用。后台禁用实时生效。
- 3、端口池:对外端口统一管理,支持用户、License独占端口。
- 4、端口映射:新增、编辑、删除、禁用实时生效。
- 5、Docker:服务端/客户端支持Docker一键部署。
- 6、SSL证书:隧道通信支持SSL加密,保护您的数据安全。
- 7、域名映射:支持绑定子域名,方便本地调试三方回调
- 8、多种协议:支持代理TCP、HTTP、HTTPS、UDP协议
- 9、原生部署:支持编译为原生可执行文件,更低部署门槛、更少内存占用
- 10、采用最为宽松的MIT协议,免去你的后顾之忧
## 本次更新内容
- solon版本升级为`2.5.12-M1`
- jdk版本升级为17
- 支持原生编译改造
- 默认支持的数据库由sqlite改为h2
- 官方默认使用的docker镜像仓库从阿里云改为dockerhub
## 安装使用说明
- 快速上手:[点击这里](https://neutrino-proxy.dromara.org/neutrino-proxy/pages/793dcb/)
- 升级须知:
- jdk版本升级为了jdk17,jar部署时请注意
- 去掉了默认的sqlite数据库,改为了h2。如果之前使用sqlite,请自行处理数据迁移
- 配置文件做了较大调整,请参照官网使用须知中的`服务端配置``客户端配置`进行更新
## 运行示例
#### 本地原生启动截图
<img src="./run1.png" width="100%"/>
#### 管理后台首页
<img src="./home.png" width="100%"/>
#### 端口映射
<img src="./port-mapping2.png" width="100%"/>
## 联系我们
笔者时间、能力有限,且开源项目非一朝一夕之事,存在众多问题亦在所难免。使用、学习过程中有任何问题欢迎大家与我联系。
对项目有什么想法或者建议,可以加我微信拉交流群,或者创建[issues](https://gitee.com/dromara/neutrino-proxy/issues),一起完善项目
- 微信号:yuyunshize
- Email: aoshiguchen@dromara.org
- 微信二维码(添加时请备注"中微子进群"):
<img src="MyWeChatQRCode.jpeg" width="100%"/>
Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 KiB

+86
View File
@@ -0,0 +1,86 @@
# 从Neutrino-Proxy(中微子代理)到Solon Native
## Neutrino-Proxy(中微子代理)是啥
#### 项目简介
- [中微子代理(neutrino-proxy)](https://gitee.com/asgc/neutrino-proxy) 是一款基于netty的内网穿透神器。该项目采用最为宽松的MIT协议,因此您可以对它进行复制、修改、传播并用于任何个人或商业行为。
- 市面上基于内网穿透的常见产品有:花生壳、TeamView、cpolar等。
- 常见的使用场景:
- 本地开发调试第三方回调
- 本地开发异地接口连调
- 远程登录内网windows机器
- 将本地服务映射到外网,用于演示
- Gitee仓库:https://gitee.com/dromara/neutrino-proxy
- Github仓库:https://github.com/dromara/neutrino-proxy
- 官网地址1: https://neutrino-proxy.dromara.org
- 官网地址2: https://dromara.gitee.io/neutrino-proxy
#### 主要特点:
- 1、流量监控:首页图表、报表管理多维度流量监控。全方位掌握实时、历史代理数据。
- 2、用户/License:支持多用户、多客户端使用。后台禁用实时生效。
- 3、端口池:对外端口统一管理,支持用户、License独占端口。
- 4、端口映射:新增、编辑、删除、禁用实时生效。
- 5、Docker:服务端/客户端支持Docker一键部署。
- 6、SSL证书:隧道通信支持SSL加密,保护您的数据安全。
- 7、域名映射:支持绑定子域名,方便本地调试三方回调
- 8、多种协议:支持代理TCP、HTTP、HTTPS、UDP协议
- 9、原生部署:支持编译为原生可执行文件,更低部署门槛、更少内存占用
- 10、采用最为宽松的MIT协议,免去你的后顾之忧
## 为什么开发Neutrino-Proxy(中微子代理)
2022年4月份左右,因工作原因需要用到内网穿透。此前一段时间使用的`coplar`,由于公司担心带来安全隐患,
因此决定自己研究一下这一块。
对于常年写业务代码的一介码农来说,内网穿透就是一个黑盒。为了打开这个黑盒,我开始到处找相关的开源项目,其中包括lanproxy、ngrok、nps、frp等。
经过一翻努力,2022年6月份,实现了中微子代理1.0.0。此版本纯粹为了练手, 所以是基于自己手写的一套底层框架(包含Ioc、Aop、SqlMaper(简易版本的mybatis)、xxljob等)
实现了客户端/服务端+纯配置文件版本的TCP代理。
千里之行,始于足下。1.0.0版本发布后,经过不断的迭代,如今2.0.0版本终于和大家见面了。
## 为什么选择Solon
中微子1.0.0版本发布之后,大约7~8个月的时间,进行了大量的更新,主要围绕在底层基础框架、
管理后台这一块。
此时中微子陆陆续续开始有了一些用户,开始迫切的需要加快代理相关功能的迭代进度。而此时自己手写
的底层框架成为了最大的掣肘,单单底层框架各种细节优化、测试、参考借鉴其他框架源码这些就足以耗光
我为数不多的业余时间,更别提在这个尚不成熟的框架上开发代理功能了。任何一次底层的优化调整,可能带来的
是上层代理功能的大量重构。
于是,我开始搜寻其它替代框架。因日常工作一直都在用Spring体系,我始终相信多种技术两相印证之后学到的东西更为深刻,所以自己的开源项目始终将Spring体系作为优先级最低
的选项。最终一个或偶然、或必然的机会,我了解到了Solon。
经过对Solon项目源码、官网文档、项目更新频率、生态完善度的深入了解,最终决定选择Solon作为中微子代理的底层框架。
2023年3月12日,中微子代理1.7.0版本发布,开始正式基于Solon框架。
2023年10月30日,中微子代理2.0.0版本发布,基于Solon Native实现原生部署。
## Solon简介
#### 项目简介
- Solon是一个全新的java生态体系,给人带来一种与众不同的开发体验,让你能更快的构建自己的应用、更小的打包产出、更快的启动速度。
- Gitee仓库:https://gitee.com/noear/solon
- Github仓库:https://github.com/noear/solon
- 官网地址:https://solon.noear.org/
#### 主要特性
> 启动快 5 ~ 10 倍;qps 高 2~ 3 倍;运行时内存节省 1/3 ~ 1/2;打包可以缩到 1/2 ~ 1/10
- 克制、简洁、高效、开放、生态
- 支持 JDK8、JDK11、JDK17、JDK21
- Http、WebSocket、Socket 三种信号统一的开发体验(俗称:三源合一)
- 支持“注解”与“手动”两种模式,按需自由操控
- Not Servlet,可以适配任何基础通讯框架(最小 0.3m 运行rpc架构)
- 独特的 IOC/AOP 容器设计。不会因为插件变多而启动变很慢
- 支持 Web、Data、Job、Remoting、Cloud 等任何开发场景
- 兼顾 Handler + Context 和 Listener + Message 两种架构模式
- 强调插件式扩展,可扩展可切换;适应不同的应用场景
- 支持 GraalVm Native Image 打包
- 允许业务插件“热插”、“热拔”、“热管理”
## 最后说点什么
开源实属不易,开源国产生态型基础框架更是举步维艰。不仅需要长时间、持续、稳定的投入迭代、测试、文档撰写、bug修复、发版、推广,
还经常需要面对来自四面八方汹涌如潮的质疑。
时光无言,它磨灭了一切、也证明着一切。
一千八百多个日日夜夜、五年的风雨兼程,Solon生态已经初具规模,社区汇聚了一大批开源爱好者。
大鹏一日通风起,扶摇直上九万里。我相信国产开源在大家的努力下,一定会越来越好!
@@ -0,0 +1,88 @@
# 客户端与服务器端采用不加密、SM2+AES加密、SSL加密方式进行的性能测试比较
* 本测试不作为性能测试参考,仅作为三种数据加密方式的性能比较使用
* 本测试使用的操作系统为windows10,
* 本测试使用的测试环境配置:内存:16G,CPU:i716核
## 1、测试程序准备情况
* 将程序分别打包为`server``client``jar`包,在本地运行一个`server`
* 拷贝三个客户端配置文件,配置文件名称为`app.yml``app-sm2-aes.yml``app-ssl.yml`,并修改相应配置,适配不加密、SM2+AES加密和SSL加密
## 2、测试思路和实现
1)准备1KB、10KB、20KB、50KB、100KB、1MB、2MB、5MB、10MB、20MB、100MB、500MB的文件
2)使用Nodejs实现的anywhere工具,在本地运行简单http服务
3)在server端生成3个licenseKey,分别对应不加密、SM2+AES加密和SSL加密通道,端口分别为9101,9102和9103,并同时映射到anywhere的8000端口
4)使用Hutool里的HttpUtil工具包,对每个文件进行下载,记录下载使用时间,重复执行10次
## 3、测试结果
序号| 加密方式 | 文件大小 |响应时间(ms)
---|---|---|---
1| 不加密 | 1KB |4
2| SM2+AES | 1KB |21
3| SSL | 1KB |67
4| 不加密 | 10KB |3
5| SM2+AES | 10KB |7
6| SSL | 10KB |4
7| 不加密 | 20KB |4
8| SM2+AES | 20KB |6
9| SSL | 20KB |5
10| 不加密 | 50KB |4
11| SM2+AES | 50KB |7
12| SSL | 50KB |6
13| 不加密 | 100KB |6
14| SM2+AES | 100KB |8
15| SSL | 100KB |6
16| 不加密 | 1MB |19
17| SM2+AES | 1MB |30
18| SSL | 1MB |19
19| 不加密 | 2MB |21
20| SM2+AES | 2MB |40
21| SSL | 2MB |24
22| 不加密 | 5MB |31
23| SM2+AES | 5MB |51
24| SSL | 5MB |36
25| 不加密 | 10MB |44
26| SM2+AES | 10MB |85
27| SSL | 10MB |47
28| 不加密 | 20MB |63
29| SM2+AES | 20MB |139
30| SSL | 20MB |92
31| 不加密 | 100MB |323
32| SM2+AES | 100MB |590
33| SSL | 100MB |322
34| 不加密 | 500MB |1414
35| SM2+AES | 500MB |2797
36| SSL | 500MB |1561
## 4、测试结论
从测试结果可以看出,SSL加密的方式在大部分情况下比SM2+AES的加密方式效率高。
## 5、测试使用的代码
```java
public static void main(String[] args) {
int serialNumber = 1;
int[] ports = new int[]{9101, 9102, 9103};
Map<Integer, String> portMap = new HashMap<>();
portMap.put(9101, "不加密");
portMap.put(9102, "SM2+AES");
portMap.put(9103, "SSL");
HttpUtil.downloadBytes("http://127.0.0.1:9101/1KB"); // 使用不加密通道做一下测试,避免初始化时耗时过高
String[] fileNames = "1KB,10KB,20KB,50KB,100KB,1MB,2MB,5MB,10MB,20MB,100MB,500MB".split(",");
for (String fileName : fileNames) {
for (int port : ports) {
String url = String.format("http://127.0.0.1:%s/%s", port, fileName);
long startTime = System.currentTimeMillis();
HttpUtil.downloadBytes(url);
long endTime = System.currentTimeMillis();
long resTimeMs = endTime - startTime;
String record = String.format("%s|%s|%s|%s", serialNumber++, portMap.get(port), fileName, resTimeMs);
System.out.println(record);
}
}
}
```
+10
View File
@@ -1,3 +1,13 @@
# native 打包
```shell
mvn clean install -pl neutrino-proxy-core -am -DskipTests
cd neutrino-proxy-server
mvn clean native:compile -P native -DskipTests
```
# 管理后台问题汇总
## 问题1...
> 描述、解决方式
+1 -1
View File
@@ -1,7 +1,7 @@
module.exports = {
NODE_ENV: '"development"',
ENV_CONFIG: '"dev"',
BASE_API: '"http://103.163.47.16:8888"',
BASE_API: '"https://neutrino-proxy.asgc.fun/neutrino-proxy-server"',
USER_NAME: '"visitor"',
USER_PWD: '"123456"'
}
+1 -1
View File
@@ -1,5 +1,5 @@
module.exports = {
NODE_ENV: '"production"',
ENV_CONFIG: '"prod"',
BASE_API: '"https://api-prod"'
BASE_API: '""'
}
+1 -1
View File
@@ -70,7 +70,7 @@
"friendly-errors-webpack-plugin": "1.6.1",
"html-webpack-plugin": "2.30.1",
"node-notifier": "5.1.2",
"node-sass": "^4.7.2",
"node-sass": "^9.0.0",
"optimize-css-assets-webpack-plugin": "3.2.0",
"ora": "1.3.0",
"portfinder": "1.0.13",
+10 -4
View File
@@ -14,13 +14,11 @@ export function portPoolList() {
method: 'get'
})
}
export function availablePortList(licenseId) {
export function availablePortList(query) {
return request({
url: '/port-pool/get-available-port-list',
method: 'get',
params: {
licenseId: licenseId
}
params: query
})
}
@@ -70,3 +68,11 @@ export function deleteBatchPortPool(ids) {
}
})
}
export function portAvailable(query) {
return request({
url: '/port-pool/port-available',
method: 'get',
params: query
})
}
@@ -0,0 +1,168 @@
<!--
采用github上面的开源组件https://github.com/johnhom1024/vue-load-select
不过有个bug就是没办法展示下拉的箭头目前修复的方式等dom加载完毕后补充箭头的class
参考https://blog.csdn.net/weixin_42381896/article/details/122258563
-->
<template>
<el-select
ref="selectLoadMore"
:value="value"
v-loadmore="loadMore"
@focus="focus"
@clear="clear"
filterable
remote
:filter-method="handleSearch"
:loading="loading"
clearable
v-bind="$attrs"
v-on="$listeners"
>
<el-option
v-for="option in data"
:label="option[dictLabel]"
:value="option[dictValue]"
:key="option.value"
></el-option>
<!-- 此处加载中的value可以随便设置只要不与其他数据重复即可 -->
<el-option
v-if="hasMore"
disabled
label="加载中..."
value="-1" />
<!--补充页面无数据时显示无数据的效果-->
<el-option
v-if="!hasMore && data.length==0"
disabled
label="暂无数据"
value="-2" />
</el-select>
</template>
<script>
export default {
name: "SelectLoadMore",
props: {
value: {
default: null
},
// 列表数据
data: {
type: Array,
default: () => []
},
dictLabel: {
type: String,
default: "label"
},
dictValue: {
type: String,
default: "value"
},
// 调用页数的接口
request: {
type: Function,
default: () => {
}
},
// 传入的页码
page: {
type: [Number, String],
default: 1
},
// 是否还有更多数据
hasMore: {
type: Boolean,
default: true
}
},
directives: {
// 这里实现一个组件内部的自定义指令
loadmore: {
// 指令的定义
bind(el, binding) {
const SELECTWRAP = el.querySelector(
".el-select-dropdown .el-select-dropdown__wrap"
);
if (!SELECTWRAP) {
throw new Error('获取不到"el-select-dropdown__wrap"节点');
}
SELECTWRAP.addEventListener("scroll", () => {
// scrollTop 这里可能因为浏览器缩放存在小数点的情况,导致了滚动到底部时
// scrollHeight 减去滚动到底部时的scrollTop ,依然大于clientHeight 导致无法请求更多数据
// 这里将scrollTop向上取整 保证滚到底部时,触发调用
const CONDITION =
SELECTWRAP.scrollHeight -
Math.ceil(SELECTWRAP.scrollTop) <=
SELECTWRAP.clientHeight;
// el.scrollTop !== 0 当输入时,如果搜索结果很少,以至于没看到滚动条,那么此时的CONDITION计算结果是true,会执行bind.value(),此时不应该执行,否则搜索结果不匹配
if (CONDITION && SELECTWRAP.scrollTop !== 0) {
binding.value();
}
});
}
}
},
data() {
return {
keyword: "", // 存储关键字用
loading: false
};
},
methods: {
// 请求下一页的数据
loadMore() {
// 如果没有更多数据,则不请求
if (!this.hasMore) {
return;
}
// 如果intercept属性为true则不请求数据,
if (this.loadMore.intercept) {
return;
}
this.loadMore.intercept = true;
this.request({
page: this.page + 1,
more: true,
keyword: this.keyword
}).then(() => {
this.loadMore.intercept = false;
});
},
// 选中下拉框没有数据时,自动请求第一页的数据
focus() {
if (!this.data.length) {
this.request({page: 1});
}
},
handleSearch(keyword) {
this.keyword = keyword;
this.loading = true;
console.log(keyword);
this.request({page: 1, keyword: keyword}).then(() => {
this.loading = false;
});
},
// 删除选中时,如果请求了关键字,则清除关键字再请求第一页的数据
clear() {
if (this.keyword) {
this.keyword = "";
this.request({page: 1});
}
}
},
mounted() {
this.$nextTick(function () {
// github代码有个bug,下拉的箭头无法加载的问题,需要等dom页面加载完毕后,添加箭头的class
let rulesDom = this.$refs["selectLoadMore"].$el.querySelector(
".el-input .el-input__suffix .el-input__suffix-inner .el-input__icon"
);// 找到dom
rulesDom.classList.add("el-icon-arrow-up");// 对dom新增class
})
}
}
</script>
<style scoped>
</style>
+1
View File
@@ -97,6 +97,7 @@ export default {
readings: 'Readings',
status: 'Status',
actions: 'Actions',
access: 'Access',
edit: 'Edit',
publish: 'Publish',
draft: 'Draft',
+2
View File
@@ -117,6 +117,8 @@ export default {
readings: '阅读数',
status: '状态',
actions: '操作',
access: '访问',
openWebPage: '打开网页',
edit: '编辑',
publish: '发布',
draft: '草稿',
@@ -1,7 +1,7 @@
<template>
<div class="app-container calendar-list-container">
<div class="filter-container">
<el-select v-model="listQuery.userId" placeholder="请选择用户" clearable>
<el-select v-model="listQuery.userId" placeholder="请选择用户" filterable clearable>
<el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
<el-select v-model="listQuery.isOnline" placeholder="请选择在线状态" clearable>
@@ -1,10 +1,10 @@
<template>
<div class="app-container calendar-list-container">
<div class="filter-container" style="display:flex">
<el-select v-model="listQuery.userId" placeholder="请选择用户" clearable style="margin-right:10px;width: 120px;">
<el-select v-model="listQuery.userId" placeholder="请选择用户" filterable clearable style="margin-right:10px;width: 120px;">
<el-option v-for="item in userList" :key="item.loginName" :label="item.name" :value="item.id" />
</el-select>
<el-select v-model="listQuery.licenseId" placeholder="请选择license" clearable style="margin-right:10px;width: 135px;">
<el-select v-model="listQuery.licenseId" placeholder="请选择license" filterable clearable style="margin-right:10px;width: 135px;">
<el-option v-for="item in licenseList" :key="item.key" :label="item.name" :value="item.id" />
</el-select>
<el-select v-model="listQuery.protocal" placeholder="请选择协议" clearable style="margin-right:10px;width: 120px;">
@@ -90,11 +90,15 @@
<el-tag :type="scope.row.isOnline | statusFilter">{{ scope.row.isOnline | isOnlineName }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.actions')" width="230" class-name="small-padding fixed-width">
<el-table-column class-name="status-col" :label="$t('table.access')" width="120">
<template slot-scope="scope">
<el-button size="mini" v-if="(scope.row.protocal === 'HTTP' || scope.row.protocal === 'HTTP(S)')" @click="handleOpenWebPage(scope.row)">{{$t('table.openWebPage')}}</el-button>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.actions')" width="320" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="handleUpdate(scope.row)">{{ $t('table.edit') }}</el-button>
<el-button v-if="scope.row.enable == '1'" size="mini" type="danger" @click="handleModifyStatus(scope.row, 2)">{{
$t('table.disable') }}</el-button>
<el-button v-if="scope.row.enable == '1'" size="mini" type="danger" @click="handleModifyStatus(scope.row, 2)">{{$t('table.disable')}}</el-button>
<el-button v-if="scope.row.enable == '2'" size="mini" type="success"
@click="handleModifyStatus(scope.row, 1)">{{ $t('table.enable') }}</el-button>
<!-- <el-button size="mini" type="danger" @click="handleDelete(scope.row,'deleted')">{{$t('table.delete')}}</el-button>-->
@@ -142,11 +146,16 @@
</el-option>
</el-select>
</el-form-item>
<el-form-item :label="$t('服务端端口')" prop="serverPort">
<el-select style="width: 280px;" class="filter-item" v-model="temp.serverPort" placeholder="请选择" filterable>
<el-option v-for="item in serverPortList" :key="item.port" :label="item.port" :value="item.port">
</el-option>
</el-select>
<el-form-item :label="$t('服务端端口')" prop="serverPort" >
<load-select style="width: 280px;" class="filter-item"
v-model="temp.serverPort"
:data="serverPortList"
:page="loadServerPortQuery.page"
:hasMore="more"
:clearable="false"
:dictLabel="'port'"
:dictValue="'port'"
:request="loadServerPort"/>
</el-form-item>
<el-form-item :label="$t('客户端IP')" prop="clientIp">
<el-input v-model="temp.clientIp"></el-input>
@@ -159,6 +168,14 @@
<template slot="append">.{{ domainName }}</template>
</el-input>
</el-form-item>
<el-form-item :label="$t('响应数量')" prop="proxyResponses" v-if="temp.protocal === 'UDP'">
<el-input v-model="temp.proxyResponses"></el-input>
</el-form-item>
<el-form-item :label="$t('超时时间')" prop="proxyTimeoutMs" v-if="temp.protocal === 'UDP'">
<el-input v-model="temp.proxyTimeoutMs">
<template slot="append">毫秒</template>
</el-input>
</el-form-item>
<el-form-item :label="$t('描述')" prop="description">
<el-input v-model="temp.description"></el-input>
</el-form-item>
@@ -187,7 +204,7 @@
<script>
import { fetchList, createUserPortMapping, updateUserPortMapping, updateEnableStatus, deletePortMapping } from '@/api/portMapping'
import { portPoolList, availablePortList } from '@/api/portPool'
import { availablePortList, portAvailable } from '@/api/portPool'
import { licenseList, licenseAuthList } from '@/api/license'
import { protocalList } from '@/api/protocal'
import { userList } from '@/api/user'
@@ -196,6 +213,8 @@ import waves from '@/directive/waves' // 水波纹指令
import { parseTime } from '@/utils'
import ButtonPopover from '../../components/Button/buttonPopover'
import DropdownTable from '../../components/Dropdown/DropdownTable'
// 下拉选择加载组件
import loadSelect from "@/components/Select/SelectLoadMore";
const calendarTypeOptions = [
{ key: 'CN', display_name: 'China' },
@@ -217,9 +236,22 @@ export default {
},
components: {
DropdownTable,
ButtonPopover
ButtonPopover,
loadSelect
},
data() {
const isPortAvailable = (rule, value, callback) => {
if (value != null) {
const param = { port: value, portMappingId: this.temp.id }
portAvailable(param).then(res => {
if (!res.data.data) {
return callback(new Error('该端口被占用'))
} else {
callback()
}
})
}
}
return {
tableKey: 0,
list: null,
@@ -257,7 +289,9 @@ export default {
serverPort: undefined,
clientIp: undefined,
clientPort: undefined,
protocal: undefined
protocal: undefined,
proxyResponses: undefined,
proxyTimeoutMs: undefined
},
selectObj: {
statusOptions: [{ label: '启用', value: 1 }, { label: '禁用', value: 2 }],
@@ -273,7 +307,9 @@ export default {
pvData: [],
rules: {
licenseId: [{ required: true, message: '请选择License', trigger: 'blur,change' }],
serverPort: [{ required: true, message: '请输入服务端端口', trigger: 'blur' }],
serverPort: [{ required: true, message: '请输入服务端端口', trigger: 'blur' },
// { validator: isPortAvailable, trigger: 'change' }
],
clientIp: [{ required: true, message: '请输入客户端IP', trigger: 'blur' }],
clientPort: [{ required: true, message: '请输入客户端端口', trigger: 'blur' }],
protocal: [{ required: true, message: '请选择协议', trigger: 'blur' }]
@@ -282,7 +318,13 @@ export default {
countryColumns: [
{ prop: 'userName', label: '用户名', align: 'center' },
{ prop: 'name', label: 'License', align: 'center' }
]
],
loadServerPortQuery:{ //下拉框加载数据请求参数
page:1,
size:50,
licenseId:null,
},
more: true,
}
},
filters: {
@@ -343,15 +385,9 @@ export default {
this.domainName = response.data.data
})
},
getPortPoolList() {
portPoolList().then(response => {
this.serverPortList = response.data.data
})
},
getAvailablePortList(licenseId) {
availablePortList(licenseId).then(response => {
this.serverPortList = response.data.data
})
this.loadServerPortQuery.licenseId = licenseId;
this.serverPortList = []; //清掉数据
},
getAllUserList() {
userList().then(response => {
@@ -405,9 +441,13 @@ export default {
serverPort: undefined,
clientIp: '127.0.0.1',
clientPort: undefined,
userId: undefined
userId: undefined,
proxyResponses: undefined,
proxyTimeoutMs: undefined
}
this.serverPortList = []
this.loadServerPortQuery.licenseId = null;
this.more = true;
},
handleCreate() {
this.resetTemp()
@@ -435,6 +475,13 @@ export default {
}
})
},
handleOpenWebPage(row) {
let url = location.protocol + '//' + location.hostname + ':' + row.serverPort
if (row.domain) {
url = location.protocol + '//' + row.domain
}
open(url)
},
handleUpdate(row) {
this.temp = Object.assign({}, row) // copy obj
this.temp.timestamp = new Date(this.temp.timestamp)
@@ -526,6 +573,36 @@ export default {
return v[j]
}
}))
},
// 传入给load-select组件的函数
loadServerPort({page = 1, more = false, keyword = ""} = {}) {
if(this.loadServerPortQuery.licenseId==null || this.loadServerPortQuery.licenseId==''){
this.more = false;
this.$message({
message: '请先选择License',
type: 'warning'
})
return ;
}
return new Promise(resolve => {
this.loadServerPortQuery.page = page;
this.loadServerPortQuery.keyword = keyword;
// 访问后端接口API
availablePortList(this.loadServerPortQuery).then(res => {
let result = res.data;
if (more) {
this.serverPortList = [...this.serverPortList, ...result.data.records];
} else {
this.serverPortList = result.data.records;
}
// this.loadServerPortQuery.page = result.data.current;
let {total, current, size} = result.data;
this.more = page * size < total;
this.loadServerPortQuery.page = current;
resolve();
});
});
}
}
}
+9 -6
View File
@@ -1,12 +1,15 @@
FROM openjdk:8-jdk-alpine
FROM openjdk:21-jdk-oracle
#同步时间
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
apk update && apk add wget unzip vim && apk add -U tzdata && \
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
#RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
# apk update && apk add wget unzip vim && apk add -U tzdata && \
# ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
# 设置时区为北京时间
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN mkdir -p /root/neutrino-proxy/config
WORKDIR /root/neutrino-proxy
COPY ../neutrino-proxy-client/target/neutrino-proxy-client.jar /root/neutrino-proxy/neutrino-proxy-client.jar
COPY ../neutrino-proxy-client/src/main/resources/app.yml /root/neutrino-proxy/config
COPY ./target/neutrino-proxy-client.jar /root/neutrino-proxy/neutrino-proxy-client.jar
COPY ./src/main/resources/app-copy.yml /root/neutrino-proxy/config/app.yml
#VOLUME ["/root/neutrino-proxy"]
ENTRYPOINT ["java","-jar","neutrino-proxy-client.jar","config=./config/app.yml"]
+7
View File
@@ -28,6 +28,13 @@
<build>
<finalName>${project.artifactId}</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>*.yml</include>
</includes>
</resource>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
<filtering>false</filtering>
@@ -1,15 +1,10 @@
package org.dromara.neutrinoproxy.client;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.noear.solon.Solon;
import org.noear.solon.SolonApp;
import org.noear.solon.Utils;
import org.noear.solon.annotation.SolonMain;
import org.slf4j.LoggerFactory;
/**
*
@@ -22,14 +17,18 @@ public class ProxyClient {
public static void main(String[] args) {
Solon.start(ProxyClient.class, args, app -> {
setAlias("neutrino.proxy.client.serverIp", "serverIp");
setAlias("neutrino.proxy.client.serverPort", "serverPort");
setAlias("neutrino.proxy.client.sslEnable", "sslEnable");
setAlias("neutrino.proxy.client.jksPath", "jksPath");
setAlias("neutrino.proxy.client.keyStorePassword", "keyStorePassword");
setAlias("neutrino.proxy.client.licenseKey", "licenseKey");
// 设置日志级别
setLogLevel(app);
String loglevel = System.getenv("LOG_LEVEL");
if (Utils.isNotEmpty(loglevel)) {
app.cfg().put("solon.logging.logger.root.level", loglevel);
}
setAlias("neutrino.proxy.tunnel.server-ip", "serverIp");
setAlias("neutrino.proxy.tunnel.server-port", "serverPort");
setAlias("neutrino.proxy.tunnel.sm2-encrypt-enable", "sm2EncryptEnable");
setAlias("neutrino.proxy.tunnel.ssl-enable", "sslEnable");
setAlias("neutrino.proxy.tunnel.jks-path", "jksPath");
setAlias("neutrino.proxy.tunnel.key-store-password", "keyStorePassword");
setAlias("neutrino.proxy.tunnel.license-key", "licenseKey");
});
}
@@ -45,20 +44,4 @@ public class ProxyClient {
}
}
private static void setLogLevel(SolonApp app) {
String loggerLevel = app.cfg().get("neutrino.proxy.logger.level");
if (StringUtils.isBlank(loggerLevel)) {
return;
}
try {
Level level = Level.toLevel(loggerLevel);
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
for (Logger logger : loggerContext.getLoggerList()) {
logger.setLevel(level);
}
} catch (Exception e) {
log.error("日志级别设置失败", e);
}
}
}
@@ -0,0 +1,26 @@
package org.dromara.neutrinoproxy.client.config;
import org.noear.solon.annotation.Component;
import org.noear.solon.aot.RuntimeNativeMetadata;
import org.noear.solon.aot.RuntimeNativeRegistrar;
import org.noear.solon.aot.hint.MemberCategory;
import org.noear.solon.core.AppContext;
/**
* @author songyinyin
* @since 2023/10/21 22:50
*/
@Component
public class NeutrinoClientRuntimeNativeRegistrar implements RuntimeNativeRegistrar {
@Override
public void register(AppContext context, RuntimeNativeMetadata metadata) {
metadata.registerResourceInclude("test.jks");
metadata.registerReflection(ProxyConfig.Protocol.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
metadata.registerReflection(ProxyConfig.Client.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
metadata.registerReflection(ProxyConfig.Tunnel.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
metadata.registerReflection(ProxyConfig.Tcp.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
metadata.registerReflection(ProxyConfig.Udp.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
metadata.registerReflection(ProxyConfig.Reconnection.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
}
}
@@ -14,6 +14,8 @@ import org.noear.solon.annotation.Inject;
public class ProxyConfig {
@Inject("${neutrino.proxy.protocol}")
private Protocol protocol;
@Inject("${neutrino.proxy.tunnel}")
private Tunnel tunnel;
@Inject("${neutrino.proxy.client}")
private Client client;
@@ -30,11 +32,12 @@ public class ProxyConfig {
}
@Data
public static class Client {
public static class Tunnel {
private String keyStorePassword;
private String jksPath;
private String serverIp;
private Integer serverPort;
private Boolean sm2EncryptEnable;
private Boolean sslEnable;
private Integer obtainLicenseInterval;
private String licenseKey;
@@ -45,9 +48,28 @@ public class ProxyConfig {
private Reconnection reconnection;
}
@Data
public static class Client {
// private Tcp tcp;
private Udp udp;
}
@Data
public static class Reconnection {
private Integer intervalSeconds;
private Boolean unlimited;
}
@Data
public static class Tcp {
}
@Data
public static class Udp {
private Integer bossThreadCount;
private Integer workThreadCount;
private String puppetPortRange;
private Boolean transferLogEnable;
}
}
@@ -1,8 +1,18 @@
package org.dromara.neutrinoproxy.client.config;
import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.dromara.neutrinoproxy.core.ProxyMessageHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateHandler;
import org.dromara.neutrinoproxy.client.core.*;
import org.dromara.neutrinoproxy.client.util.ProxyUtil;
import org.dromara.neutrinoproxy.core.*;
import org.dromara.neutrinoproxy.core.aot.NeutrinoCoreRuntimeNativeRegistrar;
import org.dromara.neutrinoproxy.core.dispatcher.DefaultDispatcher;
import org.dromara.neutrinoproxy.core.dispatcher.Dispatcher;
import io.netty.bootstrap.Bootstrap;
@@ -10,8 +20,10 @@ import io.netty.channel.ChannelHandlerContext;
import org.noear.solon.Solon;
import org.noear.solon.annotation.Bean;
import org.noear.solon.annotation.Configuration;
import org.noear.solon.annotation.Inject;
import org.noear.solon.core.bean.LifecycleBean;
import java.net.InetSocketAddress;
import java.util.List;
/**
@@ -25,25 +37,177 @@ public class ProxyConfiguration implements LifecycleBean {
@Override
public void start() throws Throwable {
List<ProxyMessageHandler> list = Solon.context().getBeansOfType(ProxyMessageHandler.class);
Dispatcher<ChannelHandlerContext, ProxyMessage> dispatcher = new DefaultDispatcher<>("消息调度器", list,
Dispatcher<ChannelHandlerContext, ProxyMessage> dispatcher = new DefaultDispatcher<>("MessageDispatcher", list,
proxyMessage -> ProxyDataTypeEnum.of((int)proxyMessage.getType()) == null ?
null : ProxyDataTypeEnum.of((int)proxyMessage.getType()).getName());
Solon.context().wrapAndPut(Dispatcher.class, dispatcher);
}
@Bean("cmdTunnelBootstrap")
public Bootstrap cmdTunnelBootstrap() {
return new Bootstrap();
@Bean("tunnelWorkGroup")
public NioEventLoopGroup tunnelWorkGroup(@Inject ProxyConfig proxyConfig) {
return new NioEventLoopGroup(proxyConfig.getTunnel().getThreadCount());
}
@Bean("proxyTunnelBootstrap")
public Bootstrap proxyTunnelBootstrap() {
return new Bootstrap();
@Bean("tcpRealServerWorkGroup")
public NioEventLoopGroup tcpRealServerWorkGroup(@Inject ProxyConfig proxyConfig) {
// 暂时先公用此配置
return new NioEventLoopGroup(proxyConfig.getTunnel().getThreadCount());
}
@Bean("udpServerGroup")
public NioEventLoopGroup udpServerGroup(@Inject ProxyConfig proxyConfig) {
// 暂时先公用此配置
return new NioEventLoopGroup(proxyConfig.getClient().getUdp().getBossThreadCount());
}
@Bean("udpWorkGroup")
public NioEventLoopGroup udpWorkGroup(@Inject ProxyConfig proxyConfig) {
// 暂时先公用此配置
return new NioEventLoopGroup(proxyConfig.getClient().getUdp().getWorkThreadCount());
}
@Bean("cmdTunnelBootstrap")
public Bootstrap cmdTunnelBootstrap(@Inject ProxyConfig proxyConfig,
@Inject("tunnelWorkGroup") NioEventLoopGroup tunnelWorkGroup) {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(tunnelWorkGroup);
bootstrap.channel(NioSocketChannel.class);
// bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000);
// bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
// /**
// * TCP/IP协议中,无论发送多少数据,总是要在数据前面加上协议头,同时,对方接收到数据,也需要发送ACK表示确认。为了尽可能的利用网络带宽,TCP总是希望尽可能的发送足够大的数据。(一个连接会设置MSS参数,因此,TCP/IP希望每次都能够以MSS尺寸的数据块来发送数据)。
// * Nagle算法就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。
// */
// bootstrap.option(ChannelOption.TCP_NODELAY, true);
bootstrap.remoteAddress(InetSocketAddress.createUnresolved(proxyConfig.getTunnel().getServerIp(), proxyConfig.getTunnel().getServerPort()));
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
if (proxyConfig.getTunnel().getSslEnable()) {
ch.pipeline().addLast(ProxyUtil.createSslHandler(proxyConfig));
}
if (null != proxyConfig.getTunnel().getTransferLogEnable() && proxyConfig.getTunnel().getTransferLogEnable()) {
ch.pipeline().addFirst(new LoggingHandler(CmdChannelHandler.class));
}
ch.pipeline().addLast(new ProxyMessageDecoder(proxyConfig.getProtocol().getMaxFrameLength(),
proxyConfig.getProtocol().getLengthFieldOffset(), proxyConfig.getProtocol().getLengthFieldLength(),
proxyConfig.getProtocol().getLengthAdjustment(), proxyConfig.getProtocol().getInitialBytesToStrip()));
ch.pipeline().addLast(new ProxyMessageEncoder());
ch.pipeline().addLast(new IdleStateHandler(proxyConfig.getProtocol().getReadIdleTime(), proxyConfig.getProtocol().getWriteIdleTime(), proxyConfig.getProtocol().getAllIdleTimeSeconds()));
ch.pipeline().addLast(new CmdChannelHandler());
}
});
return bootstrap;
}
@Bean("tcpProxyTunnelBootstrap")
public Bootstrap tcpProxyTunnelBootstrap(@Inject ProxyConfig proxyConfig,
@Inject("tunnelWorkGroup") NioEventLoopGroup tunnelWorkGroup) {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(tunnelWorkGroup);
bootstrap.channel(NioSocketChannel.class);
bootstrap.remoteAddress(InetSocketAddress.createUnresolved(proxyConfig.getTunnel().getServerIp(), proxyConfig.getTunnel().getServerPort()));
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
if (proxyConfig.getTunnel().getSslEnable()) {
ch.pipeline().addLast(ProxyUtil.createSslHandler(proxyConfig));
}
if (null != proxyConfig.getTunnel().getTransferLogEnable() && proxyConfig.getTunnel().getTransferLogEnable()) {
ch.pipeline().addFirst(new LoggingHandler(TcpProxyChannelHandler.class));
}
ch.pipeline().addLast(new ProxyMessageDecoder(proxyConfig.getProtocol().getMaxFrameLength(),
proxyConfig.getProtocol().getLengthFieldOffset(), proxyConfig.getProtocol().getLengthFieldLength(),
proxyConfig.getProtocol().getLengthAdjustment(), proxyConfig.getProtocol().getInitialBytesToStrip()));
ch.pipeline().addLast(new ProxyMessageEncoder());
ch.pipeline().addLast(new IdleStateHandler(proxyConfig.getProtocol().getReadIdleTime(), proxyConfig.getProtocol().getWriteIdleTime(), proxyConfig.getProtocol().getAllIdleTimeSeconds()));
ch.pipeline().addLast(new TcpProxyChannelHandler());
}
});
return bootstrap;
}
@Bean("udpProxyTunnelBootstrap")
public Bootstrap udpProxyTunnelBootstrap(@Inject ProxyConfig proxyConfig,
@Inject("tunnelWorkGroup") NioEventLoopGroup tunnelWorkGroup) {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(tunnelWorkGroup);
bootstrap.channel(NioSocketChannel.class);
bootstrap.remoteAddress(InetSocketAddress.createUnresolved(proxyConfig.getTunnel().getServerIp(), proxyConfig.getTunnel().getServerPort()));
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
if (proxyConfig.getTunnel().getSslEnable()) {
ch.pipeline().addLast(ProxyUtil.createSslHandler(proxyConfig));
}
if (null != proxyConfig.getTunnel().getTransferLogEnable() && proxyConfig.getTunnel().getTransferLogEnable()) {
ch.pipeline().addFirst(new LoggingHandler(TcpProxyChannelHandler.class));
}
ch.pipeline().addLast(new ProxyMessageDecoder(proxyConfig.getProtocol().getMaxFrameLength(),
proxyConfig.getProtocol().getLengthFieldOffset(), proxyConfig.getProtocol().getLengthFieldLength(),
proxyConfig.getProtocol().getLengthAdjustment(), proxyConfig.getProtocol().getInitialBytesToStrip()));
ch.pipeline().addLast(new ProxyMessageEncoder());
ch.pipeline().addLast(new IdleStateHandler(proxyConfig.getProtocol().getReadIdleTime(), proxyConfig.getProtocol().getWriteIdleTime(), proxyConfig.getProtocol().getAllIdleTimeSeconds()));
ch.pipeline().addLast(new UdpProxyChannelHandler());
}
});
return bootstrap;
}
@Bean("realServerBootstrap")
public Bootstrap realServerBootstrap() {
return new Bootstrap();
public Bootstrap realServerBootstrap(@Inject ProxyConfig proxyConfig,
@Inject("tcpRealServerWorkGroup") NioEventLoopGroup tcpRealServerWorkGroup
) {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(tcpRealServerWorkGroup);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
if (null != proxyConfig.getTunnel().getTransferLogEnable() && proxyConfig.getTunnel().getTransferLogEnable()) {
ch.pipeline().addFirst(new LoggingHandler(RealServerChannelHandler.class));
}
ch.pipeline().addLast(new RealServerChannelHandler());
}
});
return bootstrap;
}
@Bean("udpServerBootstrap")
public Bootstrap udpServerBootstrap(@Inject ProxyConfig proxyConfig,
@Inject("udpServerGroup") NioEventLoopGroup udpServerGroup,
@Inject("udpWorkGroup") NioEventLoopGroup udpWorkGroup) {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(udpServerGroup)
// 主线程处理
.channel(NioDatagramChannel.class)
// 广播
.option(ChannelOption.SO_BROADCAST, true)
// 设置读缓冲区为2M
.option(ChannelOption.SO_RCVBUF, 2048 * 1024)
// 设置写缓冲区为1M
.option(ChannelOption.SO_SNDBUF, 1024 * 1024)
.handler(new ChannelInitializer<NioDatagramChannel>() {
@Override
protected void initChannel(NioDatagramChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
if (null != proxyConfig.getClient().getUdp().getTransferLogEnable() && proxyConfig.getClient().getUdp().getTransferLogEnable()) {
ch.pipeline().addFirst(new LoggingHandler(UdpRealServerHandler.class));
}
pipeline.addLast(udpWorkGroup, new UdpRealServerHandler());
}
});
return bootstrap;
}
@Bean
public NeutrinoCoreRuntimeNativeRegistrar neutrinoCoreRuntimeNativeRegistrar() {
return new NeutrinoCoreRuntimeNativeRegistrar();
}
}
@@ -0,0 +1,13 @@
package org.dromara.neutrinoproxy.client.constant;
import io.netty.util.AttributeKey;
import org.dromara.neutrinoproxy.client.util.UdpChannelBindInfo;
/**
* @author: aoshiguchen
* @date: 2023/9/21
*/
public interface Constants {
AttributeKey<UdpChannelBindInfo> UDP_CHANNEL_BIND_KEY = AttributeKey.newInstance("udpChannelBindKey");
}
@@ -24,15 +24,15 @@ public class CmdChannelHandler extends SimpleChannelInboundHandler<ProxyMessage>
public CmdChannelHandler() {
ProxyConfig proxyConfig = Solon.context().getBean(ProxyConfig.class);
if (null != proxyConfig.getClient() && null != proxyConfig.getClient().getHeartbeatLogEnable()) {
transferLogEnable = proxyConfig.getClient().getHeartbeatLogEnable();
if (null != proxyConfig.getClient() && null != proxyConfig.getTunnel().getHeartbeatLogEnable()) {
transferLogEnable = proxyConfig.getTunnel().getHeartbeatLogEnable();
}
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception {
if (ProxyMessage.TYPE_HEARTBEAT != proxyMessage.getType() || transferLogEnable) {
log.debug("Client CmdChannel recieved proxy message, type is {}", proxyMessage.getType());
log.debug("[CMD Channel]Client CmdChannel recieved proxy message, type is {}", proxyMessage.getType());
}
Solon.context().getBean(Dispatcher.class).dispatch(ctx, proxyMessage);
}
@@ -49,7 +49,7 @@ public class CmdChannelHandler extends SimpleChannelInboundHandler<ProxyMessage>
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
log.info("Client CmdChannel 与服务端断开连接");
log.info("[CMD Channel]Client CmdChannel disconnect");
ProxyUtil.setCmdChannel(null);
ProxyUtil.clearRealServerChannels();
@@ -58,7 +58,7 @@ public class CmdChannelHandler extends SimpleChannelInboundHandler<ProxyMessage>
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.error("Client CmdChannel Error channelId:{}", ctx.channel().id().asLongText(), cause);
log.error("[CMD Channel]Client CmdChannel Error channelId:{}", ctx.channel().id().asLongText(), cause);
ctx.close();
}
@@ -69,14 +69,14 @@ public class CmdChannelHandler extends SimpleChannelInboundHandler<ProxyMessage>
switch (event.state()) {
case READER_IDLE:
// 读超时,断开连接
log.error("Client CmdChannel 读超时,断开连接");
log.error("[CMD Channel] Read timeout disconnect");
ctx.channel().close();
break;
case WRITER_IDLE:
ctx.channel().writeAndFlush(ProxyMessage.buildHeartbeatMessage());
break;
case ALL_IDLE:
log.error("Client CmdChannel 读写超时,断开连接");
log.error("[CMD Channel] ReadWrite timeout disconnect");
ctx.close();
break;
}
@@ -1,33 +1,18 @@
package org.dromara.neutrinoproxy.client.core;
import cn.hutool.core.util.StrUtil;
import io.netty.handler.logging.LoggingHandler;
import org.dromara.neutrinoproxy.client.config.ProxyConfig;
import org.dromara.neutrinoproxy.client.util.ProxyUtil;
import org.dromara.neutrinoproxy.client.util.UdpServerUtil;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.dromara.neutrinoproxy.core.ProxyMessageDecoder;
import org.dromara.neutrinoproxy.core.ProxyMessageEncoder;
import org.dromara.neutrinoproxy.core.util.FileUtil;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.Solon;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Init;
import org.noear.solon.annotation.Inject;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.security.KeyStore;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -44,24 +29,13 @@ public class ProxyClientService {
private ProxyConfig proxyConfig;
@Inject("cmdTunnelBootstrap")
private Bootstrap cmdTunnelBootstrap;
@Inject("proxyTunnelBootstrap")
private Bootstrap proxyTunnelBootstrap;
@Inject("realServerBootstrap")
private Bootstrap realServerBootstrap;
@Inject("udpServerBootstrap")
private Bootstrap udpServerBootstrap;
private volatile Channel channel;
// /**
// * 重连间隔(秒)
// */
// private static final long RECONNECT_INTERVAL_SECONDS = 5;
/**
* 重连次数
*/
private volatile int reconnectCount = 0;
// /**
// * 启用重连服务
// */
// private volatile boolean reconnectServiceEnable = false;
private NioEventLoopGroup workerGroup;
/**
* 重连服务执行器
*/
@@ -69,100 +43,35 @@ public class ProxyClientService {
@Init
public void init() {
this.reconnectExecutor.scheduleWithFixedDelay(this::reconnect, 10, proxyConfig.getClient().getReconnection().getIntervalSeconds(), TimeUnit.SECONDS);
this.workerGroup = new NioEventLoopGroup(proxyConfig.getClient().getThreadCount());
realServerBootstrap.group(workerGroup);
realServerBootstrap.channel(NioSocketChannel.class);
realServerBootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
if (null != proxyConfig.getClient().getTransferLogEnable() && proxyConfig.getClient().getTransferLogEnable()) {
ch.pipeline().addFirst(new LoggingHandler(RealServerChannelHandler.class));
}
ch.pipeline().addLast(new RealServerChannelHandler());
}
});
proxyTunnelBootstrap.group(workerGroup);
proxyTunnelBootstrap.channel(NioSocketChannel.class);
proxyTunnelBootstrap.remoteAddress(InetSocketAddress.createUnresolved(proxyConfig.getClient().getServerIp(), proxyConfig.getClient().getServerPort()));
proxyTunnelBootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
if (proxyConfig.getClient().getSslEnable()) {
ch.pipeline().addLast(createSslHandler());
}
if (null != proxyConfig.getClient().getTransferLogEnable() && proxyConfig.getClient().getTransferLogEnable()) {
ch.pipeline().addFirst(new LoggingHandler(ProxyChannelHandler.class));
}
ch.pipeline().addLast(new ProxyMessageDecoder(proxyConfig.getProtocol().getMaxFrameLength(),
proxyConfig.getProtocol().getLengthFieldOffset(), proxyConfig.getProtocol().getLengthFieldLength(),
proxyConfig.getProtocol().getLengthAdjustment(), proxyConfig.getProtocol().getInitialBytesToStrip()));
ch.pipeline().addLast(new ProxyMessageEncoder());
ch.pipeline().addLast(new IdleStateHandler(proxyConfig.getProtocol().getReadIdleTime(), proxyConfig.getProtocol().getWriteIdleTime(), proxyConfig.getProtocol().getAllIdleTimeSeconds()));
ch.pipeline().addLast(new ProxyChannelHandler());
}
});
cmdTunnelBootstrap.group(workerGroup);
cmdTunnelBootstrap.channel(NioSocketChannel.class);
// cmdTunnelBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000);
// cmdTunnelBootstrap.option(ChannelOption.SO_KEEPALIVE, true);
// /**
// * TCP/IP协议中,无论发送多少数据,总是要在数据前面加上协议头,同时,对方接收到数据,也需要发送ACK表示确认。为了尽可能的利用网络带宽,TCP总是希望尽可能的发送足够大的数据。(一个连接会设置MSS参数,因此,TCP/IP希望每次都能够以MSS尺寸的数据块来发送数据)。
// * Nagle算法就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。
// */
// cmdTunnelBootstrap.option(ChannelOption.TCP_NODELAY, true);
cmdTunnelBootstrap.remoteAddress(InetSocketAddress.createUnresolved(proxyConfig.getClient().getServerIp(), proxyConfig.getClient().getServerPort()));
cmdTunnelBootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
if (proxyConfig.getClient().getSslEnable()) {
ch.pipeline().addLast(createSslHandler());
}
if (null != proxyConfig.getClient().getTransferLogEnable() && proxyConfig.getClient().getTransferLogEnable()) {
ch.pipeline().addFirst(new LoggingHandler(CmdChannelHandler.class));
}
ch.pipeline().addLast(new ProxyMessageDecoder(proxyConfig.getProtocol().getMaxFrameLength(),
proxyConfig.getProtocol().getLengthFieldOffset(), proxyConfig.getProtocol().getLengthFieldLength(),
proxyConfig.getProtocol().getLengthAdjustment(), proxyConfig.getProtocol().getInitialBytesToStrip()));
ch.pipeline().addLast(new ProxyMessageEncoder());
ch.pipeline().addLast(new IdleStateHandler(proxyConfig.getProtocol().getReadIdleTime(), proxyConfig.getProtocol().getWriteIdleTime(), proxyConfig.getProtocol().getAllIdleTimeSeconds()));
ch.pipeline().addLast(new CmdChannelHandler());
}
});
this.reconnectExecutor.scheduleWithFixedDelay(this::reconnect, 10, proxyConfig.getTunnel().getReconnection().getIntervalSeconds(), TimeUnit.SECONDS);
try {
this.start();
UdpServerUtil.initCache(proxyConfig, udpServerBootstrap);
} catch (Exception e) {
// 启动连不上也做一下重连,因此先catch异常
log.error("[客户端指令隧道] 启动异常", e);
log.error("[CmdChannel] start error", e);
}
}
public void start() {
if (StrUtil.isEmpty(proxyConfig.getClient().getServerIp())) {
if (StrUtil.isEmpty(proxyConfig.getTunnel().getServerIp())) {
log.error("not found server-ip config.");
Solon.stop();
return;
}
if (null == proxyConfig.getClient().getServerPort()) {
if (null == proxyConfig.getTunnel().getServerPort()) {
log.error("not found server-port config.");
Solon.stop();
return;
}
if (null != proxyConfig.getClient().getSslEnable() && proxyConfig.getClient().getSslEnable()
&& StrUtil.isEmpty(proxyConfig.getClient().getJksPath())) {
if (null != proxyConfig.getTunnel().getSslEnable() && proxyConfig.getTunnel().getSslEnable()
&& StrUtil.isEmpty(proxyConfig.getTunnel().getJksPath())) {
log.error("not found jks-path config.");
Solon.stop();
return;
}
if (StrUtil.isEmpty(proxyConfig.getClient().getLicenseKey())) {
if (StrUtil.isEmpty(proxyConfig.getTunnel().getLicenseKey())) {
log.error("not found license-key config.");
Solon.stop();
return;
@@ -174,7 +83,7 @@ public class ProxyClientService {
log.error("client start error", e);
}
} else {
channel.writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getClient().getLicenseKey(), ProxyUtil.getClientId()));
channel.writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getTunnel().getLicenseKey(), ProxyUtil.getClientId()));
}
}
@@ -191,45 +100,19 @@ public class ProxyClientService {
channel = future.channel();
// 连接成功,向服务器发送客户端认证信息(licenseKey)
ProxyUtil.setCmdChannel(future.channel());
future.channel().writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getClient().getLicenseKey(), ProxyUtil.getClientId()));
log.info("[客户端指令隧道] 连接代理服务成功. channelId:{}", future.channel().id().asLongText());
future.channel().writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getTunnel().getLicenseKey(), ProxyUtil.getClientId()));
log.info("[CmdChannel] connect proxy server success. channelId:{}", future.channel().id().asLongText());
// reconnectServiceEnable = true;
reconnectCount = 0;
} else {
log.info("[客户端指令隧道] 连接代理服务失败!");
log.info("[CmdChannel] connect proxy server failed!");
}
}
}).sync();
}
private ChannelHandler createSslHandler() {
try {
InputStream jksInputStream = FileUtil.getInputStream(proxyConfig.getClient().getJksPath());
SSLContext clientContext = SSLContext.getInstance("TLS");
final KeyStore ks = KeyStore.getInstance("JKS");
ks.load(jksInputStream, proxyConfig.getClient().getKeyStorePassword().toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
TrustManager[] trustManagers = tmf.getTrustManagers();
clientContext.init(null, trustManagers, null);
SSLEngine sslEngine = clientContext.createSSLEngine();
sslEngine.setUseClientMode(true);
return new SslHandler(sslEngine);
} catch (Exception e) {
log.error("创建SSL处理器失败", e);
e.printStackTrace();
}
return null;
}
protected synchronized void reconnect() {
// if (!reconnectServiceEnable) {
// return;
// }
if (null != channel) {
if (channel.isActive()) {
return;
@@ -237,11 +120,11 @@ public class ProxyClientService {
channel.close();
}
log.info("[客户端指令隧道] 客户端重连 seq:{}", ++reconnectCount);
log.info("[CmdChannel] client reconnect seq:{}", ++reconnectCount);
try {
connectProxyServer();
} catch (Exception e) {
log.error("[客户端指令隧道] 重连异常", e);
log.error("[CmdChannel] reconnect error", e);
}
}
}
@@ -49,6 +49,17 @@ public class RealServerChannelHandler extends SimpleChannelInboundHandler<ByteBu
// 代理客户端连接断开
ctx.channel().close();
} else {
if (proxyChannel.isWritable()) {
if (!realServerChannel.config().isAutoRead()) {
realServerChannel.config().setAutoRead(true);
}
} else {
if (realServerChannel.config().isAutoRead()) {
realServerChannel.config().setAutoRead(false);
}
}
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
String visitorId = ProxyUtil.getVisitorIdByRealServerChannel(realServerChannel);
@@ -0,0 +1,82 @@
package org.dromara.neutrinoproxy.client.core;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;
import org.dromara.neutrinoproxy.client.util.ProxyUtil;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.dromara.neutrinoproxy.core.dispatcher.Dispatcher;
import org.noear.solon.Solon;
/**
* 处理与服务端之间的数据传输
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Slf4j
public class TcpProxyChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception {
if (ProxyMessage.TYPE_HEARTBEAT != proxyMessage.getType()) {
log.debug("[TCP Proxy Channel]Client ProxyChannel recieved proxy message, type is {}", proxyMessage.getType());
}
Solon.context().getBean(Dispatcher.class).dispatch(ctx, proxyMessage);
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (realServerChannel != null) {
realServerChannel.config().setOption(ChannelOption.AUTO_READ, ctx.channel().isWritable());
}
super.channelWritabilityChanged(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// 数据传输连接
Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (realServerChannel != null && realServerChannel.isActive()) {
realServerChannel.close();
}
ProxyUtil.removeTcpProxyChanel(ctx.channel());
super.channelInactive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.error("[TCP Proxy Channel]Client ProxyChannel Error channelId:{}", ctx.channel().id().asLongText(), cause);
ctx.close();
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if(evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent)evt;
switch (event.state()) {
case READER_IDLE:
if (ctx.channel().isWritable()) {
// 读超时,断开连接
log.info("[TCP Proxy Channel]Read timeout");
ctx.channel().close();
}
break;
case WRITER_IDLE:
ctx.channel().writeAndFlush(ProxyMessage.buildHeartbeatMessage());
break;
case ALL_IDLE:
// log.debug("[TCP Proxy Channel]ReadWrite timeout");
// ctx.close();
break;
}
}
}
}
@@ -18,13 +18,13 @@ import org.noear.solon.Solon;
* @date: 2022/6/16
*/
@Slf4j
public class ProxyChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
public class UdpProxyChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception {
if (ProxyMessage.TYPE_HEARTBEAT != proxyMessage.getType()) {
log.debug("Client ProxyChannel recieved proxy message, type is {}", proxyMessage.getType());
log.debug("[UDP Proxy Channel]Client ProxyChannel recieved proxy message, type is {}", proxyMessage.getType());
}
Solon.context().getBean(Dispatcher.class).dispatch(ctx, proxyMessage);
}
@@ -47,13 +47,13 @@ public class ProxyChannelHandler extends SimpleChannelInboundHandler<ProxyMessag
realServerChannel.close();
}
ProxyUtil.removeProxyChanel(ctx.channel());
ProxyUtil.removeTcpProxyChanel(ctx.channel());
super.channelInactive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.error("Client ProxyChannel Error channelId:{}", ctx.channel().id().asLongText(), cause);
log.error("[UDP Proxy Channel]Client ProxyChannel Error channelId:{}", ctx.channel().id().asLongText(), cause);
ctx.close();
}
@@ -64,14 +64,14 @@ public class ProxyChannelHandler extends SimpleChannelInboundHandler<ProxyMessag
switch (event.state()) {
case READER_IDLE:
// 读超时断开连接
// log.info("读超时");
// ctx.channel().close();
log.info("[UDP Proxy Channel]Read timeout");
ctx.channel().close();
break;
case WRITER_IDLE:
ctx.channel().writeAndFlush(ProxyMessage.buildHeartbeatMessage());
break;
case ALL_IDLE:
log.debug("读写超时");
log.debug("[UDP Proxy Channel]ReadWrite timeout");
ctx.close();
break;
}
@@ -0,0 +1,41 @@
package org.dromara.neutrinoproxy.client.core;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
import lombok.extern.slf4j.Slf4j;
import org.dromara.neutrinoproxy.client.constant.Constants;
import org.dromara.neutrinoproxy.client.util.UdpChannelBindInfo;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import java.net.InetSocketAddress;
/**
* @author: aoshiguchen
* @date: 2023/9/21
*/
@Slf4j
public class UdpRealServerHandler extends SimpleChannelInboundHandler<DatagramPacket> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket datagramPacket) throws Exception {
log.debug("chid---<:{} port:{}", ctx.channel().id().asLongText(), ((InetSocketAddress)ctx.channel().localAddress()).getPort());
UdpChannelBindInfo udpChannelBindInfo = ctx.channel().attr(Constants.UDP_CHANNEL_BIND_KEY).get();
if (null != udpChannelBindInfo) {
byte[] bytes = new byte[datagramPacket.content().readableBytes()];
datagramPacket.content().readBytes(bytes);
udpChannelBindInfo.getTunnelChannel().writeAndFlush(ProxyMessage.buildUdpTransferMessage(new ProxyMessage.UdpBaseInfo()
.setVisitorId(udpChannelBindInfo.getVisitorId())
.setVisitorIp(udpChannelBindInfo.getVisitorIp())
.setVisitorPort(udpChannelBindInfo.getVisitorPort())
.setServerPort(udpChannelBindInfo.getServerPort())
.setTargetIp(udpChannelBindInfo.getTargetIp())
.setTargetPort(udpChannelBindInfo.getTargetPort()))
.setData(bytes)
);
udpChannelBindInfo.getLockChannel().setResponseCount(udpChannelBindInfo.getLockChannel().getResponseCount() + 1);
}
}
}
@@ -1,14 +1,16 @@
package org.dromara.neutrinoproxy.client.handler;
import com.alibaba.fastjson.JSONObject;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.Attribute;
import lombok.extern.slf4j.Slf4j;
import org.dromara.neutrinoproxy.client.config.ProxyConfig;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.core.ExceptionEnum;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.dromara.neutrinoproxy.core.ProxyMessageHandler;
import org.dromara.neutrinoproxy.core.dispatcher.Match;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.dromara.neutrinoproxy.core.util.EncryptUtil;
import org.noear.snack.ONode;
import org.noear.solon.Solon;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
@@ -27,14 +29,14 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
@Override
public void handle(ChannelHandlerContext context, ProxyMessage proxyMessage) {
String info = proxyMessage.getInfo();
JSONObject data = JSONObject.parseObject(info);
Integer code = data.getInteger("code");
log.info("认证结果:{}", info);
ONode load = ONode.load(info);
Integer code = load.get("code").getInt();
log.info("Auth result: {}", load.get("msg").getString());
if (ExceptionEnum.AUTH_FAILED.getCode().equals(code)) {
// 客户端认证失败,直接停止服务
log.info("client auth failed , client stop.");
context.channel().close();
if (!proxyConfig.getClient().getReconnection().getUnlimited()) {
if (!proxyConfig.getTunnel().getReconnection().getUnlimited()) {
Solon.stop();
}
} else if (ExceptionEnum.CONNECT_FAILED.getCode().equals(code) ||
@@ -42,5 +44,26 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
){
context.channel().close();
}
// 是否进行通道加密
if (!proxyConfig.getTunnel().getSm2EncryptEnable()) {
return;
}
// 默认设置为非安全链路,需要服务端确认后,再设置为安全链路
Attribute<Boolean> booleanAttribute = context.attr(Constants.IS_SECURITY);
booleanAttribute.set(false);
// 获取认证成功的后的公钥信息,并生成随机密码,加密发到服务端确认
String publicKey = load.get("publicKey").getString();
byte[] secureKey = EncryptUtil.generateAesKey();
// 存储密码
Attribute<byte[]> secureKeyAttr = context.attr(Constants.SECURE_KEY);
secureKeyAttr.set(secureKey);
// 使用SM2算法对密钥进行加密并发送到服务端
byte[] encryptSecureKey = EncryptUtil.encryptBySm2(publicKey, secureKey);
context.writeAndFlush(ProxyMessage.buildSecureKeyMessage(encryptSecureKey));
context.flush();
}
}
@@ -21,8 +21,8 @@ import org.noear.solon.annotation.Inject;
@Match(type = Constants.ProxyDataTypeName.CONNECT)
@Component
public class ProxyMessageConnectHandler implements ProxyMessageHandler {
@Inject("proxyTunnelBootstrap")
private Bootstrap proxyTunnelBootstrap;
@Inject("tcpProxyTunnelBootstrap")
private Bootstrap tcpProxyTunnelBootstrap;
@Inject("realServerBootstrap")
private Bootstrap realServerBootstrap;
@Inject
@@ -48,7 +48,7 @@ public class ProxyMessageConnectHandler implements ProxyMessageHandler {
realServerChannel.config().setOption(ChannelOption.AUTO_READ, false);
// 获取连接
ProxyUtil.borrowProxyChanel(proxyTunnelBootstrap, new ProxyChannelBorrowListener() {
ProxyUtil.borrowTcpProxyChanel(tcpProxyTunnelBootstrap, new ProxyChannelBorrowListener() {
@Override
public void success(Channel channel) {
@@ -56,12 +56,15 @@ public class ProxyMessageConnectHandler implements ProxyMessageHandler {
channel.attr(Constants.NEXT_CHANNEL).set(realServerChannel);
realServerChannel.attr(Constants.NEXT_CHANNEL).set(channel);
// 远程绑定
channel.writeAndFlush(ProxyMessage.buildConnectMessage(visitorId + "@" + proxyConfig.getClient().getLicenseKey()));
// 通知服务端进行远程绑定,此绑定信息不加密,该条消息为身份标识
channel.writeAndFlush(ProxyMessage.buildConnectMessage(visitorId + "@" + proxyConfig.getTunnel().getLicenseKey()));
realServerChannel.config().setOption(ChannelOption.AUTO_READ, true);
ProxyUtil.addRealServerChannel(visitorId, realServerChannel);
ProxyUtil.setRealServerChannelVisitorId(realServerChannel, visitorId);
// 连接信息发送后,将该通道设置为加密
ProxyUtil.setChannelSecurity(channel);
}
@Override
@@ -26,9 +26,10 @@ public class ProxyMessageDisconnectHandler implements ProxyMessageHandler {
Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (null != realServerChannel) {
ctx.channel().attr(Constants.NEXT_CHANNEL).remove();
ProxyUtil.returnProxyChanel(ctx.channel());
ProxyUtil.returnTcpProxyChanel(ctx.channel());
realServerChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
ctx.close();
}
@Override
@@ -1,11 +1,14 @@
package org.dromara.neutrinoproxy.client.handler;
import com.alibaba.fastjson.JSONObject;
import org.dromara.neutrinoproxy.core.*;
import org.dromara.neutrinoproxy.core.*;
import org.dromara.neutrinoproxy.core.dispatcher.Match;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.core.ExceptionEnum;
import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.dromara.neutrinoproxy.core.ProxyMessageHandler;
import org.dromara.neutrinoproxy.core.dispatcher.Match;
import org.noear.snack.ONode;
import org.noear.solon.annotation.Component;
/**
@@ -20,9 +23,9 @@ public class ProxyMessageErrorHandler implements ProxyMessageHandler {
@Override
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
log.info("异常信息: {}", proxyMessage.getInfo());
JSONObject data = JSONObject.parseObject(proxyMessage.getInfo());
Integer code = data.getInteger("code");
log.info("error: {}", proxyMessage.getInfo());
ONode load = ONode.load(proxyMessage.getInfo());
Integer code = load.get("code").getInt();
if (ExceptionEnum.AUTH_FAILED.getCode().equals(code)) {
System.exit(0);
}
@@ -0,0 +1,49 @@
package org.dromara.neutrinoproxy.client.handler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.Attribute;
import lombok.extern.slf4j.Slf4j;
import org.dromara.neutrinoproxy.client.config.ProxyConfig;
import org.dromara.neutrinoproxy.client.util.ProxyUtil;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.dromara.neutrinoproxy.core.ProxyMessageHandler;
import org.dromara.neutrinoproxy.core.dispatcher.Match;
import org.dromara.neutrinoproxy.core.util.EncryptUtil;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
@Slf4j
@Match(type = Constants.ProxyDataTypeName.SECURE_KEY)
@Component
public class ProxyMessageSecureKeyHandler implements ProxyMessageHandler {
@Inject
private ProxyConfig proxyConfig;
@Override
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
if (!proxyConfig.getTunnel().getSm2EncryptEnable()) {
return;
}
log.info("收到服务端的加密确认");
Attribute<byte[]> secureKeyAttr = ctx.attr(Constants.SECURE_KEY);
byte[] secureKey = secureKeyAttr.get();
byte[] data = proxyMessage.getData();
byte[] decryptedData = EncryptUtil.decryptByAes(secureKey, data);
String m = new String(decryptedData);
if ("ok".equals(m)) {
// 设置当前cmd通道为安全,之后使用该通道传输的消息均会加密
Attribute<Boolean> booleanAttribute = ctx.attr(Constants.IS_SECURITY);
booleanAttribute.set(true);
// 全局存储密钥
ProxyUtil.setSecureKey(secureKey);
log.info("Encrypted link established successfully");
} else {
ctx.channel().close();
}
}
}
@@ -23,6 +23,10 @@ public class ProxyMessageTransferHandler implements ProxyMessageHandler {
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (realServerChannel != null) {
// 自己可写,则设置来源可读。自己不可写,则设置来源不可读
ctx.channel().config().setAutoRead(realServerChannel.isWritable());
ByteBuf buf = ctx.alloc().buffer(proxyMessage.getData().length);
buf.writeBytes(proxyMessage.getData());
realServerChannel.writeAndFlush(buf);
@@ -0,0 +1,67 @@
package org.dromara.neutrinoproxy.client.handler;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.dromara.neutrinoproxy.client.config.ProxyConfig;
import org.dromara.neutrinoproxy.client.core.ProxyChannelBorrowListener;
import org.dromara.neutrinoproxy.client.util.ProxyUtil;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.dromara.neutrinoproxy.core.ProxyMessageHandler;
import org.dromara.neutrinoproxy.core.dispatcher.Match;
import org.noear.snack.ONode;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
/**
* @author: aoshiguchen
* @date: 2023/9/19
*/
@Slf4j
@Match(type = Constants.ProxyDataTypeName.UDP_CONNECT)
@Component
public class UdpProxyMessageConnectHandler implements ProxyMessageHandler {
@Inject
private ProxyConfig proxyConfig;
@Inject("udpProxyTunnelBootstrap")
private Bootstrap udpProxyTunnelBootstrap;
@Override
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
final Channel cmdChannel = ctx.channel();
final ProxyMessage.UdpBaseInfo udpBaseInfo = ONode.deserialize(proxyMessage.getInfo(), ProxyMessage.UdpBaseInfo.class);
log.info("[UDP connect]info:{}", proxyMessage.getInfo());
// 获取连接
ProxyUtil.borrowUdpProxyChanel(udpProxyTunnelBootstrap, new ProxyChannelBorrowListener() {
@Override
public void success(Channel channel) {
channel.writeAndFlush(ProxyMessage.buildUdpConnectMessage(new ProxyMessage.UdpBaseInfo()
.setVisitorId(udpBaseInfo.getVisitorId())
.setServerPort(udpBaseInfo.getServerPort())
.setTargetIp(udpBaseInfo.getTargetIp())
.setTargetPort(udpBaseInfo.getTargetPort())
).setData(proxyConfig.getTunnel().getLicenseKey().getBytes()));
// connect类型的消息不加密,用于标识身份,发送标识消息后,再将通道设置加密标识
ProxyUtil.setChannelSecurity(channel);
}
@Override
public void error(Throwable cause) {
cmdChannel.writeAndFlush(ProxyMessage.buildDisconnectMessage(udpBaseInfo.toJsonString()));
}
});
}
@Override
public String name() {
return ProxyDataTypeEnum.UDP_CONNECT.getDesc();
}
}
@@ -0,0 +1,48 @@
package org.dromara.neutrinoproxy.client.handler;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.socket.DatagramPacket;
import lombok.extern.slf4j.Slf4j;
import org.dromara.neutrinoproxy.client.util.UdpServerUtil;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.dromara.neutrinoproxy.core.ProxyMessageHandler;
import org.dromara.neutrinoproxy.core.dispatcher.Match;
import org.noear.snack.ONode;
import org.noear.solon.annotation.Component;
import java.net.InetSocketAddress;
/**
* @author: aoshiguchen
* @date: 2023/9/20
*/
@Slf4j
@Match(type = Constants.ProxyDataTypeName.UDP_TRANSFER)
@Component
public class UdpProxyMessageTransferHandler implements ProxyMessageHandler {
@Override
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
final ProxyMessage.UdpBaseInfo udpBaseInfo = ONode.deserialize(proxyMessage.getInfo(), ProxyMessage.UdpBaseInfo.class);
log.debug("[UDP transfer]info:{} data:{}", proxyMessage.getInfo(), new String(proxyMessage.getData()));
Channel channel = UdpServerUtil.takeChannel(udpBaseInfo, ctx.channel());
if (null == channel) {
log.error("[UDP transfer] take udp channel failed.");
return;
}
log.debug("chid--->:{} port:{}", ctx.channel().id().asLongText(), ((InetSocketAddress)channel.localAddress()).getPort());
InetSocketAddress address = new InetSocketAddress(udpBaseInfo.getTargetIp(), udpBaseInfo.getTargetPort());
ByteBuf byteBuf = Unpooled.copiedBuffer(proxyMessage.getData());
channel.writeAndFlush(new DatagramPacket(byteBuf, address));
}
@Override
public String name() {
return ProxyDataTypeEnum.UDP_TRANSFER.getDesc();
}
}
@@ -0,0 +1,28 @@
package org.dromara.neutrinoproxy.client.util;
import io.netty.channel.Channel;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* @author: aoshiguchen
* @date: 2023/9/21
*/
@Accessors(chain = true)
@Data
public class LockChannel {
// 端口号
private int port;
// 通道
private Channel channel;
// 期望的响应次数
private int proxyResponses;
// 超时时间(毫秒)
private long proxyTimeoutMs;
// 被获取的时间
private Date takeTime;
// 已经响应的次数
private int responseCount;
}
@@ -21,6 +21,9 @@
*/
package org.dromara.neutrinoproxy.client.util;
import io.netty.channel.ChannelHandler;
import io.netty.handler.ssl.SslHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.neutrinoproxy.client.config.ProxyConfig;
import org.dromara.neutrinoproxy.client.core.ProxyChannelBorrowListener;
@@ -34,6 +37,12 @@ import io.netty.util.AttributeKey;
import org.dromara.neutrinoproxy.core.util.FileUtil;
import org.noear.solon.Solon;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import java.io.InputStream;
import java.security.KeyStore;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;
@@ -45,6 +54,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
* @author: aoshiguchen
* @date: 2022/8/31
*/
@Slf4j
public class ProxyUtil {
private static final AttributeKey<Boolean> USER_CHANNEL_WRITEABLE = AttributeKey.newInstance("user_channel_writeable");
@@ -54,21 +64,24 @@ public class ProxyUtil {
private static Map<String, Channel> realServerChannels = new ConcurrentHashMap<String, Channel>();
private static ConcurrentLinkedQueue<Channel> proxyChannelPool = new ConcurrentLinkedQueue<Channel>();
private static ConcurrentLinkedQueue<Channel> tcpProxyChannelPool = new ConcurrentLinkedQueue<Channel>();
private static ConcurrentLinkedQueue<Channel> udpProxyChannelPool = new ConcurrentLinkedQueue<>();
private static volatile Channel cmdChannel;
private static String clientId;
private static final String CLIENT_ID_FILE = ".NEUTRINO_PROXY_CLIENT_ID";
public static void borrowProxyChanel(Bootstrap proxyTunnelBootstrap, final ProxyChannelBorrowListener borrowListener) {
Channel channel = proxyChannelPool.poll();
private static byte[] secureKey = null;
public static void borrowTcpProxyChanel(Bootstrap tcpProxyTunnelBootstrap, final ProxyChannelBorrowListener borrowListener) {
Channel channel = tcpProxyChannelPool.poll();
if (null != channel) {
borrowListener.success(channel);
return;
}
proxyTunnelBootstrap.connect().addListener((ChannelFutureListener) future -> {
tcpProxyTunnelBootstrap.connect().addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
borrowListener.success(future.channel());
} else {
@@ -77,18 +90,60 @@ public class ProxyUtil {
});
}
public static void returnProxyChanel(Channel proxyChanel) {
if (proxyChannelPool.size() > MAX_POOL_SIZE) {
public static void returnTcpProxyChanel(Channel proxyChanel) {
if (proxyChanel != null) {
proxyChanel.attr(Constants.IS_SECURITY).set(null);
proxyChanel.attr(Constants.SECURE_KEY).set(null);
}
if (tcpProxyChannelPool.size() > MAX_POOL_SIZE) {
proxyChanel.close();
} else {
proxyChanel.config().setOption(ChannelOption.AUTO_READ, true);
proxyChanel.attr(Constants.NEXT_CHANNEL).remove();
proxyChannelPool.offer(proxyChanel);
tcpProxyChannelPool.offer(proxyChanel);
}
}
public static void removeProxyChanel(Channel proxyChanel) {
proxyChannelPool.remove(proxyChanel);
public static void removeTcpProxyChanel(Channel proxyChanel) {
tcpProxyChannelPool.remove(proxyChanel);
}
public static void borrowUdpProxyChanel(Bootstrap tcpProxyTunnelBootstrap, final ProxyChannelBorrowListener borrowListener) {
Channel channel = udpProxyChannelPool.poll();
if (null != channel) {
borrowListener.success(channel);
return;
}
tcpProxyTunnelBootstrap.connect().addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
borrowListener.success(future.channel());
} else {
borrowListener.error(future.cause());
}
});
}
public static void returnUdpProxyChanel(Channel proxyChanel) {
if (proxyChanel != null) {
proxyChanel.attr(Constants.IS_SECURITY).set(null);
proxyChanel.attr(Constants.SECURE_KEY).set(null);
}
if (udpProxyChannelPool.size() > MAX_POOL_SIZE) {
proxyChanel.close();
} else {
proxyChanel.config().setOption(ChannelOption.AUTO_READ, true);
proxyChanel.attr(Constants.NEXT_CHANNEL).remove();
udpProxyChannelPool.offer(proxyChanel);
}
}
public static void removeUdpProxyChanel(Channel proxyChanel) {
udpProxyChannelPool.remove(proxyChanel);
}
public static void setCmdChannel(Channel cmdChannel) {
@@ -140,8 +195,8 @@ public class ProxyUtil {
return clientId;
}
ProxyConfig proxyConfig = Solon.context().getBean(ProxyConfig.class);
if (StringUtils.isNotBlank(proxyConfig.getClient().getClientId())) {
clientId = proxyConfig.getClient().getClientId();
if (StringUtils.isNotBlank(proxyConfig.getTunnel().getClientId())) {
clientId = proxyConfig.getTunnel().getClientId();
return clientId;
}
String id = FileUtil.readContentAsString(CLIENT_ID_FILE);
@@ -154,4 +209,40 @@ public class ProxyUtil {
clientId = id;
return id;
}
public static ChannelHandler createSslHandler(ProxyConfig proxyConfig) {
try {
InputStream jksInputStream = FileUtil.getInputStream(proxyConfig.getTunnel().getJksPath());
SSLContext clientContext = SSLContext.getInstance("TLS");
final KeyStore ks = KeyStore.getInstance("JKS");
ks.load(jksInputStream, proxyConfig.getTunnel().getKeyStorePassword().toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
TrustManager[] trustManagers = tmf.getTrustManagers();
clientContext.init(null, trustManagers, null);
SSLEngine sslEngine = clientContext.createSSLEngine();
sslEngine.setUseClientMode(true);
return new SslHandler(sslEngine);
} catch (Exception e) {
log.error("create SSL handler failed", e);
e.printStackTrace();
}
return null;
}
public static void setSecureKey(byte[] key) {
secureKey = key;
}
public static void setChannelSecurity(Channel channel) {
if (null == secureKey) {
return;
}
channel.attr(Constants.IS_SECURITY).set(true);
channel.attr(Constants.SECURE_KEY).set(secureKey);
}
}
@@ -0,0 +1,22 @@
package org.dromara.neutrinoproxy.client.util;
import io.netty.channel.Channel;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* @author: aoshiguchen
* @date: 2023/9/21
*/
@Accessors(chain = true)
@Data
public class UdpChannelBindInfo {
private Channel tunnelChannel;
private LockChannel lockChannel;
private String visitorId;
private String visitorIp;
private int visitorPort;
private int serverPort;
private String targetIp;
private int targetPort;
}
@@ -0,0 +1,201 @@
package org.dromara.neutrinoproxy.client.util;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.neutrinoproxy.client.config.ProxyConfig;
import org.dromara.neutrinoproxy.client.constant.Constants;
import org.dromara.neutrinoproxy.client.core.CustomThreadFactory;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.noear.solon.core.runtime.NativeDetector;
import java.util.*;
import java.util.concurrent.*;
/**
* @author: aoshiguchen
* @date: 2023/9/21
*/
@Slf4j
public class UdpServerUtil {
private static final Boolean isSupportUdp = Boolean.FALSE;
private static int udpServerPortMin = 0;
private static int udpServerPortMax = 0;
private static int nextUdpServerPort = 0;
private static Bootstrap udpServerBootstrap;
private static int defaultUdpServerPort;
private static Channel defaultUdpServerChannel;
private static Map<Integer, Channel> portToChannelMap = new ConcurrentHashMap<>();
/**
* udp服务空闲端口池
*/
private static ConcurrentLinkedQueue<Integer> udpServerFreePortPool = new ConcurrentLinkedQueue<>();
private static List<LockChannel> lockChannelList = new ArrayList<>();
/**
* lockChannel扫描器
*/
private static final ScheduledExecutorService lockChannelScanner = Executors.newSingleThreadScheduledExecutor(new CustomThreadFactory("lockChannelScanner"));
/**
* 初始化UDP缓存
* 1、初始化一个基础UDP服务,用于不需要响应的UDP转发
* 2、维护一个UDP服务池,用于需要响应的UDP转发
* @param proxyConfig
*/
public static void initCache(ProxyConfig proxyConfig, Bootstrap udpServerBootstrap) {
// aot 阶段,不初始化UDP服务
if (NativeDetector.isAotRuntime()) {
return;
}
if (null == proxyConfig.getClient().getUdp() || StringUtils.isEmpty(proxyConfig.getClient().getUdp().getPuppetPortRange())) {
return;
}
ProxyConfig.Udp udpConfig = proxyConfig.getClient().getUdp();
if (StringUtils.isEmpty(udpConfig.getPuppetPortRange())) {
return;
}
String[] tmp = udpConfig.getPuppetPortRange().split("-");
if (null == tmp || tmp.length != 2) {
log.error("client udp config error!");
return;
}
try {
udpServerPortMin = Integer.parseInt(tmp[0]);
udpServerPortMax = Integer.parseInt(tmp[1]);
if (udpServerPortMax <= udpServerPortMin) {
// 至少得给1个udp端口,一个用于基础无响应UDP转发
throw new RuntimeException("client udp config error!");
}
nextUdpServerPort = udpServerPortMin;
UdpServerUtil.udpServerBootstrap = udpServerBootstrap;
log.info("udp proxy server port: {} ~ {}", udpServerPortMin, udpServerPortMax);
// 初始化udp服务
initUdpServer();
// 初始化lockChannel扫描器
lockChannelScanner.scheduleWithFixedDelay(UdpServerUtil::lockChannelScan, 5, 3, TimeUnit.SECONDS);
} catch (Exception e) {
log.error("client udp config error!", e);
return;
}
}
/**
* 初始化udp服务
*/
private static void initUdpServer() {
defaultUdpServerPort = nextUdpServerPort();
defaultUdpServerChannel = bindPort(defaultUdpServerPort);
// 初始化默认最多额外开启5个udp服务,其他的需要时再启动
for (int i = 0; i < 5; i++) {
if (!hasNextUdpServerPort()) {
return;
}
int port = nextUdpServerPort();
Channel ch = bindPort(port);
portToChannelMap.put(port, ch);
udpServerFreePortPool.offer(port);
}
while (hasNextUdpServerPort()) {
udpServerFreePortPool.offer(nextUdpServerPort());
}
}
private static Channel bindPort(int port) {
try {
ChannelFuture channelFuture = udpServerBootstrap.bind(port).sync();
log.info("[udp server] bind port:{} success!", port);
return channelFuture.channel();
} catch (InterruptedException e) {
log.error("[udp server] bind port:{} error!", port);
throw new RuntimeException(e);
}
}
public static Boolean hasNextUdpServerPort() {
return nextUdpServerPort <= udpServerPortMax;
}
public static synchronized int nextUdpServerPort() {
return nextUdpServerPort++;
}
/**
* 获取一个可用的udp通道
* 1、如果期待的响应为0,或者超时时间<=0,则认为不需要响应,直接返回默认的udp服务,否则继续下一步
* 2、从可用端口队列中找到一个可用端口,若不存在可用端口,则降级为不需要响应,返回默认的udp服务。否则继续下一步
* 3、根据该端口找到udp服务通道,找不到则绑定端口开启一个通道并返回。将该端口添加到锁定列表
* 4、维护一个定时器的,定时扫描锁定列表,及时释放锁定的端口
* @param info
* @return
*/
public static synchronized Channel takeChannel(ProxyMessage.UdpBaseInfo info, Channel tunnelChannel) {
if (info.getProxyResponses() <= 0 || info.getProxyTimeoutMs() <= 0) {
return defaultUdpServerChannel;
}
Integer port = udpServerFreePortPool.poll();
if (null == port) {
return defaultUdpServerChannel;
}
Channel channel = portToChannelMap.get(port);
if (null == channel) {
channel = bindPort(port);
portToChannelMap.put(port, channel);
}
// 添加到锁定队列
LockChannel lockChannel = new LockChannel()
.setPort(port)
.setChannel(channel)
.setProxyResponses(info.getProxyResponses())
.setProxyTimeoutMs(info.getProxyTimeoutMs())
.setTakeTime(new Date())
.setResponseCount(0);
lockChannelList.add(lockChannel);
channel.attr(Constants.UDP_CHANNEL_BIND_KEY).set(new UdpChannelBindInfo()
.setTunnelChannel(tunnelChannel)
.setVisitorId(info.getVisitorId())
.setVisitorIp(info.getVisitorIp())
.setVisitorPort(info.getVisitorPort())
.setServerPort(info.getServerPort())
.setTargetIp(info.getTargetIp())
.setTargetPort(info.getTargetPort())
.setLockChannel(lockChannel)
);
return channel;
}
/**
* lockChannel扫描
*/
public static synchronized void lockChannelScan() {
if (lockChannelList.isEmpty()) {
return;
}
Iterator<LockChannel> iter = lockChannelList.iterator();
if (iter.hasNext()) {
LockChannel lockChannel = iter.next();
if (lockChannel.getResponseCount() >= lockChannel.getProxyResponses() ||
System.currentTimeMillis() - lockChannel.getTakeTime().getTime() >= lockChannel.getProxyTimeoutMs()
) {
iter.remove();
UdpChannelBindInfo udpChannelBindInfo = lockChannel.getChannel().attr(Constants.UDP_CHANNEL_BIND_KEY).get();
// 此处必须释放代理隧道
closeChannel(udpChannelBindInfo.getTunnelChannel());
lockChannel.getChannel().attr(Constants.UDP_CHANNEL_BIND_KEY).set(null);
udpServerFreePortPool.offer(lockChannel.getPort());
log.debug("[udp channel]release udp channel port:{}", lockChannel.getPort());
}
}
}
private static void closeChannel(Channel channel) {
try {
channel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
} catch (Exception e) {
// ignore
}
}
}
@@ -1,17 +1,10 @@
---
title: 客户端配置
date: 2023-06-09 21:15:35
permalink: /pages/50ce10/
---
solon.logging.logger:
"root":
level: info
# 以下是最新的客户端配置格式(app.yml)
```yml
neutrino:
proxy:
logger:
# 日志级别
level: ${LOG_LEVEL:info}
client:
tunnel:
# 线程池相关配置,用于技术调优,可忽略
thread-count: 50
# 隧道SSL证书配置
@@ -29,10 +22,20 @@ neutrino:
client-id: ${CLIENT_ID:}
# 是否开启隧道传输报文日志(日志级别为debug时开启才有效)
transfer-log-enable: ${CLIENT_LOG:false}
# 是否开启心跳日志
heartbeat-log-enable: ${HEARTBEAT_LOG:false}
# 重连设置
reconnection:
# 重连间隔(秒)
interval-seconds: 10
# 是否开启无限重连(未开启时,客户端license不合法会自动停止应用,开启了则不会,请谨慎开启)
unlimited: false
```
client:
udp:
# 线程池相关配置,用于技术调优,可忽略
boss-thread-count: 5
work-thread-count: 20
# udp傀儡端口范围
puppet-port-range: 10000-10500
# 是否开启隧道传输报文日志(日志级别为debug时开启才有效)
transfer-log-enable: ${CLIENT_LOG:false}
@@ -1,34 +1,51 @@
solon:
config:
add: ./app.yml
app:
name: neutrino-proxy-client
version: @revision@
# 日志级别
solon.logging.appender:
console:
pattern: "%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %magenta(${PID:-}) --- %-15([%15.15thread]) %-56(%cyan(%-40.40logger{39}%L)) : %msg%n"
file:
enable: true
pattern: "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level ${PID:-} --- %-15([%15.15thread]) %-56(%-40.40logger{39}%L) : %msg%n"
name: "logs/${neutrino.application.name}"
rolling: "logs/${neutrino.application.name}_%d{yyyy-MM-dd}_%i.log.gz"
solon.logging.logger:
"root":
level: info
neutrino:
application:
name: neutrino-proxy-client
proxy:
protocol:
max-frame-length: 2097152
max-frame-length: 1048576000
length-field-offset: 0
length-field-length: 4
initial-bytes-to-strip: 0
length-adjustment: 0
read-idle-time: 8
write-idle-time: 3
all-idle-time-seconds: 8
logger:
# 日志级别
level: ${LOG_LEVEL:info}
client:
read-idle-time: 120
write-idle-time: 20
all-idle-time-seconds: 0
tunnel:
# 线程池相关配置,用于技术调优,可忽略
thread-count: 50
sm2-encrypt-enable: ${SM2_ENCRYPT_ENABLE:true}
# 隧道SSL证书配置
key-store-password: ${STORE_PASS:123456}
jks-path: ${JKS_PATH:classpath:/test.jks}
# 服务端IP
server-ip: ${SERVER_IP:localhost}
# 服务端端口(对应服务端app.yml中的tunnel.port、tunnel.ssl-port)
server-port: ${SERVER_PORT:9002}
server-port: ${SERVER_PORT:9000}
# 是否启用SSL(注意:该配置必须和server-port对应上)
ssl-enable: ${SSL_ENABLE:true}
ssl-enable: ${SSL_ENABLE:false}
# 客户端连接唯一凭证
license-key: ${LICENSE_KEY:}
license-key: ${LICENSE_KEY:b0a907332b474b25897c4dcb31fc7eb6}
# 客户端唯一身份标识(可忽略,若不设置首次启动会自动生成)
client-id: ${CLIENT_ID:}
# 是否开启隧道传输报文日志(日志级别为debug时开启才有效)
@@ -41,3 +58,12 @@ neutrino:
interval-seconds: 10
# 是否开启无限重连(未开启时,客户端license不合法会自动停止应用,开启了则不会,请谨慎开启)
unlimited: false
client:
udp:
# 线程池相关配置,用于技术调优,可忽略
boss-thread-count: 5
work-thread-count: 20
# udp傀儡端口范围
puppet-port-range: 10000-10500
# 是否开启隧道传输报文日志(日志级别为debug时开启才有效)
transfer-log-enable: ${CLIENT_LOG:false}
@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="LOG_FILE" value="./logs/neutrino-proxy-client.log"/>
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{50} - %msg%n"/>
<!-- <property name="ENCODE" value="utf8" />-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<!-- <charset>${ENCODE}</charset>-->
</encoder>
</appender>
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.zip</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<!-- <charset>${ENCODE}</charset>-->
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="console"/>
<appender-ref ref="file"/>
</root>
<logger name="com.example.logback" level="warn"/>
<logger name="io.netty" level="warn"/>
</configuration>
+29 -3
View File
@@ -13,21 +13,47 @@
<artifactId>neutrino-proxy-core</artifactId>
<dependencies>
<!--solon aot start(用于 aot 时注册 native 元信息)-->
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon.aot</artifactId>
</dependency>
<!--solon aot end-->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
</dependency>
<!--hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>${hutool.version}</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
<version>${hutool.version}</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>META-INF/native-image/neutrino-proxy-core/*.json</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
</project>
@@ -25,6 +25,8 @@ package org.dromara.neutrinoproxy.core;
import io.netty.channel.Channel;
import io.netty.util.AttributeKey;
import java.net.InetSocketAddress;
/**
*
* @author: aoshiguchen
@@ -36,8 +38,24 @@ public interface Constants {
AttributeKey<String> VISITOR_ID = AttributeKey.newInstance("visitor_id");
AttributeKey<String> SECURE_PRIVATE_KEY = AttributeKey.newInstance("secure_private_key");
AttributeKey<byte[]> SECURE_KEY = AttributeKey.newInstance("secure_key");
AttributeKey<Boolean> IS_SECURITY = AttributeKey.newInstance("is_security");
AttributeKey<Integer> LICENSE_ID = AttributeKey.newInstance("license_id");
AttributeKey<String> TARGET_IP = AttributeKey.newInstance("targetIp");
AttributeKey<Integer> TARGET_PORT = AttributeKey.newInstance("targetPort");
AttributeKey<Integer> PROXY_RESPONSES = AttributeKey.newInstance("proxyResponses");
AttributeKey<Long> PROXY_TIMEOUT_MS = AttributeKey.newInstance("proxyTimeoutMs");
// 临时解决 udp channel被close的问题
AttributeKey<Boolean> IS_UDP_KEY = AttributeKey.newInstance("isUdp");
AttributeKey<InetSocketAddress> SENDER = AttributeKey.newInstance("sender");
int HEADER_SIZE = 4;
int TYPE_SIZE = 1;
int SERIAL_NUMBER_SIZE = 8;
@@ -45,10 +63,14 @@ public interface Constants {
interface ProxyDataTypeName {
String HEARTBEAT = "HEARTBEAT";
String SECURE_KEY = "SECURE_KEY";
String AUTH = "AUTH";
String CONNECT = "CONNECT";
String DISCONNECT = "DISCONNECT";
String TRANSFER = "TRANSFER";
String UDP_CONNECT = "UDP_CONNECT";
String UDP_DISCONNECT = "UDP_DISCONNECT";
String UDP_TRANSFER = "UDP_TRANSFER";
String ERROR = "ERROR";
String PORT_MAPPING_SYNC = "PORT_MAPPING_SYNC";
}
@@ -33,11 +33,11 @@ import lombok.Getter;
@Getter
@AllArgsConstructor
public enum ExceptionEnum {
SUCCESS(0, "成功"),
AUTH_FAILED(1, "认证失败"),
CONNECT_FAILED(2, "连接失败"),
SUCCESS(0, "success"),
AUTH_FAILED(1, "auth failed"),
CONNECT_FAILED(2, "connect failed"),
@Deprecated
LICENSE_CANNOT_REPEAT_CONNECT(3, "license不能多个客户端同时使用"),
LICENSE_CANNOT_REPEAT_CONNECT(3, "license cannot multiple client simultaneous use"),
;
private Integer code;
@@ -0,0 +1,31 @@
/**
* 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 org.dromara.neutrinoproxy.core;
/**
* 存储公钥和私钥
* @param privateKey
* @param publicKey
*/
public record KeyPairRecord(String privateKey, String publicKey) {
}
@@ -38,13 +38,18 @@ import java.util.stream.Stream;
@Getter
@AllArgsConstructor
public enum ProxyDataTypeEnum {
HEARTBEAT(0x01, Constants.ProxyDataTypeName.HEARTBEAT, "心跳"),
AUTH(0x02, Constants.ProxyDataTypeName.AUTH,"认证"),
CONNECT(0x03, Constants.ProxyDataTypeName.CONNECT,"连接"),
DISCONNECT(0x04, Constants.ProxyDataTypeName.DISCONNECT,"断开连接"),
TRANSFER(0x05, Constants.ProxyDataTypeName.TRANSFER,"数据传输"),
ERROR(0x06, Constants.ProxyDataTypeName.ERROR,"异常"),
PORT_MAPPING_SYNC(0x07, Constants.ProxyDataTypeName.PORT_MAPPING_SYNC, "端口映射同步");
HEARTBEAT(0x01, Constants.ProxyDataTypeName.HEARTBEAT, "HEARTBEAT"),
AUTH(0x02, Constants.ProxyDataTypeName.AUTH,"AUTH"),
CONNECT(0x03, Constants.ProxyDataTypeName.CONNECT,"CONNECT"),
DISCONNECT(0x04, Constants.ProxyDataTypeName.DISCONNECT,"DISCONNECT"),
TRANSFER(0x05, Constants.ProxyDataTypeName.TRANSFER,"TRANSFER"),
ERROR(0x06, Constants.ProxyDataTypeName.ERROR,"ERROR"),
PORT_MAPPING_SYNC(0x07, Constants.ProxyDataTypeName.PORT_MAPPING_SYNC, "PORT_MAPPING_SYNC"),
UDP_CONNECT(0x08, Constants.ProxyDataTypeName.UDP_CONNECT,"UDP_CONNECT"),
UDP_DISCONNECT(0x09, Constants.ProxyDataTypeName.UDP_DISCONNECT,"UDP_DISCONNECT"),
UDP_TRANSFER(0x10, Constants.ProxyDataTypeName.UDP_TRANSFER,"UDP_TRANSFER"),
SECURE_KEY(0x11, Constants.ProxyDataTypeName.SECURE_KEY, "SECURE_KEY"),
;
private static Map<Integer,ProxyDataTypeEnum> cache = Stream.of(values()).collect(Collectors.toMap(ProxyDataTypeEnum::getType, Function.identity()));
private int type;
@@ -22,9 +22,10 @@
package org.dromara.neutrinoproxy.core;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import lombok.experimental.Accessors;
import org.dromara.neutrinoproxy.core.util.EncryptUtil;
import org.noear.snack.ONode;
import java.util.Arrays;
@@ -66,6 +67,23 @@ public class ProxyMessage {
* 通用异常信息
*/
public static final byte TYPE_ERROR = 0x06;
/**
* UDP代理隧道连接
*/
public static final byte TYPE_UDP_CONNECT = 0x08;
/**
* UDP代理隧道断开连接
*/
public static final byte TYPE_UDP_DISCONNECT = 0x09;
/**
* UDP数据传输
*/
public static final byte TYPE_UDP_TRANSFER = 0x10;
/**
* 安全密钥协商
*/
public static final byte TYPE_SECURE_KEY = 0x11;
/**
* 消息类型
@@ -105,13 +123,14 @@ public class ProxyMessage {
.setInfo(info + "," + clientId);
}
public static ProxyMessage buildAuthResultMessage(Integer code, String msg, String licenseKey) {
JSONObject data = new JSONObject();
data.put("code", code);
data.put("msg", msg);
data.put("licenseKey", licenseKey);
public static ProxyMessage buildAuthResultMessage(Integer code, String msg, String licenseKey, String publicKey) {
ONode data = ONode.newObject();
data.set("code", code);
data.set("msg", msg);
data.set("licenseKey", licenseKey);
data.set("publicKey", publicKey);
return create().setType(TYPE_AUTH)
.setInfo(data.toJSONString());
.setInfo(data.toJson());
}
public static ProxyMessage buildConnectMessage(String visitorId) {
@@ -124,22 +143,70 @@ public class ProxyMessage {
.setInfo(info);
}
public static ProxyMessage buildSecureKeyMessage(byte[] secureKey) {
return create().setType(TYPE_SECURE_KEY)
.setInfo(EncryptUtil.digestBySm3(secureKey))
.setData(secureKey);
}
public static ProxyMessage buildSecureKeyReturnMessage(byte[] content) {
return create().setType(TYPE_SECURE_KEY)
.setData(content);
}
public static ProxyMessage buildTransferMessage(String visitorId, byte[] data) {
return create().setType(TYPE_TRANSFER)
.setInfo(visitorId)
.setData(data);
}
public static ProxyMessage buildUdpConnectMessage(UdpBaseInfo info) {
return create().setType(TYPE_UDP_CONNECT)
.setInfo(info.toJsonString());
}
public static ProxyMessage buildUdpDisconnectMessage() {
return create().setType(TYPE_UDP_DISCONNECT);
}
public static ProxyMessage buildUdpTransferMessage(UdpBaseInfo info) {
return create().setType(TYPE_UDP_TRANSFER)
.setInfo(info.toJsonString());
}
public static ProxyMessage buildErrMessage(ExceptionEnum exceptionEnum, String info) {
JSONObject data = new JSONObject();
data.put("code", exceptionEnum.getCode());
data.put("msg", exceptionEnum.getMsg());
data.put("info", info);
ONode data = ONode.newObject();
data.set("code", exceptionEnum.getCode());
data.set("msg", exceptionEnum.getMsg());
data.set("info", info);
return create().setType(TYPE_ERROR)
.setInfo(data.toJSONString());
.setInfo(data.toJson());
}
public static ProxyMessage buildErrMessage(ExceptionEnum exceptionEnum) {
return buildErrMessage(exceptionEnum, null);
}
@Accessors(chain = true)
@Data
public static class UdpBaseInfo {
private String visitorId;
private String visitorIp;
private int visitorPort;
private int serverPort;
private String targetIp;
private int targetPort;
/**
* 期待的响应数
*/
private int proxyResponses;
/**
* 超时时间(<=0时,相当于不需要响应)
*/
private long proxyTimeoutMs;
public String toJsonString() {
return ONode.serialize(this);
}
}
}
@@ -22,11 +22,18 @@
package org.dromara.neutrinoproxy.core;
import cn.hutool.core.util.HexUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.util.Attribute;
import lombok.extern.slf4j.Slf4j;
import org.dromara.neutrinoproxy.core.util.EncryptUtil;
import static org.dromara.neutrinoproxy.core.Constants.*;
@Slf4j
/**
*
* @author: aoshiguchen
@@ -70,28 +77,52 @@ public class ProxyMessageDecoder extends LengthFieldBasedFrameDecoder {
return null;
}
int frameLength = in.readInt();
if (in.readableBytes() < frameLength) {
return null;
Attribute<Boolean> booleanAttribute = ctx.attr(Constants.IS_SECURITY);
Boolean isSecurity = booleanAttribute.get();
ByteBuf buf;
// 考虑isSecurity为null的情况,null的情况也为false
if (isSecurity != null && isSecurity) {
int packageLength = in.readInt();
if (in.readableBytes() < packageLength) {
return null;
}
// 获取加密数据
byte[] encryptedBytes = new byte[packageLength];
in.readBytes(encryptedBytes);
in.release();
// 获取解密密钥
Attribute<byte[]> secureKeyAttr = ctx.attr(SECURE_KEY);
byte[] secureKey = secureKeyAttr.get();
// 解密
byte[] decryptedData = EncryptUtil.decryptByAes(secureKey, encryptedBytes);
buf = Unpooled.wrappedBuffer(decryptedData);
} else {
buf = in;
}
ProxyMessage proxyMessage = new ProxyMessage();
byte type = in.readByte();
long sn = in.readLong();
int frameLength = buf.readInt();
byte type = buf.readByte();
long sn = buf.readLong();
proxyMessage.setSerialNumber(sn);
proxyMessage.setType(type);
int infoLength = in.readInt();
int infoLength = buf.readInt();
byte[] infoBytes = new byte[infoLength];
in.readBytes(infoBytes);
buf.readBytes(infoBytes);
proxyMessage.setInfo(new String(infoBytes));
byte[] data = new byte[frameLength - TYPE_SIZE - SERIAL_NUMBER_SIZE - INFO_LENGTH_SIZE - infoLength];
in.readBytes(data);
buf.readBytes(data);
proxyMessage.setData(data);
in.release();
buf.release();
return proxyMessage;
}
@@ -22,9 +22,15 @@
package org.dromara.neutrinoproxy.core;
import cn.hutool.core.util.HexUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.util.Attribute;
import lombok.extern.slf4j.Slf4j;
import org.dromara.neutrinoproxy.core.util.EncryptUtil;
import static org.dromara.neutrinoproxy.core.Constants.*;
/**
@@ -32,6 +38,7 @@ import static org.dromara.neutrinoproxy.core.Constants.*;
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Slf4j
public class ProxyMessageEncoder extends MessageToByteEncoder<ProxyMessage> {
public ProxyMessageEncoder() {
@@ -40,6 +47,7 @@ public class ProxyMessageEncoder extends MessageToByteEncoder<ProxyMessage> {
@Override
protected void encode(ChannelHandlerContext ctx, ProxyMessage msg, ByteBuf out) throws Exception {
int bodyLength = TYPE_SIZE + SERIAL_NUMBER_SIZE + INFO_LENGTH_SIZE;
byte[] infoBytes = null;
if (msg.getInfo() != null) {
@@ -51,21 +59,50 @@ public class ProxyMessageEncoder extends MessageToByteEncoder<ProxyMessage> {
bodyLength += msg.getData().length;
}
// write the total packet length but without length field's length.
out.writeInt(bodyLength);
Attribute<Boolean> booleanAttribute = ctx.attr(Constants.IS_SECURITY);
Boolean isSecurity = booleanAttribute.get();
out.writeByte(msg.getType());
out.writeLong(msg.getSerialNumber());
ByteBuf buf;
// 考虑isSecurity为null的情况,null的情况也为false
if (isSecurity != null && isSecurity) {
buf = Unpooled.directBuffer(bodyLength);
} else {
buf = out;
}
// write the total packet length but without length field's length.
buf.writeInt(bodyLength);
buf.writeByte(msg.getType());
buf.writeLong(msg.getSerialNumber());
if (infoBytes != null) {
out.writeInt(infoBytes.length);
out.writeBytes(infoBytes);
buf.writeInt(infoBytes.length);
buf.writeBytes(infoBytes);
} else {
out.writeInt(0x00);
buf.writeInt(0x00);
}
if (msg.getData() != null) {
out.writeBytes(msg.getData());
buf.writeBytes(msg.getData());
}
// 考虑isSecurity为null的情况,null的情况也为false
if (isSecurity != null && isSecurity) {
// 执行加密
byte[] data = new byte[buf.writerIndex()];
buf.readBytes(data);
// 获取加密密钥
Attribute<byte[]> secureKeyAttr = ctx.attr(SECURE_KEY);
byte[] secureKey = secureKeyAttr.get();
// 执行加密
byte[] encryptedData = EncryptUtil.encryptByAes(secureKey, data);
out.writeInt(encryptedData.length);
out.writeBytes(encryptedData);
buf.release();
}
}
}
@@ -0,0 +1,34 @@
package org.dromara.neutrinoproxy.core.aot;
import cn.hutool.core.util.ClassUtil;
import io.netty.channel.SimpleChannelInboundHandler;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.noear.solon.aot.RuntimeNativeMetadata;
import org.noear.solon.aot.RuntimeNativeRegistrar;
import org.noear.solon.aot.hint.MemberCategory;
import org.noear.solon.core.AppContext;
import java.util.Set;
/**
* @author songyinyin
* @since 2023/10/23 21:33
*/
public class NeutrinoCoreRuntimeNativeRegistrar implements RuntimeNativeRegistrar {
@Override
public void register(AppContext context, RuntimeNativeMetadata metadata) {
Set<Class<?>> channelInboundClasses = ClassUtil.scanPackageBySuper("org.dromara.neutrinoproxy", SimpleChannelInboundHandler.class);
for (Class<?> clazz : channelInboundClasses) {
metadata.registerReflection(clazz, MemberCategory.INVOKE_DECLARED_METHODS);
}
metadata.registerReflection(ProxyMessage.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
metadata.registerReflection(ProxyMessage.UdpBaseInfo.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
metadata.registerArg("-J--add-opens=java.base/java.lang.invoke=ALL-UNNAMED");
metadata.registerArg("-J--add-opens=java.base/java.nio=ALL-UNNAMED");
metadata.registerArg("-J--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED");
metadata.registerArg("-march=compatibility");
}
}
@@ -3,9 +3,9 @@ package org.dromara.neutrinoproxy.core.dispatcher;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import org.dromara.neutrinoproxy.core.util.TypeUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.neutrinoproxy.core.util.TypeUtil;
import org.noear.snack.ONode;
import java.util.HashMap;
import java.util.List;
@@ -35,10 +35,10 @@ public class DefaultDispatcher<Context, Data> implements Dispatcher<Context, Dat
private Function<Data,String> matcher;
public DefaultDispatcher(String name, List<? extends Handler<Context,Data>> handlerList, Function<Data,String> matcher) {
Assert.notNull(name, "名称不能为空!");
Assert.notNull(matcher, "匹配器不能为空!");
Assert.notNull(name, "name cannot empty!");
Assert.notNull(matcher, "matcher cannot empty!");
if (CollectionUtil.isEmpty(handlerList)) {
log.error("{} 处理器列表为空.", name);
log.error("{} handler list empty.", name);
return;
}
this.name = name == null ? "" : name;
@@ -48,20 +48,20 @@ public class DefaultDispatcher<Context, Data> implements Dispatcher<Context, Dat
for (Handler handler : handlerList) {
Match match = handler.getClass().getAnnotation(Match.class);
if (null == match) {
log.warn("{} 类: {} 缺失Match注解", this.name, handler.getClass().getName());
log.warn("{} class: {} notfound Match annotation", this.name, handler.getClass().getName());
continue;
}
if (StrUtil.isEmpty(match.type())) {
log.warn("{} : {} match注解缺失type参数", this.name, handler.getClass().getName());
log.warn("{} class: {} match annotation notfound type param", this.name, handler.getClass().getName());
continue;
}
if (handlerMap.containsKey(match.type())) {
log.warn("{} : {} match注解type值{} 存在重复!", this.name, handler.getClass().getName(), match.type());
log.warn("{} class: {} match annotation type value {} repeat!", this.name, handler.getClass().getName(), match.type());
continue;
}
handlerMap.put(match.type(), handler);
}
log.info("{} 处理器初始化完成", this.name);
log.info("{} dispatcher init success", this.name);
}
@Override
@@ -71,19 +71,18 @@ public class DefaultDispatcher<Context, Data> implements Dispatcher<Context, Dat
}
String type = matcher.apply(data);
if (null == type) {
log.warn("{} 获取匹配类型失败 data:{}", this.name, JSONObject.toJSONString(data));
log.warn("{} get match type failed data:{}", this.name, ONode.serialize(data));
return;
}
Handler<Context,Data> handler = handlerMap.get(type);
if (null == handler) {
// log.debug("{} 找不到匹配的处理器 type:{}", this.name, type);
return;
}
String handlerName = handler.name();
if (StrUtil.isEmpty(handlerName)) {
handlerName = TypeUtil.getSimpleName(handler.getClass());
}
log.debug("{} 处理器[{}]执行.", this.name, handlerName);
log.debug("{} handler[{}]execute.", this.name, handlerName);
handler.handle(context, data);
}
}
@@ -0,0 +1,58 @@
package org.dromara.neutrinoproxy.core.util;
import cn.hutool.core.util.RandomUtil;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Random;
/**
* AES工具
*/
public class AesUtil {
public static byte[] generateKey() {
byte[] keyBytes = new byte[16];
Random random = RandomUtil.getRandom(true);
random.nextBytes(keyBytes);
return keyBytes;
}
/**
* AES解密
* @param decryptKey 秘钥,16位
* @param encryptBytes 密文
* @return 明文
* @throws Exception
*/
public static byte[] decrypt(byte[] decryptKey, byte[] encryptBytes) {
try{
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey, "AES"));
return cipher.doFinal(encryptBytes);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* AES加密
* @param encryptKey 秘钥,必须为16个字符组成
* @param data 明文
* @return 密文
* @throws Exception
*/
public static byte[] encrypt(byte[] encryptKey, byte[] data) {
try {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey, "AES"));
return cipher.doFinal(data);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
@@ -0,0 +1,150 @@
/**
* 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 org.dromara.neutrinoproxy.core.util;
import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.util.encoders.Hex;
import org.dromara.neutrinoproxy.core.KeyPairRecord;
import javax.crypto.SecretKey;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
/**
* 国密算法加解密工具
* @author: az
* @date: 2023/11/07
*/
public class EncryptUtil {
/**
* 生成SM2密钥对
* @return
*/
public static KeyPairRecord generateSm2KeyPair() {
String privateKeyHex = null;
String publicKeyHex = null;
KeyPair keyPair = Sm2Util.createECKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
if (privateKey instanceof BCECPrivateKey) {
//获取32字节十六进制私钥串
privateKeyHex = ((BCECPrivateKey) privateKey).getD().toString(16);
}
PublicKey publicKey = keyPair.getPublic();
if (publicKey instanceof BCECPublicKey) {
//获取65字节非压缩缩的十六进制公钥串(0x04)
publicKeyHex = Hex.toHexString(((BCECPublicKey) publicKey).getQ().getEncoded(false));
}
return new KeyPairRecord(privateKeyHex, publicKeyHex);
}
/**
* 使用SM2算法对数据进行加密
* @param publicKey 加密所需的公钥
* @param data 需要加密的数据
* @return 加密后的字节数组
*/
public static byte[] encryptBySm2(String publicKey, byte[] data) {
return Sm2Util.encrypt(publicKey, data);
}
/**
* 使用SM2算法对数据进行解密
* @param privateKey 解密所需私钥
* @param data 需要解密的数据
* @return 解密后的字节数组
*/
public static byte[] decryptBySm2(String privateKey, byte[] data) {
return Sm2Util.decrypt(privateKey, data);
}
public static byte[] generateSm4Key() {
return SecureUtil.generateKey("AES", 128).getEncoded();
}
/**
* 使用SM4算法加密数据
* @param key 密钥
* @param data 待加密的数据
* @return 已加密的数据
*/
public static byte[] encryptBySm4(byte[] key, byte[] data) {
return SmUtil.sm4(key).encrypt(data);
}
/**
* 使用SM4算法解密数据
* @param key 密钥
* @param encryptedData 已加密数据
* @return 解密后的数据
*/
public static byte[] decryptBySm4(byte[] key, byte[] encryptedData) {
return SmUtil.sm4(key).decrypt(encryptedData);
}
public static byte[] generateAesKey() {
return AesUtil.generateKey();
}
/**
* 使用AES算法加密数据
* @param key 密钥
* @param data 被加密数据
* @return 加密后的数据
*/
public static byte[] encryptByAes(byte[] key, byte[] data) {
return AesUtil.encrypt(key, data);
}
/**
* 使用AES法解密数据
* @param key 密钥
* @param encryptedData 已加密数据
* @return 解密后的数据
*/
public static byte[] decryptByAes(byte[] key, byte[] encryptedData) {
return AesUtil.decrypt(key, encryptedData);
}
/**
* 使用SM3算法对内容生成摘要
* @param data
* @return
*/
public static String digestBySm3(byte[] data) {
return SmUtil.sm3().digestHex(data);
}
}
@@ -5,6 +5,7 @@ import org.dromara.neutrinoproxy.core.base.MetaDataConstant;
import org.apache.commons.lang3.StringUtils;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
@@ -56,7 +57,7 @@ public class FileUtil {
* @throws IOException
*/
public static String readContentAsString(String path) {
try (BufferedReader br = new BufferedReader(new InputStreamReader(getInputStream(path)))){
try (BufferedReader br = new BufferedReader(new InputStreamReader(getInputStream(path), StandardCharsets.UTF_8))){
return br.lines().collect(Collectors.joining("\n"));
} catch (Exception e) {
return null;
@@ -70,7 +71,7 @@ public class FileUtil {
* @throws IOException
*/
public static String readContentAsString(InputStream in) {
try (BufferedReader br = new BufferedReader(new InputStreamReader(in))){
try (BufferedReader br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))){
return br.lines().collect(Collectors.joining("\n"));
} catch (Exception e) {
return null;
@@ -84,7 +85,7 @@ public class FileUtil {
* @throws IOException
*/
public static List<String> readContentAsStringList(String path) {
try (BufferedReader br = new BufferedReader(new InputStreamReader(getInputStream(path)))){
try (BufferedReader br = new BufferedReader(new InputStreamReader(getInputStream(path), StandardCharsets.UTF_8))){
return br.lines().collect(Collectors.toList());
} catch (Exception e) {
return null;
@@ -0,0 +1,19 @@
package org.dromara.neutrinoproxy.core.util;
import org.noear.solon.Solon;
/**
* @author songyinyin
* @since 2023/10/31 11:48
*/
public class NeutrinoProxyVersion {
/**
* 获取 NeutrinoProxy 版本号
*/
public static String getVersion() {
return Solon.cfg().get("solon.app.version");
}
}
@@ -0,0 +1,216 @@
package org.dromara.neutrinoproxy.core.util;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.spec.ECGenParameterSpec;
/**
* @ClassName SM2Utils
* @Description SM2算法工具类
*/
public class Sm2Util {
/**
* @Description 生成秘钥对
* @return KeyPair
*/
public static KeyPair createECKeyPair() {
//使用标准名称创建EC参数生成的参数规范
final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
// 获取一个椭圆曲线类型的密钥对生成器
final KeyPairGenerator kpg;
try {
kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
// 使用SM2算法域参数集初始化密钥生成器(默认使用以最高优先级安装的提供者的 SecureRandom 的实现作为随机源)
// kpg.initialize(sm2Spec);
// 使用SM2的算法域参数集和指定的随机源初始化密钥生成器
kpg.initialize(sm2Spec, new SecureRandom());
// 通过密钥生成器生成密钥对
return kpg.generateKeyPair();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* @Description 公钥加密
* @param publicKeyHex SM2十六进制公钥
* @param data 明文数据
* @return String
*/
public static byte[] encrypt(String publicKeyHex, byte[] data) {
return encrypt(getECPublicKeyByPublicKeyHex(publicKeyHex), data, 1);
}
/**
* @Description 公钥加密
* @param publicKey SM2公钥
* @param data 明文数据
* @param modeType 加密模式
* @return String
*/
public static byte[] encrypt(BCECPublicKey publicKey, byte[] data, int modeType) {
//加密模式
SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2;
if (modeType != 1) {
mode = SM2Engine.Mode.C1C2C3;
}
//通过公钥对象获取公钥的基本域参数。
ECParameterSpec ecParameterSpec = publicKey.getParameters();
ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
ecParameterSpec.getG(), ecParameterSpec.getN());
//通过公钥值和公钥基本参数创建公钥参数对象
ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(publicKey.getQ(), ecDomainParameters);
//根据加密模式实例化SM2公钥加密引擎
SM2Engine sm2Engine = new SM2Engine(mode);
//初始化加密引擎
sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom()));
byte[] arrayOfBytes = null;
try {
//将明文字符串转换为指定编码的字节串
//通过加密引擎对字节数串行加密
arrayOfBytes = sm2Engine.processBlock(data, 0, data.length);
} catch (Exception e) {
System.out.println("SM2加密时出现异常:" + e.getMessage());
e.printStackTrace();
}
//将加密后的字节串转换为十六进制字符串
return arrayOfBytes;
}
/**
* @Description 私钥解密
* @param privateKeyHex SM2十六进制私钥
* @param cipherData 密文数据
* @return String
*/
public static byte[] decrypt(String privateKeyHex, byte[] cipherData) {
return decrypt(getBCECPrivateKeyByPrivateKeyHex(privateKeyHex), cipherData, 1);
}
/**
* @Description 私钥解密
* @param privateKey SM私钥
* @param cipherDataByte 密文数据
* @param modeType 解密模式
* @return
*/
public static byte[] decrypt(BCECPrivateKey privateKey, byte[] cipherDataByte, int modeType) {
//解密模式
SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2;
if (modeType != 1)
mode = SM2Engine.Mode.C1C2C3;
//通过私钥对象获取私钥的基本域参数。
ECParameterSpec ecParameterSpec = privateKey.getParameters();
ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
ecParameterSpec.getG(), ecParameterSpec.getN());
//通过私钥值和私钥钥基本参数创建私钥参数对象
ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(privateKey.getD(),
ecDomainParameters);
//通过解密模式创建解密引擎并初始化
SM2Engine sm2Engine = new SM2Engine(mode);
sm2Engine.init(false, ecPrivateKeyParameters);
try {
//通过解密引擎对密文字节串进行解密
return sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length);
} catch (Exception e) {
System.out.println("SM2解密时出现异常" + e.getMessage());
}
return new byte[0];
}
//椭圆曲线ECParameters ASN.1 结构
private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
//椭圆曲线公钥或私钥的基本域参数。
private static ECParameterSpec ecDomainParameters = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());
/**
* @Description 公钥字符串转换为 BCECPublicKey 公钥对象
* @param pubKeyHex 64字节十六进制公钥字符串(如果公钥字符串为65字节首个字节为0x04:表示该公钥为非压缩格式,操作时需要删除)
* @return BCECPublicKey SM2公钥对象
*/
public static BCECPublicKey getECPublicKeyByPublicKeyHex(String pubKeyHex) {
//截取64字节有效的SM2公钥(如果公钥首个字节为0x04)
if (pubKeyHex.length() > 128) {
pubKeyHex = pubKeyHex.substring(pubKeyHex.length() - 128);
}
//将公钥拆分为x,y分量(各32字节)
String stringX = pubKeyHex.substring(0, 64);
String stringY = pubKeyHex.substring(stringX.length());
//将公钥x、y分量转换为BigInteger类型
BigInteger x = new BigInteger(stringX, 16);
BigInteger y = new BigInteger(stringY, 16);
//通过公钥x、y分量创建椭圆曲线公钥规范
ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(x9ECParameters.getCurve().createPoint(x, y), ecDomainParameters);
//通过椭圆曲线公钥规范,创建出椭圆曲线公钥对象(可用于SM2加密及验签)
return new BCECPublicKey("EC", ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION);
}
/**
* @Description 私钥字符串转换为 BCECPrivateKey 私钥对象
* @param privateKeyHex 32字节十六进制私钥字符串
* @return BCECPrivateKey SM2私钥对象
*/
public static BCECPrivateKey getBCECPrivateKeyByPrivateKeyHex(String privateKeyHex) {
//将十六进制私钥字符串转换为BigInteger对象
BigInteger d = new BigInteger(privateKeyHex, 16);
//通过私钥和私钥域参数集创建椭圆曲线私钥规范
ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(d, ecDomainParameters);
//通过椭圆曲线私钥规范,创建出椭圆曲线私钥对象(可用于SM2解密和签名)
return new BCECPrivateKey("EC", ecPrivateKeySpec, BouncyCastleProvider.CONFIGURATION);
}
public static void main(String[] args) {
/*String publicKeyHex = null;
String privateKeyHex = null;*/
/*KeyPair keyPair = createECKeyPair();
PublicKey publicKey = keyPair.getPublic();
if (publicKey instanceof BCECPublicKey) {
//获取65字节非压缩缩的十六进制公钥串(0x04)
publicKeyHex = Hex.toHexString(((BCECPublicKey) publicKey).getQ().getEncoded(false));
System.out.println("---->SM2公钥:" + publicKeyHex);
}
PrivateKey privateKey = keyPair.getPrivate();
if (privateKey instanceof BCECPrivateKey) {
//获取32字节十六进制私钥串
privateKeyHex = ((BCECPrivateKey) privateKey).getD().toString(16);
System.out.println("---->SM2私钥:" + privateKeyHex);
}*/
/**
* 公钥加密
*/
// String data = "az";
//将十六进制公钥串转换为 BCECPublicKey 公钥对象
/*String encryptData = encrypt(publicKeyHex, data);
System.out.println("---->加密结果:" + encryptData);*/
/**
* 私钥解密
*/
//将十六进制私钥串转换为 BCECPrivateKey 私钥对象
/*data = decrypt("xx", "xx");
System.out.println("---->解密结果:" + data);*/
}
}
@@ -0,0 +1,18 @@
[
{
"name": "java.nio.channels.spi.SelectorProvider",
"allDeclaredMethods": true
},
{
"name": "sun.nio.ch.KQueueSelectorProvider",
"allDeclaredMethods": true
},
{
"name": "sun.nio.ch.PollSelectorProvider",
"allDeclaredMethods": true
},
{
"name": "sun.nio.ch.SelectorProviderImpl",
"allDeclaredMethods": true
}
]
+10 -9
View File
@@ -1,15 +1,16 @@
FROM openjdk:8-jdk-alpine
FROM openjdk:21-jdk-oracle
#同步时间
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
apk update && apk add wget unzip vim && apk add -U tzdata && \
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
#RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
# apk update && apk add wget unzip vim && apk add -U tzdata && \
# ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
# 设置时区为北京时间
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN mkdir -p /root/neutrino-proxy/config
RUN mkdir -p /root/neutrino-proxy/neutrino-proxy-admin
WORKDIR /root/neutrino-proxy
COPY ../neutrino-proxy-server/target/neutrino-proxy-server.jar /root/neutrino-proxy/neutrino-proxy-server.jar
COPY ../neutrino-proxy-server/src/main/resources/app.yml /root/neutrino-proxy/config
COPY ../neutrino-proxy-admin/dist /root/neutrino-proxy/neutrino-proxy-admin/dist
COPY ./target/neutrino-proxy-server.jar /root/neutrino-proxy/neutrino-proxy-server.jar
COPY ./src/main/resources/app-copy.yml /root/neutrino-proxy/config/app.yml
#VOLUME ["/root/neutrino-proxy"]
ENTRYPOINT ["java","-jar","neutrino-proxy-server.jar","config=./config/app.yml"]
#docker run -it -p 9000-9200:9000-9200/tcp -p 8888:8888 -v /root/neutrino-proxy/config:/root/neutrino-proxy/config -d --restart=always --name neutrino registry.cn-hangzhou.aliyuncs.com/asgc/aoshiguchen-docker-images:1.7
#docker run -it -p 9000-9200:9000-9200/tcp -p 8888:8888 -v /root/neutrino-proxy/config:/root/neutrino-proxy/config -d --restart=always --name neutrino registry.cn-hangzhou.aliyuncs.com/asgc/aoshiguchen-docker-images:1.7
+11 -8
View File
@@ -24,28 +24,24 @@
</dependency>
<dependency>
<groupId>org.noear</groupId>
<artifactId>mybatis-pagehelper-solon-plugin</artifactId>
<artifactId>mybatis-plus-extension-solon-plugin</artifactId>
</dependency>
<dependency>
<groupId>org.noear</groupId>
<artifactId>activerecord-solon-plugin</artifactId>
<artifactId>wood-solon-plugin</artifactId>
</dependency>
<dependency>
<groupId>org.dromara.solon-plugins</groupId>
<artifactId>job-solon-plugin</artifactId>
</dependency>
<dependency>
<groupId>org.dromara.solon-plugins</groupId>
<artifactId>orika-solon-plugin</artifactId>
</dependency>
<dependency>
<groupId>org.dromara.neutrino-proxy</groupId>
<artifactId>neutrino-proxy-core</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
@@ -65,6 +61,13 @@
<build>
<finalName>${project.artifactId}</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>*.yml</include>
</includes>
</resource>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
<filtering>false</filtering>
@@ -1,18 +1,12 @@
package org.dromara.neutrinoproxy.server;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.solonplugins.job.annotation.EnableJob;
import org.noear.snack.core.utils.StringUtil;
import org.noear.solon.Solon;
import org.noear.solon.SolonApp;
import org.noear.solon.Utils;
import org.noear.solon.annotation.SolonMain;
import org.noear.solon.web.cors.CrossFilter;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
*
@@ -28,25 +22,10 @@ public class ProxyServer {
Solon.start(ProxyServer.class, args, app -> {
// 跨域支持。加-1 优先级更高
app.filter(-1, new CrossFilter().allowedOrigins("*"));
// 设置日志级别
setLogLevel(app);
String loglevel = System.getenv("LOG_LEVEL");
if (Utils.isNotEmpty(loglevel)) {
app.cfg().put("solon.logging.logger.root.level", loglevel);
}
});
}
private static void setLogLevel(SolonApp app) {
String loggerLevel = app.cfg().get("neutrino.proxy.logger.level");
if (StringUtils.isBlank(loggerLevel)) {
return;
}
try {
Level level = Level.toLevel(loggerLevel);
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
for (Logger logger : loggerContext.getLoggerList()) {
logger.setLevel(level);
}
} catch (Exception e) {
log.error("日志级别设置失败", e);
}
}
}
@@ -0,0 +1,95 @@
package org.dromara.neutrinoproxy.server.base;
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
import org.dromara.neutrinoproxy.server.base.rest.ResponseBody;
import org.dromara.neutrinoproxy.server.controller.res.report.HomeDataView;
import org.dromara.neutrinoproxy.server.dal.ClientConnectRecordMapper;
import org.dromara.neutrinoproxy.server.dal.FlowReportDayMapper;
import org.dromara.neutrinoproxy.server.dal.FlowReportHourMapper;
import org.dromara.neutrinoproxy.server.dal.FlowReportMinuteMapper;
import org.dromara.neutrinoproxy.server.dal.FlowReportMonthMapper;
import org.dromara.neutrinoproxy.server.dal.JobInfoMapper;
import org.dromara.neutrinoproxy.server.dal.LicenseMapper;
import org.dromara.neutrinoproxy.server.dal.PortMappingMapper;
import org.dromara.neutrinoproxy.server.dal.PortPoolMapper;
import org.dromara.neutrinoproxy.server.dal.UserLoginRecordMapper;
import org.dromara.neutrinoproxy.server.dal.UserMapper;
import org.dromara.neutrinoproxy.server.dal.UserTokenMapper;
import org.dromara.neutrinoproxy.server.service.ClientConnectRecordService;
import org.dromara.neutrinoproxy.server.service.JobInfoService;
import org.dromara.neutrinoproxy.server.service.JobLogService;
import org.dromara.neutrinoproxy.server.service.LicenseService;
import org.dromara.neutrinoproxy.server.service.PortGroupService;
import org.dromara.neutrinoproxy.server.service.PortMappingService;
import org.dromara.neutrinoproxy.server.service.PortPoolService;
import org.dromara.neutrinoproxy.server.service.ReportService;
import org.dromara.neutrinoproxy.server.service.UserLoginRecordService;
import org.dromara.neutrinoproxy.server.service.UserService;
import org.dromara.solonplugins.job.JobBean;
import org.noear.solon.annotation.Component;
import org.noear.solon.aot.RuntimeNativeMetadata;
import org.noear.solon.aot.RuntimeNativeRegistrar;
import org.noear.solon.aot.hint.MemberCategory;
import org.noear.solon.core.AppContext;
/**
* native 打包
*
* @author songyinyin
* @since 2023/10/20 11:24
*/
@Component
public class NeutrinoServerRuntimeNativeRegistrar implements RuntimeNativeRegistrar {
@Override
public void register(AppContext context, RuntimeNativeMetadata metadata) {
metadata.registerResourceInclude("test.jks");
metadata.registerResourceInclude("sql/.*");
// 使用 MP lambda 的类,需要注册序列化
metadata.registerLambdaSerialization(ClientConnectRecordService.class);
metadata.registerLambdaSerialization(JobInfoService.class);
metadata.registerLambdaSerialization(JobLogService.class);
metadata.registerLambdaSerialization(LicenseService.class);
metadata.registerLambdaSerialization(PortGroupService.class);
metadata.registerLambdaSerialization(PortMappingService.class);
metadata.registerLambdaSerialization(PortPoolService.class);
metadata.registerLambdaSerialization(ReportService.class);
metadata.registerLambdaSerialization(UserLoginRecordService.class);
metadata.registerLambdaSerialization(UserService.class);
metadata.registerLambdaSerialization(LicenseMapper.class);
metadata.registerLambdaSerialization(ClientConnectRecordMapper.class);
metadata.registerLambdaSerialization(FlowReportDayMapper.class);
metadata.registerLambdaSerialization(FlowReportHourMapper.class);
metadata.registerLambdaSerialization(FlowReportMinuteMapper.class);
metadata.registerLambdaSerialization(FlowReportMonthMapper.class);
metadata.registerLambdaSerialization(JobInfoMapper.class);
metadata.registerLambdaSerialization(PortMappingMapper.class);
metadata.registerLambdaSerialization(PortPoolMapper.class);
metadata.registerLambdaSerialization(UserLoginRecordMapper.class);
metadata.registerLambdaSerialization(UserMapper.class);
metadata.registerLambdaSerialization(UserTokenMapper.class);
metadata.registerReflection(HomeDataView.class, MemberCategory.DECLARED_FIELDS);
metadata.registerReflection(HomeDataView.Last7dFlow.class, MemberCategory.DECLARED_FIELDS);
metadata.registerReflection(HomeDataView.License.class, MemberCategory.DECLARED_FIELDS);
metadata.registerReflection(HomeDataView.PortMapping.class, MemberCategory.DECLARED_FIELDS);
metadata.registerReflection(HomeDataView.Series.class, MemberCategory.DECLARED_FIELDS);
metadata.registerReflection(HomeDataView.SingleDayFlow.class, MemberCategory.DECLARED_FIELDS);
metadata.registerReflection(HomeDataView.TodayFlow.class, MemberCategory.DECLARED_FIELDS);
metadata.registerReflection(HomeDataView.TotalFlow.class, MemberCategory.DECLARED_FIELDS);
metadata.registerReflection(ResponseBody.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
metadata.registerSerialization(ResponseBody.class);
metadata.registerReflection(ProxyConfig.Protocol.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
metadata.registerReflection(ProxyConfig.Server.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
metadata.registerReflection(ProxyConfig.Tunnel.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
metadata.registerReflection(ProxyConfig.Tcp.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
metadata.registerReflection(ProxyConfig.Udp.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
metadata.registerReflection(JobBean.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
}
}
@@ -24,16 +24,18 @@ package org.dromara.neutrinoproxy.server.base.db;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Lists;
import com.jfinal.plugin.activerecord.Db;
import lombok.extern.slf4j.Slf4j;
import org.dromara.neutrinoproxy.core.util.Assert;
import org.dromara.neutrinoproxy.core.util.FileUtil;
import org.dromara.neutrinoproxy.server.constant.DbTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Init;
import org.noear.solon.annotation.Inject;
import org.noear.solon.core.event.AppLoadEndEvent;
import org.noear.solon.core.event.EventListener;
import org.noear.solon.core.runtime.NativeDetector;
import org.noear.wood.DbContext;
import org.noear.wood.annotation.Db;
import java.util.List;
@@ -51,13 +53,20 @@ public class DBInitialize implements EventListener<AppLoadEndEvent> {
private DbTypeEnum dbTypeEnum;
@Db
DbContext dbContext;
@Init
public void init() throws Throwable {
// aot 阶段,不初始化数据库
if (NativeDetector.isAotRuntime()) {
return;
}
Assert.notNull(dbConfig.getType(), "neutrino.data.db.type不能为空!");
dbTypeEnum = DbTypeEnum.of(dbConfig.getType());
Assert.notNull(dbTypeEnum, "neutrino.data.db.type取值异常!");
log.info("{}数据库初始化...", dbConfig.getType());
log.info("{} database init...", dbConfig.getType());
initDBStructure();
initDBData();
}
@@ -82,8 +91,8 @@ public class DBInitialize implements EventListener<AppLoadEndEvent> {
}
sql += "\r\n" + line.trim();
if (sql.endsWith(";")) {
log.debug("初始化数据库表 sql:{}", sql);
Db.update(sql);
log.debug("init database table sql:{}", sql);
dbContext.exe(sql);
sql = "";
}
}
@@ -101,8 +110,8 @@ public class DBInitialize implements EventListener<AppLoadEndEvent> {
for (String tableName : initDataTableNameList) {
// 表里没有数据的时候,才进行初始化操作
int count = Db.queryInt(String.format("select count(1) from `%s`", tableName));
if (count > 0) {
Object result = dbContext.exe(String.format("select count(1) from `%s`", tableName));
if (result != null && ((Number) result).intValue() > 0) {
continue;
}
List<String> lines = FileUtil.readContentAsStringList(String.format("classpath:/sql/%s/%s.data.sql", dbConfig.getType(), tableName));
@@ -116,8 +125,8 @@ public class DBInitialize implements EventListener<AppLoadEndEvent> {
}
sql += "\r\n" + line.trim();
if (sql.endsWith(";")) {
log.debug("初始化数据[table={}] sql:{}", tableName, sql);
Db.update(sql);
log.debug("init database data[table={}] sql:{}", tableName, sql);
dbContext.exe(sql);
sql = "";
}
}
@@ -4,14 +4,14 @@ import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.MybatisSqlSessionFactoryBuilder;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.solon.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.solon.plugins.inner.PaginationInnerInterceptor;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.ibatis.solon.annotation.Db;
import org.dromara.neutrinoproxy.server.constant.DbTypeEnum;
import org.noear.solon.annotation.Bean;
import org.noear.solon.annotation.Configuration;
import org.noear.solon.annotation.Inject;
import org.sqlite.SQLiteConfig;
import org.sqlite.SQLiteDataSource;
import javax.sql.DataSource;
@@ -25,11 +25,8 @@ public class DbConfiguration {
@Bean(value = "db", typed = true)
public DataSource dataSource(@Inject DbConfig dbConfig) {
DbTypeEnum dbTypeEnum = DbTypeEnum.of(dbConfig.getType());
if (DbTypeEnum.SQLITE == dbTypeEnum) {
SQLiteDataSource dataSource = new SQLiteDataSource();
dataSource.setUrl(dbConfig.getUrl());
dataSource.setJournalMode(SQLiteConfig.JournalMode.WAL.getValue());
return dataSource;
if (DbTypeEnum.H2 == dbTypeEnum) {
return newHikariDataSource(dbConfig, "org.h2.Driver");
} else if (DbTypeEnum.MYSQL == dbTypeEnum) {
String driver = "com.mysql.cj.jdbc.Driver";
try {
@@ -66,8 +63,12 @@ public class DbConfiguration {
@Bean
public void db1_ext2(@Db("db") MybatisConfiguration config) {
config.getTypeHandlerRegistry().register("fun.asgc.neutrino.proxy.server.dal");
config.getTypeHandlerRegistry().register("org.dromara.neutrinoproxy.server.dal.entity");
config.setDefaultEnumTypeHandler(null);
MybatisPlusInterceptor plusInterceptor = new MybatisPlusInterceptor();
plusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
config.addInterceptor(plusInterceptor);
}
@Bean
@@ -1,5 +1,6 @@
package org.dromara.neutrinoproxy.server.base.page;
import com.baomidou.mybatisplus.solon.plugins.pagination.Page;
import lombok.Data;
import java.io.Serializable;
@@ -101,11 +102,20 @@ public class PageInfo<T> implements Serializable {
}
public static <T> PageInfo<T> of(List<T> records, Long total, Integer current, Integer size) {
PageInfo<T> pageInfo = new PageInfo();
PageInfo<T> pageInfo = new PageInfo<>();
pageInfo.setRecords(records);
pageInfo.setTotal(total);
pageInfo.setCurrent(current);
pageInfo.setSize(size);
return pageInfo;
}
public static <T> PageInfo<T> of(List<T> records, Page<?> page) {
PageInfo<T> pageInfo = new PageInfo<>();
pageInfo.setRecords(records);
pageInfo.setTotal(page.getTotal());
pageInfo.setCurrent((int) page.getCurrent());
pageInfo.setSize((int) page.getSize());
return pageInfo;
}
}
@@ -42,14 +42,8 @@ public class ProxyConfig {
@Data
public static class Server {
private Integer bossThreadCount;
private Integer workThreadCount;
private String domainName;
private Integer httpProxyPort;
private Integer httpsProxyPort;
private String keyStorePassword;
private String jksPath;
private Boolean transferLogEnable;
private Tcp tcp;
private Udp udp;
}
@Data
@@ -65,4 +59,22 @@ public class ProxyConfig {
private Boolean heartbeatLogEnable;
}
@Data
public static class Tcp {
private Integer bossThreadCount;
private Integer workThreadCount;
private String domainName;
private Integer httpProxyPort;
private Integer httpsProxyPort;
private String keyStorePassword;
private String jksPath;
private Boolean transferLogEnable;
}
@Data
public static class Udp {
private Integer bossThreadCount;
private Integer workThreadCount;
private Boolean transferLogEnable;
}
}
@@ -1,20 +1,25 @@
package org.dromara.neutrinoproxy.server.base.proxy;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LoggingHandler;
import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.dromara.neutrinoproxy.core.ProxyMessageHandler;
import org.dromara.neutrinoproxy.core.aot.NeutrinoCoreRuntimeNativeRegistrar;
import org.dromara.neutrinoproxy.core.dispatcher.DefaultDispatcher;
import org.dromara.neutrinoproxy.core.dispatcher.Dispatcher;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.nio.NioEventLoopGroup;
import org.dromara.neutrinoproxy.server.proxy.core.BytesMetricsHandler;
import org.dromara.neutrinoproxy.server.proxy.core.ProxyTunnelServer;
import org.dromara.neutrinoproxy.server.proxy.core.TcpVisitorChannelHandler;
import org.dromara.neutrinoproxy.server.proxy.core.UdpVisitorChannelHandler;
import org.noear.solon.Solon;
import org.noear.solon.annotation.Bean;
import org.noear.solon.annotation.Configuration;
@@ -34,44 +39,84 @@ public class ProxyConfiguration implements LifecycleBean {
@Override
public void start() throws Throwable {
List<ProxyMessageHandler> list = Solon.context().getBeansOfType(ProxyMessageHandler.class);
Dispatcher<ChannelHandlerContext, ProxyMessage> dispatcher = new DefaultDispatcher<>("消息调度器", list,
proxyMessage -> ProxyDataTypeEnum.of((int)proxyMessage.getType()) == null ?
null : ProxyDataTypeEnum.of((int)proxyMessage.getType()).getName());
Dispatcher<ChannelHandlerContext, ProxyMessage> dispatcher = new DefaultDispatcher<>("MessageDispatcher", list,
proxyMessage -> ProxyDataTypeEnum.of((int)proxyMessage.getType()) == null ?
null : ProxyDataTypeEnum.of((int)proxyMessage.getType()).getName());
Solon.context().wrapAndPut(Dispatcher.class, dispatcher);
}
@Bean("serverBossGroup")
public NioEventLoopGroup serverBossGroup(@Inject ProxyConfig proxyConfig) {
return new NioEventLoopGroup(proxyConfig.getServer().getBossThreadCount());
@Bean("tcpServerBossGroup")
public NioEventLoopGroup tcpServerBossGroup(@Inject ProxyConfig proxyConfig) {
ProxyConfig.Server server = proxyConfig.getServer();
ProxyConfig.Tcp tcp = server.getTcp();
return new NioEventLoopGroup(tcp.getBossThreadCount());
}
@Bean("serverWorkerGroup")
public NioEventLoopGroup serverWorkerGroup(@Inject ProxyConfig proxyConfig) {
return new NioEventLoopGroup(proxyConfig.getServer().getWorkThreadCount());
@Bean("tcpServerWorkerGroup")
public NioEventLoopGroup tcpServerWorkerGroup(@Inject ProxyConfig proxyConfig) {
return new NioEventLoopGroup(proxyConfig.getServer().getTcp().getWorkThreadCount());
}
@Bean("tcpServerBootstrap")
public ServerBootstrap tcpServerBootstrap(@Inject("serverBossGroup") NioEventLoopGroup serverBossGroup,
@Inject("serverWorkerGroup") NioEventLoopGroup serverWorkerGroup,
public ServerBootstrap tcpServerBootstrap(@Inject("tcpServerBossGroup") NioEventLoopGroup tcpServerBossGroup,
@Inject("tcpServerWorkerGroup") NioEventLoopGroup tcpServerWorkerGroup,
@Inject ProxyConfig proxyConfig
) {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(serverBossGroup, serverWorkerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
if (null != proxyConfig.getServer().getTransferLogEnable() && proxyConfig.getServer().getTransferLogEnable()) {
ch.pipeline().addFirst(new LoggingHandler(TcpVisitorChannelHandler.class));
}
ch.pipeline().addFirst(new BytesMetricsHandler());
ch.pipeline().addLast(new TcpVisitorChannelHandler());
bootstrap.group(tcpServerBossGroup, tcpServerWorkerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
if (null != proxyConfig.getServer().getTcp().getTransferLogEnable() && proxyConfig.getServer().getTcp().getTransferLogEnable()) {
ch.pipeline().addFirst(new LoggingHandler(TcpVisitorChannelHandler.class));
}
ch.pipeline().addFirst(new BytesMetricsHandler());
// ch.pipeline().addLast(new ChannelTrafficShapingHandler(1024 * 1024 * 20, 1024 * 1024 * 20, 100, 20000));
ch.pipeline().addLast(new TcpVisitorChannelHandler());
}
});
return bootstrap;
}
@Bean("udpServerBossGroup")
public NioEventLoopGroup udpServerBossGroup(@Inject ProxyConfig proxyConfig) {
return new NioEventLoopGroup(proxyConfig.getServer().getUdp().getBossThreadCount());
}
@Bean("udpServerWorkerGroup")
public NioEventLoopGroup udpServerWorkerGroup(@Inject ProxyConfig proxyConfig) {
return new NioEventLoopGroup(proxyConfig.getServer().getUdp().getWorkThreadCount());
}
@Bean("udpServerBootstrap")
public Bootstrap udpServerBootstrap(@Inject("udpServerBossGroup") NioEventLoopGroup udpServerBossGroup,
@Inject("udpServerWorkerGroup") NioEventLoopGroup udpServerWorkerGroup,
@Inject ProxyConfig proxyConfig) {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(udpServerBossGroup)
// 主线程处理
.channel(NioDatagramChannel.class)
// 广播
.option(ChannelOption.SO_BROADCAST, true)
// 设置读缓冲区为2M
.option(ChannelOption.SO_RCVBUF, 2048 * 1024)
// 设置写缓冲区为1M
.option(ChannelOption.SO_SNDBUF, 1024 * 1024)
.handler(new ChannelInitializer<NioDatagramChannel>() {
@Override
protected void initChannel(NioDatagramChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
if (null != proxyConfig.getServer().getUdp().getTransferLogEnable() && proxyConfig.getServer().getUdp().getTransferLogEnable()) {
ch.pipeline().addFirst(new LoggingHandler(UdpVisitorChannelHandler.class));
}
pipeline.addLast(udpServerWorkerGroup, new UdpVisitorChannelHandler());
}
});
return bootstrap;
}
@Bean("tunnelBossGroup")
public NioEventLoopGroup tunnelBossGroup(@Inject ProxyConfig proxyConfig) {
return new NioEventLoopGroup(proxyConfig.getTunnel().getBossThreadCount());
@@ -82,4 +127,9 @@ public class ProxyConfiguration implements LifecycleBean {
return new NioEventLoopGroup(proxyConfig.getTunnel().getWorkThreadCount());
}
@Bean
public NeutrinoCoreRuntimeNativeRegistrar neutrinoCoreRuntimeNativeRegistrar() {
return new NeutrinoCoreRuntimeNativeRegistrar();
}
}
@@ -42,6 +42,7 @@ public class ServiceException extends RuntimeException {
private String msg;
public ServiceException(int code, String msg) {
super(msg);
this.code = code;
this.msg = msg;
}
@@ -74,7 +74,7 @@ public class BaseAuthInterceptor implements RouterInterceptor {
}
if (result instanceof Throwable) {
log.error("全局异常", (Throwable) result);
log.error("global error", (Throwable) result);
if (result instanceof ServiceException) {
ServiceException exception = (ServiceException) result;
@@ -23,7 +23,7 @@ public class GlobalExceptionFilter implements Filter {
try {
chain.doFilter(ctx);
} catch (Throwable e) {
log.error("全局异常", e);
log.error("global error", e);
if (e instanceof ServiceException) {
ServiceException serviceException = (ServiceException) e;
@@ -1,7 +1,7 @@
package org.dromara.neutrinoproxy.server.base.rest.interceptor;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.noear.snack.ONode;
import org.noear.solon.annotation.Component;
import org.noear.solon.core.handle.Action;
import org.noear.solon.core.handle.Context;
@@ -31,10 +31,10 @@ public class VisitLogInterceptor implements RouterInterceptor {
} finally {
Date now = new Date();
long elapsedTime = now.getTime() - startTime.getTime();
log.debug("\n-----------------------------------------------------------------接口请求日志\n{} url:{} 执行耗时:{}\n请求体参数:{}\n响应结果:{}\n客户端IP:{}\n",
log.debug("\n-----------------------------------------------------------------interface request log\n{} url:{} elapsed time:{}\nrequest:{}\nresponse:{}\nclient ip:{}\n",
ctx.method(), ctx.path(), getElapsedTimeStr(elapsedTime),
JSONObject.toJSONString(ctx.paramMap()),
JSONObject.toJSONString(JSONObject.toJSONString(ctx.result)),
ONode.serialize(ctx.paramMap()),
ONode.serialize(ctx.result),
ctx.realIp()
);
}
@@ -47,11 +47,11 @@ public class VisitLogInterceptor implements RouterInterceptor {
*/
private static String getElapsedTimeStr(long elapsedTime) {
if (elapsedTime < 1000) {
return String.format("%s毫秒", elapsedTime);
return String.format("%sms", elapsedTime);
} else if (elapsedTime < 60000) {
return String.format("%.2f", (elapsedTime * 1.0) / 1000);
return String.format("%.2fs", (elapsedTime * 1.0) / 1000);
}
return String.format("%.2f分钟", (elapsedTime * 1.0) / 1000 / 60);
return String.format("%.2fm", (elapsedTime * 1.0) / 1000 / 60);
}
}
@@ -37,7 +37,7 @@ import java.util.stream.Stream;
@Getter
@AllArgsConstructor
public enum DbTypeEnum {
SQLITE("sqlite"),
H2("h2"),
MYSQL("mysql"),
MARIADB("mariadb"),
;
@@ -50,6 +50,7 @@ public enum ExceptionConstant {
ORIGIN_PASSWORD_CHECK_FAIL(12002, "原密码验证失败"),
LOGIN_PASSWORD_LENGTH_CHECK_FAIL(12003, "登录密码不能小于6位数"),
LOGIN_PASSWORD_NO_CHANGE_MODIFY_FAIL(12004, "密码没有变化,修改失败"),
LICENSE_CANNOT_BE_DELETED(12005, "license下存在[{}]个端口映射!"),
// 端口池管理(13000)
PORT_CANNOT_REPEAT(13000,"端口不能重复"),
PORT_NOT_EXIST(13001, "该端口在端口池中不存在"),
@@ -21,7 +21,7 @@ public class DomainNameController {
@Get
@Mapping("/bind-info")
public String bindInfo () {
return proxyConfig.getServer().getDomainName();
return proxyConfig.getServer().getTcp().getDomainName();
}
}
@@ -40,7 +40,7 @@ public class PortMappingController {
ParamCheckUtil.checkNotNull(req.getServerPort(), "serverPort");
ParamCheckUtil.checkNotNull(req.getClientPort(), "clientPort");
ParamCheckUtil.checkNotEmpty(req.getProtocal(), "protocal");
ParamCheckUtil.checkMaxLength(req.getDescription(), 50, "描述", "50");
ParamCheckUtil.checkMaxLength(req.getDescription(), 50, "描述", "50");
if (StringUtils.isBlank(req.getClientIp())) {
// 没传客户端ip,默认为127.0.0.1
req.setClientIp("127.0.0.1");
@@ -52,6 +52,12 @@ public class PortMappingController {
req.setSubdomain(null);
}
req.setProtocal(networkProtocolEnum.getDesc());
if (null == req.getProxyResponses()) {
req.setProxyResponses(0);
}
if (null == req.getProxyTimeoutMs()) {
req.setProxyTimeoutMs(0L);
}
return portMappingService.create(req);
}
@@ -76,6 +82,12 @@ public class PortMappingController {
req.setSubdomain(null);
}
req.setProtocal(networkProtocolEnum.getDesc());
if (null == req.getProxyResponses()) {
req.setProxyResponses(0);
}
if (null == req.getProxyTimeoutMs()) {
req.setProxyTimeoutMs(0L);
}
return portMappingService.update(req);
}
@@ -102,7 +102,7 @@ public class PortPoolController {
@Get
@Mapping("/get-available-port-list")
public List<PortPoolListRes> getAvailablePortList(AvailablePortListReq req) {
public PageInfo<PortPoolListRes> getAvailablePortList(AvailablePortListReq req) {
ParamCheckUtil.checkNotNull(req, "req");
ParamCheckUtil.checkNotNull(req.getLicenseId(), "licenseId");
return portPoolService.getAvailablePortList(req);
@@ -133,4 +133,12 @@ public class PortPoolController {
portPoolService.deleteBatch(req.getIds());
}
@Get
@Mapping("/port-available")
public boolean portAvailable(Integer port, Integer portMappingId) {
ParamCheckUtil.checkNotNull(port, "port");
return portPoolService.portAvailable(port, portMappingId);
}
}
@@ -30,8 +30,16 @@ import lombok.Data;
*/
@Data
public class LicenseListReq {
/**
* 用户ID
*/
private Integer userId;
/**
* 是否在线
*/
private Integer isOnline;
/**
* 启动状态 1启用 2禁用
*/
private Integer enable;
}
@@ -54,6 +54,14 @@ public class PortMappingCreateReq {
* 客户端端口
*/
private Integer clientPort;
/**
* 代理响应数量(响应数据包数量,如果没有拆包则等于数据条数)
*/
private Integer proxyResponses;
/**
* 代理超时时间
*/
private Long proxyTimeoutMs;
/**
* 描述
*/
@@ -58,6 +58,14 @@ public class PortMappingUpdateReq {
* 客户端端口
*/
private Integer clientPort;
/**
* 代理响应数量(响应数据包数量,如果没有拆包则等于数据条数)
*/
private Integer proxyResponses;
/**
* 代理超时时间
*/
private Long proxyTimeoutMs;
/**
* 描述
*/
@@ -12,4 +12,18 @@ public class AvailablePortListReq {
* licenseId
*/
private Integer licenseId;
/**
* 当前页
*/
private int page = 1;
/**
* 分页大小
*/
private int size = 10;
/**
* 搜索关键字
*/
private String keyword;
}
@@ -49,6 +49,14 @@ public class PortMappingDetailRes {
* {@link OnlineStatusEnum}
*/
private Integer isOnline;
/**
* 代理响应数量(响应数据包数量,如果没有拆包则等于数据条数)
*/
private Integer proxyResponses;
/**
* 代理超时时间
*/
private Long proxyTimeoutMs;
/**
* 启用状态
* {@link EnableStatusEnum}
@@ -89,6 +89,14 @@ public class PortMappingListRes {
* {@link EnableStatusEnum}
*/
private Integer enable;
/**
* 代理响应数量(响应数据包数量,如果没有拆包则等于数据条数)
*/
private Integer proxyResponses;
/**
* 代理超时时间
*/
private Long proxyTimeoutMs;
/**
* 描述
*/
@@ -1,6 +1,7 @@
package org.dromara.neutrinoproxy.server.dal;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.dromara.neutrinoproxy.server.controller.req.system.PortGroupListReq;
import org.dromara.neutrinoproxy.server.controller.res.system.PortGroupListRes;
@@ -14,7 +15,7 @@ import java.util.List;
@Mapper
public interface PortGroupMapper extends BaseMapper<PortGroupDO> {
List<PortGroupListRes> selectPortGroupListResList(PortGroupListReq res);
List<PortGroupListRes> selectPortGroupListResList(IPage<PortGroupListRes> page, PortGroupListReq res);
default void updateEnableStatus(Integer id, Integer enable, Date now){
@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import org.dromara.neutrinoproxy.server.controller.req.proxy.PortMappingListReq;
@@ -74,6 +75,14 @@ public interface PortMappingMapper extends BaseMapper<PortMappingDO> {
);
}
default PortMappingDO findByLicenseIdAndServerPort(Integer licenseId, Integer serverPort) {
return this.selectOne(new LambdaQueryWrapper<PortMappingDO>()
.eq(PortMappingDO::getLicenseId, licenseId)
.eq(PortMappingDO::getServerPort, serverPort)
.last("limit 1")
);
}
default void updateOnlineStatus(Integer licenseId,Integer serverPort, Integer isOnline, Date updateTime) {
this.update(null, new LambdaUpdateWrapper<PortMappingDO>()
.eq(PortMappingDO::getLicenseId, licenseId)
@@ -98,5 +107,5 @@ public interface PortMappingMapper extends BaseMapper<PortMappingDO> {
);
}
List<PortMappingDO> selectPortMappingByCondition(@Param("req") PortMappingListReq req);
List<PortMappingDO> selectPortMappingByCondition(IPage<PortMappingDO> page, @Param("req") PortMappingListReq req);
}
@@ -24,6 +24,7 @@ package org.dromara.neutrinoproxy.server.dal;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.dromara.neutrinoproxy.server.controller.req.system.PortPoolListReq;
import org.dromara.neutrinoproxy.server.controller.res.system.PortPoolListRes;
import org.dromara.neutrinoproxy.server.dal.entity.PortPoolDO;
@@ -65,7 +66,7 @@ public interface PortPoolMapper extends BaseMapper<PortPoolDO> {
);
}
List<PortPoolListRes> selectResList(@Param("req") PortPoolListReq req);
List<PortPoolListRes> selectResList(IPage<PortPoolListRes> page, @Param("req") PortPoolListReq req);
List<PortPoolListRes> getAvailablePortList(@Param("licenseId") Integer licenseId,@Param("userId") Integer userId);
List<PortPoolListRes> getAvailablePortList(IPage<PortPoolListRes> page, @Param("licenseId") Integer licenseId,@Param("userId") Integer userId, @Param("keyword") String keyword);
}
@@ -1,5 +1,6 @@
package org.dromara.neutrinoproxy.server.dal;
import com.baomidou.mybatisplus.solon.plugins.pagination.Page;
import org.dromara.neutrinoproxy.server.controller.res.report.*;
import org.dromara.neutrinoproxy.server.controller.res.report.LicenseFlowMonthReportRes;
import org.dromara.neutrinoproxy.server.controller.res.report.LicenseFlowReportRes;
@@ -24,26 +25,26 @@ public interface ReportMapper {
* @param userId
* @return
*/
List<UserFlowReportRes> userFlowReportList(@Param("userId") Integer userId, @Param("curMonthBeginDate") Date curMonthBeginDate, @Param("curDayBeginDate") Date curDayBeginDate, @Param("curDate") Date curDate);
List<UserFlowReportRes> userFlowReportList(Page<UserFlowReportRes> page, @Param("userId") Integer userId, @Param("curMonthBeginDate") Date curMonthBeginDate, @Param("curDayBeginDate") Date curDayBeginDate, @Param("curDate") Date curDate);
/**
* 基于License维度的流量报表
* @param userId
* @return
*/
List<LicenseFlowReportRes> licenseFLowReportList(@Param("userId") Integer userId, @Param("curMonthBeginDate") Date curMonthBeginDate, @Param("curDayBeginDate") Date curDayBeginDate, @Param("curDate") Date curDate);
List<LicenseFlowReportRes> licenseFLowReportList(Page<LicenseFlowReportRes> page, @Param("userId") Integer userId, @Param("curMonthBeginDate") Date curMonthBeginDate, @Param("curDayBeginDate") Date curDayBeginDate, @Param("curDate") Date curDate);
/**
* 用户流量月度明细
* @param userId
* @return
*/
List<UserFlowMonthReportRes> userFlowMonthReportList(@Param("userId") Integer userId, @Param("curMonthBeginDate") Date curMonthBeginDate, @Param("curDayBeginDate") Date curDayBeginDate, @Param("curDate") Date curDate);
List<UserFlowMonthReportRes> userFlowMonthReportList(Page<UserFlowMonthReportRes> page, @Param("userId") Integer userId, @Param("curMonthBeginDate") Date curMonthBeginDate, @Param("curDayBeginDate") Date curDayBeginDate, @Param("curDate") Date curDate);
/**
* License流量月度明细
* @param userId
* @return
*/
List<LicenseFlowMonthReportRes> licenseFLowMonthReportList(@Param("userId") Integer userId, @Param("licenseId") Integer licenseId, @Param("curMonthBeginDate") Date curMonthBeginDate, @Param("curDayBeginDate") Date curDayBeginDate, @Param("curDate") Date curDate);
List<LicenseFlowMonthReportRes> licenseFLowMonthReportList(Page<LicenseFlowMonthReportRes> page, @Param("userId") Integer userId, @Param("licenseId") Integer licenseId, @Param("curMonthBeginDate") Date curMonthBeginDate, @Param("curDayBeginDate") Date curDayBeginDate, @Param("curDate") Date curDate);
/**
* 首页 - 今日流量
@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.dromara.neutrinoproxy.server.controller.res.log.ClientConnectRecordListRes;
import java.util.Date;
@@ -34,4 +35,17 @@ public class ClientConnectRecordDO {
* 创建时间
*/
private Date createTime;
public ClientConnectRecordListRes toRes() {
ClientConnectRecordListRes res = new ClientConnectRecordListRes();
res.setId(id);
res.setIp(ip);
res.setLicenseId(licenseId);
res.setType(type);
res.setMsg(msg);
res.setCode(code);
res.setErr(err);
res.setCreateTime(createTime);
return res;
}
}
@@ -29,6 +29,7 @@ import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import org.dromara.neutrinoproxy.server.controller.res.system.JobInfoListRes;
import java.util.Date;
@@ -64,4 +65,19 @@ public class JobInfoDO {
* 更新时间
*/
private Date updateTime;
public JobInfoListRes toRes() {
JobInfoListRes res = new JobInfoListRes();
res.setId(id);
res.setCron(cron);
res.setDesc(desc);
res.setAlarmEmail(alarmEmail);
res.setAlarmDing(alarmDing);
res.setHandler(handler);
res.setParam(param);
res.setEnable(enable);
res.setCreateTime(createTime);
res.setUpdateTime(updateTime);
return res;
}
}
@@ -27,6 +27,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.dromara.neutrinoproxy.server.controller.res.log.JobLogListRes;
import java.util.Date;
@@ -52,4 +53,17 @@ public class JobLogDO {
* 创建时间
*/
private Date createTime;
public JobLogListRes toRes() {
JobLogListRes res = new JobLogListRes();
res.setId(id);
res.setJobId(jobId);
res.setHandler(handler);
res.setParam(param);
res.setCode(code);
res.setMsg(msg);
res.setAlarmStatus(alarmStatus);
res.setCreateTime(createTime);
return res;
}
}
@@ -30,6 +30,7 @@ import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.dromara.neutrinoproxy.server.controller.res.proxy.LicenseListRes;
import java.util.Date;
@@ -76,4 +77,17 @@ public class LicenseDO {
* 更新时间
*/
private Date updateTime;
public LicenseListRes toRes() {
LicenseListRes res = new LicenseListRes();
res.setId(id);
res.setName(name);
res.setKey(key);
res.setUserId(userId);
res.setIsOnline(isOnline);
res.setEnable(enable);
res.setCreateTime(createTime);
res.setUpdateTime(updateTime);
return res;
}
}
@@ -29,6 +29,7 @@ import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.dromara.neutrinoproxy.server.controller.res.proxy.PortMappingListRes;
import java.util.Date;
@@ -78,6 +79,14 @@ public class PortMappingDO {
* {@link OnlineStatusEnum}
*/
private Integer isOnline;
/**
* 代理响应数量(响应数据包数量,如果没有拆包则等于数据条数)
*/
private Integer proxyResponses;
/**
* 代理超时时间
*/
private Long proxyTimeoutMs;
/**
* 启用状态
* {@link EnableStatusEnum}
@@ -91,4 +100,24 @@ public class PortMappingDO {
* 更新时间
*/
private Date updateTime;
public PortMappingListRes toRes() {
PortMappingListRes res = new PortMappingListRes();
res.setId(id);
res.setLicenseId(licenseId);
res.setProtocal(protocal);
res.setSubdomain(subdomain);
res.setServerPort(serverPort);
res.setClientIp(clientIp);
res.setClientPort(clientPort);
res.setDescription(description);
res.setIsOnline(isOnline);
res.setProxyResponses(proxyResponses);
res.setProxyTimeoutMs(proxyTimeoutMs);
res.setEnable(enable);
res.setCreateTime(createTime);
res.setUpdateTime(updateTime);
return res;
}
}
@@ -27,6 +27,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.dromara.neutrinoproxy.server.controller.res.system.PortPoolListRes;
import java.util.Date;
@@ -64,4 +65,15 @@ public class PortPoolDO {
* 更新时间
*/
private Date updateTime;
public PortPoolListRes toRes() {
PortPoolListRes res = new PortPoolListRes();
res.setId(id);
res.setGroupId(groupId);
res.setPort(port);
res.setEnable(enable);
res.setCreateTime(createTime);
res.setUpdateTime(updateTime);
return res;
}
}
@@ -26,6 +26,7 @@ import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.ToString;
import org.dromara.neutrinoproxy.server.controller.res.system.UserListRes;
import java.util.Date;
@@ -36,7 +37,7 @@ import java.util.Date;
*/
@ToString
@Data
@TableName("user")
@TableName("`user`")
public class UserDO {
@TableId(type = IdType.AUTO)
private Integer id;
@@ -64,4 +65,15 @@ public class UserDO {
* 更新时间
*/
private Date updateTime;
public UserListRes toRes() {
UserListRes res = new UserListRes();
res.setId(id);
res.setName(name);
res.setLoginName(loginName);
res.setEnable(enable);
res.setCreateTime(createTime);
res.setUpdateTime(updateTime);
return res;
}
}
@@ -18,6 +18,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.dromara.neutrinoproxy.server.controller.res.log.UserLoginRecordListRes;
import java.util.Date;
@@ -62,4 +63,14 @@ public class UserLoginRecordDO {
* 创建时间
*/
private Date createTime;
public UserLoginRecordListRes toRes() {
UserLoginRecordListRes res = new UserLoginRecordListRes();
res.setId(id);
res.setUserId(userId);
res.setIp(ip);
res.setType(type);
res.setCreateTime(createTime);
return res;
}
}
@@ -1,14 +1,19 @@
package org.dromara.neutrinoproxy.server.job;
import com.alibaba.fastjson.JSONObject;
import org.dromara.neutrinoproxy.core.util.DateUtil;
import org.dromara.neutrinoproxy.server.dal.*;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.neutrinoproxy.core.util.DateUtil;
import org.dromara.neutrinoproxy.server.dal.ClientConnectRecordMapper;
import org.dromara.neutrinoproxy.server.dal.FlowReportDayMapper;
import org.dromara.neutrinoproxy.server.dal.FlowReportHourMapper;
import org.dromara.neutrinoproxy.server.dal.FlowReportMinuteMapper;
import org.dromara.neutrinoproxy.server.dal.JobLogMapper;
import org.dromara.neutrinoproxy.server.dal.UserLoginRecordMapper;
import org.dromara.solonplugins.job.IJobHandler;
import org.dromara.solonplugins.job.annotation.JobHandler;
import org.noear.snack.ONode;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
@@ -70,37 +75,37 @@ public class DataCleanJob implements IJobHandler {
{
Date date = DateUtil.addDate(new Date(), Calendar.DATE, -1 * jobParams.getJobLogKeepDays());
log.info("清理调度管理日志 date:{}", sdf.format(date));
log.info("clean job log date:{}", sdf.format(date));
jobLogMapper.clean(date);
}
{
Date date = DateUtil.addDate(new Date(), Calendar.DATE, -1 * jobParams.getUserLoginRecordKeepDays());
log.info("清理用户登录日志 date:{}", sdf.format(date));
log.info("clean user login record date:{}", sdf.format(date));
userLoginRecordMapper.clean(date);
}
{
Date date = DateUtil.addDate(new Date(), Calendar.DATE, -1 * jobParams.getClientConnectRecordKeepDays());
log.info("清理客户端连接日志 date:{}", sdf.format(date));
log.info("clean client connect record date:{}", sdf.format(date));
clientConnectRecordMapper.clean(date);
}
{
Date date = DateUtil.addDate(new Date(), Calendar.DATE, -1 * jobParams.getFlowMinuteReportKeepDays());
log.info("清理流通统计分钟报表日志 date:{}", sdf.format(date));
log.info("clean flow report minute record date:{}", sdf.format(date));
flowReportMinuteMapper.clean(date);
}
{
Date date = DateUtil.addDate(new Date(), Calendar.DATE, -1 * jobParams.getFlowHourReportKeepDays());
log.info("清理流通统计小时报表日志 date:{}", sdf.format(date));
log.info("clean flow report hour record date:{}", sdf.format(date));
flowReportHourMapper.clean(date);
}
{
Date date = DateUtil.addDate(new Date(), Calendar.DATE, -1 * jobParams.getFlowDayReportKeepDays());
log.info("清理流通统计日报表日志 date:{}", sdf.format(date));
log.info("clean flow report day record date:{}", sdf.format(date));
flowReportDayMapper.clean(date);
}
}
@@ -108,7 +113,7 @@ public class DataCleanJob implements IJobHandler {
public static JobParams getParams(String s) {
try {
if (StringUtils.isNotBlank(s)) {
return JSONObject.parseObject(s, JobParams.class);
return ONode.deserialize(s, JobParams.class);
}
} catch (Exception e) {
// ignore
@@ -70,9 +70,9 @@ public class ProxyTunnelChannelHandler extends SimpleChannelInboundHandler<Proxy
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
Channel userChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (userChannel != null) {
userChannel.config().setOption(ChannelOption.AUTO_READ, ctx.channel().isWritable());
Channel visitorChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (visitorChannel != null) {
visitorChannel.config().setOption(ChannelOption.AUTO_READ, ctx.channel().isWritable());
}
super.channelWritabilityChanged(ctx);
@@ -89,8 +89,11 @@ public class ProxyTunnelChannelHandler extends SimpleChannelInboundHandler<Proxy
if (null != cmdChannel) {
ProxyUtil.removeVisitorChannelFromCmdChannel(cmdChannel, visitorId);
}
ProxyUtil.remoteProxyConnectAttachment(visitorId);
if (visitorChannel.isActive()) {
// 此处如果时UDP的 visitorChannel则不能close先临时判断一下
Boolean isUdp = visitorChannel.attr(Constants.IS_UDP_KEY).get();
if (visitorChannel.isActive() && null == isUdp) {
// 数据发送完成后再关闭连接解决http1.0数据传输问题
visitorChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
visitorChannel.close();
@@ -106,16 +109,16 @@ public class ProxyTunnelChannelHandler extends SimpleChannelInboundHandler<Proxy
// 防止下次换一个客户端无法连接的情况
ProxyUtil.removeClientIdByLicenseId(cmdChannelAttachInfo.getLicenseId());
}
// 即便是因为上述原因断开断开的日志依然要记录方便排查问题
Solon.context().getBean(ClientConnectRecordService.class).add(new ClientConnectRecordDO()
.setIp(((InetSocketAddress)ctx.channel().remoteAddress()).getAddress().getHostAddress())
.setLicenseId(cmdChannelAttachInfo.getLicenseId())
.setType(ClientConnectTypeEnum.DISCONNECT.getType())
.setMsg("")
.setCode(SuccessCodeEnum.SUCCESS.getCode())
.setCreateTime(new Date())
);
}
// 即便是因为上述原因断开断开的日志依然要记录方便排查问题
Solon.context().getBean(ClientConnectRecordService.class).add(new ClientConnectRecordDO()
.setIp(((InetSocketAddress)ctx.channel().remoteAddress()).getAddress().getHostAddress())
.setLicenseId(cmdChannelAttachInfo.getLicenseId())
.setType(ClientConnectTypeEnum.DISCONNECT.getType())
.setMsg("")
.setCode(SuccessCodeEnum.SUCCESS.getCode())
.setCreateTime(new Date())
);
}
super.channelInactive(ctx);
@@ -123,10 +126,11 @@ public class ProxyTunnelChannelHandler extends SimpleChannelInboundHandler<Proxy
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
if (ctx.channel().isActive()) {
ctx.channel().close();
}
// super.exceptionCaught(ctx, cause);
// if (ctx.channel().isActive()) {
// ctx.channel().close();
// }
log.error("[Tunnel Channel] error", cause);
}
@Override
@@ -135,9 +139,11 @@ public class ProxyTunnelChannelHandler extends SimpleChannelInboundHandler<Proxy
IdleStateEvent event = (IdleStateEvent)evt;
switch (event.state()) {
case READER_IDLE:
// 读超时断开连接
log.debug("读超时");
ctx.channel().close();
if (ctx.channel().isWritable()) {
// 读超时断开连接
log.warn("[Tunnel Channel]Read timeout");
ctx.channel().close();
}
break;
case WRITER_IDLE:
ctx.channel().writeAndFlush(ProxyMessage.buildHeartbeatMessage());
@@ -18,6 +18,7 @@ import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.core.event.AppLoadEndEvent;
import org.noear.solon.core.event.EventListener;
import org.noear.solon.core.runtime.NativeDetector;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
@@ -42,8 +43,11 @@ public class ProxyTunnelServer implements EventListener<AppLoadEndEvent> {
private NioEventLoopGroup serverWorkerGroup;
@Override
public void onEvent(AppLoadEndEvent appLoadEndEvent) throws Throwable {
startProxyServer();
startProxyServerForSSL();
// aot 阶段不启动代理服务
if (NativeDetector.isNotAotRuntime()) {
startProxyServer();
startProxyServerForSSL();
}
}
/**
* 启动代理服务
@@ -59,9 +63,9 @@ public class ProxyTunnelServer implements EventListener<AppLoadEndEvent> {
});
try {
bootstrap.bind(proxyConfig.getTunnel().getPort()).sync();
log.info("代理服务启动,端口{}", proxyConfig.getTunnel().getPort());
log.info("proxy server startedport{}", proxyConfig.getTunnel().getPort());
} catch (Exception e) {
log.error("代理服务异常", e);
log.error("proxy server error", e);
}
}
@@ -80,9 +84,9 @@ public class ProxyTunnelServer implements EventListener<AppLoadEndEvent> {
});
try {
bootstrap.bind(proxyConfig.getTunnel().getSslPort()).sync();
log.info("代理服务启动,SSL端口 {}", proxyConfig.getTunnel().getSslPort());
log.info("proxy server startedSSL port {}", proxyConfig.getTunnel().getSslPort());
} catch (Exception e) {
log.error("代理服务异常", e);
log.error("proxy server error", e);
}
}
@@ -105,7 +109,7 @@ public class ProxyTunnelServer implements EventListener<AppLoadEndEvent> {
return new SslHandler(sslEngine);
} catch (Exception e) {
log.error("创建SSL处理器失败", e);
log.error("create SSL handler error", e);
e.printStackTrace();
}
return null;
@@ -4,6 +4,7 @@ import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum;
import org.dromara.neutrinoproxy.server.proxy.domain.VisitorChannelAttachInfo;
import org.dromara.neutrinoproxy.server.service.FlowReportService;
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
@@ -42,6 +43,10 @@ public class TcpVisitorChannelHandler extends SimpleChannelInboundHandler<ByteBu
ctx.channel().close();
return;
}
// 代理通道可写则设置访问通道可读代理通道不可写则设置访问通道不可读
visitorChannel.config().setAutoRead(proxyChannel.isWritable());
// 转发代理数据
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
@@ -76,7 +81,7 @@ public class TcpVisitorChannelHandler extends SimpleChannelInboundHandler<ByteBu
visitorChannel.config().setOption(ChannelOption.AUTO_READ, false);
String visitorId = ProxyUtil.newVisitorId();
ProxyUtil.addVisitorChannelToCmdChannel(cmdChannel, visitorId, visitorChannel, sa.getPort());
ProxyUtil.addVisitorChannelToCmdChannel(NetworkProtocolEnum.TCP, cmdChannel, visitorId, visitorChannel, sa.getPort());
cmdChannel.writeAndFlush(ProxyMessage.buildConnectMessage(visitorId).setData(lanInfo.getBytes()));
super.channelActive(ctx);
@@ -128,7 +133,8 @@ public class TcpVisitorChannelHandler extends SimpleChannelInboundHandler<ByteBu
if (null == cmdChannel) {
// 该端口还没有代理客户端
ctx.channel().close();
} else {
}
else {
Channel proxyChannel = visitorChannel.attr(Constants.NEXT_CHANNEL).get();
if (null != proxyChannel) {
proxyChannel.config().setOption(ChannelOption.AUTO_READ, visitorChannel.isWritable());
@@ -0,0 +1,152 @@
package org.dromara.neutrinoproxy.server.proxy.core;
import cn.hutool.core.util.StrUtil;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum;
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyAttachment;
import org.dromara.neutrinoproxy.server.proxy.domain.VisitorChannelAttachInfo;
import org.dromara.neutrinoproxy.server.service.FlowReportService;
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
import org.noear.solon.Solon;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
/**
* @author: aoshiguchen
* @date: 2023/9/16
*/
@Slf4j
public class UdpVisitorChannelHandler extends SimpleChannelInboundHandler<DatagramPacket> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket datagramPacket) throws Exception {
log.debug("chid>>>{}", ctx.channel().id().asLongText());
byte[] bytes = new byte[datagramPacket.content().readableBytes()];
datagramPacket.content().readBytes(bytes);
datagramPacket.content().resetReaderIndex();
ProxyAttachment proxyAttachment = new ProxyAttachment(ctx.channel(), bytes, (channel, buf) -> {
Channel proxyChannel = channel.attr(Constants.NEXT_CHANNEL).get();
if (null == proxyChannel) {
// // 该端口还没有代理客户端
// ctx.channel().close();
return;
}
proxyChannel.attr(Constants.SENDER).set(datagramPacket.sender());
String targetIp = proxyChannel.attr(Constants.TARGET_IP).get();
int targetPort = proxyChannel.attr(Constants.TARGET_PORT).get();
Integer proxyResponses = proxyChannel.attr(Constants.PROXY_RESPONSES).get();
Long proxyTimeoutMs = proxyChannel.attr(Constants.PROXY_TIMEOUT_MS).get();
// 转发代理数据
String visitorId = ProxyUtil.getVisitorIdByChannel(channel);
proxyChannel.writeAndFlush(ProxyMessage.buildUdpTransferMessage(new ProxyMessage.UdpBaseInfo()
.setVisitorId(visitorId)
.setVisitorIp(datagramPacket.sender().getAddress().getHostAddress())
.setVisitorPort(datagramPacket.sender().getPort())
.setTargetIp(targetIp)
.setTargetPort(targetPort)
.setProxyTimeoutMs(proxyTimeoutMs)
.setProxyResponses(proxyResponses)
).setData(bytes));
// 增加流量计数
VisitorChannelAttachInfo visitorChannelAttachInfo = ProxyUtil.getAttachInfo(channel);
Solon.context().getBean(FlowReportService.class).addWriteByte(visitorChannelAttachInfo.getLicenseId(), bytes.length);
});
// String visitorId = ProxyUtil.getVisitorIdByChannel(ctx.channel());
// if (StringUtils.isNotBlank(visitorId)) {
// // UDP代理隧道已就绪直接转发
// proxyAttachment.execute();
// return;
// }
Channel proxyChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (null != proxyChannel && proxyChannel.isActive()) {
// UDP代理隧道已就绪直接转发
proxyAttachment.execute();
return;
}
Channel visitorChannel = ctx.channel();
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
// 没有指令通道直接结束
if (null == cmdChannel) {
// 该端口还没有代理客户端
// ctx.channel().close();
return;
}
// 根据代理服务端端口获取被代理客户端局域网连接信息
String lanInfo = ProxyUtil.getClientLanInfoByServerPort(sa.getPort());
if (StrUtil.isEmpty(lanInfo)) {
ctx.channel().close();
return;
}
String[] targetInfo = lanInfo.split(":");
String targetIp = targetInfo[0];
int targetPort = Integer.parseInt(targetInfo[1]);
// // 用户连接到代理服务器时设置用户连接不可读等待代理后端服务器连接成功后再改变为可读状态
// visitorChannel.config().setOption(ChannelOption.AUTO_READ, false);
// TODO UDP此处叫visitor似有不妥与TCP不同,2.x重构思考
String visitorId = ProxyUtil.newVisitorId();
// 此处需要和tcp分开
ProxyUtil.addVisitorChannelToCmdChannel(NetworkProtocolEnum.UDP, cmdChannel, visitorId, visitorChannel, sa.getPort());
ProxyUtil.addProxyConnectAttachment(visitorId, proxyAttachment);
cmdChannel.writeAndFlush(ProxyMessage.buildUdpConnectMessage(new ProxyMessage.UdpBaseInfo()
.setVisitorId(visitorId)
.setServerPort(sa.getPort())
.setTargetIp(targetIp)
.setTargetPort(targetPort)
));
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// 当出现异常就关闭连接
ctx.close();
log.error("[UDP Visitor Channel]VisitorChannel error", cause);
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
// 通知代理客户端
Channel visitorChannel = ctx.channel();
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
if (null == cmdChannel) {
// 该端口还没有代理客户端
ctx.channel().close();
}
else {
Channel proxyChannel = visitorChannel.attr(Constants.NEXT_CHANNEL).get();
if (null != proxyChannel) {
proxyChannel.config().setOption(ChannelOption.AUTO_READ, visitorChannel.isWritable());
}
}
super.channelWritabilityChanged(ctx);
}
}
@@ -23,6 +23,7 @@ package org.dromara.neutrinoproxy.server.proxy.domain;
import lombok.Data;
import lombok.experimental.Accessors;
import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum;
/**
*
@@ -32,6 +33,7 @@ import lombok.experimental.Accessors;
@Accessors(chain = true)
@Data
public class VisitorChannelAttachInfo {
private NetworkProtocolEnum protocol;
private String visitorId;
private String lanInfo;
private Integer serverPort;
@@ -28,7 +28,7 @@ public class HttpProxy implements EventListener<AppLoadEndEvent> {
private ProxyConfig proxyConfig;
@Override
public void onEvent(AppLoadEndEvent appLoadEndEvent) throws Throwable {
if (StrUtil.isBlank(proxyConfig.getServer().getDomainName()) || null == proxyConfig.getServer().getHttpProxyPort()) {
if (StrUtil.isBlank(proxyConfig.getServer().getTcp().getDomainName()) || null == proxyConfig.getServer().getTcp().getHttpProxyPort()) {
log.info("no config domain name,nonsupport http proxy.");
return;
}
@@ -42,15 +42,15 @@ public class HttpProxy implements EventListener<AppLoadEndEvent> {
.channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
if (null != proxyConfig.getServer().getTransferLogEnable() && proxyConfig.getServer().getTransferLogEnable()) {
if (null != proxyConfig.getServer().getTcp().getTransferLogEnable() && proxyConfig.getServer().getTcp().getTransferLogEnable()) {
ch.pipeline().addFirst(new LoggingHandler(HttpProxy.class));
}
ch.pipeline().addFirst(new BytesMetricsHandler());
ch.pipeline().addLast(new HttpVisitorChannelHandler(proxyConfig.getServer().getDomainName()));
ch.pipeline().addLast(new HttpVisitorChannelHandler(proxyConfig.getServer().getTcp().getDomainName()));
}
});
bootstrap.bind("0.0.0.0", proxyConfig.getServer().getHttpProxyPort()).sync();
log.info("Http代理服务启动成功port:{}", proxyConfig.getServer().getHttpProxyPort());
bootstrap.bind("0.0.0.0", proxyConfig.getServer().getTcp().getHttpProxyPort()).sync();
log.info("Http proxy server start successport:{}", proxyConfig.getServer().getTcp().getHttpProxyPort());
} catch (Exception e) {
log.error("http proxy start err!", e);
}
@@ -10,6 +10,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum;
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyAttachment;
import org.dromara.neutrinoproxy.server.proxy.domain.VisitorChannelAttachInfo;
import org.dromara.neutrinoproxy.server.service.FlowReportService;
@@ -98,7 +99,7 @@ public class HttpVisitorChannelHandler extends SimpleChannelInboundHandler<ByteB
}
visitorId = ProxyUtil.newVisitorId();
ProxyUtil.addVisitorChannelToCmdChannel(cmdChannel, visitorId, ctx.channel(), serverPort);
ProxyUtil.addVisitorChannelToCmdChannel(NetworkProtocolEnum.HTTP, cmdChannel, visitorId, ctx.channel(), serverPort);
ProxyUtil.addProxyConnectAttachment(visitorId, proxyAttachment);
cmdChannel.writeAndFlush(ProxyMessage.buildConnectMessage(visitorId).setData(lanInfo.getBytes()));
}
@@ -35,8 +35,8 @@ public class HttpsProxy implements EventListener<AppLoadEndEvent> {
private ProxyConfig proxyConfig;
@Override
public void onEvent(AppLoadEndEvent appLoadEndEvent) throws Throwable {
if (StrUtil.isBlank(proxyConfig.getServer().getDomainName()) || null == proxyConfig.getServer().getHttpsProxyPort() ||
StringUtils.isEmpty(proxyConfig.getServer().getJksPath()) || StringUtils.isEmpty(proxyConfig.getServer().getKeyStorePassword())) {
if (StrUtil.isBlank(proxyConfig.getServer().getTcp().getDomainName()) || null == proxyConfig.getServer().getTcp().getHttpsProxyPort() ||
StringUtils.isEmpty(proxyConfig.getServer().getTcp().getJksPath()) || StringUtils.isEmpty(proxyConfig.getServer().getTcp().getKeyStorePassword())) {
log.info("no config domain name,nonsupport https proxy.");
return;
}
@@ -50,16 +50,16 @@ public class HttpsProxy implements EventListener<AppLoadEndEvent> {
.channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
if (null != proxyConfig.getServer().getTransferLogEnable() && proxyConfig.getServer().getTransferLogEnable()) {
if (null != proxyConfig.getServer().getTcp().getTransferLogEnable() && proxyConfig.getServer().getTcp().getTransferLogEnable()) {
ch.pipeline().addFirst(new LoggingHandler(HttpsProxy.class));
}
ch.pipeline().addLast(createSslHandler());
ch.pipeline().addFirst(new BytesMetricsHandler());
ch.pipeline().addLast(new HttpVisitorChannelHandler(proxyConfig.getServer().getDomainName()));
ch.pipeline().addLast(new HttpVisitorChannelHandler(proxyConfig.getServer().getTcp().getDomainName()));
}
});
bootstrap.bind("0.0.0.0", proxyConfig.getServer().getHttpsProxyPort()).sync();
log.info("Https代理服务启动成功port:{}", proxyConfig.getServer().getHttpsProxyPort());
bootstrap.bind("0.0.0.0", proxyConfig.getServer().getTcp().getHttpsProxyPort()).sync();
log.info("Https proxy server startedport:{}", proxyConfig.getServer().getTcp().getHttpsProxyPort());
} catch (Exception e) {
log.error("https proxy start err!", e);
}
@@ -67,13 +67,13 @@ public class HttpsProxy implements EventListener<AppLoadEndEvent> {
private ChannelHandler createSslHandler() {
try {
InputStream jksInputStream = FileUtil.getInputStream(proxyConfig.getServer().getJksPath());
InputStream jksInputStream = FileUtil.getInputStream(proxyConfig.getServer().getTcp().getJksPath());
SSLContext serverContext = SSLContext.getInstance("TLS");
final KeyStore ks = KeyStore.getInstance("JKS");
ks.load(jksInputStream, proxyConfig.getServer().getKeyStorePassword().toCharArray());
ks.load(jksInputStream, proxyConfig.getServer().getTcp().getKeyStorePassword().toCharArray());
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, proxyConfig.getServer().getKeyStorePassword().toCharArray());
kmf.init(ks, proxyConfig.getServer().getTcp().getKeyStorePassword().toCharArray());
TrustManager[] trustManagers = null;
serverContext.init(kmf.getKeyManagers(), trustManagers, null);
@@ -84,7 +84,7 @@ public class HttpsProxy implements EventListener<AppLoadEndEvent> {
return new SslHandler(sslEngine);
} catch (Exception e) {
log.error("创建SSL处理器失败", e);
log.error("create SSL handler failed", e);
e.printStackTrace();
}
return null;
@@ -23,9 +23,10 @@
package org.dromara.neutrinoproxy.server.proxy.handler;
import cn.hutool.core.util.StrUtil;
import org.dromara.neutrinoproxy.core.*;
import io.netty.util.Attribute;
import org.dromara.neutrinoproxy.core.*;
import org.dromara.neutrinoproxy.core.dispatcher.Match;
import org.dromara.neutrinoproxy.core.util.EncryptUtil;
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
import org.dromara.neutrinoproxy.server.constant.ClientConnectTypeEnum;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
@@ -89,37 +90,37 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
}
if (StrUtil.isEmpty(licenseKey)) {
log.warn("[客户端连接] license不能为空 info:{} ", info);
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "license不能为空!", licenseKey));
log.warn("[client connection] license cannot empty info:{} ", info);
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "license不能为空!", licenseKey, null));
ctx.channel().close();
clientConnectRecordService.add(new ClientConnectRecordDO()
.setIp(ip)
.setType(ClientConnectTypeEnum.CONNECT.getType())
.setMsg(licenseKey)
.setCode(SuccessCodeEnum.FAIL.getCode())
.setErr("license不能为空!")
.setErr("license cannot empty!")
.setCreateTime(now)
);
return;
}
LicenseDO licenseDO = licenseService.findByKey(licenseKey);
if (null == licenseDO) {
log.warn("[客户端连接] license不存在 info:{} ", info);
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "license不存在!", licenseKey));
log.warn("[client connection] license not found info:{} ", info);
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "license不存在!", licenseKey, null));
ctx.channel().close();
clientConnectRecordService.add(new ClientConnectRecordDO()
.setIp(ip)
.setType(ClientConnectTypeEnum.CONNECT.getType())
.setMsg(licenseKey)
.setCode(SuccessCodeEnum.FAIL.getCode())
.setErr("license不存在!")
.setErr("license not found!")
.setCreateTime(now)
);
return;
}
if (EnableStatusEnum.DISABLE.getStatus().equals(licenseDO.getEnable())) {
log.warn("[客户端连接] 当前license已被禁用 info:{} ", info);
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "当前license已被禁用!", licenseKey));
log.warn("[client connection] the license disabled info:{} ", info);
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "the license disabled!", licenseKey, null));
ctx.channel().close();
clientConnectRecordService.add(new ClientConnectRecordDO()
.setIp(ip)
@@ -127,14 +128,14 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
.setType(ClientConnectTypeEnum.CONNECT.getType())
.setMsg(licenseKey)
.setCode(SuccessCodeEnum.FAIL.getCode())
.setErr("当前license已被禁用!")
.setErr("the license disabled!")
.setCreateTime(now));
return;
}
UserDO userDO = userService.findById(licenseDO.getUserId());
if (null == userDO || EnableStatusEnum.DISABLE.getStatus().equals(userDO.getEnable())) {
log.warn("[客户端连接] 当前license无效 info:{} ", info);
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "当前license无效!", licenseKey));
log.warn("[client connection] the license invalid info:{} ", info);
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "the license invalid!", licenseKey, null));
ctx.channel().close();
clientConnectRecordService.add(new ClientConnectRecordDO()
.setIp(ip)
@@ -142,7 +143,7 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
.setType(ClientConnectTypeEnum.CONNECT.getType())
.setMsg(licenseKey)
.setCode(SuccessCodeEnum.FAIL.getCode())
.setErr("当前license无效!")
.setErr("the license invalid!")
.setCreateTime(now));
return;
}
@@ -150,8 +151,8 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
if (null != cmdChannel) {
String _clientId = ProxyUtil.getClientIdByLicenseId(licenseDO.getId());
if (!clientId.equals(_clientId)) {
log.warn("[客户端连接] 当前license已被另一节点使用 info:{} _clientId:{}", info, _clientId);
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "当前license已被另一节点使用!", licenseKey));
log.warn("[client connection] the license on another no used info:{} _clientId:{}", info, _clientId);
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "the license on another no used!", licenseKey, null));
ctx.channel().close();
clientConnectRecordService.add(new ClientConnectRecordDO()
.setIp(ip)
@@ -159,13 +160,27 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
.setType(ClientConnectTypeEnum.CONNECT.getType())
.setMsg(licenseKey)
.setCode(SuccessCodeEnum.FAIL.getCode())
.setErr("当前license已被另一节点使用!")
.setErr("the license on another no used!")
.setCreateTime(now));
return;
}
}
// 存储状态为非安全,如果客户端响应以下的公钥信息则在响应中设置为安全
Attribute<Boolean> booleanAttribute = ctx.attr(Constants.IS_SECURITY);
booleanAttribute.set(false);
// 生成获取SM2密钥对,私钥存入ctx公钥拼装参数随Auth数据包返回
KeyPairRecord record = EncryptUtil.generateSm2KeyPair();
// 私钥存入ctx
ctx.attr(Constants.SECURE_PRIVATE_KEY).set(record.privateKey());
// 存储licenseId
ctx.attr(Constants.LICENSE_ID).set(licenseDO.getId());
// 发送认证成功消息
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.SUCCESS.getCode(), "认证成功!", licenseKey));
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.SUCCESS.getCode(), "auth success!", licenseKey, record.publicKey()));
clientConnectRecordService.add(new ClientConnectRecordDO()
.setIp(ip)
@@ -178,7 +193,7 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
// 设置当前licenseId对应的客户端ID
ProxyUtil.setLicenseIdToClientIdMap(licenseDO.getId(), clientId);
log.warn("[客户端连接] 认证成功 info:{} ", info);
log.warn("[client connection] auth success info:{} ", info);
// 更新license在线状态
licenseMapper.updateOnlineStatus(licenseDO.getId(), OnlineStatusEnum.ONLINE.getStatus(), now);
@@ -34,14 +34,14 @@ public class ProxyMessageConnectHandler implements ProxyMessageHandler {
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
String info = proxyMessage.getInfo();
if (StrUtil.isEmpty(info)) {
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "info不能为空!"));
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "info cannot be empty!"));
ctx.channel().close();
return;
}
String[] tokens = info.split("@");
if (tokens.length != 2) {
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "info格式有误!"));
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "info format error!"));
ctx.channel().close();
return;
}
@@ -50,18 +50,18 @@ public class ProxyMessageConnectHandler implements ProxyMessageHandler {
LicenseDO licenseDO = licenseService.findByKey(licenseKey);
if (null == licenseDO) {
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "license不存在!"));
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "the license not found!"));
ctx.channel().close();
return;
}
if (EnableStatusEnum.DISABLE.getStatus().equals(licenseDO.getEnable())) {
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "当前license已被禁用!"));
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "the license invalid!"));
ctx.channel().close();
return;
}
UserDO userDO = userService.findById(licenseDO.getUserId());
if (null == userDO || EnableStatusEnum.DISABLE.getStatus().equals(userDO.getEnable())) {
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "当前license无效!"));
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "the license invalid!"));
ctx.channel().close();
return;
}
@@ -69,7 +69,7 @@ public class ProxyMessageConnectHandler implements ProxyMessageHandler {
Channel cmdChannel = ProxyUtil.getCmdChannelByLicenseId(licenseDO.getId());
if (null == cmdChannel) {
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "服务端异常,指令通道不存在!"));
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "server errorcmd channel not found!"));
ctx.channel().close();
return;
}
@@ -88,8 +88,13 @@ public class ProxyMessageConnectHandler implements ProxyMessageHandler {
// 获取代理附加对象
ProxyAttachment proxyAttachment = ProxyUtil.getProxyConnectAttachment(visitorId);
if (null != proxyAttachment) {
// 及时释放
ProxyUtil.remoteProxyConnectAttachment(visitorId);
proxyAttachment.execute();
}
// 设置加密
ProxyUtil.setChannelSecurity(licenseDO.getId(), ctx.channel());
}
@Override
@@ -0,0 +1,75 @@
package org.dromara.neutrinoproxy.server.proxy.handler;
import cn.hutool.core.util.StrUtil;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.Attribute;
import lombok.extern.slf4j.Slf4j;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.dromara.neutrinoproxy.core.ProxyMessageHandler;
import org.dromara.neutrinoproxy.core.dispatcher.Match;
import org.dromara.neutrinoproxy.core.util.EncryptUtil;
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
import org.noear.solon.annotation.Component;
import java.util.Map;
@Slf4j
@Match(type= Constants.ProxyDataTypeName.SECURE_KEY)
@Component
public class ProxyMessageSecureKeyHandler implements ProxyMessageHandler {
@Override
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
log.info("收到客户端的加密信息");
// data为加密后的密码info为加密密码的摘要
byte[] data = proxyMessage.getData();
String receivedDigest = proxyMessage.getInfo();
String digest = EncryptUtil.digestBySm3(data);
if (!digest.equals(receivedDigest)) {
// 获取加密信息失败
log.warn("密码协商失败");
// TODO 应该断开连接
return;
}
// 获取私钥
Attribute<String> privateKeyAttr = ctx.attr(Constants.SECURE_PRIVATE_KEY);
String privateKey = privateKeyAttr.get();
if (StrUtil.isEmpty(privateKey)) {
// 获取私钥失败
log.warn("获取私钥失败");
// TODO 应该断开连接
return;
}
// 解密传输密码
byte[] secureKey = EncryptUtil.decryptBySm2(privateKey, data);
// 传输密码存储ctx中
Attribute<byte[]> secureKeyAttr = ctx.attr(Constants.SECURE_KEY);
secureKeyAttr.setIfAbsent(secureKey);
// 使用密码加密success给客户端表示密码已确认
byte[] encryptedSuccessInfoData = EncryptUtil.encryptByAes(secureKey, "ok".getBytes());
// 发送回去以示确认
ctx.writeAndFlush(ProxyMessage.buildSecureKeyReturnMessage(encryptedSuccessInfoData));
ctx.flush();
// 设置该链路以及相关链路状态为安全之后使用链路传输的数据均会加密
Integer licenseId = ctx.attr(Constants.LICENSE_ID).get();
ProxyUtil.setSecureKey(licenseId, secureKey);
ProxyUtil.setChannelSecurity(licenseId, ctx.channel());
}
@Override
public String name() {
return ProxyDataTypeEnum.SECURE_KEY.getDesc();
}
}
@@ -27,6 +27,17 @@ public class ProxyMessageTransferHandler implements ProxyMessageHandler {
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
Channel visitorChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (null != visitorChannel) {
if (!visitorChannel.isWritable()) {
//自己不可写通道可以读让通道关闭读
//自己可写通道不可以读让通道打开读
if (ctx.channel().config().isAutoRead()) {
ctx.channel().config().setAutoRead(false);
}
} else {
if (!ctx.channel().config().isAutoRead()) {
ctx.channel().config().setAutoRead(true);
}
}
ByteBuf buf = ctx.alloc().buffer(proxyMessage.getData().length);
buf.writeBytes(proxyMessage.getData());
visitorChannel.writeAndFlush(buf);
@@ -0,0 +1,107 @@
package org.dromara.neutrinoproxy.server.proxy.handler;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.core.ExceptionEnum;
import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.dromara.neutrinoproxy.core.ProxyMessageHandler;
import org.dromara.neutrinoproxy.core.dispatcher.Match;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import org.dromara.neutrinoproxy.server.dal.PortMappingMapper;
import org.dromara.neutrinoproxy.server.dal.entity.LicenseDO;
import org.dromara.neutrinoproxy.server.dal.entity.PortMappingDO;
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyAttachment;
import org.dromara.neutrinoproxy.server.service.LicenseService;
import org.dromara.neutrinoproxy.server.service.UserService;
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
import org.noear.snack.ONode;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
/**
* @author: aoshiguchen
* @date: 2023/9/19
*/
@Slf4j
@Match(type = Constants.ProxyDataTypeName.UDP_CONNECT)
@Component
public class UdpProxyMessageConnectHandler implements ProxyMessageHandler {
@Inject
private LicenseService licenseService;
@Inject
private UserService userService;
@Inject
private PortMappingMapper portMappingMapper;
@Override
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
final ProxyMessage.UdpBaseInfo udpBaseInfo = ONode.deserialize(proxyMessage.getInfo(), ProxyMessage.UdpBaseInfo.class);
final String licenseKey = new String(proxyMessage.getData());
log.info("[UDP connect]info:{} licenseKey:{}", proxyMessage.getInfo(), licenseKey);
LicenseDO licenseDO = licenseService.findByKey(licenseKey);
if (null == licenseDO) {
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "the license notfound!"));
ctx.channel().close();
return;
}
if (EnableStatusEnum.DISABLE.getStatus().equals(licenseDO.getEnable())) {
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "the license disabled!"));
ctx.channel().close();
return;
}
UserDO userDO = userService.findById(licenseDO.getUserId());
if (null == userDO || EnableStatusEnum.DISABLE.getStatus().equals(userDO.getEnable())) {
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "the license invalid!"));
ctx.channel().close();
return;
}
Channel cmdChannel = ProxyUtil.getCmdChannelByLicenseId(licenseDO.getId());
if (null == cmdChannel) {
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "server errorcmd channel notfound!"));
ctx.channel().close();
return;
}
Channel visitorChannel = ProxyUtil.getVisitorChannel(cmdChannel, udpBaseInfo.getVisitorId());
if (null == visitorChannel) {
return;
}
PortMappingDO portMappingDO = portMappingMapper.findByLicenseIdAndServerPort(licenseDO.getId(), udpBaseInfo.getServerPort());
if (null == portMappingDO || !EnableStatusEnum.ENABLE.getStatus().equals(portMappingDO.getEnable())) {
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "server error, port mapping notfound!"));
ctx.channel().close();
return;
}
ctx.channel().attr(Constants.VISITOR_ID).set(udpBaseInfo.getVisitorId());
ctx.channel().attr(Constants.LICENSE_ID).set(licenseDO.getId());
ctx.channel().attr(Constants.NEXT_CHANNEL).set(visitorChannel);
ctx.channel().attr(Constants.TARGET_IP).set(portMappingDO.getClientIp());
ctx.channel().attr(Constants.TARGET_PORT).set(portMappingDO.getClientPort());
ctx.channel().attr(Constants.PROXY_RESPONSES).set(portMappingDO.getProxyResponses());
ctx.channel().attr(Constants.PROXY_TIMEOUT_MS).set(portMappingDO.getProxyTimeoutMs());
visitorChannel.attr(Constants.NEXT_CHANNEL).set(ctx.channel());
visitorChannel.attr(Constants.IS_UDP_KEY).set(Boolean.TRUE);
// // 代理客户端与后端服务器连接成功修改用户连接为可读状态
// visitorChannel.config().setOption(ChannelOption.AUTO_READ, true);
// 获取代理附加对象
ProxyAttachment proxyAttachment = ProxyUtil.getProxyConnectAttachment(udpBaseInfo.getVisitorId());
if (null != proxyAttachment) {
// 及时释放
ProxyUtil.remoteProxyConnectAttachment(udpBaseInfo.getVisitorId());
proxyAttachment.execute();
}
}
@Override
public String name() {
return ProxyDataTypeEnum.UDP_CONNECT.getDesc();
}
}
@@ -0,0 +1,68 @@
package org.dromara.neutrinoproxy.server.proxy.handler;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.socket.DatagramPacket;
import lombok.extern.slf4j.Slf4j;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.dromara.neutrinoproxy.core.ProxyMessageHandler;
import org.dromara.neutrinoproxy.core.dispatcher.Match;
import org.dromara.neutrinoproxy.server.service.FlowReportService;
import org.noear.snack.ONode;
import org.noear.solon.Solon;
import org.noear.solon.annotation.Component;
import java.net.InetSocketAddress;
/**
* @author: aoshiguchen
* @date: 2023/9/21
*/
@Slf4j
@Match(type = Constants.ProxyDataTypeName.UDP_TRANSFER)
@Component
public class UdpProxyMessageTransferHandler implements ProxyMessageHandler {
@Override
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
final ProxyMessage.UdpBaseInfo udpBaseInfo = ONode.deserialize(proxyMessage.getInfo(), ProxyMessage.UdpBaseInfo.class);
log.debug("[UDP transfer]info:{} data:{}", proxyMessage.getInfo(), new String(proxyMessage.getData()));
Channel visitorChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (null != visitorChannel) {
if (!visitorChannel.isWritable()) {
//自己不可写通道可以读让通道关闭读
//自己可写通道不可以读让通道打开读
if (ctx.channel().config().isAutoRead()) {
ctx.channel().config().setAutoRead(false);
}
} else {
if (ctx.channel().config().isAutoRead()) {
ctx.channel().config().setAutoRead(true);
}
}
// InetSocketAddress address = new InetSocketAddress(udpBaseInfo.getVisitorIp(), udpBaseInfo.getVisitorPort());
// ByteBuf byteBuf = Unpooled.copiedBuffer(proxyMessage.getData());
// visitorChannel.writeAndFlush(new DatagramPacket(byteBuf, address));
InetSocketAddress address = ctx.channel().attr(Constants.SENDER).get();
if (null != address) {
visitorChannel.writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer(proxyMessage.getData()), address));
}
// 增加流量计数(TODO 如果UDP映射服务端端口修改这个似乎不准)
Integer licenseId = visitorChannel.attr(Constants.LICENSE_ID).get();
if (null != licenseId) {
Solon.context().getBean(FlowReportService.class).addReadByte(licenseId, proxyMessage.getData().length);
}
}
}
@Override
public String name() {
return ProxyDataTypeEnum.UDP_TRANSFER.getDesc();
}
}
@@ -3,8 +3,9 @@ package org.dromara.neutrinoproxy.server.service;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.baomidou.mybatisplus.solon.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.solon.annotation.Db;
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
import org.dromara.neutrinoproxy.server.base.rest.SystemContextHolder;
@@ -16,11 +17,7 @@ import org.dromara.neutrinoproxy.server.dal.UserMapper;
import org.dromara.neutrinoproxy.server.dal.entity.ClientConnectRecordDO;
import org.dromara.neutrinoproxy.server.dal.entity.LicenseDO;
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
import lombok.extern.slf4j.Slf4j;
import ma.glasnost.orika.MapperFacade;
import org.apache.ibatis.solon.annotation.Db;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import java.util.List;
import java.util.Map;
@@ -35,8 +32,6 @@ import java.util.stream.Collectors;
@Slf4j
@Component
public class ClientConnectRecordService {
@Inject
private MapperFacade mapperFacade;
@Db
private ClientConnectRecordMapper clientConnectRecordMapper;
@Db
@@ -49,19 +44,18 @@ public class ClientConnectRecordService {
}
public PageInfo<ClientConnectRecordListRes> page(PageQuery pageQuery, ClientConnectRecordListReq req) {
Page<ClientConnectRecordListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
List<ClientConnectRecordDO> list = clientConnectRecordMapper.selectList(new LambdaQueryWrapper<ClientConnectRecordDO>()
.eq(null != req.getLicenseId(), ClientConnectRecordDO::getLicenseId, req.getLicenseId())
.orderByDesc(ClientConnectRecordDO::getId)
Page<ClientConnectRecordDO> pageResult = clientConnectRecordMapper.selectPage(new Page<>(pageQuery.getCurrent(), pageQuery.getSize()), new LambdaQueryWrapper<ClientConnectRecordDO>()
.eq(null != req.getLicenseId(), ClientConnectRecordDO::getLicenseId, req.getLicenseId())
.orderByDesc(ClientConnectRecordDO::getId)
);
List<ClientConnectRecordListRes> respList = mapperFacade.mapAsList(list, ClientConnectRecordListRes.class);
if (CollectionUtils.isEmpty(list)) {
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
List<ClientConnectRecordListRes> respList = pageResult.getRecords().stream().map(ClientConnectRecordDO::toRes).collect(Collectors.toList());
if (CollectionUtils.isEmpty(pageResult.getRecords())) {
return PageInfo.of(respList, pageResult.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
}
Set<Integer> licenseIds = respList.stream().map(ClientConnectRecordListRes::getLicenseId).collect(Collectors.toSet());
List<LicenseDO> licenseList = licenseMapper.findByIds(licenseIds);
if (CollectionUtil.isEmpty(licenseList)) {
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
return PageInfo.of(respList, pageResult.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
}
Set<Integer> userIds = licenseList.stream().map(LicenseDO::getUserId).collect(Collectors.toSet());
List<UserDO> userList = userMapper.findByIds(userIds);
@@ -85,6 +79,6 @@ public class ClientConnectRecordService {
item.setMsg("******");
}
});
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
return PageInfo.of(respList, pageResult);
}
}
@@ -2,9 +2,10 @@ package org.dromara.neutrinoproxy.server.service;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.baomidou.mybatisplus.solon.plugins.pagination.Page;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.solon.annotation.Db;
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
@@ -19,11 +20,13 @@ import org.dromara.neutrinoproxy.server.controller.res.system.JobInfoUpdateEnabl
import org.dromara.neutrinoproxy.server.controller.res.system.JobInfoUpdateRes;
import org.dromara.neutrinoproxy.server.dal.JobInfoMapper;
import org.dromara.neutrinoproxy.server.dal.entity.JobInfoDO;
import org.dromara.neutrinoproxy.server.job.*;
import org.dromara.neutrinoproxy.server.job.DataCleanJob;
import org.dromara.neutrinoproxy.server.job.DemoJob;
import org.dromara.neutrinoproxy.server.job.FlowReportForDayJob;
import org.dromara.neutrinoproxy.server.job.FlowReportForHourJob;
import org.dromara.neutrinoproxy.server.job.FlowReportForMinuteJob;
import org.dromara.neutrinoproxy.server.job.FlowReportForMonthJob;
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
import lombok.extern.slf4j.Slf4j;
import ma.glasnost.orika.MapperFacade;
import org.apache.ibatis.solon.annotation.Db;
import org.dromara.solonplugins.job.IJobHandler;
import org.dromara.solonplugins.job.IJobSource;
import org.dromara.solonplugins.job.JobInfo;
@@ -32,22 +35,18 @@ import org.noear.solon.Solon;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Init;
import org.noear.solon.annotation.Inject;
import org.noear.solon.core.runtime.NativeDetector;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
/**
*
* @author: aoshiguchen
* @date: 2022/9/5
*/
@Slf4j
@Component
public class JobInfoService implements IJobSource {
@Inject
private MapperFacade mapperFacade;
@Db
private JobInfoMapper jobInfoMapper;
@Inject
@@ -66,21 +65,24 @@ public class JobInfoService implements IJobSource {
@Init
public void init() {
jobHandlerMap.put("DataCleanJob", dataCleanJob);
jobHandlerMap.put("DemoJob", demoJob);
jobHandlerMap.put("FlowReportForDayJob", flowReportForDayJob);
jobHandlerMap.put("FlowReportForHourJob", flowReportForHourJob);
jobHandlerMap.put("FlowReportForMinuteJob", flowReportForMinuteJob);
jobHandlerMap.put("FlowReportForMonthJob", flowReportForMonthJob);
// aot 阶段不初始化
if (NativeDetector.isAotRuntime()) {
return;
}
jobHandlerMap.put("DataCleanJob", dataCleanJob);
jobHandlerMap.put("DemoJob", demoJob);
jobHandlerMap.put("FlowReportForDayJob", flowReportForDayJob);
jobHandlerMap.put("FlowReportForHourJob", flowReportForHourJob);
jobHandlerMap.put("FlowReportForMinuteJob", flowReportForMinuteJob);
jobHandlerMap.put("FlowReportForMonthJob", flowReportForMonthJob);
}
public PageInfo<JobInfoListRes> page(PageQuery pageQuery, JobInfoListReq req) {
Page<JobInfoListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
List<JobInfoDO> list = jobInfoMapper.selectList(new LambdaQueryWrapper<JobInfoDO>()
.orderByAsc(JobInfoDO::getId)
Page<JobInfoDO> page = jobInfoMapper.selectPage(new Page<>(pageQuery.getCurrent(), pageQuery.getSize()), new LambdaQueryWrapper<JobInfoDO>()
.orderByAsc(JobInfoDO::getId)
);
List<JobInfoListRes> respList = mapperFacade.mapAsList(list, JobInfoListRes.class);
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
List<JobInfoListRes> respList = page.getRecords().stream().map(JobInfoDO::toRes).collect(Collectors.toList());
return PageInfo.of(respList, page);
}
public List<JobInfoDO> findList() {
@@ -107,6 +109,10 @@ public class JobInfoService implements IJobSource {
@Override
public List<JobInfo> sourceList() {
// aot 阶段不查数据库
if (NativeDetector.isAotRuntime()) {
return Collections.emptyList();
}
List<JobInfo> jobInfoList = Lists.newArrayList();
List<JobInfoDO> jobInfoDOList = jobInfoMapper.findList();
if (CollectionUtil.isEmpty(jobInfoDOList)) {
@@ -114,13 +120,13 @@ public class JobInfoService implements IJobSource {
}
for (JobInfoDO item : jobInfoDOList) {
jobInfoList.add(new JobInfo()
.setId(String.valueOf(item.getId()))
.setName(item.getHandler())
.setDesc(item.getDesc())
.setCron(item.getCron())
.setParam(item.getParam())
.setEnable(EnableStatusEnum.ENABLE.getStatus().equals(item.getEnable()))
.setJobHandler(jobHandlerMap.get(item.getHandler()))
.setId(String.valueOf(item.getId()))
.setName(item.getHandler())
.setDesc(item.getDesc())
.setCron(item.getCron())
.setParam(item.getParam())
.setEnable(EnableStatusEnum.ENABLE.getStatus().equals(item.getEnable()))
.setJobHandler(jobHandlerMap.get(item.getHandler()))
);
}
@@ -128,8 +134,8 @@ public class JobInfoService implements IJobSource {
}
public JobInfoUpdateRes update(JobInfoUpdateReq req) {
JobInfoDO jobInfoDO = jobInfoMapper.findById(req.getId());
ParamCheckUtil.checkNotNull( jobInfoDO, ExceptionConstant.PORT_MAPPING_NOT_EXIST);
JobInfoDO jobInfoDO = jobInfoMapper.findById(req.getId());
ParamCheckUtil.checkNotNull(jobInfoDO, ExceptionConstant.PORT_MAPPING_NOT_EXIST);
JobInfoDO jobInfo = new JobInfoDO();
jobInfo.setId(req.getId());
jobInfo.setCron(req.getCron());
@@ -22,25 +22,25 @@
package org.dromara.neutrinoproxy.server.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.baomidou.mybatisplus.solon.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.ibatis.solon.annotation.Db;
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
import org.dromara.neutrinoproxy.server.controller.req.log.JobLogListReq;
import org.dromara.neutrinoproxy.server.controller.res.log.JobLogListRes;
import org.dromara.neutrinoproxy.server.dal.JobLogMapper;
import org.dromara.neutrinoproxy.server.dal.entity.JobLogDO;
import lombok.extern.slf4j.Slf4j;
import ma.glasnost.orika.MapperFacade;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.ibatis.solon.annotation.Db;
import org.dromara.solonplugins.job.IJobCallback;
import org.dromara.solonplugins.job.JobInfo;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.core.runtime.NativeDetector;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
/**
*
@@ -50,21 +50,23 @@ import java.util.List;
@Slf4j
@Component
public class JobLogService implements IJobCallback {
@Inject
private MapperFacade mapperFacade;
@Db
private JobLogMapper jobLogMapper;
@Override
public void executeLog(JobInfo jobInfo, String param, Throwable throwable) {
// aot 阶段不查数据库
if (NativeDetector.isAotRuntime()) {
return;
}
Integer code = 0;
String msg = "";
if (null == throwable) {
msg = "执行成功";
log.debug("job[id={},name={}]执行完毕", jobInfo.getId(), jobInfo.getName());
msg = "execute success";
log.debug("job[id={},name={}]execute success", jobInfo.getId(), jobInfo.getName());
} else {
log.error("job[id={},name={}]执行异常", jobInfo.getId(), jobInfo.getName(), throwable);
msg = "执行异常:\r\n" + ExceptionUtils.getStackTrace(throwable);
log.error("job[id={},name={}]execute error", jobInfo.getId(), jobInfo.getName(), throwable);
msg = "execute error:\r\n" + ExceptionUtils.getStackTrace(throwable);
code = -1;
}
jobLogMapper.insert(new JobLogDO()
@@ -79,13 +81,12 @@ public class JobLogService implements IJobCallback {
}
public PageInfo<JobLogListRes> page(PageQuery pageQuery, JobLogListReq req) {
Page<JobLogListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
List<JobLogDO> list = jobLogMapper.selectList(new LambdaQueryWrapper<JobLogDO>()
.eq(null != req.getJobId(), JobLogDO::getJobId, req.getJobId())
.orderByDesc(JobLogDO::getId)
);
List<JobLogListRes> respList = mapperFacade.mapAsList(list, JobLogListRes.class);
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
Page<JobLogDO> page = jobLogMapper.selectPage(new Page<>(pageQuery.getCurrent(), pageQuery.getSize()), new LambdaQueryWrapper<JobLogDO>()
.eq(null != req.getJobId(), JobLogDO::getJobId, req.getJobId())
.orderByDesc(JobLogDO::getId)
);
List<JobLogListRes> respList = page.getRecords().stream().map(JobLogDO::toRes).collect(Collectors.toList());
return PageInfo.of(respList, page);
}
}
@@ -3,12 +3,13 @@ package org.dromara.neutrinoproxy.server.service;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.baomidou.mybatisplus.solon.plugins.pagination.Page;
import com.google.common.collect.Sets;
import org.apache.ibatis.solon.annotation.Db;
import org.dromara.neutrinoproxy.server.base.db.DBInitialize;
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
import org.dromara.neutrinoproxy.server.base.rest.ServiceException;
import org.dromara.neutrinoproxy.server.base.rest.SystemContextHolder;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import org.dromara.neutrinoproxy.server.constant.ExceptionConstant;
@@ -17,240 +18,261 @@ import org.dromara.neutrinoproxy.server.controller.req.proxy.LicenseCreateReq;
import org.dromara.neutrinoproxy.server.controller.req.proxy.LicenseListReq;
import org.dromara.neutrinoproxy.server.controller.req.proxy.LicenseUpdateEnableStatusReq;
import org.dromara.neutrinoproxy.server.controller.req.proxy.LicenseUpdateReq;
import org.dromara.neutrinoproxy.server.controller.res.proxy.*;
import org.dromara.neutrinoproxy.server.controller.res.proxy.LicenseCreateRes;
import org.dromara.neutrinoproxy.server.controller.res.proxy.LicenseDetailRes;
import org.dromara.neutrinoproxy.server.controller.res.proxy.LicenseListRes;
import org.dromara.neutrinoproxy.server.controller.res.proxy.LicenseUpdateEnableStatusRes;
import org.dromara.neutrinoproxy.server.controller.res.proxy.LicenseUpdateRes;
import org.dromara.neutrinoproxy.server.dal.LicenseMapper;
import org.dromara.neutrinoproxy.server.dal.PortMappingMapper;
import org.dromara.neutrinoproxy.server.dal.UserMapper;
import org.dromara.neutrinoproxy.server.dal.entity.LicenseDO;
import org.dromara.neutrinoproxy.server.dal.entity.PortMappingDO;
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
import ma.glasnost.orika.MapperFacade;
import org.apache.ibatis.solon.annotation.Db;
import org.dromara.neutrinoproxy.server.controller.res.proxy.*;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Init;
import org.noear.solon.annotation.Inject;
import org.noear.solon.core.Lifecycle;
import org.noear.solon.core.bean.LifecycleBean;
import org.noear.solon.core.runtime.NativeDetector;
import java.util.*;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* license服务
*
* @author: aoshiguchen
* @date: 2022/8/6
*/
@Component
public class LicenseService implements LifecycleBean {
@Inject
private MapperFacade mapperFacade;
@Db
private LicenseMapper licenseMapper;
@Db
private UserMapper userMapper;
@Inject
private VisitorChannelService visitorChannelService;
@Inject
private DBInitialize dbInitialize;
@Db
private LicenseMapper licenseMapper;
@Db
private PortMappingMapper portMappingMapper;
@Db
private UserMapper userMapper;
@Inject
private VisitorChannelService visitorChannelService;
@Inject
private DBInitialize dbInitialize;
public PageInfo<LicenseListRes> page(PageQuery pageQuery, LicenseListReq req) {
Page<LicenseListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
List<LicenseDO> list = licenseMapper.selectList(new LambdaQueryWrapper<LicenseDO>()
.eq(req.getUserId() != null, LicenseDO::getUserId, req.getUserId())
.eq(req.getIsOnline() != null, LicenseDO::getIsOnline, req.getIsOnline())
.eq(req.getEnable() != null, LicenseDO::getEnable, req.getEnable())
.orderByAsc(Arrays.asList(LicenseDO::getUserId, LicenseDO::getId))
);
List<LicenseListRes> respList = mapperFacade.mapAsList(list, LicenseListRes.class);
if (CollectionUtils.isEmpty(list)) {
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
}
if (!CollectionUtil.isEmpty(respList)) {
Set<Integer> userIds = respList.stream().map(LicenseListRes::getUserId).collect(Collectors.toSet());
List<UserDO> userList = userMapper.findByIds(userIds);
Map<Integer, UserDO> userMap = userList.stream().collect(Collectors.toMap(UserDO::getId, Function.identity()));
for (LicenseListRes item : respList) {
UserDO userDO = userMap.get(item.getUserId());
if (null != userDO) {
item.setUserName(userDO.getName());
}
item.setKey(desensitization(item.getUserId(), item.getKey()));
}
}
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
}
public PageInfo<LicenseListRes> page(PageQuery pageQuery, LicenseListReq req) {
Page<LicenseDO> page = licenseMapper.selectPage(new Page<>(pageQuery.getCurrent(), pageQuery.getSize()), new LambdaQueryWrapper<LicenseDO>()
.eq(req.getUserId() != null, LicenseDO::getUserId, req.getUserId())
.eq(req.getIsOnline() != null, LicenseDO::getIsOnline, req.getIsOnline())
.eq(req.getEnable() != null, LicenseDO::getEnable, req.getEnable())
.orderByAsc(Arrays.asList(LicenseDO::getUserId, LicenseDO::getId))
);
List<LicenseListRes> respList = page.getRecords().stream().map(LicenseDO::toRes).collect(Collectors.toList());
if (CollectionUtils.isEmpty(page.getRecords())) {
return PageInfo.of(respList, page);
}
if (!CollectionUtil.isEmpty(respList)) {
Set<Integer> userIds = respList.stream().map(LicenseListRes::getUserId).collect(Collectors.toSet());
List<UserDO> userList = userMapper.findByIds(userIds);
Map<Integer, UserDO> userMap = userList.stream().collect(Collectors.toMap(UserDO::getId, Function.identity()));
for (LicenseListRes item : respList) {
UserDO userDO = userMap.get(item.getUserId());
if (null != userDO) {
item.setUserName(userDO.getName());
}
item.setKey(desensitization(item.getUserId(), item.getKey()));
}
}
return PageInfo.of(respList, page);
}
public List<LicenseListRes> list(LicenseListReq req) {
List<LicenseDO> list = licenseMapper.selectList(new LambdaQueryWrapper<LicenseDO>()
.eq(null != req.getEnable(), LicenseDO::getEnable, req.getEnable())
);
List<LicenseListRes> licenseList = assembleConvertLicenses(list);
return licenseList;
}
public List<LicenseListRes> list(LicenseListReq req) {
List<LicenseDO> list = licenseMapper.selectList(new LambdaQueryWrapper<LicenseDO>()
.eq(null != req.getEnable(), LicenseDO::getEnable, req.getEnable())
);
List<LicenseListRes> licenseList = assembleConvertLicenses(list);
return licenseList;
}
private List<LicenseListRes> assembleConvertLicenses(List<LicenseDO> list) {
List<LicenseListRes> licenseList = mapperFacade.mapAsList(list, LicenseListRes.class);
if (!CollectionUtil.isEmpty(licenseList)) {
Set<Integer> userIds = licenseList.stream().map(LicenseListRes::getUserId).collect(Collectors.toSet());
List<UserDO> userList = userMapper.findByIds(userIds);
Map<Integer, UserDO> userMap = userList.stream().collect(Collectors.toMap(UserDO::getId, Function.identity()));
for (LicenseListRes item : licenseList) {
UserDO userDO = userMap.get(item.getUserId());
if (null != userDO) {
item.setUserName(userDO.getName());
}
item.setKey(desensitization(item.getUserId(), item.getKey()));
}
}
return licenseList;
}
private List<LicenseListRes> assembleConvertLicenses(List<LicenseDO> list) {
List<LicenseListRes> licenseList = list.stream().map(LicenseDO::toRes).collect(Collectors.toList());
if (!CollectionUtil.isEmpty(licenseList)) {
Set<Integer> userIds = licenseList.stream().map(LicenseListRes::getUserId).collect(Collectors.toSet());
List<UserDO> userList = userMapper.findByIds(userIds);
Map<Integer, UserDO> userMap = userList.stream().collect(Collectors.toMap(UserDO::getId, Function.identity()));
for (LicenseListRes item : licenseList) {
UserDO userDO = userMap.get(item.getUserId());
if (null != userDO) {
item.setUserName(userDO.getName());
}
item.setKey(desensitization(item.getUserId(), item.getKey()));
}
}
return licenseList;
}
/**
* 创建license
* @param req
* @return
*/
public LicenseCreateRes create(LicenseCreateReq req) {
LicenseDO licenseDO = licenseMapper.checkRepeat(req.getUserId(), req.getName());
ParamCheckUtil.checkExpression(null == licenseDO, ExceptionConstant.LICENSE_NAME_CANNOT_REPEAT);
/**
* 创建license
*
* @param req
* @return
*/
public LicenseCreateRes create(LicenseCreateReq req) {
LicenseDO licenseDO = licenseMapper.checkRepeat(req.getUserId(), req.getName());
ParamCheckUtil.checkExpression(null == licenseDO, ExceptionConstant.LICENSE_NAME_CANNOT_REPEAT);
String key = UUID.randomUUID().toString().replaceAll("-", "");
Date now = new Date();
String key = UUID.randomUUID().toString().replaceAll("-", "");
Date now = new Date();
licenseMapper.insert(new LicenseDO()
.setName(req.getName())
.setKey(key)
.setUserId(req.getUserId())
.setIsOnline(OnlineStatusEnum.OFFLINE.getStatus())
.setEnable(EnableStatusEnum.ENABLE.getStatus())
.setCreateTime(now)
.setUpdateTime(now)
);
return new LicenseCreateRes();
}
licenseMapper.insert(new LicenseDO()
.setName(req.getName())
.setKey(key)
.setUserId(req.getUserId())
.setIsOnline(OnlineStatusEnum.OFFLINE.getStatus())
.setEnable(EnableStatusEnum.ENABLE.getStatus())
.setCreateTime(now)
.setUpdateTime(now)
);
return new LicenseCreateRes();
}
public LicenseUpdateRes update(LicenseUpdateReq req) {
LicenseDO oldLicenseDO = licenseMapper.findById(req.getId());
ParamCheckUtil.checkNotNull(oldLicenseDO, ExceptionConstant.LICENSE_NOT_EXIST);
public LicenseUpdateRes update(LicenseUpdateReq req) {
LicenseDO oldLicenseDO = licenseMapper.findById(req.getId());
ParamCheckUtil.checkNotNull(oldLicenseDO, ExceptionConstant.LICENSE_NOT_EXIST);
LicenseDO licenseCheck = licenseMapper.checkRepeat(oldLicenseDO.getUserId(), req.getName(), Sets.newHashSet(oldLicenseDO.getId()));
ParamCheckUtil.checkMustNull(licenseCheck, ExceptionConstant.LICENSE_NAME_CANNOT_REPEAT);
LicenseDO licenseCheck = licenseMapper.checkRepeat(oldLicenseDO.getUserId(), req.getName(), Sets.newHashSet(oldLicenseDO.getId()));
ParamCheckUtil.checkMustNull(licenseCheck, ExceptionConstant.LICENSE_NAME_CANNOT_REPEAT);
licenseMapper.update(req.getId(), req.getName(), new Date());
return new LicenseUpdateRes();
}
licenseMapper.update(req.getId(), req.getName(), new Date());
return new LicenseUpdateRes();
}
public LicenseDetailRes detail(Integer id) {
LicenseDO licenseDO = licenseMapper.findById(id);
if (null == licenseDO) {
return null;
}
UserDO userDO = userMapper.findById(licenseDO.getUserId());
String userName = "";
if (null != userDO) {
userName = userDO.getName();
}
return new LicenseDetailRes()
.setId(licenseDO.getId())
.setName(licenseDO.getName())
.setKey(desensitization(licenseDO.getUserId(), licenseDO.getKey()))
.setUserId(licenseDO.getUserId())
.setUserName(userName)
.setIsOnline(licenseDO.getIsOnline())
.setEnable(licenseDO.getEnable())
.setCreateTime(licenseDO.getCreateTime())
.setUpdateTime(licenseDO.getUpdateTime())
;
}
public LicenseDetailRes detail(Integer id) {
LicenseDO licenseDO = licenseMapper.findById(id);
if (null == licenseDO) {
return null;
}
UserDO userDO = userMapper.findById(licenseDO.getUserId());
String userName = "";
if (null != userDO) {
userName = userDO.getName();
}
return new LicenseDetailRes()
.setId(licenseDO.getId())
.setName(licenseDO.getName())
.setKey(desensitization(licenseDO.getUserId(), licenseDO.getKey()))
.setUserId(licenseDO.getUserId())
.setUserName(userName)
.setIsOnline(licenseDO.getIsOnline())
.setEnable(licenseDO.getEnable())
.setCreateTime(licenseDO.getCreateTime())
.setUpdateTime(licenseDO.getUpdateTime())
;
}
/**
* 更新license启用状态
* @param req
* @return
*/
public LicenseUpdateEnableStatusRes updateEnableStatus(LicenseUpdateEnableStatusReq req) {
licenseMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
// 更新VisitorChannel
visitorChannelService.updateVisitorChannelByLicenseId(req.getId(), req.getEnable());
return new LicenseUpdateEnableStatusRes();
}
/**
* 更新license启用状态
*
* @param req
* @return
*/
public LicenseUpdateEnableStatusRes updateEnableStatus(LicenseUpdateEnableStatusReq req) {
licenseMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
// 更新VisitorChannel
visitorChannelService.updateVisitorChannelByLicenseId(req.getId(), req.getEnable());
return new LicenseUpdateEnableStatusRes();
}
/**
* 删除license
* @param id
*/
public void delete(Integer id) {
licenseMapper.deleteById(id);
// 更新VisitorChannel
visitorChannelService.updateVisitorChannelByLicenseId(id, EnableStatusEnum.DISABLE.getStatus());
}
/**
* 删除license
*
* @param id
*/
public void delete(Integer id) {
List<PortMappingDO> portMappingDOList = portMappingMapper.findListByLicenseId(id);
if (CollectionUtil.isNotEmpty(portMappingDOList)) {
throw ServiceException.create(ExceptionConstant.LICENSE_CANNOT_BE_DELETED, portMappingDOList.size());
}
licenseMapper.deleteById(id);
// 更新VisitorChannel
visitorChannelService.updateVisitorChannelByLicenseId(id, EnableStatusEnum.DISABLE.getStatus());
}
/**
* 重置license
* @param id
*/
public void reset(Integer id) {
String key = UUID.randomUUID().toString().replaceAll("-", "");
Date now = new Date();
/**
* 重置license
*
* @param id
*/
public void reset(Integer id) {
String key = UUID.randomUUID().toString().replaceAll("-", "");
Date now = new Date();
licenseMapper.reset(id, key, now);
}
licenseMapper.reset(id, key, now);
}
public LicenseDO findByKey(String license) {
return licenseMapper.findByKey(license);
}
public LicenseDO findByKey(String license) {
return licenseMapper.findByKey(license);
}
/**
* 脱敏处理
* 非当前登录人的license一律脱敏
* @param userId
* @param licenseKey
* @return
*/
private String desensitization(Integer userId, String licenseKey) {
Integer currentUserId = SystemContextHolder.getUser().getId();
if (currentUserId.equals(userId)) {
return licenseKey;
}
return licenseKey.substring(0, 10) + "****" + licenseKey.substring(licenseKey.length() - 10);
}
/**
* 脱敏处理
* 非当前登录人的license一律脱敏
*
* @param userId
* @param licenseKey
* @return
*/
private String desensitization(Integer userId, String licenseKey) {
Integer currentUserId = SystemContextHolder.getUser().getId();
if (currentUserId.equals(userId)) {
return licenseKey;
}
return licenseKey.substring(0, 10) + "****" + licenseKey.substring(licenseKey.length() - 10);
}
/**
* 服务端项目停止启动时更新在线状态为离线
*/
@Init
public void init() {
licenseMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
}
/**
* 服务端项目停止启动时更新在线状态为离线
*/
@Init
public void init() {
// aot 阶段不初始化
if (NativeDetector.isAotRuntime()) {
return;
}
licenseMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
}
@Override
public void start() throws Throwable {
@Override
public void start() throws Throwable {
}
}
/**
* 服务端项目停止启动时更新在线状态为离线
*/
@Override
public void stop() throws Throwable {
licenseMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
}
/**
* 服务端项目停止启动时更新在线状态为离线
*/
@Override
public void stop() throws Throwable {
licenseMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
}
/**
* 查询当前角色下的license若为管理员 则返回全部license
*/
public List<LicenseListRes> queryCurUserLicense(LicenseListReq req) {
if(SystemContextHolder.isAdmin()){
return this.list(req);
}
/**
* 查询当前角色下的license若为管理员 则返回全部license
*/
public List<LicenseListRes> queryCurUserLicense(LicenseListReq req) {
if (SystemContextHolder.isAdmin()) {
return this.list(req);
}
List<LicenseDO> list = licenseMapper.selectList(new LambdaQueryWrapper<LicenseDO>()
.eq(LicenseDO::getEnable, EnableStatusEnum.ENABLE.getStatus())
.eq(LicenseDO::getUserId,SystemContextHolder.getUserId())
);
List<LicenseListRes> licenseList = assembleConvertLicenses(list);
return licenseList;
}
List<LicenseDO> list = licenseMapper.selectList(new LambdaQueryWrapper<LicenseDO>()
.eq(LicenseDO::getEnable, EnableStatusEnum.ENABLE.getStatus())
.eq(LicenseDO::getUserId, SystemContextHolder.getUserId())
);
List<LicenseListRes> licenseList = assembleConvertLicenses(list);
return licenseList;
}
}
@@ -1,8 +1,8 @@
package org.dromara.neutrinoproxy.server.service;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.baomidou.mybatisplus.solon.plugins.pagination.Page;
import org.apache.ibatis.solon.annotation.Db;
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
import org.dromara.neutrinoproxy.server.base.rest.ServiceException;
@@ -21,10 +21,7 @@ import org.dromara.neutrinoproxy.server.dal.PortPoolMapper;
import org.dromara.neutrinoproxy.server.dal.entity.PortGroupDO;
import org.dromara.neutrinoproxy.server.dal.entity.PortPoolDO;
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
import ma.glasnost.orika.MapperFacade;
import org.apache.ibatis.solon.annotation.Db;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import java.util.Date;
import java.util.List;
@@ -36,9 +33,6 @@ import java.util.Objects;
@Component
public class PortGroupService {
@Inject
private MapperFacade mapperFacade;
@Db
private PortGroupMapper portGroupMapper;
@@ -64,14 +58,14 @@ public class PortGroupService {
}
public PageInfo<PortGroupListRes> page(PageQuery pageQuery, PortGroupListReq req) {
Page<PortGroupListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
List<PortGroupListRes> list = portGroupMapper.selectPortGroupListResList(req);
Page<PortGroupListRes> page = new Page<>(pageQuery.getCurrent(), pageQuery.getSize());
List<PortGroupListRes> list = portGroupMapper.selectPortGroupListResList(page, req);
return PageInfo.of(list, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
return PageInfo.of(list, page.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
}
public List<PortGroupListRes> list(PortGroupListReq req) {
List<PortGroupListRes> list = portGroupMapper.selectPortGroupListResList(req);
List<PortGroupListRes> list = portGroupMapper.selectPortGroupListResList(null, req);
return list;
}
@@ -5,10 +5,8 @@ import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.baomidou.mybatisplus.solon.plugins.pagination.Page;
import com.google.common.collect.Sets;
import ma.glasnost.orika.MapperFacade;
import org.apache.ibatis.solon.annotation.Db;
import org.dromara.neutrinoproxy.server.base.db.DBInitialize;
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
@@ -23,7 +21,11 @@ import org.dromara.neutrinoproxy.server.controller.req.proxy.PortMappingCreateRe
import org.dromara.neutrinoproxy.server.controller.req.proxy.PortMappingListReq;
import org.dromara.neutrinoproxy.server.controller.req.proxy.PortMappingUpdateEnableStatusReq;
import org.dromara.neutrinoproxy.server.controller.req.proxy.PortMappingUpdateReq;
import org.dromara.neutrinoproxy.server.controller.res.proxy.*;
import org.dromara.neutrinoproxy.server.controller.res.proxy.PortMappingCreateRes;
import org.dromara.neutrinoproxy.server.controller.res.proxy.PortMappingDetailRes;
import org.dromara.neutrinoproxy.server.controller.res.proxy.PortMappingListRes;
import org.dromara.neutrinoproxy.server.controller.res.proxy.PortMappingUpdateEnableStatusRes;
import org.dromara.neutrinoproxy.server.controller.res.proxy.PortMappingUpdateRes;
import org.dromara.neutrinoproxy.server.dal.LicenseMapper;
import org.dromara.neutrinoproxy.server.dal.PortMappingMapper;
import org.dromara.neutrinoproxy.server.dal.PortPoolMapper;
@@ -38,8 +40,13 @@ import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Init;
import org.noear.solon.annotation.Inject;
import org.noear.solon.core.bean.LifecycleBean;
import org.noear.solon.core.runtime.NativeDetector;
import java.util.*;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -49,8 +56,6 @@ import java.util.stream.Collectors;
*/
@Component
public class PortMappingService implements LifecycleBean {
@Inject
private MapperFacade mapperFacade;
@Db
private PortMappingMapper portMappingMapper;
@Db
@@ -70,7 +75,6 @@ public class PortMappingService implements LifecycleBean {
private DBInitialize dbInitialize;
public PageInfo<PortMappingListRes> page(PageQuery pageQuery, PortMappingListReq req) {
Page<PortMappingListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
if (StringUtils.isNotEmpty(req.getDescription())) {
//描述字段为模糊查询在应用层处理否则sqlite不支持
req.setDescription("%" + req.getDescription() + "%");
@@ -82,16 +86,17 @@ public class PortMappingService implements LifecycleBean {
req.setProtocal(networkProtocolEnum.getDesc());
}
List<PortMappingDO> list = portMappingMapper.selectPortMappingByCondition(req);
List<PortMappingListRes> respList = mapperFacade.mapAsList(list, PortMappingListRes.class);
Page<PortMappingDO> page = new Page<>(pageQuery.getCurrent(), pageQuery.getSize());
List<PortMappingDO> list = portMappingMapper.selectPortMappingByCondition(page, req);
List<PortMappingListRes> respList = list.stream().map(PortMappingDO::toRes).collect(Collectors.toList());
if (CollectionUtils.isEmpty(list)) {
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
return PageInfo.of(respList, page.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
}
Set<Integer> licenseIds = respList.stream().map(PortMappingListRes::getLicenseId).collect(Collectors.toSet());
List<LicenseDO> licenseList = licenseMapper.findByIds(licenseIds);
if (CollectionUtil.isEmpty(licenseList)) {
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
return PageInfo.of(respList, page.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
}
Set<Integer> userIds = licenseList.stream().map(LicenseDO::getUserId).collect(Collectors.toSet());
List<UserDO> userList = userMapper.findByIds(userIds);
@@ -110,8 +115,8 @@ public class PortMappingService implements LifecycleBean {
return;
}
item.setUserName(user.getName());
if (StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) && StrUtil.isNotBlank(item.getSubdomain())) {
item.setDomain(item.getSubdomain() + "." + proxyConfig.getServer().getDomainName());
if (StrUtil.isNotBlank(proxyConfig.getServer().getTcp().getDomainName()) && StrUtil.isNotBlank(item.getSubdomain())) {
item.setDomain(item.getSubdomain() + "." + proxyConfig.getServer().getTcp().getDomainName());
}
if (NetworkProtocolEnum.HTTP.getDesc().equals(item.getProtocal())) {
item.setProtocal("HTTP(S)");
@@ -119,7 +124,7 @@ public class PortMappingService implements LifecycleBean {
});
//sorted [userId asc] [licenseId asc] [createTime asc]
respList = respList.stream().sorted(Comparator.comparing(PortMappingListRes::getUserId).thenComparing(PortMappingListRes::getLicenseId).thenComparing(PortMappingListRes::getCreateTime)).collect(Collectors.toList());
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
return PageInfo.of(respList, page.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
}
public PortMappingCreateRes create(PortMappingCreateReq req) {
@@ -142,6 +147,8 @@ public class PortMappingService implements LifecycleBean {
portMappingDO.setServerPort(req.getServerPort());
portMappingDO.setClientIp(req.getClientIp());
portMappingDO.setClientPort(req.getClientPort());
portMappingDO.setProxyResponses(req.getProxyResponses());
portMappingDO.setProxyTimeoutMs(req.getProxyTimeoutMs());
portMappingDO.setDescription(req.getDescription());
portMappingDO.setIsOnline(OnlineStatusEnum.OFFLINE.getStatus());
portMappingDO.setEnable(EnableStatusEnum.ENABLE.getStatus());
@@ -151,7 +158,7 @@ public class PortMappingService implements LifecycleBean {
// 更新VisitorChannel
visitorChannelService.addVisitorChannelByPortMapping(portMappingDO);
// 更新域名映射
if (NetworkProtocolEnum.isHttp(portMappingDO.getProtocal()) && StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) && StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
if (NetworkProtocolEnum.isHttp(portMappingDO.getProtocal()) && StrUtil.isNotBlank(proxyConfig.getServer().getTcp().getDomainName()) && StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
ProxyUtil.setSubdomainToServerPort(portMappingDO.getSubdomain(), portMappingDO.getServerPort());
}
return new PortMappingCreateRes();
@@ -181,6 +188,8 @@ public class PortMappingService implements LifecycleBean {
portMappingDO.setServerPort(req.getServerPort());
portMappingDO.setClientIp(req.getClientIp());
portMappingDO.setClientPort(req.getClientPort());
portMappingDO.setProxyResponses(req.getProxyResponses());
portMappingDO.setProxyTimeoutMs(req.getProxyTimeoutMs());
portMappingDO.setDescription(req.getDescription());
portMappingDO.setUpdateTime(new Date());
portMappingDO.setEnable(EnableStatusEnum.ENABLE.getStatus());
@@ -192,7 +201,7 @@ public class PortMappingService implements LifecycleBean {
ProxyUtil.removeSubdomainToServerPort(oldPortMappingDO.getSubdomain());
}
// 更新域名映射
if (NetworkProtocolEnum.isHttp(portMappingDO.getProtocal()) && StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) && StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
if (NetworkProtocolEnum.isHttp(portMappingDO.getProtocal()) && StrUtil.isNotBlank(proxyConfig.getServer().getTcp().getDomainName()) && StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
ProxyUtil.setSubdomainToServerPort(portMappingDO.getSubdomain(), portMappingDO.getServerPort());
}
return new PortMappingUpdateRes();
@@ -203,7 +212,18 @@ public class PortMappingService implements LifecycleBean {
if (null == portMappingDO) {
return null;
}
PortMappingDetailRes res = new PortMappingDetailRes().setId(portMappingDO.getId()).setLicenseId(portMappingDO.getLicenseId()).setServerPort(portMappingDO.getServerPort()).setClientIp(portMappingDO.getClientIp()).setClientPort(portMappingDO.getClientPort()).setIsOnline(portMappingDO.getIsOnline()).setEnable(portMappingDO.getEnable()).setCreateTime(portMappingDO.getCreateTime()).setUpdateTime(portMappingDO.getUpdateTime());
PortMappingDetailRes res = new PortMappingDetailRes()
.setId(portMappingDO.getId())
.setLicenseId(portMappingDO.getLicenseId())
.setServerPort(portMappingDO.getServerPort())
.setClientIp(portMappingDO.getClientIp())
.setClientPort(portMappingDO.getClientPort())
.setIsOnline(portMappingDO.getIsOnline())
.setProxyTimeoutMs(portMappingDO.getProxyTimeoutMs())
.setProxyResponses(portMappingDO.getProxyResponses())
.setEnable(portMappingDO.getEnable())
.setCreateTime(portMappingDO.getCreateTime())
.setUpdateTime(portMappingDO.getUpdateTime());
LicenseDO license = licenseMapper.findById(portMappingDO.getLicenseId());
if (null != license) {
@@ -276,10 +296,14 @@ public class PortMappingService implements LifecycleBean {
*/
@Init
public void init() {
// aot 阶段不初始化
if (NativeDetector.isAotRuntime()) {
return;
}
portMappingMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
// 未配置域名则不需要处理域名映射逻辑
if (StrUtil.isBlank(proxyConfig.getServer().getDomainName())) {
if (StrUtil.isBlank(proxyConfig.getServer().getTcp().getDomainName())) {
return;
}
List<PortMappingDO> portMappingDOList = portMappingMapper.selectList(new LambdaQueryWrapper<PortMappingDO>().eq(PortMappingDO::getProtocal, NetworkProtocolEnum.HTTP.getDesc()).isNotNull(PortMappingDO::getSubdomain));
@@ -3,31 +3,36 @@ package org.dromara.neutrinoproxy.server.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.baomidou.mybatisplus.solon.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.solon.annotation.Db;
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
import org.dromara.neutrinoproxy.server.base.rest.ServiceException;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import org.dromara.neutrinoproxy.server.constant.ExceptionConstant;
import org.dromara.neutrinoproxy.server.controller.req.system.*;
import org.dromara.neutrinoproxy.server.controller.res.system.*;
import org.dromara.neutrinoproxy.server.controller.req.system.AvailablePortListReq;
import org.dromara.neutrinoproxy.server.controller.req.system.PortPoolCreateReq;
import org.dromara.neutrinoproxy.server.controller.req.system.PortPoolListReq;
import org.dromara.neutrinoproxy.server.controller.req.system.PortPoolUpdateEnableStatusReq;
import org.dromara.neutrinoproxy.server.controller.req.system.PortPoolUpdateGroupReq;
import org.dromara.neutrinoproxy.server.controller.req.system.PortPoolUpdateReq;
import org.dromara.neutrinoproxy.server.controller.res.system.PortPoolCreateRes;
import org.dromara.neutrinoproxy.server.controller.res.system.PortPoolListRes;
import org.dromara.neutrinoproxy.server.controller.res.system.PortPoolUpdateEnableStatusRes;
import org.dromara.neutrinoproxy.server.controller.res.system.PortPoolUpdateGroupRes;
import org.dromara.neutrinoproxy.server.controller.res.system.PortPoolUpdateRes;
import org.dromara.neutrinoproxy.server.dal.LicenseMapper;
import org.dromara.neutrinoproxy.server.dal.PortGroupMapper;
import org.dromara.neutrinoproxy.server.dal.PortMappingMapper;
import org.dromara.neutrinoproxy.server.dal.PortPoolMapper;
import org.dromara.neutrinoproxy.server.dal.entity.*;
import org.dromara.neutrinoproxy.server.dal.entity.LicenseDO;
import org.dromara.neutrinoproxy.server.dal.entity.PortGroupDO;
import org.dromara.neutrinoproxy.server.dal.entity.PortMappingDO;
import org.dromara.neutrinoproxy.server.dal.entity.PortPoolDO;
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
import lombok.extern.slf4j.Slf4j;
import ma.glasnost.orika.MapperFacade;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.solon.annotation.Db;
import org.dromara.neutrinoproxy.server.controller.req.system.*;
import org.dromara.neutrinoproxy.server.controller.res.system.*;
import org.dromara.neutrinoproxy.server.dal.entity.PortGroupDO;
import org.dromara.neutrinoproxy.server.util.PortAvailableUtil;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
@@ -37,7 +42,9 @@ import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static org.dromara.neutrinoproxy.server.constant.ExceptionConstant.*;
import static org.dromara.neutrinoproxy.server.constant.ExceptionConstant.PORT_CANNOT_REPEAT;
import static org.dromara.neutrinoproxy.server.constant.ExceptionConstant.PORT_GROUP_NAME_DOES_NOT_EXIST;
import static org.dromara.neutrinoproxy.server.constant.ExceptionConstant.PORT_RANGE_FAIL;
/**
*
@@ -47,8 +54,6 @@ import static org.dromara.neutrinoproxy.server.constant.ExceptionConstant.*;
@Slf4j
@Component
public class PortPoolService {
@Inject
private MapperFacade mapperFacade;
@Db
private PortPoolMapper portPoolMapper;
@Inject
@@ -62,17 +67,18 @@ public class PortPoolService {
private LicenseMapper licenseMapper;
public PageInfo<PortPoolListRes> page(PageQuery pageQuery, PortPoolListReq req) {
Page<PortPoolListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
List<PortPoolListRes> list = portPoolMapper.selectResList(req);
return PageInfo.of(list, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
Page<PortPoolListRes> page = new Page<>(pageQuery.getCurrent(), pageQuery.getSize());
List<PortPoolListRes> list = portPoolMapper.selectResList(page, req);
return PageInfo.of(list, page.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
}
public List<PortPoolListRes> list(PortPoolListReq req) {
List<PortPoolDO> list = portPoolMapper.selectList(new LambdaQueryWrapper<PortPoolDO>()
.eq(PortPoolDO::getEnable, EnableStatusEnum.ENABLE.getStatus())
);
return mapperFacade.mapAsList(this.filterUsedPorts(list), PortPoolListRes.class);
List<PortPoolDO> resultList = this.filterUsedPorts(list);
return resultList.stream().map(PortPoolDO::toRes).collect(Collectors.toList());
}
private List<PortPoolDO> filterUsedPorts(List<PortPoolDO> list) {
@@ -153,7 +159,7 @@ public class PortPoolService {
public List<PortPoolListRes> portListByGroupId(String groupId) {
List<PortPoolDO> portPoolDOList = portPoolMapper.getByGroupId(groupId);
List<PortPoolListRes> portPoolListReList = mapperFacade.mapAsList(portPoolDOList, PortPoolListRes.class);
List<PortPoolListRes> portPoolListReList = portPoolDOList.stream().map(PortPoolDO::toRes).collect(Collectors.toList());
return portPoolListReList;
}
@@ -175,9 +181,17 @@ public class PortPoolService {
* 游客全局端口 + 当前选择用户独占端口 + 当前选择license独占端口
* 非管理员身份时下拉选择license只能选当前用户下的LICENSE
*/
public List<PortPoolListRes> getAvailablePortList(AvailablePortListReq req) {
public PageInfo<PortPoolListRes> getAvailablePortList(AvailablePortListReq req) {
LicenseDO licenseDO = licenseMapper.queryById(req.getLicenseId());
return portPoolMapper.getAvailablePortList(req.getLicenseId(), licenseDO.getUserId());
if(StringUtils.isNotEmpty(req.getKeyword())){
req.setKeyword(req.getKeyword()+"%");
}
Page<PortPoolListRes> page = new Page<>(req.getPage(), req.getSize());
List<PortPoolListRes> portList = portPoolMapper.getAvailablePortList(page, req.getLicenseId(), licenseDO.getUserId(), req.getKeyword());
return PageInfo.of(portList, page.getTotal(), req.getPage(), req.getSize());
}
public void deleteBatch(List<Integer> ids) {
@@ -190,4 +204,21 @@ public class PortPoolService {
visitorChannelService.updateVisitorChannelByPortPool(portPoolDO.getPort(), EnableStatusEnum.DISABLE.getStatus());
});
}
/**
* 检查端口是否被占用
* 端口映射编辑时如果端口号没有变动则不验证避免出现端口映射正在使用时无法更新端口映射其他信息的问题
* @param port
* @param portMappingId
* @return
*/
public boolean portAvailable(Integer port, Integer portMappingId) {
if (null != portMappingId) {
PortMappingDO portMappingDO = portMappingMapper.findById(portMappingId);
if (null != portMappingDO && portMappingDO.getServerPort().equals(port)) {
return Boolean.TRUE;
}
}
return PortAvailableUtil.isPortAvailable(port);
}
}
@@ -24,7 +24,7 @@ public class ProtocalService {
return Lists.newArrayList(
new ProtocalListRes().setName("TCP").setEnable(Boolean.TRUE).setRemark("支持一切TCP之上的协议"),
new ProtocalListRes().setName("HTTP(S)").setEnable(Boolean.TRUE).setRemark("支持绑定子域名,未绑定时等价于时使用TCP。 若配置了证书,则同时支持HTTPS。"),
new ProtocalListRes().setName("UDP").setEnable(Boolean.FALSE).setRemark("暂不支持")
new ProtocalListRes().setName("UDP").setEnable(Boolean.TRUE).setRemark("支持UDP转发、响应。需要配合响应数、超时时间使用")
);
}
@@ -53,7 +53,7 @@ public class ProxyMutualService {
Date now = new Date();
portMappingMapper.updateOnlineStatus(attachInfo.getLicenseId(), serverPort, OnlineStatusEnum.ONLINE.getStatus(), now);
licenseMapper.updateOnlineStatus(attachInfo.getLicenseId(), OnlineStatusEnum.ONLINE.getStatus(), now);
log.info("绑定服务端端口 licenseId:{},ip:{},serverPort:{}", attachInfo.getLicenseId(), attachInfo.getIp(), serverPort);
log.info("bind server port licenseId:{},ip:{},serverPort:{}", attachInfo.getLicenseId(), attachInfo.getIp(), serverPort);
}
/**
@@ -64,7 +64,7 @@ public class ProxyMutualService {
Date now = new Date();
portMappingMapper.updateOnlineStatus(attachInfo.getLicenseId(), OnlineStatusEnum.OFFLINE.getStatus(), now);
licenseMapper.updateOnlineStatus(attachInfo.getLicenseId(), OnlineStatusEnum.OFFLINE.getStatus(), now);
log.info("客户端下线 licenseId:{},ip:{}", attachInfo.getLicenseId(), attachInfo.getIp());
log.info("client offline licenseId:{},ip:{}", attachInfo.getLicenseId(), attachInfo.getIp());
}
}
@@ -2,9 +2,10 @@ package org.dromara.neutrinoproxy.server.service;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.baomidou.mybatisplus.solon.plugins.pagination.Page;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.solon.annotation.Db;
import org.dromara.neutrinoproxy.core.util.DateUtil;
import org.dromara.neutrinoproxy.server.base.db.DbConfig;
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
@@ -15,7 +16,11 @@ import org.dromara.neutrinoproxy.server.controller.req.report.LicenseFlowMonthRe
import org.dromara.neutrinoproxy.server.controller.req.report.LicenseFlowReportReq;
import org.dromara.neutrinoproxy.server.controller.req.report.UserFlowMonthReportReq;
import org.dromara.neutrinoproxy.server.controller.req.report.UserFlowReportReq;
import org.dromara.neutrinoproxy.server.controller.res.report.*;
import org.dromara.neutrinoproxy.server.controller.res.report.HomeDataView;
import org.dromara.neutrinoproxy.server.controller.res.report.LicenseFlowMonthReportRes;
import org.dromara.neutrinoproxy.server.controller.res.report.LicenseFlowReportRes;
import org.dromara.neutrinoproxy.server.controller.res.report.UserFlowMonthReportRes;
import org.dromara.neutrinoproxy.server.controller.res.report.UserFlowReportRes;
import org.dromara.neutrinoproxy.server.dal.LicenseMapper;
import org.dromara.neutrinoproxy.server.dal.PortMappingMapper;
import org.dromara.neutrinoproxy.server.dal.ReportMapper;
@@ -24,13 +29,16 @@ import org.dromara.neutrinoproxy.server.dal.entity.PortMappingDO;
import org.dromara.neutrinoproxy.server.service.bo.FlowBO;
import org.dromara.neutrinoproxy.server.service.bo.SingleDayFlowBO;
import org.dromara.neutrinoproxy.server.util.FormatUtil;
import lombok.extern.slf4j.Slf4j;
import ma.glasnost.orika.MapperFacade;
import org.apache.ibatis.solon.annotation.Db;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import java.util.*;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -40,8 +48,6 @@ import java.util.stream.Collectors;
@Slf4j
@Component
public class ReportService {
@Inject
private MapperFacade mapperFacade;
@Db
private ReportMapper reportMapper;
@Db
@@ -77,17 +83,17 @@ public class ReportService {
// 今日流量
Date now = new Date();
FlowBO todayFlow = reportMapper.homeTodayFlow(DateUtil.getDayBegin(now), now);
homeDataView.setTodayFlow(mapperFacade.map(todayFlow, HomeDataView.TodayFlow.class));
homeDataView.setTodayFlow(todayFlow.todayFlow());
// 总流量
FlowBO totalFlow = reportMapper.homeTotalFlow(DateUtil.getMonthBegin(now), DateUtil.getDayBegin(now), now);
homeDataView.setTotalFlow(mapperFacade.map(totalFlow, HomeDataView.TotalFlow.class));
homeDataView.setTotalFlow(totalFlow.totalFlow());
// 最近n日流量
Integer days = Constants.HOME_FLOW_DAYS;
List<SingleDayFlowBO> last7dFlowList = reportMapper.homeLast7dFlowList(DateUtil.getDayBegin(DateUtil.addDate(now, Calendar.DATE, -(days - 1))), DateUtil.getDayBegin(now), now);
homeDataView.setLast7dFlow(new HomeDataView.Last7dFlow());
homeDataView.getLast7dFlow().setDataList(mapperFacade.mapAsList(last7dFlowList, HomeDataView.SingleDayFlow.class));
homeDataView.getLast7dFlow().setDataList(last7dFlowList.stream().map(SingleDayFlowBO::toSingleDayFlow).collect(Collectors.toList()));
// 数据处理
fillHomeDataView(homeDataView, now);
@@ -101,11 +107,11 @@ public class ReportService {
* @return
*/
public PageInfo<UserFlowReportRes> userFlowReportPage(PageQuery pageQuery, UserFlowReportReq req) {
Page<UserFlowReportRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
Page<UserFlowReportRes> page = new Page<>(pageQuery.getCurrent(), pageQuery.getSize());
Date now = new Date();
List<UserFlowReportRes> list = reportMapper.userFlowReportList(req.getUserId(), DateUtil.getMonthBegin(now), DateUtil.getDayBegin(now), now);
List<UserFlowReportRes> list = reportMapper.userFlowReportList(page, req.getUserId(), DateUtil.getMonthBegin(now), DateUtil.getDayBegin(now), now);
fillUserFlowReport(list);
return PageInfo.of(list, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
return PageInfo.of(list, page);
}
/**
@@ -115,11 +121,11 @@ public class ReportService {
* @return
*/
public PageInfo<LicenseFlowReportRes> licenseFlowReportPage(PageQuery pageQuery, LicenseFlowReportReq req) {
Page<LicenseFlowReportRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
Page<LicenseFlowReportRes> page = new Page<>(pageQuery.getCurrent(), pageQuery.getSize());
Date now = new Date();
List<LicenseFlowReportRes> list = reportMapper.licenseFLowReportList(req.getUserId(), DateUtil.getMonthBegin(now), DateUtil.getDayBegin(now), now);
List<LicenseFlowReportRes> list = reportMapper.licenseFLowReportList(page, req.getUserId(), DateUtil.getMonthBegin(now), DateUtil.getDayBegin(now), now);
fillLicenseFlowReport(list);
return PageInfo.of(list, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
return PageInfo.of(list, page);
}
/**
@@ -129,11 +135,11 @@ public class ReportService {
* @return
*/
public PageInfo<UserFlowMonthReportRes> userFlowMonthReportPage(PageQuery pageQuery, UserFlowMonthReportReq req) {
Page<UserFlowMonthReportRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
Page<UserFlowMonthReportRes> page = new Page<>(pageQuery.getCurrent(), pageQuery.getSize());
Date now = new Date();
List<UserFlowMonthReportRes> list = reportMapper.userFlowMonthReportList(req.getUserId(), DateUtil.getMonthBegin(now), DateUtil.getDayBegin(now), now);
List<UserFlowMonthReportRes> list = reportMapper.userFlowMonthReportList(page, req.getUserId(), DateUtil.getMonthBegin(now), DateUtil.getDayBegin(now), now);
fillUserFlowMonthReport(list);
return PageInfo.of(list, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
return PageInfo.of(list, page);
}
/**
@@ -143,11 +149,11 @@ public class ReportService {
* @return
*/
public PageInfo<LicenseFlowMonthReportRes> licenseFlowMonthReportPage(PageQuery pageQuery, LicenseFlowMonthReportReq req) {
Page<LicenseFlowMonthReportRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
Page<LicenseFlowMonthReportRes> page = new Page<>(pageQuery.getCurrent(), pageQuery.getSize());
Date now = new Date();
List<LicenseFlowMonthReportRes> list = reportMapper.licenseFLowMonthReportList(req.getUserId(), req.getLicenseId(), DateUtil.getMonthBegin(now), DateUtil.getDayBegin(now), now);
List<LicenseFlowMonthReportRes> list = reportMapper.licenseFLowMonthReportList(page, req.getUserId(), req.getLicenseId(), DateUtil.getMonthBegin(now), DateUtil.getDayBegin(now), now);
fillLicenseFlowMonthReport(list);
return PageInfo.of(list, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
return PageInfo.of(list, page);
}
private void fillUserFlowReport(List<UserFlowReportRes> list) {
@@ -3,8 +3,8 @@ package org.dromara.neutrinoproxy.server.service;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.baomidou.mybatisplus.solon.plugins.pagination.Page;
import org.apache.ibatis.solon.annotation.Db;
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
import org.dromara.neutrinoproxy.server.controller.req.log.UserLoginRecordListReq;
@@ -13,10 +13,7 @@ import org.dromara.neutrinoproxy.server.dal.UserLoginRecordMapper;
import org.dromara.neutrinoproxy.server.dal.UserMapper;
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
import org.dromara.neutrinoproxy.server.dal.entity.UserLoginRecordDO;
import ma.glasnost.orika.MapperFacade;
import org.apache.ibatis.solon.annotation.Db;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import java.util.List;
import java.util.Map;
@@ -31,22 +28,19 @@ import java.util.stream.Collectors;
*/
@Component
public class UserLoginRecordService {
@Inject
private MapperFacade mapperFacade;
@Db
private UserLoginRecordMapper userLoginRecordMapper;
@Db
private UserMapper userMapper;
public PageInfo<UserLoginRecordListRes> page(PageQuery pageQuery, UserLoginRecordListReq req) {
Page<UserLoginRecordListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
List<UserLoginRecordDO> list = userLoginRecordMapper.selectList(new LambdaQueryWrapper<UserLoginRecordDO>()
.eq(null != req.getUserId(), UserLoginRecordDO::getUserId, req.getUserId())
.orderByDesc(UserLoginRecordDO::getCreateTime)
Page<UserLoginRecordDO> page = userLoginRecordMapper.selectPage(new Page<>(pageQuery.getCurrent(), pageQuery.getSize()), new LambdaQueryWrapper<UserLoginRecordDO>()
.eq(null != req.getUserId(), UserLoginRecordDO::getUserId, req.getUserId())
.orderByDesc(UserLoginRecordDO::getCreateTime)
);
List<UserLoginRecordListRes> respList = mapperFacade.mapAsList(list, UserLoginRecordListRes.class);
if (CollectionUtils.isEmpty(list)) {
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
List<UserLoginRecordListRes> respList = page.getRecords().stream().map(UserLoginRecordDO::toRes).collect(Collectors.toList());
if (CollectionUtils.isEmpty(page.getRecords())) {
return PageInfo.of(respList, page);
}
if (!CollectionUtil.isEmpty(respList)) {
@@ -60,6 +54,6 @@ public class UserLoginRecordService {
}
}
}
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
return PageInfo.of(respList, page);
}
}
@@ -2,8 +2,8 @@ package org.dromara.neutrinoproxy.server.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.baomidou.mybatisplus.solon.plugins.pagination.Page;
import org.apache.ibatis.solon.annotation.Db;
import org.dromara.neutrinoproxy.core.util.DateUtil;
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
@@ -11,8 +11,20 @@ import org.dromara.neutrinoproxy.server.base.rest.ServiceException;
import org.dromara.neutrinoproxy.server.base.rest.SystemContextHolder;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import org.dromara.neutrinoproxy.server.constant.ExceptionConstant;
import org.dromara.neutrinoproxy.server.controller.req.system.*;
import org.dromara.neutrinoproxy.server.controller.res.system.*;
import org.dromara.neutrinoproxy.server.controller.req.system.LoginReq;
import org.dromara.neutrinoproxy.server.controller.req.system.UserCreateReq;
import org.dromara.neutrinoproxy.server.controller.req.system.UserInfoReq;
import org.dromara.neutrinoproxy.server.controller.req.system.UserListReq;
import org.dromara.neutrinoproxy.server.controller.req.system.UserUpdateEnableStatusReq;
import org.dromara.neutrinoproxy.server.controller.req.system.UserUpdatePasswordReq;
import org.dromara.neutrinoproxy.server.controller.req.system.UserUpdateReq;
import org.dromara.neutrinoproxy.server.controller.res.system.LoginRes;
import org.dromara.neutrinoproxy.server.controller.res.system.UserCreateRes;
import org.dromara.neutrinoproxy.server.controller.res.system.UserInfoRes;
import org.dromara.neutrinoproxy.server.controller.res.system.UserListRes;
import org.dromara.neutrinoproxy.server.controller.res.system.UserUpdateEnableStatusRes;
import org.dromara.neutrinoproxy.server.controller.res.system.UserUpdatePasswordRes;
import org.dromara.neutrinoproxy.server.controller.res.system.UserUpdateRes;
import org.dromara.neutrinoproxy.server.dal.UserLoginRecordMapper;
import org.dromara.neutrinoproxy.server.dal.UserMapper;
import org.dromara.neutrinoproxy.server.dal.UserTokenMapper;
@@ -21,8 +33,6 @@ import org.dromara.neutrinoproxy.server.dal.entity.UserLoginRecordDO;
import org.dromara.neutrinoproxy.server.dal.entity.UserTokenDO;
import org.dromara.neutrinoproxy.server.util.Md5Util;
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
import ma.glasnost.orika.MapperFacade;
import org.apache.ibatis.solon.annotation.Db;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
@@ -30,6 +40,7 @@ import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
/**
*
@@ -39,8 +50,7 @@ import java.util.UUID;
@Component
public class UserService {
private static final String DEFAULT_PASSWORD = "123456";
@Inject
private MapperFacade mapperFacade;
@Db
private UserMapper userMapper;
@Db
@@ -120,12 +130,11 @@ public class UserService {
}
public PageInfo<UserListRes> page(PageQuery pageQuery, UserListReq req) {
Page<UserListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
List<UserDO> list = userMapper.selectList(new LambdaQueryWrapper<UserDO>()
.orderByAsc(UserDO::getId)
);
List<UserListRes> respList = mapperFacade.mapAsList(list, UserListRes.class);
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
Page<UserDO> page = userMapper.selectPage(new Page<>(pageQuery.getCurrent(), pageQuery.getSize()), new LambdaQueryWrapper<UserDO>()
.orderByAsc(UserDO::getId)
);
List<UserListRes> respList = page.getRecords().stream().map(UserDO::toRes).collect(Collectors.toList());
return PageInfo.of(respList, page);
}
public List<UserListRes> list(UserListReq req) {
@@ -133,7 +142,7 @@ public class UserService {
.eq(UserDO::getEnable, EnableStatusEnum.ENABLE.getStatus())
.orderByAsc(UserDO::getId)
);
return mapperFacade.mapAsList(userDOList, UserListRes.class);
return userDOList.stream().map(UserDO::toRes).collect(Collectors.toList());
}
public UserInfoRes info(UserInfoReq req) {
@@ -3,8 +3,10 @@ package org.dromara.neutrinoproxy.server.service;
import cn.hutool.core.collection.CollectionUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import io.netty.bootstrap.Bootstrap;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum;
import org.dromara.neutrinoproxy.server.dal.LicenseMapper;
import org.dromara.neutrinoproxy.server.dal.PortMappingMapper;
import org.dromara.neutrinoproxy.server.dal.PortPoolMapper;
@@ -43,6 +45,8 @@ import java.util.stream.Collectors;
public class VisitorChannelService {
@Inject("tcpServerBootstrap")
private ServerBootstrap tcpServerBootstrap;
@Inject("udpServerBootstrap")
private Bootstrap udpServerBootstrap;
@Inject
private ProxyMutualService proxyMutualService;
@Db
@@ -207,7 +211,8 @@ public class VisitorChannelService {
if (null != proxyChannel) {
proxyChannel.close();
}
visitorChannel.close();
// TODO 此处如果时UDP的visitorChannel,则不能close后续重构考虑
// visitorChannel.close();
}
ProxyUtil.removeProxyInfo(portMappingDO.getServerPort());
}
@@ -219,14 +224,20 @@ public class VisitorChannelService {
for (PortMappingDO portMapping : portMappingList) {
if (EnableStatusEnum.DISABLE.getStatus().equals(portMapping.getEnable())) {
// 端口映射被禁用了忽略 TODO 端口被禁用了也需要处理
// 端口映射被禁用了忽略 TODO 映射没被禁用端口被禁用了也需要处理
continue;
}
// TODO 此处切入TCP/UDP代理
try {
proxyMutualService.bindServerPort(cmdChannelAttachInfo, portMapping.getServerPort());
tcpServerBootstrap.bind(portMapping.getServerPort()).get();
log.info("绑定用户端口: {}", portMapping.getServerPort());
NetworkProtocolEnum networkProtocolEnum = NetworkProtocolEnum.of(portMapping.getProtocal());
if (networkProtocolEnum == NetworkProtocolEnum.UDP) {
udpServerBootstrap.bind(portMapping.getServerPort()).get();
log.info("bind UDP user port {}", portMapping.getServerPort());
} else {
tcpServerBootstrap.bind(portMapping.getServerPort()).get();
log.info("bind TCP user port {}", portMapping.getServerPort());
}
} catch (Exception ex) {
// BindException表示该端口已经绑定过
if (!(ex.getCause() instanceof BindException)) {
@@ -1,6 +1,7 @@
package org.dromara.neutrinoproxy.server.service.bo;
import lombok.Data;
import org.dromara.neutrinoproxy.server.controller.res.report.HomeDataView;
/**
* @author: aoshiguchen
@@ -16,4 +17,18 @@ public class FlowBO {
* 下行流量字节数
*/
private Long downFlowBytes;
public HomeDataView.TodayFlow todayFlow() {
HomeDataView.TodayFlow todayFlow = new HomeDataView.TodayFlow();
todayFlow.setUpFlowBytes(upFlowBytes);
todayFlow.setDownFlowBytes(downFlowBytes);
return todayFlow;
}
public HomeDataView.TotalFlow totalFlow() {
HomeDataView.TotalFlow totalFlow = new HomeDataView.TotalFlow();
totalFlow.setUpFlowBytes(upFlowBytes);
totalFlow.setDownFlowBytes(downFlowBytes);
return totalFlow;
}
}
@@ -1,6 +1,7 @@
package org.dromara.neutrinoproxy.server.service.bo;
import lombok.Data;
import org.dromara.neutrinoproxy.server.controller.res.report.HomeDataView;
import java.util.Date;
@@ -23,4 +24,12 @@ public class SingleDayFlowBO {
* 下行流量字节数
*/
private Long downFlowBytes;
public HomeDataView.SingleDayFlow toSingleDayFlow() {
HomeDataView.SingleDayFlow singleDayFlow = new HomeDataView.SingleDayFlow();
singleDayFlow.setDate(date);
singleDayFlow.setUpFlowBytes(upFlowBytes);
singleDayFlow.setDownFlowBytes(downFlowBytes);
return singleDayFlow;
}
}
@@ -0,0 +1,39 @@
package org.dromara.neutrinoproxy.server.util;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
/**
* 检查端口是否被占用
* 文章参考https://blog.csdn.net/xingluxiaoseng/article/details/40148527
*/
public class PortAvailableUtil {
private static void bindPort(String host, int port) throws IOException {
Socket s = new Socket();
s.bind(new InetSocketAddress(host, port));
s.close();
}
/**
* 端口占用判断若是端口被占用则会抛出IOException异常表示端口被占用
* @param port
* @return
*/
public static boolean isPortAvailable(int port) {
try {
bindPort("0.0.0.0", port);
bindPort(InetAddress.getLocalHost().getHostAddress(), port);
return true;
} catch (Exception e) {
return false;
}
}
public static void main(String[] args) {
System.out.println("端口被占用:"+isPortAvailable(9527));
}
}
@@ -5,6 +5,7 @@ import com.google.common.collect.Sets;
import org.apache.commons.lang3.StringUtils;
import org.dromara.neutrinoproxy.core.ChannelAttribute;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum;
import org.dromara.neutrinoproxy.server.proxy.domain.CmdChannelAttachInfo;
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyAttachment;
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyMapping;
@@ -37,15 +38,15 @@ public class ProxyUtil {
/**
* 服务端口 -> 指令通道映射
*/
private static Map<Integer, Channel> serverPortToCmdChannelMap = new ConcurrentHashMap<>();
private static final Map<Integer, Channel> serverPortToCmdChannelMap = new ConcurrentHashMap<>();
/**
* license -> 指令通道映射
*/
private static Map<Integer, Channel> licenseToCmdChannelMap = new ConcurrentHashMap<>();
private static final Map<Integer, Channel> licenseToCmdChannelMap = new ConcurrentHashMap<>();
/**
* 服务端口 -> 访问通道映射
*/
private static Map<Integer, Channel> serverPortToVisitorChannel = new ConcurrentHashMap<>();
private static final Map<Integer, Channel> serverPortToVisitorChannel = new ConcurrentHashMap<>();
/**
* cmdChannelAttachInfo.getUserChannelMap() 读写锁
@@ -54,19 +55,21 @@ public class ProxyUtil {
/**
* 访问者ID生成器
*/
private static AtomicLong visitorIdProducer = new AtomicLong(0);
private static final AtomicLong visitorIdProducer = new AtomicLong(0);
/**
* 代理 - connect附加映射
*/
private static Map<String, ProxyAttachment> proxyConnectAttachmentMap = new HashMap<>();
private static final Map<String, ProxyAttachment> proxyConnectAttachmentMap = new HashMap<>();
/**
* 子域名 - 服务端端口映射
*/
private static Map<String, Integer> subdomainToServerPort = new HashMap<>();
private static final Map<String, Integer> subdomainToServerPort = new HashMap<>();
/**
* licenseId - 客户端Id映射
*/
private static Map<Integer, String> licenseIdToClientIdMap = new HashMap<>();
private static final Map<Integer, String> licenseIdToClientIdMap = new HashMap<>();
private static final Map<Integer, byte[]> licenseIdToSecureKeyMap = new ConcurrentHashMap<>();
/**
* 初始化代理信息
@@ -201,18 +204,21 @@ public class ProxyUtil {
* @param visitorId
* @param visitorChannel
*/
public static void addVisitorChannelToCmdChannel(Channel cmdChannel, String visitorId, Channel visitorChannel, Integer serverPort) {
public static void addVisitorChannelToCmdChannel(NetworkProtocolEnum protocol, Channel cmdChannel, String visitorId, Channel visitorChannel, Integer serverPort) {
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
String lanInfo = getClientLanInfoByServerPort(sa.getPort());
CmdChannelAttachInfo cmdChannelAttachInfo = getAttachInfo(cmdChannel);
setAttachInfo(visitorChannel, new VisitorChannelAttachInfo()
.setVisitorId(visitorId)
.setLanInfo(lanInfo)
.setServerPort(serverPort)
.setLicenseId(cmdChannelAttachInfo.getLicenseId())
.setIp(((InetSocketAddress)visitorChannel.remoteAddress()).getAddress().getHostAddress())
);
VisitorChannelAttachInfo attachInfo = new VisitorChannelAttachInfo()
.setProtocol(protocol)
.setVisitorId(visitorId)
.setLanInfo(lanInfo)
.setServerPort(serverPort)
.setLicenseId(cmdChannelAttachInfo.getLicenseId());
if (NetworkProtocolEnum.UDP != protocol) {
attachInfo.setIp(((InetSocketAddress)visitorChannel.remoteAddress()).getAddress().getHostAddress());
}
setAttachInfo(visitorChannel, attachInfo);
userChannelMapLock.writeLock().lock();
try {
cmdChannelAttachInfo.getVisitorChannelMap().put(visitorId, visitorChannel);
@@ -417,4 +423,22 @@ public class ProxyUtil {
public static void removeClientIdByLicenseId(Integer licenseId) {
licenseIdToClientIdMap.remove(licenseId);
}
public static void setSecureKey(Integer licenseId, byte[] key) {
licenseIdToSecureKeyMap.put(licenseId, key);
}
public static void setLicenseIdRelativeProxyChannelSecurity(Integer licenseId) {
Set<Integer> portSet = licenseToServerPortMap.get(licenseId);
for(Integer port : portSet) {
// TODO 代理客户端
}
}
public static void setChannelSecurity(Integer licenseId, Channel channel) {
if (channel != null && licenseIdToSecureKeyMap.containsKey(licenseId)) {
channel.attr(Constants.IS_SECURITY).set(true);
channel.attr(Constants.SECURE_KEY).set(licenseIdToSecureKeyMap.get(licenseId));
}
}
}
@@ -0,0 +1,59 @@
server:
# 服务端web端口,用于支持HTTP接口,管理后台页面访问
port: 8888
# 日志级别
solon.logging.logger:
"root":
level: info
neutrino:
proxy:
# 隧道相关配置-用于维持服务端与客户端的通信
tunnel:
# 线程池相关配置,用于技术调优,可忽略
boss-thread-count: 2
work-thread-count: 10
# 隧道非SSL端口
port: ${OPEN_PORT:9000}
# 隧道SSL端口
ssl-port: ${SSL_PORT:9002}
# 隧道SSL证书配置
key-store-password: ${STORE_PASS:123456}
key-manager-password: ${MGR_PASS:123456}
jks-path: ${JKS_PATH:classpath:/test.jks}
# 是否开启隧道传输报文日志(日志级别为debug时开启才有效)
transfer-log-enable: ${TUNNEL_LOG:false}
# 是否开启心跳日志
heartbeat-log-enable: ${HEARTBEAT_LOG:false}
server:
tcp:
# 线程池相关配置,用于技术调优,可忽略
boss-thread-count: 5
work-thread-count: 20
# http代理端口,默认80
http-proxy-port: ${HTTP_PROXY_PORT:80}
# https代理端口,默认443 (需要配置域名、证书)
https-proxy-port: ${HTTPS_PROXY_PORT:443}
# 如果不配置,则不支持域名映射
domain-name: ${DOMAIN_NAME:}
# https证书配置
key-store-password: ${HTTPS_STORE_PASS:}
jks-path: ${HTTPS_JKS_PATH:}
# 是否开启代理服务报文日志(日志级别为debug时开启才有效)
transfer-log-enable: ${SERVER_LOG:false}
udp:
# 线程池相关配置,用于技术调优,可忽略
boss-thread-count: 5
work-thread-count: 20
# 是否开启代理服务报文日志(日志级别为debug时开启才有效)
transfer-log-enable: ${SERVER_LOG:false}
data:
db:
# 数据库类型,目前支持h2、mysql、mariadb
type: ${DB_TYPE:h2}
# 数据库连接URL
url: ${DB_URL:jdbc:h2:file:./data/db;MODE=MySQL;AUTO_SERVER=TRUE}
# 数据库用户名
username: ${DB_USER:}
# 数据库密码
password: ${DB_PASSWORD:}
@@ -2,21 +2,36 @@ server:
# 服务端web端口,用于支持HTTP接口,管理后台页面访问
port: ${WEB_PORT:8888}
context-path: /neutrino-proxy-server
solon:
app:
name: neutrino-proxy-server
version: @revision@
config:
add: ./app.yml
# 日志级别
solon.logging.appender:
console:
pattern: "%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %magenta(${PID:-}) --- %-15([%15.15thread]) %-56(%cyan(%-40.40logger{39}%L)) : %msg%n"
file:
enable: true
pattern: "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level ${PID:-} --- %-15([%15.15thread]) %-56(%-40.40logger{39}%L) : %msg%n"
name: "logs/${solon.app.name}"
rolling: "logs/${solon.app.name}_%d{yyyy-MM-dd}_%i.log"
solon.logging.logger:
"root":
level: info
neutrino:
proxy:
protocol:
max-frame-length: ${MAX_FRAME_LENGTH:2097152}
max-frame-length: ${MAX_FRAME_LENGTH:1048576000}
length-field-offset: 0
length-field-length: 4
initial-bytes-to-strip: 0
length-adjustment: 0
read-idle-time: 30
write-idle-time: 5
read-idle-time: 120
write-idle-time: 20
all-idle-time-seconds: 0
logger:
# 日志级别
level: ${LOG_LEVEL:info}
# 隧道相关配置-用于维持服务端与客户端的通信
tunnel:
# 线程池相关配置,用于技术调优,可忽略
@@ -35,26 +50,33 @@ neutrino:
# 是否开启心跳日志
heartbeat-log-enable: ${HEARTBEAT_LOG:false}
server:
# 线程池相关配置,用于技术调优,可忽略
boss-thread-count: 5
work-thread-count: 20
# http代理端口,默认80
http-proxy-port: ${HTTP_PROXY_PORT:80}
# https代理端口,默认443 (需要配置域名、证书)
https-proxy-port: ${HTTPS_PROXY_PORT:443}
# 如果不配置,则不支持域名映射
domain-name: ${DOMAIN_NAME:}
# https证书配置
key-store-password: ${HTTPS_STORE_PASS:}
jks-path: ${HTTPS_JKS_PATH:}
# 是否开启代理服务报文日志(日志级别为debug时开启才有效)
transfer-log-enable: ${SERVER_LOG:false}
tcp:
# 线程池相关配置,用于技术调优,可忽略
boss-thread-count: 5
work-thread-count: 20
# http代理端口,默认80
http-proxy-port: ${HTTP_PROXY_PORT:80}
# https代理端口,默认443 (需要配置域名、证书)
https-proxy-port: ${HTTPS_PROXY_PORT:443}
# 如果不配置,则不支持域名映射
domain-name: ${DOMAIN_NAME:}
# https证书配置
key-store-password: ${HTTPS_STORE_PASS:}
jks-path: ${HTTPS_JKS_PATH:}
# 是否开启代理服务报文日志(日志级别为debug时开启才有效)
transfer-log-enable: ${SERVER_LOG:false}
udp:
# 线程池相关配置,用于技术调优,可忽略
boss-thread-count: 5
work-thread-count: 20
# 是否开启代理服务报文日志(日志级别为debug时开启才有效)
transfer-log-enable: ${SERVER_LOG:false}
data:
db:
# 数据库类型,目前支持sqlite、mysql、mariadb
type: ${DB_TYPE:sqlite}
# 数据库类型,目前支持h2、mysql、mariadb
type: ${DB_TYPE:h2}
# 数据库连接URL
url: ${DB_URL:jdbc:sqlite:data.db}
url: ${DB_URL:jdbc:h2:file:./data/db;MODE=MySQL;AUTO_SERVER=TRUE}
# 数据库用户名
username: ${DB_USER:}
# 数据库密码
@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="LOG_FILE" value="./logs/neutrino-proxy-server.log"/>
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{50} - %msg%n"/>
<!-- <property name="ENCODE" value="utf8" />-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<!-- <charset>${ENCODE}</charset>-->
</encoder>
</appender>
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.zip</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<!-- <charset>${ENCODE}</charset>-->
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="console"/>
<appender-ref ref="file"/>
</root>
<logger name="com.example.logback" level="warn"/>
<logger name="io.netty" level="warn"/>
</configuration>
@@ -32,6 +32,9 @@
possessor_type = 0
OR ( possessor_type = 1 AND possessor_id = #{userId,jdbcType=INTEGER} )
OR ( possessor_type = 2 AND possessor_id = #{licenseId,jdbcType=INTEGER} )
)
)
<if test="keyword!=null and keyword!=''">
AND `port` LIKE #{keyword}
</if>
</select>
</mapper>
@@ -4,16 +4,16 @@
<select id="userFlowReportList" resultType="org.dromara.neutrinoproxy.server.controller.res.report.UserFlowReportRes">
SELECT
u.id AS 'userId',
u.name AS 'userName',
SUM(IFNULL(frm.write_bytes,0) + IFNULL(frd.write_bytes,0) + IFNULL(frm2.write_bytes,0)) AS 'upFlowBytes',
SUM(IFNULL(frm.read_bytes,0) + IFNULL(frd.read_bytes,0) + IFNULL(frm2.read_bytes,0)) AS 'downFlowBytes',
IFNULL(SUM(frm.write_bytes),0) monthWriteBytes,
IFNULL(SUM(frm.read_bytes),0) monthReadBytes,
IFNULL(SUM(frd.write_bytes),0) dayWriteBytes,
IFNULL(SUM(frd.read_bytes),0) dayReadBytes,
IFNULL(SUM(frm2.write_bytes),0) minuteWriteBytes,
IFNULL(SUM(frm2.read_bytes),0) minuteReadBytes
u.id AS userId,
u.name AS userName,
SUM(COALESCE(frm.write_bytes,0) + COALESCE(frd.write_bytes,0) + COALESCE(frm2.write_bytes,0)) AS upFlowBytes,
SUM(COALESCE(frm.read_bytes,0) + COALESCE(frd.read_bytes,0) + COALESCE(frm2.read_bytes,0)) AS downFlowBytes,
COALESCE(SUM(frm.write_bytes),0) monthWriteBytes,
COALESCE(SUM(frm.read_bytes),0) monthReadBytes,
COALESCE(SUM(frd.write_bytes),0) dayWriteBytes,
COALESCE(SUM(frd.read_bytes),0) dayReadBytes,
COALESCE(SUM(frm2.write_bytes),0) minuteWriteBytes,
COALESCE(SUM(frm2.read_bytes),0) minuteReadBytes
FROM `user` u
LEFT JOIN (SELECT user_id,sum(write_bytes) write_bytes,sum(read_bytes) read_bytes from flow_report_month GROUP BY user_id) frm ON u.id = frm.user_id
LEFT JOIN (SELECT user_id,sum(write_bytes) write_bytes,sum(read_bytes) read_bytes from flow_report_day
@@ -32,18 +32,18 @@
<select id="licenseFLowReportList" resultType="org.dromara.neutrinoproxy.server.controller.res.report.LicenseFlowReportRes">
SELECT
l.id AS 'licenseId',
l.name AS 'licenseName',
u.id AS 'userId',
u.name AS 'userName',
SUM(IFNULL(frm.write_bytes,0) + IFNULL(frd.write_bytes,0) + IFNULL(frm2.write_bytes,0)) AS 'upFlowBytes',
SUM(IFNULL(frm.read_bytes,0) + IFNULL(frd.read_bytes,0) + IFNULL(frm2.read_bytes,0)) AS 'downFlowBytes',
IFNULL(SUM(frm.write_bytes),0) monthWriteBytes,
IFNULL(SUM(frm.read_bytes),0) monthReadBytes,
IFNULL(SUM(frd.write_bytes),0) dayWriteBytes,
IFNULL(SUM(frd.read_bytes),0) dayReadBytes,
IFNULL(SUM(frm2.write_bytes),0) minuteWriteBytes,
IFNULL(SUM(frm2.read_bytes),0) minuteReadBytes
l.id AS licenseId,
l.name AS licenseName,
u.id AS userId,
u.name AS userName,
SUM(COALESCE(frm.write_bytes,0) + COALESCE(frd.write_bytes,0) + COALESCE(frm2.write_bytes,0)) AS upFlowBytes,
SUM(COALESCE(frm.read_bytes,0) + COALESCE(frd.read_bytes,0) + COALESCE(frm2.read_bytes,0)) AS downFlowBytes,
COALESCE(SUM(frm.write_bytes),0) monthWriteBytes,
COALESCE(SUM(frm.read_bytes),0) monthReadBytes,
COALESCE(SUM(frd.write_bytes),0) dayWriteBytes,
COALESCE(SUM(frd.read_bytes),0) dayReadBytes,
COALESCE(SUM(frm2.write_bytes),0) minuteWriteBytes,
COALESCE(SUM(frm2.read_bytes),0) minuteReadBytes
FROM `license` l
LEFT JOIN `user` u ON l.user_id = u.id
LEFT JOIN (SELECT license_id,sum(write_bytes) write_bytes,sum(read_bytes) read_bytes from flow_report_month GROUP BY license_id) frm ON l.id = frm.license_id
@@ -64,7 +64,7 @@
<select id="userFlowMonthReportList" resultType="org.dromara.neutrinoproxy.server.controller.res.report.UserFlowMonthReportRes">
SELECT
T2.user_id,
u.name AS 'userName',
u.name AS userName,
T2.date,
T2.upFlowBytes,
T2.downFlowBytes
@@ -73,8 +73,8 @@
SELECT
frm.user_id,
frm.date,
IFNULL(SUM(frm.write_bytes),0) AS 'upFlowBytes',
IFNULL(SUM(frm.read_bytes),0) AS 'downFlowBytes'
COALESCE(SUM(frm.write_bytes),0) AS upFlowBytes,
COALESCE(SUM(frm.read_bytes),0) AS downFlowBytes
FROM flow_report_month frm
GROUP BY frm.user_id,frm.date
@@ -82,16 +82,16 @@
SELECT
T1.user_id,
#{curMonthBeginDate} AS 'date',
IFNULL(SUM(T1.upFlowBytes),0) AS 'upFlowBytes',
IFNULL(SUM(T1.downFlowBytes),0) AS 'downFlowBytes'
#{curMonthBeginDate} AS date,
COALESCE(SUM(T1.upFlowBytes),0) AS upFlowBytes,
COALESCE(SUM(T1.downFlowBytes),0) AS downFlowBytes
FROM (
SELECT
frd.user_id,
frd.license_id,
frd.write_bytes AS 'upFlowBytes',
frd.read_bytes AS 'downFlowBytes',
frd.write_bytes AS upFlowBytes,
frd.read_bytes AS downFlowBytes,
frd.date
FROM flow_report_day frd
WHERE frd.date >= #{curMonthBeginDate} AND frd.date &lt;= #{curDayBeginDate}
@@ -101,9 +101,9 @@
SELECT
frm.user_id,
frm.license_id,
IFNULL(SUM(frm.write_bytes),0) AS 'upFlowBytes',
IFNULL(SUM(frm.read_bytes),0) AS 'downFlowBytes',
#{curDayBeginDate} AS 'date'
COALESCE(SUM(frm.write_bytes),0) AS upFlowBytes,
COALESCE(SUM(frm.read_bytes),0) AS downFlowBytes,
#{curDayBeginDate} AS date
FROM flow_report_minute frm
WHERE frm.date >= #{curDayBeginDate} AND frm.date &lt;= #{curDate}
GROUP BY frm.user_id,frm.license_id
@@ -124,9 +124,9 @@
<select id="licenseFLowMonthReportList" resultType="org.dromara.neutrinoproxy.server.controller.res.report.LicenseFlowMonthReportRes">
SELECT
T2.user_id,
u.name AS 'userName',
u.name AS userName,
T2.license_id,
l.`name` AS 'licenseName',
l.`name` AS licenseName,
T2.date,
T2.upFlowBytes,
T2.downFlowBytes
@@ -136,8 +136,8 @@
frm.user_id,
frm.license_id,
frm.date,
IFNULL(SUM(frm.write_bytes),0) AS 'upFlowBytes',
IFNULL(SUM(frm.read_bytes),0) AS 'downFlowBytes'
COALESCE(SUM(frm.write_bytes),0) AS upFlowBytes,
COALESCE(SUM(frm.read_bytes),0) AS downFlowBytes
FROM flow_report_month frm
GROUP BY frm.user_id,frm.license_id,frm.date
@@ -146,16 +146,16 @@
SELECT
T1.user_id,
T1.license_id,
#{curMonthBeginDate} AS 'date',
IFNULL(SUM(T1.upFlowBytes),0) AS 'upFlowBytes',
IFNULL(SUM(T1.downFlowBytes),0) AS 'downFlowBytes'
#{curMonthBeginDate} AS date,
COALESCE(SUM(T1.upFlowBytes),0) AS upFlowBytes,
COALESCE(SUM(T1.downFlowBytes),0) AS downFlowBytes
FROM (
SELECT
frd.user_id,
frd.license_id,
frd.write_bytes AS 'upFlowBytes',
frd.read_bytes AS 'downFlowBytes',
frd.write_bytes AS upFlowBytes,
frd.read_bytes AS downFlowBytes,
frd.date
FROM flow_report_day frd
WHERE frd.date >= #{curMonthBeginDate} AND frd.date &lt;= #{curDayBeginDate}
@@ -165,9 +165,9 @@
SELECT
frm.user_id,
frm.license_id,
IFNULL(SUM(frm.write_bytes),0) AS 'upFlowBytes',
IFNULL(SUM(frm.read_bytes),0) AS 'downFlowBytes',
#{curDayBeginDate} AS 'date'
COALESCE(SUM(frm.write_bytes),0) AS upFlowBytes,
COALESCE(SUM(frm.read_bytes),0) AS downFlowBytes,
#{curDayBeginDate} AS date
FROM flow_report_minute frm
WHERE frm.date >= #{curDayBeginDate} AND frm.date &lt;= #{curDate}
GROUP BY frm.user_id,frm.license_id
@@ -191,35 +191,35 @@
<select id="homeTodayFlow" resultType="org.dromara.neutrinoproxy.server.service.bo.FlowBO">
SELECT
IFNULL(SUM(frm.write_bytes),0) AS 'upFlowBytes',
IFNULL(SUM(frm.read_bytes),0) AS 'downFlowBytes'
COALESCE(SUM(frm.write_bytes),0) AS upFlowBytes,
COALESCE(SUM(frm.read_bytes),0) AS downFlowBytes
FROM flow_report_minute frm
WHERE frm.date >= #{curDayBeginDate} AND frm.date &lt;= #{curDate}
</select>
<select id="homeTotalFlow" resultType="org.dromara.neutrinoproxy.server.service.bo.FlowBO">
SELECT
IFNULL(SUM(T.upFlowBytes),0) AS 'upFlowBytes',
IFNULL(SUM(T.downFlowBytes),0) AS 'downFlowBytes'
COALESCE(SUM(T.upFlowBytes),0) AS upFlowBytes,
COALESCE(SUM(T.downFlowBytes),0) AS downFlowBytes
FROM (
SELECT
IFNULL(SUM(frm.write_bytes),0) AS 'upFlowBytes',
IFNULL(SUM(frm.read_bytes),0) AS 'downFlowBytes'
COALESCE(SUM(frm.write_bytes),0) AS upFlowBytes,
COALESCE(SUM(frm.read_bytes),0) AS downFlowBytes
FROM flow_report_month frm
UNION ALL
SELECT
IFNULL(SUM(frd.write_bytes),0) AS 'upFlowBytes',
IFNULL(SUM(frd.read_bytes),0) AS 'downFlowBytes'
COALESCE(SUM(frd.write_bytes),0) AS upFlowBytes,
COALESCE(SUM(frd.read_bytes),0) AS downFlowBytes
FROM flow_report_day frd
WHERE frd.date >= #{curMonthBeginDate} AND frd.date &lt;= #{curDayBeginDate}
UNION ALL
SELECT
IFNULL(SUM(frm.write_bytes),0) AS 'upFlowBytes',
IFNULL(SUM(frm.read_bytes),0) AS 'downFlowBytes'
COALESCE(SUM(frm.write_bytes),0) AS upFlowBytes,
COALESCE(SUM(frm.read_bytes),0) AS downFlowBytes
FROM flow_report_minute frm
WHERE frm.date >= #{curDayBeginDate} AND frm.date &lt;= #{curDate}
@@ -233,8 +233,8 @@
SELECT
frd.date,
IFNULL(SUM(frd.write_bytes),0) AS 'upFlowBytes',
IFNULL(SUM(frd.read_bytes),0) AS 'downFlowBytes'
COALESCE(SUM(frd.write_bytes),0) AS upFlowBytes,
COALESCE(SUM(frd.read_bytes),0) AS downFlowBytes
FROM flow_report_day frd
WHERE frd.date >= #{beginDate} AND frd.date &lt;= #{curDayBeginDate}
GROUP BY frd.date
@@ -242,9 +242,9 @@
UNION ALL
SELECT
#{curDayBeginDate} AS 'date',
IFNULL(SUM(frm.write_bytes),0) AS 'upFlowBytes',
IFNULL(SUM(frm.read_bytes),0) AS 'downFlowBytes'
#{curDayBeginDate} AS date,
COALESCE(SUM(frm.write_bytes),0) AS upFlowBytes,
COALESCE(SUM(frm.read_bytes),0) AS downFlowBytes
FROM flow_report_minute frm
WHERE frm.date >= #{curDayBeginDate} AND frm.date &lt;= #{curDate}
@@ -1,24 +1,24 @@
##########################################################
#
CREATE TABLE IF NOT EXISTS `user` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL,
`login_name` VARCHAR(50) NOT NULL,
`login_password` VARCHAR(255) NOT NULL,
`enable` INTEGER(2) NOT NULL,
`create_time` INTEGER(20) NOT NULL,
`update_time` INTEGER(20) NOT NULL
`create_time` TIMESTAMP NOT NULL,
`update_time` TIMESTAMP NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS I_user_login_name ON `user` (login_name ASC);
#token表
CREATE TABLE IF NOT EXISTS `user_token` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
`token` VARCHAR(50) NOT NULL,
`user_id` INTEGER NOT NULL,
`expiration_time` INTEGER NOT NULL,
`create_time` INTEGER NOT NULL,
`update_time` INTEGER NOT NULL
`expiration_time` TIMESTAMP NOT NULL,
`create_time` TIMESTAMP NOT NULL,
`update_time` TIMESTAMP NOT NULL
);
CREATE INDEX IF NOT EXISTS I_user_token_user_id ON user_token(user_id);
CREATE INDEX IF NOT EXISTS I_user_token_token ON user_token(token);
@@ -26,42 +26,42 @@ CREATE INDEX IF NOT EXISTS I_user_token_expiration_time ON user_token(expiration
#
CREATE TABLE IF NOT EXISTS `port_pool` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
`group_id` INTEGER NOT NULL DEFAULT 1,
`port` INTEGER NOT NULL,
`enable` INTEGER(2) NOT NULL,
`update_time` INTEGER(20) NOT NULL,
`create_time` INTEGER(20) NOT NULL
`update_time` TIMESTAMP NOT NULL,
`create_time` TIMESTAMP NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS I_port_pool_port ON port_pool (port ASC);
#
CREATE TABLE IF NOT EXISTS `port_group` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`possessor_type` INTEGER NOT NULL DEFAULT '0',
`possessor_id` INTEGER NOT NULL DEFAULT '-1',
`enable` INTEGER NOT NULL,
`create_time` datetime(3) NOT NULL,
`update_time` datetime(3) NOT NULL
`create_time` TIMESTAMP NOT NULL,
`update_time` TIMESTAMP NOT NULL
);
##########################################################
#license表
CREATE TABLE IF NOT EXISTS `license` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL,
`key` VARCHAR(100) NOT NULL,
`user_id` INTEGER NOT NULL,
`is_online` INTEGER(2) NOT NULL,
`enable` INTEGER(2) NOT NULL,
`create_time` INTEGER(20) NOT NULL,
`update_time` INTEGER(20) NOT NULL
`create_time` TIMESTAMP NOT NULL,
`update_time` TIMESTAMP NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS I_license_key ON `license` (`key` ASC);
#
CREATE TABLE IF NOT EXISTS `port_mapping` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
`license_id` INTEGER(20) NOT NULL,
`protocal` VARCHAR(10) NOT NULL DEFAULT 'TCP',
`subdomain` VARCHAR(50) DEFAULT NULL,
@@ -70,37 +70,39 @@ CREATE TABLE IF NOT EXISTS `port_mapping` (
`client_port` INTEGER NOT NULL,
`is_online` INTEGER(2) NOT NULL,
`description` VARCHAR(100) DEFAULT NULL,
`proxy_responses` INTEGER(20) NOT NULL DEFAULT 0,
`proxy_timeout_ms` INTEGER(20) NOT NULL DEFAULT 0,
`enable` INTEGER(2) NOT NULL,
`create_time` INTEGER(20) NOT NULL,
`update_time` INTEGER(20) NOT NULL
`create_time` TIMESTAMP NOT NULL,
`update_time` TIMESTAMP NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS I_port_mapping_server_port ON port_mapping (server_port ASC);
##########################################################
#
CREATE TABLE IF NOT EXISTS `user_login_record` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
`user_id` INTEGER NOT NULL,
`ip` VARCHAR(50) NOT NULL,
`token` VARCHAR(100) NOT NULL,
`type` INTEGER(2) NOT NULL,
`create_time` INTEGER(20) NOT NULL
`create_time` TIMESTAMP NOT NULL
);
#
CREATE TABLE IF NOT EXISTS `client_connect_record` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
`ip` VARCHAR(50) NOT NULL,
`license_id` INTEGER(20) DEFAULT NULL,
`type` INTEGER(2) NOT NULL,
`msg` VARCHAR(512) DEFAULT NULL,
`code` INTEGER(2) NOT NULL,
`err` VARCHAR(512) DEFAULT NULL,
`create_time` INTEGER(20) NOT NULL
`create_time` TIMESTAMP NOT NULL
);
##########################################################
#
CREATE TABLE IF NOT EXISTS `job_info` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
`desc` VARCHAR(255) NOT NULL,
`handler` VARCHAR(255) NOT NULL,
`cron` VARCHAR(128) NOT NULL ,
@@ -108,21 +110,21 @@ CREATE TABLE IF NOT EXISTS `job_info` (
`alarm_email` VARCHAR(255) DEFAULT NULL,
`alarm_ding` VARCHAR(255) DEFAULT NULL,
`enable` INTEGER(2) NOT NULL,
`create_time` INTEGER(20) NOT NULL,
`update_time` INTEGER(20) NOT NULL
`create_time` TIMESTAMP NOT NULL,
`update_time` TIMESTAMP NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS I_job_info_handler ON `job_info` (`handler` ASC);
#
CREATE TABLE IF NOT EXISTS `job_log` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
`job_id` INTEGER(20) NOT NULL,
`handler` VARCHAR(255) NOT NULL,
`param` VARCHAR(512) DEFAULT NULL,
`code` INTEGER(20) NOT NULL,
`msg` TEXT DEFAULT NULL,
`alarm_status` INTEGER(2) NOT NULL DEFAULT '0',
`create_time` INTEGER(20) NOT NULL
`create_time` TIMESTAMP NOT NULL
);
CREATE INDEX IF NOT EXISTS I_job_log_create_time ON job_log(create_time);
CREATE INDEX IF NOT EXISTS I_job_log_code ON job_log(code);
@@ -130,14 +132,14 @@ CREATE INDEX IF NOT EXISTS I_job_log_code ON job_log(code);
##########################################################
#-(24)
CREATE TABLE IF NOT EXISTS `flow_report_minute` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
`user_id` INTEGER(20) NOT NULL,
`license_id` INTEGER(20) NOT NULL,
`write_bytes` INTEGER(20) NOT NULL,
`read_bytes` INTEGER(20) NOT NULL,
`date` INTEGER(20) NOT NULL,
`date` TIMESTAMP NOT NULL,
`date_str` VARCHAR(20) NOT NULL,
`create_time` INTEGER(20) NOT NULL
`create_time` TIMESTAMP NOT NULL
);
CREATE INDEX IF NOT EXISTS I_flow_report_minute_create_time ON flow_report_minute(create_time);
CREATE INDEX IF NOT EXISTS I_flow_report_minute_date ON flow_report_minute(`date`);
@@ -146,14 +148,14 @@ CREATE INDEX IF NOT EXISTS I_flow_report_minute_license_id ON flow_report_minute
#-(60)
CREATE TABLE IF NOT EXISTS `flow_report_hour` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
`user_id` INTEGER(20) NOT NULL,
`license_id` INTEGER(20) NOT NULL,
`write_bytes` INTEGER(20) NOT NULL,
`read_bytes` INTEGER(20) NOT NULL,
`date` INTEGER(20) NOT NULL,
`date` TIMESTAMP NOT NULL,
`date_str` VARCHAR(20) NOT NULL,
`create_time` INTEGER(20) NOT NULL
`create_time` TIMESTAMP NOT NULL
);
CREATE INDEX IF NOT EXISTS I_flow_report_hour_create_time ON flow_report_hour(create_time);
CREATE INDEX IF NOT EXISTS I_flow_report_hour_date ON flow_report_hour(`date`);
@@ -162,14 +164,14 @@ CREATE INDEX IF NOT EXISTS I_flow_report_hour_license_id ON flow_report_hour(`li
#-(1)
CREATE TABLE IF NOT EXISTS `flow_report_day` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
`user_id` INTEGER(20) NOT NULL,
`license_id` INTEGER(20) NOT NULL,
`write_bytes` INTEGER(20) NOT NULL,
`read_bytes` INTEGER(20) NOT NULL,
`date` INTEGER(20) NOT NULL,
`date` TIMESTAMP NOT NULL,
`date_str` VARCHAR(20) NOT NULL,
`create_time` INTEGER(20) NOT NULL
`create_time` TIMESTAMP NOT NULL
);
CREATE INDEX IF NOT EXISTS I_flow_report_day_create_time ON flow_report_day(create_time);
CREATE INDEX IF NOT EXISTS I_flow_report_day_date ON flow_report_day(`date`);
@@ -178,16 +180,16 @@ CREATE INDEX IF NOT EXISTS I_flow_report_day_license_id ON flow_report_day(`lice
#-()
CREATE TABLE IF NOT EXISTS `flow_report_month` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
`user_id` INTEGER(20) NOT NULL,
`license_id` INTEGER(20) NOT NULL,
`write_bytes` INTEGER(20) NOT NULL,
`read_bytes` INTEGER(20) NOT NULL,
`date` INTEGER(20) NOT NULL,
`date` TIMESTAMP NOT NULL,
`date_str` VARCHAR(20) NOT NULL,
`create_time` INTEGER(20) NOT NULL
`create_time` TIMESTAMP NOT NULL
);
CREATE INDEX IF NOT EXISTS I_flow_report_month_create_time ON flow_report_month(create_time);
CREATE INDEX IF NOT EXISTS I_flow_report_month_date ON flow_report_month(`date`);
CREATE INDEX IF NOT EXISTS I_flow_report_month_user_id ON flow_report_month(`user_id`);
CREATE INDEX IF NOT EXISTS I_flow_report_month_license_id ON flow_report_month(`license_id`);
CREATE INDEX IF NOT EXISTS I_flow_report_month_license_id ON flow_report_month(`license_id`);
@@ -1,13 +1,13 @@
#job_qrtz_trigger_info
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
(1, '示例Job', 'DemoJob', '0/10 * * * * ?', '{"a":101}', 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(1, '示例Job', 'DemoJob', '0/10 * * * * ?', '{"a":101}', 1, now(), now());
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
(2, '数据清理任务', 'DataCleanJob', '0 0 1 * * ?', '', 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(2, '数据清理任务', 'DataCleanJob', '0 0 1 * * ?', '', 1, now(), now());
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
(3, '流量统计报表-分钟', 'FlowReportForMinuteJob', '0 */1 * * * ?', '', 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(3, '流量统计报表-分钟', 'FlowReportForMinuteJob', '0 */1 * * * ?', '', 1, now(), now());
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
(4, '流量统计报表-小时', 'FlowReportForHourJob', '0 0 */1 * * ?', '', 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(4, '流量统计报表-小时', 'FlowReportForHourJob', '0 0 */1 * * ?', '', 1, now(), now());
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
(5, '流量统计报表-天', 'FlowReportForDayJob', '0 0 1 * * ?', '', 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(5, '流量统计报表-天', 'FlowReportForDayJob', '0 0 1 * * ?', '', 1, now(), now());
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
(6, '流量统计报表-月', 'FlowReportForMonthJob', '0 30 1 1 * ?', '', 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(6, '流量统计报表-月', 'FlowReportForMonthJob', '0 30 1 1 * ?', '', 1, now(), now());
@@ -1,3 +1,3 @@
#license
INSERT INTO license(`id`, `name`, `key`, `user_id`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
(1, '我的mac', 'b0a907332b474b25897c4dcb31fc7eb6', 1, 2, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(1, '我的mac', 'b0a907332b474b25897c4dcb31fc7eb6', 1, 2, 1, now(), now());
@@ -1,3 +1,3 @@
#port_group
INSERT INTO `port_group`(`id`,`name`,`possessor_type`,`possessor_id`,`enable`,`create_time`,`update_time`) VALUES
(1, '全局(默认)', 0, -1, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(1, '全局(默认)', 0, -1, 1, now(), now());
@@ -1,7 +1,7 @@
#port_mapping
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`,`client_ip`, `client_port`, `is_online`, `enable`,`description`,`create_time`, `update_time`) VALUES
(1, 1, 9101, 'HTTP', 'test1', '127.0.0.1', 8080, 2, 1,'test1', STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(1, 1, 9101, 'HTTP', 'test1', '127.0.0.1', 8080, 2, 1,'test1', now(), now());
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`,`description`, `create_time`, `update_time`) VALUES
(2, 1, 9102, 'TCP', '', '127.0.0.1', 3306, 2, 1,'test2', STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(2, 1, 9102, 'TCP', '', '127.0.0.1', 3306, 2, 1,'test2', now(), now());
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`,`description`, `create_time`, `update_time`) VALUES
(3, 1, 9103, 'HTTP', 'test2', '127.0.0.1', 8081, 2, 1,'test3', STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(3, 1, 9103, 'HTTP', 'test2', '127.0.0.1', 8081, 2, 1,'test3', now(), now());
@@ -1,41 +1,41 @@
#
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(1, 1, 9101, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(1, 1, 9101, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(2, 1, 9102, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(2, 1, 9102, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(3, 1, 9103, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(3, 1, 9103, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(4, 1, 9104, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(4, 1, 9104, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(5, 1, 9105, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(5, 1, 9105, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(6, 1, 9106, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(6, 1, 9106, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(7, 1, 9107, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(7, 1, 9107, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(8, 1, 9108, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(8, 1, 9108, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(9, 1, 9109, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(9, 1, 9109, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(10, 1, 9110, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(10, 1, 9110, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(11, 1, 9111, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(11, 1, 9111, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(12, 1, 9112, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(12, 1, 9112, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(13, 1, 9113, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(13, 1, 9113, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(14, 1, 9114, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(14, 1, 9114, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(15, 1, 9115, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(15, 1, 9115, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(16, 1, 9116, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(16, 1, 9116, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(17, 1, 9117, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(17, 1, 9117, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(18, 1, 9118, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(18, 1, 9118, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(19, 1, 9119, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(19, 1, 9119, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(20, 1, 9120, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(20, 1, 9120, 1, now(), now());
@@ -0,0 +1,2 @@
ALTER TABLE port_mapping ADD `proxy_responses` INTEGER(20) NOT NULL DEFAULT 0;
ALTER TABLE port_mapping ADD `proxy_timeout_ms` INTEGER(20) NOT NULL DEFAULT 0;
@@ -1,5 +1,5 @@
# 6613b92b77056faeb72068f184ed4c4f
INSERT INTO `user`(`id`, `name`,`login_name`,`login_password`,`enable`,`create_time`, `update_time`) VALUES
(1, '管理员', 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(1, '管理员', 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, now(), now());
INSERT INTO `user`(`id`, `name`,`login_name`,`login_password`,`enable`,`create_time`, `update_time`) VALUES
(2, '游客', 'visitor', 'e10adc3949ba59abbe56e057f20f883e', 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
(2, '游客', 'visitor', 'e10adc3949ba59abbe56e057f20f883e', 1, now(), now());
@@ -76,6 +76,8 @@ CREATE TABLE IF NOT EXISTS `port_mapping` (
`client_port` int NOT NULL COMMENT '客户端端口',
`is_online` int NOT NULL COMMENT '是否在线(1、在线 2、离线)',
`description` varchar(100) DEFAULT NULL COMMENT '描述',
`proxy_responses` int NOT NULL DEFAULT 0 COMMENT '代理响应数据包数量',
`proxy_timeout_ms` int NOT NULL DEFAULT 0 COMMENT '代理超时毫秒数',
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
@@ -204,4 +206,4 @@ CREATE TABLE IF NOT EXISTS `flow_report_month` (
KEY `I_flow_report_month_date` (`date`) USING BTREE,
KEY `I_flow_report_month_user_id` (`user_id`),
KEY `I_flow_report_month_license_id` (`license_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
@@ -76,6 +76,8 @@ CREATE TABLE IF NOT EXISTS `port_mapping` (
`client_port` int NOT NULL COMMENT '客户端端口',
`is_online` int NOT NULL COMMENT '是否在线(1、在线 2、离线)',
`description` varchar(100) DEFAULT NULL COMMENT '描述',
`proxy_responses` int NOT NULL DEFAULT 0 COMMENT '代理响应数据包数量',
`proxy_timeout_ms` int NOT NULL DEFAULT 0 COMMENT '代理超时毫秒数',
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
@@ -204,4 +206,4 @@ CREATE TABLE IF NOT EXISTS `flow_report_month` (
KEY `I_flow_report_month_date` (`date`) USING BTREE,
KEY `I_flow_report_month_user_id` (`user_id`),
KEY `I_flow_report_month_license_id` (`license_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
@@ -0,0 +1,2 @@
ALTER TABLE port_mapping ADD `proxy_responses` int NOT NULL DEFAULT 0 COMMENT '代理响应数据包数量';
ALTER TABLE port_mapping ADD `proxy_timeout_ms` int NOT NULL DEFAULT 0 COMMENT '代理超时毫秒数';
@@ -0,0 +1,19 @@
package org.dromara.neutrinoproxy.server;
import org.dromara.neutrinoproxy.core.util.NeutrinoProxyVersion;
import org.dromara.neutrinoproxy.server.app.AppBaseTest;
import org.junit.Assert;
import org.junit.Test;
/**
* @author songyinyin
* @since 2023/10/31 11:51
*/
public class VersionTest extends AppBaseTest {
@Test
public void testVersion() {
Assert.assertNotNull(NeutrinoProxyVersion.getVersion());
System.out.println("version: " + NeutrinoProxyVersion.getVersion());
}
}
@@ -0,0 +1,15 @@
package org.dromara.neutrinoproxy.server.app;
import org.dromara.neutrinoproxy.server.ProxyServer;
import org.junit.runner.RunWith;
import org.noear.solon.test.SolonJUnit4ClassRunner;
import org.noear.solon.test.SolonTest;
/**
* @author songyinyin
* @since 2023/10/31 11:56
*/
@SolonTest(value = ProxyServer.class, properties = "server.port=18888")
@RunWith(SolonJUnit4ClassRunner.class)
public class AppBaseTest {
}
@@ -30,7 +30,9 @@ module.exports = {
{text: '后台操作指南', link: '/pages/793dcc/'},
]
},
{text: '演示', link: '/pages/db78e2/'},
{text: '案例', link: '/pages/5d571c/'},
{text: '支持', link: '/pages/1ab20b/'},
{text: '更新记录', link: '/pages/ff3519/'},
{text: 'Gitee', link: 'https://gitee.com/dromara/neutrino-proxy'},
],
sidebarDepth: 2, // 侧边栏显示深度,默认1,最大2(显示到h3标题)
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@@ -0,0 +1,11 @@
<svg width="206" height="53" viewBox="0 0 206 53" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_501_120)">
<path d="M59.0087 36H54.5243V13.5938H59.0087V22.5625H67.9618V13.5938H72.4462V36H67.9618V27.0469H59.0087V36ZM82.5712 31.9062C82.7378 31.9583 82.9045 31.9948 83.0712 32.0156C83.2378 32.026 83.4045 32.0312 83.5712 32.0312C83.9878 32.0312 84.3889 31.974 84.7743 31.8594C85.1597 31.7448 85.5191 31.5833 85.8524 31.375C86.1962 31.1562 86.4983 30.8958 86.7587 30.5938C87.0295 30.2812 87.2483 29.9375 87.4149 29.5625L90.5399 32.7031C90.1441 33.2656 89.6858 33.7708 89.1649 34.2188C88.6545 34.6667 88.0972 35.0469 87.493 35.3594C86.8993 35.6719 86.2691 35.9062 85.6024 36.0625C84.9462 36.2292 84.2691 36.3125 83.5712 36.3125C82.3941 36.3125 81.2847 36.0938 80.243 35.6562C79.2118 35.2188 78.3055 34.6094 77.5243 33.8281C76.7535 33.0469 76.1441 32.1198 75.6962 31.0469C75.2483 29.9635 75.0243 28.776 75.0243 27.4844C75.0243 26.1615 75.2483 24.9531 75.6962 23.8594C76.1441 22.7656 76.7535 21.8333 77.5243 21.0625C78.3055 20.2917 79.2118 19.6927 80.243 19.2656C81.2847 18.8385 82.3941 18.625 83.5712 18.625C84.2691 18.625 84.9514 18.7083 85.618 18.875C86.2847 19.0417 86.9149 19.2812 87.5087 19.5938C88.1128 19.9062 88.6753 20.2917 89.1962 20.75C89.717 21.1979 90.1753 21.7031 90.5712 22.2656L82.5712 31.9062ZM84.7587 23.1094C84.5608 23.0365 84.3628 22.9896 84.1649 22.9688C83.9774 22.9479 83.7795 22.9375 83.5712 22.9375C82.9878 22.9375 82.4358 23.0469 81.9149 23.2656C81.4045 23.474 80.9566 23.776 80.5712 24.1719C80.1962 24.5677 79.8993 25.0469 79.6805 25.6094C79.4618 26.1615 79.3524 26.7865 79.3524 27.4844C79.3524 27.6406 79.3576 27.8177 79.368 28.0156C79.3889 28.2135 79.4149 28.4167 79.4462 28.625C79.4878 28.8229 79.5347 29.0156 79.5868 29.2031C79.6389 29.3906 79.7066 29.5573 79.7899 29.7031L84.7587 23.1094ZM96.993 36H92.7274V19.2656H93.7587L95.1649 21.25C95.8524 20.625 96.6337 20.1458 97.5087 19.8125C98.3837 19.4688 99.2899 19.2969 100.227 19.2969H103.993V23.5469H100.227C99.7795 23.5469 99.3576 23.6302 98.9618 23.7969C98.566 23.9635 98.2222 24.1927 97.9305 24.4844C97.6389 24.776 97.4097 25.1198 97.243 25.5156C97.0764 25.9115 96.993 26.3333 96.993 26.7812V36ZM114.368 36C113.337 36 112.368 35.8073 111.462 35.4219C110.556 35.026 109.759 34.4896 109.071 33.8125C108.394 33.125 107.858 32.3281 107.462 31.4219C107.076 30.5156 106.884 29.5469 106.884 28.5156V23.5469H104.806V19.2969H106.884V12.6094H111.134V19.2969H117.602V23.5469H111.134V28.5156C111.134 28.9635 111.217 29.3854 111.384 29.7812C111.55 30.1667 111.78 30.5052 112.071 30.7969C112.363 31.0885 112.707 31.3229 113.102 31.5C113.498 31.6667 113.92 31.75 114.368 31.75H117.602V36H114.368ZM133.071 36H119.118L126.165 23.5469H119.118V19.2969H133.071L126.024 31.75H133.071V36ZM153.821 29.2812C153.821 30.2083 153.644 31.0781 153.29 31.8906C152.936 32.7031 152.451 33.4167 151.837 34.0312C151.233 34.6354 150.524 35.1146 149.712 35.4688C148.899 35.8229 148.03 36 147.102 36H135.899V13.5938H147.102C148.03 13.5938 148.899 13.7708 149.712 14.125C150.524 14.4792 151.233 14.9635 151.837 15.5781C152.451 16.1823 152.936 16.8906 153.29 17.7031C153.644 18.5156 153.821 19.3854 153.821 20.3125C153.821 20.7292 153.764 21.1562 153.649 21.5938C153.535 22.0312 153.373 22.4531 153.165 22.8594C152.957 23.2656 152.707 23.6354 152.415 23.9688C152.123 24.3021 151.8 24.5781 151.446 24.7969C151.811 24.9948 152.139 25.2656 152.431 25.6094C152.722 25.9427 152.972 26.3125 153.181 26.7188C153.389 27.125 153.545 27.5521 153.649 28C153.764 28.4375 153.821 28.8646 153.821 29.2812ZM140.384 31.5156H147.102C147.415 31.5156 147.707 31.4583 147.977 31.3438C148.248 31.2292 148.483 31.0729 148.681 30.875C148.889 30.6667 149.05 30.4271 149.165 30.1562C149.28 29.8854 149.337 29.5938 149.337 29.2812C149.337 28.9688 149.28 28.6771 149.165 28.4062C149.05 28.1354 148.889 27.901 148.681 27.7031C148.483 27.4948 148.248 27.3333 147.977 27.2188C147.707 27.1042 147.415 27.0469 147.102 27.0469H140.384V31.5156ZM140.384 22.5625H147.102C147.415 22.5625 147.707 22.5052 147.977 22.3906C148.248 22.276 148.483 22.1198 148.681 21.9219C148.889 21.7135 149.05 21.474 149.165 21.2031C149.28 20.9219 149.337 20.625 149.337 20.3125C149.337 20 149.28 19.7083 149.165 19.4375C149.05 19.1667 148.889 18.9323 148.681 18.7344C148.483 18.526 148.248 18.3646 147.977 18.25C147.707 18.1354 147.415 18.0781 147.102 18.0781H140.384V22.5625ZM163.415 31.9062C163.582 31.9583 163.748 31.9948 163.915 32.0156C164.082 32.026 164.248 32.0312 164.415 32.0312C164.832 32.0312 165.233 31.974 165.618 31.8594C166.003 31.7448 166.363 31.5833 166.696 31.375C167.04 31.1562 167.342 30.8958 167.602 30.5938C167.873 30.2812 168.092 29.9375 168.259 29.5625L171.384 32.7031C170.988 33.2656 170.53 33.7708 170.009 34.2188C169.498 34.6667 168.941 35.0469 168.337 35.3594C167.743 35.6719 167.113 35.9062 166.446 36.0625C165.79 36.2292 165.113 36.3125 164.415 36.3125C163.238 36.3125 162.128 36.0938 161.087 35.6562C160.056 35.2188 159.149 34.6094 158.368 33.8281C157.597 33.0469 156.988 32.1198 156.54 31.0469C156.092 29.9635 155.868 28.776 155.868 27.4844C155.868 26.1615 156.092 24.9531 156.54 23.8594C156.988 22.7656 157.597 21.8333 158.368 21.0625C159.149 20.2917 160.056 19.6927 161.087 19.2656C162.128 18.8385 163.238 18.625 164.415 18.625C165.113 18.625 165.795 18.7083 166.462 18.875C167.128 19.0417 167.759 19.2812 168.352 19.5938C168.957 19.9062 169.519 20.2917 170.04 20.75C170.561 21.1979 171.019 21.7031 171.415 22.2656L163.415 31.9062ZM165.602 23.1094C165.405 23.0365 165.207 22.9896 165.009 22.9688C164.821 22.9479 164.623 22.9375 164.415 22.9375C163.832 22.9375 163.28 23.0469 162.759 23.2656C162.248 23.474 161.8 23.776 161.415 24.1719C161.04 24.5677 160.743 25.0469 160.524 25.6094C160.306 26.1615 160.196 26.7865 160.196 27.4844C160.196 27.6406 160.201 27.8177 160.212 28.0156C160.233 28.2135 160.259 28.4167 160.29 28.625C160.332 28.8229 160.378 29.0156 160.431 29.2031C160.483 29.3906 160.55 29.5573 160.634 29.7031L165.602 23.1094ZM189.649 36H188.618L186.962 33.7031C186.556 34.0677 186.123 34.4115 185.665 34.7344C185.217 35.0469 184.743 35.3229 184.243 35.5625C183.743 35.7917 183.227 35.974 182.696 36.1094C182.175 36.2448 181.644 36.3125 181.102 36.3125C179.925 36.3125 178.816 36.1146 177.774 35.7188C176.743 35.3229 175.837 34.75 175.056 34C174.285 33.2396 173.675 32.3125 173.227 31.2188C172.78 30.125 172.556 28.8802 172.556 27.4844C172.556 26.1823 172.78 24.9896 173.227 23.9062C173.675 22.8125 174.285 21.875 175.056 21.0938C175.837 20.3125 176.743 19.7083 177.774 19.2812C178.816 18.8438 179.925 18.625 181.102 18.625C181.644 18.625 182.181 18.6927 182.712 18.8281C183.243 18.9635 183.759 19.151 184.259 19.3906C184.759 19.6302 185.233 19.9115 185.681 20.2344C186.139 20.5573 186.566 20.9062 186.962 21.2812L188.618 19.2969H189.649V36ZM185.352 27.4844C185.352 26.901 185.238 26.3385 185.009 25.7969C184.79 25.2448 184.488 24.7604 184.102 24.3438C183.717 23.9167 183.264 23.5781 182.743 23.3281C182.233 23.0677 181.686 22.9375 181.102 22.9375C180.519 22.9375 179.967 23.0365 179.446 23.2344C178.936 23.4323 178.488 23.724 178.102 24.1094C177.727 24.4948 177.431 24.974 177.212 25.5469C176.993 26.1094 176.884 26.7552 176.884 27.4844C176.884 28.2135 176.993 28.8646 177.212 29.4375C177.431 30 177.727 30.474 178.102 30.8594C178.488 31.2448 178.936 31.5365 179.446 31.7344C179.967 31.9323 180.519 32.0312 181.102 32.0312C181.686 32.0312 182.233 31.9062 182.743 31.6562C183.264 31.3958 183.717 31.0573 184.102 30.6406C184.488 30.2135 184.79 29.7292 185.009 29.1875C185.238 28.6354 185.352 28.0677 185.352 27.4844ZM201.306 36C200.274 36 199.306 35.8073 198.399 35.4219C197.493 35.026 196.696 34.4896 196.009 33.8125C195.332 33.125 194.795 32.3281 194.399 31.4219C194.014 30.5156 193.821 29.5469 193.821 28.5156V23.5469H191.743V19.2969H193.821V12.6094H198.071V19.2969H204.54V23.5469H198.071V28.5156C198.071 28.9635 198.155 29.3854 198.321 29.7812C198.488 30.1667 198.717 30.5052 199.009 30.7969C199.3 31.0885 199.644 31.3229 200.04 31.5C200.436 31.6667 200.858 31.75 201.306 31.75H204.54V36H201.306Z" fill="#B421CC"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.8453 0C11.1261 0 0 10.745 0 24.001C0 33.826 6.11788 42.271 14.8744 45.982C14.804 44.308 14.862 42.293 15.304 40.47C15.7823 38.522 17.7077 27.5 17.7077 27.5C17.7077 27.5 17.7077 25.861 17.7077 23.595C17.7077 20.039 20.1176 21.819 22.7739 21.819L26.397 23C33.1256 23 43.995 23 43.995 23C43.995 23 34.1018 15.5 27.4322 15.5C19.8971 15.5 19.1508 15.5 19.1508 15.5C19.1508 17.594 13.9749 18 13.9749 18L11.9045 15.5L9.31658 14.5C9.31658 7.684 13.9956 8.069 25.7987 8.069C35.285 8.069 49.6884 14.5 43.995 23C43.995 23 34.3875 23 26.397 23C23.718 23 29.3327 31.09 28.4673 29.5C28.4673 29.5 28.9849 33 22.7739 37C22.248 38.85 18.7481 45.575 17.805 47.015C20.04 47.653 22.4012 48 24.8453 48C38.5655 48 49.6884 37.255 49.6884 24.001C49.6884 10.745 38.5655 0 24.8453 0Z" fill="#B421CC"/>
</g>
<defs>
<clipPath id="clip0_501_120">
<rect width="206" height="53" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="380px" height="120px" viewBox="0 0 380 120" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>jpom-logo</title>
<defs>
<linearGradient x1="0.0077378419%" y1="0%" x2="99.9922622%" y2="100%" id="linearGradient-1">
<stop stop-color="#5988FF" offset="0%"></stop>
<stop stop-color="#0D58FF" offset="100%"></stop>
</linearGradient>
<path d="M97.0595063,0 C98.6792225,0 99.9922619,1.31303939 99.9922619,2.93275555 L99.9922619,83.9820475 C98.0456091,82.5565127 95.6960926,81.7868903 93.2832934,81.7844154 L92.9319967,81.7897766 C90.0097544,81.8790045 87.225876,83.0788415 85.1523588,85.1523588 C82.9959008,87.3088167 81.7844154,90.2336013 81.7844154,93.2832934 C81.7887374,95.7018607 82.5669783,98.0555651 84.0052619,100 L2.93275555,100 C1.31303939,100 0,98.6869606 0,97.0672444 L0,23.3072816 C0,21.6875654 1.31303939,20.374526 2.93275555,20.374526 L40.9038149,20.374526 C41.6822604,20.3724639 42.429417,20.6807875 42.979863,21.2312335 C43.530309,21.7816795 43.8386326,22.5288361 43.8365705,23.3072816 L43.8365705,78.9367794 L30.5114911,78.9367794 C29.733676,78.9367794 28.9877182,78.6277935 28.4377198,78.0777952 C27.8877214,77.5277968 27.5787356,76.781839 27.5787356,76.0040238 L27.5787356,34.4192525 C27.5787412,32.7982798 26.2669471,31.4830245 24.64598,31.4787588 L19.113209,31.4787588 C18.3340513,31.4787561 17.5869184,31.7888039 17.036697,32.3404771 C16.4864757,32.8921502 16.1783976,33.6400975 16.1804535,34.4192525 L16.1804535,73.9456782 C16.1783993,77.9092655 17.7520185,81.7111045 20.5546982,84.5137843 C23.357378,87.3164641 27.1592171,88.8900833 31.1228043,88.8880291 L68.8849338,88.8880291 C72.8478917,88.8880291 76.6485371,87.3137502 79.4507714,84.5115159 C82.2530058,81.7092815 83.8272847,77.9086361 83.8272847,73.9456782 L83.8272847,26.0543218 C83.8230153,17.8036584 77.1355972,11.1162403 68.8849338,11.1119709 L2.93275555,11.1119709 C1.31303939,11.1119709 0,9.79893152 0,8.17921535 L0,2.93275555 C0,1.31303939 1.31303939,0 2.93275555,0 Z M69.5271996,20.4054786 C71.1481666,20.4097443 72.4599551,21.7249996 72.4599551,23.3459723 L72.4599551,76.0427145 C72.4599551,76.8205297 72.1509693,77.5664875 71.6009709,78.1164859 C71.0509726,78.6664842 70.3050147,78.9754701 69.5271996,78.9754701 L59.1967809,78.9754701 C57.5758082,78.9754701 56.260553,77.6636816 56.2562872,76.0427145 L56.2562872,34.3573474 C56.2583431,33.5781924 55.950265,32.830245 55.4000437,32.2785719 C54.8498223,31.7268988 54.1026894,31.416851 53.3235317,31.4168537 L49.2842219,31.4168537 C47.6645058,31.4168537 46.3514664,30.1038143 46.3514664,28.4840981 L46.3514664,23.3459723 C46.3514664,21.7249996 47.6632549,20.4097443 49.2842219,20.4054786 Z" id="path-2"></path>
<filter x="-6.5%" y="-5.5%" width="113.0%" height="113.0%" filterUnits="objectBoundingBox" id="filter-3">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0.239215686 0 0 0 0 0.466666667 0 0 0 0 1 0 0 0 0.4 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter>
<linearGradient x1="0%" y1="41.21675%" x2="100%" y2="58.78325%" id="linearGradient-4">
<stop stop-color="#5988FF" offset="0%"></stop>
<stop stop-color="#0D58FF" offset="100%"></stop>
</linearGradient>
<path d="M321.177106,22 C326.648252,22 330.951915,23.8568225 334.001288,27.5645829 C337.018271,31.2329589 338.5054,36.5562047 338.5054,43.5075594 L338.5054,72.2764579 C338.5054,73.5037494 337.841444,74.4996824 336.640847,75.1857377 C335.575623,75.7944374 334.335668,76.0950324 332.937365,76.0950324 C331.544467,76.0950324 330.325067,75.8136323 329.292975,75.240248 C328.109924,74.5829978 327.466523,73.558321 327.466523,72.2764579 L327.466523,45.8401728 C327.466523,40.8955189 326.694202,37.2501639 325.194838,34.9111564 C323.764436,32.6797287 321.623311,31.5809935 318.650108,31.5809935 C316.79872,31.5809935 314.959692,32.2377892 313.114393,33.5771188 C311.238478,34.9386702 309.704728,36.7913184 308.510889,39.1483861 C307.319542,41.5005314 306.723542,44.0831986 306.723542,46.9092873 L306.723542,72.2764579 C306.723542,73.558321 306.08014,74.5829978 304.89709,75.240248 C303.864998,75.8136323 302.645598,76.0950324 301.2527,76.0950324 C299.859802,76.0950324 298.640401,75.8136323 297.60831,75.240248 C296.425259,74.5829978 295.781857,73.558321 295.781857,72.2764579 L295.781857,45.8401728 C295.781857,40.8955189 295.009536,37.2501639 293.510173,34.9111564 C292.080954,32.6815753 289.908011,31.5809935 286.868251,31.5809935 C285.088109,31.5809935 283.283388,32.2504868 281.43818,33.6195127 C279.556179,35.0158357 278.019002,36.8726239 276.823821,39.2016947 C275.633386,41.5215173 275.038877,44.0215023 275.038877,46.7149028 L275.038877,72.2764579 C275.038877,73.5037494 274.374922,74.4996824 273.174325,75.1857377 C272.111504,75.793064 270.90363,76.0950324 269.568035,76.0950324 C268.169731,76.0950324 266.929777,75.7944374 265.864552,75.1857377 C264.663955,74.4996824 264,73.5037494 264,72.2764579 L264,26.4989201 C264,25.2077738 264.668286,24.181477 265.88847,23.5278073 C266.948186,22.9601019 268.179114,22.6803456 269.568035,22.6803456 C270.71541,22.6803456 271.794876,22.969488 272.789139,23.5431017 C273.934643,24.2039694 274.552916,25.2264969 274.552916,26.4989201 L274.552,30.281 L274.790224,29.9647489 C276.112461,28.2407943 277.63116,26.7395118 279.345238,25.4631222 L279.777828,25.1487113 C282.731605,23.0535904 285.978736,22 289.492441,22 C293.59303,22 297.017667,23.0550649 299.730507,25.1766448 C301.838091,26.8248839 303.45357,29.0787041 304.580218,31.9217647 L304.664,32.141 L304.894304,31.758482 C306.256617,29.541499 308.04526,27.5894747 310.255056,25.9061867 L310.674327,25.5937203 C313.951412,23.204179 317.460734,22 321.177106,22 Z M231.214173,22.2721382 C235.842656,22.2721382 240.114809,23.2813081 244.017009,25.2996873 C247.949275,27.3336181 251.102191,30.4187296 253.463829,34.534728 C255.824475,38.648996 256.99819,43.6792053 256.99819,49.6112311 C256.99819,55.477913 255.824553,60.4910223 253.465574,64.636085 C251.105109,68.7837598 247.953791,71.9011925 244.023018,73.968237 C240.119404,76.0209992 235.845055,77.0475162 231.214173,77.0475162 C226.582371,77.0475162 222.323058,76.0205671 218.450763,73.9665673 C215.78895,72.5546487 213.481953,70.6522464 211.530874,68.2601777 C209.80805,70.6713158 207.747453,72.5853025 205.353205,73.9936837 C201.893057,76.0290653 198.056892,77.0475162 193.863464,77.0475162 C189.54284,77.0475162 185.913019,76.1613627 182.990636,74.373552 C181.090035,73.2108313 179.432511,71.7008619 178.019516,69.8490491 L177.854,69.627 L177.854825,92.1814255 C177.854825,93.3365234 177.266684,94.2866808 176.196546,94.966436 L175.990272,95.0907053 C174.927451,95.6980316 173.719578,96 172.383982,96 C170.985679,96 169.745724,95.699405 168.6805,95.0907053 C167.479903,94.40465 166.815948,93.408717 166.815948,92.1814255 L166.815948,26.7710583 C166.815948,25.479912 167.484234,24.4536152 168.704417,23.7999455 C169.764134,23.2322402 170.995062,22.9524838 172.383982,22.9524838 C173.531358,22.9524838 174.610823,23.2416263 175.605087,23.8152399 C176.750591,24.4761076 177.368863,25.4986352 177.368863,26.7710583 L177.368,30.16 L177.461941,30.0361727 C178.783959,28.309545 180.389965,26.8166359 182.276634,25.5604571 L182.685215,25.2949004 C185.864442,23.2779717 189.597959,22.2721382 193.863464,22.2721382 C198.128773,22.2721382 202.004283,23.3430028 205.46887,25.4828953 C207.771196,26.9049197 209.757739,28.7668286 211.429887,31.0693813 C213.399057,28.641774 215.743808,26.7167895 218.456791,25.3013201 C222.327675,23.2817281 226.584779,22.2721382 231.214173,22.2721382 Z M191.530851,31.6587473 C187.243462,31.6587473 183.896417,33.3099564 181.39759,36.6517607 C178.844201,40.0665344 177.563248,44.3106557 177.563248,49.4168467 C177.563248,55.109921 178.816344,59.5808448 181.296842,62.8575524 C183.727136,66.0679395 187.106609,67.6609071 191.530851,67.6609071 C195.643176,67.6609071 198.975743,66.1216499 201.603835,63.0102305 C203.930389,60.255805 205.247205,56.4902582 205.534777,51.6932527 L205.559596,51.2685058 L205.545125,50.8489568 C205.533272,50.4407123 205.527348,50.0281371 205.527348,49.6112311 C205.527348,49.189798 205.533269,48.7729418 205.545271,48.355724 L205.556796,48.0938945 L205.551214,47.9513662 C205.38458,44.9043393 204.749828,42.2339124 203.652128,39.9339693 C202.366962,37.2412414 200.654503,35.195383 198.511261,33.7766173 C196.374155,32.3619136 194.055203,31.6587473 191.530851,31.6587473 Z M231.214173,31.6587473 C226.836455,31.6587473 223.357717,33.2031872 220.700173,36.3137216 C218.430591,38.9701643 217.111936,42.5417911 216.759905,47.0508924 L216.700355,47.9290956 L216.714085,48.3122437 C216.725839,48.7404962 216.731714,49.1734918 216.731714,49.6112311 C216.731714,50.0470061 216.726182,50.47794 216.71489,50.911505 L216.700355,51.2251958 L216.724381,51.660711 C217.015654,56.4203676 218.347416,60.1767886 220.703261,62.9609692 C223.36111,66.1020631 226.838531,67.6609071 231.214173,67.6609071 C235.591349,67.6609071 239.086753,66.1007268 241.777295,62.9567219 C244.491519,59.7850452 245.862121,55.3498364 245.862121,49.6112311 C245.862121,43.8739162 244.492208,39.456332 241.78041,36.317958 C239.090148,33.2045095 235.593424,31.6587473 231.214173,31.6587473 Z M150.712743,4 C149.259053,4 147.963783,4.29606168 146.840427,4.89518486 C145.554497,5.58101431 144.853132,6.6564405 144.853132,8.01295896 L144.853132,50.3887689 C144.853132,54.9604969 144.458747,58.4784132 143.684829,60.9239931 C142.944236,63.264268 141.87171,64.8087043 140.491802,65.6090509 C139.040305,66.4509193 137.071912,66.8833693 134.578834,66.8833693 C133.004999,66.8833693 131.710962,66.6900075 130.699158,66.3141946 C129.625877,65.9155473 128.398225,65.3094909 127.020608,64.495445 C126.731999,64.3493116 126.45579,64.2036746 126.173893,64.0486339 L125.744519,63.8077828 C125.260149,63.5309997 124.711978,63.2569143 124.099443,62.9846766 C123.527077,62.7889863 123.145105,62.7041037 122.818575,62.7041037 C121.483129,62.7041037 120.469249,63.6183631 119.75338,65.2384268 L119.527332,65.7673253 C119.172059,66.6629279 119,67.5887714 119,68.5637149 C119,70.7152148 119.984373,72.4418969 121.873536,73.6494261 L122.114388,73.7976565 C125.574241,75.9689259 130.225662,77.0475162 136.036717,77.0475162 C140.933982,77.0475162 144.881961,76.1664222 147.872655,74.3788806 C150.905083,72.5663953 153.107534,69.6529168 154.47669,65.6823622 C155.813792,61.8047679 156.475162,56.6460815 156.475162,50.1943844 L156.475162,8.01295896 C156.475162,6.66509921 155.798106,5.59114857 154.548952,4.90196011 C153.45357,4.29761162 152.17012,4 150.712743,4 Z" id="path-5"></path>
<filter x="-3.0%" y="-6.0%" width="105.9%" height="114.1%" filterUnits="objectBoundingBox" id="filter-6">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0.239215686 0 0 0 0 0.466666667 0 0 0 0 1 0 0 0 0.4 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="jpom-logo" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="1" transform="translate(12.000000, 10.000000)" fill-rule="nonzero">
<g id="2">
<use fill="black" fill-opacity="1" filter="url(#filter-3)" xlink:href="#path-2"></use>
<use fill="url(#linearGradient-1)" xlink:href="#path-2"></use>
</g>
<rect id="4" fill="#F74C00" x="345" y="64" width="12" height="12" rx="6"></rect>
<g id="5">
<use fill="black" fill-opacity="1" filter="url(#filter-6)" xlink:href="#path-5"></use>
<use fill="url(#linearGradient-4)" xlink:href="#path-5"></use>
</g>
<rect id="3" fill="#F74C00" x="88" y="88" width="12" height="12" rx="6"></rect>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="_图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 240">
<defs>
<style>
.cls-1 {
fill: #ff9045;
}
.cls-2 {
fill: #333;
}
.cls-3 {
fill: #0079dd;
}
.cls-4 {
fill: #595959;
}
</style>
</defs>
<g>
<g>
<circle class="cls-1" cx="145.28" cy="62.13" r="13.63"/>
<path class="cls-1" d="M154.29,83.78h-18.09c-4.56,0-8.26,3.7-8.26,8.26v92.19c0,4.02,3.26,7.28,7.28,7.28h.07c4.02,0,7.28-3.26,7.28-7.28v-54.05c2.96,0,5.36,2.4,5.36,5.36v16.41c0,4.02,3.26,7.28,7.28,7.28h.07c4.02,0,7.28-3.26,7.28-7.28v-21.16s0-38.75,0-38.75c0-4.56-3.7-8.26-8.26-8.26Z"/>
</g>
<path class="cls-3" d="M117.19,125.56h-1.98c-3.07,0-5.56-2.49-5.56-5.56s2.49-5.56,5.56-5.56h1.98s.04,0,.06,0v-25.71c0-4.77-2.32-9.24-6.21-11.98L50.81,34.3c-4.86-3.42-11.55,.05-11.55,5.99V199.71c0,5.94,6.7,9.41,11.55,5.99l60.23-42.45c3.9-2.75,6.21-7.22,6.21-11.98v-25.71s-.04,0-.06,0Z"/>
<path class="cls-3" d="M239.76,34.3l-60.23,42.45c-3.9,2.75-6.21,7.22-6.21,11.98v25.71s.04,0,.06,0h1.98c3.07,0,5.56,2.49,5.56,5.56s-2.49,5.56-5.56,5.56h-1.98s-.04,0-.06,0v25.71c0,4.77,2.32,9.24,6.21,11.98l60.23,42.45c4.86,3.42,11.55-.05,11.55-5.99V40.29c0-5.94-6.7-9.41-11.55-5.99Z"/>
</g>
<g>
<g>
<path class="cls-2" d="M344.63,60.54v-7.61h8.13v7.61h20.8c4.41,0,7.8,.88,10.16,2.64,3.06,2.31,4.58,5.45,4.58,9.43l-.04,32.47c0,3.86-.78,6.82-2.34,8.87-2.42,3.2-6.01,4.8-10.77,4.8h-22.4l.04,7.52-8.17-.04v-7.48h-20.54c-4.15,0-7.24-.59-9.25-1.77-3.66-2.13-5.49-6.18-5.49-12.15v-30.57c0-4.44,1.12-7.77,3.37-9.99,2.51-2.48,5.89-3.72,10.16-3.72h21.75Zm0,50.07v-41.94h-22.01c-2.05,0-3.47,.55-4.28,1.64-.58,.81-.87,2.08-.87,3.8v30.05c0,1.96,.07,3.13,.22,3.5,.72,1.99,2.31,2.98,4.76,2.98l22.18-.04Zm8.13,0l20.93,.04c4.32,.03,6.49-1.79,6.49-5.45v-30.48c0-2.48-.49-4.11-1.47-4.89-.92-.72-2.48-1.1-4.67-1.12l-21.27-.09v41.99Z"/>
<path class="cls-2" d="M401.72,75.41v-8.78c2.13-1.73,3.62-3.34,4.45-4.84,1.12-1.93,2-4.94,2.64-9.04h8.13c-.46,2.19-.79,3.84-.99,4.93-.81,4.12-2.12,7.46-3.93,10.03-1.87,2.62-4.08,4.64-6.62,6.05-.81,.43-2.03,.98-3.67,1.64Zm0,20.67v-8.86c2.36-1.9,3.91-3.56,4.63-4.97,.89-1.67,1.7-4.63,2.42-8.86h8.17l-.91,4.15v48.86h-8.09v-33.77c-1.93,1.44-4.01,2.59-6.23,3.46Zm17.25,30.31v-8.47c1.35-.78,2.03-1.9,2.03-3.37v-10.59c0-2.6,.88-4.79,2.64-6.57,1.59-1.58,4.53-2.38,8.82-2.38h6.87c6.72,0,10.08,3.19,10.08,9.56v8.86c0,1.73,.23,2.87,.69,3.42,.26,.32,.72,.68,1.38,1.08v8.47c-2.88-.43-5.16-1.3-6.83-2.59-.87-.66-1.59-1.56-2.16-2.68-.81-1.53-1.21-3.07-1.21-4.63v-9.95c0-2.39-1.47-3.59-4.41-3.59h-3.5c-1.47,0-2.51,.32-3.11,.95-.81,.81-1.21,2.05-1.21,3.72v8.87c0,2.79-.88,5.07-2.64,6.83-1.39,1.41-3.86,2.44-7.44,3.07Zm.91-73.68h7.22v15.52c0,1.21,.23,2.02,.69,2.42,.52,.43,1.41,.65,2.68,.65h1.08v-18.59h7.74v18.59h1.12c1.07,0,1.84-.29,2.34-.86s.74-1.17,.74-1.77v-15.96h7.05v17.99c0,3.08-1.11,5.35-3.33,6.79-1.21,.78-3.37,1.17-6.49,1.17h-11.68c-3.2,0-5.59-.81-7.18-2.42-.69-.72-1.14-1.37-1.34-1.95-.43-1.21-.65-3.01-.65-5.4v-16.17Zm0,38.18v-7.22h30.66v7.22h-30.66Zm33.12,35.11v-7.74c2.36-.92,4.08-1.72,5.15-2.38,1.1-.69,2.62-1.92,4.58-3.67-2.02-3.83-3.39-7.16-4.11-9.99-.89-3.34-1.34-6.76-1.34-10.25v-14.87c-1.12,.75-2.45,1.37-3.98,1.86v-8.39c1.38-1.53,2.35-3.56,2.9-6.1,.63-3.08,1.08-7,1.34-11.76h7.26v4.89h17.08v8h-4.2v27.46c0,6.05-1.83,12.44-5.49,19.16,1.44,1.38,2.61,2.41,3.5,3.07,1.27,.89,3.33,1.89,6.18,2.98v7.74c-3.98-1.1-6.99-2.28-9.04-3.55-1.5-.89-3.27-2.29-5.32-4.19-3.08,2.54-5.49,4.24-7.22,5.1-1.53,.81-3.96,1.69-7.31,2.64Zm4.67-49.12h6.66l-.13,12.8c-.03,4.56,1.05,9.7,3.24,15.44,2.02-4.44,3.03-8.62,3.03-12.54v-26.98h-6.74c-1.15,4.24-2.38,7.18-3.68,8.82-.52,.69-1.31,1.51-2.38,2.47Z"/>
<path class="cls-2" d="M494.58,91.89v-8h41.21l21.32-17.81c1.21-1.01,1.82-1.9,1.82-2.68,0-.46-.16-.89-.47-1.3-.66-.83-1.75-1.25-3.24-1.25h-56.56v-8h58.98c3.6,0,6.36,.68,8.26,2.03,2.36,1.67,3.55,3.99,3.55,6.96,0,2.56-1.04,4.97-3.11,7.22-1.21,1.3-2.8,2.75-4.76,4.37l-12.93,10.46h26.12v8h-29.45l.04,21.53c0,3.92-1.21,7.1-3.63,9.56-2.25,2.31-5.61,3.46-10.07,3.46h-16.73v-8.04h15.52c2.45,.03,4.12-.47,5.01-1.51,.81-.95,1.21-2.39,1.21-4.33v-20.71l-42.07,.04Z"/>
<path class="cls-2" d="M587.86,86.78v-10.29c3-2.13,5.17-4.28,6.53-6.44,2.88-4.58,4.96-10.29,6.23-17.12h9.08c-1.24,6.26-2.96,11.93-5.15,17.04v56.38h-8.3v-44.88c-2.02,1.62-3.34,2.61-3.98,2.98-.61,.4-2.08,1.18-4.41,2.34Zm21.02-8.78v-8.13h19.46l-5.06-17.08h8.69l4.8,17.08h30.61v8.13h-28.32l5.1,18.81c1.01,3.72,2.31,7.28,3.89,10.68,1.47,3.14,3.66,5.69,6.57,7.65,3.29,2.22,6.69,3.33,10.2,3.33,.46,0,1.17-.04,2.12-.13v8.09c-4.7-.09-8.53-.68-11.5-1.77-5.51-2.07-9.71-5.65-12.63-10.72-1.87-3.23-3.72-7.91-5.53-14.05l-6.53-21.88h-21.88Zm40.99-16.78v-8h17.51v8h-17.51Z"/>
<path class="cls-2" d="M680.84,124.06v-8h7.05v-24.95l-7.09,.04,.04-7.87h7.05v-20.41h-7.05v-8h22.4v8h-7.35v20.41h7.35v7.83h-7.35v24.95h7.35v8h-22.4Zm26.59,2.2v-8h22.92v-9.25h-20.76v-8h20.76v-7.05l-14.01-.04c-2.48,0-4.44-.88-5.88-2.64-.89-1.12-1.41-2.31-1.56-3.55-.09-.72-.13-1.7-.13-2.94v-23.44c0-2.91,.74-5.1,2.2-6.57,1.38-1.38,3.24-2.08,5.58-2.08h33.25c3.52,0,6.04,.72,7.57,2.16,1.33,1.27,1.99,3.17,1.99,5.71v25.47c0,2.42-.71,4.35-2.12,5.79-1.35,1.38-3.06,2.07-5.1,2.07l-13.79,.04v7.05h20.8v8h-20.8v9.25h22.4v8h-53.31Zm9.34-56.99h13.58v-8.56h-10.25c-1.21,0-2.11,.29-2.68,.86-.43,.43-.65,1.25-.65,2.47v5.23Zm13.58,16.56v-8.6h-13.58v4.71c0,1.56,.23,2.6,.69,3.11,.43,.52,1.24,.78,2.42,.78h10.46Zm8-16.56h13.19v-5.23c.03-2.28-1.41-3.42-4.32-3.42l-8.86,.09v8.56Zm0,16.56h9.64c1.41,0,2.35-.26,2.81-.78,.52-.58,.76-1.54,.74-2.9v-4.93h-13.19v8.6Z"/>
</g>
<g>
<path class="cls-4" d="M315.77,154.13l16.1,23.16h.12v-23.16h3.27v28.46h-3.35l-16.18-23.4h-.12v23.4h-3.27v-28.46h3.43Z"/>
<path class="cls-4" d="M370.12,164.77c1.51,1.99,2.31,4.82,2.39,8.41h-16.26c.16,2.31,.76,4.11,1.87,5.38,1.12,1.28,2.67,1.91,4.63,1.91,1.67,0,3.07-.44,4.1-1.28,.88-.72,1.56-1.79,2.07-3.23h3.19c-.48,1.99-1.39,3.59-2.79,4.86-1.71,1.52-3.91,2.31-6.58,2.31-2.95,0-5.34-1-7.1-2.91-1.83-1.99-2.71-4.62-2.71-7.97,0-3.03,.84-5.58,2.59-7.61,1.75-2.15,4.11-3.23,7.1-3.23,3.31,0,5.82,1.12,7.49,3.35Zm-11.84,1.12c-1.12,1.2-1.76,2.79-1.95,4.82h12.88c-.4-4.42-2.59-6.62-6.5-6.62-1.83,0-3.31,.6-4.42,1.79Z"/>
<path class="cls-4" d="M392.99,161.98v12.6c0,1.95,.4,3.43,1.24,4.38,.84,.96,2.19,1.48,4.07,1.48,1.35,0,2.59-.56,3.67-1.59,1.12-1.12,1.79-2.55,2.03-4.3v-12.56h3.19v20.61h-3.19v-3.19c-1.68,2.47-3.91,3.75-6.7,3.75-5.02,0-7.5-2.83-7.5-8.41v-12.76h3.19Z"/>
<path class="cls-4" d="M431.17,161.98h5.1v2.67h-5.1v13.11c0,.76,.12,1.31,.44,1.63,.28,.32,.8,.52,1.51,.52h2.63v2.67h-3.11c-1.67,0-2.91-.44-3.63-1.31-.68-.8-1-1.95-1-3.51v-13.11h-4.15v-2.67h4.15v-5.34l3.15-1.31v6.66Z"/>
<path class="cls-4" d="M465.24,161.9v3.19c-1.04-.32-2.07-.44-3.07-.44-1.59,0-2.95,.68-4.03,2.11-1.12,1.35-1.64,2.99-1.64,4.9v10.92h-3.15v-20.61h3.15v3.59c.56-1.19,1.28-2.15,2.15-2.83,1.08-.88,2.31-1.32,3.75-1.32,1.12,0,2.07,.16,2.83,.48Z"/>
<path class="cls-4" d="M485.8,154.88c.48,.4,.72,.96,.72,1.68,0,.68-.24,1.24-.72,1.71-.48,.44-1.04,.68-1.71,.68s-1.24-.24-1.71-.68c-.48-.48-.68-1.04-.68-1.71,0-.72,.2-1.28,.68-1.68,.48-.48,1.04-.68,1.71-.68s1.24,.2,1.71,.68Zm-.12,7.1v20.61h-3.19v-20.61h3.19Z"/>
<path class="cls-4" d="M522.54,169.91v12.67h-3.19v-12.44c0-4.03-1.83-6.02-5.42-6.02-1.43,0-2.67,.52-3.71,1.56-1.12,1.12-1.71,2.59-1.83,4.46v12.44h-3.19v-20.61h3.19v3.03c.72-1.16,1.64-2.03,2.75-2.63,1.12-.64,2.35-.96,3.71-.96,5.1,0,7.69,2.83,7.69,8.49Z"/>
<path class="cls-4" d="M557.61,164.61c1.75,2.03,2.63,4.58,2.63,7.69s-.88,5.62-2.59,7.61c-1.87,2.15-4.35,3.23-7.41,3.23s-5.54-1.08-7.37-3.23c-1.75-1.99-2.63-4.54-2.63-7.61s.88-5.66,2.63-7.69c1.83-2.15,4.27-3.19,7.37-3.19s5.54,1.04,7.38,3.19Zm-12.48,1.99c-1.12,1.51-1.63,3.39-1.63,5.7s.52,4.15,1.63,5.66c1.2,1.68,2.91,2.51,5.1,2.51s3.87-.84,5.14-2.51c1.07-1.51,1.63-3.43,1.63-5.66s-.56-4.19-1.63-5.7c-1.28-1.68-2.99-2.51-5.14-2.51s-3.91,.84-5.1,2.51Z"/>
<path class="cls-4" d="M616.79,154.13c6.54,0,9.8,2.79,9.8,8.37s-3.31,8.45-9.84,8.45h-8.33v11.64h-3.27v-28.46h11.64Zm-8.37,13.99h8.17c2.31,0,3.99-.48,5.1-1.39,1.07-.92,1.63-2.31,1.63-4.23s-.56-3.31-1.67-4.15c-1.12-.96-2.79-1.4-5.06-1.4h-8.17v11.16Z"/>
<path class="cls-4" d="M655.76,161.9v3.19c-1.04-.32-2.07-.44-3.07-.44-1.59,0-2.95,.68-4.03,2.11-1.11,1.35-1.63,2.99-1.63,4.9v10.92h-3.15v-20.61h3.15v3.59c.56-1.19,1.28-2.15,2.15-2.83,1.07-.88,2.31-1.32,3.75-1.32,1.12,0,2.07,.16,2.83,.48Z"/>
<path class="cls-4" d="M688.55,164.61c1.76,2.03,2.63,4.58,2.63,7.69s-.88,5.62-2.59,7.61c-1.87,2.15-4.34,3.23-7.41,3.23s-5.54-1.08-7.37-3.23c-1.75-1.99-2.63-4.54-2.63-7.61s.88-5.66,2.63-7.69c1.83-2.15,4.27-3.19,7.37-3.19s5.54,1.04,7.37,3.19Zm-12.48,1.99c-1.12,1.51-1.63,3.39-1.63,5.7s.52,4.15,1.63,5.66c1.2,1.68,2.91,2.51,5.1,2.51s3.87-.84,5.14-2.51c1.08-1.51,1.64-3.43,1.64-5.66s-.56-4.19-1.64-5.7c-1.28-1.68-2.99-2.51-5.14-2.51s-3.91,.84-5.1,2.51Z"/>
<path class="cls-4" d="M711.23,161.98l5.34,7.33,5.3-7.33h3.75l-7.29,9.69,8.17,10.92h-3.79l-6.14-8.57-6.18,8.57h-3.79l8.17-10.92-7.29-9.69h3.75Z"/>
<path class="cls-4" d="M743.9,161.98l6.3,16.26,6.1-16.26h3.51l-11.28,28.5h-3.47l3.55-8.37-8.17-20.13h3.47Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 213.97 125.78"><defs><style>.cls-1{fill:#231815;}</style></defs><title>资源 6</title><g id="图层_2" data-name="图层 2"><g id="图层_1-2" data-name="图层 1"><path class="cls-1" d="M103.07,10C104.83,4.87,101.58-.51,95.14,0A11.77,11.77,0,0,0,90,1.83C58.43,21,59.5,62.37,90.42,82.43A10.38,10.38,0,0,0,95,84.08c7.4.71,12-6.51,7.31-12a10.5,10.5,0,0,0-2.89-2.23c-23.85-13.2-24.7-41.33-.38-55.2A8.85,8.85,0,0,0,103.07,10Z"/><path class="cls-1" d="M124.06,14.2l10.52,6.9a8.84,8.84,0,0,0,1,.56c7.18,3.31,13.75-3.74,9.77-9.95A8.46,8.46,0,0,0,143,9.36L131.66,1.8A8.44,8.44,0,0,0,125.37.4c-4.29.82-6.12,4.75-4.94,9A8.54,8.54,0,0,0,124.06,14.2Z"/><path class="cls-1" d="M132,51.62V62.88a4.06,4.06,0,0,1-2,3.5l-5.34,3.14a7.59,7.59,0,0,0-4.13,8.69c.77,2.74,3.12,5,7.16,5.25a7.37,7.37,0,0,0,2.75-.43,35.87,35.87,0,0,0,13.38-8.55,9.57,9.57,0,0,0,2.76-6.75V42.06h0a6.78,6.78,0,0,0-6.78-6.78H132"/><path class="cls-1" d="M124.17,37.57V48.93a2.3,2.3,0,0,1-2.29,2.29h-7.11a8.48,8.48,0,0,1-7.79-5,7.39,7.39,0,0,1-.33-5.41,8.39,8.39,0,0,1,8-5.57h7.18A2.3,2.3,0,0,1,124.17,37.57Z"/><path class="cls-1" d="M11.91,100.28A2.38,2.38,0,0,0,9.31,97a3.89,3.89,0,0,0-1.68.59c-10.37,6.28-10,19.87.13,26.45a3.4,3.4,0,0,0,1.5.54c2.43.24,3.94-2.14,2.4-3.93a3.26,3.26,0,0,0-.95-.73c-7.82-4.33-8.1-13.57-.12-18.12A2.87,2.87,0,0,0,11.91,100.28Z"/><path class="cls-1" d="M18.8,101.68l3.46,2.26a3.37,3.37,0,0,0,.32.19c2.36,1.08,4.51-1.23,3.21-3.27a2.86,2.86,0,0,0-.78-.77L21.3,97.61a2.78,2.78,0,0,0-2.07-.46,2.18,2.18,0,0,0-1.62,2.95A2.81,2.81,0,0,0,18.8,101.68Z"/><path class="cls-1" d="M39.24,98.82V123a2.3,2.3,0,0,0,0,.37c.37,2.57,3.48,3.12,4.48.91a2.44,2.44,0,0,0,.22-1.07L44,98.69a2.78,2.78,0,0,0-.74-2,2.19,2.19,0,0,0-3.36.25A2.86,2.86,0,0,0,39.24,98.82Z"/><path class="cls-1" d="M71.05,108.91,71,123c0,.12,0,.25,0,.37.38,2.57,3.49,3.12,4.49.91a2.6,2.6,0,0,0,.22-1.07l.06-14.47a2.81,2.81,0,0,0-.73-2,2.2,2.2,0,0,0-3.37.25A2.85,2.85,0,0,0,71.05,108.91Z"/><path class="cls-1" d="M126.44,95.88l-10.13,0a2.42,2.42,0,0,0-.38,0c-2.57.37-3.12,3.49-.91,4.49a2.86,2.86,0,0,0,1.08.22l10.46.06a2.76,2.76,0,0,0,2-.74,2.18,2.18,0,0,0-.25-3.36A2.75,2.75,0,0,0,126.44,95.88Z"/><path class="cls-1" d="M126.44,121l-10.13,0-.38,0c-2.57.38-3.12,3.49-.91,4.49a2.86,2.86,0,0,0,1.08.22l10.46.06a2.8,2.8,0,0,0,2-.73,2.19,2.19,0,0,0-.25-3.37A2.81,2.81,0,0,0,126.44,121Z"/><path class="cls-1" d="M172.85,98.18a2.86,2.86,0,0,1-.91,1.92l-12.34,12v10.86a2.81,2.81,0,0,1-.68,1.86,2.19,2.19,0,0,1-3.36.25,2.85,2.85,0,0,1-.74-2l0-6.36,0-6.61,4.76-4.62,8.91-8.68a2.82,2.82,0,0,1,1.8-.81A2.19,2.19,0,0,1,172.85,98.18Z"/><path class="cls-1" d="M121.44,108.44h-5.13a2.42,2.42,0,0,0-.38,0c-2.57.37-3.12,3.48-.91,4.48a2.68,2.68,0,0,0,1.08.22l5.46.07a2.76,2.76,0,0,0,2-.74,2.18,2.18,0,0,0-.25-3.36A2.81,2.81,0,0,0,121.44,108.44Z"/><path class="cls-1" d="M211.18,95.79H201.05a2.3,2.3,0,0,0-.37,0c-2.57.37-3.12,3.48-.92,4.48a2.68,2.68,0,0,0,1.08.22l10.46.07a2.85,2.85,0,0,0,2-.74,2.19,2.19,0,0,0-.25-3.36A2.86,2.86,0,0,0,211.18,95.79Z"/><path class="cls-1" d="M211.18,120.93l-10.13,0a2.3,2.3,0,0,0-.37,0c-2.57.37-3.12,3.49-.92,4.49a2.86,2.86,0,0,0,1.08.22l10.46.06a2.8,2.8,0,0,0,2-.74,2.19,2.19,0,0,0-.25-3.36A2.8,2.8,0,0,0,211.18,120.93Z"/><path class="cls-1" d="M206.18,108.36l-5.13,0-.37,0c-2.57.38-3.12,3.49-.92,4.49a2.86,2.86,0,0,0,1.08.22l5.46.06a2.84,2.84,0,0,0,2-.73A2.2,2.2,0,0,0,208,109,2.85,2.85,0,0,0,206.18,108.36Z"/><path class="cls-1" d="M151.22,100.83l-3.88-3.36a2.37,2.37,0,0,0-.3-.23c-2.19-1.39-4.63.62-3.61,2.81a2.48,2.48,0,0,0,.67.87l4.1,3.61a2.83,2.83,0,0,0,2,.73,2.18,2.18,0,0,0,2-2.71A2.8,2.8,0,0,0,151.22,100.83Z"/><path class="cls-1" d="M61.45,100.75l24.13-.24.38,0c2.56-.4,3.07-3.52.86-4.49a2.67,2.67,0,0,0-1.08-.21L61.28,96a2.82,2.82,0,0,0-2,.76,2.19,2.19,0,0,0,.29,3.36A2.78,2.78,0,0,0,61.45,100.75Z"/><path class="cls-1" d="M102.73,98.84,103,123a2.42,2.42,0,0,0,0,.38c.4,2.56,3.52,3.08,4.5.86a2.67,2.67,0,0,0,.21-1.08l-.21-24.46a2.76,2.76,0,0,0-.76-2,2.18,2.18,0,0,0-3.35.28A2.81,2.81,0,0,0,102.73,98.84Z"/><path class="cls-1" d="M187.27,98.84l.24,24.13a2.43,2.43,0,0,0,0,.38c.4,2.56,3.52,3.08,4.49.86a2.67,2.67,0,0,0,.21-1.08L192,98.67a2.8,2.8,0,0,0-.76-2,2.19,2.19,0,0,0-3.36.28A2.8,2.8,0,0,0,187.27,98.84Z"/><path class="cls-1" d="M26.18,110.82v8.42a3.13,3.13,0,0,1-.91,2.22,11.64,11.64,0,0,1-4.39,2.8,2.3,2.3,0,0,1-.9.15,2.4,2.4,0,0,1-2.35-1.73A2.49,2.49,0,0,1,19,119.83l1.75-1a1.33,1.33,0,0,0,.65-1.15V114a.23.23,0,0,0,0-.14h-5.6a.86.86,0,0,1-.23,0,2.76,2.76,0,0,1-2.33-1.6,2.43,2.43,0,0,1-.1-1.78,2.74,2.74,0,0,1,2.43-1.82h8.6a2.18,2.18,0,0,1,1.41.64A2.26,2.26,0,0,1,26.18,110.82Z"/><path class="cls-1" d="M15.73,108.59h0Z"/><path class="cls-1" d="M15.52,113.8a.86.86,0,0,0,.23,0h-.23Z"/><path class="cls-1" d="M24.12,108.59h0Z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@@ -0,0 +1,15 @@
<svg width="1053" height="170" viewBox="0 0 1053 170" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M73.9973 35.0579C54.1616 69.8286 42.9756 83.2381 35.8964 77.1848C30.9901 72.9896 47.7987 41.7206 58.7276 21.3898C62.1781 14.9707 65.0426 9.64207 66.4524 6.60013C66.7845 5.88338 66.1105 5.51842 65.5618 6.11847C40.0106 34.0615 -21.3347 135.895 7.5775 162.983C8.93141 164.252 11.0512 163.631 11.7989 161.933L33.1473 113.459C34.035 111.443 36.5449 110.432 38.7533 111.201L48.8546 114.714C51.063 115.483 52.1337 117.739 51.246 119.755L31.2362 165.189C30.3407 167.223 31.8971 169.466 34.0878 169.095C74.8305 162.188 98.0507 93.0493 117.5 36.5C124 14.5 124 14.5 128 0C119.318 15.677 82.7367 82.7294 70.4985 81.4891C61.0082 80.5273 63.8387 71.85 70.8524 50.3486C72.2789 45.9755 73.8785 41.072 75.5826 35.595C75.865 34.6874 74.4765 34.218 73.9973 35.0579Z" fill="#FF5C00"/>
<path d="M200.782 129.157H178.611L171.579 108.423H138.084L131.052 129.157H109L144.163 32.6779H165.5L200.782 129.157ZM154.533 60.2434L144.759 88.7678H164.904L155.249 60.2434H154.533Z" fill="black"/>
<path d="M208.662 159V57.6067H228.806V66.1161H229.522C231.667 62.9201 234.409 60.4432 237.746 58.6854C241.084 56.8477 244.62 55.9288 248.355 55.9288C253.997 55.9288 258.924 57.4469 263.135 60.4832C267.426 63.4395 270.724 67.6742 273.029 73.1873C275.333 78.7004 276.485 85.1723 276.485 92.603C276.485 100.353 275.333 107.105 273.029 112.858C270.724 118.61 267.426 123.045 263.135 126.161C258.844 129.277 253.838 130.835 248.116 130.835C244.302 130.835 240.766 129.916 237.508 128.079C234.329 126.321 231.667 123.844 229.522 120.648H228.806V159H208.662ZM228.33 93.4419C228.33 99.035 229.561 103.509 232.025 106.865C234.488 110.141 237.746 111.779 241.799 111.779C246.09 111.779 249.507 110.141 252.05 106.865C254.593 103.509 255.864 99.035 255.864 93.4419C255.864 87.769 254.633 83.2946 252.169 80.0187C249.706 76.6629 246.408 74.985 242.276 74.985C238.064 74.985 234.687 76.6629 232.144 80.0187C229.601 83.2946 228.33 87.769 228.33 93.4419Z" fill="black"/>
<path d="M306.184 130.835C299.907 130.835 294.702 129.037 290.569 125.442C286.517 121.767 284.49 116.733 284.49 110.341C284.49 105.387 285.881 101.032 288.662 97.2772C291.443 93.5218 295.377 90.6454 300.463 88.6479C305.628 86.5705 311.628 85.5318 318.462 85.5318H324.898V83.7341C324.898 80.2185 323.905 77.5418 321.918 75.7041C320.011 73.7865 317.309 72.8277 313.813 72.8277C308.807 72.8277 304.277 75.3046 300.224 80.2584L287.113 70.191C294.821 60.6829 304.595 55.9288 316.435 55.9288C322.077 55.9288 327.084 57.0874 331.454 59.4045C335.825 61.6417 339.202 64.7978 341.586 68.8727C343.97 72.9476 345.162 77.6617 345.162 83.015V129.157H324.898V121.007H324.183C322.753 124.203 320.448 126.64 317.27 128.318C314.091 129.996 310.396 130.835 306.184 130.835ZM312.502 114.176C316.078 114.176 319.018 113.377 321.322 111.779C323.706 110.101 324.898 107.824 324.898 104.948V102.071H320.488C315.323 102.071 311.469 102.591 308.926 103.629C306.383 104.668 305.111 106.426 305.111 108.903C305.111 110.421 305.747 111.699 307.019 112.738C308.37 113.697 310.197 114.176 312.502 114.176Z" fill="black"/>
<path d="M391.139 130.835C383.829 130.835 377.471 129.317 372.068 126.281C366.744 123.245 362.611 118.93 359.671 113.337C356.811 107.664 355.38 101.032 355.38 93.4419C355.38 85.8514 356.811 79.2197 359.671 73.5468C362.611 67.8739 366.744 63.5194 372.068 60.4832C377.471 57.4469 383.829 55.9288 391.139 55.9288C397.656 55.9288 403.576 57.8464 408.9 61.6816C414.224 65.5169 418.118 70.7104 420.581 77.2622L401.867 84.3333C400.834 81.377 399.205 79.0999 396.98 77.5019C394.835 75.9039 392.451 75.1049 389.828 75.1049C385.617 75.1049 382.239 76.7828 379.696 80.1386C377.233 83.4145 376.001 87.8489 376.001 93.4419C376.001 99.035 377.233 103.469 379.696 106.745C382.239 110.021 385.617 111.659 389.828 111.659C392.451 111.659 394.835 110.86 396.98 109.262C399.205 107.584 400.834 105.307 401.867 102.431L420.581 109.502C418.118 116.054 414.224 121.247 408.9 125.082C403.576 128.918 397.656 130.835 391.139 130.835Z" fill="black"/>
<path d="M430.993 129.157V32.6779H451.138V66.9551H451.734C453.8 63.5194 456.502 60.8427 459.839 58.9251C463.256 56.9276 467.15 55.9288 471.52 55.9288C478.593 55.9288 484.076 58.2459 487.97 62.8801C491.943 67.4345 493.93 73.9463 493.93 82.4157V129.157H473.785V87.0899C473.785 83.4145 472.871 80.5381 471.044 78.4607C469.216 76.3034 466.713 75.2247 463.534 75.2247C459.799 75.2247 456.78 76.4232 454.475 78.8202C452.25 81.1373 451.138 84.3333 451.138 88.4082V129.157H430.993Z" fill="black"/>
<path d="M571.491 88.2884C571.491 93.3221 570.974 97.6367 569.941 101.232H525.361C526.871 109.861 531.719 114.176 539.903 114.176C542.764 114.176 545.347 113.337 547.651 111.659C549.956 109.981 551.783 107.664 553.134 104.708L569.703 111.899C567.319 117.891 563.505 122.566 558.26 125.921C553.095 129.197 546.936 130.835 539.784 130.835C532.632 130.835 526.355 129.357 520.951 126.401C515.547 123.365 511.376 119.09 508.435 113.577C505.575 107.984 504.144 101.472 504.144 94.0412C504.144 86.2909 505.575 79.5793 508.435 73.9064C511.296 68.1536 515.309 63.7191 520.474 60.603C525.719 57.4869 531.838 55.9288 538.831 55.9288C545.347 55.9288 551.068 57.2472 555.995 59.8839C560.922 62.5206 564.736 66.2759 567.438 71.1498C570.14 76.0237 571.491 81.7366 571.491 88.2884ZM550.989 84.573C550.989 80.9775 549.876 78.0612 547.651 75.824C545.506 73.5868 542.565 72.4682 538.831 72.4682C531.997 72.4682 527.626 76.5031 525.719 84.573H550.989Z" fill="black"/>
<path d="M658.286 130.835C649.545 130.835 641.916 128.678 635.4 124.363C628.884 119.969 623.997 114.176 620.739 106.985L638.857 98.4757C640.526 102.79 643.069 106.066 646.486 108.303C649.903 110.541 653.876 111.659 658.405 111.659C663.094 111.659 666.829 110.78 669.61 109.022C672.391 107.265 673.782 104.988 673.782 102.191C673.782 100.194 673.265 98.5556 672.232 97.2772C671.199 95.9189 669.411 94.6804 666.868 93.5618C664.325 92.4432 660.63 91.2047 655.783 89.8464C649.426 88.0087 644.141 86.171 639.93 84.3333C635.718 82.4956 631.983 79.6592 628.725 75.824C625.546 71.9089 623.957 66.7553 623.957 60.3633C623.957 54.7703 625.507 49.7366 628.606 45.2622C631.784 40.7878 636.076 37.3121 641.479 34.8352C646.962 32.2784 653.002 31 659.597 31C676.205 31 688.165 38.5905 695.476 53.7715L677.596 62.5206C675.53 58.206 672.987 55.0899 669.967 53.1723C667.027 51.1748 663.491 50.176 659.359 50.176C655.386 50.176 652.048 51.0549 649.346 52.8127C646.644 54.5705 645.294 56.7278 645.294 59.2846C645.294 61.4419 645.85 63.1997 646.962 64.5581C648.075 65.8365 649.863 67.035 652.326 68.1536C654.869 69.1923 658.604 70.3908 663.531 71.7491C669.808 73.5069 675.053 75.3046 679.265 77.1423C683.476 78.9001 687.172 81.7366 690.35 85.6517C693.529 89.4869 695.118 94.6005 695.118 100.993C695.118 106.985 693.529 112.258 690.35 116.813C687.251 121.287 682.92 124.723 677.358 127.12C671.795 129.597 665.438 130.835 658.286 130.835Z" fill="black"/>
<path d="M706.521 129.157V32.6779H726.665V66.9551H727.261C729.327 63.5194 732.029 60.8427 735.367 58.9251C738.784 56.9276 742.678 55.9288 747.048 55.9288C754.12 55.9288 759.604 58.2459 763.497 62.8801C767.471 67.4345 769.457 73.9463 769.457 82.4157V129.157H749.313V87.0899C749.313 83.4145 748.399 80.5381 746.571 78.4607C744.744 76.3034 742.24 75.2247 739.062 75.2247C735.327 75.2247 732.307 76.4232 730.003 78.8202C727.778 81.1373 726.665 84.3333 726.665 88.4082V129.157H706.521Z" fill="black"/>
<path d="M847.018 88.2884C847.018 93.3221 846.502 97.6367 845.469 101.232H800.889C802.399 109.861 807.246 114.176 815.431 114.176C818.292 114.176 820.874 113.337 823.179 111.659C825.483 109.981 827.311 107.664 828.662 104.708L845.231 111.899C842.847 117.891 839.032 122.566 833.788 125.921C828.622 129.197 822.464 130.835 815.312 130.835C808.16 130.835 801.882 129.357 796.479 126.401C791.075 123.365 786.903 119.09 783.963 113.577C781.102 107.984 779.672 101.472 779.672 94.0412C779.672 86.2909 781.102 79.5793 783.963 73.9064C786.824 68.1536 790.837 63.7191 796.002 60.603C801.247 57.4869 807.365 55.9288 814.358 55.9288C820.874 55.9288 826.596 57.2472 831.523 59.8839C836.45 62.5206 840.264 66.2759 842.966 71.1498C845.668 76.0237 847.018 81.7366 847.018 88.2884ZM826.516 84.573C826.516 80.9775 825.404 78.0612 823.179 75.824C821.033 73.5868 818.093 72.4682 814.358 72.4682C807.524 72.4682 803.154 76.5031 801.247 84.573H826.516Z" fill="black"/>
<path d="M857.962 129.157V57.6067H878.107V66.9551H878.703C880.769 63.5194 883.471 60.8427 886.808 58.9251C890.225 56.9276 894.119 55.9288 898.489 55.9288C905.562 55.9288 911.045 58.2459 914.939 62.8801C918.912 67.4345 920.899 73.9463 920.899 82.4157V129.157H900.754V87.0899C900.754 83.4145 899.84 80.5381 898.013 78.4607C896.185 76.3034 893.682 75.2247 890.503 75.2247C886.768 75.2247 883.749 76.4232 881.444 78.8202C879.219 81.1373 878.107 84.3333 878.107 88.4082V129.157H857.962Z" fill="black"/>
<path d="M944.073 91.4045L909.625 32.6779H934.776L954.205 73.1873H954.92L974.349 32.6779H999.619L965.052 91.4045V129.157H944.073V91.4045Z" fill="black"/>
<path d="M1053 57.6067V129.157H1032.86V120.049H1032.14C1030.15 123.484 1027.49 126.121 1024.15 127.959C1020.9 129.876 1017.08 130.835 1012.71 130.835C1005.64 130.835 1000.08 128.558 996.024 124.004C992.05 119.37 990.064 112.938 990.064 104.708V57.6067H1010.21V100.034C1010.21 103.629 1011.16 106.506 1013.07 108.663C1014.98 110.82 1017.48 111.899 1020.58 111.899C1024.31 111.899 1027.29 110.74 1029.52 108.423C1031.74 106.026 1032.86 102.83 1032.86 98.8352V57.6067H1053Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

@@ -0,0 +1,40 @@
---
title: 快速上手
date: 2023-03-28 20:30:00
permalink: /pages/793dcb
article: false
---
::: tip
当前最新版本为2.0.0,如果从之前的版本升级而来,请注意以下变更
1、jdk版本升级为了jdk17,jar部署时请注意
2、去掉了默认的sqlite数据库,改为了h2。如果之前使用sqlite,请自行处理数据迁移
3、配置文件做了较大调整,请参照使用须知中的`服务端配置``客户端配置`进行更新
:::
## 1、快速上手须知
- 所谓快速上手,就是针对从来没用过的新手编写的、快速完成从安装部署到首次使用的最简化流程
- 该流程中请严格按照说明步骤执行,不要靠猜测自作聪明的修改配置
- 如果的确需要修改,请先把安装部署、使用须知、常见问题全部仔细读一遍
- 快速上手中涉及到的配置,仅仅是快速体验所必须的配置,完整的配置请参照使用须知中的`服务端配置``客户端配置`
## 2、部署服务
- 根据需要自行选择jar、docker、原生部署任意一种完成部署
- 三种方式分别参见对应的文档
- 任何一种部署方式,不要求客户端与服务端一致。如:服务端采用docker部署,客户端可以用docker部署、也可以用jar、原生部署
- 管理后台说明
- 服务端部署成功后,访问http://{服务端IP}:8888打开后台管理页面。使用默认的管理员帐号登录:admin/123456
- 打开代理配置>License管理页面,可以看到系统已经自动为管理员初始化了一条License记录,复制该LicenseKey备用,后续客户端配置需要。
- 客户端说明
- 客户端启动后,查看服务端License管理,刷新页面,对应的License在线状态为`在线`,则表明客户端已正常连接。
## 3、代理验证
- 本地启动被代理服务,如:redis、本地web项目、本地mysql等等,假设被代理服务端口为:8080
- 先确保本地能正常访问被代理服务,如果本地都不能访问,不用想代理更不可能!!!
- 服务端管理后台端口映射管理,使用默认的licesne创建一条端口映射,如:9101 -> 127.0.0.1:8080
- 通过服务端IP+9101(上面License配置的端口映射重的服务端端口)访问本地被代理服务
访问成功,至此首次完整的内网穿透体验完成。开源不易,请速至[Gitee仓库](https://gitee.com/dromara/neutrino-proxy)一键三连🤝
@@ -0,0 +1,55 @@
---
title: 使用jar部署
date: 2023-10-26 13:50:45
permalink: /pages/eebea1/
---
## 1、环境准备
- 首先确保已安装Java17运行环境
- 打开[发行版页面](https://gitee.com/dromara/neutrino-proxy/releases),下载最新的release包:`neutrino-proxy-server-jar.zip``neutrino-proxy-client-jar.zip`
## 2、部署服务端
- 在服务器上新建部署目录:`/work/projects/neutrino-proxy-server`
- 将`neutrino-proxy-server-jar.zip`上传至服务器部署目录,并解压
- 执行命令`java -Dfile.encoding=utf-8 -jar neutrino-proxy-server.jar`启动服务端完成部署,默认使用h2数据库。
- 若需要指定自己的mysql数据库,则需要在当前目录下的`app.yml`文件中,修改数据库配置如下:
```yml
neutrino:
data:
db:
type: mysql
# 自己的数据库实例,创建一个空的名为'neutrino-proxy'的数据库即可,首次启动服务端会自动初始化
url: jdbc:mysql://xxxx:3306/neutrino-proxy?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useAffectedRows=true&useSSL=false
driver-class: com.mysql.jdbc.Driver
# 数据库帐号
username: xxx
# 数据库密码
password: xxx
```
- 可参照 https://gitee.com/dromara/neutrino-proxy/blob/master/scripts/unix/server_start.sh 使用shell脚本启动服务端。
## 3、部署客户端
- 本地解压`neutrino-proxy-client-jar.zip`文件
- 修改`app.yml`文件中的server-ip为服务器公网ip,并配置license-key,以下是相关的部分配置:
```yml
neutrino:
proxy:
tunnel:
# ssl证书密钥(使用jjar包内自带的证书,则此处无需修改)
key-store-password: 123456
# ssl证书管理密钥(使用jjar包内自带的证书,则此处无需修改。自定义证书,则此处配置对应的路径)
jks-path: classpath:/test.jks
# 代理服务端IP
server-ip: xxxx
# 代理服务端IP, 若是非ssl端口,则ssl-enable需要配置为false
server-port: 9002
# 是否启用ssl
ssl-enable: true
# licenseKey,客户端凭证。此处需要配置刚刚从管理后台复制的LicenseKey
license-key: xxxx
```
- 执行命令`java -jar neutrino-proxy-client.jar`启动客户端
@@ -0,0 +1,42 @@
---
title: 使用docker容器化部署
date: 2023-10-26 13:51:29
permalink: /pages/5b9bef/
---
## 1、 部署服务端
### 使用默认h2数据库一键部署
```shell
docker run -it -p 9000-9200:9000-9200/tcp -p 8888:8888 \
-d --restart=always --name neutrino-proxy \
-v /root/neutrino-proxy-server/config:/root/neutrino-proxy/config \
-v /root/neutrino-proxy-server/data:/root/neutrino-proxy/data \
-v /root/neutrino-proxy-server/logs:/root/neutrino-proxy/logs \
registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:latest
```
### 指定自己的mysql数据库
- 在服务器上创建目录:/root/neutrino-proxy/config
- 在该目录下创建`app.yml`文本文件,并配置如下内容:
```yml
neutrino:
data:
db:
type: mysql
# 自己的数据库实例,创建一个空的名为'neutrino-proxy'的数据库即可,首次启动服务端会自动初始化
url: jdbc:mysql://xxxx:3306/neutrino-proxy?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useAffectedRows=true&useSSL=false
driver-class: com.mysql.jdbc.Driver
# 数据库帐号
username: xxx
# 数据库密码
password: xxx
```
- 然后再执行上面的docker一键部署命令即可
## 2、部署客户端
- 命令中的服务端ip、license请自行补充
- 若是首次使用,请仔细阅读快速上手,以确定license从哪里取得
```shell
docker run -it -d --restart=always --name npclient -e SERVER_IP=xxxx -e LICENSE_KEY=xxxx \
aoshiguchen/neutrino-proxy-client:latest
```
@@ -0,0 +1,61 @@
---
title: 原生部署
date: 2023-10-26 13:51:44
permalink: /pages/69699a/
---
::: tip
1、目前的原生部署包支持mac、linux、windows三个平台
2、这三个平台的部署包均由作者分别测试通过
3、尽管如此,仍然可能存在有的机器不支持的情况,可自行拉取仓库源码编译出目标平台可执行文件、或换成其它部署方式
4、若linux/mac下提示文件没有执行权限,可执行`chmod +x {文件路径}`解决
5、若mac下运行提示移到废纸篓,可执行`sudo xattr -rd com.apple.quarantine {文件路径}`解决
:::
## 1、安装包下载
- 打开[发行版页面](https://gitee.com/dromara/neutrino-proxy/releases),下载所需的最新的release包:
- 比如服务器为linux则可下载`neutrino-proxy-server-ubuntu-20.04-native.zip`文件,客户端为windows则可下载`neutrino-proxy-client-windows-2022-native.zip`
## 2、部署服务端
- 将服务端安装包上传至服务器,并解压
- 服务端默认使用h2数据库,若需要改为mysql,则可修改安装包`app.yml`内的数据库配置,如下:
```yml
neutrino:
data:
db:
type: mysql
# 自己的数据库实例,创建一个空的名为'neutrino-proxy'的数据库即可,首次启动服务端会自动初始化
url: jdbc:mysql://xxxx:3306/neutrino-proxy?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useAffectedRows=true&useSSL=false
driver-class: com.mysql.jdbc.Driver
# 数据库帐号
username: xxx
# 数据库密码
password: xxx
```
- 然后直接启动服务端可执行程序即可
## 3、部署客户端
- 本地解压客户端安装包
- 修改`app.yml`文件中的server-ip为服务器公网ip,并配置license-key,以下是相关的部分配置:
```yml
neutrino:
proxy:
tunnel:
# ssl证书密钥(使用jjar包内自带的证书,则此处无需修改)
key-store-password: 123456
# ssl证书管理密钥(使用jjar包内自带的证书,则此处无需修改。自定义证书,则此处配置对应的路径)
jks-path: classpath:/test.jks
# 代理服务端IP
server-ip: xxxx
# 代理服务端IP, 若是非ssl端口,则ssl-enable需要配置为false
server-port: 9002
# 是否启用ssl
ssl-enable: true
# licenseKey,客户端凭证。此处需要配置刚刚从管理后台复制的LicenseKey
license-key: xxxx
```
- 然后直接启动客户端可执行程序即可
@@ -1,123 +0,0 @@
---
title: 快速上手
date: 2023-03-28 20:30:00
permalink: /pages/793dcb
article: false
---
## 1、 部署服务端
### 1.1、 Docker一键部署
> 当前最新版本为1.8.5,下面的脚本中,可以使用:`registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:1.8.5` 指定版本安装,推荐使用`latest`直接安装最新版。
#### 使用默认sqlite数据库
```shell
docker run -it -p 9000-9200:9000-9200/tcp -p 8888:8888 \
-d --restart=always --name neutrino-proxy \
registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:latest
```
#### 指定自己的mysql数据库
- 在服务器上创建目录:/root/neutrino-proxy/config
- 在该目录下创建`app.yml`文本文件,并配置如下内容:
```yml
neutrino:
data:
db:
type: mysql
# 自己的数据库实例,创建一个空的名为'neutrino-proxy'的数据库即可,首次启动服务端会自动初始化
url: jdbc:mysql://xxxx:3306/neutrino-proxy?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useAffectedRows=true&useSSL=false
driver-class: com.mysql.jdbc.Driver
# 数据库帐号
username: xxx
# 数据库密码
password: xxx
```
- 然后执行如下命令:
```shell
docker run -it -p 9000-9200:9000-9200/tcp -p 8888:8888 \
-v /root/neutrino-proxy/config:/root/neutrino-proxy/config \
-d --restart=always --name neutrino \
registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:latest
```
### 1.2、使用jar包自行部署
- 首先确保服务器上已安装java8运行环境
- 打开[发行版页面](https://gitee.com/dromara/neutrino-proxy/releases),下载最新的release包:`neutrino-proxy-server.jar``neutrino-proxy-admin.zip`
- 在服务器上新建部署目录:`/work/projects/neutrino-proxy-server`
- 将` neutrino-proxy-server.jar``neutrino-proxy-admin.zip`上传至服务器部署目录。
- 解压`neutrino-proxy-admin.zip`文件
- 执行命令`java -Dfile.encoding=utf-8 -jar neutrino-proxy-server.jar`启动服务端完成部署,默认使用sqlite数据库。
- 若需要指定自己的mysql数据库,同样的需要在当前目录下新建`app.yml`文件,文件内容同上。执行命令`java -Dfile.encoding=utf-8 -jar neutrino-proxy-server.jar config=app.yml`启动服务端完成部署
- 可参照 https://gitee.com/dromara/neutrino-proxy/blob/master/bin/server_start.sh 使用shell脚本启动服务端。
## 2、管理后台配置
- 服务端部署成功后,访问`http://{服务端IP}:8888`打开后台管理页面。
- 使用默认的管理员帐号登录:admin/123456
- 打开`代理配置>License管理`页面,可以看到系统已经自动为管理员初始化了一条License记录,复制该`LicenseKey`备用,后续客户端配置需要。
- 打开`代理配置>端口映射`页面,可以看到系统已经自动为初始化了几条端口映射。可根据需要自行添加、修改。这里我们以`9101 -> 127.0.0.1:8080`映射为例
## 3、启动客户端
- 首先确保本地已安装java8运行环境
- 打开[发行版页面](https://gitee.com/dromara/neutrino-proxy/releases),下载最新的release包:`neutrino-proxy-client.jar`
- 在本地`neutrino-proxy-client.jar`同级别目录下新建`app.yml`文件,并配置如下内容:
```yml
neutrino:
proxy:
client:
# ssl证书密钥(使用jjar包内自带的证书,则此处无需修改)
key-store-password: 123456
# ssl证书管理密钥(使用jjar包内自带的证书,则此处无需修改。自定义证书,则此处配置对应的路径)
jks-path: classpath:/test.jks
# 代理服务端IP
server-ip: localhost
# 代理服务端IP, 若是非ssl端口,则ssl-enable需要配置为false
server-port: 9002
# 是否启用ssl
ssl-enable: true
# licenseKey,客户端凭证。此处需要配置刚刚从管理后台复制的LicenseKey
license-key: xxxx
```
- 执行命令`java -jar neutrino-proxy-client.jar config=app.yml`启动客户端
- 查看服务端License管理,刷新页面,对应的License在线状态为`在线`,则表明客户端已正常连接。
## 4、代理验证
- 本地启动被代理服务,如:redis、本地web项目、本地mysql等等
- 先确保本地能正常访问被代理服务,如果本地都不能访问,不用想代理更不可能!!!
- 通过服务端IP+9101(上面License配置的端口映射重的服务端端口)访问本地被代理服务
访问成功,至此首次完整的内网穿透体验完成。开源不易,请速至[Gitee仓库](https://gitee.com/dromara/neutrino-proxy)一键三连🤝
## 5、客户端Docker部署
> 因为一些原因,官方不推荐客户端使用Docker方式部署,但有不少朋友希望采用这种方式,因此从1.8.5版本开始维护了客户端镜像,版本号同服务端,也可使用`latest`直接安装最新版。
- 客户端镜像地址:registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy-client:latest
- 注意:客户端采用docker部署时,服务端端口映射客户端ip请不要配置127.0.0.1应该配置为容器访问宿主机的ip或者hostname,具体请自行百度
<!--
## 4.开发&调试
::: tip
1. 不建议在原默认vuepress项目上单独安装使用本主题包,而是clone我的整个项目再替换你自己的内容即可。
2. 修改`config.js`配置后需要重新启动项目才会生效。
3. 更多关于项目上手的问题,请查阅 [问答](/pages/9cc27d/)。
:::
## 版本升级
主题的版本会不定期更新,你只需更新npm主题包即可:
```sh
npm update vuepress-theme-vdoing
```
::: tip
1. 如更新后没起作用或报错,尝试把`node_modules`文件夹删除再`npm i`重新安装。
2. 在.vuepress/config.js中,设置`theme: 'vdoing'`才是使用npm主题依赖包:
```js
// config.js
module.exports = {
theme: 'vdoing', // npm主题依赖包
// theme: require.resolve('../../vdoing'), // 使用本地主题包
}
```
:::
-->
@@ -1,65 +0,0 @@
---
title: 服务端配置
date: 2023-05-27 11:41:44
permalink: /pages/f2d0f1/
---
::: tip
1.8.2版本,服务端配置格式有所调整。从1.8.2之前的版本升级到1.8.2之后的版本,需要注意
:::
# 以下是最新的服务端配置格式(app.yml)
```yml
server:
# 服务端web端口,用于支持HTTP接口,管理后台页面访问
port: ${WEB_PORT:8888}
neutrino:
proxy:
logger:
# 日志级别
level: ${LOG_LEVEL:info}
# 隧道相关配置-用于维持服务端与客户端的通信
tunnel:
# 线程池相关配置,用于技术调优,可忽略
boss-thread-count: 2
work-thread-count: 10
# 隧道非SSL端口
port: ${OPEN_PORT:9000}
# 隧道SSL端口
ssl-port: ${SSL_PORT:9002}
# 隧道SSL证书配置
key-store-password: ${STORE_PASS:123456}
key-manager-password: ${MGR_PASS:123456}
jks-path: ${JKS_PATH:classpath:/test.jks}
# 是否开启隧道传输报文日志(日志级别为debug时开启才有效)
transfer-log-enable: ${TUNNEL_LOG:false}
server:
# 线程池相关配置,用于技术调优,可忽略
boss-thread-count: 5
work-thread-count: 20
# http代理端口,默认80
http-proxy-port: ${HTTP_PROXY_PORT:80}
# https代理端口,默认443 (需要配置域名、证书)
https-proxy-port: ${HTTPS_PROXY_PORT:443}
# 如果不配置,则不支持域名映射
domain-name: ${DOMAIN_NAME:}
# https证书配置
key-store-password: ${HTTPS_STORE_PASS:}
jks-path: ${HTTPS_JKS_PATH:}
# 是否开启代理服务报文日志(日志级别为debug时开启才有效)
transfer-log-enable: ${SERVER_LOG:false}
data:
db:
# 数据库类型,目前支持sqlite、mysql、mariadb
type: ${DB_TYPE:sqlite}
# 数据库连接URL
url: ${DB_URL:jdbc:sqlite:data.db}
# 数据库用户名
username: ${DB_USER:}
# 数据库密码
password: ${DB_PASSWORD:}
```
@@ -0,0 +1,75 @@
---
title: 服务端配置
date: 2023-05-27 11:41:44
permalink: /pages/f2d0f1/
---
::: tip
1.9.0版本,服务端配置格式有所调整。从之前的版本升级的,需要注意
:::
# 以下是最新的服务端配置格式(app.yml)
```yml
server:
# 服务端web端口,用于支持HTTP接口,管理后台页面访问
port: ${WEB_PORT:8888}
# 日志级别
solon.logging.logger:
"root":
level: info
neutrino:
proxy:
# 隧道相关配置-用于维持服务端与客户端的通信
tunnel:
# 线程池相关配置,用于技术调优,可忽略
boss-thread-count: 2
work-thread-count: 10
# 隧道非SSL端口
port: ${OPEN_PORT:9000}
# 隧道SSL端口
ssl-port: ${SSL_PORT:9002}
# 隧道SSL证书配置
key-store-password: ${STORE_PASS:123456}
key-manager-password: ${MGR_PASS:123456}
jks-path: ${JKS_PATH:classpath:/test.jks}
# 是否开启隧道传输报文日志(日志级别为debug时开启才有效)
transfer-log-enable: ${TUNNEL_LOG:false}
# 是否开启心跳日志
heartbeat-log-enable: ${HEARTBEAT_LOG:false}
server:
tcp:
# 线程池相关配置,用于技术调优,可忽略
boss-thread-count: 5
work-thread-count: 20
# http代理端口,默认80
http-proxy-port: ${HTTP_PROXY_PORT:80}
# https代理端口,默认443 (需要配置域名、证书)
https-proxy-port: ${HTTPS_PROXY_PORT:443}
# 如果不配置,则不支持域名映射
domain-name: ${DOMAIN_NAME:}
# https证书配置
key-store-password: ${HTTPS_STORE_PASS:}
jks-path: ${HTTPS_JKS_PATH:}
# 是否开启代理服务报文日志(日志级别为debug时开启才有效)
transfer-log-enable: ${SERVER_LOG:false}
udp:
# 线程池相关配置,用于技术调优,可忽略
boss-thread-count: 5
work-thread-count: 20
# 是否开启代理服务报文日志(日志级别为debug时开启才有效)
transfer-log-enable: ${SERVER_LOG:false}
data:
db:
# 数据库类型,目前支持h2、mysql、mariadb
type: ${DB_TYPE:h2}
# 数据库连接URL
url: ${DB_URL:jdbc:h2:file:./data/db;MODE=MySQL;AUTO_SERVER=TRUE}
# 数据库用户名
username: ${DB_USER:}
# 数据库密码
password: ${DB_PASSWORD:}
```
@@ -0,0 +1,57 @@
---
title: 客户端配置
date: 2023-06-09 21:15:35
permalink: /pages/50ce10/
---
::: tip
1.9.0版本,客户端配置格式有所调整。从之前的版本升级的,需要注意
:::
# 以下是最新的客户端配置格式(app.yml)
```yml
# 日志级别
solon.logging.logger:
"root":
level: info
neutrino:
proxy:
tunnel:
# 线程池相关配置,用于技术调优,可忽略
thread-count: 50
# 隧道SSL证书配置
key-store-password: ${STORE_PASS:123456}
jks-path: ${JKS_PATH:classpath:/test.jks}
# 服务端IP
server-ip: ${SERVER_IP:localhost}
# 服务端端口(对应服务端app.yml中的tunnel.port、tunnel.ssl-port)
server-port: ${SERVER_PORT:9002}
# 是否启用SSL(注意:该配置必须和server-port对应上)
ssl-enable: ${SSL_ENABLE:true}
# 客户端连接唯一凭证
license-key: ${LICENSE_KEY:}
# 客户端唯一身份标识(可忽略,若不设置首次启动会自动生成)
client-id: ${CLIENT_ID:}
# 是否开启隧道传输报文日志(日志级别为debug时开启才有效)
transfer-log-enable: ${CLIENT_LOG:false}
# 是否开启心跳日志
heartbeat-log-enable: ${HEARTBEAT_LOG:false}
# 重连设置
reconnection:
# 重连间隔(秒)
interval-seconds: 10
# 是否开启无限重连(未开启时,客户端license不合法会自动停止应用,开启了则不会,请谨慎开启)
unlimited: false
client:
udp:
# 线程池相关配置,用于技术调优,可忽略
boss-thread-count: 5
work-thread-count: 20
# udp傀儡端口范围
puppet-port-range: 10000-10500
# 是否开启隧道传输报文日志(日志级别为debug时开启才有效)
transfer-log-enable: ${CLIENT_LOG:false}
```
@@ -1,39 +0,0 @@
---
title: 项目结构
date: 2020-05-11 13:54:56
permalink: /pages/2f674a
article: false
---
# 主要目录、文件
```
├── data.db (为配置mysql时,项目启动默认初始化生成的本地sqlite数据库文件)
├── docs (项目相关的一些文档)
├── neutrino-proxy-admin (管理后台前端项目,基于vue-element-admin)
├── neutrino-proxy-client (基于netty的代理客户端,用于和服务端交互、转发内网数据)
├── neutrino-proxy-core (代理相关的公共代码(协议、常量))
├── neutrino-proxy-server (基于netty的代理服务端,用于和客户段交互,将客户端转发的内网数据转发至外网端口)
└── todolist.MD (近期的开发计划)
```
# 代理服务端 `neutrino-proxy-server`
- base:基础的配置、初始化代码
- constant:服务端常量、枚举定义
- dal:持久层mapper、实体
- job:定时任务执行入口
- proxy:代理核心实现
- service:基础业务
- util:基础工具封装
- resource/mappermybatis SQL文件
- resource/sql:维护mysql/sqlite的初始化SQL、SQL变更记录
- resource/app.yml:项目默认配置
- resource/test.jks:默认的SSL证书
# 代理客户端 `neutrino-proxy-client`
- config:基础配置
- core:客户端代理核心入口
- handler:客户端代理数据处理逻辑
- util:基础工具封装
- resource/app.yml:项目默认配置
- resource/test.jks:默认的SSL证书
@@ -1,13 +0,0 @@
---
title: 演示环境
date: 2020-05-12 15:10:15
permalink: /pages/db78e2
article: false
---
### 演示环境
可使用分配好的游客license试用。服务器带宽较低,仅供学习使用!
管理后台地址:<a href="http://103.163.47.16:9527" target="_blank">http://103.163.47.16:9527</a>
游客账号:visitor/123456
@@ -1,25 +0,0 @@
---
title: 2023年
date: 2023-06-09 21:33:17
permalink: /pages/cded59/
---
| 日期 | 渠道 | 金额 |昵称| 备注 |
|:-----------|:---|:---|:-|:-----------------|
| 2023-06-29 |微信红包| 16.8 |迟迟🌱| 加油 |
| 2023-06-27 |微信红包| 30 |Arno| |
| 2023-06-25 |Gitee捐助| 10 |zhujue888| 感谢您的开源项目! |
| 2023-06-16 |微信红包| 50 |小跟班| |
| 2023-06-10 |Gitee捐助| 10 |失败女神| 感谢您的开源项目! |
| 2023-06-09 |Gitee捐助| 50 |Admin| 感谢您的开源项目! |
| 2023-06-09 |微信红包| 50 |李阳| 开源无限好 |
| 2023-06-09 |Gitee捐助| 50 |罗宾| 感谢您的开源项目! |
| 2023-06-07 |Gitee捐助| 50 |罗宾| 感谢您的开源项目! |
| 2023-06-03 |Gitee捐助| 50 |Admin| 感谢您的开源项目! |
| 2023-05-29 |微信红包| 20 |TYY| |
| 2023-05-29 |Gitee捐助| 10 |笑看| 感谢您的开源项目! |
| 2023-05-26 |微信红包| 50 |至少还有满天星光照耀你| |
| 2023-02-23 |Gitee捐助| 10 |Yohanes| 感谢您的开源项目! |
| 2023-02-23 |Gitee捐助| 20 |jam_lee| 感谢您的开源项目!希望能支持域名映射|
| 2023-02-10 |Gitee捐助| 5 |实习两年半| 感谢您的开源项目! |
| 2023-02-02 |Gitee捐助| 10 |阳光很暖| 感谢您的开源项目! |
@@ -1,11 +1,10 @@
---
title: 常见问题汇总
date: 2023-07-01 13:35:00
permalink: /pages/a00001/
title: 客户端连接服务端失败
date: 2023-10-25 17:55:20
permalink: /pages/31d253/
---
## 1、客户端启动后连不上服务端,一直在重连
### 1.1、启动日志包含`not found license-key config`相关字样
## 1.1. 启动日志包含`not found license-key config`相关字样
出现这种情况,说明客户端没获取到配置的license,可按如下检查:
- 客户端是否有配置app.yml,app.yml中是否有配置`license-key`
- 客户端启动参数是否指定app.yml配置,若未指定客户端不会加载任何jar外部的配置文件
@@ -14,18 +13,8 @@ permalink: /pages/a00001/
- `java -jar -DLICENSE_KEY=xxxx -DSERVER_IP=x.x.x.x -DSERVER_PORT=9000 -DSSL_ENABLE=false neutrino-proxy-client.jar`
- `java -jar neutrino-proxy-client.jar serverIp=x.x.x.x serverPort=9000 sslEnable=false licenseKey=xxxx`
### 1.2配置文件确定都已经配置,且启动参数正确指定了,但是没有出现`认证成功日志`
## 1.2. 配置文件确定都已经配置,且启动参数正确指定了,但是没有出现`认证成功日志`
- 确认客户端配置的`server-ip`,必须是服务端所在机器的ip,且保证客户端所在机器能正常ping通
- 确认客户端配置的`server-port``ssl-enable`,默认情况应配置为9000、false或9002、true,除非服务端app.yml有改动隧道对外端口
- 确认服务端对外的9000、9002端口是否已经放开
- 服务端如果采用docker部署,需要确保容器的9000、9002端口映射到宿主机
## 2、关于中微子代理涉及的2类SSL证书
### 2.1、HTTPS证书
该证书用于服务端支持HTTPS,若需要在服务端`app.yml`中配置即可
### 2.2、隧道SSL证书
该证书用于对客户端-服务端之间的数据通信进行加密,因此客户端/服务端均需要配置该证书。可参考如下命令生成:
```
keytool -genkey -alias test1 -keyalg RSA -keysize 1024 -validity 3650 -keypass 123456 -storepass 123456 -keystore "./test.jks"
```
@@ -0,0 +1,14 @@
---
title: 易混淆的2类证书
date: 2023-10-25 17:56:59
permalink: /pages/e4c3d7/
---
## 2.1. HTTPS证书
该证书用于服务端支持HTTPS,若需要在服务端`app.yml`中配置即可
## 2.2. 隧道SSL证书
该证书用于对客户端-服务端之间的数据通信进行加密,因此客户端/服务端均需要配置该证书。可参考如下命令生成:
```
keytool -genkey -alias test1 -keyalg RSA -keysize 1024 -validity 3650 -keypass 123456 -storepass 123456 -keystore "./test.jks"
```
@@ -0,0 +1,16 @@
---
title: udp代理无效
date: 2023-10-25 17:58:35
permalink: /pages/7b990f/
---
## 问题描述
代理本地udp端口9231,公网访问代理端口无效,代理端口测试发送成功,但是本地服务没有收到,使用路由器的ddns功能也可以成功代理。
## 解决方案
+ 防火墙开放端口的时候一定要指定udp协议
+ 还有如果想收到回复需要配置两个响应参数
+ 响应数量根据实际响应数量配置,可以配置的大一些,但是不能比实际小
+ 超时时间根据实际响应时间配置,比如正常响应时间是10s,这里就可以配置15000毫秒等
<img :src="$withBase('/img/run-example/question_3_1.png')"></img>
+ 相关链接:[1.9.0版本udp代理无效 · Issue #I83XOI · dromara/neutrino-proxy - Gitee.com](https://gitee.com/dromara/neutrino-proxy/issues/I83XOI)
+ 贡献者:gitee:眉黛如画
@@ -0,0 +1,19 @@
---
title: Vue项目出现Invalid Host header 的解决方案
date: 2023-10-25 17:59:29
permalink: /pages/441163/
---
## 问题描述
内网穿透 Vue项目出现Invalid Host header 的解决方案
## 解决方案
manifest.json中配置
```
"h5":{
"devServer":{
"disableHostCheck":true
}
},
```
+ 相关链接:[内网穿透 Vue项目出现Invalid Host header 的解决方案](https://blog.csdn.net/weixin_41549915/article/details/96132925)
+ 贡献者:wx:明天会更好
@@ -0,0 +1,39 @@
---
title: 个人赞赏
date: 2023-10-26 13:08:02
permalink: /pages/1ab20b/
---
## 个人赞赏说明
随着用户增加,需要协助解决的问题也多种多样,为了能给用户提供 `更优质``更准确` 的 疑难问题 解答我们推出赞赏咨询服务
| 赞赏金额 | 服务时长 | 服务说明 |
|:-----|:-----|:----------------------|
| 任意金额 | - | 您将获得赞助者列表的永久留名。 |
| 30 | 一个月 | 您在群内反馈的问题将优先得到作者响应 |
| 80 | 三个月 | 获得微信一对一的咨询服务 |
| 180 | 三个月 | 获得官网首页特别赞助席位一个,一次远程协助 |
| 更多金额 |-| 您将获得私人定制服务 |
> 注意1:服务项目是逐级叠加:如,您赞赏的金额是 80 员那么您将享受:任意金额、一个月、三个月的服务内容
> 注意2:二开问题不在个人赞赏支持服务范畴内,如需咨询二开问题或者二开方案请找作者协商
## 赞赏途径
> 除4以外,赞赏后请及时通过官网首页微信二维码与作者取得联系
- 1、微信赞赏
- 2、支付宝转账
- 3、Gitee项目捐赠:[点击这里](https://gitee.com/dromara/neutrino-proxy)打开后拉到最底部点击捐赠
- 4、微信好友转账
<table>
<tr>
<td align="center">微信赞赏</td><td align="center">支付宝转账</td>
</tr>
<tr>
<td><img width="300px" src="./a1.png"></td><td><img width="300px" src="./a2.png"></td>
</tr>
<tr>
</tr>
</table>
@@ -36,8 +36,8 @@ permalink: /pages/a3f096/
- 注释完备,尤其每个新增的方法应按照Java文档规范标明方法说明、参数说明、返回值说明等信息,必要时请添加单元测试,如果愿意,也可以加上你的大名。
- 原则上,不允许自行添加第三方依赖库。若确有必要,请先和项目负责人沟通达成一致。
- 对现有代码的优化,请在注释中说明原因。
- 如果涉及到数据库的变更,则至少需要做到以下点:
- sqlite、mysql下均测试通过
- 如果涉及到数据库的变更,则至少需要做到以下点:
- h2、mysql下均测试通过
- 在`./neutrino-proxy-server/src/main/resource/sql`下维护更新对应的表结构sql文件
- 对于新增的表,维护必要的初始化数据
- 在对应的mysql、sqlite的update目录下,维护SQL变更记录,确保SQL执行测试通过
- 在对应的mysql、h2的update目录下,维护SQL变更记录,确保SQL执行测试通过
@@ -0,0 +1,28 @@
---
title: 项目结构
date: 2020-05-11 13:54:56
permalink: /pages/2f674a
article: false
---
# 主要目录、文件
```
├── assets (资源文件)
├── docs (项目相关的一些文档)
├── neutrino-proxy-admin (管理后台前端项目,基于vue-element-admin)
├── neutrino-proxy-client (基于netty的代理客户端,用于和服务端交互、转发内网数据)
├── neutrino-proxy-core (代理相关的公共代码(协议、常量))
├── neutrino-proxy-server (基于netty的代理服务端,用于和客户段交互,将客户端转发的内网数据转发至外网端口)
├── neutrino-proxy-vuepress (中微子代理官网文档)
├── scripts (常用的编译、打包脚本)
├── Milestone.md (里程碑事件节点)
├── VLog.md (版本变更记录)
└── todolist.MD (近期的开发计划)
```
# 代码分支说明
- dev:常规开发分支,日常的开发、Bug修复、提交PR都在此分支
- feature/xxx:特征分支,一些试验性开发、提交PR都在此分支,xxx可自行取一个有意义的名称
- release/xxx:发行版分支,作为阶段性成果、重大更新的版本固化分支。受保护,不允许任何提交。
- master:主分支,定期同步最新代码,目前仅支持本人(傲世孤尘/雨韵诗泽)提交。
@@ -0,0 +1,34 @@
---
title: 2023年
date: 2023-06-09 21:33:17
permalink: /pages/cded59/
---
| 日期 | 渠道 | 金额 |昵称| 备注 |
|:-----------|:---|:-----|:-|:-----------------|
|2023-11-01|微信红包|20|Sun|感谢大佬,请你喝杯奶茶[微笑]|
|2023-10-30|微信赞赏|50|杨娃娃||
|2023-10-16|微信转账|50|Mark Isaac赵方丈|金额不大,开源感恩。|
|2023-10-11|微信红包|50|喜鸽小宝||
|2023-10-10|微信红包|20|海洋||
| 2023-07-31 |Gitee捐助| 50 |失败女神| 感谢您的开源项目! |
| 2023-07-28 |微信转账| 50 |AdrianPteLtd.com-咨询| |
| 2023-07-12 |微信红包| 100 |MaxKeyTop| 请大佬抽包烟 |
| 2023-07-04 |微信红包| 50 |姫野永遠| |
| 2023-06-29 |微信红包| 16.8 |迟迟🌱| 加油 |
| 2023-06-27 |微信红包| 30 |Arno| |
| 2023-06-25 |Gitee捐助| 10 |zhujue888| 感谢您的开源项目! |
| 2023-06-16 |微信红包| 50 |小跟班| |
| 2023-06-10 |Gitee捐助| 10 |失败女神| 感谢您的开源项目! |
| 2023-06-09 |Gitee捐助| 50 |Admin| 感谢您的开源项目! |
| 2023-06-09 |微信红包| 50 |李阳| 开源无限好 |
| 2023-06-09 |Gitee捐助| 50 |罗宾| 感谢您的开源项目! |
| 2023-06-07 |Gitee捐助| 50 |罗宾| 感谢您的开源项目! |
| 2023-06-03 |Gitee捐助| 50 |Admin| 感谢您的开源项目! |
| 2023-05-29 |微信红包| 20 |TYY| |
| 2023-05-29 |Gitee捐助| 10 |笑看| 感谢您的开源项目! |
| 2023-05-26 |微信红包| 50 |至少还有满天星光照耀你| |
| 2023-02-23 |Gitee捐助| 10 |Yohanes| 感谢您的开源项目! |
| 2023-02-23 |Gitee捐助| 20 |jam_lee| 感谢您的开源项目!希望能支持域名映射|
| 2023-02-10 |Gitee捐助| 5 |实习两年半| 感谢您的开源项目! |
| 2023-02-02 |Gitee捐助| 10 |阳光很暖| 感谢您的开源项目! |
Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

@@ -1,428 +0,0 @@
---
title: 案例
date: 2020-05-14 11:39:45
permalink: /pages/5d571c
# sidebar: false
article: false
---
## 特别用户
::: cardImgList 3
```yaml
config:
imgHeight: 140px
data:
# - img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/blog/QQ20210729-233554@2x.22bdqeiem2sg.png
# link: https://docs.openharmony.cn/pages/000000/
# name: OpenHarmony
# desc: 开放原子开源基金会
# author: OpenHarmony
# avatar: https://www.openharmony.cn/static/img/core-card-item2.a72a0d10.png
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/blog/QQ20211215-144040.hgt2875r2zc.webp
link: https://baomidou.com/
name: MyBatis-Plus官网
desc: 🚀为简化开发而生
author: 青苗
avatar: https://baomidou.com/img/logo.svg
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/blog/QQ20210729-235804@2x.75e9lfd65t40.png
link: https://docs.deepin.org
name: Deepin 社区文档
desc: Deepin 应用开发技术分享、DTK开发经验等
author: Deepin
avatar: https://fastly.jsdelivr.net/gh/xmuli/xmuliPic@pic/2021/deepin.png
- img: https://ks3-cn-beijing.ksyuncs.com/vform-static/img/vform_website.png
link: http://www.vform666.com
name: VForm官网
desc: 低代码表单优选方案,拖拽式设计,一键生成源码
author: vformAdmin
avatar: https://www.vform666.com/vform-logo.png
- img: https://fastly.jsdelivr.net/gh/xugaoyi/blog-gitalk-comment@master/img/xxx.7feub7n1y0g0.png
link: https://liteflow.yomahub.com
name: LiteFlow官网
desc: 轻量,快速,稳定可编排的组件式规则引擎
author: 铂赛东
avatar: https://portrait.gitee.com/uploads/avatars/user/367/1102362_bryan31_1578940308.png!avatar60
- img: https://fastly.jsdelivr.net/gh/xugaoyi/blog-gitalk-comment@master/img/176866696-743faf44-4afd-4c12-9728-f982ea885836.2205nb3vf5mo.webp
link: https://easy-es.cn/
name: Easy-Es官网
desc: 傻瓜级ElasticSearch搜索引擎ORM框架
author: 老汉
avatar: https://iknow.hs.net/9fa0407f-30ff-4d8b-82da-a4990e41a04b.png
```
:::
## 知识库兼博客
::: cardImgList 4
```yaml
config:
imgHeight: 140px
data:
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200530114033.png
link: https://xugaoyi.com/
name: Evan's blog
desc: Web前端技术博客,积跬步以至千里,致敬每个爱学习的你。
author: Evan Xu
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200103123203.jpg
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200727170555.jpg
link: https://miluluyo.github.io/vdoingBlog/
name: 麋鹿鲁哟
desc: 运气交给锦鲤,</br>你只管努力就好。 (●ˇ∀ˇ●)
author: 麋鹿鲁哟
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200122153807.jpg
- img: https://i.loli.net/2020/07/26/BUCplirGIq9YTNA.png
link: https://lingze.xyz/
name: Lingze's blog
desc: 少侠, 别来无恙?
author: 令则
avatar: https://i.loli.net/2020/07/11/XhqR7Idnk5LD8bC.jpg
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/blog/QQ20210730-000829@2x.6pjgy10flk00.png
link: https://www.xswsym.online/
name: summer's blog
desc: Devops运维技术博客,分享运维技术
author: 夏苏文
avatar: https://fastly.jsdelivr.net/gh/summerking1/image@main/tx.png
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20201110221457.png
link: https://gocifer.github.io
name: Gocifer's DB & Blog
desc: 一个中二少年,专注于计算机基础架构、云原生、网络、云计算的所学、所思、所行、所想。
author: gocifer.jay
avatar: https://gocifer.github.io/img/avatar/gocifer.png
- img: https://dra-m.com/images/thumbnail.png
link: https://dra-m.com/
name: Dra-M
desc: JAVA后端
author: 莫小龙
avatar: https://q1.qlogo.cn/g?b=qq&nk=975425198&s=640
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200905142134.png
link: https://www.coder163.com/
name: 跟着老侯玩编程
desc: 一个乐于编程知识分享的站点
author: 舞动的代码
avatar: https://www.coder163.com/img/qun.png
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20210119213748.jpg
link: https://haijunit.top/
name: 学习笔记
desc: 平时的技术积累|分享交流技术心得|温故而知新
author: 爱做梦的奋斗青年
avatar: https://haijunit.top/images/avatar.png
- img: https://fastly.jsdelivr.net/gh/yxw839841231/images/studying-icu/20210120110320.png
link: https://www.studying.icu/
name: 研究院
desc: 一万年太久,只争朝夕
author: xwyang
avatar: https://avatars1.githubusercontent.com/u/13757119?s=80&v=4
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/blog/QQ20210704-230751@2x.3pw80u1fo6u0.png
link: https://blog.kimen.com.cn/
name: Kimen's Blog
desc: 全沾攻城狮
author: Kimen
avatar: https://avatars.githubusercontent.com/u/25970284?s=460&u=69b419ad6de33eaa1d6b73d7f065f710076d6c55&v=4
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/blog/QQ20211101-165045-(1).4b4axinii160.png
link: https://lixianglong3210.gitee.io/xlong-vdoing/
name: XLONG's blog
desc: 蒸汽波、咖啡
author: lixianglong3210
avatar: http://www.lixianglong.cn/bg_store/img/avatar/default.jpg
- img: https://fastly.jsdelivr.net/gh/Awrtiger/mirrorfile/img/web.png
link: https://www.ool.cool/
name: 偷吃了鸡蛋的梨
desc: 捣鼓这,捣鼓那。
author: Awrtiger
avatar: https://fastly.jsdelivr.net/gh/Awrtiger/mirrorfile/img/avatar.jpg
- img: https://image-1302577725.cos.ap-beijing.myqcloud.com/img/20210402183053.png
link: https://f4de-bak.github.io/
name: Xinghai's Blog
desc: Web Security | Java Security
author: Xinghai
avatar: https://image-1302577725.cos.ap-beijing.myqcloud.com/img/20210328234543.jpg
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/blog/eyjf.png
link: https://wiki.eryajf.net
name: 二丫讲梵
desc: 学习,记录,分享。(运维生活编程)
author: 二丫讲梵
avatar: https://wiki.eryajf.net/img/logo.png
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/blog/ss.2tev477ruza0.png
link: https://masongsong.cn
name: 松松的一尺三分地
desc: 记录工作和学习中的所得
author: masongsong
avatar: https://fastly.jsdelivr.net/gh/MssText/learn@master/images/49203535.35emn5vryma0.png
- img: https://fastly.jsdelivr.net/gh/Kele-Bingtang/static/user/20211218235045.png
link: https://notes.youngkbt.cn/
name: Young Kbt Blog
desc: 记录学习Java, Web, 框架, 工具, 前端等相关知识, 记录生活和技术路程, 分享编程技巧。
author: Ship Liu
avatar: https://fastly.jsdelivr.net/gh/Kele-Bingtang/static/user/avatar2.png
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/blog/123.57gcfayi85k0.webp
link: https://blog.dragon1573.wang/
name: 断桥烟雨の学习笔记
desc: 学习爱我,我爱学习~
author: べ断桥烟雨ミ
avatar: https://avatars.githubusercontent.com/u/49941141
- img: https://fastly.jsdelivr.net/gh/xugaoyi/blog-gitalk-comment@master/img/20220210094650.3q09d26tigc0.webp
link: https://chuck6.github.io/blog/
name: 梵一的博客
desc: 个人博客和知识分享
author: 梵一
avatar: https://chuck6.github.io/blog/img/paizhao.jpg
- img: https://fastly.jsdelivr.net/gh/niumoo/cdn-assets/2021/20220316145528.png
link: https://www.wdbyte.com
name: 未读代码
desc: Java 开发知识库,分享原创文章
author: 程序猿阿朗
avatar: https://avatars.githubusercontent.com/u/26371673?v=4
- img: https://fastly.jsdelivr.net/gh/simonzhangs/image-hosting@master/vue-plugin-example/blog.qg2buhe5h4g.webp
link: https://simonzhangs.github.io/
name: 松本松的博客儿
desc: Web前端技术博客,积跬步以至千里。
author: simonzhangs
avatar: https://fastly.jsdelivr.net/gh/simonzhangs/image-hosting@master/20220319/image.4x708q9wzse0.webp
- img: https://fastly.jsdelivr.net/gh/terwer/upload/img/image-20220422000045653.png
link: http://terwergreen.com
name: 远方的灯塔
desc: 专注于服务端技术分享
author: terwer
avatar: https://fastly.jsdelivr.net/gh/terwer/upload/img/photo.jpg
- img: https://fastly.jsdelivr.net/gh/nksuya/image_store@main/suyablog_home.5iou2ogjrm80.webp
link: https://suyaspace.com/
name: Suya's blog
desc: 个人博客,分享技术文章,学习笔记,植物相关知识等。
author: Suya
avatar: https://fastly.jsdelivr.net/gh/nksuya/image_store@master/tech/avatar.2tycyyc1ebr4.jpg
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store2@master/img/QQ20220526-224620@2x.44o82ibj9qq0.png
link: https://colorpanda.aifan.jp/
name: ColorPanda
desc: 日语中文英文学习网站
author: ColorPanda
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store2@master/img/image.3qr8m501tl20.png
- img: https://fastly.jsdelivr.net/gh/xugaoyi/blog-gitalk-comment@master/img/asdf.23jsekfejla8.jpeg
link: https://inannan423.github.io/
name: Zihanio
desc: Zihan的学习博客
author: Zihan
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/blog-gitalk-comment@master/img/xxx.67b3ygadagk0.jpeg
- img: https://user-images.githubusercontent.com/866409/175853573-28ded198-2348-4a82-8ddd-05088161e3fe.png
link: https://xingcxb.com
name: 不器小窝
desc: 但知行好事,莫要问前程
author: 不器
avatar: https://avatars.githubusercontent.com/u/866409?v=4
- img: https://cdn.staticaly.com/gh/xugaoyi/blog-gitalk-comment@master/img/QQ20220722-141037.22uk9ow7ary.png
link: https://eryajf.github.io/vdoing-template/
name: Vdoing主题博客模板
desc: Vdoing主题博客模板
author: eryajf
avatar: https://cdn.staticaly.com/gh/xugaoyi/blog-gitalk-comment@master/img/33259379.277tur21ir40.jpeg
- img: https://cdn.jsdelivr.net/gh/FireHH/github_img_repository/logo/微信截图_20220810164903.png
link: https://javaessay.cn/
name: Java essay
desc: Java散文知识库,Spring全家桶,SpringCloud全家桶,高可用高并发架构,面试等
author: Mr.Fire
avatar: https://cdn.jsdelivr.net/gh/FireHH/github_img_repository/logo/huge.jpg
- img: https://chendapeng.cn/images/about/blog_image.png
link: https://chendapeng.cn
name: 行百里er
desc: Java,个人技术博客,后端开发,技术架构,分布式技术,Spring Cloud Alibaba,Elasticsearch,Redis,算法,数据结构,Git
author: 行百里er
avatar: https://chendapeng.cn/images/about/avatar.png
- img: http://xyhwh-nav.cn/img/index.png
link: http://xyhwh-nav.cn
name: Captain
desc: 学习技术,Java基础、面试知识点、项目经验总结和一些学习笔记
author: Captain
avatar: http://xyhwh-nav.cn/img/logo.png
- img: https://xiaoxue-images.oss-cn-shenzhen.aliyuncs.com/blog/202212271108541.jpg
link: https://blog.xueqimiao.com/
name: 小薛博客
desc: 小薛博客,专注IT技术分享,助力人人成为架构师
author: xueqimiao
avatar: https://xiaoxue-images.oss-cn-shenzhen.aliyuncs.com/blog/202212271110209.png
- img: https://cdn.jsdelivr.net/gh/su-dd/cdn/博客/202301311644669.png
link: https://blog.addai.cn/
name: 呆呆的博客
desc: 个人博客
author: 呆呆
avatar: https://cdn.addai.cn/博客/网站使用/呆呆.webp
- img: https://raw.githubusercontent.com/jorgen-zhao/blog/master/images/snapshot.png
link: https://jorgen.website
name: Jorgen's blog
desc: 🚀个人知识库兼博客🚀
author: jorgen
avatar: https://jorgen.website/img/avatar.jpg
```
:::
## 知识库
::: cardImgList 4
```yaml
config:
imgHeight: 150px
data:
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200530114035.png
link: https://xugaoyi.github.io/vdoing-demo-repository/
name: 知识库演示
desc: Vdoing主题演示-知识库
author: Evan Xu
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200103123203.jpg
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20201219205536.jpg
link: https://wiki.router86.com/
name: X86软路由和NAS
desc: 记录X86软路由和NAS的一些知识
author: MonoLogueChi
avatar: https://blog.xxwhite.com/assets/img/avatar.jpg
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20201219205318.jpg
link: https://blog.northword.cn/dft-learning
name: 计算模拟学习笔记
desc: 《能源环境材料计算模拟方法》学习笔记,涉及密度泛函理论、第一性原理等和Materials Studio、VASP等的使用。
author: Northword
avatar: https://storage.live.com/items/28C1032A24A9C53B!25785?authkey=AHAx3GOYEKGqm8I
- img: https://singerwimg-1300001977.cos.accelerate.myqcloud.com/20211008/WccSrJ0s.png
link: https://repository.singerw.com
name: Singerw's Repository
desc: 技术的风花雪月之事,有个存档的地方,对于复盘,回忆,都是一个极好的方谭。
author: Singerw
avatar: https://singerwimg-1300001977.cos.accelerate.myqcloud.com/2021/09/20/76f29482ffc9b.png
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/QQ20220113-114927.5oha7j06c580.webp
link: https://hippox.cn
name: hippo4j
desc: 🔥 动态线程池框架,附带监控报警功能,轻量级的运维监控平台
author: Acmenlt
avatar: https://hippox.cn/img/web.png
- img: https://pics.landcover100.com/pics/624e63f4c82b8.png
link: https://www.gisrsdata.com
name: 地信遥感数据汇
desc: 解决目前地信遥感方向数据混杂,资源难以寻找的问题,构建一个地信遥感学习、讨论、交流的平台。
author: 锐多宝
avatar: https://pics.landcover100.com/pics/624e6469cbb8a.jpg
- img: https://img.de7v.com/img/site-pic.jpg
link: https://www.de7v.com
name: De7v
desc: 专注于安卓领域的技术传播
author: wresource
avatar: https://img.de7v.com/img/wresource.png
- img: https://cdn.staticaly.com/gh/xugaoyi/blog-gitalk-comment@master/img/11123.1aljpnjyr074.png
link: https://eryajf.github.io/HowToStartOpenSource/
name: HowToStartOpenSource
desc: GitHub开源项目维护协作指南
author: eryajf
avatar: https://cdn.staticaly.com/gh/xugaoyi/blog-gitalk-comment@master/img/33259379.277tur21ir40.jpeg
```
:::
## 社区类
:::cardImgList
```yaml
config:
imgHeight: 150px
data:
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200921220111.png
link: https://course.yiwiz.com/
name: 奕维投资教程站
desc: 股票投资
author: 奕维
avatar: https://course.yiwiz.com/img/logo.png
```
:::
## 博客类
::: cardImgList
```yaml
config:
imgHeight: 150px
data:
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200530114034.png
link: https://xugaoyi.github.io/vdoing-demo-blog/
name: Vdoing's blog
desc: Vdoing主题演示-博客
author: Evan Xu
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200103123203.jpg
- img: https://s4.ax1x.com/2022/02/25/bAjINF.png
link: https://fangweilong.github.io/
name: Teler的日常
desc: 好好学习,天天向上
author: Teler
avatar: https://s4.ax1x.com/2022/02/25/bAva8J.jpg
- img: https://user-images.githubusercontent.com/53399655/163007243-1b99b96b-cac3-49ca-9950-03a1e877a6d8.png
link: https://xustudyxu.github.io/
name: xustudyxu's Blog
desc: 一起学习编程!
author: xustudyxu
avatar: https://xustudyxu.github.io/img/01.png
```
:::
## 文档类
::: cardImgList 4
```yaml
config:
imgHeight: 150px
data:
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200530114036.png
link: https://xugaoyi.github.io/vuepress-theme-vdoing-doc/
name: vdoing(本站)
desc: 🚀一款简洁高效的VuePress 知识管理&博客 主题
author: Evan Xu
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200103123203.jpg
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/blog/QQ20210925-124836@2x (1).6ogvf85maog0.png
link: https://justauth.plus/
name: JA Plus 开发者文档
desc: 一款开源的登录认证中间件
author: yadong,zhang
avatar: https://portrait.gitee.com/uploads/avatars/user/261/784199_yadong.zhang_1578932767.png!avatar200
- img: https://assets.imgradeone.com/docsdokimod-pv.png
link: https://docs.dokimod.cn
name: DokiMod 开发文档
desc: 为 DDLC Mod 开发提供的完善文档
author: imgradeone
avatar: https://docs.dokimod.cn/logo_128x128.png
# - img: http://yogoyun.oss-cn-beijing.aliyuncs.com/help/public/other/20200630125515.png
# link: http://help.yogoyun.com/
# name: 柚果云服务
# desc: 智能商业应用程序服务商。
# author: 柚果云服务
# avatar: http://yogoyun.oss-cn-beijing.aliyuncs.com/admin/console/logo.png
- img: https://jeesite.com/docs/img/docs.png
link: http://docs.jeesite.com
name: JeeSite 在线文档
desc: JeeSite 快速开发平台 - 在线文档
author: ThinkGem
avatar: https://jeesite.com/docs/img/logo.png
- img: https://fastly.jsdelivr.net/gh/dreamncn/picBed@master/uPic/2022_05_12_20_40_04_1652359204_1652359204449_9mVkaR.png
link: https://cleanphp.ankio.net/
name: CleanPHP
desc: CleanPHP开发者文档
author: ankio
avatar: https://fastly.jsdelivr.net/gh/dreamncn/picBed@master/uPic/2022_04_04_19_48_51_1649072931_1649072931346_h4BGpQ.jpg
- img: https://cdn.staticaly.com/gh/xugaoyi/blog-gitalk-comment@master/img/28284d.56bkx9qvhc00.webp
link: https://jpom-docs.keepbx.cn/
name: Jpom
desc: 简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件
author: 不忘初心
avatar: https://jpom-docs.keepbx.cn/images/jpom_logo.png
- img: https://wumei.live/kerwincui/document/raw/branch/master/images/img.png
link: https://wumei.live/doc/
name: 物美智能
desc: 物美智能开源物联网平台,简单易用,可用于搭建物联网平台以及二次开发和学习。适用于智能家居、智慧办公、智慧社区、农业监测、水利监测、工业控制等。
author: 随遇而安 / kerwincui
avatar: https://portrait.gitee.com/uploads/avatars/user/611/1834441_kerwincui_1581523162.png!avatar200
- img: https://pic.imgdb.cn/item/637c92ef16f2c2beb11293c7.jpg
link: https://trace-recorder.xusc.cn
name: trace-recorder官网
desc: 简单的, 可伸缩的, 高性能的跟踪记录仪
author: 蔡旺
avatar: https://pic.imgdb.cn/item/637c933516f2c2beb112ed43.jpg
```
:::
</br></br>
## 申请加入案例
::: tip 你想在这个页面展示你的站点吗?
欢迎使用Vdoing主题的小伙伴到 [**这里**](https://github.com/xugaoyi/vuepress-theme-vdoing/issues/new?assignees=&labels=&template=+join_case.md) 留下你的站点信息,你的站点将有机会出现在这个页面里~
:::
@@ -0,0 +1,21 @@
---
title: 企业登记案例
date: 2020-05-14 11:39:45
permalink: /pages/5d571c
---
## 企业登记
如果您所在的企业使用到了中微子代理,麻烦抽出宝贵的时间[点击这里](https://gitee.com/dromara/neutrino-proxy/issues/I6WC71)进行登记,这将会助力中微子代理走得更远。
## 案例展示
|企业名称| logo | 官网地址 | 使用人数 |用途|
|:-|:-----------------------------------------------------------------------------------------------------------|:-------------------------|:-----|:-|
|北京吉威空间信息股份有限公司| ![logo](https://foruda.gitee.com/images/1681717113043198586/b7f0b6b3_8006959.png) | http://www.geoway.com.cn | 10 |开发环境的session会话同步与开发环境的对接测试华为云与微信等云端回调接口|
|悦邻供应链科技有限公司|![logo](https://foruda.gitee.com/images/1684996781645413637/277d0b9b_5234431.png)| https://yinghecloud.com | 10 |开发及生产环境内网服务器VPN搭建,本地开发服务端口调试|
|阿依瓦(北京)技术有限公司|![logo](https://foruda.gitee.com/images/1684998300312143862/74a96cec_4788807.png)| https://www.alva.com.cn | 50 |开发及生产环境内网服务器VPN搭建,本地开发服务端口调试,云端同步数据等|
|常州裕景信息科技有限公司|![logo](https://foruda.gitee.com/images/1688542732735699110/bb238481_2342807.jpeg)| 无 | 5 |远程管理虚拟机,接口、数据库等功能调试|
|湖北楚商联盟金融信息服务有限公司|![logo](https://foruda.gitee.com/images/1675831034400302128/3621dd88_9993534.png)| http://www.chually.cn | 10 |开发及生产环境内网服务器VPN搭建,本地开发服务端口调试,云端同步数据等|
|中科元景智能(深圳)有限公司|![logo](https://foruda.gitee.com/images/1689584776864189267/d66c78a8_9373604.png)| https://yjupi.com | 20 |本地开发连接测试环境及生产环境内网服务器,包含数据库、redis、模块服务等|
|长沙小蜂科技有限公司|![logo](https://foruda.gitee.com/images/1692156755102754023/c7d82730_1207310.png)| 无 | 1~3人 |主要用于测试环境内网服务器,包含数据库,远程桌面等|
|江西六湛科技|![logo](https://foruda.gitee.com/images/1694073479398767455/0f7570a7_10166803.png)| http://www.liuzhankj.com | 12 |主要用于部署一些使用时长较短的项目|
|杭州艾佳智能科技有限公司|![logo](https://foruda.gitee.com/images/1695608742879653626/314c72a4_5720492.png)| http://www.ajaiot.com | 12-16人 |公司部分设备是通过udp通信的,对接调试设备,搭建测试环境服务网关的时候需要|
@@ -0,0 +1,15 @@
---
title: 2.x
date: 2023-10-26 17:43:43
permalink: /pages/ff3519/
---
## 2.0.0
- solon版本升级为`2.5.11`
- jdk版本升级为17
- 支持原生编译改造
- 默认支持的数据库由sqlite改为h2
- 升级须知:
- jdk版本升级为了jdk17,jar部署时请注意
- 去掉了默认的sqlite数据库,改为了h2。如果之前使用sqlite,请自行处理数据迁移
- 配置文件做了较大调整,请参照官网使用须知中的`服务端配置``客户端配置`进行更新
@@ -0,0 +1,72 @@
---
title: 1.x
date: 2023-10-26 17:43:34
permalink: /pages/dda8e5/
---
## 1.9.0
- 端口映射支持UDP协议
- 端口映射HTTP(S)新增打开网页按钮,优先使用域名打开
- 新增/编辑端口映射时端口占用检测功能屏蔽,解决docker下使用的各种问题
## 1.8.6
- 端口映射选择端口支持分页
- 新增/更新端口映射,增加端口占用检测(端口映射编辑时,如果端口号没有变动,则不验证。避免出现端口映射正在使用时,无法更新端口映射其他信息的问题)
- 下拉搜索license,支持模糊搜索
- license下拉用户搜索,支持模糊搜索
- 端口映射HTTP(S)新增打开网页按钮
- 客户端断开连接时,记录日志空指针异常问题修复
## 1.8.5
- 后台端口池管理支持批量删除
- 端口映射下拉选择端口支持搜索
- 客户端/服务端配置增加心跳日志开关,有需要时开启,方便排查问题
- 客户端/服务端读写超时逻辑微调
## 1.8.4
- 增加对mariadb的支持
- 服务端/客户端,支持配置文件、启动参数指定日志级别
- 客户端支持配置文件指定重连间隔,是否开启无限重连
## 1.8.3
- 客户端重连优化
- 服务端验证客户端身份逻辑优化
## 1.8.2
- 增加HTTPS的支持。
## 1.8.0
- 端口映射支持选择协议
- HTTP协议下映射支持配置子域名(前提是server端配置了域名)
## 1.7.1
- 针对管理后台各页面的排序、搜索条件的优化
- 增加端口池分组、相关的端口映射优化
- 增加了报表管理模块:用户流量报表、license流量报表、用户流量月度明细、license流量月度明细
- 完成首页图表展示:license统计、端口映射统计、今日流量统计、汇总流量统计、最近15日流量折线图
## 1.7.0
- 底层框架更换为Solon + MybatisPlus
## 1.6.4
- 支持代理服务端用户(删除/禁用)、端口池(删除/禁用/启用)、License(删除/禁用/启用)、端口映射(新增/删除/禁用/启用)实时生效
- 启动参数优化
- 服务端静态资源服务支持缓存、gzip压缩,提升响应速度
## 1.6.0
- 新增日志管理模块,调度日志迁移至日志管理
- 管理后台新增登录日志
- 新增首页流量统计
## 1.5.0
- 管理后台新增用户管理、License管理、端口映射、调度管理、客户端连接日志、调度日志
- 支持默认sqlite数据库
- 底层框架Aop、Banner、SqlMapper、Ioc、@Value注解、Yml配置加载、静态资源服务等优化
- 底层框架新增@Async@Singleton等注解
- 新增AsgcCompiler、ApplicationEvent机制封装
## 1.0.0
> 第一个基本可用版本
- 基于自研底层应用框架、Netty
- 纯配置实现、无管理后台
- 支持TCP代理
@@ -0,0 +1,24 @@
---
title: 里程碑
date: 2023-10-26 17:41:26
permalink: /pages/e406dd/
---
- 2023-10-31 2.0.0版本发布,升级jdk版本至jdk17、支持原生编译、去掉sqlite改为默认H2数据库
- 2023-09-25 中微子代理Gitee Star数突破1000
- 2023-09-22 1.9.0版本发布,端口映射支持UDP协议
- 2023-09-07 1.8.6版本发布,后台若干细节优化
- 2023-07-04 1.8.5版本发布,后台若干细节优化、配置文件优化
- 2023-06-08 1.8.4版本发布,增加对mariadb的支持、配置文件支持指定日志级别、重连参数
- 2023-06-03 1.8.3版本发布,客户端重连优化、服务端验证客户端身份逻辑优化
- 2023-05-27 1.8.2版本发布,增加HTTPS支持
- 2023-04-03 1.8.0版本发布,端口映射支持选择协议,HTTP映射支持子域名
- 2023-03-26 1.7.1版本发布,新增端口池分组、新增报表管理模块、首页图表优化
- 2023-03-12 1.7.0版本发布,底层框架更换为Solon + MybatisPlus
- 2023-02-23 1.6.4版本发布,管理后台增、删、改、禁用实时生效,启动参数、GZip优化
- 2023-01-16 加入Dromara组织
- 2023-01-08 中微子代理Gitee Star突破100
- 2022-11-23 1.6.0版本发布,新增日志管理模块,新增首页流量统计
- 2022-10-12 1.5.0版本发布,底层框架优化,新增管理后台
- 2022-06-16 1.0.0版本发布,基于自研应用框架、纯配置无管理后台
@@ -1,39 +0,0 @@
---
title: 最近更新
date: 2020-05-25 12:01:52
permalink: /pages/9cc27d
# sidebar: false
article: false
---
# TODO
### 1.x剩余规划
- [ ] 参考鹊桥,增加对域名的支持
- [ ] 官网上线
#### Bug
- windows环境下直接运行发布版的jar包,日志输出乱码
- 代理mysql时,使用未开启远程访问的账号走代理访问mysql,代理客户端出现断开现象
### 2.x规划
- 插件开发
- [ ] neutrino-proxy-solon-plugin
- [ ] neutrino-proxy-jetbrains-plugin
- [ ] neutrino-proxy-starter (SpringBoot)
- 基础优化
- [ ] 支持批量端口映射
- [ ] 代理协议规范化,方便后续更好扩展、支持不同语言客户端接入
- 监控运维
- [ ] 监控服务端状态
- [ ] 支持调整服务端端口、证书
- [ ] 服务端版本、协议版本、客户端版本管理
- [ ] 服务端日志
- client+计划启动
- [ ] 安卓客户端
- [ ] 基于electron-egg的多平台客户端
### 3.x规划
- [ ] 支持针对用户限速、限流
- [ ] 支持P2P穿透
- [ ] 支持原生编译
@@ -1,45 +0,0 @@
---
title: 如何贡献
date: 2023-03-29 12:55:44
permalink: /pages/69a632/
---
# 🎋代码分支说明
- dev:常规开发分支,日常的开发、Bug修复、提交PR都在此分支
- feature/xxx:特征分支,一些试验性开发、提交PR都在此分支,xxx可自行取一个有意义的名称
- release/xxx:发行版分支,作为阶段性成果、重大更新的版本固化分支。受保护,不允许任何提交。
- master:主分支,定期同步最新代码,目前仅支持本人(傲世孤尘/雨韵诗泽)提交。
# ✊贡献的方式
包括,但不限于以下形式:
- 提交[issues](https://gitee.com/dromara/neutrino-proxy/issues)
- 参与[issues](https://gitee.com/dromara/neutrino-proxy/issues)解决的必要性、解决方案的讨论、给出建设性的意见
- 领取[issues](https://gitee.com/dromara/neutrino-proxy/issues) 并完成开发、提交PR
- 为[本项目](https://gitee.com/dromara/neutrino-proxy)完善代码注释
- 为[本项目](https://gitee.com/dromara/neutrino-proxy)增加测试代码
- 为[本项目](https://gitee.com/dromara/neutrino-proxy)优化现有代码
- 为[本项目](https://gitee.com/dromara/neutrino-proxy)优化操作体验
- 通过公众号、博客、贴吧、论坛等形式分享/宣传中微子代理
- 丰富项目文档,包括使用过程中踩过的坑、需要优化的点、具体问题的解决方法等
# 🧬贡献代码的步骤
- 在Gitee或者Github上fork项目到自己的repofork,一定要把项目fork一份。
- 把fork过去的项目也就是你的项目clone到你的本地
- 同步feature/1.7.1最新代码
- 修改代码
- 开发完成后,不忙着提PR,再拉一遍最新代码,如果有冲突、解决冲突
- commit后push到自己的库
- 登录Gitee在你首页可以看到一个 pull request 按钮,点击它,填写一些说明信息,然后提交即可。
- 等待维护者合并
# 📐PR的规范
> 你可能发现有的代码并不符合这个规范,但我们后续都会朝着这个方向迈进。后续我们会逐步全面规范化,请先保证新提交的代码满足以下要求:
- 注释完备,尤其每个新增的方法应按照Java文档规范标明方法说明、参数说明、返回值说明等信息,必要时请添加单元测试,如果愿意,也可以加上你的大名。
- 原则上,不允许自行添加第三方依赖库。若确有必要,请先和项目负责人沟通达成一致。
- 对现有代码的优化,请在注释中说明原因。
- 如果涉及到数据库的变更,则至少需要做到以下绩点:
- sqlite、mysql下均测试通过
- 在`./neutrino-proxy-server/src/main/resource/sql`下维护更新对应的表结构sql文件
- 对于新增的表,维护必要的初始化数据
- 在对应的mysql、sqlite的update目录下,维护SQL变更记录,确保SQL执行测试通过
+145 -111
View File
@@ -20,13 +20,13 @@ features: # 可选的
- title: 端口映射
details: 新增、编辑、删除、禁用实时生效。
- title: Docker
details: 服务端支持Docker一键部署。
details: 服务端/客户端支持Docker一键部署。
- title: 隧道SSL加密
details: 隧道通信支持SSL,保护您的数据安全
- title: HTTPS
details: 支持HTTPS
- title: 多客户端支持
details: 暂未支持
- title: 多种协议
details: 支持TCP、HTTP、HTTPS、UDP
- title: 原生部署
details: 支持原生部署,更简单、性能更好的使用体验
# 文章列表显示方式: detailed 默认,显示详细版文章列表(包括作者、分类、标签、摘要、分页等)| simple => 显示简约版文章列表(仅标题和日期)| none 不显示文章列表
postList: none
@@ -137,166 +137,200 @@ postList: none
或者加入我们的交流群:
<div align="center">
<img :src="$withBase('/img/qrcode/wxq.png')" class="no-zoom" style="width:200px;margin: 10px;">
<p>中微子 微信群(添加我微信备注"进群")</p>
<p>中微子 微信群(添加我微信备注"中微子进群")</p>
</div>
<br/>
<h2 id="🌈特别赞助"><a href="#🌈特别赞助" class="header-anchor">#</a> 🌈特别赞助</h2>
<p align="center">等待有缘人赞助</p>
<!-- <p align="center">等待有缘人赞助</p> -->
<div class="cardListContainer">
<div class="card-list">
<a href="https://www.xigexb.com" target="_blank" class="card-item row-3" style="background-color:#102863;--random-color:#102863;color:#FFFFFF;">
<img src="img/sponsor/xigexiaobao.jpg" class="no-zoom">
<div><p class="name">喜鸽小宝</p> <p class="desc">一个爱好写代码的同学</p></div>
</a>
</div>
</div>
<!--
<div class="cardListContainer"><div class="card-list"><a href="https://datayi.cn/w/nPN4JJe9" target="_blank" class="card-item row-3" style="background-color:#102863;--random-color:#102863;color:#FFFFFF;"><img src="/images/friends/ad/postcat.png" class="no-zoom"> <div><p class="name">Postcat</p> <p class="desc">开源 API 管理工具,接口文档、接口测试、Mock...</p></div></a> <a href="https://gitee.com/dromara/MaxKey" target="_blank" class="card-item row-3" style="background-color:#9ed3e5;--random-color:#9ed3e5;color:#1f2328;"><img src="/images/friends/ad/maxkey.png" class="no-zoom"> <div><p class="name">MaxKey</p> <p class="desc"><br>业界领先的单点登录</p></div></a> <a href="https://www.xiaonuo.vip" target="_blank" class="card-item row-3" style="background-color:#1373CC;--random-color:#1373CC;color:#ffffff;"><img src="/images/friends/ad/snowy.png" class="no-zoom"> <div><p class="name">Snowy</p> <p class="desc">国内首个国密前后端分离快速开发平台,中国式工作流、独家双模式租户</p></div></a></div><div class="language-yaml line-numbers-mode"><pre class="language-yaml codecopy-enabled"><code><span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Postcat
<span class="token key atrule">desc</span><span class="token punctuation">:</span> 开源 API 管理工具,接口文档、接口测试、Mock<span class="token punctuation">...</span>
<span class="token key atrule">avatar</span><span class="token punctuation">:</span> /images/friends/ad/postcat.png
<span class="token key atrule">link</span><span class="token punctuation">:</span> https<span class="token punctuation">:</span>//datayi.cn/w/nPN4JJe9
<span class="token key atrule">bgColor</span><span class="token punctuation">:</span> <span class="token string">'#102863'</span>
<span class="token key atrule">textColor</span><span class="token punctuation">:</span> <span class="token string">'#FFFFFF'</span>
<span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> MaxKey
<span class="token key atrule">desc</span><span class="token punctuation">:</span> &lt;/br<span class="token punctuation">&gt;</span>业界领先的单点登录
<span class="token key atrule">avatar</span><span class="token punctuation">:</span> /images/friends/ad/maxkey.png
<span class="token key atrule">link</span><span class="token punctuation">:</span> https<span class="token punctuation">:</span>//gitee.com/dromara/MaxKey
<span class="token key atrule">bgColor</span><span class="token punctuation">:</span> <span class="token string">'#9ed3e5'</span>
<span class="token key atrule">textColor</span><span class="token punctuation">:</span> <span class="token string">'#1f2328'</span>
<span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Snowy
<span class="token key atrule">desc</span><span class="token punctuation">:</span> 国内首个国密前后端分离快速开发平台,中国式工作流、独家双模式租户
<span class="token key atrule">avatar</span><span class="token punctuation">:</span> /images/friends/ad/snowy.png
<span class="token key atrule">link</span><span class="token punctuation">:</span> https<span class="token punctuation">:</span>//www.xiaonuo.vip
<span class="token key atrule">bgColor</span><span class="token punctuation">:</span> <span class="token string">'#1373CC'</span>
<span class="token key atrule">textColor</span><span class="token punctuation">:</span> <span class="token string">'#ffffff'</span>
</code><i class="code-copy" title="Copy to clipboard"><svg style="color:#aaa;font-size:14px" t="1572422231464" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3201" width="14" height="14"><path d="M866.461538 39.384615H354.461538c-43.323077 0-78.769231 35.446154-78.76923 78.769231v39.384616h472.615384c43.323077 0 78.769231 35.446154 78.769231 78.76923v551.384616h39.384615c43.323077 0 78.769231-35.446154 78.769231-78.769231V118.153846c0-43.323077-35.446154-78.769231-78.769231-78.769231z m-118.153846 275.692308c0-43.323077-35.446154-78.769231-78.76923-78.769231H157.538462c-43.323077 0-78.769231 35.446154-78.769231 78.769231v590.769231c0 43.323077 35.446154 78.769231 78.769231 78.769231h512c43.323077 0 78.769231-35.446154 78.76923-78.769231V315.076923z m-354.461538 137.846154c0 11.815385-7.876923 19.692308-19.692308 19.692308h-157.538461c-11.815385 0-19.692308-7.876923-19.692308-19.692308v-39.384615c0-11.815385 7.876923-19.692308 19.692308-19.692308h157.538461c11.815385 0 19.692308 7.876923 19.692308 19.692308v39.384615z m157.538461 315.076923c0 11.815385-7.876923 19.692308-19.692307 19.692308H216.615385c-11.815385 0-19.692308-7.876923-19.692308-19.692308v-39.384615c0-11.815385 7.876923-19.692308 19.692308-19.692308h315.076923c11.815385 0 19.692308 7.876923 19.692307 19.692308v39.384615z m78.769231-157.538462c0 11.815385-7.876923 19.692308-19.692308 19.692308H216.615385c-11.815385 0-19.692308-7.876923-19.692308-19.692308v-39.384615c0-11.815385 7.876923-19.692308 19.692308-19.692308h393.846153c11.815385 0 19.692308 7.876923 19.692308 19.692308v39.384615z" p-id="3202"></path></svg></i></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br></div></div></div>
<div class="cardListContainer">
<div class="card-list">
<a href="https://datayi.cn/w/nPN4JJe9" target="_blank" class="card-item row-3" style="background-color:#102863;--random-color:#102863;color:#FFFFFF;">
<img src="/images/friends/ad/postcat.png" class="no-zoom">
<div><p class="name">Postcat</p> <p class="desc">开源 API 管理工具,接口文档、接口测试、Mock...</p></div>
</a>
<a href="https://gitee.com/dromara/MaxKey" target="_blank" class="card-item row-3" style="background-color:#9ed3e5;--random-color:#9ed3e5;color:#1f2328;">
<img src="/images/friends/ad/maxkey.png" class="no-zoom">
<div><p class="name">MaxKey</p> <p class="desc"><br>业界领先的单点登录</p></div>
</a>
<a href="https://www.xiaonuo.vip" target="_blank" class="card-item row-3" style="background-color:#1373CC;--random-color:#1373CC;color:#ffffff;">
<img src="/images/friends/ad/snowy.png" class="no-zoom">
<div><p class="name">Snowy</p> <p class="desc">国内首个国密前后端分离快速开发平台,中国式工作流、独家双模式租户</p></div>
</a>
</div>
<div class="language-yaml line-numbers-mode"><pre class="language-yaml codecopy-enabled">
<code>
<span class="token punctuation">-</span>
<span class="token key atrule">name</span>
<span class="token punctuation">:</span> Postcat
<span class="token key atrule">desc</span>
<span class="token punctuation">:</span>
开源 API 管理工具,接口文档、接口测试、Mock<span class="token punctuation">...</span>
<span class="token key atrule">avatar</span><span class="token punctuation">:</span> /images/friends/ad/postcat.png
<span class="token key atrule">link</span><span class="token punctuation">:</span> https<span class="token punctuation">:</span>//datayi.cn/w/nPN4JJe9
<span class="token key atrule">bgColor</span><span class="token punctuation">:</span> <span class="token string">'#102863'</span>
<span class="token key atrule">textColor</span><span class="token punctuation">:</span> <span class="token string">'#FFFFFF'</span>
<span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> MaxKey
<span class="token key atrule">desc</span><span class="token punctuation">:</span> &lt;/br<span class="token punctuation">&gt;</span>业界领先的单点登录
<span class="token key atrule">avatar</span><span class="token punctuation">:</span> /images/friends/ad/maxkey.png
<span class="token key atrule">link</span><span class="token punctuation">:</span> https<span class="token punctuation">:</span>//gitee.com/dromara/MaxKey
<span class="token key atrule">bgColor</span><span class="token punctuation">:</span> <span class="token string">'#9ed3e5'</span>
<span class="token key atrule">textColor</span><span class="token punctuation">:</span> <span class="token string">'#1f2328'</span>
<span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Snowy
<span class="token key atrule">desc</span><span class="token punctuation">:</span> 国内首个国密前后端分离快速开发平台,中国式工作流、独家双模式租户
<span class="token key atrule">avatar</span><span class="token punctuation">:</span> /images/friends/ad/snowy.png
<span class="token key atrule">link</span><span class="token punctuation">:</span> https<span class="token punctuation">:</span>//www.xiaonuo.vip
<span class="token key atrule">bgColor</span><span class="token punctuation">:</span> <span class="token string">'#1373CC'</span>
<span class="token key atrule">textColor</span><span class="token punctuation">:</span> <span class="token string">'#ffffff'</span>
</code><i class="code-copy" title="Copy to clipboard"><svg style="color:#aaa;font-size:14px" t="1572422231464" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3201" width="14" height="14"><path d="M866.461538 39.384615H354.461538c-43.323077 0-78.769231 35.446154-78.76923 78.769231v39.384616h472.615384c43.323077 0 78.769231 35.446154 78.769231 78.76923v551.384616h39.384615c43.323077 0 78.769231-35.446154 78.769231-78.769231V118.153846c0-43.323077-35.446154-78.769231-78.769231-78.769231z m-118.153846 275.692308c0-43.323077-35.446154-78.769231-78.76923-78.769231H157.538462c-43.323077 0-78.769231 35.446154-78.769231 78.769231v590.769231c0 43.323077 35.446154 78.769231 78.769231 78.769231h512c43.323077 0 78.769231-35.446154 78.76923-78.769231V315.076923z m-354.461538 137.846154c0 11.815385-7.876923 19.692308-19.692308 19.692308h-157.538461c-11.815385 0-19.692308-7.876923-19.692308-19.692308v-39.384615c0-11.815385 7.876923-19.692308 19.692308-19.692308h157.538461c11.815385 0 19.692308 7.876923 19.692308 19.692308v39.384615z m157.538461 315.076923c0 11.815385-7.876923 19.692308-19.692307 19.692308H216.615385c-11.815385 0-19.692308-7.876923-19.692308-19.692308v-39.384615c0-11.815385 7.876923-19.692308 19.692308-19.692308h315.076923c11.815385 0 19.692308 7.876923 19.692307 19.692308v39.384615z m78.769231-157.538462c0 11.815385-7.876923 19.692308-19.692308 19.692308H216.615385c-11.815385 0-19.692308-7.876923-19.692308-19.692308v-39.384615c0-11.815385 7.876923-19.692308 19.692308-19.692308h393.846153c11.815385 0 19.692308 7.876923 19.692308 19.692308v39.384615z" p-id="3202"></path></svg></i></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br></div>
</div>
</div>
-->
<h2 id="🤝-dromara-组织项目"><a href="#🤝-dromara-组织项目" class="header-anchor">#</a> 🤝 dromara 组织项目</h2>
<p align="center"><b><a href="https://dromara.org/zh/projects/" target="_blank">为往圣继绝学,一个人或许能走的更快,但一群人会走的更远。</a></b></p>
<p align="center"><b><a href="https://dromara.org/zh/projects/?from=neutrino-proxy" target="_blank">为往圣继绝学,一个人或许能走的更快,但一群人会走的更远。</a></b></p>
<p>
<a href="https://gitee.com/dromara/TLog" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/tlog.png" msg="一个轻量级的分布式日志标记追踪神器,10分钟即可接入,自动对日志打标签完成微服务的链路追踪">
<a href="https://gitee.com/dromara/TLog?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/tlog.png" msg="一个轻量级的分布式日志标记追踪神器,10分钟即可接入,自动对日志打标签完成微服务的链路追踪">
</a>
<a href="https://gitee.com/dromara/liteFlow" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/liteflow.png" msg="轻量,快速,稳定,可编排的组件式流程引擎">
<a href="https://gitee.com/dromara/liteFlow?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/liteflow.png" msg="轻量,快速,稳定,可编排的组件式流程引擎">
</a>
<a href="https://hutool.cn/" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/hutool.jpg" msg="🍬小而全的Java工具类库,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。">
<a href="https://hutool.cn/?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/hutool.jpg" msg="🍬小而全的Java工具类库,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。">
</a>
<a href="https://sa-token.cc/" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/sa-token.png" msg="一个轻量级 java 权限认证框架,让鉴权变得简单、优雅!">
<a href="https://sa-token.cc/?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/sa-token.png" msg="一个轻量级 java 权限认证框架,让鉴权变得简单、优雅!">
</a>
<a href="https://gitee.com/dromara/hmily" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/hmily.png" msg="高性能一站式分布式事务解决方案。">
<a href="https://gitee.com/dromara/hmily?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/hmily.png" msg="高性能一站式分布式事务解决方案。">
</a>
<a href="https://gitee.com/dromara/Raincat" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/raincat.png" msg="强一致性分布式事务解决方案。">
<a href="https://gitee.com/dromara/Raincat?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/raincat.png" msg="强一致性分布式事务解决方案。">
</a>
<a href="https://gitee.com/dromara/myth" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/myth.png" msg="可靠消息分布式事务解决方案。">
<a href="https://gitee.com/dromara/myth?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/myth.png" msg="可靠消息分布式事务解决方案。">
</a>
<a href="https://cubic.jiagoujishu.com/" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/cubic.png" msg="一站式问题定位平台,以agent的方式无侵入接入应用,完整集成arthas功能模块,致力于应用级监控,帮助开发人员快速定位问题">
<a href="https://cubic.jiagoujishu.com/?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/cubic.png" msg="一站式问题定位平台,以agent的方式无侵入接入应用,完整集成arthas功能模块,致力于应用级监控,帮助开发人员快速定位问题">
</a>
<a href="https://maxkey.top/" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/maxkey.png" msg="业界领先的身份管理和认证产品">
<a href="https://maxkey.top/?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/maxkey.png" msg="业界领先的身份管理和认证产品">
</a>
<a href="http://forest.dtflyx.com/" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/forest-logo.png" msg="Forest能够帮助您使用更简单的方式编写Java的HTTP客户端" nf="">
<a href="http://forest.dtflyx.com/?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/forest-logo.png" msg="Forest能够帮助您使用更简单的方式编写Java的HTTP客户端" nf="">
</a>
<a href="https://jpom.top/" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/jpom.png" msg="一款简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件">
<a href="https://jpom.top/?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/jpom.svg" msg="一款简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件">
</a>
<a href="https://su.usthe.com/" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/sureness.png" msg="面向 REST API 的高性能认证鉴权框架">
<a href="https://su.usthe.com/?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/sureness.png" msg="面向 REST API 的高性能认证鉴权框架">
</a>
<a href="https://easy-es.cn/" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/easy-es2.png" msg="🚀傻瓜级ElasticSearch搜索引擎ORM框架">
<a href="https://easy-es.cn/?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/easy-es2.png" msg="🚀傻瓜级ElasticSearch搜索引擎ORM框架">
</a>
<a href="https://gitee.com/dromara/northstar" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/northstar_logo.png" msg="Northstar盈富量化交易平台">
<a href="https://gitee.com/dromara/northstar?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/northstar_logo.png" msg="Northstar盈富量化交易平台">
</a>
<a href="https://hertzbeat.com/" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/hertzbeat-brand.svg" msg="易用友好的云监控系统">
<a href="https://hertzbeat.com/?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/hertzbeat-brand.svg" msg="易用友好的云监控系统">
</a>
<a href="https://dromara.gitee.io/fast-request/" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/fast-request.gif" msg="Idea 版 Postman,为简化调试API而生">
<a href="https://dromara.gitee.io/fast-request/?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/fast-request.gif" msg="Idea 版 Postman,为简化调试API而生">
</a>
<a href="https://www.jeesuite.com/" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/mendmix.png" msg="开源分布式云原生架构一站式解决方案">
<a href="https://www.jeesuite.com/?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/mendmix.png" msg="开源分布式云原生架构一站式解决方案">
</a>
<a href="https://gitee.com/dromara/koalas-rpc" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/koalas-rpc2.png" msg="企业生产级百亿日PV高可用可拓展的RPC框架。">
<a href="https://gitee.com/dromara/koalas-rpc?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/koalas-rpc2.png" msg="企业生产级百亿日PV高可用可拓展的RPC框架。">
</a>
<a href="https://async.sizegang.cn/" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/gobrs-async.png" msg="🔥 配置极简功能强大的异步任务动态编排框架">
<a href="https://async.sizegang.cn/?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/gobrs-async.png" msg="🔥 配置极简功能强大的异步任务动态编排框架">
</a>
<a href="https://dynamictp.cn/" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/dynamic-tp.png" msg="🔥🔥🔥 基于配置中心的轻量级动态可监控线程池">
<a href="https://dynamictp.cn/?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/dynamic-tp.png" msg="🔥🔥🔥 基于配置中心的轻量级动态可监控线程池">
</a>
<a href="https://www.x-easypdf.cn" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/x-easypdf.png" msg="一个用搭积木的方式构建pdf的框架(基于pdfbox">
<a href="https://www.x-easypdf.cn?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/x-easypdf.png" msg="一个用搭积木的方式构建pdf的框架(基于pdfbox">
</a>
<a href="http://dromara.gitee.io/image-combiner" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/image-combiner.png" msg="一个专门用于图片合成的工具,没有很复杂的功能,简单实用,却不失强大">
<a href="http://dromara.gitee.io/image-combiner?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/image-combiner.png" msg="一个专门用于图片合成的工具,没有很复杂的功能,简单实用,却不失强大">
</a>
<a href="https://www.herodotus.cn/" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/dante-cloud2.png" msg="Dante-Cloud 是一款企业级微服务架构和服务能力开发平台。">
<a href="https://www.herodotus.cn/?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/dante-cloud.jpg" msg="Dante-Cloud 是一款企业级微服务架构和服务能力开发平台。">
</a>
<a href="http://www.mtruning.club" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/go-view.png" msg="低代码数据可视化开发平台">
<a href="http://www.mtruning.club?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/go-view.png" msg="低代码数据可视化开发平台">
</a>
<a href="https://tangyh.top/" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/lamp-cloud.png" msg="微服务中后台快速开发平台,支持租户(SaaS)模式、非租户模式">
<a href="https://tangyh.top/?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/lamp-cloud.png" msg="微服务中后台快速开发平台,支持租户(SaaS)模式、非租户模式">
</a>
<a href="https://www.redisfront.com/" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/redis-front.png" msg="RedisFront 是一款开源免费的跨平台 Redis 桌面客户端工具, 支持单机模式, 集群模式, 哨兵模式以及 SSH 隧道连接, 可轻松管理Redis缓存数据.">
<a href="https://www.redisfront.com/?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/redis-front.png" msg="RedisFront 是一款开源免费的跨平台 Redis 桌面客户端工具, 支持单机模式, 集群模式, 哨兵模式以及 SSH 隧道连接, 可轻松管理Redis缓存数据.">
</a>
<a href="https://www.yuque.com/u34495/mivcfg" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/electron-egg.png" msg="一个入门简单、跨平台、企业级桌面软件开发框架">
<a href="https://www.yuque.com/u34495/mivcfg?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/electron-egg.png" msg="一个入门简单、跨平台、企业级桌面软件开发框架">
</a>
<a href="https://gitee.com/dromara/open-capacity-platform" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/open-capacity-platform.jpg" msg="简称ocp是基于Spring Cloud的企业级微服务框架(用户权限管理,配置中心管理,应用管理,....)">
<a href="https://gitee.com/dromara/open-capacity-platform?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/open-capacity-platform.jpg" msg="简称ocp是基于Spring Cloud的企业级微服务框架(用户权限管理,配置中心管理,应用管理,....)">
</a>
<a href="http://easy-trans.fhs-opensource.top/" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/easy_trans.png" msg="Easy-Trans 一个注解搞定数据翻译,减少30%SQL代码量">
<a href="http://easy-trans.fhs-opensource.top/?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/easy_trans.png" msg="Easy-Trans 一个注解搞定数据翻译,减少30%SQL代码量">
</a>
<a href="https://gitee.com/dromara/neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/neutrino-proxy.svg" msg="一款基于 Netty 的、开源的内网穿透神器。">
<a href="https://gitee.com/dromara/neutrino-proxy?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/neutrino-proxy.svg" msg="一款基于 Netty 的、开源的内网穿透神器。">
</a>
<a href="https://chatgpt.cn.obiscr.com/" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/chatgpt.png" msg="一个支持在 JetBrains 系列 IDE 上运行的 ChatGPT 的插件。">
<a href="https://chatgpt.cn.obiscr.com/?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/chatgpt.png" msg="一个支持在 JetBrains 系列 IDE 上运行的 ChatGPT 的插件。">
</a>
<a href="https://gitee.com/dromara/zyplayer-doc" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/zyplayer-doc.png" msg="zyplayer-doc是一款适合团队和个人使用的WIKI文档管理工具,同时还包含数据库文档、Api接口文档。">
<a href="https://gitee.com/dromara/zyplayer-doc?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/zyplayer-doc.png" msg="zyplayer-doc是一款适合团队和个人使用的WIKI文档管理工具,同时还包含数据库文档、Api接口文档。">
</a>
<a href="https://gitee.com/dromara/payment-spring-boot" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/payment-spring-boot.png" msg="最全最好用的微信支付V3 Spring Boot 组件。">
<a href="https://gitee.com/dromara/payment-spring-boot?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/payment-spring-boot.png" msg="最全最好用的微信支付V3 Spring Boot 组件。">
</a>
<a href="https://www.j2eefast.com/" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/j2eefast.png" msg="J2eeFAST 是一个致力于中小企业 Java EE 企业级快速开发平台,我们永久开源!">
<a href="https://www.j2eefast.com/?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/j2eefast.png" msg="J2eeFAST 是一个致力于中小企业 Java EE 企业级快速开发平台,我们永久开源!">
</a>
<a href="https://gitee.com/dromara/data-compare" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/dataCompare.png" msg="数据库比对工具:hive 表数据比对,mysql、Doris 数据比对,实现自动化配置进行数据比对,避免频繁写sql 进行处理,低代码(Low-Code) 平台">
<a href="https://gitee.com/dromara/data-compare?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/dataCompare.png" msg="数据库比对工具:hive 表数据比对,mysql、Doris 数据比对,实现自动化配置进行数据比对,避免频繁写sql 进行处理,低代码(Low-Code) 平台">
</a>
<a href="https://gitee.com/dromara/open-giteye-api" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/open-giteye-api.svg" msg="giteye.net 是专为开源作者设计的数据图表服务工具类站点,提供了包括 Star 趋势图、贡献者列表、Gitee指数等数据图表服务。">
<a href="https://gitee.com/dromara/open-giteye-api?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/open-giteye-api.svg" msg="giteye.net 是专为开源作者设计的数据图表服务工具类站点,提供了包括 Star 趋势图、贡献者列表、Gitee指数等数据图表服务。">
</a>
<a href="https://gitee.com/dromara/RuoYi-Vue-Plus" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/RuoYi-Vue-Plus.png" msg="后台管理系统 重写 RuoYi-Vue 所有功能 集成 Sa-Token + Mybatis-Plus + Jackson + Xxl-Job + SpringDoc + Hutool + OSS 定期同步">
<a href="https://gitee.com/dromara/RuoYi-Vue-Plus?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/RuoYi-Vue-Plus.png" msg="后台管理系统 重写 RuoYi-Vue 所有功能 集成 Sa-Token + Mybatis-Plus + Jackson + Xxl-Job + SpringDoc + Hutool + OSS 定期同步">
</a>
<a href="https://gitee.com/dromara/RuoYi-Cloud-Plus" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/RuoYi-Cloud-Plus.png" msg="微服务管理系统 重写RuoYi-Cloud所有功能 整合 SpringCloudAlibaba Dubbo3.0 Sa-Token Mybatis-Plus MQ OSS ES Xxl-Job Docker 全方位升级 定期同步">
<a href="https://gitee.com/dromara/RuoYi-Cloud-Plus?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/RuoYi-Cloud-Plus.png" msg="微服务管理系统 重写RuoYi-Cloud所有功能 整合 SpringCloudAlibaba Dubbo3.0 Sa-Token Mybatis-Plus MQ OSS ES Xxl-Job Docker 全方位升级 定期同步">
</a>
<a href="https://gitee.com/dromara/stream-query" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/stream-query.png" msg="允许完全摆脱 Mapper 的 mybatis-plus 体验!封装 stream 和 lambda 操作进行数据返回处理。">
<a href="https://gitee.com/dromara/stream-query?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/stream-query.png" msg="允许完全摆脱 Mapper 的 mybatis-plus 体验!封装 stream 和 lambda 操作进行数据返回处理。">
</a>
<a href="https://dromara.org/zh/projects/" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="https://oss.dev33.cn/sa-token/link/dromara.png" msg="让每一位开源爱好者,体会到开源的快乐。">
<a href="https://dromara.org/zh/projects/?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" src="img/logo/dromara.png" msg="让每一位开源爱好者,体会到开源的快乐。">
</a>
</p>
<h2>🤝 友情开源项目</h2>
<p>
<a href="https://gitee.com/noear/solon" target="_blank" class="friends-item" >
<img class="no-zoom friends-item-img hover-alt" :src="$withBase('/img/logo/solon_logo_500_150.png')" msg="一个高效的应用开发框架:更快、更小、更简单。" />
<a href="https://gitee.com/noear/solon?from=neutrino-proxy" target="_blank" class="friends-item" >
<img class="no-zoom friends-item-img hover-alt" :src="$withBase('img/logo/solon_logo_500_150.png')" msg="一个高效的应用开发框架:更快、更小、更简单。" />
</a>
<a href="https://gitee.com/xiaonuobase/snowy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" :src="$withBase('/img/logo/xiaonuo.png')" msg="国内首个国密前后端分离快速开发平台" />
<a href="https://gitee.com/xiaonuobase/snowy?from=neutrino-proxy" target="_blank" class="friends-item">
<img class="no-zoom friends-item-img hover-alt" :src="$withBase('img/logo/xiaonuo.png')" msg="国内首个国密前后端分离快速开发平台" />
</a>
</p>
+1 -213
View File
@@ -1,213 +1 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.noear</groupId>
<artifactId>solon-parent</artifactId>
<version>2.3.6</version>
<relativePath />
</parent>
<groupId>org.dromara.neutrino-proxy</groupId>
<artifactId>neutrino-proxy</artifactId>
<packaging>pom</packaging>
<version>${revision}</version>
<modules>
<module>neutrino-proxy-core</module>
<module>neutrino-proxy-client</module>
<module>neutrino-proxy-server</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<revision>1.0-SNAPSHOT</revision>
<java.version>1.8</java.version>
<maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>
<maven-flatten.version>1.1.0</maven-flatten.version>
<org.projectlombok.version>1.18.4</org.projectlombok.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.22.Final</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.33</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.0-jre</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.39.2.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>2.7.4</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.3</version>
</dependency>
<dependency>
<groupId>org.dromara.solon-plugins</groupId>
<artifactId>job-solon-plugin</artifactId>
<version>0.0.4</version>
<exclusions>
<exclusion>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.dromara.solon-plugins</groupId>
<artifactId>orika-solon-plugin</artifactId>
<version>0.0.4</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<!-- 添加flatten-maven-plugin插件 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>${maven-flatten.version}</version>
<configuration>
<updatePomFile>true</updatePomFile>
<flattenMode>resolveCiFriendliesOnly</flattenMode>
</configuration>
<executions>
<execution>
<id>flatten</id>
<phase>process-resources</phase>
<goals>
<goal>flatten</goal>
</goals>
</execution>
<execution>
<id>flatten.clean</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.noear</groupId> <artifactId>solon-parent</artifactId> <version>2.5.12</version> <relativePath /> </parent> <groupId>org.dromara.neutrino-proxy</groupId> <artifactId>neutrino-proxy</artifactId> <packaging>pom</packaging> <version>${revision}</version> <modules> <module>neutrino-proxy-core</module> <module>neutrino-proxy-client</module> <module>neutrino-proxy-server</module> </modules> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <maven.compiler.encoding>UTF-8</maven.compiler.encoding> <revision>2.0.1-SNAPSHOT</revision> <native.version>0.9.28</native.version> <java.version>21</java.version> <maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version> <maven-flatten.version>1.1.0</maven-flatten.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.100.Final</version> </dependency> <dependency> <groupId>org.yaml</groupId> <artifactId>snakeyaml</artifactId> <version>1.33</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.9</version> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>28.0-jre</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>2.2.224</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> <dependency> <groupId>org.mariadb.jdbc</groupId> <artifactId>mariadb-java-client</artifactId> <version>2.7.4</version> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>4.0.3</version> </dependency> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15to18</artifactId> <version>1.69</version> </dependency> <dependency> <groupId>org.dromara.solon-plugins</groupId> <artifactId>job-solon-plugin</artifactId> <version>0.1.1</version> <exclusions> <exclusion> <groupId>cn.hutool</groupId> <artifactId>hutool-core</artifactId> </exclusion> </exclusions> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.noear</groupId> <artifactId>solon.logging.logback</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> </dependency> <dependency> <groupId>org.noear</groupId> <artifactId>solon-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <resources> <resource> <directory>src/main/resources</directory> </resource> </resources> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>${maven-compiler-plugin.version}</version> <configuration> <source>${java.version}</source> <target>${java.version}</target> <encoding>UTF-8</encoding> <annotationProcessorPaths> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </path> </annotationProcessorPaths> </configuration> </plugin> <!-- 添加flatten-maven-plugin插件 --> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>flatten-maven-plugin</artifactId> <version>${maven-flatten.version}</version> <configuration> <updatePomFile>true</updatePomFile> <flattenMode>resolveCiFriendliesOnly</flattenMode> </configuration> <executions> <execution> <id>flatten</id> <phase>process-resources</phase> <goals> <goal>flatten</goal> </goals> </execution> <execution> <id>flatten.clean</id> <phase>clean</phase> <goals> <goal>clean</goal> </goals> </execution> </executions> </plugin> </plugins> </build> <repositories> <repository> <id>tencent</id> <url>https://mirrors.cloud.tencent.com/nexus/repository/maven-public/</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <id>sonatype-nexus-snapshots</id> <name>Sonatype Nexus Snapshots</name> <url>https://oss.sonatype.org/content/repositories/snapshots</url> <releases> <enabled>false</enabled> </releases> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>sonatype-nexus-snapshots</id> <name>Sonatype Nexus Snapshots</name> <url>https://oss.sonatype.org/content/repositories/snapshots</url> <releases> <enabled>false</enabled> </releases> </pluginRepository> </pluginRepositories> <profiles> <profile> <id>native</id> <build> <plugins> <plugin> <groupId>org.noear</groupId> <artifactId>solon-maven-plugin</artifactId> <version>${solon.version}</version> <executions> <execution> <id>process-aot</id> <goals> <goal>process-aot</goal> </goals> </execution> </executions> <dependencies> <dependency> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-utils</artifactId> <version>3.5.1</version> </dependency> </dependencies> </plugin> <plugin> <groupId>org.graalvm.buildtools</groupId> <artifactId>native-maven-plugin</artifactId> <version>${native.version}</version> <!-- 使用graalvm提供的可达性元数据,很多第三方库就直接可以构建成可执行文件了 --> <configuration> <metadataRepository> <enabled>true</enabled> </metadataRepository> </configuration> <executions> <execution> <id>add-reachability-metadata</id> <goals> <goal>add-reachability-metadata</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </profile> </profiles> </project>
+9 -1
View File
@@ -41,4 +41,12 @@ npm run build:$env
#拷贝
cd ..
cp -rf ./neutrino-proxy-admin/dist $adminDeployDir/
cp -rf ./neutrino-proxy-admin/dist/ $giteePagesDir
cp -rf ./neutrino-proxy-admin/dist/ $giteePagesDir
cd $serverDeployDir
zip -r neutrino-proxy-admin.zip "neutrino-proxy-admin/"
#拷贝到neutrino-proxy-server项目静态资源目录下
cd ../..
rm -rf ./neutrino-proxy-server/src/main/resources/static
mkdir -p neutrino-proxy-server/src/main/resources/static
cp -rf ./neutrino-proxy-admin/dist/ ./neutrino-proxy-server/src/main/resources/static
+6
View File
@@ -44,3 +44,9 @@ cp -rf ./neutrino-proxy-admin/dist $adminDeployDir/
cp -rf ./neutrino-proxy-admin/dist/ $giteePagesDir
cd $serverDeployDir
zip -r neutrino-proxy-admin.zip "neutrino-proxy-admin/"
#拷贝到neutrino-proxy-server项目静态资源目录下
cd ../..
rm -rf ./neutrino-proxy-server/src/main/resources/static
mkdir -p neutrino-proxy-server/src/main/resources/static
cp -rf ./neutrino-proxy-admin/dist/ ./neutrino-proxy-server/src/main/resources/static
+15 -9
View File
@@ -1,9 +1,9 @@
#!/bin/sh
# 中微子代理客户端编译打包脚本,基础参数请自行修改
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home
export MAVANE_HOME=/Users/yangwen/my/service/maven/apache-maven-3.8.1
export PATH=:$PATH:$JAVA_HOME/bin:$MAVANE_HOME/bin
export JAVA_HOME=/Users/yangwen/my/service/graalvm/graalvm-community-openjdk-21.0.1+12.1/Contents/Home
export MAVEN_HOME=/Users/yangwen/my/service/maven/apache-maven-3.8.1
export PATH=:$PATH:$JAVA_HOME/bin:$MAVEN_HOME/bin
deployDir="deploy"
clientDeployDir=$deployDir"/client"
@@ -11,14 +11,20 @@ clientDeployDir=$deployDir"/client"
#切到项目根目录
cd ../..
#初始化文件夹
if [ ! -d "$deployDir" ];then
mkdir $deployDir
fi
if [ ! -d "$clientDeployDir" ];then
mkdir $clientDeployDir
fi
mkdir -p $clientDeployDir
rm -rf $clientDeployDir/neutrino-proxy-client.jar
rm -rf $clientDeployDir/neutrino-proxy-client-jar
rm -rf $clientDeployDir/neutrino-proxy-client-jar.zip
#客户端打包
mvn clean install -U -pl neutrino-proxy-client -am -Dmaven.test.skip=true
# 拷贝到deploy目录下
cp ./neutrino-proxy-client/target/neutrino-proxy-client.jar $clientDeployDir/neutrino-proxy-client.jar
#打zip包,用于发版
cd $clientDeployDir
mkdir neutrino-proxy-client-jar
cp ../../neutrino-proxy-client/target/neutrino-proxy-client.jar ./neutrino-proxy-client-jar/neutrino-proxy-client.jar
cp ../../neutrino-proxy-client/src/main/resources/app-copy.yml ./neutrino-proxy-client-jar/app.yml
zip -r neutrino-proxy-client-jar.zip ./neutrino-proxy-client-jar
rm -rf $clientDeployDir/neutrino-proxy-client-jar
+29
View File
@@ -0,0 +1,29 @@
#!/bin/sh
# 镜像版本,每次更新版本时需要调整
ImageVer=2.0.0
ImageName=neutrino-proxy-client
DockerFilePath=$PWD/../../neutrino-proxy-client
##初始化文件夹
#deployDir=$PWD/../../"deploy"
#clientDeployDir=$deployDir"/client"
#if [ ! -d "$deployDir" ];then
# mkdir $deployDir
#fi
#if [ ! -d "$clientDeployDir" ];then
# mkdir $clientDeployDir
#fi
#rm -rf $clientDeployDir/$ImageName.tar
#echo '打包jar...'
#sh ./client_build.sh
# 删除老的本地镜像
docker rmi -f $(docker images | grep $ImageName | awk '{print $3}')
# 构建镜像
docker build -t $ImageName:$ImageVer -t $ImageName:latest $DockerFilePath
# 保存镜像到本地
#docker save -o $clientDeployDir/$ImageName.tar $ImageName:$ImageVer
# docker run -it -e LICENSE_KEY=b0a907332b474b25897c4dcb31fc7eb6 -e SERVER_IP=host.docker.internal --name np-client neutrino-proxy-client:2.0.0
+52
View File
@@ -0,0 +1,52 @@
#!/bin/sh
# 中微子代理客户端编译打包脚本,基础参数请自行修改
export JAVA_HOME=/Users/yangwen/my/service/graalvm/graalvm-community-openjdk-21.0.1+12.1/Contents/Home
export MAVEN_HOME=/Users/yangwen/my/service/maven/apache-maven-3.8.1
export PATH=:$PATH:$JAVA_HOME/bin:$MAVEN_HOME/bin
deployDir="deploy"
clientDeployDir=$deployDir"/client"
machine=macos
#切到项目根目录
cd ../..
#初始化文件夹
mkdir -p $clientDeployDir
# 删除原来的编译文件
rm -rf $clientDeployDir/neutrino-proxy-client.jar
rm -rf $clientDeployDir/neutrino-proxy-client
rm -rf $clientDeployDir/neutrino-proxy-client-jar
rm -rf $clientDeployDir/neutrino-proxy-client-jar.zip
rm -rf $clientDeployDir/neutrino-proxy-client-${machine}-native
rm -rf $clientDeployDir/neutrino-proxy-client-${machine}-native.zip
#客户端打包
mvn clean install -U -pl neutrino-proxy-client -am -Dmaven.test.skip=true
cd neutrino-proxy-client
mvn clean native:compile -P native -DskipTests
cd ..
# 给执行权限
chmod +x ./neutrino-proxy-client/target/neutrino-proxy-client
# 拷贝到deploy目录下
cp ./neutrino-proxy-client/target/neutrino-proxy-client.jar $clientDeployDir/neutrino-proxy-client.jar
cp ./neutrino-proxy-client/target/neutrino-proxy-client $clientDeployDir/neutrino-proxy-client
#打zip包,用于发版
cd $clientDeployDir
## jar
mkdir neutrino-proxy-client-jar
cp ../../neutrino-proxy-client/target/neutrino-proxy-client.jar ./neutrino-proxy-client-jar/neutrino-proxy-client.jar
cp ../../neutrino-proxy-client/src/main/resources/app-copy.yml ./neutrino-proxy-client-jar/app.yml
zip -r neutrino-proxy-client-jar.zip ./neutrino-proxy-client-jar
rm -rf ./neutrino-proxy-client-jar
## native
mkdir neutrino-proxy-client-${machine}-native
cp ../../neutrino-proxy-client/target/neutrino-proxy-client ./neutrino-proxy-client-${machine}-native/neutrino-proxy-client
cp ../../neutrino-proxy-client/src/main/resources/app-copy.yml ./neutrino-proxy-client-${machine}-native/app.yml
zip -r neutrino-proxy-client-${machine}-native.zip ./neutrino-proxy-client-${machine}-native
rm -rf ./neutrino-proxy-client-${machine}-native
+2 -2
View File
@@ -1,8 +1,8 @@
#!/bin/sh
# 中微子代理客户端启动脚本,基础参数请自行修改
JAVA_OPS="-server -Xms256m -Xmx512m -XX:PermSize=128M -XX:MaxPermSize=256M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/work/$NAME/heapError/"
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home
JAVA_OPS="-server -Xms256m -Xmx512m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/work/$NAME/heapError/"
export JAVA_HOME=/Users/yangwen/my/service/graalvm/graalvm-community-openjdk-21.0.1+12.1/Contents/Home
export PATH=:$PATH:$JAVA_HOME/bin
export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
mkdir -p /work/$NAME/heapError/
+17 -12
View File
@@ -1,9 +1,9 @@
!/bin/sh
# 中微子代理客户端编译打包脚本,基础参数请自行修改
#!/bin/sh
# 中微子代理服务端编译打包脚本,基础参数请自行修改
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home
export MAVANE_HOME=/Users/yangwen/my/service/maven/apache-maven-3.8.1
export PATH=:$PATH:$JAVA_HOME/bin:$MAVANE_HOME/bin
export JAVA_HOME=/Users/yangwen/my/service/graalvm/graalvm-community-openjdk-21.0.1+12.1/Contents/Home
export MAVEN_HOME=/Users/yangwen/my/service/maven/apache-maven-3.8.1
export PATH=:$PATH:$JAVA_HOME/bin:$MAVEN_HOME/bin
deployDir="deploy"
serverDeployDir=$deployDir"/server"
@@ -11,15 +11,20 @@ serverDeployDir=$deployDir"/server"
#切到项目根目录
cd ../..
#初始化文件夹
if [ ! -d "$deployDir" ];then
mkdir $deployDir
fi
if [ ! -d "$serverDeployDir" ];then
mkdir $serverDeployDir
fi
mkdir -p $serverDeployDir
rm -rf $serverDeployDir/neutrino-proxy-server.jar
rm -rf $serverDeployDir/neutrino-proxy-server-jar
rm -rf $serverDeployDir/neutrino-proxy-server-jar.zip
#客户端打包
#服务端打包
mvn clean install -U -pl neutrino-proxy-server -am -Dmaven.test.skip=true
# 拷贝到deploy目录下
cp ./neutrino-proxy-server/target/neutrino-proxy-server.jar $serverDeployDir/neutrino-proxy-server.jar
#打zip包,用于发版
cd $serverDeployDir
mkdir neutrino-proxy-server-jar
cp ../../neutrino-proxy-server/target/neutrino-proxy-server.jar ./neutrino-proxy-server-jar/neutrino-proxy-server.jar
cp ../../neutrino-proxy-server/src/main/resources/app-copy.yml ./neutrino-proxy-server-jar/app.yml
zip -r neutrino-proxy-server-jar.zip ./neutrino-proxy-server-jar
rm -rf $serverDeployDir/neutrino-proxy-server-jar
+5 -4
View File
@@ -1,17 +1,18 @@
#!/bin/sh
# 镜像版本,每次更新版本时需要调整
ImageVer=1.0.0
ImageVer=2.0.0
ImageName=neutrino-proxy
DockerFilePath=$PWD/../..
DockerFilePath=$PWD/../../neutrino-proxy-server
#echo '打包jar...'
sh ./server_build.sh
#echo '打包管理后台...'
sh ./admin_build_docker.sh
#echo '打包jar...'
sh ./server_build.sh
# 删除老的本地镜像
docker rmi -f $(docker images | grep $ImageName | awk '{print $3}')
# 构建镜像
docker build -t $ImageName:$ImageVer -t $ImageName:latest $DockerFilePath
# docker run -it -p 9000-9200:9000-9200/tcp -p 8888:8888 --name np-server neutrino-proxy:2.0.0
+61
View File
@@ -0,0 +1,61 @@
#!/bin/sh
# 中微子代理服务端编译打包脚本,基础参数请自行修改
export JAVA_HOME=/Users/yangwen/my/service/graalvm/graalvm-community-openjdk-21.0.1+12.1/Contents/Home
export MAVEN_HOME=/Users/yangwen/my/service/maven/apache-maven-3.8.1
export PATH=:$PATH:$JAVA_HOME/bin:$MAVEN_HOME/bin
deployDir="deploy"
serverDeployDir=$deployDir"/server"
machine=macos
#切到项目根目录
cd ../..
#初始化文件夹
mkdir -p $serverDeployDir
# 删除原来的编译文件
rm -rf $serverDeployDir/neutrino-proxy-server.jar
rm -rf $serverDeployDir/neutrino-proxy-server
rm -rf $serverDeployDir/neutrino-proxy-server-jar
rm -rf $serverDeployDir/neutrino-proxy-server-jar.zip
rm -rf $serverDeployDir/neutrino-proxy-server-${machine}-native
rm -rf $serverDeployDir/neutrino-proxy-server-${machine}-native.zip
# 不需要每次都重新编译一次前端代码,如果前端代码有变更,编译前需要手动执行`admin_build_docker.sh`
##前端页面打包
#rm -rf ./neutrino-proxy-server/src/main/resources/static
#cd ./scripts/unix
#sh admin_build_docker.sh
#cd ../..
#mkdir -p neutrino-proxy-server/src/main/resources/static
#cp -rf ./neutrino-proxy-admin/dist/ ./neutrino-proxy-server/src/main/resources/static
#服务端打包
mvn clean install -U -pl neutrino-proxy-server -am -Dmaven.test.skip=true
cd neutrino-proxy-server
mvn clean native:compile -P native -DskipTests
cd ..
# 给执行权限
chmod +x ./neutrino-proxy-server/target/neutrino-proxy-server
# 拷贝到deploy目录下
cp ./neutrino-proxy-server/target/neutrino-proxy-server.jar $serverDeployDir/neutrino-proxy-server.jar
cp ./neutrino-proxy-server/target/neutrino-proxy-server $serverDeployDir/neutrino-proxy-server
#打zip包,用于发版
cd $serverDeployDir
## jar
mkdir neutrino-proxy-server-jar
cp ../../neutrino-proxy-server/target/neutrino-proxy-server.jar ./neutrino-proxy-server-jar/neutrino-proxy-server.jar
cp ../../neutrino-proxy-server/src/main/resources/app-copy.yml ./neutrino-proxy-server-jar/app.yml
zip -r neutrino-proxy-server-jar.zip ./neutrino-proxy-server-jar
rm -rf ./neutrino-proxy-server-jar
## native
mkdir neutrino-proxy-server-${machine}-native
cp ../../neutrino-proxy-server/target/neutrino-proxy-server ./neutrino-proxy-server-${machine}-native/neutrino-proxy-server
cp ../../neutrino-proxy-server/src/main/resources/app-copy.yml ./neutrino-proxy-server-${machine}-native/app.yml
zip -r neutrino-proxy-server-${machine}-native.zip ./neutrino-proxy-server-${machine}-native
rm -rf ./neutrino-proxy-server-${machine}-native
+2 -2
View File
@@ -1,8 +1,8 @@
#!/bin/sh
# 中微子代理服务端启动脚本,基础参数请自行修改
JAVA_OPS="-server -Xms256m -Xmx1024m -XX:PermSize=128M -XX:MaxPermSize=256M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/work/$NAME/heapError/"
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home
JAVA_OPS="-server -Xms256m -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/work/$NAME/heapError/"
export JAVA_HOME=/Users/yangwen/my/service/graalvm/graalvm-community-openjdk-21.0.1+12.1/Contents/Home
export PATH=:$PATH:$JAVA_HOME/bin
export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
mkdir -p /work/$NAME/heapError/
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
echo 'hello'
+21 -27
View File
@@ -1,30 +1,21 @@
# 1.x规划
- Bug
- 优化
- 心跳日志开关
- 客户端启动无限重连开关
- UDP支持
- 官网文档完善
- 常见问题汇总
- HTTPS配置说明
- 协议重构
- 代码重构
- [x] 适配mariadb
# Bug
- 指令通达被close的问题,org.dromara.neutrinoproxy.server.proxy.core.ProxyTunnelChannelHandler.channelInactive
- windows环境下直接运行发布版的jar包,日志输出乱码
- 代理mysql时,使用未开启远程访问的账号走代理访问mysql,代理客户端出现断开现象
- 客户端连接映射某个端口以后,如果在服务器端禁用了,没有立刻反映出来,要灯客户端重连以后才会屏蔽被禁用的端口。
# 2.x规划
- 插件开发
- [ ] neutrino-proxy-solon-plugin
- [ ] neutrino-proxy-jetbrains-plugin
- [ ] neutrino-proxy-starter (SpringBoot)
- 思考
- 流量编排
- 插件化定制
- 重构
- 代码重构
- 多租户支持
- 多个代理服务端节点支持
- 协议重构
- 支持多个隧道协议,支持扩展(TCP、KCP、QUIC)
- 支持不同语言客户端、服务端接入
- 基础优化
- [ ] 访问白名单
- [ ] 端口映射分组
- [ ] 支持批量端口映射
- [ ] 代理协议规范化,方便后续更好扩展、支持不同语言客户端接入
- [ ] 如果UDP映射服务端端口变动,流量统计似乎不准,转发功能或许也受到影响,需要评估、测试、思考如何优化
- [ ] 排查解决问题:https://gitee.com/dromara/neutrino-proxy/issues/I7LGLB
- 监控运维
- [ ] 监控服务端状态
- [ ] 支持调整服务端端口、证书
@@ -32,11 +23,14 @@
- [ ] 服务端日志
- [ ] 支持持管理后台动态调整日志级别。
- [ ] 动态调整转发、报文、心跳输出
- client+计划启动
- [ ] 安卓客户端
- [ ] 基于electron-egg的多平台客户端
# 3.x规划
- [ ] 支持针对用户限速、限流
- [ ] 支持P2P穿透
- [ ] 支持原生编译
- 插件开发
- [ ] neutrino-proxy-solon-plugin
- [ ] neutrino-proxy-jetbrains-plugin
- [ ] neutrino-proxy-starter (SpringBoot)
- client+计划启动
- [ ] 安卓客户端
- [ ] 基于electron-egg的多平台客户端
-7
View File
@@ -1,7 +0,0 @@
以下为部分使用中机构/组织/个人:
---
|单位/组织名称| 主营业务描述 |使用中微子的主要用途|
|:----|:----------|:--------|
|常州裕景信息科技有限公司| 高校数字化软件建设 |统筹管理个单位的服务器|