Compare commits

...
Author SHA1 Message Date
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 c3e7bf72fe 更新todolist。 2023-03-26 23:41:52 +08:00
aoshiguchen 01d67312cb Merge branch 'feature/1.7.1' 2023-03-26 23:38:50 +08:00
aoshiguchen a82829002a 更新todolist 2023-03-26 23:38:32 +08:00
aoshiguchen 1f723b5d0f 增加运行时截图 2023-03-26 23:17:18 +08:00
aoshiguchen 08a963c4b7 首页图表调整 2023-03-26 23:06:13 +08:00
zCans 66ae66b8a9 首页布局调整 2023-03-26 22:58:25 +08:00
zCans 71eb72d726 Merge remote-tracking branch 'origin/feature/1.7.1' into feature/1.7.1 2023-03-26 22:33:23 +08:00
zCans 98ac3c67c1 首页图表改造,对接数据接口 2023-03-26 22:33:17 +08:00
aoshiguchen 7de1c37d6b 目录调整. 2023-03-26 20:29:38 +08:00
aoshiguchen 07de9eec98 新增首页图表接口 2023-03-26 18:53:45 +08:00
aoshiguchen 4b596e99bb 用户流量表表、license流量报表新增操作列,跳转到月度明细 2023-03-26 16:34:39 +08:00
aoshiguchen 9d36a4a32a license流量月度明细增加根据license过滤 2023-03-26 16:11:29 +08:00
aoshiguchen c4726941ea 新增用户流量月度明细、license流量月度明细 2023-03-26 15:54:00 +08:00
aoshiguchen 144adfe73f 修复流量统计数据不准确的问题 2023-03-26 14:18:57 +08:00
aoshiguchen 247ec55b17 修复流量统计数据为查询到的问题 2023-03-26 14:04:47 +08:00
aoshiguchen e2c5c6975f 修复流量统计数据为查询到的问题. 2023-03-26 14:04:19 +08:00
aoshiguchen 9fc6541e98 修复流量统计数据为查询到的问题 2023-03-25 23:52:30 +08:00
aoshiguchen 7dba9ac173 端口池管理,新增根据端口池分组过滤 2023-03-25 21:17:30 +08:00
aoshiguchen 3f014418e1 端口池管理增加编辑功能,支持修改端口池所属分组 2023-03-25 19:07:38 +08:00
aoshiguchen 43967bc23e 日志管理优化. 2023-03-25 17:38:22 +08:00
aoshiguchen f723545a4d 宣传文章维护到docs/article下 2023-03-24 22:46:31 +08:00
aoshiguchen 1d58118f09 更新todolist 2023-03-24 17:15:56 +08:00
qiuyue a500133701 feat: 首页微信二维码替换 2023-03-24 13:28:56 +08:00
qiuyue bde25f0648 fix: 快速上手运行截图打包后也可以运行 2023-03-24 13:16:58 +08:00
qiuyue bf13f081c0 feat: 右上角更改为gitee地址 2023-03-24 13:06:48 +08:00
aoshiguchen c6f757c4b6 调整vuepress配置 2023-03-22 22:38:51 +08:00
aoshiguchen 5d32a3b922 Merge remote-tracking branch 'origin/feature/1.7.1' into feature/1.7.1 2023-03-22 22:09:44 +08:00
aoshiguchen ecb68c004c 更新pom文件,打包排除app-*.yml 2023-03-22 22:09:39 +08:00
qiuyue bbf1a316df feat: 去除赞助商广告 2023-03-22 13:27:45 +08:00
qiuyue 13b4abe868 feat: 演示环境链接跳转 2023-03-22 13:22:34 +08:00
qiuyue 1b1cbbce1f feat: 快速上手整体 2023-03-22 13:14:51 +08:00
aoshiguchen 2cb672e184 Merge branch 'feature/1.7.1' 2023-03-21 23:01:41 +08:00
aoshiguchen 77868a7e89 调整图片资源. 2023-03-21 23:00:39 +08:00
aoshiguchen bb766a434a Merge branch 'feature/1.7.1' 2023-03-21 22:53:35 +08:00
aoshiguchen 58a02b8e60 调整图片资源 2023-03-21 22:52:33 +08:00
aoshiguchen 8b560699b3 test1 2023-03-21 22:43:09 +08:00
aoshiguchen a2293e35a4 解决mysql下报错的问题. 2023-03-21 18:37:52 +08:00
傲世孤尘 7fea324219 !13 端口映射管理BUG修复
Merge pull request !13 from Yohanes/feature/1.7.1
2023-03-21 10:11:47 +00:00
Yohanes 559c6bd5f5 修复端口映射管理bug 2023-03-21 18:07:45 +08:00
Yohanes 8e1dfc48ca 端口池新建下拉无法选择分组问题修复 2023-03-21 16:27:44 +08:00
aoshiguchen 5807c4fbc4 新增安卓客户端项目目录 2023-03-21 14:23:47 +08:00
傲世孤尘 e50176b3bb !12 端口映射管理页面优化
Merge pull request !12 from Yohanes/feature/1.7.1
2023-03-20 08:44:21 +00:00
Yohanes 0f6625e1e1 端口映射管理搜索条件增加 2023-03-20 15:48:50 +08:00
aoshiguchen ec95da42ec Merge branch 'feature/1.7.1' 2023-03-20 14:25:11 +08:00
aoshiguchen c46062d83b Merge remote-tracking branch 'origin/feature/1.7.1' into feature/1.7.1 2023-03-20 14:23:46 +08:00
aoshiguchen 2e34bf9bb6 流量报表兼容sqlite 2023-03-20 14:23:39 +08:00
qiuyue 1fe4d032e6 feat: 快速上手:打包部署 2023-03-20 13:23:36 +08:00
aoshiguchen 5a5b336baf 解决license流量报表分页数量错误的问题 2023-03-20 13:08:13 +08:00
Yohanes ae5140b543 Merge remote-tracking branch 'upstream/feature/1.7.1' into feature/1.7.1
# Conflicts:
#	neutrino-proxy-admin/src/views/proxy/portMapping.vue
#	neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/service/PortPoolService.java
2023-03-20 10:11:47 +08:00
Yohanes 4392f94d74 端口映射管理排序优化、新增/更新时,下拉服务端端口,过滤掉被禁用、已被使用的端口 2023-03-20 09:22:50 +08:00
aoshiguchen 70ff088a9d 新增SQL更新记录维护 2023-03-19 18:49:54 +08:00
aoshiguchen 26dc08a737 新增用户流量报表、license流量报表实现 2023-03-19 17:54:17 +08:00
aoshiguchen e2c152a569 新增用户流量报表、license流量报表页面,及后端接口定义 2023-03-19 15:55:38 +08:00
aoshiguchen 983d5dde8c Merge remote-tracking branch 'origin/feature/1.7.1' into feature/1.7.1 2023-03-18 18:16:35 +08:00
aoshiguchen 1e90bafe81 调整脚本. 2023-03-18 18:16:30 +08:00
qiuyue 0130aeb741 feat: 快速使用--> 目录结构调整 2023-03-18 13:04:58 +08:00
aoshiguchen 9e8d1a7c74 代码优化 2023-03-18 12:27:31 +08:00
aoshiguchen 0b4785cde6 新增开发问题汇总文档 2023-03-18 12:12:20 +08:00
aoshiguchen 50b34866ba sql调整 2023-03-18 12:05:45 +08:00
aoshiguchen 4d16ea6543 Merge branch 'feature/1.7.1' 2023-03-18 00:27:35 +08:00
aoshiguchen 626cdcf3d0 端口池分组相关调整. 2023-03-18 00:26:21 +08:00
aoshiguchen 14df0da7f4 Merge remote-tracking branch 'origin/feature/1.7.1-vuepress' into feature/1.7.1 2023-03-17 23:53:07 +08:00
aoshiguchen 0beb1c114a Merge remote-tracking branch 'origin/feature/1.7.1' 2023-03-17 23:52:17 +08:00
aoshiguchen 97220987a2 README新增贡献者列表维护. 2023-03-17 23:51:01 +08:00
aoshiguchen a27438aa01 README新增贡献者列表维护 2023-03-17 23:47:54 +08:00
aoshiguchen 2f189d9ecd 调整build脚本 2023-03-17 23:07:19 +08:00
aoshiguchen 7a21a5dcf2 配置调整. 2023-03-17 22:59:56 +08:00
傲世孤尘 0e8ea85805 !10 端口分组
Merge pull request !10 from 嫉妒,期待/feature/1.7.1
2023-03-17 14:48:29 +00:00
xuefm 78cf5d95e5 端口分组 2023-03-17 22:38:51 +08:00
xuefm 3d6c83a11b 端口分组 2023-03-17 22:03:32 +08:00
xuefm df01e93c64 端口分组 2023-03-17 22:01:43 +08:00
xuefm 987eb9cd2a Merge remote-tracking branch 'origin/feature/1.7.1' into feature/1.7.1 2023-03-17 21:45:08 +08:00
xuefm 1be0e0d389 端口分组 2023-03-17 21:41:48 +08:00
傲世孤尘 c4716c45e6 !8 License管理页面优化
Merge pull request !8 from pingso/feature/1.7.1
2023-03-17 05:14:57 +00:00
Chunping.Li dffcf2932c https://gitee.com/dromara/neutrino-proxy/issues/I6NGHP
License管理页面优化
2023-03-17 11:56:10 +08:00
aoshiguchen 97a248a16f 端口映射配置优化,不配置时,默认为127.0.0.1 2023-03-15 18:34:08 +08:00
aoshiguchen ecf0b97493 logo资源调整. 2023-03-15 16:06:11 +08:00
aoshiguchen 5341f0d33a admin分页参数调整. 2023-03-15 16:01:49 +08:00
aoshiguchen 917ce765e5 更新README 2023-03-15 15:34:53 +08:00
aoshiguchen db64f8b31d 打包脚本调整. 2023-03-15 12:51:21 +08:00
aoshiguchen c70baad0f6 打包脚本调整. 2023-03-15 12:22:13 +08:00
aoshiguchen 7fd6fd5313 线程池调整. 2023-03-15 12:01:49 +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
485 changed files with 9182 additions and 5875 deletions
+4 -1
View File
@@ -52,4 +52,7 @@ hs_err_pid*
**/app-**.yml
**/memo/**
**/memo/**
neutrino-proxy-vuepress/deploy.sh
.NEUTRINO_PROXY_CLIENT_ID
+100 -37
View File
@@ -1,15 +1,24 @@
<p align="center">
<img src="https://i.imgtg.com/2023/02/10/crIMY.png" width="45%">
<img src="assets/logo/logo-text-vertical.png" width="45%">
</p>
<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
- 技术栈: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,11 +28,11 @@
因此以中微子命名,寓意着中微子代理拥有中微子"安全"、"快速"、"穿透力强"的特点。
# 3、运行示例
![用户管理](assets/user-manager1.png)
![端口池管理](assets/port-pool1.png)
![License管理](assets/license1.png)
![端口映射管理](assets/port-mapping1.png)
![客户端启动示例](assets/client-run1.png)
![首页](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)
# 4、项目结构
- neutrino-proxy
@@ -38,38 +47,52 @@
keytool -genkey -alias test1 -keyalg RSA -keysize 1024 -validity 3650 -keypass 123456 -storepass 123456 -keystore "./test.jks"
```
## 5.2、修改服务端配置(application.yml
## 5.2、修改服务端配置(app.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:
neutrino:
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
tunnel:
boss-thread-count: 2
work-thread-count: 10
# 服务端端口,用于保持与客户端的连接,非SSL
port: ${OPEN_PORT:9000}
# 服务端端口,用于保持与客户端的连接,SSL,需要jks证书文件,若不需要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}
server:
boss-thread-count: 5
work-thread-count: 20
# HTTP代理端口,默认80,也可以用其他端口,走nginx转发
http-proxy-port: ${HTTP_PROXY_PORT:80}
# HTTPS代理端口,默认443,也可以用其他端口,走nginx转发
https-proxy-port: ${HTTPS_PROXY_PORT:443}
# 如果不配置,则不支持域名映射
domain-name: ${DOMAIN_NAME:}
# 证书配置,用于支持HTTPS
key-store-password: ${HTTPS_STORE_PASS:}
jks-path: ${HTTPS_JKS_PATH:}
data:
# 数据库配置(支持mysql
type: sqlite
url: jdbc:sqlite:data.db
driver-class: org.sqlite.JDBC
username:
password:
```
## 5.3、启动服务端
@@ -103,7 +126,7 @@ proxy:
ssl-enable: false
```
## 5.5、代理示意图
![代理流程](assets/neutrino-proxy-process.jpg)
![代理流程](assets/design/neutrino-proxy-process.jpg)
## 5.6、启动客户端
### 5.6.1、启动参数直接指定配置
@@ -148,12 +171,52 @@ neutrino.proxy.client.license-key=b0a907332b474b25897c4dcb31fc7eb6
- 微信: yuyunshize
- Gitee: https://gitee.com/asgc/neutrino-proxy
# 10、贡献者列表
<p>
<a href="https://gitee.com/zcans" target="_blank">
<img src="assets/developer/zcans.png" width="12%">
</a>
<a href="https://gitee.com/bmlt" target="_blank">
<img src="assets/developer/jiuye.png" width="12%">
</a>
<a href="https://gitee.com/wangke666" target="_blank">
<img src="assets/developer/keke.png" width="12%">
</a>
<a href="https://gitee.com/xue-fangming" target="_blank">
<img src="assets/developer/fangming.png" width="12%">
</a>
<a href="https://gitee.com/y0hanes" target="_blank">
<img src="assets/developer/yohanes.png" width="12%">
</a>
<a href="https://gitee.com/noear_admin" target="_blank">
<img src="assets/developer/near.png" width="12%">
</a>
<a href="https://gitee.com/westboy" target="_blank">
<img src="assets/developer/westboy.jpg" width="12%">
</a>
<a href="https://gitee.com/liugddx" target="_blank">
<img src="assets/developer/liugddx.png" width="12%">
</a>
<a href="https://gitee.com/DianZiFaPiao" target="_blank">
<img src="assets/developer/tan90.png" width="12%">
</a>
<a href="https://gitee.com/dearsny" target="_blank">
<img src="assets/developer/pingon.png" width="12%">
</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>
</p>
# ❤️ 感谢
* [Solon](https://gitee.com/noear/solon?from=NeutrinoProxy)
* [Hutool](https://hutool.cn?from=NeutrinoProxy)
* [JetBrains](https://www.jetbrains.com?from=NeutrinoProxy)
![JenBrains logo](assets/jetbrains.svg)
![JenBrains logo](assets/thanks/jetbrains.svg)
# 📚 Dromara 成员项目
@@ -0,0 +1 @@
安卓客户端项目
-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: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 833 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 804 KiB

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 KiB

Before

Width:  |  Height:  |  Size: 521 KiB

After

Width:  |  Height:  |  Size: 521 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.

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.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

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.

After

Width:  |  Height:  |  Size: 311 KiB

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

+3
View File
@@ -52,3 +52,6 @@
通过`ProxyChannel`向服务端发送`Transfer`指令,并携带响应数据。
- 5.4、服务端通过`ProxyChannel`收到来自客户端的`Transfer`指令,找到`ProxyChannel`绑定的`VisitorChannel`
通过`VisitorChannel`向用户端写入响应数据。
# 请求流程图
![流程图](../assets/design/flowchart.jpg)
+200
View File
@@ -0,0 +1,200 @@
## 1、关于作者
- dromara开源组织成员,[dromara/neutrino-proxy](https://gitee.com/asgc/neutrino-proxy)项目作者
- 名称:傲世孤尘、雨韵诗泽
- 名言:
- 扎根土壤,心向太阳。积蓄能量,绽放微光。
- 拘浊酒邀明月,借赤日暖苍穹。喻万物为星斗,化沧海成诗篇。
- 还生命以坦然,遗沧海以一笑。
- 微信号:yuyunshize
- Gitee: https://gitee.com/asgc
## 2、项目简介
- [中微子代理(neutrino-proxy)](https://gitee.com/asgc/neutrino-proxy) 是一款基于netty的内网穿透神器。该项目采用最为宽松的MIT协议,因此您可以对它进行复制、修改、传播并用于任何个人或商业行为。
- 演示环境:
- 地址:http://103.163.47.16:9527
- 账号:visitor/123456
- 该开源版本针对开发者个人,不提供任何付费服务。上面提供的地址、账号仅用于演示,已提供现成的license供大家测试。
- 市面上基于内网穿透的常见产品有:花生壳、TeamView、cpolar等。
- 该项目的目标是打开内网穿透功能黑盒,让更多感兴趣的朋友能更简单、低成本的掌握内网穿透技术。
- 学习、使用过程中,有任何问题都可以通过微信(通过上面的微信号,或扫描结尾的二维码)与笔者联系。同时,若该项目对您有所帮助,也请大家不吝转发推荐。
## 3、目录&文件说明
```
├── 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 '近期的开发计划
```
## 4、项目亮点
- 性能:基于netty,天然拥有netty的高性能优势
- 学习:除netty之外,只依赖了最基本的日志、数据库驱动/连接池、json等jar包,代理层代码量非常精简。极其适合java初学者用来学习和掌握Web框架底层原理、内网穿透实现机制。
- 调试:只需要安装jdk8,甚至连数据库都无需考虑,即可直接在本地运行服务端+客户端,调试代理流程。尽可能减少前期步骤,确保大家能更快上手。
- 测试:针对框架层封装(如:AOP、Web容器、ORM、调度管理等),提供了丰富的测试代码。
## 5、运行环境
以下版本均为笔者开发时实际的使用版本
- `Node`v13.12.0
- `Java`1.8.0_351
- `Mysql`8.0.31 (非必需,使用sqlite则无需考虑数据库)
- `Nginx`:1.21.4 (非必需,可直接使用服务端项目提供的静态资源服务)
## 6、使用方法
### 6.1、打包
可直接前往[Gitee仓库发行版页面](https://gitee.com/dromara/neutrino-proxy/releases)下载所需版本已打好的包。
若需手动打包,则可参照下面的执行命令:
```sh
# 服务端打包
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
```
### 6.2、部署
#### 6.2.1、服务端部署
使用常规的jar包部署方式即可,如:java -jar xxxx
#### 6.2.2、管理后台部署
Nginx方式部署(推荐):
```
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时,为了快速体验代理效果,可直接使用服务端项目提供的静态资源服务。
直接将`neutrino-proxy-admin`打包后的文件解压放在`neutrino-proxy-server.jar`同级别目录下即可。
例如:服务端配置的web端口为8080,则访问http://服务端IP:8080, 则会直接解析渲染`neutrino-proxy-server.jar`同级别目录下`neutrino-proxy-admin/dist/index.html`
需要注意的是,使用服务端自带的静态资源服务时,由于框架目前未支持缓存、gzip压缩,所以访问速度没有使用nginx快,正式使用推荐用nginx。
### 6.3、配置
#### 6.3.1、配置端口池
端口池的作用是将代理服务需要对外暴露的端口进行集中管理,方便安全组设置时统一操作。
本项目为了简化首次配置,默认初始化数据会将9101~9120的所有端口加入端口池。
可以在服务端项目sql配置(port_pool.data.sql文件)中自行修改初始化数据,也可以运行后在端口池管理页面手动维护。
首次使用如果为了快速体验,建议直接使用默认端口池,则无需任何配置。
#### 6.3.2、配置用户
用户是登录管理后台的身份主体,同时也是持有license的身份主体,用于支持代理连接的合法性认证。
本项目为了简化首次配置,默认初始化admin、visitor两个用户,默认密码为123456。
可以在服务端项目sql配置(user.data.sql文件)中自行修改初始化数据,也可以运行后在用户管理页面手动维护。
首次使用如果为了快速体验,建议直接使用默认用户,则无需任何配置。
#### 6.3.3、配置License
License是客户端连接代理服务端时所需要的唯一合法凭证,一个License同时只能被一个客户端使用。
本项目为了简化首次配置,默认为每个用户初始化了一些license。
可以在服务端项目sql配置(license.data.sql文件)中自行修改初始化数据,也可以运行后在License管理页面手动维护。
首次使用如果为了快速体验,建议直接使用默认License,则无需任何配置。
#### 6.3.4、配置端口映射
端口映射是代理的基本单元,一个外网端口在同一时刻被唯一的映射到一个内网IP+端口。所有流出该外网端口的流量都转发自对应的内网端口,同理所有流入该外网端口的流量都会转发到对应的内网端口。
本项目为了简化首次配置,默认为每个用户初始化了一些端口映射。可以在服务端项目sql配置(port_mapping.data.sql文件)中自行修改初始化数据,也可以运行后在端口映射管理页面手动维护。
首次使用如果为了快速体验,建议直接使用默认端口映射,则无需任何配置。
### 6.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后,保存文件命名为`.neutrino-proxy-client.json`,放在客户端当前目录下(jar包启动时,放在jar包同级别目录下。idea启动时,放在项目根目录下),然后直接启动客户端。
此时通过访问外网ip+端口,可以成功访问内网服务。通过此方式,可以代理任何基于TCP之上的协议,如:socket、websocket、http、ftp、ssh等。
## 7、开发&调试
篇幅所限,此处不便赘述管理后台相关的开发,有vue基础的童鞋基本都可以自行开发。服务端的初始化数据足够调试工作,开发过程无需单独在管理页面操作。
与SpringBoot项目类似,客户端、服务端项目均由一个入口类完成整个项目的启动,分别为`ProxyClient``ProxyServer`
内置配置采用yml风格,主要涉及Http端口、静态资源路径、协议参数、代理服务端端口、jks证书、数据源、license等。
整个项目基于`neutrino-core`,风格类似于SpringBoot。笔者不喜欢因过分炫技而引入过多花式操作,因为项目的定位是个人开发者,且满足使用的同时兼顾学习其原理的需求。基本使用方式与SpringBoot类似,尽可能降低首次学习成本。
`neutrino-core`项目test目录下包含众多核心封装的测试代码,通过调试这些代码,能尽可能减少大家学习的障碍。
## 8、运行截图
License管理:
<img src="./license1.png" width="100%"/>
端口映射管理:
<img src="./port-mapping1.png" width="100%"/>
端口池管理:
<img src="./port-pool1.png" width="100%"/>
用户管理:
<img src="./user-manager1.png" width="100%"/>
## 9、迭代进度&未来计划
`2022-06-16` 第一个完整可用版本发布(无管理后台、无DB,纯配置实现)起,该项目已具备完整的内网穿透功能。经过后续的不断重构,初步完成了以下迭代:
- 使用sqlite取代纯配置,增加管理后台界面。解决修改端口映射需要重新打包的问题。
- 增加mysql支持。初学时,直接默认使用sqlite,可以专注于学习代理相关细节而不必额外安装、配置数据库。有了自己的服务器后,可配置使用mysql,完全兼容。
- 增加端口池管理。方便服务器统一设置安全组,避免暴露的外网端口杂乱无章。
- 集成调度管理。个人版项目自带调度管理,降低学习、使用成本。无需部署、接入其他调度服务。
- 增加简单的游客权限限制。个人版项目无需权限管理,但是为了演示需要,同时避免游客篡改管理员数据。
- 封装一套类似于SpringBoot + Mybatis的简化框架,用于支撑上层代理项目,减少外部依赖,为后期孵化一个开源框架做准备。
由于笔者工作之余才能进行开发,可投入时间不固定。截至目前(2023-02),该项目2.0版本仍未发布,基于此未来的计划如下:
- 完成2.0版本发布(还剩下报表管理、首页图表功能)。
- 底层框架全部重构。
- 分离出一个开源框架项目,并持续维护。
- 思考或着手基于内网穿透的产品开发,包括但不限于内网穿透SaaS服务、堡垒机。
## 10、联系我们
笔者时间、能力有限,且开源项目非一朝一夕之事,存在众多问题亦在所难免。使用、学习过程中有任何问题欢迎大家与我联系。
对项目有什么想法或者建议,可以加我微信拉交流群,或者创建[issues](https://gitee.com/dromara/neutrino-proxy/issues),一起完善项目
<img src="MyWeChatQRCode.jpeg" width="100%"/>
Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Before

Width:  |  Height:  |  Size: 276 KiB

After

Width:  |  Height:  |  Size: 276 KiB

Before

Width:  |  Height:  |  Size: 216 KiB

After

Width:  |  Height:  |  Size: 216 KiB

Before

Width:  |  Height:  |  Size: 425 KiB

After

Width:  |  Height:  |  Size: 425 KiB

Before

Width:  |  Height:  |  Size: 202 KiB

After

Width:  |  Height:  |  Size: 202 KiB

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执行测试通过
+8
View File
@@ -0,0 +1,8 @@
# 管理后台问题汇总
## 问题1...
> 描述、解决方式
# 代理服务端问题汇总
# 代理客户端问题汇总
+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"'
}
Regular → Executable
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

+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
})
}
+6
View File
@@ -14,6 +14,12 @@ export function licenseList() {
method: 'get'
})
}
export function licenseAuthList() {
return request({
url: '/license/auth-list',
method: 'get'
})
}
export function createLicense(data) {
return request({
+45
View File
@@ -0,0 +1,45 @@
import request from '@/utils/request'
export function fetchList(query) {
return request({
url: '/port-group/page',
method: 'get',
params: query
})
}
export function portGroupList() {
return request({
url: '/port-group/list',
method: 'get'
})
}
export function updateEnableStatus(id, enable) {
return request({
url: '/port-group/update/enable-status',
method: 'post',
data: {
id: id,
enable: enable
}
})
}
export function createPortGroup(data) {
return request({
url: '/port-group/create',
method: 'post',
data
})
}
export function deleteGroup(id) {
return request({
url: '/port-group/delete',
method: 'post',
params: {
id: id
}
})
}
+17
View File
@@ -14,6 +14,15 @@ export function portPoolList() {
method: 'get'
})
}
export function availablePortList(licenseId) {
return request({
url: '/port-pool/get-available-port-list',
method: 'get',
params: {
licenseId: licenseId
}
})
}
export function updateEnableStatus(id, enable) {
return request({
@@ -34,6 +43,14 @@ export function createPortPool(data) {
})
}
export function updatePortPool(data) {
return request({
url: '/port-pool/update',
method: 'post',
data
})
}
export function deletePortPool(id) {
return request({
url: '/port-pool/delete',
+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
})
}
+40
View File
@@ -0,0 +1,40 @@
import request from '@/utils/request'
export function fetchUserFlowReportList(query) {
return request({
url: '/report/user/flow-report/page',
method: 'get',
params: query
})
}
export function fetchLicenseFlowReportList(query) {
return request({
url: '/report/license/flow-report/page',
method: 'get',
params: query
})
}
export function fetchUserFlowMonthReportList(query) {
return request({
url: '/report/user/flow-month-report/page',
method: 'get',
params: query
})
}
export function fetchLicenseFlowMonthReportList(query) {
return request({
url: '/report/license/flow-month-report/page',
method: 'get',
params: query
})
}
export function homeData(query) {
return request({
url: '/report/home/data-view',
method: 'get',
params: query
})
}
+2 -1
View File
@@ -102,7 +102,8 @@ export default {
draft: 'Draft',
delete: 'Delete',
cancel: 'Cancel',
confirm: 'Confirm'
confirm: 'Confirm',
portOrRange: 'example: 3306 or 1000-2000'
},
errorLog: {
tips: 'Please click the bug icon in the upper right corner',
+21 -2
View File
@@ -48,6 +48,8 @@ export default {
user: '用户管理',
system: '系统管理',
portPool: '端口池管理',
portGroup: '端口分组管理',
protocal: '协议管理',
proxy: '代理配置',
license: 'License管理',
portMapping: '端口映射',
@@ -55,7 +57,12 @@ export default {
jobLog: '调度日志',
log: '日志管理',
loginLog: '登录日志',
clientConnectLog: '客户端连接日志'
clientConnectLog: '客户端连接日志',
report: '报表管理',
userFlowReport: '用户流量报表',
licenseFlowReport: 'License流量报表',
userFlowMonthReport: '用户流量月度明细',
licenseFlowMonthReport: 'License流量月度明细'
},
navbar: {
logOut: '退出登录',
@@ -118,6 +125,11 @@ export default {
confirm: '确 定',
userId: '用户ID',
userName: '用户名',
group: '分组',
groupName: '分组名称',
groupPossessor: '分组所属',
possessorType: '所属类',
possessorId: '分组所属',
license: 'License',
createTime: '创建时间',
updateTime: '更新时间',
@@ -125,6 +137,7 @@ export default {
enable: '启用',
loginName: '登录名',
port: '端口',
portOrRange: '端口或范围,如: 3306或1000-2000',
licenseName: 'License名称',
licenseKey: 'License Key',
isOnline: '在线状态',
@@ -147,7 +160,13 @@ export default {
msg: '消息',
outcome: '结果',
err: '异常信息',
resetKey: '重置Key'
resetKey: '重置Key',
upFlow: '上行流量',
downFlow: '下行流量',
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);
})();
+18
View File
@@ -75,10 +75,28 @@ export const asyncRouterMap = [
},
children: [
{ 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' }}
]
},
{
path: '/report',
component: Layout,
redirect: 'noredirect',
name: 'report',
meta: {
title: 'report',
icon: 'component'
},
children: [
{ path: 'userFlowReport', component: _import('report/userFlowReport'), name: 'userFlowReport', meta: { title: 'userFlowReport' }},
{ path: 'licenseFlowReport', component: _import('report/licenseFlowReport'), name: 'licenseFlowReport', meta: { title: 'licenseFlowReport' }},
{ path: 'userFlowMonthReport', component: _import('report/userFlowMonthReport'), name: 'userFlowMonthReport', meta: { title: 'userFlowMonthReport' }},
{ path: 'licenseFlowMonthReport', component: _import('report/licenseFlowMonthReport'), name: 'licenseFlowMonthReport', meta: { title: 'licenseFlowMonthReport' }}
]
},
{
path: '/log',
component: Layout,
+29
View File
@@ -0,0 +1,29 @@
const SIZE_UNINTS = ['B', 'KB', 'MB', 'GB', 'TB']
const SIZE_SYSTEM = 1024
/**
* 根据字节数获取大小描述
* 1、小于1024字节的以B为单位
* 2、小于1024KB的以KB为单位
* 3、小于1024M的以MB为单位
* 4、小于1024G的以GB为单位
* 5、其他以TB为单位
*/
export function getSizeDescByByteCount(byteCount) {
if (byteCount <= 0) {
return '0B'
}
let res = byteCount
let index = 0
while (index < SIZE_UNINTS.length && res >= SIZE_SYSTEM) {
res /= SIZE_SYSTEM
index++
}
if (index >= SIZE_UNINTS.length) {
index = SIZE_UNINTS.length - 1
res *= 1024
}
return parseFloat(res.toFixed(2)) + SIZE_UNINTS[index]
}
@@ -22,14 +22,14 @@ export default {
},
height: {
type: String,
default: '250px'
default: '220px'
},
data: {
type: Object,
default: () => {
return {
upload: 90, // 上行
download: 100, // 下行
upFlowBytes: 0, // 上行
downFlowBytes: 0, // 下行
text: '今日流量'
}
}
@@ -57,7 +57,7 @@ export default {
const option = {
title: {
text: this.data.text,
subtext: '上行:' + this.data.upload + '\n\n' + '下行:' + this.data.download,
subtext: '上行:' + this.data.upFlowDesc + '\n\n' + '下行:' + this.data.downFlowDesc + '\n\n' + '汇总:' + this.data.totalFlowDesc,
textStyle: {
fontSize: 18,
fontWeight: 800,
@@ -65,7 +65,10 @@ export default {
}
},
tooltip: {
trigger: 'item'
trigger: 'item',
formatter: (value) => {
return `${value.seriesName} <br/>${value.marker} : ${value.name} ${value.name === '上行' ? this.data.upFlowDesc : this.data.downFlowDesc}`
}
},
legend: {
data: ['上行', '下行'],
@@ -77,7 +80,7 @@ export default {
name: this.data.text,
type: 'pie',
radius: [45, 65],
center: ['50%', '58%'],
center: ['60%', '60%'],
// 隐藏指示线
labelLine: {
normal: {
@@ -93,30 +96,30 @@ export default {
},
data: [
{
value: this.data.upload,
value: this.data.upFlowBytes,
name: '上行',
lineStyle: {
normal: {
color: '#2B81B1'
color: '#63b2ee'
}
},
itemStyle: {
normal: {
color: '#2B81B1'
color: '#63b2ee'
}
}
},
{
value: this.data.download,
value: this.data.downFlowBytes,
name: '下行',
lineStyle: {
normal: {
color: '#dbebf7'
color: '#76da91'
}
},
itemStyle: {
normal: {
color: '#dbebf7'
color: '#76da91'
}
}
}
@@ -20,14 +20,14 @@ export default {
},
height: {
type: String,
default: '250px'
default: '220px'
},
data: {
type: Object,
default: () => {
return {
onLine: 90, // 进度条最大值
total: 100 // 当前进度
onlineCount: 0, // 进度条最大值
totalCount: 0 // 当前进度
}
}
}
@@ -54,7 +54,7 @@ export default {
const option = {
title: {
text: 'License统计',
subtext: '在线数:' + this.data.onLine + '\n\n' + '离线数:' + (this.data.total - this.data.onLine),
subtext: '在线数' + this.data.onlineCount + '\n\n' + '离线数' + (this.data.totalCount - this.data.onlineCount) + '\n\n' + '汇总数量:' + this.data.totalCount,
textStyle: {
fontSize: 18,
fontWeight: 800,
@@ -75,7 +75,7 @@ export default {
name: 'License',
type: 'pie',
radius: [45, 65],
center: ['50%', '58%'],
center: ['60%', '60%'],
avoidLabelOverlap: false,
// 隐藏指示线
labelLine: {
@@ -103,29 +103,29 @@ export default {
// value当前进度 + 颜色
{
name: '在线数',
value: this.data.onLine,
value: this.data.onlineCount,
lineStyle: {
normal: {
color: '#2B81B1'
color: '#63b2ee'
}
},
itemStyle: {
normal: {
color: '#2B81B1'
color: '#63b2ee'
}
}
},
{
name: '离线数',
value: this.data.total - this.data.onLine,
value: this.data.totalCount - this.data.onlineCount,
lineStyle: {
normal: {
color: '#dbebf7'
color: '#76da91'
}
},
itemStyle: {
normal: {
color: '#dbebf7'
color: '#76da91'
}
}
}
@@ -18,14 +18,14 @@ export default {
},
height: {
type: String,
default: '250px'
default: '220px'
},
data: {
type: Object,
default: () => {
return {
onLine: 90, // 进度条最大值
total: 100 // 当前进度
onlineCount: 0, // 进度条最大值
totalCount: 0 // 当前进度
}
}
}
@@ -52,7 +52,7 @@ export default {
const option = {
title: {
text: '端口映射统计',
subtext: '在线数:' + this.data.onLine + '\n\n' + '离线数:' + (this.data.total - this.data.onLine),
subtext: '在线数' + this.data.onlineCount + '\n\n' + '离线数' + (this.data.totalCount - this.data.onlineCount) + '\n\n' + '汇总数量:' + this.data.totalCount,
textStyle: {
fontSize: 18,
fontWeight: 800,
@@ -73,7 +73,7 @@ export default {
name: '端口映射',
type: 'pie',
radius: [45, 65],
center: ['50%', '58%'],
center: ['60%', '60%'],
avoidLabelOverlap: false,
// 隐藏指示线
labelLine: {
@@ -92,29 +92,29 @@ export default {
// value当前进度 + 颜色
{
name: '在线数',
value: this.data.onLine,
value: this.data.onlineCount,
lineStyle: {
normal: {
color: '#2B81B1'
color: '#63b2ee'
}
},
itemStyle: {
normal: {
color: '#2B81B1'
color: '#63b2ee'
}
}
},
{
name: '离线数',
value: this.data.total - this.data.onLine,
value: this.data.totalCount - this.data.onlineCount,
lineStyle: {
normal: {
color: '#dbebf7'
color: '#76da91'
}
},
itemStyle: {
normal: {
color: '#dbebf7'
color: '#76da91'
}
}
}
@@ -5,7 +5,7 @@
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { getSizeDescByByteCount } from '@/utils/utils'
export default {
props: {
chartId: {
@@ -26,13 +26,13 @@ export default {
},
height: {
type: String,
default: '450px'
default: '500px'
}
},
data() {
return {
chartDom: null,
colorList: ['#2B81B1', '#75ddfa', '#53a7e3', '#029fe8', '#015dae']
colorList: ['#63b2ee', '#76da91', '#f8cb7f', '#f89588', '#7cd6cf', '#9192ab', '#7898e1', '#7898e1']
}
},
mounted() {
@@ -50,12 +50,11 @@ export default {
this.chartDom = document.getElementById(this.chartId)
this.myChart = echarts.init(this.chartDom)
const seriesList = []
const legendList = []
this.data.list.forEach((item, index) => {
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: {
@@ -71,25 +70,33 @@ export default {
normal: {
color: this.colorList[index]
}
}
},
smooth: true
})
legendList.push(item.name)
})
const option = {
title: {
text: this.data.text + '折线图',
text: this.data.text,
subtext: this.data.subtext || ''
},
tooltip: {
trigger: 'axis'
trigger: 'axis',
formatter: (value) => {
let title = this.data.text + '<br/>'
value.forEach(item => {
title = title + item.marker + item.seriesName + ' : ' + getSizeDescByByteCount(item.data) + '<br/>'
})
return title
}
},
legend: {
data: legendList,
data: this.data.legendList,
left: 'right'
},
grid: {
left: '2%',
right: '2%',
left: '0',
right: '3%',
bottom: '2%',
containLabel: true
},
@@ -99,7 +106,12 @@ export default {
data: this.data.title
},
yAxis: {
type: 'value'
type: 'value',
axisLabel: {
formatter: (value) => {
return getSizeDescByByteCount(value)
}
}
},
series: seriesList
}
@@ -1,40 +1,32 @@
<template>
<div class="dashboard-editor">
<div class="log-div">
</div>
<div class="container">
<el-row class="line-chart" :gutter="16">
<el-col :span="12">
<el-card>
<traffic-sum-chart :data="echartsData.trafficSumChart" :chartId="'traffic-sum-div1'"/>
</el-card>
</el-col>
<el-col :span="12">
<el-card>
<traffic-sum-chart :data="echartsData.trafficSumChart2"/>
</el-card>
</el-col>
</el-row>
<el-row class="pie-chart" :gutter="16">
<el-col :span="6">
<el-card class="box-card">
<license-chart :data="echartsData.licenseChart"/>
<license-chart :data="licenseChart" v-if="licenseChart"/>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="box-card">
<port-mapping-chart :data="echartsData.portMappingChart"/>
<port-mapping-chart :data="portMappingChart" v-if="portMappingChart"/>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="box-card">
<daily-traffic-chart :data="echartsData.dailyTrafficChart" :chartId="'daily-traffic-div'"/>
<daily-traffic-chart :data="dailyTrafficChart" :chartId="'daily-traffic-div'" v-if="dailyTrafficChart"/>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="box-card">
<daily-traffic-chart :data="echartsData.historyTrafficChart" :chartId="'history-traffic-div'"/>
<daily-traffic-chart :data="historyTrafficChart" :chartId="'history-traffic-div'" v-if="historyTrafficChart"/>
</el-card>
</el-col>
</el-row>
<el-row class="line-chart" :gutter="16">
<el-col :span="24">
<el-card>
<traffic-sum-chart :data="trafficSumChart" :chartId="'traffic-sum-div1'" v-if="trafficSumChart"/>
</el-card>
</el-col>
</el-row>
@@ -48,69 +40,51 @@ import LicenseChart from './components/LicenseChart'
import PortMappingChart from './components/PortMappingChart'
import DailyTrafficChart from './components/DailyTrafficChart'
import TrafficSumChart from './components/TrafficSumChart'
const echartsData = {
licenseChart: {
total: 100,
onLine: 80
},
portMappingChart: {
total: 100,
onLine: 25
},
dailyTrafficChart: {
upload: 300,
download: 680,
text: '今日流量'
},
historyTrafficChart: {
upload: 4100,
download: 9520,
text: '历史流量'
},
trafficSumChart: {
text: '今日流量',
subtext: '当日0-24时',
title: ['1:00', '2:00', '3:00', '4:00', '5:00', '6:00', '7:00'],
list: [
{
name: '上行',
value: [120, 132, 101, 134, 90, 230, 210]
},
{
name: '下行',
value: [112, 333, 150, 60, 99, 50, 102]
}
]
},
trafficSumChart2: {
text: '历史流量',
title: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
list: [
{
name: '上行',
value: [132, 101, 134, 90, 120, 132, 101, 134, 90, 230, 210, 120]
},
{
name: '下行',
value: [111, 0, 50, 101, 134, 90, 230, 120, 132, 101, 60, 20]
}
]
}
}
import { homeData } from '@/api/report'
export default {
name: 'dashboard-admin',
components: { TrafficSumChart, DailyTrafficChart, PortMappingChart, LicenseChart },
data() {
return {
echartsData: echartsData
licenseChart: undefined,
portMappingChart: undefined,
dailyTrafficChart: undefined,
historyTrafficChart: undefined,
trafficSumChart: undefined
}
},
created() {
this.getHomeData()
},
mounted() {
},
methods: {
getHomeData() {
homeData().then(res => {
this.licenseChart = res.data.data.license
this.portMappingChart = res.data.data.portMapping
this.dailyTrafficChart = Object.assign(res.data.data.todayFlow, { text: '今日流量' })
this.historyTrafficChart = Object.assign(res.data.data.totalFlow, { text: '流量汇总' })
this.trafficSumChart = this.getChartData(res.data.data.last7dFlow)
})
},
getChartData(last7dFlow) {
const list = []
last7dFlow.seriesList.forEach(item => {
list.push({
name: item.seriesName,
value: item.seriesData
})
})
return {
text: '流量监控',
subtext: `最近${last7dFlow.dataList.length || 0}天流量监控`,
title: last7dFlow.xDate,
list: list,
legendList: last7dFlow.legendData
}
}
}
}
</script>
@@ -127,9 +101,9 @@ export default {
flex: 0 0 auto;
}
.pie-chart{
padding-top: 16px;
padding-bottom: 16px;
.el-card{
min-height: 300px;
min-height: 250px;
}
}
}
@@ -1,6 +1,9 @@
<template>
<div class="app-container calendar-list-container">
<div class="filter-container">
<el-select v-model="listQuery.licenseId" placeholder="请选择license" clearable style="margin-right:10px">
<el-option v-for="item in licenseList" :key="item.key" :label="item.name" :value="item.id" />
</el-select>
<el-button type="primary" v-waves icon="el-icon-search" @click="handleFilter">{{$t('table.search')}}</el-button>
</div>
@@ -48,8 +51,8 @@
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.currentPage"
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total">
<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>
@@ -66,6 +69,7 @@
<script>
import { fetchList } from '@/api/clientConnectLog'
import { licenseList } from '@/api/license'
import waves from '@/directive/waves' // 水波纹指令
export default {
@@ -80,12 +84,13 @@ export default {
total: null,
listLoading: false,
listQuery: {
currentPage: 1,
pageSize: 10,
current: 1,
size: 10,
jobId: undefined
},
dialogVisible: false,
selectRow: {}
selectRow: {},
licenseList: []
}
},
filters: {
@@ -112,6 +117,7 @@ export default {
}
},
created() {
this.getLicenseList()
this.getList()
},
methods: {
@@ -123,6 +129,11 @@ export default {
this.listLoading = false
})
},
getLicenseList() {
licenseList().then(response => {
this.licenseList = response.data.data
})
},
handleFilter() {
this.listQuery.current = 1
this.getList()
@@ -46,8 +46,8 @@
</el-table-column>-->
</el-table>
<div class="pagination-container">
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.currentPage"
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total">
<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>
@@ -79,8 +79,8 @@ export default {
total: null,
listLoading: false,
listQuery: {
currentPage: 1,
pageSize: 10,
current: 1,
size: 10,
jobId: undefined
},
jobList: [],
+30 -11
View File
@@ -1,6 +1,9 @@
<template>
<div class="app-container calendar-list-container">
<div class="filter-container">
<el-select v-model="listQuery.userId" placeholder="请选择用户" clearable>
<el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
<el-button class="filter-item" type="primary" v-waves icon="el-icon-search" @click="handleFilter">{{$t('table.search')}}</el-button>
</div>
@@ -28,8 +31,8 @@
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.currentPage"
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total">
<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>
@@ -37,6 +40,7 @@
<script>
import { fetchList } from '@/api/loginLog'
import { userList } from '@/api/user'
import waves from '@/directive/waves' // 水波纹指令
export default {
@@ -51,10 +55,11 @@ export default {
total: null,
listLoading: false,
listQuery: {
currentPage: 1,
pageSize: 10,
jobId: undefined
}
current: 1,
size: 10,
userId: null
},
userList: []
}
},
filters: {
@@ -98,13 +103,11 @@ export default {
},
created() {
this.getList()
this.getDataList()
},
activated() {
if (this.$route.query.jobId) {
this.listQuery.jobId = this.$route.query.jobId
console.log(this.listQuery.jobId, this.$route.query.jobId)
this.getList()
}
this.getUserList()
this.getList()
},
methods: {
getList() {
@@ -115,6 +118,22 @@ export default {
this.listLoading = false
})
},
getUserList() {
userList().then(response => {
this.userList = response.data.data
})
},
getDataList() {
const loginName = this.$store.state.user.loginName
userList().then(response => {
this.userList = response.data.data
const curUser = this.userList.find((val) => val.loginName === loginName)
if (curUser) {
this.listQuery.userId = curUser.id
}
this.getList()
})
},
handleFilter() {
this.listQuery.current = 1
this.getList()
+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,6 +1,15 @@
<template>
<div class="app-container calendar-list-container">
<div class="filter-container">
<el-select v-model="listQuery.userId" placeholder="请选择用户" clearable>
<el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
<el-select v-model="listQuery.isOnline" placeholder="请选择在线状态" clearable>
<el-option v-for="item in selectObj.onlineOptions" :key="item.value" :label="item.label" :value="item.value"/>
</el-select>
<el-select v-model="listQuery.enable" placeholder="请选择启用状态" clearable>
<el-option v-for="item in selectObj.statusOptions" :key="item.value" :label="item.label" :value="item.value"/>
</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>
</div>
@@ -60,8 +69,8 @@
</el-table>
<div class="pagination-container">
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.currentPage"
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total">
<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>
@@ -132,11 +141,11 @@
total: null,
listLoading: true,
listQuery: {
currentPage: 1,
pageSize: 10,
importance: undefined,
title: undefined,
type: undefined
current: 1,
size: 10,
enable: undefined,
isOnline: undefined,
userId: null
},
importanceOptions: [1, 2, 3],
calendarTypeOptions,
@@ -165,7 +174,11 @@
userId: [{ required: true, message: '请选择用户', trigger: 'blur' }],
name: [{ required: true, message: 'License名称不能为空', trigger: 'blur' }]
},
downloadLoading: false
downloadLoading: false,
selectObj: {
statusOptions: [{ label: '启用', value: 1 }, { label: '禁用', value: 2 }],
onlineOptions: [{ label: '在线', value: 1 }, { label: '离线', value: 2 }]
}
}
},
filters: {
@@ -195,8 +208,7 @@
}
},
created() {
this.getList()
this.getUserList()
this.getDataList()
},
methods: {
getList() {
@@ -212,6 +224,17 @@
this.userList = response.data.data
})
},
getDataList() {
const loginName = this.$store.state.user.loginName
userList().then(response => {
this.userList = response.data.data
const curUser = this.userList.find((val) => val.loginName === loginName)
if (curUser) {
this.listQuery.userId = curUser.id
}
this.getList()
})
},
handleFilter() {
this.listQuery.current = 1
this.getList()
@@ -1,95 +1,130 @@
<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>
<el-button class="filter-item" style="margin-left: 10px;" @click="handleCreate" type="primary" icon="el-icon-edit">{{$t('table.add')}}</el-button>
<div class="filter-container" style="display:flex">
<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;width: 135px;">
<el-option v-for="item in licenseList" :key="item.key" :label="item.name" :value="item.id" />
</el-select>
<el-select v-model="listQuery.protocal" placeholder="请选择协议" clearable style="margin-right:10px;width: 120px;">
<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" />
</el-select>
<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"
icon="el-icon-edit">{{ $t('table.add') }}</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 :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.id')" width="50">
<template slot-scope="scope">
<span>{{scope.row.id}}</span>
<span>{{ scope.row.id }}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.userName')" width="100">
<template slot-scope="scope">
<span>{{scope.row.userName}}</span>
<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>
<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.serverPort}}</span>
<span>{{ scope.row.protocal }}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.proxyClient')" width="200">
<el-table-column align="center" :label="$t('table.domainName')" width="200">
<template slot-scope="scope">
<span>{{scope.row.clientIp}}:{{scope.row.clientPort}}</span>
<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="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>
<span>{{ scope.row.createTime | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<el-table-column width="150px" align="center" :label="$t('table.updateTime')">
<template slot-scope="scope">
<span>{{scope.row.updateTime | parseTime('{y}-{m}-{d} {h}:{i}')}}</span>
<span>{{ scope.row.updateTime | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<el-table-column class-name="status-col" :label="$t('table.enableStatus')" width="100">
<template slot-scope="scope">
<el-tag :type="scope.row.enable | statusFilter">{{scope.row.enable | statusName}}</el-tag>
<el-tag :type="scope.row.enable | statusFilter">{{ scope.row.enable | statusName }}</el-tag>
</template>
</el-table-column>
<el-table-column class-name="status-col" :label="$t('table.isOnline')" width="100">
<template slot-scope="scope">
<el-tag :type="scope.row.isOnline | statusFilter">{{scope.row.isOnline | isOnlineName}}</el-tag>
<el-tag :type="scope.row.isOnline | statusFilter">{{ scope.row.isOnline | isOnlineName }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.actions')" width="230" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="handleUpdate(scope.row)">{{$t('table.edit')}}</el-button>
<el-button v-if="scope.row.enable =='1'" size="mini" type="danger" @click="handleModifyStatus(scope.row,2)">{{$t('table.disable')}}</el-button>
<el-button v-if="scope.row.enable =='2'" size="mini" type="success" @click="handleModifyStatus(scope.row,1)">{{$t('table.enable')}}</el-button>
<el-button type="primary" size="mini" @click="handleUpdate(scope.row)">{{ $t('table.edit') }}</el-button>
<el-button v-if="scope.row.enable == '1'" size="mini" type="danger" @click="handleModifyStatus(scope.row, 2)">{{
$t('table.disable') }}</el-button>
<el-button v-if="scope.row.enable == '2'" size="mini" type="success"
@click="handleModifyStatus(scope.row, 1)">{{ $t('table.enable') }}</el-button>
<!-- <el-button size="mini" type="danger" @click="handleDelete(scope.row,'deleted')">{{$t('table.delete')}}</el-button>-->
<ButtonPopover @handleCommitClick="handleDelete2(scope.row)" style="margin-left: 10px"/>
<ButtonPopover @handleCommitClick="handleDelete2(scope.row)" style="margin-left: 10px" />
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.currentPage"
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total">
<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>
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
<el-form :rules="rules" ref="dataForm" :model="temp" label-position="left" label-width="120px" style='width: 400px; margin-left:50px;'>
<!-- <el-form-item :label="$t('License')" prop="licenseId">-->
<!-- <el-select style="width: 280px;" class="filter-item" v-model="temp.licenseId" placeholder="请选择" :disabled="dialogStatus=='update'">-->
<!-- <el-option v-for="item in licenseList" :key="item.id" :label="item.name" :value="item.id">-->
<!-- </el-option>-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<el-form :rules="rules" ref="dataForm" :model="temp" label-position="left" label-width="120px"
style='width: 400px; margin-left:50px;'>
<!-- <el-form-item :label="$t('License')" prop="licenseId">-->
<!-- <el-select style="width: 280px;" class="filter-item" v-model="temp.licenseId" placeholder="请选择" :disabled="dialogStatus=='update'">-->
<!-- <el-option v-for="item in licenseList" :key="item.id" :label="item.name" :value="item.id">-->
<!-- </el-option>-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<el-form-item :label="$t('License')" prop="licenseId">
<DropdownTable
v-model="temp.licenseId"
:name.sync="temp.licenseName"
:tableData= "licenseList"
@selectedData="selectedFeeItem"
placeholder="请选择"
:width="280"
:disabled="dialogStatus==='update'"
/>
<!-- <DropdownTable
<DropdownTable v-model="temp.licenseId" :name.sync="temp.licenseName" :tableData="licenseAuthList"
@selectedData="selectedFeeItem" placeholder="请选择" :width="280" :disabled="dialogStatus === 'update'" />
<!-- <DropdownTable
:columns="countryColumns"
:data="licenseList"
v-model="temp.licenseId"
@@ -100,6 +135,13 @@
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-option v-for="item in serverPortList" :key="item.port" :label="item.port" :value="item.port">
@@ -112,11 +154,21 @@
<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' && 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="dialogFormVisible = false">{{$t('table.cancel')}}</el-button>
<el-button v-if="dialogStatus=='create'" type="primary" @click="createData">{{$t('table.confirm')}}</el-button>
<el-button v-else type="primary" @click="updateData">{{$t('table.confirm')}}</el-button>
<el-button @click="
dialogFormVisible = false">{{ $t('table.cancel') }}</el-button>
<el-button v-if="dialogStatus == 'create'" type="primary" @click="createData">{{ $t('table.confirm')
}}</el-button>
<el-button v-else type="primary" @click="updateData">{{ $t('table.confirm') }}</el-button>
</div>
</el-dialog>
@@ -126,7 +178,7 @@
<el-table-column prop="pv" label="Pv"> </el-table-column>
</el-table>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="dialogPvVisible = false">{{$t('table.confirm')}}</el-button>
<el-button type="primary" @click="dialogPvVisible = false">{{ $t('table.confirm') }}</el-button>
</span>
</el-dialog>
@@ -134,251 +186,302 @@
</template>
<script>
import { fetchList, createUserPortMapping, updateUserPortMapping, updateEnableStatus, deletePortMapping } from '@/api/portMapping'
import { portPoolList } from '@/api/portPool'
import { licenseList } from '@/api/license'
import waves from '@/directive/waves' // 水波纹指令
import { parseTime } from '@/utils'
import ButtonPopover from '../../components/Button/buttonPopover'
import DropdownTable from '../../components/Dropdown/DropdownTable'
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'
import DropdownTable from '../../components/Dropdown/DropdownTable'
const calendarTypeOptions = [
{ key: 'CN', display_name: 'China' },
{ key: 'US', display_name: 'USA' },
{ key: 'JP', display_name: 'Japan' },
{ key: 'EU', display_name: 'Eurozone' }
]
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
}, {})
// 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: {
DropdownTable,
ButtonPopover
},
data() {
return {
tableKey: 0,
list: null,
total: null,
listLoading: true,
listQuery: {
currentPage: 1,
pageSize: 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'],
licenseList: [],
serverPortList: [],
showReviewer: false,
temp: {
id: undefined,
licenseId: undefined,
licenseName: undefined,
serverPort: undefined,
clientIp: undefined,
clientPort: undefined
},
dialogFormVisible: false,
dialogStatus: '',
textMap: {
update: '编辑',
create: '新建'
},
dialogPvVisible: false,
pvData: [],
rules: {
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' }]
},
downloadLoading: false,
countryColumns: [
{ prop: 'userName', label: '用户名', align: 'center' },
{ prop: 'name', label: 'License', align: 'center' }
]
export default {
name: 'complexTable',
directives: {
waves
},
components: {
DropdownTable,
ButtonPopover
},
data() {
return {
tableKey: 0,
list: null,
total: null,
listLoading: true,
listQuery: {
current: 1,
size: 10,
importance: undefined,
title: undefined,
type: undefined,
userId: undefined,
license: undefined,
port: undefined,
isOnline: undefined,
enable: undefined,
description: undefined
},
importanceOptions: [1, 2, 3],
calendarTypeOptions,
sortOptions: [{ label: 'ID Ascending', key: '+id' }, { label: 'ID Descending', key: '-id' }],
statusOptions: ['published', 'draft', 'deleted'],
userList: [],
licenseList: [],
protocalList: [],
licenseAuthList: [],
serverPortList: [],
domainName: '',
showReviewer: false,
temp: {
id: undefined,
licenseId: undefined,
licenseName: undefined,
serverPort: undefined,
clientIp: undefined,
clientPort: undefined,
protocal: undefined
},
selectObj: {
statusOptions: [{ label: '启用', value: 1 }, { label: '禁用', value: 2 }],
onlineOptions: [{ label: '在线', value: 1 }, { label: '离线', value: 2 }]
},
dialogFormVisible: false,
dialogStatus: '',
textMap: {
update: '编辑',
create: '新建'
},
dialogPvVisible: false,
pvData: [],
rules: {
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' }],
protocal: [{ required: true, message: '请选择协议', trigger: 'blur' }]
},
downloadLoading: false,
countryColumns: [
{ prop: 'userName', label: '用户名', align: 'center' },
{ prop: 'name', label: 'License', align: 'center' }
]
}
},
filters: {
statusName(status) {
const statusMap = {
1: '启用',
2: '禁用'
}
return statusMap[status]
},
filters: {
statusName(status) {
const statusMap = {
1: '启用',
2: '禁用'
}
return statusMap[status]
},
isOnlineName(isOnline) {
const isOnlineMap = {
1: '在线',
2: '离线'
}
return isOnlineMap[isOnline]
},
statusFilter(status) {
const statusMap = {
1: 'success',
2: 'danger'
}
return statusMap[status]
},
typeFilter(type) {
return calendarTypeKeyValue[type]
isOnlineName(isOnline) {
const isOnlineMap = {
1: '在线',
2: '离线'
}
return isOnlineMap[isOnline]
},
created() {
statusFilter(status) {
const statusMap = {
1: 'success',
2: 'danger'
}
return statusMap[status]
},
typeFilter(type) {
return calendarTypeKeyValue[type]
}
},
created() {
this.getDomainNameBindInfo()
this.getDataList()
this.getLicenseList()
this.getLicenseAuthList()
this.getProtocalList()
},
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
})
},
getDataList() {
const loginName = this.$store.state.user.loginName
userList().then(response => {
this.userList = response.data.data
const curUser = this.userList.find((val) => val.loginName === loginName)
if (curUser) {
this.listQuery.userId = curUser.id
}
this.getList()
})
},
getDomainNameBindInfo() {
domainNameBindInfo().then(response => {
this.domainName = response.data.data
})
},
getPortPoolList() {
portPoolList().then(response => {
this.serverPortList = response.data.data
})
},
getAvailablePortList(licenseId) {
availablePortList(licenseId).then(response => {
this.serverPortList = response.data.data
})
},
getAllUserList() {
userList().then(response => {
this.userList = response.data.data
})
},
getLicenseList() {
licenseList().then(response => {
this.licenseList = response.data.data
})
},
getProtocalList() {
protocalList().then(response => {
this.protocalList = response.data.data
})
},
getLicenseAuthList() {
licenseAuthList().then(response => {
this.licenseAuthList = response.data.data
})
},
handleFilter() {
this.listQuery.current = 1
this.getList()
this.getPortPoolList()
this.getLicenseList()
},
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
})
},
getPortPoolList() {
portPoolList().then(response => {
this.serverPortList = response.data.data
})
},
getLicenseList() {
licenseList().then(response => {
this.licenseList = response.data.data
})
},
handleFilter() {
this.listQuery.current = 1
this.getList()
},
handleSizeChange(val) {
this.listQuery.size = val
this.getList()
},
handleCurrentChange(val) {
this.listQuery.current = val
this.getList()
},
handleModifyStatus(row, enable) {
console.log('route', this.$route)
updateEnableStatus(row.id, enable).then(response => {
if (response.data.data.code === 0) {
this.$message({
message: '操作成功',
type: 'success'
})
}
this.getList()
})
},
resetTemp() {
this.temp = {
id: undefined,
licenseId: undefined,
licenseName: undefined,
serverPort: undefined,
clientIp: undefined,
clientPort: undefined
handleSizeChange(val) {
this.listQuery.size = val
this.getList()
},
handleCurrentChange(val) {
this.listQuery.current = val
this.getList()
},
handleModifyStatus(row, enable) {
console.log('route', this.$route)
updateEnableStatus(row.id, enable).then(response => {
if (response.data.data.code === 0) {
this.$message({
message: '操作成功',
type: 'success'
})
}
},
handleCreate() {
this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
createData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
createUserPortMapping(this.temp).then(response => {
if (response.data.code === 0) {
this.dialogFormVisible = false
this.$notify({
title: '成功',
message: '创建成功',
type: 'success',
duration: 2000
})
this.getList()
}
})
}
})
},
handleUpdate(row) {
this.temp = Object.assign({}, row) // copy obj
this.temp.timestamp = new Date(this.temp.timestamp)
this.dialogStatus = 'update'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
updateData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
const tempData = Object.assign({}, this.temp)
updateUserPortMapping(tempData).then(response => {
if (response.data.code === 0) {
// this.$message({
// message: '操作成功',
// type: 'success'
// })
this.$notify({
title: '成功',
message: '更新成功',
type: 'success',
duration: 2000
})
this.dialogFormVisible = false
this.getList()
}
})
}
})
},
selectedFeeItem(row, list) {
this.temp.licenseId = row.id
this.temp.licenseName = row.name
},
handleDelete(row) {
this.$confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deletePortMapping(row.id).then(response => {
this.getList()
})
},
resetTemp() {
this.temp = {
id: undefined,
licenseId: undefined,
licenseName: undefined,
serverPort: undefined,
clientIp: '127.0.0.1',
clientPort: undefined,
userId: undefined
}
this.serverPortList = []
},
handleCreate() {
this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
createData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
createUserPortMapping(this.temp).then(response => {
if (response.data.code === 0) {
this.dialogFormVisible = false
this.$notify({
title: '成功',
message: '删除成功',
message: '创建成功',
type: 'success',
duration: 2000
})
this.getList()
}
})
}).catch(() => {})
},
handleDelete2(row) {
}
})
},
handleUpdate(row) {
this.temp = Object.assign({}, row) // copy obj
this.temp.timestamp = new Date(this.temp.timestamp)
this.dialogStatus = 'update'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
this.getAvailablePortList(row.licenseId)
},
updateData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
const tempData = Object.assign({}, this.temp)
updateUserPortMapping(tempData).then(response => {
if (response.data.code === 0) {
// this.$message({
// message: '操作成功',
// type: 'success'
// })
this.$notify({
title: '成功',
message: '更新成功',
type: 'success',
duration: 2000
})
this.dialogFormVisible = false
this.getList()
}
})
}
})
},
selectedFeeItem(row, list) {
if (this.temp.licenseId !== row.id) {
this.temp.licenseId = row.id
this.temp.licenseName = row.name
this.getAvailablePortList(row.id)
this.temp.serverPort = null
}
},
handleDelete(row) {
this.$confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deletePortMapping(row.id).then(response => {
if (response.data.code === 0) {
this.$notify({
@@ -390,26 +493,40 @@
this.getList()
}
})
},
handleDownload() {
this.downloadLoading = true
import('@/vendor/Export2Excel').then(excel => {
const tHeader = ['timestamp', 'title', 'type', 'importance', 'status']
const filterVal = ['timestamp', 'title', 'type', 'importance', 'status']
const data = this.formatJson(filterVal, this.list)
excel.export_json_to_excel(tHeader, data, 'table-list')
this.downloadLoading = false
})
},
formatJson(filterVal, jsonData) {
return jsonData.map(v => filterVal.map(j => {
if (j === 'timestamp') {
return parseTime(v[j])
} else {
return v[j]
}
}))
}
}).catch(() => { })
},
handleDelete2(row) {
deletePortMapping(row.id).then(response => {
if (response.data.code === 0) {
this.$notify({
title: '成功',
message: '删除成功',
type: 'success',
duration: 2000
})
this.getList()
}
})
},
handleDownload() {
this.downloadLoading = true
import('@/vendor/Export2Excel').then(excel => {
const tHeader = ['timestamp', 'title', 'type', 'importance', 'status']
const filterVal = ['timestamp', 'title', 'type', 'importance', 'status']
const data = this.formatJson(filterVal, this.list)
excel.export_json_to_excel(tHeader, data, 'table-list')
this.downloadLoading = false
})
},
formatJson(filterVal, jsonData) {
return jsonData.map(v => filterVal.map(j => {
if (j === 'timestamp') {
return parseTime(v[j])
} else {
return v[j]
}
}))
}
}
}
</script>
@@ -0,0 +1,149 @@
<template>
<div class="app-container calendar-list-container">
<div class="filter-container">
<el-select v-model="listQuery.userId" placeholder="请选择用户" clearable>
<el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
<el-select v-model="listQuery.licenseId" placeholder="请选择License" clearable>
<el-option v-for="item in licenseList" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
<el-button 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 type="index" width="100" :label="$t('table.id')"></el-table-column>
<el-table-column align="center" :label="$t('table.userName')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.userName}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.licenseName')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.licenseName}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.upFlow')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.upFlowDesc}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.downFlow')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.downFlowDesc}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.totalFlow')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.totalFlowDesc}}</span>
</template>
</el-table-column>
<el-table-column width="150px" align="center" :label="$t('table.date')">
<template slot-scope="scope">
<span>{{scope.row.date | parseTime('{y}-{m}')}}</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 { fetchLicenseFlowMonthReportList } from '@/api/report'
import { userList } from '@/api/user'
import waves from '@/directive/waves'
import { licenseList } from '@/api/license'
export default {
name: 'licenseFlowMonthReport',
directives: {
waves
},
data() {
return {
tableKey: 0,
list: null,
total: null,
listLoading: false,
listQuery: {
current: 1,
size: 10,
userId: undefined,
licenseId: undefined
},
userList: [],
licenseList: [],
dialogVisible: false,
selectRow: {}
}
},
filters: {
},
created() {
this.getList()
this.getUserList()
this.getLicenseList()
},
activated() {
this.getUserList()
this.getLicenseList()
if (this.$route.query.userId) {
this.listQuery.userId = this.$route.query.userId
}
if (this.$route.query.licenseId) {
this.listQuery.licenseId = this.$route.query.licenseId
}
this.getList()
},
methods: {
getList() {
this.listLoading = true
fetchLicenseFlowMonthReportList(this.listQuery).then(response => {
this.list = response.data.data.records
this.total = response.data.data.total
this.listLoading = false
})
},
getUserList() {
userList().then(response => {
this.userList = response.data.data
})
},
getLicenseList() {
licenseList().then(response => {
this.licenseList = response.data.data
})
},
handleFilter() {
this.listQuery.current = 1
this.getList()
},
handleSizeChange(val) {
this.listQuery.size = val
this.listQuery.current = 1
this.getList()
},
handleCurrentChange(val) {
this.listQuery.current = val
this.getList()
},
handleShowClick(row) {
console.log(row)
},
handleLookOver(row) {
this.selectRow = row
this.dialogVisible = true
}
}
}
</script>
<style>
.job-msg-div{
max-height: 400px;
overflow-y: auto;
}
</style>
@@ -0,0 +1,136 @@
<template>
<div class="app-container calendar-list-container">
<div class="filter-container">
<el-select v-model="listQuery.userId" placeholder="请选择用户" clearable>
<el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
<el-button 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 type="index" width="100" :label="$t('table.id')"></el-table-column>
<el-table-column align="center" :label="$t('table.userName')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.userName}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.licenseName')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.licenseName}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.upFlow')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.upFlowDesc}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.downFlow')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.downFlowDesc}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.totalFlow')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.totalFlowDesc}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.actions')" min-width="230" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" @click="handleFlowMonthReportClick(scope.row)">流量月度明细</el-button>
</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 { fetchLicenseFlowReportList } from '@/api/report'
import { userList } from '@/api/user'
import waves from '@/directive/waves'
export default {
name: 'licenseFlowReport',
directives: {
waves
},
data() {
return {
tableKey: 0,
list: null,
total: null,
listLoading: false,
listQuery: {
current: 1,
size: 10,
jobId: undefined
},
userList: [],
dialogVisible: false,
selectRow: {}
}
},
filters: {
},
created() {
this.getList()
this.getUserList()
},
activated() {
this.getUserList()
if (this.$route.query.jobId) {
this.listQuery.jobId = this.$route.query.jobId
this.getList()
}
},
methods: {
getList() {
this.listLoading = true
fetchLicenseFlowReportList(this.listQuery).then(response => {
this.list = response.data.data.records
this.total = response.data.data.total
this.listLoading = false
})
},
getUserList() {
userList().then(response => {
this.userList = response.data.data
})
},
handleFilter() {
this.listQuery.current = 1
this.getList()
},
handleSizeChange(val) {
this.listQuery.size = val
this.listQuery.current = 1
this.getList()
},
handleCurrentChange(val) {
this.listQuery.current = val
this.getList()
},
handleShowClick(row) {
console.log(row)
},
handleLookOver(row) {
this.selectRow = row
this.dialogVisible = true
},
handleFlowMonthReportClick(row) {
this.$router.push({ path: '/report/licenseFlowMonthReport', query: { userId: row.userId, licenseId: row.licenseId }})
}
}
}
</script>
<style>
.job-msg-div{
max-height: 400px;
overflow-y: auto;
}
</style>
@@ -0,0 +1,128 @@
<template>
<div class="app-container calendar-list-container">
<div class="filter-container">
<el-select v-model="listQuery.userId" placeholder="请选择用户" clearable>
<el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
<el-button 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 type="index" width="100" :label="$t('table.id')"></el-table-column>
<el-table-column align="center" :label="$t('table.userName')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.userName}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.upFlow')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.upFlowDesc}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.downFlow')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.downFlowDesc}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.totalFlow')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.totalFlowDesc}}</span>
</template>
</el-table-column>
<el-table-column width="150px" align="center" :label="$t('table.date')">
<template slot-scope="scope">
<span>{{scope.row.date | parseTime('{y}-{m}')}}</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 { fetchUserFlowMonthReportList } from '@/api/report'
import { userList } from '@/api/user'
import waves from '@/directive/waves' // 水波纹指令
export default {
name: 'userFlowMonthReport',
directives: {
waves
},
data() {
return {
tableKey: 0,
list: null,
total: null,
listLoading: false,
listQuery: {
current: 1,
size: 10,
userId: undefined
},
userList: [],
dialogVisible: false,
selectRow: {}
}
},
filters: {
},
created() {
this.getList()
this.getUserList()
},
activated() {
this.getUserList()
if (this.$route.query.userId) {
this.listQuery.userId = this.$route.query.userId
this.getList()
}
},
methods: {
getList() {
this.listLoading = true
fetchUserFlowMonthReportList(this.listQuery).then(response => {
this.list = response.data.data.records
this.total = response.data.data.total
this.listLoading = false
})
},
getUserList() {
userList().then(response => {
this.userList = response.data.data
})
},
handleFilter() {
this.listQuery.current = 1
this.getList()
},
handleSizeChange(val) {
this.listQuery.size = val
this.listQuery.current = 1
this.getList()
},
handleCurrentChange(val) {
this.listQuery.current = val
this.getList()
},
handleShowClick(row) {
console.log(row)
},
handleLookOver(row) {
this.selectRow = row
this.dialogVisible = true
}
}
}
</script>
<style>
.job-msg-div{
max-height: 400px;
overflow-y: auto;
}
</style>
@@ -0,0 +1,135 @@
<template>
<div class="app-container calendar-list-container">
<div class="filter-container">
<el-select v-model="listQuery.userId" placeholder="请选择用户" clearable>
<el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
<el-button 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 type="index" width="100" :label="$t('table.id')"></el-table-column>
<el-table-column align="center" :label="$t('table.userName')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.userName}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.upFlow')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.upFlowDesc}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.downFlow')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.downFlowDesc}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.totalFlow')" min-width="120">
<template slot-scope="scope">
<span>{{scope.row.totalFlowDesc}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.actions')" min-width="230" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" @click="handleFlowMonthReportClick(scope.row)">流量月度明细</el-button>
<el-button size="mini" type="text" @click="handleLicenseFlowMonthReportClick(scope.row)">License流量月度明细</el-button>
</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 { fetchUserFlowReportList } from '@/api/report'
import { userList } from '@/api/user'
import waves from '@/directive/waves' // 水波纹指令
export default {
name: 'userFlowReport',
directives: {
waves
},
data() {
return {
tableKey: 0,
list: null,
total: null,
listLoading: false,
listQuery: {
current: 1,
size: 10,
userId: undefined
},
userList: [],
dialogVisible: false,
selectRow: {}
}
},
filters: {
},
created() {
this.getList()
this.getUserList()
},
activated() {
this.getUserList()
if (this.$route.query.userId) {
this.listQuery.userId = this.$route.query.userId
this.getList()
}
},
methods: {
getList() {
this.listLoading = true
fetchUserFlowReportList(this.listQuery).then(response => {
this.list = response.data.data.records
this.total = response.data.data.total
this.listLoading = false
})
},
getUserList() {
userList().then(response => {
this.userList = response.data.data
})
},
handleFilter() {
this.listQuery.current = 1
this.getList()
},
handleSizeChange(val) {
this.listQuery.size = val
this.listQuery.current = 1
this.getList()
},
handleCurrentChange(val) {
this.listQuery.current = val
this.getList()
},
handleShowClick(row) {
console.log(row)
},
handleLookOver(row) {
this.selectRow = row
this.dialogVisible = true
},
handleFlowMonthReportClick(row) {
this.$router.push({ path: '/report/userFlowMonthReport', query: { userId: row.userId }})
},
handleLicenseFlowMonthReportClick(row) {
this.$router.push({ path: '/report/licenseFlowMonthReport', query: { userId: row.userId }})
}
}
}
</script>
<style>
.job-msg-div{
max-height: 400px;
overflow-y: auto;
}
</style>
@@ -51,8 +51,8 @@
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.currentPage"
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total">
<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>
@@ -114,8 +114,8 @@
total: null,
listLoading: true,
listQuery: {
currentPage: 1,
pageSize: 10,
current: 1,
size: 10,
importance: undefined,
title: undefined,
type: undefined
@@ -0,0 +1,346 @@
<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>
<el-button class="filter-item" style="margin-left: 10px;" @click="handleCreate" type="primary"
icon="el-icon-edit">{{ $t('table.add') }}
</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.id')" width="120">
<template slot-scope="scope">
<span>{{ scope.row.id }}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.groupName')" width="200">
<template slot-scope="scope">
<span>{{ scope.row.name }}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.groupPossessor')" width="200">
<template slot-scope="scope">
<span>{{ scope.row.possessorType == 1 ? "用户:" : (scope.row.possessorType == 2 ? "License:" : "全局") }} {{ scope.row.possessor }}</span>
</template>
</el-table-column>
<el-table-column width="200" align="center" :label="$t('table.createTime')">
<template slot-scope="scope">
<span>{{ scope.row.createTime | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<el-table-column width="200" align="center" :label="$t('table.updateTime')">
<template slot-scope="scope">
<span>{{ scope.row.updateTime | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<!-- <el-table-column class-name="status-col" :label="$t('table.enableStatus')" 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="center" :label="$t('table.actions')" width="250" class-name="small-padding fixed-width">
<template slot-scope="scope">
<!-- <el-button v-if="scope.row.enable =='1'" size="mini" type="danger" @click="handleModifyStatus(scope.row,2)">-->
<!-- {{ $t('table.disable') }}-->
<!-- </el-button>-->
<!-- <el-button v-if="scope.row.enable =='2'" size="mini" type="success" @click="handleModifyStatus(scope.row,1)">-->
<!-- {{ $t('table.enable') }}-->
<!-- </el-button>-->
<!-- <el-button v-if="scope.row.status!='deleted'" size="mini" type="danger" @click="handleDelete(scope.row,'deleted')">{{$t('table.delete')}}</el-button>-->
<ButtonPopover @handleCommitClick="handleDelete(scope.row)" style="margin-left: 10px"/>
</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>
<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 style="width: 350px" class="filter-item" :label="$t('table.groupName')" prop="name">
<el-input v-model="temp.name"></el-input>
</el-form-item>
<el-form-item :label="$t('table.possessorType')" prop="possessorType">
<el-select style="width: 280px" class="filter-item" v-model="temp.possessorType" placeholder="请选择"
:disabled="dialogStatus=='update'">
<el-option v-for="item in possessorTypeList" :key="item.id" :label="item.name" :value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item v-show="temp.possessorType==1" :label="$t('table.userName')" prop="possessorId">
<el-select style="width: 280px" class="filter-item" v-model="temp.possessorId" placeholder="请选择"
:disabled="dialogStatus=='update'">
<el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item v-show="temp.possessorType==2" :label="$t('table.license')" prop="possessorId">
<el-select style="width: 280px" class="filter-item" v-model="temp.possessorId" placeholder="请选择"
:disabled="dialogStatus=='update'">
<el-option v-for="item in licenseList" :key="item.id" :label="item.name" :value="item.id">
</el-option>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">{{ $t('table.cancel') }}</el-button>
<el-button v-if="dialogStatus=='create'" type="primary" @click="createData">{{ $t('table.confirm') }}
</el-button>
</div>
</el-dialog>
<el-dialog title="Reading statistics" :visible.sync="dialogPvVisible">
<el-table :data="pvData" border fit highlight-current-row style="width: 100%">
<el-table-column prop="key" label="Channel"></el-table-column>
<el-table-column prop="pv" label="Pv"></el-table-column>
</el-table>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="dialogPvVisible = false">{{ $t('table.confirm') }}</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { fetchList, updateEnableStatus, createPortGroup, deleteGroup } from '@/api/portGroup'
import waves from '@/directive/waves' // 水波纹指令
import { parseTime } from '@/utils'
import { userList } from '@/api/user'
import ButtonPopover from '../../components/Button/buttonPopover'
import { licenseList } from '../../api/license'
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,
userList: [],
licenseList: [],
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 = {
1: '启用',
2: '禁用'
}
return statusMap[status]
},
statusFilter(status) {
const statusMap = {
1: 'success',
2: 'danger'
}
return statusMap[status]
},
typeFilter(type) {
return calendarTypeKeyValue[type]
}
},
created() {
this.getList()
this.getUserList()
this.getLicenseList()
},
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
})
},
getUserList() {
userList().then(response => {
this.userList = response.data.data
})
},
getLicenseList() {
licenseList().then(response => {
this.licenseList = response.data.data
})
},
handleFilter() {
this.listQuery.current = 1
this.getList()
},
handleSizeChange(val) {
this.listQuery.size = val
this.getList()
},
handleCurrentChange(val) {
this.listQuery.current = val
this.getList()
},
handleModifyStatus(row, enable) {
console.log('route', this.$route)
updateEnableStatus(row.id, enable).then(response => {
if (response.data.data.code === 0) {
this.$message({
message: '操作成功',
type: 'success'
})
}
this.getList()
})
},
resetTemp() {
this.temp = {
id: undefined,
importance: 1,
remark: '',
timestamp: new Date(),
title: '',
status: 'published',
type: ''
}
},
handleCreate() {
this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
createData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
createPortGroup(this.temp).then(response => {
if (response.data.code === 0) {
this.dialogFormVisible = false
this.$notify({
title: '成功',
message: '创建成功',
type: 'success',
duration: 2000
})
this.getList()
}
})
}
})
},
handleUpdate(row) {
this.temp = Object.assign({}, row) // copy obj
this.temp.timestamp = new Date(this.temp.timestamp)
this.dialogStatus = 'update'
this.dialogFormVisible = true
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
},
handleDelete(row) {
deleteGroup(row.id).then(response => {
if (response.data.code === 0) {
this.$notify({
title: '成功',
message: '删除成功',
type: 'success',
duration: 2000
})
this.getList()
}
})
},
handleDownload() {
this.downloadLoading = true
import('@/vendor/Export2Excel').then(excel => {
const tHeader = ['timestamp', 'title', 'type', 'importance', 'status']
const filterVal = ['timestamp', 'title', 'type', 'importance', 'status']
const data = this.formatJson(filterVal, this.list)
excel.export_json_to_excel(tHeader, data, 'table-list')
this.downloadLoading = false
})
},
formatJson(filterVal, jsonData) {
return jsonData.map(v => filterVal.map(j => {
if (j === 'timestamp') {
return parseTime(v[j])
} else {
return v[j]
}
}))
}
}
}
</script>
@@ -1,6 +1,9 @@
<template>
<div class="app-container calendar-list-container">
<div class="filter-container">
<el-select v-model="listQuery.groupId" placeholder="请选择端口池分组" clearable>
<el-option v-for="item in portGroupList" :key="item.id" :label="item.name" :value="item.id"/>
</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>
</div>
@@ -17,6 +20,11 @@
<span>{{scope.row.port}}</span>
</template>
</el-table-column>
<el-table-column align="center" :label="$t('table.groupName')" width="200">
<template slot-scope="scope">
<span>{{scope.row.groupName}}</span>
</template>
</el-table-column>
<el-table-column width="200" align="center" :label="$t('table.createTime')">
<template slot-scope="scope">
<span>{{scope.row.createTime | parseTime('{y}-{m}-{d} {h}:{i}')}}</span>
@@ -34,6 +42,7 @@
</el-table-column>
<el-table-column align="center" :label="$t('table.actions')" width="250" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="handleUpdate(scope.row)">{{$t('table.edit')}}</el-button>
<el-button v-if="scope.row.enable =='1'" size="mini" type="danger" @click="handleModifyStatus(scope.row,2)">{{$t('table.disable')}}</el-button>
<el-button v-if="scope.row.enable =='2'" size="mini" type="success" @click="handleModifyStatus(scope.row,1)">{{$t('table.enable')}}</el-button>
<!-- <el-button v-if="scope.row.status!='deleted'" size="mini" type="danger" @click="handleDelete(scope.row,'deleted')">{{$t('table.delete')}}</el-button>-->
@@ -43,20 +52,29 @@
</el-table>
<div class="pagination-container">
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.currentPage"
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total">
<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>
<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"></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">
<el-select style="width: 330px" class="filter-item" v-model="temp.groupId">
<el-option v-for="item in portGroupList" :key="item.id" :label="item.name" :value="item.id">
</el-option>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">{{$t('table.cancel')}}</el-button>
<el-button v-if="dialogStatus=='create'" type="primary" @click="createData">{{$t('table.confirm')}}</el-button>
<el-button v-else type="primary" @click="updateData">{{$t('table.confirm')}}</el-button>
</div>
</el-dialog>
@@ -74,12 +92,13 @@
</template>
<script>
import { fetchList, updateEnableStatus, createPortPool, deletePortPool } from '@/api/portPool'
import waves from '@/directive/waves' // 水波纹指令
import { parseTime } from '@/utils'
import ButtonPopover from '../../components/Button/buttonPopover'
import { createPortPool, deletePortPool, 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' },
@@ -107,11 +126,9 @@
total: null,
listLoading: true,
listQuery: {
currentPage: 1,
pageSize: 10,
importance: undefined,
title: undefined,
type: undefined
current: 1,
size: 10,
groupId: undefined
},
importanceOptions: [1, 2, 3],
calendarTypeOptions,
@@ -119,6 +136,7 @@
statusOptions: ['published', 'draft', 'deleted'],
showReviewer: false,
temp: {
groupId: 1,
id: undefined,
importance: 1,
remark: '',
@@ -133,6 +151,7 @@
update: '编辑',
create: '新建'
},
portGroupList: [],
dialogPvVisible: false,
pvData: [],
rules: {
@@ -161,7 +180,9 @@
}
},
created() {
this.getList()
// eslint-disable-next-line no-sequences
this.getList(),
this.getPortGroupList()
},
methods: {
getList() {
@@ -172,6 +193,11 @@
this.listLoading = false
})
},
getPortGroupList() {
portGroupList().then(response => {
this.portGroupList = response.data.data
})
},
handleFilter() {
this.listQuery.current = 1
this.getList()
@@ -204,13 +230,15 @@
timestamp: new Date(),
title: '',
status: 'published',
type: ''
type: '',
groupId: 1
}
},
handleCreate() {
this.resetTemp()
this.dialogStatus = 'create'
this.dialogFormVisible = true
this.temp.groupId = 1
this.$nextTick(() => {
this.$refs['dataForm'].clearValidate()
})
@@ -242,6 +270,25 @@
this.$refs['dataForm'].clearValidate()
})
},
updateData() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
const tempData = Object.assign({}, this.temp)
updatePortPool(tempData).then(response => {
if (response.data.code === 0) {
this.$notify({
title: '成功',
message: '更新成功',
type: 'success',
duration: 2000
})
this.dialogFormVisible = false
this.getList()
}
})
}
})
},
handleDelete(row) {
deletePortPool(row.id).then(response => {
if (response.data.code === 0) {
@@ -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>
@@ -50,8 +50,8 @@
</el-table>
<div class="pagination-container">
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.currentPage"
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total">
<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>
@@ -128,8 +128,8 @@
total: null,
listLoading: true,
listQuery: {
currentPage: 1,
pageSize: 10,
current: 1,
size: 10,
importance: undefined,
title: undefined,
type: undefined
+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
+16 -50
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,52 +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>
</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,8 +1,7 @@
package fun.asgc.neutrino.proxy.client;
package org.dromara.neutrinoproxy.client;
import cn.hutool.core.util.StrUtil;
import org.noear.solon.Solon;
import org.noear.solon.SolonApp;
import org.noear.solon.annotation.SolonMain;
/**
@@ -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;
@@ -38,5 +38,7 @@ public class ProxyConfig {
private Boolean sslEnable;
private Integer obtainLicenseInterval;
private String licenseKey;
private Integer threadCount;
private String clientId;
}
}
@@ -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,77 @@
package org.dromara.neutrinoproxy.client.core;
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> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception {
if (ProxyMessage.TYPE_HEARTBEAT != proxyMessage.getType()) {
log.info("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.info("读超时");
// ctx.channel().close();
break;
case WRITER_IDLE:
ctx.channel().writeAndFlush(ProxyMessage.buildHeartbeatMessage());
break;
case ALL_IDLE:
log.info("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.info("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.info("读写超时");
ctx.close();
break;
}
}
@@ -1,12 +1,12 @@
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 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 +25,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,8 +41,10 @@ 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;
@@ -53,10 +56,11 @@ public class ProxyClientService {
* 重连次数
*/
private volatile int reconnectCount = 0;
/**
* 启用重连服务
*/
private volatile boolean reconnectServiceEnable = false;
// /**
// * 启用重连服务
// */
// private volatile boolean reconnectServiceEnable = false;
private NioEventLoopGroup workerGroup;
/**
* 重连服务执行器
*/
@@ -64,9 +68,9 @@ public class ProxyClientService {
@Init
public void init() {
this.reconnectExecutor.scheduleWithFixedDelay(this::reconnect, 0, RECONNECT_INTERVAL_SECONDS, TimeUnit.SECONDS);
this.reconnectExecutor.scheduleWithFixedDelay(this::reconnect, 10, RECONNECT_INTERVAL_SECONDS, TimeUnit.SECONDS);
this.workerGroup = new NioEventLoopGroup(proxyConfig.getClient().getThreadCount());
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
realServerBootstrap.group(workerGroup);
realServerBootstrap.channel(NioSocketChannel.class);
realServerBootstrap.handler(new ChannelInitializer<SocketChannel>() {
@@ -77,25 +81,60 @@ public class ProxyClientService {
}
});
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());
}
// ch.pipeline().addFirst(new LoggingHandler(ProxyClientService.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());
}
// ch.pipeline().addFirst(new LoggingHandler(ProxyClientService.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 +149,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 +166,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 +174,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 +183,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 +219,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,40 @@
package org.dromara.neutrinoproxy.client.handler;
import com.alibaba.fastjson.JSONObject;
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;
/**
* 认证信息处理器
* @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);
if (ExceptionEnum.AUTH_FAILED.getCode().equals(code)) {
// 客户端认证失败,直接停止服务
log.info("client auth failed , client stop.");
context.channel().close();
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,14 +9,16 @@ 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
client:
key-store-password: 123456
jks-path: classpath:/test.jks
server-ip: localhost
server-port: 9002
ssl-enable: true
thread-count: 50
key-store-password: ${STORE_PASS:123456}
jks-path: ${JKS_PATH:classpath:/test.jks}
server-ip: ${SERVER_IP:localhost}
server-port: ${SERVER_PORT:9002}
ssl-enable: ${SSL_ENABLE:true}
obtain-license-interval: 5
license-key:
license-key: ${LICENSE_KEY:}
client-id: ${CLIENT_ID:}
@@ -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="./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;
@@ -286,7 +286,7 @@ public class DateUtil {
public static Date getHourBegin(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
setCalender(calendar, calendar.get(Calendar.HOUR), 0, 0, 0);
setCalender(calendar, calendar.get(Calendar.HOUR_OF_DAY), 0, 0, 0);
return calendar.getTime();
}
@@ -298,7 +298,7 @@ public class DateUtil {
public static Date getHourEnd(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
setCalender(calendar, calendar.get(Calendar.HOUR), 59, 59, 999);
setCalender(calendar, calendar.get(Calendar.HOUR_OF_DAY), 59, 59, 999);
return calendar.getTime();
}
@@ -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
+21 -54
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>
@@ -61,53 +57,24 @@
</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>
</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.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,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() {
return new NioEventLoopGroup();
}
@Bean("serverWorkerGroup")
public NioEventLoopGroup serverWorkerGroup() {
return new NioEventLoopGroup();
}
}
@@ -1,77 +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.*;
import fun.asgc.neutrino.proxy.server.controller.res.*;
import fun.asgc.neutrino.proxy.server.service.PortMappingService;
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
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.checkNotEmpty(req.getClientIp(), "clientIp");
ParamCheckUtil.checkNotNull(req.getClientPort(), "clientPort");
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,80 +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.LicenseFlowReportReq;
import fun.asgc.neutrino.proxy.server.controller.req.UserFlowReportReq;
import fun.asgc.neutrino.proxy.server.controller.res.LicenseFlowReportRes;
import fun.asgc.neutrino.proxy.server.controller.res.ReportDataViewRes;
import fun.asgc.neutrino.proxy.server.controller.res.UserFlowReportRes;
import fun.asgc.neutrino.proxy.server.service.ReportService;
import fun.asgc.neutrino.proxy.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;
/**
* 报表管理
* @author: aoshiguchen
* @date: 2022/9/12
*/
@Mapping("/report")
@Controller
public class ReportController {
@Inject
private ReportService reportService;
/**
* 数据一览:
* 在线用户数 查询token有效的用户ID数
* 在线Client数 查询license表,状态为在线的数量
* 今日流量 上行/下行/总计
* 历史流量 上行/下行/总计
*
* 1、点击在线用户数:下方展示在线的用户列表,带分页
* 2、点击在线client数:下方展示在线的license列表,带分页
* 3、点击今日流量:下方展示今日的流量折线图(小时)。 筛选项:全部/用户列表
* 4、点击历史流量:下方展示历史流量折线图。筛选项:日/月 日期选择:日(展示最近30天,最多跨30日),月(展示最近12个月,最多跨12个月)
* @return
*/
@Get
@Mapping("/data-view")
public ReportDataViewRes dataView() {
return new ReportDataViewRes()
.setUserOnlineNumber(2).setEnableUserNumber(3).setUserNumber(5)
.setLicenseNumber(3).setEnableLicenseNumber(6).setLicenseNumber(6)
.setServerPortOnlineNumber(3).setEnableServerPortNumber(5).setServerPortNumber(6)
.setTotalUpstreamFlow("23K").setTotalDownwardFlow("47M")
;
}
/**
* 用户流量报表分页
* @param pageQuery
* @param req
* @return
*/
@Get
@Mapping("/user/flow-report/page")
public PageInfo<UserFlowReportRes> userFlowReportPage(PageQuery pageQuery, UserFlowReportReq req) {
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
return reportService.userFlowReportPage(pageQuery, req);
}
/**
* license流量报表分页
* @param pageQuery
* @param req
* @return
*/
@Get
@Mapping("/license/flow-report/page")
public PageInfo<LicenseFlowReportRes> licenseFlowReportPage(PageQuery pageQuery, LicenseFlowReportReq req) {
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
return reportService.licenseFlowReportPage(pageQuery, req);
}
}
@@ -1,33 +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.controller.req;
import lombok.Data;
/**
* @author: aoshiguchen
* @date: 2022/12/21
*/
@Data
public class LicenseFlowReportReq {
}
@@ -1,34 +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.controller.req;
import lombok.Data;
/**
* 端口映射列表请求
* @author: aoshiguchen
* @date: 2022/8/8
*/
@Data
public class PortMappingListReq {
}
@@ -1,33 +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.controller.req;
import lombok.Data;
/**
* @author: aoshiguchen
* @date: 2022/12/21
*/
@Data
public class UserFlowReportReq {
}
@@ -1,57 +0,0 @@
package fun.asgc.neutrino.proxy.server.controller.res;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* @author: aoshiguchen
* @date: 2022/9/12
*/
@Accessors(chain = true)
@Data
public class ReportDataViewRes {
/**
* 在线用户数
*/
private Integer userOnlineNumber;
/**
* 启用用户数
*/
private Integer enableUserNumber;
/**
* 用户总数
*/
private Integer userNumber;
/**
* 在线license数
*/
private Integer licenseOnlineNumber;
/**
* 启用license数
*/
private Integer enableLicenseNumber;
/**
* license总数
*/
private Integer licenseNumber;
/**
* 服务端口在线数
*/
private Integer serverPortOnlineNumber;
/**
* 启用服务端口数
*/
private Integer enableServerPortNumber;
/**
* 总的服务端口数
*/
private Integer serverPortNumber;
/**
* 累计上行流量
*/
private String totalUpstreamFlow;
/**
* 累计下行流量
*/
private String totalDownwardFlow;
}
@@ -1,78 +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.controller.res;
import lombok.Data;
import java.util.Date;
/**
* @author: aoshiguchen
* @date: 2022/12/21
*/
@Data
public class UserFlowReportRes {
/**
* 用户ID
*/
private Integer userId;
/**
* 用户名称
*/
private String userName;
/**
* 历史写入字节数
*/
private Long historyWriteBytes;
/**
* 历史读取字节数
*/
private Long historyReadBytes;
/**
* 写入字节数
*/
private Long writeBytes;
/**
* 读取字节数
*/
private Long readBytes;
/**
* 写入流量描述
*/
private String writeFlowStr;
/**
* 读取流量描述
*/
private String readFlowStr;
/**
* 流量描述
*/
private String flowStr;
/**
* 报表时间
*/
private Date date;
/**
* 创建时间
*/
private Date createTime;
}
@@ -1,121 +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.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.apache.ibatis.annotations.Mapper;
import java.util.Date;
import java.util.List;
import java.util.Set;
/**
*
* @author: aoshiguchen
* @date: 2022/8/6
*/
@Mapper
public interface LicenseMapper extends BaseMapper<LicenseDO> {
default List<LicenseDO> listAll() {
return this.selectList(new LambdaQueryWrapper<>());
}
default List<LicenseDO> listByUserId(Integer userId) {
return this.selectList(new LambdaQueryWrapper<LicenseDO>()
.eq(LicenseDO::getUserId, userId)
);
}
default void updateEnableStatus(Integer id, Integer enable, Date updateTime) {
this.update(null, new LambdaUpdateWrapper<LicenseDO>()
.eq(LicenseDO::getId, id)
.set(LicenseDO::getEnable, enable)
.set(LicenseDO::getUpdateTime, updateTime)
);
}
default void updateOnlineStatus(Integer id, Integer isOnline, Date updateTime) {
this.update(null, new LambdaUpdateWrapper<LicenseDO>()
.eq(LicenseDO::getId, id)
.set(LicenseDO::getIsOnline, isOnline)
.set(LicenseDO::getUpdateTime, updateTime)
);
}
default void updateOnlineStatus(Integer isOnline, Date updateTime) {
this.update(null, new LambdaUpdateWrapper<LicenseDO>()
.set(LicenseDO::getIsOnline, updateTime)
.set(LicenseDO::getUpdateTime, updateTime)
);
}
default void reset(Integer id, String key, Date updateTime) {
this.update(null, new LambdaUpdateWrapper<LicenseDO>()
.eq(LicenseDO::getId, id)
.set(LicenseDO::getKey, key)
.set(LicenseDO::getUpdateTime, updateTime)
);
}
default LicenseDO findById(Integer id) {
return this.selectById(id);
}
default void update(Integer id, String name, Date updateTime) {
this.update(null, new LambdaUpdateWrapper<LicenseDO>()
.eq(LicenseDO::getId, id)
.set(LicenseDO::getName, name)
.set(LicenseDO::getUpdateTime, updateTime)
);
}
default List<LicenseDO> findByIds(Set<Integer> ids) {
return selectBatchIds(ids);
}
default LicenseDO checkRepeat(Integer userId, String name) {
return this.selectOne(new LambdaQueryWrapper<LicenseDO>()
.eq(LicenseDO::getUserId, userId)
.eq(LicenseDO::getName, name)
.last("limit 1")
);
}
default LicenseDO checkRepeat(Integer userId, String name, Set<Integer> excludeIds) {
return this.selectOne(new LambdaQueryWrapper<LicenseDO>()
.eq(LicenseDO::getUserId, userId)
.eq(LicenseDO::getName, name)
.notIn(LicenseDO::getId, excludeIds)
.last("limit 1")
);
}
default LicenseDO findByKey(String licenseKey) {
return selectOne(new LambdaQueryWrapper<LicenseDO>()
.eq(LicenseDO::getKey, licenseKey)
);
}
}
@@ -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,245 +0,0 @@
package fun.asgc.neutrino.proxy.server.service;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.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.PortMappingCreateReq;
import fun.asgc.neutrino.proxy.server.controller.req.PortMappingListReq;
import fun.asgc.neutrino.proxy.server.controller.req.PortMappingUpdateEnableStatusReq;
import fun.asgc.neutrino.proxy.server.controller.req.PortMappingUpdateReq;
import fun.asgc.neutrino.proxy.server.controller.res.*;
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.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
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;
public PageInfo<PortMappingListRes> page(PageQuery pageQuery, PortMappingListReq req) {
Page<PortMappingListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
List<PortMappingDO> list = portMappingMapper.selectList(new LambdaQueryWrapper<PortMappingDO>()
.orderByAsc(PortMappingDO::getId)
);
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());
});
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,117 +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.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.constant.EnableStatusEnum;
import fun.asgc.neutrino.proxy.server.constant.ExceptionConstant;
import fun.asgc.neutrino.proxy.server.controller.req.PortPoolCreateReq;
import fun.asgc.neutrino.proxy.server.controller.req.PortPoolListReq;
import fun.asgc.neutrino.proxy.server.controller.req.PortPoolUpdateEnableStatusReq;
import fun.asgc.neutrino.proxy.server.controller.res.PortPoolCreateRes;
import fun.asgc.neutrino.proxy.server.controller.res.PortPoolListRes;
import fun.asgc.neutrino.proxy.server.controller.res.PortPoolUpdateEnableStatusRes;
import fun.asgc.neutrino.proxy.server.dal.PortPoolMapper;
import fun.asgc.neutrino.proxy.server.dal.entity.PortPoolDO;
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 java.util.Date;
import java.util.List;
/**
*
* @author: aoshiguchen
* @date: 2022/8/7
*/
@Component
public class PortPoolService {
@Inject
private MapperFacade mapperFacade;
@Db
private PortPoolMapper portPoolMapper;
@Inject
private VisitorChannelService visitorChannelService;
public PageInfo<PortPoolListRes> page(PageQuery pageQuery, PortPoolListReq req) {
Page<PortPoolListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
List<PortPoolDO> list = portPoolMapper.selectList(new LambdaQueryWrapper<PortPoolDO>()
.orderByAsc(PortPoolDO::getId)
);
List<PortPoolListRes> respList = mapperFacade.mapAsList(list, PortPoolListRes.class);
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
}
public List<PortPoolListRes> list(PortPoolListReq req) {
List<PortPoolDO> list = portPoolMapper.selectList(new LambdaQueryWrapper<PortPoolDO>()
.eq(PortPoolDO::getEnable, EnableStatusEnum.ENABLE.getStatus())
);
return mapperFacade.mapAsList(list, PortPoolListRes.class);
}
public PortPoolCreateRes create(PortPoolCreateReq req) {
PortPoolDO oldPortPoolDO = portPoolMapper.findByPort(req.getPort());
ParamCheckUtil.checkMustNull(oldPortPoolDO, ExceptionConstant.PORT_CANNOT_REPEAT);
Date now = new Date();
portPoolMapper.insert(new PortPoolDO()
.setPort(req.getPort())
.setEnable(EnableStatusEnum.ENABLE.getStatus())
.setCreateTime(now)
.setUpdateTime(now)
);
// 更新visitorChannel
visitorChannelService.updateVisitorChannelByPortPool(req.getPort(), EnableStatusEnum.ENABLE.getStatus());
return new PortPoolCreateRes();
}
public PortPoolUpdateEnableStatusRes updateEnableStatus(PortPoolUpdateEnableStatusReq req) {
PortPoolDO portPoolDO = portPoolMapper.findById(req.getId());
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
portPoolMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
// 更新visitorChannel
visitorChannelService.updateVisitorChannelByPortPool(portPoolDO.getPort(), req.getEnable());
return new PortPoolUpdateEnableStatusRes();
}
public void delete(Integer id) {
PortPoolDO portPoolDO = portPoolMapper.findById(id);
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
portPoolMapper.deleteById(id);
// 更新visitorChannel
visitorChannelService.updateVisitorChannelByPortPool(portPoolDO.getPort(), EnableStatusEnum.DISABLE.getStatus());
}
}
@@ -1,41 +0,0 @@
package fun.asgc.neutrino.proxy.server.service;
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.LicenseFlowReportReq;
import fun.asgc.neutrino.proxy.server.controller.req.UserFlowReportReq;
import fun.asgc.neutrino.proxy.server.controller.res.LicenseFlowReportRes;
import fun.asgc.neutrino.proxy.server.controller.res.UserFlowReportRes;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.annotation.Component;
/**
* @author: aoshiguchen
* @date: 2022/12/23
*/
@Slf4j
@Component
public class ReportService {
/**
* 用户流量报表分页
* @param pageQuery
* @param req
* @return
*/
public PageInfo<UserFlowReportRes> userFlowReportPage(PageQuery pageQuery, UserFlowReportReq req) {
// TODO
return null;
}
/**
* license流量报表分页
* @param pageQuery
* @param req
* @return
*/
public PageInfo<LicenseFlowReportRes> licenseFlowReportPage(PageQuery pageQuery, LicenseFlowReportReq req) {
// TODO
return null;
}
}
@@ -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);
}
}
}
@@ -1,6 +1,6 @@
package fun.asgc.neutrino.proxy.server;
package org.dromara.neutrinoproxy.server;
import fun.asgc.solon.extend.job.annotation.EnableJob;
import org.dromara.solonplugins.job.annotation.EnableJob;
import org.noear.solon.Solon;
import org.noear.solon.annotation.SolonMain;
import org.noear.solon.web.cors.CrossFilter;
@@ -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;
@@ -45,7 +45,7 @@ import java.util.List;
@Slf4j
@Component
public class DBInitialize implements EventListener<AppLoadEndEvent> {
private static List<String> initDataTableNameList = Lists.newArrayList("user", "license", "port_pool", "port_mapping", "job_info");
private static List<String> initDataTableNameList = Lists.newArrayList("user", "license", "port_group", "port_pool", "port_mapping", "job_info");
@Inject
private DbConfig dbConfig;
@@ -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;
@@ -32,7 +32,14 @@ public class DbConfiguration {
return dataSource;
} else if (DbTypeEnum.MYSQL == dbTypeEnum) {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setDriverClassName(dbConfig.getDriverClass());
String driver = "com.mysql.cj.jdbc.Driver";
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
// 对类名的判断,异常则说明不存在:
driver = "com.mysql.jdbc.Driver";
}
dataSource.setDriverClassName(driver);
dataSource.setJdbcUrl(dbConfig.getUrl());
dataSource.setMinimumIdle(5);
dataSource.setMaximumPoolSize(20);
@@ -57,13 +64,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,6 +42,19 @@ 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;
}
@Data
public static class Tunnel {
private Integer bossThreadCount;
private Integer workThreadCount;
private Integer port;
private Integer sslPort;
private String keyStorePassword;
@@ -0,0 +1,78 @@
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 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.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) {
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 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;
@@ -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;
@@ -0,0 +1,16 @@
package org.dromara.neutrinoproxy.server.constant;
/**
* @author: aoshiguchen
* @date: 2023/3/18
*/
public interface Constants {
/**
* 默认的端口分组ID
*/
int DEFAULT_PORT_GROUP_ID = 1;
/**
* 首页流量监控展示天数
*/
int HOME_FLOW_DAYS = 15;
}
@@ -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;
@@ -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,12 +53,20 @@ 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, "系统异常"),
PORT_GROUP_NAME_ALREADY_EXIST(16000,"端口分组名称[{}]已经存在"),
PORT_GROUP_NAME_DOES_NOT_EXIST(16001,"端口分组不存在"),
DEFAULT_GROUP_FORBID_DELETE(16002,"默认分组禁止删除")
;
private int code;
@@ -0,0 +1,36 @@
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);
}
}
@@ -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;
@@ -0,0 +1,21 @@
package org.dromara.neutrinoproxy.server.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 端口池分组枚举类型
* @author Yohanes
* @date 2023/03/17
*/
@Getter
@AllArgsConstructor
public enum PoolGroupEnum {
GLOBAL(0, "全局"),
USER(1, "用户"),
LICENSE(2, "License");
private Integer status;
private String desc;
}
@@ -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.ClientConnectRecordListReq;
import fun.asgc.neutrino.proxy.server.controller.res.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.LoginReq;
import fun.asgc.neutrino.proxy.server.controller.res.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.JobInfoExecuteReq;
import fun.asgc.neutrino.proxy.server.controller.req.JobInfoListReq;
import fun.asgc.neutrino.proxy.server.controller.req.JobInfoUpdateEnableStatusReq;
import fun.asgc.neutrino.proxy.server.controller.req.JobInfoUpdateReq;
import fun.asgc.neutrino.proxy.server.controller.res.JobInfoExecuteRes;
import fun.asgc.neutrino.proxy.server.controller.res.JobInfoListRes;
import fun.asgc.neutrino.proxy.server.controller.res.JobInfoUpdateEnableStatusRes;
import fun.asgc.neutrino.proxy.server.controller.res.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.JobLogListReq;
import fun.asgc.neutrino.proxy.server.controller.res.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.*;
import fun.asgc.neutrino.proxy.server.controller.res.*;
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;
@@ -56,6 +58,11 @@ public class LicenseController {
return licenseService.list(req);
}
@Mapping("/auth-list")
public List<LicenseListRes> queryCurUserLicense(LicenseListReq req) {
return licenseService.queryCurUserLicense(req);
}
@Post
@Mapping("/create")
@Authorization(onlyAdmin = true)
@@ -0,0 +1,76 @@
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.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;
/**
* 端口分组控制层
*/
@Mapping("/port-group")
@Controller
public class PortGroupController {
@Inject
private PortGroupService portGroupService;
@Post
@Mapping("/create")
public PortGroupCreateRes create(PortGroupCreateReq req) {
ParamCheckUtil.checkNotNull(req, "req");
ParamCheckUtil.checkNotEmpty(req.getName(), "name");
ParamCheckUtil.checkNotNull(req.getPossessorType(), "possessorType");
ParamCheckUtil.checkNotNull(req.getPossessorId(), "possessorId");
return portGroupService.create(req);
}
@Get
@Mapping("/page")
public PageInfo<PortGroupListRes> page(PageQuery pageQuery, PortGroupListReq req) {
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
return portGroupService.page(pageQuery, req);
}
@Get
@Mapping("/list")
public List<PortGroupListRes> list(PortGroupListReq req) {
return portGroupService.list(req);
}
@Post
@Mapping("/update/enable-status")
public PortGroupUpdateEnableStatusRes updateEnableStatus(PortGroupUpdateEnableStatusReq req) {
ParamCheckUtil.checkNotNull(req, "req");
ParamCheckUtil.checkNotNull(req.getId(), "id");
ParamCheckUtil.checkNotNull(req.getEnable(), "enable");
return portGroupService.updateEnableStatus(req);
}
@Post
@Mapping("/delete")
@Authorization(onlyAdmin = true)
public void delete(PortGroupDeleteReq req) {
ParamCheckUtil.checkNotNull(req, "req");
ParamCheckUtil.checkNotNull(req.getId(), "id");
portGroupService.delete(req.getId());
}
}
@@ -0,0 +1,108 @@
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);
}
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);
}
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,20 +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.PortPoolCreateReq;
import fun.asgc.neutrino.proxy.server.controller.req.PortPoolDeleteReq;
import fun.asgc.neutrino.proxy.server.controller.req.PortPoolListReq;
import fun.asgc.neutrino.proxy.server.controller.req.PortPoolUpdateEnableStatusReq;
import fun.asgc.neutrino.proxy.server.controller.res.PortPoolCreateRes;
import fun.asgc.neutrino.proxy.server.controller.res.PortPoolListRes;
import fun.asgc.neutrino.proxy.server.controller.res.PortPoolUpdateEnableStatusRes;
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;
@@ -56,6 +53,15 @@ public class PortPoolController {
return portPoolService.page(pageQuery, req);
}
@Post
@Mapping("/update")
public PortPoolUpdateRes update(PortPoolUpdateReq req) {
ParamCheckUtil.checkNotNull(req, "req");
ParamCheckUtil.checkNotNull(req.getId(), "id");
ParamCheckUtil.checkNotNull(req.getGroupId(), "groupId");
return portPoolService.update(req);
}
@Get
@Mapping("/list")
public List<PortPoolListRes> list(PortPoolListReq req) {
@@ -67,7 +73,8 @@ 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);
}
@@ -92,4 +99,28 @@ public class PortPoolController {
portPoolService.delete(req.getId());
}
@Get
@Mapping("/get-available-port-list")
public List<PortPoolListRes> getAvailablePortList(AvailablePortListReq req) {
ParamCheckUtil.checkNotNull(req, "req");
ParamCheckUtil.checkNotNull(req.getLicenseId(), "licenseId");
return portPoolService.getAvailablePortList(req);
}
@Get
@Mapping("/get-by-group")
public List<PortPoolListRes> portListByGroupId(String groupId) {
return portPoolService.portListByGroupId(groupId);
}
@Put
@Mapping("/update-group")
public PortPoolUpdateGroupRes updateGroup(PortPoolUpdateGroupReq req) {
ParamCheckUtil.checkNotNull(req, "req");
ParamCheckUtil.checkNotNull(req.getGroupId(), "groupId");
ParamCheckUtil.checkNotEmpty(req.getPortIdList(), "portIdList");
return portPoolService.updateGroup(req);
}
}
@@ -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();
}
}
@@ -0,0 +1,95 @@
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.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;
import org.noear.solon.annotation.Mapping;
/**
* 报表管理
* @author: aoshiguchen
* @date: 2022/9/12
*/
@Mapping("/report")
@Controller
public class ReportController {
@Inject
private ReportService reportService;
/**
* 首页数据一览
* @return
*/
@Get
@Mapping("/home/data-view")
public HomeDataView homeDataView() {
return reportService.homeDataView();
}
/**
* 用户流量报表分页
* @param pageQuery
* @param req
* @return
*/
@Get
@Mapping("/user/flow-report/page")
public PageInfo<UserFlowReportRes> userFlowReportPage(PageQuery pageQuery, UserFlowReportReq req) {
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
return reportService.userFlowReportPage(pageQuery, req);
}
/**
* license流量报表分页
* @param pageQuery
* @param req
* @return
*/
@Get
@Mapping("/license/flow-report/page")
public PageInfo<LicenseFlowReportRes> licenseFlowReportPage(PageQuery pageQuery, LicenseFlowReportReq req) {
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
return reportService.licenseFlowReportPage(pageQuery, req);
}
/**
* 用户流量报表月度明细
* @param pageQuery
* @param req
* @return
*/
@Get
@Mapping("/user/flow-month-report/page")
public PageInfo<UserFlowMonthReportRes> userFlowMonthReportPage(PageQuery pageQuery, UserFlowMonthReportReq req) {
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
return reportService.userFlowMonthReportPage(pageQuery, req);
}
/**
* license流量报表月度明细
* @param pageQuery
* @param req
* @return
*/
@Get
@Mapping("/license/flow-month-report/page")
public PageInfo<LicenseFlowMonthReportRes> licenseFlowMonthReportPage(PageQuery pageQuery, LicenseFlowMonthReportReq req) {
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
return reportService.licenseFlowMonthReportPage(pageQuery, req);
}
}
@@ -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.*;
import fun.asgc.neutrino.proxy.server.controller.res.*;
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.UserLoginRecordListReq;
import fun.asgc.neutrino.proxy.server.controller.res.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;
package org.dromara.neutrinoproxy.server.controller.req.log;
import lombok.Data;
@@ -29,5 +29,8 @@ import lombok.Data;
*/
@Data
public class ClientConnectRecordListReq {
/**
* licenseId
*/
private Integer licenseId;
}
@@ -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;
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;
package org.dromara.neutrinoproxy.server.controller.req.log;
import lombok.Data;
@@ -30,5 +30,8 @@ import lombok.Data;
*/
@Data
public class UserLoginRecordListReq {
/**
* 用户ID
*/
private Integer userId;
}
@@ -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;
package org.dromara.neutrinoproxy.server.controller.req.proxy;
import lombok.Data;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.req;
package org.dromara.neutrinoproxy.server.controller.req.proxy;
import lombok.Data;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.req;
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;
package org.dromara.neutrinoproxy.server.controller.req.proxy;
import lombok.Data;
@@ -31,4 +31,7 @@ import lombok.Data;
@Data
public class LicenseListReq {
private Integer userId;
private Integer isOnline;
private Integer enable;
}
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.req;
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;
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;
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;
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;
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;
package org.dromara.neutrinoproxy.server.controller.req.proxy;
import lombok.Data;
@@ -19,60 +19,49 @@
* 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;
package org.dromara.neutrinoproxy.server.controller.req.proxy;
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
import lombok.Data;
import java.util.Date;
/**
* 端口映射列表请求
* @author: aoshiguchen
* @date: 2022/12/21
* @date: 2022/8/8
*/
@Data
public class LicenseFlowReportRes {
public class PortMappingListReq {
/**
* 用户ID
*/
private Integer userId;
/**
* 用户名称
* 协议
*/
private String userName;
private String protocal;
/**
* licenseId
*/
private Integer licenseId;
/**
* license名称
* 服务端口号
*/
private String licenseName;
private Integer serverPort;
/**
* 写入字节数
* 描述
*/
private Long writeBytes;
private String description;
/**
* 读取字节数
* 是否在线
* {@link OnlineStatusEnum}
*/
private Long readBytes;
private Integer isOnline;
/**
* 写入流量描述
* 启用状态
* {@link EnableStatusEnum}
*/
private String writeFlowStr;
/**
* 读取流量描述
*/
private String readFlowStr;
/**
* 流量描述
*/
private String flowStr;
/**
* 报表时间
*/
private Date date;
/**
* 创建时间
*/
private Date createTime;
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;
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;
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;
}
@@ -0,0 +1,13 @@
package org.dromara.neutrinoproxy.server.controller.req.report;
import lombok.Data;
/**
* @author: aoshiguchen
* @date: 2022/12/21
*/
@Data
public class LicenseFlowMonthReportReq {
private Integer userId;
private Integer licenseId;
}
@@ -0,0 +1,13 @@
package org.dromara.neutrinoproxy.server.controller.req.report;
import lombok.Data;
/**
* @author: aoshiguchen
* @date: 2022/12/21
*/
@Data
public class LicenseFlowReportReq {
private Integer userId;
private Integer licenseId;
}
@@ -0,0 +1,15 @@
package org.dromara.neutrinoproxy.server.controller.req.report;
import lombok.Data;
/**
* @author: aoshiguchen
* @date: 2022/12/21
*/
@Data
public class UserFlowMonthReportReq {
/**
* 用户ID
*/
private Integer userId;
}
@@ -0,0 +1,15 @@
package org.dromara.neutrinoproxy.server.controller.req.report;
import lombok.Data;
/**
* @author: aoshiguchen
* @date: 2022/12/21
*/
@Data
public class UserFlowReportReq {
/**
* 用户ID
*/
private Integer userId;
}
@@ -0,0 +1,15 @@
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
/**
* 获取可用端口请求
*/
@Data
public class AvailablePortListReq {
/**
* licenseId
*/
private Integer licenseId;
}
@@ -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;
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;
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;
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;
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;
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;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
@@ -0,0 +1,27 @@
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
/**
* 端口分组创建请求
*
*
*/
@Data
public class PortGroupCreateReq {
/**
* 分组名称
*/
private String name ;
/**
* 所有者类型 (0、全局共享 1、用户所有 2License所有)
*/
private Integer possessorType ;
/**
* 所有者id(当type为0时 固定为-1、当type为1时为用户id 、当type为2时为licenseid)
*/
private Integer possessorId ;
}
@@ -0,0 +1,11 @@
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
/**
* 删除端口分组请求
*/
@Data
public class PortGroupDeleteReq {
private Integer id;
}
@@ -0,0 +1,27 @@
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
/**
* 端口分组查询请求
*
*
*/
@Data
public class PortGroupListReq {
/**
* 分组名称
*/
private String name ;
/**
* 所有者类型 (0、全局共享 1、用户所有 2License所有)
*/
private Integer possessorType ;
/**
* 所有者id(当type为0时 固定为-1、当type为1时为用户id 、当type为2时为licenseid)
*/
private Integer possessorId ;
}
@@ -0,0 +1,13 @@
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
/**
* 修改端口分组请求
*/
@Data
public class PortGroupUpdateEnableStatusReq {
private Integer id;
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;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
@@ -30,5 +30,7 @@ import lombok.Data;
*/
@Data
public class PortPoolCreateReq {
private Integer port;
private String port;
private Integer groupId;
}
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.req;
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;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
@@ -30,5 +30,8 @@ import lombok.Data;
*/
@Data
public class PortPoolListReq {
/**
* 分组ID
*/
private Integer groupId;
}
@@ -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;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
@@ -0,0 +1,17 @@
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
import java.util.List;
/**
* 批量修改端口分组请求
*/
@Data
public class PortPoolUpdateGroupReq {
private String groupId;
private List<Integer> portIdList;
}
@@ -0,0 +1,14 @@
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
/**
* @author: aoshiguchen
* @date: 2023/3/25
*/
@Data
public class PortPoolUpdateReq {
private Integer id;
private Integer groupId;
}
@@ -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;
package org.dromara.neutrinoproxy.server.controller.req.system;
import lombok.Data;
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.req;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
package org.dromara.neutrinoproxy.server.controller.res.proxy;
import lombok.Data;
@@ -1,6 +1,7 @@
package fun.asgc.neutrino.proxy.server.controller.res;
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;
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;
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;
package org.dromara.neutrinoproxy.server.controller.res.proxy;
import lombok.Data;
@@ -0,0 +1,208 @@
package org.dromara.neutrinoproxy.server.controller.res.report;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
import java.util.List;
/**
* @author: aoshiguchen
* @date: 2023/3/26
*/
@Accessors(chain = true)
@Data
public class HomeDataView {
/**
* licenses数据
*/
private License license;
/**
* 端口映射
*/
private PortMapping portMapping;
/**
* 今日流量
*/
private TodayFlow todayFlow;
/**
* 总流量
*/
private TotalFlow totalFlow;
/**
* 最近7日流量
*/
private Last7dFlow last7dFlow;
@Accessors(chain = true)
@Data
public static class License {
/**
* 总数
*/
private Integer totalCount;
/**
* 在线数
*/
private Integer onlineCount;
/**
* 离线数
*/
private Integer offlineCount;
}
@Accessors(chain = true)
@Data
public static class PortMapping {
/**
* 总数
*/
private Integer totalCount;
/**
* 在线数
*/
private Integer onlineCount;
/**
* 离线数
*/
private Integer offlineCount;
}
@Accessors(chain = true)
@Data
public static class TodayFlow {
/**
* 上行流量字节数
*/
private Long upFlowBytes;
/**
* 下行流量字节数
*/
private Long downFlowBytes;
/**
* 总流量字节数
*/
private Long totalFlowBytes;
/**
* 上行流量描述
*/
private String upFlowDesc;
/**
* 下行流量描述
*/
private String downFlowDesc;
/**
* 总流量描述
*/
private String totalFlowDesc;
}
@Accessors(chain = true)
@Data
public static class TotalFlow {
/**
* 上行流量字节数
*/
private Long upFlowBytes;
/**
* 下行流量字节数
*/
private Long downFlowBytes;
/**
* 总流量字节数
*/
private Long totalFlowBytes;
/**
* 上行流量描述
*/
private String upFlowDesc;
/**
* 下行流量描述
*/
private String downFlowDesc;
/**
* 总流量描述
*/
private String totalFlowDesc;
}
@Accessors(chain = true)
@Data
public static class Last7dFlow {
/**
* 最近7日流量数据
*/
private List<SingleDayFlow> dataList;
/**
* x轴日期
*/
private List<String> xDate;
/**
* 图例(上行流量、下行流量、总流量)
*/
private List<String> legendData;
/**
* 折线列表
*/
private List<Series> seriesList;
}
@Accessors(chain = true)
@Data
public static class SingleDayFlow {
/**
* 日期字符串
*/
private String dateStr;
/**
* 日期
*/
private Date date;
/**
* 上行流量字节数
*/
private Long upFlowBytes;
/**
* 下行流量字节数
*/
private Long downFlowBytes;
/**
* 总流量字节数
*/
private Long totalFlowBytes;
/**
* 上行流量描述
*/
private String upFlowDesc;
/**
* 下行流量描述
*/
private String downFlowDesc;
/**
* 总流量描述
*/
private String totalFlowDesc;
}
@Accessors(chain = true)
@Data
public static class Series {
/**
* 此处目前固定为:line
*/
private String seriesType;
/**
* 名称:上行流量、下行流量、总流量
*/
private String seriesName;
/**
* y值序列
*/
private List<Long> seriesData;
}
}
@@ -0,0 +1,59 @@
package org.dromara.neutrinoproxy.server.controller.res.report;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* @author: aoshiguchen
* @date: 2022/12/21
*/
@Accessors(chain = true)
@Data
public class LicenseFlowMonthReportRes {
/**
* 用户ID
*/
private Integer userId;
/**
* 用户名称
*/
private String userName;
/**
* licenseId
*/
private Integer licenseId;
/**
* license名称
*/
private String licenseName;
/**
* 上行流量字节数
*/
private Long upFlowBytes;
/**
* 下行流量字节数
*/
private Long downFlowBytes;
/**
* 总流量字节数
*/
private Long totalFlowBytes;
/**
* 上行流量描述
*/
private String upFlowDesc;
/**
* 下行流量描述
*/
private String downFlowDesc;
/**
* 总流量描述
*/
private String totalFlowDesc;
/**
* 日期
*/
private Date date;
}
@@ -0,0 +1,55 @@
package org.dromara.neutrinoproxy.server.controller.res.report;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* @author: aoshiguchen
* @date: 2022/12/21
*/
@Accessors(chain = true)
@Data
public class LicenseFlowReportRes {
/**
* 用户ID
*/
private Integer userId;
/**
* 用户名称
*/
private String userName;
/**
* licenseId
*/
private Integer licenseId;
/**
* license名称
*/
private String licenseName;
/**
* 上行流量字节数
*/
private Long upFlowBytes;
/**
* 下行流量字节数
*/
private Long downFlowBytes;
/**
* 总流量字节数
*/
private Long totalFlowBytes;
/**
* 上行流量描述
*/
private String upFlowDesc;
/**
* 下行流量描述
*/
private String downFlowDesc;
/**
* 总流量描述
*/
private String totalFlowDesc;
}
@@ -0,0 +1,51 @@
package org.dromara.neutrinoproxy.server.controller.res.report;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* @author: aoshiguchen
* @date: 2022/12/21
*/
@Accessors(chain = true)
@Data
public class UserFlowMonthReportRes {
/**
* 用户ID
*/
private Integer userId;
/**
* 用户名称
*/
private String userName;
/**
* 上行流量字节数
*/
private Long upFlowBytes;
/**
* 下行流量字节数
*/
private Long downFlowBytes;
/**
* 总流量字节数
*/
private Long totalFlowBytes;
/**
* 上行流量描述
*/
private String upFlowDesc;
/**
* 下行流量描述
*/
private String downFlowDesc;
/**
* 总流量描述
*/
private String totalFlowDesc;
/**
* 日期
*/
private Date date;
}
@@ -0,0 +1,47 @@
package org.dromara.neutrinoproxy.server.controller.res.report;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* @author: aoshiguchen
* @date: 2022/12/21
*/
@Accessors(chain = true)
@Data
public class UserFlowReportRes {
/**
* 用户ID
*/
private Integer userId;
/**
* 用户名称
*/
private String userName;
/**
* 上行流量字节数
*/
private Long upFlowBytes;
/**
* 下行流量字节数
*/
private Long downFlowBytes;
/**
* 总流量字节数
*/
private Long totalFlowBytes;
/**
* 上行流量描述
*/
private String upFlowDesc;
/**
* 下行流量描述
*/
private String downFlowDesc;
/**
* 总流量描述
*/
private String totalFlowDesc;
}
@@ -0,0 +1,7 @@
package org.dromara.neutrinoproxy.server.controller.res.system;
import lombok.Data;
@Data
public class AvailablePortListRes {
}
@@ -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;
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;
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;
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;
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;
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;
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 {
}
@@ -0,0 +1,50 @@
package org.dromara.neutrinoproxy.server.controller.res.system;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.util.Date;
public class PortGroupListRes {
/**
* 主键
*/
@TableId(type = IdType.AUTO)
private Integer id;
/**
* 分组名称
*/
private String name;
/**
* 所有者类型 (0、全局共享 1、用户所有 2License所有)
*/
private Integer possessorType;
/**
* 所有者id(当type为0时 固定为-1、当type为1时为用户id 、当type为2时为licenseid)
*/
private Integer possessorId;
/**
* 是否启用(1、启用 2、禁用)
*/
private Integer enable;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
/**
* 来源
*/
private String possessor;
}
@@ -0,0 +1,7 @@
package org.dromara.neutrinoproxy.server.controller.res.system;
import lombok.Data;
@Data
public class PortGroupUpdateEnableStatusRes {
}
@@ -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;
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;
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;
@@ -49,4 +51,18 @@ public class PortPoolListRes {
* 更新时间
*/
private Date updateTime;
/**
* 分组类型
*/
private Integer possessorType;
/**
* 分组
*/
private String groupName;
/**
* 分组ID
*/
private Integer groupId;
}
@@ -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;
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 {
}
@@ -0,0 +1,11 @@
package org.dromara.neutrinoproxy.server.controller.res.system;
import lombok.Data;
/**
* @author: aoshiguchen
* @date: 2023/3/25
*/
@Data
public class PortPoolUpdateRes {
}
@@ -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;
package org.dromara.neutrinoproxy.server.controller.res.system;
/**
*
@@ -1,4 +1,4 @@
package fun.asgc.neutrino.proxy.server.controller.res;
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;
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;
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;
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;
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;
@@ -33,7 +33,7 @@ import java.util.List;
public interface FlowReportDayMapper extends BaseMapper<FlowReportDayDO> {
default void clean(Date date) {
this.delete(new LambdaQueryWrapper<FlowReportDayDO>()
.lt(FlowReportDayDO::getCreateTime, date)
.lt(FlowReportDayDO::getDate, date)
);
}
@@ -45,8 +45,8 @@ public interface FlowReportDayMapper extends BaseMapper<FlowReportDayDO> {
default List<FlowReportDayDO> findListByDateRange(Date startDate, Date endDate) {
return this.selectList(new LambdaQueryWrapper<FlowReportDayDO>()
.le(FlowReportDayDO::getDate, startDate)
.gt(FlowReportDayDO::getDate, endDate)
.ge(FlowReportDayDO::getDate, startDate)
.le(FlowReportDayDO::getDate, endDate)
);
}
}
@@ -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;
@@ -33,7 +33,7 @@ import java.util.List;
public interface FlowReportHourMapper extends BaseMapper<FlowReportHourDO> {
default void clean(Date date) {
this.delete(new LambdaQueryWrapper<FlowReportHourDO>()
.lt(FlowReportHourDO::getCreateTime, date)
.lt(FlowReportHourDO::getDate, 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;
@@ -34,7 +34,7 @@ import java.util.Set;
public interface FlowReportMinuteMapper extends BaseMapper<FlowReportMinuteDO> {
default void clean(Date date) {
this.delete(new LambdaQueryWrapper<FlowReportMinuteDO>()
.lt(FlowReportMinuteDO::getCreateTime, date)
.lt(FlowReportMinuteDO::getDate, 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;
@@ -0,0 +1,126 @@
/**
* Copyright (c) 2022 aoshiguchen
* <p>
* 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:
* <p>
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* <p>
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.dromara.neutrinoproxy.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 org.dromara.neutrinoproxy.server.dal.entity.LicenseDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Date;
import java.util.List;
import java.util.Set;
/**
*
* @author: aoshiguchen
* @date: 2022/8/6
*/
@Mapper
public interface LicenseMapper extends BaseMapper<LicenseDO> {
default List<LicenseDO> listAll() {
return this.selectList(new LambdaQueryWrapper<>());
}
default List<LicenseDO> listByUserId(Integer userId) {
return this.selectList(new LambdaQueryWrapper<LicenseDO>()
.eq(LicenseDO::getUserId, userId)
);
}
default LicenseDO queryById(Integer licenseId) {
return this.selectOne(new LambdaQueryWrapper<LicenseDO>()
.eq(LicenseDO::getId, licenseId));
}
default void updateEnableStatus(Integer id, Integer enable, Date updateTime) {
this.update(null, new LambdaUpdateWrapper<LicenseDO>()
.eq(LicenseDO::getId, id)
.set(LicenseDO::getEnable, enable)
.set(LicenseDO::getUpdateTime, updateTime)
);
}
default void updateOnlineStatus(Integer id, Integer isOnline, Date updateTime) {
this.update(null, new LambdaUpdateWrapper<LicenseDO>()
.eq(LicenseDO::getId, id)
.set(LicenseDO::getIsOnline, isOnline)
.set(LicenseDO::getUpdateTime, updateTime)
);
}
default void updateOnlineStatus(Integer isOnline, Date updateTime) {
this.update(null, new LambdaUpdateWrapper<LicenseDO>()
.set(LicenseDO::getIsOnline, isOnline)
.set(LicenseDO::getUpdateTime, updateTime)
);
}
default void reset(Integer id, String key, Date updateTime) {
this.update(null, new LambdaUpdateWrapper<LicenseDO>()
.eq(LicenseDO::getId, id)
.set(LicenseDO::getKey, key)
.set(LicenseDO::getUpdateTime, updateTime)
);
}
default LicenseDO findById(Integer id) {
return this.selectById(id);
}
default void update(Integer id, String name, Date updateTime) {
this.update(null, new LambdaUpdateWrapper<LicenseDO>()
.eq(LicenseDO::getId, id)
.set(LicenseDO::getName, name)
.set(LicenseDO::getUpdateTime, updateTime)
);
}
default List<LicenseDO> findByIds(Set<Integer> ids) {
return selectBatchIds(ids);
}
default LicenseDO checkRepeat(Integer userId, String name) {
return this.selectOne(new LambdaQueryWrapper<LicenseDO>()
.eq(LicenseDO::getUserId, userId)
.eq(LicenseDO::getName, name)
.last("limit 1")
);
}
default LicenseDO checkRepeat(Integer userId, String name, Set<Integer> excludeIds) {
return this.selectOne(new LambdaQueryWrapper<LicenseDO>()
.eq(LicenseDO::getUserId, userId)
.eq(LicenseDO::getName, name)
.notIn(LicenseDO::getId, excludeIds)
.last("limit 1")
);
}
default LicenseDO findByKey(String licenseKey) {
return selectOne(new LambdaQueryWrapper<LicenseDO>()
.eq(LicenseDO::getKey, licenseKey)
);
}
}
@@ -0,0 +1,27 @@
package org.dromara.neutrinoproxy.server.dal;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.dromara.neutrinoproxy.server.controller.req.system.PortGroupListReq;
import org.dromara.neutrinoproxy.server.controller.res.system.PortGroupListRes;
import org.dromara.neutrinoproxy.server.dal.entity.PortGroupDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Date;
import java.util.List;
@Mapper
public interface PortGroupMapper extends BaseMapper<PortGroupDO> {
List<PortGroupListRes> selectPortGroupListResList(PortGroupListReq res);
default void updateEnableStatus(Integer id, Integer enable, Date now){
this.update(null, Wrappers.lambdaUpdate(PortGroupDO.class)
.eq(PortGroupDO::getId,id)
.set(PortGroupDO::getEnable,enable)
.set(PortGroupDO::getUpdateTime,now)
);
}
}
@@ -1,12 +1,15 @@
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.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;
import java.util.Date;
import java.util.List;
@@ -40,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)
@@ -82,4 +97,6 @@ public interface PortMappingMapper extends BaseMapper<PortMappingDO> {
.set(PortMappingDO::getUpdateTime, updateTime)
);
}
List<PortMappingDO> selectPortMappingByCondition(@Param("req") PortMappingListReq req);
}
@@ -19,15 +19,19 @@
* 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.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;
import java.util.Date;
import java.util.List;
/**
*
@@ -53,4 +57,15 @@ public interface PortPoolMapper extends BaseMapper<PortPoolDO> {
default PortPoolDO findById(Integer id) {
return this.selectById(id);
}
default List<PortPoolDO> getByGroupId(String groupId){
return this.selectList(
new LambdaQueryWrapper<PortPoolDO>()
.eq(PortPoolDO::getGroupId, groupId)
);
}
List<PortPoolListRes> selectResList(@Param("req") PortPoolListReq req);
List<PortPoolListRes> getAvailablePortList(@Param("licenseId") Integer licenseId,@Param("userId") Integer userId);
}
@@ -0,0 +1,73 @@
package org.dromara.neutrinoproxy.server.dal;
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;
import java.util.Date;
import java.util.List;
/**
* @author: aoshiguchen
* @date: 2023/3/19
*/
@Mapper
public interface ReportMapper {
/**
* 基于用户维度的流量报表
* @param userId
* @return
*/
List<UserFlowReportRes> userFlowReportList(@Param("userId") Integer userId, @Param("curMonthBeginDate") Date curMonthBeginDate, @Param("curDayBeginDate") Date curDayBeginDate, @Param("curDate") Date curDate);
/**
* 基于License维度的流量报表
* @param userId
* @return
*/
List<LicenseFlowReportRes> licenseFLowReportList(@Param("userId") Integer userId, @Param("curMonthBeginDate") Date curMonthBeginDate, @Param("curDayBeginDate") Date curDayBeginDate, @Param("curDate") Date curDate);
/**
* 用户流量月度明细
* @param userId
* @return
*/
List<UserFlowMonthReportRes> userFlowMonthReportList(@Param("userId") Integer userId, @Param("curMonthBeginDate") Date curMonthBeginDate, @Param("curDayBeginDate") Date curDayBeginDate, @Param("curDate") Date curDate);
/**
* License流量月度明细
* @param userId
* @return
*/
List<LicenseFlowMonthReportRes> licenseFLowMonthReportList(@Param("userId") Integer userId, @Param("licenseId") Integer licenseId, @Param("curMonthBeginDate") Date curMonthBeginDate, @Param("curDayBeginDate") Date curDayBeginDate, @Param("curDate") Date curDate);
/**
* 首页 - 今日流量
* @param curDayBeginDate
* @param curDate
* @return
*/
FlowBO homeTodayFlow(@Param("curDayBeginDate") Date curDayBeginDate, @Param("curDate") Date curDate);
/**
* 首页 - 总流量
* @param curMonthBeginDate
* @param curDayBeginDate
* @param curDate
* @return
*/
FlowBO homeTotalFlow(@Param("curMonthBeginDate") Date curMonthBeginDate, @Param("curDayBeginDate") Date curDayBeginDate, @Param("curDate") Date curDate);
/**
* 首页最近7日流量
* @param beginDate
* @param curDayBeginDate
* @param curDate
* @return
*/
List<SingleDayFlowBO> homeLast7dFlowList(@Param("beginDate") Date beginDate, @Param("curDayBeginDate") Date curDayBeginDate, @Param("curDate") Date curDate);
}
@@ -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;
/**
@@ -0,0 +1,57 @@
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 lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* 端口组
*/
@ToString
@Accessors(chain = true)
@Data
@TableName("port_group")
public class PortGroupDO {
/**
* 主键
*/
@TableId(type = IdType.AUTO)
private Integer id;
/**
* 分组名称
*/
private String name;
/**
* 所有者类型 (0、全局共享 1、用户所有 2License所有)
*/
private Integer possessorType;
/**
* 所有者id(当type为0时 固定为-1、当type为1时为用户id 、当type为2时为licenseid)
*/
private Integer possessorId;
/**
* 是否启用(1、启用 2、禁用)
*/
private Integer enable;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
}
@@ -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;
@@ -42,6 +42,12 @@ import java.util.Date;
public class PortPoolDO {
@TableId(type = IdType.AUTO)
private Integer id;
/**
* 分组id
*/
private Integer groupId;
/**
* 端口
*/
@@ -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;
/**
@@ -19,20 +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;
@@ -60,7 +61,7 @@ public class FlowReportForDayJob implements IJobHandler {
@Override
public void execute(String param) throws Exception {
Date now = new Date();
String dateStr = DateUtil.format(DateUtil.addDate(now, Calendar.DATE, -1), "yyyy-MM-dd");
String dateStr = getDateStr(now, param); // DateUtil.format(DateUtil.addDate(now, Calendar.DATE, -1), "yyyy-MM-dd");
Date date = DateUtil.parse(dateStr, "yyyy-MM-dd");
Date startHourDate = DateUtil.getDayBegin(date);
Date endHourDate = DateUtil.getDayEnd(date);
@@ -99,4 +100,16 @@ public class FlowReportForDayJob implements IJobHandler {
}
}
private String getDateStr(Date now, String params) {
if (StringUtils.isNotBlank(params)) {
try {
// 参数格式错误则取当前时间
DateUtil.parse(params, "yyyy-MM-dd");
return params;
} catch (Exception e) {
// ignore
}
}
return DateUtil.format(DateUtil.addDate(now, Calendar.DATE, -1), "yyyy-MM-dd");
}
}
@@ -1,16 +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;
@@ -36,7 +37,7 @@ public class FlowReportForHourJob implements IJobHandler {
@Override
public void execute(String param) throws Exception {
Date now = new Date();
String dateStr = DateUtil.format(DateUtil.addDate(now, Calendar.HOUR, -1), "yyyy-MM-dd HH");
String dateStr = getDateStr(now, param); // DateUtil.format(DateUtil.addDate(now, Calendar.HOUR, -1), "yyyy-MM-dd HH");
Date date = DateUtil.parse(dateStr, "yyyy-MM-dd HH");
Date startHourDate = DateUtil.getHourBegin(date);
Date endHourDate = DateUtil.getHourEnd(date);
@@ -75,4 +76,17 @@ public class FlowReportForHourJob implements IJobHandler {
}
}
private String getDateStr(Date now, String params) {
if (StringUtils.isNotBlank(params)) {
try {
// 参数格式错误则取当前时间
DateUtil.parse(params, "yyyy-MM-dd HH");
return params;
} catch (Exception e) {
// ignore
}
}
return DateUtil.format(DateUtil.addDate(now, Calendar.HOUR, -1), "yyyy-MM-dd HH");
}
}
@@ -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,14 +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;
@@ -38,7 +39,7 @@ public class FlowReportForMonthJob implements IJobHandler {
@Override
public void execute(String param) throws Exception {
Date now = new Date();
String dateStr = DateUtil.format(DateUtil.addDate(now, Calendar.MONTH, -1), "yyyy-MM");
String dateStr = getDateStr(now, param); // DateUtil.format(DateUtil.addDate(now, Calendar.MONTH, -1), "yyyy-MM");
Date date = DateUtil.parse(dateStr, "yyyy-MM");
Date startDayDate = DateUtil.getMonthBegin(date);
Date endEndDate = DateUtil.getMonthEnd(date);
@@ -77,4 +78,16 @@ public class FlowReportForMonthJob implements IJobHandler {
}
}
private String getDateStr(Date now, String params) {
if (StringUtils.isNotBlank(params)) {
try {
// 参数格式错误则取当前时间
DateUtil.parse(params, "yyyy-MM");
return params;
} catch (Exception e) {
// ignore
}
}
return DateUtil.format(DateUtil.addDate(now, Calendar.MONTH, -1), "yyyy-MM");
}
}
@@ -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,18 @@
* 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.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,10 +47,10 @@ 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;
public ServerChannelHandler() {
public ProxyTunnelChannelHandler() {
dispatcher = Solon.context().getBean(Dispatcher.class);
}
@@ -71,33 +71,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 +115,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
@@ -119,7 +131,6 @@ public class ServerChannelHandler extends SimpleChannelInboundHandler<ProxyMessa
ctx.channel().close();
break;
case WRITER_IDLE:
log.info("写超时");
break;
case ALL_IDLE:
break;
@@ -1,9 +1,9 @@
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 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 +26,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 +57,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 +78,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 +87,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 +111,12 @@ public class ProxyServerRunner implements EventListener<AppLoadEndEvent> {
}
private void proxyServerCommonInitHandler(SocketChannel ch) {
// ch.pipeline().addFirst(new LoggingHandler(ProxyServerRunner.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,53 @@
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 lombok.extern.slf4j.Slf4j;
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
import org.dromara.neutrinoproxy.server.proxy.core.BytesMetricsHandler;
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 {
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代理服务启动成功!");
} catch (Exception e) {
log.error("http proxy start err!", e);
}
}
}
@@ -0,0 +1,170 @@
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);
if (StringUtils.isBlank(host)) {
ctx.channel().close();
return;
}
log.debug("HttpProxy host: {}", host);
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;
}
return null;
}
}
@@ -0,0 +1,87 @@
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.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.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 {
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代理服务启动成功!");
} 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.ClientConnectRecordListReq;
import fun.asgc.neutrino.proxy.server.controller.res.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;
@@ -51,6 +51,7 @@ public class ClientConnectRecordService {
public PageInfo<ClientConnectRecordListRes> page(PageQuery pageQuery, ClientConnectRecordListReq req) {
Page<ClientConnectRecordListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
List<ClientConnectRecordDO> list = clientConnectRecordMapper.selectList(new LambdaQueryWrapper<ClientConnectRecordDO>()
.eq(null != req.getLicenseId(), ClientConnectRecordDO::getLicenseId, req.getLicenseId())
.orderByDesc(ClientConnectRecordDO::getId)
);
List<ClientConnectRecordListRes> respList = mapperFacade.mapAsList(list, ClientConnectRecordListRes.class);
@@ -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.JobInfoExecuteReq;
import fun.asgc.neutrino.proxy.server.controller.req.JobInfoListReq;
import fun.asgc.neutrino.proxy.server.controller.req.JobInfoUpdateEnableStatusReq;
import fun.asgc.neutrino.proxy.server.controller.req.JobInfoUpdateReq;
import fun.asgc.neutrino.proxy.server.controller.res.JobInfoExecuteRes;
import fun.asgc.neutrino.proxy.server.controller.res.JobInfoListRes;
import fun.asgc.neutrino.proxy.server.controller.res.JobInfoUpdateEnableStatusRes;
import fun.asgc.neutrino.proxy.server.controller.res.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.JobLogListReq;
import fun.asgc.neutrino.proxy.server.controller.res.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;
@@ -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,27 +6,32 @@ 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.LicenseCreateReq;
import fun.asgc.neutrino.proxy.server.controller.req.LicenseListReq;
import fun.asgc.neutrino.proxy.server.controller.req.LicenseUpdateEnableStatusReq;
import fun.asgc.neutrino.proxy.server.controller.req.LicenseUpdateReq;
import fun.asgc.neutrino.proxy.server.controller.res.*;
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.dromara.neutrinoproxy.server.controller.res.proxy.*;
import org.jetbrains.annotations.Nullable;
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;
@@ -38,7 +43,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
@@ -47,11 +52,16 @@ 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());
List<LicenseDO> list = licenseMapper.selectList(new LambdaQueryWrapper<LicenseDO>()
.orderByAsc(LicenseDO::getId)
.eq(req.getUserId() != null, LicenseDO::getUserId, req.getUserId())
.eq(req.getIsOnline() != null, LicenseDO::getIsOnline, req.getIsOnline())
.eq(req.getEnable() != null, LicenseDO::getEnable, req.getEnable())
.orderByAsc(Arrays.asList(LicenseDO::getUserId, LicenseDO::getId))
);
List<LicenseListRes> respList = mapperFacade.mapAsList(list, LicenseListRes.class);
if (CollectionUtils.isEmpty(list)) {
@@ -74,8 +84,14 @@ public class LicenseService implements Lifecycle {
public List<LicenseListRes> list(LicenseListReq req) {
List<LicenseDO> list = licenseMapper.selectList(new LambdaQueryWrapper<LicenseDO>()
.eq(LicenseDO::getEnable, EnableStatusEnum.ENABLE.getStatus())
.eq(null != req.getEnable(), LicenseDO::getEnable, req.getEnable())
);
List<LicenseListRes> licenseList = assembleConvertLicenses(list);
return licenseList;
}
@Nullable
private List<LicenseListRes> assembleConvertLicenses(List<LicenseDO> list) {
List<LicenseListRes> licenseList = mapperFacade.mapAsList(list, LicenseListRes.class);
if (!CollectionUtil.isEmpty(licenseList)) {
Set<Integer> userIds = licenseList.stream().map(LicenseListRes::getUserId).collect(Collectors.toSet());
@@ -205,9 +221,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());
}
/**
@@ -217,4 +239,20 @@ public class LicenseService implements Lifecycle {
public void stop() throws Throwable {
licenseMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
}
/**
* 查询当前角色下的license若为管理员 则返回全部license
*/
public List<LicenseListRes> queryCurUserLicense(LicenseListReq req) {
if(SystemContextHolder.isAdmin()){
return this.list(req);
}
List<LicenseDO> list = licenseMapper.selectList(new LambdaQueryWrapper<LicenseDO>()
.eq(LicenseDO::getEnable, EnableStatusEnum.ENABLE.getStatus())
.eq(LicenseDO::getUserId,SystemContextHolder.getUserId())
);
List<LicenseListRes> licenseList = assembleConvertLicenses(list);
return licenseList;
}
}
@@ -0,0 +1,105 @@
package org.dromara.neutrinoproxy.server.service;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
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;
import org.noear.solon.annotation.Inject;
import java.util.Date;
import java.util.List;
import java.util.Objects;
/**
* 端口分组服务
*/
@Component
public class PortGroupService {
@Inject
private MapperFacade mapperFacade;
@Db
private PortGroupMapper portGroupMapper;
@Db
private PortPoolMapper portPoolMapper;
public PortGroupCreateRes create(PortGroupCreateReq req) {
PortGroupDO portGroupDO = portGroupMapper.selectOne(Wrappers.lambdaQuery(PortGroupDO.class)
.eq(PortGroupDO::getName, req.getName()));
if (Objects.nonNull(portGroupDO)) {
throw ServiceException.create(ExceptionConstant.PORT_GROUP_NAME_ALREADY_EXIST, req.getName());
}
Date now = new Date();
portGroupDO = new PortGroupDO();
portGroupDO.setName(req.getName());
portGroupDO.setPossessorType(req.getPossessorType());
portGroupDO.setPossessorId(req.getPossessorId());
portGroupDO.setEnable(EnableStatusEnum.ENABLE.getStatus());
portGroupDO.setCreateTime(now);
portGroupDO.setUpdateTime(now);
portGroupMapper.insert(portGroupDO);
return new PortGroupCreateRes();
}
public PageInfo<PortGroupListRes> page(PageQuery pageQuery, PortGroupListReq req) {
Page<PortGroupListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
List<PortGroupListRes> list = portGroupMapper.selectPortGroupListResList(req);
return PageInfo.of(list, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
}
public List<PortGroupListRes> list(PortGroupListReq req) {
List<PortGroupListRes> list = portGroupMapper.selectPortGroupListResList(req);
return list;
}
public PortGroupUpdateEnableStatusRes updateEnableStatus(PortGroupUpdateEnableStatusReq req) {
PortGroupDO portGroupDO = portGroupMapper.selectById(req.getId());
ParamCheckUtil.checkNotNull(portGroupDO, ExceptionConstant.PORT_GROUP_NAME_DOES_NOT_EXIST);
if (!SystemContextHolder.isAdmin()) {
ParamCheckUtil.checkExpression(false, ExceptionConstant.NO_PERMISSION_VISIT);
}
portGroupMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
return new PortGroupUpdateEnableStatusRes();
}
public void delete(Integer id) {
if (id == Constants.DEFAULT_PORT_GROUP_ID) {
throw ServiceException.create(ExceptionConstant.DEFAULT_GROUP_FORBID_DELETE);
}
PortGroupDO portGroupDO = portGroupMapper.selectById(id);
//检验分组是否存在
ParamCheckUtil.checkNotNull(portGroupDO, ExceptionConstant.PORT_GROUP_NAME_DOES_NOT_EXIST);
//删除
portGroupMapper.deleteById(id);
//修改绑定此分组的端口到默认分组
portPoolMapper.update(null, Wrappers.lambdaUpdate(PortPoolDO.class)
.eq(PortPoolDO::getGroupId, portGroupDO.getId())
.set(PortPoolDO::getGroupId, Constants.DEFAULT_PORT_GROUP_ID)
.set(PortPoolDO::getUpdateTime, new Date())
);
}
}
@@ -0,0 +1,325 @@
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 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 ma.glasnost.orika.MapperFacade;
import org.apache.ibatis.solon.annotation.Db;
import org.dromara.neutrinoproxy.server.controller.res.proxy.*;
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.Lifecycle;
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() + "%");
}
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.HTTP.getDesc().equals(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.HTTP.getDesc().equals(oldPortMappingDO.getProtocal()) &&
StrUtil.isNotBlank(oldPortMappingDO.getSubdomain())) {
ProxyUtil.removeSubdomainToServerPort(oldPortMappingDO.getSubdomain());
}
// 更新域名映射
if (NetworkProtocolEnum.HTTP.getDesc().equals(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.HTTP.getDesc().equals(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());
}
}
@@ -0,0 +1,182 @@
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 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
private MapperFacade mapperFacade;
@Db
private PortPoolMapper portPoolMapper;
@Inject
private VisitorChannelService visitorChannelService;
@Db
private PortGroupMapper portGroupMapper;
@Db
private PortMappingMapper portMappingMapper;
@Db
private LicenseMapper licenseMapper;
public PageInfo<PortPoolListRes> page(PageQuery pageQuery, PortPoolListReq req) {
Page<PortPoolListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
List<PortPoolListRes> list = portPoolMapper.selectResList(req);
return PageInfo.of(list, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
}
public List<PortPoolListRes> list(PortPoolListReq req) {
List<PortPoolDO> list = portPoolMapper.selectList(new LambdaQueryWrapper<PortPoolDO>()
.eq(PortPoolDO::getEnable, EnableStatusEnum.ENABLE.getStatus())
);
return mapperFacade.mapAsList(this.filterUsedPorts(list), PortPoolListRes.class);
}
private List<PortPoolDO> filterUsedPorts(List<PortPoolDO> list) {
//Gets the used ports
List<PortMappingDO> usePorts = portMappingMapper.selectList(new LambdaQueryWrapper<PortMappingDO>().orderByAsc(PortMappingDO::getId));
List<Integer> serverPorts = usePorts.stream().map(item -> item.getServerPort()).collect(Collectors.toList());
return list.stream().filter(item -> !serverPorts.contains(item.getPort())).collect(Collectors.toList());
}
public PortPoolUpdateRes update(PortPoolUpdateReq req) {
portPoolMapper.update(null, new LambdaUpdateWrapper<PortPoolDO>()
.eq(PortPoolDO::getId, req.getId())
.set(PortPoolDO::getGroupId, req.getGroupId())
.set(PortPoolDO::getUpdateTime, new Date())
);
return new PortPoolUpdateRes();
}
public PortPoolCreateRes create(PortPoolCreateReq req) {
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();
}
public PortPoolUpdateEnableStatusRes updateEnableStatus(PortPoolUpdateEnableStatusReq req) {
PortPoolDO portPoolDO = portPoolMapper.findById(req.getId());
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
portPoolMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
// 更新visitorChannel
visitorChannelService.updateVisitorChannelByPortPool(portPoolDO.getPort(), req.getEnable());
return new PortPoolUpdateEnableStatusRes();
}
public void delete(Integer id) {
PortPoolDO portPoolDO = portPoolMapper.findById(id);
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
portPoolMapper.deleteById(id);
// 更新visitorChannel
visitorChannelService.updateVisitorChannelByPortPool(portPoolDO.getPort(), EnableStatusEnum.DISABLE.getStatus());
}
public List<PortPoolListRes> portListByGroupId(String groupId) {
List<PortPoolDO> portPoolDOList = portPoolMapper.getByGroupId(groupId);
List<PortPoolListRes> portPoolListReList = mapperFacade.mapAsList(portPoolDOList, PortPoolListRes.class);
return portPoolListReList;
}
public PortPoolUpdateGroupRes updateGroup(PortPoolUpdateGroupReq req) {
PortGroupDO portGroupDO = portGroupMapper.selectById(req.getGroupId());
if (Objects.isNull(portGroupDO)) {
throw ServiceException.create(ExceptionConstant.PARAMS_INVALID);
}
portPoolMapper.update(null, Wrappers.lambdaUpdate(PortPoolDO.class)
.in(PortPoolDO::getId, req.getPortIdList())
.set(PortPoolDO::getGroupId, req.getGroupId())
.set(PortPoolDO::getUpdateTime, new Date())
);
return new PortPoolUpdateGroupRes();
}
/**
* 管理员: 全局端口 + 当前选择的用户独占端口 + 当前选择license独占端口
* 游客:全局端口 + 当前选择用户独占端口 + 当前选择license独占端口
* 非管理员身份时:下拉选择license,只能选当前用户下的LICENSE
*/
public List<PortPoolListRes> getAvailablePortList(AvailablePortListReq req) {
LicenseDO licenseDO = licenseMapper.queryById(req.getLicenseId());
return portPoolMapper.getAvailablePortList(req.getLicenseId(), licenseDO.getUserId());
}
}
@@ -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;
@@ -0,0 +1,353 @@
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 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;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author: aoshiguchen
* @date: 2022/12/23
*/
@Slf4j
@Component
public class ReportService {
@Inject
private MapperFacade mapperFacade;
@Db
private ReportMapper reportMapper;
@Db
private LicenseMapper licenseMapper;
@Db
private PortMappingMapper portMappingMapper;
@Inject
private DbConfig dbConfig;
/**
* 首页图表
* @return
*/
public HomeDataView homeDataView() {
HomeDataView homeDataView = new HomeDataView();
homeDataView.setLicense(new HomeDataView.License().setTotalCount(0).setOnlineCount(0));
homeDataView.setPortMapping(new HomeDataView.PortMapping().setTotalCount(0).setOnlineCount(0));
homeDataView.setTodayFlow(new HomeDataView.TodayFlow().setUpFlowBytes(0L).setDownFlowBytes(0L));
homeDataView.setTotalFlow(new HomeDataView.TotalFlow().setUpFlowBytes(0L).setDownFlowBytes(0L));
// 查询license列表
List<LicenseDO> licenseDOList = licenseMapper.listAll();
if (CollectionUtil.isNotEmpty(licenseDOList)) {
homeDataView.getLicense().setTotalCount(licenseDOList.size());
homeDataView.getLicense().setOnlineCount((int)licenseDOList.stream().filter(e -> OnlineStatusEnum.ONLINE.getStatus().equals(e.getIsOnline())).count());
}
// 查询端口映射列表
List<PortMappingDO> portMappingDOList = portMappingMapper.selectList(new LambdaQueryWrapper<>());
if (CollectionUtil.isNotEmpty(portMappingDOList)) {
homeDataView.getPortMapping().setTotalCount(portMappingDOList.size());
homeDataView.getPortMapping().setOnlineCount((int)portMappingDOList.stream().filter(e -> OnlineStatusEnum.ONLINE.getStatus().equals(e.getIsOnline())).count());
}
// 今日流量
Date now = new Date();
FlowBO todayFlow = reportMapper.homeTodayFlow(DateUtil.getDayBegin(now), now);
homeDataView.setTodayFlow(mapperFacade.map(todayFlow, HomeDataView.TodayFlow.class));
// 总流量
FlowBO totalFlow = reportMapper.homeTotalFlow(DateUtil.getMonthBegin(now), DateUtil.getDayBegin(now), now);
homeDataView.setTotalFlow(mapperFacade.map(totalFlow, HomeDataView.TotalFlow.class));
// 最近n日流量
Integer days = Constants.HOME_FLOW_DAYS;
List<SingleDayFlowBO> last7dFlowList = reportMapper.homeLast7dFlowList(DateUtil.getDayBegin(DateUtil.addDate(now, Calendar.DATE, -(days - 1))), DateUtil.getDayBegin(now), now);
homeDataView.setLast7dFlow(new HomeDataView.Last7dFlow());
homeDataView.getLast7dFlow().setDataList(mapperFacade.mapAsList(last7dFlowList, HomeDataView.SingleDayFlow.class));
// 数据处理
fillHomeDataView(homeDataView, now);
return homeDataView;
}
/**
* 用户流量报表分页
* @param pageQuery
* @param req
* @return
*/
public PageInfo<UserFlowReportRes> userFlowReportPage(PageQuery pageQuery, UserFlowReportReq req) {
Page<UserFlowReportRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
Date now = new Date();
List<UserFlowReportRes> list = reportMapper.userFlowReportList(req.getUserId(), DateUtil.getMonthBegin(now), DateUtil.getDayBegin(now), now);
fillUserFlowReport(list);
return PageInfo.of(list, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
}
/**
* license流量报表分页
* @param pageQuery
* @param req
* @return
*/
public PageInfo<LicenseFlowReportRes> licenseFlowReportPage(PageQuery pageQuery, LicenseFlowReportReq req) {
Page<LicenseFlowReportRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
Date now = new Date();
List<LicenseFlowReportRes> list = reportMapper.licenseFLowReportList(req.getUserId(), DateUtil.getMonthBegin(now), DateUtil.getDayBegin(now), now);
fillLicenseFlowReport(list);
return PageInfo.of(list, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
}
/**
* 用户流量月度明细
* @param pageQuery
* @param req
* @return
*/
public PageInfo<UserFlowMonthReportRes> userFlowMonthReportPage(PageQuery pageQuery, UserFlowMonthReportReq req) {
Page<UserFlowMonthReportRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
Date now = new Date();
List<UserFlowMonthReportRes> list = reportMapper.userFlowMonthReportList(req.getUserId(), DateUtil.getMonthBegin(now), DateUtil.getDayBegin(now), now);
fillUserFlowMonthReport(list);
return PageInfo.of(list, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
}
/**
* license流量月度明细
* @param pageQuery
* @param req
* @return
*/
public PageInfo<LicenseFlowMonthReportRes> licenseFlowMonthReportPage(PageQuery pageQuery, LicenseFlowMonthReportReq req) {
Page<LicenseFlowMonthReportRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
Date now = new Date();
List<LicenseFlowMonthReportRes> list = reportMapper.licenseFLowMonthReportList(req.getUserId(), req.getLicenseId(), DateUtil.getMonthBegin(now), DateUtil.getDayBegin(now), now);
fillLicenseFlowMonthReport(list);
return PageInfo.of(list, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
}
private void fillUserFlowReport(List<UserFlowReportRes> list) {
if (CollectionUtil.isEmpty(list)) {
return;
}
for (UserFlowReportRes item : list) {
long upFlowBytes = (null == item.getUpFlowBytes()) ? 0 : item.getUpFlowBytes();
long downFlowBytes = (null == item.getDownFlowBytes()) ? 0 : item.getDownFlowBytes();
long totalFlowBytes = upFlowBytes + downFlowBytes;
item.setUpFlowBytes(upFlowBytes);
item.setDownFlowBytes(downFlowBytes);
item.setTotalFlowBytes(totalFlowBytes);
item.setUpFlowDesc(FormatUtil.getSizeDescByByteCount(upFlowBytes));
item.setDownFlowDesc(FormatUtil.getSizeDescByByteCount(downFlowBytes));
item.setTotalFlowDesc(FormatUtil.getSizeDescByByteCount(totalFlowBytes));
}
}
private void fillLicenseFlowReport(List<LicenseFlowReportRes> list) {
if (CollectionUtil.isEmpty(list)) {
return;
}
for (LicenseFlowReportRes item : list) {
long upFlowBytes = (null == item.getUpFlowBytes()) ? 0 : item.getUpFlowBytes();
long downFlowBytes = (null == item.getDownFlowBytes()) ? 0 : item.getDownFlowBytes();
long totalFlowBytes = upFlowBytes + downFlowBytes;
item.setUpFlowBytes(upFlowBytes);
item.setDownFlowBytes(downFlowBytes);
item.setTotalFlowBytes(totalFlowBytes);
item.setUpFlowDesc(FormatUtil.getSizeDescByByteCount(upFlowBytes));
item.setDownFlowDesc(FormatUtil.getSizeDescByByteCount(downFlowBytes));
item.setTotalFlowDesc(FormatUtil.getSizeDescByByteCount(totalFlowBytes));
}
}
private void fillUserFlowMonthReport(List<UserFlowMonthReportRes> list) {
if (CollectionUtil.isEmpty(list)) {
return;
}
for (UserFlowMonthReportRes item : list) {
long upFlowBytes = (null == item.getUpFlowBytes()) ? 0 : item.getUpFlowBytes();
long downFlowBytes = (null == item.getDownFlowBytes()) ? 0 : item.getDownFlowBytes();
long totalFlowBytes = upFlowBytes + downFlowBytes;
item.setUpFlowBytes(upFlowBytes);
item.setDownFlowBytes(downFlowBytes);
item.setTotalFlowBytes(totalFlowBytes);
item.setUpFlowDesc(FormatUtil.getSizeDescByByteCount(upFlowBytes));
item.setDownFlowDesc(FormatUtil.getSizeDescByByteCount(downFlowBytes));
item.setTotalFlowDesc(FormatUtil.getSizeDescByByteCount(totalFlowBytes));
}
}
private void fillLicenseFlowMonthReport(List<LicenseFlowMonthReportRes> list) {
if (CollectionUtil.isEmpty(list)) {
return;
}
for (LicenseFlowMonthReportRes item : list) {
long upFlowBytes = (null == item.getUpFlowBytes()) ? 0 : item.getUpFlowBytes();
long downFlowBytes = (null == item.getDownFlowBytes()) ? 0 : item.getDownFlowBytes();
long totalFlowBytes = upFlowBytes + downFlowBytes;
item.setUpFlowBytes(upFlowBytes);
item.setDownFlowBytes(downFlowBytes);
item.setTotalFlowBytes(totalFlowBytes);
item.setUpFlowDesc(FormatUtil.getSizeDescByByteCount(upFlowBytes));
item.setDownFlowDesc(FormatUtil.getSizeDescByByteCount(downFlowBytes));
item.setTotalFlowDesc(FormatUtil.getSizeDescByByteCount(totalFlowBytes));
}
}
private void fillHomeDataView(HomeDataView homeDataView, Date now) {
if (null == homeDataView) {
return;
}
// License
if (null == homeDataView.getLicense()) {
homeDataView.setLicense(new HomeDataView.License());
}
if (null == homeDataView.getLicense().getTotalCount()) {
homeDataView.getLicense().setTotalCount(0);
}
if (null == homeDataView.getLicense().getOnlineCount()) {
homeDataView.getLicense().setOnlineCount(0);
}
homeDataView.getLicense().setOfflineCount(homeDataView.getLicense().getTotalCount() - homeDataView.getLicense().getOnlineCount());
// PortMapping
if (null == homeDataView.getPortMapping()) {
homeDataView.setPortMapping(new HomeDataView.PortMapping());
}
if (null == homeDataView.getPortMapping().getTotalCount()) {
homeDataView.getPortMapping().setTotalCount(0);
}
if (null == homeDataView.getPortMapping().getOnlineCount()) {
homeDataView.getPortMapping().setOnlineCount(0);
}
homeDataView.getPortMapping().setOfflineCount(homeDataView.getPortMapping().getTotalCount() - homeDataView.getPortMapping().getOnlineCount());
// TodayFlow
if (null == homeDataView.getTodayFlow()) {
homeDataView.setTodayFlow(new HomeDataView.TodayFlow());
}
if (null == homeDataView.getTodayFlow().getUpFlowBytes()) {
homeDataView.getTodayFlow().setUpFlowBytes(0L);
}
if (null == homeDataView.getTodayFlow().getDownFlowBytes()) {
homeDataView.getTodayFlow().setDownFlowBytes(0L);
}
homeDataView.getTodayFlow().setTotalFlowBytes(homeDataView.getTodayFlow().getUpFlowBytes() + homeDataView.getTodayFlow().getDownFlowBytes());
homeDataView.getTodayFlow().setUpFlowDesc(FormatUtil.getSizeDescByByteCount(homeDataView.getTodayFlow().getUpFlowBytes()));
homeDataView.getTodayFlow().setDownFlowDesc(FormatUtil.getSizeDescByByteCount(homeDataView.getTodayFlow().getDownFlowBytes()));
homeDataView.getTodayFlow().setTotalFlowDesc(FormatUtil.getSizeDescByByteCount(homeDataView.getTodayFlow().getTotalFlowBytes()));
// TotalFlow
if (null == homeDataView.getTotalFlow()) {
homeDataView.setTotalFlow(new HomeDataView.TotalFlow());
}
if (null == homeDataView.getTotalFlow().getUpFlowBytes()) {
homeDataView.getTotalFlow().setUpFlowBytes(0L);
}
if (null == homeDataView.getTotalFlow().getDownFlowBytes()) {
homeDataView.getTotalFlow().setDownFlowBytes(0L);
}
homeDataView.getTotalFlow().setTotalFlowBytes(homeDataView.getTotalFlow().getUpFlowBytes() + homeDataView.getTotalFlow().getDownFlowBytes());
homeDataView.getTotalFlow().setUpFlowDesc(FormatUtil.getSizeDescByByteCount(homeDataView.getTotalFlow().getUpFlowBytes()));
homeDataView.getTotalFlow().setDownFlowDesc(FormatUtil.getSizeDescByByteCount(homeDataView.getTotalFlow().getDownFlowBytes()));
homeDataView.getTotalFlow().setTotalFlowDesc(FormatUtil.getSizeDescByByteCount(homeDataView.getTotalFlow().getTotalFlowBytes()));
// 最近N日流量
Integer days = Constants.HOME_FLOW_DAYS;
HomeDataView.Last7dFlow last7dFlow = homeDataView.getLast7dFlow();
if (null == last7dFlow) {
last7dFlow = new HomeDataView.Last7dFlow();
homeDataView.setLast7dFlow(last7dFlow);
}
if (null == last7dFlow.getDataList()) {
last7dFlow.setDataList(Collections.emptyList());
}
// 数据列表
Set<String> existDateStrList = new HashSet<>();
if (CollectionUtil.isNotEmpty(last7dFlow.getDataList())) {
for (HomeDataView.SingleDayFlow singleDayFlow : last7dFlow.getDataList()) {
if (null == singleDayFlow.getUpFlowBytes()) {
singleDayFlow.setUpFlowBytes(0L);
}
if (null == singleDayFlow.getDownFlowBytes()) {
singleDayFlow.setDownFlowBytes(0L);
}
singleDayFlow.setTotalFlowBytes(singleDayFlow.getUpFlowBytes() + singleDayFlow.getDownFlowBytes());
singleDayFlow.setUpFlowDesc(FormatUtil.getSizeDescByByteCount(singleDayFlow.getUpFlowBytes()));
singleDayFlow.setDownFlowDesc(FormatUtil.getSizeDescByByteCount(singleDayFlow.getDownFlowBytes()));
singleDayFlow.setTotalFlowDesc(FormatUtil.getSizeDescByByteCount(singleDayFlow.getTotalFlowBytes()));
singleDayFlow.setDateStr(DateUtil.format(singleDayFlow.getDate(), "yyyy-MM-dd"));
existDateStrList.add(singleDayFlow.getDateStr());
}
}
// 获取最近7天的日期字符串列表。防止因统计数据缺失,造成图表展示错误的问题,缺失的日期数据,自动填充0
List<String> dateList = DateUtil.getBetweenTimes(DateUtil.format(DateUtil.addDate(now, Calendar.DATE, -(days - 1)), "yyyy-MM-dd"), DateUtil.format(now, "yyyy-MM-dd"));
for (String dateStr : dateList) {
if (existDateStrList.contains(dateStr)) {
continue;
}
last7dFlow.getDataList().add(new HomeDataView.SingleDayFlow()
.setDate(DateUtil.parse(dateStr, "yyyy-MM-dd"))
.setDateStr(dateStr)
.setUpFlowBytes(0L)
.setUpFlowDesc("0B")
.setDownFlowBytes(0L)
.setDownFlowDesc("0B")
.setTotalFlowBytes(0L)
.setTotalFlowDesc("0B")
);
}
// 按日期升序
Collections.sort(last7dFlow.getDataList(), Comparator.comparing(HomeDataView.SingleDayFlow::getDate));
// x轴日期
last7dFlow.setXDate(last7dFlow.getDataList().stream().map(HomeDataView.SingleDayFlow::getDateStr).collect(Collectors.toList()));
// 图例
last7dFlow.setLegendData(Lists.newArrayList("上行流量", "下行流量", "总流量"));
// 折线图
List<HomeDataView.Series> seriesList = Lists.newArrayList();
last7dFlow.setSeriesList(seriesList);
// 上行流量折线
seriesList.add(new HomeDataView.Series()
.setSeriesType("line")
.setSeriesName(last7dFlow.getLegendData().get(0))
.setSeriesData(last7dFlow.getDataList().stream().map(HomeDataView.SingleDayFlow::getUpFlowBytes).collect(Collectors.toList()))
);
// 下行流量折线
seriesList.add(new HomeDataView.Series()
.setSeriesType("line")
.setSeriesName(last7dFlow.getLegendData().get(1))
.setSeriesData(last7dFlow.getDataList().stream().map(HomeDataView.SingleDayFlow::getDownFlowBytes).collect(Collectors.toList()))
);
// 总流量折线
seriesList.add(new HomeDataView.Series()
.setSeriesType("line")
.setSeriesName(last7dFlow.getLegendData().get(2))
.setSeriesData(last7dFlow.getDataList().stream().map(HomeDataView.SingleDayFlow::getTotalFlowBytes).collect(Collectors.toList()))
);
}
}
@@ -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.UserLoginRecordListReq;
import fun.asgc.neutrino.proxy.server.controller.res.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;
@@ -41,6 +41,7 @@ public class UserLoginRecordService {
public PageInfo<UserLoginRecordListRes> page(PageQuery pageQuery, UserLoginRecordListReq req) {
Page<UserLoginRecordListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
List<UserLoginRecordDO> list = userLoginRecordMapper.selectList(new LambdaQueryWrapper<UserLoginRecordDO>()
.eq(null != req.getUserId(), UserLoginRecordDO::getUserId, req.getUserId())
.orderByDesc(UserLoginRecordDO::getCreateTime)
);
List<UserLoginRecordListRes> respList = mapperFacade.mapAsList(list, UserLoginRecordListRes.class);
@@ -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.*;
import fun.asgc.neutrino.proxy.server.controller.res.*;
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表示该端口已经绑定过
@@ -0,0 +1,19 @@
package org.dromara.neutrinoproxy.server.service.bo;
import lombok.Data;
/**
* @author: aoshiguchen
* @date: 2023/3/26
*/
@Data
public class FlowBO {
/**
* 上行流量字节数
*/
private Long upFlowBytes;
/**
* 下行流量字节数
*/
private Long downFlowBytes;
}
@@ -0,0 +1,26 @@
package org.dromara.neutrinoproxy.server.service.bo;
import lombok.Data;
import java.util.Date;
/**
* 单日流量
* @author: aoshiguchen
* @date: 2023/3/26
*/
@Data
public class SingleDayFlowBO {
/**
* 日期
*/
private Date date;
/**
* 上行流量字节数
*/
private Long upFlowBytes;
/**
* 下行流量字节数
*/
private Long downFlowBytes;
}
@@ -0,0 +1,49 @@
package org.dromara.neutrinoproxy.server.util;
/**
* @author: aoshiguchen
* @date: 2023/3/19
*/
public class FormatUtil {
private static final String[] SIZE_UNINTS = {"B", "KB", "MB", "GB", "TB"};
private static final int SIZE_SYSTEM = 1024;
/**
* 根据字节数获取大小描述
* 1、小于1024字节的以B为单位
* 2、小于1024KB的以KB为单位
* 3、小于1024M的以MB为单位
* 4、小于1024G的以GB为单位
* 5、其他以TB为单位
* @param byteCount
* @return
*/
public static String getSizeDescByByteCount(long byteCount){
if(byteCount <= 0){
return "0B";
}
double res = byteCount;
int index = 0;
while (index < SIZE_UNINTS.length && res >= SIZE_SYSTEM){
res /= SIZE_SYSTEM;
index++;
}
if(index >= SIZE_UNINTS.length){
index = SIZE_UNINTS.length - 1;
res *= 1024;
}
return trimZero(String.format("%.2f", res)) + SIZE_UNINTS[index];
}
private static String trimZero(String s) {
if (s.indexOf(".") > 0) {
// 去掉多余的0
s = s.replaceAll("0+?$", "");
// 如最后一位是.则去掉
s = s.replaceAll("[.]$", "");
}
return s;
}
}
@@ -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,30 +1,40 @@
server:
port: 8888
port: ${WEB_PORT:8888}
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
tunnel:
boss-thread-count: 2
work-thread-count: 10
port: ${OPEN_PORT:9000}
ssl-port: ${SSL_PORT:9002}
key-store-password: ${STORE_PASS:123456}
key-manager-password: ${MGR_PASS:123456}
jks-path: ${JKS_PATH:classpath:/test.jks}
server:
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-proxy-port: ${HTTP_PROXY_PORT:80}
https-proxy-port: ${HTTPS_PROXY_PORT:443}
# 如果不配置,则不支持域名映射
domain-name: ${DOMAIN_NAME:}
key-store-password: ${HTTPS_STORE_PASS:}
jks-path: ${HTTPS_JKS_PATH:}
data:
db:
type: sqlite
url: jdbc:sqlite:data.db
driver-class: org.sqlite.JDBC
username:
password:
type: ${DB_TYPE:sqlite}
url: ${DB_URL:jdbc:sqlite:data.db}
username: ${DB_USER:}
password: ${DB_PASSWORD:}
#添加MIME印射(如果有需要?)
#是否启用静态文件服务。(可不配,默认为启用)
@@ -38,12 +48,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="./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>
@@ -0,0 +1,19 @@
<?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="org.dromara.neutrinoproxy.server.dal.PortGroupMapper">
<select id="selectPortGroupListResList" resultType="org.dromara.neutrinoproxy.server.controller.res.system.PortGroupListRes">
SELECT g.*,
CASE
WHEN g.possessor_type = 1 THEN
u.`name`
WHEN g.possessor_type = 2 THEN
l.`name`
ELSE ''
END possessor
FROM port_group g
LEFT JOIN `user` u ON g.possessor_id = u.id
LEFT JOIN license l ON g.possessor_id = l.id
ORDER BY g.id
</select>
</mapper>
@@ -0,0 +1,32 @@
<?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="org.dromara.neutrinoproxy.server.dal.PortMappingMapper">
<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">
AND license.user_id = #{req.userId}
</if>
<if test="req.licenseId != null">
AND pm.license_id = #{req.licenseId}
</if>
<if test="req.serverPort != null">
AND pm.server_port = #{req.serverPort}
</if>
<if test="req.isOnline != null">
AND pm.is_online = #{req.isOnline}
</if>
<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>
</mapper>
@@ -0,0 +1,37 @@
<?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="org.dromara.neutrinoproxy.server.dal.PortPoolMapper">
<select id="selectResList" resultType="org.dromara.neutrinoproxy.server.controller.res.system.PortPoolListRes">
SELECT p.*,
g.possessor_type,
g.`name` group_name
FROM port_pool p
LEFT JOIN port_group g ON p.group_id = g.id
<where>
<if test="req.groupId != null">
AND p.group_id = #{req.groupId}
</if>
</where>
ORDER BY p.id
</select>
<select id="getAvailablePortList" resultType="org.dromara.neutrinoproxy.server.controller.res.system.PortPoolListRes">
SELECT
*
FROM
port_pool
WHERE
`port` NOT IN ( SELECT server_port FROM port_mapping )
AND group_id IN (
SELECT
id
FROM
port_group
WHERE
possessor_type = 0
OR ( possessor_type = 1 AND possessor_id = #{userId,jdbcType=INTEGER} )
OR ( possessor_type = 2 AND possessor_id = #{licenseId,jdbcType=INTEGER} )
)
</select>
</mapper>
@@ -0,0 +1,253 @@
<?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="org.dromara.neutrinoproxy.server.dal.ReportMapper">
<select id="userFlowReportList" resultType="org.dromara.neutrinoproxy.server.controller.res.report.UserFlowReportRes">
SELECT
u.id AS 'userId',
u.name AS 'userName',
SUM(IFNULL(frm.write_bytes,0) + IFNULL(frd.write_bytes,0) + IFNULL(frm2.write_bytes,0)) AS 'upFlowBytes',
SUM(IFNULL(frm.read_bytes,0) + IFNULL(frd.read_bytes,0) + IFNULL(frm2.read_bytes,0)) AS 'downFlowBytes',
IFNULL(SUM(frm.write_bytes),0) monthWriteBytes,
IFNULL(SUM(frm.read_bytes),0) monthReadBytes,
IFNULL(SUM(frd.write_bytes),0) dayWriteBytes,
IFNULL(SUM(frd.read_bytes),0) dayReadBytes,
IFNULL(SUM(frm2.write_bytes),0) minuteWriteBytes,
IFNULL(SUM(frm2.read_bytes),0) minuteReadBytes
FROM `user` u
LEFT JOIN (SELECT user_id,sum(write_bytes) write_bytes,sum(read_bytes) read_bytes from flow_report_month GROUP BY user_id) frm ON u.id = frm.user_id
LEFT JOIN (SELECT user_id,sum(write_bytes) write_bytes,sum(read_bytes) read_bytes from flow_report_day
WHERE date >= #{curMonthBeginDate} AND date &lt;= #{curDayBeginDate}
GROUP BY user_id) frd ON u.id = frd.user_id
LEFT JOIN (SELECT user_id,sum(write_bytes) write_bytes,sum(read_bytes) read_bytes from flow_report_minute
WHERE date >= #{curDayBeginDate} AND date &lt;= #{curDate}
GROUP BY user_id) frm2 ON u.id = frm2.user_id
<where>
<if test="userId != null">
AND u.id = #{userId}
</if>
</where>
GROUP BY u.id
</select>
<select id="licenseFLowReportList" resultType="org.dromara.neutrinoproxy.server.controller.res.report.LicenseFlowReportRes">
SELECT
l.id AS 'licenseId',
l.name AS 'licenseName',
u.id AS 'userId',
u.name AS 'userName',
SUM(IFNULL(frm.write_bytes,0) + IFNULL(frd.write_bytes,0) + IFNULL(frm2.write_bytes,0)) AS 'upFlowBytes',
SUM(IFNULL(frm.read_bytes,0) + IFNULL(frd.read_bytes,0) + IFNULL(frm2.read_bytes,0)) AS 'downFlowBytes',
IFNULL(SUM(frm.write_bytes),0) monthWriteBytes,
IFNULL(SUM(frm.read_bytes),0) monthReadBytes,
IFNULL(SUM(frd.write_bytes),0) dayWriteBytes,
IFNULL(SUM(frd.read_bytes),0) dayReadBytes,
IFNULL(SUM(frm2.write_bytes),0) minuteWriteBytes,
IFNULL(SUM(frm2.read_bytes),0) minuteReadBytes
FROM `license` l
LEFT JOIN `user` u ON l.user_id = u.id
LEFT JOIN (SELECT license_id,sum(write_bytes) write_bytes,sum(read_bytes) read_bytes from flow_report_month GROUP BY license_id) frm ON l.id = frm.license_id
LEFT JOIN (SELECT license_id,sum(write_bytes) write_bytes,sum(read_bytes) read_bytes from flow_report_day
WHERE date >= #{curMonthBeginDate} AND #{curDayBeginDate}
GROUP BY license_id) frd ON l.id = frd.license_id
LEFT JOIN (SELECT license_id,sum(write_bytes) write_bytes,sum(read_bytes) read_bytes from flow_report_minute
WHERE date >= #{curDayBeginDate} AND date &lt;= #{curDate}
GROUP BY license_id)frm2 ON l.id = frm2.license_id
<where>
<if test="userId != null">
AND u.id = #{userId}
</if>
</where>
GROUP BY l.id
</select>
<select id="userFlowMonthReportList" resultType="org.dromara.neutrinoproxy.server.controller.res.report.UserFlowMonthReportRes">
SELECT
T2.user_id,
u.name AS 'userName',
T2.date,
T2.upFlowBytes,
T2.downFlowBytes
FROM (
SELECT
frm.user_id,
frm.date,
IFNULL(SUM(frm.write_bytes),0) AS 'upFlowBytes',
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,
frd.write_bytes AS 'upFlowBytes',
frd.read_bytes AS 'downFlowBytes',
frd.date
FROM flow_report_day frd
WHERE frd.date >= #{curMonthBeginDate} AND frd.date &lt;= #{curDayBeginDate}
UNION ALL
SELECT
frm.user_id,
frm.license_id,
IFNULL(SUM(frm.write_bytes),0) AS 'upFlowBytes',
IFNULL(SUM(frm.read_bytes),0) AS 'downFlowBytes',
#{curDayBeginDate} AS 'date'
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>
<if test="userId != null">
AND u.id = #{userId}
</if>
</where>
ORDER BY T2.user_id ASC,T2.date DESC
</select>
<select id="licenseFLowMonthReportList" resultType="org.dromara.neutrinoproxy.server.controller.res.report.LicenseFlowMonthReportRes">
SELECT
T2.user_id,
u.name AS 'userName',
T2.license_id,
l.`name` AS 'licenseName',
T2.date,
T2.upFlowBytes,
T2.downFlowBytes
FROM (
SELECT
frm.user_id,
frm.license_id,
frm.date,
IFNULL(SUM(frm.write_bytes),0) AS 'upFlowBytes',
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,
#{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,
frd.write_bytes AS 'upFlowBytes',
frd.read_bytes AS 'downFlowBytes',
frd.date
FROM flow_report_day frd
WHERE frd.date >= #{curMonthBeginDate} AND frd.date &lt;= #{curDayBeginDate}
UNION ALL
SELECT
frm.user_id,
frm.license_id,
IFNULL(SUM(frm.write_bytes),0) AS 'upFlowBytes',
IFNULL(SUM(frm.read_bytes),0) AS 'downFlowBytes',
#{curDayBeginDate} AS 'date'
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
<where>
<if test="userId != null">
AND u.id = #{userId}
</if>
<if test="licenseId != null">
AND l.id = #{licenseId}
</if>
</where>
ORDER BY T2.user_id ASC,T2.license_id,T2.date DESC
</select>
<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'
FROM flow_report_minute frm
WHERE frm.date >= #{curDayBeginDate} AND frm.date &lt;= #{curDate}
</select>
<select id="homeTotalFlow" resultType="org.dromara.neutrinoproxy.server.service.bo.FlowBO">
SELECT
IFNULL(SUM(T.upFlowBytes),0) AS 'upFlowBytes',
IFNULL(SUM(T.downFlowBytes),0) AS 'downFlowBytes'
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
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
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="org.dromara.neutrinoproxy.server.service.bo.SingleDayFlowBO">
SELECT
T.*
FROM (
SELECT
frd.date,
IFNULL(SUM(frd.write_bytes),0) AS 'upFlowBytes',
IFNULL(SUM(frd.read_bytes),0) AS 'downFlowBytes'
FROM flow_report_day frd
WHERE frd.date >= #{beginDate} AND frd.date &lt;= #{curDayBeginDate}
GROUP BY frd.date
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>
@@ -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,18 +24,31 @@ 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` (
`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 COLLATE=utf8mb4_0900_ai_ci;
) 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表
@@ -50,23 +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` (
@@ -77,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 '消息',
@@ -89,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` (
@@ -105,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` (
@@ -120,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)
@@ -138,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` (
@@ -148,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` (
@@ -165,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` (
@@ -182,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;
@@ -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());
@@ -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());
@@ -1,41 +1,41 @@
#
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(1, 9101, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(2, 9102, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(3, 9103, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(4, 9104, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(5, 9105, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(6, 9106, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(7, 9107, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(8, 9108, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(9, 9109, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(10, 9110, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(11, 9111, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(12, 9112, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(13, 9113, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(14, 9114, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(15, 9115, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(16, 9116, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(17, 9117, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(18, 9118, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(19, 9119, 1, now(), now());
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(20, 9120, 1, now(), now());
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,13 @@
ALTER TABLE port_pool ADD group_id INT NOT NULL DEFAULT 1 COMMENT "分组ID";
#
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 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 '描述';
@@ -27,6 +27,7 @@ CREATE INDEX IF NOT EXISTS I_user_token_expiration_time ON user_token(expiration
#
CREATE TABLE IF NOT EXISTS `port_pool` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`group_id` INTEGER NOT NULL DEFAULT 1,
`port` INTEGER NOT NULL,
`enable` INTEGER(2) NOT NULL,
`update_time` INTEGER(20) NOT NULL,
@@ -34,6 +35,16 @@ CREATE TABLE IF NOT EXISTS `port_pool` (
);
CREATE UNIQUE INDEX IF NOT EXISTS I_port_pool_port ON port_pool (port ASC);
#
CREATE TABLE IF NOT EXISTS `port_group` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`name` VARCHAR(255) NOT NULL,
`possessor_type` INTEGER NOT NULL DEFAULT '0',
`possessor_id` INTEGER NOT NULL DEFAULT '-1',
`enable` INTEGER NOT NULL,
`create_time` datetime(3) NOT NULL,
`update_time` datetime(3) NOT NULL
);
##########################################################
#license表
CREATE TABLE IF NOT EXISTS `license` (
@@ -52,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
@@ -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, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
@@ -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'));
@@ -1,41 +1,41 @@
#
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(1, 9101, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(2, 9102, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(3, 9103, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(4, 9104, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(5, 9105, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(6, 9106, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(7, 9107, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(8, 9108, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(9, 9109, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(10, 9110, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(11, 9111, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(12, 9112, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(13, 9113, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(14, 9114, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(15, 9115, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(16, 9116, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(17, 9117, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(18, 9118, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(19, 9119, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(20, 9120, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(1, 1, 9101, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(2, 1, 9102, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(3, 1, 9103, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(4, 1, 9104, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(5, 1, 9105, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(6, 1, 9106, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(7, 1, 9107, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(8, 1, 9108, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(9, 1, 9109, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(10, 1, 9110, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(11, 1, 9111, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(12, 1, 9112, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(13, 1, 9113, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(14, 1, 9114, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(15, 1, 9115, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(16, 1, 9116, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(17, 1, 9117, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(18, 1, 9118, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(19, 1, 9119, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
(20, 1, 9120, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
@@ -0,0 +1,12 @@
ALTER TABLE port_pool ADD group_id INTEGER NOT NULL DEFAULT 1;
#
CREATE TABLE IF NOT EXISTS `port_group` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`name` VARCHAR(255) NOT NULL,
`possessor_type` INTEGER NOT NULL DEFAULT '0',
`possessor_id` INTEGER NOT NULL DEFAULT '-1',
`enable` INTEGER NOT NULL,
`create_time` datetime(3) NOT NULL,
`update_time` datetime(3) NOT NULL
);
@@ -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
+166 -225
View File
@@ -4,236 +4,177 @@ const htmlModules = require('./config/htmlModules.js');
module.exports = {
theme: 'vdoing', // 使用依赖包主题
// theme: require.resolve('../../vdoing'), // 使用本地主题 (先将vdoing主题文件下载到本地:https://github.com/xugaoyi/vuepress-theme-vdoing)
theme: 'vdoing', // 使用依赖包主题
// theme: require.resolve('../../vdoing'), // 使用本地主题 (先将vdoing主题文件下载到本地:https://github.com/xugaoyi/vuepress-theme-vdoing)
title: "中微子代理",
description: '一个基于VuePress的 知识管理&博客 主题',
// base: '/', // 默认'/'。如果你想将你的网站部署到如 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' }], // 移动浏览器主题颜色
title: "中微子代理",
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' }], // 广告相关,你可以去掉
],
// ['meta', { name: 'wwads-cn-verify', content: '6c4b761a28b734fe93831e3fb400ce87' }], // 广告相关,你可以去掉
// ['script', { src: 'https://cdn.wwads.cn/js/makemoney.js', type: 'text/javascript' }], // 广告相关,你可以去掉
],
// 主题配置
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/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/' }
// 主题配置
themeConfig: {
nav: [
{text: '首页', link: '/'},
{
text: '文档', link: '/pages/793dcb/', items: [
{text: '快速上手', link: '/pages/793dcb/'},
{text: '后台操作指南', link: '/pages/793dcc/'},
]
},
{text: '演示', link: '/pages/db78e2/'},
{text: 'Gitee', link: 'https://gitee.com/dromara/neutrino-proxy'},
],
sidebarDepth: 2, // 侧边栏显示深度,默认1,最大2(显示到h3标题)
logo: '/img/logo.png', // 导航栏logo
repo: 'https://github.com/dromara/neutrino-proxy', // 导航栏右侧生成Github链接
repoLabel: 'Github',
searchMaxSuggestions: 10, // 搜索结果显示最大数
lastUpdated: '上次更新', // 更新的时间,及前缀文字 string | boolean (取值为git提交时间)
// docsDir: 'docs', // 编辑的文件夹
// editLinks: true, // 编辑链接
// editLinkText: '编辑',
// 以下配置是Vdoing主题改动的和新增的配置
sidebar: {mode: 'structuring', collapsable: false}, // 侧边栏 'structuring' | { mode: 'structuring', collapsable: Boolean} | 'auto' | 自定义 温馨提示:目录页数据依赖于结构化的侧边栏数据,如果你不设置为'structuring',将无法使用目录页
// sidebarOpen: false, // 初始状态是否打开侧边栏,默认true
updateBar: { // 最近更新栏
showToArticle: false, // 显示到文章页底部,默认true
// moreArticle: '/archives' // “更多文章”跳转的页面,默认'/archives'
},
// titleBadge: false, // 文章标题前的图标是否显示,默认true
// titleBadgeIcons: [ // 文章标题前图标的地址,默认主题内置图标
// '图标地址1',
// '图标地址2'
// ],
pageStyle: 'line', // 页面风格,可选值:'card'卡片 | 'line' 线(未设置bodyBgImg时才生效), 默认'card'。 说明:card时背景显示灰色衬托出卡片样式,line时背景显示纯色,并且部分模块带线条边框
// contentBgStyle: 1,
category: false, // 是否打开分类功能,默认true。 如打开,会做的事情有:1. 自动生成的frontmatter包含分类字段 2.页面中显示与分类相关的信息和模块 3.自动生成分类页面(在@pages文件夹)。如关闭,则反之。
tag: false, // 是否打开标签功能,默认true。 如打开,会做的事情有:1. 自动生成的frontmatter包含标签字段 2.页面中显示与标签相关的信息和模块 3.自动生成标签页面(在@pages文件夹)。如关闭,则反之。
// archive: false, // 是否打开归档功能,默认true。 如打开,会做的事情有:1.自动生成归档页面(在@pages文件夹)。如关闭,则反之。
author: { // 文章默认的作者信息,可在md文件中单独配置此信息 String | {name: String, href: String}
name: '傲世孤尘', // 必需
href: 'https://gitee.com/dromara/neutrino-proxy' // 可选的
},
social: { // 社交图标,显示于博主信息栏和页脚栏
// iconfontCssFile: '//at.alicdn.com/t/font_1678482_u4nrnp8xp6g.css', // 可选,阿里图标库在线css文件地址,对于主题没有的图标可自由添加
icons: [
{
iconClass: 'icon-youjian',
title: '发邮件',
link: 'mailto:[email protected]'
},
{
iconClass: 'icon-gitee',
title: 'Gitee',
link: 'https://gitee.com/dromara/neutrino-proxy'
},
{
iconClass: 'icon-github',
title: 'Github',
link: 'https://github.com/aoshiguchen/neutrino-proxy'
}
]
},
footer: { // 页脚信息
createYear: 2023, // 博客创建年份
copyrightInfo: '傲世孤尘 | MIT License', // 博客版权信息,支持a标签
},
htmlModules,
},
// 插件
plugins: [
// [require('./plugins/love-me'), { // 鼠标点击爱心特效
// color: '#11a8cd', // 爱心颜色,默认随机色
// excludeClassName: 'theme-vdoing-content' // 要排除元素的class, 默认空''
// }],
['fulltext-search'], // 全文搜索
// ['thirdparty-search', { // 可以添加第三方搜索链接的搜索框(原官方搜索框的参数仍可用)
// thirdparty: [ // 可选,默认 []
// {
// title: '在GitHub中搜索',
// frontUrl: 'https://github.com/search?q=', // 搜索链接的前面部分
// behindUrl: '' // 搜索链接的后面部分,可选,默认 ''
// },
// {
// title: '在npm中搜索',
// frontUrl: 'https://www.npmjs.com/search?q=',
// },
// {
// title: '在Bing中搜索',
// frontUrl: 'https://cn.bing.com/search?q='
// }
// ]
// }],
[
'vuepress-plugin-baidu-tongji', // 百度统计
{
hm: baiduCode || 'f5901e55fecee5322d9f613841c717b8'
}
],
['one-click-copy', { // 代码块复制按钮
copySelector: ['div[class*="language-"] pre', 'div[class*="aside-code"] aside'], // String or Array
copyMessage: '复制成功', // default is 'Copy successfully and then paste it for use.'
duration: 1000, // prompt message display time.
showInMobile: false // whether to display on the mobile side, default: false.
}],
['demo-block', { // demo演示模块 https://github.com/xiguaxigua/vuepress-plugin-demo-block
settings: {
// jsLib: ['http://xxx'], // 在线示例(jsfiddle, codepen)中的js依赖
// cssLib: ['http://xxx'], // 在线示例中的css依赖
// vue: 'https://fastly.jsdelivr.net/npm/vue/dist/vue.min.js', // 在线示例中的vue依赖
jsfiddle: false, // 是否显示 jsfiddle 链接
codepen: true, // 是否显示 codepen 链接
horizontal: false // 是否展示为横向样式
}
}],
[
'vuepress-plugin-zooming', // 放大图片
{
selector: '.theme-vdoing-content img:not(.no-zoom)',
options: {
bgColor: 'rgba(0,0,0,0.6)'
},
},
],
[
'@vuepress/last-updated', // "上次更新"时间格式
{
transformer: (timestamp, lang) => {
const dayjs = require('dayjs') // https://day.js.org/
return dayjs(timestamp).format('YYYY/MM/DD, HH:mm:ss')
},
}
]
},
{
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/' },
],
sidebarDepth: 2, // 侧边栏显示深度,默认1,最大2(显示到h3标题)
logo: '/img/logo.png', // 导航栏logo
repo: 'xugaoyi/vuepress-theme-vdoing', // 导航栏右侧生成Github链接
// repo: 'https://gitee.com/dromara/neutrino-proxy', // 导航栏右侧生成Github链接
searchMaxSuggestions: 10, // 搜索结果显示最大数
lastUpdated: '上次更新', // 更新的时间,及前缀文字 string | boolean (取值为git提交时间)
// docsDir: 'docs', // 编辑的文件夹
// editLinks: true, // 编辑链接
// editLinkText: '编辑',
// 以下配置是Vdoing主题改动的和新增的配置
sidebar: { mode: 'structuring', collapsable: false }, // 侧边栏 'structuring' | { mode: 'structuring', collapsable: Boolean} | 'auto' | 自定义 温馨提示:目录页数据依赖于结构化的侧边栏数据,如果你不设置为'structuring',将无法使用目录页
// sidebarOpen: false, // 初始状态是否打开侧边栏,默认true
updateBar: { // 最近更新栏
showToArticle: false, // 显示到文章页底部,默认true
// moreArticle: '/archives' // “更多文章”跳转的页面,默认'/archives'
},
// titleBadge: false, // 文章标题前的图标是否显示,默认true
// titleBadgeIcons: [ // 文章标题前图标的地址,默认主题内置图标
// '图标地址1',
// '图标地址2'
// ],
pageStyle: 'line', // 页面风格,可选值:'card'卡片 | 'line' 线(未设置bodyBgImg时才生效), 默认'card'。 说明:card时背景显示灰色衬托出卡片样式,line时背景显示纯色,并且部分模块带线条边框
// contentBgStyle: 1,
category: false, // 是否打开分类功能,默认true。 如打开,会做的事情有:1. 自动生成的frontmatter包含分类字段 2.页面中显示与分类相关的信息和模块 3.自动生成分类页面(在@pages文件夹)。如关闭,则反之。
tag: false, // 是否打开标签功能,默认true。 如打开,会做的事情有:1. 自动生成的frontmatter包含标签字段 2.页面中显示与标签相关的信息和模块 3.自动生成标签页面(在@pages文件夹)。如关闭,则反之。
// archive: false, // 是否打开归档功能,默认true。 如打开,会做的事情有:1.自动生成归档页面(在@pages文件夹)。如关闭,则反之。
author: { // 文章默认的作者信息,可在md文件中单独配置此信息 String | {name: String, href: String}
name: 'Evan Xu', // 必需
href: 'https://github.com/xugaoyi' // 可选的
},
social: { // 社交图标,显示于博主信息栏和页脚栏
// iconfontCssFile: '//at.alicdn.com/t/font_1678482_u4nrnp8xp6g.css', // 可选,阿里图标库在线css文件地址,对于主题没有的图标可自由添加
icons: [
{
iconClass: 'icon-youjian',
title: '发邮件',
link: 'mailto:[email protected]'
},
{
iconClass: 'icon-gitee',
title: 'Gitee',
link: 'https://gitee.com/dromara/neutrino-proxy'
},
{
iconClass: 'icon-github',
title: 'Github',
link: 'https://github.com/aoshiguchen/neutrino-proxy'
}
]
},
footer: { // 页脚信息
createYear: 2023, // 博客创建年份
copyrightInfo: '傲世孤尘 | MIT License', // 博客版权信息,支持a标签
},
htmlModules,
},
// 插件
plugins: [
// [require('./plugins/love-me'), { // 鼠标点击爱心特效
// color: '#11a8cd', // 爱心颜色,默认随机色
// excludeClassName: 'theme-vdoing-content' // 要排除元素的class, 默认空''
// }],
['fulltext-search'], // 全文搜索
// ['thirdparty-search', { // 可以添加第三方搜索链接的搜索框(原官方搜索框的参数仍可用)
// thirdparty: [ // 可选,默认 []
// {
// title: '在GitHub中搜索',
// frontUrl: 'https://github.com/search?q=', // 搜索链接的前面部分
// behindUrl: '' // 搜索链接的后面部分,可选,默认 ''
// },
// {
// title: '在npm中搜索',
// frontUrl: 'https://www.npmjs.com/search?q=',
// },
// {
// title: '在Bing中搜索',
// frontUrl: 'https://cn.bing.com/search?q='
// }
// ]
// }],
[
'vuepress-plugin-baidu-tongji', // 百度统计
{
hm: baiduCode || '01293bffa6c3962016c08ba685c79d78'
}
],
['one-click-copy', { // 代码块复制按钮
copySelector: ['div[class*="language-"] pre', 'div[class*="aside-code"] aside'], // String or Array
copyMessage: '复制成功', // default is 'Copy successfully and then paste it for use.'
duration: 1000, // prompt message display time.
showInMobile: false // whether to display on the mobile side, default: false.
}],
['demo-block', { // demo演示模块 https://github.com/xiguaxigua/vuepress-plugin-demo-block
settings: {
// jsLib: ['http://xxx'], // 在线示例(jsfiddle, codepen)中的js依赖
// cssLib: ['http://xxx'], // 在线示例中的css依赖
// vue: 'https://fastly.jsdelivr.net/npm/vue/dist/vue.min.js', // 在线示例中的vue依赖
jsfiddle: false, // 是否显示 jsfiddle 链接
codepen: true, // 是否显示 codepen 链接
horizontal: false // 是否展示为横向样式
}
}],
[
'vuepress-plugin-zooming', // 放大图片
{
selector: '.theme-vdoing-content img:not(.no-zoom)',
options: {
bgColor: 'rgba(0,0,0,0.6)'
},
},
],
[
'@vuepress/last-updated', // "上次更新"时间格式
{
transformer: (timestamp, lang) => {
const dayjs = require('dayjs') // https://day.js.org/
return dayjs(timestamp).format('YYYY/MM/DD, HH:mm:ss')
},
}
markdown: {
// lineNumbers: true,
extractHeaders: ['h2', 'h3', 'h4', 'h5', 'h6'], // 提取标题到侧边栏的级别,默认['h2', 'h3']
},
// 监听文件变化并重新构建
extraWatchFiles: [
'.vuepress/config.js',
'.vuepress/config/htmlModules.js',
]
],
markdown: {
// lineNumbers: true,
extractHeaders: ['h2', 'h3', 'h4', 'h5', 'h6'], // 提取标题到侧边栏的级别,默认['h2', 'h3']
},
// 监听文件变化并重新构建
extraWatchFiles: [
'.vuepress/config.js',
'.vuepress/config/htmlModules.js',
]
}
@@ -46,7 +46,7 @@ module.exports = {
</style>
`,
// 赞助商广告
sidebarT: `<a href="http://apifox.cn/a103xugaoyi" target="_blank"><img src="https://cdn.staticaly.com/gh/xugaoyi/blog-gitalk-comment@master/img/431669861564_.2470ykdcpbds.jpg" alt="npm" class="no-zoom" style="width: 100%;border-radius: 2px;"></a>`,
// sidebarT: `<a href="http://apifox.cn/a103xugaoyi" target="_blank"><img src="https://cdn.staticaly.com/gh/xugaoyi/blog-gitalk-comment@master/img/431669861564_.2470ykdcpbds.jpg" alt="npm" class="no-zoom" style="width: 100%;border-radius: 2px;"></a>`,
// windowRB: `
@@ -1,59 +0,0 @@
/**
* to主题使用者:你可以去掉本文件的所有代码
*/
export default ({
Vue, // VuePress 正在使用的 Vue 构造函数
options, // 附加到根实例的一些选项
router, // 当前应用的路由实例
siteData, // 站点元数据
isServer // 当前应用配置是处于 服务端渲染 还是 客户端
}) => {
// 用于监控在路由变化时检查广告拦截器 (to主题使用者:你可以去掉本文件的所有代码)
if (!isServer) {
router.afterEach(() => {
//check if wwads' fire function was blocked after document is ready with 3s timeout (waiting the ad loading)
docReady(function () {
setTimeout(function () {
if (window._AdBlockInit === undefined) {
ABDetected();
}
}, 3000);
});
// 删除事件改为隐藏事件
setTimeout(() => {
const pageAD = document.querySelector('.page-wwads');
if (!pageAD) return;
const btnEl = pageAD.querySelector('.wwads-hide');
if (btnEl) {
btnEl.onclick = () => {
pageAD.style.display = 'none';
}
}
// 显示广告模块
if (pageAD.style.display === 'none') {
pageAD.style.display = 'flex';
}
}, 900);
})
}
}
function ABDetected() {
const h = "<style>.wwads-horizontal,.wwads-vertical{background-color:#f4f8fa;padding:5px;min-height:120px;margin-top:20px;box-sizing:border-box;border-radius:3px;font-family:sans-serif;display:flex;min-width:150px;position:relative;overflow:hidden;}.wwads-horizontal{flex-wrap:wrap;justify-content:center}.wwads-vertical{flex-direction:column;align-items:center;padding-bottom:32px}.wwads-horizontal a,.wwads-vertical a{text-decoration:none}.wwads-horizontal .wwads-img,.wwads-vertical .wwads-img{margin:5px}.wwads-horizontal .wwads-content,.wwads-vertical .wwads-content{margin:5px}.wwads-horizontal .wwads-content{flex:130px}.wwads-vertical .wwads-content{margin-top:10px}.wwads-horizontal .wwads-text,.wwads-content .wwads-text{font-size:14px;line-height:1.4;color:#0e1011;-webkit-font-smoothing:antialiased}.wwads-horizontal .wwads-poweredby,.wwads-vertical .wwads-poweredby{display:block;font-size:11px;color:#a6b7bf;margin-top:1em}.wwads-vertical .wwads-poweredby{position:absolute;left:10px;bottom:10px}.wwads-horizontal .wwads-poweredby span,.wwads-vertical .wwads-poweredby span{transition:all 0.2s ease-in-out;margin-left:-1em}.wwads-horizontal .wwads-poweredby span:first-child,.wwads-vertical .wwads-poweredby span:first-child{opacity:0}.wwads-horizontal:hover .wwads-poweredby span,.wwads-vertical:hover .wwads-poweredby span{opacity:1;margin-left:0}.wwads-horizontal .wwads-hide,.wwads-vertical .wwads-hide{position:absolute;right:-23px;bottom:-23px;width:46px;height:46px;border-radius:23px;transition:all 0.3s ease-in-out;cursor:pointer;}.wwads-horizontal .wwads-hide:hover,.wwads-vertical .wwads-hide:hover{background:rgb(0 0 0 /0.05)}.wwads-horizontal .wwads-hide svg,.wwads-vertical .wwads-hide svg{position:absolute;left:10px;top:10px;fill:#a6b7bf}.wwads-horizontal .wwads-hide:hover svg,.wwads-vertical .wwads-hide:hover svg{fill:#3E4546}</style><a href='https://wwads.cn/page/whitelist-wwads' class='wwads-img' target='_blank' rel='nofollow'><img src='https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/blog/wwads.2a3pidhlh4ys.webp' width='130'></a><div class='wwads-content'><a href='https://wwads.cn/page/whitelist-wwads' class='wwads-text' target='_blank' rel='nofollow'>为了本站的长期运营,请将我们的网站加入广告拦截器的白名单,感谢您的支持!<span style='color: #11a8cd'>如何添加白名单?</span></a><a href='https://wwads.cn/page/end-user-privacy' class='wwads-poweredby' title='万维广告 让广告更优雅,且有用' target='_blank'><span>广告</span></a></div><a class='wwads-hide' onclick='parentNode.remove()' title='隐藏广告'><svg xmlns='http://www.w3.org/2000/svg' width='6' height='7'><path d='M.879.672L3 2.793 5.121.672a.5.5 0 11.707.707L3.708 3.5l2.12 2.121a.5.5 0 11-.707.707l-2.12-2.12-2.122 2.12a.5.5 0 11-.707-.707l2.121-2.12L.172 1.378A.5.5 0 01.879.672z'></path></svg></a>";
const wwadsEl = document.getElementsByClassName("wwads-cn");
const wwadsContentEl = document.querySelector('.wwads-content');
if (wwadsEl[0] && !wwadsContentEl) {
wwadsEl[0].innerHTML = h;
}
};
//check document ready
function docReady(t) {
"complete" === document.readyState ||
"interactive" === document.readyState
? setTimeout(t, 1)
: document.addEventListener("DOMContentLoaded", t);
}
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.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 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.

After

Width:  |  Height:  |  Size: 327 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

Binary file not shown.

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.

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,118 @@
---
title: 快速上手
date: 2023-03-28 20:30:00
permalink: /pages/793dcb
article: false
---
## 1、 部署服务端
### 1.1、 Docker一键部署
> 当前最新版本为1.8.2,下面的脚本中,可以使用:`registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:1.8.2` 指定版本安装,推荐使用`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)一键三连🤝
<!--
## 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,59 @@
---
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:
# 隧道相关配置-用于维持服务端与客户端的通信
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}
# 代理服务相关配置
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:}
data:
db:
# 数据库类型,目前支持sqlite、mysql两种
type: ${DB_TYPE:sqlite}
# 数据库连接URL
url: ${DB_URL:jdbc:sqlite:data.db}
# 数据库用户名
username: ${DB_USER:}
# 数据库密码
password: ${DB_PASSWORD:}
```
@@ -1,39 +0,0 @@
---
title: 主题初衷与诞生
date: 2020-05-11 13:59:38
permalink: /pages/52d5c3
article: false
---
这个主题的初衷是打造一个好用的、面向程序员的`知识管理工具`
对于程序员来说,繁杂的知识体系难免会有遗忘的地方。如果有一个方便好用的知识管理工具,可以帮助我们很好的管理知识,并能够快速地把遗忘的知识点找回来。
## Markdown
最初接触[Markdown](https://xugaoyi.com/pages/ad247c4332211551/)的时候,我就被它简洁的语法干净的文本结构吸引住,它的代码块和兼容`html`标签的能力更是让我爱上它,很高兴找到了一个高效记录学习笔记的工具。
## 知识管理
在一段学习的日子里,我尝试过用`txt`记录笔记、云笔记、`Markdown`笔记,并把`Markdown`文件上传到`github`进行管理,但总感觉还是不够方便...直到我发现了`VuePress`,它似乎可以管理我的学习笔记,并且把站点部署到`github pages`不就是一个在线的云笔记网站了吗
## VuePress
[VuePress](https://vuepress.vuejs.org/zh/)是一个 Vue 驱动的静态网站生成器,正是以`Markdown`为中心的项目结构,它简洁至上的理念正合我心。对于我这个对Vue还算有一些了解的前端,迫不及待的想去使用它来搭建一个我的云笔记网站。
## 知识管理&博客主题-Vdoing的诞生
我以前的一个领导和我们说过一个好的知识管理可以帮助我们提高开发质量和开发效率,下面这张图就是他想传达的,我表示赞同:
![知识库](https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200318125116.jpg)
什么是一个好用的知识管理工具呢?其实我也在不断的寻找和思考中。不过我觉得它至少要结构清晰、管理方便,在查找知识点的过程中可以快速的找到,正像上图所说的要在15秒内找到一个知识点,在添加内容的时候可以很方便的添加,并且保证结构清晰。
VuePress的官方默认主题是专门为写文档而生的,和我心目中的知识库有一些差距,比如在满足结构清晰这点上,每添加一个内容就得手动的添加侧边栏数据,还有在满足15秒内找到一个知识点上还是不够好,因此我觉得需要做一些改造。
在知识管理方面,有三种典型的知识形态:
> * 结构化:看重知识的条理性和实用性,有一定专业背景才能看懂
> * 碎片化:描述具体的知识点,通俗易懂
> * 体系化:对某一领域的完整解读,通常是某类知识的专业之作
从这三种知识形态出发,我给主题添加了自动生成结构化侧边栏、目录页、面包屑等等。在满足了结构化之后,写博客应该算得上是一种碎片化的形态,我就给主题添加了分类、标签、归档等等。在满足更方便添加内容上,有了自动生成front matter、批量操作front matter等,更多关于主题的知识从开始使用它去探索吧~~
### 主题名称
Vdoing,中文大概是维度的意思。起这个名的原因,首先是我看过一个视频《尤雨溪创立vue的心路历程》[B站传送](https://b23.tv/xI9ONW) [youtube传送](https://www.youtube.com/watch?v=OrxmtDw4pVI),里面有讲到vue起名的故事,一开始想起的名其实是Seed.js,但是在npm上被占用了,然后发现vue是一个挺酷的一个词,才决定用vue。发现大佬起名可以这么酷。再者,这个主题我想它可以多维度的快速寻找一个知识点,要么就叫维度吧,那英文名就用Vdoing好了...
@@ -1,73 +0,0 @@
---
title: 介绍
date: 2020-05-11 13:54:03
permalink: /pages/a2f161
article: false
---
Vdoing是[VuePress v1.x](https://vuepress.vuejs.org/zh/)的一个主题,是在[默认主题](https://vuepress.vuejs.org/zh/theme/option-api.html)基础上做的修改和扩展,很多配置仍然沿用[官方配置](https://vuepress.vuejs.org/zh/config/)。使用本主题可以很方便的搭建一个结构化的知识库或博客。
这个主题的初衷是打造一个好用的、面向程序员的知识管理工具:
[**主题初衷与诞生**。](/pages/52d5c3/)
::: warning 注意
1. Node请使用`v14.17.x`或以上版本
2. 在使用本主题前,要求你至少会VuePress v1.x的基本使用和默认主题的基本配置,然后再查看本文档。
3. 本文档仅负责介绍Vdoing主题对默认主题的扩展部分,更多配置请移步 [VuePress v1.x文档](https://vuepress.vuejs.org/zh/)
:::
## 特性
* **知识管理**
包含三种典型的知识管理形态:结构化、碎片化、体系化。轻松打造属于你自己的知识管理平台。
* **结构化**
自动生成侧边栏、目录页、索引页、面包屑等,轻松构建一个结构化知识库。
* **碎片化&个性化**
博客功能提供一种知识的碎片化形态,并提供个性化的博客配置。
* **简洁高效**
以 Markdown 为中心的项目结构,内置自动化工具,以更少的配置完成更多的事。配合多维索引快速定位每个知识点。
* **沉浸式阅读体验**
专为阅读设计的UI,配合多种颜色模式、可关闭的侧边栏和导航栏,带给你一种沉浸式阅读体验。
## 扩展功能
相较于默认主题,添加的功能内容主要有:
* 添加方便管理学习笔记和技术文档的`自动生成结构化侧边栏``自动生成front matter``目录页``扩展的搜索框插件``面包屑``快捷翻页按钮` 等,让你快速定位到任何你想要找的内容。
* 添加博客相关的 `文章信息栏(作者与创建时间)``最近更新栏``博主信息栏``页脚版权栏``分类功能+分类页``标签功能+标签页``归档页``评论插件`等。
* 方便好用的 `Markdown 容器`
* 首页`文章列表``个性化配置``样式美化`等。
* 多种颜色模式供用户选择:`跟随系统``浅色模式``深色模式``阅读模式`
* 提高搬砖效率的辅助工具: `批量操作front matter工具`
* ...
## 安利
* 拥有它你就同时拥有了一个专属你个人的在线知识库(云笔记)、博客、文档库、Demo库、一站式技术搜索工具,内容全部采用Markdown编写,简单高效,各种代码随便贴。
* 你可以在`.md`文件中写html、css、js、甚至是vue组件代码,[markdown天然的就支持vue组件](https://v1.vuepress.vuejs.org/zh/guide/using-vue.html),魔改页面什么的不要太简单。
* 相当多的程序员喜欢深色模式,还有的视力也不太好(🤓),我们有浅色、深色和阅读模式,更有跟随系统自动响应深浅色模式功能,想怎么换就怎么换。
> 点击右下角换肤按钮
* 当你习惯用vdoing主题后,在别处看文档发现是markdown编写的,但所在站点的目录、导航、主题等某个地方用起来不是很爽,你都可以把文档拷贝或把整个专栏下载(如支持下载的话)下来放到vdoing主题,vdoing的自动化工具助你生成一个结构清晰的、拥有目录、页面导航的,而且有多种颜色模式的文档站。让你更专注于内容的学习。
> 参考我博客中的[文档专栏](https://xugaoyi.com/note/typescript-axios/)
* 如果你想和更多的人分享你的文章,那么这款seo友好的主题是一个很不错的选择,更有为了加快百度收录而定制的每天定时[百度推送程序](https://xugaoyi.com/pages/f44d2f9ad04ab8d3/)。
> 参考我的博客[收录情况](https://www.baidu.com/s?word=site%3Axugaoyi.com)。
别犹豫了,赶快上手吧
@@ -1,110 +0,0 @@
---
title: 快速上手
date: 2020-05-11 13:54:40
permalink: /pages/793dcb
article: false
---
## 安装和启动
<code-group>
<code-block title="知识库兼博客风格预设配置" active>
```bash
# clone the project
git clone https://github.com/xugaoyi/vuepress-theme-vdoing.git
# enter the project directory
cd vuepress-theme-vdoing
# install dependency 注意:如安装不成功请关闭淘宝源。
npm install # or yarn install
# develop
npm run dev # or yarn dev
```
</code-block>
<code-block title="文档风格预设配置">
```bash
# clone the project
git clone https://github.com/xugaoyi/vuepress-theme-vdoing-doc.git
# enter the project directory
cd vuepress-theme-vdoing-doc
# install dependency 注意:如安装不成功请关闭淘宝源。
npm install # or yarn install
# develop
npm run dev # or yarn dev
```
> 仓库地址: <https://github.com/xugaoyi/vuepress-theme-vdoing-doc>
</code-block>
<code-block title="简洁模板预设配置(社区提供)">
```bash
# clone the project
git clone https://github.com/u2sb/vuepress-theme-vdoing-template.git
# enter the project directory
cd vuepress-theme-vdoing-template
# install dependency 注意:如安装不成功请关闭淘宝源。
npm install # or yarn install
# develop
npm run dev # or yarn dev
```
> 仓库地址: <https://github.com/u2sb/vuepress-theme-vdoing-template>
</code-block>
</code-group>
::: warning
Node请使用`v14.17.x`或以上版本
:::
## 使用Vdoing主题
1. 安装最新的Vdoing主题包:
```sh
npm install vuepress-theme-vdoing -D
```
2.`.vuepress/config.js`中配置使用主题:
```js
// config.js
module.exports = {
theme: 'vdoing'
}
```
::: 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,75 +0,0 @@
---
title: 目录结构
date: 2020-05-11 13:54:56
permalink: /pages/2f674a
article: false
---
```
.
├── .github (可选,GitHub 相关文件)
│ ├── workflows
│ │ ├── baiduPush.yml (可选,百度定时自动推送)
│ │ └── ci.yml (可选,自动部署)
├── docs (必须,不要修改文件夹名称)
│ ├── .vuepress (同官方,查看:https://vuepress.vuejs.org/zh/guide/directory-structure.html#目录结构)
│ ├── @pages (可选,自动生成的文件夹,存放分类页、标签页、归档页)
│ ├── _posts (可选,专门存放碎片化博客文章的文件夹)
│ ├── <结构化目录>
│ └── index.md (首页)
├── vdoing (可选,本地的vdoing主题)
├── utils (可选,vdoing主题使用的node工具)
│ ├── modules
│ ├── config.yml (可选,批量操作front matter配置)
│ ├── editFrontmatter.js (可选,批量操作front matter工具)
├── baiduPush.sh (可选,百度推送命令脚本)
├── deploy.sh (可选,部署命令脚本)
└── package.json
```
* `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,107 +0,0 @@
---
title: 构建结构化站点的核心配置和约定
date: 2020-05-12 11:01:21
permalink: /pages/33d574
article: false
---
本主题遵循“**约定优于配置**”原则,如果你想构建一个结构化的站点,需要遵循下面这些约定,约定可以为你省去很多配置,轻松拥有一个结构清晰的站点。
你需要在`config.js`中有如下配置:
```js
// config.js
module.exports = {
themeConfig: {
sidebar: 'structuring' // 'structuring' | { mode: 'structuring', collapsable: Boolean} | 'auto' | 自定义
}
}
```
在运行开发服务`npm run dev`或打包`npm run build`时主题内部将会按照目录约定自动生成一个结构化的**侧边栏、目录页、面包屑等**数据。
在源目录(一般是`docs`)文件夹中,除了`.vuepress``@pages``_posts`、``index.md 或 README.md``之外的**文件夹**将会为其生成对应的侧边栏。生成的顺序取自序号,标题取自文件(夹)名称。
### 命名约定
* 无论是**文件**还是**文件夹**,请为其名称添加上正确的**正整数序号**和`.`,从`00`或`01`开始累计,如`01.文件夹`、`02.文件.md`,我们将会按照序号的顺序来决定其在侧边栏当中的顺序。
* 同一级别目录别内即使只有一个文件或文件夹也要为其加上序号。
<!-- * 文件或文件夹名称中间不能出现多余的点`.`,如`01.我是.名称.md`中间出现`.`将会导致解析错误。 -->
::: tip 提示
序号只是用于决定先后顺序,并不一定需要连着,如`01、02、03...`,实际工作中可能会在两个文章中间插入一篇新的文章,因此为了方便可以采用间隔序号`10、20、30...`,后面如果需要在`10`和`20`中间插入一篇新文章,可以给定序号`15`。
:::
### 级别说明
源目录(一般是`docs`)底下的级别现在我们称之为`一级目录``一级目录`的下一级为`二级目录`,以此类推,最多只能到`四级目录`。
* **一级目录**
1. `.vuepress`、`@pages`、`_posts`、`index.md 或 README.md` 这些文件(文件夹)不参与数据生成。
2. 序号非必须。(如一些专栏,可以不用序号)
* **二级目录**
1. 该级别下可以同时放文件夹和`.md`文件,但是两者序号要连贯(参考下面的例子中的`其他`)。
2. 必须有序号
* **三级目录**
- (同上)
* **四级目录** <Badge text="v1.6.0 +"/>
1. 该级别下**只能**放`.md`文件。
2. 必须有序号
所有级别内至少有一个文件或文件夹。
### 目录结构例子
```html
.
├── docs
│ │ (不参与数据生成)
│ ├── .vuepress
│ ├── @pages
│ ├── _posts
│ ├── index.md
│ │
│ │ (以下部分参与数据生成)
│ ├── 《JavaScript教程》专栏 (一级目录)
│ │ ├── 01.章节1 (二级目录)
│ │ | ├── 01.js1.md (三级目录-文件)
│ │ | ├── 02.js2.md
│ │ | └── 03.js3.md
│ │ └── 02.章节2 (二级目录)
│ │ | ├── 01.jsa.md
│ │ | ├── 02.小节 (三级目录)
│ │ | | └── 01.jsxx.md (四级目录-文件)
│ ├── 01.前端
│ │ ├── 01.JavaScript
│ │ | ├── 01.js1.md
│ │ | ├── 02.js2.md
│ │ | └── 03.js3.md
│ │ └── 02.vue
│ │ | ├── 01.vue1.md
│ │ | └── 02.vue2.md
│ ├── 02.其他
│ │ ├── 01.学习
│ │ | ├── 01.xxa.md
│ │ | └── 02.xxb.md
│ │ ├── 02.学习笔记
│ │ | ├── 01.xxa.md
│ │ | └── 02.xxb.md
│ │ ├── 03.文件x.md
│ │ └── 04.文件xx.md
│ └── 03.关于我
│ │ └── 01.关于我.md
. .
```
### 如何知道侧边栏数据有没有正确生成?
在运行开发服务时(`npm run dev`),在命令行查看打印记录,如果正确生成会有这样的`绿色`提示记录:
```bash
tip: add sidebar data. 侧边栏数据添加成功。
```
如果有未按约定的文件,会有`黄色`警告记录,如:
```bash
warning: 该文件'xxx'序号出错,请填写正确的序号。
```
@@ -1,108 +0,0 @@
---
title: 自动生成front matter
date: 2020-05-12 11:46:37
permalink: /pages/088c16
article: false
---
当你没有给`.md`文件的[front matter](https://vuepress.vuejs.org/zh/guide/frontmatter.html)指定标题(`title`)、时间(`date`)、永久链接(`permalink`)、分类(`categories`)、标签(`tags`)、主题配置中[extendFrontmatter](/pages/a20ce8/#extendfrontmatte)配置的字段时,在运行开发服务`npm run dev`或打包`npm run build`时将自动为你生成这些数据,你也可以自己手动设置这些数据,当你手动设置之后,相应的数据就不会再自动生成。
### 生成示例
```yaml
---
title: 《JavaScript教程》笔记
date: 2020-01-12 11:51:53
permalink: /pages/d8cae9
categories:
- 前端
- JavaScript
tags:
-
---
```
### title
* 类型: `string`
* 默认:`.md`文件的名称
当前页面的标题
### date
* 类型: `string`
* 格式:`YYYY-MM-DD HH:MM:SS`
* 默认:`.md`文件在系统中创建的时间
当前页面的创建时间,如需手动添加或修改该字段时请按照格式添加或修改
### permalink
* 类型: `string`
* 默认:`/pages/`+ 6位字母加数字的随机码
当前页面的永久链接
> Q:自动生成front matter为什么要包含永久链接?
>
> A:使用永久链接是出于以下几点考虑:
>
> * 在config.js配置nav时使用永久链接,就不会因为文件的路径或名称的改变而改变。
>* 对于博客而言,当别人收藏了你的文章,在未来的时间里都可以通过永久链接来访问到。
>* 主题中的目录页需要通过永久链接来访问文章。
### categories
* 类型: `array`
* 默认:
* `.md`所在的文件夹名称。
* 如果`.md`文件所在的目录是`三级目录`,则会有两个分类值,分别是`二级目录``一级目录`的文件夹名称。如果在`四级目录`,则再多一个`三级目录`的文件夹名称分类。([级别说明](/pages/33d574/#级别说明)
* 如果`.md`文件所在的目录是`_posts`,则默认值是`随笔`,这个默认值可以在`config.js`中修改,参考:[config.js配置](/pages/a20ce8/#碎片化博文默认分类值)
* 如果在 [config.js配置](/pages/a20ce8/#category) 设置了`category: false` 将不会自动生成该字段
当前页面的分类
### tags
* 类型: `array`
* 默认:空数组
* 如果在 [config.js配置]() 设置了`tag: false` 将不会自动生成该字段
当前页面的标签,默认值是空数组,自动生成该字段只是为了方便后面添加标签值。
### 扩展自动生成front matter
当在主题配置中配置了`extendFrontmatter`时,将在自动生成front matter时添加相应配置的字段和数据。详见:[extendFrontmatter](/pages/a20ce8/#extendfrontmatter)
### 碎片化文章‘分类’的自动生成规则 <Badge text="v1.12.5+"/>
> 碎片化文章即放在_posts文件夹的文章,里面的`.md`文件不需要遵循命名约定,不会生成结构化侧边栏和目录页。
当文章在_posts根目录时,分类获取 `themeConfig.categoryText` 的值,如`_posts/foo.md` ,则`foo.md`文件的分类会生成为:
```yaml
categories:
- 随笔
```
> categoryText的默认值是‘随笔’,可在themeConfig修改,详见[categorytext](/pages/a20ce8/#categorytext)。
当文章在非_posts根目录时,获取父文件夹的名称作为分类,如
`_posts/想法/奇思妙想/foo.md` ,则`foo.md`文件的分类会生成为:
```yaml
categories:
- 想法
- 奇思妙想
```
@@ -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 [email protected]:<USERNAME>/<USERNAME>.github.io.git master
# 如果发布到 https://<USERNAME>.github.io/<REPO>
# git push -f [email protected]:<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,13 @@
---
title: 演示环境
date: 2020-05-12 15:10:15
permalink: /pages/db78e2
article: false
---
### 演示环境
可使用分配好的游客license试用。服务器带宽较低,仅供学习使用!
管理后台地址:<a href="http://103.163.47.16:9527" target="_blank">http://103.163.47.16:9527</a>
游客账号:visitor/123456
@@ -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执行测试通过
@@ -1,208 +0,0 @@
---
title: 资源
date: 2020-05-12 15:10:15
permalink: /pages/db78e2
article: false
---
## 插件推荐
* [vuepress-plugin-fulltext-search](https://github.com/leo-buneev/vuepress-plugin-fulltext-search) 全文搜索
* [vuepress-plugin-thirdparty-search](https://github.com/xugaoyi/vuepress-plugin-thirdparty-search) 可以添加第三方搜索链接的搜索框
* [vuepress-plugin-one-click-copy](https://www.npmjs.com/package/vuepress-plugin-one-click-copy) 代码块一键复制
* [vuepress-plugin-comment](https://github.com/dongyuanxin/vuepress-plugin-comment) 评论区
* [vuepress-plugin-vssue](https://vssue.js.org/) 评论区(单页)
* [vuepress-plugin-vssue-global](https://github.com/u2sb/vuepress-plugin-vssue-global) 评论区(全局)
* [vuepress-plugin-smplayer](https://github.com/u2sb/vuepress-plugin-smplayer) 播放器
* [vuepress-plugin-flowchart](https://www.npmjs.com/package/vuepress-plugin-flowchart) 流程图
* [vuepress-plugin-mathjax](https://www.npmjs.com/package/vuepress-plugin-mathjax) 数学公式
* [vuepress-plugin-tabs](https://www.npmjs.com/package/vuepress-plugin-tabs/) 选项卡
* [vuepress-plugin-element-ui](https://www.npmjs.com/package/vuepress-plugin-element-ui/) Element UI
* [花里胡哨的插件](https://moefyit.github.io/moefy-vuepress/) 鼠标点击特效、背景彩带、音乐播放器等花里胡哨的插件
**更多插件...**
- [Awesome VuePress](https://github.com/vuepressjs/awesome-vuepress)
- [在npm中搜索"vuepressplugin"](https://www.npmjs.com/search?q=vuepress%E2%80%93plugin)
## 社区优秀解决方案
### [1. 站点信息模块](https://notes.youngkbt.cn/about/website/info/)
在首页添加`站点信息模块`,效果:
![](https://fastly.jsdelivr.net/gh/Kele-Bingtang/static/img/%E5%85%B3%E4%BA%8E/%E5%85%B3%E4%BA%8E%E6%9C%AC%E7%AB%99/20220102230720.png)
在文章页添加`文章字数``阅读时间``浏览量`,效果:
![](https://fastly.jsdelivr.net/gh/Kele-Bingtang/static/img/%E5%85%B3%E4%BA%8E/%E5%85%B3%E4%BA%8E%E6%9C%AC%E7%AB%99/20220103180059.png)
### [2. 私密文章功能](https://notes.youngkbt.cn/about/website/private/)
当大家想要「云端备份」文章到博客时,又不希望别人看到,该功能能满足你。
### [3. 首页大图模块](https://notes.youngkbt.cn/about/website/index-big-img/)
喜欢首页大图模式的朋友可以参考此教程。
### [4. 优雅的全文搜索方案](https://wiki.eryajf.net/pages/dfc792/#%E5%89%8D%E8%A8%80)
## 文章管理和发布
使用 [此插件 src-sy-post-publisher](https://github.com/terwer/src-sy-post-publisher) 发布[思源笔记](https://b3log.org/siyuan/)的文章到Vuepress等平台。
## 图标&配图
说明:以下图标**非主题内置**,你可以选择喜欢的图标右键保存或到图标库下载。更多图标:[阿里图标库](https://www.iconfont.cn/home/index)
### 静态图标
<table class="icons-table">
<tbody>
<tr>
<td align="center" valign="middle">
<img src="/img/png/编程.png" width=60>
<p class="name">编程</p>
</td>
<td align="center" valign="middle">
<img src="/img/png/服务器.png" width=60>
<p class="name">服务器</p>
</td>
<td align="center" valign="middle">
<img src="/img/png/机器学习.png" width=60>
<p class="name">机器学习</p>
</td>
<td align="center" valign="middle">
<img src="/img/png/计算机网络.png" width=60>
<p class="name">计算机网络</p>
</td>
<td align="center" valign="middle">
<img src="/img/png/面向对象.png" width=60>
<p class="name">面向对象</p>
</td>
<td align="center" valign="middle">
<img src="/img/png/软件开发.png" width=60>
<p class="name">软件开发</p>
</td>
<td align="center" valign="middle">
<img src="/img/png/数据结构.png" width=60>
<p class="name">数据结构</p>
</td>
</tr><tr></tr>
<tr>
<td align="center" valign="middle">
<img src="/img/png/数据库.png" width=60>
<p class="name">数据库</p>
</td>
<td align="center" valign="middle">
<img src="/img/png/思维导图.png" width=60>
<p class="name">思维导图</p>
</td>
<td align="center" valign="middle">
<img src="/img/png/算法.png" width=60>
<p class="name">算法</p>
</td>
<td align="center" valign="middle">
<img src="/img/png/网络技术.png" width=60>
<p class="name">网络技术</p>
</td>
<td align="center" valign="middle">
<img src="/img/png/系统.png" width=60>
<p class="name">系统</p>
</td>
<td align="center" valign="middle">
<img src="/img/png/系统分析.png" width=60>
<p class="name">系统分析</p>
</td>
<td align="center" valign="middle">
<img src="/img/png/项目管理.png" width=60>
<p class="name">项目管理</p>
</td>
</tr><tr></tr>
</tbody>
</table>
<style>
.icons-table td{
padding: 1em;
}
.icons-table p.name{
font-size: 14px;
margin: 10px 0 0 0;
}
</style>
### 萌系图标
- [猫咪系列](https://www.iconfont.cn/collections/detail?spm=a313x.7781069.1998910419.dc64b3430&cid=37776)
![](https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/maomi.51n7h2qwlv00.webp)</br>
- [数码宝贝](https://www.iconfont.cn/collections/detail?spm=a313x.7781069.1998910419.dc64b3430&cid=38124)
![](https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/smbb.3h83bez4dka0.webp)
- [水果系列](https://www.iconfont.cn/collections/detail?spm=a313x.7781069.1998910419.dc64b3430&cid=38124)
![](https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/QQ20220122-122349@2x.4oqggy71iso0.webp)
### 动态图标
<table class="icons-table">
<tbody>
<tr>
<td align="center" valign="middle">
<img src="/img/gif/猫1.gif" width=60>
<p class="name">猫1</p>
</td>
<td align="center" valign="middle">
<img src="/img/gif/猫2.gif" width=60>
<p class="name">猫2</p>
</td>
<td align="center" valign="middle">
<img src="/img/gif/猫3.gif" width=60>
<p class="name">猫3</p>
</td>
<td align="center" valign="middle">
<img src="/img/gif/猫4.gif" width=60>
<p class="name">猫4</p>
</td>
</tr><tr></tr>
</tbody>
</table>
### 插画
[免费插画](https://undraw.co/illustrations)</br>
[2D/3D/手绘插画](https://storytale.io/)</br>
[阿里插画库](https://www.iconfont.cn/illustrations/index)</br>
### 配图
[可画-文章配图](https://www.canva.cn/)
### Logo
[logo生成1](https://www.designevo.com/logo-maker/)</br>
[logo生成2](https://instantlogodesign.com/)
### Emoji表情
[Emoji百科](https://emojipedia.org/)
::: tip 小技巧
在任意输入框快速打开emoji表情方法:</br>
Windows系统下按<kbd>Win</kbd> + <kbd>.</kbd></br>
Mac系统下按<kbd>Control</kbd> + <kbd>Command</kbd> + <kbd>空格</kbd>
:::
## 共享资源
如果您有不错的资源,欢迎在 [资源分享区](https://github.com/xugaoyi/vuepress-theme-vdoing/discussions/categories/%E8%B5%84%E6%BA%90%E5%88%86%E4%BA%AB) 留言。
<br/>
<br/>
<a href="http://apifox.cn/a103xugaoyi" target="_blank"><img src="/img/Apifox-860x320.png" alt="npm" class="no-zoom" style="width: 100%;border-radius: 2px;"></a>
@@ -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 -99
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,118 +135,171 @@ postList: none
你也可以在Issue查看别人提的问题和给出解决方案。
或者加入我们的交流群:
<table>
<tbody>
<tr>
<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>
<td align="center" valign="middle">
<img :src="$withBase('/img/qrcode/qqq.webp')" alt="群号: 694387113" class="no-zoom" style="width:120px;margin: 10px;">
<p>vdoing QQ群: 694387113</p>
</td>
</tr>
</tbody>
</table>
<br/>
## 🧬贡献代码的步骤
贡献代码注意事项:
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>
@@ -258,4 +322,32 @@ postList: none
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"
+18 -4
View File
@@ -7,11 +7,11 @@
<parent>
<groupId>org.noear</groupId>
<artifactId>solon-parent</artifactId>
<version>2.2.2</version>
<version>2.2.8</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>
@@ -104,6 +102,22 @@
<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>
+44
View File
@@ -0,0 +1,44 @@
#!/bin/sh
# 中微子代理管理后台编译打包脚本,基础参数请自行修改
#环境
env=dev
nvmDir=$HOME/.nvm
nodeVersion=v13.12.0
deployDir="deploy"
serverDeployDir=$deployDir"/server"
adminDeployDir=$serverDeployDir"/neutrino-proxy-admin"
giteePagesDir=$deployDir"/gitee-pages"
#设置nvm生效
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
# This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
#初始化文件夹
cd ../..
rm -rf $adminDeployDir
if [ ! -d "$deployDir" ];then
mkdir $deployDir
fi
if [ ! -d "$serverDeployDir" ];then
mkdir $serverDeployDir
fi
if [ ! -d "$adminDeployDir" ];then
mkdir $adminDeployDir
fi
#切node版本
nvm use $nodeVersion
#进入admin项目目录
cd ./neutrino-proxy-admin
#删除之前的build
rm -rf ./dist
#安装依赖
npm i
#编译
npm run build:$env
#拷贝
cd ..
cp -rf ./neutrino-proxy-admin/dist $adminDeployDir/
cp -rf ./neutrino-proxy-admin/dist/ $giteePagesDir
@@ -8,6 +8,7 @@ nodeVersion=v13.12.0
deployDir="deploy"
serverDeployDir=$deployDir"/server"
adminDeployDir=$serverDeployDir"/neutrino-proxy-admin"
giteePagesDir=$deployDir"/gitee-pages"
#设置nvm生效
export NVM_DIR="$HOME/.nvm"
@@ -16,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
@@ -39,4 +40,5 @@ npm i
npm run build:$env
#拷贝
cd ..
cp -rf ./neutrino-proxy-admin/dist $adminDeployDir
cp -rf ./neutrino-proxy-admin/dist $adminDeployDir/
cp -rf ./neutrino-proxy-admin/dist/ $giteePagesDir
@@ -9,15 +9,15 @@ deployDir="deploy"
clientDeployDir=$deployDir"/client"
#切到项目根目录
cd ..
cd ../..
#初始化文件夹
rm -rf $clientDeployDir
if [ ! -d "$deployDir" ];then
mkdir $deployDir
fi
if [ ! -d "$clientDeployDir" ];then
mkdir $clientDeployDir
fi
rm -rf $clientDeployDir/neutrino-proxy-client.jar
#客户端打包
mvn clean install -U -pl neutrino-proxy-client -am -Dmaven.test.skip=true
# 拷贝到deploy目录下
@@ -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,15 +9,16 @@ deployDir="deploy"
serverDeployDir=$deployDir"/server"
#切到项目根目录
cd ..
cd ../..
#初始化文件夹
rm -rf $serverDeployDir
if [ ! -d "$deployDir" ];then
mkdir $deployDir
fi
if [ ! -d "$serverDeployDir" ];then
mkdir $serverDeployDir
fi
rm -rf $serverDeployDir/neutrino-proxy-server.jar
#客户端打包
mvn clean install -U -pl neutrino-proxy-server -am -Dmaven.test.skip=true
# 拷贝到deploy目录下
+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%
+22 -46
View File
@@ -1,51 +1,27 @@
# 功能点
- 用户流量报表
- 弹框展示月度明细
- 弹框展示今日流量明细
- License流量报表
- 弹框展示月度明细
- 弹框展示今日流量明细
- 首页图表📈
- 1、License在线数
- 2、端口映射在线数
- 3、今日流量(上行、下行)
- 4、历史流量(上行、下行)
- 点击14 切换列表
- 在线License列表
- 用户名
- License名称
- LicenseKey
- 在线端口映射列表
- 用户名
- License名称
- 服务端口
- 代理客户端
- 今日24小时流量列表(按小时到排序)
- 用户名
- License名称
- 时间
- 流量
- 历史流量列表(取最近12个月按月到排序)
- 用户名
- License名称
- 时间
- 流量
- 今日流量折线图(上行、下行、总流量,按分钟统计0~24小时)
# Bug
- 指令通达被close的问题,org.dromara.neutrinoproxy.server.proxy.core.ProxyTunnelChannelHandler.channelInactive
- windows环境下直接运行发布版的jar包,日志输出乱码
- 部份用户windows环境下启动客户端,扫描类个数为0个
- 代理mysql时,使用未开启远程访问的账号走代理访问mysql,代理客户端出现断开现象
- 客户端连接映射某个端口以后,如果在服务器端禁用了,没有立刻反映出来,要灯客户端重连以后才会屏蔽被禁用的端口。
# 2.x规划
- 全面重构:底层更换为Solon + Mybatis Plus
- 规范协议:代理协议规范化,方便后续更好扩展、支持不同语言客户端接入
- 端口池优化:支持为license设置独占端口。方便后续开发jetbrains插件、solon插件
- 精细化控制:支持针对用户限速、限流
- 参考鹊桥,增加对域名的支持
- MGT增加服务端维护
- 支持调整服务端端口
- 支持重启
- 支持查看服务端日志
- 支持证书上传下载
- 支持客户端版本管理
- 支持原生编译
- 插件开发
- [ ] neutrino-proxy-solon-plugin
- [ ] neutrino-proxy-jetbrains-plugin
- [ ] neutrino-proxy-starter (SpringBoot)
- 基础优化
- [ ] 支持批量端口映射
- [ ] 代理协议规范化,方便后续更好扩展、支持不同语言客户端接入
- 监控运维
- [ ] 监控服务端状态
- [ ] 支持调整服务端端口、证书
- [ ] 服务端版本、协议版本、客户端版本管理
- [ ] 服务端日志
- client+计划启动
- [ ] 安卓客户端
- [ ] 基于electron-egg的多平台客户端
# 3.x规划
- [ ] 支持针对用户限速、限流
- [ ] 支持P2P穿透
- [ ] 支持原生编译