Compare commits

...
Author SHA1 Message Date
aoshiguchen 0a5d50b84d 更新README 2023-07-04 21:46:22 +08:00
aoshiguchen 22461066a4 更新官网文档. 2023-07-04 21:38:11 +08:00
aoshiguchen cd4c9f2aae 调整url命名 2023-07-04 21:03:26 +08:00
傲世孤尘 30b115610b !29 端口池管理和端口映射修改
Merge pull request !29 from Metal/dev
2023-07-04 12:41:45 +00:00
aoshiguchen c971bee96c 更新前端打包脚本 2023-07-04 20:39:55 +08:00
Metal 7ea723b578 1、系统管理-端口池管理添加批量删除的功能
2、代理配置-端口映射添加和编辑页面服务端端口字段,支持输入搜索的功能
2023-07-04 20:08:27 +08:00
aoshiguchen 1625187bb1 报文相关日志优化. 2023-07-01 17:02:07 +08:00
aoshiguchen 934ceaadca 报文相关日志优化 2023-07-01 16:09:01 +08:00
aoshiguchen bb7fe668b1 更新官网文档,项目README. 2023-07-01 14:17:16 +08:00
aoshiguchen c385030fc4 更新官网文档,项目README 2023-07-01 14:04:53 +08:00
aoshiguchen 767860643d 新增心跳日志开关处理 2023-06-30 18:16:52 +08:00
aoshiguchen c20a6db44a 服务端客户端,cmd通道读写超时处理调整. 2023-06-30 18:06:03 +08:00
aoshiguchen f423056361 更新赞赏记录 2023-06-30 14:05:09 +08:00
aoshiguchen 86a3cf92b5 更新赞赏记录. 2023-06-26 18:04:40 +08:00
aoshiguchen b28d476081 更新赞赏记录 2023-06-26 18:00:25 +08:00
aoshiguchen 48c8df15e7 1、升级solon版本为2.3.6
2、管理后台支持带context-path访问
2023-06-26 10:30:44 +08:00
傲世孤尘 a31b0ebc95 !28 修复端口映射页面分页查询BUG
Merge pull request !28 from tan90/dev
2023-06-22 14:17:23 +00:00
陈浩杰 fe86d786f4 fix: 修复端口映射查询BUG 2023-06-22 15:51:50 +08:00
aoshiguchen c48404e3bb 更新官网赞助记录 2023-06-12 21:19:50 +08:00
aoshiguchen 5daec7195f 更新文档. 2023-06-09 23:22:56 +08:00
aoshiguchen 533675b367 更新文档.新增打赏记录 2023-06-09 21:43:32 +08:00
aoshiguchen e547895a9c 更新文档. 2023-06-09 21:27:23 +08:00
aoshiguchen 42b7163f5b 支持指定重连间隔、是否无限重连 2023-06-09 20:54:15 +08:00
aoshiguchen 2ceacc6916 支持配置文件、启动参数指定日志级别 2023-06-09 20:41:11 +08:00
aoshiguchen 124273e8d7 日志调整. 2023-06-09 13:07:25 +08:00
aoshiguchen 5a36c88875 支持域名带端口访问 2023-06-09 12:55:24 +08:00
aoshiguchen 70e6d2ab45 更新官网文档 2023-06-08 22:13:52 +08:00
aoshiguchen 2dc8965cdb 适配mariadb. 2023-06-08 22:00:11 +08:00
aoshiguchen 0d997bbe92 适配mariadb 2023-06-08 21:58:39 +08:00
aoshiguchen f88bbb53cb 更新TODOLIST 2023-06-07 21:35:02 +08:00
aoshiguchen 2ae3267049 更新logback配置 2023-06-07 20:41:33 +08:00
aoshiguchen 62bbd48b33 更新TODOLIST 2023-06-07 20:37:26 +08:00
aoshiguchen aa131b0705 解决http域名映射的问题 2023-06-06 10:11:12 +08:00
aoshiguchen 1510e16e50 客户端服务端、日志优化 2023-06-05 18:02:03 +08:00
aoshiguchen a3b324c72b http代理日志调整. 2023-06-05 17:48:33 +08:00
aoshiguchen c959ce13a3 解决端口映射选择https,域名输入框没出来的问题 2023-06-05 10:00:59 +08:00
aoshiguchen 4c828f814c 服务端、客户端认证逻辑调整 2023-06-03 03:19:45 +08:00
aoshiguchen 5a65eebfd3 服务端、客户端认证逻辑调整 2023-06-03 01:34:22 +08:00
aoshiguchen 5a3375ce80 服务端、客户端认证逻辑调整. 2023-06-03 01:30:52 +08:00
aoshiguchen eb277b414a 服务端、客户端认证逻辑调整. 2023-06-03 00:47:30 +08:00
aoshiguchen fc4097529f 1、服务端、客户端认证逻辑调整.
2、解决端口映射被禁用时,客户端启动依然能访问的问题
2023-06-02 23:26:13 +08:00
aoshiguchen 375687aa5f 1、服务端、客户端认证逻辑调整.
2、解决端口映射被禁用时,客户端启动依然能访问的问题.
2023-06-02 22:46:15 +08:00
aoshiguchen 32504d33af 1、服务端、客户端认证逻辑调整.
2、解决端口映射被禁用时,客户端启动依然能访问的问题
2023-06-02 22:36:39 +08:00
aoshiguchen bcd429d54f 客户端连接时认证失败,直接断开连接 2023-06-02 22:24:20 +08:00
aoshiguchen 2ec87abda9 客户端重连机制调整,启动首次连接失败也进行重连 2023-06-02 22:09:01 +08:00
aoshiguchen 8c2e1b8f6c 客户端重连优化. 2023-06-02 21:20:19 +08:00
aoshiguchen e2195ab966 客户端重连优化 2023-06-01 22:34:41 +08:00
aoshiguchen 2d0b9132e9 协议枚举调整,维护协议关联的下层基础协议 2023-05-31 22:42:16 +08:00
aoshiguchen 5a502115b1 代码优化,避免新增端口映射时不断创建新的ServerBootstrap实例. 2023-05-31 22:32:22 +08:00
aoshiguchen a972e0f309 代码优化,避免新增端口映射时不断创建新的ServerBootstrap实例 2023-05-31 22:29:44 +08:00
aoshiguchen b52225937c http、https代理线程池分开 2023-05-31 22:23:12 +08:00
aoshiguchen 98737f0e9d 服务端代理相关命名调整,使得表义更加准确. 2023-05-31 21:41:22 +08:00
aoshiguchen ea67c2bce0 服务端代理相关命名调整,使得表义更加准确 2023-05-31 21:34:53 +08:00
aoshiguchen 7dda047d51 解决新增报不支持的协议HTTPS的问题 2023-05-29 22:57:16 +08:00
aoshiguchen b518dbfd58 更新官网 2023-05-29 22:23:19 +08:00
aoshiguchen 7054a1df0e 官网文档调整. 2023-05-27 12:05:43 +08:00
aoshiguchen da0d1946c6 更新README 2023-05-27 11:10:27 +08:00
aoshiguchen afff036641 新增HTTPS支持 2023-05-27 00:47:01 +08:00
aoshiguchen 9944e60825 启动脚本调整 2023-05-23 20:09:44 +08:00
aoshiguchen 74ff0c3b44 Merge branch 'feature/20230520' into dev 2023-05-20 14:28:53 +08:00
aoshiguchen 5bb6a09ce2 更改日志配置 2023-05-20 14:21:10 +08:00
aoshiguchen 3e9e7cb370 server端代码优化,将代理隧道配置分离出来 2023-05-03 18:07:12 +08:00
zCans 3cf6bcbe6e 首页图表剔除冗余 2023-04-24 22:52:27 +08:00
aoshiguchen 8f9375152e 管理后台首页折线图展示问题修复 2023-04-24 22:46:57 +08:00
aoshiguchen 501012d9bc 更新快速上手文档. 2023-04-22 15:44:50 +08:00
aoshiguchen f2b560fa1f 客户端优化,解决长时间数据写入,导致指令通道被关闭的问题 2023-04-20 23:04:39 +08:00
aoshiguchen 2b95967820 配置调整&服务端代码优化 2023-04-20 22:22:50 +08:00
aoshiguchen 9b1fafd379 更新初始化sql 2023-04-20 19:03:02 +08:00
qiuyue e41e346dff feat: 百度统计 2023-04-19 20:35:27 +08:00
aoshiguchen fff5c0121a 移除_solon_plugin模块,引入solon-plugins外部依赖 2023-04-15 03:43:14 +08:00
aoshiguchen 406edaf172 新增docker-compose.yml 2023-04-14 10:24:12 +08:00
aoshiguchen b6cd8ab3a2 解决通过映射域名,代理文件上传接口失效的问题 2023-04-13 21:34:03 +08:00
aoshiguchen 18653410f9 更新官网文档. 2023-04-13 16:40:31 +08:00
傲世孤尘 6d6e58af52 !25 删除配置
Merge pull request !25 from Tany/dev
2023-04-11 07:43:29 +00:00
谭勇 e9e9ce8c90 干掉配置数据库驱动,根据数据库类型直接添加 2023-04-11 15:41:10 +08:00
傲世孤尘 1e1b376883 !24 干掉配置数据库驱动,根据数据库类型直接添加
Merge pull request !24 from Tany/dev
2023-04-11 07:26:10 +00:00
aoshiguchen 9cb5eb3225 logback编码调整。 2023-04-11 15:24:40 +08:00
谭勇 5b38bb6b22 干掉配置数据库驱动,根据数据库类型直接添加 2023-04-11 15:20:03 +08:00
aoshiguchen 2b28d4eff5 调整Dockerfile文件位置. 2023-04-10 17:18:54 +08:00
aoshiguchen 42f39ddb0f 更新docker脚本. 2023-04-09 16:36:08 +08:00
aoshiguchen fabc575c9e 调整docker脚本位置 2023-04-09 16:04:39 +08:00
aoshiguchen 3e1f14917b 更新README.MD。 2023-04-07 20:14:09 +08:00
aoshiguchen f17c341325 更新README.MD 2023-04-07 20:09:10 +08:00
aoshiguchen 5bf21b7c2c 异常处理优化 2023-04-07 15:20:48 +08:00
aoshiguchen 252a6ac7c1 新增性能测试文档 2023-04-07 15:14:12 +08:00
aoshiguchen 0b78367b9f 新增性能测试文档. 2023-04-07 13:19:54 +08:00
aoshiguchen bb91abae00 域名映射逻辑优化,支持websocket. 2023-04-07 12:46:15 +08:00
aoshiguchen 1e4312fcb1 域名映射逻辑优化,支持websocket 2023-04-07 10:48:35 +08:00
傲世孤尘 f9009bb869 !23 脚本层级变更导致路径问题修复
Merge pull request !23 from 霓虹深处/dev
2023-04-06 09:05:38 +00:00
谢飞 c4e1ae2a17 bugfix: 脚本层级变更导致路径问题修复 2023-04-06 16:59:30 +08:00
aoshiguchen d2fc7b6d45 更新快速使用文档. 2023-04-06 16:25:30 +08:00
傲世孤尘 4d4c06e4b1 !22 client dockerfile
Merge pull request !22 from Tany/dev
2023-04-06 05:10:40 +00:00
谭勇 98cc7ead6e client dockerfile 2023-04-06 13:02:50 +08:00
aoshiguchen e1be4523ed http代理配置优化 2023-04-06 12:05:18 +08:00
aoshiguchen 99ddfa65f3 更新官网文档. 2023-04-06 11:00:14 +08:00
aoshiguchen 31e36d1939 新增宣传文档. 2023-04-06 00:18:34 +08:00
aoshiguchen f46ae98867 http代理优化. 2023-04-05 23:34:59 +08:00
aoshiguchen f87370397e http代理优化. 2023-04-05 22:55:00 +08:00
aoshiguchen ce6634d51c http代理优化. 2023-04-05 21:11:10 +08:00
aoshiguchen f43d447ea0 更新todolist 2023-04-05 21:07:14 +08:00
aoshiguchen baafc9bf21 更新快速上手文档 2023-04-05 20:59:26 +08:00
aoshiguchen 56226831f9 更新快速上手文档. 2023-04-05 20:56:24 +08:00
aoshiguchen b3e15225b3 更新SQL脚本 2023-04-05 20:15:28 +08:00
Yohanes e725cdbb28 端口映射增加描述字段 2023-04-05 19:59:52 +08:00
Yohanes 480867e655 sqlite初始化脚本及增量脚本 增加描述字段 2023-04-05 19:52:39 +08:00
Yohanes a5c568392b 参数检查工具类增加最大长度检查方法,异常常量池增加超出长度提示模板 2023-04-05 19:49:49 +08:00
aoshiguchen bbb0e5dca3 新增官网文档。 2023-04-05 19:07:38 +08:00
aoshiguchen 2b80279256 新增官网文档 2023-04-05 19:04:28 +08:00
傲世孤尘 b7b4432d09 !21 update域名空字符未判空
Merge pull request !21 from 陈賝/dev
2023-04-05 09:47:35 +00:00
chenchen a7269229b0 update域名空字符未判空 2023-04-05 17:42:10 +08:00
aoshiguchen f84695d665 配置调整. 2023-04-05 17:41:17 +08:00
aoshiguchen 4258845985 脚本调整. 2023-04-05 17:36:52 +08:00
aoshiguchen 87e6c066ed 更新贡献者列表. 2023-04-04 23:17:07 +08:00
aoshiguchen 2ea8a5890f 更新贡献者列表 2023-04-04 23:16:30 +08:00
傲世孤尘 89ea802245 !18 feat: admin打包脚本适配windows
Merge pull request !18 from 霓虹深处/dev
2023-04-04 15:10:41 +00:00
aoshiguchen 88387003e3 更新初始化SQL 2023-04-04 23:09:45 +08:00
aoshiguchen b1797686f1 首页6大特性调整. 2023-04-04 23:05:34 +08:00
aoshiguchen 9a08a7c72e 首页6大特性调整 2023-04-04 23:02:46 +08:00
aoshiguchen 0f15bbc94d 首页6大特性调整. 2023-04-04 22:59:15 +08:00
aoshiguchen e454510967 官网首页增加特别赞助栏目 2023-04-04 22:50:21 +08:00
aoshiguchen 090c129f60 首页二维码图片调整 2023-04-04 22:45:23 +08:00
aoshiguchen 8b41038d13 官网首页增加友情开源项目Solon 2023-04-04 22:40:30 +08:00
aoshiguchen 980d6d1a46 去除多余的文案 2023-04-04 17:56:34 +08:00
傲世孤尘 6b805089ba !20 app.yml增加对容器环境变量的支持
Merge pull request !20 from Tany/dev
2023-04-04 09:55:16 +00:00
谭勇 d81cbb3f7b app.yml增加对容器环境变量的支持,可不挂载config直接定义配置 2023-04-04 17:53:49 +08:00
谭勇 b6b91b69ab Merge remote-tracking branch 'me/dev' into dev
# Conflicts:
#	neutrino-proxy-server/src/main/resources/app.yml
2023-04-04 17:52:51 +08:00
谭勇 3185934ff9 app.yml增加对容器环境变量的支持,可不挂载config直接定义配置 2023-04-04 17:44:08 +08:00
谢飞 238eb64c04 feat: admin打包脚本适配windows 2023-04-04 10:59:45 +08:00
aoshiguchen 31e3d56f9b . 2023-04-04 10:20:14 +08:00
aoshiguchen 7b344d6c0c 更新pom 2023-04-03 21:19:31 +08:00
傲世孤尘 4b68f1925d !17 升级 solon 为 2.2.8,顺带简化打包配置
Merge pull request !17 from 西东/dev
2023-04-03 12:32:05 +00:00
noear 62dcc43444 升级 solon 为 2.2.8,顺带简化打包配置 2023-04-03 20:28:54 +08:00
傲世孤尘 092fe74b78 !16 feat: server打包、client打包、启动脚本适配windows
Merge pull request !16 from 霓虹深处/dev
2023-04-03 09:08:32 +00:00
谢飞 99ecdc5cd2 feat: server打包、client打包、启动脚本适配windows 2023-04-03 14:59:57 +08:00
aoshiguchen d64d25254e groupId调整 2023-04-03 13:10:55 +08:00
aoshiguchen b6e76ecc74 域名映射功能测试、bug修复 2023-04-03 12:09:30 +08:00
aoshiguchen 8a37ae2d4b 代码优化 2023-04-03 11:08:48 +08:00
aoshiguchen 1be2326873 更新图片资源 2023-04-03 10:53:58 +08:00
aoshiguchen 22d9bb9d39 增加域名映射支持 2023-04-02 23:35:21 +08:00
aoshiguchen b07b7567e2 Merge remote-tracking branch 'origin/dev' 2023-04-02 16:33:18 +08:00
zCans 0a70d58817 根据当前启动环境,动态显示不同测试账号 2023-04-01 22:36:17 +08:00
aoshiguchen e96083e996 关闭EsLint校验 2023-04-01 20:59:46 +08:00
aoshiguchen cbbaca8590 基础包名改为org.dromara.neutrinoproxy 2023-04-01 20:41:56 +08:00
qiuyue bb4ca4daf5 feat: 百度统计 2023-03-31 13:12:56 +08:00
aoshiguchen 751ddd744a 官网调整. 2023-03-30 14:09:53 +08:00
aoshiguchen 07da864c09 调整官网样式 2023-03-30 09:58:35 +08:00
aoshiguchen 0e315b00c9 更新贡献者列表 2023-03-30 00:18:54 +08:00
傲世孤尘 96997a1f22 !15 参数注释
Merge pull request !15 from Tany/dev
2023-03-29 16:13:53 +00:00
aoshiguchen cbc4b2dced 官网首页调整. 2023-03-30 00:13:14 +08:00
aoshiguchen 79a3170234 官网文档调整. 2023-03-30 00:06:07 +08:00
tany 7033627a9f 端口池端口参数说明 2023-03-29 23:36:58 +08:00
aoshiguchen 3500e8fca8 调整官网首页"开始使用"跳转 2023-03-29 22:37:59 +08:00
aoshiguchen 215412d5d2 官网配置调整,增加CNAME配置 2023-03-29 22:32:32 +08:00
qiuyue 5665426fdb Merge remote-tracking branch 'origin/dev' into dev 2023-03-29 13:22:08 +08:00
qiuyue ca48d9ffda feat: 快速上手、如何贡献更新 2023-03-29 13:22:02 +08:00
qiuyue b8d1a36395 feat: 首页修改 2023-03-29 13:21:22 +08:00
aoshiguchen 9ca5273177 报表兼容sqlite问题修复 2023-03-29 13:16:27 +08:00
qiuyue 3f274a7e5c feat: 首页六大核心优势 2023-03-29 12:42:45 +08:00
aoshiguchen 14ba030efe 更新docker部署命令 2023-03-29 12:35:46 +08:00
aoshiguchen 8d0a796706 更新文档. 2023-03-29 09:38:08 +08:00
aoshiguchen 32f038b6de 新增管理后台使用指南 2023-03-29 00:23:54 +08:00
aoshiguchen bf432c0f44 新增"如何贡献"文档 2023-03-29 00:04:26 +08:00
aoshiguchen ef8d1b0036 新增快速使用文档 2023-03-28 23:25:00 +08:00
傲世孤尘 57117c3dd6 !14 端口池添加时,可输入端口范围:如10000-10005
Merge pull request !14 from Tany/dev
2023-03-28 12:25:14 +00:00
谭勇 d0de78ce60 端口池添加时,可输入端口范围:如10000-10005 2023-03-28 17:04:31 +08:00
qiuyue 00d2918e76 feat: 整体config修改 2023-03-28 13:06:24 +08:00
qiuyue 950f679c79 feat: 相关文章以及去除杂乱文章 2023-03-28 13:06:02 +08:00
qiuyue 41531a2ad9 feat: 最近更新 2023-03-28 13:05:38 +08:00
aoshiguchen 5a24ff6aa4 Merge branch 'feature/1.7.1' into dev 2023-03-27 22:40:31 +08:00
zCans 76129db46f 解决this指向性问题 2023-03-27 22:38:28 +08:00
qiuyue e9a2c7072d feat: 首页加入我们 2023-03-27 13:18:05 +08:00
qiuyue 221b0ca52c feat: 关于我们功能页面删减 2023-03-27 13:17:16 +08:00
aoshiguchen ed5feee43b Merge branch 'feature/1.7.1' 2023-03-26 23:42:04 +08:00
aoshiguchen 01d67312cb Merge branch 'feature/1.7.1' 2023-03-26 23:38:50 +08:00
aoshiguchen 2cb672e184 Merge branch 'feature/1.7.1' 2023-03-21 23:01:41 +08:00
aoshiguchen bb766a434a Merge branch 'feature/1.7.1' 2023-03-21 22:53:35 +08:00
aoshiguchen ec95da42ec Merge branch 'feature/1.7.1' 2023-03-20 14:25:11 +08:00
aoshiguchen 4d16ea6543 Merge branch 'feature/1.7.1' 2023-03-18 00:27:35 +08:00
aoshiguchen 0beb1c114a Merge remote-tracking branch 'origin/feature/1.7.1' 2023-03-17 23:52:17 +08:00
aoshiguchen f717fcc63a Merge remote-tracking branch 'origin/feature/1.7.1' 2023-03-12 23:04:42 +08:00
傲世孤尘 5b57cd9173 !6 增加运行时序图
Merge pull request !6 from liugddx/master
2023-03-09 07:10:19 +00:00
liugddx 0d4c5597b5 增加uml时序图 2023-03-09 14:41:26 +08:00
aoshiguchen 132353e58c 更新依赖版本 2023-02-27 10:07:27 +08:00
feng128328 8cfd4fef2c update pom.xml.
升级snakeyaml到1.31.0,1.31.0版本之前存在dos漏洞

Signed-off-by: feng128328 <[email protected]>
2023-02-26 13:22:10 +00:00
421 changed files with 5378 additions and 4378 deletions
+5 -1
View File
@@ -52,4 +52,8 @@ hs_err_pid*
**/app-**.yml
**/memo/**
**/memo/**
neutrino-proxy-vuepress/deploy.sh
.NEUTRINO_PROXY_CLIENT_ID
logs
+24 -117
View File
@@ -4,12 +4,21 @@
<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>
<a href="./LICENSE">
<img src="https://img.shields.io/badge/license-MIT-red" alt="license MIT">
</a>
</p>
# 1、简介
- 中微子代理(neutrino-proxy)是一个基于netty的、开源的java内网穿透项目。
- 技术栈:Solon、MybatisPlus、Netty
- 遵循MIT许可,因此您可以对它进行复制、修改、传播并用于任何个人或商业行为。
- 官网地址1https://neutrino-proxy.dromara.org
- 官网地址2https://dromara.gitee.io/neutrino-proxy
- 快速上手请[点击这里](https://dromara.gitee.io/neutrino-proxy/pages/793dcb)
# 2、名称由来
中微子,是轻子的一种,是组成自然界的最基本的粒子之一。它十分微小、不带电,可自由穿过地球,以接近光速运动,与其他物质的相互作用十分微弱,号称宇宙间的“隐身人”。
@@ -19,136 +28,25 @@
因此以中微子命名,寓意着中微子代理拥有中微子"安全"、"快速"、"穿透力强"的特点。
# 3、运行示例
![首页](assets/run-example/home.png)
![用户管理](assets/run-example/user-manager1.png)
![端口池管理](assets/run-example/port-pool1.png)
![License管理](assets/run-example/license1.png)
![端口映射管理](assets/run-example/port-mapping1.png)
![客户端启动示例](assets/run-example/client-run1.png)
# 4、项目结构
- neutrino-proxy
- neutrino-proxy-core 与代理相关的公共常量、编解码器
- neutrino-proxy-client 代理客户端项目
- neutrino-proxy-server 代理服务端项目
- neutrino-proxy-admin 代理监控项目(基于vue-element-admin开发)
# 4、代理示意图
![代理流程](assets/design/neutrino-proxy-process.jpg)
# 5、运行
## 5.1、使用keytool工具生成ssl证书, 若不需要ssl加密可跳过
```shell
keytool -genkey -alias test1 -keyalg RSA -keysize 1024 -validity 3650 -keypass 123456 -storepass 123456 -keystore "./test.jks"
```
## 5.2、修改服务端配置(application.yml
```yml
application:
name: neutrino-proxy-server
proxy:
protocol:
max-frame-length: 2097152
length-field-offset: 0
length-field-length: 4
initial-bytes-to-strip: 0
length-adjustment: 0
read-idle-time: 60
write-idle-time: 40
all-idle-time-seconds: 0
server:
# 服务端端口,用于保持与客户端的连接,非SSL
port: 9000
# 服务端端口,用于保持与客户端的连接,SSL,需要jks证书文件,若不需要ssl支持,可不配置
ssl-port: 9002
# 证书密码
key-store-password: 123456
key-manager-password: 123456
# 证书存放路径,若不想打进jar包,可不带classpath:前缀
jks-path: classpath:/test.jks
data:
# 数据库配置(支持mysql
type: sqlite
url: jdbc:sqlite:data.db
driver-class: org.sqlite.JDBC
username:
password:
```
## 5.3、启动服务端
> java -jar neutrino-proxy-server.jar
## 5.4、修改客户端配置
```yml
application:
name: neutrino-proxy-client
proxy:
protocol:
max-frame-length: 2097152
length-field-offset: 0
length-field-length: 4
initial-bytes-to-strip: 0
length-adjustment: 0
read-idle-time: 60
write-idle-time: 30
all-idle-time-seconds: 0
client:
# ssl证书密码
key-store-password: 123456
# ssl证书存放位置
jks-path: classpath:/test.jks
# 服务端ip,若部署到服务器,则配置服务器的ip
server-ip: localhost
# 服务端端口,若使用ssl,则需要配置为服务端的"ssl-port"
server-port: 9000
# 是否启用ssl,启用则必须配置ssl相关参数
ssl-enable: false
```
## 5.5、代理示意图
![代理流程](assets/run-example/neutrino-proxy-process.jpg)
## 5.6、启动客户端
### 5.6.1、启动参数直接指定配置
> java -jar neutrino-proxy-client.jar serverIp=localhost serverPort=9000 licenseKey=b0a907332b474b25897c4dcb31fc7eb6
### 5.6.2、启动参数指定外部配置文件
> java -jar neutrino-proxy-client.jar config=app.properties
配置文件格式如下:
```
neutrino.proxy.client.server-ip=localhost
neutrino.proxy.client.server-port=9002
neutrino.proxy.client.ssl-enable=true
neutrino.proxy.client.key-store-password=123456
neutrino.proxy.client.jks-path=classpath:/test.jks
neutrino.proxy.client.license-key=b0a907332b474b25897c4dcb31fc7eb6
```
## 5.7、Docker快速启动
```shell script
# 一键部署前端和后端,客户端按上方部署即可
docker run -it -p 9000-9200:9000-9200/tcp -p 8888:8888 -d --name neutrino registry.cn-hangzhou.aliyuncs.com/asgc/aoshiguchen-docker-images:1.64
# 如没docker环境,可以一键部署docker环境(centos 7.8)系统
curl http://www.wangke666.cn/static/createDocker.sh | bash
```
# 6、演示环境
# 5、演示环境
> 可使用分配好的游客license试用。服务器带宽较低,仅供学习使用!
- 管理后台地址:http://103.163.47.16:9527
- 游客账号:visitor/123456
# 7、未来迭代方向
- 优化代码、增强稳定性
- 服务端增加管理页面,提供报表、授权、限流等功能
- 从项目中分离、孵化出另一个开源项目(neutrino-framework)
# 8、技术文档
- [Aop](./docs/Aop.MD)
- [Channel](./docs/Channel.MD)
# 9、联系我们
# 6、联系我们
- 微信: yuyunshize
- Gitee: https://gitee.com/asgc/neutrino-proxy
# 10、贡献者列表
# 7、贡献者列表
<p>
<a href="https://gitee.com/zcans" target="_blank">
<img src="assets/developer/zcans.png" width="12%">
@@ -180,6 +78,15 @@ neutrino.proxy.client.license-key=b0a907332b474b25897c4dcb31fc7eb6
<a href="https://gitee.com/dearsny" target="_blank">
<img src="assets/developer/pingon.png" width="12%">
</a>
<a href="https://gitee.com/ixxxk" target="_blank">
<img src="assets/developer/tany.png" width="12%">
</a>
<a href="https://gitee.com/zoo-plus" target="_blank">
<img src="assets/developer/zoo-plus.png" width="12%">
</a>
<a href="https://gitee.com/MetalXingxing" target="_blank">
<img src="assets/developer/metal.png" width="12%">
</a>
</p>
# ❤️ 感谢
-39
View File
@@ -1,39 +0,0 @@
<?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.2.2</version>
</parent>
<groupId>fun.asgc</groupId>
<artifactId>job-solon-plugin</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--quartz-->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.1</version>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.8.15</version>
</dependency>
</dependencies>
</project>
@@ -1,34 +0,0 @@
package fun.asgc.solon.extend.job;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
*
* @author: aoshiguchen
* @date: 2022/9/4
*/
public class CustomThreadFactory implements ThreadFactory {
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
public CustomThreadFactory(String prefix) {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = prefix + "-thread-";
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
if (t.isDaemon()) {
t.setDaemon(false);
}
if (t.getPriority() != Thread.NORM_PRIORITY) {
t.setPriority(Thread.NORM_PRIORITY);
}
return t;
}
}
@@ -1,17 +0,0 @@
package fun.asgc.solon.extend.job;
/**
*
* @author: aoshiguchen
* @date: 2022/9/4
*/
public interface IJobCallback {
/**
* 执行日志
* @param jobInfo
* @param param
* @param throwable
*/
void executeLog(JobInfo jobInfo, String param, Throwable throwable);
}
@@ -1,34 +0,0 @@
package fun.asgc.solon.extend.job;
/**
*
* @author: aoshiguchen
* @date: 2022/9/4
*/
public interface IJobExecutor {
/**
* 初始化
* @throws JobException
*/
void init() throws Exception;
/**
* 新增job
* @param jobInfo
*/
void add(JobInfo jobInfo);
/**
* 删除job
* @param jobName
*/
void remove(String jobName);
/**
* 触发
* @param jobName
* @param param
*/
void trigger(String jobName, String param);
}
@@ -1,16 +0,0 @@
package fun.asgc.solon.extend.job;
/**
* @author: aoshiguchen
* @date: 2022/9/4
*/
public interface IJobHandler {
/**
* job执行
* @param param
* @throws Exception
*/
void execute(String param) throws Exception;
}
@@ -1,17 +0,0 @@
package fun.asgc.solon.extend.job;
import java.util.List;
/**
*
* @author: aoshiguchen
* @date: 2022/9/4
*/
public interface IJobSource {
/**
* 获取所有job列表
* @return
*/
List<JobInfo> sourceList();
}
@@ -1,26 +0,0 @@
package fun.asgc.solon.extend.job;
import fun.asgc.solon.extend.job.impl.JobExecutor;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.Solon;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
/**
*
* @author: aoshiguchen
* @date: 2022/9/4
*/
@Slf4j
public class JobBean implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
JobExecutor jobExecutor = Solon.context().getBean(JobExecutor.class);
if (null != jobExecutor) {
jobExecutor.execute(jobExecutionContext);
}
}
}
@@ -1,23 +0,0 @@
package fun.asgc.solon.extend.job;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Map;
/**
*
* @author: aoshiguchen
* @date: 2022/9/4
*/
@Accessors(chain = true)
@Data
public class JobInfo {
private String id;
private String name;
private String desc;
private String cron;
private String param;
private boolean enable;
private Map<String, Object> extension;
}
@@ -1,45 +0,0 @@
package fun.asgc.solon.extend.job;
import fun.asgc.solon.extend.job.annotation.EnableJob;
import fun.asgc.solon.extend.job.impl.DefaultJobCallback;
import fun.asgc.solon.extend.job.impl.DefaultJobSource;
import fun.asgc.solon.extend.job.impl.JobExecutor;
import org.noear.solon.Solon;
import org.noear.solon.core.AopContext;
import org.noear.solon.core.Plugin;
import org.noear.solon.core.event.AppLoadEndEvent;
/**
* @author: aoshiguchen
* @date: 2023/3/11
*/
public class XPluginImp implements Plugin {
@Override
public void start(AopContext context) throws Throwable {
EnableJob enableJob = Solon.app().source().getAnnotation(EnableJob.class);
if (null == enableJob || !enableJob.value()) {
return;
}
//应用加载完后,再启动任务
Solon.app().onEvent(AppLoadEndEvent.class, e -> {
IJobSource jobSource = context.getBean(IJobSource.class);
IJobCallback jobCallback = context.getBean(IJobCallback.class);
if (null == jobSource) {
jobSource = new DefaultJobSource();
}
if (null == jobCallback) {
jobCallback = new DefaultJobCallback();
}
JobExecutor jobExecutor = new JobExecutor();
jobExecutor.setJobSource(jobSource);
jobExecutor.setJobCallback(jobCallback);
jobExecutor.start();
context.wrapAndPut(JobExecutor.class, jobExecutor);
});
}
}
@@ -1,16 +0,0 @@
package fun.asgc.solon.extend.job.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author: aoshiguchen
* @date: 2023/3/12
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableJob {
boolean value() default true;
}
@@ -1,20 +0,0 @@
package fun.asgc.solon.extend.job.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* @author: aoshiguchen
* @date: 2022/9/4
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface JobHandler {
String name();
String desc() default "";
String cron();
String param() default "";
}
@@ -1,23 +0,0 @@
package fun.asgc.solon.extend.job.impl;
import fun.asgc.solon.extend.job.IJobCallback;
import fun.asgc.solon.extend.job.JobInfo;
import lombok.extern.slf4j.Slf4j;
/**
* @author: aoshiguchen
* @date: 2023/3/12
*/
@Slf4j
public class DefaultJobCallback implements IJobCallback {
@Override
public void executeLog(JobInfo jobInfo, String param, Throwable throwable) {
if (null == throwable) {
log.debug("[Solon Plugin Job] Job执行 id:{} name:{} desc:{} param:{}", jobInfo.getId(), jobInfo.getName(), jobInfo.getDesc(), param);
} else {
log.error("[Solon Plugin Job] Job执行 id:{} name:{} desc:{} param:{}", jobInfo.getId(), jobInfo.getName(), jobInfo.getDesc(), param, throwable);
}
}
}
@@ -1,44 +0,0 @@
package fun.asgc.solon.extend.job.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import fun.asgc.solon.extend.job.IJobHandler;
import fun.asgc.solon.extend.job.IJobSource;
import fun.asgc.solon.extend.job.annotation.JobHandler;
import fun.asgc.solon.extend.job.JobInfo;
import org.noear.solon.Solon;
import java.util.List;
/**
*
* @author: aoshiguchen
* @date: 2022/9/4
*/
public class DefaultJobSource implements IJobSource {
@Override
public List<JobInfo> sourceList() {
List<IJobHandler> jobHandlerList = Solon.context().getBeansOfType(IJobHandler.class);
if (CollectionUtil.isEmpty(jobHandlerList)) {
return CollectionUtil.newArrayList();
}
List<JobInfo> jobInfoList = CollectionUtil.newArrayList();
for (IJobHandler jobHandler : jobHandlerList) {
JobHandler handler = jobHandler.getClass().getAnnotation(JobHandler.class);
if (null == handler || StrUtil.isEmpty(handler.name()) || StrUtil.isEmpty(handler.cron())) {
continue;
}
jobInfoList.add(new JobInfo()
.setId(handler.name())
.setName(handler.name())
.setDesc(handler.desc())
.setCron(handler.cron())
.setParam(handler.param())
.setEnable(true)
);
}
return jobInfoList;
}
}
@@ -1,175 +0,0 @@
package fun.asgc.solon.extend.job.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import fun.asgc.solon.extend.job.*;
import fun.asgc.solon.extend.job.annotation.JobHandler;
import org.noear.solon.Solon;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Job执行器
* @author: aoshiguchen
* @date: 2022/9/4
*/
public class JobExecutor implements IJobExecutor {
private IJobSource jobSource;
private ThreadPoolExecutor threadPoolExecutor;
private Map<String, JobInfo> jobInfoMap = new ConcurrentHashMap<>();
private SchedulerFactory schedulerFactory;
private Scheduler scheduler;
private Map<String, IJobHandler> jobHandlerMap = new ConcurrentHashMap<>();
private Set<String> runJobSet = CollectionUtil.newHashSet();
private IJobCallback jobCallback;
private Map<String, TriggerKey> triggerKeyMap = new ConcurrentHashMap<>();
public void start() {
if (null == threadPoolExecutor) {
threadPoolExecutor = new ThreadPoolExecutor(5, 20, 10L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(), new CustomThreadFactory("SolonJob"));
}
List<IJobHandler> jobHandlerList = Solon.context().getBeansOfType(IJobHandler.class);
if (!CollectionUtil.isEmpty(jobHandlerList)) {
for (IJobHandler item : jobHandlerList) {
JobHandler jobHandler = item.getClass().getAnnotation(JobHandler.class);
if (null == jobHandler) {
continue;
}
jobHandlerMap.put(jobHandler.name(), item);
}
}
try {
this.schedulerFactory = new StdSchedulerFactory();
this.scheduler = schedulerFactory.getScheduler();
this.init();
} catch (Exception e){
throw new RuntimeException("job初始化异常");
}
}
public void setJobSource(IJobSource jobSource) {
this.jobSource = jobSource;
}
public void setThreadPoolExecutor(ThreadPoolExecutor threadPoolExecutor) {
this.threadPoolExecutor = threadPoolExecutor;
}
public void setJobCallback(IJobCallback jobCallback) {
this.jobCallback = jobCallback;
}
@Override
public void init() throws Exception {
List<JobInfo> jobInfoList = jobSource.sourceList();
if (CollectionUtil.isEmpty(jobInfoList)) {
return;
}
for (JobInfo jobInfo : jobInfoList) {
add(jobInfo);
}
}
@Override
public void add(JobInfo jobInfo) {
if (null == jobInfo || StrUtil.isEmpty(jobInfo.getId()) || StrUtil.isEmpty(jobInfo.getName()) ||
StrUtil.isEmpty(jobInfo.getCron())) {
return;
}
synchronized (jobInfo.getId()) {
runJobSet.add(jobInfo.getName());
jobInfoMap.put(jobInfo.getId(), jobInfo);
TriggerKey triggerKey = TriggerKey.triggerKey(jobInfo.getId());
triggerKeyMap.put(jobInfo.getId(), triggerKey);
JobKey jobKey = new JobKey(jobInfo.getName());
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobInfo.getCron()).withMisfireHandlingInstructionDoNothing();
CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
JobDetail jobDetail = JobBuilder.newJob(JobBean.class).withIdentity(jobKey).build();
try {
if (jobInfo.isEnable()) {
scheduler.scheduleJob(jobDetail, cronTrigger);
scheduler.start();
}
} catch (Exception e) {
throw new RuntimeException(String.format("新增job[name=%s]异常", jobInfo.getName()));
}
}
}
@Override
public void remove(String jobId) {
JobInfo jobInfo = jobInfoMap.get(jobId);
if (null == jobInfo) {
return;
}
synchronized (jobId) {
runJobSet.remove(jobInfo.getName());
unscheduleJob(jobId);
}
}
@Override
public void trigger(String jobId, String param) {
doExecute(jobId, param);
}
public void execute(JobExecutionContext context) throws JobExecutionException {
if (null == context || null == context.getTrigger()) {
return;
}
String jobId = context.getTrigger().getKey().getName();
JobInfo jobInfo = jobInfoMap.get(jobId);
if (null == jobInfo) {
unscheduleJob(jobId);
return;
}
doExecute(jobId, jobInfo.getParam());
}
private void unscheduleJob(String jobId) {
TriggerKey triggerKey = triggerKeyMap.get(jobId);
if (null != triggerKey) {
try {
scheduler.unscheduleJob(triggerKey);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void doExecute(String jobId, String param) {
JobInfo jobInfo = jobInfoMap.get(jobId);
if (null == jobInfo) {
return;
}
IJobHandler jobHandler =jobHandlerMap.get(jobInfo.getName());
if (null == jobHandler) {
return;
}
threadPoolExecutor.submit(() -> {
try {
jobHandler.execute(param);
if (null != jobCallback) {
jobCallback.executeLog(jobInfo, param, null);
}
} catch (Throwable e) {
jobCallback.executeLog(jobInfo, param, e);
}
});
}
}
@@ -1 +0,0 @@
package fun.asgc.solon.extend.job;
@@ -1,2 +0,0 @@
solon.plugin=fun.asgc.solon.extend.job.XPluginImp
solon.plugin.priority=2
-30
View File
@@ -1,30 +0,0 @@
<?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.2.2</version>
</parent>
<groupId>fun.asgc</groupId>
<artifactId>orika-solon-plugin</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon</artifactId>
</dependency>
<!--orika-->
<dependency>
<groupId>ma.glasnost.orika</groupId>
<artifactId>orika-core</artifactId>
<version>1.5.4</version>
</dependency>
</dependencies>
</project>
@@ -1,33 +0,0 @@
package fun.asgc.solon.extend.orika;
import ma.glasnost.orika.CustomConverter;
import ma.glasnost.orika.Mapper;
import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import ma.glasnost.orika.metadata.ClassMapBuilder;
import org.noear.solon.core.AopContext;
import org.noear.solon.core.Plugin;
/**
* @author: aoshiguchen
* @date: 2023/3/11
*/
public class XPluginImp implements Plugin {
@Override
public void start(AopContext context) throws Throwable {
DefaultMapperFactory factory = new DefaultMapperFactory.Builder().build();
context.subBeansOfType(CustomConverter.class, bean -> {
factory.getConverterFactory().registerConverter(bean);
});
context.subBeansOfType(Mapper.class, bean -> {
factory.registerMapper(bean);
});
context.subBeansOfType(ClassMapBuilder.class, bean -> {
factory.registerClassMap((ClassMapBuilder<? extends Object, ? extends Object>) bean);
});
context.wrapAndPut(MapperFactory.class, factory);
context.wrapAndPut(MapperFacade.class, factory.getMapperFacade());
}
}
@@ -1 +0,0 @@
package fun.asgc.solon.extend.orika;
@@ -1,2 +0,0 @@
solon.plugin=fun.asgc.solon.extend.orika.XPluginImp
solon.plugin.priority=1
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Before

Width:  |  Height:  |  Size: 183 KiB

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 804 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 354 KiB

After

Width:  |  Height:  |  Size: 217 KiB

+3
View File
@@ -52,3 +52,6 @@
通过`ProxyChannel`向服务端发送`Transfer`指令,并携带响应数据。
- 5.4、服务端通过`ProxyChannel`收到来自客户端的`Transfer`指令,找到`ProxyChannel`绑定的`VisitorChannel`
通过`VisitorChannel`向用户端写入响应数据。
# 请求流程图
![流程图](../assets/design/flowchart.jpg)
Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 KiB

+113
View File
@@ -0,0 +1,113 @@
# 内网穿透神器NeutrinoProxy 1.8.0版本发布
## 项目简介
- [中微子代理(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、采用最为宽松的MIT协议,免去你的后顾之忧
## 快速使用
> 更多使用姿势、细节请通过官网或结尾微信二维码加我备注"中微子代理"入群交流。
### 1、 部署服务端
#### 1.1、 Docker一键部署
> 当前最新版本为1.8.0,下面的脚本中,可以使用:`registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:1.8.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:
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`启动客户端
- 查看服务端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%"/>
+37
View File
@@ -0,0 +1,37 @@
# 🎋代码分支说明
- 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上fork项目到自己的仓库
- 将fork后的自己仓库的项目clone到本地
- 拉取dev分支最新代码,并修改
- commit并push到自己的仓库
- 登录Gitee在的仓库上可以看到一个 pull request 按钮,点击它,填写一些说明信息,然后提交即可。
- 等待合并
# 📐PR的规范
> 你可能发现有的代码并不符合这个规范,但我们后续都会朝着这个方向迈进。后续我们会逐步全面规范化,请先保证新提交的代码满足以下要求:
- 注释完备,尤其每个新增的方法应按照Java文档规范标明方法说明、参数说明、返回值说明等信息,必要时请添加单元测试,如果愿意,也可以加上你的大名。
- 原则上,不允许自行添加第三方依赖库。若确有必要,请先和项目负责人沟通达成一致。
- 对现有代码的优化,请在注释中说明原因。
- 如果涉及到数据库的变更,则至少需要做到以下绩点:
- sqlite、mysql下均测试通过
-`./neutrino-proxy-server/src/main/resource/sql`下维护更新对应的表结构sql文件
- 对于新增的表,维护必要的初始化数据
- 在对应的mysql、sqlite的update目录下,维护SQL变更记录,确保SQL执行测试通过
+79
View File
@@ -0,0 +1,79 @@
# 1、部署服务端
## 1.1、Docker一键部署
### 使用默认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:1.7.1
```
### 指定自己的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: wHCvf3@hmw^D*
```
- 然后执行如下命令:
```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:1.7.1
```
## 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:
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`启动客户端
- 查看服务端License管理,刷新页面,对应的License在线状态为`在线`,则表明客户端已正常连接。
# 4、代理验证
- 本地启动被代理服务,如:redis、本地web项目、本地mysql等等
- 先确保本地能正常访问被代理服务,如果本地都不能访问,不用想代理更不可能!!!
- 通过服务端IP+9101(上面License配置的端口映射重的服务端端口)访问本地被代理服务
访问成功,至此首次完整的内网穿透体验完成。开源不易,请速至[Gitee仓库](https://gitee.com/dromara/neutrino-proxy)一键三连🤝
+38
View File
@@ -0,0 +1,38 @@
# 本地启动SpringBoot,提供hello接口以供测试:
## 1、10线程 100连接直接测试
> wrk -t 10 -c 100 -d 1s http://localhost:8080/hello
测试结果:
```
10 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 10.51ms 5.82ms 53.28ms 83.61%
Req/Sec 0.99k 123.01 1.21k 81.82%
10797 requests in 1.10s, 1.35MB read
Requests/sec: 9799.85
Transfer/sec: 1.23MB
```
## 2、10线程 100连接走端口转发测试
> wrk -t 10 -c 100 -d 1s http://localhost:9105/hello
测试结果:
```
10 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 126.37ms 163.69ms 971.32ms 81.43%
Req/Sec 24.04 21.06 80.00 84.62%
70 requests in 1.02s, 8.96KB read
Requests/sec: 68.46
Transfer/sec: 8.76K
```
## 3、10线程 100连接走域名转发测试
> wrk -t 10 -c 100 -d 1s http://test1.neutrino-proxy.asgc.fun/hello
测试结果:
```
10 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 327.86ms 218.93ms 903.28ms 56.76%
Req/Sec 30.65 20.46 88.00 62.67%
259 requests in 1.02s, 44.77KB read
Requests/sec: 253.75
Transfer/sec: 43.86KB
```
+30
View File
@@ -0,0 +1,30 @@
# 首页
- License统计:统计License相关数量指标
- 端口映射统计:统计端口映射相关数量指标
- 今日流量:统计当天的服务端上行/下行流量数据
- 流量汇总:统计所有(包含当天)的服务端上行/下行流量数据
- 流量监控:按天统计最近15天(可能会动态调整)每天的上行流量、下行流量、汇总流量
# 代理配置
- License管理:License是客户端连接服务端的唯一合法凭证。一个License同时只能被一个客户端使用,一个License可以维护多条端口映射
- 端口映射:服务端IP+端口 -> 客户端IP+端口的四元组映射(因目前服务端单节点只有一个公网IP,所以不体现出来),是内网穿透的基本单元。
# 系统管理
- 用户管理:支持多用户,一个用户可持有多个License。由于项目目前主推个人版,所以暂是没有权限这一套,管理员之外的所有用户都属于游客。对于绝大多数操作,游客仅有只读权限。
- 端口池管理:用于统一管理服务器内网穿透端口,方便统一设置安全组。
- 端口池分组:对端口池的一个分组。
- 全局分组:该分组下的端口全局通用。
- 用户分组:该分组下的端口由分组绑定的用户独占。
- License分组:该分组下的端口由分组绑定的License独占。
- 调度管理:维护服务端定时任务。方便开发、调试。正常使用时无需关心。
# 报表管理
- 用户流量报表:基于用户维度的流量统计
- License流量报表:基于License的流量统计
- 用户流量月度明细:基于用户的流量月度统计
- License流量月度明细:基于License的流量月度统计
# 日志管理
- 调度日志:服务端定时任务执行日志
- 登录日志:管理后端登录、退出登录的日志
- 客户端连接日志:客户端连接、断开的日志
@@ -40,7 +40,8 @@ module.exports = {
},
module: {
rules: [
...(config.dev.useEslint ? [createLintingRule()] : []),
// 关闭EsLint校验
// ...(config.dev.useEslint ? [createLintingRule()] : []),
{
test: /\.vue$/,
loader: 'vue-loader',
+3 -1
View File
@@ -1,5 +1,7 @@
module.exports = {
NODE_ENV: '"development"',
ENV_CONFIG: '"dev"',
BASE_API: '"http://103.163.47.16:8888"'
BASE_API: '"http://103.163.47.16:8888"',
USER_NAME: '"visitor"',
USER_PWD: '"123456"'
}
+1 -1
View File
@@ -1,5 +1,5 @@
module.exports = {
NODE_ENV: '"development"',
ENV_CONFIG: '"docker"',
BASE_API: '"/"'
BASE_API: '"/neutrino-proxy-server"'
}
+9
View File
@@ -0,0 +1,9 @@
import request from '@/utils/request'
export function domainNameBindInfo(query) {
return request({
url: '/domain-name/bind-info',
method: 'get',
params: query
})
}
+10
View File
@@ -60,3 +60,13 @@ export function deletePortPool(id) {
}
})
}
export function deleteBatchPortPool(ids) {
return request({
url: '/port-pool/delete-batch',
method: 'post',
data: {
ids: ids
}
})
}
+17
View File
@@ -0,0 +1,17 @@
import request from '@/utils/request'
export function fetchList(query) {
return request({
url: '/protocal/page',
method: 'get',
params: query
})
}
export function protocalList(query) {
return request({
url: '/protocal/list',
method: 'get',
params: query
})
}
@@ -8,60 +8,68 @@
<el-button size="mini" @click="handleCancelClick">{{cancelText}}</el-button>
<el-button type="primary" size="mini" @click="handleCommitClick">{{okText}}</el-button>
</div>
<el-button slot="reference" :type="type" :size="size">{{buttonText}}</el-button>
<el-button slot="reference" :type="type" :size="size" :icon="icon" :disabled="disabled">{{buttonText}}</el-button>
</el-popover>
</template>
<script>
export default {
name: 'deleteButton',
props: {
width: {
type: Number,
default: 160
export default {
name: 'deleteButton',
props: {
width: {
type: Number,
default: 160
},
buttonText: {
type: String,
default: '删除'
},
type: {
type: String,
default: 'danger'
},
size: {
type: String,
default: 'mini'
},
icon: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
},
title: {
type: String,
default: '确定删除吗?'
},
okText: {
type: String,
default: '确定'
},
cancelText: {
type: String,
default: '取消'
}
},
buttonText: {
type: String,
default: '删除'
data() {
return {
visible: false
}
},
type: {
type: String,
default: 'danger'
},
size: {
type: String,
default: 'mini'
},
title: {
type: String,
default: '确定删除吗?'
},
okText: {
type: String,
default: '确定'
},
cancelText: {
type: String,
default: '取消'
}
},
data() {
return {
visible: false
}
},
methods: {
handleCancelClick() {
this.visible = false
this.$emit('handleCancelClick')
},
handleCommitClick() {
this.visible = false
this.$emit('handleCommitClick')
methods: {
handleCancelClick() {
this.visible = false
this.$emit('handleCancelClick')
},
handleCommitClick() {
this.visible = false
this.$emit('handleCommitClick')
}
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
+3 -1
View File
@@ -101,8 +101,10 @@ export default {
publish: 'Publish',
draft: 'Draft',
delete: 'Delete',
deleteBatch: 'DeleteBatch',
cancel: 'Cancel',
confirm: 'Confirm'
confirm: 'Confirm',
portOrRange: 'example: 3306 or 1000-2000'
},
errorLog: {
tips: 'Please click the bug icon in the upper right corner',
+7 -1
View File
@@ -49,6 +49,7 @@ export default {
system: '系统管理',
portPool: '端口池管理',
portGroup: '端口分组管理',
protocal: '协议管理',
proxy: '代理配置',
license: 'License管理',
portMapping: '端口映射',
@@ -120,6 +121,7 @@ export default {
publish: '发布',
draft: '草稿',
delete: '删除',
deleteBatch: '批量删除',
cancel: '取 消',
confirm: '确 定',
userId: '用户ID',
@@ -136,6 +138,7 @@ export default {
enable: '启用',
loginName: '登录名',
port: '端口',
portOrRange: '端口或范围,如: 3306或1000-2000',
licenseName: 'License名称',
licenseKey: 'License Key',
isOnline: '在线状态',
@@ -161,7 +164,10 @@ export default {
resetKey: '重置Key',
upFlow: '上行流量',
downFlow: '下行流量',
totalFlow: '总流量'
totalFlow: '总流量',
protocalName: '协议',
supportStatus: '支持状态',
domainName: '域名'
},
button: {
lookOver: '查看'
+1
View File
@@ -16,6 +16,7 @@ import './icons' // icon
import './errorLog'// error log
import './permission' // permission control
import './mock' // simulation data
import './plugins/baiduhm' // 百度统计
import * as filters from './filters' // global filters
@@ -0,0 +1,7 @@
const _hmt = _hmt || [];
(function() {
const hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?173e771eef816c412396d2cb4fe2d632";
const s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
+1
View File
@@ -77,6 +77,7 @@ export const asyncRouterMap = [
{ path: 'user', component: _import('system/user'), name: 'user', meta: { title: 'user' }},
{ path: 'portGroup', component: _import('system/portGroup'), name: 'portGroup', meta: { title: 'portGroup' }},
{ path: 'portPool', component: _import('system/portPool'), name: 'portPool', meta: { title: 'portPool' }},
{ path: 'protocal', component: _import('system/protocal'), name: 'protocal', meta: { title: 'protocal' }},
{ path: 'jobManager', component: _import('system/jobManager'), name: 'jobManager', meta: { title: 'jobManager' }}
]
},
@@ -5,7 +5,6 @@
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
let that = null
export default {
props: {
@@ -42,7 +41,6 @@ export default {
}
},
mounted() {
that = this
this.initChart()
},
beforeDestroy() {
@@ -68,8 +66,8 @@ export default {
},
tooltip: {
trigger: 'item',
formatter: function(value) {
return `${value.seriesName} <br/>${value.marker} : ${value.name} ${value.name === '上行' ? that.data.upFlowDesc : that.data.downFlowDesc}`
formatter: (value) => {
return `${value.seriesName} <br/>${value.marker} : ${value.name} ${value.name === '上行' ? this.data.upFlowDesc : this.data.downFlowDesc}`
}
},
legend: {
@@ -6,8 +6,6 @@
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { getSizeDescByByteCount } from '@/utils/utils'
let that = null
export default {
props: {
chartId: {
@@ -38,7 +36,6 @@ export default {
}
},
mounted() {
that = this
this.initChart()
},
beforeDestroy() {
@@ -53,12 +50,11 @@ export default {
this.chartDom = document.getElementById(this.chartId)
this.myChart = echarts.init(this.chartDom)
const seriesList = []
const legendList = []
this.data.list && this.data.list.forEach((item, index) => {
seriesList.push({
name: item.name,
type: 'line',
stack: 'Total',
// stack: 'Total',
data: item.value,
areaStyle: {
normal: {
@@ -77,7 +73,6 @@ export default {
},
smooth: true
})
legendList.push(item.name)
})
const option = {
@@ -87,16 +82,16 @@ export default {
},
tooltip: {
trigger: 'axis',
formatter: function(value) {
let title = that.data.text + '<br/>'
formatter: (value) => {
let title = this.data.text + '<br/>'
value.forEach(item => {
title = title + item.marker + item.seriesName + ' : ' + that.data.list[item.seriesIndex].label[item.dataIndex] + '<br/>'
title = title + item.marker + item.seriesName + ' : ' + getSizeDescByByteCount(item.data) + '<br/>'
})
return title
}
},
legend: {
data: legendList,
data: this.data.legendList,
left: 'right'
},
grid: {
@@ -113,7 +108,7 @@ export default {
yAxis: {
type: 'value',
axisLabel: {
formatter: function(value) {
formatter: (value) => {
return getSizeDescByByteCount(value)
}
}
@@ -70,37 +70,19 @@ export default {
})
},
getChartData(last7dFlow) {
const title = []
const downFlowDesc = []
const totalFlowDesc = []
const upFlowDesc = []
last7dFlow.dataList.forEach(item => {
title.push(item.dateStr)
totalFlowDesc.push(item.totalFlowDesc)
downFlowDesc.push(item.downFlowDesc)
upFlowDesc.push(item.upFlowDesc)
})
const list = []
last7dFlow.seriesList.forEach(item => {
let label = []
if (item.seriesName.indexOf('上') > -1) {
label = upFlowDesc
} else if (item.seriesName.indexOf('下') > -1) {
label = downFlowDesc
} else if (item.seriesName.indexOf('总') > -1) {
label = totalFlowDesc
}
list.push({
name: item.seriesName,
value: item.seriesData,
label: label
value: item.seriesData
})
})
return {
text: '流量监控',
subtext: `最近${last7dFlow.dataList.length || 0}天流量监控`,
title: title,
list: list
title: last7dFlow.xDate,
list: list,
legendList: last7dFlow.legendData
}
}
}
+16 -6
View File
@@ -27,9 +27,9 @@
<el-button type="primary" style="width:100%;margin-bottom:30px;" :loading="loading" @click.native.prevent="handleLogin">{{$t('login.logIn')}}</el-button>
<div class="tips">
<span>{{$t('login.username')}} : visitor</span>
<span>{{$t('login.password')}} : 123456</span>
<div class="tips" v-if="user_name && user_pwd">
<span>{{$t('login.username')}} : {{ user_name }}</span>
<span>{{$t('login.password')}} : {{ user_pwd }}</span>
</div>
</el-form>
@@ -60,8 +60,8 @@ export default {
}
return {
loginForm: {
username: 'visitor',
password: '123456'
username: '',
password: ''
},
loginRules: {
username: [{ required: true, trigger: 'blur', validator: validateUsername }],
@@ -69,7 +69,9 @@ export default {
},
passwordType: 'password',
loading: false,
showDialog: false
showDialog: false,
user_name: undefined,
user_pwd: undefined
}
},
methods: {
@@ -115,6 +117,14 @@ export default {
// }
}
},
mounted() {
if (process.env.USER_NAME) {
this.user_name = process.env.USER_NAME
this.user_pwd = process.env.USER_PWD
this.loginForm.username = process.env.USER_NAME
this.loginForm.password = process.env.USER_PWD
}
},
created() {
// window.addEventListener('hashchange', this.afterQRScan)
},
@@ -1,13 +1,17 @@
<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">
<el-select v-model="listQuery.userId" placeholder="请选择用户" 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">
<el-select v-model="listQuery.licenseId" placeholder="请选择license" 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-input v-model="listQuery.serverPort" type="text" style="width:150px;margin-right:10px" class="filter-item"
<el-select v-model="listQuery.protocal" placeholder="请选择协议" clearable style="margin-right:10px;width: 120px;">
<el-option v-for="item in protocalList" :key="item.name" :label="item.name" :value="item.name"
:disabled="!item.enable" />
</el-select>
<el-input v-model="listQuery.serverPort" type="text" style="width:145px;margin-right:10px" class="filter-item"
placeholder="请输入服务端端口" :maxlength="5" show-word-limit />
<el-select v-model="listQuery.isOnline" placeholder="请选择在线状态" clearable style="width:145px;margin-right:10px">
<el-option v-for="item in selectObj.onlineOptions" :key="item.value" :label="item.label" :value="item.value" />
@@ -15,6 +19,8 @@
<el-select v-model="listQuery.enable" placeholder="请选择启用状态" clearable style="width:145px;margin-right:10px">
<el-option v-for="item in selectObj.statusOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-input v-model="listQuery.description" type="text" style="width:145px;margin-right:10px" class="filter-item"
placeholder="请输入描述详情" show-word-limit />
<el-button class="filter-item" type="primary" v-waves icon="el-icon-search" @click="handleFilter">{{
$t('table.search') }}</el-button>
<el-button class="filter-item" style="margin-left: 10px;" @click="handleCreate" type="primary"
@@ -33,21 +39,37 @@
<span>{{ scope.row.userName }}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.licenseName')" width="150">
<el-table-column align="center" :label="$t('table.licenseName')" width="130">
<template slot-scope="scope">
<span>{{ scope.row.licenseName }}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.serverPort')" width="200">
<el-table-column align="center" :label="$t('table.protocalName')" width="100">
<template slot-scope="scope">
<span>{{ scope.row.protocal }}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.domainName')" width="200">
<template slot-scope="scope">
<span>{{ scope.row.domain }}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.serverPort')" width="80">
<template slot-scope="scope">
<span>{{ scope.row.serverPort }}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.proxyClient')" width="200">
<el-table-column align="center" :label="$t('table.proxyClient')" width="120">
<template slot-scope="scope">
<span>{{ scope.row.clientIp }}:{{ scope.row.clientPort }}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.desc')" width="120">
<template slot-scope="scope">
<span>{{ scope.row.description }}</span>
</template>
</el-table-column>
<el-table-column width="150px" align="center" :label="$t('table.createTime')">
<template slot-scope="scope">
<span>{{ scope.row.createTime | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
@@ -113,8 +135,15 @@
style="width: 280px"
/>-->
</el-form-item>
<el-form-item :label="$t('协议')" prop="protocal">
<el-select style="width: 280px;" class="filter-item" v-model="temp.protocal" placeholder="请选择">
<el-option v-for="item in protocalList" :key="item.name" :label="item.name" :value="item.name"
:disabled="!item.enable">
</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="请选择">
<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>
@@ -125,6 +154,14 @@
<el-form-item :label="$t('客户端端口')" prop="clientPort">
<el-input v-model="temp.clientPort"></el-input>
</el-form-item>
<el-form-item :label="$t('域名')" prop="subdomain" v-if="(temp.protocal === 'HTTP' || temp.protocal === 'HTTP(S)') && domainName && domainName != ''">
<el-input v-model="temp.subdomain">
<template slot="append">.{{ domainName }}</template>
</el-input>
</el-form-item>
<el-form-item :label="$t('描述')" prop="description">
<el-input v-model="temp.description"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="
@@ -152,7 +189,9 @@
import { fetchList, createUserPortMapping, updateUserPortMapping, updateEnableStatus, deletePortMapping } from '@/api/portMapping'
import { portPoolList, availablePortList } from '@/api/portPool'
import { licenseList, licenseAuthList } from '@/api/license'
import { protocalList } from '@/api/protocal'
import { userList } from '@/api/user'
import { domainNameBindInfo } from '@/api/domain'
import waves from '@/directive/waves' // 水波纹指令
import { parseTime } from '@/utils'
import ButtonPopover from '../../components/Button/buttonPopover'
@@ -196,7 +235,8 @@ export default {
license: undefined,
port: undefined,
isOnline: undefined,
enable: undefined
enable: undefined,
description: undefined
},
importanceOptions: [1, 2, 3],
@@ -205,8 +245,10 @@ export default {
statusOptions: ['published', 'draft', 'deleted'],
userList: [],
licenseList: [],
protocalList: [],
licenseAuthList: [],
serverPortList: [],
domainName: '',
showReviewer: false,
temp: {
id: undefined,
@@ -214,7 +256,8 @@ export default {
licenseName: undefined,
serverPort: undefined,
clientIp: undefined,
clientPort: undefined
clientPort: undefined,
protocal: undefined
},
selectObj: {
statusOptions: [{ label: '启用', value: 1 }, { label: '禁用', value: 2 }],
@@ -232,7 +275,8 @@ export default {
licenseId: [{ required: true, message: '请选择License', trigger: 'blur,change' }],
serverPort: [{ required: true, message: '请输入服务端端口', trigger: 'blur' }],
clientIp: [{ required: true, message: '请输入客户端IP', trigger: 'blur' }],
clientPort: [{ required: true, message: '请输入客户端端口', trigger: 'blur' }]
clientPort: [{ required: true, message: '请输入客户端端口', trigger: 'blur' }],
protocal: [{ required: true, message: '请选择协议', trigger: 'blur' }]
},
downloadLoading: false,
countryColumns: [
@@ -268,9 +312,11 @@ export default {
}
},
created() {
this.getDomainNameBindInfo()
this.getDataList()
this.getLicenseList()
this.getLicenseAuthList()
this.getProtocalList()
},
methods: {
getList() {
@@ -292,6 +338,11 @@ export default {
this.getList()
})
},
getDomainNameBindInfo() {
domainNameBindInfo().then(response => {
this.domainName = response.data.data
})
},
getPortPoolList() {
portPoolList().then(response => {
this.serverPortList = response.data.data
@@ -312,6 +363,11 @@ export default {
this.licenseList = response.data.data
})
},
getProtocalList() {
protocalList().then(response => {
this.protocalList = response.data.data
})
},
getLicenseAuthList() {
licenseAuthList().then(response => {
this.licenseAuthList = response.data.data
@@ -6,10 +6,13 @@
</el-select>
<el-button class="filter-item" type="primary" v-waves icon="el-icon-search" @click="handleFilter">{{$t('table.search')}}</el-button>
<el-button class="filter-item" style="margin-left: 10px;" @click="handleCreate" type="primary" icon="el-icon-edit">{{$t('table.add')}}</el-button>
<button-popover class="filter-item" style="margin-left: 10px;" size="medium" icon="el-icon-delete"
:buttonText="$t('table.deleteBatch')" :disabled="checkBoxData.length==0" @handleCommitClick="handleDeleteBatch" />
</div>
<el-table :key='tableKey' :data="list" v-loading="listLoading" element-loading-text="给我一点时间" border fit highlight-current-row
style="width: 100%">
style="width: 100%" @selection-change="onTableRowChange">
<el-table-column align="center" width="40" type="selection" />
<el-table-column align="center" :label="$t('table.id')" width="120">
<template slot-scope="scope">
<span>{{scope.row.id}}</span>
@@ -60,7 +63,7 @@
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
<el-form :rules="rules" ref="dataForm" :model="temp" label-position="left" label-width="70px" style='width: 400px; margin-left:50px;'>
<el-form-item :label="$t('table.port')" prop="port">
<el-input v-model="temp.port" :disabled="dialogStatus=='update'"></el-input>
<el-input :placeholder="$t('table.portOrRange')" v-model="temp.port" :disabled="dialogStatus=='update'"></el-input>
</el-form-item>
<el-form-item :label="$t('table.group')" prop="group">
@@ -92,13 +95,13 @@
</template>
<script>
import { fetchList, updateEnableStatus, createPortPool, deletePortPool, updatePortPool } from '@/api/portPool'
import { portGroupList } from '@/api/portGroup'
import waves from '@/directive/waves' // 水波纹指令
import { parseTime } from '@/utils'
import ButtonPopover from '../../components/Button/buttonPopover'
import { createPortPool, deletePortPool, deleteBatchPortPool, fetchList, updateEnableStatus, updatePortPool } from '@/api/portPool'
import { portGroupList } from '@/api/portGroup'
import waves from '@/directive/waves' // 水波纹指令
import { parseTime } from '@/utils'
import ButtonPopover from '../../components/Button/buttonPopover'
const calendarTypeOptions = [
const calendarTypeOptions = [
{ key: 'CN', display_name: 'China' },
{ key: 'US', display_name: 'USA' },
{ key: 'JP', display_name: 'Japan' },
@@ -157,7 +160,8 @@
rules: {
port: [{ required: true, message: '端口必填', trigger: 'blur' }]
},
downloadLoading: false
downloadLoading: false,
checkBoxData:[], //表单勾选的行
}
},
filters: {
@@ -320,6 +324,24 @@
return v[j]
}
}))
},
onTableRowChange(val){ //表单勾选后赋值
this.checkBoxData = val
},
handleDeleteBatch(){ //批量删除操作
let ids = []
this.checkBoxData.forEach(item => {ids.push(item.id)})
deleteBatchPortPool(ids).then(response => {
if (response.data.code === 0) {
this.$notify({
title: '成功',
message: '删除成功',
type: 'success',
duration: 2000
})
this.getList()
}
})
}
}
}
@@ -0,0 +1,169 @@
<template>
<div class="app-container calendar-list-container">
<div class="filter-container">
<el-button class="filter-item" type="primary" v-waves icon="el-icon-search" @click="handleFilter">
{{ $t('table.search') }}
</el-button>
</div>
<el-table :key='tableKey' :data="list" v-loading="listLoading" element-loading-text="给我一点时间" border fit
highlight-current-row
style="width: 100%">
<el-table-column align="center" :label="$t('table.protocalName')" width="150">
<template slot-scope="scope">
<span>{{ scope.row.name }}</span>
</template>
</el-table-column>
<el-table-column class-name="status-col" :label="$t('table.supportStatus')" width="150">
<template slot-scope="scope">
<el-tag :type="scope.row.enable | statusFilter">{{scope.row.enable | statusName}}</el-tag>
</template>
</el-table-column>
<el-table-column align="left" :label="$t('table.remark')" width="500">
<template slot-scope="scope">
<span>{{ scope.row.remark }}</span>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange"
:current-pageInfo.sync="listQuery.current"
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.size"
layout="total, sizes, prev, pager, next, jumper" :total="total">
</el-pagination>
</div>
</div>
</template>
<script>
import { fetchList } from '@/api/protocal'
import waves from '@/directive/waves' // 水波纹指令
import { parseTime } from '@/utils'
import ButtonPopover from '../../components/Button/buttonPopover'
const calendarTypeOptions = [
{ key: 'CN', display_name: 'China' },
{ key: 'US', display_name: 'USA' },
{ key: 'JP', display_name: 'Japan' },
{ key: 'EU', display_name: 'Eurozone' }
]
// arr to obj ,such as { CN : "China", US : "USA" }
const calendarTypeKeyValue = calendarTypeOptions.reduce((acc, cur) => {
acc[cur.key] = cur.display_name
return acc
}, {})
export default {
name: 'complexTable',
directives: {
waves
},
components: {
ButtonPopover
},
data() {
return {
tableKey: 0,
list: null,
total: null,
listLoading: true,
listQuery: {
current: 1,
size: 10,
importance: undefined,
title: undefined,
type: undefined
},
importanceOptions: [1, 2, 3],
calendarTypeOptions,
sortOptions: [{ label: 'ID Ascending', key: '+id' }, { label: 'ID Descending', key: '-id' }],
statusOptions: ['published', 'draft', 'deleted'],
showReviewer: false,
possessorTypeList: [
{
'id': 1,
'name': '用户'
},
{
'id': 2,
'name': '通道'
}
],
temp: {
name: '',
possessorType: 1,
possessorId: 1
},
dialogFormVisible: false,
dialogStatus: '',
textMap: {
update: '编辑',
create: '新建'
},
dialogPvVisible: false,
pvData: [],
rules: {
port: [{ required: true, message: '端口必填', trigger: 'blur' }]
},
downloadLoading: false
}
},
filters: {
statusName(status) {
const statusMap = {
true: '支持',
false: '不支持'
}
return statusMap[status]
},
statusFilter(status) {
const statusMap = {
true: 'success',
false: 'danger'
}
return statusMap[status]
},
typeFilter(type) {
return calendarTypeKeyValue[type]
}
},
created() {
this.getList()
},
methods: {
getList() {
this.listLoading = true
fetchList(this.listQuery).then(response => {
this.list = response.data.data.records
this.total = response.data.data.total
this.listLoading = false
})
},
handleFilter() {
this.listQuery.current = 1
this.getList()
},
handleSizeChange(val) {
this.listQuery.size = val
this.getList()
},
handleCurrentChange(val) {
this.listQuery.current = val
this.getList()
},
formatJson(filterVal, jsonData) {
return jsonData.map(v => filterVal.map(j => {
if (j === 'timestamp') {
return parseTime(v[j])
} else {
return v[j]
}
}))
}
}
}
</script>
+13
View File
@@ -0,0 +1,13 @@
FROM openjdk:8-jdk-alpine
#同步时间
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 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
#VOLUME ["/root/neutrino-proxy"]
ENTRYPOINT ["java","-jar","neutrino-proxy-client.jar","config=./config/app.yml"]
#docker run -it -d --restart=always --name np_client -e SERVER_IP=127.0.0.1
+7 -51
View File
@@ -4,7 +4,7 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>neutrino-proxy</artifactId>
<groupId>fun.asgc.neutrino</groupId>
<groupId>org.dromara.neutrino-proxy</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -14,13 +14,8 @@
<artifactId>neutrino-proxy-client</artifactId>
<dependencies>
<!-- <dependency>-->
<!-- <groupId>fun.asgc.neutrino</groupId>-->
<!-- <artifactId>neutrino-core</artifactId>-->
<!-- <version>${revision}</version>-->
<!-- </dependency>-->
<dependency>
<groupId>fun.asgc.neutrino</groupId>
<groupId>org.dromara.neutrino-proxy</groupId>
<artifactId>neutrino-proxy-core</artifactId>
<version>${revision}</version>
</dependency>
@@ -30,62 +25,23 @@
</dependency>
</dependencies>
<!-- <build>-->
<!-- <finalName>${artifactId}</finalName>-->
<!-- <plugins>-->
<!-- <plugin>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-maven-plugin</artifactId>-->
<!-- <version>2.1.3.RELEASE</version>-->
<!-- <configuration>-->
<!-- <mainClass>fun.asgc.neutrino.proxy.client.ProxyClient</mainClass>-->
<!-- </configuration>-->
<!-- <executions>-->
<!-- <execution>-->
<!-- <goals>-->
<!-- <goal>repackage</goal>-->
<!-- </goals>-->
<!-- </execution>-->
<!-- </executions>-->
<!-- </plugin>-->
<!-- </plugins>-->
<!-- </build>-->
<build>
<finalName>${project.artifactId}</finalName>
<resources>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>app-*.yml</exclude>
<exclude>app-dev.yml</exclude>
</excludes>
</resource>
</resources>
<plugins>
<!-- 配置打包插件(并打包成胖包) -->
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<finalName>${artifactId}</finalName>
<archive>
<manifest>
<!--这里指定要运行的main类-->
<mainClass>fun.asgc.neutrino.proxy.client.ProxyClient</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
<groupId>org.noear</groupId>
<artifactId>solon-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
@@ -1,37 +0,0 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.client.config;
import lombok.Data;
/**
* @author: aoshiguchen
* @date: 2022/10/18
*/
@Data
public class CustomConfig {
private String jksPath;
private String serverIp;
private Integer serverPort;
private Boolean sslEnable;
private String licenseKey;
}
@@ -1,55 +0,0 @@
///**
// * Copyright (c) 2022 aoshiguchen
// *
// * Permission is hereby granted, free of charge, to any person obtaining a copy
// * of this software and associated documentation files (the "Software"), to deal
// * in the Software without restriction, including without limitation the rights
// * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// * copies of the Software, and to permit persons to whom the Software is
// * furnished to do so, subject to the following conditions:
// *
// * The above copyright notice and this permission notice shall be included in all
// * copies or substantial portions of the Software.
// *
// * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// * SOFTWARE.
// */
//package fun.asgc.neutrino.proxy.client.core;
//
//import fun.asgc.neutrino.core.annotation.Autowired;
//import fun.asgc.neutrino.core.annotation.Component;
//import fun.asgc.neutrino.core.annotation.NonIntercept;
//import fun.asgc.neutrino.core.annotation.Subscribe;
//import fun.asgc.neutrino.core.base.event.ApplicationEvent;
//import fun.asgc.neutrino.core.base.event.ApplicationEventReceiver;
//import fun.asgc.neutrino.core.constant.AppLifeCycleStatusEnum;
//import fun.asgc.neutrino.core.constant.MetaDataConstant;
//import lombok.extern.slf4j.Slf4j;
//
///**
// * 应用生命周期事件监听器
// * @author: aoshiguchen
// * @date: 2022/10/10
// */
//@Slf4j
//@NonIntercept
//@Component
//@Subscribe(topic = MetaDataConstant.TOPIC_APP_LIFE_CYCLE)
//public class ApplicationLifeCycleListener extends ApplicationEventReceiver<AppLifeCycleStatusEnum> {
// @Autowired
// private LicenseObtainService licenseObtainService;
//
// @Override
// public void receive(ApplicationEvent<AppLifeCycleStatusEnum> msg) {
// log.debug("ApplicationLifeCycleListener:{}", msg.data().getDesc());
// if (msg.data() == AppLifeCycleStatusEnum.APP_STARTUP) {
// licenseObtainService.start();
// }
// }
//
//}
@@ -1,166 +0,0 @@
///**
// * Copyright (c) 2022 aoshiguchen
// *
// * Permission is hereby granted, free of charge, to any person obtaining a copy
// * of this software and associated documentation files (the "Software"), to deal
// * in the Software without restriction, including without limitation the rights
// * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// * copies of the Software, and to permit persons to whom the Software is
// * furnished to do so, subject to the following conditions:
// *
// * The above copyright notice and this permission notice shall be included in all
// * copies or substantial portions of the Software.
// *
// * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// * SOFTWARE.
// */
//package fun.asgc.neutrino.proxy.client.core;
//
//import com.alibaba.fastjson.JSONObject;
//import fun.asgc.neutrino.core.base.CustomThreadFactory;
//import fun.asgc.neutrino.core.util.ArrayUtil;
//import fun.asgc.neutrino.core.util.FileUtil;
//import fun.asgc.neutrino.core.util.StringUtil;
//import fun.asgc.neutrino.proxy.client.config.CustomConfig;
//import fun.asgc.neutrino.proxy.client.config.ProxyConfig;
//import lombok.extern.slf4j.Slf4j;
//import org.noear.solon.annotation.Component;
//import org.noear.solon.annotation.Inject;
//
//import java.util.HashMap;
//import java.util.Map;
//import java.util.Scanner;
//import java.util.concurrent.Executors;
//import java.util.concurrent.ScheduledExecutorService;
//import java.util.concurrent.TimeUnit;
//import java.util.concurrent.locks.ReentrantLock;
//
///**
// * license获取服务
// * @author: aoshiguchen
// * @date: 2022/9/4
// */
//@Slf4j
//@Component
//public class LicenseObtainService {
// /**
// * 调度器
// */
// private static final ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor(new CustomThreadFactory("LicenseObtain"));
// private ReentrantLock runLock = new ReentrantLock();
// private Scanner scanner = new Scanner(System.in);
// @Inject
// private ProxyConfig proxyConfig;
// @Inject
// private ProxyClientService proxyClientService;
//
// public void start() {
// scheduledExecutor.scheduleWithFixedDelay(() -> {
// boolean lock = runLock.tryLock();
// try {
// if (lock) {
// this.process();
// }
// } finally {
// if (runLock.isHeldByCurrentThread()){
// runLock.unlock();
// }
// }
// }, 0, proxyConfig.getClient().getObtainLicenseInterval(), TimeUnit.SECONDS);
// }
//
// public void stop() {
// scheduledExecutor.shutdown();
// log.info("licenseKey获取任务停止");
// }
//
// public void process() {
//
//
// proxyClientService.start();
// }
//
// private String
//
// private CustomConfig getCustomConfigByCliParams() {
// CustomConfig customConfig = new CustomConfig();
// // 默认无需输入
// customConfig.setJksPath(environment.getMainArgsForString("jksPath", proxyConfig.getClient().getJksPath()));
// customConfig.setServerIp(environment.getMainArgsForString("serverIp", proxyConfig.getClient().getServerIp()));
// customConfig.setServerPort(environment.getMainArgsForInteger("serverPort", proxyConfig.getClient().getServerPort()));
// customConfig.setSslEnable(environment.getMainArgsForBoolean("sslEnable", proxyConfig.getClient().getSslEnable()));
// customConfig.setLicenseKey(environment.getMainArgsForString("licenseKey"));
// // 启动参数指定了license,则无需后续处理
// if (StringUtil.notEmpty(customConfig.getLicenseKey())) {
//// FileUtil.write("./.neutrino-proxy-client.json", JSONObject.toJSONString(customConfig, SerializerFeature.PrettyFormat));
// return customConfig;
// }
//
// // 启动参数未指定license,则优先读取外部配置
// String config = FileUtil.readContentAsString("./.neutrino-proxy-client.json");
// if (StringUtil.notEmpty(config)) {
// try {
// CustomConfig tmp = JSONObject.parseObject(config, CustomConfig.class);
// if (!StringUtil.isEmpty(tmp.getJksPath())) {
// customConfig.setJksPath(tmp.getJksPath());
// }
// if (!StringUtil.isEmpty(tmp.getServerIp())) {
// customConfig.setServerIp(tmp.getServerIp());
// }
// if (null != tmp.getServerPort()) {
// customConfig.setServerPort(tmp.getServerPort());
// }
// if (null != tmp.getSslEnable()) {
// customConfig.setSslEnable(tmp.getSslEnable());
// }
// if (!StringUtil.isEmpty(tmp.getLicenseKey())) {
// customConfig.setLicenseKey(tmp.getLicenseKey());
// }
// } catch (Exception e) {
// log.error("file '.neutrino-proxy-client.json' config exception!", e);
// }
// if (StringUtil.notEmpty(customConfig.getLicenseKey())) {
//// FileUtil.write("./.neutrino-proxy-client.json", JSONObject.toJSONString(customConfig, SerializerFeature.PrettyFormat));
// return customConfig;
// }
// }
//
// String license = "";
// while (StringUtil.isEmpty(license)) {
// System.out.print("Please input license:");
// license = scanner.next();
// }
// customConfig.setLicenseKey(license);
//// FileUtil.write("./.neutrino-proxy-client.json", JSONObject.toJSONString(customConfig, SerializerFeature.PrettyFormat));
//
// return customConfig;
// }
//
// /**
// * 获取命令行参数
// * @param args
// * @return
// */
// private Map<String, String> getCliParams(String[] args) {
// Map<String, String> res = new HashMap<>();
// if (ArrayUtil.notEmpty(args)) {
// for (String item : args) {
// if (StringUtil.isEmpty(item) || !item.contains("=")) {
// continue;
// }
// int index = item.indexOf("=");
// if (index <= 0 || index == item.length() - 1) {
// continue;
// }
// res.put(item.substring(0, index), item.substring(index + 1));
// }
// }
//
// return res;
// }
//}
@@ -1,28 +0,0 @@
package fun.asgc.neutrino.proxy.client.handler;
import com.alibaba.fastjson.JSONObject;
import fun.asgc.neutrino.proxy.core.Constants;
import fun.asgc.neutrino.proxy.core.ProxyMessage;
import fun.asgc.neutrino.proxy.core.ProxyMessageHandler;
import fun.asgc.neutrino.proxy.core.dispatcher.Match;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.annotation.Component;
/**
* 认证信息处理器
* @author: aoshiguchen
* @date: 2022/9/4
*/
@Slf4j
@Match(type = Constants.ProxyDataTypeName.AUTH)
@Component
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);
}
}
@@ -1,15 +1,22 @@
package fun.asgc.neutrino.proxy.client;
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.annotation.SolonMain;
import org.slf4j.LoggerFactory;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Slf4j
@SolonMain
public class ProxyClient {
@@ -21,6 +28,8 @@ public class ProxyClient {
setAlias("neutrino.proxy.client.jksPath", "jksPath");
setAlias("neutrino.proxy.client.keyStorePassword", "keyStorePassword");
setAlias("neutrino.proxy.client.licenseKey", "licenseKey");
// 设置日志级别
setLogLevel(app);
});
}
@@ -35,4 +44,21 @@ public class ProxyClient {
Solon.cfg().put(key, val);
}
}
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);
}
}
}
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.client.config;
package org.dromara.neutrinoproxy.client.config;
import lombok.Data;
import org.noear.solon.annotation.Component;
@@ -39,5 +39,15 @@ public class ProxyConfig {
private Integer obtainLicenseInterval;
private String licenseKey;
private Integer threadCount;
private String clientId;
private Boolean transferLogEnable;
private Boolean heartbeatLogEnable;
private Reconnection reconnection;
}
@Data
public static class Reconnection {
private Integer intervalSeconds;
private Boolean unlimited;
}
}
@@ -1,13 +1,12 @@
package fun.asgc.neutrino.proxy.client.config;
package org.dromara.neutrinoproxy.client.config;
import fun.asgc.neutrino.proxy.core.ProxyDataTypeEnum;
import fun.asgc.neutrino.proxy.core.ProxyMessage;
import fun.asgc.neutrino.proxy.core.ProxyMessageHandler;
import fun.asgc.neutrino.proxy.core.dispatcher.DefaultDispatcher;
import fun.asgc.neutrino.proxy.core.dispatcher.Dispatcher;
import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.dromara.neutrinoproxy.core.ProxyMessageHandler;
import org.dromara.neutrinoproxy.core.dispatcher.DefaultDispatcher;
import org.dromara.neutrinoproxy.core.dispatcher.Dispatcher;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.nio.NioEventLoopGroup;
import org.noear.solon.Solon;
import org.noear.solon.annotation.Bean;
import org.noear.solon.annotation.Configuration;
@@ -32,8 +31,13 @@ public class ProxyConfiguration implements LifecycleBean {
Solon.context().wrapAndPut(Dispatcher.class, dispatcher);
}
@Bean("bootstrap")
public Bootstrap bootstrap() {
@Bean("cmdTunnelBootstrap")
public Bootstrap cmdTunnelBootstrap() {
return new Bootstrap();
}
@Bean("proxyTunnelBootstrap")
public Bootstrap proxyTunnelBootstrap() {
return new Bootstrap();
}
@@ -0,0 +1,85 @@
package org.dromara.neutrinoproxy.client.core;
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.dispatcher.Dispatcher;
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.noear.solon.Solon;
/**
* 处理与服务端之间的数据传输
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Slf4j
public class CmdChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
private static volatile Boolean transferLogEnable = Boolean.FALSE;
public CmdChannelHandler() {
ProxyConfig proxyConfig = Solon.context().getBean(ProxyConfig.class);
if (null != proxyConfig.getClient() && null != proxyConfig.getClient().getHeartbeatLogEnable()) {
transferLogEnable = proxyConfig.getClient().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());
}
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 {
log.info("Client CmdChannel 与服务端断开连接");
ProxyUtil.setCmdChannel(null);
ProxyUtil.clearRealServerChannels();
super.channelInactive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.error("Client CmdChannel 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:
// 读超时,断开连接
log.error("Client CmdChannel 读超时,断开连接");
ctx.channel().close();
break;
case WRITER_IDLE:
ctx.channel().writeAndFlush(ProxyMessage.buildHeartbeatMessage());
break;
case ALL_IDLE:
log.error("Client CmdChannel 读写超时,断开连接");
ctx.close();
break;
}
}
}
}
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.client.core;
package org.dromara.neutrinoproxy.client.core;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
@@ -20,7 +20,7 @@
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.client.core;
package org.dromara.neutrinoproxy.client.core;
import io.netty.channel.Channel;
@@ -1,15 +1,15 @@
package fun.asgc.neutrino.proxy.client.core;
package org.dromara.neutrinoproxy.client.core;
import fun.asgc.neutrino.proxy.client.util.ProxyUtil;
import fun.asgc.neutrino.proxy.core.Constants;
import fun.asgc.neutrino.proxy.core.ProxyMessage;
import fun.asgc.neutrino.proxy.core.dispatcher.Dispatcher;
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;
/**
@@ -18,13 +18,13 @@ import org.noear.solon.Solon;
* @date: 2022/6/16
*/
@Slf4j
public class ClientChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
public class ProxyChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception {
if (ProxyMessage.TYPE_HEARTBEAT != proxyMessage.getType()) {
log.info("recieved proxy message, type is {}", proxyMessage.getType());
log.debug("Client ProxyChannel recieved proxy message, type is {}", proxyMessage.getType());
}
Solon.context().getBean(Dispatcher.class).dispatch(ctx, proxyMessage);
}
@@ -41,17 +41,10 @@ public class ClientChannelHandler extends SimpleChannelInboundHandler<ProxyMessa
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// 控制连接
if (ProxyUtil.getCmdChannel() == ctx.channel()) {
log.info("与服务端断开连接");
ProxyUtil.setCmdChannel(null);
ProxyUtil.clearRealServerChannels();
} else {
// 数据传输连接
Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (realServerChannel != null && realServerChannel.isActive()) {
realServerChannel.close();
}
// 数据传输连接
Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (realServerChannel != null && realServerChannel.isActive()) {
realServerChannel.close();
}
ProxyUtil.removeProxyChanel(ctx.channel());
@@ -60,8 +53,8 @@ public class ClientChannelHandler extends SimpleChannelInboundHandler<ProxyMessa
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
cause.printStackTrace();
log.error("Client ProxyChannel Error channelId:{}", ctx.channel().id().asLongText(), cause);
ctx.close();
}
@Override
@@ -71,13 +64,15 @@ public class ClientChannelHandler extends SimpleChannelInboundHandler<ProxyMessa
switch (event.state()) {
case READER_IDLE:
// 读超时断开连接
log.info("读超时");
ctx.channel().close();
// log.info("读超时");
// ctx.channel().close();
break;
case WRITER_IDLE:
ctx.channel().writeAndFlush(ProxyMessage.buildHeartbeatMessage());
break;
case ALL_IDLE:
log.debug("读写超时");
ctx.close();
break;
}
}
@@ -1,12 +1,13 @@
package fun.asgc.neutrino.proxy.client.core;
package org.dromara.neutrinoproxy.client.core;
import cn.hutool.core.util.StrUtil;
import fun.asgc.neutrino.proxy.client.config.ProxyConfig;
import fun.asgc.neutrino.proxy.client.util.ProxyUtil;
import fun.asgc.neutrino.proxy.core.ProxyMessage;
import fun.asgc.neutrino.proxy.core.ProxyMessageDecoder;
import fun.asgc.neutrino.proxy.core.ProxyMessageEncoder;
import fun.asgc.neutrino.proxy.core.util.FileUtil;
import io.netty.handler.logging.LoggingHandler;
import org.dromara.neutrinoproxy.client.config.ProxyConfig;
import org.dromara.neutrinoproxy.client.util.ProxyUtil;
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;
@@ -25,13 +26,14 @@ 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;
/**
* 客户端服务
* 代理客户端服务
* @author: aoshiguchen
* @date: 2022/6/16
*/
@@ -40,23 +42,26 @@ import java.util.concurrent.TimeUnit;
public class ProxyClientService {
@Inject
private ProxyConfig proxyConfig;
@Inject("bootstrap")
private Bootstrap bootstrap;
@Inject("cmdTunnelBootstrap")
private Bootstrap cmdTunnelBootstrap;
@Inject("proxyTunnelBootstrap")
private Bootstrap proxyTunnelBootstrap;
@Inject("realServerBootstrap")
private Bootstrap realServerBootstrap;
private volatile Channel channel;
/**
* 重连间隔
*/
private static final long RECONNECT_INTERVAL_SECONDS = 5;
// /**
// * 重连间隔
// */
// private static final long RECONNECT_INTERVAL_SECONDS = 5;
/**
* 重连次数
*/
private volatile int reconnectCount = 0;
/**
* 启用重连服务
*/
private volatile boolean reconnectServiceEnable = false;
// /**
// * 启用重连服务
// */
// private volatile boolean reconnectServiceEnable = false;
private NioEventLoopGroup workerGroup;
/**
* 重连服务执行器
*/
@@ -64,38 +69,80 @@ public class ProxyClientService {
@Init
public void init() {
this.reconnectExecutor.scheduleWithFixedDelay(this::reconnect, 0, RECONNECT_INTERVAL_SECONDS, TimeUnit.SECONDS);
this.reconnectExecutor.scheduleWithFixedDelay(this::reconnect, 10, proxyConfig.getClient().getReconnection().getIntervalSeconds(), TimeUnit.SECONDS);
this.workerGroup = new NioEventLoopGroup(proxyConfig.getClient().getThreadCount());
NioEventLoopGroup 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());
}
});
bootstrap.group(workerGroup);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
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 ClientChannelHandler());
ch.pipeline().addLast(new ProxyChannelHandler());
}
});
this.start();
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());
}
});
try {
this.start();
} catch (Exception e) {
// 启动连不上也做一下重连因此先catch异常
log.error("[客户端指令隧道] 启动异常", e);
}
}
public void start() {
@@ -110,7 +157,7 @@ public class ProxyClientService {
return;
}
if (null != proxyConfig.getClient().getSslEnable() && proxyConfig.getClient().getSslEnable()
&& StrUtil.isEmpty(proxyConfig.getClient().getJksPath())) {
&& StrUtil.isEmpty(proxyConfig.getClient().getJksPath())) {
log.error("not found jks-path config.");
Solon.stop();
return;
@@ -127,7 +174,7 @@ public class ProxyClientService {
log.error("client start error", e);
}
} else {
channel.writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getClient().getLicenseKey()));
channel.writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getClient().getLicenseKey(), ProxyUtil.getClientId()));
}
}
@@ -135,7 +182,7 @@ public class ProxyClientService {
* 连接代理服务器
*/
private void connectProxyServer() throws InterruptedException {
bootstrap.connect(proxyConfig.getClient().getServerIp(), proxyConfig.getClient().getServerPort())
cmdTunnelBootstrap.connect()
.addListener(new ChannelFutureListener() {
@Override
@@ -144,13 +191,13 @@ public class ProxyClientService {
channel = future.channel();
// 连接成功向服务器发送客户端认证信息licenseKey
ProxyUtil.setCmdChannel(future.channel());
future.channel().writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getClient().getLicenseKey()));
log.info("连接代理服务成功. channelId:{}", future.channel().id().asLongText());
future.channel().writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getClient().getLicenseKey(), ProxyUtil.getClientId()));
log.info("[客户端指令隧道] 连接代理服务成功. channelId:{}", future.channel().id().asLongText());
reconnectServiceEnable = true;
// reconnectServiceEnable = true;
reconnectCount = 0;
} else {
log.info("连接代理服务失败!");
log.info("[客户端指令隧道] 连接代理服务失败!");
}
}
}).sync();
@@ -180,17 +227,21 @@ public class ProxyClientService {
}
protected synchronized void reconnect() {
if (!reconnectServiceEnable) {
return;
// if (!reconnectServiceEnable) {
// return;
// }
if (null != channel) {
if (channel.isActive()) {
return;
}
channel.close();
}
if (null != channel && channel.isActive()) {
return;
}
log.info("客户端重连 seq:{}", ++reconnectCount);
log.info("[客户端指令隧道] 客户端重连 seq:{}", ++reconnectCount);
try {
connectProxyServer();
} catch (Exception e) {
log.error("重连异常", e);
log.error("[客户端指令隧道] 重连异常", e);
}
}
}
@@ -20,11 +20,12 @@
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.client.core;
package org.dromara.neutrinoproxy.client.core;
import fun.asgc.neutrino.proxy.client.util.ProxyUtil;
import fun.asgc.neutrino.proxy.core.Constants;
import fun.asgc.neutrino.proxy.core.ProxyMessage;
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 io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
@@ -36,6 +37,7 @@ import io.netty.channel.SimpleChannelInboundHandler;
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Slf4j
public class RealServerChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
@@ -85,6 +87,6 @@ public class RealServerChannelHandler extends SimpleChannelInboundHandler<ByteBu
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
log.error("Client ProxyChannel Error", cause);
}
}
@@ -0,0 +1,46 @@
package org.dromara.neutrinoproxy.client.handler;
import com.alibaba.fastjson.JSONObject;
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.noear.solon.Solon;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
/**
* 认证信息处理器
* @author: aoshiguchen
* @date: 2022/9/4
*/
@Slf4j
@Match(type = Constants.ProxyDataTypeName.AUTH)
@Component
public class ProxyMessageAuthHandler implements ProxyMessageHandler {
@Inject
private ProxyConfig proxyConfig;
@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);
if (ExceptionEnum.AUTH_FAILED.getCode().equals(code)) {
// 客户端认证失败,直接停止服务
log.info("client auth failed , client stop.");
context.channel().close();
if (!proxyConfig.getClient().getReconnection().getUnlimited()) {
Solon.stop();
}
} else if (ExceptionEnum.CONNECT_FAILED.getCode().equals(code) ||
ExceptionEnum.LICENSE_CANNOT_REPEAT_CONNECT.getCode().equals(code)
){
context.channel().close();
}
}
}
@@ -1,13 +1,13 @@
package fun.asgc.neutrino.proxy.client.handler;
package org.dromara.neutrinoproxy.client.handler;
import fun.asgc.neutrino.proxy.client.config.ProxyConfig;
import fun.asgc.neutrino.proxy.client.core.ProxyChannelBorrowListener;
import fun.asgc.neutrino.proxy.client.util.ProxyUtil;
import fun.asgc.neutrino.proxy.core.Constants;
import fun.asgc.neutrino.proxy.core.ProxyDataTypeEnum;
import fun.asgc.neutrino.proxy.core.ProxyMessage;
import fun.asgc.neutrino.proxy.core.ProxyMessageHandler;
import fun.asgc.neutrino.proxy.core.dispatcher.Match;
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 io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import org.noear.solon.annotation.Component;
@@ -21,8 +21,8 @@ import org.noear.solon.annotation.Inject;
@Match(type = Constants.ProxyDataTypeName.CONNECT)
@Component
public class ProxyMessageConnectHandler implements ProxyMessageHandler {
@Inject("bootstrap")
private Bootstrap bootstrap;
@Inject("proxyTunnelBootstrap")
private Bootstrap proxyTunnelBootstrap;
@Inject("realServerBootstrap")
private Bootstrap realServerBootstrap;
@Inject
@@ -48,7 +48,7 @@ public class ProxyMessageConnectHandler implements ProxyMessageHandler {
realServerChannel.config().setOption(ChannelOption.AUTO_READ, false);
// 获取连接
ProxyUtil.borrowProxyChanel(bootstrap, new ProxyChannelBorrowListener() {
ProxyUtil.borrowProxyChanel(proxyTunnelBootstrap, new ProxyChannelBorrowListener() {
@Override
public void success(Channel channel) {
@@ -1,11 +1,11 @@
package fun.asgc.neutrino.proxy.client.handler;
package org.dromara.neutrinoproxy.client.handler;
import fun.asgc.neutrino.proxy.client.util.ProxyUtil;
import fun.asgc.neutrino.proxy.core.Constants;
import fun.asgc.neutrino.proxy.core.ProxyDataTypeEnum;
import fun.asgc.neutrino.proxy.core.ProxyMessage;
import fun.asgc.neutrino.proxy.core.ProxyMessageHandler;
import fun.asgc.neutrino.proxy.core.dispatcher.Match;
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 io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
@@ -1,8 +1,9 @@
package fun.asgc.neutrino.proxy.client.handler;
package org.dromara.neutrinoproxy.client.handler;
import com.alibaba.fastjson.JSONObject;
import fun.asgc.neutrino.proxy.core.*;
import fun.asgc.neutrino.proxy.core.dispatcher.Match;
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.noear.solon.annotation.Component;
@@ -1,10 +1,10 @@
package fun.asgc.neutrino.proxy.client.handler;
package org.dromara.neutrinoproxy.client.handler;
import fun.asgc.neutrino.proxy.core.Constants;
import fun.asgc.neutrino.proxy.core.ProxyDataTypeEnum;
import fun.asgc.neutrino.proxy.core.ProxyMessage;
import fun.asgc.neutrino.proxy.core.ProxyMessageHandler;
import fun.asgc.neutrino.proxy.core.dispatcher.Match;
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 io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
@@ -19,22 +19,24 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.client.util;
package org.dromara.neutrinoproxy.client.util;
import fun.asgc.neutrino.proxy.client.config.ProxyConfig;
import fun.asgc.neutrino.proxy.client.core.ProxyChannelBorrowListener;
import fun.asgc.neutrino.proxy.core.Constants;
import org.apache.commons.lang3.StringUtils;
import org.dromara.neutrinoproxy.client.config.ProxyConfig;
import org.dromara.neutrinoproxy.client.core.ProxyChannelBorrowListener;
import org.dromara.neutrinoproxy.core.Constants;
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 io.netty.channel.ChannelOption;
import io.netty.util.AttributeKey;
import org.dromara.neutrinoproxy.core.util.FileUtil;
import org.noear.solon.Solon;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
@@ -56,16 +58,17 @@ public class ProxyUtil {
private static volatile Channel cmdChannel;
public static void borrowProxyChanel(Bootstrap bootstrap, final ProxyChannelBorrowListener borrowListener) {
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();
if (null != channel) {
borrowListener.success(channel);
return;
}
String serverIp = Solon.cfg().get("neutrino.proxy.client.server-ip");
Integer serverPort = Solon.cfg().getInt("neutrino.proxy.client.server-port", 9000);
bootstrap.connect(serverIp, serverPort).addListener((ChannelFutureListener) future -> {
proxyTunnelBootstrap.connect().addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
borrowListener.success(future.channel());
} else {
@@ -131,4 +134,24 @@ public class ProxyUtil {
realServerChannels.clear();
}
public static String getClientId() {
if (StringUtils.isNotBlank(clientId)) {
return clientId;
}
ProxyConfig proxyConfig = Solon.context().getBean(ProxyConfig.class);
if (StringUtils.isNotBlank(proxyConfig.getClient().getClientId())) {
clientId = proxyConfig.getClient().getClientId();
return clientId;
}
String id = FileUtil.readContentAsString(CLIENT_ID_FILE);
if (StringUtils.isNotBlank(id)) {
clientId = id;
return id;
}
id = UUID.randomUUID().toString().replace("-", "");
FileUtil.write(CLIENT_ID_FILE, id);
clientId = id;
return id;
}
}
@@ -9,15 +9,35 @@ neutrino:
length-field-length: 4
initial-bytes-to-strip: 0
length-adjustment: 0
read-idle-time: 40
write-idle-time: 8
all-idle-time-seconds: 0
read-idle-time: 8
write-idle-time: 3
all-idle-time-seconds: 8
logger:
# 日志级别
level: ${LOG_LEVEL:info}
client:
# 线程池相关配置,用于技术调优,可忽略
thread-count: 50
key-store-password: 123456
jks-path: classpath:/test.jks
server-ip: localhost
server-port: 9002
ssl-enable: true
obtain-license-interval: 5
license-key:
# 隧道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
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="LOG_FILE" value="/work/projects/neutrino-proxy-client/app.log"/>
<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" />
<!-- <property name="ENCODE" value="utf8" />-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
@@ -10,7 +10,7 @@
</filter>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>${ENCODE}</charset>
<!-- <charset>${ENCODE}</charset>-->
</encoder>
</appender>
@@ -22,7 +22,7 @@
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>${ENCODE}</charset>
<!-- <charset>${ENCODE}</charset>-->
</encoder>
</appender>
+2 -2
View File
@@ -4,7 +4,7 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>neutrino-proxy</artifactId>
<groupId>fun.asgc.neutrino</groupId>
<groupId>org.dromara.neutrino-proxy</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -21,7 +21,7 @@
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>${hutool.ver}</version>
<version>${hutool.version}</version>
</dependency>
<!--lombok-->
<dependency>
@@ -19,10 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.core;
import io.netty.channel.Channel;
import io.netty.util.AttributeKey;
package org.dromara.neutrinoproxy.core;
import java.util.HashMap;
@@ -20,7 +20,7 @@
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.core;
package org.dromara.neutrinoproxy.core;
import io.netty.channel.Channel;
import io.netty.util.AttributeKey;
@@ -20,7 +20,7 @@
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.core;
package org.dromara.neutrinoproxy.core;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -35,7 +35,10 @@ import lombok.Getter;
public enum ExceptionEnum {
SUCCESS(0, "成功"),
AUTH_FAILED(1, "认证失败"),
CONNECT_FAILED(2, "连接失败");
CONNECT_FAILED(2, "连接失败"),
@Deprecated
LICENSE_CANNOT_REPEAT_CONNECT(3, "license不能多个客户端同时使用"),
;
private Integer code;
private String msg;
@@ -20,7 +20,7 @@
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.core;
package org.dromara.neutrinoproxy.core;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.timeout.IdleStateEvent;
@@ -20,7 +20,7 @@
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.core;
package org.dromara.neutrinoproxy.core;
import lombok.Data;
@@ -20,7 +20,7 @@
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.core;
package org.dromara.neutrinoproxy.core;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -20,7 +20,7 @@
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.core;
package org.dromara.neutrinoproxy.core;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
@@ -100,9 +100,9 @@ public class ProxyMessage {
return create().setType(TYPE_HEARTBEAT);
}
public static ProxyMessage buildAuthMessage(String info) {
public static ProxyMessage buildAuthMessage(String info, String clientId) {
return create().setType(TYPE_AUTH)
.setInfo(info);
.setInfo(info + "," + clientId);
}
public static ProxyMessage buildAuthResultMessage(Integer code, String msg, String licenseKey) {
@@ -20,12 +20,12 @@
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.core;
package org.dromara.neutrinoproxy.core;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import static fun.asgc.neutrino.proxy.core.Constants.*;
import static org.dromara.neutrinoproxy.core.Constants.*;
/**
*
@@ -20,12 +20,12 @@
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.core;
package org.dromara.neutrinoproxy.core;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import static fun.asgc.neutrino.proxy.core.Constants.*;
import static org.dromara.neutrinoproxy.core.Constants.*;
/**
*
@@ -1,6 +1,6 @@
package fun.asgc.neutrino.proxy.core;
package org.dromara.neutrinoproxy.core;
import fun.asgc.neutrino.proxy.core.dispatcher.Handler;
import org.dromara.neutrinoproxy.core.dispatcher.Handler;
import io.netty.channel.ChannelHandlerContext;
/**
@@ -20,7 +20,7 @@
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.core.base;
package org.dromara.neutrinoproxy.core.base;
/**
*
@@ -1,6 +1,6 @@
package fun.asgc.neutrino.proxy.core.base;
package org.dromara.neutrinoproxy.core.base;
/**
*
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.core.base;
package org.dromara.neutrinoproxy.core.base;
/**
*
@@ -1,10 +1,10 @@
package fun.asgc.neutrino.proxy.core.dispatcher;
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 fun.asgc.neutrino.proxy.core.util.TypeUtil;
import org.dromara.neutrinoproxy.core.util.TypeUtil;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
@@ -20,7 +20,7 @@
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.core.dispatcher;
package org.dromara.neutrinoproxy.core.dispatcher;
/**
*
@@ -20,7 +20,7 @@
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.core.dispatcher;
package org.dromara.neutrinoproxy.core.dispatcher;
/**
*
@@ -20,7 +20,7 @@
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.core.dispatcher;
package org.dromara.neutrinoproxy.core.dispatcher;
import java.lang.annotation.*;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.core.type;
package org.dromara.neutrinoproxy.core.type;
import java.util.ArrayList;
import java.util.List;
@@ -1,6 +1,6 @@
package fun.asgc.neutrino.proxy.core.type;
package org.dromara.neutrinoproxy.core.type;
import fun.asgc.neutrino.proxy.core.util.TypeUtil;
import org.dromara.neutrinoproxy.core.util.TypeUtil;
/**
* 默认的类型匹配器组
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.core.type;
package org.dromara.neutrinoproxy.core.type;
/**
* 类型转换器
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.core.type;
package org.dromara.neutrinoproxy.core.type;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.core.type;
package org.dromara.neutrinoproxy.core.type;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -1,5 +1,5 @@
package fun.asgc.neutrino.proxy.core.type;
package org.dromara.neutrinoproxy.core.type;
/**
* 值类型匹配器
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.core.type;
package org.dromara.neutrinoproxy.core.type;
import java.util.List;
@@ -1,29 +1,9 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.core.type;
package org.dromara.neutrinoproxy.core.type;
import cn.hutool.core.collection.CollectionUtil;
import fun.asgc.neutrino.proxy.core.type.extension.*;
import fun.asgc.neutrino.proxy.core.util.Assert;
import org.dromara.neutrinoproxy.core.type.extension.*;
import org.dromara.neutrinoproxy.core.util.Assert;
import org.dromara.neutrinoproxy.core.type.extension.*;
import java.util.ArrayList;
import java.util.Comparator;
@@ -1,8 +1,8 @@
package fun.asgc.neutrino.proxy.core.type.extension;
package org.dromara.neutrinoproxy.core.type.extension;
import fun.asgc.neutrino.proxy.core.type.AbstractMatcherGroup;
import fun.asgc.neutrino.proxy.core.type.TypeMatchInfo;
import fun.asgc.neutrino.proxy.core.type.TypeMatcher;
import org.dromara.neutrinoproxy.core.type.AbstractMatcherGroup;
import org.dromara.neutrinoproxy.core.type.TypeMatchInfo;
import org.dromara.neutrinoproxy.core.type.TypeMatcher;
import java.util.List;
import java.util.Set;
@@ -1,9 +1,9 @@
package fun.asgc.neutrino.proxy.core.type.extension;
package org.dromara.neutrinoproxy.core.type.extension;
import fun.asgc.neutrino.proxy.core.type.AbstractMatcherGroup;
import fun.asgc.neutrino.proxy.core.type.TypeMatchInfo;
import fun.asgc.neutrino.proxy.core.type.TypeMatcher;
import fun.asgc.neutrino.proxy.core.util.TypeUtil;
import org.dromara.neutrinoproxy.core.type.AbstractMatcherGroup;
import org.dromara.neutrinoproxy.core.type.TypeMatchInfo;
import org.dromara.neutrinoproxy.core.type.TypeMatcher;
import org.dromara.neutrinoproxy.core.util.TypeUtil;
/**
* 布尔值匹配器组
@@ -1,9 +1,9 @@
package fun.asgc.neutrino.proxy.core.type.extension;
package org.dromara.neutrinoproxy.core.type.extension;
import fun.asgc.neutrino.proxy.core.type.AbstractMatcherGroup;
import fun.asgc.neutrino.proxy.core.type.TypeMatchInfo;
import fun.asgc.neutrino.proxy.core.type.TypeMatcher;
import fun.asgc.neutrino.proxy.core.util.TypeUtil;
import org.dromara.neutrinoproxy.core.type.AbstractMatcherGroup;
import org.dromara.neutrinoproxy.core.type.TypeMatchInfo;
import org.dromara.neutrinoproxy.core.type.TypeMatcher;
import org.dromara.neutrinoproxy.core.util.TypeUtil;
import java.time.Instant;
import java.time.LocalDateTime;
@@ -1,8 +1,8 @@
package fun.asgc.neutrino.proxy.core.type.extension;
package org.dromara.neutrinoproxy.core.type.extension;
import fun.asgc.neutrino.proxy.core.type.AbstractMatcherGroup;
import fun.asgc.neutrino.proxy.core.type.TypeMatchInfo;
import fun.asgc.neutrino.proxy.core.type.TypeMatcher;
import org.dromara.neutrinoproxy.core.type.AbstractMatcherGroup;
import org.dromara.neutrinoproxy.core.type.TypeMatchInfo;
import org.dromara.neutrinoproxy.core.type.TypeMatcher;
import java.util.List;
import java.util.stream.Collectors;
@@ -1,6 +1,6 @@
package fun.asgc.neutrino.proxy.core.type.extension;
package org.dromara.neutrinoproxy.core.type.extension;
import fun.asgc.neutrino.proxy.core.type.AbstractMatcherGroup;
import org.dromara.neutrinoproxy.core.type.AbstractMatcherGroup;
/**
* Map匹配器组
@@ -1,9 +1,9 @@
package fun.asgc.neutrino.proxy.core.type.extension;
package org.dromara.neutrinoproxy.core.type.extension;
import fun.asgc.neutrino.proxy.core.type.AbstractMatcherGroup;
import fun.asgc.neutrino.proxy.core.type.TypeMatchInfo;
import fun.asgc.neutrino.proxy.core.type.TypeMatcher;
import fun.asgc.neutrino.proxy.core.util.TypeUtil;
import org.dromara.neutrinoproxy.core.type.AbstractMatcherGroup;
import org.dromara.neutrinoproxy.core.type.TypeMatchInfo;
import org.dromara.neutrinoproxy.core.type.TypeMatcher;
import org.dromara.neutrinoproxy.core.util.TypeUtil;
/**
* 字符串匹配器组
@@ -1,8 +1,8 @@
package fun.asgc.neutrino.proxy.core.type.extension;
package org.dromara.neutrinoproxy.core.type.extension;
import fun.asgc.neutrino.proxy.core.type.AbstractMatcherGroup;
import fun.asgc.neutrino.proxy.core.type.TypeMatchInfo;
import fun.asgc.neutrino.proxy.core.type.TypeMatcher;
import org.dromara.neutrinoproxy.core.type.AbstractMatcherGroup;
import org.dromara.neutrinoproxy.core.type.TypeMatchInfo;
import org.dromara.neutrinoproxy.core.type.TypeMatcher;
import java.util.Collection;
import java.util.Set;
@@ -1,9 +1,9 @@
package fun.asgc.neutrino.proxy.core.type.extension;
package org.dromara.neutrinoproxy.core.type.extension;
import fun.asgc.neutrino.proxy.core.type.AbstractMatcherGroup;
import fun.asgc.neutrino.proxy.core.type.TypeMatchInfo;
import fun.asgc.neutrino.proxy.core.type.TypeMatcher;
import fun.asgc.neutrino.proxy.core.util.TypeUtil;
import org.dromara.neutrinoproxy.core.type.AbstractMatcherGroup;
import org.dromara.neutrinoproxy.core.type.TypeMatchInfo;
import org.dromara.neutrinoproxy.core.type.TypeMatcher;
import org.dromara.neutrinoproxy.core.util.TypeUtil;
/**
* 字符串匹配器组
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.core.util;
package org.dromara.neutrinoproxy.core.util;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.core.util;
package org.dromara.neutrinoproxy.core.util;
import org.apache.commons.lang3.StringUtils;
@@ -1,7 +1,7 @@
package fun.asgc.neutrino.proxy.core.util;
package org.dromara.neutrinoproxy.core.util;
import fun.asgc.neutrino.proxy.core.base.InternalException;
import fun.asgc.neutrino.proxy.core.base.MetaDataConstant;
import org.dromara.neutrinoproxy.core.base.InternalException;
import org.dromara.neutrinoproxy.core.base.MetaDataConstant;
import org.apache.commons.lang3.StringUtils;
import java.io.*;
@@ -1,6 +1,6 @@
package fun.asgc.neutrino.proxy.core.util;
package org.dromara.neutrinoproxy.core.util;
import fun.asgc.neutrino.proxy.core.base.CodeBlock;
import org.dromara.neutrinoproxy.core.base.CodeBlock;
import java.util.function.BooleanSupplier;
import java.util.function.Supplier;
@@ -1,11 +1,11 @@
package fun.asgc.neutrino.proxy.core.util;
package org.dromara.neutrinoproxy.core.util;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Pair;
import com.google.common.collect.Lists;
import fun.asgc.neutrino.proxy.core.type.TypeMatchInfo;
import fun.asgc.neutrino.proxy.core.type.TypeMatchers;
import org.dromara.neutrinoproxy.core.type.TypeMatchInfo;
import org.dromara.neutrinoproxy.core.type.TypeMatchers;
import java.lang.reflect.Field;
import java.util.*;
@@ -6,9 +6,9 @@ RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
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 ../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
#VOLUME ["/root/neutrino-proxy"]
ENTRYPOINT ["java","-jar","neutrino-proxy-server.jar","config=./config/app.yml"]
+18
View File
@@ -0,0 +1,18 @@
version: '3.1'
services:
neutrino-proxy11:
container_name: 'np-server'
restart: always
image: registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:1.8.0
ports:
- "9000-9200:9000-9200"
- "8888:8888"
environment:
- TZ=Asia/Shanghai
volumes:
- /root/neutrino-proxy/config:/root/neutrino-proxy/config
privileged: true
+16 -56
View File
@@ -4,7 +4,7 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>neutrino-proxy</artifactId>
<groupId>fun.asgc.neutrino</groupId>
<groupId>org.dromara.neutrino-proxy</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -30,20 +30,16 @@
<groupId>org.noear</groupId>
<artifactId>activerecord-solon-plugin</artifactId>
</dependency>
<!--orika-->
<dependency>
<groupId>fun.asgc</groupId>
<artifactId>orika-solon-plugin</artifactId>
<version>2.2.2</version>
</dependency>
<!--job-->
<dependency>
<groupId>fun.asgc</groupId>
<groupId>org.dromara.solon-plugins</groupId>
<artifactId>job-solon-plugin</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>fun.asgc.neutrino</groupId>
<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>
@@ -55,70 +51,34 @@
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
</dependencies>
<!-- <build>-->
<!-- <finalName>${artifactId}</finalName>-->
<!-- <plugins>-->
<!-- <plugin>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-maven-plugin</artifactId>-->
<!-- <version>2.1.3.RELEASE</version>-->
<!-- <configuration>-->
<!-- <mainClass>fun.asgc.neutrino.proxy.server.ProxyServer</mainClass>-->
<!-- </configuration>-->
<!-- <executions>-->
<!-- <execution>-->
<!-- <goals>-->
<!-- <goal>repackage</goal>-->
<!-- </goals>-->
<!-- </execution>-->
<!-- </executions>-->
<!-- </plugin>-->
<!-- </plugins>-->
<!-- </build>-->
<build>
<finalName>${project.artifactId}</finalName>
<resources>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>app-*.yml</exclude>
<exclude>app-dev.yml</exclude>
</excludes>
</resource>
</resources>
<plugins>
<!-- 配置打包插件(并打包成胖包) -->
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<encoding>${project.build.sourceEncoding}</encoding>
<appendAssemblyId>false</appendAssemblyId>
<finalName>${artifactId}</finalName>
<archive>
<manifest>
<!--这里指定要运行的main类-->
<mainClass>fun.asgc.neutrino.proxy.server.ProxyServer</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
<groupId>org.noear</groupId>
<artifactId>solon-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
@@ -1,23 +0,0 @@
package fun.asgc.neutrino.proxy.server;
import fun.asgc.solon.extend.job.annotation.EnableJob;
import org.noear.solon.Solon;
import org.noear.solon.annotation.SolonMain;
import org.noear.solon.web.cors.CrossFilter;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@EnableJob
@SolonMain
public class ProxyServer {
public static void main(String[] args) {
Solon.start(ProxyServer.class, args, app -> {
// 跨域支持。加-1 优先级更高
app.filter(-1, new CrossFilter().allowedOrigins("*"));
});
}
}
@@ -1,47 +0,0 @@
package fun.asgc.neutrino.proxy.server.base.proxy;
import fun.asgc.neutrino.proxy.core.ProxyDataTypeEnum;
import fun.asgc.neutrino.proxy.core.ProxyMessage;
import fun.asgc.neutrino.proxy.core.ProxyMessageHandler;
import fun.asgc.neutrino.proxy.core.dispatcher.DefaultDispatcher;
import fun.asgc.neutrino.proxy.core.dispatcher.Dispatcher;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.nio.NioEventLoopGroup;
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.AopContext;
import org.noear.solon.core.bean.LifecycleBean;
import java.util.List;
/**
* 代理配置
* @author: aoshiguchen
* @date: 2022/10/8
*/
@Configuration
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());
Solon.context().wrapAndPut(Dispatcher.class, dispatcher);
}
@Bean("serverBossGroup")
public NioEventLoopGroup serverBossGroup(@Inject ProxyConfig proxyConfig) {
return new NioEventLoopGroup(proxyConfig.getServer().getBossThreadCount());
}
@Bean("serverWorkerGroup")
public NioEventLoopGroup serverWorkerGroup(@Inject ProxyConfig proxyConfig) {
return new NioEventLoopGroup(proxyConfig.getServer().getWorkThreadCount());
}
}
@@ -1,81 +0,0 @@
package fun.asgc.neutrino.proxy.server.controller;
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
import fun.asgc.neutrino.proxy.server.controller.req.proxy.*;
import fun.asgc.neutrino.proxy.server.controller.res.proxy.*;
import fun.asgc.neutrino.proxy.server.service.PortMappingService;
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
import org.apache.commons.lang3.StringUtils;
import org.noear.solon.annotation.*;
/**
* 端口映射
* @author: aoshiguchen
* @date: 2022/8/8
*/
@Mapping("/port-mapping")
@Controller
public class PortMappingController {
@Inject
private PortMappingService portMappingService;
@Get
@Mapping("/page")
public PageInfo<PortMappingListRes> page(PageQuery pageQuery, PortMappingListReq req) {
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
return portMappingService.page(pageQuery, req);
}
@Post
@Mapping("/create")
public PortMappingCreateRes create(PortMappingCreateReq req) {
ParamCheckUtil.checkNotNull(req, "req");
ParamCheckUtil.checkNotNull(req.getLicenseId(), "licenseId");
ParamCheckUtil.checkNotNull(req.getServerPort(), "serverPort");
ParamCheckUtil.checkNotNull(req.getClientPort(), "clientPort");
if (StringUtils.isBlank(req.getClientIp())) {
// 没传客户端ip,默认为127.0.0.1
req.setClientIp("127.0.0.1");
}
return portMappingService.create(req);
}
@Post
@Mapping("/update")
public PortMappingUpdateRes update(PortMappingUpdateReq req) {
ParamCheckUtil.checkNotNull(req, "req");
return portMappingService.update(req);
}
@Get
@Mapping("/detail")
public PortMappingDetailRes detail(PortMappingDetailReq req) {
ParamCheckUtil.checkNotNull(req, "req");
ParamCheckUtil.checkNotNull(req.getId(), "id");
return portMappingService.detail(req.getId());
}
@Post
@Mapping("/update/enable-status")
public PortMappingUpdateEnableStatusRes updateEnableStatus(PortMappingUpdateEnableStatusReq req) {
ParamCheckUtil.checkNotNull(req, "req");
ParamCheckUtil.checkNotNull(req.getId(), "id");
ParamCheckUtil.checkNotNull(req.getEnable(), "enable");
return portMappingService.updateEnableStatus(req);
}
@Post
@Mapping("/delete")
public void delete(PortMappingDeleteReq req) {
ParamCheckUtil.checkNotNull(req, "req");
ParamCheckUtil.checkNotNull(req.getId(), "id");
portMappingService.delete(req.getId());
}
}
@@ -1,4 +0,0 @@
package fun.asgc.neutrino.proxy.server.controller.res.system;
public class PortGroupCreateRes {
}
@@ -1,4 +0,0 @@
package fun.asgc.neutrino.proxy.server.controller.res.system;
public class PortPoolUpdateGroupRes {
}
@@ -1,44 +0,0 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.proxy.domain;
import lombok.Data;
import java.io.Serializable;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Data
public class Metrics implements Serializable {
private static final long serialVersionUID = 1L;
private int port;
private long readBytes;
private long wroteBytes;
private long readMsgs;
private long wroteMsgs;
private int channels;
private long timestamp;
}
@@ -1,45 +0,0 @@
package fun.asgc.neutrino.proxy.server.proxy.handler;
import fun.asgc.neutrino.proxy.core.Constants;
import fun.asgc.neutrino.proxy.core.ProxyDataTypeEnum;
import fun.asgc.neutrino.proxy.core.ProxyMessage;
import fun.asgc.neutrino.proxy.core.ProxyMessageHandler;
import fun.asgc.neutrino.proxy.core.dispatcher.Match;
import fun.asgc.neutrino.proxy.server.util.ProxyUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import org.noear.solon.annotation.Component;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Match(type = Constants.ProxyDataTypeName.DISCONNECT)
@Component
public class ProxyMessageDisconnectHandler implements ProxyMessageHandler {
@Override
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
Integer licenseId = ctx.channel().attr(Constants.LICENSE_ID).get();
// licenseId为空,说明访问者通道已经关闭,无需处理
if (null == licenseId) {
return;
}
// 代理连接没有连上服务器由控制连接发送用户端断开连接消息
String visitorId = proxyMessage.getInfo();
Channel userChannel = ProxyUtil.removeVisitorChannelFromCmdChannel(ctx.channel(), visitorId);
if (null != userChannel) {
// 数据发送完成后再关闭连接,解决http1.0数据传输问题
userChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
}
@Override
public String name() {
return ProxyDataTypeEnum.DISCONNECT.getDesc();
}
}
@@ -1,250 +0,0 @@
package fun.asgc.neutrino.proxy.server.service;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.google.common.collect.Sets;
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
import fun.asgc.neutrino.proxy.server.base.rest.SystemContextHolder;
import fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum;
import fun.asgc.neutrino.proxy.server.constant.ExceptionConstant;
import fun.asgc.neutrino.proxy.server.constant.OnlineStatusEnum;
import fun.asgc.neutrino.proxy.server.controller.req.proxy.PortMappingCreateReq;
import fun.asgc.neutrino.proxy.server.controller.req.proxy.PortMappingListReq;
import fun.asgc.neutrino.proxy.server.controller.req.proxy.PortMappingUpdateEnableStatusReq;
import fun.asgc.neutrino.proxy.server.controller.req.proxy.PortMappingUpdateReq;
import fun.asgc.neutrino.proxy.server.controller.res.proxy.*;
import fun.asgc.neutrino.proxy.server.dal.LicenseMapper;
import fun.asgc.neutrino.proxy.server.dal.PortMappingMapper;
import fun.asgc.neutrino.proxy.server.dal.PortPoolMapper;
import fun.asgc.neutrino.proxy.server.dal.UserMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.LicenseDO;
import fun.asgc.neutrino.proxy.server.dal.entity.PortMappingDO;
import fun.asgc.neutrino.proxy.server.dal.entity.PortPoolDO;
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
import fun.asgc.neutrino.proxy.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 org.noear.solon.core.Lifecycle;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
*
* @author: aoshiguchen
* @date: 2022/8/8
*/
@Component
public class PortMappingService implements Lifecycle {
@Inject
private MapperFacade mapperFacade;
@Db
private PortMappingMapper portMappingMapper;
@Db
private LicenseMapper licenseMapper;
@Db
private UserMapper userMapper;
@Db
private PortPoolMapper portPoolMapper;
@Inject
private VisitorChannelService visitorChannelService;
@Inject
private PortPoolService portPoolService;
public PageInfo<PortMappingListRes> page(PageQuery pageQuery, PortMappingListReq req) {
Page<PortMappingListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
List<PortMappingDO> list = portMappingMapper.selectPortMappingByCondition(req);
List<PortMappingListRes> respList = mapperFacade.mapAsList(list, PortMappingListRes.class);
if (CollectionUtils.isEmpty(list)) {
return PageInfo.of(respList, result.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());
}
Set<Integer> userIds = licenseList.stream().map(LicenseDO::getUserId).collect(Collectors.toSet());
List<UserDO> userList = userMapper.findByIds(userIds);
Map<Integer, LicenseDO> licenseMap = licenseList.stream().collect(Collectors.toMap(LicenseDO::getId, Function.identity()));
Map<Integer, UserDO> userMap = userList.stream().collect(Collectors.toMap(UserDO::getId, Function.identity()));
respList.forEach(item -> {
LicenseDO license = licenseMap.get(item.getLicenseId());
if (null == license) {
return;
}
item.setLicenseName(license.getName());
item.setUserId(license.getUserId());
UserDO user = userMap.get(license.getUserId());
if (null == user) {
return;
}
item.setUserName(user.getName());
});
//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());
}
public PortMappingCreateRes create(PortMappingCreateReq req) {
LicenseDO licenseDO = licenseMapper.findById(req.getLicenseId());
ParamCheckUtil.checkNotNull(licenseDO, ExceptionConstant.LICENSE_NOT_EXIST);
if (!SystemContextHolder.isAdmin()) {
// 临时处理,如果当前用户不是管理员,则操作userId不能为1
ParamCheckUtil.checkExpression(!licenseDO.getUserId().equals(1), ExceptionConstant.NO_PERMISSION_VISIT);
}
PortPoolDO portPoolDO = portPoolMapper.findByPort(req.getServerPort());
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
ParamCheckUtil.checkExpression(null == portMappingMapper.findByPort(req.getServerPort(), null), ExceptionConstant.PORT_CANNOT_REPEAT_MAPPING, req.getServerPort());
Date now = new Date();
PortMappingDO portMappingDO = new PortMappingDO();
portMappingDO.setLicenseId(req.getLicenseId());
portMappingDO.setServerPort(req.getServerPort());
portMappingDO.setClientIp(req.getClientIp());
portMappingDO.setClientPort(req.getClientPort());
portMappingDO.setIsOnline(OnlineStatusEnum.OFFLINE.getStatus());
portMappingDO.setEnable(EnableStatusEnum.ENABLE.getStatus());
portMappingDO.setCreateTime(now);
portMappingDO.setUpdateTime(now);
portMappingMapper.insert(portMappingDO);
// 更新VisitorChannel
visitorChannelService.addVisitorChannelByPortMapping(portMappingDO);
return new PortMappingCreateRes();
}
public PortMappingUpdateRes update(PortMappingUpdateReq req) {
LicenseDO licenseDO = licenseMapper.findById(req.getLicenseId());
ParamCheckUtil.checkNotNull(licenseDO, ExceptionConstant.LICENSE_NOT_EXIST);
if (!SystemContextHolder.isAdmin()) {
// 临时处理,如果当前用户不是管理员,则操作userId不能为1
ParamCheckUtil.checkExpression(!licenseDO.getUserId().equals(1), ExceptionConstant.NO_PERMISSION_VISIT);
}
PortPoolDO portPoolDO = portPoolMapper.findByPort(req.getServerPort());
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
ParamCheckUtil.checkExpression(null == portMappingMapper.findByPort(req.getServerPort(), Sets.newHashSet(req.getId())), ExceptionConstant.PORT_CANNOT_REPEAT_MAPPING, req.getServerPort());
// 查询原端口映射
PortMappingDO oldPortMappingDO = portMappingMapper.findById(req.getId());
ParamCheckUtil.checkNotNull(oldPortMappingDO, ExceptionConstant.PORT_MAPPING_NOT_EXIST);
PortMappingDO portMappingDO = new PortMappingDO();
portMappingDO.setId(req.getId());
portMappingDO.setLicenseId(req.getLicenseId());
portMappingDO.setServerPort(req.getServerPort());
portMappingDO.setClientIp(req.getClientIp());
portMappingDO.setClientPort(req.getClientPort());
portMappingDO.setUpdateTime(new Date());
portMappingDO.setEnable(EnableStatusEnum.ENABLE.getStatus());
portMappingMapper.updateById(portMappingDO);
// 更新VisitorChannel
visitorChannelService.updateVisitorChannelByPortMapping(oldPortMappingDO, portMappingDO);
return new PortMappingUpdateRes();
}
public PortMappingDetailRes detail(Integer id) {
PortMappingDO portMappingDO = portMappingMapper.findById(id);
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());
LicenseDO license = licenseMapper.findById(portMappingDO.getLicenseId());
if (null != license) {
res.setLicenseName(license.getName());
res.setUserId(license.getUserId());
UserDO user = userMapper.findById(license.getUserId());
if (null != user) {
res.setUserName(user.getName());
}
}
return res;
}
public PortMappingUpdateEnableStatusRes updateEnableStatus(PortMappingUpdateEnableStatusReq req) {
PortMappingDO portMappingDO = portMappingMapper.findById(req.getId());
ParamCheckUtil.checkNotNull(portMappingDO, ExceptionConstant.PORT_MAPPING_NOT_EXIST);
LicenseDO licenseDO = licenseMapper.findById(portMappingDO.getLicenseId());
ParamCheckUtil.checkNotNull(licenseDO, ExceptionConstant.LICENSE_NOT_EXIST);
if (!SystemContextHolder.isAdmin()) {
ParamCheckUtil.checkExpression(!licenseDO.getUserId().equals(1), ExceptionConstant.NO_PERMISSION_VISIT);
}
portMappingMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
// 更新VisitorChannel
portMappingDO.setEnable(req.getEnable());
if (EnableStatusEnum.ENABLE == EnableStatusEnum.of(req.getEnable())) {
visitorChannelService.addVisitorChannelByPortMapping(portMappingDO);
} else {
visitorChannelService.removeVisitorChannelByPortMapping(portMappingDO);
}
return new PortMappingUpdateEnableStatusRes();
}
public void delete(Integer id) {
PortMappingDO portMappingDO = portMappingMapper.findById(id);
ParamCheckUtil.checkNotNull(portMappingDO, ExceptionConstant.PORT_MAPPING_NOT_EXIST);
LicenseDO licenseDO = licenseMapper.findById(portMappingDO.getLicenseId());
if (null != licenseDO && !SystemContextHolder.isAdmin()) {
// 临时处理,如果当前用户不是管理员,则操作userId不能为1
ParamCheckUtil.checkExpression(!licenseDO.getUserId().equals(1), ExceptionConstant.NO_PERMISSION_VISIT);
}
portMappingMapper.deleteById(id);
// 更新VisitorChannel
visitorChannelService.removeVisitorChannelByPortMapping(portMappingDO);
}
/**
* 根据license查询可用的端口映射列表
* @param licenseId
* @return
*/
public List<PortMappingDO> findEnableListByLicenseId(Integer licenseId) {
return portMappingMapper.findEnableListByLicenseId(licenseId);
}
/**
* 服务端项目停止、启动时,更新在线状态为离线
*/
@Override
public void start() throws Throwable {
portMappingMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
}
/**
* 服务端项目停止、启动时,更新在线状态为离线
*/
@Override
public void stop() throws Throwable {
portMappingMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
}
}
@@ -1,90 +0,0 @@
package fun.asgc.neutrino.proxy.server.util;
import cn.hutool.core.util.StrUtil;
import fun.asgc.neutrino.proxy.server.constant.ExceptionConstant;
import fun.asgc.neutrino.proxy.server.base.rest.ServiceException;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
/**
*
* @author: aoshiguchen
* @date: 2022/8/1
*/
public class ParamCheckUtil {
public static void checkNotNull(Object obj, String name) {
if (null == obj) {
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_NULL, name);
}
}
public static void checkNotEmpty(String str, String name) {
if (StrUtil.isEmpty(str)) {
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_EMPTY, name);
}
}
public static void checkNotEmpty(Collection collection, String name) {
if (null == collection || collection.isEmpty()) {
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_EMPTY, name);
}
}
public static void checkNotEmpty(Map map, String name) {
if (null == map || map.isEmpty()) {
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_EMPTY, name);
}
}
public static void checkNotEmpty(Set set, String name) {
if (null == set || set.isEmpty()) {
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_EMPTY, name);
}
}
public static void checkMustNull(Object obj, ExceptionConstant constant, Object... params) {
if (null != obj) {
throw ServiceException.create(constant, params);
}
}
public static void checkNotNull(Object obj, ExceptionConstant constant, Object... params) {
if (null == obj) {
throw ServiceException.create(constant, params);
}
}
public static void checkNotEmpty(String str, ExceptionConstant constant, Object... params) {
if (StrUtil.isEmpty(str)) {
throw ServiceException.create(constant, params);
}
}
public static void checkNotEmpty(Collection collection, ExceptionConstant constant, Object... params) {
if (null == collection || collection.isEmpty()) {
throw ServiceException.create(constant, params);
}
}
public static void checkNotEmpty(Map map, ExceptionConstant constant, Object... params) {
if (null == map || map.isEmpty()) {
throw ServiceException.create(constant, params);
}
}
public static void checkNotEmpty(Set set, ExceptionConstant constant, Object... params) {
if (null == set || set.isEmpty()) {
throw ServiceException.create(constant, params);
}
}
public static void checkExpression(boolean expression, ExceptionConstant constant, Object... params) {
if (!expression) {
throw ServiceException.create(constant, params);
}
}
}
@@ -0,0 +1,52 @@
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.solon.Solon;
import org.noear.solon.SolonApp;
import org.noear.solon.annotation.SolonMain;
import org.noear.solon.web.cors.CrossFilter;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Slf4j
@EnableJob
@SolonMain
public class ProxyServer {
public static void main(String[] args) {
Solon.start(ProxyServer.class, args, app -> {
// 跨域支持。加-1 优先级更高
app.filter(-1, new CrossFilter().allowedOrigins("*"));
// 设置日志级别
setLogLevel(app);
});
}
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);
}
}
}
@@ -19,15 +19,15 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.base.db;
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 fun.asgc.neutrino.proxy.core.util.Assert;
import fun.asgc.neutrino.proxy.core.util.FileUtil;
import fun.asgc.neutrino.proxy.server.constant.DbTypeEnum;
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;
@@ -64,8 +64,7 @@ public class DBInitialize implements EventListener<AppLoadEndEvent> {
@Override
public void onEvent(AppLoadEndEvent appLoadEndEvent) throws Throwable {
// TODO 该事件有50%的概率不触发
System.out.println("11");
}
/**
@@ -19,9 +19,9 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.base.db;
package org.dromara.neutrinoproxy.server.base.db;
import fun.asgc.neutrino.proxy.server.constant.DbTypeEnum;
import org.dromara.neutrinoproxy.server.constant.DbTypeEnum;
import lombok.Data;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
@@ -45,11 +45,6 @@ public class DbConfig {
*/
@Inject("${neutrino.data.db.url}")
private String url;
/**
* 驱动类
*/
@Inject("${neutrino.data.db.driver-class}")
private String driverClass;
/**
* 用户名
@@ -1,12 +1,12 @@
package fun.asgc.neutrino.proxy.server.base.db;
package org.dromara.neutrinoproxy.server.base.db;
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.zaxxer.hikari.HikariDataSource;
import fun.asgc.neutrino.proxy.server.constant.DbTypeEnum;
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;
@@ -31,25 +31,33 @@ public class DbConfiguration {
dataSource.setJournalMode(SQLiteConfig.JournalMode.WAL.getValue());
return dataSource;
} else if (DbTypeEnum.MYSQL == dbTypeEnum) {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setDriverClassName(dbConfig.getDriverClass());
dataSource.setJdbcUrl(dbConfig.getUrl());
dataSource.setMinimumIdle(5);
dataSource.setMaximumPoolSize(20);
dataSource.setMaxLifetime(60000);
// dataSource.setInitialSize(5);
// dataSource.setMinIdle(5);
// dataSource.setMaxActive(20);
// dataSource.setMaxWait(60000);
// dataSource.setPoolPreparedStatements(true);
dataSource.setUsername(dbConfig.getUsername());
dataSource.setPassword(dbConfig.getPassword());
return dataSource;
String driver = "com.mysql.cj.jdbc.Driver";
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
// 对类名的判断,异常则说明不存在:
driver = "com.mysql.jdbc.Driver";
}
return newHikariDataSource(dbConfig, driver);
} else if (DbTypeEnum.MARIADB == dbTypeEnum) {
return newHikariDataSource(dbConfig, "org.mariadb.jdbc.Driver");
}
return null;
}
private HikariDataSource newHikariDataSource(DbConfig dbConfig, String driverClass) {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setDriverClassName(driverClass);
dataSource.setJdbcUrl(dbConfig.getUrl());
dataSource.setMinimumIdle(5);
dataSource.setMaximumPoolSize(20);
dataSource.setMaxLifetime(60000);
dataSource.setUsername(dbConfig.getUsername());
dataSource.setPassword(dbConfig.getPassword());
return dataSource;
}
@Bean
public void db1_ext(@Db("db") GlobalConfig globalConfig) {
MetaObjectHandler metaObjectHandler = new MetaObjectHandlerImpl();
@@ -57,13 +65,13 @@ public class DbConfiguration {
}
@Bean
public void db1_ext2(@Db("db") MybatisConfiguration config){
public void db1_ext2(@Db("db") MybatisConfiguration config) {
config.getTypeHandlerRegistry().register("fun.asgc.neutrino.proxy.server.dal");
config.setDefaultEnumTypeHandler(null);
}
@Bean
public MybatisSqlSessionFactoryBuilder factoryBuilderNew(){
public MybatisSqlSessionFactoryBuilder factoryBuilderNew() {
return new MybatisSqlSessionFactoryBuilderImpl();
}
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.base.db;
package org.dromara.neutrinoproxy.server.base.db;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.base.db;
package org.dromara.neutrinoproxy.server.base.db;
import com.baomidou.mybatisplus.core.MybatisSqlSessionFactoryBuilder;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.base.page;
package org.dromara.neutrinoproxy.server.base.page;
import lombok.Data;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.base.page;
package org.dromara.neutrinoproxy.server.base.page;
import lombok.Data;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.base.proxy;
package org.dromara.neutrinoproxy.server.base.proxy;
import lombok.Data;
import org.noear.solon.annotation.Component;
@@ -18,10 +18,15 @@ public class ProxyConfig {
@Inject("${neutrino.proxy.protocol}")
private Protocol protocol;
/**
* 服务配置
* 代理服务配置
*/
@Inject("${neutrino.proxy.server}")
private Server server;
/**
* 代理隧道配置
*/
@Inject("${neutrino.proxy.tunnel}")
private Tunnel tunnel;
@Data
public static class Protocol {
@@ -37,13 +42,27 @@ 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;
}
@Data
public static class Tunnel {
private Integer bossThreadCount;
private Integer workThreadCount;
private Integer port;
private Integer sslPort;
private String keyStorePassword;
private String keyManagerPassword;
private String jksPath;
private Integer bossThreadCount;
private Integer workThreadCount;
private Boolean transferLogEnable;
private Boolean heartbeatLogEnable;
}
}
@@ -0,0 +1,85 @@
package org.dromara.neutrinoproxy.server.base.proxy;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
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.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.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.util.List;
/**
* 代理配置
* @author: aoshiguchen
* @date: 2022/10/8
*/
@Configuration
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());
Solon.context().wrapAndPut(Dispatcher.class, dispatcher);
}
@Bean("serverBossGroup")
public NioEventLoopGroup serverBossGroup(@Inject ProxyConfig proxyConfig) {
return new NioEventLoopGroup(proxyConfig.getServer().getBossThreadCount());
}
@Bean("serverWorkerGroup")
public NioEventLoopGroup serverWorkerGroup(@Inject ProxyConfig proxyConfig) {
return new NioEventLoopGroup(proxyConfig.getServer().getWorkThreadCount());
}
@Bean("tcpServerBootstrap")
public ServerBootstrap tcpServerBootstrap(@Inject("serverBossGroup") NioEventLoopGroup serverBossGroup,
@Inject("serverWorkerGroup") NioEventLoopGroup serverWorkerGroup,
@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());
}
});
return bootstrap;
}
@Bean("tunnelBossGroup")
public NioEventLoopGroup tunnelBossGroup(@Inject ProxyConfig proxyConfig) {
return new NioEventLoopGroup(proxyConfig.getTunnel().getBossThreadCount());
}
@Bean("tunnelWorkerGroup")
public NioEventLoopGroup tunnelWorkerGroup(@Inject ProxyConfig proxyConfig) {
return new NioEventLoopGroup(proxyConfig.getTunnel().getWorkThreadCount());
}
}
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.base.rest;
package org.dromara.neutrinoproxy.server.base.rest;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.base.rest;
package org.dromara.neutrinoproxy.server.base.rest;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -19,9 +19,9 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.base.rest;
package org.dromara.neutrinoproxy.server.base.rest;
import fun.asgc.neutrino.proxy.server.constant.ExceptionConstant;
import org.dromara.neutrinoproxy.server.constant.ExceptionConstant;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
@@ -19,9 +19,9 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.base.rest;
package org.dromara.neutrinoproxy.server.base.rest;
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
import lombok.Data;
import lombok.experimental.Accessors;
import org.noear.solon.core.handle.Action;
@@ -19,9 +19,9 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.base.rest;
package org.dromara.neutrinoproxy.server.base.rest;
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
/**
* 系统上下文持有者
@@ -1,13 +1,14 @@
package fun.asgc.neutrino.proxy.server.base.rest.interceptor;
package org.dromara.neutrinoproxy.server.base.rest.interceptor;
import cn.hutool.core.util.StrUtil;
import fun.asgc.neutrino.proxy.server.base.rest.*;
import fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum;
import fun.asgc.neutrino.proxy.server.constant.ExceptionConstant;
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
import fun.asgc.neutrino.proxy.server.service.UserService;
import org.dromara.neutrinoproxy.server.base.rest.*;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import org.dromara.neutrinoproxy.server.constant.ExceptionConstant;
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
import org.dromara.neutrinoproxy.server.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.dromara.neutrinoproxy.server.base.rest.*;
import org.noear.solon.Solon;
import org.noear.solon.annotation.Component;
import org.noear.solon.core.handle.Action;
@@ -1,8 +1,8 @@
package fun.asgc.neutrino.proxy.server.base.rest.interceptor;
package org.dromara.neutrinoproxy.server.base.rest.interceptor;
import fun.asgc.neutrino.proxy.server.base.rest.ResponseBody;
import fun.asgc.neutrino.proxy.server.base.rest.ServiceException;
import fun.asgc.neutrino.proxy.server.constant.ExceptionConstant;
import org.dromara.neutrinoproxy.server.base.rest.ResponseBody;
import org.dromara.neutrinoproxy.server.base.rest.ServiceException;
import org.dromara.neutrinoproxy.server.constant.ExceptionConstant;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.noear.solon.annotation.Component;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.base.rest.interceptor;
package org.dromara.neutrinoproxy.server.base.rest.interceptor;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
@@ -31,7 +31,7 @@ public class VisitLogInterceptor implements RouterInterceptor {
} finally {
Date now = new Date();
long elapsedTime = now.getTime() - startTime.getTime();
log.info("\n-----------------------------------------------------------------接口请求日志:\n{} url:{} 执行耗时:{}\n请求体参数:{}\n响应结果:{}\n客户端IP:{}\n",
log.debug("\n-----------------------------------------------------------------接口请求日志:\n{} url:{} 执行耗时:{}\n请求体参数:{}\n响应结果:{}\n客户端IP:{}\n",
ctx.method(), ctx.path(), getElapsedTimeStr(elapsedTime),
JSONObject.toJSONString(ctx.paramMap()),
JSONObject.toJSONString(JSONObject.toJSONString(ctx.result)),
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.constant;
package org.dromara.neutrinoproxy.server.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.constant;
package org.dromara.neutrinoproxy.server.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.constant;
package org.dromara.neutrinoproxy.server.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.constant;
package org.dromara.neutrinoproxy.server.constant;
/**
* @author: aoshiguchen
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.constant;
package org.dromara.neutrinoproxy.server.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -38,7 +38,9 @@ import java.util.stream.Stream;
@AllArgsConstructor
public enum DbTypeEnum {
SQLITE("sqlite"),
MYSQL("mysql");
MYSQL("mysql"),
MARIADB("mariadb"),
;
private String type;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.constant;
package org.dromara.neutrinoproxy.server.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.constant;
package org.dromara.neutrinoproxy.server.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -41,6 +41,8 @@ public enum ExceptionConstant {
PARAMS_NOT_NULL(10, "参数[{}]不能为空"),
PARAMS_NOT_EMPTY(11, "参数[{}]不能为空"),
FILED_LENGTH_OUT(12 ,"{}不能超出长度{}"),
// 用户管理(11000)
// license管理(12000)
LICENSE_NAME_CANNOT_REPEAT(12000, "license名称不能重复"),
@@ -51,9 +53,13 @@ public enum ExceptionConstant {
// 端口池管理(13000)
PORT_CANNOT_REPEAT(13000,"端口不能重复"),
PORT_NOT_EXIST(13001, "该端口在端口池中不存在"),
PORT_RANGE_FAIL(13002, "端口值范围为MIN-MAX"),
// 端口映射管理(14000)
PORT_MAPPING_NOT_EXIST(14000, "端口映射记录不存在"),
PORT_CANNOT_REPEAT_MAPPING(14001, "服务端口[{}]不能重复映射"),
AN_UNSUPPORTED_PROTOCOL(14002, "不支持的协议[{}]"),
PORT_MAPPING_SUBDONAME_CONNOT_REPEAT(14003, "子域名不能重复使用!"),
// 调度管理(15000)
JOB_INFO_NOT_EXIST(15000, "调度管理记录不存在"),
SYSTEM_ERROR(500, "系统异常"),
@@ -0,0 +1,41 @@
package org.dromara.neutrinoproxy.server.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author: aoshiguchen
* @date: 2023/4/2
*/
@Getter
@AllArgsConstructor
public enum NetworkProtocolEnum {
TCP("TCP", "TCP"),
UDP("UDP", "UDP"),
HTTP("HTTP", "TCP"),
;
private String desc;
private String baseProtocol;
private static final Map<String, NetworkProtocolEnum> map = Stream.of(NetworkProtocolEnum.values()).collect(Collectors.toMap(NetworkProtocolEnum::getDesc, Function.identity()));
public static NetworkProtocolEnum of(String desc) {
if (StringUtils.isBlank(desc)) {
return null;
}
if (desc.startsWith("HTTP")) {
return NetworkProtocolEnum.HTTP;
}
return map.get(desc);
}
public static Boolean isHttp(String desc) {
NetworkProtocolEnum networkProtocolEnum = of(desc);
return NetworkProtocolEnum.HTTP == networkProtocolEnum;
}
}
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.constant;
package org.dromara.neutrinoproxy.server.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.constant;
package org.dromara.neutrinoproxy.server.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.constant;
package org.dromara.neutrinoproxy.server.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -19,14 +19,14 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller;
package org.dromara.neutrinoproxy.server.controller;
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
import fun.asgc.neutrino.proxy.server.controller.req.log.ClientConnectRecordListReq;
import fun.asgc.neutrino.proxy.server.controller.res.log.ClientConnectRecordListRes;
import fun.asgc.neutrino.proxy.server.service.ClientConnectRecordService;
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
import org.dromara.neutrinoproxy.server.controller.req.log.ClientConnectRecordListReq;
import org.dromara.neutrinoproxy.server.controller.res.log.ClientConnectRecordListRes;
import org.dromara.neutrinoproxy.server.service.ClientConnectRecordService;
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.annotation.Controller;
import org.noear.solon.annotation.Get;
@@ -0,0 +1,27 @@
package org.dromara.neutrinoproxy.server.controller;
import lombok.extern.slf4j.Slf4j;
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
import org.noear.solon.annotation.Controller;
import org.noear.solon.annotation.Get;
import org.noear.solon.annotation.Inject;
import org.noear.solon.annotation.Mapping;
/**
* @author: aoshiguchen
* @date: 2023/4/2
*/
@Slf4j
@Mapping("/domain-name")
@Controller
public class DomainNameController {
@Inject
private ProxyConfig proxyConfig;
@Get
@Mapping("/bind-info")
public String bindInfo () {
return proxyConfig.getServer().getDomainName();
}
}
@@ -19,13 +19,13 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller;
package org.dromara.neutrinoproxy.server.controller;
import fun.asgc.neutrino.proxy.server.base.rest.Authorization;
import fun.asgc.neutrino.proxy.server.controller.req.system.LoginReq;
import fun.asgc.neutrino.proxy.server.controller.res.system.LoginRes;
import fun.asgc.neutrino.proxy.server.service.UserService;
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
import org.dromara.neutrinoproxy.server.base.rest.Authorization;
import org.dromara.neutrinoproxy.server.controller.req.system.LoginReq;
import org.dromara.neutrinoproxy.server.controller.res.system.LoginRes;
import org.dromara.neutrinoproxy.server.service.UserService;
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
import org.noear.solon.annotation.*;
import org.noear.solon.core.handle.Context;
@@ -19,22 +19,22 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller;
package org.dromara.neutrinoproxy.server.controller;
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
import fun.asgc.neutrino.proxy.server.base.rest.Authorization;
import fun.asgc.neutrino.proxy.server.controller.req.system.JobInfoExecuteReq;
import fun.asgc.neutrino.proxy.server.controller.req.system.JobInfoListReq;
import fun.asgc.neutrino.proxy.server.controller.req.system.JobInfoUpdateEnableStatusReq;
import fun.asgc.neutrino.proxy.server.controller.req.system.JobInfoUpdateReq;
import fun.asgc.neutrino.proxy.server.controller.res.system.JobInfoExecuteRes;
import fun.asgc.neutrino.proxy.server.controller.res.system.JobInfoListRes;
import fun.asgc.neutrino.proxy.server.controller.res.system.JobInfoUpdateEnableStatusRes;
import fun.asgc.neutrino.proxy.server.controller.res.system.JobInfoUpdateRes;
import fun.asgc.neutrino.proxy.server.dal.entity.JobInfoDO;
import fun.asgc.neutrino.proxy.server.service.JobInfoService;
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
import org.dromara.neutrinoproxy.server.base.rest.Authorization;
import org.dromara.neutrinoproxy.server.controller.req.system.JobInfoExecuteReq;
import org.dromara.neutrinoproxy.server.controller.req.system.JobInfoListReq;
import org.dromara.neutrinoproxy.server.controller.req.system.JobInfoUpdateEnableStatusReq;
import org.dromara.neutrinoproxy.server.controller.req.system.JobInfoUpdateReq;
import org.dromara.neutrinoproxy.server.controller.res.system.JobInfoExecuteRes;
import org.dromara.neutrinoproxy.server.controller.res.system.JobInfoListRes;
import org.dromara.neutrinoproxy.server.controller.res.system.JobInfoUpdateEnableStatusRes;
import org.dromara.neutrinoproxy.server.controller.res.system.JobInfoUpdateRes;
import org.dromara.neutrinoproxy.server.dal.entity.JobInfoDO;
import org.dromara.neutrinoproxy.server.service.JobInfoService;
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.annotation.*;
@@ -19,14 +19,14 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller;
package org.dromara.neutrinoproxy.server.controller;
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
import fun.asgc.neutrino.proxy.server.controller.req.log.JobLogListReq;
import fun.asgc.neutrino.proxy.server.controller.res.log.JobLogListRes;
import fun.asgc.neutrino.proxy.server.service.JobLogService;
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
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.service.JobLogService;
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.annotation.Controller;
import org.noear.solon.annotation.Get;
@@ -19,15 +19,17 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller;
package org.dromara.neutrinoproxy.server.controller;
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
import fun.asgc.neutrino.proxy.server.base.rest.Authorization;
import fun.asgc.neutrino.proxy.server.controller.req.proxy.*;
import fun.asgc.neutrino.proxy.server.controller.res.proxy.*;
import fun.asgc.neutrino.proxy.server.service.LicenseService;
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
import org.dromara.neutrinoproxy.server.base.rest.Authorization;
import org.dromara.neutrinoproxy.server.controller.req.proxy.*;
import org.dromara.neutrinoproxy.server.controller.res.proxy.*;
import org.dromara.neutrinoproxy.server.controller.req.proxy.*;
import org.dromara.neutrinoproxy.server.controller.res.proxy.*;
import org.dromara.neutrinoproxy.server.service.LicenseService;
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
import org.noear.solon.annotation.*;
import java.util.List;
@@ -1,17 +1,17 @@
package fun.asgc.neutrino.proxy.server.controller;
package org.dromara.neutrinoproxy.server.controller;
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
import fun.asgc.neutrino.proxy.server.base.rest.Authorization;
import fun.asgc.neutrino.proxy.server.controller.req.system.PortGroupCreateReq;
import fun.asgc.neutrino.proxy.server.controller.req.system.PortGroupDeleteReq;
import fun.asgc.neutrino.proxy.server.controller.req.system.PortGroupListReq;
import fun.asgc.neutrino.proxy.server.controller.req.system.PortGroupUpdateEnableStatusReq;
import fun.asgc.neutrino.proxy.server.controller.res.system.PortGroupCreateRes;
import fun.asgc.neutrino.proxy.server.controller.res.system.PortGroupListRes;
import fun.asgc.neutrino.proxy.server.controller.res.system.PortGroupUpdateEnableStatusRes;
import fun.asgc.neutrino.proxy.server.service.PortGroupService;
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
import org.dromara.neutrinoproxy.server.base.rest.Authorization;
import org.dromara.neutrinoproxy.server.controller.req.system.PortGroupCreateReq;
import org.dromara.neutrinoproxy.server.controller.req.system.PortGroupDeleteReq;
import org.dromara.neutrinoproxy.server.controller.req.system.PortGroupListReq;
import org.dromara.neutrinoproxy.server.controller.req.system.PortGroupUpdateEnableStatusReq;
import org.dromara.neutrinoproxy.server.controller.res.system.PortGroupCreateRes;
import org.dromara.neutrinoproxy.server.controller.res.system.PortGroupListRes;
import org.dromara.neutrinoproxy.server.controller.res.system.PortGroupUpdateEnableStatusRes;
import org.dromara.neutrinoproxy.server.service.PortGroupService;
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
import org.noear.solon.annotation.*;
import java.util.List;
@@ -0,0 +1,110 @@
package org.dromara.neutrinoproxy.server.controller;
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
import org.dromara.neutrinoproxy.server.constant.ExceptionConstant;
import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum;
import org.dromara.neutrinoproxy.server.controller.req.proxy.*;
import org.dromara.neutrinoproxy.server.controller.res.proxy.*;
import org.dromara.neutrinoproxy.server.controller.req.proxy.*;
import org.dromara.neutrinoproxy.server.controller.res.proxy.*;
import org.dromara.neutrinoproxy.server.service.PortMappingService;
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
import org.apache.commons.lang3.StringUtils;
import org.noear.solon.annotation.*;
/**
* 端口映射
* @author: aoshiguchen
* @date: 2022/8/8
*/
@Mapping("/port-mapping")
@Controller
public class PortMappingController {
@Inject
private PortMappingService portMappingService;
@Get
@Mapping("/page")
public PageInfo<PortMappingListRes> page(PageQuery pageQuery, PortMappingListReq req) {
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
return portMappingService.page(pageQuery, req);
}
@Post
@Mapping("/create")
public PortMappingCreateRes create(PortMappingCreateReq req) {
ParamCheckUtil.checkNotNull(req, "req");
ParamCheckUtil.checkNotNull(req.getLicenseId(), "licenseId");
ParamCheckUtil.checkNotNull(req.getServerPort(), "serverPort");
ParamCheckUtil.checkNotNull(req.getClientPort(), "clientPort");
ParamCheckUtil.checkNotEmpty(req.getProtocal(), "protocal");
ParamCheckUtil.checkMaxLength(req.getDescription(), 50, "描述", "50");
if (StringUtils.isBlank(req.getClientIp())) {
// 没传客户端ip默认为127.0.0.1
req.setClientIp("127.0.0.1");
}
NetworkProtocolEnum networkProtocolEnum = NetworkProtocolEnum.of(req.getProtocal());
ParamCheckUtil.checkNotNull(networkProtocolEnum, ExceptionConstant.AN_UNSUPPORTED_PROTOCOL, req.getProtocal());
if (networkProtocolEnum != NetworkProtocolEnum.HTTP) {
// 目前仅HTTP支持绑定域名
req.setSubdomain(null);
}
req.setProtocal(networkProtocolEnum.getDesc());
return portMappingService.create(req);
}
@Post
@Mapping("/update")
public PortMappingUpdateRes update(PortMappingUpdateReq req) {
ParamCheckUtil.checkNotNull(req, "req");
ParamCheckUtil.checkNotNull(req.getLicenseId(), "licenseId");
ParamCheckUtil.checkNotNull(req.getServerPort(), "serverPort");
ParamCheckUtil.checkNotNull(req.getClientPort(), "clientPort");
ParamCheckUtil.checkNotEmpty(req.getProtocal(), "protocal");
ParamCheckUtil.checkMaxLength(req.getDescription(), 50, "描述", "50");
if (StringUtils.isBlank(req.getClientIp())) {
// 没传客户端ip默认为127.0.0.1
req.setClientIp("127.0.0.1");
}
NetworkProtocolEnum networkProtocolEnum = NetworkProtocolEnum.of(req.getProtocal());
ParamCheckUtil.checkNotNull(networkProtocolEnum, ExceptionConstant.AN_UNSUPPORTED_PROTOCOL, req.getProtocal());
if (networkProtocolEnum != NetworkProtocolEnum.HTTP) {
// 目前仅HTTP支持绑定域名
req.setSubdomain(null);
}
req.setProtocal(networkProtocolEnum.getDesc());
return portMappingService.update(req);
}
@Get
@Mapping("/detail")
public PortMappingDetailRes detail(PortMappingDetailReq req) {
ParamCheckUtil.checkNotNull(req, "req");
ParamCheckUtil.checkNotNull(req.getId(), "id");
return portMappingService.detail(req.getId());
}
@Post
@Mapping("/update/enable-status")
public PortMappingUpdateEnableStatusRes updateEnableStatus(PortMappingUpdateEnableStatusReq req) {
ParamCheckUtil.checkNotNull(req, "req");
ParamCheckUtil.checkNotNull(req.getId(), "id");
ParamCheckUtil.checkNotNull(req.getEnable(), "enable");
return portMappingService.updateEnableStatus(req);
}
@Post
@Mapping("/delete")
public void delete(PortMappingDeleteReq req) {
ParamCheckUtil.checkNotNull(req, "req");
ParamCheckUtil.checkNotNull(req.getId(), "id");
portMappingService.delete(req.getId());
}
}
@@ -19,15 +19,17 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller;
package org.dromara.neutrinoproxy.server.controller;
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
import fun.asgc.neutrino.proxy.server.base.rest.Authorization;
import fun.asgc.neutrino.proxy.server.controller.req.system.*;
import fun.asgc.neutrino.proxy.server.controller.res.system.*;
import fun.asgc.neutrino.proxy.server.service.PortPoolService;
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
import org.dromara.neutrinoproxy.server.base.rest.Authorization;
import org.dromara.neutrinoproxy.server.controller.req.system.*;
import org.dromara.neutrinoproxy.server.controller.res.system.*;
import org.dromara.neutrinoproxy.server.service.PortPoolService;
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
import org.dromara.neutrinoproxy.server.controller.req.system.*;
import org.dromara.neutrinoproxy.server.controller.res.system.*;
import org.noear.solon.annotation.*;
import java.util.List;
@@ -71,7 +73,7 @@ public class PortPoolController {
@Authorization(onlyAdmin = true)
public PortPoolCreateRes create(PortPoolCreateReq req) {
ParamCheckUtil.checkNotNull(req, "req");
ParamCheckUtil.checkNotNull(req.getPort(), "port");
ParamCheckUtil.checkNotEmpty(req.getPort(), "port");
ParamCheckUtil.checkNotNull(req.getGroupId(), "groupId");
return portPoolService.create(req);
@@ -121,4 +123,14 @@ public class PortPoolController {
ParamCheckUtil.checkNotEmpty(req.getPortIdList(), "portIdList");
return portPoolService.updateGroup(req);
}
@Post
@Mapping("/delete-batch")
@Authorization(onlyAdmin = true)
public void deleteBatch(PortPoolDeleteBatchReq req) {
ParamCheckUtil.checkNotNull(req, "req");
ParamCheckUtil.checkNotNull(req.getIds(), "ids");
portPoolService.deleteBatch(req.getIds());
}
}
@@ -0,0 +1,40 @@
package org.dromara.neutrinoproxy.server.controller;
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
import org.dromara.neutrinoproxy.server.controller.res.system.ProtocalListRes;
import org.dromara.neutrinoproxy.server.service.ProtocalService;
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
import org.noear.solon.annotation.Controller;
import org.noear.solon.annotation.Get;
import org.noear.solon.annotation.Inject;
import org.noear.solon.annotation.Mapping;
import java.util.List;
/**
* @author: aoshiguchen
* @date: 2023/4/2
*/
@Mapping("/protocal")
@Controller
public class ProtocalController {
@Inject
private ProtocalService protocalService;
@Get
@Mapping("/page")
public PageInfo<ProtocalListRes> page(PageQuery pageQuery) {
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
List<ProtocalListRes> list = protocalService.list();
return PageInfo.of(protocalService.list(), (long)list.size(), pageQuery.getCurrent(), pageQuery.getSize());
}
@Get
@Mapping("/list")
public List<ProtocalListRes> list() {
return protocalService.list();
}
}
@@ -1,14 +1,15 @@
package fun.asgc.neutrino.proxy.server.controller;
package org.dromara.neutrinoproxy.server.controller;
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
import fun.asgc.neutrino.proxy.server.controller.req.report.LicenseFlowMonthReportReq;
import fun.asgc.neutrino.proxy.server.controller.req.report.LicenseFlowReportReq;
import fun.asgc.neutrino.proxy.server.controller.req.report.UserFlowMonthReportReq;
import fun.asgc.neutrino.proxy.server.controller.req.report.UserFlowReportReq;
import fun.asgc.neutrino.proxy.server.controller.res.report.*;
import fun.asgc.neutrino.proxy.server.service.ReportService;
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
import org.dromara.neutrinoproxy.server.controller.req.report.LicenseFlowMonthReportReq;
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.*;
import org.dromara.neutrinoproxy.server.service.ReportService;
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
import org.noear.solon.annotation.Controller;
import org.noear.solon.annotation.Get;
import org.noear.solon.annotation.Inject;
@@ -19,20 +19,22 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller;
package org.dromara.neutrinoproxy.server.controller;
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
import fun.asgc.neutrino.proxy.server.base.rest.Authorization;
import fun.asgc.neutrino.proxy.server.base.rest.SystemContextHolder;
import fun.asgc.neutrino.proxy.server.constant.ExceptionConstant;
import fun.asgc.neutrino.proxy.server.controller.req.system.*;
import fun.asgc.neutrino.proxy.server.controller.res.system.*;
import fun.asgc.neutrino.proxy.server.dal.UserMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
import fun.asgc.neutrino.proxy.server.service.UserService;
import fun.asgc.neutrino.proxy.server.util.Md5Util;
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
import org.dromara.neutrinoproxy.server.base.rest.Authorization;
import org.dromara.neutrinoproxy.server.base.rest.SystemContextHolder;
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.res.system.*;
import org.dromara.neutrinoproxy.server.dal.UserMapper;
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
import org.dromara.neutrinoproxy.server.service.UserService;
import org.dromara.neutrinoproxy.server.util.Md5Util;
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
import org.dromara.neutrinoproxy.server.controller.req.system.*;
import org.noear.solon.annotation.*;
import java.util.List;
@@ -19,14 +19,14 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller;
package org.dromara.neutrinoproxy.server.controller;
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
import fun.asgc.neutrino.proxy.server.controller.req.log.UserLoginRecordListReq;
import fun.asgc.neutrino.proxy.server.controller.res.log.UserLoginRecordListRes;
import fun.asgc.neutrino.proxy.server.service.UserLoginRecordService;
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
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;
import org.dromara.neutrinoproxy.server.controller.res.log.UserLoginRecordListRes;
import org.dromara.neutrinoproxy.server.service.UserLoginRecordService;
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
import org.noear.solon.annotation.Controller;
import org.noear.solon.annotation.Get;
import org.noear.solon.annotation.Inject;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.req.log;
package org.dromara.neutrinoproxy.server.controller.req.log;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.req.log;
package org.dromara.neutrinoproxy.server.controller.req.log;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.req.log;
package org.dromara.neutrinoproxy.server.controller.req.log;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.req.proxy;
package org.dromara.neutrinoproxy.server.controller.req.proxy;
import lombok.Data;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.req.proxy;
package org.dromara.neutrinoproxy.server.controller.req.proxy;
import lombok.Data;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.req.proxy;
package org.dromara.neutrinoproxy.server.controller.req.proxy;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.req.proxy;
package org.dromara.neutrinoproxy.server.controller.req.proxy;
import lombok.Data;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.req.proxy;
package org.dromara.neutrinoproxy.server.controller.req.proxy;
import lombok.Data;
@@ -19,9 +19,10 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.req.proxy;
package org.dromara.neutrinoproxy.server.controller.req.proxy;
import lombok.Data;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
/**
* 更新启用状态请求
@@ -36,7 +37,7 @@ public class LicenseUpdateEnableStatusReq {
private Integer id;
/**
* 启用状态
* {@link fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum}
* {@link EnableStatusEnum}
*/
private Integer enable;
}
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.req.proxy;
package org.dromara.neutrinoproxy.server.controller.req.proxy;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.req.proxy;
package org.dromara.neutrinoproxy.server.controller.req.proxy;
import lombok.Data;
@@ -34,6 +34,14 @@ public class PortMappingCreateReq {
* licenseId
*/
private Integer licenseId;
/**
* 协议
*/
private String protocal;
/**
* 子域名
*/
private String subdomain;
/**
* 服务端端口
*/
@@ -46,4 +54,8 @@ public class PortMappingCreateReq {
* 客户端端口
*/
private Integer clientPort;
/**
* 描述
*/
private String description;
}
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.req.proxy;
package org.dromara.neutrinoproxy.server.controller.req.proxy;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.req.proxy;
package org.dromara.neutrinoproxy.server.controller.req.proxy;
import lombok.Data;
@@ -19,9 +19,10 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.req.proxy;
package org.dromara.neutrinoproxy.server.controller.req.proxy;
import fun.asgc.neutrino.proxy.server.constant.OnlineStatusEnum;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
import lombok.Data;
/**
@@ -35,6 +36,10 @@ public class PortMappingListReq {
* 用户ID
*/
private Integer userId;
/**
* 协议
*/
private String protocal;
/**
* licenseId
*/
@@ -43,6 +48,11 @@ public class PortMappingListReq {
* 服务端口号
*/
private Integer serverPort;
/**
* 描述
*/
private String description;
/**
* 是否在线
@@ -51,7 +61,7 @@ public class PortMappingListReq {
private Integer isOnline;
/**
* 启用状态
* {@link fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum}
* {@link EnableStatusEnum}
*/
private Integer enable;
}
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.req.proxy;
package org.dromara.neutrinoproxy.server.controller.req.proxy;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.req.proxy;
package org.dromara.neutrinoproxy.server.controller.req.proxy;
import lombok.Data;
@@ -38,6 +38,14 @@ public class PortMappingUpdateReq {
* licenseId
*/
private Integer licenseId;
/**
* 协议
*/
private String protocal;
/**
* 子域名
*/
private String subdomain;
/**
* 服务端端口
*/
@@ -50,4 +58,8 @@ public class PortMappingUpdateReq {
* 客户端端口
*/
private Integer clientPort;
/**
* 描述
*/
private String description;
}
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.req.report;
package org.dromara.neutrinoproxy.server.controller.req.report;
import lombok.Data;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.req.report;
package org.dromara.neutrinoproxy.server.controller.req.report;
import lombok.Data;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.req.report;
package org.dromara.neutrinoproxy.server.controller.req.report;
import lombok.Data;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.req.report;
package org.dromara.neutrinoproxy.server.controller.req.report;
import lombok.Data;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.req.system;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.req.system;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.req.system;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.req.system;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
@@ -19,9 +19,10 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.req.system;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import java.util.Date;
@@ -59,7 +60,7 @@ public class JobInfoUpdateReq {
private String alarmDing;
/**
* 启用状态
* {@link fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum}
* {@link EnableStatusEnum}
*/
private Integer enable;
/**
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.req.system;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.req.system;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.req.system;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.req.system;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.req.system;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.req.system;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.req.system;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
@@ -30,7 +30,7 @@ import lombok.Data;
*/
@Data
public class PortPoolCreateReq {
private Integer port;
private String port;
private Integer groupId;
}
@@ -0,0 +1,14 @@
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
import java.util.List;
/**
* @author: Metal
* @date: 2023/7/3
*/
@Data
public class PortPoolDeleteBatchReq {
private List<Integer> ids;
}
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.req.system;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.req.system;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.req.system;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.req.system;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.req.system;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.req.system;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.req.system;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.req.system;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.req.system;
package org.dromara.neutrinoproxy.server.controller.req.system;
/**
* 用户列表请求
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.req.system;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.req.system;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.req.system;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.res.log;
package org.dromara.neutrinoproxy.server.controller.res.log;
import lombok.Data;
@@ -19,9 +19,10 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.res.log;
package org.dromara.neutrinoproxy.server.controller.res.log;
import lombok.Data;
import org.dromara.neutrinoproxy.server.constant.AlarmStatusEnum;
import java.util.Date;
@@ -55,7 +56,7 @@ public class JobLogListRes {
private String msg;
/**
* 报警状态
* {@link fun.asgc.neutrino.proxy.server.constant.AlarmStatusEnum}
* {@link AlarmStatusEnum}
*/
private Integer alarmStatus;
/**
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.res.log;
package org.dromara.neutrinoproxy.server.controller.res.log;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.res.proxy;
package org.dromara.neutrinoproxy.server.controller.res.proxy;
import lombok.Data;
@@ -19,9 +19,10 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.res.proxy;
package org.dromara.neutrinoproxy.server.controller.res.proxy;
import fun.asgc.neutrino.proxy.server.constant.OnlineStatusEnum;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -59,7 +60,7 @@ public class LicenseDetailRes {
private Integer isOnline;
/**
* 启用状态
* {@link fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum}
* {@link EnableStatusEnum}
*/
private Integer enable;
/**
@@ -1,6 +1,7 @@
package fun.asgc.neutrino.proxy.server.controller.res.proxy;
package org.dromara.neutrinoproxy.server.controller.res.proxy;
import fun.asgc.neutrino.proxy.server.constant.OnlineStatusEnum;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -38,7 +39,7 @@ public class LicenseListRes {
private Integer isOnline;
/**
* 启用状态
* {@link fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum}
* {@link EnableStatusEnum}
*/
private Integer enable;
/**
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.res.proxy;
package org.dromara.neutrinoproxy.server.controller.res.proxy;
/**
* 更新启用状态响应
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.res.proxy;
package org.dromara.neutrinoproxy.server.controller.res.proxy;
/**
*
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.res.proxy;
package org.dromara.neutrinoproxy.server.controller.res.proxy;
import lombok.Data;
@@ -1,6 +1,7 @@
package fun.asgc.neutrino.proxy.server.controller.res.proxy;
package org.dromara.neutrinoproxy.server.controller.res.proxy;
import fun.asgc.neutrino.proxy.server.constant.OnlineStatusEnum;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -50,7 +51,7 @@ public class PortMappingDetailRes {
private Integer isOnline;
/**
* 启用状态
* {@link fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum}
* {@link EnableStatusEnum}
*/
private Integer enable;
/**
@@ -19,9 +19,10 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.res.proxy;
package org.dromara.neutrinoproxy.server.controller.res.proxy;
import fun.asgc.neutrino.proxy.server.constant.OnlineStatusEnum;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
import lombok.Data;
import java.util.Date;
@@ -38,6 +39,18 @@ public class PortMappingListRes {
* licenseId
*/
private Integer licenseId;
/**
* 协议
*/
private String protocal;
/**
* 子域名
*/
private String subdomain;
/**
* 域名
*/
private String domain;
/**
* license名称
*/
@@ -73,9 +86,13 @@ public class PortMappingListRes {
private Integer isOnline;
/**
* 启用状态
* {@link fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum}
* {@link EnableStatusEnum}
*/
private Integer enable;
/**
* 描述
*/
private String description;
/**
* 创建时间
*/
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.res.proxy;
package org.dromara.neutrinoproxy.server.controller.res.proxy;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.res.proxy;
package org.dromara.neutrinoproxy.server.controller.res.proxy;
import lombok.Data;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.res.report;
package org.dromara.neutrinoproxy.server.controller.res.report;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.res.report;
package org.dromara.neutrinoproxy.server.controller.res.report;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.res.report;
package org.dromara.neutrinoproxy.server.controller.res.report;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.res.report;
package org.dromara.neutrinoproxy.server.controller.res.report;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.res.report;
package org.dromara.neutrinoproxy.server.controller.res.report;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.res.system;
package org.dromara.neutrinoproxy.server.controller.res.system;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.res.system;
package org.dromara.neutrinoproxy.server.controller.res.system;
import lombok.Data;
@@ -19,9 +19,11 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.res.system;
package org.dromara.neutrinoproxy.server.controller.res.system;
import lombok.Data;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import java.util.Date;
/**
@@ -58,7 +60,7 @@ public class JobInfoListRes {
private String alarmDing;
/**
* 启用状态
* {@link fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum}
* {@link EnableStatusEnum}
*/
private Integer enable;
/**
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.res.system;
package org.dromara.neutrinoproxy.server.controller.res.system;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.res.system;
package org.dromara.neutrinoproxy.server.controller.res.system;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.res.system;
package org.dromara.neutrinoproxy.server.controller.res.system;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.res.system;
package org.dromara.neutrinoproxy.server.controller.res.system;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -0,0 +1,4 @@
package org.dromara.neutrinoproxy.server.controller.res.system;
public class PortGroupCreateRes {
}
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.res.system;
package org.dromara.neutrinoproxy.server.controller.res.system;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.res.system;
package org.dromara.neutrinoproxy.server.controller.res.system;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.res.system;
package org.dromara.neutrinoproxy.server.controller.res.system;
import lombok.Data;
@@ -19,9 +19,10 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.res.system;
package org.dromara.neutrinoproxy.server.controller.res.system;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
@@ -30,6 +31,7 @@ import java.util.Date;
* @author: aoshiguchen
* @date: 2022/8/7
*/
@Accessors(chain = true)
@Data
public class PortPoolListRes {
private Integer id;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.res.system;
package org.dromara.neutrinoproxy.server.controller.res.system;
import lombok.Data;
@@ -0,0 +1,4 @@
package org.dromara.neutrinoproxy.server.controller.res.system;
public class PortPoolUpdateGroupRes {
}
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.res.system;
package org.dromara.neutrinoproxy.server.controller.res.system;
import lombok.Data;
@@ -0,0 +1,26 @@
package org.dromara.neutrinoproxy.server.controller.res.system;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 协议列表响应
* @author: aoshiguchen
* @date: 2023/4/2
*/
@Accessors(chain = true)
@Data
public class ProtocalListRes {
/**
* 协议名称
*/
private String name;
/**
* 启用状态
*/
private Boolean enable;
/**
* 备注
*/
private String remark;
}
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.res.system;
package org.dromara.neutrinoproxy.server.controller.res.system;
/**
*
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.res.system;
package org.dromara.neutrinoproxy.server.controller.res.system;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.res.system;
package org.dromara.neutrinoproxy.server.controller.res.system;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.res.system;
package org.dromara.neutrinoproxy.server.controller.res.system;
import lombok.Data;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.res.system;
package org.dromara.neutrinoproxy.server.controller.res.system;
/**
*
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.controller.res.system;
package org.dromara.neutrinoproxy.server.controller.res.system;
/**
*
@@ -1,8 +1,8 @@
package fun.asgc.neutrino.proxy.server.dal;
package org.dromara.neutrinoproxy.server.dal;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.ClientConnectRecordDO;
import org.dromara.neutrinoproxy.server.dal.entity.ClientConnectRecordDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Date;
@@ -19,11 +19,11 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.dal;
package org.dromara.neutrinoproxy.server.dal;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.FlowReportDayDO;
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportDayDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Date;
@@ -19,11 +19,11 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.dal;
package org.dromara.neutrinoproxy.server.dal;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.FlowReportHourDO;
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportHourDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Date;
@@ -19,11 +19,11 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.dal;
package org.dromara.neutrinoproxy.server.dal;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.FlowReportMinuteDO;
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportMinuteDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Date;
@@ -19,11 +19,11 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.dal;
package org.dromara.neutrinoproxy.server.dal;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.FlowReportMonthDO;
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportMonthDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
@@ -19,12 +19,12 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.dal;
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 fun.asgc.neutrino.proxy.server.dal.entity.JobInfoDO;
import org.dromara.neutrinoproxy.server.dal.entity.JobInfoDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Date;
@@ -19,11 +19,11 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.dal;
package org.dromara.neutrinoproxy.server.dal;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.JobLogDO;
import org.dromara.neutrinoproxy.server.dal.entity.JobLogDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Date;
@@ -19,12 +19,12 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.dal;
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 fun.asgc.neutrino.proxy.server.dal.entity.LicenseDO;
import org.dromara.neutrinoproxy.server.dal.entity.LicenseDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Date;
@@ -72,7 +72,7 @@ public interface LicenseMapper extends BaseMapper<LicenseDO> {
default void updateOnlineStatus(Integer isOnline, Date updateTime) {
this.update(null, new LambdaUpdateWrapper<LicenseDO>()
.set(LicenseDO::getIsOnline, updateTime)
.set(LicenseDO::getIsOnline, isOnline)
.set(LicenseDO::getUpdateTime, updateTime)
);
}
@@ -1,10 +1,10 @@
package fun.asgc.neutrino.proxy.server.dal;
package org.dromara.neutrinoproxy.server.dal;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import fun.asgc.neutrino.proxy.server.controller.req.system.PortGroupListReq;
import fun.asgc.neutrino.proxy.server.controller.res.system.PortGroupListRes;
import fun.asgc.neutrino.proxy.server.dal.entity.PortGroupDO;
import org.dromara.neutrinoproxy.server.controller.req.system.PortGroupListReq;
import org.dromara.neutrinoproxy.server.controller.res.system.PortGroupListRes;
import org.dromara.neutrinoproxy.server.dal.entity.PortGroupDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Date;
@@ -1,12 +1,13 @@
package fun.asgc.neutrino.proxy.server.dal;
package org.dromara.neutrinoproxy.server.dal;
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 fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum;
import fun.asgc.neutrino.proxy.server.controller.req.proxy.PortMappingListReq;
import fun.asgc.neutrino.proxy.server.dal.entity.PortMappingDO;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import org.dromara.neutrinoproxy.server.controller.req.proxy.PortMappingListReq;
import org.dromara.neutrinoproxy.server.dal.entity.PortMappingDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@@ -42,6 +43,18 @@ public interface PortMappingMapper extends BaseMapper<PortMappingDO> {
);
}
/**
* 校验子域名是否重复
* @param subdomain
* @return
*/
default Boolean checkRepeatBySubdomain(String subdomain, Set<Integer> excludeIds) {
return StringUtils.isEmpty(subdomain)? Boolean.FALSE : this.selectCount(new LambdaQueryWrapper<PortMappingDO>()
.eq(PortMappingDO::getSubdomain, subdomain)
.notIn(!CollectionUtil.isEmpty(excludeIds), PortMappingDO::getId, excludeIds)
).intValue() > 0;
}
default List<PortMappingDO> findEnableListByLicenseId(Integer licenseId) {
return this.selectList(new LambdaQueryWrapper<PortMappingDO>()
.eq(PortMappingDO::getLicenseId, licenseId)
@@ -19,14 +19,14 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.dal;
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 fun.asgc.neutrino.proxy.server.controller.req.system.PortPoolListReq;
import fun.asgc.neutrino.proxy.server.controller.res.system.PortPoolListRes;
import fun.asgc.neutrino.proxy.server.dal.entity.PortPoolDO;
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;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@@ -1,8 +1,12 @@
package fun.asgc.neutrino.proxy.server.dal;
package org.dromara.neutrinoproxy.server.dal;
import fun.asgc.neutrino.proxy.server.controller.res.report.*;
import fun.asgc.neutrino.proxy.server.service.bo.FlowBO;
import fun.asgc.neutrino.proxy.server.service.bo.SingleDayFlowBO;
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;
import org.dromara.neutrinoproxy.server.controller.res.report.UserFlowMonthReportRes;
import org.dromara.neutrinoproxy.server.controller.res.report.UserFlowReportRes;
import org.dromara.neutrinoproxy.server.service.bo.FlowBO;
import org.dromara.neutrinoproxy.server.service.bo.SingleDayFlowBO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@@ -19,11 +19,11 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.dal;
package org.dromara.neutrinoproxy.server.dal;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.UserLoginRecordDO;
import org.dromara.neutrinoproxy.server.dal.entity.UserLoginRecordDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Date;
@@ -1,9 +1,9 @@
package fun.asgc.neutrino.proxy.server.dal;
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 fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
@@ -19,12 +19,12 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.dal;
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 fun.asgc.neutrino.proxy.server.dal.entity.UserTokenDO;
import org.dromara.neutrinoproxy.server.dal.entity.UserTokenDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Date;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.dal.entity;
package org.dromara.neutrinoproxy.server.dal.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.dal.entity;
package org.dromara.neutrinoproxy.server.dal.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.dal.entity;
package org.dromara.neutrinoproxy.server.dal.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.dal.entity;
package org.dromara.neutrinoproxy.server.dal.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.dal.entity;
package org.dromara.neutrinoproxy.server.dal.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.dal.entity;
package org.dromara.neutrinoproxy.server.dal.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
@@ -28,6 +28,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import java.util.Date;
@@ -52,7 +53,7 @@ public class JobInfoDO {
private String param;
/**
* 启用状态
* {@link fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum}
* {@link EnableStatusEnum}
*/
private Integer enable;
/**
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.dal.entity;
package org.dromara.neutrinoproxy.server.dal.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -19,13 +19,14 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.dal.entity;
package org.dromara.neutrinoproxy.server.dal.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import fun.asgc.neutrino.proxy.server.constant.OnlineStatusEnum;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;
@@ -64,7 +65,7 @@ public class LicenseDO {
private Integer isOnline;
/**
* 启用状态
* {@link fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum}
* {@link EnableStatusEnum}
*/
private Integer enable;
/**
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.dal.entity;
package org.dromara.neutrinoproxy.server.dal.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -19,12 +19,13 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.dal.entity;
package org.dromara.neutrinoproxy.server.dal.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import fun.asgc.neutrino.proxy.server.constant.OnlineStatusEnum;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;
@@ -47,6 +48,14 @@ public class PortMappingDO {
* licenseId
*/
private Integer licenseId;
/**
* 协议
*/
private String protocal;
/**
* 子域名
*/
private String subdomain;
/**
* 服务端端口
*/
@@ -59,6 +68,11 @@ public class PortMappingDO {
* 客户端端口
*/
private Integer clientPort;
/**
* 描述
*/
private String description;
/**
* 是否在线
* {@link OnlineStatusEnum}
@@ -66,7 +80,7 @@ public class PortMappingDO {
private Integer isOnline;
/**
* 启用状态
* {@link fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum}
* {@link EnableStatusEnum}
*/
private Integer enable;
/**
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.dal.entity;
package org.dromara.neutrinoproxy.server.dal.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.dal.entity;
package org.dromara.neutrinoproxy.server.dal.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -10,7 +10,7 @@
* in accordance with the terms of the contract agreement
* you entered into with Zeyi inc.
*/
package fun.asgc.neutrino.proxy.server.dal.entity;
package org.dromara.neutrinoproxy.server.dal.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.dal.entity;
package org.dromara.neutrinoproxy.server.dal.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -1,14 +1,14 @@
package fun.asgc.neutrino.proxy.server.job;
package org.dromara.neutrinoproxy.server.job;
import com.alibaba.fastjson.JSONObject;
import fun.asgc.neutrino.proxy.core.util.DateUtil;
import fun.asgc.neutrino.proxy.server.dal.*;
import fun.asgc.solon.extend.job.IJobHandler;
import fun.asgc.solon.extend.job.annotation.JobHandler;
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.solonplugins.job.IJobHandler;
import org.dromara.solonplugins.job.annotation.JobHandler;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
@@ -1,8 +1,8 @@
package fun.asgc.neutrino.proxy.server.job;
package org.dromara.neutrinoproxy.server.job;
import fun.asgc.solon.extend.job.IJobHandler;
import fun.asgc.solon.extend.job.annotation.JobHandler;
import lombok.extern.slf4j.Slf4j;
import org.dromara.solonplugins.job.IJobHandler;
import org.dromara.solonplugins.job.annotation.JobHandler;
import org.noear.solon.annotation.Component;
/**
@@ -17,6 +17,6 @@ public class DemoJob implements IJobHandler {
@Override
public void execute(String param) throws Exception {
System.out.println("DemoJob execute param:" + param);
log.debug("DemoJob execute param: {}", param);
}
}
@@ -19,21 +19,21 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.job;
package org.dromara.neutrinoproxy.server.job;
import cn.hutool.core.collection.CollectionUtil;
import fun.asgc.neutrino.proxy.core.util.DateUtil;
import fun.asgc.neutrino.proxy.server.dal.FlowReportDayMapper;
import fun.asgc.neutrino.proxy.server.dal.FlowReportHourMapper;
import fun.asgc.neutrino.proxy.server.dal.FlowReportMinuteMapper;
import fun.asgc.neutrino.proxy.server.dal.LicenseMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.FlowReportDayDO;
import fun.asgc.neutrino.proxy.server.dal.entity.FlowReportHourDO;
import fun.asgc.neutrino.proxy.server.service.FlowReportService;
import fun.asgc.solon.extend.job.IJobHandler;
import fun.asgc.solon.extend.job.annotation.JobHandler;
import org.dromara.neutrinoproxy.core.util.DateUtil;
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.LicenseMapper;
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportDayDO;
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportHourDO;
import org.dromara.neutrinoproxy.server.service.FlowReportService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.solonplugins.job.IJobHandler;
import org.dromara.solonplugins.job.annotation.JobHandler;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
@@ -1,17 +1,17 @@
package fun.asgc.neutrino.proxy.server.job;
package org.dromara.neutrinoproxy.server.job;
import cn.hutool.core.collection.CollectionUtil;
import fun.asgc.neutrino.proxy.core.util.DateUtil;
import fun.asgc.neutrino.proxy.server.dal.FlowReportHourMapper;
import fun.asgc.neutrino.proxy.server.dal.FlowReportMinuteMapper;
import fun.asgc.neutrino.proxy.server.dal.LicenseMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.FlowReportHourDO;
import fun.asgc.neutrino.proxy.server.dal.entity.FlowReportMinuteDO;
import fun.asgc.neutrino.proxy.server.service.FlowReportService;
import fun.asgc.solon.extend.job.IJobHandler;
import fun.asgc.solon.extend.job.annotation.JobHandler;
import org.dromara.neutrinoproxy.core.util.DateUtil;
import org.dromara.neutrinoproxy.server.dal.FlowReportHourMapper;
import org.dromara.neutrinoproxy.server.dal.FlowReportMinuteMapper;
import org.dromara.neutrinoproxy.server.dal.LicenseMapper;
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportHourDO;
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportMinuteDO;
import org.dromara.neutrinoproxy.server.service.FlowReportService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.solonplugins.job.IJobHandler;
import org.dromara.solonplugins.job.annotation.JobHandler;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
@@ -1,15 +1,15 @@
package fun.asgc.neutrino.proxy.server.job;
package org.dromara.neutrinoproxy.server.job;
import cn.hutool.core.collection.CollectionUtil;
import fun.asgc.neutrino.proxy.core.util.DateUtil;
import fun.asgc.neutrino.proxy.server.dal.FlowReportMinuteMapper;
import fun.asgc.neutrino.proxy.server.dal.LicenseMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.FlowReportMinuteDO;
import fun.asgc.neutrino.proxy.server.dal.entity.LicenseDO;
import fun.asgc.neutrino.proxy.server.service.FlowReportService;
import fun.asgc.solon.extend.job.IJobHandler;
import fun.asgc.solon.extend.job.annotation.JobHandler;
import org.dromara.neutrinoproxy.core.util.DateUtil;
import org.dromara.neutrinoproxy.server.dal.FlowReportMinuteMapper;
import org.dromara.neutrinoproxy.server.dal.LicenseMapper;
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportMinuteDO;
import org.dromara.neutrinoproxy.server.dal.entity.LicenseDO;
import org.dromara.neutrinoproxy.server.service.FlowReportService;
import lombok.extern.slf4j.Slf4j;
import org.dromara.solonplugins.job.IJobHandler;
import org.dromara.solonplugins.job.annotation.JobHandler;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
@@ -1,15 +1,15 @@
package fun.asgc.neutrino.proxy.server.job;
package org.dromara.neutrinoproxy.server.job;
import cn.hutool.core.collection.CollectionUtil;
import fun.asgc.neutrino.proxy.core.util.DateUtil;
import fun.asgc.neutrino.proxy.server.dal.*;
import fun.asgc.neutrino.proxy.server.dal.entity.FlowReportDayDO;
import fun.asgc.neutrino.proxy.server.dal.entity.FlowReportMonthDO;
import fun.asgc.neutrino.proxy.server.service.FlowReportService;
import fun.asgc.solon.extend.job.IJobHandler;
import fun.asgc.solon.extend.job.annotation.JobHandler;
import org.dromara.neutrinoproxy.core.util.DateUtil;
import org.dromara.neutrinoproxy.server.dal.*;
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportDayDO;
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportMonthDO;
import org.dromara.neutrinoproxy.server.service.FlowReportService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.solonplugins.job.IJobHandler;
import org.dromara.solonplugins.job.annotation.JobHandler;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
@@ -20,9 +20,9 @@
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.proxy.core;
package org.dromara.neutrinoproxy.server.proxy.core;
import fun.asgc.neutrino.proxy.server.proxy.domain.MetricsCollector;
import org.dromara.neutrinoproxy.server.proxy.domain.MetricsCollector;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
@@ -20,18 +20,19 @@
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.proxy.core;
package org.dromara.neutrinoproxy.server.proxy.core;
import fun.asgc.neutrino.proxy.core.Constants;
import fun.asgc.neutrino.proxy.core.ProxyMessage;
import fun.asgc.neutrino.proxy.core.dispatcher.Dispatcher;
import fun.asgc.neutrino.proxy.server.constant.ClientConnectTypeEnum;
import fun.asgc.neutrino.proxy.server.constant.SuccessCodeEnum;
import fun.asgc.neutrino.proxy.server.dal.entity.ClientConnectRecordDO;
import fun.asgc.neutrino.proxy.server.proxy.domain.CmdChannelAttachInfo;
import fun.asgc.neutrino.proxy.server.service.ClientConnectRecordService;
import fun.asgc.neutrino.proxy.server.service.ProxyMutualService;
import fun.asgc.neutrino.proxy.server.util.ProxyUtil;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.dromara.neutrinoproxy.core.dispatcher.Dispatcher;
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
import org.dromara.neutrinoproxy.server.constant.ClientConnectTypeEnum;
import org.dromara.neutrinoproxy.server.constant.SuccessCodeEnum;
import org.dromara.neutrinoproxy.server.dal.entity.ClientConnectRecordDO;
import org.dromara.neutrinoproxy.server.proxy.domain.CmdChannelAttachInfo;
import org.dromara.neutrinoproxy.server.service.ClientConnectRecordService;
import org.dromara.neutrinoproxy.server.service.ProxyMutualService;
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.timeout.IdleStateEvent;
@@ -47,15 +48,23 @@ import java.util.Date;
* @date: 2022/6/16
*/
@Slf4j
public class ServerChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
public class ProxyTunnelChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
private static volatile Dispatcher<ChannelHandlerContext, ProxyMessage> dispatcher;
private static volatile Boolean transferLogEnable = Boolean.FALSE;
public ServerChannelHandler() {
public ProxyTunnelChannelHandler() {
dispatcher = Solon.context().getBean(Dispatcher.class);
ProxyConfig proxyConfig = Solon.context().getBean(ProxyConfig.class);
if (null != proxyConfig.getTunnel() && 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("Server CmdChannel recieved proxy message, type is {}", proxyMessage.getType());
}
dispatcher.dispatch(ctx, proxyMessage);
}
@@ -71,33 +80,42 @@ public class ServerChannelHandler extends SimpleChannelInboundHandler<ProxyMessa
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Channel userChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (userChannel != null && userChannel.isActive()) {
Channel visitorChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (null != visitorChannel) {
Integer licenseId = ctx.channel().attr(Constants.LICENSE_ID).get();
String visitorId = ctx.channel().attr(Constants.VISITOR_ID).get();
Channel cmdChannel = ProxyUtil.getCmdChannelByLicenseId(licenseId);
if (cmdChannel != null) {
if (null != cmdChannel) {
ProxyUtil.removeVisitorChannelFromCmdChannel(cmdChannel, visitorId);
}
// 数据发送完成后再关闭连接解决http1.0数据传输问题
userChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
userChannel.close();
if (visitorChannel.isActive()) {
// 数据发送完成后再关闭连接解决http1.0数据传输问题
visitorChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
visitorChannel.close();
}
} else {
CmdChannelAttachInfo cmdChannelAttachInfo = ProxyUtil.getAttachInfo(ctx.channel());
if (null != cmdChannelAttachInfo) {
Solon.context().getBean(ProxyMutualService.class).offline(cmdChannelAttachInfo);
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())
);
Channel curCmdChannel = ProxyUtil.getCmdChannelByLicenseId(cmdChannelAttachInfo.getLicenseId());
// 客户端切换网络后连接断开但服务端还未触发断开事件此时客户端重连上了然后服务端触发了断开此时不应该更新在线状态
if (curCmdChannel == ctx.channel()) {
Solon.context().getBean(ProxyMutualService.class).offline(cmdChannelAttachInfo);
ProxyUtil.removeCmdChannel(ctx.channel());
// 防止下次换一个客户端无法连接的情况
ProxyUtil.removeClientIdByLicenseId(cmdChannelAttachInfo.getLicenseId());
}
}
ProxyUtil.removeCmdChannel(ctx.channel());
// 即便是因为上述原因断开断开的日志依然要记录方便排查问题
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);
@@ -106,6 +124,9 @@ public class ServerChannelHandler extends SimpleChannelInboundHandler<ProxyMessa
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
if (ctx.channel().isActive()) {
ctx.channel().close();
}
}
@Override
@@ -115,11 +136,11 @@ public class ServerChannelHandler extends SimpleChannelInboundHandler<ProxyMessa
switch (event.state()) {
case READER_IDLE:
// 读超时断开连接
log.info("读超时");
log.debug("读超时");
ctx.channel().close();
break;
case WRITER_IDLE:
log.info("写超时");
ctx.channel().writeAndFlush(ProxyMessage.buildHeartbeatMessage());
break;
case ALL_IDLE:
break;
@@ -1,9 +1,10 @@
package fun.asgc.neutrino.proxy.server.proxy.core;
package org.dromara.neutrinoproxy.server.proxy.core;
import fun.asgc.neutrino.proxy.core.ProxyMessageDecoder;
import fun.asgc.neutrino.proxy.core.ProxyMessageEncoder;
import fun.asgc.neutrino.proxy.core.util.FileUtil;
import fun.asgc.neutrino.proxy.server.base.proxy.ProxyConfig;
import io.netty.handler.logging.LoggingHandler;
import org.dromara.neutrinoproxy.core.ProxyMessageDecoder;
import org.dromara.neutrinoproxy.core.ProxyMessageEncoder;
import org.dromara.neutrinoproxy.core.util.FileUtil;
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
@@ -26,29 +27,19 @@ import java.io.InputStream;
import java.security.KeyStore;
/**
*
* 代理隧道服务
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Slf4j
@Component
public class ProxyServerRunner implements EventListener<AppLoadEndEvent> {
public class ProxyTunnelServer implements EventListener<AppLoadEndEvent> {
@Inject
private ProxyConfig proxyConfig;
@Inject("serverBossGroup")
@Inject("tunnelBossGroup")
private NioEventLoopGroup serverBossGroup;
@Inject("serverWorkerGroup")
@Inject("tunnelWorkerGroup")
private NioEventLoopGroup serverWorkerGroup;
@Inject("${neutrino.proxy.server.port}")
private Integer port;
@Inject("${neutrino.proxy.server.ssl-port}")
private Integer sslPort;
@Inject("${neutrino.proxy.server.jks-path}")
private String jksPath;
@Inject("${neutrino.proxy.server.key-store-password}")
private String keyStorePassword;
@Inject("${neutrino.proxy.server.key-manager-password}")
private String keyManagerPassword;
@Override
public void onEvent(AppLoadEndEvent appLoadEndEvent) throws Throwable {
startProxyServer();
@@ -67,15 +58,15 @@ public class ProxyServerRunner implements EventListener<AppLoadEndEvent> {
}
});
try {
bootstrap.bind(port).sync();
log.info("代理服务启动,端口:{}", port);
bootstrap.bind(proxyConfig.getTunnel().getPort()).sync();
log.info("代理服务启动,端口:{}", proxyConfig.getTunnel().getPort());
} catch (Exception e) {
log.error("代理服务异常", e);
}
}
private void startProxyServerForSSL() {
if (null == sslPort) {
if (null == proxyConfig.getTunnel().getSslPort()) {
return;
}
ServerBootstrap bootstrap = new ServerBootstrap();
@@ -88,8 +79,8 @@ public class ProxyServerRunner implements EventListener<AppLoadEndEvent> {
}
});
try {
bootstrap.bind(sslPort).sync();
log.info("代理服务启动,SSL端口: {}", sslPort);
bootstrap.bind(proxyConfig.getTunnel().getSslPort()).sync();
log.info("代理服务启动,SSL端口: {}", proxyConfig.getTunnel().getSslPort());
} catch (Exception e) {
log.error("代理服务异常", e);
}
@@ -97,13 +88,13 @@ public class ProxyServerRunner implements EventListener<AppLoadEndEvent> {
private ChannelHandler createSslHandler() {
try {
InputStream jksInputStream = FileUtil.getInputStream(jksPath);
InputStream jksInputStream = FileUtil.getInputStream(proxyConfig.getTunnel().getJksPath());
SSLContext serverContext = SSLContext.getInstance("TLS");
final KeyStore ks = KeyStore.getInstance("JKS");
ks.load(jksInputStream, keyStorePassword.toCharArray());
ks.load(jksInputStream, proxyConfig.getTunnel().getKeyStorePassword().toCharArray());
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, keyManagerPassword.toCharArray());
kmf.init(ks, proxyConfig.getTunnel().getKeyManagerPassword().toCharArray());
TrustManager[] trustManagers = null;
serverContext.init(kmf.getKeyManagers(), trustManagers, null);
@@ -121,11 +112,14 @@ public class ProxyServerRunner implements EventListener<AppLoadEndEvent> {
}
private void proxyServerCommonInitHandler(SocketChannel ch) {
if (null != proxyConfig.getTunnel().getTransferLogEnable() && proxyConfig.getTunnel().getTransferLogEnable()) {
ch.pipeline().addFirst(new LoggingHandler(ProxyTunnelServer.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 ServerChannelHandler());
ch.pipeline().addLast(new ProxyTunnelChannelHandler());
}
}
@@ -1,11 +1,12 @@
package fun.asgc.neutrino.proxy.server.proxy.core;
package org.dromara.neutrinoproxy.server.proxy.core;
import cn.hutool.core.util.StrUtil;
import fun.asgc.neutrino.proxy.core.Constants;
import fun.asgc.neutrino.proxy.core.ProxyMessage;
import fun.asgc.neutrino.proxy.server.proxy.domain.VisitorChannelAttachInfo;
import fun.asgc.neutrino.proxy.server.service.FlowReportService;
import fun.asgc.neutrino.proxy.server.util.ProxyUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.core.ProxyMessage;
import org.dromara.neutrinoproxy.server.proxy.domain.VisitorChannelAttachInfo;
import org.dromara.neutrinoproxy.server.service.FlowReportService;
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
@@ -14,44 +15,42 @@ import io.netty.channel.SimpleChannelInboundHandler;
import org.noear.solon.Solon;
import java.net.InetSocketAddress;
import java.util.concurrent.atomic.AtomicLong;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
private static AtomicLong visitorIdProducer = new AtomicLong(0);
@Slf4j
public class TcpVisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// 当出现异常就关闭连接
ctx.close();
log.error("VisitorChannel error", cause);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) throws Exception {
// 通知代理客户端
Channel visitorChannel = ctx.channel();
Channel proxyChannel = visitorChannel.attr(Constants.NEXT_CHANNEL).get();
if (proxyChannel == null) {
if (null == proxyChannel) {
// 该端口还没有代理客户端
ctx.channel().close();
} else {
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
String visitorId = ProxyUtil.getVisitorIdByChannel(visitorChannel);
proxyChannel.writeAndFlush(ProxyMessage.buildTransferMessage(visitorId, bytes));
// 增加流量计数
VisitorChannelAttachInfo visitorChannelAttachInfo = ProxyUtil.getAttachInfo(visitorChannel);
Solon.context().getBean(FlowReportService.class).addWriteByte(visitorChannelAttachInfo.getLicenseId(), bytes.length);
return;
}
// 转发代理数据
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
String visitorId = ProxyUtil.getVisitorIdByChannel(visitorChannel);
proxyChannel.writeAndFlush(ProxyMessage.buildTransferMessage(visitorId, bytes));
// 增加流量计数
VisitorChannelAttachInfo visitorChannelAttachInfo = ProxyUtil.getAttachInfo(visitorChannel);
Solon.context().getBean(FlowReportService.class).addWriteByte(visitorChannelAttachInfo.getLicenseId(), bytes.length);
}
@Override
@@ -60,23 +59,26 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
if (cmdChannel == null) {
if (null == cmdChannel) {
// 该端口还没有代理客户端
ctx.channel().close();
} else {
String visitorId = newVisitorId();
String lanInfo = ProxyUtil.getClientLanInfoByServerPort(sa.getPort());
if (StrUtil.isEmpty(lanInfo)) {
ctx.channel().close();
} else {
// 用户连接到代理服务器时设置用户连接不可读等待代理后端服务器连接成功后再改变为可读状态
visitorChannel.config().setOption(ChannelOption.AUTO_READ, false);
ProxyUtil.addVisitorChannelToCmdChannel(cmdChannel, visitorId, visitorChannel, sa.getPort());
cmdChannel.writeAndFlush(ProxyMessage.buildConnectMessage(visitorId).setData(lanInfo.getBytes()));
}
return;
}
// 根据代理服务端端口获取被代理客户端局域网连接信息
String lanInfo = ProxyUtil.getClientLanInfoByServerPort(sa.getPort());
if (StrUtil.isEmpty(lanInfo)) {
ctx.channel().close();
return;
}
// 用户连接到代理服务器时设置用户连接不可读等待代理后端服务器连接成功后再改变为可读状态
visitorChannel.config().setOption(ChannelOption.AUTO_READ, false);
String visitorId = ProxyUtil.newVisitorId();
ProxyUtil.addVisitorChannelToCmdChannel(cmdChannel, visitorId, visitorChannel, sa.getPort());
cmdChannel.writeAndFlush(ProxyMessage.buildConnectMessage(visitorId).setData(lanInfo.getBytes()));
super.channelActive(ctx);
}
@@ -84,21 +86,23 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// 通知代理客户端
Channel userChannel = ctx.channel();
InetSocketAddress sa = (InetSocketAddress) userChannel.localAddress();
Channel visitorChannel = ctx.channel();
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
if (cmdChannel == null) {
if (null == cmdChannel) {
// 该端口还没有代理客户端
ctx.channel().close();
} else {
// 用户连接断开从控制连接中移除
String userId = ProxyUtil.getVisitorIdByChannel(userChannel);
ProxyUtil.removeVisitorChannelFromCmdChannel(cmdChannel, userId);
String visitorId = ProxyUtil.getVisitorIdByChannel(visitorChannel);
ProxyUtil.removeVisitorChannelFromCmdChannel(cmdChannel, visitorId);
Channel proxyChannel = userChannel.attr(Constants.NEXT_CHANNEL).get();
// 删除代理附加对象
ProxyUtil.remoteProxyConnectAttachment(visitorId);
Channel proxyChannel = visitorChannel.attr(Constants.NEXT_CHANNEL).get();
if (proxyChannel != null && proxyChannel.isActive()) {
proxyChannel.attr(Constants.NEXT_CHANNEL).remove();
proxyChannel.attr(Constants.LICENSE_ID).remove();
@@ -106,7 +110,7 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
proxyChannel.config().setOption(ChannelOption.AUTO_READ, true);
// 通知客户端用户连接已经断开
proxyChannel.writeAndFlush(ProxyMessage.buildDisconnectMessage(userId));
proxyChannel.writeAndFlush(ProxyMessage.buildDisconnectMessage(visitorId));
}
}
@@ -121,12 +125,12 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
if (cmdChannel == null) {
if (null == cmdChannel) {
// 该端口还没有代理客户端
ctx.channel().close();
} else {
Channel proxyChannel = visitorChannel.attr(Constants.NEXT_CHANNEL).get();
if (proxyChannel != null) {
if (null != proxyChannel) {
proxyChannel.config().setOption(ChannelOption.AUTO_READ, visitorChannel.isWritable());
}
}
@@ -134,12 +138,4 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
super.channelWritabilityChanged(ctx);
}
/**
* 为访问者连接产生ID
*
* @return
*/
private static String newVisitorId() {
return String.valueOf(visitorIdProducer.incrementAndGet());
}
}
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.proxy.domain;
package org.dromara.neutrinoproxy.server.proxy.domain;
import io.netty.channel.Channel;
import lombok.Data;
@@ -0,0 +1,22 @@
package org.dromara.neutrinoproxy.server.proxy.domain;
import lombok.Data;
import java.io.Serializable;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Data
public class Metrics implements Serializable {
private static final long serialVersionUID = 1L;
private int port;
private long readBytes;
private long wroteBytes;
private long readMsgs;
private long wroteMsgs;
private int channels;
private long timestamp;
}
@@ -20,7 +20,7 @@
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.proxy.domain;
package org.dromara.neutrinoproxy.server.proxy.domain;
import java.util.ArrayList;
import java.util.Iterator;
@@ -0,0 +1,29 @@
package org.dromara.neutrinoproxy.server.proxy.domain;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import java.util.function.BiConsumer;
/**
* 代理连接附件
* @author: aoshiguchen
* @date: 2023/4/2
*/
public class ProxyAttachment {
private Channel channel;
private byte[] bytes;
private BiConsumer<Channel, byte[]> executor;
public ProxyAttachment(Channel channel, byte[] bytes, BiConsumer<Channel, byte[]> executor) {
this.channel = channel;
this.bytes = bytes;
this.executor = executor;
}
public void execute() {
if (null != executor) {
this.executor.accept(channel, bytes);
}
}
}
@@ -1,7 +1,7 @@
package fun.asgc.neutrino.proxy.server.proxy.domain;
package org.dromara.neutrinoproxy.server.proxy.domain;
import cn.hutool.core.collection.CollectionUtil;
import fun.asgc.neutrino.proxy.server.dal.entity.PortMappingDO;
import org.dromara.neutrinoproxy.server.dal.entity.PortMappingDO;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.proxy.domain;
package org.dromara.neutrinoproxy.server.proxy.domain;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -0,0 +1,58 @@
package org.dromara.neutrinoproxy.server.proxy.enhance;
import cn.hutool.core.util.StrUtil;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
import org.dromara.neutrinoproxy.server.proxy.core.BytesMetricsHandler;
import org.dromara.neutrinoproxy.server.proxy.core.ProxyTunnelServer;
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;
/**
* HTTP代理
* @author: aoshiguchen
* @date: 2023/4/2
*/
@Slf4j
@Component
public class HttpProxy implements EventListener<AppLoadEndEvent> {
@Inject
private ProxyConfig proxyConfig;
@Override
public void onEvent(AppLoadEndEvent appLoadEndEvent) throws Throwable {
if (StrUtil.isBlank(proxyConfig.getServer().getDomainName()) || null == proxyConfig.getServer().getHttpProxyPort()) {
log.info("no config domain name,nonsupport http proxy.");
return;
}
this.start();
}
private void start() {
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup())
.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(HttpProxy.class));
}
ch.pipeline().addFirst(new BytesMetricsHandler());
ch.pipeline().addLast(new HttpVisitorChannelHandler(proxyConfig.getServer().getDomainName()));
}
});
bootstrap.bind("0.0.0.0", proxyConfig.getServer().getHttpProxyPort()).sync();
log.info("Http代理服务启动成功!port:{}", proxyConfig.getServer().getHttpProxyPort());
} catch (Exception e) {
log.error("http proxy start err!", e);
}
}
}
@@ -0,0 +1,171 @@
package org.dromara.neutrinoproxy.server.proxy.enhance;
import cn.hutool.core.util.StrUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;
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.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;
/**
* @author: aoshiguchen
* @date: 2023/5/27
*/
@Slf4j
public class HttpVisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
/**
* 域名
*/
private String domainName;
public HttpVisitorChannelHandler(String domainName) {
this.domainName = domainName;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception {
if (StrUtil.isBlank(domainName)) {
ctx.channel().close();
return;
}
byte[] bytes = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(bytes);
byteBuf.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.writeAndFlush(ProxyMessage.buildTransferMessage(ProxyUtil.getVisitorIdByChannel(channel), 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)) {
proxyAttachment.execute();
return;
}
// 用户连接到代理服务器时设置用户连接不可读等待代理后端服务器连接成功后再改变为可读状态
ctx.channel().config().setOption(ChannelOption.AUTO_READ, false);
String host = getHost(bytes);
log.debug("HttpProxy host: {}", host);
if (StringUtils.isBlank(host)) {
ctx.channel().close();
return;
}
if (!host.endsWith(domainName)) {
ctx.channel().close();
return;
}
int index = host.lastIndexOf("." + domainName);
String subdomain = host.substring(0, index);
// 根据域名拿到绑定的映射对应的cmdChannel
Integer serverPort = ProxyUtil.getServerPortBySubdomain(subdomain);
if (null == serverPort) {
ctx.channel().close();
return;
}
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(serverPort);
if (null == cmdChannel) {
ctx.channel().close();
return;
}
String lanInfo = ProxyUtil.getClientLanInfoByServerPort(serverPort);
if (StringUtils.isBlank(lanInfo)) {
ctx.channel().close();
return;
}
visitorId = ProxyUtil.newVisitorId();
ProxyUtil.addVisitorChannelToCmdChannel(cmdChannel, visitorId, ctx.channel(), serverPort);
ProxyUtil.addProxyConnectAttachment(visitorId, proxyAttachment);
cmdChannel.writeAndFlush(ProxyMessage.buildConnectMessage(visitorId).setData(lanInfo.getBytes()));
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// 通知代理客户端
Channel visitorChannel = ctx.channel();
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
if (cmdChannel == null) {
// 该端口还没有代理客户端
ctx.channel().close();
} else {
// 用户连接断开从控制连接中移除
String visitorId = ProxyUtil.getVisitorIdByChannel(visitorChannel);
ProxyUtil.removeVisitorChannelFromCmdChannel(cmdChannel, visitorId);
// 删除代理附加对象
ProxyUtil.remoteProxyConnectAttachment(visitorId);
Channel proxyChannel = visitorChannel.attr(Constants.NEXT_CHANNEL).get();
if (proxyChannel != null && proxyChannel.isActive()) {
proxyChannel.attr(Constants.NEXT_CHANNEL).remove();
proxyChannel.attr(Constants.LICENSE_ID).remove();
proxyChannel.attr(Constants.VISITOR_ID).remove();
proxyChannel.config().setOption(ChannelOption.AUTO_READ, true);
// 通知客户端用户连接已经断开
proxyChannel.writeAndFlush(ProxyMessage.buildDisconnectMessage(visitorId));
}
}
super.channelInactive(ctx);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// 当出现异常就关闭连接
ctx.close();
}
private String getHost(byte[] buf) {
String req = new String(buf);
String[] lines = req.split("\r\n");
String firstLine = lines[0];
if (!(firstLine.endsWith("HTTP/1.1") || firstLine.endsWith("HTTP/1.0"))) {
return null;
}
for (int i = 1; i < lines.length; i++) {
String line = lines[i];
if (!line.startsWith("Host: ")) {
continue;
}
// 域名
String domain = line.substring(6);
// 去掉域名后面的端口号
return domain.replaceAll(":.*", "");
}
return null;
}
}
@@ -0,0 +1,92 @@
package org.dromara.neutrinoproxy.server.proxy.enhance;
import cn.hutool.core.util.StrUtil;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.neutrinoproxy.core.util.FileUtil;
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
import org.dromara.neutrinoproxy.server.proxy.core.BytesMetricsHandler;
import org.dromara.neutrinoproxy.server.proxy.core.ProxyTunnelServer;
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 javax.net.ssl.*;
import java.io.InputStream;
import java.security.KeyStore;
/**
* HTTPS代理
* @author: aoshiguchen
* @date: 2023/4/2
*/
@Slf4j
@Component
public class HttpsProxy implements EventListener<AppLoadEndEvent> {
@Inject
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())) {
log.info("no config domain name,nonsupport https proxy.");
return;
}
this.start();
}
private void start() {
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup())
.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(HttpsProxy.class));
}
ch.pipeline().addLast(createSslHandler());
ch.pipeline().addFirst(new BytesMetricsHandler());
ch.pipeline().addLast(new HttpVisitorChannelHandler(proxyConfig.getServer().getDomainName()));
}
});
bootstrap.bind("0.0.0.0", proxyConfig.getServer().getHttpsProxyPort()).sync();
log.info("Https代理服务启动成功!port:{}", proxyConfig.getServer().getHttpsProxyPort());
} catch (Exception e) {
log.error("https proxy start err!", e);
}
}
private ChannelHandler createSslHandler() {
try {
InputStream jksInputStream = FileUtil.getInputStream(proxyConfig.getServer().getJksPath());
SSLContext serverContext = SSLContext.getInstance("TLS");
final KeyStore ks = KeyStore.getInstance("JKS");
ks.load(jksInputStream, proxyConfig.getServer().getKeyStorePassword().toCharArray());
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, proxyConfig.getServer().getKeyStorePassword().toCharArray());
TrustManager[] trustManagers = null;
serverContext.init(kmf.getKeyManagers(), trustManagers, null);
SSLEngine sslEngine = serverContext.createSSLEngine();
sslEngine.setUseClientMode(false);
sslEngine.setNeedClientAuth(false);
return new SslHandler(sslEngine);
} catch (Exception e) {
log.error("创建SSL处理器失败", e);
e.printStackTrace();
}
return null;
}
}
@@ -20,22 +20,23 @@
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.proxy.handler;
package org.dromara.neutrinoproxy.server.proxy.handler;
import cn.hutool.core.util.StrUtil;
import fun.asgc.neutrino.proxy.core.*;
import fun.asgc.neutrino.proxy.core.dispatcher.Match;
import fun.asgc.neutrino.proxy.server.base.proxy.ProxyConfig;
import fun.asgc.neutrino.proxy.server.constant.ClientConnectTypeEnum;
import fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum;
import fun.asgc.neutrino.proxy.server.constant.OnlineStatusEnum;
import fun.asgc.neutrino.proxy.server.constant.SuccessCodeEnum;
import fun.asgc.neutrino.proxy.server.dal.LicenseMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.ClientConnectRecordDO;
import fun.asgc.neutrino.proxy.server.dal.entity.LicenseDO;
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
import fun.asgc.neutrino.proxy.server.service.*;
import fun.asgc.neutrino.proxy.server.util.ProxyUtil;
import org.dromara.neutrinoproxy.core.*;
import org.dromara.neutrinoproxy.core.*;
import org.dromara.neutrinoproxy.core.dispatcher.Match;
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
import org.dromara.neutrinoproxy.server.constant.ClientConnectTypeEnum;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
import org.dromara.neutrinoproxy.server.constant.SuccessCodeEnum;
import org.dromara.neutrinoproxy.server.dal.LicenseMapper;
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 org.dromara.neutrinoproxy.server.service.*;
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
@@ -78,9 +79,19 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
String ip = ((InetSocketAddress)ctx.channel().remoteAddress()).getAddress().getHostAddress();
Date now = new Date();
String info = proxyMessage.getInfo();
String[] tmp = info.split(",");
String licenseKey = proxyMessage.getInfo();
String clientId = "";
if (tmp.length == 2) {
licenseKey = tmp[0];
clientId = tmp[1];
}
if (StrUtil.isEmpty(licenseKey)) {
log.warn("[客户端连接] license不能为空 info:{} ", info);
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "license不能为空!", licenseKey));
ctx.channel().close();
clientConnectRecordService.add(new ClientConnectRecordDO()
.setIp(ip)
.setType(ClientConnectTypeEnum.CONNECT.getType())
@@ -93,7 +104,9 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
}
LicenseDO licenseDO = licenseService.findByKey(licenseKey);
if (null == licenseDO) {
log.warn("[客户端连接] license不存在 info:{} ", info);
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "license不存在!", licenseKey));
ctx.channel().close();
clientConnectRecordService.add(new ClientConnectRecordDO()
.setIp(ip)
.setType(ClientConnectTypeEnum.CONNECT.getType())
@@ -105,7 +118,9 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
return;
}
if (EnableStatusEnum.DISABLE.getStatus().equals(licenseDO.getEnable())) {
log.warn("[客户端连接] 当前license已被禁用 info:{} ", info);
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "当前license已被禁用!", licenseKey));
ctx.channel().close();
clientConnectRecordService.add(new ClientConnectRecordDO()
.setIp(ip)
.setLicenseId(licenseDO.getId())
@@ -118,7 +133,9 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
}
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));
ctx.channel().close();
clientConnectRecordService.add(new ClientConnectRecordDO()
.setIp(ip)
.setLicenseId(licenseDO.getId())
@@ -131,16 +148,21 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
}
Channel cmdChannel = ProxyUtil.getCmdChannelByLicenseId(licenseDO.getId());
if (null != cmdChannel) {
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "当前license已被另一节点使用!", licenseKey));
clientConnectRecordService.add(new ClientConnectRecordDO()
.setIp(ip)
.setLicenseId(licenseDO.getId())
.setType(ClientConnectTypeEnum.CONNECT.getType())
.setMsg(licenseKey)
.setCode(SuccessCodeEnum.FAIL.getCode())
.setErr("当前license已被另一节点使用!")
.setCreateTime(now));
return;
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));
ctx.channel().close();
clientConnectRecordService.add(new ClientConnectRecordDO()
.setIp(ip)
.setLicenseId(licenseDO.getId())
.setType(ClientConnectTypeEnum.CONNECT.getType())
.setMsg(licenseKey)
.setCode(SuccessCodeEnum.FAIL.getCode())
.setErr("当前license已被另一节点使用!")
.setCreateTime(now));
return;
}
}
// 发送认证成功消息
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.SUCCESS.getCode(), "认证成功!", licenseKey));
@@ -149,10 +171,15 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
.setIp(ip)
.setLicenseId(licenseDO.getId())
.setType(ClientConnectTypeEnum.CONNECT.getType())
.setMsg(licenseKey)
.setMsg(info)
.setCode(SuccessCodeEnum.SUCCESS.getCode())
.setCreateTime(now));
// 设置当前licenseId对应的客户端ID
ProxyUtil.setLicenseIdToClientIdMap(licenseDO.getId(), clientId);
log.warn("[客户端连接] 认证成功 info:{} ", info);
// 更新license在线状态
licenseMapper.updateOnlineStatus(licenseDO.getId(), OnlineStatusEnum.ONLINE.getStatus(), now);
// 初始化VisitorChannel
@@ -1,14 +1,16 @@
package fun.asgc.neutrino.proxy.server.proxy.handler;
package org.dromara.neutrinoproxy.server.proxy.handler;
import cn.hutool.core.util.StrUtil;
import fun.asgc.neutrino.proxy.core.*;
import fun.asgc.neutrino.proxy.core.dispatcher.Match;
import fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum;
import fun.asgc.neutrino.proxy.server.dal.entity.LicenseDO;
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
import fun.asgc.neutrino.proxy.server.service.LicenseService;
import fun.asgc.neutrino.proxy.server.service.UserService;
import fun.asgc.neutrino.proxy.server.util.ProxyUtil;
import org.dromara.neutrinoproxy.core.*;
import org.dromara.neutrinoproxy.core.*;
import org.dromara.neutrinoproxy.core.dispatcher.Match;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import org.dromara.neutrinoproxy.server.dal.entity.LicenseDO;
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 io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
@@ -73,13 +75,20 @@ public class ProxyMessageConnectHandler implements ProxyMessageHandler {
}
Channel visitorChannel = ProxyUtil.getVisitorChannel(cmdChannel, visitorId);
if (visitorChannel != null) {
ctx.channel().attr(Constants.VISITOR_ID).set(visitorId);
ctx.channel().attr(Constants.LICENSE_ID).set(licenseDO.getId());
ctx.channel().attr(Constants.NEXT_CHANNEL).set(visitorChannel);
visitorChannel.attr(Constants.NEXT_CHANNEL).set(ctx.channel());
// 代理客户端与后端服务器连接成功修改用户连接为可读状态
visitorChannel.config().setOption(ChannelOption.AUTO_READ, true);
if (null == visitorChannel) {
return;
}
ctx.channel().attr(Constants.VISITOR_ID).set(visitorId);
ctx.channel().attr(Constants.LICENSE_ID).set(licenseDO.getId());
ctx.channel().attr(Constants.NEXT_CHANNEL).set(visitorChannel);
visitorChannel.attr(Constants.NEXT_CHANNEL).set(ctx.channel());
// 代理客户端与后端服务器连接成功修改用户连接为可读状态
visitorChannel.config().setOption(ChannelOption.AUTO_READ, true);
// 获取代理附加对象
ProxyAttachment proxyAttachment = ProxyUtil.getProxyConnectAttachment(visitorId);
if (null != proxyAttachment) {
proxyAttachment.execute();
}
}
@@ -0,0 +1,51 @@
package org.dromara.neutrinoproxy.server.proxy.handler;
import org.checkerframework.checker.units.qual.C;
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.util.ProxyUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import org.noear.solon.annotation.Component;
/**
*
* @author: aoshiguchen
* @date: 2022/6/16
*/
@Match(type = Constants.ProxyDataTypeName.DISCONNECT)
@Component
public class ProxyMessageDisconnectHandler implements ProxyMessageHandler {
@Override
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
Integer licenseId = ctx.channel().attr(Constants.LICENSE_ID).get();
// licenseId为空说明访问者通道已经关闭无需处理
if (null == licenseId) {
return;
}
// 代理连接没有连上服务器由控制连接发送用户端断开连接消息
Channel visitorChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
if (null != visitorChannel) {
// 数据发送完成后再关闭连接解决http1.0数据传输问题
visitorChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
// String visitorId = proxyMessage.getInfo();
// Channel visitorChannel = ProxyUtil.removeVisitorChannelFromCmdChannel(ctx.channel(), visitorId);
// if (null != visitorChannel) {
// // 数据发送完成后再关闭连接解决http1.0数据传输问题
// visitorChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
// }
}
@Override
public String name() {
return ProxyDataTypeEnum.DISCONNECT.getDesc();
}
}
@@ -1,10 +1,10 @@
package fun.asgc.neutrino.proxy.server.proxy.handler;
package org.dromara.neutrinoproxy.server.proxy.handler;
import fun.asgc.neutrino.proxy.core.Constants;
import fun.asgc.neutrino.proxy.core.ProxyDataTypeEnum;
import fun.asgc.neutrino.proxy.core.ProxyMessage;
import fun.asgc.neutrino.proxy.core.ProxyMessageHandler;
import fun.asgc.neutrino.proxy.core.dispatcher.Match;
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 io.netty.channel.ChannelHandlerContext;
import org.noear.solon.annotation.Component;
@@ -1,13 +1,13 @@
package fun.asgc.neutrino.proxy.server.proxy.handler;
package org.dromara.neutrinoproxy.server.proxy.handler;
import fun.asgc.neutrino.proxy.core.Constants;
import fun.asgc.neutrino.proxy.core.ProxyDataTypeEnum;
import fun.asgc.neutrino.proxy.core.ProxyMessage;
import fun.asgc.neutrino.proxy.core.ProxyMessageHandler;
import fun.asgc.neutrino.proxy.core.dispatcher.Match;
import fun.asgc.neutrino.proxy.server.proxy.domain.VisitorChannelAttachInfo;
import fun.asgc.neutrino.proxy.server.service.FlowReportService;
import fun.asgc.neutrino.proxy.server.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.dromara.neutrinoproxy.server.proxy.domain.VisitorChannelAttachInfo;
import org.dromara.neutrinoproxy.server.service.FlowReportService;
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
@@ -31,6 +31,9 @@ public class ProxyMessageTransferHandler implements ProxyMessageHandler {
buf.writeBytes(proxyMessage.getData());
visitorChannel.writeAndFlush(buf);
// 关闭http响应通道
// ProxyUtil.closeHttpProxyResponseChannel(visitorChannel);
// 增加流量计数
VisitorChannelAttachInfo visitorChannelAttachInfo = ProxyUtil.getAttachInfo(visitorChannel);
Solon.context().getBean(FlowReportService.class).addReadByte(visitorChannelAttachInfo.getLicenseId(), proxyMessage.getData().length);
@@ -1,21 +1,21 @@
package fun.asgc.neutrino.proxy.server.service;
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 fun.asgc.neutrino.proxy.server.base.page.PageInfo;
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
import fun.asgc.neutrino.proxy.server.base.rest.SystemContextHolder;
import fun.asgc.neutrino.proxy.server.controller.req.log.ClientConnectRecordListReq;
import fun.asgc.neutrino.proxy.server.controller.res.log.ClientConnectRecordListRes;
import fun.asgc.neutrino.proxy.server.dal.ClientConnectRecordMapper;
import fun.asgc.neutrino.proxy.server.dal.LicenseMapper;
import fun.asgc.neutrino.proxy.server.dal.UserMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.ClientConnectRecordDO;
import fun.asgc.neutrino.proxy.server.dal.entity.LicenseDO;
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
import org.dromara.neutrinoproxy.server.base.rest.SystemContextHolder;
import org.dromara.neutrinoproxy.server.controller.req.log.ClientConnectRecordListReq;
import org.dromara.neutrinoproxy.server.controller.res.log.ClientConnectRecordListRes;
import org.dromara.neutrinoproxy.server.dal.ClientConnectRecordMapper;
import org.dromara.neutrinoproxy.server.dal.LicenseMapper;
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;
@@ -19,9 +19,9 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.service;
package org.dromara.neutrinoproxy.server.service;
import fun.asgc.neutrino.proxy.core.util.LockUtil;
import org.dromara.neutrinoproxy.core.util.LockUtil;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.annotation.Component;
@@ -1,37 +1,42 @@
package fun.asgc.neutrino.proxy.server.service;
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.google.common.collect.Lists;
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
import fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum;
import fun.asgc.neutrino.proxy.server.constant.ExceptionConstant;
import fun.asgc.neutrino.proxy.server.controller.req.system.JobInfoExecuteReq;
import fun.asgc.neutrino.proxy.server.controller.req.system.JobInfoListReq;
import fun.asgc.neutrino.proxy.server.controller.req.system.JobInfoUpdateEnableStatusReq;
import fun.asgc.neutrino.proxy.server.controller.req.system.JobInfoUpdateReq;
import fun.asgc.neutrino.proxy.server.controller.res.system.JobInfoExecuteRes;
import fun.asgc.neutrino.proxy.server.controller.res.system.JobInfoListRes;
import fun.asgc.neutrino.proxy.server.controller.res.system.JobInfoUpdateEnableStatusRes;
import fun.asgc.neutrino.proxy.server.controller.res.system.JobInfoUpdateRes;
import fun.asgc.neutrino.proxy.server.dal.JobInfoMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.JobInfoDO;
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
import fun.asgc.solon.extend.job.IJobSource;
import fun.asgc.solon.extend.job.JobInfo;
import fun.asgc.solon.extend.job.impl.JobExecutor;
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import org.dromara.neutrinoproxy.server.constant.ExceptionConstant;
import org.dromara.neutrinoproxy.server.controller.req.system.JobInfoExecuteReq;
import org.dromara.neutrinoproxy.server.controller.req.system.JobInfoListReq;
import org.dromara.neutrinoproxy.server.controller.req.system.JobInfoUpdateEnableStatusReq;
import org.dromara.neutrinoproxy.server.controller.req.system.JobInfoUpdateReq;
import org.dromara.neutrinoproxy.server.controller.res.system.JobInfoExecuteRes;
import org.dromara.neutrinoproxy.server.controller.res.system.JobInfoListRes;
import org.dromara.neutrinoproxy.server.controller.res.system.JobInfoUpdateEnableStatusRes;
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.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;
import org.dromara.solonplugins.job.impl.JobExecutor;
import org.noear.solon.Solon;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Init;
import org.noear.solon.annotation.Inject;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
*
@@ -45,6 +50,29 @@ public class JobInfoService implements IJobSource {
private MapperFacade mapperFacade;
@Db
private JobInfoMapper jobInfoMapper;
@Inject
private DataCleanJob dataCleanJob;
@Inject
private DemoJob demoJob;
@Inject
private FlowReportForDayJob flowReportForDayJob;
@Inject
private FlowReportForHourJob flowReportForHourJob;
@Inject
private FlowReportForMinuteJob flowReportForMinuteJob;
@Inject
private FlowReportForMonthJob flowReportForMonthJob;
private Map<String, IJobHandler> jobHandlerMap = new HashMap<>();
@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);
}
public PageInfo<JobInfoListRes> page(PageQuery pageQuery, JobInfoListReq req) {
Page<JobInfoListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
@@ -65,22 +93,15 @@ public class JobInfoService implements IJobSource {
ParamCheckUtil.checkNotNull(jobInfoDO, ExceptionConstant.JOB_INFO_NOT_EXIST);
jobInfoMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
if (EnableStatusEnum.ENABLE.getStatus().equals(req.getEnable())) {
Solon.context().getBean(JobExecutor.class).add(new JobInfo()
.setId(String.valueOf(jobInfoDO.getId()))
.setName(jobInfoDO.getHandler())
.setDesc(jobInfoDO.getDesc())
.setCron(jobInfoDO.getCron())
.setParam(jobInfoDO.getParam())
.setEnable(true)
);
Solon.context().getBean(JobExecutor.class).startById(String.valueOf(req.getId()));
} else {
Solon.context().getBean(JobExecutor.class).remove(String.valueOf(req.getId()));
Solon.context().getBean(JobExecutor.class).stopById(String.valueOf(req.getId()));
}
return new JobInfoUpdateEnableStatusRes();
}
public JobInfoExecuteRes execute(JobInfoExecuteReq req) {
Solon.context().getBean(JobExecutor.class).trigger(String.valueOf(req.getId()), req.getParam());
Solon.context().getBean(JobExecutor.class).triggerById(String.valueOf(req.getId()), req.getParam());
return new JobInfoExecuteRes();
}
@@ -99,6 +120,7 @@ public class JobInfoService implements IJobSource {
.setCron(item.getCron())
.setParam(item.getParam())
.setEnable(EnableStatusEnum.ENABLE.getStatus().equals(item.getEnable()))
.setJobHandler(jobHandlerMap.get(item.getHandler()))
);
}
@@ -19,23 +19,23 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.service;
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 fun.asgc.neutrino.proxy.server.base.page.PageInfo;
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
import fun.asgc.neutrino.proxy.server.controller.req.log.JobLogListReq;
import fun.asgc.neutrino.proxy.server.controller.res.log.JobLogListRes;
import fun.asgc.neutrino.proxy.server.dal.JobLogMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.JobLogDO;
import fun.asgc.solon.extend.job.IJobCallback;
import fun.asgc.solon.extend.job.JobInfo;
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;
@@ -61,7 +61,7 @@ public class JobLogService implements IJobCallback {
String msg = "";
if (null == throwable) {
msg = "执行成功";
log.info("job[id={},name={}]执行完毕", jobInfo.getId(), jobInfo.getName());
log.debug("job[id={},name={}]执行完毕", jobInfo.getId(), jobInfo.getName());
} else {
log.error("job[id={},name={}]执行异常", jobInfo.getId(), jobInfo.getName(), throwable);
msg = "执行异常:\r\n" + ExceptionUtils.getStackTrace(throwable);
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.service;
package org.dromara.neutrinoproxy.server.service;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -6,28 +6,31 @@ import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.google.common.collect.Sets;
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
import fun.asgc.neutrino.proxy.server.base.rest.SystemContextHolder;
import fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum;
import fun.asgc.neutrino.proxy.server.constant.ExceptionConstant;
import fun.asgc.neutrino.proxy.server.constant.OnlineStatusEnum;
import fun.asgc.neutrino.proxy.server.controller.req.proxy.LicenseCreateReq;
import fun.asgc.neutrino.proxy.server.controller.req.proxy.LicenseListReq;
import fun.asgc.neutrino.proxy.server.controller.req.proxy.LicenseUpdateEnableStatusReq;
import fun.asgc.neutrino.proxy.server.controller.req.proxy.LicenseUpdateReq;
import fun.asgc.neutrino.proxy.server.controller.res.proxy.*;
import fun.asgc.neutrino.proxy.server.dal.LicenseMapper;
import fun.asgc.neutrino.proxy.server.dal.UserMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.LicenseDO;
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
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.SystemContextHolder;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import org.dromara.neutrinoproxy.server.constant.ExceptionConstant;
import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
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.dal.LicenseMapper;
import org.dromara.neutrinoproxy.server.dal.UserMapper;
import org.dromara.neutrinoproxy.server.dal.entity.LicenseDO;
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.jetbrains.annotations.Nullable;
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 java.util.*;
import java.util.function.Function;
@@ -39,7 +42,7 @@ import java.util.stream.Collectors;
* @date: 2022/8/6
*/
@Component
public class LicenseService implements Lifecycle {
public class LicenseService implements LifecycleBean {
@Inject
private MapperFacade mapperFacade;
@Db
@@ -48,6 +51,8 @@ public class LicenseService implements Lifecycle {
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());
@@ -84,7 +89,6 @@ public class LicenseService implements Lifecycle {
return licenseList;
}
@Nullable
private List<LicenseListRes> assembleConvertLicenses(List<LicenseDO> list) {
List<LicenseListRes> licenseList = mapperFacade.mapAsList(list, LicenseListRes.class);
if (!CollectionUtil.isEmpty(licenseList)) {
@@ -215,9 +219,15 @@ public class LicenseService implements Lifecycle {
/**
* 服务端项目停止启动时更新在线状态为离线
*/
@Init
public void init() {
licenseMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
}
@Override
public void start() throws Throwable {
licenseMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
}
/**
@@ -1,26 +1,26 @@
package fun.asgc.neutrino.proxy.server.service;
package org.dromara.neutrinoproxy.server.service;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
import fun.asgc.neutrino.proxy.server.base.rest.ServiceException;
import fun.asgc.neutrino.proxy.server.base.rest.SystemContextHolder;
import fun.asgc.neutrino.proxy.server.constant.Constants;
import fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum;
import fun.asgc.neutrino.proxy.server.constant.ExceptionConstant;
import fun.asgc.neutrino.proxy.server.controller.req.system.PortGroupCreateReq;
import fun.asgc.neutrino.proxy.server.controller.req.system.PortGroupListReq;
import fun.asgc.neutrino.proxy.server.controller.req.system.PortGroupUpdateEnableStatusReq;
import fun.asgc.neutrino.proxy.server.controller.res.system.PortGroupCreateRes;
import fun.asgc.neutrino.proxy.server.controller.res.system.PortGroupListRes;
import fun.asgc.neutrino.proxy.server.controller.res.system.PortGroupUpdateEnableStatusRes;
import fun.asgc.neutrino.proxy.server.dal.PortGroupMapper;
import fun.asgc.neutrino.proxy.server.dal.PortPoolMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.PortGroupDO;
import fun.asgc.neutrino.proxy.server.dal.entity.PortPoolDO;
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
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.Constants;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import org.dromara.neutrinoproxy.server.constant.ExceptionConstant;
import org.dromara.neutrinoproxy.server.controller.req.system.PortGroupCreateReq;
import org.dromara.neutrinoproxy.server.controller.req.system.PortGroupListReq;
import org.dromara.neutrinoproxy.server.controller.req.system.PortGroupUpdateEnableStatusReq;
import org.dromara.neutrinoproxy.server.controller.res.system.PortGroupCreateRes;
import org.dromara.neutrinoproxy.server.controller.res.system.PortGroupListRes;
import org.dromara.neutrinoproxy.server.controller.res.system.PortGroupUpdateEnableStatusRes;
import org.dromara.neutrinoproxy.server.dal.PortGroupMapper;
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;
@@ -0,0 +1,309 @@
package org.dromara.neutrinoproxy.server.service;
import cn.hutool.core.collection.CollectionUtil;
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.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;
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
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.constant.NetworkProtocolEnum;
import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
import org.dromara.neutrinoproxy.server.controller.req.proxy.PortMappingCreateReq;
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.dal.LicenseMapper;
import org.dromara.neutrinoproxy.server.dal.PortMappingMapper;
import org.dromara.neutrinoproxy.server.dal.PortPoolMapper;
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.PortPoolDO;
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
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 java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @author: aoshiguchen
* @date: 2022/8/8
*/
@Component
public class PortMappingService implements LifecycleBean {
@Inject
private MapperFacade mapperFacade;
@Db
private PortMappingMapper portMappingMapper;
@Db
private LicenseMapper licenseMapper;
@Db
private UserMapper userMapper;
@Db
private PortPoolMapper portPoolMapper;
@Inject
private VisitorChannelService visitorChannelService;
@Inject
private PortPoolService portPoolService;
@Inject
private ProxyConfig proxyConfig;
@Inject
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() + "%");
}
// 协议名称转换
if (StringUtils.isNotBlank(req.getProtocal())) {
NetworkProtocolEnum networkProtocolEnum = NetworkProtocolEnum.of(req.getProtocal());
req.setProtocal(networkProtocolEnum.getDesc());
}
List<PortMappingDO> list = portMappingMapper.selectPortMappingByCondition(req);
List<PortMappingListRes> respList = mapperFacade.mapAsList(list, PortMappingListRes.class);
if (CollectionUtils.isEmpty(list)) {
return PageInfo.of(respList, result.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());
}
Set<Integer> userIds = licenseList.stream().map(LicenseDO::getUserId).collect(Collectors.toSet());
List<UserDO> userList = userMapper.findByIds(userIds);
Map<Integer, LicenseDO> licenseMap = licenseList.stream().collect(Collectors.toMap(LicenseDO::getId, Function.identity()));
Map<Integer, UserDO> userMap = userList.stream().collect(Collectors.toMap(UserDO::getId, Function.identity()));
respList.forEach(item -> {
LicenseDO license = licenseMap.get(item.getLicenseId());
if (null == license) {
return;
}
item.setLicenseName(license.getName());
item.setUserId(license.getUserId());
UserDO user = userMap.get(license.getUserId());
if (null == user) {
return;
}
item.setUserName(user.getName());
if (StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) && StrUtil.isNotBlank(item.getSubdomain())) {
item.setDomain(item.getSubdomain() + "." + proxyConfig.getServer().getDomainName());
}
if (NetworkProtocolEnum.HTTP.getDesc().equals(item.getProtocal())) {
item.setProtocal("HTTP(S)");
}
});
//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());
}
public PortMappingCreateRes create(PortMappingCreateReq req) {
LicenseDO licenseDO = licenseMapper.findById(req.getLicenseId());
ParamCheckUtil.checkNotNull(licenseDO, ExceptionConstant.LICENSE_NOT_EXIST);
if (!SystemContextHolder.isAdmin()) {
// 临时处理如果当前用户不是管理员则操作userId不能为1
ParamCheckUtil.checkExpression(!licenseDO.getUserId().equals(1), ExceptionConstant.NO_PERMISSION_VISIT);
}
PortPoolDO portPoolDO = portPoolMapper.findByPort(req.getServerPort());
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
ParamCheckUtil.checkExpression(null == portMappingMapper.findByPort(req.getServerPort(), null), ExceptionConstant.PORT_CANNOT_REPEAT_MAPPING, req.getServerPort());
ParamCheckUtil.checkExpression(!portMappingMapper.checkRepeatBySubdomain(req.getSubdomain(), null), ExceptionConstant.PORT_MAPPING_SUBDONAME_CONNOT_REPEAT);
Date now = new Date();
PortMappingDO portMappingDO = new PortMappingDO();
portMappingDO.setLicenseId(req.getLicenseId());
portMappingDO.setProtocal(req.getProtocal());
portMappingDO.setSubdomain(req.getSubdomain());
portMappingDO.setServerPort(req.getServerPort());
portMappingDO.setClientIp(req.getClientIp());
portMappingDO.setClientPort(req.getClientPort());
portMappingDO.setDescription(req.getDescription());
portMappingDO.setIsOnline(OnlineStatusEnum.OFFLINE.getStatus());
portMappingDO.setEnable(EnableStatusEnum.ENABLE.getStatus());
portMappingDO.setCreateTime(now);
portMappingDO.setUpdateTime(now);
portMappingMapper.insert(portMappingDO);
// 更新VisitorChannel
visitorChannelService.addVisitorChannelByPortMapping(portMappingDO);
// 更新域名映射
if (NetworkProtocolEnum.isHttp(portMappingDO.getProtocal()) && StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) && StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
ProxyUtil.setSubdomainToServerPort(portMappingDO.getSubdomain(), portMappingDO.getServerPort());
}
return new PortMappingCreateRes();
}
public PortMappingUpdateRes update(PortMappingUpdateReq req) {
LicenseDO licenseDO = licenseMapper.findById(req.getLicenseId());
ParamCheckUtil.checkNotNull(licenseDO, ExceptionConstant.LICENSE_NOT_EXIST);
if (!SystemContextHolder.isAdmin()) {
// 临时处理如果当前用户不是管理员则操作userId不能为1
ParamCheckUtil.checkExpression(!licenseDO.getUserId().equals(1), ExceptionConstant.NO_PERMISSION_VISIT);
}
PortPoolDO portPoolDO = portPoolMapper.findByPort(req.getServerPort());
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
ParamCheckUtil.checkExpression(null == portMappingMapper.findByPort(req.getServerPort(), Sets.newHashSet(req.getId())), ExceptionConstant.PORT_CANNOT_REPEAT_MAPPING, req.getServerPort());
ParamCheckUtil.checkExpression(!portMappingMapper.checkRepeatBySubdomain(req.getSubdomain(), Sets.newHashSet(req.getId())), ExceptionConstant.PORT_MAPPING_SUBDONAME_CONNOT_REPEAT);
// 查询原端口映射
PortMappingDO oldPortMappingDO = portMappingMapper.findById(req.getId());
ParamCheckUtil.checkNotNull(oldPortMappingDO, ExceptionConstant.PORT_MAPPING_NOT_EXIST);
PortMappingDO portMappingDO = new PortMappingDO();
portMappingDO.setId(req.getId());
portMappingDO.setProtocal(req.getProtocal());
portMappingDO.setSubdomain(req.getSubdomain());
portMappingDO.setLicenseId(req.getLicenseId());
portMappingDO.setServerPort(req.getServerPort());
portMappingDO.setClientIp(req.getClientIp());
portMappingDO.setClientPort(req.getClientPort());
portMappingDO.setDescription(req.getDescription());
portMappingDO.setUpdateTime(new Date());
portMappingDO.setEnable(EnableStatusEnum.ENABLE.getStatus());
portMappingMapper.updateById(portMappingDO);
// 更新VisitorChannel
visitorChannelService.updateVisitorChannelByPortMapping(oldPortMappingDO, portMappingDO);
// 删除老的域名映射
if (NetworkProtocolEnum.isHttp(oldPortMappingDO.getProtocal()) && StrUtil.isNotBlank(oldPortMappingDO.getSubdomain())) {
ProxyUtil.removeSubdomainToServerPort(oldPortMappingDO.getSubdomain());
}
// 更新域名映射
if (NetworkProtocolEnum.isHttp(portMappingDO.getProtocal()) && StrUtil.isNotBlank(proxyConfig.getServer().getDomainName()) && StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
ProxyUtil.setSubdomainToServerPort(portMappingDO.getSubdomain(), portMappingDO.getServerPort());
}
return new PortMappingUpdateRes();
}
public PortMappingDetailRes detail(Integer id) {
PortMappingDO portMappingDO = portMappingMapper.findById(id);
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());
LicenseDO license = licenseMapper.findById(portMappingDO.getLicenseId());
if (null != license) {
res.setLicenseName(license.getName());
res.setUserId(license.getUserId());
UserDO user = userMapper.findById(license.getUserId());
if (null != user) {
res.setUserName(user.getName());
}
}
return res;
}
public PortMappingUpdateEnableStatusRes updateEnableStatus(PortMappingUpdateEnableStatusReq req) {
PortMappingDO portMappingDO = portMappingMapper.findById(req.getId());
ParamCheckUtil.checkNotNull(portMappingDO, ExceptionConstant.PORT_MAPPING_NOT_EXIST);
LicenseDO licenseDO = licenseMapper.findById(portMappingDO.getLicenseId());
ParamCheckUtil.checkNotNull(licenseDO, ExceptionConstant.LICENSE_NOT_EXIST);
if (!SystemContextHolder.isAdmin()) {
ParamCheckUtil.checkExpression(!licenseDO.getUserId().equals(1), ExceptionConstant.NO_PERMISSION_VISIT);
}
portMappingMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
// 更新VisitorChannel
portMappingDO.setEnable(req.getEnable());
if (EnableStatusEnum.ENABLE == EnableStatusEnum.of(req.getEnable())) {
visitorChannelService.addVisitorChannelByPortMapping(portMappingDO);
} else {
visitorChannelService.removeVisitorChannelByPortMapping(portMappingDO);
}
return new PortMappingUpdateEnableStatusRes();
}
public void delete(Integer id) {
PortMappingDO portMappingDO = portMappingMapper.findById(id);
ParamCheckUtil.checkNotNull(portMappingDO, ExceptionConstant.PORT_MAPPING_NOT_EXIST);
LicenseDO licenseDO = licenseMapper.findById(portMappingDO.getLicenseId());
if (null != licenseDO && !SystemContextHolder.isAdmin()) {
// 临时处理如果当前用户不是管理员则操作userId不能为1
ParamCheckUtil.checkExpression(!licenseDO.getUserId().equals(1), ExceptionConstant.NO_PERMISSION_VISIT);
}
portMappingMapper.deleteById(id);
// 更新VisitorChannel
visitorChannelService.removeVisitorChannelByPortMapping(portMappingDO);
// 更新域名映射
if (NetworkProtocolEnum.isHttp(portMappingDO.getProtocal()) && StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
ProxyUtil.removeSubdomainToServerPort(portMappingDO.getSubdomain());
}
}
/**
* 根据license查询可用的端口映射列表
*
* @param licenseId
* @return
*/
public List<PortMappingDO> findEnableListByLicenseId(Integer licenseId) {
return portMappingMapper.findEnableListByLicenseId(licenseId);
}
/**
* 服务端项目停止启动时更新在线状态为离线
*/
@Init
public void init() {
portMappingMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
// 未配置域名则不需要处理域名映射逻辑
if (StrUtil.isBlank(proxyConfig.getServer().getDomainName())) {
return;
}
List<PortMappingDO> portMappingDOList = portMappingMapper.selectList(new LambdaQueryWrapper<PortMappingDO>().eq(PortMappingDO::getProtocal, NetworkProtocolEnum.HTTP.getDesc()).isNotNull(PortMappingDO::getSubdomain));
if (CollectionUtil.isEmpty(portMappingDOList)) {
return;
}
portMappingDOList.forEach(item -> {
if (StrUtil.isBlank(item.getSubdomain())) {
return;
}
ProxyUtil.setSubdomainToServerPort(item.getSubdomain(), item.getServerPort());
});
}
@Override
public void start() throws Throwable {
}
/**
* 服务端项目停止启动时更新在线状态为离线
*/
@Override
public void stop() throws Throwable {
portMappingMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
}
}
@@ -1,38 +1,50 @@
package fun.asgc.neutrino.proxy.server.service;
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 fun.asgc.neutrino.proxy.server.base.page.PageInfo;
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
import fun.asgc.neutrino.proxy.server.base.rest.ServiceException;
import fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum;
import fun.asgc.neutrino.proxy.server.constant.ExceptionConstant;
import fun.asgc.neutrino.proxy.server.controller.req.system.*;
import fun.asgc.neutrino.proxy.server.controller.res.system.*;
import fun.asgc.neutrino.proxy.server.dal.LicenseMapper;
import fun.asgc.neutrino.proxy.server.dal.PortGroupMapper;
import fun.asgc.neutrino.proxy.server.dal.PortMappingMapper;
import fun.asgc.neutrino.proxy.server.dal.PortPoolMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.*;
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
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.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.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.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static org.dromara.neutrinoproxy.server.constant.ExceptionConstant.*;
/**
*
* @author: aoshiguchen
* @date: 2022/8/7
*/
@Slf4j
@Component
public class PortPoolService {
@Inject
@@ -83,23 +95,38 @@ public class PortPoolService {
}
public PortPoolCreateRes create(PortPoolCreateReq req) {
PortPoolDO oldPortPoolDO = portPoolMapper.findByPort(req.getPort());
ParamCheckUtil.checkMustNull(oldPortPoolDO, ExceptionConstant.PORT_CANNOT_REPEAT);
PortGroupDO portGroupDO = portGroupMapper.selectById(req.getGroupId());
ParamCheckUtil.checkNotNull(portGroupDO, ExceptionConstant.PORT_GROUP_NAME_DOES_NOT_EXIST);
Date now = new Date();
portPoolMapper.insert(new PortPoolDO()
.setPort(req.getPort())
.setGroupId(req.getGroupId())
.setEnable(EnableStatusEnum.ENABLE.getStatus())
.setCreateTime(now)
.setUpdateTime(now)
);
// 更新visitorChannel
visitorChannelService.updateVisitorChannelByPortPool(req.getPort(), EnableStatusEnum.ENABLE.getStatus());
Consumer<Integer> consumer = port -> {
PortPoolDO oldPortPoolDO = portPoolMapper.findByPort(port);
ParamCheckUtil.checkMustNull(oldPortPoolDO, PORT_CANNOT_REPEAT);
PortGroupDO portGroupDO = portGroupMapper.selectById(req.getGroupId());
ParamCheckUtil.checkNotNull(portGroupDO, PORT_GROUP_NAME_DOES_NOT_EXIST);
Date now = new Date();
portPoolMapper.insert(new PortPoolDO()
.setPort(port)
.setGroupId(req.getGroupId())
.setEnable(EnableStatusEnum.ENABLE.getStatus())
.setCreateTime(now)
.setUpdateTime(now)
);
// 更新visitorChannel
visitorChannelService.updateVisitorChannelByPortPool(port, EnableStatusEnum.ENABLE.getStatus());
};
String[] portArr = StringUtils.split(req.getPort(), "-");
if(portArr.length == 1){
Integer port = Integer.valueOf(portArr[0]);
consumer.accept(port);
}else if(portArr.length == 2){
int min = Integer.parseInt(portArr[0]),max = Integer.parseInt(portArr[1]);
for (int i = min; i <= max; i++) {
try {
consumer.accept(i);
} catch (Exception e) {
log.warn("bulk add port err:{}",e.getMessage());
}
}
}else{
throw ServiceException.create(PORT_RANGE_FAIL);
}
return new PortPoolCreateRes();
}
@@ -152,4 +179,15 @@ public class PortPoolService {
LicenseDO licenseDO = licenseMapper.queryById(req.getLicenseId());
return portPoolMapper.getAvailablePortList(req.getLicenseId(), licenseDO.getUserId());
}
public void deleteBatch(List<Integer> ids) {
List<PortPoolDO> portPoolDOList = portPoolMapper.selectBatchIds(ids);
ParamCheckUtil.checkNotNull(portPoolDOList, ExceptionConstant.PORT_NOT_EXIST);
portPoolMapper.deleteBatchIds(ids);
// 更新visitorChannel
portPoolDOList.stream().forEach(portPoolDO -> {
visitorChannelService.updateVisitorChannelByPortPool(portPoolDO.getPort(), EnableStatusEnum.DISABLE.getStatus());
});
}
}
@@ -0,0 +1,31 @@
package org.dromara.neutrinoproxy.server.service;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum;
import org.dromara.neutrinoproxy.server.controller.res.system.ProtocalListRes;
import org.noear.solon.annotation.Component;
import java.util.List;
/**
* @author: aoshiguchen
* @date: 2023/4/2
*/
@Slf4j
@Component
public class ProtocalService {
/**
* 获取协议列表
* @return
*/
public List<ProtocalListRes> list() {
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("暂不支持")
);
}
}
@@ -19,12 +19,12 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.service;
package org.dromara.neutrinoproxy.server.service;
import fun.asgc.neutrino.proxy.server.constant.OnlineStatusEnum;
import fun.asgc.neutrino.proxy.server.dal.LicenseMapper;
import fun.asgc.neutrino.proxy.server.dal.PortMappingMapper;
import fun.asgc.neutrino.proxy.server.proxy.domain.CmdChannelAttachInfo;
import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
import org.dromara.neutrinoproxy.server.dal.LicenseMapper;
import org.dromara.neutrinoproxy.server.dal.PortMappingMapper;
import org.dromara.neutrinoproxy.server.proxy.domain.CmdChannelAttachInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.solon.annotation.Db;
import org.noear.solon.annotation.Component;
@@ -1,29 +1,29 @@
package fun.asgc.neutrino.proxy.server.service;
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.google.common.collect.Lists;
import fun.asgc.neutrino.proxy.core.util.DateUtil;
import fun.asgc.neutrino.proxy.server.base.db.DbConfig;
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
import fun.asgc.neutrino.proxy.server.constant.Constants;
import fun.asgc.neutrino.proxy.server.constant.OnlineStatusEnum;
import fun.asgc.neutrino.proxy.server.controller.req.report.LicenseFlowMonthReportReq;
import fun.asgc.neutrino.proxy.server.controller.req.report.LicenseFlowReportReq;
import fun.asgc.neutrino.proxy.server.controller.req.report.UserFlowMonthReportReq;
import fun.asgc.neutrino.proxy.server.controller.req.report.UserFlowReportReq;
import fun.asgc.neutrino.proxy.server.controller.res.report.*;
import fun.asgc.neutrino.proxy.server.dal.LicenseMapper;
import fun.asgc.neutrino.proxy.server.dal.PortMappingMapper;
import fun.asgc.neutrino.proxy.server.dal.ReportMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.LicenseDO;
import fun.asgc.neutrino.proxy.server.dal.entity.PortMappingDO;
import fun.asgc.neutrino.proxy.server.service.bo.FlowBO;
import fun.asgc.neutrino.proxy.server.service.bo.SingleDayFlowBO;
import fun.asgc.neutrino.proxy.server.util.FormatUtil;
import org.dromara.neutrinoproxy.core.util.DateUtil;
import org.dromara.neutrinoproxy.server.base.db.DbConfig;
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
import org.dromara.neutrinoproxy.server.constant.Constants;
import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
import org.dromara.neutrinoproxy.server.controller.req.report.LicenseFlowMonthReportReq;
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.dal.LicenseMapper;
import org.dromara.neutrinoproxy.server.dal.PortMappingMapper;
import org.dromara.neutrinoproxy.server.dal.ReportMapper;
import org.dromara.neutrinoproxy.server.dal.entity.LicenseDO;
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;
@@ -1,18 +1,18 @@
package fun.asgc.neutrino.proxy.server.service;
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 fun.asgc.neutrino.proxy.server.base.page.PageInfo;
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
import fun.asgc.neutrino.proxy.server.controller.req.log.UserLoginRecordListReq;
import fun.asgc.neutrino.proxy.server.controller.res.log.UserLoginRecordListRes;
import fun.asgc.neutrino.proxy.server.dal.UserLoginRecordMapper;
import fun.asgc.neutrino.proxy.server.dal.UserMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
import fun.asgc.neutrino.proxy.server.dal.entity.UserLoginRecordDO;
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;
import org.dromara.neutrinoproxy.server.controller.res.log.UserLoginRecordListRes;
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;
@@ -1,26 +1,26 @@
package fun.asgc.neutrino.proxy.server.service;
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 fun.asgc.neutrino.proxy.core.util.DateUtil;
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
import fun.asgc.neutrino.proxy.server.base.rest.ServiceException;
import fun.asgc.neutrino.proxy.server.base.rest.SystemContextHolder;
import fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum;
import fun.asgc.neutrino.proxy.server.constant.ExceptionConstant;
import fun.asgc.neutrino.proxy.server.controller.req.system.*;
import fun.asgc.neutrino.proxy.server.controller.res.system.*;
import fun.asgc.neutrino.proxy.server.dal.UserLoginRecordMapper;
import fun.asgc.neutrino.proxy.server.dal.UserMapper;
import fun.asgc.neutrino.proxy.server.dal.UserTokenMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
import fun.asgc.neutrino.proxy.server.dal.entity.UserLoginRecordDO;
import fun.asgc.neutrino.proxy.server.dal.entity.UserTokenDO;
import fun.asgc.neutrino.proxy.server.util.Md5Util;
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
import org.dromara.neutrinoproxy.core.util.DateUtil;
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;
import org.dromara.neutrinoproxy.server.controller.req.system.*;
import org.dromara.neutrinoproxy.server.controller.res.system.*;
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.dal.entity.UserDO;
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;
@@ -1,23 +1,23 @@
package fun.asgc.neutrino.proxy.server.service;
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 fun.asgc.neutrino.proxy.core.Constants;
import fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum;
import fun.asgc.neutrino.proxy.server.dal.LicenseMapper;
import fun.asgc.neutrino.proxy.server.dal.PortMappingMapper;
import fun.asgc.neutrino.proxy.server.dal.PortPoolMapper;
import fun.asgc.neutrino.proxy.server.dal.UserMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.LicenseDO;
import fun.asgc.neutrino.proxy.server.dal.entity.PortMappingDO;
import fun.asgc.neutrino.proxy.server.dal.entity.PortPoolDO;
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
import fun.asgc.neutrino.proxy.server.proxy.core.BytesMetricsHandler;
import fun.asgc.neutrino.proxy.server.proxy.core.VisitorChannelHandler;
import fun.asgc.neutrino.proxy.server.proxy.domain.CmdChannelAttachInfo;
import fun.asgc.neutrino.proxy.server.proxy.domain.ProxyMapping;
import fun.asgc.neutrino.proxy.server.util.ProxyUtil;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
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.UserMapper;
import org.dromara.neutrinoproxy.server.dal.entity.LicenseDO;
import org.dromara.neutrinoproxy.server.dal.entity.PortMappingDO;
import org.dromara.neutrinoproxy.server.dal.entity.PortPoolDO;
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
import org.dromara.neutrinoproxy.server.proxy.core.BytesMetricsHandler;
import org.dromara.neutrinoproxy.server.proxy.core.TcpVisitorChannelHandler;
import org.dromara.neutrinoproxy.server.proxy.domain.CmdChannelAttachInfo;
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyMapping;
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
@@ -41,10 +41,8 @@ import java.util.stream.Collectors;
@Slf4j
@Component
public class VisitorChannelService {
@Inject("serverBossGroup")
private NioEventLoopGroup serverBossGroup;
@Inject("serverWorkerGroup")
private NioEventLoopGroup serverWorkerGroup;
@Inject("tcpServerBootstrap")
private ServerBootstrap tcpServerBootstrap;
@Inject
private ProxyMutualService proxyMutualService;
@Db
@@ -218,20 +216,16 @@ public class VisitorChannelService {
if (CollectionUtil.isEmpty(portMappingList)) {
return;
}
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(serverBossGroup, serverWorkerGroup)
.channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addFirst(new BytesMetricsHandler());
ch.pipeline().addLast(new VisitorChannelHandler());
}
});
for (PortMappingDO portMapping : portMappingList) {
if (EnableStatusEnum.DISABLE.getStatus().equals(portMapping.getEnable())) {
// 端口映射被禁用了忽略 TODO 端口被禁用了也需要处理
continue;
}
// TODO 此处切入TCP/UDP代理
try {
proxyMutualService.bindServerPort(cmdChannelAttachInfo, portMapping.getServerPort());
bootstrap.bind(portMapping.getServerPort()).get();
tcpServerBootstrap.bind(portMapping.getServerPort()).get();
log.info("绑定用户端口: {}", portMapping.getServerPort());
} catch (Exception ex) {
// BindException表示该端口已经绑定过
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.service.bo;
package org.dromara.neutrinoproxy.server.service.bo;
import lombok.Data;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.service.bo;
package org.dromara.neutrinoproxy.server.service.bo;
import lombok.Data;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.util;
package org.dromara.neutrinoproxy.server.util;
/**
* @author: aoshiguchen
@@ -19,7 +19,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.proxy.server.util;
package org.dromara.neutrinoproxy.server.util;
import java.math.BigInteger;
import java.security.MessageDigest;
@@ -0,0 +1,106 @@
package org.dromara.neutrinoproxy.server.util;
import cn.hutool.core.util.StrUtil;
import org.dromara.neutrinoproxy.server.constant.ExceptionConstant;
import org.dromara.neutrinoproxy.server.base.rest.ServiceException;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
/**
* @author: aoshiguchen
* @date: 2022/8/1
*/
public class ParamCheckUtil {
public static void checkNotNull(Object obj, String name) {
if (null == obj) {
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_NULL, name);
}
}
public static void checkNotEmpty(String str, String name) {
if (StrUtil.isEmpty(str)) {
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_EMPTY, name);
}
}
public static void checkNotEmpty(Collection collection, String name) {
if (null == collection || collection.isEmpty()) {
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_EMPTY, name);
}
}
public static void checkNotEmpty(Map map, String name) {
if (null == map || map.isEmpty()) {
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_EMPTY, name);
}
}
public static void checkNotEmpty(Set set, String name) {
if (null == set || set.isEmpty()) {
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_EMPTY, name);
}
}
public static void checkMustNull(Object obj, ExceptionConstant constant, Object... params) {
if (null != obj) {
throw ServiceException.create(constant, params);
}
}
public static void checkNotNull(Object obj, ExceptionConstant constant, Object... params) {
if (null == obj) {
throw ServiceException.create(constant, params);
}
}
public static void checkNotEmpty(String str, ExceptionConstant constant, Object... params) {
if (StrUtil.isEmpty(str)) {
throw ServiceException.create(constant, params);
}
}
public static void checkNotEmpty(Collection collection, ExceptionConstant constant, Object... params) {
if (null == collection || collection.isEmpty()) {
throw ServiceException.create(constant, params);
}
}
public static void checkNotEmpty(Map map, ExceptionConstant constant, Object... params) {
if (null == map || map.isEmpty()) {
throw ServiceException.create(constant, params);
}
}
public static void checkNotEmpty(Set set, ExceptionConstant constant, Object... params) {
if (null == set || set.isEmpty()) {
throw ServiceException.create(constant, params);
}
}
public static void checkExpression(boolean expression, ExceptionConstant constant, Object... params) {
if (!expression) {
throw ServiceException.create(constant, params);
}
}
/**
* check max length , if the string length exceeded ,an exception is thrown
* if the string is empty , it's not checked
* 检查最大长度,超出长度,则抛出异常 . 如果字符串本身为空,则不进行检查
*
* @param str String
* @param maxLength Maximum length
* @param params Exception parameters
* @throws {@link ServiceException}
*/
public static void checkMaxLength(String str, int maxLength, Object... params) {
if (!StrUtil.isEmpty(str) && maxLength < str.length()) {
throw ServiceException.create(ExceptionConstant.FILED_LENGTH_OUT, params);
}
}
}
@@ -1,17 +1,21 @@
package fun.asgc.neutrino.proxy.server.util;
package org.dromara.neutrinoproxy.server.util;
import cn.hutool.core.collection.CollectionUtil;
import com.google.common.collect.Sets;
import fun.asgc.neutrino.proxy.core.ChannelAttribute;
import fun.asgc.neutrino.proxy.server.proxy.domain.CmdChannelAttachInfo;
import fun.asgc.neutrino.proxy.server.proxy.domain.ProxyMapping;
import fun.asgc.neutrino.proxy.server.proxy.domain.VisitorChannelAttachInfo;
import org.apache.commons.lang3.StringUtils;
import org.dromara.neutrinoproxy.core.ChannelAttribute;
import org.dromara.neutrinoproxy.core.Constants;
import org.dromara.neutrinoproxy.server.proxy.domain.CmdChannelAttachInfo;
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyAttachment;
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyMapping;
import org.dromara.neutrinoproxy.server.proxy.domain.VisitorChannelAttachInfo;
import io.netty.channel.Channel;
import io.netty.util.AttributeKey;
import java.net.InetSocketAddress;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -47,6 +51,22 @@ public class ProxyUtil {
* cmdChannelAttachInfo.getUserChannelMap() 读写锁
*/
private static final ReadWriteLock userChannelMapLock = new ReentrantReadWriteLock();
/**
* 访问者ID生成器
*/
private static AtomicLong visitorIdProducer = new AtomicLong(0);
/**
* 代理 - connect附加映射
*/
private static Map<String, ProxyAttachment> proxyConnectAttachmentMap = new HashMap<>();
/**
* 子域名 - 服务端端口映射
*/
private static Map<String, Integer> subdomainToServerPort = new HashMap<>();
/**
* licenseId - 客户端Id映射
*/
private static Map<Integer, String> licenseIdToClientIdMap = new HashMap<>();
/**
* 初始化代理信息
@@ -278,4 +298,123 @@ public class ProxyUtil {
}
return channel.attr(CHANNEL_ATTR_KEY).get().get("attachInfo");
}
/**
* 为访问者连接产生ID
*
* @return
*/
public static String newVisitorId() {
return String.valueOf(visitorIdProducer.incrementAndGet());
}
/**
* 添加代理附加对象
* @param visitorId
* @param proxyAttachment
*/
public static void addProxyConnectAttachment(String visitorId, ProxyAttachment proxyAttachment) {
proxyConnectAttachmentMap.put(visitorId, proxyAttachment);
}
/**
* 获取代理附加对象
* @param visitorId
* @return
*/
public static ProxyAttachment getProxyConnectAttachment(String visitorId) {
return proxyConnectAttachmentMap.get(visitorId);
}
/**
* 删除代理附加对象
* @param visitorId
*/
public static void remoteProxyConnectAttachment(String visitorId) {
proxyConnectAttachmentMap.remove(visitorId);
}
/**
* 设置子域名到服务端端口的映射
* @param subdomain
* @param serverPort
*/
public static void setSubdomainToServerPort(String subdomain, Integer serverPort) {
subdomainToServerPort.put(subdomain, serverPort);
}
/**
* 删除子域名到服务端端口的映射
* @param subdomain
*/
public static void removeSubdomainToServerPort(String subdomain) {
subdomainToServerPort.remove(subdomain);
}
/**
* 根据子域名获取外网端口
* @param subdomain
* @return
*/
public static Integer getServerPortBySubdomain(String subdomain) {
return subdomainToServerPort.get(subdomain);
}
/**
* 关闭http响应channel
* @param channel
* @return
*/
public static void closeHttpProxyResponseChannel(Channel channel) {
if (null == channel) {
return;
}
String visitorId = getVisitorIdByChannel(channel); // channel.attr(Constants.VISITOR_ID).get();
if (StringUtils.isBlank(visitorId)) {
return;
}
ProxyAttachment proxyAttachment = ProxyUtil.getProxyConnectAttachment(visitorId);
if (null != proxyAttachment) {
tryClose(channel);
}
}
/**
* 关闭channel
* @param channel
*/
public static void tryClose(Channel channel) {
try {
channel.close();
} catch (Exception e) {
// ignore
}
}
/**
* 设置licenseId - clientId映射
* @param licenseId
* @param clientId
*/
public static void setLicenseIdToClientIdMap(Integer licenseId, String clientId) {
licenseIdToClientIdMap.put(licenseId, clientId);
}
/**
* 根据licenseId获取clientId
* @param licenseId
* @return
*/
public static String getClientIdByLicenseId(Integer licenseId) {
return licenseIdToClientIdMap.get(licenseId);
}
/**
* 根据licenseId删除clientId
* @param licenseId
*/
public static void removeClientIdByLicenseId(Integer licenseId) {
licenseIdToClientIdMap.remove(licenseId);
}
}
@@ -1,32 +1,64 @@
server:
port: 8888
# 服务端web端口,用于支持HTTP接口,管理后台页面访问
port: ${WEB_PORT:8888}
context-path: /neutrino-proxy-server
neutrino:
proxy:
protocol:
max-frame-length: 2097152
max-frame-length: ${MAX_FRAME_LENGTH:2097152}
length-field-offset: 0
length-field-length: 4
initial-bytes-to-strip: 0
length-adjustment: 0
read-idle-time: 40
write-idle-time: 10
read-idle-time: 30
write-idle-time: 5
all-idle-time-seconds: 0
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}
# 是否开启心跳日志
heartbeat-log-enable: ${HEARTBEAT_LOG:false}
server:
boss-thread-count: 10
work-thread-count: 60
port: 9000
ssl-port: 9002
key-store-password: 123456
key-manager-password: 123456
jks-path: classpath:/test.jks
# 线程池相关配置,用于技术调优,可忽略
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:
type: sqlite
url: jdbc:sqlite:data.db
driver-class: org.sqlite.JDBC
username:
password:
# 数据库类型,目前支持sqlite、mysql、mariadb
type: ${DB_TYPE:sqlite}
# 数据库连接URL
url: ${DB_URL:jdbc:sqlite:data.db}
# 数据库用户名
username: ${DB_USER:}
# 数据库密码
password: ${DB_PASSWORD:}
#添加MIME印射(如果有需要?)
#是否启用静态文件服务。(可不配,默认为启用)
@@ -40,12 +72,12 @@ solon.staticfiles.mappings:
mybatis.db:
typeAliases: #支持包名 或 类名(大写开头 或 *)//支持 ** 或 * 占位符
- "fun.asgc.neutrino.proxy.server.dal.entity"
- "org.dromara.neutrinoproxy.server.dal.entity"
mappers: #支持包名 或 类名(大写开头 或 *)或 xml(.xml结尾)//支持 ** 或 * 占位符
- "classpath:mapper/*.xml"
- "fun.asgc.neutrino.proxy.server.dal"
- "org.dromara.neutrinoproxy.server.dal"
configuration: #扩展配置(要与 MybatisConfiguration 类的属性一一对应)
cacheEnabled: false
mapUnderscoreToCamelCase: true
globalConfig: #全局配置(要与 GlobalConfig 类的属性一一对应)
banner: true
banner: true
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="LOG_FILE" value="/work/projects/neutrino-proxy-server/app.log"/>
<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" />
<!-- <property name="ENCODE" value="utf8" />-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
@@ -10,7 +10,7 @@
</filter>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>${ENCODE}</charset>
<!-- <charset>${ENCODE}</charset>-->
</encoder>
</appender>
@@ -22,7 +22,7 @@
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>${ENCODE}</charset>
<!-- <charset>${ENCODE}</charset>-->
</encoder>
</appender>
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="fun.asgc.neutrino.proxy.server.dal.PortGroupMapper">
<mapper namespace="org.dromara.neutrinoproxy.server.dal.PortGroupMapper">
<select id="selectPortGroupListResList" resultType="fun.asgc.neutrino.proxy.server.controller.res.system.PortGroupListRes">
<select id="selectPortGroupListResList" resultType="org.dromara.neutrinoproxy.server.controller.res.system.PortGroupListRes">
SELECT g.*,
CASE
WHEN g.possessor_type = 1 THEN
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="fun.asgc.neutrino.proxy.server.dal.PortMappingMapper">
<mapper namespace="org.dromara.neutrinoproxy.server.dal.PortMappingMapper">
<select id="selectPortMappingByCondition" resultType="fun.asgc.neutrino.proxy.server.dal.entity.PortMappingDO">
<select id="selectPortMappingByCondition" resultType="org.dromara.neutrinoproxy.server.dal.entity.PortMappingDO">
SELECT pm.* from port_mapping pm left join license on pm.license_id = license.`id`
<where>
<if test="req.userId != null">
@@ -20,6 +20,12 @@
<if test="req.enable != null">
AND pm.enable = #{req.enable}
</if>
<if test="req.protocal != null and req.protocal != ''">
AND pm.protocal = #{req.protocal}
</if>
<if test="req.description != null and req.description != '' ">
AND pm.description like #{req.description}
</if>
</where>
order by pm.id asc
</select>
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="fun.asgc.neutrino.proxy.server.dal.PortPoolMapper">
<mapper namespace="org.dromara.neutrinoproxy.server.dal.PortPoolMapper">
<select id="selectResList" resultType="fun.asgc.neutrino.proxy.server.controller.res.system.PortPoolListRes">
<select id="selectResList" resultType="org.dromara.neutrinoproxy.server.controller.res.system.PortPoolListRes">
SELECT p.*,
g.possessor_type,
g.`name` group_name
@@ -16,7 +16,7 @@
ORDER BY p.id
</select>
<select id="getAvailablePortList" resultType="fun.asgc.neutrino.proxy.server.controller.res.system.PortPoolListRes">
<select id="getAvailablePortList" resultType="org.dromara.neutrinoproxy.server.controller.res.system.PortPoolListRes">
SELECT
*
FROM
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="fun.asgc.neutrino.proxy.server.dal.ReportMapper">
<mapper namespace="org.dromara.neutrinoproxy.server.dal.ReportMapper">
<select id="userFlowReportList" resultType="fun.asgc.neutrino.proxy.server.controller.res.report.UserFlowReportRes">
<select id="userFlowReportList" resultType="org.dromara.neutrinoproxy.server.controller.res.report.UserFlowReportRes">
SELECT
u.id AS 'userId',
u.name AS 'userName',
@@ -30,7 +30,7 @@
GROUP BY u.id
</select>
<select id="licenseFLowReportList" resultType="fun.asgc.neutrino.proxy.server.controller.res.report.LicenseFlowReportRes">
<select id="licenseFLowReportList" resultType="org.dromara.neutrinoproxy.server.controller.res.report.LicenseFlowReportRes">
SELECT
l.id AS 'licenseId',
l.name AS 'licenseName',
@@ -61,7 +61,7 @@
GROUP BY l.id
</select>
<select id="userFlowMonthReportList" resultType="fun.asgc.neutrino.proxy.server.controller.res.report.UserFlowMonthReportRes">
<select id="userFlowMonthReportList" resultType="org.dromara.neutrinoproxy.server.controller.res.report.UserFlowMonthReportRes">
SELECT
T2.user_id,
u.name AS 'userName',
@@ -70,7 +70,6 @@
T2.downFlowBytes
FROM (
(
SELECT
frm.user_id,
frm.date,
@@ -78,16 +77,16 @@
IFNULL(SUM(frm.read_bytes),0) AS 'downFlowBytes'
FROM flow_report_month frm
GROUP BY frm.user_id,frm.date
)
UNION ALL
(
SELECT
T1.user_id,
#{curMonthBeginDate} AS 'date',
IFNULL(SUM(T1.upFlowBytes),0) AS 'upFlowBytes',
IFNULL(SUM(T1.downFlowBytes),0) AS 'downFlowBytes'
FROM (
(
SELECT
frd.user_id,
frd.license_id,
@@ -96,9 +95,9 @@
frd.date
FROM flow_report_day frd
WHERE frd.date >= #{curMonthBeginDate} AND frd.date &lt;= #{curDayBeginDate}
)
UNION ALL
(
SELECT
frm.user_id,
frm.license_id,
@@ -108,10 +107,10 @@
FROM flow_report_minute frm
WHERE frm.date >= #{curDayBeginDate} AND frm.date &lt;= #{curDate}
GROUP BY frm.user_id,frm.license_id
)
) T1
GROUP BY T1.user_id
)
) T2
LEFT JOIN `user` u ON u.id = T2.user_id
<where>
@@ -122,7 +121,7 @@
ORDER BY T2.user_id ASC,T2.date DESC
</select>
<select id="licenseFLowMonthReportList" resultType="fun.asgc.neutrino.proxy.server.controller.res.report.LicenseFlowMonthReportRes">
<select id="licenseFLowMonthReportList" resultType="org.dromara.neutrinoproxy.server.controller.res.report.LicenseFlowMonthReportRes">
SELECT
T2.user_id,
u.name AS 'userName',
@@ -133,7 +132,6 @@
T2.downFlowBytes
FROM (
(
SELECT
frm.user_id,
frm.license_id,
@@ -142,9 +140,9 @@
IFNULL(SUM(frm.read_bytes),0) AS 'downFlowBytes'
FROM flow_report_month frm
GROUP BY frm.user_id,frm.license_id,frm.date
)
UNION ALL
(
SELECT
T1.user_id,
T1.license_id,
@@ -152,7 +150,7 @@
IFNULL(SUM(T1.upFlowBytes),0) AS 'upFlowBytes',
IFNULL(SUM(T1.downFlowBytes),0) AS 'downFlowBytes'
FROM (
(
SELECT
frd.user_id,
frd.license_id,
@@ -161,9 +159,9 @@
frd.date
FROM flow_report_day frd
WHERE frd.date >= #{curMonthBeginDate} AND frd.date &lt;= #{curDayBeginDate}
)
UNION ALL
(
SELECT
frm.user_id,
frm.license_id,
@@ -173,10 +171,10 @@
FROM flow_report_minute frm
WHERE frm.date >= #{curDayBeginDate} AND frm.date &lt;= #{curDate}
GROUP BY frm.user_id,frm.license_id
)
) T1
GROUP BY T1.user_id,T1.license_id
)
) T2
LEFT JOIN `user` u ON u.id = T2.user_id
LEFT JOIN `license` l ON l.id = T2.license_id
@@ -191,7 +189,7 @@
ORDER BY T2.user_id ASC,T2.license_id,T2.date DESC
</select>
<select id="homeTodayFlow" resultType="fun.asgc.neutrino.proxy.server.service.bo.FlowBO">
<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'
@@ -199,37 +197,40 @@
WHERE frm.date >= #{curDayBeginDate} AND frm.date &lt;= #{curDate}
</select>
<select id="homeTotalFlow" resultType="fun.asgc.neutrino.proxy.server.service.bo.FlowBO">
<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'
FROM (
(
SELECT
IFNULL(SUM(frm.write_bytes),0) AS 'upFlowBytes',
IFNULL(SUM(frm.read_bytes),0) AS 'downFlowBytes'
FROM flow_report_month frm
) UNION ALL (
UNION ALL
SELECT
IFNULL(SUM(frd.write_bytes),0) AS 'upFlowBytes',
IFNULL(SUM(frd.read_bytes),0) AS 'downFlowBytes'
FROM flow_report_day frd
WHERE frd.date >= #{curMonthBeginDate} AND frd.date &lt;= #{curDayBeginDate}
) UNION ALL (
UNION ALL
SELECT
IFNULL(SUM(frm.write_bytes),0) AS 'upFlowBytes',
IFNULL(SUM(frm.read_bytes),0) AS 'downFlowBytes'
FROM flow_report_minute frm
WHERE frm.date >= #{curDayBeginDate} AND frm.date &lt;= #{curDate}
)
) T
</select>
<select id="homeLast7dFlowList" resultType="fun.asgc.neutrino.proxy.server.service.bo.SingleDayFlowBO">
<select id="homeLast7dFlowList" resultType="org.dromara.neutrinoproxy.server.service.bo.SingleDayFlowBO">
SELECT
T.*
FROM (
(
SELECT
frd.date,
IFNULL(SUM(frd.write_bytes),0) AS 'upFlowBytes',
@@ -237,14 +238,16 @@
FROM flow_report_day frd
WHERE frd.date >= #{beginDate} AND frd.date &lt;= #{curDayBeginDate}
GROUP BY frd.date
) UNION ALL (
UNION ALL
SELECT
#{curDayBeginDate} AS 'date',
IFNULL(SUM(frm.write_bytes),0) AS 'upFlowBytes',
IFNULL(SUM(frm.read_bytes),0) AS 'downFlowBytes'
FROM flow_report_minute frm
WHERE frm.date >= #{curDayBeginDate} AND frm.date &lt;= #{curDate}
)
) T
</select>
</mapper>
@@ -0,0 +1,207 @@
##########################################################
#
CREATE TABLE IF NOT EXISTS `user` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` varchar(50) NOT NULL COMMENT '用户名',
`login_name` varchar(50) NOT NULL COMMENT '登录名',
`login_password` varchar(255) NOT NULL COMMENT '登录密码',
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `I_user_login_name` (`login_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#token表
CREATE TABLE IF NOT EXISTS `user_token` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`token` varchar(50) NOT NULL COMMENT 'token',
`user_id` int NOT NULL COMMENT '用户ID',
`expiration_time` datetime(3) NOT NULL COMMENT '过期时间',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `I_user_token_user_id` (`user_id`),
KEY `I_user_token_token` (`token`),
KEY `I_user_token_expiration_time` (`expiration_time`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#
CREATE TABLE IF NOT EXISTS `port_pool` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`group_id` int NOT NULL DEFAULT 1 COMMENT '分组ID',
`port` int NOT NULL COMMENT '端口',
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `I_port_pool_port` (`port`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#
CREATE TABLE IF NOT EXISTS `port_group` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` varchar(255) NOT NULL COMMENT '分组名称',
`possessor_type` int NOT NULL DEFAULT '0' COMMENT '所有者类型 (0、全局共享 1、用户所有 2License所有) ',
`possessor_id` int NOT NULL DEFAULT '-1' COMMENT '所有者id(当type为0时 固定为-1、当type为1时为用户id 、当type为2时为licenseid)',
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
##########################################################
#license表
CREATE TABLE IF NOT EXISTS `license` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` varchar(50) NOT NULL COMMENT 'license名称',
`key` varchar(100) NOT NULL COMMENT 'license key',
`user_id` int NOT NULL COMMENT '用户ID',
`is_online` int NOT NULL COMMENT '是否在线(1、在线 2、离线)',
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
KEY `I_license_key` (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#
CREATE TABLE IF NOT EXISTS `port_mapping` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`license_id` int NOT NULL COMMENT 'licenseID',
`protocal` varchar(10) NOT NULL DEFAULT 'TCP' COMMENT '协议',
`subdomain` varchar(50) DEFAULT NULL COMMENT '子域名(仅HTTP时有效)',
`server_port` int NOT NULL COMMENT '服务端端口',
`client_ip` varchar(20) NOT NULL COMMENT '客户端IP',
`client_port` int NOT NULL COMMENT '客户端端口',
`is_online` int NOT NULL COMMENT '是否在线(1、在线 2、离线)',
`description` varchar(100) DEFAULT NULL COMMENT '描述',
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `I_port_mapping_server_port` (`server_port`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
##########################################################
#
CREATE TABLE IF NOT EXISTS `user_login_record` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` int NOT NULL COMMENT '用户ID',
`ip` varchar(50) NOT NULL COMMENT 'IP',
`token` varchar(100) NOT NULL COMMENT 'token',
`type` int NOT NULL COMMENT '类型(1、登录 2、登出)',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#
CREATE TABLE IF NOT EXISTS `client_connect_record` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`ip` varchar(50) NOT NULL COMMENT 'IP',
`license_id` int NOT NULL COMMENT 'licenseId',
`type` int NOT NULL COMMENT '类型(1、连接 2、断开连接)',
`msg` varchar(512) DEFAULT NULL COMMENT '消息',
`code` int NOT NULL COMMENT '结果 1、成功 2、失败)',
`err` text DEFAULT NULL COMMENT '异常信息',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
##########################################################
#
CREATE TABLE IF NOT EXISTS `job_info` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`desc` varchar(255) NOT NULL COMMENT '描述',
`handler` varchar(255) NOT NULL COMMENT '处理器',
`cron` varchar(128) NOT NULL COMMENT 'cron',
`param` varchar(512) DEFAULT NULL COMMENT '参数',
`alarm_email` varchar(255) DEFAULT NULL COMMENT '报警邮箱',
`alarm_ding` varchar(255) DEFAULT NULL COMMENT '报警钉钉配置',
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `I_job_info_handler` (`handler`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#
CREATE TABLE IF NOT EXISTS `job_log` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`job_id` int NOT NULL COMMENT 'JobId',
`handler` varchar(255) NOT NULL COMMENT '处理器',
`param` varchar(512) DEFAULT NULL COMMENT '参数',
`code` int NOT NULL COMMENT '结果(1、成功 2、失败)',
`msg` text COMMENT '消息',
`alarm_status` int NOT NULL COMMENT '报警状态(1、未报警 2、已报警)',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE,
KEY `I_job_log_create_time` (`create_time`) USING BTREE,
KEY `I_job_log_code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
##########################################################
#-(24)
CREATE TABLE IF NOT EXISTS `flow_report_minute` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` int NOT NULL COMMENT '用户ID',
`license_id` int NOT NULL COMMENT 'licenseId',
`write_bytes` int NOT NULL COMMENT '写入流量',
`read_bytes` int NOT NULL COMMENT '读取流量',
`date` datetime(3) NOT NULL COMMENT '时间',
`date_str` varchar(20) NOT NULL COMMENT '时间 yyyy-MM-dd HH:mm',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `I_flow_report_minute_create_time` (`create_time`) USING BTREE,
KEY `I_flow_report_minute_date` (`date`) USING BTREE,
KEY `I_flow_report_minute_user_id` (`user_id`),
KEY `I_flow_report_minute_license_id` (`license_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#-(60)
CREATE TABLE IF NOT EXISTS `flow_report_hour` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` int NOT NULL COMMENT '用户ID',
`license_id` int NOT NULL COMMENT 'licenseId',
`write_bytes` int NOT NULL COMMENT '写入流量',
`read_bytes` int NOT NULL COMMENT '读取流量',
`date` datetime(3) NOT NULL COMMENT '时间',
`date_str` varchar(20) NOT NULL COMMENT '时间 yyyy-MM-dd HH',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `I_flow_report_hour_create_time` (`create_time`) USING BTREE,
KEY `I_flow_report_hour_date` (`date`) USING BTREE,
KEY `I_flow_report_hour_user_id` (`user_id`),
KEY `I_flow_report_hour_license_id` (`license_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#-(1)
CREATE TABLE IF NOT EXISTS `flow_report_day` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` int NOT NULL COMMENT '用户ID',
`license_id` int NOT NULL COMMENT 'licenseId',
`write_bytes` int NOT NULL COMMENT '写入流量',
`read_bytes` int NOT NULL COMMENT '读取流量',
`date` datetime(3) NOT NULL COMMENT '时间',
`date_str` varchar(20) NOT NULL COMMENT '时间 yyyy-MM-dd',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `I_flow_report_day_create_time` (`create_time`) USING BTREE,
KEY `I_flow_report_day_date` (`date`) USING BTREE,
KEY `I_flow_report_day_user_id` (`user_id`),
KEY `I_flow_report_day_license_id` (`license_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#-()
CREATE TABLE IF NOT EXISTS `flow_report_month` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` int NOT NULL COMMENT '用户ID',
`license_id` int NOT NULL COMMENT 'licenseId',
`write_bytes` int NOT NULL COMMENT '写入流量',
`read_bytes` int NOT NULL COMMENT '读取流量',
`date` datetime(3) NOT NULL COMMENT '时间',
`date_str` varchar(20) NOT NULL COMMENT '时间 yyyy-MM',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `I_flow_report_month_create_time` (`create_time`) USING BTREE,
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;
@@ -0,0 +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, now(), now());
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
(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, now(), now());
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
(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, now(), now());
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
(6, '流量统计报表-月', 'FlowReportForMonthJob', '0 30 1 1 * ?', '', 1, now(), now());
@@ -0,0 +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, now(), now());
@@ -0,0 +1,3 @@
#port_group
INSERT INTO `port_group`(`id`,`name`,`possessor_type`,`possessor_id`,`enable`,`create_time`,`update_time`) VALUES
(1, '全局(默认)', 0, -1, 1, now(), now());
@@ -0,0 +1,7 @@
#port_mapping
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
(1, 1, 9101, 'HTTP', 'test1', '127.0.0.1', 8080, 2, 1, now(), now());
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
(2, 1, 9102, 'TCP', '', '127.0.0.1', 3306, 2, 1, now(), now());
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
(3, 1, 9103, 'HTTP', 'test2', '127.0.0.1', 8081, 2, 1, now(), now());
@@ -0,0 +1,41 @@
#
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(1, 1, 9101, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(2, 1, 9102, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(3, 1, 9103, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(4, 1, 9104, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(5, 1, 9105, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(6, 1, 9106, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(7, 1, 9107, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(8, 1, 9108, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(9, 1, 9109, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(10, 1, 9110, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(11, 1, 9111, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(12, 1, 9112, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(13, 1, 9113, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(14, 1, 9114, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(15, 1, 9115, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(16, 1, 9116, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(17, 1, 9117, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(18, 1, 9118, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(19, 1, 9119, 1, now(), now());
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(20, 1, 9120, 1, now(), now());
@@ -0,0 +1,5 @@
# 6613b92b77056faeb72068f184ed4c4f
INSERT INTO `user`(`id`, `name`,`login_name`,`login_password`,`enable`,`create_time`, `update_time`) VALUES
(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, now(), now());
@@ -10,7 +10,7 @@ CREATE TABLE IF NOT EXISTS `user` (
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `I_user_login_name` (`login_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#token表
CREATE TABLE IF NOT EXISTS `user_token` (
@@ -24,7 +24,7 @@ CREATE TABLE IF NOT EXISTS `user_token` (
KEY `I_user_token_user_id` (`user_id`),
KEY `I_user_token_token` (`token`),
KEY `I_user_token_expiration_time` (`expiration_time`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#
CREATE TABLE IF NOT EXISTS `port_pool` (
@@ -36,7 +36,7 @@ CREATE TABLE IF NOT EXISTS `port_pool` (
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `I_port_pool_port` (`port`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#
CREATE TABLE IF NOT EXISTS `port_group` (
@@ -48,7 +48,7 @@ CREATE TABLE IF NOT EXISTS `port_group` (
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='端口分组';
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
##########################################################
#license表
@@ -63,22 +63,25 @@ CREATE TABLE IF NOT EXISTS `license` (
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
KEY `I_license_key` (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#
CREATE TABLE IF NOT EXISTS `port_mapping` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`license_id` int NOT NULL COMMENT 'licenseID',
`protocal` varchar(10) NOT NULL DEFAULT 'TCP' COMMENT '协议',
`subdomain` varchar(50) DEFAULT NULL COMMENT '子域名(仅HTTP时有效)',
`server_port` int NOT NULL COMMENT '服务端端口',
`client_ip` varchar(20) NOT NULL COMMENT '客户端IP',
`client_port` int NOT NULL COMMENT '客户端端口',
`is_online` int NOT NULL COMMENT '是否在线(1、在线 2、离线)',
`description` varchar(100) DEFAULT NULL COMMENT '描述',
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `I_port_mapping_server_port` (`server_port`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
##########################################################
#
CREATE TABLE IF NOT EXISTS `user_login_record` (
@@ -89,11 +92,11 @@ CREATE TABLE IF NOT EXISTS `user_login_record` (
`type` int NOT NULL COMMENT '类型(1、登录 2、登出)',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#
CREATE TABLE IF NOT EXISTS `client_connect_record` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'IP',
`ip` varchar(50) NOT NULL COMMENT 'IP',
`license_id` int NOT NULL COMMENT 'licenseId',
`type` int NOT NULL COMMENT '类型(1、连接 2、断开连接)',
`msg` varchar(512) DEFAULT NULL COMMENT '消息',
@@ -101,7 +104,7 @@ CREATE TABLE IF NOT EXISTS `client_connect_record` (
`err` text DEFAULT NULL COMMENT '异常信息',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
##########################################################
#
CREATE TABLE IF NOT EXISTS `job_info` (
@@ -117,7 +120,7 @@ CREATE TABLE IF NOT EXISTS `job_info` (
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `I_job_info_handler` (`handler`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#
CREATE TABLE IF NOT EXISTS `job_log` (
@@ -132,7 +135,7 @@ CREATE TABLE IF NOT EXISTS `job_log` (
PRIMARY KEY (`id`) USING BTREE,
KEY `I_job_log_create_time` (`create_time`) USING BTREE,
KEY `I_job_log_code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
##########################################################
#-(24)
@@ -150,7 +153,7 @@ CREATE TABLE IF NOT EXISTS `flow_report_minute` (
KEY `I_flow_report_minute_date` (`date`) USING BTREE,
KEY `I_flow_report_minute_user_id` (`user_id`),
KEY `I_flow_report_minute_license_id` (`license_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#-(60)
CREATE TABLE IF NOT EXISTS `flow_report_hour` (
@@ -160,14 +163,14 @@ CREATE TABLE IF NOT EXISTS `flow_report_hour` (
`write_bytes` int NOT NULL COMMENT '写入流量',
`read_bytes` int NOT NULL COMMENT '读取流量',
`date` datetime(3) NOT NULL COMMENT '时间',
`date_str` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '时间 yyyy-MM-dd HH',
`date_str` varchar(20) NOT NULL COMMENT '时间 yyyy-MM-dd HH',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `I_flow_report_hour_create_time` (`create_time`) USING BTREE,
KEY `I_flow_report_hour_date` (`date`) USING BTREE,
KEY `I_flow_report_hour_user_id` (`user_id`),
KEY `I_flow_report_hour_license_id` (`license_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#-(1)
CREATE TABLE IF NOT EXISTS `flow_report_day` (
@@ -177,14 +180,14 @@ CREATE TABLE IF NOT EXISTS `flow_report_day` (
`write_bytes` int NOT NULL COMMENT '写入流量',
`read_bytes` int NOT NULL COMMENT '读取流量',
`date` datetime(3) NOT NULL COMMENT '时间',
`date_str` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '时间 yyyy-MM-dd',
`date_str` varchar(20) NOT NULL COMMENT '时间 yyyy-MM-dd',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `I_flow_report_day_create_time` (`create_time`) USING BTREE,
KEY `I_flow_report_day_date` (`date`) USING BTREE,
KEY `I_flow_report_day_user_id` (`user_id`),
KEY `I_flow_report_day_license_id` (`license_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
#-()
CREATE TABLE IF NOT EXISTS `flow_report_month` (
@@ -194,11 +197,11 @@ CREATE TABLE IF NOT EXISTS `flow_report_month` (
`write_bytes` int NOT NULL COMMENT '写入流量',
`read_bytes` int NOT NULL COMMENT '读取流量',
`date` datetime(3) NOT NULL COMMENT '时间',
`date_str` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '时间 yyyy-MM',
`date_str` varchar(20) NOT NULL COMMENT '时间 yyyy-MM',
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `I_flow_report_month_create_time` (`create_time`) USING BTREE,
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 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
@@ -1,5 +1,7 @@
#port_mapping
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
(1, 1, 9101, '127.0.0.1', 8080, 2, 1, now(), now());
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
(2, 1, 9102, '127.0.0.1', 3306, 2, 1, now(), now());
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
(1, 1, 9101, 'HTTP', 'test1', '127.0.0.1', 8080, 2, 1, now(), now());
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
(2, 1, 9102, 'TCP', '', '127.0.0.1', 3306, 2, 1, now(), now());
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
(3, 1, 9103, 'HTTP', 'test2', '127.0.0.1', 8081, 2, 1, now(), now());
@@ -10,4 +10,4 @@ CREATE TABLE IF NOT EXISTS `port_group` (
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='端口分组';
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COMMENT='端口分组';
@@ -0,0 +1,3 @@
ALTER TABLE port_mapping ADD `protocal` varchar(10) NOT NULL DEFAULT 'TCP' COMMENT '协议';
ALTER TABLE port_mapping ADD `subdomain` varchar(50) DEFAULT NULL COMMENT '子域名(仅HTTP时有效)';
ALTER TABLE port_mapping ADD `description` varchar(100) DEFAULT NULL COMMENT '描述';
@@ -63,10 +63,13 @@ 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,
`license_id` INTEGER(20) NOT NULL,
`protocal` VARCHAR(10) NOT NULL DEFAULT 'TCP',
`subdomain` VARCHAR(50) DEFAULT NULL,
`server_port` INTEGER NOT NULL,
`client_ip` VARCHAR(20) NOT NULL,
`client_port` INTEGER NOT NULL,
`is_online` INTEGER(2) NOT NULL,
`description` VARCHAR(100) DEFAULT NULL,
`enable` INTEGER(2) NOT NULL,
`create_time` INTEGER(20) NOT NULL,
`update_time` INTEGER(20) NOT NULL
@@ -1,5 +1,7 @@
#port_mapping
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
(1, 1, 9101, '127.0.0.1', 8080, 2, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
(2, 1, 9102, '127.0.0.1', 3306, 2, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', '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
(1, 1, 9101, 'HTTP', 'test1', '127.0.0.1', 8080, 2, 1,'test1', STRFTIME('%s000', 'NOW'), STRFTIME('%s000', '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'));
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'));
@@ -0,0 +1,3 @@
ALTER TABLE port_mapping ADD `protocal` varchar(10) NOT NULL DEFAULT 'TCP';
ALTER TABLE port_mapping ADD `subdomain` varchar(50) DEFAULT NULL;
ALTER TABLE port_mapping ADD `description` varchar(100);
+1
View File
@@ -0,0 +1 @@
neutrino-proxy.dromara.org
@@ -8,12 +8,13 @@ module.exports = {
// theme: require.resolve('../../vdoing'), // 使用本地主题 (先将vdoing主题文件下载到本地:https://github.com/xugaoyi/vuepress-theme-vdoing)
title: "中微子代理",
description: '一个基于 netty 的、开源的 java 内网穿透项目',
base: '/neutrino-proxy-vuepress/', // 默认'/'。如果你想将你的网站部署到如 https://foo.github.io/bar/,那么 base 应该被设置成 "/bar/",(否则页面将失去样式等文件)
description: '一个基于Netty的开源内网穿透神器',
base: '/neutrino-proxy/', // 默认'/'。如果你想将你的网站部署到如 https://foo.github.io/bar/,那么 base 应该被设置成 "/bar/",(否则页面将失去样式等文件)
head: [ // 注入到页面<head> 中的标签,格式[tagName, { attrName: attrValue }, innerHTML?]
['link', {rel: 'icon', href: '/img/favicon.ico'}], //favicons,资源放在public文件夹
['meta', {name: 'keywords', content: 'vuepress,theme,blog,vdoing'}],
['meta', {name: 'theme-color', content: '#11a8cd'}], // 移动浏览器主题颜色
['meta', {name: 'referrer', content: 'no-referrer-when-downgrade'}], //统计在 Chrome 85 版本中,为了保护用户的隐私,默认的 Referrer Policy 则变成了 strict-origin-when-cross-origin
// ['meta', { name: 'wwads-cn-verify', content: '6c4b761a28b734fe93831e3fb400ce87' }], // 广告相关,你可以去掉
// ['script', { src: 'https://cdn.wwads.cn/js/makemoney.js', type: 'text/javascript' }], // 广告相关,你可以去掉
@@ -21,89 +22,21 @@ module.exports = {
// 主题配置
themeConfig: {
// nav: [
// { text: '首页', link: '/' },
// {
// text: '指南', link: '/pages/a2f161/', items: [
// { text: '主题初衷与诞生', link: '/pages/52d5c3/' },
// { text: '介绍', link: '/pages/a2f161/' },
// { text: '快速上手', link: '/pages/793dcb/' },
// { text: '目录结构', link: '/pages/2f674a/' },
// { text: '核心配置和约定', link: '/pages/33d574/' },
// { text: '自动生成front matter', link: '/pages/088c16/' },
// { text: 'Markdown 容器', link: '/pages/d0d7eb/' },
// { text: 'Markdown 中使用组件', link: '/pages/197691/' },
// {
// text: '相关文章', items: [
// { text: '使目录栏支持h2~h6标题', link: '/pages/8dfab5/' },
// { text: '如何让你的笔记更有表现力', link: '/pages/dd027d/' },
// { text: '批量操作front matter工具', link: '/pages/2b8e22/' },
// { text: '部署', link: '/pages/0fc1d2/' },
// { text: '关于写文章和H1标题', link: '/pages/9ae0bd/' },
// { text: '关于博客搭建与管理', link: '/pages/26997d/' },
// { text: '在线编辑和新增文章的方法', link: '/pages/c5a54d/' },
// ]
// }
// ]
// },
// {
// text: '配置', link: '/pages/a20ce8/', items: [
// { text: '主题配置', link: '/pages/a20ce8/' },
// { text: '首页配置', link: '/pages/f14bdb/' },
// { text: 'front matter配置', link: '/pages/3216b0/' },
// { text: '目录页配置', link: '/pages/54651a/' },
// { text: '添加摘要', link: '/pages/1cc523/' },
// { text: '修改主题颜色和样式', link: '/pages/f51918/' },
// { text: '评论栏', link: '/pages/ce175c/' },
// ]
// },
// { text: '资源', link: '/pages/db78e2/' },
// { text: '案例', link: '/pages/5d571c/' },
// { text: '问答', link: '/pages/9cc27d/' },
// { text: '赞助', link: '/pages/1b12ed/' },
// ],
nav: [
{text: '首页', link: '/'},
{
text: '快速使用', link: '/pages/793dcb/', items: [
text: '文档', link: '/pages/793dcb/', items: [
{text: '快速上手', link: '/pages/793dcb/'},
{text: '目录结构', link: '/pages/2f674a/'},
{text: 'Markdown 容器', link: '/pages/d0d7eb/'},
{text: 'Markdown 中使用组件', link: '/pages/197691/'},
{
text: '相关文章', items: [
{text: '使目录栏支持h2~h6标题', link: '/pages/8dfab5/'},
{text: '如何让你的笔记更有表现力', link: '/pages/dd027d/'},
{text: '批量操作front matter工具', link: '/pages/2b8e22/'},
{text: '部署', link: '/pages/0fc1d2/'},
{text: '关于写文章和H1标题', link: '/pages/9ae0bd/'},
{text: '关于博客搭建与管理', link: '/pages/26997d/'},
{text: '在线编辑和新增文章的方法', link: '/pages/c5a54d/'},
]
}
]
},
{
text: '常见问题', link: '/pages/a20ce8/', items: [
{text: '主题配置', link: '/pages/a20ce8/'},
{text: '首页配置', link: '/pages/f14bdb/'},
{text: 'front matter配置', link: '/pages/3216b0/'},
{text: '目录页配置', link: '/pages/54651a/'},
{text: '添加摘要', link: '/pages/1cc523/'},
{text: '修改主题颜色和样式', link: '/pages/f51918/'},
{text: '评论栏', link: '/pages/ce175c/'},
{text: '后台操作指南', link: '/pages/793dcc/'},
]
},
{text: '演示', link: '/pages/db78e2/'},
{text: '案例', link: '/pages/5d571c/'},
{text: '最近更新', link: '/pages/9cc27d/'},
{text: '关于我们', link: '/pages/1b12ed/'},
{text: 'Gitee', link: 'https://gitee.com/dromara/neutrino-proxy'},
],
sidebarDepth: 2, // 侧边栏显示深度,默认1,最大2(显示到h3标题)
logo: '/img/logo.png', // 导航栏logo
// repo: 'aoshiguchen/neutrino-proxy', // 导航栏右侧生成Github链接
repo: 'https://gitee.com/dromara/neutrino-proxy', // 导航栏右侧生成Github链接
repoLabel: 'Gitee地址',
repo: 'https://github.com/dromara/neutrino-proxy', // 导航栏右侧生成Github链接
repoLabel: 'Github',
searchMaxSuggestions: 10, // 搜索结果显示最大数
lastUpdated: '上次更新', // 更新的时间,及前缀文字 string | boolean (取值为git提交时间)
@@ -194,7 +127,7 @@ module.exports = {
[
'vuepress-plugin-baidu-tongji', // 百度统计
{
hm: baiduCode || '01293bffa6c3962016c08ba685c79d78'
hm: baiduCode || 'f5901e55fecee5322d9f613841c717b8'
}
],
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 KiB

After

Width:  |  Height:  |  Size: 327 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

After

Width:  |  Height:  |  Size: 354 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 425 KiB

After

Width:  |  Height:  |  Size: 495 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 202 KiB

After

Width:  |  Height:  |  Size: 311 KiB

@@ -1,24 +1,25 @@
// .home-wrapper .banner .banner-conent .hero h1{
// font-size 2.8rem!important
// }
// //
// table
// width auto
// .page >*:not(.footer),.card-box
// box-shadow: none!important
.home-wrapper .banner .banner-conent .hero h1{
font-size 2.8rem!important
}
// .page
// @media (min-width $contentWidth + 80)
// padding-top $navbarHeight!important
// .home-wrapper .banner .banner-conent
// padding 0 2.9rem
// box-sizing border-box
// .home-wrapper .banner .slide-banner .slide-banner-wrapper .slide-item a
// h2
// margin-top 2rem
// font-size 1.2rem!important
// p
// padding 0 1rem
//
table
width auto
.page >*:not(.footer),.card-box
box-shadow: none!important
.page
@media (min-width $contentWidth + 80)
padding-top $navbarHeight!important
.home-wrapper .banner .banner-conent
padding 0 2.9rem
box-sizing border-box
.home-wrapper .banner .slide-banner .slide-banner-wrapper .slide-item a
h2
margin-top 2rem
font-size 1.2rem!important
p
padding 0 1rem
//
.gt-container
@@ -0,0 +1,93 @@
// .home-wrapper .banner .banner-conent .hero h1{
// font-size 2.8rem!important
// }
// // 文档中适配
// table
// width auto
// .page >*:not(.footer),.card-box
// box-shadow: none!important
// .page
// @media (min-width $contentWidth + 80)
// padding-top $navbarHeight!important
// .home-wrapper .banner .banner-conent
// padding 0 2.9rem
// box-sizing border-box
// .home-wrapper .banner .slide-banner .slide-banner-wrapper .slide-item a
// h2
// margin-top 2rem
// font-size 1.2rem!important
// p
// padding 0 1rem
// 评论区颜色重置
.gt-container
.gt-ico-tip
&::after
content: '。( Win + . ) or ( ⌃ + ⌘ + ␣ ) open Emoji'
color: #999
.gt-meta
border-color var(--borderColor)!important
.gt-comments-null
color var(--textColor)
opacity .5
.gt-header-textarea
color var(--textColor)
background rgba(180,180,180,0.1)!important
.gt-btn
border-color $accentColor!important
background-color $accentColor!important
.gt-btn-preview
background-color rgba(255,255,255,0)!important
color $accentColor!important
a
color $accentColor!important
.gt-svg svg
fill $accentColor!important
.gt-comment-content,.gt-comment-admin .gt-comment-content
background-color rgba(150,150,150,0.1)!important
&:hover
box-shadow 0 0 25px rgba(150,150,150,.5)!important
.gt-comment-body
color var(--textColor)!important
// qq徽章
.qq
position: relative;
.qq::after
content: "可撩";
background: $accentColor;
color:#fff;
padding: 0 5px;
border-radius: 10px;
font-size:12px;
position: absolute;
top: -4px;
right: -35px;
transform:scale(0.85);
// demo模块图标颜色
body .vuepress-plugin-demo-block__wrapper
&,.vuepress-plugin-demo-block__display
border-color rgba(160,160,160,.3)
.vuepress-plugin-demo-block__footer:hover
.vuepress-plugin-demo-block__expand::before
border-top-color: $accentColor !important;
border-bottom-color: $accentColor !important;
svg
fill: $accentColor !important;
// 全文搜索框
.suggestions
overflow: auto
max-height: calc(100vh - 6rem)
@media (max-width: 719px) {
width: 90vw;
min-width: 90vw!important;
margin-right: -20px;
}
.highlight
color: $accentColor
font-weight: bold
@@ -4,9 +4,9 @@
//***vdoing-***//
// //
$nprogressColor = #4274F4
// $bannerTextColor = #fff // 首页banner区()
// $accentColor = #11A8CD
$accentColor = #4274F4
// $arrowBgColor = #ccc
// $badgeTipColor = #42b983
// $badgeWarningColor = darken(#ffe564, 35%)
@@ -23,40 +23,40 @@
// $lineNumbersWrapperWidth = 2.5rem
//
// .theme-mode-light
// --bodyBg: rgba(255,255,255,1)
// --mainBg: rgba(255,255,255,1)
// --sidebarBg: rgba(255,255,255,.8)
// --blurBg: rgba(255,255,255,.9)
// // --textColor: #004050
// --textLightenColor: #0085AD
// --borderColor: rgba(0,0,0,.15)
// --codeBg: #f6f6f6
// --codeColor: #525252
// codeThemeLight()
.theme-mode-light
--bodyBg: #f4f4f4
--mainBg: #fff
--sidebarBg: #fff
--blurBg: #fff
// --textColor: #004050
--textLightenColor: #4274F4
--borderColor: rgba(0,0,0,.15)
--codeBg: #282C34
--codeColor: #fff
codeThemeDark()
// //
// .theme-mode-dark
// --bodyBg: rgba(30,30,34,1)
// --mainBg: rgba(30,30,34,1)
// --sidebarBg: rgba(30,30,34,.8)
// --blurBg: rgba(30,30,34,.8)
// --textColor: rgb(140,140,150)
// --textLightenColor: #0085AD
// --borderColor: #2C2C3A
// --codeBg: #252526
// --codeColor: #fff
// codeThemeDark()
//
.theme-mode-dark
--bodyBg: #22272E
--mainBg: #22272E
--sidebarBg: #22272E
--blurBg: #22272E
--textColor: #ADBAC7
--textLightenColor: #4274F4
--borderColor: #2C2C3A
--codeBg: #282C34
--codeColor: #fff
codeThemeDark()
// //
// .theme-mode-read
// --bodyBg: rgba(245,245,213,1)
// --mainBg: rgba(245,245,213,1)
// --sidebarBg: rgba(245,245,213,.8)
// --blurBg: rgba(245,245,213,.9)
// --textColor: #004050
// --textLightenColor: #0085AD
// --borderColor: rgba(0,0,0,.15)
// --codeBg: #282c34
// --codeColor: #fff
// codeThemeDark()
//
.theme-mode-read
--bodyBg: rgba(245,245,213,1)
--mainBg: rgba(245,245,213,1)
--sidebarBg: rgba(245,245,213,.8)
--blurBg: rgba(245,245,213,.9)
--textColor: #004050
--textLightenColor: #4274F4
--borderColor: rgba(0,0,0,.15)
--codeBg: #282C34
--codeColor: #fff
codeThemeDark()
@@ -0,0 +1,62 @@
// 原主题变量已弃用,以下是vdoing使用的变量,你可以在这个文件内修改它们。
//***vdoing主题-变量***//
// // 颜色
// $bannerTextColor = #fff // 首页banner区(博客标题)文本颜色
// $accentColor = #11A8CD
// $arrowBgColor = #ccc
// $badgeTipColor = #42b983
// $badgeWarningColor = darken(#ffe564, 35%)
// $badgeErrorColor = #DA5961
// // 布局
// $navbarHeight = 3.6rem
// $sidebarWidth = 18rem
// $contentWidth = 860px
// $homePageWidth = 1100px
// $rightMenuWidth = 230px // 右侧菜单
// // 代码块
// $lineNumbersWrapperWidth = 2.5rem
// 浅色模式
// .theme-mode-light
// --bodyBg: rgba(255,255,255,1)
// --mainBg: rgba(255,255,255,1)
// --sidebarBg: rgba(255,255,255,.8)
// --blurBg: rgba(255,255,255,.9)
// // --textColor: #004050
// --textLightenColor: #0085AD
// --borderColor: rgba(0,0,0,.15)
// --codeBg: #f6f6f6
// --codeColor: #525252
// codeThemeLight()
// // 深色模式
// .theme-mode-dark
// --bodyBg: rgba(30,30,34,1)
// --mainBg: rgba(30,30,34,1)
// --sidebarBg: rgba(30,30,34,.8)
// --blurBg: rgba(30,30,34,.8)
// --textColor: rgb(140,140,150)
// --textLightenColor: #0085AD
// --borderColor: #2C2C3A
// --codeBg: #252526
// --codeColor: #fff
// codeThemeDark()
// // 阅读模式
// .theme-mode-read
// --bodyBg: rgba(245,245,213,1)
// --mainBg: rgba(245,245,213,1)
// --sidebarBg: rgba(245,245,213,.8)
// --blurBg: rgba(245,245,213,.9)
// --textColor: #004050
// --textLightenColor: #0085AD
// --borderColor: rgba(0,0,0,.15)
// --codeBg: #282c34
// --codeColor: #fff
// codeThemeDark()
@@ -0,0 +1,123 @@
---
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'), // 使用本地主题包
}
```
:::
-->
@@ -0,0 +1,47 @@
---
title: 后台操作指南
date: 2023-03-28 20:30:00
permalink: /pages/793dcc
article: false
---
# 首页
- License统计:统计License相关数量指标
- 端口映射统计:统计端口映射相关数量指标
- 今日流量:统计当天的服务端上行/下行流量数据
- 流量汇总:统计所有(包含当天)的服务端上行/下行流量数据
- 流量监控:按天统计最近15天(可能会动态调整)每天的上行流量、下行流量、汇总流量
<img :src="$withBase('/img/run-example/home.png')"></img>
# 代理配置
- License管理:License是客户端连接服务端的唯一合法凭证。一个License同时只能被一个客户端使用,一个License可以维护多条端口映射
- 端口映射:服务端IP+端口 -> 客户端IP+端口的四元组映射(因目前服务端单节点只有一个公网IP,所以不体现出来),是内网穿透的基本单元。
<img :src="$withBase('/img/run-example/license1.png')"></img>
# 系统管理
- 用户管理:支持多用户,一个用户可持有多个License。由于项目目前主推个人版,所以暂是没有权限这一套,管理员之外的所有用户都属于游客。对于绝大多数操作,游客仅有只读权限。
- 端口池管理:用于统一管理服务器内网穿透端口,方便统一设置安全组。
- 端口池分组:对端口池的一个分组。
- 全局分组:该分组下的端口全局通用。
- 用户分组:该分组下的端口由分组绑定的用户独占。
- License分组:该分组下的端口由分组绑定的License独占。
- 调度管理:维护服务端定时任务。方便开发、调试。正常使用时无需关心。
<img :src="$withBase('/img/run-example/user-manager1.png')"></img>
# 报表管理
- 用户流量报表:基于用户维度的流量统计
- License流量报表:基于License的流量统计
- 用户流量月度明细:基于用户的流量月度统计
- License流量月度明细:基于License的流量月度统计
<img :src="$withBase('/img/run-example/user-flow1.png')"></img>
# 日志管理
- 调度日志:服务端定时任务执行日志
- 登录日志:管理后端登录、退出登录的日志
- 客户端连接日志:客户端连接、断开的日志
<img :src="$withBase('/img/run-example/login-log1.png')"></img>
@@ -0,0 +1,31 @@
---
title: 关于端口
date: 2023-04-05 18:20:18
permalink: /pages/68badb/
---
# 端口池
> 端口池用于统一、集中管理服务端代理对外开放的代理端口。
- 1、这里不包含服务端启动本身所需要的一些特殊端口,如:WEB端口、对客户端开放的端口(9000、9002)、HTTP代理端口。
- 2、端口映射里所有的服务端端口,都包含在端口池内。方便了统一管理、安全组/防火墙设置。
# 端口池分组
> 顾名思义,它是对端口池下的端口做了一个划分。
- 1、允许同一个分组内的端口拥有特定的归属。比如:全局分组下的端口,进行代理时所有用户共享,这里极端情况下涉及到端口抢占问题。而用户分组下的端口,归属于某一个特定的用户。
License分组下的端口,归属于某一个特定的License。
- 2、该划分,使得特定的用户、License能够独占一部分端口。为后续更多场景下代理,避免端口被占用提供了支持。
# 端口映射
> 当前版本代理功能的基本单元。
- 1、该映射由一个四元组唯一标识(代理服务端IP、代理服务端端口、被代理服务局域网IP、被代理服务端口)。
- 2、由于本项目为个人版,代理服务端仅支持一个节点,所以四元组中的服务端IP可以省略,不在端口映射管理中体现。
- 3、代理成功的情况下,通过访问代理服务端IP+端口,可以达到访问内网被代理服务IP+端口的同等效果
- 4、例如:本地启动一个socket服务,需要和异地的同事联调。可以通过中微子代理jetbrains客户端插件(后续会推出)将本地socket端口代理至公网。同事通过访问代理服务端公网IP+端口进行调试。
# 基础端口说明
- WEB端口:服务端API、后台管理访问端口,默认为:8888
- 服务端等待客户端连接的端口,非SSL:默认9000,SSL端口:默认9002(若不需要SSL支持,可不配置SSL端口)
- HTTP代理端口:默认80,用于域名映射,若无需域名映射,可以忽略。
@@ -0,0 +1,62 @@
---
title: 域名映射
date: 2023-04-05 18:46:12
permalink: /pages/269a2e/
---
# 什么是域名映射?
域名映射是将指定端口映射绑定到某一个子域名上,通过直接访问域名的方式,达到访问被代理服务的目的。
# 域名映射能解决什么问题?
- 1、当服务端端口需要经常变动的时候,可以通过固定域名的方式,使得开发调试能够长期稳定的进行,不用每次更改访问地址。
- 2、使用域名可以使用有意义的名称方便记忆
- 3、调试某些第三方回调时,回调地址不能填写IP+端口的形式,此时需要域名来完成
# 域名映射的大致流程?
> 例如你有一个已备案的域名:asgc.fun,以下是使用域名映射的大致流程
## 情况1:代理服务端使用80端口
- 1、DNS解析配置泛域名解析 proxy.asgc.fun 指向代理服务端IP
- 2、代理服务端app.yml配置域名:proxy.asgc.fun,部署
- 3、服务端管理后台新建端口映射,协议选择:HTTP,设置好子域名,如:test1
- 4、配置好客户端并启动,代理映射完成
- 5、通过访问: http://test1.proxy.asgc.fun 实现访问被代理服务
## 情况2:代理服务端不使用80端口,80端口被nginx占用
> 假设代理服务端使用82作为HTTP代理端口
- 1、DNS解析配置泛域名解析 proxy.asgc.fun 指向代理服务端IP
- 2、nginx 80端口下将 *.proxy.asgc.fun 请求转到82端口,并附带原有请求头(特别是`Host`
- 3、代理服务端app.yml配置域名:proxy.asgc.fun,部署
- 4、服务端管理后台新建端口映射,协议选择:HTTP,设置好子域名,如:test1
- 5、配置好客户端并启动,代理映射完成
- 6、通过访问: http://test1.proxy.asgc.fun 实现访问被代理服务
# 所有的代理映射都能绑定域名吗?
不行的。之所以能实现域名绑定,原因是HTTP请求附带了Host请求头,包含了请求的域名信息,代理服务端能根据请求的域名做分发。而TCP协议本身并不包含域名,因此无法实现。
所以,目前只有HTTP(涵盖HTTPS)协议能支持。
# 代理服务端使用非80端口时,nginx配置示例
```
server {
listen 80;
# 此处配不配貌似不影响
server_name localhost *.neutrino-proxy.asgc.fun;
location / {
if ($http_host ~* "(.*?).neutrino-proxy.asgc.fun") {
# 转发到代理服务端HTTP代理端口
proxy_pass http://localhost:8899;
}
tcp_nodelay on;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#以上三行,目的是将代理服务器收到的用户的信息传到真实服务器上
root html;
index index.html index.htm;
}
}
```
@@ -0,0 +1,25 @@
---
title: HTTPS配置
date: 2023-05-27 11:40:12
permalink: /pages/99a300/
---
## 基础说明
- 在中微子代理中,HTTP的定位是:为ip+端口的访问形式增加域名访问的支持。因此:
- 如果端口映射正确配置了域名,那么该映射同时支持IP+端口、域名2种访问形式。
- 如果端口映射未配置域名,那么只能以ip+端口的形式访问,此时等价于选择TCP协议。
- 在中微子代理中,HTTPS作为HTTP的一种增强行为,不作为一个单独的协议。因此:
- 在端口映射中只需要选择HTTP即可
- 若端口映射未正确配置域名,那么同理,此时等价于选择TCP协议。
- 若端口映射配置了域名,但是未正确配置HTTPS端口、证书,那么此时可以通过域名访问,但不支持HTTPS
- 若端口映射配置了域名,且正确配置了HTTPS端口、证书,那么此时可以通过IP+端口、http域名、https域名3种形式访问
## HTTPS配置流程
- 按照域名映射流程,完成域名的配置
- 在服务端配置域名的HTTPS证书信息(此处应该是通配符证书)
## HTTPS端口问题
- 与HTTP类似,HTTPS也有一个默认端口:443,默认情况下需要占用服务端443端口
- 如果代理服务端不能独占443端口,此处可以指定任意端口,但需要在nginx中配置443端口的转发规则。此处与域名映射中代理服务端不使用80端口的情况类似
@@ -0,0 +1,65 @@
---
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,38 @@
---
title: 客户端配置
date: 2023-06-09 21:15:35
permalink: /pages/50ce10/
---
# 以下是最新的客户端配置格式(app.yml)
```yml
neutrino:
proxy:
logger:
# 日志级别
level: ${LOG_LEVEL:info}
client:
# 线程池相关配置,用于技术调优,可忽略
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}
# 重连设置
reconnection:
# 重连间隔(秒)
interval-seconds: 10
# 是否开启无限重连(未开启时,客户端license不合法会自动停止应用,开启了则不会,请谨慎开启)
unlimited: false
```
@@ -0,0 +1,31 @@
---
title: 常见问题汇总
date: 2023-07-01 13:35:00
permalink: /pages/a00001/
---
## 1、客户端启动后连不上服务端,一直在重连
### 1.1、启动日志包含`not found license-key config`相关字样
出现这种情况,说明客户端没获取到配置的license,可按如下检查:
- 客户端是否有配置app.yml,app.yml中是否有配置`license-key`
- 客户端启动参数是否指定app.yml配置,若未指定客户端不会加载任何jar外部的配置文件
- 客户端启动参数指定的配置文件路径是否正确?如:`java -jar neutrino-proxy-client.jar config=app.yml`,代表`app.yml`文件与jar在同级别目录下,如果不在同级别目录,请使用绝对/相对路径指定
- 除此之外license还可以用以下方法直接在启动参数中指定:
- `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、配置文件确定都已经配置,且启动参数正确指定了,但是没有出现`认证成功日志`
- 确认客户端配置的`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"
```
@@ -1,150 +0,0 @@
---
title: 快速上手
date: 2020-05-11 13:54:40
permalink: /pages/793dcb
article: false
---
## 1. 打包
可直接前往Gitee仓库发行版页面下载所需版本已打好的包。若需手动打包,则可参照下面的执行命令:
```text
# 服务端打包
mvn clean install -U -pl neutrino-proxy-server -am -Dmaven.test.skip=true
# 客户端打包
clean install -U -pl neutrino-proxy-client -am -Dmaven.test.skip=true
# 管理后台前端项目打包(本地环境,local改为dev则为dev环境,同时需要修改config目录下面的环境配置)
npm run build:local
```
## 2. 部署
### 2.2 服务端部署
- 使用常规的jar包部署方式即可,如:java -jar xxxx
### 2.4 管理后台部署
- Nginx方式部署(推荐):
```conf
server {
listen 9527;
server_name localhost;
#开启gzip
gzip on;
#低于1kb的资源不压缩
gzip_min_length 1k;
#压缩级别1-9,越大压缩率越高,同时消耗cpu资源也越多,建议设置在5左右。
gzip_comp_level 5;
#需要压缩哪些响应类型的资源,多个空格隔开。不建议压缩图片.
gzip_types text/plain application/javascript application/x-javascript text/javascript text/xml text/css;
#配置禁用gzip条件,支持正则。此处表示ie6及以下不启用gzip(因为ie低版本不支持)
gzip_disable "MSIE [1-6]\.";
#是否添加“Vary: Accept-Encoding”响应头
gzip_vary on;
location / {
root /work/projects/neutrino-proxy-server/neutrino-proxy-admin/dist;
try_files $uri $uri/ /index.html;
add_header Last-Modified $date_gmt;
}
}
````
- 无nginx时
在没有Nginx时,为了快速体验代理效果,可直接使用服务端项目提供的静态资源服务。直接将neutrino-proxy-admin打包后的文件解压放在neutrino-proxy-server.jar同级别目录下即可。例如:服务端配置的web端口为8080,则访问http://服务端IP:8080, 则会直接解析渲染neutrino-proxy-server.jar同级别目录下neutrino-proxy-admin/dist/index.html。
::: warning
需要注意的是,使用服务端自带的静态资源服务时,由于框架目前未支持缓存、gzip压缩,所以访问速度没有使用nginx快,正式使用推荐用nginx。
:::
## 3.配置
### 3.1 配置端口池
端口池的作用是将代理服务需要对外暴露的端口进行集中管理,方便安全组设置时统一操作。
本项目为了简化首次配置,默认初始化数据会将9101~9120的所有端口加入端口池。可以在服务端项目sql配置(port_pool.data.sql文件)中自行修改初始化数据,也可以运行后在端口池管理页面手动维护。
首次使用如果为了快速体验,建议直接使用默认端口池,则无需任何配置。
### 3.2 配置License
License是客户端连接代理服务端时所需要的唯一合法凭证,一个License同时只能被一个客户端使用。
本项目为了简化首次配置,默认为每个用户初始化了一些license。可以在服务端项目sql配置(license.data.sql文件)中自行修改初始化数据,也可以运行后在License管理页面手动维护。
首次使用如果为了快速体验,建议直接使用默认License,则无需任何配置。
### 3.3 配置端口映射
端口映射是代理的基本单元,一个外网端口在同一时刻被唯一的映射到一个内网IP+端口。所有流出该外网端口的流量都转发自对应的内网端口,同理所有流入该外网端口的流量都会转发到对应的内网端口。
本项目为了简化首次配置,默认为每个用户初始化了一些端口映射。可以在服务端项目sql配置(port_mapping.data.sql文件)中自行修改初始化数据,也可以运行后在端口映射管理页面手动维护。
首次使用如果为了快速体验,建议直接使用默认端口映射,则无需任何配置。
### 3.4 开启代理
上述步骤完成后,就可以开始代理自己的局域网设备了。
比如现在有一个licensea123456,该license配置了一个或多个端口映射,其中包含9010到localhost:8080的映射(localhost表示代理客户端所在主机的端口,可以换成客户端所在局域网的任何IP)。此时修改客户端配置的服务端ip、license等启动客户端,就可以开启代理了,端口映射管理对应的记录在线状态为"在线"则证明代理成功建立。
该license配置也可以通过客户端启动参数、启动后引导式输入、外置配置提供,为了简化首次体验门槛,这里直接提供一个外置配置模版:
```json
{
"jksPath":"classpath:/test.jks",
"licenseKey":"b0a907332b474b25897c4dcb31fc7eb6",
"serverIp":"localhost",
"serverPort":9002,
"sslEnable":true
}
```
修改上述json中的licenseKeyserverIp、serverPort、sslEnabled后,保存文件命名为<font color="#9b6e23">.neutrino-proxy-client.json</font>,放在客户端当前目录下(jar包启动时,放在jar包同级别目录下。idea启动时,放在项目根目录下),然后直接启动客户端。
此时通过访问外网ip+端口,可以成功访问内网服务。通过此方式,可以代理任何基于TCP之上的协议,如:socket、websocket、http、ftp、ssh等。
## 4.开发&调试
篇幅所限,此处不便赘述管理后台相关的开发,有vue基础的童鞋基本都可以自行开发。服务端的初始化数据足够调试工作,开发过程无需单独在管理页面操作。
与SpringBoot项目类似,客户端、服务端项目均由一个入口类完成整个项目的启动,分别为ProxyClient、ProxyServer。
内置配置采用yml风格,主要涉及Http端口、静态资源路径、协议参数、代理服务端端口、jks证书、数据源、license等。整个项目基于neutrino-core,风格类似于SpringBoot。笔者不喜欢因过分炫技而引入过多花式操作,因为项目的定位是个人开发者,且满足使用的同时兼顾学习其原理的需求。基本使用方式与SpringBoot类似,尽可能降低首次学习成本。
neutrino-core项目test目录下包含众多核心封装的测试代码,通过调试这些代码,能尽可能减少大家学习的障碍。
## 5.运行截图
### 用户管理
<img :src="$withBase('/img/run-example/user-manager1.png')"></img>
### 端口池管理
<img :src="$withBase('/img/run-example/port-pool1.png')"></img>
### License管理
<img :src="$withBase('/img/run-example/license1.png')"></img>
### 端口映射管理
<img :src="$withBase('/img/run-example/port-mapping1.png')"></img>
### 客户端启动示例
<img :src="$withBase('/img/run-example/client-run1.png')"></img>
<!--
## 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,68 +0,0 @@
---
title: 目录结构
date: 2020-05-11 13:54:56
permalink: /pages/2f674a
article: false
---
```
├── data.db (sqlite数据库文件。若未配置mysql,默认使用sqlite,项目首次启动会自动初始化sqlite数据库。)
├── docs (项目相关的一些文档)
├── ├── Aop.MD (框架层Aop机制、使用说明)
├── └── Channel.MD (内网穿透实现原理、代理实现流程说明)
├── lib (项目开启了将自动生成的类保存到本地后,运行过程中动态生成的类自动保存到此处,方便学习、调试)
├── neutrino-core (一套手写的基于netty的框架,相当于简易版的SpringBoot + Mybatis + xxljob,计划后期分离为单独开源项目维护)
├── neutrino-proxy-admin (基于vue-element-admin开发的一个管理系统,用于可视化操作端口映射、代理数据实时监控)
├── neutrino-proxy-client (基于netty的代理客户端,用于和服务端交互、转发内网数据)
├── neutrino-proxy-core (代理相关的公共代码(协议、常量))
├── neutrino-proxy-server (基于netty的代理服务端,用于和客户段交互,将客户端转发的内网数据转发至外网端口)
└── todolist.MD (近期的开发计划)
```
<!--
* `docs` 文件夹名称请不要修改
* `docs/.vuepress` 用于存放全局的配置、样式、静态资源等,同官方,查看 [详情](https://vuepress.vuejs.org/zh/guide/directory-structure.html#目录结构)
* `docs/@pages` 此文件夹是自动生成的,存放分类页、标签页、归档页对应的`.md`文件,一般不需要改动
* `docs/_posts` 专门用于存放碎片化博客文章,里面的`.md`文件不需要遵循命名约定,不会生成结构化侧边栏和目录页。
* `docs/<结构化目录>` 请查看[《构建结构化站点的核心配置和约定》](/pages/33d574/)。
* `docs/index.md` 首页
* `vdoing` 存放在本地的vdoing主题文件,如果你想深度的修改主题,首先要在`docs/.vuepress/config.js`中配置使用的主题指向这个文件。
<code-group>
<code-block title="config.js" active>
``` js
module.exports = {
// theme: 'vdoing', // npm主题依赖包
theme: require.resolve('../../vdoing'), // 使用本地主题包
}
```
</code-block>
<code-block title="config.ts">
``` typescript
import { resolve } from 'path'
import { defineConfig4CustomTheme } from 'vuepress/config'
import { VdoingThemeConfig } from 'vuepress-theme-vdoing/types'
export default defineConfig4CustomTheme<VdoingThemeConfig>({
// theme: 'vdoing', // 使用npm主题包
theme: resolve(__dirname, '../../vdoing'), // 使用本地主题包
})
```
</code-block>
</code-group>
**注意**:主题的后续维护升级只对npm主题包负责,就是说你使用本地主题就等于放弃了后续的升级服务。因此,建议能在`docs/.vuepress/`内配置和修改的,就尽量不要改动主题内部代码。
---
-->
::: tip 提示
为了方便您更快的学习和使用本主题,我在代码当中添加了比较多的注释说明。
:::
@@ -1,373 +0,0 @@
---
title: Markdown 容器
date: 2020-05-29 11:16:18
permalink: /pages/d0d7eb/
article: false
---
Markdown 容器是对 Markdown 语法的一个扩展,使用简单的语法就可以在页面中呈现丰富的效果。
除了原默认主题自带的容器外,本主题还新增了一些好用的自定义容器。
## 信息框容器
**输入**
```` md
::: tip
这是一条提示
:::
::: warning
这是一条注意
:::
::: danger
这是一条警告
:::
::: note
这是笔记容器,在 <Badge text="v1.5.0 +" /> 版本才支持哦~
:::
````
**输出**
::: tip
这是一条提示
:::
::: warning
这是一条注意
:::
::: danger
这是一条警告
:::
::: note
这是笔记容器,在 <Badge text="v1.5.0 +" /> 以上版本才支持哦~
:::
以上容器均可自定义标题,如:
````
::: tip 我的提示
自定义标题的提示框
:::
````
::: tip 我的提示
自定义标题的提示框
:::
## 布局容器 <Badge text="v1.3.3 +" />
**输入**
```` md
::: center
### 我是居中的内容
(可用于标题、图片等的居中)
:::
::: right
[我是右浮动的内容](https://zh.wikipedia.org/wiki/%E7%89%9B%E9%A1%BF%E8%BF%90%E5%8A%A8%E5%AE%9A%E5%BE%8B)
:::
::: details
这是一个详情块,在 IE / Edge 中不生效
```js
console.log('这是一个详情块')
```
:::
::: theorem 牛顿第一定律
假若施加于某物体的外力为零,则该物体的运动速度不变。
::: right
来自 [维基百科](https://zh.wikipedia.org/wiki/%E7%89%9B%E9%A1%BF%E8%BF%90%E5%8A%A8%E5%AE%9A%E5%BE%8B)
:::
````
**输出**
::: center
### 我是居中的内容
(可用于标题、图片等的居中)
:::
::: right
[我是右浮动的内容](https://zh.wikipedia.org/wiki/%E7%89%9B%E9%A1%BF%E8%BF%90%E5%8A%A8%E5%AE%9A%E5%BE%8B)
:::
::: details
这是一个详情块,在 IE / Edge 中不生效
```js
console.log('这是一个详情块')
```
:::
::: theorem 牛顿第一定律
假若施加于某物体的外力为零,则该物体的运动速度不变。
::: right
来自 [维基百科](https://zh.wikipedia.org/wiki/%E7%89%9B%E9%A1%BF%E8%BF%90%E5%8A%A8%E5%AE%9A%E5%BE%8B)
:::
> 注意:`right``details``theorem`这三个容器在`v1.3.0 +`版本才支持。`center`容器在`v1.3.3 +`版本才支持。
## 普通卡片列表 <Badge text="v1.1.0 +"/>
普通卡片列表容器,可用于`友情链接``项目推荐``诗词展示`等。
先来看看效果:
**输出**
::: cardList
```yaml
- name: 麋鹿鲁哟
desc: 大道至简,知易行难
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200122153807.jpg # 可选
link: https://www.cnblogs.com/miluluyo/ # 可选
bgColor: '#CBEAFA' # 可选,默认var(--bodyBg)。颜色值有#号时请添加单引号
textColor: '#6854A1' # 可选,默认var(--textColor)
- name: XAOXUU
desc: '#IOS #Volantis主题作者'
avatar: https://fastly.jsdelivr.net/gh/xaoxuu/assets@master/avatar/avatar.png
link: https://xaoxuu.com
bgColor: '#718971'
textColor: '#fff'
- name: 平凡的你我
desc: 理想成为大牛的小陈同学
avatar: https://reinness.com/avatar.png
link: https://reinness.com
bgColor: '#FCDBA0'
textColor: '#A05F2C'
```
:::
上面效果在Markdown中的代码是这样的:
**输入**
```` md
::: cardList
```yaml
- name: 麋鹿鲁哟
desc: 大道至简,知易行难
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200122153807.jpg # 可选
link: https://www.cnblogs.com/miluluyo/ # 可选
bgColor: '#CBEAFA' # 可选,默认var(--bodyBg)。颜色值有#号时请添加单引号
textColor: '#6854A1' # 可选,默认var(--textColor)
- name: XAOXUU
desc: '#IOS #Volantis主题作者'
avatar: https://fastly.jsdelivr.net/gh/xaoxuu/assets@master/avatar/avatar.png
link: https://xaoxuu.com
bgColor: '#718971'
textColor: '#fff'
- name: 平凡的你我
desc: 理想成为大牛的小陈同学
avatar: https://reinness.com/avatar.png
link: https://reinness.com
bgColor: '#FCDBA0'
textColor: '#A05F2C'
```
:::
````
### 语法
````md
::: cardList <每行显示数量>
``` yaml
- name: 名称
desc: 描述
avatar: https://xxx.jpg # 头像,可选
link: https://xxx/ # 链接,可选
bgColor: '#CBEAFA' # 背景色,可选,默认var(--bodyBg)。颜色值有#号时请添加引号
textColor: '#6854A1' # 文本色,可选,默认var(--textColor)
```
:::
````
* `<每行显示数量>` 数字,表示每行最多显示多少个,选值范围1~4,默认3。在小屏时会根据屏幕宽度减少每行显示数量。
* 代码块需指定语言为`yaml`
* 代码块内是一个`yaml`格式的数组列表
* 数组成员的属性有:
* `name`名称
* `desc`描述
* `avatar`头像,可选
* `link`链接,可选
* `bgColor`背景色,可选,默认`var(--bodyBg)`。颜色值有`#`号时请添加引号
* `textColor`文本色,可选,默认`var(--textColor)`
下面再来看另外一个示例:
**输入**
```` md
::: cardList 2
```yaml
- name: 《静夜思》
desc: 床前明月光,疑是地上霜。举头望明月,低头思故乡。
bgColor: '#F0DFB1'
textColor: '#242A38'
- name: Vdoing
desc: 🚀一款简洁高效的VuePress 知识管理&博客(blog) 主题
link: https://github.com/xugaoyi/vuepress-theme-vdoing
bgColor: '#DFEEE7'
textColor: '#2A3344'
```
:::
````
**输出**
::: cardList 2
```yaml
- name: 《静夜思》
desc: 床前明月光,疑是地上霜。举头望明月,低头思故乡。
bgColor: '#F0DFB1'
textColor: '#242A38'
- name: Vdoing
desc: 🚀一款简洁高效的VuePress 知识管理&博客(blog) 主题
link: https://github.com/xugaoyi/vuepress-theme-vdoing
bgColor: '#DFEEE7'
textColor: '#2A3344'
```
:::
## 图文卡片列表 <Badge text="v1.1.0 +" />
图文卡片列表容器,可用于`项目展示``产品展示`等。
先看效果:
**输出**
::: cardImgList
```yaml
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200529162253.jpg
link: https://xugaoyi.com/
name: 标题
desc: 描述内容描述内容描述内容描述内容描述内容描述内容描述内容描述内容 # 描述,可选
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/20200530100256.jpg
link: https://xugaoyi.com/
name: 标题
desc: 描述内容描述内容描述内容描述内容描述内容描述内容描述内容描述内容
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/20200530100257.jpg
link: https://xugaoyi.com/
name: 标题
desc: 描述内容描述内容描述内容描述内容描述内容描述内容描述内容描述内容
author: Evan Xu
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200103123203.jpg
```
:::
**输入**
````md
::: cardImgList
```yaml
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200529162253.jpg
link: https://xugaoyi.com/
name: 标题
desc: 描述内容描述内容描述内容描述内容描述内容描述内容描述内容描述内容 # 描述,可选
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/20200530100256.jpg
link: https://xugaoyi.com/
name: 标题
desc: 描述内容描述内容描述内容描述内容描述内容描述内容描述内容描述内容
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/20200530100257.jpg
link: https://xugaoyi.com/
name: 标题
desc: 描述内容描述内容描述内容描述内容描述内容描述内容描述内容描述内容
author: Evan Xu
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200103123203.jpg
```
:::
````
### 语法
````md
::: cardImgList <每行显示数量>
``` yaml
- img: https://xxx.jpg # 图片地址
link: https://xxx.com # 链接地址
name: 标题
desc: 描述 # 可选
author: 作者名称 # 可选
avatar: https://xxx.jpg # 作者头像,可选
```
:::
````
* `<每行显示数量>` 数字,表示每行最多显示多少个,选值范围1~4,默认3。在小屏时会根据屏幕宽度减少每行显示数量。
* 代码块需指定语言为`yaml`
* 代码块内是一个`yaml`格式的数组列表
* 数组成员的属性有:
* `img`图片地址
* `link`链接地址
* `name`标题
* `desc`描述,可选
* `author`作者名称,可选
* `avatar`作者头像,可选
## 增强配置 <Badge text="v1.9.0 +"/>
为了适应更多需求场景,`v1.9.0+`版本的普通卡片和图文卡片容器添加了一些新的配置:
### 1. 普通卡片和图文卡片容器
#### target
- 链接的打开方式,默认`_blank`
- `_self` 当前页面
- `_blank` 新窗口打开
### 2. 图文卡片容器
#### imgHeight
- 设置图片高度,默认 `auto`
- 带单位
#### objectFit
- 设置图片的填充方式(object-fit),默认 `cover`
- `fill` 拉伸 (会改变宽高比)
- `contain` 缩放 (保持宽高比,会留空)
- `cover` 填充 (会裁剪)
- `none` 保持原有尺寸 (会留空或裁剪)
- `scale-down` 保证显示完整图片 (保持宽高比,会留空)
#### lineClamp
- 描述文本超出多少行显示省略号,默认`1`
### 3. 配置示例:
````yaml
::: cardImgList
``` yaml
config:
target: _blank
imgHeight: auto
objectFit: cover
lineClamp: 1
data:
- img: https://xxx.jpg
link: https://xugaoyi.com/
name: 标题
desc: 描述内容
author: Evan Xu
avatar: https://xxx.jpg
```
:::
````
@@ -1,70 +0,0 @@
---
title: Markdown 中使用组件
date: 2020-11-10 18:56:22
permalink: /pages/197691/
article: false
---
主题的内置组件可以直接在`Markdown`文件中以类似html标签的方式使用。
## 标记
- **Props:**
- `text`- string
- `type` - string, 可选值: `tip | warning | error`,默认: `tip`
- `vertical` - string, 可选值: `top | middle`,默认: `top`
- **Usage:**
你可以在标题或其他内容中使用标记:
```md
#### 《沁园春·雪》 <Badge text="摘"/>
北国风光<Badge text="注释" type="warning"/>,千里冰封,万里雪飘。
> <Badge text="译文" type="error" vertical="middle"/>: 北方的风光。
```
**效果:**
#### 《沁园春·雪》 <Badge text="摘"/>
北国风光<Badge text="注释" type="warning"/>,千里冰封,万里雪飘。
> <Badge text="译文" type="error" vertical="middle"/>: 北方的风光。
## 代码块选项卡 <Badge text="v1.8.0 +"/>
`<code-group>`中嵌套`<code-block>`来配合使用。在`<code-block>`标签添加`title`来指定tab标题,`active`指定当前tab
````md
<code-group>
<code-block title="YARN" active>
```bash
yarn add vuepress-theme-vdoing -D
```
</code-block>
<code-block title="NPM">
```bash
npm install vuepress-theme-vdoing -D
```
</code-block>
</code-group>
````
**效果:**
<code-group>
<code-block title="YARN" active>
```bash
yarn add vuepress-theme-vdoing -D
```
</code-block>
<code-block title="NPM">
```bash
npm install vuepress-theme-vdoing -D
```
</code-block>
</code-group>
::: warning
- 请在`<code-group>`标签与markdown内容之间使用空行隔开,否则可能会解析不出来。
- 该组件只适用于放置代码块,放其他内容在体验上并不友好。如您确实需要放置其他内容的选项卡,推荐使用[vuepress-plugin-tabs](https://superbiger.github.io/vuepress-plugin-tabs)插件。
:::
@@ -0,0 +1,39 @@
---
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证书
@@ -0,0 +1,61 @@
---
title: 实现原理
date: 2020-09-26 21:13:59
permalink: /pages/dd027d/
article: false
---
# 代理实现中涉及的几类Channel
## 指令通道(CmdChannel)
- 该channel负责维护客户端与服务端之间的指令通讯,由客户端启动后向服务端发起连接请求,服务端验证license成功后连接建立。
- 服务端维护第一个CmdChannel的映射表,key为licenseId。服务端可以根据licenseId,向指定的客户端指令通道发布指令。
- 该通道建立完成后,服务端会将该license授权的外网映射端口打开,等待用户访问。正常情况下,只要客户端不下线,该通道一直可用。
- 然后服务端维护第二个CmdChannel的映射表,key为服务端外网端口。服务端可以根据指定的外网端口,向指定的客户端指令通道发布指令。
## 用户访问通道(VisitorChannel)
- 该channel负责维护访问者与服务端端口之间的通讯,由访问者向服务端映射的外网端口发起请求开始建立,具体断开时机由实际的被代理的
协议决定。
- 如HTTP/1.0下,用户向该端口发起请求,响应结束后,该通道随之关闭,下一次发起请求后,重新建立新的连接。
- 该连接建立后,服务端会向该外网端口映射的指令通道发送`Connect`指令,传输该外网端口需要代理的内网信息,
如:`127.0.0.1:3306`
## 被代理服务的实际通道(RealServerChannel)
- 该channel负责维护客户端与实际被代理服务之间的通讯,当客户端接收到服务端的`Connect`指令后,客户端便会建立该通道。
## 代理数据传输的通道(ProxyChannel)
- 该channel负责完成内网被代理服务与代理服务端之间的数据转发任务。
- 每个客户端维护一个`ProxyChannel`的缓存队列,需要时从该队列中取,当取不到时,直接新建一个`ProxyChannel`返回。
当一个`ProxyChannel`实例用完后,需要归还到缓存队列中(`ProxyChannel`收到`DisConnect`指令时)。
- 当`RealServerChannel`建立完成后,就会获取相关联的`ProxyChannel`,并与其绑定。设置`RealServerChannel`
可读状态。然后通过`ProxyChannel`向服务端发送`Connect`指令。
# 代理实现流程
## 1、服务连接阶段
- 1.1、客户端根据是否需要使用SSL,选择对应的服务端端口发起连接,建立`CmdChannel`
- 1.2、客户端根据用户输入或配置文件获取`license`,并携带`license`通过`CmdChannel`向服务端发送`Auth`指令。
- 1.3、服务端通过`CmdChannel`接收到来自客户端的`Auth`指令。若验证`license`有效,则建立`licenseId``CmdChannel`的映射缓存、
外网端口与`CmdChannel`的映射缓存。并启动服务端代理端口,等待用户连接。
## 2、用户连接阶段
- 2.1、用户向服务端代理的外网端口发起请求,服务端建立`VisitorChannel`
- 2.2、根据外网端口查找`CmdChannel`,若不存在有效的`CmdChannel`,则关闭该`VisitorChannel`。否则,
设置`VisitorChannel`为不可读,并携带内网映射信息(如:`127.0.0.1:3306`)通过`CmdChannel`向客户端发送`Connect`指令。
## 3、实际被代理服务连接阶段
- 3.1、客户端通过`CmdChannel`接收到服务端的`Connect`指令。拿到需要代理的内网IP、端口号,向实际被
代理服务发起连接请求,若连接失败,则通过`CmdChannel`向服务端发送`DisConnect`指令。建立`RealServerChannel`成功,设置`RealServerChannel`为不可读
状态,并进入4.1阶段
## 4、代理通道连接阶段
- 4.1、客户端通过`ProxyChannelQueue`获取或新建一个`ProxyChannel`,并将`RealServerChannel``ProxyChannel`进行绑定。
- 4.2、客户端通过`ProxyChannel`向服务端发送`Connect`指令。
- 4.3、服务端通过`ProxyChannel`通道收到来自客户端的`Connect`指令后,将`ProxyChannel`与对应的`VisitorChannel`进行绑定,并
设置`VisitorChannel`为可读状态。
## 5、数据传输阶段
- 5.1、服务端通过`VisitorChannel`收到来自用户的请求数据,然后找到`VisitorChannel`绑定的`ProxyChannel`
通过`ProxyChannel`发送`Transfer`指令,并携带用户请求数据。
- 5.2、客户端通过`ProxyChannel`收到来自服务端的`Transfer`指令,取出用户请求数据。找到`ProxyChannel`绑定的`RealServerChannel`,
通过`RealServerChannel`向被代理服务写入用户请求数据。
- 5.3、客户端通过`RealServerChannel`收到被代理服务响应的数据,找到`RealServerChannel`绑定的`ProxyChannel`
通过`ProxyChannel`向服务端发送`Transfer`指令,并携带响应数据。
- 5.4、服务端通过`ProxyChannel`收到来自客户端的`Transfer`指令,找到`ProxyChannel`绑定的`VisitorChannel`
通过`VisitorChannel`向用户端写入响应数据。
@@ -1,26 +0,0 @@
---
title: 使目录栏支持h2~h6标题
date: 2022-03-18 15:02:52
permalink: /pages/8dfab5/
article: false
---
`.vuepress/config.js`添加如下配置即可使 VuePress 提取相应标题级别的数据,并应用到主题的右侧目录栏中<Badge text="v1.10.0 +"/>。
## markdown.extractHeaders
- 类型: Array
- 默认: ['h2', 'h3']
Markdown 文件的 headers (标题 & 小标题) 会在准备阶段被提取出来,并存储在 this.$page.headers 中。默认情况下,VuePress 会提取 h2 和 h3 标题。你可以通过这个选项来修改提取出的标题级别。
```js
module.exports = {
markdown: {
extractHeaders: [ 'h2', 'h3', 'h4', 'h5', 'h6' ]
}
}
```
注:此配置来自 [VuePress官方文档](https://vuepress.vuejs.org/zh/config/#markdown-extractheaders)
@@ -1,230 +0,0 @@
---
title: 如何让你的笔记更有表现力
date: 2020-09-26 21:13:59
permalink: /pages/dd027d/
article: false
---
你的知识笔记枯燥无味没有重点?基于本主题,配合各种骚操作,让你的知识笔记表现力爆棚~~
::: note
Markdown的基本语法就不再重复啦 (对Markdown不了解的可以看 [这里](https://xugaoyi.com/pages/ad247c4332211551/)),
下面将介绍一些可以在本主题Markdown中使用的骚操作~
:::
## 1. 文本高亮
使用`<mark>`标签让文本高亮
```text
Vdoing是一款简洁高效的 <mark>知识管理&博客</mark> 主题
```
Vdoing是一款简洁高效的 <mark> 知识管理&博客 </mark> 主题
## 2. 标记
### 内置标记
主题内置的[Badge组件](https://vuepress.vuejs.org/zh/guide/using-vue.html#badge),直接在 Markdown 文件中使用
```html
<Badge text="beta" type="warning"/>
<Badge text="Vdoing主题"/>
```
<Badge text="beta" type="warning"/>
<Badge text="Vdoing主题"/>
### 外部标记
使用 [shields](https://shields.io/) 生成标记,在Markdown中使用
```markdown
![npm](https://img.shields.io/npm/v/vuepress-theme-vdoing)
![star](https://img.shields.io/github/stars/xugaoyi/vuepress-theme-vdoing)
```
![npm](https://img.shields.io/npm/v/vuepress-theme-vdoing)
![star](https://img.shields.io/github/stars/xugaoyi/vuepress-theme-vdoing)
> 这类标记图标可以生成动态统计数据。
## 3. 折叠列表
主题内置的容器,直接在 Markdown 文件中使用
````html
::: details
这是一个详情块
```js
console.log('这是一个详情块')
```
:::
````
::: details
这是一个详情块
```js
console.log('这是一个详情块')
```
:::
> 更多:[Markdown 容器](/pages/d0d7eb/)
## 4. 思维导图 & 流程图
### 方法一:
1. 使用[Markmap](https://markmap.js.org/)生成思维导图html文件
2. 将html文件放在`docs/.vuepress/public/markmap/`
3. 通过`<iframe>`插入到Markdown
``` html
<iframe :src="$withBase('/markmap/01.html')" width="100%" height="400" frameborder="0" scrolling="No" leftmargin="0" topmargin="0"></iframe>
```
<iframe :src="$withBase('/markmap/01.html')" width="100%" height="400" frameborder="0" scrolling="No" leftmargin="0" topmargin="0"></iframe>
### 方法二:
通过`<iframe>`标签引入[processon](https://www.processon.com/)或其他在线作图工具生成的链接。
```html
<iframe src="https://www.processon.com/view/link/5e718942e4b015182028682c" width="100%" height="500" frameborder="0" scrolling="No" leftmargin="0" topmargin="0"></iframe>
```
<iframe src="https://www.processon.com/view/link/5e718942e4b015182028682c" width="100%" height="500" frameborder="0" scrolling="No" leftmargin="0" topmargin="0"></iframe>
### 方法三:
使用流程图插件:
* [vuepress-plugin-flowchart](https://www.npmjs.com/package/vuepress-plugin-flowchart)
* [vuepress-plugin-mermaidjs](https://github.com/eFrane/vuepress-plugin-mermaidjs)
## 5.Demo演示框
### 方法一:
1. 安装 [vuepress-plugin-demo-block](https://www.npmjs.com/package/vuepress-plugin-demo-block)或其他同类插件,使用方法看插件文档
2. 在`.vuepress/config.js`配置插件
3. Markdown中使用
> 同类插件:[vuepress-plugin-demo-container](https://github.com/calebman/vuepress-plugin-demo-container)
::: demo [vanilla]
```html
<html>
<div class="animationBox">
<div class="rotate">旋转动画1</div>
<div class="play">
<div class="img">旋转动画2</div>
<span><p class="p2"></p></span>
<span><p></p></span>
<span><p></p></span>
<span><p class="p2"></p></span>
</div>
<div class="elasticity">弹性动画</div>
<div class="elasticity2">曲线弹性</div>
</div>
</html>
<style>
.animationBox{overflow: hidden;}
.animationBox>div{
width: 100px;height: 100px;background: #eee;border-radius: 50%;text-align: center;line-height: 100px;margin: 30px;float:left;
}
.rotate{
animation: rotate 5s linear infinite
}
.rotate:hover{ animation-play-state: paused}
@keyframes rotate {
0%{transform: rotate(0);}
100%{transform: rotate(360deg);}
}
.animationBox>.play {
position: relative;
margin: 50px 30px;
background:none;
}
.play .img{
position: absolute;
top: 0;
left:0;
z-index: 1;
width: 100px;height: 100px; background: #eee;
border-radius: 50%;
animation: rotate 5s linear infinite
}
.play span {
position: absolute;
top: 1px;
left:1px;
z-index: 0;
display: block;
width: 96px;
height: 96px;
border: 1px solid #999;
border-radius: 50%;
}
.play span p{display: block;width: 4px;height: 4px;background: #000;margin: -2px 0 0 50%;border-radius: 50%;opacity: 0.5;}
.play span .p2{margin: 50% 0 0 -2px;}
.play span{
animation: wave 5s linear infinite
}
.play>span:nth-child(3){
/* 延迟时间 */
animation-delay:1s;
}
.play>span:nth-child(4){
animation-delay:2.2s;
}
.play>span:nth-child(5){
animation-delay:3.8s;
}
@keyframes wave {
0%
{
transform:scale(1) rotate(360deg);
opacity: 0.8;
}
100%
{
transform:scale(1.8) rotate(0deg);
opacity: 0;
}
}
.elasticity{
animation: elasticity 1s linear 2s infinite
}
@keyframes elasticity{
0%{
transform: scale(0);
}
60%{
transform: scale(1.1);
}
90%{
transform: scale(1);
}
}
.elasticity2{
animation: elasticity2 1s cubic-bezier(.39,.62,.74,1.39) 2s infinite
}
@keyframes elasticity2{
0%{
transform: scale(0);
}
90%{
transform: scale(1);
}
}
</style>
```
:::
### 方法二:
嵌入[codepen](https://codepen.io/)
```html
<iframe height="400" style="width: 100%;" scrolling="no" title="【CSS:行为】使用:hover和attr()定制悬浮提示" src="https://codepen.io/xugaoyi/embed/vYNKNaq?height=400&theme-id=light&default-tab=css,result" frameborder="no" allowtransparency="true" allowfullscreen="true" loading="lazy">
See the Pen <a href='https://codepen.io/xugaoyi/pen/vYNKNaq'>【CSS:行为】使用:hover和attr()定制悬浮提示</a> by xugaoyi
(<a href='https://codepen.io/xugaoyi'>@xugaoyi</a>) on <a href='https://codepen.io'>CodePen</a>.
</iframe>
```
<iframe height="400" style="width: 100%;" scrolling="no" title="【CSS:行为】使用:hover和attr()定制悬浮提示" src="https://codepen.io/xugaoyi/embed/vYNKNaq?height=400&theme-id=light&default-tab=css,result" frameborder="no" allowtransparency="true" allowfullscreen="true" loading="lazy">
See the Pen <a href='https://codepen.io/xugaoyi/pen/vYNKNaq'>【CSS:行为】使用:hover和attr()定制悬浮提示</a> by xugaoyi
(<a href='https://codepen.io/xugaoyi'>@xugaoyi</a>) on <a href='https://codepen.io'>CodePen</a>.
</iframe>
::: note
`<iframe>`标签还可以嵌入其他任何外部网页,如视频、地图等
:::
@@ -1,67 +0,0 @@
---
title: 批量操作front matter工具
date: 2020-05-13 11:52:45
permalink: /pages/2b8e22
article: false
---
当你想为某个文件夹下的所有`.md`文件添加、修改、删除某些front matter字段时,这个工具可以快速的为你批量操作。
首先,你需要在`package.json``scripts`中写入脚本:
```json
// package.json
{
"scripts": {
"editFm": "node utils/editFrontmatter.js",
}
}
```
`utils/config.yml`配置要批量操作的文件夹和要编辑的字段,示例:
```yaml
# utils/config.yml
#批量添加和修改、删除front matter配置文件
# 需要批量处理的路径,docs文件夹内的文件夹 (数组。映射路径:docs/arr[1]/arr[2] ... )
path:
- docs # 第一个成员必须是docs
- 《JS教程》专辑
- 第一章节
# 要删除的字段 (数组)
delete:
- article
# 要添加、修改front matter的数据 front matter中没有的数据则添加,已有的数据则覆盖)
data:
author: 齐天大圣
sidebar: false
```
比如你要操作的文件夹路径是`docs/《JS教程》专辑/第一章节`,你需要这样配置路径:
```yaml
path:
- docs # 第一个成员必须是docs
- 《JS教程》专辑
- 第一章节
```
`path`数组的第一个成员必须是`docs`,如果你想操作`docs`底下除了首页之外所有的`.md`文件,只需保留第一个成员`docs`即可。
你想删除`article`字段:
```yaml
delete:
- article
```
你想为这个文件夹下的所有`.md`文件添加作者`author`和隐藏侧边栏`sidebar`
```yaml
data:
author: 齐天大圣
sidebar: false
```
最后,执行`npm run editFm`命令,为了防止误操作,会有一个询问过程:
```sh
npm run editFm
? 批量操作frontmatter有修改数据的风险,确定要继续吗? (Y/n)
...
```
@@ -1,84 +0,0 @@
---
title: 部署
date: 2020-05-13 12:10:53
permalink: /pages/0fc1d2
article: false
---
::: warning
目前作者使用的部署方式已改为 [vercel](https://vercel.com/),部署方法参考 [这里](https://zhuanlan.zhihu.com/p/347990778)。(2022.01.01</br>
更多 [部署方式](https://vuepress.vuejs.org/zh/guide/deploy.html#%E9%83%A8%E7%BD%B2)
:::
本项目内置了两种`自动部署`脚本,用于一键部署到 GitHub Pages 或 国内访问速度更快的Coding Pages
## 1.使用deploy.sh脚本部署
第一步,修改`deploy.sh`脚本内的仓库地址为你的仓库,如有自定义域名则一并修改,没有则注释
```bash
# 如果是发布到自定义域名
echo 'xugaoyi.com' > CNAME
# 如果发布到 https://<USERNAME>.github.io
# git push -f git@github.com:<USERNAME>/<USERNAME>.github.io.git master
# 如果发布到 https://<USERNAME>.github.io/<REPO>
# git push -f git@github.com:<USERNAME>/<REPO>.git master:gh-pages
```
第二步,一键部署命令
```bash
npm run deploy
```
> windows系统下使用bash命令窗
第三步,设置仓库的GitHub Pages。
> `deploy.sh`文件看 [这里](https://github.com/xugaoyi/vuepress-theme-vdoing/blob/master/deploy.sh)
> 看不懂shell代码? 参考:[shell教程](https://ipcmen.com/)
## 2. 使用GitHub Action自动持续集成
第一步,按照[官方文档](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line),生成一个github token (令牌)。
第二步,将这个密钥储存到当前仓库的`Settings/Secrets`里面。
> `Settings/Secrets`是储存秘密的环境变量的地方。环境变量的名字可以随便起,这里用的是`ACCESS_TOKEN`。如果你不用这个名字,`.github/workflows/ci.yml`脚本里的变量名也要跟着改。
第三步,push提交代码到GitHub仓库master分支。
第四步,设置仓库的GitHub Pages
> 参考 [GitHub Action实现自动部署静态博客](http://xugaoyi.com/pages/6b9d359ec5aa5019/)
> `ci.yml`文件看 [这里](https://github.com/xugaoyi/blog/blob/master/.github/workflows/ci.yml)
> 看不懂yaml代码? 参考:[yaml教程](https://xugaoyi.com/pages/4e8444e2d534d14f/)
## 部署升级:同时部署到github和coding
为了让博客站能够让百度收录,因此我把博客部署到了coding。相关[文章](https://xugaoyi.com/pages/41f87d890d0a02af/)。
在原有部署方式上做了升级,主要修改代码文件有下面这两个:
使用前先将[github token (令牌) ](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line)和[coding token (令牌)](https://dev.tencent.com/help/doc/account/access-token) 同时设置到github仓库的`Settings/Secrets`位置。
![token设置](https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/token.jpg)
### 部署方式有两种:
#### 1) 、使用如下命令一键部署到github和coding
```sh
npm run deploy
```
#### 2)、使用GitHub Action自动部署到github和coding
只需要push提交代码到github仓库master分支即可。
> 参考 [《GitHub Action实现自动部署静态博客》](http://xugaoyi.com/pages/6b9d359ec5aa5019/)
@@ -1,30 +0,0 @@
---
title: 关于写文章和一级标题
date: 2020-05-13 12:07:32
permalink: /pages/9ae0bd
article: false
---
写文章时,大概的流程是这样的:
1.首先是创建`.md`文件,如果在结构化目录下则**需要**添加相应的序号,在碎片化博文目录`_posts`下**不需要**添加序号,文件名填写文章的标题。
2.如果你想手动指定文章的分类、标签,则需要在头部front matter填写相应的字段。一般情况下不需要再填写其他字段。不想手动指定分类和标签也可以省略这一步。
``` yaml
---
categories:
- 分类1
tags:
- 标签1
- 标签2
---
```
3.**关于一级标题**,即markdown中的一级标题(`# 标题`),你可以写也可以不写,在页面中实际显示的文章标题是取自front matter中的`title`,然后把`.md`中的一级标题隐藏了。
> 这样做即可以保留本地文件的一级标题,也可以兼容线上页面的标题。
> 同时,对于在新建`.md`文件时已经输入了一次标题在文件名,不想在文档中重复输入一次标题的,也是可以实现的。
4.**关于文章摘要**,你想在首先文章列表中显示摘要时可以在合适的位置添加一个`<!-- more -->`注释,参考:[添加摘要](/pages/1cc523/)
5.最后,就可以正式开始写作啦。
@@ -1,35 +0,0 @@
---
title: 关于博客搭建与管理
date: 2020-05-13 12:12:33
permalink: /pages/26997d
article: false
---
这里是我在搭建和管理博客过程中写的一些文章和小技巧。
1. 评论模块的搭建
[使用Gitalk实现静态博客无后台评论系统](https://xugaoyi.com/pages/1da0bf9a988eafe5/)
2. 自定义域名及解析,[详情](https://github.com/xugaoyi/vuepress-theme-vdoing/issues/326)
3. SEO相关
```js
// config.js
module.exports = {
description: '填写网站描述', // 以 <meta> 标签渲染到页面html中
head: [ // 注入到页面<head> 中的标签,[tagName, { attrName: attrValue }]
['meta', { name: 'keywords', content: '填写关键字'}]
]
}
```
4. 图床
[GitHub + jsDelivr + TinyPNG+ PicGo 打造稳定快速、高效免费图床](https://xugaoyi.com/pages/a5f73af5185fdf0a/)
5. 结合GitHub Actions开发的每天定时百度推送,加快收录
[GitHub Actions 定时运行代码:每天定时百度链接推送](https://xugaoyi.com/pages/f44d2f9ad04ab8d3/)
@@ -1,31 +0,0 @@
---
title: 在线编辑和新增文章的方法
date: 2020-05-19 11:17:58
permalink: /pages/c5a54d
article: false
---
::: warning 说明
以下方法实现的前提是把博客源码上传到github仓库,并配置好 [GitHub Actions](https://github.com/features/actions) 自动部署。
:::
### 在线编辑原有的文章
首先,在`config.js`启用页面的编辑按钮,并配置好github仓库和`.md`文件所在根目录等,详见 [官方文档](https://vuepress.vuejs.org/zh/theme/default-theme-config.html#git-%E4%BB%93%E5%BA%93%E5%92%8C%E7%BC%96%E8%BE%91%E9%93%BE%E6%8E%A5)。
示例:
```js
// config.js
module.exports = {
themeConfig: {
repo: 'xugaoyi/vuepress-theme-vdoing', // Github仓库地址
docsDir: 'docs', // .md文件放在了docs目录下
editLinks: true, // 启用编辑链接
editLinkText: '编辑',
}
}
```
配置好之后,每个文章页面底下都会有一个编辑按钮,点击即可跳到github在线编辑,编辑完成后提交就会自动触发GitHub Actions自动部署。
### 在线新增文章
在github博客源码仓库相应的文章目录下,新建`.md`文件,编辑好文章并提交后会触发GitHub Actions自动部署。
> 提示:当使用过在线编辑或新增后,下次在本地编辑之前先拉取代码。
@@ -0,0 +1,43 @@
---
title: 如何贡献
date: 2023-03-29 23:34:07
permalink: /pages/a3f096/
---
# 🎋代码分支说明
- 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上fork项目到自己的仓库
- 将fork后的自己仓库的项目clone到本地
- 拉取dev分支最新代码,并修改
- commit并push到自己的仓库
- 登录Gitee在的仓库上可以看到一个 pull request 按钮,点击它,填写一些说明信息,然后提交即可。
- 等待合并
# 📐PR的规范
> 你可能发现有的代码并不符合这个规范,但我们后续都会朝着这个方向迈进。后续我们会逐步全面规范化,请先保证新提交的代码满足以下要求:
- 注释完备,尤其每个新增的方法应按照Java文档规范标明方法说明、参数说明、返回值说明等信息,必要时请添加单元测试,如果愿意,也可以加上你的大名。
- 原则上,不允许自行添加第三方依赖库。若确有必要,请先和项目负责人沟通达成一致。
- 对现有代码的优化,请在注释中说明原因。
- 如果涉及到数据库的变更,则至少需要做到以下绩点:
- sqlite、mysql下均测试通过
- 在`./neutrino-proxy-server/src/main/resource/sql`下维护更新对应的表结构sql文件
- 对于新增的表,维护必要的初始化数据
- 在对应的mysql、sqlite的update目录下,维护SQL变更记录,确保SQL执行测试通过
@@ -0,0 +1,25 @@
---
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 |阳光很暖| 感谢您的开源项目! |
@@ -0,0 +1,39 @@
---
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,48 +0,0 @@
---
title: 问答
date: 2020-05-25 12:01:52
permalink: /pages/9cc27d
# sidebar: false
article: false
---
## 我是一个小白,想使用这个主题搭建博客(知识库)需要做哪些工作?
答:使用这个主题前需要你掌握下面这些知识:
* 掌握 [markdown](https://xugaoyi.com/pages/ad247c4332211551/)、[yaml](https://xugaoyi.com/pages/4e8444e2d534d14f/) 语法
* 会使用终端(命令行),会使用 git
* 会阅读文档、搜索文档
* 会[VuePress](https://vuepress.vuejs.org/zh/)的基本使用和默认主题的基本配置
以上知识都掌握之后,再查看本文档。你也可以运行我的主题项目,一边看代码,一边查看文档。主题项目内写了比较多的注释代码,方便你的学习和使用。
## clone 项目后需要修改哪些地方?
答:大致的修改流程是这样的:
1. 首先让项目正常的跑起来
2. 根据需求构建和替换 [docs/<结构化目录>](/pages/2f674a/) 的目录及内容
3. 根据需求修改 [config.js](/pages/a20ce8/) 配置
4. 修改 [首页配置](/pages/f14bdb/)
5. 修改 [主题颜色和样式](/pages/f51918/)(如果你想修改的话)
## 可减少项目冗余的地方有哪些?
答:在完成项目的搭建之后,对于没有使用到的一些文件和代码,可以看情况删减,可删减的地方有:
* 卸载`config.js`中未使用的插件依赖包
* 参照 [目录结构](/pages/2f674a/),删除未使用到的 `可选` 文件
* 删除`.vusepress`目录内未使用到的 `可选` 文件
* 删除各文件内的注释代码
## 我可以不使用永久链接吗?
答:**不可以**。当你没有在front matter指定永久链接时,主题会[自动生成永久链接](/pages/088c16/)到front matter,你可以修改永久链接的值。
使用永久链接是出于以下几点考虑:
1. 在`config.js`配置nav时使用永久链接,就不会因为文件的路径或名称的改变而改变。
2. 对于博客而言,当别人收藏了你的文章,在未来的时间里都可以通过永久链接来访问到。
3. 主题中的`目录页`需要通过永久链接来访问文章。
## 是否支持国际化?
答:暂不支持。目前的国际化方案,只能是使用不同项目来处理,比如:一个项目是中文,一个项目是英文,在导航中添加中英文站点的链接。
## 打包后在本地预览怎么没有样式?
答:因vuepress打包后的CSS、JS等资源链接采用绝对路径引入,在本地预览时的引入链接是不正确的,部署到服务器即可正常引入和预览。如果你确实需要本地预览,可以使用[vuepress-plugin-serve](https://vuepress.github.io/zh/plugins/serve/)插件,使用方法看相应文档。
## 在`build`时出现错误,但是在`dev`时是正常的
答:这种情况多半是因为你在`created``beforeCreate`生命周期调用了浏览器API的原因导致,详情查看:[浏览器的 API 访问限制](https://vuepress.vuejs.org/zh/guide/using-vue.html#%E6%B5%8F%E8%A7%88%E5%99%A8%E7%9A%84-api-%E8%AE%BF%E9%97%AE%E9%99%90%E5%88%B6)
@@ -0,0 +1,45 @@
---
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执行测试通过
@@ -8,12 +8,15 @@ article: false
如果您正在使用这个项目并感觉良好,或者是想支持我继续开发,您可以通过如下`任意`方式支持我:
1. Star并分享 [vuepress-theme-vdoing](https://github.com/xugaoyi/vuepress-theme-vdoing) :rocket:
1. Star并分享 [neutrino-proxy](https://gitee.com/dromara/neutrino-proxy) :rocket:
谢谢! :heart:
<!--
2. 轻轻点击一次上面的广告 :D
2. 进入下面赞助商花十几秒钟扫码注册一下, 赞助商就会代您赞助一笔小钱。 :D
3. 通过以下二维码 一次性捐款,打赏作者一杯茶。:tea:
谢谢! :heart:
| 微信赞赏 | 微信 | 支付宝 |
| :---: | :---: | :---: |
@@ -22,7 +25,8 @@ article: false
## 赞助商
> 如果您想支持本项目,奈何囊中羞涩,您可以花十几秒点击下方赞助商进入注册(只需微信扫码就可以),赞助商就会代您赞助一笔小钱。
<!-- <a href="http://apifox.cn/a103xugaoyi" target="_blank"><img src="/img/Apifox-860x320.png" class="no-zoom" style="width: 400px;border-radius: 2px;"></a> -->
<a href="http://apifox.cn/a103xugaoyi" target="_blank"><img src="/img/Apifox-860x320.png" class="no-zoom" style="width: 400px;border-radius: 2px;"></a>
<a href="http://apifox.cn/a103xugaoyi" target="_blank"><img src="https://cdn.staticaly.com/gh/xugaoyi/blog-gitalk-comment@master/img/431669861564_.2470ykdcpbds.jpg" class="no-zoom" style="border-radius: 2px;"></a>
@@ -47,5 +51,6 @@ article: false
<img :src="$withBase('/img/qrcode/gzh.jpg')" style="width:180px;" />
-->
## 致谢
感谢给予支持的朋友,您的支持是我前进的动力 🎉
+191 -121
View File
@@ -1,26 +1,38 @@
---
home: true
heroImage: /img/logo.png
heroImage: /img/logo-0.75x.png
heroText: Neutrino-Proxy
tagline: 🚀一个基于 netty 的、开源的 java 内网穿透项目
tagline: 🚀一个基于Netty的开源内网穿透神器
actionText: 开始使用 →
actionLink: /pages/a2f161/
actionLink: /pages/793dcb/
bannerBg: none # auto => 网格纹背景(有bodyBgImg时无背景),默认 | none => 无 | '大图地址' | background: 自定义背景样式 提示:如发现文本颜色不适应你的背景时可以到palette.styl修改$bannerTextColor变量
features: # 可选的
- title: 安全
details: 内外网传输数据。
- title: 快速
details: 快速部署
- title: 穿透力强
details: 穿透力
- title: 流量监控
details: 首页图表、报表管理多维度流量监控。全方位掌握实时、历史代理数据。
- title: 域名映射
details: HTTP代理绑定子域名,开发调试三方回调更方便
- title: 用户/License
details: 支持多用户、多客户端使用。后台禁用实时生效
- title: 端口池
details: 对外端口统一管理,支持用户、License独占端口。
- title: 端口映射
details: 新增、编辑、删除、禁用实时生效。
- title: Docker
details: 服务端支持Docker一键部署。
- title: 隧道SSL加密
details: 隧道通信支持SSL,保护您的数据安全
- title: HTTPS
details: 支持HTTPS
- title: 多客户端支持
details: 暂未支持
# 文章列表显示方式: detailed 默认,显示详细版文章列表(包括作者、分类、标签、摘要、分页等)| simple => 显示简约版文章列表(仅标题和日期)| none 不显示文章列表
postList: none
---
<p align="center">
<a class="become-sponsor" href="/pages/1b12ed/">支持这个项目</a>
<a class="become-sponsor" href="https://gitee.com/dromara/neutrino-proxy">支持这个项目</a>
</p>
<style>
@@ -36,9 +48,7 @@ postList: none
<br/>
<p align="center">
<a href="https://www.npmjs.com/package/vuepress-theme-vdoing" target="_blank"><img src="https://img.shields.io/npm/v/vuepress-theme-vdoing" alt="npm" class="no-zoom"></a>
<a href="https://www.npmjs.com/package/vuepress-theme-vdoing" target="_blank"><img src="https://img.shields.io/npm/dt/vuepress-theme-vdoing" alt="npm" class="no-zoom"></a>
<a href="https://gitee.com/dromara/neutrino-proxy" target="_blank"><img src='https://gitee.com/dromara/neutrino-proxy/badge/star.svg?theme=dark' alt='star' class="no-zoom"></img></a>
<a href="https://gitee.com/dromara/neutrino-proxy" target="_blank"><img src='https://gitee.com/dromara/neutrino-proxy/badge/star.svg?theme=dark' alt='star' class="no-zoom"></a>
<a href="https://gitee.com/dromara/neutrino-proxy" target="_blank"><img src='https://gitee.com/dromara/neutrino-proxy/badge/fork.svg?theme=dark' alt='forks' class="no-zoom"></a>
</p>
@@ -79,7 +89,7 @@ postList: none
:::
-->
<br/>
<!--
## 🎉上新推荐
* `v1.7.0`:项目重构、底层框架更换为Solon。
* `v1.6.4`:代理使用细节优化。
@@ -93,6 +103,7 @@ postList: none
更多上新请查阅:[**更新日志**](https://gitee.com/dromara/neutrino-proxy/releases)
<br/>
-->
<!-- ## ⚡️未来...
* `v1.5.0`:新增配置文件对TypeScript的支持,参考[config.ts](https://github.com/xugaoyi/vuepress-theme-vdoing/blob/master/docs/.vuepress/config.ts)。新增[标题标记](/pages/3216b0/#titletag)。
@@ -124,140 +135,171 @@ postList: none
你也可以在Issue查看别人提的问题和给出解决方案。
或者加入我们的交流群:
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<img :src="$withBase('/img/qrcode/wxq.png')" class="no-zoom" style="width:120px;margin: 10px;">
<p>中微子 微信群(添加我微信备注"进群")</p>
</td>
<!--
<td align="center" valign="middle">
<img src="https://cdn.staticaly.com/gh/xugaoyi/blog-gitalk-comment@master/img/0.4pp7r95mdai0.jpeg" class="no-zoom" style="width:120px;margin: 10px;">
<p>vdoing微信群(添加我微信备注"进群")</p>
</td>
-->
</tr>
</tbody>
</table>
<br/>
## 🏗️添砖加瓦
### 🎋分支说明
neutrino-proxy主要的源码分为两个分支,功能如下:
| 分支 | 作用 |
|---|---------------------------------------------------------------|
| master | 主分支,不接收任何pr或修改 |
| feature/1.7.1 | 开发分支,默认为下个版本的SNAPSHOT版本,接受修改或pr |
### 🐞提供bug反馈或建议
提交问题反馈请说明正在使用环境以及相关问题
- [Gitee issue](https://gitee.com/dromara/neutrino-proxy/issues)
[//]: # (- [Github issue]&#40;https://github.com/dromara/hutool/issues&#41;)
### 🧬贡献代码的步骤
贡献代码注意事项:
1. 在Gitee或者Github上fork项目到自己的repofork,一定要把项目fork一份。
2. 把fork过去的项目也就是你的项目clone到你的本地
3. 同步feature/1.7.1最新代码
4. 修改代码
5. 开发完成后,不忙着提PR,再拉一遍最新代码,如果有冲突、解决冲突
6. commit后push到自己的库
7. 登录Gitee在你首页可以看到一个 pull request 按钮,点击它,填写一些说明信息,然后提交即可。
8. 等待维护者合并
<div align="center">
<img :src="$withBase('/img/qrcode/wxq.png')" class="no-zoom" style="width:200px;margin: 10px;">
<p>中微子 微信群(添加我微信备注"进群")</p>
</div>
<br/>
## 📚 Dromara 成员项目
<p align="center">
<a href="https://gitee.com/dromara/TLog" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/tlog2.png" alt="一个轻量级的分布式日志标记追踪神器,10分钟即可接入,自动对日志打标签完成微服务的链路追踪" width="15%">
<h2 id="🌈特别赞助"><a href="#🌈特别赞助" class="header-anchor">#</a> 🌈特别赞助</h2>
<p align="center">等待有缘人赞助</p>
<!--
<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>
<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>
<a href="https://gitee.com/dromara/liteFlow" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/liteflow.png" alt="轻量,快速,稳定,可编排的组件式流程引擎" width="15%">
<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>
<a href="https://hutool.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/hutool.jpg" alt="小而全的Java工具类库,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。" width="15%">
<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>
<a href="https://sa-token.dev33.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/sa-token.png" alt="一个轻量级 java 权限认证框架,让鉴权变得简单、优雅!" width="15%">
<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>
<a href="https://gitee.com/dromara/hmily" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/hmily.png" alt="高性能一站式分布式事务解决方案。" width="15%">
<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>
<a href="https://gitee.com/dromara/Raincat" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/raincat.png" alt="强一致性分布式事务解决方案。" width="15%">
<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>
</p>
<p align="center">
<a href="https://gitee.com/dromara/myth" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/myth.png" alt="可靠消息分布式事务解决方案。" width="15%">
<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>
<a href="https://cubic.jiagoujishu.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/cubic.png" alt="一站式问题定位平台,以agent的方式无侵入接入应用,完整集成arthas功能模块,致力于应用级监控,帮助开发人员快速定位问题" width="15%">
<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>
<a href="https://maxkey.top/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/maxkey.png" alt="业界领先的身份管理和认证产品" width="15%">
<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>
<a href="http://forest.dtflyx.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/forest-logo.png" alt="Forest能够帮助您使用更简单的方式编写Java的HTTP客户端" width="15%">
<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>
<a href="https://jpom.io/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/jpom.png" alt="一款简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件" width="15%">
<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>
<a href="https://su.usthe.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/sureness.png" alt="面向 REST API 的高性能认证鉴权框架" width="15%">
<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>
</p>
<p align="center">
<a href="https://easy-es.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/easy-es2.png" alt="傻瓜级ElasticSearch搜索引擎ORM框架" width="15%">
<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>
<a href="https://gitee.com/dromara/northstar" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/northstar_logo.png" alt="Northstar盈富量化交易平台" width="15%">
<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>
<a href="https://hertzbeat.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/hertzbeat_brand.jpg" alt="易用友好的云监控系统" width="15%">
<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>
<a href="https://plugins.sheng90.wang/fast-request/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/fast-request.gif" alt="Idea 版 Postman,为简化调试API而生" width="15%">
<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>
<a href="https://www.jeesuite.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/mendmix.png" alt="开源分布式云原生架构一站式解决方案" width="15%">
<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>
<a href="https://gitee.com/dromara/koalas-rpc" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/koalas-rpc2.png" alt="企业生产级百亿日PV高可用可拓展的RPC框架。" width="15%">
<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>
</p>
<p align="center">
<a href="https://async.sizegang.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/gobrs-async.png" alt="配置极简功能强大的异步任务动态编排框架" width="15%">
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<a href="https://dynamictp.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/dynamic-tp.png" alt="基于配置中心的轻量级动态可监控线程池" width="15%">
<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>
<a href="https://www.x-easypdf.cn" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/x-easypdf.png" alt="一个用搭积木的方式构建pdf的框架(基于pdfbox" width="15%">
<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>
<a href="http://dromara.gitee.io/image-combiner" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/image-combiner.png" alt="一个专门用于图片合成的工具,没有很复杂的功能,简单实用,却不失强大" width="15%">
<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>
<a href="https://www.herodotus.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/dante-cloud2.png" alt="Dante-Cloud 是一款企业级微服务架构和服务能力开发平台。" width="15%">
<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>
<a href="https://dromara.org/zh/projects/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/dromara.png" alt="让每一位开源爱好者,体会到开源的快乐。" width="15%">
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
</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>
<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>
</p>
<!-- AD -->
<div class="wwads-cn wwads-horizontal page-wwads" data-id="136"></div>
<style>
@@ -280,4 +322,32 @@ neutrino-proxy主要的源码分为两个分支,功能如下:
padding-top: 5px;
display: block;
}
.friends-item {
width: 10em;
height: 3em;
flex: 1;
text-align: center;
display: inline-block;
margin: 5px;
border: 2px solid #ebeef5;
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
border-radius: 8px;
padding: 10px
}
.friends-item-img {
-o-object-fit: contain;
object-fit: contain;
max-width: 150px!important;
height: 100%
}
/*a {*/
/* font-weight: 500;*/
/* text-decoration: none*/
/*}*/
/*a,p a code {*/
/*color: #4274f4*/
/*}*/
/*p a code {*/
/*font-weight: 400*/
/*}*/
</style>
+1
View File
@@ -4,6 +4,7 @@
"scripts": {
"dev": "vuepress dev docs",
"build": "vuepress build docs",
"buildAndDeploy": "vuepress build docs && bash deploy.sh",
"deploy": "bash deploy.sh",
"updateTheme": "yarn remove vuepress-theme-vdoing && rm -rf node_modules && yarn && yarn add vuepress-theme-vdoing -D",
"editFm": "node utils/editFrontmatter.js"
+23 -4
View File
@@ -7,11 +7,11 @@
<parent>
<groupId>org.noear</groupId>
<artifactId>solon-parent</artifactId>
<version>2.2.2</version>
<version>2.3.6</version>
<relativePath />
</parent>
<groupId>fun.asgc.neutrino</groupId>
<groupId>org.dromara.neutrino-proxy</groupId>
<artifactId>neutrino-proxy</artifactId>
<packaging>pom</packaging>
<version>${revision}</version>
@@ -20,8 +20,6 @@
<module>neutrino-proxy-core</module>
<module>neutrino-proxy-client</module>
<module>neutrino-proxy-server</module>
<module>_solon_plugin/job-solon-plugin</module>
<module>_solon_plugin/orika-solon-plugin</module>
</modules>
<properties>
@@ -99,11 +97,32 @@
<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>
@@ -17,7 +17,7 @@ export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
#初始化文件夹
cd ..
cd ../..
rm -rf $adminDeployDir
if [ ! -d "$deployDir" ];then
mkdir $deployDir
@@ -17,7 +17,7 @@ export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
#初始化文件夹
cd ..
cd ../..
rm -rf $adminDeployDir
if [ ! -d "$deployDir" ];then
mkdir $deployDir
@@ -41,4 +41,6 @@ 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/"
@@ -9,7 +9,7 @@ deployDir="deploy"
clientDeployDir=$deployDir"/client"
#切到项目根目录
cd ..
cd ../..
#初始化文件夹
if [ ! -d "$deployDir" ];then
mkdir $deployDir
@@ -8,7 +8,7 @@ export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/li
mkdir -p /work/$NAME/heapError/
NAME=neutrino-proxy-client
WORK=$PWD/../deploy/client
WORK=$PWD/../../deploy/client
OUT=$WORK/$NAME.out
JAR_PATH=$WORK
@@ -37,7 +37,7 @@ cp $OUT $JAR_PATH/logs/back_$time.out
fi
rm -f $OUT
cd $JAR_PATH
nohup java $JAVA_OPS -jar $NAME.jar $startupParams > $OUT 2>&1 &
nohup java -Dfile.encoding=utf-8 $JAVA_OPS -jar $NAME.jar $startupParams > $OUT 2>&1 &
echo "sleep 15s wating service start"
sleep 15
tail -200 $OUT
@@ -9,7 +9,7 @@ deployDir="deploy"
serverDeployDir=$deployDir"/server"
#切到项目根目录
cd ..
cd ../..
#初始化文件夹
if [ ! -d "$deployDir" ];then
mkdir $deployDir
+17
View File
@@ -0,0 +1,17 @@
#!/bin/sh
# 镜像版本,每次更新版本时需要调整
ImageVer=1.0.0
ImageName=neutrino-proxy
DockerFilePath=$PWD/../..
#echo '打包jar...'
sh ./server_build.sh
#echo '打包管理后台...'
sh ./admin_build_docker.sh
# 删除老的本地镜像
docker rmi -f $(docker images | grep $ImageName | awk '{print $3}')
# 构建镜像
docker build -t $ImageName:$ImageVer -t $ImageName:latest $DockerFilePath
@@ -8,7 +8,7 @@ export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/li
mkdir -p /work/$NAME/heapError/
NAME=neutrino-proxy-server
WORK=$PWD/../deploy/server
WORK=$PWD/../../deploy/server
OUT=$WORK/$NAME.out
JAR_PATH=$WORK
@@ -29,7 +29,7 @@ cp $OUT $JAR_PATH/logs/back_$time.out
fi
rm -f $OUT
cd $JAR_PATH
nohup java $JAVA_OPS -jar $NAME.jar > $OUT 2>&1 &
nohup java -Dfile.encoding=utf-8 $JAVA_OPS -jar $NAME.jar > $OUT 2>&1 &
echo "sleep 15s wating service start"
sleep 15
tail -200 $OUT
+29
View File
@@ -0,0 +1,29 @@
@echo off
@rem basic params
set ENV=dev
set NODE_VERSION=v13.12.0
set MODULE_NAME=neutrino-proxy-admin
@rem 判断当前node版本是否符合,如果不符合切换node版本
for /f "tokens=1" %%v in ('node -v') do set v=%%v
if not "%v%" == "%NODE_VERSION%" (
echo Node.js version is not %NODE_VERSION%.
@rem 判断是否安装nvm
set "nvm_home=%NVM_HOME%"
set "nvm_symlink=%NVM_SYMLINK%"
if not defined nvm_home (
echo nvm is not installed,then use nvm to install node %NODE_VERSION%.
) else if not defined nvm_symlink (
echo nvm is not installed,then use nvm to install node %NODE_VERSION%.
) else (
echo nvm is installed.
nvm use %NODE_VERSION%
)
)
cd %~dp0 && cd ../../%MODULE_NAME%
@rem 检查dist文件夹是否存在,存在就删除,/s选项表示将目录及其所有子目录一起删除,/q选项表示不需要确认操作
if exist dist (
rmdir /s /q dist
)
@rem 等待npm执行完毕
call npm install
npm run build:%ENV% && pause
+29
View File
@@ -0,0 +1,29 @@
@echo off
@rem basic params
set ENV=docker
set NODE_VERSION=v13.12.0
set MODULE_NAME=neutrino-proxy-admin
@rem 判断当前node版本是否符合,如果不符合切换node版本
for /f "tokens=1" %%v in ('node -v') do set v=%%v
if not "%v%" == "%NODE_VERSION%" (
echo Node.js version is not %NODE_VERSION%.
@rem 判断是否安装nvm
set "nvm_home=%NVM_HOME%"
set "nvm_symlink=%NVM_SYMLINK%"
if not defined nvm_home (
echo nvm is not installed,then use nvm to install node %NODE_VERSION%.
) else if not defined nvm_symlink (
echo nvm is not installed,then use nvm to install node %NODE_VERSION%.
) else (
echo nvm is installed.
nvm use %NODE_VERSION%
)
)
cd %~dp0 && cd ../../%MODULE_NAME%
@rem 检查dist文件夹是否存在,存在就删除,/s选项表示将目录及其所有子目录一起删除,/q选项表示不需要确认操作
if exist dist (
rmdir /s /q dist
)
@rem 等待npm执行完毕
call npm install
npm run build:%ENV% && pause
+8
View File
@@ -0,0 +1,8 @@
@echo off
@rem console output code changed to UTF-8
chcp 65001
@rem basic params
set MODULE_NAME="neutrino-proxy-client"
@rem step
cd %~dp0 && cd ../../
call mvn clean package -pl %MODULE_NAME% -am -Dmaven.test.skip=true
+10
View File
@@ -0,0 +1,10 @@
@echo off
@rem console output code changed to UTF-8
chcp 65001
@rem basic params
set MODULE_NAME="neutrino-proxy-client"
set JAR_NAME="neutrino-proxy-client.jar"
set JVM_OPTS="-Xmx256m" "-Xms256m"
@rem step
cd %~dp0 && cd ../../%MODULE_NAME%/target
java -jar %JVM_OPTS% %JAR_NAME%
+8
View File
@@ -0,0 +1,8 @@
@echo off
@rem console output code changed to UTF-8
chcp 65001
@rem basic params
set MODULE_NAME="neutrino-proxy-server"
@rem step
cd %~dp0 && cd ../../
call mvn clean package -pl %MODULE_NAME% -am -Dmaven.test.skip=true
+10
View File
@@ -0,0 +1,10 @@
@echo off
@rem console output code changed to UTF-8
chcp 65001
@rem basic params
set MODULE_NAME="neutrino-proxy-server"
set JAR_NAME="neutrino-proxy-server.jar"
set JVM_OPTS="-Xmx256m" "-Xms256m"
@rem step
cd %~dp0 && cd ../../%MODULE_NAME%/target
java -jar %JVM_OPTS% %JAR_NAME%
+17 -4
View File
@@ -1,10 +1,21 @@
# 1.x剩余规划
- [ ] 参考鹊桥,增加对域名的支持
- [ ] 官网上线
# 1.x规划
- Bug
- 优化
- 心跳日志开关
- 客户端启动无限重连开关
- UDP支持
- 官网文档完善
- 常见问题汇总
- HTTPS配置说明
- 协议重构
- 代码重构
- [x] 适配mariadb
# Bug
- 指令通达被close的问题,org.dromara.neutrinoproxy.server.proxy.core.ProxyTunnelChannelHandler.channelInactive
- windows环境下直接运行发布版的jar包,日志输出乱码
- 代理mysql时,使用未开启远程访问的账号走代理访问mysql,代理客户端出现断开现象
- 客户端连接映射某个端口以后,如果在服务器端禁用了,没有立刻反映出来,要灯客户端重连以后才会屏蔽被禁用的端口。
# 2.x规划
- 插件开发
@@ -19,6 +30,8 @@
- [ ] 支持调整服务端端口、证书
- [ ] 服务端版本、协议版本、客户端版本管理
- [ ] 服务端日志
- [ ] 支持持管理后台动态调整日志级别。
- [ ] 动态调整转发、报文、心跳输出
- client+计划启动
- [ ] 安卓客户端
- [ ] 基于electron-egg的多平台客户端
@@ -26,4 +39,4 @@
# 3.x规划
- [ ] 支持针对用户限速、限流
- [ ] 支持P2P穿透
- [ ] 支持原生编译
- [ ] 支持原生编译