Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bcf984e191 | ||
|
|
06ad9b6782 | ||
|
|
a90d5c5ce7 | ||
|
|
7ce0439ab2 | ||
|
|
a52b3dab51 | ||
|
|
8e97625327 | ||
|
|
fce65d1dfe | ||
|
|
90487d5540 | ||
|
|
e7b2c17a35 | ||
|
|
aa48af86a6 | ||
|
|
933b80b2ff | ||
|
|
51a172fd88 | ||
|
|
f117ae22e7 | ||
|
|
2b9c942b5b | ||
|
|
235541364e | ||
|
|
36fdf4605a | ||
|
|
5bd7fc8354 | ||
|
|
f1f4f455bc | ||
|
|
b36b131aa7 | ||
|
|
c97804a72e | ||
|
|
f7735345f0 | ||
|
|
e6a9e16a65 | ||
|
|
a7d02e9976 | ||
|
|
aab60fc5e2 | ||
|
|
9aa0d362d1 | ||
|
|
beb3d4b3a6 | ||
|
|
3f71e2790b | ||
|
|
417a53fec5 | ||
|
|
42886aef91 | ||
|
|
eecd5d9d06 | ||
|
|
37ee8021be | ||
|
|
796cf7b18e | ||
|
|
82b5e2cfd3 | ||
|
|
65a1fb5222 | ||
|
|
955c054897 | ||
|
|
c3c04a1720 | ||
|
|
3b8b871811 | ||
|
|
43f0876e61 | ||
|
|
228e49c418 | ||
|
|
8adcdb4a2d | ||
|
|
0b934b6b83 | ||
|
|
2b8ee080f8 | ||
|
|
8f2cf557db | ||
|
|
cfd079279f | ||
|
|
461805e652 | ||
|
|
98ca2bed97 | ||
|
|
27b2c82b9f | ||
|
|
508a53b931 | ||
|
|
add31f55d5 | ||
|
|
8ba617c724 | ||
|
|
915f667a56 | ||
|
|
11e60d4da9 | ||
|
|
d12946c937 | ||
|
|
324da0bb11 | ||
|
|
2dc8f3c141 | ||
|
|
826082b35b | ||
|
|
1feb1d4132 | ||
|
|
aaa06de7a4 | ||
|
|
ee6467d507 | ||
|
|
083d7b13f2 | ||
|
|
128308fe9a | ||
|
|
af7e9374d5 | ||
|
|
0c96aa8bfd | ||
|
|
ab7f27f891 | ||
|
|
24afa23707 | ||
|
|
c58e040a80 | ||
|
|
e17ae87ba2 | ||
|
|
cc69dde5f1 | ||
|
|
155eeb2141 | ||
|
|
781d308c09 | ||
|
|
1a3da96267 | ||
|
|
29b03f4b1a | ||
|
|
6e4033f45d | ||
|
|
dd3656e559 | ||
|
|
110f298176 | ||
|
|
f8bfe87eea | ||
|
|
1bd8fabce6 | ||
|
|
be00799276 | ||
|
|
f893da84a5 | ||
|
|
c8101ed91c | ||
|
|
85afeae3db | ||
|
|
ae345a6161 | ||
|
|
aacec48ad6 | ||
|
|
60b1dec9b2 | ||
|
|
a227919017 | ||
|
|
96648ea7bc | ||
|
|
507d4ebb04 | ||
|
|
c2ccb9d2c7 | ||
|
|
09424a7f07 | ||
|
|
44909e20b5 | ||
|
|
639d7d1707 | ||
|
|
8033666a70 | ||
|
|
98f855505d | ||
|
|
c79d2937cc | ||
|
|
d24ef43f45 | ||
|
|
04841dcbd9 | ||
|
|
de59752bb9 | ||
|
|
f378f9e744 | ||
|
|
13128e0a9e | ||
|
|
a212056a46 | ||
|
|
2f841ad7a5 | ||
|
|
aadee74d8e | ||
|
|
8fce7d1677 | ||
|
|
4ebc53b755 | ||
|
|
5f3cd8f60e | ||
|
|
f75a1fa57b | ||
|
|
59f8c9c042 | ||
|
|
41a9ded54a | ||
|
|
b13dbc687b | ||
|
|
92aa25940c | ||
|
|
df2ced99a0 | ||
|
|
540bedb809 | ||
|
|
7e9a4d5fad | ||
|
|
32f5e2397c | ||
|
|
bbee153491 | ||
|
|
8626917441 | ||
|
|
58504d8b49 | ||
|
|
4b8ec0b33b | ||
|
|
f71055b6ea | ||
|
|
8f46518b06 | ||
|
|
81e1efd296 | ||
|
|
87d6e0a07d | ||
|
|
7db85e6379 | ||
|
|
81b9dcf6c4 | ||
|
|
4d4aeaf8fc | ||
|
|
80573ee718 | ||
|
|
ded5e2f927 | ||
|
|
f43f46fe9e | ||
|
|
d00346a376 | ||
|
|
033196d955 | ||
|
|
47be82ba95 | ||
|
|
7fa4d6d8fa | ||
|
|
855789c5e4 | ||
|
|
1d729ba35a | ||
|
|
bed244b7cc | ||
|
|
75c7d8a4f8 | ||
|
|
d4e1d64f6e | ||
|
|
6e8a804368 |
@@ -3,7 +3,9 @@
|
||||
*.class
|
||||
data.db*
|
||||
.neutrino-proxy.license
|
||||
.neutrino-proxy-client.json*
|
||||
lib/*
|
||||
SensitiveInformation.txt
|
||||
|
||||
|
||||
# Log file
|
||||
@@ -46,3 +48,4 @@ hs_err_pid*
|
||||
**yarn-error.log*
|
||||
**/*.log
|
||||
**/gifs/
|
||||
**/deploy/
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
<p align="center">
|
||||
<img src="https://i.imgtg.com/2023/02/10/crIMY.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>
|
||||
</p>
|
||||
|
||||
# 1、简介
|
||||
中微子代理(neutrino-proxy)是一个基于netty的、开源的java内网穿透项目。遵循MIT许可,因此您可以对它进行复制、修改、传播并用于任何个人或商业行为。
|
||||
|
||||
@@ -99,20 +107,33 @@ proxy:
|
||||
默认情况下,客户端会加载当前目录下的.neutrino-proxy.license里的license,可通过命令行参数指定,如:java -jar neutrino-proxy-client.jar license=xxx
|
||||
若启动参数未指定license,且是首次启动(当前目录下未缓存license),则需要根据命令行提示输入正确的license, 输入完成后完成连接,可在服务端管理页面控制端口转发,参见[2、运行示例](#2)
|
||||
|
||||
# 6、未来迭代方向
|
||||
## 5.6、Docker快速启动
|
||||
```shell script
|
||||
# 一键部署前端和后端,客户端按上方部署即可
|
||||
docker run -it -p 9000-9200:9000-9200/tcp -p 8888:8888 -d --name neutrino registry.cn-hangzhou.aliyuncs.com/asgc/aoshiguchen-docker-images:1.64
|
||||
# 如没docker环境,可以一键部署docker环境(centos 7.8)系统
|
||||
curl http://www.wangke666.cn/static/createDocker.sh | bash
|
||||
```
|
||||
|
||||
# 6、演示环境
|
||||
> 可使用分配好的游客license试用。服务器带宽较低,仅供学习使用!
|
||||
- 管理后台地址:http://103.163.47.16:9527
|
||||
- 游客账号:visitor/123456
|
||||
|
||||
# 7、未来迭代方向
|
||||
- 优化代码、增强稳定性
|
||||
- 服务端增加管理页面,提供报表、授权、限流等功能
|
||||
- 从项目中分离、孵化出另一个开源项目(neutrino-framework)
|
||||
|
||||
# 7、技术文档
|
||||
# 8、技术文档
|
||||
- [Aop](./docs/Aop.MD)
|
||||
- [Channel](./docs/Channel.MD)
|
||||
|
||||
# 8、联系我们
|
||||
# 9、联系我们
|
||||
- 微信: yuyunshize
|
||||
- Gitee(主更): https://gitee.com/asgc/neutrino-proxy
|
||||
- Github: https://github.com/aoshiguchen/neutrino-proxy
|
||||
- Gitee: https://gitee.com/asgc/neutrino-proxy
|
||||
|
||||
# 9、特别鸣谢
|
||||
# 10、特别鸣谢
|
||||
* [JetBrains](https://www.jetbrains.com?from=RedisFront)
|
||||
|
||||

|
||||
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="_图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 240">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #ff9045;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #333;
|
||||
}
|
||||
|
||||
.cls-3 {
|
||||
fill: #0079dd;
|
||||
}
|
||||
|
||||
.cls-4 {
|
||||
fill: #595959;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g>
|
||||
<g>
|
||||
<circle class="cls-1" cx="145.28" cy="62.13" r="13.63"/>
|
||||
<path class="cls-1" d="M154.29,83.78h-18.09c-4.56,0-8.26,3.7-8.26,8.26v92.19c0,4.02,3.26,7.28,7.28,7.28h.07c4.02,0,7.28-3.26,7.28-7.28v-54.05c2.96,0,5.36,2.4,5.36,5.36v16.41c0,4.02,3.26,7.28,7.28,7.28h.07c4.02,0,7.28-3.26,7.28-7.28v-21.16s0-38.75,0-38.75c0-4.56-3.7-8.26-8.26-8.26Z"/>
|
||||
</g>
|
||||
<path class="cls-3" d="M117.19,125.56h-1.98c-3.07,0-5.56-2.49-5.56-5.56s2.49-5.56,5.56-5.56h1.98s.04,0,.06,0v-25.71c0-4.77-2.32-9.24-6.21-11.98L50.81,34.3c-4.86-3.42-11.55,.05-11.55,5.99V199.71c0,5.94,6.7,9.41,11.55,5.99l60.23-42.45c3.9-2.75,6.21-7.22,6.21-11.98v-25.71s-.04,0-.06,0Z"/>
|
||||
<path class="cls-3" d="M239.76,34.3l-60.23,42.45c-3.9,2.75-6.21,7.22-6.21,11.98v25.71s.04,0,.06,0h1.98c3.07,0,5.56,2.49,5.56,5.56s-2.49,5.56-5.56,5.56h-1.98s-.04,0-.06,0v25.71c0,4.77,2.32,9.24,6.21,11.98l60.23,42.45c4.86,3.42,11.55-.05,11.55-5.99V40.29c0-5.94-6.7-9.41-11.55-5.99Z"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="cls-2" d="M344.63,60.54v-7.61h8.13v7.61h20.8c4.41,0,7.8,.88,10.16,2.64,3.06,2.31,4.58,5.45,4.58,9.43l-.04,32.47c0,3.86-.78,6.82-2.34,8.87-2.42,3.2-6.01,4.8-10.77,4.8h-22.4l.04,7.52-8.17-.04v-7.48h-20.54c-4.15,0-7.24-.59-9.25-1.77-3.66-2.13-5.49-6.18-5.49-12.15v-30.57c0-4.44,1.12-7.77,3.37-9.99,2.51-2.48,5.89-3.72,10.16-3.72h21.75Zm0,50.07v-41.94h-22.01c-2.05,0-3.47,.55-4.28,1.64-.58,.81-.87,2.08-.87,3.8v30.05c0,1.96,.07,3.13,.22,3.5,.72,1.99,2.31,2.98,4.76,2.98l22.18-.04Zm8.13,0l20.93,.04c4.32,.03,6.49-1.79,6.49-5.45v-30.48c0-2.48-.49-4.11-1.47-4.89-.92-.72-2.48-1.1-4.67-1.12l-21.27-.09v41.99Z"/>
|
||||
<path class="cls-2" d="M401.72,75.41v-8.78c2.13-1.73,3.62-3.34,4.45-4.84,1.12-1.93,2-4.94,2.64-9.04h8.13c-.46,2.19-.79,3.84-.99,4.93-.81,4.12-2.12,7.46-3.93,10.03-1.87,2.62-4.08,4.64-6.62,6.05-.81,.43-2.03,.98-3.67,1.64Zm0,20.67v-8.86c2.36-1.9,3.91-3.56,4.63-4.97,.89-1.67,1.7-4.63,2.42-8.86h8.17l-.91,4.15v48.86h-8.09v-33.77c-1.93,1.44-4.01,2.59-6.23,3.46Zm17.25,30.31v-8.47c1.35-.78,2.03-1.9,2.03-3.37v-10.59c0-2.6,.88-4.79,2.64-6.57,1.59-1.58,4.53-2.38,8.82-2.38h6.87c6.72,0,10.08,3.19,10.08,9.56v8.86c0,1.73,.23,2.87,.69,3.42,.26,.32,.72,.68,1.38,1.08v8.47c-2.88-.43-5.16-1.3-6.83-2.59-.87-.66-1.59-1.56-2.16-2.68-.81-1.53-1.21-3.07-1.21-4.63v-9.95c0-2.39-1.47-3.59-4.41-3.59h-3.5c-1.47,0-2.51,.32-3.11,.95-.81,.81-1.21,2.05-1.21,3.72v8.87c0,2.79-.88,5.07-2.64,6.83-1.39,1.41-3.86,2.44-7.44,3.07Zm.91-73.68h7.22v15.52c0,1.21,.23,2.02,.69,2.42,.52,.43,1.41,.65,2.68,.65h1.08v-18.59h7.74v18.59h1.12c1.07,0,1.84-.29,2.34-.86s.74-1.17,.74-1.77v-15.96h7.05v17.99c0,3.08-1.11,5.35-3.33,6.79-1.21,.78-3.37,1.17-6.49,1.17h-11.68c-3.2,0-5.59-.81-7.18-2.42-.69-.72-1.14-1.37-1.34-1.95-.43-1.21-.65-3.01-.65-5.4v-16.17Zm0,38.18v-7.22h30.66v7.22h-30.66Zm33.12,35.11v-7.74c2.36-.92,4.08-1.72,5.15-2.38,1.1-.69,2.62-1.92,4.58-3.67-2.02-3.83-3.39-7.16-4.11-9.99-.89-3.34-1.34-6.76-1.34-10.25v-14.87c-1.12,.75-2.45,1.37-3.98,1.86v-8.39c1.38-1.53,2.35-3.56,2.9-6.1,.63-3.08,1.08-7,1.34-11.76h7.26v4.89h17.08v8h-4.2v27.46c0,6.05-1.83,12.44-5.49,19.16,1.44,1.38,2.61,2.41,3.5,3.07,1.27,.89,3.33,1.89,6.18,2.98v7.74c-3.98-1.1-6.99-2.28-9.04-3.55-1.5-.89-3.27-2.29-5.32-4.19-3.08,2.54-5.49,4.24-7.22,5.1-1.53,.81-3.96,1.69-7.31,2.64Zm4.67-49.12h6.66l-.13,12.8c-.03,4.56,1.05,9.7,3.24,15.44,2.02-4.44,3.03-8.62,3.03-12.54v-26.98h-6.74c-1.15,4.24-2.38,7.18-3.68,8.82-.52,.69-1.31,1.51-2.38,2.47Z"/>
|
||||
<path class="cls-2" d="M494.58,91.89v-8h41.21l21.32-17.81c1.21-1.01,1.82-1.9,1.82-2.68,0-.46-.16-.89-.47-1.3-.66-.83-1.75-1.25-3.24-1.25h-56.56v-8h58.98c3.6,0,6.36,.68,8.26,2.03,2.36,1.67,3.55,3.99,3.55,6.96,0,2.56-1.04,4.97-3.11,7.22-1.21,1.3-2.8,2.75-4.76,4.37l-12.93,10.46h26.12v8h-29.45l.04,21.53c0,3.92-1.21,7.1-3.63,9.56-2.25,2.31-5.61,3.46-10.07,3.46h-16.73v-8.04h15.52c2.45,.03,4.12-.47,5.01-1.51,.81-.95,1.21-2.39,1.21-4.33v-20.71l-42.07,.04Z"/>
|
||||
<path class="cls-2" d="M587.86,86.78v-10.29c3-2.13,5.17-4.28,6.53-6.44,2.88-4.58,4.96-10.29,6.23-17.12h9.08c-1.24,6.26-2.96,11.93-5.15,17.04v56.38h-8.3v-44.88c-2.02,1.62-3.34,2.61-3.98,2.98-.61,.4-2.08,1.18-4.41,2.34Zm21.02-8.78v-8.13h19.46l-5.06-17.08h8.69l4.8,17.08h30.61v8.13h-28.32l5.1,18.81c1.01,3.72,2.31,7.28,3.89,10.68,1.47,3.14,3.66,5.69,6.57,7.65,3.29,2.22,6.69,3.33,10.2,3.33,.46,0,1.17-.04,2.12-.13v8.09c-4.7-.09-8.53-.68-11.5-1.77-5.51-2.07-9.71-5.65-12.63-10.72-1.87-3.23-3.72-7.91-5.53-14.05l-6.53-21.88h-21.88Zm40.99-16.78v-8h17.51v8h-17.51Z"/>
|
||||
<path class="cls-2" d="M680.84,124.06v-8h7.05v-24.95l-7.09,.04,.04-7.87h7.05v-20.41h-7.05v-8h22.4v8h-7.35v20.41h7.35v7.83h-7.35v24.95h7.35v8h-22.4Zm26.59,2.2v-8h22.92v-9.25h-20.76v-8h20.76v-7.05l-14.01-.04c-2.48,0-4.44-.88-5.88-2.64-.89-1.12-1.41-2.31-1.56-3.55-.09-.72-.13-1.7-.13-2.94v-23.44c0-2.91,.74-5.1,2.2-6.57,1.38-1.38,3.24-2.08,5.58-2.08h33.25c3.52,0,6.04,.72,7.57,2.16,1.33,1.27,1.99,3.17,1.99,5.71v25.47c0,2.42-.71,4.35-2.12,5.79-1.35,1.38-3.06,2.07-5.1,2.07l-13.79,.04v7.05h20.8v8h-20.8v9.25h22.4v8h-53.31Zm9.34-56.99h13.58v-8.56h-10.25c-1.21,0-2.11,.29-2.68,.86-.43,.43-.65,1.25-.65,2.47v5.23Zm13.58,16.56v-8.6h-13.58v4.71c0,1.56,.23,2.6,.69,3.11,.43,.52,1.24,.78,2.42,.78h10.46Zm8-16.56h13.19v-5.23c.03-2.28-1.41-3.42-4.32-3.42l-8.86,.09v8.56Zm0,16.56h9.64c1.41,0,2.35-.26,2.81-.78,.52-.58,.76-1.54,.74-2.9v-4.93h-13.19v8.6Z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="cls-4" d="M315.77,154.13l16.1,23.16h.12v-23.16h3.27v28.46h-3.35l-16.18-23.4h-.12v23.4h-3.27v-28.46h3.43Z"/>
|
||||
<path class="cls-4" d="M370.12,164.77c1.51,1.99,2.31,4.82,2.39,8.41h-16.26c.16,2.31,.76,4.11,1.87,5.38,1.12,1.28,2.67,1.91,4.63,1.91,1.67,0,3.07-.44,4.1-1.28,.88-.72,1.56-1.79,2.07-3.23h3.19c-.48,1.99-1.39,3.59-2.79,4.86-1.71,1.52-3.91,2.31-6.58,2.31-2.95,0-5.34-1-7.1-2.91-1.83-1.99-2.71-4.62-2.71-7.97,0-3.03,.84-5.58,2.59-7.61,1.75-2.15,4.11-3.23,7.1-3.23,3.31,0,5.82,1.12,7.49,3.35Zm-11.84,1.12c-1.12,1.2-1.76,2.79-1.95,4.82h12.88c-.4-4.42-2.59-6.62-6.5-6.62-1.83,0-3.31,.6-4.42,1.79Z"/>
|
||||
<path class="cls-4" d="M392.99,161.98v12.6c0,1.95,.4,3.43,1.24,4.38,.84,.96,2.19,1.48,4.07,1.48,1.35,0,2.59-.56,3.67-1.59,1.12-1.12,1.79-2.55,2.03-4.3v-12.56h3.19v20.61h-3.19v-3.19c-1.68,2.47-3.91,3.75-6.7,3.75-5.02,0-7.5-2.83-7.5-8.41v-12.76h3.19Z"/>
|
||||
<path class="cls-4" d="M431.17,161.98h5.1v2.67h-5.1v13.11c0,.76,.12,1.31,.44,1.63,.28,.32,.8,.52,1.51,.52h2.63v2.67h-3.11c-1.67,0-2.91-.44-3.63-1.31-.68-.8-1-1.95-1-3.51v-13.11h-4.15v-2.67h4.15v-5.34l3.15-1.31v6.66Z"/>
|
||||
<path class="cls-4" d="M465.24,161.9v3.19c-1.04-.32-2.07-.44-3.07-.44-1.59,0-2.95,.68-4.03,2.11-1.12,1.35-1.64,2.99-1.64,4.9v10.92h-3.15v-20.61h3.15v3.59c.56-1.19,1.28-2.15,2.15-2.83,1.08-.88,2.31-1.32,3.75-1.32,1.12,0,2.07,.16,2.83,.48Z"/>
|
||||
<path class="cls-4" d="M485.8,154.88c.48,.4,.72,.96,.72,1.68,0,.68-.24,1.24-.72,1.71-.48,.44-1.04,.68-1.71,.68s-1.24-.24-1.71-.68c-.48-.48-.68-1.04-.68-1.71,0-.72,.2-1.28,.68-1.68,.48-.48,1.04-.68,1.71-.68s1.24,.2,1.71,.68Zm-.12,7.1v20.61h-3.19v-20.61h3.19Z"/>
|
||||
<path class="cls-4" d="M522.54,169.91v12.67h-3.19v-12.44c0-4.03-1.83-6.02-5.42-6.02-1.43,0-2.67,.52-3.71,1.56-1.12,1.12-1.71,2.59-1.83,4.46v12.44h-3.19v-20.61h3.19v3.03c.72-1.16,1.64-2.03,2.75-2.63,1.12-.64,2.35-.96,3.71-.96,5.1,0,7.69,2.83,7.69,8.49Z"/>
|
||||
<path class="cls-4" d="M557.61,164.61c1.75,2.03,2.63,4.58,2.63,7.69s-.88,5.62-2.59,7.61c-1.87,2.15-4.35,3.23-7.41,3.23s-5.54-1.08-7.37-3.23c-1.75-1.99-2.63-4.54-2.63-7.61s.88-5.66,2.63-7.69c1.83-2.15,4.27-3.19,7.37-3.19s5.54,1.04,7.38,3.19Zm-12.48,1.99c-1.12,1.51-1.63,3.39-1.63,5.7s.52,4.15,1.63,5.66c1.2,1.68,2.91,2.51,5.1,2.51s3.87-.84,5.14-2.51c1.07-1.51,1.63-3.43,1.63-5.66s-.56-4.19-1.63-5.7c-1.28-1.68-2.99-2.51-5.14-2.51s-3.91,.84-5.1,2.51Z"/>
|
||||
<path class="cls-4" d="M616.79,154.13c6.54,0,9.8,2.79,9.8,8.37s-3.31,8.45-9.84,8.45h-8.33v11.64h-3.27v-28.46h11.64Zm-8.37,13.99h8.17c2.31,0,3.99-.48,5.1-1.39,1.07-.92,1.63-2.31,1.63-4.23s-.56-3.31-1.67-4.15c-1.12-.96-2.79-1.4-5.06-1.4h-8.17v11.16Z"/>
|
||||
<path class="cls-4" d="M655.76,161.9v3.19c-1.04-.32-2.07-.44-3.07-.44-1.59,0-2.95,.68-4.03,2.11-1.11,1.35-1.63,2.99-1.63,4.9v10.92h-3.15v-20.61h3.15v3.59c.56-1.19,1.28-2.15,2.15-2.83,1.07-.88,2.31-1.32,3.75-1.32,1.12,0,2.07,.16,2.83,.48Z"/>
|
||||
<path class="cls-4" d="M688.55,164.61c1.76,2.03,2.63,4.58,2.63,7.69s-.88,5.62-2.59,7.61c-1.87,2.15-4.34,3.23-7.41,3.23s-5.54-1.08-7.37-3.23c-1.75-1.99-2.63-4.54-2.63-7.61s.88-5.66,2.63-7.69c1.83-2.15,4.27-3.19,7.37-3.19s5.54,1.04,7.37,3.19Zm-12.48,1.99c-1.12,1.51-1.63,3.39-1.63,5.7s.52,4.15,1.63,5.66c1.2,1.68,2.91,2.51,5.1,2.51s3.87-.84,5.14-2.51c1.08-1.51,1.64-3.43,1.64-5.66s-.56-4.19-1.64-5.7c-1.28-1.68-2.99-2.51-5.14-2.51s-3.91,.84-5.1,2.51Z"/>
|
||||
<path class="cls-4" d="M711.23,161.98l5.34,7.33,5.3-7.33h3.75l-7.29,9.69,8.17,10.92h-3.79l-6.14-8.57-6.18,8.57h-3.79l8.17-10.92-7.29-9.69h3.75Z"/>
|
||||
<path class="cls-4" d="M743.9,161.98l6.3,16.26,6.1-16.26h3.51l-11.28,28.5h-3.47l3.55-8.37-8.17-20.13h3.47Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 19 KiB |
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="_图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 480">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #ff9045;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #333;
|
||||
}
|
||||
|
||||
.cls-3 {
|
||||
fill: #0079dd;
|
||||
}
|
||||
|
||||
.cls-4 {
|
||||
fill: #595959;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g>
|
||||
<circle class="cls-1" cx="243.67" cy="69.33" r="17.83"/>
|
||||
<path class="cls-1" d="M255.44,97.63h-23.66c-5.96,0-10.79,4.83-10.79,10.79v120.54c0,5.25,4.26,9.51,9.51,9.51h.09c5.25,0,9.51-4.26,9.51-9.51v-70.67c3.87,0,7.01,3.14,7.01,7.01v21.45c0,5.25,4.26,9.51,9.51,9.51h.09c5.25,0,9.51-4.26,9.51-9.51v-27.67s0-50.66,0-50.66c0-5.96-4.83-10.79-10.79-10.79Z"/>
|
||||
</g>
|
||||
<path class="cls-3" d="M206.94,152.26h-2.59c-4.01,0-7.27-3.25-7.27-7.27s3.25-7.27,7.27-7.27h2.59s.05,0,.08,0v-33.61c0-6.23-3.03-12.08-8.12-15.67L120.15,32.94c-6.35-4.47-15.1,.07-15.1,7.83V249.2c0,7.77,8.76,12.31,15.1,7.83l78.75-55.5c5.09-3.59,8.12-9.43,8.12-15.67v-33.61s-.05,0-.08,0Z"/>
|
||||
<path class="cls-3" d="M367.19,32.94l-78.75,55.5c-5.09,3.59-8.12,9.43-8.12,15.67v33.61s.05,0,.08,0h2.59c4.01,0,7.27,3.25,7.27,7.27s-3.25,7.27-7.27,7.27h-2.59s-.05,0-.08,0v33.61c0,6.23,3.03,12.08,8.12,15.67l78.75,55.5c6.35,4.47,15.1-.07,15.1-7.83V40.77c0-7.77-8.75-12.31-15.1-7.83Z"/>
|
||||
<g>
|
||||
<path class="cls-2" d="M52.96,324.39v-7.48h7.98v7.48h20.43c4.33,0,7.66,.86,9.98,2.59,3,2.26,4.5,5.35,4.5,9.26l-.04,31.9c0,3.79-.76,6.7-2.29,8.71-2.38,3.14-5.9,4.71-10.58,4.71h-22l.05,7.39-8.03-.04v-7.35h-20.18c-4.08,0-7.11-.58-9.09-1.74-3.6-2.1-5.39-6.07-5.39-11.93v-30.03c0-4.36,1.1-7.63,3.31-9.81,2.47-2.44,5.79-3.65,9.98-3.65h21.36Zm0,49.18v-41.2H31.35c-2.01,0-3.41,.54-4.21,1.61-.56,.79-.85,2.04-.85,3.74v29.52c0,1.93,.07,3.07,.21,3.44,.71,1.95,2.26,2.93,4.67,2.93l21.79-.04Zm7.98,0l20.56,.04c4.25,.03,6.37-1.76,6.37-5.35v-29.94c0-2.44-.48-4.03-1.44-4.8-.91-.71-2.44-1.07-4.59-1.1l-20.9-.09v41.24Z"/>
|
||||
<path class="cls-2" d="M109.04,339v-8.62c2.09-1.7,3.55-3.28,4.37-4.76,1.1-1.9,1.97-4.86,2.59-8.88h7.98c-.45,2.15-.78,3.77-.98,4.84-.79,4.05-2.08,7.33-3.86,9.85-1.84,2.58-4.01,4.56-6.5,5.95-.79,.42-2,.96-3.61,1.61Zm0,20.3v-8.71c2.32-1.87,3.83-3.5,4.54-4.88,.88-1.64,1.67-4.55,2.38-8.71h8.02l-.89,4.08v47.99h-7.94v-33.17c-1.9,1.42-3.94,2.55-6.11,3.4Zm16.95,29.77v-8.32c1.33-.76,2-1.87,2-3.31v-10.41c0-2.55,.86-4.7,2.59-6.46,1.56-1.56,4.45-2.34,8.66-2.34h6.75c6.6,0,9.9,3.13,9.9,9.39v8.71c0,1.7,.23,2.82,.68,3.35,.26,.31,.71,.67,1.36,1.06v8.32c-2.83-.42-5.07-1.27-6.71-2.55-.85-.65-1.56-1.53-2.12-2.63-.79-1.5-1.19-3.02-1.19-4.55v-9.77c0-2.35-1.44-3.53-4.33-3.53h-3.44c-1.45,0-2.47,.31-3.06,.94-.79,.79-1.19,2.01-1.19,3.65v8.71c0,2.75-.86,4.98-2.59,6.71-1.36,1.39-3.79,2.39-7.3,3.02Zm.89-72.37h7.09v15.25c0,1.19,.23,1.98,.68,2.38,.51,.42,1.39,.64,2.63,.64h1.06v-18.26h7.6v18.26h1.1c1.05,0,1.81-.28,2.3-.85,.48-.57,.72-1.15,.72-1.74v-15.67h6.92v17.67c0,3.03-1.09,5.25-3.27,6.67-1.19,.76-3.31,1.15-6.37,1.15h-11.47c-3.14,0-5.49-.79-7.05-2.38-.68-.71-1.12-1.34-1.32-1.91-.42-1.19-.64-2.96-.64-5.31v-15.88Zm0,37.5v-7.09h30.11v7.09h-30.11Zm32.53,34.49v-7.6c2.32-.91,4.01-1.68,5.06-2.34,1.07-.68,2.57-1.88,4.5-3.61-1.98-3.77-3.33-7.04-4.03-9.81-.88-3.28-1.32-6.64-1.32-10.07v-14.61c-1.1,.74-2.41,1.35-3.91,1.83v-8.24c1.36-1.5,2.31-3.5,2.85-5.99,.62-3.03,1.06-6.88,1.32-11.55h7.14v4.8h16.77v7.86h-4.12v26.97c0,5.95-1.8,12.22-5.39,18.81,1.42,1.36,2.56,2.36,3.44,3.02,1.24,.88,3.27,1.85,6.07,2.93v7.6c-3.9-1.07-6.86-2.24-8.87-3.48-1.47-.88-3.21-2.25-5.22-4.12-3.03,2.49-5.39,4.16-7.09,5.01-1.5,.79-3.89,1.66-7.18,2.59Zm4.59-48.25h6.54l-.13,12.57c-.03,4.47,1.03,9.53,3.18,15.16,1.98-4.36,2.97-8.47,2.97-12.32v-26.5h-6.63c-1.13,4.16-2.33,7.05-3.61,8.66-.51,.68-1.29,1.49-2.34,2.42Z"/>
|
||||
<path class="cls-2" d="M200.25,355.18v-7.86h40.47l20.94-17.5c1.19-.99,1.78-1.87,1.78-2.63,0-.45-.15-.88-.47-1.27-.65-.82-1.71-1.23-3.19-1.23h-55.55v-7.86h57.93c3.54,0,6.24,.67,8.11,2,2.32,1.64,3.48,3.92,3.48,6.84,0,2.52-1.02,4.88-3.06,7.09-1.19,1.27-2.75,2.7-4.68,4.29l-12.7,10.28h25.65v7.86h-28.92l.04,21.15c0,3.85-1.19,6.98-3.57,9.38-2.21,2.27-5.51,3.4-9.9,3.4h-16.44v-7.9h15.25c2.41,.03,4.05-.47,4.93-1.49,.79-.93,1.19-2.35,1.19-4.25v-20.34l-41.32,.04Z"/>
|
||||
<path class="cls-2" d="M291.87,350.17v-10.11c2.95-2.1,5.09-4.21,6.42-6.33,2.83-4.5,4.87-10.11,6.11-16.82h8.92c-1.22,6.14-2.9,11.72-5.06,16.73v55.38h-8.15v-44.08c-1.98,1.59-3.28,2.56-3.91,2.93-.59,.4-2.04,1.16-4.33,2.29Zm20.64-8.62v-7.98h19.11l-4.97-16.78h8.54l4.71,16.78h30.07v7.98h-27.82l5.01,18.47c.99,3.65,2.27,7.15,3.82,10.49,1.45,3.09,3.6,5.59,6.46,7.52,3.23,2.18,6.57,3.27,10.02,3.27,.45,0,1.15-.04,2.08-.13v7.94c-4.62-.08-8.38-.67-11.3-1.74-5.41-2.04-9.54-5.55-12.4-10.53-1.84-3.17-3.65-7.77-5.43-13.8l-6.42-21.49h-21.49Zm40.26-16.48v-7.86h17.2v7.86h-17.2Z"/>
|
||||
<path class="cls-2" d="M383.2,386.78v-7.86h6.92v-24.5l-6.96,.04,.04-7.73h6.92v-20.05h-6.92v-7.86h22v7.86h-7.22v20.05h7.22v7.69h-7.22v24.5h7.22v7.86h-22Zm26.12,2.17v-7.86h22.51v-9.09h-20.39v-7.86h20.39v-6.92l-13.76-.04c-2.44,0-4.36-.86-5.77-2.59-.88-1.1-1.39-2.26-1.53-3.48-.09-.71-.13-1.67-.13-2.89v-23.02c0-2.86,.72-5.01,2.16-6.46,1.36-1.36,3.19-2.04,5.48-2.04h32.66c3.45,0,5.93,.71,7.43,2.12,1.3,1.25,1.95,3.12,1.95,5.61v25.01c0,2.38-.69,4.28-2.08,5.69-1.33,1.36-3,2.04-5.01,2.04l-13.55,.04v6.92h20.43v7.86h-20.43v9.09h22v7.86h-52.37Zm9.17-55.98h13.34v-8.41h-10.07c-1.19,0-2.07,.28-2.63,.85-.42,.42-.64,1.23-.64,2.42v5.14Zm13.34,16.27v-8.45h-13.34v4.63c0,1.53,.23,2.55,.68,3.06,.42,.51,1.22,.76,2.38,.76h10.28Zm7.86-16.27h12.96v-5.14c.03-2.24-1.39-3.36-4.25-3.36l-8.71,.09v8.41Zm0,16.27h9.47c1.39,0,2.31-.25,2.76-.76,.51-.57,.75-1.51,.72-2.85v-4.84h-12.96v8.45Z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="cls-4" d="M26.71,413.56l15.62,22.47h.12v-22.47h3.17v27.61h-3.25l-15.7-22.7h-.12v22.7h-3.17v-27.61h3.33Z"/>
|
||||
<path class="cls-4" d="M79.47,423.88c1.47,1.93,2.24,4.68,2.32,8.16h-15.78c.15,2.24,.73,3.98,1.82,5.22,1.08,1.24,2.59,1.86,4.48,1.86,1.62,0,2.98-.42,3.98-1.24,.85-.7,1.51-1.74,2.01-3.13h3.09c-.46,1.93-1.35,3.48-2.71,4.72-1.66,1.47-3.79,2.24-6.38,2.24-2.86,0-5.18-.97-6.88-2.82-1.78-1.93-2.63-4.49-2.63-7.73,0-2.94,.81-5.41,2.51-7.39,1.7-2.09,3.98-3.13,6.88-3.13,3.21,0,5.65,1.08,7.27,3.25Zm-11.49,1.08c-1.08,1.16-1.7,2.71-1.89,4.68h12.49c-.39-4.29-2.51-6.42-6.3-6.42-1.78,0-3.21,.58-4.29,1.74Z"/>
|
||||
<path class="cls-4" d="M101.67,421.18v12.22c0,1.89,.39,3.32,1.2,4.25s2.13,1.43,3.95,1.43c1.32,0,2.51-.54,3.56-1.55,1.08-1.08,1.74-2.47,1.97-4.18v-12.18h3.09v19.99h-3.09v-3.1c-1.62,2.4-3.79,3.64-6.49,3.64-4.87,0-7.27-2.75-7.27-8.16v-12.37h3.09Z"/>
|
||||
<path class="cls-4" d="M138.73,421.18h4.95v2.59h-4.95v12.72c0,.73,.11,1.28,.42,1.58,.27,.31,.77,.5,1.47,.5h2.55v2.59h-3.02c-1.62,0-2.82-.43-3.52-1.28-.66-.77-.97-1.9-.97-3.4v-12.72h-4.02v-2.59h4.02v-5.18l3.06-1.28v6.46Z"/>
|
||||
<path class="cls-4" d="M171.79,421.1v3.09c-1.01-.31-2.01-.42-2.98-.42-1.55,0-2.86,.66-3.91,2.05-1.08,1.32-1.58,2.9-1.58,4.76v10.6h-3.06v-19.99h3.06v3.48c.54-1.16,1.24-2.09,2.09-2.75,1.04-.85,2.24-1.28,3.63-1.28,1.08,0,2.01,.15,2.75,.46Z"/>
|
||||
<path class="cls-4" d="M191.76,414.29c.46,.39,.7,.93,.7,1.62,0,.66-.23,1.2-.7,1.66-.47,.42-1.01,.66-1.66,.66s-1.2-.23-1.66-.66c-.47-.46-.66-1.01-.66-1.66,0-.7,.19-1.24,.66-1.62,.46-.46,1-.66,1.66-.66s1.2,.19,1.66,.66Zm-.12,6.88v19.99h-3.09v-19.99h3.09Z"/>
|
||||
<path class="cls-4" d="M227.41,428.87v12.3h-3.09v-12.07c0-3.9-1.78-5.84-5.26-5.84-1.39,0-2.59,.5-3.6,1.51-1.08,1.08-1.66,2.51-1.78,4.33v12.07h-3.09v-19.99h3.09v2.94c.7-1.12,1.59-1.97,2.67-2.55,1.08-.62,2.28-.93,3.6-.93,4.95,0,7.46,2.75,7.46,8.24Z"/>
|
||||
<path class="cls-4" d="M261.45,423.73c1.7,1.97,2.55,4.45,2.55,7.46s-.85,5.45-2.51,7.39c-1.82,2.09-4.21,3.13-7.19,3.13s-5.37-1.04-7.15-3.13c-1.7-1.93-2.55-4.41-2.55-7.39s.85-5.49,2.55-7.46c1.78-2.09,4.14-3.09,7.15-3.09s5.38,1.01,7.16,3.09Zm-12.11,1.93c-1.08,1.47-1.58,3.29-1.58,5.53s.5,4.02,1.58,5.49c1.16,1.62,2.83,2.44,4.95,2.44s3.75-.81,4.99-2.44c1.04-1.47,1.59-3.33,1.59-5.49s-.54-4.06-1.59-5.53c-1.24-1.62-2.9-2.44-4.99-2.44s-3.79,.81-4.95,2.44Z"/>
|
||||
<path class="cls-4" d="M318.89,413.56c6.34,0,9.52,2.71,9.52,8.12s-3.21,8.2-9.55,8.2h-8.08v11.29h-3.17v-27.61h11.29Zm-8.12,13.57h7.93c2.24,0,3.87-.46,4.95-1.35,1.04-.89,1.59-2.24,1.59-4.1s-.54-3.21-1.62-4.02c-1.08-.93-2.71-1.35-4.91-1.35h-7.93v10.83Z"/>
|
||||
<path class="cls-4" d="M356.72,421.1v3.09c-1-.31-2.01-.42-2.98-.42-1.55,0-2.86,.66-3.9,2.05-1.08,1.32-1.59,2.9-1.59,4.76v10.6h-3.06v-19.99h3.06v3.48c.54-1.16,1.24-2.09,2.09-2.75,1.04-.85,2.24-1.28,3.63-1.28,1.08,0,2.01,.15,2.74,.46Z"/>
|
||||
<path class="cls-4" d="M388.55,423.73c1.7,1.97,2.55,4.45,2.55,7.46s-.85,5.45-2.51,7.39c-1.82,2.09-4.21,3.13-7.19,3.13s-5.37-1.04-7.15-3.13c-1.7-1.93-2.55-4.41-2.55-7.39s.85-5.49,2.55-7.46c1.78-2.09,4.14-3.09,7.15-3.09s5.38,1.01,7.16,3.09Zm-12.11,1.93c-1.08,1.47-1.58,3.29-1.58,5.53s.5,4.02,1.58,5.49c1.16,1.62,2.83,2.44,4.95,2.44s3.75-.81,4.99-2.44c1.04-1.47,1.59-3.33,1.59-5.49s-.54-4.06-1.59-5.53c-1.24-1.62-2.9-2.44-4.99-2.44s-3.79,.81-4.95,2.44Z"/>
|
||||
<path class="cls-4" d="M410.56,421.18l5.18,7.11,5.14-7.11h3.64l-7.08,9.4,7.93,10.6h-3.68l-5.95-8.32-5.99,8.32h-3.67l7.93-10.6-7.08-9.4h3.63Z"/>
|
||||
<path class="cls-4" d="M442.28,421.18l6.11,15.78,5.92-15.78h3.41l-10.94,27.65h-3.36l3.44-8.12-7.93-19.53h3.36Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="_图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 420">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #ff9045;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #0079dd;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g>
|
||||
<circle class="cls-1" cx="240" cy="94.5" r="27.21"/>
|
||||
<path class="cls-1" d="M257.97,137.7h-36.11c-9.1,0-16.48,7.38-16.48,16.48v184.01c0,8.02,6.5,14.52,14.52,14.52h.14c8.02,0,14.52-6.5,14.52-14.52v-107.89c5.91,0,10.7,4.79,10.7,10.7v32.75c0,8.02,6.5,14.52,14.52,14.52h.14c8.02,0,14.52-6.5,14.52-14.52v-42.24s0-77.34,0-77.34c0-9.1-7.38-16.48-16.48-16.48Z"/>
|
||||
</g>
|
||||
<path class="cls-2" d="M183.93,221.1h-3.95c-6.13,0-11.1-4.97-11.1-11.1s4.97-11.1,11.1-11.1h3.95s.08,0,.13,0v-51.31c0-9.51-4.63-18.43-12.4-23.92L51.43,38.95c-9.69-6.83-23.06,.1-23.06,11.96V369.09c0,11.86,13.37,18.79,23.06,11.96l120.22-84.73c7.78-5.48,12.4-14.4,12.4-23.92v-51.31s-.08,0-.13,0Z"/>
|
||||
<path class="cls-2" d="M428.57,38.95l-120.22,84.73c-7.78,5.48-12.4,14.4-12.4,23.92v51.31s.08,0,.13,0h3.95c6.13,0,11.1,4.97,11.1,11.1s-4.97,11.1-11.1,11.1h-3.95s-.08,0-.13,0v51.31c0,9.51,4.63,18.44,12.4,23.92l120.22,84.73c9.69,6.83,23.06-.1,23.06-11.96V50.91c0-11.86-13.37-18.79-23.06-11.96Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,42 @@
|
||||
#!/bin/sh
|
||||
# 中微子代理管理后台编译打包脚本,基础参数请自行修改
|
||||
|
||||
#环境
|
||||
env=docker
|
||||
nvmDir=$HOME/.nvm
|
||||
nodeVersion=v13.12.0
|
||||
deployDir="deploy"
|
||||
serverDeployDir=$deployDir"/server"
|
||||
adminDeployDir=$serverDeployDir"/neutrino-proxy-admin"
|
||||
|
||||
#设置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
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/bin/sh
|
||||
# 中微子代理客户端编译打包脚本,基础参数请自行修改
|
||||
|
||||
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home
|
||||
export MAVANE_HOME=/Users/yangwen/my/service/maven/apache-maven-3.8.1
|
||||
export PATH=:$PATH:$JAVA_HOME/bin:$MAVANE_HOME/bin
|
||||
|
||||
deployDir="deploy"
|
||||
clientDeployDir=$deployDir"/client"
|
||||
|
||||
#切到项目根目录
|
||||
cd ..
|
||||
#初始化文件夹
|
||||
rm -rf $clientDeployDir
|
||||
if [ ! -d "$deployDir" ];then
|
||||
mkdir $deployDir
|
||||
fi
|
||||
if [ ! -d "$clientDeployDir" ];then
|
||||
mkdir $clientDeployDir
|
||||
fi
|
||||
#客户端打包
|
||||
mvn clean install -U -pl neutrino-proxy-client -am -Dmaven.test.skip=true
|
||||
# 拷贝到deploy目录下
|
||||
cp ./neutrino-proxy-client/target/neutrino-proxy-client.jar $clientDeployDir/neutrino-proxy-client.jar
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/bin/sh
|
||||
# 中微子代理客户端启动脚本,基础参数请自行修改
|
||||
|
||||
JAVA_OPS="-server -Xms256m -Xmx512m -XX:PermSize=128M -XX:MaxPermSize=256M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/work/$NAME/heapError/"
|
||||
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home
|
||||
export PATH=:$PATH:$JAVA_HOME/bin
|
||||
export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
|
||||
mkdir -p /work/$NAME/heapError/
|
||||
|
||||
NAME=neutrino-proxy-client
|
||||
WORK=$PWD/../deploy/client
|
||||
OUT=$WORK/$NAME.out
|
||||
JAR_PATH=$WORK
|
||||
|
||||
#客户端配置
|
||||
jksPath=classpath:/test.jks
|
||||
serverIp=localhost
|
||||
serverPort=9002
|
||||
sslEnable=true
|
||||
licenseKey=b0a907332b474b25897c4dcb31fc7eb6
|
||||
startupParams="jksPath=$jksPath serverIp=$serverIp serverPort=$serverPort sslEnable=$sslEnable licenseKey=$licenseKey"
|
||||
|
||||
function start(){
|
||||
PID=`jps -l | grep $NAME.jar | cut -d' ' -f 1`
|
||||
if [ -n "$PID" ]; then
|
||||
echo "kill pid : $PID"
|
||||
kill $PID
|
||||
fi
|
||||
sleep 3
|
||||
if [ -n "$PID" ]; then
|
||||
echo "kill fail & kill -9 pid : $PID"
|
||||
kill -9 $PID
|
||||
fi
|
||||
if [ -f "$OUT" ]; then
|
||||
time=$(date "+%Y%m%d-%H%M%S")
|
||||
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 &
|
||||
echo "sleep 15s wating service start"
|
||||
sleep 15
|
||||
tail -200 $OUT
|
||||
echo "start ${JAR_PATH} success ;log path:$OUT"
|
||||
}
|
||||
|
||||
start
|
||||
@@ -0,0 +1,24 @@
|
||||
!/bin/sh
|
||||
# 中微子代理客户端编译打包脚本,基础参数请自行修改
|
||||
|
||||
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home
|
||||
export MAVANE_HOME=/Users/yangwen/my/service/maven/apache-maven-3.8.1
|
||||
export PATH=:$PATH:$JAVA_HOME/bin:$MAVANE_HOME/bin
|
||||
|
||||
deployDir="deploy"
|
||||
serverDeployDir=$deployDir"/server"
|
||||
|
||||
#切到项目根目录
|
||||
cd ..
|
||||
#初始化文件夹
|
||||
rm -rf $serverDeployDir
|
||||
if [ ! -d "$deployDir" ];then
|
||||
mkdir $deployDir
|
||||
fi
|
||||
if [ ! -d "$serverDeployDir" ];then
|
||||
mkdir $serverDeployDir
|
||||
fi
|
||||
#客户端打包
|
||||
mvn clean install -U -pl neutrino-proxy-server -am -Dmaven.test.skip=true
|
||||
# 拷贝到deploy目录下
|
||||
cp ./neutrino-proxy-server/target/neutrino-proxy-server.jar $serverDeployDir/neutrino-proxy-server.jar
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/bin/sh
|
||||
# 中微子代理服务端启动脚本,基础参数请自行修改
|
||||
|
||||
JAVA_OPS="-server -Xms256m -Xmx1024m -XX:PermSize=128M -XX:MaxPermSize=256M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/work/$NAME/heapError/"
|
||||
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home
|
||||
export PATH=:$PATH:$JAVA_HOME/bin
|
||||
export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
|
||||
mkdir -p /work/$NAME/heapError/
|
||||
|
||||
NAME=neutrino-proxy-server
|
||||
WORK=$PWD/../deploy/server
|
||||
OUT=$WORK/$NAME.out
|
||||
JAR_PATH=$WORK
|
||||
|
||||
function start(){
|
||||
PID=`jps -l | grep $NAME.jar | cut -d' ' -f 1`
|
||||
if [ -n "$PID" ]; then
|
||||
echo "kill pid : $PID"
|
||||
kill $PID
|
||||
fi
|
||||
sleep 3
|
||||
if [ -n "$PID" ]; then
|
||||
echo "kill fail & kill -9 pid : $PID"
|
||||
kill -9 $PID
|
||||
fi
|
||||
if [ -f "$OUT" ]; then
|
||||
time=$(date "+%Y%m%d-%H%M%S")
|
||||
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 &
|
||||
echo "sleep 15s wating service start"
|
||||
sleep 15
|
||||
tail -200 $OUT
|
||||
echo "start ${JAR_PATH} success ;log path:$OUT"
|
||||
}
|
||||
|
||||
start
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# 代理实现中涉及的几类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`向用户端写入响应数据。
|
||||
@@ -171,6 +171,7 @@ public class AsgcCompiler {
|
||||
|
||||
/**
|
||||
* 编译代码
|
||||
* @param pkg 包名
|
||||
* @param className 类名
|
||||
* @param sourceCode 源代码
|
||||
*/
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
*/
|
||||
package fun.asgc.neutrino.core.aop.compiler;
|
||||
|
||||
import fun.asgc.neutrino.core.aop.compiler.internal.SpringBootJarParser;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.NestingKind;
|
||||
import javax.tools.JavaFileObject;
|
||||
@@ -50,6 +52,10 @@ public class CustomJavaFileObject implements JavaFileObject {
|
||||
|
||||
@Override
|
||||
public InputStream openInputStream() throws IOException {
|
||||
InputStream in = SpringBootJarParser.getInputStream(uri);
|
||||
if (null != in) {
|
||||
return in;
|
||||
}
|
||||
return uri.toURL().openStream();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A class path index file that provides ordering information for JARs.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
final class ClassPathIndexFile {
|
||||
|
||||
private final File root;
|
||||
|
||||
private final List<String> lines;
|
||||
|
||||
private ClassPathIndexFile(File root, List<String> lines) {
|
||||
this.root = root;
|
||||
this.lines = lines.stream().map(this::extractName).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private String extractName(String line) {
|
||||
if (line.startsWith("- \"") && line.endsWith("\"")) {
|
||||
return line.substring(3, line.length() - 1);
|
||||
}
|
||||
throw new IllegalStateException("Malformed classpath index line [" + line + "]");
|
||||
}
|
||||
|
||||
int size() {
|
||||
return this.lines.size();
|
||||
}
|
||||
|
||||
boolean containsEntry(String name) {
|
||||
if (name == null || name.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return this.lines.contains(name);
|
||||
}
|
||||
|
||||
List<URL> getUrls() {
|
||||
return this.lines.stream().map(this::asUrl).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private URL asUrl(String line) {
|
||||
try {
|
||||
return new File(this.root, line).toURI().toURL();
|
||||
}
|
||||
catch (MalformedURLException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
static ClassPathIndexFile loadIfPossible(URL root, String location) throws IOException {
|
||||
return loadIfPossible(asFile(root), location);
|
||||
}
|
||||
|
||||
private static ClassPathIndexFile loadIfPossible(File root, String location) throws IOException {
|
||||
return loadIfPossible(root, new File(root, location));
|
||||
}
|
||||
|
||||
private static ClassPathIndexFile loadIfPossible(File root, File indexFile) throws IOException {
|
||||
if (indexFile.exists() && indexFile.isFile()) {
|
||||
try (InputStream inputStream = new FileInputStream(indexFile)) {
|
||||
return new ClassPathIndexFile(root, loadLines(inputStream));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static List<String> loadLines(InputStream inputStream) throws IOException {
|
||||
List<String> lines = new ArrayList<>();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
|
||||
String line = reader.readLine();
|
||||
while (line != null) {
|
||||
if (!line.trim().isEmpty()) {
|
||||
lines.add(line);
|
||||
}
|
||||
line = reader.readLine();
|
||||
}
|
||||
return Collections.unmodifiableList(lines);
|
||||
}
|
||||
|
||||
private static File asFile(URL url) {
|
||||
if (!"file".equals(url.getProtocol())) {
|
||||
throw new IllegalArgumentException("URL does not reference a file");
|
||||
}
|
||||
try {
|
||||
return new File(url.toURI());
|
||||
}
|
||||
catch (URISyntaxException ex) {
|
||||
return new File(url.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal;
|
||||
|
||||
import fun.asgc.neutrino.core.aop.compiler.internal.archive.Archive;
|
||||
import fun.asgc.neutrino.core.aop.compiler.internal.archive.ExplodedArchive;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
/**
|
||||
* Base class for executable archive {@link Launcher}s.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @author Madhura Bhave
|
||||
* @author Scott Frederick
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public abstract class ExecutableArchiveLauncher extends Launcher {
|
||||
|
||||
private static final String START_CLASS_ATTRIBUTE = "Start-Class";
|
||||
|
||||
protected static final String BOOT_CLASSPATH_INDEX_ATTRIBUTE = "Spring-Boot-Classpath-Index";
|
||||
|
||||
protected static final String DEFAULT_CLASSPATH_INDEX_FILE_NAME = "classpath.idx";
|
||||
|
||||
private Archive archive;
|
||||
|
||||
private ClassPathIndexFile classPathIndex;
|
||||
|
||||
public ExecutableArchiveLauncher() {
|
||||
try {
|
||||
this.archive = createArchive();
|
||||
this.classPathIndex = getClassPathIndex(this.archive);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected ExecutableArchiveLauncher(Archive archive) {
|
||||
try {
|
||||
this.archive = archive;
|
||||
this.classPathIndex = getClassPathIndex(this.archive);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected ClassPathIndexFile getClassPathIndex(Archive archive) throws IOException {
|
||||
// Only needed for exploded archives, regular ones already have a defined order
|
||||
if (archive instanceof ExplodedArchive) {
|
||||
String location = getClassPathIndexFileLocation(archive);
|
||||
return ClassPathIndexFile.loadIfPossible(archive.getUrl(), location);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getClassPathIndexFileLocation(Archive archive) throws IOException {
|
||||
Manifest manifest = archive.getManifest();
|
||||
Attributes attributes = (manifest != null) ? manifest.getMainAttributes() : null;
|
||||
String location = (attributes != null) ? attributes.getValue(BOOT_CLASSPATH_INDEX_ATTRIBUTE) : null;
|
||||
return (location != null) ? location : getArchiveEntryPathPrefix() + DEFAULT_CLASSPATH_INDEX_FILE_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMainClass() throws Exception {
|
||||
Manifest manifest = this.archive.getManifest();
|
||||
String mainClass = null;
|
||||
if (manifest != null) {
|
||||
mainClass = manifest.getMainAttributes().getValue(START_CLASS_ATTRIBUTE);
|
||||
}
|
||||
if (mainClass == null) {
|
||||
throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
|
||||
}
|
||||
return mainClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClassLoader createClassLoader(Iterator<Archive> archives) throws Exception {
|
||||
List<URL> urls = new ArrayList<>(guessClassPathSize());
|
||||
while (archives.hasNext()) {
|
||||
urls.add(archives.next().getUrl());
|
||||
}
|
||||
if (this.classPathIndex != null) {
|
||||
urls.addAll(this.classPathIndex.getUrls());
|
||||
}
|
||||
return createClassLoader(urls.toArray(new URL[0]));
|
||||
}
|
||||
|
||||
private int guessClassPathSize() {
|
||||
if (this.classPathIndex != null) {
|
||||
return this.classPathIndex.size() + 10;
|
||||
}
|
||||
return 50;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Iterator<Archive> getClassPathArchivesIterator() throws Exception {
|
||||
Archive.EntryFilter searchFilter = this::isSearchCandidate;
|
||||
Iterator<Archive> archives = this.archive.getNestedArchives(searchFilter,
|
||||
(entry) -> isNestedArchive(entry) && !isEntryIndexed(entry));
|
||||
if (isPostProcessingClassPathArchives()) {
|
||||
archives = applyClassPathArchivePostProcessing(archives);
|
||||
}
|
||||
return archives;
|
||||
}
|
||||
|
||||
private boolean isEntryIndexed(Archive.Entry entry) {
|
||||
if (this.classPathIndex != null) {
|
||||
return this.classPathIndex.containsEntry(entry.getName());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Iterator<Archive> applyClassPathArchivePostProcessing(Iterator<Archive> archives) throws Exception {
|
||||
List<Archive> list = new ArrayList<>();
|
||||
while (archives.hasNext()) {
|
||||
list.add(archives.next());
|
||||
}
|
||||
postProcessClassPathArchives(list);
|
||||
return list.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the specified entry is a candidate for further searching.
|
||||
* @param entry the entry to check
|
||||
* @return {@code true} if the entry is a candidate for further searching
|
||||
* @since 2.3.0
|
||||
*/
|
||||
protected boolean isSearchCandidate(Archive.Entry entry) {
|
||||
if (getArchiveEntryPathPrefix() == null) {
|
||||
return true;
|
||||
}
|
||||
return entry.getName().startsWith(getArchiveEntryPathPrefix());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the specified entry is a nested item that should be added to the
|
||||
* classpath.
|
||||
* @param entry the entry to check
|
||||
* @return {@code true} if the entry is a nested item (jar or directory)
|
||||
*/
|
||||
protected abstract boolean isNestedArchive(Archive.Entry entry);
|
||||
|
||||
/**
|
||||
* Return if post-processing needs to be applied to the archives. For back
|
||||
* compatibility this method returns {@code true}, but subclasses that don't override
|
||||
* {@link #postProcessClassPathArchives(List)} should provide an implementation that
|
||||
* returns {@code false}.
|
||||
* @return if the {@link #postProcessClassPathArchives(List)} method is implemented
|
||||
* @since 2.3.0
|
||||
*/
|
||||
protected boolean isPostProcessingClassPathArchives() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to post-process archive entries before they are used. Implementations can
|
||||
* add and remove entries.
|
||||
* @param archives the archives
|
||||
* @throws Exception if the post-processing fails
|
||||
* @see #isPostProcessingClassPathArchives()
|
||||
*/
|
||||
protected void postProcessClassPathArchives(List<Archive> archives) throws Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path prefix for entries in the archive.
|
||||
* @return the path prefix
|
||||
*/
|
||||
protected String getArchiveEntryPathPrefix() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isExploded() {
|
||||
return this.archive.isExploded();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Archive getArchive() {
|
||||
return this.archive;
|
||||
}
|
||||
|
||||
|
||||
protected void setArchive(Archive archive) {
|
||||
this.archive = archive;
|
||||
}
|
||||
|
||||
protected void setClassPathIndex(ClassPathIndexFile classPathIndex) {
|
||||
this.classPathIndex = classPathIndex;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal;
|
||||
|
||||
import fun.asgc.neutrino.core.aop.compiler.internal.archive.Archive;
|
||||
import fun.asgc.neutrino.core.aop.compiler.internal.archive.ExplodedArchive;
|
||||
import fun.asgc.neutrino.core.aop.compiler.internal.archive.JarFileArchive;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* {@link Launcher} for JAR based archives. This launcher assumes that dependency jars are
|
||||
* included inside a {@code /BOOT-INF/lib} directory and that application classes are
|
||||
* included inside a {@code /BOOT-INF/classes} directory.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @author Madhura Bhave
|
||||
* @author Scott Frederick
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class JarLauncher extends ExecutableArchiveLauncher {
|
||||
|
||||
private String path;
|
||||
|
||||
static final Archive.EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> {
|
||||
if (entry.isDirectory()) {
|
||||
return entry.getName().equals("BOOT-INF/classes/");
|
||||
}
|
||||
return entry.getName().startsWith("BOOT-INF/lib/");
|
||||
};
|
||||
|
||||
public JarLauncher(String path) {
|
||||
this.path = path;
|
||||
try {
|
||||
this.setArchive(createArchive(this.path));
|
||||
this.setClassPathIndex(getClassPathIndex(this.getArchive()));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public JarLauncher(ClassLoader classLoader, String path) {
|
||||
this.path = path;
|
||||
try {
|
||||
this.setArchive(createArchive(this.path));
|
||||
this.setClassPathIndex(getClassPathIndex(this.getArchive()));
|
||||
this.setClassLoader(classLoader);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public ClassLoader createClassLoader() throws Exception {
|
||||
return createClassLoader(getClassPathArchivesIterator());
|
||||
}
|
||||
|
||||
protected final Archive createArchive(String path) throws Exception {
|
||||
if (path == null) {
|
||||
throw new IllegalStateException("Unable to determine code source archive");
|
||||
}
|
||||
File root = new File(path);
|
||||
if (!root.exists()) {
|
||||
throw new IllegalStateException("Unable to determine code source archive from " + root);
|
||||
}
|
||||
return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));
|
||||
}
|
||||
|
||||
protected JarLauncher(Archive archive) {
|
||||
super(archive);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isPostProcessingClassPathArchives() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isNestedArchive(Archive.Entry entry) {
|
||||
return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getArchiveEntryPathPrefix() {
|
||||
return "BOOT-INF/";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal;
|
||||
|
||||
import fun.asgc.neutrino.core.aop.compiler.internal.archive.Archive;
|
||||
import fun.asgc.neutrino.core.aop.compiler.internal.jar.Handler;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.JarURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.net.URLConnection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
/**
|
||||
* {@link ClassLoader} used by the {@link Launcher}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class LaunchedURLClassLoader extends URLClassLoader {
|
||||
|
||||
private static final int BUFFER_SIZE = 4096;
|
||||
|
||||
static {
|
||||
ClassLoader.registerAsParallelCapable();
|
||||
}
|
||||
|
||||
private final boolean exploded;
|
||||
|
||||
private final Archive rootArchive;
|
||||
|
||||
private final Object packageLock = new Object();
|
||||
|
||||
private volatile DefinePackageCallType definePackageCallType;
|
||||
|
||||
/**
|
||||
* Create a new {@link LaunchedURLClassLoader} instance.
|
||||
* @param urls the URLs from which to load classes and resources
|
||||
* @param parent the parent class loader for delegation
|
||||
*/
|
||||
public LaunchedURLClassLoader(URL[] urls, ClassLoader parent) {
|
||||
this(false, urls, parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link LaunchedURLClassLoader} instance.
|
||||
* @param exploded if the underlying archive is exploded
|
||||
* @param urls the URLs from which to load classes and resources
|
||||
* @param parent the parent class loader for delegation
|
||||
*/
|
||||
public LaunchedURLClassLoader(boolean exploded, URL[] urls, ClassLoader parent) {
|
||||
this(exploded, null, urls, parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link LaunchedURLClassLoader} instance.
|
||||
* @param exploded if the underlying archive is exploded
|
||||
* @param rootArchive the root archive or {@code null}
|
||||
* @param urls the URLs from which to load classes and resources
|
||||
* @param parent the parent class loader for delegation
|
||||
* @since 2.3.1
|
||||
*/
|
||||
public LaunchedURLClassLoader(boolean exploded, Archive rootArchive, URL[] urls, ClassLoader parent) {
|
||||
super(urls, parent);
|
||||
this.exploded = exploded;
|
||||
this.rootArchive = rootArchive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL findResource(String name) {
|
||||
if (this.exploded) {
|
||||
return super.findResource(name);
|
||||
}
|
||||
Handler.setUseFastConnectionExceptions(true);
|
||||
try {
|
||||
return super.findResource(name);
|
||||
}
|
||||
finally {
|
||||
Handler.setUseFastConnectionExceptions(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<URL> findResources(String name) throws IOException {
|
||||
if (this.exploded) {
|
||||
return super.findResources(name);
|
||||
}
|
||||
Handler.setUseFastConnectionExceptions(true);
|
||||
try {
|
||||
return new UseFastConnectionExceptionsEnumeration(super.findResources(name));
|
||||
}
|
||||
finally {
|
||||
Handler.setUseFastConnectionExceptions(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||
if (name.startsWith("org.springframework.boot.loader.jarmode.")) {
|
||||
try {
|
||||
Class<?> result = loadClassInLaunchedClassLoader(name);
|
||||
if (resolve) {
|
||||
resolveClass(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
}
|
||||
}
|
||||
if (this.exploded) {
|
||||
return super.loadClass(name, resolve);
|
||||
}
|
||||
Handler.setUseFastConnectionExceptions(true);
|
||||
try {
|
||||
try {
|
||||
definePackageIfNecessary(name);
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
// Tolerate race condition due to being parallel capable
|
||||
// if (getDefinedPackage(name) == null) {
|
||||
// // This should never happen as the IllegalArgumentException indicates
|
||||
// // that the package has already been defined and, therefore,
|
||||
// // getDefinedPackage(name) should not return null.
|
||||
// throw new AssertionError("Package " + name + " has already been defined but it could not be found");
|
||||
// }
|
||||
}
|
||||
return super.loadClass(name, resolve);
|
||||
}
|
||||
finally {
|
||||
Handler.setUseFastConnectionExceptions(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void addURL(URL url) {
|
||||
addURL(url);
|
||||
}
|
||||
|
||||
private Class<?> loadClassInLaunchedClassLoader(String name) throws ClassNotFoundException {
|
||||
String internalName = name.replace('.', '/') + ".class";
|
||||
InputStream inputStream = getParent().getResourceAsStream(internalName);
|
||||
if (inputStream == null) {
|
||||
throw new ClassNotFoundException(name);
|
||||
}
|
||||
try {
|
||||
try {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int bytesRead = -1;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
inputStream.close();
|
||||
byte[] bytes = outputStream.toByteArray();
|
||||
Class<?> definedClass = defineClass(name, bytes, 0, bytes.length);
|
||||
definePackageIfNecessary(name);
|
||||
return definedClass;
|
||||
}
|
||||
finally {
|
||||
inputStream.close();
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new ClassNotFoundException("Cannot load resource for class [" + name + "]", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a package before a {@code findClass} call is made. This is necessary to
|
||||
* ensure that the appropriate manifest for nested JARs is associated with the
|
||||
* package.
|
||||
* @param className the class name being found
|
||||
*/
|
||||
private void definePackageIfNecessary(String className) {
|
||||
int lastDot = className.lastIndexOf('.');
|
||||
if (lastDot >= 0) {
|
||||
String packageName = className.substring(0, lastDot);
|
||||
// if (getDefinedPackage(packageName) == null) {
|
||||
try {
|
||||
definePackage(className, packageName);
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
// Tolerate race condition due to being parallel capable
|
||||
// if (getDefinedPackage(packageName) == null) {
|
||||
// // This should never happen as the IllegalArgumentException
|
||||
// // indicates that the package has already been defined and,
|
||||
// // therefore, getDefinedPackage(name) should not have returned
|
||||
// // null.
|
||||
// throw new AssertionError(
|
||||
// "Package " + packageName + " has already been defined but it could not be found");
|
||||
// }
|
||||
}
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
private void definePackage(String className, String packageName) {
|
||||
String packageEntryName = packageName.replace('.', '/') + "/";
|
||||
String classEntryName = className.replace('.', '/') + ".class";
|
||||
for (URL url : getURLs()) {
|
||||
try {
|
||||
URLConnection connection = url.openConnection();
|
||||
if (connection instanceof JarURLConnection) {
|
||||
JarFile jarFile = ((JarURLConnection)connection).getJarFile();
|
||||
if (jarFile.getEntry(classEntryName) != null && jarFile.getEntry(packageEntryName) != null
|
||||
&& jarFile.getManifest() != null) {
|
||||
definePackage(packageName, jarFile.getManifest(), url);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Package definePackage(String name, Manifest man, URL url) throws IllegalArgumentException {
|
||||
if (!this.exploded) {
|
||||
return super.definePackage(name, man, url);
|
||||
}
|
||||
synchronized (this.packageLock) {
|
||||
return doDefinePackage(DefinePackageCallType.MANIFEST, () -> super.definePackage(name, man, url));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Package definePackage(String name, String specTitle, String specVersion, String specVendor,
|
||||
String implTitle, String implVersion, String implVendor, URL sealBase) throws IllegalArgumentException {
|
||||
if (!this.exploded) {
|
||||
return super.definePackage(name, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor,
|
||||
sealBase);
|
||||
}
|
||||
synchronized (this.packageLock) {
|
||||
if (this.definePackageCallType == null) {
|
||||
// We're not part of a call chain which means that the URLClassLoader
|
||||
// is trying to define a package for our exploded JAR. We use the
|
||||
// manifest version to ensure package attributes are set
|
||||
Manifest manifest = getManifest(this.rootArchive);
|
||||
if (manifest != null) {
|
||||
return definePackage(name, manifest, sealBase);
|
||||
}
|
||||
}
|
||||
return doDefinePackage(DefinePackageCallType.ATTRIBUTES, () -> super.definePackage(name, specTitle,
|
||||
specVersion, specVendor, implTitle, implVersion, implVendor, sealBase));
|
||||
}
|
||||
}
|
||||
|
||||
private Manifest getManifest(Archive archive) {
|
||||
try {
|
||||
return (archive != null) ? archive.getManifest() : null;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T doDefinePackage(DefinePackageCallType type, Supplier<T> call) {
|
||||
DefinePackageCallType existingType = this.definePackageCallType;
|
||||
try {
|
||||
this.definePackageCallType = type;
|
||||
return call.get();
|
||||
}
|
||||
finally {
|
||||
this.definePackageCallType = existingType;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear URL caches.
|
||||
*/
|
||||
public void clearCache() {
|
||||
if (this.exploded) {
|
||||
return;
|
||||
}
|
||||
for (URL url : getURLs()) {
|
||||
try {
|
||||
URLConnection connection = url.openConnection();
|
||||
if (connection instanceof JarURLConnection) {
|
||||
clearCache(connection);
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void clearCache(URLConnection connection) throws IOException {
|
||||
Object jarFile = ((JarURLConnection) connection).getJarFile();
|
||||
if (jarFile instanceof fun.asgc.neutrino.core.aop.compiler.internal.jar.JarFile) {
|
||||
((fun.asgc.neutrino.core.aop.compiler.internal.jar.JarFile) jarFile).clearCache();
|
||||
}
|
||||
}
|
||||
|
||||
private static class UseFastConnectionExceptionsEnumeration implements Enumeration<URL> {
|
||||
|
||||
private final Enumeration<URL> delegate;
|
||||
|
||||
UseFastConnectionExceptionsEnumeration(Enumeration<URL> delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMoreElements() {
|
||||
Handler.setUseFastConnectionExceptions(true);
|
||||
try {
|
||||
return this.delegate.hasMoreElements();
|
||||
}
|
||||
finally {
|
||||
Handler.setUseFastConnectionExceptions(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL nextElement() {
|
||||
Handler.setUseFastConnectionExceptions(true);
|
||||
try {
|
||||
return this.delegate.nextElement();
|
||||
}
|
||||
finally {
|
||||
Handler.setUseFastConnectionExceptions(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The different types of call made to define a package. We track these for exploded
|
||||
* jars so that we can detect packages that should have manifest attributes applied.
|
||||
*/
|
||||
private enum DefinePackageCallType {
|
||||
|
||||
/**
|
||||
* A define package call from a resource that has a manifest.
|
||||
*/
|
||||
MANIFEST,
|
||||
|
||||
/**
|
||||
* A define package call with a direct set of attributes.
|
||||
*/
|
||||
ATTRIBUTES
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal;
|
||||
|
||||
import fun.asgc.neutrino.core.aop.compiler.internal.archive.Archive;
|
||||
import fun.asgc.neutrino.core.aop.compiler.internal.archive.ExplodedArchive;
|
||||
import fun.asgc.neutrino.core.aop.compiler.internal.archive.JarFileArchive;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.security.CodeSource;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Base class for launchers that can start an application with a fully configured
|
||||
* classpath backed by one or more {@link Archive}s.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Dave Syer
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public abstract class Launcher {
|
||||
|
||||
private ClassLoader classLoader;
|
||||
|
||||
/**
|
||||
* Create a classloader for the specified archives.
|
||||
* @param archives the archives
|
||||
* @return the classloader
|
||||
* @throws Exception if the classloader cannot be created
|
||||
* @since 2.3.0
|
||||
*/
|
||||
protected ClassLoader createClassLoader(Iterator<Archive> archives) throws Exception {
|
||||
List<URL> urls = new ArrayList<>(50);
|
||||
while (archives.hasNext()) {
|
||||
urls.add(archives.next().getUrl());
|
||||
}
|
||||
return createClassLoader(urls.toArray(new URL[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a classloader for the specified URLs.
|
||||
* @param urls the URLs
|
||||
* @return the classloader
|
||||
* @throws Exception if the classloader cannot be created
|
||||
*/
|
||||
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
|
||||
return new LaunchedURLClassLoader(isExploded(),
|
||||
getArchive(),
|
||||
urls,
|
||||
(null == classLoader) ? getClass().getClassLoader() : classLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the main class that should be launched.
|
||||
* @return the name of the main class
|
||||
* @throws Exception if the main class cannot be obtained
|
||||
*/
|
||||
protected abstract String getMainClass() throws Exception;
|
||||
|
||||
/**
|
||||
* Returns the archives that will be used to construct the class path.
|
||||
* @return the class path archives
|
||||
* @throws Exception if the class path archives cannot be obtained
|
||||
* @since 2.3.0
|
||||
*/
|
||||
protected abstract Iterator<Archive> getClassPathArchivesIterator() throws Exception;
|
||||
|
||||
protected final Archive createArchive() throws Exception {
|
||||
ProtectionDomain protectionDomain = getClass().getProtectionDomain();
|
||||
CodeSource codeSource = protectionDomain.getCodeSource();
|
||||
URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null;
|
||||
String path = (location != null) ? location.getSchemeSpecificPart() : null;
|
||||
if (path == null) {
|
||||
throw new IllegalStateException("Unable to determine code source archive");
|
||||
}
|
||||
File root = new File(path);
|
||||
if (!root.exists()) {
|
||||
throw new IllegalStateException("Unable to determine code source archive from " + root);
|
||||
}
|
||||
return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the launcher is running in an exploded mode. If this method returns
|
||||
* {@code true} then only regular JARs are supported and the additional URL and
|
||||
* ClassLoader support infrastructure can be optimized.
|
||||
* @return if the jar is exploded.
|
||||
* @since 2.3.0
|
||||
*/
|
||||
protected boolean isExploded() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the root archive.
|
||||
* @return the root archive
|
||||
* @since 2.3.1
|
||||
*/
|
||||
protected Archive getArchive() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setClassLoader(ClassLoader classLoader) {
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal;
|
||||
|
||||
import fun.asgc.neutrino.core.aop.compiler.internal.jar.JarFile;
|
||||
import fun.asgc.neutrino.core.aop.compiler.internal.jar.JarURLConnection;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/2/23
|
||||
*/
|
||||
public final class SpringBootJarParser {
|
||||
|
||||
/**
|
||||
* 根据一个URI从SpringBoot jar包中解析出输入流
|
||||
* 1、先尝试直接获取流,若能获取则到此结束
|
||||
* 2、若不能获取,且URI标识定位的是一个jar包,则进行处理(只处理SpringBoot情况的jar)
|
||||
* @param uri
|
||||
* @return
|
||||
*/
|
||||
public static InputStream getInputStream(URI uri) {
|
||||
try {
|
||||
if (uri.getScheme().equals("jar")) {
|
||||
URL url = uri.toURL();
|
||||
String path = url.getPath();
|
||||
int index = path.indexOf("!");
|
||||
if (index >= 0) {
|
||||
path = path.substring(0, index);
|
||||
}
|
||||
if (path.startsWith("file:")) {
|
||||
path = path.substring(5);
|
||||
}
|
||||
JarFile jarFile = new JarFile(new File(path));
|
||||
return JarURLConnection.get(url, jarFile).getInputStream();
|
||||
}
|
||||
} catch (Exception e1) {
|
||||
// ignore
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Helper class for resolving placeholders in texts. Usually applied to file paths.
|
||||
* <p>
|
||||
* A text may contain {@code $ ...} placeholders, to be resolved as system properties:
|
||||
* e.g. {@code $ user.dir}. Default values can be supplied using the ":" separator between
|
||||
* key and value.
|
||||
* <p>
|
||||
* Adapted from Spring.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Rob Harrop
|
||||
* @author Dave Syer
|
||||
* @since 1.0.0
|
||||
* @see System#getProperty(String)
|
||||
*/
|
||||
public abstract class SystemPropertyUtils {
|
||||
|
||||
/**
|
||||
* Prefix for system property placeholders: "${".
|
||||
*/
|
||||
public static final String PLACEHOLDER_PREFIX = "${";
|
||||
|
||||
/**
|
||||
* Suffix for system property placeholders: "}".
|
||||
*/
|
||||
public static final String PLACEHOLDER_SUFFIX = "}";
|
||||
|
||||
/**
|
||||
* Value separator for system property placeholders: ":".
|
||||
*/
|
||||
public static final String VALUE_SEPARATOR = ":";
|
||||
|
||||
private static final String SIMPLE_PREFIX = PLACEHOLDER_PREFIX.substring(1);
|
||||
|
||||
/**
|
||||
* Resolve ${...} placeholders in the given text, replacing them with corresponding
|
||||
* system property values.
|
||||
* @param text the String to resolve
|
||||
* @return the resolved String
|
||||
* @throws IllegalArgumentException if there is an unresolvable placeholder
|
||||
* @see #PLACEHOLDER_PREFIX
|
||||
* @see #PLACEHOLDER_SUFFIX
|
||||
*/
|
||||
public static String resolvePlaceholders(String text) {
|
||||
if (text == null) {
|
||||
return text;
|
||||
}
|
||||
return parseStringValue(null, text, text, new HashSet<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve ${...} placeholders in the given text, replacing them with corresponding
|
||||
* system property values.
|
||||
* @param properties a properties instance to use in addition to System
|
||||
* @param text the String to resolve
|
||||
* @return the resolved String
|
||||
* @throws IllegalArgumentException if there is an unresolvable placeholder
|
||||
* @see #PLACEHOLDER_PREFIX
|
||||
* @see #PLACEHOLDER_SUFFIX
|
||||
*/
|
||||
public static String resolvePlaceholders(Properties properties, String text) {
|
||||
if (text == null) {
|
||||
return text;
|
||||
}
|
||||
return parseStringValue(properties, text, text, new HashSet<>());
|
||||
}
|
||||
|
||||
private static String parseStringValue(Properties properties, String value, String current,
|
||||
Set<String> visitedPlaceholders) {
|
||||
|
||||
StringBuilder buf = new StringBuilder(current);
|
||||
|
||||
int startIndex = current.indexOf(PLACEHOLDER_PREFIX);
|
||||
while (startIndex != -1) {
|
||||
int endIndex = findPlaceholderEndIndex(buf, startIndex);
|
||||
if (endIndex != -1) {
|
||||
String placeholder = buf.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);
|
||||
String originalPlaceholder = placeholder;
|
||||
if (!visitedPlaceholders.add(originalPlaceholder)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
|
||||
}
|
||||
// Recursive invocation, parsing placeholders contained in the
|
||||
// placeholder
|
||||
// key.
|
||||
placeholder = parseStringValue(properties, value, placeholder, visitedPlaceholders);
|
||||
// Now obtain the value for the fully resolved key...
|
||||
String propVal = resolvePlaceholder(properties, value, placeholder);
|
||||
if (propVal == null) {
|
||||
int separatorIndex = placeholder.indexOf(VALUE_SEPARATOR);
|
||||
if (separatorIndex != -1) {
|
||||
String actualPlaceholder = placeholder.substring(0, separatorIndex);
|
||||
String defaultValue = placeholder.substring(separatorIndex + VALUE_SEPARATOR.length());
|
||||
propVal = resolvePlaceholder(properties, value, actualPlaceholder);
|
||||
if (propVal == null) {
|
||||
propVal = defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (propVal != null) {
|
||||
// Recursive invocation, parsing placeholders contained in the
|
||||
// previously resolved placeholder value.
|
||||
propVal = parseStringValue(properties, value, propVal, visitedPlaceholders);
|
||||
buf.replace(startIndex, endIndex + PLACEHOLDER_SUFFIX.length(), propVal);
|
||||
startIndex = buf.indexOf(PLACEHOLDER_PREFIX, startIndex + propVal.length());
|
||||
}
|
||||
else {
|
||||
// Proceed with unprocessed value.
|
||||
startIndex = buf.indexOf(PLACEHOLDER_PREFIX, endIndex + PLACEHOLDER_SUFFIX.length());
|
||||
}
|
||||
visitedPlaceholders.remove(originalPlaceholder);
|
||||
}
|
||||
else {
|
||||
startIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static String resolvePlaceholder(Properties properties, String text, String placeholderName) {
|
||||
String propVal = getProperty(placeholderName, null, text);
|
||||
if (propVal != null) {
|
||||
return propVal;
|
||||
}
|
||||
return (properties != null) ? properties.getProperty(placeholderName) : null;
|
||||
}
|
||||
|
||||
public static String getProperty(String key) {
|
||||
return getProperty(key, null, "");
|
||||
}
|
||||
|
||||
public static String getProperty(String key, String defaultValue) {
|
||||
return getProperty(key, defaultValue, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the System properties and environment variables for a value with the
|
||||
* provided key. Environment variables in {@code UPPER_CASE} style are allowed where
|
||||
* System properties would normally be {@code lower.case}.
|
||||
* @param key the key to resolve
|
||||
* @param defaultValue the default value
|
||||
* @param text optional extra context for an error message if the key resolution fails
|
||||
* (e.g. if System properties are not accessible)
|
||||
* @return a static property value or null of not found
|
||||
*/
|
||||
public static String getProperty(String key, String defaultValue, String text) {
|
||||
try {
|
||||
String propVal = System.getProperty(key);
|
||||
if (propVal == null) {
|
||||
// Fall back to searching the system environment.
|
||||
propVal = System.getenv(key);
|
||||
}
|
||||
if (propVal == null) {
|
||||
// Try with underscores.
|
||||
String name = key.replace('.', '_');
|
||||
propVal = System.getenv(name);
|
||||
}
|
||||
if (propVal == null) {
|
||||
// Try uppercase with underscores as well.
|
||||
String name = key.toUpperCase(Locale.ENGLISH).replace('.', '_');
|
||||
propVal = System.getenv(name);
|
||||
}
|
||||
if (propVal != null) {
|
||||
return propVal;
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
System.err.println("Could not resolve key '" + key + "' in '" + text
|
||||
+ "' as system property or in environment: " + ex);
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private static int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
|
||||
int index = startIndex + PLACEHOLDER_PREFIX.length();
|
||||
int withinNestedPlaceholder = 0;
|
||||
while (index < buf.length()) {
|
||||
if (substringMatch(buf, index, PLACEHOLDER_SUFFIX)) {
|
||||
if (withinNestedPlaceholder > 0) {
|
||||
withinNestedPlaceholder--;
|
||||
index = index + PLACEHOLDER_SUFFIX.length();
|
||||
}
|
||||
else {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
else if (substringMatch(buf, index, SIMPLE_PREFIX)) {
|
||||
withinNestedPlaceholder++;
|
||||
index = index + SIMPLE_PREFIX.length();
|
||||
}
|
||||
else {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static boolean substringMatch(CharSequence str, int index, CharSequence substring) {
|
||||
for (int j = 0; j < substring.length(); j++) {
|
||||
int i = index + j;
|
||||
if (i >= str.length() || str.charAt(i) != substring.charAt(j)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal.archive;
|
||||
|
||||
import fun.asgc.neutrino.core.aop.compiler.internal.Launcher;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Iterator;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
/**
|
||||
* An archive that can be launched by the {@link Launcher}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 1.0.0
|
||||
* @see JarFileArchive
|
||||
*/
|
||||
public interface Archive extends Iterable<Archive.Entry>, AutoCloseable {
|
||||
|
||||
/**
|
||||
* Returns a URL that can be used to load the archive.
|
||||
* @return the archive URL
|
||||
* @throws MalformedURLException if the URL is malformed
|
||||
*/
|
||||
URL getUrl() throws MalformedURLException;
|
||||
|
||||
/**
|
||||
* Returns the manifest of the archive.
|
||||
* @return the manifest
|
||||
* @throws IOException if the manifest cannot be read
|
||||
*/
|
||||
Manifest getManifest() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns nested {@link Archive}s for entries that match the specified filters.
|
||||
* @param searchFilter filter used to limit when additional sub-entry searching is
|
||||
* required or {@code null} if all entries should be considered.
|
||||
* @param includeFilter filter used to determine which entries should be included in
|
||||
* the result or {@code null} if all entries should be included
|
||||
* @return the nested archives
|
||||
* @throws IOException on IO error
|
||||
* @since 2.3.0
|
||||
*/
|
||||
Iterator<Archive> getNestedArchives(EntryFilter searchFilter, EntryFilter includeFilter) throws IOException;
|
||||
|
||||
/**
|
||||
* Return if the archive is exploded (already unpacked).
|
||||
* @return if the archive is exploded
|
||||
* @since 2.3.0
|
||||
*/
|
||||
default boolean isExploded() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the {@code Archive}, releasing any open resources.
|
||||
* @throws Exception if an error occurs during close processing
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@Override
|
||||
default void close() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single entry in the archive.
|
||||
*/
|
||||
interface Entry {
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the entry represents a directory.
|
||||
* @return if the entry is a directory
|
||||
*/
|
||||
boolean isDirectory();
|
||||
|
||||
/**
|
||||
* Returns the name of the entry.
|
||||
* @return the name of the entry
|
||||
*/
|
||||
String getName();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Strategy interface to filter {@link Entry Entries}.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface EntryFilter {
|
||||
|
||||
/**
|
||||
* Apply the jar entry filter.
|
||||
* @param entry the entry to filter
|
||||
* @return {@code true} if the filter matches
|
||||
*/
|
||||
boolean matches(Entry entry);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal.archive;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
/**
|
||||
* {@link Archive} implementation backed by an exploded archive directory.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @author Madhura Bhave
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class ExplodedArchive implements Archive {
|
||||
|
||||
private static final Set<String> SKIPPED_NAMES = new HashSet<>(Arrays.asList(".", ".."));
|
||||
|
||||
private final File root;
|
||||
|
||||
private final boolean recursive;
|
||||
|
||||
private final File manifestFile;
|
||||
|
||||
private Manifest manifest;
|
||||
|
||||
/**
|
||||
* Create a new {@link ExplodedArchive} instance.
|
||||
* @param root the root directory
|
||||
*/
|
||||
public ExplodedArchive(File root) {
|
||||
this(root, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ExplodedArchive} instance.
|
||||
* @param root the root directory
|
||||
* @param recursive if recursive searching should be used to locate the manifest.
|
||||
* Defaults to {@code true}, directories with a large tree might want to set this to
|
||||
* {@code false}.
|
||||
*/
|
||||
public ExplodedArchive(File root, boolean recursive) {
|
||||
if (!root.exists() || !root.isDirectory()) {
|
||||
throw new IllegalArgumentException("Invalid source directory " + root);
|
||||
}
|
||||
this.root = root;
|
||||
this.recursive = recursive;
|
||||
this.manifestFile = getManifestFile(root);
|
||||
}
|
||||
|
||||
private File getManifestFile(File root) {
|
||||
File metaInf = new File(root, "META-INF");
|
||||
return new File(metaInf, "MANIFEST.MF");
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getUrl() throws MalformedURLException {
|
||||
return this.root.toURI().toURL();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Manifest getManifest() throws IOException {
|
||||
if (this.manifest == null && this.manifestFile.exists()) {
|
||||
try (FileInputStream inputStream = new FileInputStream(this.manifestFile)) {
|
||||
this.manifest = new Manifest(inputStream);
|
||||
}
|
||||
}
|
||||
return this.manifest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Archive> getNestedArchives(EntryFilter searchFilter, EntryFilter includeFilter) throws IOException {
|
||||
return new ArchiveIterator(this.root, this.recursive, searchFilter, includeFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
// @Deprecated(since = "2.3.10", forRemoval = false)
|
||||
public Iterator<Entry> iterator() {
|
||||
return new EntryIterator(this.root, this.recursive, null, null);
|
||||
}
|
||||
|
||||
protected Archive getNestedArchive(Entry entry) {
|
||||
File file = ((FileEntry) entry).getFile();
|
||||
return (file.isDirectory() ? new ExplodedArchive(file) : new SimpleJarFileArchive((FileEntry) entry));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExploded() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
try {
|
||||
return getUrl().toString();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return "exploded archive";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* File based {@link Entry} {@link Iterator}.
|
||||
*/
|
||||
private abstract static class AbstractIterator<T> implements Iterator<T> {
|
||||
|
||||
private static final Comparator<File> entryComparator = Comparator.comparing(File::getAbsolutePath);
|
||||
|
||||
private final File root;
|
||||
|
||||
private final boolean recursive;
|
||||
|
||||
private final EntryFilter searchFilter;
|
||||
|
||||
private final EntryFilter includeFilter;
|
||||
|
||||
private final Deque<Iterator<File>> stack = new LinkedList<>();
|
||||
|
||||
private FileEntry current;
|
||||
|
||||
private final String rootUrl;
|
||||
|
||||
AbstractIterator(File root, boolean recursive, EntryFilter searchFilter, EntryFilter includeFilter) {
|
||||
this.root = root;
|
||||
this.rootUrl = this.root.toURI().getPath();
|
||||
this.recursive = recursive;
|
||||
this.searchFilter = searchFilter;
|
||||
this.includeFilter = includeFilter;
|
||||
this.stack.add(listFiles(root));
|
||||
this.current = poll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.current != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
FileEntry entry = this.current;
|
||||
if (entry == null) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
this.current = poll();
|
||||
return adapt(entry);
|
||||
}
|
||||
|
||||
private FileEntry poll() {
|
||||
while (!this.stack.isEmpty()) {
|
||||
while (this.stack.peek().hasNext()) {
|
||||
File file = this.stack.peek().next();
|
||||
if (SKIPPED_NAMES.contains(file.getName())) {
|
||||
continue;
|
||||
}
|
||||
FileEntry entry = getFileEntry(file);
|
||||
if (isListable(entry)) {
|
||||
this.stack.addFirst(listFiles(file));
|
||||
}
|
||||
if (this.includeFilter == null || this.includeFilter.matches(entry)) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
this.stack.poll();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private FileEntry getFileEntry(File file) {
|
||||
URI uri = file.toURI();
|
||||
String name = uri.getPath().substring(this.rootUrl.length());
|
||||
try {
|
||||
return new FileEntry(name, file, uri.toURL());
|
||||
}
|
||||
catch (MalformedURLException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isListable(FileEntry entry) {
|
||||
return entry.isDirectory() && (this.recursive || entry.getFile().getParentFile().equals(this.root))
|
||||
&& (this.searchFilter == null || this.searchFilter.matches(entry))
|
||||
&& (this.includeFilter == null || !this.includeFilter.matches(entry));
|
||||
}
|
||||
|
||||
private Iterator<File> listFiles(File file) {
|
||||
File[] files = file.listFiles();
|
||||
if (files == null) {
|
||||
return Collections.emptyIterator();
|
||||
}
|
||||
Arrays.sort(files, entryComparator);
|
||||
return Arrays.asList(files).iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException("remove");
|
||||
}
|
||||
|
||||
protected abstract T adapt(FileEntry entry);
|
||||
|
||||
}
|
||||
|
||||
private static class EntryIterator extends AbstractIterator<Entry> {
|
||||
|
||||
EntryIterator(File root, boolean recursive, EntryFilter searchFilter, EntryFilter includeFilter) {
|
||||
super(root, recursive, searchFilter, includeFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Entry adapt(FileEntry entry) {
|
||||
return entry;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ArchiveIterator extends AbstractIterator<Archive> {
|
||||
|
||||
ArchiveIterator(File root, boolean recursive, EntryFilter searchFilter, EntryFilter includeFilter) {
|
||||
super(root, recursive, searchFilter, includeFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Archive adapt(FileEntry entry) {
|
||||
File file = entry.getFile();
|
||||
return (file.isDirectory() ? new ExplodedArchive(file) : new SimpleJarFileArchive(entry));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Entry} backed by a File.
|
||||
*/
|
||||
private static class FileEntry implements Entry {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final File file;
|
||||
|
||||
private final URL url;
|
||||
|
||||
FileEntry(String name, File file, URL url) {
|
||||
this.name = name;
|
||||
this.file = file;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
File getFile() {
|
||||
return this.file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory() {
|
||||
return this.file.isDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
URL getUrl() {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Archive} implementation backed by a simple JAR file that doesn't itself
|
||||
* contain nested archives.
|
||||
*/
|
||||
private static class SimpleJarFileArchive implements Archive {
|
||||
|
||||
private final URL url;
|
||||
|
||||
SimpleJarFileArchive(FileEntry file) {
|
||||
this.url = file.getUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getUrl() throws MalformedURLException {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Manifest getManifest() throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Archive> getNestedArchives(EntryFilter searchFilter, EntryFilter includeFilter)
|
||||
throws IOException {
|
||||
return Collections.emptyIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
// @Deprecated(since = "2.3.10", forRemoval = false)
|
||||
public Iterator<Entry> iterator() {
|
||||
return Collections.emptyIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
try {
|
||||
return getUrl().toString();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return "jar archive";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal.archive;
|
||||
|
||||
import fun.asgc.neutrino.core.aop.compiler.internal.jar.JarFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.FileAttribute;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.nio.file.attribute.PosixFilePermissions;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.UUID;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
/**
|
||||
* {@link Archive} implementation backed by a {@link JarFile}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class JarFileArchive implements Archive {
|
||||
|
||||
private static final String UNPACK_MARKER = "UNPACK:";
|
||||
|
||||
private static final int BUFFER_SIZE = 32 * 1024;
|
||||
|
||||
private static final FileAttribute<?>[] NO_FILE_ATTRIBUTES = {};
|
||||
|
||||
private static final EnumSet<PosixFilePermission> DIRECTORY_PERMISSIONS = EnumSet.of(PosixFilePermission.OWNER_READ,
|
||||
PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE);
|
||||
|
||||
private static final EnumSet<PosixFilePermission> FILE_PERMISSIONS = EnumSet.of(PosixFilePermission.OWNER_READ,
|
||||
PosixFilePermission.OWNER_WRITE);
|
||||
|
||||
private final JarFile jarFile;
|
||||
|
||||
private URL url;
|
||||
|
||||
private Path tempUnpackDirectory;
|
||||
|
||||
public JarFileArchive(File file) throws IOException {
|
||||
this(file, file.toURI().toURL());
|
||||
}
|
||||
|
||||
public JarFileArchive(File file, URL url) throws IOException {
|
||||
this(new JarFile(file));
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public JarFileArchive(JarFile jarFile) {
|
||||
this.jarFile = jarFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getUrl() throws MalformedURLException {
|
||||
if (this.url != null) {
|
||||
return this.url;
|
||||
}
|
||||
return this.jarFile.getUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Manifest getManifest() throws IOException {
|
||||
return this.jarFile.getManifest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Archive> getNestedArchives(EntryFilter searchFilter, EntryFilter includeFilter) throws IOException {
|
||||
return new NestedArchiveIterator(this.jarFile.iterator(), searchFilter, includeFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
// @Deprecated(since = "2.3.10", forRemoval = false)
|
||||
public Iterator<Entry> iterator() {
|
||||
return new EntryIterator(this.jarFile.iterator(), null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
this.jarFile.close();
|
||||
}
|
||||
|
||||
protected Archive getNestedArchive(Entry entry) throws IOException {
|
||||
JarEntry jarEntry = ((JarFileEntry) entry).getJarEntry();
|
||||
if (jarEntry.getComment().startsWith(UNPACK_MARKER)) {
|
||||
return getUnpackedNestedArchive(jarEntry);
|
||||
}
|
||||
try {
|
||||
JarFile jarFile = this.jarFile.getNestedJarFile(jarEntry);
|
||||
return new JarFileArchive(jarFile);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Failed to get nested archive for entry " + entry.getName(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Archive getUnpackedNestedArchive(JarEntry jarEntry) throws IOException {
|
||||
String name = jarEntry.getName();
|
||||
if (name.lastIndexOf('/') != -1) {
|
||||
name = name.substring(name.lastIndexOf('/') + 1);
|
||||
}
|
||||
Path path = getTempUnpackDirectory().resolve(name);
|
||||
if (!Files.exists(path) || Files.size(path) != jarEntry.getSize()) {
|
||||
unpack(jarEntry, path);
|
||||
}
|
||||
return new JarFileArchive(path.toFile(), path.toUri().toURL());
|
||||
}
|
||||
|
||||
private Path getTempUnpackDirectory() {
|
||||
if (this.tempUnpackDirectory == null) {
|
||||
Path tempDirectory = Paths.get(System.getProperty("java.io.tmpdir"));
|
||||
this.tempUnpackDirectory = createUnpackDirectory(tempDirectory);
|
||||
}
|
||||
return this.tempUnpackDirectory;
|
||||
}
|
||||
|
||||
private Path createUnpackDirectory(Path parent) {
|
||||
int attempts = 0;
|
||||
while (attempts++ < 1000) {
|
||||
String fileName = Paths.get(this.jarFile.getName()).getFileName().toString();
|
||||
Path unpackDirectory = parent.resolve(fileName + "-spring-boot-libs-" + UUID.randomUUID());
|
||||
try {
|
||||
createDirectory(unpackDirectory);
|
||||
return unpackDirectory;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Failed to create unpack directory in directory '" + parent + "'");
|
||||
}
|
||||
|
||||
private void unpack(JarEntry entry, Path path) throws IOException {
|
||||
createFile(path);
|
||||
path.toFile().deleteOnExit();
|
||||
try (InputStream inputStream = this.jarFile.getInputStream(entry);
|
||||
OutputStream outputStream = Files.newOutputStream(path, StandardOpenOption.WRITE,
|
||||
StandardOpenOption.TRUNCATE_EXISTING)) {
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
outputStream.flush();
|
||||
}
|
||||
}
|
||||
|
||||
private void createDirectory(Path path) throws IOException {
|
||||
Files.createDirectory(path, getFileAttributes(path.getFileSystem(), DIRECTORY_PERMISSIONS));
|
||||
}
|
||||
|
||||
private void createFile(Path path) throws IOException {
|
||||
Files.createFile(path, getFileAttributes(path.getFileSystem(), FILE_PERMISSIONS));
|
||||
}
|
||||
|
||||
private FileAttribute<?>[] getFileAttributes(FileSystem fileSystem, EnumSet<PosixFilePermission> ownerReadWrite) {
|
||||
if (!fileSystem.supportedFileAttributeViews().contains("posix")) {
|
||||
return NO_FILE_ATTRIBUTES;
|
||||
}
|
||||
return new FileAttribute<?>[] { PosixFilePermissions.asFileAttribute(ownerReadWrite) };
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
try {
|
||||
return getUrl().toString();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return "jar archive";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract base class for iterator implementations.
|
||||
*/
|
||||
private abstract static class AbstractIterator<T> implements Iterator<T> {
|
||||
|
||||
private final Iterator<JarEntry> iterator;
|
||||
|
||||
private final EntryFilter searchFilter;
|
||||
|
||||
private final EntryFilter includeFilter;
|
||||
|
||||
private Entry current;
|
||||
|
||||
AbstractIterator(Iterator<JarEntry> iterator, EntryFilter searchFilter, EntryFilter includeFilter) {
|
||||
this.iterator = iterator;
|
||||
this.searchFilter = searchFilter;
|
||||
this.includeFilter = includeFilter;
|
||||
this.current = poll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.current != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
T result = adapt(this.current);
|
||||
this.current = poll();
|
||||
return result;
|
||||
}
|
||||
|
||||
private Entry poll() {
|
||||
while (this.iterator.hasNext()) {
|
||||
JarFileEntry candidate = new JarFileEntry(this.iterator.next());
|
||||
if ((this.searchFilter == null || this.searchFilter.matches(candidate))
|
||||
&& (this.includeFilter == null || this.includeFilter.matches(candidate))) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected abstract T adapt(Entry entry);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Entry} iterator implementation backed by {@link JarEntry}.
|
||||
*/
|
||||
private static class EntryIterator extends AbstractIterator<Entry> {
|
||||
|
||||
EntryIterator(Iterator<JarEntry> iterator, EntryFilter searchFilter, EntryFilter includeFilter) {
|
||||
super(iterator, searchFilter, includeFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Entry adapt(Entry entry) {
|
||||
return entry;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Nested {@link Archive} iterator implementation backed by {@link JarEntry}.
|
||||
*/
|
||||
private class NestedArchiveIterator extends AbstractIterator<Archive> {
|
||||
|
||||
NestedArchiveIterator(Iterator<JarEntry> iterator, EntryFilter searchFilter, EntryFilter includeFilter) {
|
||||
super(iterator, searchFilter, includeFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Archive adapt(Entry entry) {
|
||||
try {
|
||||
return getNestedArchive(entry);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Entry} implementation backed by a {@link JarEntry}.
|
||||
*/
|
||||
private static class JarFileEntry implements Entry {
|
||||
|
||||
private final JarEntry jarEntry;
|
||||
|
||||
JarFileEntry(JarEntry jarEntry) {
|
||||
this.jarEntry = jarEntry;
|
||||
}
|
||||
|
||||
JarEntry getJarEntry() {
|
||||
return this.jarEntry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory() {
|
||||
return this.jarEntry.isDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.jarEntry.getName();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal.data;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Interface that provides read-only random access to some underlying data.
|
||||
* Implementations must allow concurrent reads in a thread-safe manner.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public interface RandomAccessData {
|
||||
|
||||
/**
|
||||
* Returns an {@link InputStream} that can be used to read the underlying data. The
|
||||
* caller is responsible close the underlying stream.
|
||||
* @return a new input stream that can be used to read the underlying data.
|
||||
* @throws IOException if the stream cannot be opened
|
||||
*/
|
||||
InputStream getInputStream() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns a new {@link RandomAccessData} for a specific subsection of this data.
|
||||
* @param offset the offset of the subsection
|
||||
* @param length the length of the subsection
|
||||
* @return the subsection data
|
||||
*/
|
||||
RandomAccessData getSubsection(long offset, long length);
|
||||
|
||||
/**
|
||||
* Reads all the data and returns it as a byte array.
|
||||
* @return the data
|
||||
* @throws IOException if the data cannot be read
|
||||
*/
|
||||
byte[] read() throws IOException;
|
||||
|
||||
/**
|
||||
* Reads the {@code length} bytes of data starting at the given {@code offset}.
|
||||
* @param offset the offset from which data should be read
|
||||
* @param length the number of bytes to be read
|
||||
* @return the data
|
||||
* @throws IOException if the data cannot be read
|
||||
* @throws IndexOutOfBoundsException if offset is beyond the end of the file or
|
||||
* subsection
|
||||
* @throws EOFException if offset plus length is greater than the length of the file
|
||||
* or subsection
|
||||
*/
|
||||
byte[] read(long offset, long length) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns the size of the data.
|
||||
* @return the size
|
||||
*/
|
||||
long getSize();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal.data;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* {@link RandomAccessData} implementation backed by a {@link RandomAccessFile}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class RandomAccessDataFile implements RandomAccessData {
|
||||
|
||||
private final FileAccess fileAccess;
|
||||
|
||||
private final long offset;
|
||||
|
||||
private final long length;
|
||||
|
||||
/**
|
||||
* Create a new {@link RandomAccessDataFile} backed by the specified file.
|
||||
* @param file the underlying file
|
||||
* @throws IllegalArgumentException if the file is null or does not exist
|
||||
*/
|
||||
public RandomAccessDataFile(File file) {
|
||||
if (file == null) {
|
||||
throw new IllegalArgumentException("File must not be null");
|
||||
}
|
||||
this.fileAccess = new FileAccess(file);
|
||||
this.offset = 0L;
|
||||
this.length = file.length();
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor used to create a {@link #getSubsection(long, long) subsection}.
|
||||
* @param fileAccess provides access to the underlying file
|
||||
* @param offset the offset of the section
|
||||
* @param length the length of the section
|
||||
*/
|
||||
private RandomAccessDataFile(FileAccess fileAccess, long offset, long length) {
|
||||
this.fileAccess = fileAccess;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying File.
|
||||
* @return the underlying file
|
||||
*/
|
||||
public File getFile() {
|
||||
return this.fileAccess.file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return new DataInputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RandomAccessData getSubsection(long offset, long length) {
|
||||
if (offset < 0 || length < 0 || offset + length > this.length) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
return new RandomAccessDataFile(this.fileAccess, this.offset + offset, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] read() throws IOException {
|
||||
return read(0, this.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] read(long offset, long length) throws IOException {
|
||||
if (offset > this.length) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
if (offset + length > this.length) {
|
||||
throw new EOFException();
|
||||
}
|
||||
byte[] bytes = new byte[(int) length];
|
||||
read(bytes, offset, 0, bytes.length);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private int readByte(long position) throws IOException {
|
||||
if (position >= this.length) {
|
||||
return -1;
|
||||
}
|
||||
return this.fileAccess.readByte(this.offset + position);
|
||||
}
|
||||
|
||||
private int read(byte[] bytes, long position, int offset, int length) throws IOException {
|
||||
if (position > this.length) {
|
||||
return -1;
|
||||
}
|
||||
return this.fileAccess.read(bytes, this.offset + position, offset, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize() {
|
||||
return this.length;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
this.fileAccess.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link InputStream} implementation for the {@link RandomAccessDataFile}.
|
||||
*/
|
||||
private class DataInputStream extends InputStream {
|
||||
|
||||
private int position;
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int read = RandomAccessDataFile.this.readByte(this.position);
|
||||
if (read > -1) {
|
||||
moveOn(1);
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b) throws IOException {
|
||||
return read(b, 0, (b != null) ? b.length : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
if (b == null) {
|
||||
throw new NullPointerException("Bytes must not be null");
|
||||
}
|
||||
return doRead(b, off, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the actual read.
|
||||
* @param b the bytes to read or {@code null} when reading a single byte
|
||||
* @param off the offset of the byte array
|
||||
* @param len the length of data to read
|
||||
* @return the number of bytes read into {@code b} or the actual read byte if
|
||||
* {@code b} is {@code null}. Returns -1 when the end of the stream is reached
|
||||
* @throws IOException in case of I/O errors
|
||||
*/
|
||||
int doRead(byte[] b, int off, int len) throws IOException {
|
||||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
int cappedLen = cap(len);
|
||||
if (cappedLen <= 0) {
|
||||
return -1;
|
||||
}
|
||||
return (int) moveOn(RandomAccessDataFile.this.read(b, this.position, off, cappedLen));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long n) throws IOException {
|
||||
return (n <= 0) ? 0 : moveOn(cap(n));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
return (int) RandomAccessDataFile.this.length - this.position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cap the specified value such that it cannot exceed the number of bytes
|
||||
* remaining.
|
||||
* @param n the value to cap
|
||||
* @return the capped value
|
||||
*/
|
||||
private int cap(long n) {
|
||||
return (int) Math.min(RandomAccessDataFile.this.length - this.position, n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the stream position forwards the specified amount.
|
||||
* @param amount the amount to move
|
||||
* @return the amount moved
|
||||
*/
|
||||
private long moveOn(int amount) {
|
||||
this.position += amount;
|
||||
return amount;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class FileAccess {
|
||||
|
||||
private final Object monitor = new Object();
|
||||
|
||||
private final File file;
|
||||
|
||||
private RandomAccessFile randomAccessFile;
|
||||
|
||||
private FileAccess(File file) {
|
||||
this.file = file;
|
||||
openIfNecessary();
|
||||
}
|
||||
|
||||
private int read(byte[] bytes, long position, int offset, int length) throws IOException {
|
||||
synchronized (this.monitor) {
|
||||
openIfNecessary();
|
||||
this.randomAccessFile.seek(position);
|
||||
return this.randomAccessFile.read(bytes, offset, length);
|
||||
}
|
||||
}
|
||||
|
||||
private void openIfNecessary() {
|
||||
if (this.randomAccessFile == null) {
|
||||
try {
|
||||
this.randomAccessFile = new RandomAccessFile(this.file, "r");
|
||||
}
|
||||
catch (FileNotFoundException ex) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("File %s must exist", this.file.getAbsolutePath()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void close() throws IOException {
|
||||
synchronized (this.monitor) {
|
||||
if (this.randomAccessFile != null) {
|
||||
this.randomAccessFile.close();
|
||||
this.randomAccessFile = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int readByte(long position) throws IOException {
|
||||
synchronized (this.monitor) {
|
||||
openIfNecessary();
|
||||
this.randomAccessFile.seek(position);
|
||||
return this.randomAccessFile.read();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal.jar;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.security.Permission;
|
||||
|
||||
/**
|
||||
* Base class for extended variants of {@link java.util.jar.JarFile}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
abstract class AbstractJarFile extends java.util.jar.JarFile {
|
||||
|
||||
/**
|
||||
* Create a new {@link AbstractJarFile}.
|
||||
* @param file the root jar file.
|
||||
* @throws IOException on IO error
|
||||
*/
|
||||
AbstractJarFile(File file) throws IOException {
|
||||
super(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a URL that can be used to access this JAR file. NOTE: the specified URL
|
||||
* cannot be serialized and or cloned.
|
||||
* @return the URL
|
||||
* @throws MalformedURLException if the URL is malformed
|
||||
*/
|
||||
abstract URL getUrl() throws MalformedURLException;
|
||||
|
||||
/**
|
||||
* Return the {@link JarFileType} of this instance.
|
||||
* @return the jar file type
|
||||
*/
|
||||
abstract JarFileType getType();
|
||||
|
||||
/**
|
||||
* Return the security permission for this JAR.
|
||||
* @return the security permission.
|
||||
*/
|
||||
abstract Permission getPermission();
|
||||
|
||||
/**
|
||||
* Return an {@link InputStream} for the entire jar contents.
|
||||
* @return the contents input stream
|
||||
* @throws IOException on IO error
|
||||
*/
|
||||
abstract InputStream getInputStream() throws IOException;
|
||||
|
||||
/**
|
||||
* The type of a {@link JarFile}.
|
||||
*/
|
||||
enum JarFileType {
|
||||
|
||||
DIRECT, NESTED_DIRECTORY, NESTED_JAR
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal.jar;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Simple wrapper around a byte array that represents an ASCII. Used for performance
|
||||
* reasons to save constructing Strings for ZIP data.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
final class AsciiBytes {
|
||||
|
||||
private static final String EMPTY_STRING = "";
|
||||
|
||||
private static final int[] INITIAL_BYTE_BITMASK = { 0x7F, 0x1F, 0x0F, 0x07 };
|
||||
|
||||
private static final int SUBSEQUENT_BYTE_BITMASK = 0x3F;
|
||||
|
||||
private final byte[] bytes;
|
||||
|
||||
private final int offset;
|
||||
|
||||
private final int length;
|
||||
|
||||
private String string;
|
||||
|
||||
private int hash;
|
||||
|
||||
/**
|
||||
* Create a new {@link AsciiBytes} from the specified String.
|
||||
* @param string the source string
|
||||
*/
|
||||
AsciiBytes(String string) {
|
||||
this(string.getBytes(StandardCharsets.UTF_8));
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link AsciiBytes} from the specified bytes. NOTE: underlying bytes
|
||||
* are not expected to change.
|
||||
* @param bytes the source bytes
|
||||
*/
|
||||
AsciiBytes(byte[] bytes) {
|
||||
this(bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link AsciiBytes} from the specified bytes. NOTE: underlying bytes
|
||||
* are not expected to change.
|
||||
* @param bytes the source bytes
|
||||
* @param offset the offset
|
||||
* @param length the length
|
||||
*/
|
||||
AsciiBytes(byte[] bytes, int offset, int length) {
|
||||
if (offset < 0 || length < 0 || (offset + length) > bytes.length) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
this.bytes = bytes;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
int length() {
|
||||
return this.length;
|
||||
}
|
||||
|
||||
boolean startsWith(AsciiBytes prefix) {
|
||||
if (this == prefix) {
|
||||
return true;
|
||||
}
|
||||
if (prefix.length > this.length) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < prefix.length; i++) {
|
||||
if (this.bytes[i + this.offset] != prefix.bytes[i + prefix.offset]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean endsWith(AsciiBytes postfix) {
|
||||
if (this == postfix) {
|
||||
return true;
|
||||
}
|
||||
if (postfix.length > this.length) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < postfix.length; i++) {
|
||||
if (this.bytes[this.offset + (this.length - 1) - i] != postfix.bytes[postfix.offset + (postfix.length - 1)
|
||||
- i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
AsciiBytes substring(int beginIndex) {
|
||||
return substring(beginIndex, this.length);
|
||||
}
|
||||
|
||||
AsciiBytes substring(int beginIndex, int endIndex) {
|
||||
int length = endIndex - beginIndex;
|
||||
if (this.offset + length > this.bytes.length) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
return new AsciiBytes(this.bytes, this.offset + beginIndex, length);
|
||||
}
|
||||
|
||||
boolean matches(CharSequence name, char suffix) {
|
||||
int charIndex = 0;
|
||||
int nameLen = name.length();
|
||||
int totalLen = nameLen + ((suffix != 0) ? 1 : 0);
|
||||
for (int i = this.offset; i < this.offset + this.length; i++) {
|
||||
int b = this.bytes[i];
|
||||
int remainingUtfBytes = getNumberOfUtfBytes(b) - 1;
|
||||
b &= INITIAL_BYTE_BITMASK[remainingUtfBytes];
|
||||
for (int j = 0; j < remainingUtfBytes; j++) {
|
||||
b = (b << 6) + (this.bytes[++i] & SUBSEQUENT_BYTE_BITMASK);
|
||||
}
|
||||
char c = getChar(name, suffix, charIndex++);
|
||||
if (b <= 0xFFFF) {
|
||||
if (c != b) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (c != ((b >> 0xA) + 0xD7C0)) {
|
||||
return false;
|
||||
}
|
||||
c = getChar(name, suffix, charIndex++);
|
||||
if (c != ((b & 0x3FF) + 0xDC00)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return charIndex == totalLen;
|
||||
}
|
||||
|
||||
private char getChar(CharSequence name, char suffix, int index) {
|
||||
if (index < name.length()) {
|
||||
return name.charAt(index);
|
||||
}
|
||||
if (index == name.length()) {
|
||||
return suffix;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int getNumberOfUtfBytes(int b) {
|
||||
if ((b & 0x80) == 0) {
|
||||
return 1;
|
||||
}
|
||||
int numberOfUtfBytes = 0;
|
||||
while ((b & 0x80) != 0) {
|
||||
b <<= 1;
|
||||
numberOfUtfBytes++;
|
||||
}
|
||||
return numberOfUtfBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj.getClass() == AsciiBytes.class) {
|
||||
AsciiBytes other = (AsciiBytes) obj;
|
||||
if (this.length == other.length) {
|
||||
for (int i = 0; i < this.length; i++) {
|
||||
if (this.bytes[this.offset + i] != other.bytes[other.offset + i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = this.hash;
|
||||
if (hash == 0 && this.bytes.length > 0) {
|
||||
for (int i = this.offset; i < this.offset + this.length; i++) {
|
||||
int b = this.bytes[i];
|
||||
int remainingUtfBytes = getNumberOfUtfBytes(b) - 1;
|
||||
b &= INITIAL_BYTE_BITMASK[remainingUtfBytes];
|
||||
for (int j = 0; j < remainingUtfBytes; j++) {
|
||||
b = (b << 6) + (this.bytes[++i] & SUBSEQUENT_BYTE_BITMASK);
|
||||
}
|
||||
if (b <= 0xFFFF) {
|
||||
hash = 31 * hash + b;
|
||||
}
|
||||
else {
|
||||
hash = 31 * hash + ((b >> 0xA) + 0xD7C0);
|
||||
hash = 31 * hash + ((b & 0x3FF) + 0xDC00);
|
||||
}
|
||||
}
|
||||
this.hash = hash;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (this.string == null) {
|
||||
if (this.length == 0) {
|
||||
this.string = EMPTY_STRING;
|
||||
}
|
||||
else {
|
||||
this.string = new String(this.bytes, this.offset, this.length, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
return this.string;
|
||||
}
|
||||
|
||||
static String toString(byte[] bytes) {
|
||||
return new String(bytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
static int hashCode(CharSequence charSequence) {
|
||||
// We're compatible with String's hashCode()
|
||||
if (charSequence instanceof StringSequence) {
|
||||
// ... but save making an unnecessary String for StringSequence
|
||||
return charSequence.hashCode();
|
||||
}
|
||||
return charSequence.toString().hashCode();
|
||||
}
|
||||
|
||||
static int hashCode(int hash, char suffix) {
|
||||
return (suffix != 0) ? (31 * hash + suffix) : hash;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal.jar;
|
||||
|
||||
/**
|
||||
* Utilities for dealing with bytes from ZIP files.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
final class Bytes {
|
||||
|
||||
private Bytes() {
|
||||
}
|
||||
|
||||
static long littleEndianValue(byte[] bytes, int offset, int length) {
|
||||
long value = 0;
|
||||
for (int i = length - 1; i >= 0; i--) {
|
||||
value = ((value << 8) | (bytes[offset + i] & 0xFF));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal.jar;
|
||||
|
||||
import fun.asgc.neutrino.core.aop.compiler.internal.data.RandomAccessData;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A ZIP File "End of central directory record" (EOCD).
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @author Camille Vienot
|
||||
* @see <a href="https://en.wikipedia.org/wiki/Zip_%28file_format%29">Zip File Format</a>
|
||||
*/
|
||||
class CentralDirectoryEndRecord {
|
||||
|
||||
private static final int MINIMUM_SIZE = 22;
|
||||
|
||||
private static final int MAXIMUM_COMMENT_LENGTH = 0xFFFF;
|
||||
|
||||
private static final int MAXIMUM_SIZE = MINIMUM_SIZE + MAXIMUM_COMMENT_LENGTH;
|
||||
|
||||
private static final int SIGNATURE = 0x06054b50;
|
||||
|
||||
private static final int COMMENT_LENGTH_OFFSET = 20;
|
||||
|
||||
private static final int READ_BLOCK_SIZE = 256;
|
||||
|
||||
private final Zip64End zip64End;
|
||||
|
||||
private byte[] block;
|
||||
|
||||
private int offset;
|
||||
|
||||
private int size;
|
||||
|
||||
/**
|
||||
* Create a new {@link CentralDirectoryEndRecord} instance from the specified
|
||||
* {@link RandomAccessData}, searching backwards from the end until a valid block is
|
||||
* located.
|
||||
* @param data the source data
|
||||
* @throws IOException in case of I/O errors
|
||||
*/
|
||||
CentralDirectoryEndRecord(RandomAccessData data) throws IOException {
|
||||
this.block = createBlockFromEndOfData(data, READ_BLOCK_SIZE);
|
||||
this.size = MINIMUM_SIZE;
|
||||
this.offset = this.block.length - this.size;
|
||||
while (!isValid()) {
|
||||
this.size++;
|
||||
if (this.size > this.block.length) {
|
||||
if (this.size >= MAXIMUM_SIZE || this.size > data.getSize()) {
|
||||
throw new IOException(
|
||||
"Unable to find ZIP central directory records after reading " + this.size + " bytes");
|
||||
}
|
||||
this.block = createBlockFromEndOfData(data, this.size + READ_BLOCK_SIZE);
|
||||
}
|
||||
this.offset = this.block.length - this.size;
|
||||
}
|
||||
long startOfCentralDirectoryEndRecord = data.getSize() - this.size;
|
||||
Zip64Locator zip64Locator = Zip64Locator.find(data, startOfCentralDirectoryEndRecord);
|
||||
this.zip64End = (zip64Locator != null) ? new Zip64End(data, zip64Locator) : null;
|
||||
}
|
||||
|
||||
private byte[] createBlockFromEndOfData(RandomAccessData data, int size) throws IOException {
|
||||
int length = (int) Math.min(data.getSize(), size);
|
||||
return data.read(data.getSize() - length, length);
|
||||
}
|
||||
|
||||
private boolean isValid() {
|
||||
if (this.block.length < MINIMUM_SIZE || Bytes.littleEndianValue(this.block, this.offset + 0, 4) != SIGNATURE) {
|
||||
return false;
|
||||
}
|
||||
// Total size must be the structure size + comment
|
||||
long commentLength = Bytes.littleEndianValue(this.block, this.offset + COMMENT_LENGTH_OFFSET, 2);
|
||||
return this.size == MINIMUM_SIZE + commentLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the location in the data that the archive actually starts. For most files
|
||||
* the archive data will start at 0, however, it is possible to have prefixed bytes
|
||||
* (often used for startup scripts) at the beginning of the data.
|
||||
* @param data the source data
|
||||
* @return the offset within the data where the archive begins
|
||||
*/
|
||||
long getStartOfArchive(RandomAccessData data) {
|
||||
long length = Bytes.littleEndianValue(this.block, this.offset + 12, 4);
|
||||
long specifiedOffset = (this.zip64End != null) ? this.zip64End.centralDirectoryOffset
|
||||
: Bytes.littleEndianValue(this.block, this.offset + 16, 4);
|
||||
long zip64EndSize = (this.zip64End != null) ? this.zip64End.getSize() : 0L;
|
||||
int zip64LocSize = (this.zip64End != null) ? Zip64Locator.ZIP64_LOCSIZE : 0;
|
||||
long actualOffset = data.getSize() - this.size - length - zip64EndSize - zip64LocSize;
|
||||
return actualOffset - specifiedOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bytes of the "Central directory" based on the offset indicated in this
|
||||
* record.
|
||||
* @param data the source data
|
||||
* @return the central directory data
|
||||
*/
|
||||
RandomAccessData getCentralDirectory(RandomAccessData data) {
|
||||
if (this.zip64End != null) {
|
||||
return this.zip64End.getCentralDirectory(data);
|
||||
}
|
||||
long offset = Bytes.littleEndianValue(this.block, this.offset + 16, 4);
|
||||
long length = Bytes.littleEndianValue(this.block, this.offset + 12, 4);
|
||||
return data.getSubsection(offset, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of ZIP entries in the file.
|
||||
* @return the number of records in the zip
|
||||
*/
|
||||
int getNumberOfRecords() {
|
||||
if (this.zip64End != null) {
|
||||
return this.zip64End.getNumberOfRecords();
|
||||
}
|
||||
long numberOfRecords = Bytes.littleEndianValue(this.block, this.offset + 10, 2);
|
||||
return (int) numberOfRecords;
|
||||
}
|
||||
|
||||
String getComment() {
|
||||
int commentLength = (int) Bytes.littleEndianValue(this.block, this.offset + COMMENT_LENGTH_OFFSET, 2);
|
||||
AsciiBytes comment = new AsciiBytes(this.block, this.offset + COMMENT_LENGTH_OFFSET + 2, commentLength);
|
||||
return comment.toString();
|
||||
}
|
||||
|
||||
boolean isZip64() {
|
||||
return this.zip64End != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A Zip64 end of central directory record.
|
||||
*
|
||||
* @see <a href="https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT">Chapter
|
||||
* 4.3.14 of Zip64 specification</a>
|
||||
*/
|
||||
private static final class Zip64End {
|
||||
|
||||
private static final int ZIP64_ENDTOT = 32; // total number of entries
|
||||
|
||||
private static final int ZIP64_ENDSIZ = 40; // central directory size in bytes
|
||||
|
||||
private static final int ZIP64_ENDOFF = 48; // offset of first CEN header
|
||||
|
||||
private final Zip64Locator locator;
|
||||
|
||||
private final long centralDirectoryOffset;
|
||||
|
||||
private final long centralDirectoryLength;
|
||||
|
||||
private final int numberOfRecords;
|
||||
|
||||
private Zip64End(RandomAccessData data, Zip64Locator locator) throws IOException {
|
||||
this.locator = locator;
|
||||
byte[] block = data.read(locator.getZip64EndOffset(), 56);
|
||||
this.centralDirectoryOffset = Bytes.littleEndianValue(block, ZIP64_ENDOFF, 8);
|
||||
this.centralDirectoryLength = Bytes.littleEndianValue(block, ZIP64_ENDSIZ, 8);
|
||||
this.numberOfRecords = (int) Bytes.littleEndianValue(block, ZIP64_ENDTOT, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the size of this zip 64 end of central directory record.
|
||||
* @return size of this zip 64 end of central directory record
|
||||
*/
|
||||
private long getSize() {
|
||||
return this.locator.getZip64EndSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bytes of the "Central directory" based on the offset indicated in
|
||||
* this record.
|
||||
* @param data the source data
|
||||
* @return the central directory data
|
||||
*/
|
||||
private RandomAccessData getCentralDirectory(RandomAccessData data) {
|
||||
return data.getSubsection(this.centralDirectoryOffset, this.centralDirectoryLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of entries in the zip64 archive.
|
||||
* @return the number of records in the zip
|
||||
*/
|
||||
private int getNumberOfRecords() {
|
||||
return this.numberOfRecords;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A Zip64 end of central directory locator.
|
||||
*
|
||||
* @see <a href="https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT">Chapter
|
||||
* 4.3.15 of Zip64 specification</a>
|
||||
*/
|
||||
private static final class Zip64Locator {
|
||||
|
||||
static final int SIGNATURE = 0x07064b50;
|
||||
|
||||
static final int ZIP64_LOCSIZE = 20; // locator size
|
||||
|
||||
static final int ZIP64_LOCOFF = 8; // offset of zip64 end
|
||||
|
||||
private final long zip64EndOffset;
|
||||
|
||||
private final long offset;
|
||||
|
||||
private Zip64Locator(long offset, byte[] block) {
|
||||
this.offset = offset;
|
||||
this.zip64EndOffset = Bytes.littleEndianValue(block, ZIP64_LOCOFF, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the size of the zip 64 end record located by this zip64 end locator.
|
||||
* @return size of the zip 64 end record located by this zip64 end locator
|
||||
*/
|
||||
private long getZip64EndSize() {
|
||||
return this.offset - this.zip64EndOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the offset to locate {@link Zip64End}.
|
||||
* @return offset of the Zip64 end of central directory record
|
||||
*/
|
||||
private long getZip64EndOffset() {
|
||||
return this.zip64EndOffset;
|
||||
}
|
||||
|
||||
private static Zip64Locator find(RandomAccessData data, long centralDirectoryEndOffset) throws IOException {
|
||||
long offset = centralDirectoryEndOffset - ZIP64_LOCSIZE;
|
||||
if (offset >= 0) {
|
||||
byte[] block = data.read(offset, ZIP64_LOCSIZE);
|
||||
if (Bytes.littleEndianValue(block, 0, 4) == SIGNATURE) {
|
||||
return new Zip64Locator(offset, block);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal.jar;
|
||||
|
||||
import fun.asgc.neutrino.core.aop.compiler.internal.data.RandomAccessData;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.ChronoField;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.time.temporal.ValueRange;
|
||||
|
||||
/**
|
||||
* A ZIP File "Central directory file header record" (CDFH).
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @author Dmytro Nosan
|
||||
* @see <a href="https://en.wikipedia.org/wiki/Zip_%28file_format%29">Zip File Format</a>
|
||||
*/
|
||||
|
||||
final class CentralDirectoryFileHeader implements FileHeader {
|
||||
|
||||
private static final AsciiBytes SLASH = new AsciiBytes("/");
|
||||
|
||||
private static final byte[] NO_EXTRA = {};
|
||||
|
||||
private static final AsciiBytes NO_COMMENT = new AsciiBytes("");
|
||||
|
||||
private byte[] header;
|
||||
|
||||
private int headerOffset;
|
||||
|
||||
private AsciiBytes name;
|
||||
|
||||
private byte[] extra;
|
||||
|
||||
private AsciiBytes comment;
|
||||
|
||||
private long localHeaderOffset;
|
||||
|
||||
CentralDirectoryFileHeader() {
|
||||
}
|
||||
|
||||
CentralDirectoryFileHeader(byte[] header, int headerOffset, AsciiBytes name, byte[] extra, AsciiBytes comment,
|
||||
long localHeaderOffset) {
|
||||
this.header = header;
|
||||
this.headerOffset = headerOffset;
|
||||
this.name = name;
|
||||
this.extra = extra;
|
||||
this.comment = comment;
|
||||
this.localHeaderOffset = localHeaderOffset;
|
||||
}
|
||||
|
||||
void load(byte[] data, int dataOffset, RandomAccessData variableData, long variableOffset, JarEntryFilter filter)
|
||||
throws IOException {
|
||||
// Load fixed part
|
||||
this.header = data;
|
||||
this.headerOffset = dataOffset;
|
||||
long compressedSize = Bytes.littleEndianValue(data, dataOffset + 20, 4);
|
||||
long uncompressedSize = Bytes.littleEndianValue(data, dataOffset + 24, 4);
|
||||
long nameLength = Bytes.littleEndianValue(data, dataOffset + 28, 2);
|
||||
long extraLength = Bytes.littleEndianValue(data, dataOffset + 30, 2);
|
||||
long commentLength = Bytes.littleEndianValue(data, dataOffset + 32, 2);
|
||||
long localHeaderOffset = Bytes.littleEndianValue(data, dataOffset + 42, 4);
|
||||
// Load variable part
|
||||
dataOffset += 46;
|
||||
if (variableData != null) {
|
||||
data = variableData.read(variableOffset + 46, nameLength + extraLength + commentLength);
|
||||
dataOffset = 0;
|
||||
}
|
||||
this.name = new AsciiBytes(data, dataOffset, (int) nameLength);
|
||||
if (filter != null) {
|
||||
this.name = filter.apply(this.name);
|
||||
}
|
||||
this.extra = NO_EXTRA;
|
||||
this.comment = NO_COMMENT;
|
||||
if (extraLength > 0) {
|
||||
this.extra = new byte[(int) extraLength];
|
||||
System.arraycopy(data, (int) (dataOffset + nameLength), this.extra, 0, this.extra.length);
|
||||
}
|
||||
this.localHeaderOffset = getLocalHeaderOffset(compressedSize, uncompressedSize, localHeaderOffset, this.extra);
|
||||
if (commentLength > 0) {
|
||||
this.comment = new AsciiBytes(data, (int) (dataOffset + nameLength + extraLength), (int) commentLength);
|
||||
}
|
||||
}
|
||||
|
||||
private long getLocalHeaderOffset(long compressedSize, long uncompressedSize, long localHeaderOffset, byte[] extra)
|
||||
throws IOException {
|
||||
if (localHeaderOffset != 0xFFFFFFFFL) {
|
||||
return localHeaderOffset;
|
||||
}
|
||||
int extraOffset = 0;
|
||||
while (extraOffset < extra.length - 2) {
|
||||
int id = (int) Bytes.littleEndianValue(extra, extraOffset, 2);
|
||||
int length = (int) Bytes.littleEndianValue(extra, extraOffset, 2);
|
||||
extraOffset += 4;
|
||||
if (id == 1) {
|
||||
int localHeaderExtraOffset = 0;
|
||||
if (compressedSize == 0xFFFFFFFFL) {
|
||||
localHeaderExtraOffset += 4;
|
||||
}
|
||||
if (uncompressedSize == 0xFFFFFFFFL) {
|
||||
localHeaderExtraOffset += 4;
|
||||
}
|
||||
return Bytes.littleEndianValue(extra, extraOffset + localHeaderExtraOffset, 8);
|
||||
}
|
||||
extraOffset += length;
|
||||
}
|
||||
throw new IOException("Zip64 Extended Information Extra Field not found");
|
||||
}
|
||||
|
||||
AsciiBytes getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasName(CharSequence name, char suffix) {
|
||||
return this.name.matches(name, suffix);
|
||||
}
|
||||
|
||||
boolean isDirectory() {
|
||||
return this.name.endsWith(SLASH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMethod() {
|
||||
return (int) Bytes.littleEndianValue(this.header, this.headerOffset + 10, 2);
|
||||
}
|
||||
|
||||
long getTime() {
|
||||
long datetime = Bytes.littleEndianValue(this.header, this.headerOffset + 12, 4);
|
||||
return decodeMsDosFormatDateTime(datetime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode MS-DOS Date Time details. See <a href=
|
||||
* "https://docs.microsoft.com/en-gb/windows/desktop/api/winbase/nf-winbase-dosdatetimetofiletime">
|
||||
* Microsoft's documentation</a> for more details of the format.
|
||||
* @param datetime the date and time
|
||||
* @return the date and time as milliseconds since the epoch
|
||||
*/
|
||||
private long decodeMsDosFormatDateTime(long datetime) {
|
||||
int year = getChronoValue(((datetime >> 25) & 0x7f) + 1980, ChronoField.YEAR);
|
||||
int month = getChronoValue((datetime >> 21) & 0x0f, ChronoField.MONTH_OF_YEAR);
|
||||
int day = getChronoValue((datetime >> 16) & 0x1f, ChronoField.DAY_OF_MONTH);
|
||||
int hour = getChronoValue((datetime >> 11) & 0x1f, ChronoField.HOUR_OF_DAY);
|
||||
int minute = getChronoValue((datetime >> 5) & 0x3f, ChronoField.MINUTE_OF_HOUR);
|
||||
int second = getChronoValue((datetime << 1) & 0x3e, ChronoField.SECOND_OF_MINUTE);
|
||||
return ZonedDateTime.of(year, month, day, hour, minute, second, 0, ZoneId.systemDefault())
|
||||
.toInstant()
|
||||
.truncatedTo(ChronoUnit.SECONDS)
|
||||
.toEpochMilli();
|
||||
}
|
||||
|
||||
long getCrc() {
|
||||
return Bytes.littleEndianValue(this.header, this.headerOffset + 16, 4);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCompressedSize() {
|
||||
return Bytes.littleEndianValue(this.header, this.headerOffset + 20, 4);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize() {
|
||||
return Bytes.littleEndianValue(this.header, this.headerOffset + 24, 4);
|
||||
}
|
||||
|
||||
byte[] getExtra() {
|
||||
return this.extra;
|
||||
}
|
||||
|
||||
boolean hasExtra() {
|
||||
return this.extra.length > 0;
|
||||
}
|
||||
|
||||
AsciiBytes getComment() {
|
||||
return this.comment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLocalHeaderOffset() {
|
||||
return this.localHeaderOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CentralDirectoryFileHeader clone() {
|
||||
byte[] header = new byte[46];
|
||||
System.arraycopy(this.header, this.headerOffset, header, 0, header.length);
|
||||
return new CentralDirectoryFileHeader(header, 0, this.name, header, this.comment, this.localHeaderOffset);
|
||||
}
|
||||
|
||||
static CentralDirectoryFileHeader fromRandomAccessData(RandomAccessData data, long offset, JarEntryFilter filter)
|
||||
throws IOException {
|
||||
CentralDirectoryFileHeader fileHeader = new CentralDirectoryFileHeader();
|
||||
byte[] bytes = data.read(offset, 46);
|
||||
fileHeader.load(bytes, 0, data, offset, filter);
|
||||
return fileHeader;
|
||||
}
|
||||
|
||||
private static int getChronoValue(long value, ChronoField field) {
|
||||
ValueRange range = field.range();
|
||||
return Math.toIntExact(Math.min(Math.max(value, range.getMinimum()), range.getMaximum()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal.jar;
|
||||
|
||||
import fun.asgc.neutrino.core.aop.compiler.internal.data.RandomAccessData;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Parses the central directory from a JAR file.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @see CentralDirectoryVisitor
|
||||
*/
|
||||
class CentralDirectoryParser {
|
||||
|
||||
private static final int CENTRAL_DIRECTORY_HEADER_BASE_SIZE = 46;
|
||||
|
||||
private final List<CentralDirectoryVisitor> visitors = new ArrayList<>();
|
||||
|
||||
<T extends CentralDirectoryVisitor> T addVisitor(T visitor) {
|
||||
this.visitors.add(visitor);
|
||||
return visitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the source data, triggering {@link CentralDirectoryVisitor visitors}.
|
||||
* @param data the source data
|
||||
* @param skipPrefixBytes if prefix bytes should be skipped
|
||||
* @return the actual archive data without any prefix bytes
|
||||
* @throws IOException on error
|
||||
*/
|
||||
RandomAccessData parse(RandomAccessData data, boolean skipPrefixBytes) throws IOException {
|
||||
CentralDirectoryEndRecord endRecord = new CentralDirectoryEndRecord(data);
|
||||
if (skipPrefixBytes) {
|
||||
data = getArchiveData(endRecord, data);
|
||||
}
|
||||
RandomAccessData centralDirectoryData = endRecord.getCentralDirectory(data);
|
||||
visitStart(endRecord, centralDirectoryData);
|
||||
parseEntries(endRecord, centralDirectoryData);
|
||||
visitEnd();
|
||||
return data;
|
||||
}
|
||||
|
||||
private void parseEntries(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData)
|
||||
throws IOException {
|
||||
byte[] bytes = centralDirectoryData.read(0, centralDirectoryData.getSize());
|
||||
CentralDirectoryFileHeader fileHeader = new CentralDirectoryFileHeader();
|
||||
int dataOffset = 0;
|
||||
for (int i = 0; i < endRecord.getNumberOfRecords(); i++) {
|
||||
fileHeader.load(bytes, dataOffset, null, 0, null);
|
||||
visitFileHeader(dataOffset, fileHeader);
|
||||
dataOffset += CENTRAL_DIRECTORY_HEADER_BASE_SIZE + fileHeader.getName().length()
|
||||
+ fileHeader.getComment().length() + fileHeader.getExtra().length;
|
||||
}
|
||||
}
|
||||
|
||||
private RandomAccessData getArchiveData(CentralDirectoryEndRecord endRecord, RandomAccessData data) {
|
||||
long offset = endRecord.getStartOfArchive(data);
|
||||
if (offset == 0) {
|
||||
return data;
|
||||
}
|
||||
return data.getSubsection(offset, data.getSize() - offset);
|
||||
}
|
||||
|
||||
private void visitStart(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData) {
|
||||
for (CentralDirectoryVisitor visitor : this.visitors) {
|
||||
visitor.visitStart(endRecord, centralDirectoryData);
|
||||
}
|
||||
}
|
||||
|
||||
private void visitFileHeader(long dataOffset, CentralDirectoryFileHeader fileHeader) {
|
||||
for (CentralDirectoryVisitor visitor : this.visitors) {
|
||||
visitor.visitFileHeader(fileHeader, dataOffset);
|
||||
}
|
||||
}
|
||||
|
||||
private void visitEnd() {
|
||||
for (CentralDirectoryVisitor visitor : this.visitors) {
|
||||
visitor.visitEnd();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal.jar;
|
||||
|
||||
import fun.asgc.neutrino.core.aop.compiler.internal.data.RandomAccessData;
|
||||
|
||||
/**
|
||||
* Callback visitor triggered by {@link CentralDirectoryParser}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
interface CentralDirectoryVisitor {
|
||||
|
||||
void visitStart(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData);
|
||||
|
||||
void visitFileHeader(CentralDirectoryFileHeader fileHeader, long dataOffset);
|
||||
|
||||
void visitEnd();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal.jar;
|
||||
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
/**
|
||||
* A file header record that has been loaded from a Jar file.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @see JarEntry
|
||||
* @see CentralDirectoryFileHeader
|
||||
*/
|
||||
interface FileHeader {
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the header has the given name.
|
||||
* @param name the name to test
|
||||
* @param suffix an additional suffix (or {@code 0})
|
||||
* @return {@code true} if the header has the given name
|
||||
*/
|
||||
boolean hasName(CharSequence name, char suffix);
|
||||
|
||||
/**
|
||||
* Return the offset of the load file header within the archive data.
|
||||
* @return the local header offset
|
||||
*/
|
||||
long getLocalHeaderOffset();
|
||||
|
||||
/**
|
||||
* Return the compressed size of the entry.
|
||||
* @return the compressed size.
|
||||
*/
|
||||
long getCompressedSize();
|
||||
|
||||
/**
|
||||
* Return the uncompressed size of the entry.
|
||||
* @return the uncompressed size.
|
||||
*/
|
||||
long getSize();
|
||||
|
||||
/**
|
||||
* Return the method used to compress the data.
|
||||
* @return the zip compression method
|
||||
* @see ZipEntry#STORED
|
||||
* @see ZipEntry#DEFLATED
|
||||
*/
|
||||
int getMethod();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,448 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal.jar;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.net.*;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* {@link URLStreamHandler} for Spring Boot loader {@link JarFile}s.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.0.0
|
||||
* @see JarFile#registerUrlProtocolHandler()
|
||||
*/
|
||||
public class Handler extends URLStreamHandler {
|
||||
|
||||
// NOTE: in order to be found as a URL protocol handler, this class must be public,
|
||||
// must be named Handler and must be in a package ending '.jar'
|
||||
|
||||
private static final String JAR_PROTOCOL = "jar:";
|
||||
|
||||
private static final String FILE_PROTOCOL = "file:";
|
||||
|
||||
private static final String TOMCAT_WARFILE_PROTOCOL = "war:file:";
|
||||
|
||||
private static final String SEPARATOR = "!/";
|
||||
|
||||
private static final Pattern SEPARATOR_PATTERN = Pattern.compile(SEPARATOR, Pattern.LITERAL);
|
||||
|
||||
private static final String CURRENT_DIR = "/./";
|
||||
|
||||
private static final Pattern CURRENT_DIR_PATTERN = Pattern.compile(CURRENT_DIR, Pattern.LITERAL);
|
||||
|
||||
private static final String PARENT_DIR = "/../";
|
||||
|
||||
private static final String PROTOCOL_HANDLER = "java.protocol.handler.pkgs";
|
||||
|
||||
private static final String[] FALLBACK_HANDLERS = { "sun.net.www.protocol.jar.Handler" };
|
||||
|
||||
private static URL jarContextUrl;
|
||||
|
||||
private static SoftReference<Map<File, JarFile>> rootFileCache;
|
||||
|
||||
static {
|
||||
rootFileCache = new SoftReference<>(null);
|
||||
}
|
||||
|
||||
private final JarFile jarFile;
|
||||
|
||||
private URLStreamHandler fallbackHandler;
|
||||
|
||||
public Handler() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public Handler(JarFile jarFile) {
|
||||
this.jarFile = jarFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected URLConnection openConnection(URL url) throws IOException {
|
||||
if (this.jarFile != null && isUrlInJarFile(url, this.jarFile)) {
|
||||
return fun.asgc.neutrino.core.aop.compiler.internal.jar.JarURLConnection.get(url, this.jarFile);
|
||||
}
|
||||
try {
|
||||
return fun.asgc.neutrino.core.aop.compiler.internal.jar.JarURLConnection.get(url, getRootJarFileFromUrl(url));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return openFallbackConnection(url, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isUrlInJarFile(URL url, JarFile jarFile) throws MalformedURLException {
|
||||
// Try the path first to save building a new url string each time
|
||||
return url.getPath().startsWith(jarFile.getUrl().getPath())
|
||||
&& url.toString().startsWith(jarFile.getUrlString());
|
||||
}
|
||||
|
||||
private URLConnection openFallbackConnection(URL url, Exception reason) throws IOException {
|
||||
try {
|
||||
URLConnection connection = openFallbackTomcatConnection(url);
|
||||
connection = (connection != null) ? connection : openFallbackContextConnection(url);
|
||||
return (connection != null) ? connection : openFallbackHandlerConnection(url);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (reason instanceof IOException) {
|
||||
log(false, "Unable to open fallback handler", ex);
|
||||
IOException ioException = (IOException)reason;
|
||||
throw ioException;
|
||||
}
|
||||
log(true, "Unable to open fallback handler", ex);
|
||||
if (reason instanceof RuntimeException) {
|
||||
RuntimeException runtimeException = (RuntimeException)reason;
|
||||
throw runtimeException;
|
||||
}
|
||||
throw new IllegalStateException(reason);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to open a Tomcat formatted 'jar:war:file:...' URL. This method allows us to
|
||||
* use our own nested JAR support to open the content rather than the logic in
|
||||
* {@code sun.net.www.protocol.jar.URLJarFile} which will extract the nested jar to
|
||||
* the temp folder to that its content can be accessed.
|
||||
* @param url the URL to open
|
||||
* @return a {@link URLConnection} or {@code null}
|
||||
*/
|
||||
private URLConnection openFallbackTomcatConnection(URL url) {
|
||||
String file = url.getFile();
|
||||
if (isTomcatWarUrl(file)) {
|
||||
file = file.substring(TOMCAT_WARFILE_PROTOCOL.length());
|
||||
file = file.replaceFirst("\\*/", "!/");
|
||||
try {
|
||||
URLConnection connection = openConnection(new URL("jar:file:" + file));
|
||||
connection.getInputStream().close();
|
||||
return connection;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isTomcatWarUrl(String file) {
|
||||
if (file.startsWith(TOMCAT_WARFILE_PROTOCOL) || !file.contains("*/")) {
|
||||
try {
|
||||
URLConnection connection = new URL(file).openConnection();
|
||||
if (connection.getClass().getName().startsWith("org.apache.catalina")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to open a fallback connection by using a context URL captured before the
|
||||
* jar handler was replaced with our own version. Since this method doesn't use
|
||||
* reflection it won't trigger "illegal reflective access operation has occurred"
|
||||
* warnings on Java 13+.
|
||||
* @param url the URL to open
|
||||
* @return a {@link URLConnection} or {@code null}
|
||||
*/
|
||||
private URLConnection openFallbackContextConnection(URL url) {
|
||||
try {
|
||||
if (jarContextUrl != null) {
|
||||
return new URL(jarContextUrl, url.toExternalForm()).openConnection();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to open a fallback connection by using reflection to access Java's default
|
||||
* jar {@link URLStreamHandler}.
|
||||
* @param url the URL to open
|
||||
* @return the {@link URLConnection}
|
||||
* @throws Exception if not connection could be opened
|
||||
*/
|
||||
private URLConnection openFallbackHandlerConnection(URL url) throws Exception {
|
||||
URLStreamHandler fallbackHandler = getFallbackHandler();
|
||||
return new URL(null, url.toExternalForm(), fallbackHandler).openConnection();
|
||||
}
|
||||
|
||||
private URLStreamHandler getFallbackHandler() {
|
||||
if (this.fallbackHandler != null) {
|
||||
return this.fallbackHandler;
|
||||
}
|
||||
for (String handlerClassName : FALLBACK_HANDLERS) {
|
||||
try {
|
||||
Class<?> handlerClass = Class.forName(handlerClassName);
|
||||
this.fallbackHandler = (URLStreamHandler) handlerClass.getDeclaredConstructor().newInstance();
|
||||
return this.fallbackHandler;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Unable to find fallback handler");
|
||||
}
|
||||
|
||||
private void log(boolean warning, String message, Exception cause) {
|
||||
try {
|
||||
Level level = warning ? Level.WARNING : Level.FINEST;
|
||||
Logger.getLogger(getClass().getName()).log(level, message, cause);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (warning) {
|
||||
System.err.println("WARNING: " + message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void parseURL(URL context, String spec, int start, int limit) {
|
||||
if (spec.regionMatches(true, 0, JAR_PROTOCOL, 0, JAR_PROTOCOL.length())) {
|
||||
setFile(context, getFileFromSpec(spec.substring(start, limit)));
|
||||
}
|
||||
else {
|
||||
setFile(context, getFileFromContext(context, spec.substring(start, limit)));
|
||||
}
|
||||
}
|
||||
|
||||
private String getFileFromSpec(String spec) {
|
||||
int separatorIndex = spec.lastIndexOf("!/");
|
||||
if (separatorIndex == -1) {
|
||||
throw new IllegalArgumentException("No !/ in spec '" + spec + "'");
|
||||
}
|
||||
try {
|
||||
new URL(spec.substring(0, separatorIndex));
|
||||
return spec;
|
||||
}
|
||||
catch (MalformedURLException ex) {
|
||||
throw new IllegalArgumentException("Invalid spec URL '" + spec + "'", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private String getFileFromContext(URL context, String spec) {
|
||||
String file = context.getFile();
|
||||
if (spec.startsWith("/")) {
|
||||
return trimToJarRoot(file) + SEPARATOR + spec.substring(1);
|
||||
}
|
||||
if (file.endsWith("/")) {
|
||||
return file + spec;
|
||||
}
|
||||
int lastSlashIndex = file.lastIndexOf('/');
|
||||
if (lastSlashIndex == -1) {
|
||||
throw new IllegalArgumentException("No / found in context URL's file '" + file + "'");
|
||||
}
|
||||
return file.substring(0, lastSlashIndex + 1) + spec;
|
||||
}
|
||||
|
||||
private String trimToJarRoot(String file) {
|
||||
int lastSeparatorIndex = file.lastIndexOf(SEPARATOR);
|
||||
if (lastSeparatorIndex == -1) {
|
||||
throw new IllegalArgumentException("No !/ found in context URL's file '" + file + "'");
|
||||
}
|
||||
return file.substring(0, lastSeparatorIndex);
|
||||
}
|
||||
|
||||
private void setFile(URL context, String file) {
|
||||
String path = normalize(file);
|
||||
String query = null;
|
||||
int queryIndex = path.lastIndexOf('?');
|
||||
if (queryIndex != -1) {
|
||||
query = path.substring(queryIndex + 1);
|
||||
path = path.substring(0, queryIndex);
|
||||
}
|
||||
setURL(context, JAR_PROTOCOL, null, -1, null, null, path, query, context.getRef());
|
||||
}
|
||||
|
||||
private String normalize(String file) {
|
||||
if (!file.contains(CURRENT_DIR) && !file.contains(PARENT_DIR)) {
|
||||
return file;
|
||||
}
|
||||
int afterLastSeparatorIndex = file.lastIndexOf(SEPARATOR) + SEPARATOR.length();
|
||||
String afterSeparator = file.substring(afterLastSeparatorIndex);
|
||||
afterSeparator = replaceParentDir(afterSeparator);
|
||||
afterSeparator = replaceCurrentDir(afterSeparator);
|
||||
return file.substring(0, afterLastSeparatorIndex) + afterSeparator;
|
||||
}
|
||||
|
||||
private String replaceParentDir(String file) {
|
||||
int parentDirIndex;
|
||||
while ((parentDirIndex = file.indexOf(PARENT_DIR)) >= 0) {
|
||||
int precedingSlashIndex = file.lastIndexOf('/', parentDirIndex - 1);
|
||||
if (precedingSlashIndex >= 0) {
|
||||
file = file.substring(0, precedingSlashIndex) + file.substring(parentDirIndex + 3);
|
||||
}
|
||||
else {
|
||||
file = file.substring(parentDirIndex + 4);
|
||||
}
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
private String replaceCurrentDir(String file) {
|
||||
return CURRENT_DIR_PATTERN.matcher(file).replaceAll("/");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int hashCode(URL u) {
|
||||
return hashCode(u.getProtocol(), u.getFile());
|
||||
}
|
||||
|
||||
private int hashCode(String protocol, String file) {
|
||||
int result = (protocol != null) ? protocol.hashCode() : 0;
|
||||
int separatorIndex = file.indexOf(SEPARATOR);
|
||||
if (separatorIndex == -1) {
|
||||
return result + file.hashCode();
|
||||
}
|
||||
String source = file.substring(0, separatorIndex);
|
||||
String entry = canonicalize(file.substring(separatorIndex + 2));
|
||||
try {
|
||||
result += new URL(source).hashCode();
|
||||
}
|
||||
catch (MalformedURLException ex) {
|
||||
result += source.hashCode();
|
||||
}
|
||||
result += entry.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean sameFile(URL u1, URL u2) {
|
||||
if (!u1.getProtocol().equals("jar") || !u2.getProtocol().equals("jar")) {
|
||||
return false;
|
||||
}
|
||||
int separator1 = u1.getFile().indexOf(SEPARATOR);
|
||||
int separator2 = u2.getFile().indexOf(SEPARATOR);
|
||||
if (separator1 == -1 || separator2 == -1) {
|
||||
return super.sameFile(u1, u2);
|
||||
}
|
||||
String nested1 = u1.getFile().substring(separator1 + SEPARATOR.length());
|
||||
String nested2 = u2.getFile().substring(separator2 + SEPARATOR.length());
|
||||
if (!nested1.equals(nested2)) {
|
||||
String canonical1 = canonicalize(nested1);
|
||||
String canonical2 = canonicalize(nested2);
|
||||
if (!canonical1.equals(canonical2)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
String root1 = u1.getFile().substring(0, separator1);
|
||||
String root2 = u2.getFile().substring(0, separator2);
|
||||
try {
|
||||
return super.sameFile(new URL(root1), new URL(root2));
|
||||
}
|
||||
catch (MalformedURLException ex) {
|
||||
// Continue
|
||||
}
|
||||
return super.sameFile(u1, u2);
|
||||
}
|
||||
|
||||
private String canonicalize(String path) {
|
||||
return SEPARATOR_PATTERN.matcher(path).replaceAll("/");
|
||||
}
|
||||
|
||||
public JarFile getRootJarFileFromUrl(URL url) throws IOException {
|
||||
String spec = url.getFile();
|
||||
int separatorIndex = spec.indexOf(SEPARATOR);
|
||||
if (separatorIndex == -1) {
|
||||
throw new MalformedURLException("Jar URL does not contain !/ separator");
|
||||
}
|
||||
String name = spec.substring(0, separatorIndex);
|
||||
return getRootJarFile(name);
|
||||
}
|
||||
|
||||
private JarFile getRootJarFile(String name) throws IOException {
|
||||
try {
|
||||
if (!name.startsWith(FILE_PROTOCOL)) {
|
||||
throw new IllegalStateException("Not a file URL");
|
||||
}
|
||||
File file = new File(URI.create(name));
|
||||
Map<File, JarFile> cache = rootFileCache.get();
|
||||
JarFile result = (cache != null) ? cache.get(file) : null;
|
||||
if (result == null) {
|
||||
result = new JarFile(file);
|
||||
addToRootFileCache(file, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IOException("Unable to open root Jar file '" + name + "'", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given {@link JarFile} to the root file cache.
|
||||
* @param sourceFile the source file to add
|
||||
* @param jarFile the jar file.
|
||||
*/
|
||||
static void addToRootFileCache(File sourceFile, JarFile jarFile) {
|
||||
Map<File, JarFile> cache = rootFileCache.get();
|
||||
if (cache == null) {
|
||||
cache = new ConcurrentHashMap<>();
|
||||
rootFileCache = new SoftReference<>(cache);
|
||||
}
|
||||
cache.put(sourceFile, jarFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* If possible, capture a URL that is configured with the original jar handler so that
|
||||
* we can use it as a fallback context later. We can only do this if we know that we
|
||||
* can reset the handlers after.
|
||||
*/
|
||||
static void captureJarContextUrl() {
|
||||
if (canResetCachedUrlHandlers()) {
|
||||
String handlers = System.getProperty(PROTOCOL_HANDLER);
|
||||
try {
|
||||
System.clearProperty(PROTOCOL_HANDLER);
|
||||
try {
|
||||
resetCachedUrlHandlers();
|
||||
jarContextUrl = new URL("jar:file:context.jar!/");
|
||||
URLConnection connection = jarContextUrl.openConnection();
|
||||
if (connection instanceof fun.asgc.neutrino.core.aop.compiler.internal.jar.JarURLConnection) {
|
||||
jarContextUrl = null;
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (handlers == null) {
|
||||
System.clearProperty(PROTOCOL_HANDLER);
|
||||
}
|
||||
else {
|
||||
System.setProperty(PROTOCOL_HANDLER, handlers);
|
||||
}
|
||||
}
|
||||
resetCachedUrlHandlers();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean canResetCachedUrlHandlers() {
|
||||
try {
|
||||
resetCachedUrlHandlers();
|
||||
return true;
|
||||
}
|
||||
catch (Error ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void resetCachedUrlHandlers() {
|
||||
URL.setURLStreamHandlerFactory(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if a generic static exception can be thrown when a URL cannot be connected.
|
||||
* This optimization is used during class loading to save creating lots of exceptions
|
||||
* which are then swallowed.
|
||||
* @param useFastConnectionExceptions if fast connection exceptions can be used.
|
||||
*/
|
||||
public static void setUseFastConnectionExceptions(boolean useFastConnectionExceptions) {
|
||||
JarURLConnection.setUseFastExceptions(useFastConnectionExceptions);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal.jar;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.security.CodeSigner;
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
/**
|
||||
* Extended variant of {@link java.util.jar.JarEntry} returned by {@link JarFile}s.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class JarEntry extends java.util.jar.JarEntry implements FileHeader {
|
||||
|
||||
private final int index;
|
||||
|
||||
private final AsciiBytes name;
|
||||
|
||||
private final AsciiBytes headerName;
|
||||
|
||||
private final JarFile jarFile;
|
||||
|
||||
private final long localHeaderOffset;
|
||||
|
||||
private volatile JarEntryCertification certification;
|
||||
|
||||
JarEntry(JarFile jarFile, int index, CentralDirectoryFileHeader header, AsciiBytes nameAlias) {
|
||||
super((nameAlias != null) ? nameAlias.toString() : header.getName().toString());
|
||||
this.index = index;
|
||||
this.name = (nameAlias != null) ? nameAlias : header.getName();
|
||||
this.headerName = header.getName();
|
||||
this.jarFile = jarFile;
|
||||
this.localHeaderOffset = header.getLocalHeaderOffset();
|
||||
setCompressedSize(header.getCompressedSize());
|
||||
setMethod(header.getMethod());
|
||||
setCrc(header.getCrc());
|
||||
setComment(header.getComment().toString());
|
||||
setSize(header.getSize());
|
||||
setTime(header.getTime());
|
||||
if (header.hasExtra()) {
|
||||
setExtra(header.getExtra());
|
||||
}
|
||||
}
|
||||
|
||||
int getIndex() {
|
||||
return this.index;
|
||||
}
|
||||
|
||||
AsciiBytes getAsciiBytesName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasName(CharSequence name, char suffix) {
|
||||
return this.headerName.matches(name, suffix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link URL} for this {@link JarEntry}.
|
||||
* @return the URL for the entry
|
||||
* @throws MalformedURLException if the URL is not valid
|
||||
*/
|
||||
URL getUrl() throws MalformedURLException {
|
||||
return new URL(this.jarFile.getUrl(), getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attributes getAttributes() throws IOException {
|
||||
Manifest manifest = this.jarFile.getManifest();
|
||||
return (manifest != null) ? manifest.getAttributes(getName()) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate[] getCertificates() {
|
||||
return getCertification().getCertificates();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeSigner[] getCodeSigners() {
|
||||
return getCertification().getCodeSigners();
|
||||
}
|
||||
|
||||
private JarEntryCertification getCertification() {
|
||||
if (!this.jarFile.isSigned()) {
|
||||
return JarEntryCertification.NONE;
|
||||
}
|
||||
JarEntryCertification certification = this.certification;
|
||||
if (certification == null) {
|
||||
certification = this.jarFile.getCertification(this);
|
||||
this.certification = certification;
|
||||
}
|
||||
return certification;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLocalHeaderOffset() {
|
||||
return this.localHeaderOffset;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal.jar;
|
||||
|
||||
import java.security.CodeSigner;
|
||||
import java.security.cert.Certificate;
|
||||
|
||||
/**
|
||||
* {@link Certificate} and {@link CodeSigner} details for a {@link JarEntry} from a signed
|
||||
* {@link JarFile}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class JarEntryCertification {
|
||||
|
||||
static final JarEntryCertification NONE = new JarEntryCertification(null, null);
|
||||
|
||||
private final Certificate[] certificates;
|
||||
|
||||
private final CodeSigner[] codeSigners;
|
||||
|
||||
JarEntryCertification(Certificate[] certificates, CodeSigner[] codeSigners) {
|
||||
this.certificates = certificates;
|
||||
this.codeSigners = codeSigners;
|
||||
}
|
||||
|
||||
Certificate[] getCertificates() {
|
||||
return (this.certificates != null) ? this.certificates.clone() : null;
|
||||
}
|
||||
|
||||
CodeSigner[] getCodeSigners() {
|
||||
return (this.codeSigners != null) ? this.codeSigners.clone() : null;
|
||||
}
|
||||
|
||||
static JarEntryCertification from(java.util.jar.JarEntry certifiedEntry) {
|
||||
Certificate[] certificates = (certifiedEntry != null) ? certifiedEntry.getCertificates() : null;
|
||||
CodeSigner[] codeSigners = (certifiedEntry != null) ? certifiedEntry.getCodeSigners() : null;
|
||||
if (certificates == null && codeSigners == null) {
|
||||
return NONE;
|
||||
}
|
||||
return new JarEntryCertification(certificates, codeSigners);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal.jar;
|
||||
|
||||
/**
|
||||
* Interface that can be used to filter and optionally rename jar entries.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
interface JarEntryFilter {
|
||||
|
||||
/**
|
||||
* Apply the jar entry filter.
|
||||
* @param name the current entry name. This may be different that the original entry
|
||||
* name if a previous filter has been applied
|
||||
* @return the new name of the entry or {@code null} if the entry should not be
|
||||
* included.
|
||||
*/
|
||||
AsciiBytes apply(AsciiBytes name);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,460 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal.jar;
|
||||
|
||||
import fun.asgc.neutrino.core.aop.compiler.internal.data.RandomAccessData;
|
||||
import fun.asgc.neutrino.core.aop.compiler.internal.data.RandomAccessDataFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilePermission;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLStreamHandler;
|
||||
import java.net.URLStreamHandlerFactory;
|
||||
import java.security.Permission;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.Spliterator;
|
||||
import java.util.Spliterators;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
/**
|
||||
* Extended variant of {@link java.util.jar.JarFile} that behaves in the same way but
|
||||
* offers the following additional functionality.
|
||||
* <ul>
|
||||
* <li>A nested {@link JarFile} can be {@link #getNestedJarFile(ZipEntry) obtained} based
|
||||
* on any directory entry.</li>
|
||||
* <li>A nested {@link JarFile} can be {@link #getNestedJarFile(ZipEntry) obtained} for
|
||||
* embedded JAR files (as long as their entry is not compressed).</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class JarFile extends AbstractJarFile implements Iterable<java.util.jar.JarEntry> {
|
||||
|
||||
private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
|
||||
|
||||
private static final String PROTOCOL_HANDLER = "java.protocol.handler.pkgs";
|
||||
|
||||
private static final String HANDLERS_PACKAGE = "org.springframework.boot.loader";
|
||||
|
||||
private static final AsciiBytes META_INF = new AsciiBytes("META-INF/");
|
||||
|
||||
private static final AsciiBytes SIGNATURE_FILE_EXTENSION = new AsciiBytes(".SF");
|
||||
|
||||
private static final String READ_ACTION = "read";
|
||||
|
||||
private final RandomAccessDataFile rootFile;
|
||||
|
||||
private final String pathFromRoot;
|
||||
|
||||
private final RandomAccessData data;
|
||||
|
||||
private final JarFileType type;
|
||||
|
||||
private URL url;
|
||||
|
||||
private String urlString;
|
||||
|
||||
private final JarFileEntries entries;
|
||||
|
||||
private final Supplier<Manifest> manifestSupplier;
|
||||
|
||||
private SoftReference<Manifest> manifest;
|
||||
|
||||
private boolean signed;
|
||||
|
||||
private String comment;
|
||||
|
||||
private volatile boolean closed;
|
||||
|
||||
private volatile JarFileWrapper wrapper;
|
||||
|
||||
/**
|
||||
* Create a new {@link JarFile} backed by the specified file.
|
||||
* @param file the root jar file
|
||||
* @throws IOException if the file cannot be read
|
||||
*/
|
||||
public JarFile(File file) throws IOException {
|
||||
this(new RandomAccessDataFile(file));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link JarFile} backed by the specified file.
|
||||
* @param file the root jar file
|
||||
* @throws IOException if the file cannot be read
|
||||
*/
|
||||
JarFile(RandomAccessDataFile file) throws IOException {
|
||||
this(file, "", file, JarFileType.DIRECT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor used to create a new {@link JarFile} either directly or from a
|
||||
* nested entry.
|
||||
* @param rootFile the root jar file
|
||||
* @param pathFromRoot the name of this file
|
||||
* @param data the underlying data
|
||||
* @param type the type of the jar file
|
||||
* @throws IOException if the file cannot be read
|
||||
*/
|
||||
private JarFile(RandomAccessDataFile rootFile, String pathFromRoot, RandomAccessData data, JarFileType type)
|
||||
throws IOException {
|
||||
this(rootFile, pathFromRoot, data, null, type, null);
|
||||
}
|
||||
|
||||
private JarFile(RandomAccessDataFile rootFile, String pathFromRoot, RandomAccessData data, JarEntryFilter filter,
|
||||
JarFileType type, Supplier<Manifest> manifestSupplier) throws IOException {
|
||||
super(rootFile.getFile());
|
||||
super.close();
|
||||
this.rootFile = rootFile;
|
||||
this.pathFromRoot = pathFromRoot;
|
||||
CentralDirectoryParser parser = new CentralDirectoryParser();
|
||||
this.entries = parser.addVisitor(new JarFileEntries(this, filter));
|
||||
this.type = type;
|
||||
parser.addVisitor(centralDirectoryVisitor());
|
||||
try {
|
||||
this.data = parser.parse(data, filter == null);
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
try {
|
||||
this.rootFile.close();
|
||||
super.close();
|
||||
}
|
||||
catch (IOException ioex) {
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
this.manifestSupplier = (manifestSupplier != null) ? manifestSupplier : () -> {
|
||||
try (InputStream inputStream = getInputStream(MANIFEST_NAME)) {
|
||||
if (inputStream == null) {
|
||||
return null;
|
||||
}
|
||||
return new Manifest(inputStream);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private CentralDirectoryVisitor centralDirectoryVisitor() {
|
||||
return new CentralDirectoryVisitor() {
|
||||
|
||||
@Override
|
||||
public void visitStart(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData) {
|
||||
JarFile.this.comment = endRecord.getComment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitFileHeader(CentralDirectoryFileHeader fileHeader, long dataOffset) {
|
||||
AsciiBytes name = fileHeader.getName();
|
||||
if (name.startsWith(META_INF) && name.endsWith(SIGNATURE_FILE_EXTENSION)) {
|
||||
JarFile.this.signed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
JarFileWrapper getWrapper() throws IOException {
|
||||
JarFileWrapper wrapper = this.wrapper;
|
||||
if (wrapper == null) {
|
||||
wrapper = new JarFileWrapper(this);
|
||||
this.wrapper = wrapper;
|
||||
}
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
Permission getPermission() {
|
||||
return new FilePermission(this.rootFile.getFile().getPath(), READ_ACTION);
|
||||
}
|
||||
|
||||
protected final RandomAccessDataFile getRootJarFile() {
|
||||
return this.rootFile;
|
||||
}
|
||||
|
||||
RandomAccessData getData() {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Manifest getManifest() throws IOException {
|
||||
Manifest manifest = (this.manifest != null) ? this.manifest.get() : null;
|
||||
if (manifest == null) {
|
||||
try {
|
||||
manifest = this.manifestSupplier.get();
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
this.manifest = new SoftReference<>(manifest);
|
||||
}
|
||||
return manifest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<java.util.jar.JarEntry> entries() {
|
||||
return new JarEntryEnumeration(this.entries.iterator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<java.util.jar.JarEntry> stream() {
|
||||
Spliterator<java.util.jar.JarEntry> spliterator = Spliterators.spliterator(iterator(), size(),
|
||||
Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.IMMUTABLE | Spliterator.NONNULL);
|
||||
return StreamSupport.stream(spliterator, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an iterator for the contained entries.
|
||||
* @since 2.3.0
|
||||
* @see Iterable#iterator()
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public Iterator<java.util.jar.JarEntry> iterator() {
|
||||
return (Iterator) this.entries.iterator(this::ensureOpen);
|
||||
}
|
||||
|
||||
public JarEntry getJarEntry(CharSequence name) {
|
||||
return this.entries.getEntry(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JarEntry getJarEntry(String name) {
|
||||
return (JarEntry) getEntry(name);
|
||||
}
|
||||
|
||||
public boolean containsEntry(String name) {
|
||||
return this.entries.containsEntry(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ZipEntry getEntry(String name) {
|
||||
ensureOpen();
|
||||
return this.entries.getEntry(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
InputStream getInputStream() throws IOException {
|
||||
return this.data.getInputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized InputStream getInputStream(ZipEntry entry) throws IOException {
|
||||
ensureOpen();
|
||||
if (entry instanceof JarEntry) {
|
||||
JarEntry jarEntry = (JarEntry) entry;
|
||||
return this.entries.getInputStream(jarEntry);
|
||||
}
|
||||
return getInputStream((entry != null) ? entry.getName() : null);
|
||||
}
|
||||
|
||||
InputStream getInputStream(String name) throws IOException {
|
||||
return this.entries.getInputStream(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a nested {@link JarFile} loaded from the specified entry.
|
||||
* @param entry the zip entry
|
||||
* @return a {@link JarFile} for the entry
|
||||
* @throws IOException if the nested jar file cannot be read
|
||||
*/
|
||||
public synchronized JarFile getNestedJarFile(ZipEntry entry) throws IOException {
|
||||
return getNestedJarFile((JarEntry) entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a nested {@link JarFile} loaded from the specified entry.
|
||||
* @param entry the zip entry
|
||||
* @return a {@link JarFile} for the entry
|
||||
* @throws IOException if the nested jar file cannot be read
|
||||
*/
|
||||
public synchronized JarFile getNestedJarFile(JarEntry entry) throws IOException {
|
||||
try {
|
||||
return createJarFileFromEntry(entry);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IOException("Unable to open nested jar file '" + entry.getName() + "'", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private JarFile createJarFileFromEntry(JarEntry entry) throws IOException {
|
||||
if (entry.isDirectory()) {
|
||||
return createJarFileFromDirectoryEntry(entry);
|
||||
}
|
||||
return createJarFileFromFileEntry(entry);
|
||||
}
|
||||
|
||||
private JarFile createJarFileFromDirectoryEntry(JarEntry entry) throws IOException {
|
||||
AsciiBytes name = entry.getAsciiBytesName();
|
||||
JarEntryFilter filter = (candidate) -> {
|
||||
if (candidate.startsWith(name) && !candidate.equals(name)) {
|
||||
return candidate.substring(name.length());
|
||||
}
|
||||
return null;
|
||||
};
|
||||
return new JarFile(this.rootFile, this.pathFromRoot + "!/" + entry.getName().substring(0, name.length() - 1),
|
||||
this.data, filter, JarFileType.NESTED_DIRECTORY, this.manifestSupplier);
|
||||
}
|
||||
|
||||
private JarFile createJarFileFromFileEntry(JarEntry entry) throws IOException {
|
||||
if (entry.getMethod() != ZipEntry.STORED) {
|
||||
throw new IllegalStateException(
|
||||
"Unable to open nested entry '" + entry.getName() + "'. It has been compressed and nested "
|
||||
+ "jar files must be stored without compression. Please check the "
|
||||
+ "mechanism used to create your executable jar file");
|
||||
}
|
||||
RandomAccessData entryData = this.entries.getEntryData(entry.getName());
|
||||
return new JarFile(this.rootFile, this.pathFromRoot + "!/" + entry.getName(), entryData,
|
||||
JarFileType.NESTED_JAR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComment() {
|
||||
ensureOpen();
|
||||
return this.comment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
ensureOpen();
|
||||
return this.entries.getSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (this.closed) {
|
||||
return;
|
||||
}
|
||||
super.close();
|
||||
if (this.type == JarFileType.DIRECT) {
|
||||
this.rootFile.close();
|
||||
}
|
||||
this.closed = true;
|
||||
}
|
||||
|
||||
private void ensureOpen() {
|
||||
if (this.closed) {
|
||||
throw new IllegalStateException("zip file closed");
|
||||
}
|
||||
}
|
||||
|
||||
boolean isClosed() {
|
||||
return this.closed;
|
||||
}
|
||||
|
||||
String getUrlString() throws MalformedURLException {
|
||||
if (this.urlString == null) {
|
||||
this.urlString = getUrl().toString();
|
||||
}
|
||||
return this.urlString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getUrl() throws MalformedURLException {
|
||||
if (this.url == null) {
|
||||
String file = this.rootFile.getFile().toURI() + this.pathFromRoot + "!/";
|
||||
file = file.replace("file:////", "file://"); // Fix UNC paths
|
||||
this.url = new URL("jar", "", -1, file, new Handler(this));
|
||||
}
|
||||
return this.url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.rootFile.getFile() + this.pathFromRoot;
|
||||
}
|
||||
|
||||
boolean isSigned() {
|
||||
return this.signed;
|
||||
}
|
||||
|
||||
JarEntryCertification getCertification(JarEntry entry) {
|
||||
try {
|
||||
return this.entries.getCertification(entry);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearCache() {
|
||||
this.entries.clearCache();
|
||||
}
|
||||
|
||||
protected String getPathFromRoot() {
|
||||
return this.pathFromRoot;
|
||||
}
|
||||
|
||||
@Override
|
||||
JarFileType getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@literal 'java.protocol.handler.pkgs'} property so that a
|
||||
* {@link URLStreamHandler} will be located to deal with jar URLs.
|
||||
*/
|
||||
public static void registerUrlProtocolHandler() {
|
||||
Handler.captureJarContextUrl();
|
||||
String handlers = System.getProperty(PROTOCOL_HANDLER, "");
|
||||
System.setProperty(PROTOCOL_HANDLER,
|
||||
((handlers == null || handlers.isEmpty()) ? HANDLERS_PACKAGE : handlers + "|" + HANDLERS_PACKAGE));
|
||||
resetCachedUrlHandlers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset any cached handlers just in case a jar protocol has already been used. We
|
||||
* reset the handler by trying to set a null {@link URLStreamHandlerFactory} which
|
||||
* should have no effect other than clearing the handlers cache.
|
||||
*/
|
||||
private static void resetCachedUrlHandlers() {
|
||||
try {
|
||||
URL.setURLStreamHandlerFactory(null);
|
||||
}
|
||||
catch (Error ex) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link Enumeration} on {@linkplain java.util.jar.JarEntry jar entries}.
|
||||
*/
|
||||
private static class JarEntryEnumeration implements Enumeration<java.util.jar.JarEntry> {
|
||||
|
||||
private final Iterator<JarEntry> iterator;
|
||||
|
||||
JarEntryEnumeration(Iterator<JarEntry> iterator) {
|
||||
this.iterator = iterator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMoreElements() {
|
||||
return this.iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.util.jar.JarEntry nextElement() {
|
||||
return this.iterator.next();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,470 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal.jar;
|
||||
|
||||
import fun.asgc.neutrino.core.aop.compiler.internal.data.RandomAccessData;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.*;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Attributes.Name;
|
||||
import java.util.jar.JarInputStream;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
/**
|
||||
* Provides access to entries from a {@link JarFile}. In order to reduce memory
|
||||
* consumption entry details are stored using arrays. The {@code hashCodes} array stores
|
||||
* the hash code of the entry name, the {@code centralDirectoryOffsets} provides the
|
||||
* offset to the central directory record and {@code positions} provides the original
|
||||
* order position of the entry. The arrays are stored in hashCode order so that a binary
|
||||
* search can be used to find a name.
|
||||
* <p>
|
||||
* A typical Spring Boot application will have somewhere in the region of 10,500 entries
|
||||
* which should consume about 122K.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class JarFileEntries implements CentralDirectoryVisitor, Iterable<JarEntry> {
|
||||
|
||||
private static final Runnable NO_VALIDATION = () -> {
|
||||
};
|
||||
|
||||
private static final String META_INF_PREFIX = "META-INF/";
|
||||
|
||||
private static final Name MULTI_RELEASE = new Name("Multi-Release");
|
||||
|
||||
private static final int BASE_VERSION = 8;
|
||||
|
||||
private static final int RUNTIME_VERSION = 1;// TODO Runtime.version().feature();
|
||||
|
||||
private static final long LOCAL_FILE_HEADER_SIZE = 30;
|
||||
|
||||
private static final char SLASH = '/';
|
||||
|
||||
private static final char NO_SUFFIX = 0;
|
||||
|
||||
protected static final int ENTRY_CACHE_SIZE = 25;
|
||||
|
||||
private final JarFile jarFile;
|
||||
|
||||
private final JarEntryFilter filter;
|
||||
|
||||
private RandomAccessData centralDirectoryData;
|
||||
|
||||
private int size;
|
||||
|
||||
private int[] hashCodes;
|
||||
|
||||
private Offsets centralDirectoryOffsets;
|
||||
|
||||
private int[] positions;
|
||||
|
||||
private Boolean multiReleaseJar;
|
||||
|
||||
private JarEntryCertification[] certifications;
|
||||
|
||||
private final Map<Integer, FileHeader> entriesCache = Collections
|
||||
.synchronizedMap(new LinkedHashMap<Integer, FileHeader>(16, 0.75f, true) {
|
||||
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry<Integer, FileHeader> eldest) {
|
||||
return size() >= ENTRY_CACHE_SIZE;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
JarFileEntries(JarFile jarFile, JarEntryFilter filter) {
|
||||
this.jarFile = jarFile;
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitStart(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData) {
|
||||
int maxSize = endRecord.getNumberOfRecords();
|
||||
this.centralDirectoryData = centralDirectoryData;
|
||||
this.hashCodes = new int[maxSize];
|
||||
this.centralDirectoryOffsets = Offsets.from(endRecord);
|
||||
this.positions = new int[maxSize];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitFileHeader(CentralDirectoryFileHeader fileHeader, long dataOffset) {
|
||||
AsciiBytes name = applyFilter(fileHeader.getName());
|
||||
if (name != null) {
|
||||
add(name, dataOffset);
|
||||
}
|
||||
}
|
||||
|
||||
private void add(AsciiBytes name, long dataOffset) {
|
||||
this.hashCodes[this.size] = name.hashCode();
|
||||
this.centralDirectoryOffsets.set(this.size, dataOffset);
|
||||
this.positions[this.size] = this.size;
|
||||
this.size++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
sort(0, this.size - 1);
|
||||
int[] positions = this.positions;
|
||||
this.positions = new int[positions.length];
|
||||
for (int i = 0; i < this.size; i++) {
|
||||
this.positions[positions[i]] = i;
|
||||
}
|
||||
}
|
||||
|
||||
int getSize() {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
private void sort(int left, int right) {
|
||||
// Quick sort algorithm, uses hashCodes as the source but sorts all arrays
|
||||
if (left < right) {
|
||||
int pivot = this.hashCodes[left + (right - left) / 2];
|
||||
int i = left;
|
||||
int j = right;
|
||||
while (i <= j) {
|
||||
while (this.hashCodes[i] < pivot) {
|
||||
i++;
|
||||
}
|
||||
while (this.hashCodes[j] > pivot) {
|
||||
j--;
|
||||
}
|
||||
if (i <= j) {
|
||||
swap(i, j);
|
||||
i++;
|
||||
j--;
|
||||
}
|
||||
}
|
||||
if (left < j) {
|
||||
sort(left, j);
|
||||
}
|
||||
if (right > i) {
|
||||
sort(i, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void swap(int i, int j) {
|
||||
swap(this.hashCodes, i, j);
|
||||
this.centralDirectoryOffsets.swap(i, j);
|
||||
swap(this.positions, i, j);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<JarEntry> iterator() {
|
||||
return new EntryIterator(NO_VALIDATION);
|
||||
}
|
||||
|
||||
Iterator<JarEntry> iterator(Runnable validator) {
|
||||
return new EntryIterator(validator);
|
||||
}
|
||||
|
||||
boolean containsEntry(CharSequence name) {
|
||||
return getEntry(name, FileHeader.class, true) != null;
|
||||
}
|
||||
|
||||
JarEntry getEntry(CharSequence name) {
|
||||
return getEntry(name, JarEntry.class, true);
|
||||
}
|
||||
|
||||
InputStream getInputStream(String name) throws IOException {
|
||||
FileHeader entry = getEntry(name, FileHeader.class, false);
|
||||
return getInputStream(entry);
|
||||
}
|
||||
|
||||
InputStream getInputStream(FileHeader entry) throws IOException {
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
InputStream inputStream = getEntryData(entry).getInputStream();
|
||||
if (entry.getMethod() == ZipEntry.DEFLATED) {
|
||||
inputStream = new ZipInflaterInputStream(inputStream, (int) entry.getSize());
|
||||
}
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
RandomAccessData getEntryData(String name) throws IOException {
|
||||
FileHeader entry = getEntry(name, FileHeader.class, false);
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
return getEntryData(entry);
|
||||
}
|
||||
|
||||
private RandomAccessData getEntryData(FileHeader entry) throws IOException {
|
||||
// aspectjrt-1.7.4.jar has a different ext bytes length in the
|
||||
// local directory to the central directory. We need to re-read
|
||||
// here to skip them
|
||||
RandomAccessData data = this.jarFile.getData();
|
||||
byte[] localHeader = data.read(entry.getLocalHeaderOffset(), LOCAL_FILE_HEADER_SIZE);
|
||||
long nameLength = Bytes.littleEndianValue(localHeader, 26, 2);
|
||||
long extraLength = Bytes.littleEndianValue(localHeader, 28, 2);
|
||||
return data.getSubsection(entry.getLocalHeaderOffset() + LOCAL_FILE_HEADER_SIZE + nameLength + extraLength,
|
||||
entry.getCompressedSize());
|
||||
}
|
||||
|
||||
private <T extends FileHeader> T getEntry(CharSequence name, Class<T> type, boolean cacheEntry) {
|
||||
T entry = doGetEntry(name, type, cacheEntry, null);
|
||||
if (!isMetaInfEntry(name) && isMultiReleaseJar()) {
|
||||
int version = RUNTIME_VERSION;
|
||||
AsciiBytes nameAlias = (entry instanceof JarEntry) ? ((JarEntry)entry).getAsciiBytesName()
|
||||
: new AsciiBytes(name.toString());
|
||||
while (version > BASE_VERSION) {
|
||||
T versionedEntry = doGetEntry("META-INF/versions/" + version + "/" + name, type, cacheEntry, nameAlias);
|
||||
if (versionedEntry != null) {
|
||||
return versionedEntry;
|
||||
}
|
||||
version--;
|
||||
}
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
private boolean isMetaInfEntry(CharSequence name) {
|
||||
return name.toString().startsWith(META_INF_PREFIX);
|
||||
}
|
||||
|
||||
private boolean isMultiReleaseJar() {
|
||||
Boolean multiRelease = this.multiReleaseJar;
|
||||
if (multiRelease != null) {
|
||||
return multiRelease;
|
||||
}
|
||||
try {
|
||||
Manifest manifest = this.jarFile.getManifest();
|
||||
if (manifest == null) {
|
||||
multiRelease = false;
|
||||
}
|
||||
else {
|
||||
Attributes attributes = manifest.getMainAttributes();
|
||||
multiRelease = attributes.containsKey(MULTI_RELEASE);
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
multiRelease = false;
|
||||
}
|
||||
this.multiReleaseJar = multiRelease;
|
||||
return multiRelease;
|
||||
}
|
||||
|
||||
private <T extends FileHeader> T doGetEntry(CharSequence name, Class<T> type, boolean cacheEntry,
|
||||
AsciiBytes nameAlias) {
|
||||
int hashCode = AsciiBytes.hashCode(name);
|
||||
T entry = getEntry(hashCode, name, NO_SUFFIX, type, cacheEntry, nameAlias);
|
||||
if (entry == null) {
|
||||
hashCode = AsciiBytes.hashCode(hashCode, SLASH);
|
||||
entry = getEntry(hashCode, name, SLASH, type, cacheEntry, nameAlias);
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
private <T extends FileHeader> T getEntry(int hashCode, CharSequence name, char suffix, Class<T> type,
|
||||
boolean cacheEntry, AsciiBytes nameAlias) {
|
||||
int index = getFirstIndex(hashCode);
|
||||
while (index >= 0 && index < this.size && this.hashCodes[index] == hashCode) {
|
||||
T entry = getEntry(index, type, cacheEntry, nameAlias);
|
||||
if (entry.hasName(name, suffix)) {
|
||||
return entry;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends FileHeader> T getEntry(int index, Class<T> type, boolean cacheEntry, AsciiBytes nameAlias) {
|
||||
try {
|
||||
long offset = this.centralDirectoryOffsets.get(index);
|
||||
FileHeader cached = this.entriesCache.get(index);
|
||||
FileHeader entry = (cached != null) ? cached
|
||||
: CentralDirectoryFileHeader.fromRandomAccessData(this.centralDirectoryData, offset, this.filter);
|
||||
if (CentralDirectoryFileHeader.class.equals(entry.getClass()) && type.equals(JarEntry.class)) {
|
||||
entry = new JarEntry(this.jarFile, index, (CentralDirectoryFileHeader) entry, nameAlias);
|
||||
}
|
||||
if (cacheEntry && cached != entry) {
|
||||
this.entriesCache.put(index, entry);
|
||||
}
|
||||
return (T) entry;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private int getFirstIndex(int hashCode) {
|
||||
int index = Arrays.binarySearch(this.hashCodes, 0, this.size, hashCode);
|
||||
if (index < 0) {
|
||||
return -1;
|
||||
}
|
||||
while (index > 0 && this.hashCodes[index - 1] == hashCode) {
|
||||
index--;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
void clearCache() {
|
||||
this.entriesCache.clear();
|
||||
}
|
||||
|
||||
private AsciiBytes applyFilter(AsciiBytes name) {
|
||||
return (this.filter != null) ? this.filter.apply(name) : name;
|
||||
}
|
||||
|
||||
JarEntryCertification getCertification(JarEntry entry) throws IOException {
|
||||
JarEntryCertification[] certifications = this.certifications;
|
||||
if (certifications == null) {
|
||||
certifications = new JarEntryCertification[this.size];
|
||||
// We fall back to use JarInputStream to obtain the certs. This isn't that
|
||||
// fast, but hopefully doesn't happen too often.
|
||||
try (JarInputStream certifiedJarStream = new JarInputStream(this.jarFile.getData().getInputStream())) {
|
||||
java.util.jar.JarEntry certifiedEntry;
|
||||
while ((certifiedEntry = certifiedJarStream.getNextJarEntry()) != null) {
|
||||
// Entry must be closed to trigger a read and set entry certificates
|
||||
certifiedJarStream.closeEntry();
|
||||
int index = getEntryIndex(certifiedEntry.getName());
|
||||
if (index != -1) {
|
||||
certifications[index] = JarEntryCertification.from(certifiedEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.certifications = certifications;
|
||||
}
|
||||
JarEntryCertification certification = certifications[entry.getIndex()];
|
||||
return (certification != null) ? certification : JarEntryCertification.NONE;
|
||||
}
|
||||
|
||||
private int getEntryIndex(CharSequence name) {
|
||||
int hashCode = AsciiBytes.hashCode(name);
|
||||
int index = getFirstIndex(hashCode);
|
||||
while (index >= 0 && index < this.size && this.hashCodes[index] == hashCode) {
|
||||
FileHeader candidate = getEntry(index, FileHeader.class, false, null);
|
||||
if (candidate.hasName(name, NO_SUFFIX)) {
|
||||
return index;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static void swap(int[] array, int i, int j) {
|
||||
int temp = array[i];
|
||||
array[i] = array[j];
|
||||
array[j] = temp;
|
||||
}
|
||||
|
||||
private static void swap(long[] array, int i, int j) {
|
||||
long temp = array[i];
|
||||
array[i] = array[j];
|
||||
array[j] = temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterator for contained entries.
|
||||
*/
|
||||
private final class EntryIterator implements Iterator<JarEntry> {
|
||||
|
||||
private final Runnable validator;
|
||||
|
||||
private int index = 0;
|
||||
|
||||
private EntryIterator(Runnable validator) {
|
||||
this.validator = validator;
|
||||
validator.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
this.validator.run();
|
||||
return this.index < JarFileEntries.this.size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JarEntry next() {
|
||||
this.validator.run();
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
int entryIndex = JarFileEntries.this.positions[this.index];
|
||||
this.index++;
|
||||
return getEntry(entryIndex, JarEntry.class, false, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to manage offsets to central directory records. Regular zip files are
|
||||
* backed by an {@code int[]} based implementation, Zip64 files are backed by a
|
||||
* {@code long[]} and will consume more memory.
|
||||
*/
|
||||
private interface Offsets {
|
||||
|
||||
void set(int index, long value);
|
||||
|
||||
long get(int index);
|
||||
|
||||
void swap(int i, int j);
|
||||
|
||||
static Offsets from(CentralDirectoryEndRecord endRecord) {
|
||||
int size = endRecord.getNumberOfRecords();
|
||||
return endRecord.isZip64() ? new Zip64Offsets(size) : new ZipOffsets(size);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Offsets} implementation for regular zip files.
|
||||
*/
|
||||
private static final class ZipOffsets implements Offsets {
|
||||
|
||||
private final int[] offsets;
|
||||
|
||||
private ZipOffsets(int size) {
|
||||
this.offsets = new int[size];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void swap(int i, int j) {
|
||||
JarFileEntries.swap(this.offsets, i, j);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(int index, long value) {
|
||||
this.offsets[index] = (int) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long get(int index) {
|
||||
return this.offsets[index];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Offsets} implementation for zip64 files.
|
||||
*/
|
||||
private static final class Zip64Offsets implements Offsets {
|
||||
|
||||
private final long[] offsets;
|
||||
|
||||
private Zip64Offsets(int size) {
|
||||
this.offsets = new long[size];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void swap(int i, int j) {
|
||||
JarFileEntries.swap(this.offsets, i, j);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(int index, long value) {
|
||||
this.offsets[index] = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long get(int index) {
|
||||
return this.offsets[index];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal.jar;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.security.Permission;
|
||||
import java.util.Enumeration;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
/**
|
||||
* A wrapper used to create a copy of a {@link JarFile} so that it can be safely closed
|
||||
* without closing the original.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class JarFileWrapper extends AbstractJarFile {
|
||||
|
||||
private final JarFile parent;
|
||||
|
||||
JarFileWrapper(JarFile parent) throws IOException {
|
||||
super(parent.getRootJarFile().getFile());
|
||||
this.parent = parent;
|
||||
super.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
URL getUrl() throws MalformedURLException {
|
||||
return this.parent.getUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
JarFileType getType() {
|
||||
return this.parent.getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
Permission getPermission() {
|
||||
return this.parent.getPermission();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Manifest getManifest() throws IOException {
|
||||
return this.parent.getManifest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<JarEntry> entries() {
|
||||
return this.parent.entries();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<JarEntry> stream() {
|
||||
return this.parent.stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JarEntry getJarEntry(String name) {
|
||||
return this.parent.getJarEntry(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ZipEntry getEntry(String name) {
|
||||
return this.parent.getEntry(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
InputStream getInputStream() throws IOException {
|
||||
return this.parent.getInputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized InputStream getInputStream(ZipEntry ze) throws IOException {
|
||||
return this.parent.getInputStream(ze);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComment() {
|
||||
return this.parent.getComment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return this.parent.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.parent.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.parent.getName();
|
||||
}
|
||||
|
||||
static JarFile unwrap(java.util.jar.JarFile jarFile) {
|
||||
if (jarFile instanceof JarFile) {
|
||||
return (JarFile)jarFile;
|
||||
}
|
||||
if (jarFile instanceof JarFileWrapper) {
|
||||
return unwrap(((JarFileWrapper)jarFile).parent);
|
||||
}
|
||||
throw new IllegalStateException("Not a JarFile or Wrapper");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal.jar;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.security.Permission;
|
||||
|
||||
/**
|
||||
* {@link java.net.JarURLConnection} used to support {@link JarFile#getUrl()}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @author Rostyslav Dudka
|
||||
*/
|
||||
public final class JarURLConnection extends java.net.JarURLConnection {
|
||||
|
||||
private static final ThreadLocal<Boolean> useFastExceptions = new ThreadLocal<>();
|
||||
|
||||
private static final FileNotFoundException FILE_NOT_FOUND_EXCEPTION = new FileNotFoundException(
|
||||
"Jar file or entry not found");
|
||||
|
||||
private static final IllegalStateException NOT_FOUND_CONNECTION_EXCEPTION = new IllegalStateException(
|
||||
FILE_NOT_FOUND_EXCEPTION);
|
||||
|
||||
private static final String SEPARATOR = "!/";
|
||||
|
||||
private static final URL EMPTY_JAR_URL;
|
||||
|
||||
static {
|
||||
try {
|
||||
EMPTY_JAR_URL = new URL("jar:", null, 0, "file:!/", new URLStreamHandler() {
|
||||
@Override
|
||||
protected URLConnection openConnection(URL u) throws IOException {
|
||||
// Stub URLStreamHandler to prevent the wrong JAR Handler from being
|
||||
// Instantiated and cached.
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (MalformedURLException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static final JarEntryName EMPTY_JAR_ENTRY_NAME = new JarEntryName(new StringSequence(""));
|
||||
|
||||
private static final JarURLConnection NOT_FOUND_CONNECTION = JarURLConnection.notFound();
|
||||
|
||||
private final AbstractJarFile jarFile;
|
||||
|
||||
private Permission permission;
|
||||
|
||||
private URL jarFileUrl;
|
||||
|
||||
private final JarEntryName jarEntryName;
|
||||
|
||||
private java.util.jar.JarEntry jarEntry;
|
||||
|
||||
private JarURLConnection(URL url, AbstractJarFile jarFile, JarEntryName jarEntryName) throws IOException {
|
||||
// What we pass to super is ultimately ignored
|
||||
super(EMPTY_JAR_URL);
|
||||
this.url = url;
|
||||
this.jarFile = jarFile;
|
||||
this.jarEntryName = jarEntryName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect() throws IOException {
|
||||
if (this.jarFile == null) {
|
||||
throw FILE_NOT_FOUND_EXCEPTION;
|
||||
}
|
||||
if (!this.jarEntryName.isEmpty() && this.jarEntry == null) {
|
||||
this.jarEntry = this.jarFile.getJarEntry(getEntryName());
|
||||
if (this.jarEntry == null) {
|
||||
throwFileNotFound(this.jarEntryName, this.jarFile);
|
||||
}
|
||||
}
|
||||
this.connected = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.util.jar.JarFile getJarFile() throws IOException {
|
||||
connect();
|
||||
return this.jarFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getJarFileURL() {
|
||||
if (this.jarFile == null) {
|
||||
throw NOT_FOUND_CONNECTION_EXCEPTION;
|
||||
}
|
||||
if (this.jarFileUrl == null) {
|
||||
this.jarFileUrl = buildJarFileUrl();
|
||||
}
|
||||
return this.jarFileUrl;
|
||||
}
|
||||
|
||||
private URL buildJarFileUrl() {
|
||||
try {
|
||||
String spec = this.jarFile.getUrl().getFile();
|
||||
if (spec.endsWith(SEPARATOR)) {
|
||||
spec = spec.substring(0, spec.length() - SEPARATOR.length());
|
||||
}
|
||||
if (!spec.contains(SEPARATOR)) {
|
||||
return new URL(spec);
|
||||
}
|
||||
return new URL("jar:" + spec);
|
||||
}
|
||||
catch (MalformedURLException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.util.jar.JarEntry getJarEntry() throws IOException {
|
||||
if (this.jarEntryName == null || this.jarEntryName.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
connect();
|
||||
return this.jarEntry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEntryName() {
|
||||
if (this.jarFile == null) {
|
||||
throw NOT_FOUND_CONNECTION_EXCEPTION;
|
||||
}
|
||||
return this.jarEntryName.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
if (this.jarFile == null) {
|
||||
throw FILE_NOT_FOUND_EXCEPTION;
|
||||
}
|
||||
if (this.jarEntryName.isEmpty() && this.jarFile.getType() == JarFile.JarFileType.DIRECT) {
|
||||
throw new IOException("no entry name specified");
|
||||
}
|
||||
connect();
|
||||
InputStream inputStream = (this.jarEntryName.isEmpty() ? this.jarFile.getInputStream()
|
||||
: this.jarFile.getInputStream(this.jarEntry));
|
||||
if (inputStream == null) {
|
||||
throwFileNotFound(this.jarEntryName, this.jarFile);
|
||||
}
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
private void throwFileNotFound(Object entry, AbstractJarFile jarFile) throws FileNotFoundException {
|
||||
if (Boolean.TRUE.equals(useFastExceptions.get())) {
|
||||
throw FILE_NOT_FOUND_EXCEPTION;
|
||||
}
|
||||
throw new FileNotFoundException("JAR entry " + entry + " not found in " + jarFile.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getContentLength() {
|
||||
long length = getContentLengthLong();
|
||||
if (length > Integer.MAX_VALUE) {
|
||||
return -1;
|
||||
}
|
||||
return (int) length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContentLengthLong() {
|
||||
if (this.jarFile == null) {
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
if (this.jarEntryName.isEmpty()) {
|
||||
return this.jarFile.size();
|
||||
}
|
||||
java.util.jar.JarEntry entry = getJarEntry();
|
||||
return (entry != null) ? (int) entry.getSize() : -1;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getContent() throws IOException {
|
||||
connect();
|
||||
return this.jarEntryName.isEmpty() ? this.jarFile : super.getContent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return (this.jarEntryName != null) ? this.jarEntryName.getContentType() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Permission getPermission() throws IOException {
|
||||
if (this.jarFile == null) {
|
||||
throw FILE_NOT_FOUND_EXCEPTION;
|
||||
}
|
||||
if (this.permission == null) {
|
||||
this.permission = this.jarFile.getPermission();
|
||||
}
|
||||
return this.permission;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastModified() {
|
||||
if (this.jarFile == null || this.jarEntryName.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
java.util.jar.JarEntry entry = getJarEntry();
|
||||
return (entry != null) ? entry.getTime() : 0;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void setUseFastExceptions(boolean useFastExceptions) {
|
||||
JarURLConnection.useFastExceptions.set(useFastExceptions);
|
||||
}
|
||||
|
||||
public static JarURLConnection get(URL url, JarFile jarFile) throws IOException {
|
||||
StringSequence spec = new StringSequence(url.getFile());
|
||||
int index = indexOfRootSpec(spec, jarFile.getPathFromRoot());
|
||||
if (index == -1) {
|
||||
return (Boolean.TRUE.equals(useFastExceptions.get()) ? NOT_FOUND_CONNECTION
|
||||
: new JarURLConnection(url, null, EMPTY_JAR_ENTRY_NAME));
|
||||
}
|
||||
int separator;
|
||||
while ((separator = spec.indexOf(SEPARATOR, index)) > 0) {
|
||||
JarEntryName entryName = JarEntryName.get(spec.subSequence(index, separator));
|
||||
JarEntry jarEntry = jarFile.getJarEntry(entryName.toCharSequence());
|
||||
if (jarEntry == null) {
|
||||
return JarURLConnection.notFound(jarFile, entryName);
|
||||
}
|
||||
jarFile = jarFile.getNestedJarFile(jarEntry);
|
||||
index = separator + SEPARATOR.length();
|
||||
}
|
||||
JarEntryName jarEntryName = JarEntryName.get(spec, index);
|
||||
if (Boolean.TRUE.equals(useFastExceptions.get()) && !jarEntryName.isEmpty()
|
||||
&& !jarFile.containsEntry(jarEntryName.toString())) {
|
||||
return NOT_FOUND_CONNECTION;
|
||||
}
|
||||
return new JarURLConnection(url, jarFile.getWrapper(), jarEntryName);
|
||||
}
|
||||
|
||||
private static int indexOfRootSpec(StringSequence file, String pathFromRoot) {
|
||||
int separatorIndex = file.indexOf(SEPARATOR);
|
||||
if (separatorIndex < 0 || !file.startsWith(pathFromRoot, separatorIndex)) {
|
||||
return -1;
|
||||
}
|
||||
return separatorIndex + SEPARATOR.length() + pathFromRoot.length();
|
||||
}
|
||||
|
||||
private static JarURLConnection notFound() {
|
||||
try {
|
||||
return notFound(null, null);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static JarURLConnection notFound(JarFile jarFile, JarEntryName jarEntryName) throws IOException {
|
||||
if (Boolean.TRUE.equals(useFastExceptions.get())) {
|
||||
return NOT_FOUND_CONNECTION;
|
||||
}
|
||||
return new JarURLConnection(null, jarFile, jarEntryName);
|
||||
}
|
||||
|
||||
/**
|
||||
* A JarEntryName parsed from a URL String.
|
||||
*/
|
||||
static class JarEntryName {
|
||||
|
||||
private final StringSequence name;
|
||||
|
||||
private String contentType;
|
||||
|
||||
JarEntryName(StringSequence spec) {
|
||||
this.name = decode(spec);
|
||||
}
|
||||
|
||||
private StringSequence decode(StringSequence source) {
|
||||
if (source.isEmpty() || (source.indexOf('%') < 0)) {
|
||||
return source;
|
||||
}
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream(source.length());
|
||||
write(source.toString(), bos);
|
||||
// AsciiBytes is what is used to store the JarEntries so make it symmetric
|
||||
return new StringSequence(AsciiBytes.toString(bos.toByteArray()));
|
||||
}
|
||||
|
||||
private void write(String source, ByteArrayOutputStream outputStream) {
|
||||
int length = source.length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
int c = source.charAt(i);
|
||||
if (c > 127) {
|
||||
try {
|
||||
String encoded = URLEncoder.encode(String.valueOf((char) c), "UTF-8");
|
||||
write(encoded, outputStream);
|
||||
}
|
||||
catch (UnsupportedEncodingException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (c == '%') {
|
||||
if ((i + 2) >= length) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid encoded sequence \"" + source.substring(i) + "\"");
|
||||
}
|
||||
c = decodeEscapeSequence(source, i);
|
||||
i += 2;
|
||||
}
|
||||
outputStream.write(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private char decodeEscapeSequence(String source, int i) {
|
||||
int hi = Character.digit(source.charAt(i + 1), 16);
|
||||
int lo = Character.digit(source.charAt(i + 2), 16);
|
||||
if (hi == -1 || lo == -1) {
|
||||
throw new IllegalArgumentException("Invalid encoded sequence \"" + source.substring(i) + "\"");
|
||||
}
|
||||
return ((char) ((hi << 4) + lo));
|
||||
}
|
||||
|
||||
CharSequence toCharSequence() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.name.toString();
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return this.name.isEmpty();
|
||||
}
|
||||
|
||||
String getContentType() {
|
||||
if (this.contentType == null) {
|
||||
this.contentType = deduceContentType();
|
||||
}
|
||||
return this.contentType;
|
||||
}
|
||||
|
||||
private String deduceContentType() {
|
||||
// Guess the content type, don't bother with streams as mark is not supported
|
||||
String type = isEmpty() ? "x-java/jar" : null;
|
||||
type = (type != null) ? type : guessContentTypeFromName(toString());
|
||||
type = (type != null) ? type : "content/unknown";
|
||||
return type;
|
||||
}
|
||||
|
||||
static JarEntryName get(StringSequence spec) {
|
||||
return get(spec, 0);
|
||||
}
|
||||
|
||||
static JarEntryName get(StringSequence spec, int beginIndex) {
|
||||
if (spec.length() <= beginIndex) {
|
||||
return EMPTY_JAR_ENTRY_NAME;
|
||||
}
|
||||
return new JarEntryName(spec.subSequence(beginIndex));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal.jar;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A {@link CharSequence} backed by a single shared {@link String}. Unlike a regular
|
||||
* {@link String}, {@link #subSequence(int, int)} operations will not copy the underlying
|
||||
* character array.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
final class StringSequence implements CharSequence {
|
||||
|
||||
private final String source;
|
||||
|
||||
private final int start;
|
||||
|
||||
private final int end;
|
||||
|
||||
private int hash;
|
||||
|
||||
StringSequence(String source) {
|
||||
this(source, 0, (source != null) ? source.length() : -1);
|
||||
}
|
||||
|
||||
StringSequence(String source, int start, int end) {
|
||||
Objects.requireNonNull(source, "Source must not be null");
|
||||
if (start < 0) {
|
||||
throw new StringIndexOutOfBoundsException(start);
|
||||
}
|
||||
if (end > source.length()) {
|
||||
throw new StringIndexOutOfBoundsException(end);
|
||||
}
|
||||
this.source = source;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
StringSequence subSequence(int start) {
|
||||
return subSequence(start, length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringSequence subSequence(int start, int end) {
|
||||
int subSequenceStart = this.start + start;
|
||||
int subSequenceEnd = this.start + end;
|
||||
if (subSequenceStart > this.end) {
|
||||
throw new StringIndexOutOfBoundsException(start);
|
||||
}
|
||||
if (subSequenceEnd > this.end) {
|
||||
throw new StringIndexOutOfBoundsException(end);
|
||||
}
|
||||
if (start == 0 && subSequenceEnd == this.end) {
|
||||
return this;
|
||||
}
|
||||
return new StringSequence(this.source, subSequenceStart, subSequenceEnd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the sequence is empty. Public to be compatible with JDK 15.
|
||||
* @return {@code true} if {@link #length()} is {@code 0}, otherwise {@code false}
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return length() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return this.end - this.start;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char charAt(int index) {
|
||||
return this.source.charAt(this.start + index);
|
||||
}
|
||||
|
||||
int indexOf(char ch) {
|
||||
return this.source.indexOf(ch, this.start) - this.start;
|
||||
}
|
||||
|
||||
int indexOf(String str) {
|
||||
return this.source.indexOf(str, this.start) - this.start;
|
||||
}
|
||||
|
||||
int indexOf(String str, int fromIndex) {
|
||||
return this.source.indexOf(str, this.start + fromIndex) - this.start;
|
||||
}
|
||||
|
||||
boolean startsWith(String prefix) {
|
||||
return startsWith(prefix, 0);
|
||||
}
|
||||
|
||||
boolean startsWith(String prefix, int offset) {
|
||||
int prefixLength = prefix.length();
|
||||
int length = length();
|
||||
if (length - prefixLength - offset < 0) {
|
||||
return false;
|
||||
}
|
||||
return this.source.startsWith(prefix, this.start + offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof CharSequence)) {
|
||||
return false;
|
||||
}
|
||||
CharSequence other = (CharSequence)obj;
|
||||
int n = length();
|
||||
if (n != other.length()) {
|
||||
return false;
|
||||
}
|
||||
int i = 0;
|
||||
while (n-- != 0) {
|
||||
if (charAt(i) != other.charAt(i)) {
|
||||
return false;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = this.hash;
|
||||
if (hash == 0 && length() > 0) {
|
||||
for (int i = this.start; i < this.end; i++) {
|
||||
hash = 31 * hash + this.source.charAt(i);
|
||||
}
|
||||
this.hash = hash;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.source.substring(this.start, this.end);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package fun.asgc.neutrino.core.aop.compiler.internal.jar;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.zip.Inflater;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
/**
|
||||
* {@link InflaterInputStream} that supports the writing of an extra "dummy" byte (which
|
||||
* is required with JDK 6) and returns accurate available() results.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ZipInflaterInputStream extends InflaterInputStream {
|
||||
|
||||
private int available;
|
||||
|
||||
private boolean extraBytesWritten;
|
||||
|
||||
ZipInflaterInputStream(InputStream inputStream, int size) {
|
||||
super(inputStream, new Inflater(true), getInflaterBufferSize(size));
|
||||
this.available = size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
if (this.available < 0) {
|
||||
return super.available();
|
||||
}
|
||||
return this.available;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
int result = super.read(b, off, len);
|
||||
if (result != -1) {
|
||||
this.available -= result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
this.inf.end();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fill() throws IOException {
|
||||
try {
|
||||
super.fill();
|
||||
}
|
||||
catch (EOFException ex) {
|
||||
if (this.extraBytesWritten) {
|
||||
throw ex;
|
||||
}
|
||||
this.len = 1;
|
||||
this.buf[0] = 0x0;
|
||||
this.extraBytesWritten = true;
|
||||
this.inf.setInput(this.buf, 0, this.len);
|
||||
}
|
||||
}
|
||||
|
||||
private static int getInflaterBufferSize(long size) {
|
||||
size += 2; // inflater likes some space
|
||||
size = (size > 65536) ? 8192 : size;
|
||||
size = (size <= 0) ? 4096 : size;
|
||||
return (int) size;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -40,7 +40,6 @@ public class AsgcProxyFactory implements ProxyFactory {
|
||||
private static final String classNameTemplate = "%s" + SYMBOLIC + "%s";
|
||||
private static AtomicLong proxyClassCounter = new AtomicLong();
|
||||
private AsgcCompiler compiler = new AsgcCompiler();
|
||||
private ProxyClassLoader classLoader = new ProxyClassLoader();
|
||||
|
||||
@Override
|
||||
public <T> T get(Class<T> targetType) throws Exception {
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.core.aop.proxy;
|
||||
|
||||
import fun.asgc.neutrino.core.base.GlobalConfig;
|
||||
import fun.asgc.neutrino.core.util.FileUtil;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 代理类加载器
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/24
|
||||
*/
|
||||
public class ProxyClassLoader extends ClassLoader {
|
||||
|
||||
protected Map<String, byte[]> byteCodeMap = new ConcurrentHashMap<>();
|
||||
|
||||
static {
|
||||
registerAsParallelCapable();
|
||||
}
|
||||
|
||||
public ProxyClassLoader() {
|
||||
super(getParentClassLoader());
|
||||
}
|
||||
|
||||
protected static ClassLoader getParentClassLoader() {
|
||||
ClassLoader ret = Thread.currentThread().getContextClassLoader();
|
||||
return ret != null ? ret : ProxyClassLoader.class.getClassLoader();
|
||||
}
|
||||
|
||||
public Class<?> loadProxyClass(ProxyClass proxyClass) {
|
||||
if (null != proxyClass.getByteCode()) {
|
||||
for (Map.Entry<String, byte[]> e : proxyClass.getByteCode().entrySet()) {
|
||||
byteCodeMap.putIfAbsent(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return loadClass(proxyClass.getPkg() + "." + proxyClass.getName());
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||
byte[] bytes = byteCodeMap.get(name);
|
||||
if (null == bytes) {
|
||||
bytes = FileUtil.readBytes( GlobalConfig.getGeneratorCodeSavePath() + name.replaceAll("\\.", "/") + ".class");
|
||||
}
|
||||
if (bytes != null) {
|
||||
Class<?> ret = defineClass(name, bytes, 0, bytes.length);
|
||||
byteCodeMap.remove(name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return super.findClass(name);
|
||||
}
|
||||
}
|
||||
@@ -23,11 +23,16 @@
|
||||
package fun.asgc.neutrino.core.context;
|
||||
|
||||
import fun.asgc.neutrino.core.base.event.SimpleApplicationEventManager;
|
||||
import fun.asgc.neutrino.core.util.ArrayUtil;
|
||||
import fun.asgc.neutrino.core.util.LockUtil;
|
||||
import fun.asgc.neutrino.core.util.StringUtil;
|
||||
import fun.asgc.neutrino.core.util.SystemUtil;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -69,4 +74,71 @@ public class Environment {
|
||||
* 默认的应用事件管理器
|
||||
*/
|
||||
private SimpleApplicationEventManager defaultApplicationEventManager;
|
||||
/**
|
||||
* 参数map
|
||||
*/
|
||||
private Map<String, String> mainArgsMap = null;
|
||||
|
||||
private Map<String, String> getMainArgsMap() {
|
||||
return LockUtil.doubleCheckProcessForNoException(
|
||||
() -> null == mainArgsMap,
|
||||
this,
|
||||
() -> {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
if (ArrayUtil.notEmpty(mainArgs)) {
|
||||
for (String param : mainArgs) {
|
||||
if (!StringUtil.isEmpty(param)) {
|
||||
int index = param.indexOf("=");
|
||||
if (index > 0 && index < param.length() - 1) {
|
||||
String key = param.substring(0, index);
|
||||
String val = param.substring(index + 1);
|
||||
map.put(key, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mainArgsMap = map;
|
||||
},
|
||||
() -> mainArgsMap
|
||||
);
|
||||
}
|
||||
|
||||
private String getMainArgs(String key) {
|
||||
return getMainArgsMap().get(key);
|
||||
}
|
||||
|
||||
public Integer getMainArgsForInteger(String key, Integer defaultValue) {
|
||||
String val = getMainArgs(key);
|
||||
Integer res = null;
|
||||
try {
|
||||
if (!StringUtil.isEmpty(val)) {
|
||||
res = Integer.valueOf(val);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
return (null != res) ? res : defaultValue;
|
||||
}
|
||||
|
||||
public Boolean getMainArgsForBoolean(String key, Boolean defaultValue) {
|
||||
String val = getMainArgs(key);
|
||||
Boolean res = null;
|
||||
try {
|
||||
if (!StringUtil.isEmpty(val)) {
|
||||
res = Boolean.valueOf(val);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
return (null != res) ? res : defaultValue;
|
||||
}
|
||||
|
||||
public String getMainArgsForString(String key, String defaultValue) {
|
||||
String res = getMainArgs(key);
|
||||
return !StringUtil.isEmpty(res) ? res : defaultValue;
|
||||
}
|
||||
|
||||
public String getMainArgsForString(String key) {
|
||||
return getMainArgsForString(key, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.core.db.crisp;
|
||||
|
||||
/**
|
||||
* 清爽的数据库工具
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/3
|
||||
*/
|
||||
public class CrispDbKit {
|
||||
private static final JdbcManager manager = new JdbcManager();
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.core.db.crisp;
|
||||
|
||||
import fun.asgc.neutrino.core.db.crisp.ds.DefaultDataSourceProvider;
|
||||
import fun.asgc.neutrino.core.db.crisp.ds.IDataSourceProvider;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/3
|
||||
*/
|
||||
public class DbConfig {
|
||||
/**
|
||||
* 数据库名称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 数据源提供者
|
||||
*/
|
||||
private IDataSourceProvider dataSourceProvider;
|
||||
/**
|
||||
* 是否显示sql
|
||||
*/
|
||||
private Boolean showSql;
|
||||
/**
|
||||
* 是否开启调试模式
|
||||
*/
|
||||
private Boolean debugMode;
|
||||
/**
|
||||
* 上下文
|
||||
*/
|
||||
private DbContext context;
|
||||
/**
|
||||
* 数据库执行器
|
||||
*/
|
||||
private DbExecutor dbExecutor;
|
||||
|
||||
public DbConfig(String name, IDataSourceProvider dataSourceProvider) {
|
||||
this.name = name;
|
||||
this.dataSourceProvider = dataSourceProvider;
|
||||
this.showSql = Boolean.FALSE;
|
||||
this.debugMode = Boolean.FALSE;
|
||||
this.context = new DbContext(this);
|
||||
this.dbExecutor = new DbExecutor(this);
|
||||
}
|
||||
|
||||
public DbConfig(String name, DataSource dataSource) {
|
||||
this(name, new DefaultDataSourceProvider(dataSource));
|
||||
}
|
||||
|
||||
public DbConfig(IDataSourceProvider dataSourceProvider) {
|
||||
this("unknown", dataSourceProvider);
|
||||
}
|
||||
|
||||
public DbConfig(DataSource dataSource) {
|
||||
this(new DefaultDataSourceProvider(dataSource));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库操作实例
|
||||
* @return 数据库操作实例
|
||||
*/
|
||||
public DbExecutor getDbExecutor() {
|
||||
return dbExecutor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.core.db.crisp;
|
||||
|
||||
import fun.asgc.neutrino.core.db.crisp.session.SqlSessionFactory;
|
||||
|
||||
import java.sql.Connection;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/3
|
||||
*/
|
||||
public class DbContext {
|
||||
private DbConfig dbConfig;
|
||||
private final ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();
|
||||
|
||||
public DbContext(DbConfig dbConfig) {
|
||||
this.dbConfig = dbConfig;
|
||||
}
|
||||
public SqlSessionFactory getSqlSessionFactory() {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.core.db.crisp;
|
||||
|
||||
import fun.asgc.neutrino.core.db.crisp.base.DbUpdateResult;
|
||||
import fun.asgc.neutrino.core.db.crisp.ds.IDataSourceProvider;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/3
|
||||
*/
|
||||
class DbExecutor implements DbOperator {
|
||||
/**
|
||||
* 配置
|
||||
*/
|
||||
private DbConfig config;
|
||||
|
||||
|
||||
public DbExecutor(DbConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public DbExecutor(IDataSourceProvider dataSourceProvider) {
|
||||
this(new DbConfig(dataSourceProvider));
|
||||
}
|
||||
|
||||
public DbExecutor(String name, IDataSourceProvider dataSourceProvider) {
|
||||
this(new DbConfig(name, dataSourceProvider));
|
||||
}
|
||||
|
||||
public DbExecutor(DataSource dataSource) {
|
||||
this(new DbConfig(dataSource));
|
||||
}
|
||||
|
||||
public DbExecutor(String name, DataSource dataSource) {
|
||||
this(new DbConfig(name, dataSource));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<T> queryList(Class<T> resultType, String sql, Object... params) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T query(Class<T> resultType, String sql, Object... params) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbUpdateResult update(String sql, Object... params) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbUpdateResult insert(String sql, Object... params) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbUpdateResult delete(String sql, Object... params) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.core.db.crisp;
|
||||
|
||||
import fun.asgc.neutrino.core.db.crisp.base.DbUpdateResult;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/3
|
||||
*/
|
||||
public interface DbOperator {
|
||||
/**
|
||||
* 查询数据列表
|
||||
* @param resultType 结果类型
|
||||
* @param sql sql
|
||||
* @param params 参数列表
|
||||
* @return 查询结果列表
|
||||
* @param <T>
|
||||
*/
|
||||
<T> List<T> queryList(Class<T> resultType, String sql, Object... params);
|
||||
|
||||
/**
|
||||
* 查询单条记录
|
||||
* @param resultType 结果类型
|
||||
* @param sql sql
|
||||
* @param params 参数列表
|
||||
* @return 查询结果
|
||||
* @param <T>
|
||||
*/
|
||||
<T> T query(Class<T> resultType, String sql, Object... params);
|
||||
|
||||
/**
|
||||
* 更新
|
||||
* @param sql sql
|
||||
* @param params 参数列表
|
||||
* @return 更新结果
|
||||
*/
|
||||
DbUpdateResult update(String sql, Object... params);
|
||||
|
||||
/**
|
||||
* 新增
|
||||
* @param sql sql
|
||||
* @param params 参数列表
|
||||
* @return 新增结果
|
||||
*/
|
||||
DbUpdateResult insert(String sql, Object... params);
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param sql sql
|
||||
* @param params 参数列表
|
||||
* @return 删除结果
|
||||
*/
|
||||
DbUpdateResult delete(String sql, Object... params);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package fun.asgc.neutrino.core.db.crisp;
|
||||
|
||||
import fun.asgc.neutrino.core.exception.InternalException;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/7
|
||||
*/
|
||||
public class JdbcException extends InternalException {
|
||||
|
||||
public JdbcException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public JdbcException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.core.db.crisp;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/4
|
||||
*/
|
||||
public class JdbcManager {
|
||||
/**
|
||||
* 数据源缓存
|
||||
*/
|
||||
private Map<String, DbConfig> dbConfigMap = new HashMap<>();
|
||||
/**
|
||||
* 默认数据库的key
|
||||
*/
|
||||
private final String DEFAULT_KEY = "default";
|
||||
|
||||
/**
|
||||
* 设置默认数据库配置
|
||||
* @param config 数据库配置
|
||||
*/
|
||||
public void setConfig(DbConfig config) {
|
||||
setConfig(DEFAULT_KEY, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据库配置
|
||||
* @param key 键
|
||||
* @param config 数据库配置
|
||||
*/
|
||||
public void setConfig(String key, DbConfig config) {
|
||||
dbConfigMap.put(key, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据名称获取数据库配置
|
||||
* @param key 键
|
||||
* @return 数据库配置
|
||||
*/
|
||||
public DbConfig getConfig(String key) {
|
||||
return dbConfigMap.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认数据库配置
|
||||
* @return 数据库配置
|
||||
*/
|
||||
public DbConfig getConfig() {
|
||||
return getConfig(DEFAULT_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认数据库操作实例
|
||||
* @return
|
||||
*/
|
||||
private DbExecutor getDbExecutor(String key) {
|
||||
DbConfig dbConfig = getConfig(key);
|
||||
return null == dbConfig ? null : dbConfig.getDbExecutor();
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换数据源
|
||||
* @param name 数据源名称
|
||||
* @return 数据库操作实例
|
||||
*/
|
||||
public DbExecutor use(String name) {
|
||||
return getDbExecutor(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换为默认数据源
|
||||
* @return 默认数据库操作实例
|
||||
*/
|
||||
public DbExecutor useDefault() {
|
||||
return getDbExecutor(DEFAULT_KEY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.core.db.crisp.base;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/4
|
||||
*/
|
||||
public class BatchResult {
|
||||
|
||||
private final MappedStatement mappedStatement;
|
||||
private final String sql;
|
||||
private final List<Object> parameterObjects;
|
||||
|
||||
private int[] updateCounts;
|
||||
|
||||
public BatchResult(MappedStatement mappedStatement, String sql) {
|
||||
super();
|
||||
this.mappedStatement = mappedStatement;
|
||||
this.sql = sql;
|
||||
this.parameterObjects = new ArrayList<>();
|
||||
}
|
||||
|
||||
public BatchResult(MappedStatement mappedStatement, String sql, Object parameterObject) {
|
||||
this(mappedStatement, sql);
|
||||
addParameterObject(parameterObject);
|
||||
}
|
||||
|
||||
public MappedStatement getMappedStatement() {
|
||||
return mappedStatement;
|
||||
}
|
||||
|
||||
public String getSql() {
|
||||
return sql;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Object getParameterObject() {
|
||||
return parameterObjects.get(0);
|
||||
}
|
||||
|
||||
public List<Object> getParameterObjects() {
|
||||
return parameterObjects;
|
||||
}
|
||||
|
||||
public int[] getUpdateCounts() {
|
||||
return updateCounts;
|
||||
}
|
||||
|
||||
public void setUpdateCounts(int[] updateCounts) {
|
||||
this.updateCounts = updateCounts;
|
||||
}
|
||||
|
||||
public void addParameterObject(Object parameterObject) {
|
||||
this.parameterObjects.add(parameterObject);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.core.db.crisp.base;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/3
|
||||
*/
|
||||
public class DbUpdateResult {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.core.db.crisp.base;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/4
|
||||
*/
|
||||
public class MappedStatement {
|
||||
// TODO
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.core.db.crisp.base;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/4
|
||||
*/
|
||||
public class RowBounds {
|
||||
|
||||
public static final int NO_ROW_OFFSET = 0;
|
||||
public static final int NO_ROW_LIMIT = Integer.MAX_VALUE;
|
||||
public static final RowBounds DEFAULT = new RowBounds();
|
||||
|
||||
private final int offset;
|
||||
private final int limit;
|
||||
|
||||
public RowBounds() {
|
||||
this.offset = NO_ROW_OFFSET;
|
||||
this.limit = NO_ROW_LIMIT;
|
||||
}
|
||||
|
||||
public RowBounds(int offset, int limit) {
|
||||
this.offset = offset;
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public int getLimit() {
|
||||
return limit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.core.db.crisp.base;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/4
|
||||
*/
|
||||
public enum SqlExecutorType {
|
||||
SIMPLE, REUSE, BATCH
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.core.db.crisp.ds;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/3
|
||||
*/
|
||||
public class DefaultDataSourceProvider implements IDataSourceProvider {
|
||||
/**
|
||||
* 数据源
|
||||
*/
|
||||
private DataSource dataSource;
|
||||
|
||||
public DefaultDataSourceProvider(DataSource dataSource) {
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSource getDataSource() {
|
||||
return dataSource;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.core.db.crisp.ds;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
/**
|
||||
* 数据源提供商
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/3
|
||||
*/
|
||||
public interface IDataSourceProvider {
|
||||
/**
|
||||
* 获取数据源
|
||||
* @return
|
||||
*/
|
||||
DataSource getDataSource();
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.core.db.crisp.session;
|
||||
|
||||
import fun.asgc.neutrino.core.db.crisp.DbConfig;
|
||||
import fun.asgc.neutrino.core.db.crisp.base.SqlExecutorType;
|
||||
import fun.asgc.neutrino.core.db.crisp.tx.TransactionIsolationLevel;
|
||||
|
||||
import java.sql.Connection;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/4
|
||||
*/
|
||||
public class DefaultSqlSessionFactory implements SqlSessionFactory {
|
||||
|
||||
private final DbConfig config;
|
||||
|
||||
public DefaultSqlSessionFactory(DbConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlSession openSession() {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlSession openSession(boolean autoCommit) {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlSession openSession(Connection connection) {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlSession openSession(TransactionIsolationLevel level) {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlSession openSession(SqlExecutorType execType) {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlSession openSession(SqlExecutorType execType, boolean autoCommit) {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlSession openSession(SqlExecutorType execType, TransactionIsolationLevel level) {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlSession openSession(SqlExecutorType execType, Connection connection) {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbConfig getDbConfig() {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.core.db.crisp.session;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/23
|
||||
*/
|
||||
public interface ResultContext<T> {
|
||||
T getResultObject();
|
||||
|
||||
int getResultCount();
|
||||
|
||||
boolean isStopped();
|
||||
|
||||
void stop();
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.core.db.crisp.session;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/23
|
||||
*/
|
||||
public interface ResultHandler<T> {
|
||||
void handleResult(ResultContext<? extends T> resultContext);
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.core.db.crisp.session;
|
||||
|
||||
import fun.asgc.neutrino.core.db.crisp.DbConfig;
|
||||
import fun.asgc.neutrino.core.db.crisp.base.BatchResult;
|
||||
import fun.asgc.neutrino.core.db.crisp.base.RowBounds;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.sql.Connection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/4
|
||||
*/
|
||||
public interface SqlSession extends Closeable {
|
||||
|
||||
/**
|
||||
* Retrieve a single row mapped from the statement key.
|
||||
* @param <T> the returned object type
|
||||
* @param statement
|
||||
* @return Mapped object
|
||||
*/
|
||||
<T> T selectOne(String statement);
|
||||
|
||||
/**
|
||||
* Retrieve a single row mapped from the statement key and parameter.
|
||||
* @param <T> the returned object type
|
||||
* @param statement Unique identifier matching the statement to use.
|
||||
* @param parameter A parameter object to pass to the statement.
|
||||
* @return Mapped object
|
||||
*/
|
||||
<T> T selectOne(String statement, Object parameter);
|
||||
|
||||
/**
|
||||
* Retrieve a list of mapped objects from the statement key and parameter.
|
||||
* @param <E> the returned list element type
|
||||
* @param statement Unique identifier matching the statement to use.
|
||||
* @return List of mapped object
|
||||
*/
|
||||
<E> List<E> selectList(String statement);
|
||||
|
||||
/**
|
||||
* Retrieve a list of mapped objects from the statement key and parameter.
|
||||
* @param <E> the returned list element type
|
||||
* @param statement Unique identifier matching the statement to use.
|
||||
* @param parameter A parameter object to pass to the statement.
|
||||
* @return List of mapped object
|
||||
*/
|
||||
<E> List<E> selectList(String statement, Object parameter);
|
||||
|
||||
/**
|
||||
* Retrieve a list of mapped objects from the statement key and parameter,
|
||||
* within the specified row bounds.
|
||||
* @param <E> the returned list element type
|
||||
* @param statement Unique identifier matching the statement to use.
|
||||
* @param parameter A parameter object to pass to the statement.
|
||||
* @param rowBounds Bounds to limit object retrieval
|
||||
* @return List of mapped object
|
||||
*/
|
||||
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
|
||||
|
||||
/**
|
||||
* The selectMap is a special case in that it is designed to convert a list
|
||||
* of results into a Map based on one of the properties in the resulting
|
||||
* objects.
|
||||
* Eg. Return a of Map[Integer,Author] for selectMap("selectAuthors","id")
|
||||
* @param <K> the returned Map keys type
|
||||
* @param <V> the returned Map values type
|
||||
* @param statement Unique identifier matching the statement to use.
|
||||
* @param mapKey The property to use as key for each value in the list.
|
||||
* @return Map containing key pair data.
|
||||
*/
|
||||
<K, V> Map<K, V> selectMap(String statement, String mapKey);
|
||||
|
||||
/**
|
||||
* The selectMap is a special case in that it is designed to convert a list
|
||||
* of results into a Map based on one of the properties in the resulting
|
||||
* objects.
|
||||
* @param <K> the returned Map keys type
|
||||
* @param <V> the returned Map values type
|
||||
* @param statement Unique identifier matching the statement to use.
|
||||
* @param parameter A parameter object to pass to the statement.
|
||||
* @param mapKey The property to use as key for each value in the list.
|
||||
* @return Map containing key pair data.
|
||||
*/
|
||||
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
|
||||
|
||||
/**
|
||||
* The selectMap is a special case in that it is designed to convert a list
|
||||
* of results into a Map based on one of the properties in the resulting
|
||||
* objects.
|
||||
* @param <K> the returned Map keys type
|
||||
* @param <V> the returned Map values type
|
||||
* @param statement Unique identifier matching the statement to use.
|
||||
* @param parameter A parameter object to pass to the statement.
|
||||
* @param mapKey The property to use as key for each value in the list.
|
||||
* @param rowBounds Bounds to limit object retrieval
|
||||
* @return Map containing key pair data.
|
||||
*/
|
||||
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
|
||||
|
||||
/**
|
||||
* Retrieve a single row mapped from the statement key and parameter
|
||||
* using a {@code ResultHandler}.
|
||||
*
|
||||
* @param statement Unique identifier matching the statement to use.
|
||||
* @param parameter A parameter object to pass to the statement.
|
||||
* @param handler ResultHandler that will handle each retrieved row
|
||||
*/
|
||||
default void select(String statement, Object parameter, ResultHandler handler) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a single row mapped from the statement
|
||||
* using a {@code ResultHandler}.
|
||||
* @param statement Unique identifier matching the statement to use.
|
||||
* @param handler ResultHandler that will handle each retrieved row
|
||||
*/
|
||||
void select(String statement, ResultHandler handler);
|
||||
|
||||
/**
|
||||
* Retrieve a single row mapped from the statement key and parameter
|
||||
* using a {@code ResultHandler} and {@code RowBounds}.
|
||||
* @param statement Unique identifier matching the statement to use.
|
||||
* @param rowBounds RowBound instance to limit the query results
|
||||
* @param handler ResultHandler that will handle each retrieved row
|
||||
*/
|
||||
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
|
||||
|
||||
/**
|
||||
* Execute an insert statement.
|
||||
* @param statement Unique identifier matching the statement to execute.
|
||||
* @return int The number of rows affected by the insert.
|
||||
*/
|
||||
int insert(String statement);
|
||||
|
||||
/**
|
||||
* Execute an insert statement with the given parameter object. Any generated
|
||||
* autoincrement values or selectKey entries will modify the given parameter
|
||||
* object properties. Only the number of rows affected will be returned.
|
||||
* @param statement Unique identifier matching the statement to execute.
|
||||
* @param parameter A parameter object to pass to the statement.
|
||||
* @return int The number of rows affected by the insert.
|
||||
*/
|
||||
int insert(String statement, Object parameter);
|
||||
|
||||
/**
|
||||
* Execute an update statement. The number of rows affected will be returned.
|
||||
* @param statement Unique identifier matching the statement to execute.
|
||||
* @return int The number of rows affected by the update.
|
||||
*/
|
||||
int update(String statement);
|
||||
|
||||
/**
|
||||
* Execute an update statement. The number of rows affected will be returned.
|
||||
* @param statement Unique identifier matching the statement to execute.
|
||||
* @param parameter A parameter object to pass to the statement.
|
||||
* @return int The number of rows affected by the update.
|
||||
*/
|
||||
int update(String statement, Object parameter);
|
||||
|
||||
/**
|
||||
* Execute a delete statement. The number of rows affected will be returned.
|
||||
* @param statement Unique identifier matching the statement to execute.
|
||||
* @return int The number of rows affected by the delete.
|
||||
*/
|
||||
int delete(String statement);
|
||||
|
||||
/**
|
||||
* Execute a delete statement. The number of rows affected will be returned.
|
||||
* @param statement Unique identifier matching the statement to execute.
|
||||
* @param parameter A parameter object to pass to the statement.
|
||||
* @return int The number of rows affected by the delete.
|
||||
*/
|
||||
int delete(String statement, Object parameter);
|
||||
|
||||
/**
|
||||
* Flushes batch statements and commits database connection.
|
||||
* Note that database connection will not be committed if no updates/deletes/inserts were called.
|
||||
* To force the commit call {@link SqlSession#commit(boolean)}
|
||||
*/
|
||||
void commit();
|
||||
|
||||
/**
|
||||
* Flushes batch statements and commits database connection.
|
||||
* @param force forces connection commit
|
||||
*/
|
||||
void commit(boolean force);
|
||||
|
||||
/**
|
||||
* Discards pending batch statements and rolls database connection back.
|
||||
* Note that database connection will not be rolled back if no updates/deletes/inserts were called.
|
||||
* To force the rollback call {@link SqlSession#rollback(boolean)}
|
||||
*/
|
||||
void rollback();
|
||||
|
||||
/**
|
||||
* Discards pending batch statements and rolls database connection back.
|
||||
* Note that database connection will not be rolled back if no updates/deletes/inserts were called.
|
||||
* @param force forces connection rollback
|
||||
*/
|
||||
void rollback(boolean force);
|
||||
|
||||
/**
|
||||
* Flushes batch statements.
|
||||
* @return BatchResult list of updated records
|
||||
* @since 3.0.6
|
||||
*/
|
||||
List<BatchResult> flushStatements();
|
||||
|
||||
/**
|
||||
* Closes the session.
|
||||
*/
|
||||
@Override
|
||||
void close();
|
||||
|
||||
/**
|
||||
* Clears local session cache.
|
||||
*/
|
||||
void clearCache();
|
||||
|
||||
/**
|
||||
* Retrieves current configuration.
|
||||
* @return DbConfig
|
||||
*/
|
||||
DbConfig getDbConfig();
|
||||
|
||||
/**
|
||||
* Retrieves a mapper.
|
||||
* @param <T> the mapper type
|
||||
* @param type Mapper interface class
|
||||
* @return a mapper bound to this SqlSession
|
||||
*/
|
||||
<T> T getMapper(Class<T> type);
|
||||
|
||||
/**
|
||||
* Retrieves inner database connection.
|
||||
* @return Connection
|
||||
*/
|
||||
Connection getConnection();
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.core.db.crisp.session;
|
||||
|
||||
import fun.asgc.neutrino.core.db.crisp.DbConfig;
|
||||
import fun.asgc.neutrino.core.db.crisp.base.SqlExecutorType;
|
||||
import fun.asgc.neutrino.core.db.crisp.tx.TransactionIsolationLevel;
|
||||
|
||||
import java.sql.Connection;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/4
|
||||
*/
|
||||
public interface SqlSessionFactory {
|
||||
|
||||
SqlSession openSession();
|
||||
|
||||
SqlSession openSession(boolean autoCommit);
|
||||
|
||||
SqlSession openSession(Connection connection);
|
||||
|
||||
SqlSession openSession(TransactionIsolationLevel level);
|
||||
|
||||
SqlSession openSession(SqlExecutorType execType);
|
||||
|
||||
SqlSession openSession(SqlExecutorType execType, boolean autoCommit);
|
||||
|
||||
SqlSession openSession(SqlExecutorType execType, TransactionIsolationLevel level);
|
||||
|
||||
SqlSession openSession(SqlExecutorType execType, Connection connection);
|
||||
|
||||
DbConfig getDbConfig();
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.core.db.crisp.session;
|
||||
|
||||
import fun.asgc.neutrino.core.db.crisp.DbConfig;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/7
|
||||
*/
|
||||
public class SqlSessionFactoryBuilder {
|
||||
public SqlSessionFactory build(DbConfig config) {
|
||||
return new DefaultSqlSessionFactory(config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package fun.asgc.neutrino.core.db.crisp.tx;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/7
|
||||
*/
|
||||
@Slf4j
|
||||
public class JdbcTransaction implements Transaction {
|
||||
|
||||
protected Connection connection;
|
||||
protected DataSource dataSource;
|
||||
protected TransactionIsolationLevel level;
|
||||
protected boolean autoCommit;
|
||||
|
||||
public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
|
||||
dataSource = ds;
|
||||
level = desiredLevel;
|
||||
autoCommit = desiredAutoCommit;
|
||||
}
|
||||
|
||||
public JdbcTransaction(Connection connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getConnection() throws SQLException {
|
||||
if (connection == null) {
|
||||
openConnection();
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit() throws SQLException {
|
||||
if (connection != null && !connection.getAutoCommit()) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Committing JDBC Connection [" + connection + "]");
|
||||
}
|
||||
connection.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback() throws SQLException {
|
||||
if (connection != null && !connection.getAutoCommit()) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Rolling back JDBC Connection [" + connection + "]");
|
||||
}
|
||||
connection.rollback();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws SQLException {
|
||||
if (connection != null) {
|
||||
resetAutoCommit();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Closing JDBC Connection [" + connection + "]");
|
||||
}
|
||||
connection.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getTimeout() throws SQLException {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void openConnection() throws SQLException {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Opening JDBC Connection");
|
||||
}
|
||||
connection = dataSource.getConnection();
|
||||
if (level != null) {
|
||||
connection.setTransactionIsolation(level.getLevel());
|
||||
}
|
||||
setDesiredAutoCommit(autoCommit);
|
||||
}
|
||||
|
||||
protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
|
||||
try {
|
||||
if (connection.getAutoCommit() != desiredAutoCommit) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]");
|
||||
}
|
||||
connection.setAutoCommit(desiredAutoCommit);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
// Only a very poorly implemented driver would fail here,
|
||||
// and there's not much we can do about that.
|
||||
throw new TransactionException("Error configuring AutoCommit. "
|
||||
+ "Your driver may not support getAutoCommit() or setAutoCommit(). "
|
||||
+ "Requested setting: " + desiredAutoCommit + ". Cause: " + e, e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void resetAutoCommit() {
|
||||
try {
|
||||
if (!connection.getAutoCommit()) {
|
||||
// MyBatis does not call commit/rollback on a connection if just selects were performed.
|
||||
// Some databases start transactions with select statements
|
||||
// and they mandate a commit/rollback before closing the connection.
|
||||
// A workaround is setting the autocommit to true before closing the connection.
|
||||
// Sybase throws an exception here.
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]");
|
||||
}
|
||||
connection.setAutoCommit(true);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Error resetting autocommit to true "
|
||||
+ "before closing the connection. Cause: " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.core.db.crisp.tx;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/7
|
||||
*/
|
||||
public interface Transaction {
|
||||
|
||||
/**
|
||||
* Retrieve inner database connection.
|
||||
* @return DataBase connection
|
||||
* @throws SQLException
|
||||
*/
|
||||
Connection getConnection() throws SQLException;
|
||||
|
||||
/**
|
||||
* Commit inner database connection.
|
||||
* @throws SQLException
|
||||
*/
|
||||
void commit() throws SQLException;
|
||||
|
||||
/**
|
||||
* Rollback inner database connection.
|
||||
* @throws SQLException
|
||||
*/
|
||||
void rollback() throws SQLException;
|
||||
|
||||
/**
|
||||
* Close inner database connection.
|
||||
* @throws SQLException
|
||||
*/
|
||||
void close() throws SQLException;
|
||||
|
||||
/**
|
||||
* Get transaction timeout if set.
|
||||
* @throws SQLException
|
||||
*/
|
||||
Integer getTimeout() throws SQLException;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package fun.asgc.neutrino.core.db.crisp.tx;
|
||||
|
||||
import fun.asgc.neutrino.core.db.crisp.JdbcException;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/7
|
||||
*/
|
||||
public class TransactionException extends JdbcException {
|
||||
|
||||
public TransactionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public TransactionException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.core.db.crisp.tx;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.sql.Connection;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/4
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum TransactionIsolationLevel {
|
||||
NONE(Connection.TRANSACTION_NONE),
|
||||
READ_COMMITTED(Connection.TRANSACTION_READ_COMMITTED),
|
||||
READ_UNCOMMITTED(Connection.TRANSACTION_READ_UNCOMMITTED),
|
||||
REPEATABLE_READ(Connection.TRANSACTION_REPEATABLE_READ),
|
||||
SERIALIZABLE(Connection.TRANSACTION_SERIALIZABLE);
|
||||
|
||||
private final int level;
|
||||
}
|
||||
@@ -21,15 +21,15 @@
|
||||
*/
|
||||
package fun.asgc.neutrino.core.db.template;
|
||||
|
||||
import fun.asgc.neutrino.core.db.annotation.Id;
|
||||
import fun.asgc.neutrino.core.util.CollectionUtil;
|
||||
import fun.asgc.neutrino.core.util.ReflectUtil;
|
||||
import fun.asgc.neutrino.core.util.TypeUtil;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -38,6 +38,8 @@ import java.util.Map;
|
||||
*/
|
||||
public class JdbcOperations {
|
||||
private static final JdbcOperations instance = new JdbcOperations();
|
||||
|
||||
private static final Map<Class<?>, Field> generateIdFieldMap = new ConcurrentHashMap<>();
|
||||
|
||||
private JdbcOperations() {
|
||||
|
||||
@@ -85,6 +87,45 @@ public class JdbcOperations {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行更新操作
|
||||
* 临时兼容返设主键问题
|
||||
* @param conn
|
||||
* @param sql
|
||||
* @param params
|
||||
* @return
|
||||
*/
|
||||
public int executeUpdateByModel(final Connection conn , final String sql, final Object model, final Object[] params) throws SQLException {
|
||||
return this.execute(new PreparedStatementJdbcCallback<Integer>(){
|
||||
@Override
|
||||
public Integer execute(PreparedStatement ps) throws SQLException {
|
||||
Integer res = ps.executeUpdate();
|
||||
if (null != model) {
|
||||
ResultSet resultSet = ps.getGeneratedKeys();
|
||||
if (resultSet.next()) {
|
||||
Field field = getGenerateIdField(model.getClass());
|
||||
if (null != field) {
|
||||
ReflectUtil.setFieldValue(field, model, resultSet.getInt(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@Override
|
||||
public Object[] getParams() {
|
||||
return params;
|
||||
}
|
||||
@Override
|
||||
public String getSql() {
|
||||
return sql;
|
||||
}
|
||||
@Override
|
||||
public Connection getConnection(){
|
||||
return conn;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行单条查询操作
|
||||
* @param conn
|
||||
@@ -225,4 +266,28 @@ public class JdbcOperations {
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取自动生成ID字段
|
||||
* @param clazz
|
||||
* @return
|
||||
*/
|
||||
private static Field getGenerateIdField(Class<?> clazz) {
|
||||
if (null == clazz) {
|
||||
return null;
|
||||
}
|
||||
if (generateIdFieldMap.containsKey(clazz)) {
|
||||
return generateIdFieldMap.get(clazz);
|
||||
}
|
||||
Set<Field> fields = ReflectUtil.getDeclaredFields(clazz);
|
||||
if (CollectionUtil.isEmpty(fields)) {
|
||||
return null;
|
||||
}
|
||||
Field field = fields.stream().filter(f -> f.isAnnotationPresent(Id.class)).findFirst().orElse(null);
|
||||
if (null != field) {
|
||||
return field;
|
||||
}
|
||||
field = fields.stream().filter(f -> f.getName().equals("id")).findFirst().orElse(null);
|
||||
return field;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,16 +21,12 @@
|
||||
*/
|
||||
package fun.asgc.neutrino.core.db.template;
|
||||
|
||||
import fun.asgc.neutrino.core.util.ArrayUtil;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -74,6 +70,31 @@ public class JdbcTemplate {
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO 临时用来兼容insert之后需要返设主键的问题
|
||||
* @param sql
|
||||
* @param params
|
||||
* @return
|
||||
* @throws SQLException
|
||||
*/
|
||||
public int updateByModel(String sql, Object model, Object ...params) throws SQLException {
|
||||
int res = -1;
|
||||
Connection conn = null;
|
||||
|
||||
try {
|
||||
conn = dataSourceHolder.getConnection();
|
||||
res = jdbcOperations.executeUpdateByModel(conn,sql, model, params);
|
||||
} finally {
|
||||
try {
|
||||
dataSourceHolder.tryClose(conn);
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public int update(SqlAndParams sqlAndParams) throws SQLException {
|
||||
return update(sqlAndParams.getSql(), sqlAndParams.getParamArray());
|
||||
}
|
||||
@@ -83,7 +104,8 @@ public class JdbcTemplate {
|
||||
}
|
||||
|
||||
public int updateByModel(String sql, Object model) throws SQLException {
|
||||
return update(new SqlAndParams(sql, model));
|
||||
SqlAndParams sqlAndParams = new SqlAndParams(sql, model);
|
||||
return updateByModel(sqlAndParams.getSql(), model, sqlAndParams.getParamArray());
|
||||
}
|
||||
|
||||
public <T> T query(Class<T> clazz, String sql, Object ...params) throws SQLException {
|
||||
|
||||
@@ -36,6 +36,10 @@ import java.util.function.Consumer;
|
||||
* @date: 2022/6/28
|
||||
*/
|
||||
public class DateUtil {
|
||||
public static final long SECOND_LONG = 1000L;
|
||||
public static final long MINUTE_LONG = 60 * SECOND_LONG;
|
||||
public static final long HOUR_LONG = 60 * MINUTE_LONG;
|
||||
|
||||
private static final Cache<String, SimpleDateFormat> sdfCache = new MemoryCache<>();
|
||||
private static SimpleDateFormat getSimpleDateFormat(String format) {
|
||||
try {
|
||||
@@ -141,6 +145,19 @@ public class DateUtil {
|
||||
return cal.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取该日期当月第一天
|
||||
*
|
||||
* @param date 日期
|
||||
* @return 结果日期
|
||||
*/
|
||||
public static Date getMonthBegin(Date date) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(getDayBegin(date));
|
||||
calendar.set(Calendar.DAY_OF_MONTH, 1);
|
||||
return calendar.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取该日期当月最后一天
|
||||
*
|
||||
@@ -149,10 +166,10 @@ public class DateUtil {
|
||||
*/
|
||||
public static Date getMonthEnd(Date date) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(getDayEnd(date));
|
||||
calendar.setTime(getDayBegin(date));
|
||||
calendar.add(Calendar.MONTH, 1);
|
||||
calendar.set(Calendar.DAY_OF_MONTH, 1);
|
||||
calendar.add(Calendar.DAY_OF_MONTH, -1);
|
||||
calendar.add(Calendar.MILLISECOND, -1);
|
||||
return calendar.getTime();
|
||||
}
|
||||
|
||||
@@ -284,6 +301,30 @@ public class DateUtil {
|
||||
return calendar.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定小时开始时间
|
||||
* @param date 日期
|
||||
* @return 指定小时开始时间
|
||||
*/
|
||||
public static Date getHourBegin(Date date) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(date);
|
||||
setCalender(calendar, calendar.get(Calendar.HOUR), 0, 0, 0);
|
||||
return calendar.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定小时结束时间
|
||||
* @param date 日期
|
||||
* @return 指定小时结束时间
|
||||
*/
|
||||
public static Date getHourEnd(Date date) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(date);
|
||||
setCalender(calendar, calendar.get(Calendar.HOUR), 59, 59, 999);
|
||||
return calendar.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当天开始时间
|
||||
*
|
||||
|
||||
@@ -43,18 +43,18 @@ import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpHeaderValues;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -70,6 +70,10 @@ public class HttpRequestHandler {
|
||||
@Autowired
|
||||
private DefaultHttpRouter defaultHttpRouter;
|
||||
private PathMatcher pathMatcher = new AntPathMatcher();
|
||||
/**
|
||||
* 静态自愿缓存
|
||||
*/
|
||||
private Map<String, DataWrapper> staticResourceCache = new ConcurrentReferenceHashMap<>(16, 0.75F, 1, ConcurrentReferenceHashMap.ReferenceType.WEAK);
|
||||
|
||||
public void handle() {
|
||||
ChannelHandlerContext context = HttpContextHolder.getChannelHandlerContext();
|
||||
@@ -115,7 +119,7 @@ public class HttpRequestHandler {
|
||||
|
||||
HttpResponseWrapper httpResponseWrapper = HttpContextHolder.getHttpResponseWrapper();
|
||||
httpResponseWrapper.setContent(Unpooled.wrappedBuffer(res.getBytes()));
|
||||
httpResponseWrapper.headers().add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON);
|
||||
httpResponseWrapper.headers().add(HttpHeaderNames.CONTENT_TYPE, "application/json;charset=UTF-8");
|
||||
httpResponseWrapper.writeAndFlush();
|
||||
return;
|
||||
} else if(HttpRouterType.PAGE == httpRouteResult.getType()) {
|
||||
@@ -133,7 +137,7 @@ public class HttpRequestHandler {
|
||||
postHandle(httpRouteResult.getPageRoute(), null, null);
|
||||
|
||||
HttpResponseWrapper httpResponseWrapper = HttpContextHolder.getHttpResponseWrapper();
|
||||
httpResponseWrapper.setContent(Unpooled.wrappedBuffer(FileUtil.readBytes(httpRouteResult.getPageLocation())));
|
||||
httpResponseWrapper.setContent(Unpooled.wrappedBuffer(getStaticResourceData(httpRouteResult.getPageLocation(), mimeType)));
|
||||
httpResponseWrapper.headers().add(HttpHeaderNames.CONTENT_TYPE, mimeType);
|
||||
httpResponseWrapper.headers().add(HttpHeaderNames.CONTENT_LANGUAGE, "zh-CN");
|
||||
httpResponseWrapper.headers().add(HttpHeaderNames.SERVER, MetaDataConstant.SERVER_VS);
|
||||
@@ -160,6 +164,38 @@ public class HttpRequestHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getStaticResourceData(String pageLocation, String mimeType) {
|
||||
DataWrapper dataWrapper = staticResourceCache.get(pageLocation);
|
||||
if (null != dataWrapper) {
|
||||
if (!StringUtil.isEmpty(dataWrapper.getContentEncoding())) {
|
||||
HttpContextHolder.getHttpResponseWrapper().headers().add("Content-Encoding", dataWrapper.getContentEncoding());
|
||||
}
|
||||
return dataWrapper.getData();
|
||||
}
|
||||
byte[] res = FileUtil.readBytes(pageLocation);
|
||||
if (null == res) {
|
||||
res = new byte[0];
|
||||
staticResourceCache.put(pageLocation, new DataWrapper(res, null));
|
||||
return res;
|
||||
}
|
||||
staticResourceCache.put(pageLocation, new DataWrapper(res, null));
|
||||
if ((mimeType.startsWith("text/") || mimeType.endsWith("javascript")) && res.length > 2048) {
|
||||
// 文本,且大于2kb才做gzip压缩
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try (GZIPOutputStream gZipOs = new GZIPOutputStream(baos)){
|
||||
gZipOs.write(res);
|
||||
gZipOs.flush();
|
||||
gZipOs.finish();
|
||||
res = baos.toByteArray();
|
||||
staticResourceCache.put(pageLocation, new DataWrapper(res, "gzip"));
|
||||
HttpContextHolder.getHttpResponseWrapper().headers().add("Content-Encoding", "gzip");
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private boolean preHandle(String route, Method targetMethod) throws Exception {
|
||||
if (!CollectionUtil.isEmpty(HttpContextHolder.getInterceptorList())) {
|
||||
for (HandlerInterceptor handlerInterceptor : HttpContextHolder.getInterceptorList()) {
|
||||
@@ -290,4 +326,11 @@ public class HttpRequestHandler {
|
||||
private void release() {
|
||||
HttpContextHolder.remove();
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@Data
|
||||
public static class DataWrapper {
|
||||
private byte[] data;
|
||||
private String contentEncoding;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import fun.asgc.neutrino.core.annotation.NonIntercept;
|
||||
import fun.asgc.neutrino.core.bean.BeanWrapper;
|
||||
import fun.asgc.neutrino.core.bean.factory.SimpleBeanFactory;
|
||||
import fun.asgc.neutrino.core.context.ApplicationConfig;
|
||||
import fun.asgc.neutrino.core.context.Environment;
|
||||
import fun.asgc.neutrino.core.util.*;
|
||||
import fun.asgc.neutrino.core.web.annotation.RestController;
|
||||
import fun.asgc.neutrino.core.web.config.WebMvcConfigurer;
|
||||
@@ -59,6 +60,8 @@ public class WebContextHolder {
|
||||
private ApplicationConfig applicationConfig;
|
||||
@Autowired
|
||||
private SimpleBeanFactory applicationBeanFactory;
|
||||
@Autowired
|
||||
private Environment environment;
|
||||
|
||||
@Init
|
||||
public void init() {
|
||||
@@ -68,7 +71,7 @@ public class WebContextHolder {
|
||||
initMaxContentLength();
|
||||
initControllerBeanWrapperList();
|
||||
initInterceptorRegistry();
|
||||
port = http.getPort();
|
||||
port = environment.getMainArgsForInteger("neutrino.http.port", http.getPort());
|
||||
}
|
||||
|
||||
public static String getHttpContextPath() {
|
||||
|
||||
@@ -21,11 +21,13 @@
|
||||
*/
|
||||
package fun.asgc.neutrino.core.aop.compiler;
|
||||
|
||||
import fun.asgc.neutrino.core.aop.compiler.internal.JarLauncher;
|
||||
import fun.asgc.neutrino.core.util.ReflectUtil;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -104,6 +106,50 @@ public class AsgcCompilerTest {
|
||||
method.invoke(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* jar in jar测试
|
||||
*
|
||||
* 使用jdk1.8跑,tools.jar放在classpath下
|
||||
* 测试jar in jar
|
||||
* jar结构:
|
||||
* asgc-package-lab2.jar
|
||||
* - BOOT-INF/
|
||||
* - classes/fun/asgc/lab/pkg/lab2/
|
||||
* - Console.class
|
||||
* - lib/asgc-package-lab1-1.0-SNAPSHOT.jar
|
||||
* - BOOT-INF/classes/fun/asgc/lab/pkg/lab1/
|
||||
* - Dog.class
|
||||
*/
|
||||
@Test
|
||||
public void compileAndLoadClass5() throws Exception {
|
||||
JarLauncher jarLauncher = new JarLauncher("/Users/yangwen/my/tmp/java/asgc-package-lab2.jar");
|
||||
AsgcCompiler compiler = new AsgcCompiler(jarLauncher.createClassLoader());
|
||||
String code = "package a.b;\n" +
|
||||
"import fun.asgc.lab.pkg.lab2.Console;\n" +
|
||||
"import fun.asgc.lab.pkg.lab1.Dog;\n" +
|
||||
"public class Test {\n" +
|
||||
"\tpublic void execute() {\n" +
|
||||
"\t\tSystem.out.println(\"1122\");\n" +
|
||||
"\t}\n" +
|
||||
"\tpublic void echo(String str){\n" +
|
||||
"\t\tConsole.println(str);\n" +
|
||||
"\t}\n" +
|
||||
"\tpublic String dogEat(String name, String food){\n" +
|
||||
"\t\treturn new Dog(name).eat(food);\n" +
|
||||
"\t}\n" +
|
||||
"}\n";
|
||||
System.out.println(code);
|
||||
Class clazz = compiler.compile("a.b","Test", code);
|
||||
Method executeMethod = ReflectUtil.getMethods(clazz).stream().filter(m -> m.getName().equals("execute")).findFirst().get();
|
||||
Method echoMethod = Stream.of(clazz.getDeclaredMethods()).filter(m -> m.getName().equals("echo")).findFirst().get();
|
||||
Method dogEatMethod = Stream.of(clazz.getDeclaredMethods()).filter(m -> m.getName().equals("dogEat")).findFirst().get();
|
||||
|
||||
Object instance = clazz.newInstance();
|
||||
executeMethod.invoke(instance);
|
||||
echoMethod.invoke(instance, "哈哈哈哈");
|
||||
System.out.println(dogEatMethod.invoke(instance, "大黄", "骨头"));
|
||||
}
|
||||
|
||||
private Object eval(String expression) {
|
||||
String code = "package fun.asgc.test;\n" +
|
||||
"import static java.lang.Math.*;\n" +
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
'use strict'
|
||||
const path = require('path')
|
||||
const utils = require('./utils')
|
||||
const webpack = require('webpack')
|
||||
const config = require('../config')
|
||||
const merge = require('webpack-merge')
|
||||
const baseWebpackConfig = require('./webpack.base.conf')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
|
||||
const portfinder = require('portfinder')
|
||||
|
||||
function resolve (dir) {
|
||||
return path.join(__dirname, '..', dir)
|
||||
}
|
||||
|
||||
const HOST = process.env.HOST
|
||||
const PORT = process.env.PORT && Number(process.env.PORT)
|
||||
|
||||
const devWebpackConfig = merge(baseWebpackConfig, {
|
||||
module: {
|
||||
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
|
||||
},
|
||||
// cheap-module-eval-source-map is faster for development
|
||||
devtool: config.dev.devtool,
|
||||
|
||||
// these devServer options should be customized in /config/index.js
|
||||
devServer: {
|
||||
clientLogLevel: 'warning',
|
||||
historyApiFallback: true,
|
||||
hot: true,
|
||||
compress: true,
|
||||
host: HOST || config.dev.host,
|
||||
port: PORT || config.dev.port,
|
||||
open: config.dev.autoOpenBrowser,
|
||||
overlay: config.dev.errorOverlay
|
||||
? { warnings: false, errors: true }
|
||||
: false,
|
||||
publicPath: config.dev.assetsPublicPath,
|
||||
proxy: config.dev.proxyTable,
|
||||
quiet: true, // necessary for FriendlyErrorsPlugin
|
||||
watchOptions: {
|
||||
poll: config.dev.poll,
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': require('../config/docker.env')
|
||||
}),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
// https://github.com/ampedandwired/html-webpack-plugin
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'index.html',
|
||||
template: 'index.html',
|
||||
inject: true,
|
||||
favicon: resolve('favicon.ico'),
|
||||
title: 'vue-element-admin',
|
||||
path: config.dev.assetsPublicPath + config.dev.assetsSubDirectory
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
module.exports = new Promise((resolve, reject) => {
|
||||
portfinder.basePort = process.env.PORT || config.dev.port
|
||||
portfinder.getPort((err, port) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
// publish the new Port, necessary for e2e tests
|
||||
process.env.PORT = port
|
||||
// add port to devServer config
|
||||
devWebpackConfig.devServer.port = port
|
||||
|
||||
// Add FriendlyErrorsPlugin
|
||||
devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
|
||||
compilationSuccessInfo: {
|
||||
messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
|
||||
},
|
||||
onErrors: config.dev.notifyOnErrors
|
||||
? utils.createNotifierCallback()
|
||||
: undefined
|
||||
}))
|
||||
|
||||
resolve(devWebpackConfig)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,88 @@
|
||||
'use strict'
|
||||
const path = require('path')
|
||||
const utils = require('./utils')
|
||||
const webpack = require('webpack')
|
||||
const config = require('../config')
|
||||
const merge = require('webpack-merge')
|
||||
const baseWebpackConfig = require('./webpack.base.conf')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
|
||||
const portfinder = require('portfinder')
|
||||
|
||||
function resolve (dir) {
|
||||
return path.join(__dirname, '..', dir)
|
||||
}
|
||||
|
||||
const HOST = process.env.HOST
|
||||
const PORT = process.env.PORT && Number(process.env.PORT)
|
||||
|
||||
const devWebpackConfig = merge(baseWebpackConfig, {
|
||||
module: {
|
||||
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
|
||||
},
|
||||
// cheap-module-eval-source-map is faster for development
|
||||
devtool: config.dev.devtool,
|
||||
|
||||
// these devServer options should be customized in /config/index.js
|
||||
devServer: {
|
||||
clientLogLevel: 'warning',
|
||||
historyApiFallback: true,
|
||||
hot: true,
|
||||
compress: true,
|
||||
host: HOST || config.dev.host,
|
||||
port: PORT || config.dev.port,
|
||||
open: config.dev.autoOpenBrowser,
|
||||
overlay: config.dev.errorOverlay
|
||||
? { warnings: false, errors: true }
|
||||
: false,
|
||||
publicPath: config.dev.assetsPublicPath,
|
||||
proxy: config.dev.proxyTable,
|
||||
quiet: true, // necessary for FriendlyErrorsPlugin
|
||||
watchOptions: {
|
||||
poll: config.dev.poll,
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': require('../config/local.env')
|
||||
}),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
// https://github.com/ampedandwired/html-webpack-plugin
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'index.html',
|
||||
template: 'index.html',
|
||||
inject: true,
|
||||
favicon: resolve('favicon.ico'),
|
||||
title: 'vue-element-admin',
|
||||
path: config.dev.assetsPublicPath + config.dev.assetsSubDirectory
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
module.exports = new Promise((resolve, reject) => {
|
||||
portfinder.basePort = process.env.PORT || config.dev.port
|
||||
portfinder.getPort((err, port) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
// publish the new Port, necessary for e2e tests
|
||||
process.env.PORT = port
|
||||
// add port to devServer config
|
||||
devWebpackConfig.devServer.port = port
|
||||
|
||||
// Add FriendlyErrorsPlugin
|
||||
devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
|
||||
compilationSuccessInfo: {
|
||||
messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
|
||||
},
|
||||
onErrors: config.dev.notifyOnErrors
|
||||
? utils.createNotifierCallback()
|
||||
: undefined
|
||||
}))
|
||||
|
||||
resolve(devWebpackConfig)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
NODE_ENV: '"development"',
|
||||
ENV_CONFIG: '"dev"',
|
||||
BASE_API: '"http://localhost:8080"'
|
||||
BASE_API: '"http://103.163.47.16:8888"'
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
NODE_ENV: '"development"',
|
||||
ENV_CONFIG: '"docker"',
|
||||
BASE_API: '"/"'
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
NODE_ENV: '"development"',
|
||||
ENV_CONFIG: '"local"',
|
||||
BASE_API: '"http://localhost:8888"'
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 12 KiB |
@@ -6,8 +6,12 @@
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js --host=0.0.0.0",
|
||||
"local": "webpack-dev-server --inline --progress --config build/webpack.local.conf.js",
|
||||
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
|
||||
"docker": "webpack-dev-server --inline --progress --config build/webpack.docker.conf.js",
|
||||
"build:local": "cross-env NODE_ENV=dev env_config=local node build/build.js",
|
||||
"build:dev": "cross-env NODE_ENV=dev env_config=dev node build/build.js",
|
||||
"build:docker": "cross-env NODE_ENV=dev env_config=docker node build/build.js",
|
||||
"build:prod": "cross-env NODE_ENV=production env_config=prod node build/build.js",
|
||||
"build:sit": "cross-env NODE_ENV=production env_config=sit node build/build.js",
|
||||
"lint": "eslint --ext .js,.vue src",
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function fetchList(query) {
|
||||
return request({
|
||||
url: '/article/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchArticle() {
|
||||
return request({
|
||||
url: '/article/detail',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchPv(pv) {
|
||||
return request({
|
||||
url: '/article/pv',
|
||||
method: 'get',
|
||||
params: { pv }
|
||||
})
|
||||
}
|
||||
|
||||
export function createArticle(data) {
|
||||
return request({
|
||||
url: '/article/create',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateArticle(data) {
|
||||
return request({
|
||||
url: '/article/update',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function fetchList(query) {
|
||||
return request({
|
||||
url: '/client-connect-record/page',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
@@ -51,3 +51,13 @@ export function deleteLicense(id) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function resetLicense(id) {
|
||||
return request({
|
||||
url: '/license/reset',
|
||||
method: 'post',
|
||||
params: {
|
||||
id: id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function fetchList(query) {
|
||||
return request({
|
||||
url: '/user-login-record/page',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getToken() {
|
||||
return request({
|
||||
url: '/qiniu/upload/token', // 假地址 自行替换
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function userSearch(name) {
|
||||
return request({
|
||||
url: '/search/user',
|
||||
method: 'get',
|
||||
params: { name }
|
||||
})
|
||||
}
|
||||
@@ -73,3 +73,27 @@ export function hello() {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员修改成员密码
|
||||
* @param params
|
||||
*/
|
||||
export function updatePassword(params) {
|
||||
return request({
|
||||
url: '/user/update/password',
|
||||
method: 'post',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改自己密码
|
||||
* @param params
|
||||
*/
|
||||
export function updateUserPassword(params) {
|
||||
return request({
|
||||
url: '/user/current-user/update/password',
|
||||
method: 'post',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="_图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 240">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #ff9045;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
<!-- fill: #333;-->
|
||||
fill: #ffffff;
|
||||
}
|
||||
|
||||
.cls-3 {
|
||||
fill: #0079dd;
|
||||
}
|
||||
|
||||
.cls-4 {
|
||||
<!-- fill: #595959;-->
|
||||
fill: #ffffff;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g>
|
||||
<g>
|
||||
<circle class="cls-1" cx="145.28" cy="62.13" r="13.63"/>
|
||||
<path class="cls-1" d="M154.29,83.78h-18.09c-4.56,0-8.26,3.7-8.26,8.26v92.19c0,4.02,3.26,7.28,7.28,7.28h.07c4.02,0,7.28-3.26,7.28-7.28v-54.05c2.96,0,5.36,2.4,5.36,5.36v16.41c0,4.02,3.26,7.28,7.28,7.28h.07c4.02,0,7.28-3.26,7.28-7.28v-21.16s0-38.75,0-38.75c0-4.56-3.7-8.26-8.26-8.26Z"/>
|
||||
</g>
|
||||
<path class="cls-3" d="M117.19,125.56h-1.98c-3.07,0-5.56-2.49-5.56-5.56s2.49-5.56,5.56-5.56h1.98s.04,0,.06,0v-25.71c0-4.77-2.32-9.24-6.21-11.98L50.81,34.3c-4.86-3.42-11.55,.05-11.55,5.99V199.71c0,5.94,6.7,9.41,11.55,5.99l60.23-42.45c3.9-2.75,6.21-7.22,6.21-11.98v-25.71s-.04,0-.06,0Z"/>
|
||||
<path class="cls-3" d="M239.76,34.3l-60.23,42.45c-3.9,2.75-6.21,7.22-6.21,11.98v25.71s.04,0,.06,0h1.98c3.07,0,5.56,2.49,5.56,5.56s-2.49,5.56-5.56,5.56h-1.98s-.04,0-.06,0v25.71c0,4.77,2.32,9.24,6.21,11.98l60.23,42.45c4.86,3.42,11.55-.05,11.55-5.99V40.29c0-5.94-6.7-9.41-11.55-5.99Z"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="cls-2" d="M344.63,60.54v-7.61h8.13v7.61h20.8c4.41,0,7.8,.88,10.16,2.64,3.06,2.31,4.58,5.45,4.58,9.43l-.04,32.47c0,3.86-.78,6.82-2.34,8.87-2.42,3.2-6.01,4.8-10.77,4.8h-22.4l.04,7.52-8.17-.04v-7.48h-20.54c-4.15,0-7.24-.59-9.25-1.77-3.66-2.13-5.49-6.18-5.49-12.15v-30.57c0-4.44,1.12-7.77,3.37-9.99,2.51-2.48,5.89-3.72,10.16-3.72h21.75Zm0,50.07v-41.94h-22.01c-2.05,0-3.47,.55-4.28,1.64-.58,.81-.87,2.08-.87,3.8v30.05c0,1.96,.07,3.13,.22,3.5,.72,1.99,2.31,2.98,4.76,2.98l22.18-.04Zm8.13,0l20.93,.04c4.32,.03,6.49-1.79,6.49-5.45v-30.48c0-2.48-.49-4.11-1.47-4.89-.92-.72-2.48-1.1-4.67-1.12l-21.27-.09v41.99Z"/>
|
||||
<path class="cls-2" d="M401.72,75.41v-8.78c2.13-1.73,3.62-3.34,4.45-4.84,1.12-1.93,2-4.94,2.64-9.04h8.13c-.46,2.19-.79,3.84-.99,4.93-.81,4.12-2.12,7.46-3.93,10.03-1.87,2.62-4.08,4.64-6.62,6.05-.81,.43-2.03,.98-3.67,1.64Zm0,20.67v-8.86c2.36-1.9,3.91-3.56,4.63-4.97,.89-1.67,1.7-4.63,2.42-8.86h8.17l-.91,4.15v48.86h-8.09v-33.77c-1.93,1.44-4.01,2.59-6.23,3.46Zm17.25,30.31v-8.47c1.35-.78,2.03-1.9,2.03-3.37v-10.59c0-2.6,.88-4.79,2.64-6.57,1.59-1.58,4.53-2.38,8.82-2.38h6.87c6.72,0,10.08,3.19,10.08,9.56v8.86c0,1.73,.23,2.87,.69,3.42,.26,.32,.72,.68,1.38,1.08v8.47c-2.88-.43-5.16-1.3-6.83-2.59-.87-.66-1.59-1.56-2.16-2.68-.81-1.53-1.21-3.07-1.21-4.63v-9.95c0-2.39-1.47-3.59-4.41-3.59h-3.5c-1.47,0-2.51,.32-3.11,.95-.81,.81-1.21,2.05-1.21,3.72v8.87c0,2.79-.88,5.07-2.64,6.83-1.39,1.41-3.86,2.44-7.44,3.07Zm.91-73.68h7.22v15.52c0,1.21,.23,2.02,.69,2.42,.52,.43,1.41,.65,2.68,.65h1.08v-18.59h7.74v18.59h1.12c1.07,0,1.84-.29,2.34-.86s.74-1.17,.74-1.77v-15.96h7.05v17.99c0,3.08-1.11,5.35-3.33,6.79-1.21,.78-3.37,1.17-6.49,1.17h-11.68c-3.2,0-5.59-.81-7.18-2.42-.69-.72-1.14-1.37-1.34-1.95-.43-1.21-.65-3.01-.65-5.4v-16.17Zm0,38.18v-7.22h30.66v7.22h-30.66Zm33.12,35.11v-7.74c2.36-.92,4.08-1.72,5.15-2.38,1.1-.69,2.62-1.92,4.58-3.67-2.02-3.83-3.39-7.16-4.11-9.99-.89-3.34-1.34-6.76-1.34-10.25v-14.87c-1.12,.75-2.45,1.37-3.98,1.86v-8.39c1.38-1.53,2.35-3.56,2.9-6.1,.63-3.08,1.08-7,1.34-11.76h7.26v4.89h17.08v8h-4.2v27.46c0,6.05-1.83,12.44-5.49,19.16,1.44,1.38,2.61,2.41,3.5,3.07,1.27,.89,3.33,1.89,6.18,2.98v7.74c-3.98-1.1-6.99-2.28-9.04-3.55-1.5-.89-3.27-2.29-5.32-4.19-3.08,2.54-5.49,4.24-7.22,5.1-1.53,.81-3.96,1.69-7.31,2.64Zm4.67-49.12h6.66l-.13,12.8c-.03,4.56,1.05,9.7,3.24,15.44,2.02-4.44,3.03-8.62,3.03-12.54v-26.98h-6.74c-1.15,4.24-2.38,7.18-3.68,8.82-.52,.69-1.31,1.51-2.38,2.47Z"/>
|
||||
<path class="cls-2" d="M494.58,91.89v-8h41.21l21.32-17.81c1.21-1.01,1.82-1.9,1.82-2.68,0-.46-.16-.89-.47-1.3-.66-.83-1.75-1.25-3.24-1.25h-56.56v-8h58.98c3.6,0,6.36,.68,8.26,2.03,2.36,1.67,3.55,3.99,3.55,6.96,0,2.56-1.04,4.97-3.11,7.22-1.21,1.3-2.8,2.75-4.76,4.37l-12.93,10.46h26.12v8h-29.45l.04,21.53c0,3.92-1.21,7.1-3.63,9.56-2.25,2.31-5.61,3.46-10.07,3.46h-16.73v-8.04h15.52c2.45,.03,4.12-.47,5.01-1.51,.81-.95,1.21-2.39,1.21-4.33v-20.71l-42.07,.04Z"/>
|
||||
<path class="cls-2" d="M587.86,86.78v-10.29c3-2.13,5.17-4.28,6.53-6.44,2.88-4.58,4.96-10.29,6.23-17.12h9.08c-1.24,6.26-2.96,11.93-5.15,17.04v56.38h-8.3v-44.88c-2.02,1.62-3.34,2.61-3.98,2.98-.61,.4-2.08,1.18-4.41,2.34Zm21.02-8.78v-8.13h19.46l-5.06-17.08h8.69l4.8,17.08h30.61v8.13h-28.32l5.1,18.81c1.01,3.72,2.31,7.28,3.89,10.68,1.47,3.14,3.66,5.69,6.57,7.65,3.29,2.22,6.69,3.33,10.2,3.33,.46,0,1.17-.04,2.12-.13v8.09c-4.7-.09-8.53-.68-11.5-1.77-5.51-2.07-9.71-5.65-12.63-10.72-1.87-3.23-3.72-7.91-5.53-14.05l-6.53-21.88h-21.88Zm40.99-16.78v-8h17.51v8h-17.51Z"/>
|
||||
<path class="cls-2" d="M680.84,124.06v-8h7.05v-24.95l-7.09,.04,.04-7.87h7.05v-20.41h-7.05v-8h22.4v8h-7.35v20.41h7.35v7.83h-7.35v24.95h7.35v8h-22.4Zm26.59,2.2v-8h22.92v-9.25h-20.76v-8h20.76v-7.05l-14.01-.04c-2.48,0-4.44-.88-5.88-2.64-.89-1.12-1.41-2.31-1.56-3.55-.09-.72-.13-1.7-.13-2.94v-23.44c0-2.91,.74-5.1,2.2-6.57,1.38-1.38,3.24-2.08,5.58-2.08h33.25c3.52,0,6.04,.72,7.57,2.16,1.33,1.27,1.99,3.17,1.99,5.71v25.47c0,2.42-.71,4.35-2.12,5.79-1.35,1.38-3.06,2.07-5.1,2.07l-13.79,.04v7.05h20.8v8h-20.8v9.25h22.4v8h-53.31Zm9.34-56.99h13.58v-8.56h-10.25c-1.21,0-2.11,.29-2.68,.86-.43,.43-.65,1.25-.65,2.47v5.23Zm13.58,16.56v-8.6h-13.58v4.71c0,1.56,.23,2.6,.69,3.11,.43,.52,1.24,.78,2.42,.78h10.46Zm8-16.56h13.19v-5.23c.03-2.28-1.41-3.42-4.32-3.42l-8.86,.09v8.56Zm0,16.56h9.64c1.41,0,2.35-.26,2.81-.78,.52-.58,.76-1.54,.74-2.9v-4.93h-13.19v8.6Z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="cls-4" d="M315.77,154.13l16.1,23.16h.12v-23.16h3.27v28.46h-3.35l-16.18-23.4h-.12v23.4h-3.27v-28.46h3.43Z"/>
|
||||
<path class="cls-4" d="M370.12,164.77c1.51,1.99,2.31,4.82,2.39,8.41h-16.26c.16,2.31,.76,4.11,1.87,5.38,1.12,1.28,2.67,1.91,4.63,1.91,1.67,0,3.07-.44,4.1-1.28,.88-.72,1.56-1.79,2.07-3.23h3.19c-.48,1.99-1.39,3.59-2.79,4.86-1.71,1.52-3.91,2.31-6.58,2.31-2.95,0-5.34-1-7.1-2.91-1.83-1.99-2.71-4.62-2.71-7.97,0-3.03,.84-5.58,2.59-7.61,1.75-2.15,4.11-3.23,7.1-3.23,3.31,0,5.82,1.12,7.49,3.35Zm-11.84,1.12c-1.12,1.2-1.76,2.79-1.95,4.82h12.88c-.4-4.42-2.59-6.62-6.5-6.62-1.83,0-3.31,.6-4.42,1.79Z"/>
|
||||
<path class="cls-4" d="M392.99,161.98v12.6c0,1.95,.4,3.43,1.24,4.38,.84,.96,2.19,1.48,4.07,1.48,1.35,0,2.59-.56,3.67-1.59,1.12-1.12,1.79-2.55,2.03-4.3v-12.56h3.19v20.61h-3.19v-3.19c-1.68,2.47-3.91,3.75-6.7,3.75-5.02,0-7.5-2.83-7.5-8.41v-12.76h3.19Z"/>
|
||||
<path class="cls-4" d="M431.17,161.98h5.1v2.67h-5.1v13.11c0,.76,.12,1.31,.44,1.63,.28,.32,.8,.52,1.51,.52h2.63v2.67h-3.11c-1.67,0-2.91-.44-3.63-1.31-.68-.8-1-1.95-1-3.51v-13.11h-4.15v-2.67h4.15v-5.34l3.15-1.31v6.66Z"/>
|
||||
<path class="cls-4" d="M465.24,161.9v3.19c-1.04-.32-2.07-.44-3.07-.44-1.59,0-2.95,.68-4.03,2.11-1.12,1.35-1.64,2.99-1.64,4.9v10.92h-3.15v-20.61h3.15v3.59c.56-1.19,1.28-2.15,2.15-2.83,1.08-.88,2.31-1.32,3.75-1.32,1.12,0,2.07,.16,2.83,.48Z"/>
|
||||
<path class="cls-4" d="M485.8,154.88c.48,.4,.72,.96,.72,1.68,0,.68-.24,1.24-.72,1.71-.48,.44-1.04,.68-1.71,.68s-1.24-.24-1.71-.68c-.48-.48-.68-1.04-.68-1.71,0-.72,.2-1.28,.68-1.68,.48-.48,1.04-.68,1.71-.68s1.24,.2,1.71,.68Zm-.12,7.1v20.61h-3.19v-20.61h3.19Z"/>
|
||||
<path class="cls-4" d="M522.54,169.91v12.67h-3.19v-12.44c0-4.03-1.83-6.02-5.42-6.02-1.43,0-2.67,.52-3.71,1.56-1.12,1.12-1.71,2.59-1.83,4.46v12.44h-3.19v-20.61h3.19v3.03c.72-1.16,1.64-2.03,2.75-2.63,1.12-.64,2.35-.96,3.71-.96,5.1,0,7.69,2.83,7.69,8.49Z"/>
|
||||
<path class="cls-4" d="M557.61,164.61c1.75,2.03,2.63,4.58,2.63,7.69s-.88,5.62-2.59,7.61c-1.87,2.15-4.35,3.23-7.41,3.23s-5.54-1.08-7.37-3.23c-1.75-1.99-2.63-4.54-2.63-7.61s.88-5.66,2.63-7.69c1.83-2.15,4.27-3.19,7.37-3.19s5.54,1.04,7.38,3.19Zm-12.48,1.99c-1.12,1.51-1.63,3.39-1.63,5.7s.52,4.15,1.63,5.66c1.2,1.68,2.91,2.51,5.1,2.51s3.87-.84,5.14-2.51c1.07-1.51,1.63-3.43,1.63-5.66s-.56-4.19-1.63-5.7c-1.28-1.68-2.99-2.51-5.14-2.51s-3.91,.84-5.1,2.51Z"/>
|
||||
<path class="cls-4" d="M616.79,154.13c6.54,0,9.8,2.79,9.8,8.37s-3.31,8.45-9.84,8.45h-8.33v11.64h-3.27v-28.46h11.64Zm-8.37,13.99h8.17c2.31,0,3.99-.48,5.1-1.39,1.07-.92,1.63-2.31,1.63-4.23s-.56-3.31-1.67-4.15c-1.12-.96-2.79-1.4-5.06-1.4h-8.17v11.16Z"/>
|
||||
<path class="cls-4" d="M655.76,161.9v3.19c-1.04-.32-2.07-.44-3.07-.44-1.59,0-2.95,.68-4.03,2.11-1.11,1.35-1.63,2.99-1.63,4.9v10.92h-3.15v-20.61h3.15v3.59c.56-1.19,1.28-2.15,2.15-2.83,1.07-.88,2.31-1.32,3.75-1.32,1.12,0,2.07,.16,2.83,.48Z"/>
|
||||
<path class="cls-4" d="M688.55,164.61c1.76,2.03,2.63,4.58,2.63,7.69s-.88,5.62-2.59,7.61c-1.87,2.15-4.34,3.23-7.41,3.23s-5.54-1.08-7.37-3.23c-1.75-1.99-2.63-4.54-2.63-7.61s.88-5.66,2.63-7.69c1.83-2.15,4.27-3.19,7.37-3.19s5.54,1.04,7.37,3.19Zm-12.48,1.99c-1.12,1.51-1.63,3.39-1.63,5.7s.52,4.15,1.63,5.66c1.2,1.68,2.91,2.51,5.1,2.51s3.87-.84,5.14-2.51c1.08-1.51,1.64-3.43,1.64-5.66s-.56-4.19-1.64-5.7c-1.28-1.68-2.99-2.51-5.14-2.51s-3.91,.84-5.1,2.51Z"/>
|
||||
<path class="cls-4" d="M711.23,161.98l5.34,7.33,5.3-7.33h3.75l-7.29,9.69,8.17,10.92h-3.79l-6.14-8.57-6.18,8.57h-3.79l8.17-10.92-7.29-9.69h3.75Z"/>
|
||||
<path class="cls-4" d="M743.9,161.98l6.3,16.26,6.1-16.26h3.51l-11.28,28.5h-3.47l3.55-8.37-8.17-20.13h3.47Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.1 KiB |
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="_图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 480">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #ff9045;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #333;
|
||||
}
|
||||
|
||||
.cls-3 {
|
||||
fill: #0079dd;
|
||||
}
|
||||
|
||||
.cls-4 {
|
||||
fill: #595959;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g>
|
||||
<circle class="cls-1" cx="243.67" cy="69.33" r="17.83"/>
|
||||
<path class="cls-1" d="M255.44,97.63h-23.66c-5.96,0-10.79,4.83-10.79,10.79v120.54c0,5.25,4.26,9.51,9.51,9.51h.09c5.25,0,9.51-4.26,9.51-9.51v-70.67c3.87,0,7.01,3.14,7.01,7.01v21.45c0,5.25,4.26,9.51,9.51,9.51h.09c5.25,0,9.51-4.26,9.51-9.51v-27.67s0-50.66,0-50.66c0-5.96-4.83-10.79-10.79-10.79Z"/>
|
||||
</g>
|
||||
<path class="cls-3" d="M206.94,152.26h-2.59c-4.01,0-7.27-3.25-7.27-7.27s3.25-7.27,7.27-7.27h2.59s.05,0,.08,0v-33.61c0-6.23-3.03-12.08-8.12-15.67L120.15,32.94c-6.35-4.47-15.1,.07-15.1,7.83V249.2c0,7.77,8.76,12.31,15.1,7.83l78.75-55.5c5.09-3.59,8.12-9.43,8.12-15.67v-33.61s-.05,0-.08,0Z"/>
|
||||
<path class="cls-3" d="M367.19,32.94l-78.75,55.5c-5.09,3.59-8.12,9.43-8.12,15.67v33.61s.05,0,.08,0h2.59c4.01,0,7.27,3.25,7.27,7.27s-3.25,7.27-7.27,7.27h-2.59s-.05,0-.08,0v33.61c0,6.23,3.03,12.08,8.12,15.67l78.75,55.5c6.35,4.47,15.1-.07,15.1-7.83V40.77c0-7.77-8.75-12.31-15.1-7.83Z"/>
|
||||
<g>
|
||||
<path class="cls-2" d="M52.96,324.39v-7.48h7.98v7.48h20.43c4.33,0,7.66,.86,9.98,2.59,3,2.26,4.5,5.35,4.5,9.26l-.04,31.9c0,3.79-.76,6.7-2.29,8.71-2.38,3.14-5.9,4.71-10.58,4.71h-22l.05,7.39-8.03-.04v-7.35h-20.18c-4.08,0-7.11-.58-9.09-1.74-3.6-2.1-5.39-6.07-5.39-11.93v-30.03c0-4.36,1.1-7.63,3.31-9.81,2.47-2.44,5.79-3.65,9.98-3.65h21.36Zm0,49.18v-41.2H31.35c-2.01,0-3.41,.54-4.21,1.61-.56,.79-.85,2.04-.85,3.74v29.52c0,1.93,.07,3.07,.21,3.44,.71,1.95,2.26,2.93,4.67,2.93l21.79-.04Zm7.98,0l20.56,.04c4.25,.03,6.37-1.76,6.37-5.35v-29.94c0-2.44-.48-4.03-1.44-4.8-.91-.71-2.44-1.07-4.59-1.1l-20.9-.09v41.24Z"/>
|
||||
<path class="cls-2" d="M109.04,339v-8.62c2.09-1.7,3.55-3.28,4.37-4.76,1.1-1.9,1.97-4.86,2.59-8.88h7.98c-.45,2.15-.78,3.77-.98,4.84-.79,4.05-2.08,7.33-3.86,9.85-1.84,2.58-4.01,4.56-6.5,5.95-.79,.42-2,.96-3.61,1.61Zm0,20.3v-8.71c2.32-1.87,3.83-3.5,4.54-4.88,.88-1.64,1.67-4.55,2.38-8.71h8.02l-.89,4.08v47.99h-7.94v-33.17c-1.9,1.42-3.94,2.55-6.11,3.4Zm16.95,29.77v-8.32c1.33-.76,2-1.87,2-3.31v-10.41c0-2.55,.86-4.7,2.59-6.46,1.56-1.56,4.45-2.34,8.66-2.34h6.75c6.6,0,9.9,3.13,9.9,9.39v8.71c0,1.7,.23,2.82,.68,3.35,.26,.31,.71,.67,1.36,1.06v8.32c-2.83-.42-5.07-1.27-6.71-2.55-.85-.65-1.56-1.53-2.12-2.63-.79-1.5-1.19-3.02-1.19-4.55v-9.77c0-2.35-1.44-3.53-4.33-3.53h-3.44c-1.45,0-2.47,.31-3.06,.94-.79,.79-1.19,2.01-1.19,3.65v8.71c0,2.75-.86,4.98-2.59,6.71-1.36,1.39-3.79,2.39-7.3,3.02Zm.89-72.37h7.09v15.25c0,1.19,.23,1.98,.68,2.38,.51,.42,1.39,.64,2.63,.64h1.06v-18.26h7.6v18.26h1.1c1.05,0,1.81-.28,2.3-.85,.48-.57,.72-1.15,.72-1.74v-15.67h6.92v17.67c0,3.03-1.09,5.25-3.27,6.67-1.19,.76-3.31,1.15-6.37,1.15h-11.47c-3.14,0-5.49-.79-7.05-2.38-.68-.71-1.12-1.34-1.32-1.91-.42-1.19-.64-2.96-.64-5.31v-15.88Zm0,37.5v-7.09h30.11v7.09h-30.11Zm32.53,34.49v-7.6c2.32-.91,4.01-1.68,5.06-2.34,1.07-.68,2.57-1.88,4.5-3.61-1.98-3.77-3.33-7.04-4.03-9.81-.88-3.28-1.32-6.64-1.32-10.07v-14.61c-1.1,.74-2.41,1.35-3.91,1.83v-8.24c1.36-1.5,2.31-3.5,2.85-5.99,.62-3.03,1.06-6.88,1.32-11.55h7.14v4.8h16.77v7.86h-4.12v26.97c0,5.95-1.8,12.22-5.39,18.81,1.42,1.36,2.56,2.36,3.44,3.02,1.24,.88,3.27,1.85,6.07,2.93v7.6c-3.9-1.07-6.86-2.24-8.87-3.48-1.47-.88-3.21-2.25-5.22-4.12-3.03,2.49-5.39,4.16-7.09,5.01-1.5,.79-3.89,1.66-7.18,2.59Zm4.59-48.25h6.54l-.13,12.57c-.03,4.47,1.03,9.53,3.18,15.16,1.98-4.36,2.97-8.47,2.97-12.32v-26.5h-6.63c-1.13,4.16-2.33,7.05-3.61,8.66-.51,.68-1.29,1.49-2.34,2.42Z"/>
|
||||
<path class="cls-2" d="M200.25,355.18v-7.86h40.47l20.94-17.5c1.19-.99,1.78-1.87,1.78-2.63,0-.45-.15-.88-.47-1.27-.65-.82-1.71-1.23-3.19-1.23h-55.55v-7.86h57.93c3.54,0,6.24,.67,8.11,2,2.32,1.64,3.48,3.92,3.48,6.84,0,2.52-1.02,4.88-3.06,7.09-1.19,1.27-2.75,2.7-4.68,4.29l-12.7,10.28h25.65v7.86h-28.92l.04,21.15c0,3.85-1.19,6.98-3.57,9.38-2.21,2.27-5.51,3.4-9.9,3.4h-16.44v-7.9h15.25c2.41,.03,4.05-.47,4.93-1.49,.79-.93,1.19-2.35,1.19-4.25v-20.34l-41.32,.04Z"/>
|
||||
<path class="cls-2" d="M291.87,350.17v-10.11c2.95-2.1,5.09-4.21,6.42-6.33,2.83-4.5,4.87-10.11,6.11-16.82h8.92c-1.22,6.14-2.9,11.72-5.06,16.73v55.38h-8.15v-44.08c-1.98,1.59-3.28,2.56-3.91,2.93-.59,.4-2.04,1.16-4.33,2.29Zm20.64-8.62v-7.98h19.11l-4.97-16.78h8.54l4.71,16.78h30.07v7.98h-27.82l5.01,18.47c.99,3.65,2.27,7.15,3.82,10.49,1.45,3.09,3.6,5.59,6.46,7.52,3.23,2.18,6.57,3.27,10.02,3.27,.45,0,1.15-.04,2.08-.13v7.94c-4.62-.08-8.38-.67-11.3-1.74-5.41-2.04-9.54-5.55-12.4-10.53-1.84-3.17-3.65-7.77-5.43-13.8l-6.42-21.49h-21.49Zm40.26-16.48v-7.86h17.2v7.86h-17.2Z"/>
|
||||
<path class="cls-2" d="M383.2,386.78v-7.86h6.92v-24.5l-6.96,.04,.04-7.73h6.92v-20.05h-6.92v-7.86h22v7.86h-7.22v20.05h7.22v7.69h-7.22v24.5h7.22v7.86h-22Zm26.12,2.17v-7.86h22.51v-9.09h-20.39v-7.86h20.39v-6.92l-13.76-.04c-2.44,0-4.36-.86-5.77-2.59-.88-1.1-1.39-2.26-1.53-3.48-.09-.71-.13-1.67-.13-2.89v-23.02c0-2.86,.72-5.01,2.16-6.46,1.36-1.36,3.19-2.04,5.48-2.04h32.66c3.45,0,5.93,.71,7.43,2.12,1.3,1.25,1.95,3.12,1.95,5.61v25.01c0,2.38-.69,4.28-2.08,5.69-1.33,1.36-3,2.04-5.01,2.04l-13.55,.04v6.92h20.43v7.86h-20.43v9.09h22v7.86h-52.37Zm9.17-55.98h13.34v-8.41h-10.07c-1.19,0-2.07,.28-2.63,.85-.42,.42-.64,1.23-.64,2.42v5.14Zm13.34,16.27v-8.45h-13.34v4.63c0,1.53,.23,2.55,.68,3.06,.42,.51,1.22,.76,2.38,.76h10.28Zm7.86-16.27h12.96v-5.14c.03-2.24-1.39-3.36-4.25-3.36l-8.71,.09v8.41Zm0,16.27h9.47c1.39,0,2.31-.25,2.76-.76,.51-.57,.75-1.51,.72-2.85v-4.84h-12.96v8.45Z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="cls-4" d="M26.71,413.56l15.62,22.47h.12v-22.47h3.17v27.61h-3.25l-15.7-22.7h-.12v22.7h-3.17v-27.61h3.33Z"/>
|
||||
<path class="cls-4" d="M79.47,423.88c1.47,1.93,2.24,4.68,2.32,8.16h-15.78c.15,2.24,.73,3.98,1.82,5.22,1.08,1.24,2.59,1.86,4.48,1.86,1.62,0,2.98-.42,3.98-1.24,.85-.7,1.51-1.74,2.01-3.13h3.09c-.46,1.93-1.35,3.48-2.71,4.72-1.66,1.47-3.79,2.24-6.38,2.24-2.86,0-5.18-.97-6.88-2.82-1.78-1.93-2.63-4.49-2.63-7.73,0-2.94,.81-5.41,2.51-7.39,1.7-2.09,3.98-3.13,6.88-3.13,3.21,0,5.65,1.08,7.27,3.25Zm-11.49,1.08c-1.08,1.16-1.7,2.71-1.89,4.68h12.49c-.39-4.29-2.51-6.42-6.3-6.42-1.78,0-3.21,.58-4.29,1.74Z"/>
|
||||
<path class="cls-4" d="M101.67,421.18v12.22c0,1.89,.39,3.32,1.2,4.25s2.13,1.43,3.95,1.43c1.32,0,2.51-.54,3.56-1.55,1.08-1.08,1.74-2.47,1.97-4.18v-12.18h3.09v19.99h-3.09v-3.1c-1.62,2.4-3.79,3.64-6.49,3.64-4.87,0-7.27-2.75-7.27-8.16v-12.37h3.09Z"/>
|
||||
<path class="cls-4" d="M138.73,421.18h4.95v2.59h-4.95v12.72c0,.73,.11,1.28,.42,1.58,.27,.31,.77,.5,1.47,.5h2.55v2.59h-3.02c-1.62,0-2.82-.43-3.52-1.28-.66-.77-.97-1.9-.97-3.4v-12.72h-4.02v-2.59h4.02v-5.18l3.06-1.28v6.46Z"/>
|
||||
<path class="cls-4" d="M171.79,421.1v3.09c-1.01-.31-2.01-.42-2.98-.42-1.55,0-2.86,.66-3.91,2.05-1.08,1.32-1.58,2.9-1.58,4.76v10.6h-3.06v-19.99h3.06v3.48c.54-1.16,1.24-2.09,2.09-2.75,1.04-.85,2.24-1.28,3.63-1.28,1.08,0,2.01,.15,2.75,.46Z"/>
|
||||
<path class="cls-4" d="M191.76,414.29c.46,.39,.7,.93,.7,1.62,0,.66-.23,1.2-.7,1.66-.47,.42-1.01,.66-1.66,.66s-1.2-.23-1.66-.66c-.47-.46-.66-1.01-.66-1.66,0-.7,.19-1.24,.66-1.62,.46-.46,1-.66,1.66-.66s1.2,.19,1.66,.66Zm-.12,6.88v19.99h-3.09v-19.99h3.09Z"/>
|
||||
<path class="cls-4" d="M227.41,428.87v12.3h-3.09v-12.07c0-3.9-1.78-5.84-5.26-5.84-1.39,0-2.59,.5-3.6,1.51-1.08,1.08-1.66,2.51-1.78,4.33v12.07h-3.09v-19.99h3.09v2.94c.7-1.12,1.59-1.97,2.67-2.55,1.08-.62,2.28-.93,3.6-.93,4.95,0,7.46,2.75,7.46,8.24Z"/>
|
||||
<path class="cls-4" d="M261.45,423.73c1.7,1.97,2.55,4.45,2.55,7.46s-.85,5.45-2.51,7.39c-1.82,2.09-4.21,3.13-7.19,3.13s-5.37-1.04-7.15-3.13c-1.7-1.93-2.55-4.41-2.55-7.39s.85-5.49,2.55-7.46c1.78-2.09,4.14-3.09,7.15-3.09s5.38,1.01,7.16,3.09Zm-12.11,1.93c-1.08,1.47-1.58,3.29-1.58,5.53s.5,4.02,1.58,5.49c1.16,1.62,2.83,2.44,4.95,2.44s3.75-.81,4.99-2.44c1.04-1.47,1.59-3.33,1.59-5.49s-.54-4.06-1.59-5.53c-1.24-1.62-2.9-2.44-4.99-2.44s-3.79,.81-4.95,2.44Z"/>
|
||||
<path class="cls-4" d="M318.89,413.56c6.34,0,9.52,2.71,9.52,8.12s-3.21,8.2-9.55,8.2h-8.08v11.29h-3.17v-27.61h11.29Zm-8.12,13.57h7.93c2.24,0,3.87-.46,4.95-1.35,1.04-.89,1.59-2.24,1.59-4.1s-.54-3.21-1.62-4.02c-1.08-.93-2.71-1.35-4.91-1.35h-7.93v10.83Z"/>
|
||||
<path class="cls-4" d="M356.72,421.1v3.09c-1-.31-2.01-.42-2.98-.42-1.55,0-2.86,.66-3.9,2.05-1.08,1.32-1.59,2.9-1.59,4.76v10.6h-3.06v-19.99h3.06v3.48c.54-1.16,1.24-2.09,2.09-2.75,1.04-.85,2.24-1.28,3.63-1.28,1.08,0,2.01,.15,2.74,.46Z"/>
|
||||
<path class="cls-4" d="M388.55,423.73c1.7,1.97,2.55,4.45,2.55,7.46s-.85,5.45-2.51,7.39c-1.82,2.09-4.21,3.13-7.19,3.13s-5.37-1.04-7.15-3.13c-1.7-1.93-2.55-4.41-2.55-7.39s.85-5.49,2.55-7.46c1.78-2.09,4.14-3.09,7.15-3.09s5.38,1.01,7.16,3.09Zm-12.11,1.93c-1.08,1.47-1.58,3.29-1.58,5.53s.5,4.02,1.58,5.49c1.16,1.62,2.83,2.44,4.95,2.44s3.75-.81,4.99-2.44c1.04-1.47,1.59-3.33,1.59-5.49s-.54-4.06-1.59-5.53c-1.24-1.62-2.9-2.44-4.99-2.44s-3.79,.81-4.95,2.44Z"/>
|
||||
<path class="cls-4" d="M410.56,421.18l5.18,7.11,5.14-7.11h3.64l-7.08,9.4,7.93,10.6h-3.68l-5.95-8.32-5.99,8.32h-3.67l7.93-10.6-7.08-9.4h3.63Z"/>
|
||||
<path class="cls-4" d="M442.28,421.18l6.11,15.78,5.92-15.78h3.41l-10.94,27.65h-3.36l3.44-8.12-7.93-19.53h3.36Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.0 KiB |
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="_图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 420">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #ff9045;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #0079dd;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g>
|
||||
<circle class="cls-1" cx="240" cy="94.5" r="27.21"/>
|
||||
<path class="cls-1" d="M257.97,137.7h-36.11c-9.1,0-16.48,7.38-16.48,16.48v184.01c0,8.02,6.5,14.52,14.52,14.52h.14c8.02,0,14.52-6.5,14.52-14.52v-107.89c5.91,0,10.7,4.79,10.7,10.7v32.75c0,8.02,6.5,14.52,14.52,14.52h.14c8.02,0,14.52-6.5,14.52-14.52v-42.24s0-77.34,0-77.34c0-9.1-7.38-16.48-16.48-16.48Z"/>
|
||||
</g>
|
||||
<path class="cls-2" d="M183.93,221.1h-3.95c-6.13,0-11.1-4.97-11.1-11.1s4.97-11.1,11.1-11.1h3.95s.08,0,.13,0v-51.31c0-9.51-4.63-18.43-12.4-23.92L51.43,38.95c-9.69-6.83-23.06,.1-23.06,11.96V369.09c0,11.86,13.37,18.79,23.06,11.96l120.22-84.73c7.78-5.48,12.4-14.4,12.4-23.92v-51.31s-.08,0-.13,0Z"/>
|
||||
<path class="cls-2" d="M428.57,38.95l-120.22,84.73c-7.78,5.48-12.4,14.4-12.4,23.92v51.31s.08,0,.13,0h3.95c6.13,0,11.1,4.97,11.1,11.1s-4.97,11.1-11.1,11.1h-3.95s-.08,0-.13,0v51.31c0,9.51,4.63,18.44,12.4,23.92l120.22,84.73c9.69,6.83,23.06-.1,23.06-11.96V50.91c0-11.86-13.37-18.79-23.06-11.96Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -52,12 +52,16 @@ export default {
|
||||
license: 'License管理',
|
||||
portMapping: '端口映射',
|
||||
jobManager: '调度管理',
|
||||
jobLog: '调度日志'
|
||||
jobLog: '调度日志',
|
||||
log: '日志管理',
|
||||
loginLog: '登录日志',
|
||||
clientConnectLog: '客户端连接日志'
|
||||
},
|
||||
navbar: {
|
||||
logOut: '退出登录',
|
||||
dashboard: '首页',
|
||||
github: '项目地址',
|
||||
updatePwd: '修改密码',
|
||||
screenfull: '全屏',
|
||||
theme: '换肤'
|
||||
},
|
||||
@@ -133,9 +137,20 @@ export default {
|
||||
jobParam: '任务参数',
|
||||
alarmEmail: '任务报警邮箱',
|
||||
alarmDing: '任务报警钉钉',
|
||||
jobLogCode: '执行结果',
|
||||
jobLogMsg: '执行日志',
|
||||
alarmStatus: '报警状态'
|
||||
jobLogCode: '调度结果',
|
||||
jobLogMsg: '调度日志',
|
||||
jobLogTime: '调度时间',
|
||||
alarmStatus: '报警状态',
|
||||
ip: 'IP',
|
||||
happendTime: '发生时间',
|
||||
updatePwd: '修改密码',
|
||||
msg: '消息',
|
||||
outcome: '结果',
|
||||
err: '异常信息',
|
||||
resetKey: '重置Key'
|
||||
},
|
||||
button: {
|
||||
lookOver: '查看'
|
||||
},
|
||||
errorLog: {
|
||||
tips: '请点击右上角bug小图标',
|
||||
|
||||
@@ -29,17 +29,6 @@ export const constantRouterMap = [
|
||||
{ path: '/authredirect', component: _import('login/authredirect'), hidden: true },
|
||||
{ path: '/404', component: _import('errorPage/404'), hidden: true },
|
||||
{ path: '/401', component: _import('errorPage/401'), hidden: true },
|
||||
// {
|
||||
// path: '',
|
||||
// component: Layout,
|
||||
// redirect: 'dashboard',
|
||||
// children: [{
|
||||
// path: 'dashboard',
|
||||
// component: _import('dashboard/index'),
|
||||
// name: 'dashboard',
|
||||
// meta: { title: 'dashboard', icon: 'dashboard', noCache: true }
|
||||
// }]
|
||||
// },
|
||||
{
|
||||
path: '',
|
||||
component: Layout,
|
||||
@@ -51,18 +40,6 @@ export const constantRouterMap = [
|
||||
meta: { title: 'home', icon: 'dashboard', noCache: true }
|
||||
}]
|
||||
}
|
||||
|
||||
// , {
|
||||
// path: '/documentation',
|
||||
// component: Layout,
|
||||
// redirect: '/documentation/index',
|
||||
// children: [{
|
||||
// path: 'index',
|
||||
// component: _import('documentation/index'),
|
||||
// name: 'documentation',
|
||||
// meta: { title: 'documentation', icon: 'documentation', noCache: true }
|
||||
// }]
|
||||
// }
|
||||
]
|
||||
|
||||
export default new Router({
|
||||
@@ -72,184 +49,6 @@ export default new Router({
|
||||
})
|
||||
|
||||
export const asyncRouterMap = [
|
||||
// {
|
||||
// path: '/permission',
|
||||
// component: Layout,
|
||||
// redirect: '/permission/index',
|
||||
// meta: { roles: ['admin'] }, // you can set roles in root nav
|
||||
// children: [{
|
||||
// path: 'index',
|
||||
// component: _import('permission/index'),
|
||||
// name: 'permission',
|
||||
// meta: {
|
||||
// title: 'permission',
|
||||
// icon: 'lock',
|
||||
// roles: ['admin'] // or you can only set roles in sub nav
|
||||
// }
|
||||
// }]
|
||||
// },
|
||||
//
|
||||
// {
|
||||
// path: '/icon',
|
||||
// component: Layout,
|
||||
// children: [{
|
||||
// path: 'index',
|
||||
// component: _import('svg-icons/index'),
|
||||
// name: 'icons',
|
||||
// meta: { title: 'icons', icon: 'icon', noCache: true }
|
||||
// }]
|
||||
// },
|
||||
//
|
||||
// {
|
||||
// path: '/components',
|
||||
// component: Layout,
|
||||
// redirect: 'noredirect',
|
||||
// name: 'component-demo',
|
||||
// meta: {
|
||||
// title: 'components',
|
||||
// icon: 'component'
|
||||
// },
|
||||
// children: [
|
||||
// { path: 'tinymce', component: _import('components-demo/tinymce'), name: 'tinymce-demo', meta: { title: 'tinymce' }},
|
||||
// { path: 'markdown', component: _import('components-demo/markdown'), name: 'markdown-demo', meta: { title: 'markdown' }},
|
||||
// { path: 'json-editor', component: _import('components-demo/jsonEditor'), name: 'jsonEditor-demo', meta: { title: 'jsonEditor' }},
|
||||
// { path: 'dnd-list', component: _import('components-demo/dndList'), name: 'dndList-demo', meta: { title: 'dndList' }},
|
||||
// { path: 'splitpane', component: _import('components-demo/splitpane'), name: 'splitpane-demo', meta: { title: 'splitPane' }},
|
||||
// { path: 'avatar-upload', component: _import('components-demo/avatarUpload'), name: 'avatarUpload-demo', meta: { title: 'avatarUpload' }},
|
||||
// { path: 'dropzone', component: _import('components-demo/dropzone'), name: 'dropzone-demo', meta: { title: 'dropzone' }},
|
||||
// { path: 'sticky', component: _import('components-demo/sticky'), name: 'sticky-demo', meta: { title: 'sticky' }},
|
||||
// { path: 'count-to', component: _import('components-demo/countTo'), name: 'countTo-demo', meta: { title: 'countTo' }},
|
||||
// { path: 'mixin', component: _import('components-demo/mixin'), name: 'componentMixin-demo', meta: { title: 'componentMixin' }},
|
||||
// { path: 'back-to-top', component: _import('components-demo/backToTop'), name: 'backToTop-demo', meta: { title: 'backToTop' }}
|
||||
// ]
|
||||
// },
|
||||
//
|
||||
// {
|
||||
// path: '/charts',
|
||||
// component: Layout,
|
||||
// redirect: 'noredirect',
|
||||
// name: 'charts',
|
||||
// meta: {
|
||||
// title: 'charts',
|
||||
// icon: 'chart'
|
||||
// },
|
||||
// children: [
|
||||
// { path: 'keyboard', component: _import('charts/keyboard'), name: 'keyboardChart', meta: { title: 'keyboardChart', noCache: true }},
|
||||
// { path: 'line', component: _import('charts/line'), name: 'lineChart', meta: { title: 'lineChart', noCache: true }},
|
||||
// { path: 'mixchart', component: _import('charts/mixChart'), name: 'mixChart', meta: { title: 'mixChart', noCache: true }}
|
||||
// ]
|
||||
// },
|
||||
//
|
||||
// {
|
||||
// path: '/example',
|
||||
// component: Layout,
|
||||
// redirect: '/example/table/complex-table',
|
||||
// name: 'example',
|
||||
// meta: {
|
||||
// title: 'example',
|
||||
// icon: 'example'
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// path: '/example/table',
|
||||
// component: _import('example/table/index'),
|
||||
// redirect: '/example/table/complex-table',
|
||||
// name: 'Table',
|
||||
// meta: {
|
||||
// title: 'Table',
|
||||
// icon: 'table'
|
||||
// },
|
||||
// children: [
|
||||
// { path: 'dynamic-table', component: _import('example/table/dynamicTable/index'), name: 'dynamicTable', meta: { title: 'dynamicTable' }},
|
||||
// { path: 'drag-table', component: _import('example/table/dragTable'), name: 'dragTable', meta: { title: 'dragTable' }},
|
||||
// { path: 'inline-edit-table', component: _import('example/table/inlineEditTable'), name: 'inlineEditTable', meta: { title: 'inlineEditTable' }},
|
||||
// { path: 'complex-table', component: _import('example/table/complexTable'), name: 'complexTable', meta: { title: 'complexTable' }}
|
||||
// ]
|
||||
// },
|
||||
// { path: 'tab/index', icon: 'tab', component: _import('example/tab/index'), name: 'tab', meta: { title: 'tab' }}
|
||||
// ]
|
||||
// },
|
||||
//
|
||||
// {
|
||||
// path: '/form',
|
||||
// component: Layout,
|
||||
// redirect: 'noredirect',
|
||||
// name: 'form',
|
||||
// meta: {
|
||||
// title: 'form',
|
||||
// icon: 'form'
|
||||
// },
|
||||
// children: [
|
||||
// { path: 'create-form', component: _import('form/create'), name: 'createForm', meta: { title: 'createForm', icon: 'table' }},
|
||||
// { path: 'edit-form', component: _import('form/edit'), name: 'editForm', meta: { title: 'editForm', icon: 'table' }}
|
||||
// ]
|
||||
// },
|
||||
//
|
||||
// {
|
||||
// path: '/error',
|
||||
// component: Layout,
|
||||
// redirect: 'noredirect',
|
||||
// name: 'errorPages',
|
||||
// meta: {
|
||||
// title: 'errorPages',
|
||||
// icon: '404'
|
||||
// },
|
||||
// children: [
|
||||
// { path: '401', component: _import('errorPage/401'), name: 'page401', meta: { title: 'page401', noCache: true }},
|
||||
// { path: '404', component: _import('errorPage/404'), name: 'page404', meta: { title: 'page404', noCache: true }}
|
||||
// ]
|
||||
// },
|
||||
//
|
||||
// {
|
||||
// path: '/error-log',
|
||||
// component: Layout,
|
||||
// redirect: 'noredirect',
|
||||
// children: [{ path: 'log', component: _import('errorLog/index'), name: 'errorLog', meta: { title: 'errorLog', icon: 'bug' }}]
|
||||
// },
|
||||
//
|
||||
// {
|
||||
// path: '/excel',
|
||||
// component: Layout,
|
||||
// redirect: '/excel/export-excel',
|
||||
// name: 'excel',
|
||||
// meta: {
|
||||
// title: 'excel',
|
||||
// icon: 'excel'
|
||||
// },
|
||||
// children: [
|
||||
// { path: 'export-excel', component: _import('excel/exportExcel'), name: 'exportExcel', meta: { title: 'exportExcel' }},
|
||||
// { path: 'export-selected-excel', component: _import('excel/selectExcel'), name: 'selectExcel', meta: { title: 'selectExcel' }},
|
||||
// { path: 'upload-excel', component: _import('excel/uploadExcel'), name: 'uploadExcel', meta: { title: 'uploadExcel' }}
|
||||
// ]
|
||||
// },
|
||||
//
|
||||
// {
|
||||
// path: '/zip',
|
||||
// component: Layout,
|
||||
// redirect: '/zip/download',
|
||||
// children: [{ path: 'download', component: _import('zip/index'), name: 'exportZip', meta: { title: 'exportZip', icon: 'zip' }}]
|
||||
// },
|
||||
//
|
||||
// {
|
||||
// path: '/theme',
|
||||
// component: Layout,
|
||||
// redirect: 'noredirect',
|
||||
// children: [{ path: 'index', component: _import('theme/index'), name: 'theme', meta: { title: 'theme', icon: 'theme' }}]
|
||||
// },
|
||||
//
|
||||
// {
|
||||
// path: '/clipboard',
|
||||
// component: Layout,
|
||||
// redirect: 'noredirect',
|
||||
// children: [{ path: 'index', component: _import('clipboard/index'), name: 'clipboardDemo', meta: { title: 'clipboardDemo', icon: 'clipboard' }}]
|
||||
// },
|
||||
//
|
||||
// {
|
||||
// path: '/i18n',
|
||||
// component: Layout,
|
||||
// children: [{ path: 'index', component: _import('i18n-demo/index'), name: 'i18n', meta: { title: 'i18n', icon: 'international' }}]
|
||||
// },
|
||||
|
||||
{ path: '*', redirect: '/404', hidden: true },
|
||||
{
|
||||
path: '/proxy',
|
||||
@@ -277,8 +76,22 @@ export const asyncRouterMap = [
|
||||
children: [
|
||||
{ path: 'user', component: _import('system/user'), name: 'user', meta: { title: 'user' }},
|
||||
{ path: 'portPool', component: _import('system/portPool'), name: 'portPool', meta: { title: 'portPool' }},
|
||||
{ path: 'jobManager', component: _import('system/jobManager'), name: 'jobManager', meta: { title: 'jobManager' }},
|
||||
{ path: 'jobLog', component: _import('system/jobLog'), name: 'jobLog', meta: { title: 'jobLog' }}
|
||||
{ path: 'jobManager', component: _import('system/jobManager'), name: 'jobManager', meta: { title: 'jobManager' }}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/log',
|
||||
component: Layout,
|
||||
redirect: 'noredirect',
|
||||
name: 'log',
|
||||
meta: {
|
||||
title: 'log',
|
||||
icon: 'component'
|
||||
},
|
||||
children: [
|
||||
{ path: 'jobLog', component: _import('log/jobLog'), name: 'jobLog', meta: { title: 'jobLog' }},
|
||||
{ path: 'loginLog', component: _import('log/loginLog'), name: 'loginLog', meta: { title: 'loginLog' }},
|
||||
{ path: 'clientConnectLog', component: _import('log/clientConnectLog'), name: 'clientConnectLog', meta: { title: 'clientConnectLog' }}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
.fixed-width{
|
||||
.el-button--mini{
|
||||
padding: 7px 10px;
|
||||
width: 60px;
|
||||
min-width: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ service.interceptors.response.use(
|
||||
type: 'error',
|
||||
duration: 5 * 1000
|
||||
})
|
||||
if (res.code === 4 && !window.location.href.endsWith('#/login')) {
|
||||
if ((res.code === 4 || res.code === 1) && !window.location.href.endsWith('#/login')) {
|
||||
store.dispatch('FedLogOut').then(() => {
|
||||
location.reload() // 为了重新实例化vue-router对象 避免bug
|
||||
})
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
<template>
|
||||
<div class='chart-container'>
|
||||
<chart height='100%' width='100%'></chart>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Chart from '@/components/Charts/keyboard'
|
||||
|
||||
export default {
|
||||
name: 'keyboardChart',
|
||||
components: { Chart }
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.chart-container{
|
||||
position: relative;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
height: 85vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
<template>
|
||||
<div class='chart-container'>
|
||||
<chart height='100%' width='100%'></chart>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Chart from '@/components/Charts/lineMarker'
|
||||
|
||||
export default {
|
||||
name: 'lineChart',
|
||||
components: { Chart }
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.chart-container{
|
||||
position: relative;
|
||||
padding:20px;
|
||||
width: 100%;
|
||||
height:85vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
<template>
|
||||
<div class='chart-container'>
|
||||
<chart height='100%' width='100%'></chart>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Chart from '@/components/Charts/mixChart'
|
||||
|
||||
export default {
|
||||
name: 'mixChart',
|
||||
components: { Chart }
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.chart-container{
|
||||
position: relative;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
height:85vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-tabs v-model="activeName">
|
||||
<el-tab-pane label="use clipboard directly" name="directly">
|
||||
<el-input v-model="inputData" placeholder="Please input" style='width:400px;'></el-input>
|
||||
<el-button type="primary" icon="document" @click='handleCopy(inputData,$event)'>copy</el-button>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="use clipboard by v-directive" name="v-directive">
|
||||
<el-input v-model="inputData" placeholder="Please input" style='width:400px;'></el-input>
|
||||
<el-button type="primary" icon="document" v-clipboard:copy='inputData' v-clipboard:success='clipboardSuccess'>copy</el-button>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import clip from '@/utils/clipboard' // use clipboard directly
|
||||
import clipboard from '@/directive/clipboard/index.js' // use clipboard by v-directive
|
||||
|
||||
export default {
|
||||
name: 'clipboardDemo',
|
||||
directives: {
|
||||
clipboard
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeName: 'directly',
|
||||
inputData: 'https://github.com/PanJiaChen/vue-element-admin'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleCopy(text, event) {
|
||||
clip(text, event)
|
||||
},
|
||||
clipboardSuccess() {
|
||||
this.$message({
|
||||
message: '复制成功',
|
||||
type: 'success',
|
||||
duration: 1500
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
<template>
|
||||
<div class="components-container">
|
||||
<code>This is based on
|
||||
<a class="link-type" href="//github.com/dai-siki/vue-image-crop-upload"> vue-image-crop-upload</a>.
|
||||
{{$t('components.imageUploadTips')}}
|
||||
</code>
|
||||
|
||||
<pan-thumb :image="image"></pan-thumb>
|
||||
|
||||
<el-button type="primary" icon="upload" style="position: absolute;bottom: 15px;margin-left: 40px;" @click="imagecropperShow=true">Change avatar
|
||||
</el-button>
|
||||
|
||||
<image-cropper :width="300" :height="300" url="https://httpbin.org/post" @close='close' @crop-upload-success="cropSuccess" langType="en"
|
||||
:key="imagecropperKey" v-show="imagecropperShow"></image-cropper>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ImageCropper from '@/components/ImageCropper'
|
||||
import PanThumb from '@/components/PanThumb'
|
||||
|
||||
export default {
|
||||
name: 'avatarUpload-demo',
|
||||
components: { ImageCropper, PanThumb },
|
||||
data() {
|
||||
return {
|
||||
imagecropperShow: false,
|
||||
imagecropperKey: 0,
|
||||
image: 'https://wpimg.wallstcn.com/577965b9-bb9e-4e02-9f0c-095b41417191'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
cropSuccess(resData) {
|
||||
this.imagecropperShow = false
|
||||
this.imagecropperKey = this.imagecropperKey + 1
|
||||
this.image = resData.files.avatar
|
||||
},
|
||||
close() {
|
||||
this.imagecropperShow = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.avatar{
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
<template>
|
||||
<div class="components-container">
|
||||
<code>{{$t('components.backToTopTips1')}}</code>
|
||||
<code>{{$t('components.backToTopTips2')}}</code>
|
||||
<div class="placeholder-container">
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
</div>
|
||||
<!--可自定义按钮的样式、show/hide临界点、返回的位置 -->
|
||||
<!--如需文字提示,可在外部添加element的<el-tooltip></el-tooltip>元素 -->
|
||||
<el-tooltip placement="top" content="tooltip">
|
||||
<back-to-top transitionName="fade" :customStyle="myBackToTopStyle" :visibilityHeight="300" :backPosition="50"></back-to-top>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BackToTop from '@/components/BackToTop'
|
||||
|
||||
export default {
|
||||
name: 'backToTop-demo',
|
||||
components: { BackToTop },
|
||||
data() {
|
||||
return {
|
||||
myBackToTopStyle: {
|
||||
right: '50px',
|
||||
bottom: '50px',
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
'border-radius': '4px',
|
||||
'line-height': '45px', // 请保持与高度一致以垂直居中 Please keep consistent with height to center vertically
|
||||
background: '#e7eaf1'// 按钮的背景颜色 The background color of the button
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.placeholder-container div {
|
||||
margin: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,205 +0,0 @@
|
||||
<template>
|
||||
<div class="components-container">
|
||||
<p class="warn-content">
|
||||
<a href="https://github.com/PanJiaChen/vue-countTo" target="_blank">countTo-component</a>
|
||||
</p>
|
||||
<count-to ref="example" class="example" :start-val="_startVal" :end-val="_endVal" :duration="_duration" :decimals="_decimals"
|
||||
:separator="_separator" :prefix="_prefix" :suffix="_suffix" :autoplay="false"></count-to>
|
||||
<div style="margin-left: 25%;margin-top: 40px;">
|
||||
<label class="label" for="startValInput">startVal:
|
||||
<input type="number" v-model.number="setStartVal" name="startValInput" />
|
||||
</label>
|
||||
<label class="label" for="endValInput">endVal:
|
||||
<input type="number" v-model.number="setEndVal" name="endVaInput" />
|
||||
</label>
|
||||
<label class="label" for="durationInput">duration:
|
||||
<input type="number" v-model.number="setDuration" name="durationInput" />
|
||||
</label>
|
||||
<div class="startBtn example-btn" @click="start">开始</div>
|
||||
<div class="pause-resume-btn example-btn" @click="pauseResume">暂停/恢复</div>
|
||||
<br/>
|
||||
<label class="label" for="decimalsInput">decimals:
|
||||
<input type="number" v-model.number="setDecimals" name="decimalsInput" />
|
||||
</label>
|
||||
<label class="label" for="separatorInput">separator:
|
||||
<input v-model="setSeparator" name="separatorInput" />
|
||||
</label>
|
||||
<label class="label" for="prefixInput">prefix:
|
||||
<input v-model="setPrefix" name="prefixInput" />
|
||||
</label>
|
||||
<label class="label" for="suffixInput">suffix:
|
||||
<input v-model="setSuffix" name="suffixInput" />
|
||||
</label>
|
||||
</div>
|
||||
<code><count-to :start-val='{{_startVal}}' :end-val='{{_endVal}}' :duration='{{_duration}}'
|
||||
:decimals='{{_decimals}}' :separator='{{_separator}}' :prefix='{{_prefix}}' :suffix='{{_suffix}}'
|
||||
:autoplay=false></code>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import countTo from 'vue-count-to'
|
||||
|
||||
export default {
|
||||
name: 'countTo-demo',
|
||||
components: { countTo },
|
||||
data() {
|
||||
return {
|
||||
setStartVal: 0,
|
||||
setEndVal: 2017,
|
||||
setDuration: 4000,
|
||||
setDecimals: 0,
|
||||
setSeparator: ',',
|
||||
setSuffix: ' rmb',
|
||||
setPrefix: '¥ '
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
_startVal() {
|
||||
if (this.setStartVal) {
|
||||
return this.setStartVal
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
},
|
||||
_endVal() {
|
||||
if (this.setEndVal) {
|
||||
return this.setEndVal
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
},
|
||||
_duration() {
|
||||
if (this.setDuration) {
|
||||
return this.setDuration
|
||||
} else {
|
||||
return 100
|
||||
}
|
||||
},
|
||||
_decimals() {
|
||||
if (this.setDecimals) {
|
||||
if (this.setDecimals < 0 || this.setDecimals > 20) {
|
||||
alert('digits argument must be between 0 and 20')
|
||||
return 0
|
||||
}
|
||||
return this.setDecimals
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
},
|
||||
_separator() {
|
||||
return this.setSeparator
|
||||
},
|
||||
_suffix() {
|
||||
return this.setSuffix
|
||||
},
|
||||
_prefix() {
|
||||
return this.setPrefix
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
start() {
|
||||
this.$refs.example.start()
|
||||
},
|
||||
pauseResume() {
|
||||
this.$refs.example.pauseResume()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.example-btn {
|
||||
display: inline-block;
|
||||
margin-bottom: 0;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
-ms-touch-action: manipulation;
|
||||
touch-action: manipulation;
|
||||
cursor: pointer;
|
||||
background-image: none;
|
||||
border: 1px solid transparent;
|
||||
white-space: nowrap;
|
||||
line-height: 1.5;
|
||||
padding: 4px 15px;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-transition: all .3s cubic-bezier(.645, .045, .355, 1);
|
||||
transition: all .3s cubic-bezier(.645, .045, .355, 1);
|
||||
position: relative;
|
||||
color: rgba(0, 0, 0, .65);
|
||||
background-color: #fff;
|
||||
border-color: #d9d9d9;
|
||||
}
|
||||
|
||||
.example-btn:hover {
|
||||
color: #4AB7BD;
|
||||
background-color: #fff;
|
||||
border-color: #4AB7BD;
|
||||
}
|
||||
.example {
|
||||
font-size: 50px;
|
||||
color: #F6416C;
|
||||
display: block;
|
||||
margin: 10px 0;
|
||||
text-align: center;
|
||||
font-size: 80px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #2f4f4f;
|
||||
font-size: 16px;
|
||||
display: inline-block;
|
||||
margin: 15px 30px 15px 0;
|
||||
}
|
||||
|
||||
input {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
padding: 4px 7px;
|
||||
width: 70px;
|
||||
height: 28px;
|
||||
cursor: text;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: rgba(0, 0, 0, .65);
|
||||
background-color: #fff;
|
||||
background-image: none;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
-webkit-transition: all .3s;
|
||||
transition: all .3s;
|
||||
}
|
||||
|
||||
.startBtn {
|
||||
margin-left: 20px;
|
||||
font-size: 20px;
|
||||
color: #30B08F;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.startBtn:hover {
|
||||
background-color: #30B08F;
|
||||
color: #fff;
|
||||
border-color: #30B08F;
|
||||
}
|
||||
|
||||
.pause-resume-btn {
|
||||
font-size: 20px;
|
||||
color: #E65D6E;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.pause-resume-btn:hover {
|
||||
background-color: #E65D6E;
|
||||
color: #fff;
|
||||
border-color: #E65D6E;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
<template>
|
||||
<div class="components-container">
|
||||
<code>drag-list base on
|
||||
<a href="https://github.com/SortableJS/Vue.Draggable" target="_blank">Vue.Draggable</a>
|
||||
</code>
|
||||
<div class="editor-container">
|
||||
<dnd-list :list1="list1" :list2="list2" list1Title="List" list2Title="Article pool"></dnd-list>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DndList from '@/components/DndList'
|
||||
import { fetchList } from '@/api/article'
|
||||
|
||||
export default {
|
||||
name: 'dndList-demo',
|
||||
components: { DndList },
|
||||
data() {
|
||||
return {
|
||||
list1: [],
|
||||
list2: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getData()
|
||||
},
|
||||
methods: {
|
||||
getData() {
|
||||
this.listLoading = true
|
||||
fetchList().then(response => {
|
||||
this.list1 = response.data.items.splice(0, 5)
|
||||
this.list2 = response.data.items
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
<template>
|
||||
<div class="components-container">
|
||||
<code>
|
||||
Based on <a class="link-type" href="https://github.com/rowanwins/vue-dropzone"> dropzone </a>.
|
||||
{{$t('components.dropzoneTips')}}
|
||||
</code>
|
||||
<div class="editor-container">
|
||||
<dropzone v-on:dropzone-removedFile="dropzoneR" v-on:dropzone-success="dropzoneS" id="myVueDropzone" url="https://httpbin.org/post"></dropzone>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dropzone from '@/components/Dropzone'
|
||||
|
||||
export default {
|
||||
name: 'dropzone-demo',
|
||||
components: { Dropzone },
|
||||
methods: {
|
||||
dropzoneS(file) {
|
||||
console.log(file)
|
||||
this.$message({ message: 'Upload success', type: 'success' })
|
||||
},
|
||||
dropzoneR(file) {
|
||||
console.log(file)
|
||||
this.$message({ message: 'Delete success', type: 'success' })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
<template>
|
||||
<div class="components-container">
|
||||
<code>JsonEditor is base on <a href="https://github.com/codemirror/CodeMirror" target="_blank">CodeMirrorr</a> , lint base on json-lint </code>
|
||||
<div class="editor-container">
|
||||
<json-editor ref="jsonEditor" v-model="value"></json-editor>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JsonEditor from '@/components/JsonEditor'
|
||||
|
||||
const jsonData = '[{"items":[{"market_type":"forexdata","symbol":"XAUUSD"},{"market_type":"forexdata","symbol":"UKOIL"},{"market_type":"forexdata","symbol":"CORN"}],"name":""},{"items":[{"market_type":"forexdata","symbol":"XAUUSD"},{"market_type":"forexdata","symbol":"XAGUSD"},{"market_type":"forexdata","symbol":"AUTD"},{"market_type":"forexdata","symbol":"AGTD"}],"name":"贵金属"},{"items":[{"market_type":"forexdata","symbol":"CORN"},{"market_type":"forexdata","symbol":"WHEAT"},{"market_type":"forexdata","symbol":"SOYBEAN"},{"market_type":"forexdata","symbol":"SUGAR"}],"name":"农产品"},{"items":[{"market_type":"forexdata","symbol":"UKOIL"},{"market_type":"forexdata","symbol":"USOIL"},{"market_type":"forexdata","symbol":"NGAS"}],"name":"能源化工"}]'
|
||||
|
||||
export default {
|
||||
name: 'jsonEditor-demo',
|
||||
components: { JsonEditor },
|
||||
data() {
|
||||
return {
|
||||
value: JSON.parse(jsonData)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.editor-container{
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
<template>
|
||||
<div class="components-container">
|
||||
<code>Markdown is based on
|
||||
<a href="https://github.com/sparksuite/simplemde-markdown-editor" target="_blank">simplemde-markdown-editor</a> ,Simply encapsulated in Vue.
|
||||
<a target="_blank" href="https://segmentfault.com/a/1190000009762198#articleHeader14">
|
||||
相关文章 </a>
|
||||
</code>
|
||||
<div class="editor-container">
|
||||
<markdown-editor id="contentEditor" ref="contentEditor" v-model="content" :height="300" :zIndex="20"></markdown-editor>
|
||||
</div>
|
||||
<el-button @click="markdown2Html" style="margin-top:80px;" type="primary" icon="el-icon-document">To HTML</el-button>
|
||||
<div v-html="html"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MarkdownEditor from '@/components/MarkdownEditor'
|
||||
|
||||
const content = `
|
||||
**this is test**
|
||||
|
||||
* vue
|
||||
* element
|
||||
* webpack
|
||||
|
||||
## Simplemde
|
||||
`
|
||||
|
||||
export default {
|
||||
name: 'markdown-demo',
|
||||
components: { MarkdownEditor },
|
||||
data() {
|
||||
return {
|
||||
content: content,
|
||||
html: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
markdown2Html() {
|
||||
import('showdown').then(showdown => {
|
||||
const converter = new showdown.Converter()
|
||||
this.html = converter.makeHtml(this.content)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
<template>
|
||||
<div class="mixin-components-container">
|
||||
<el-row>
|
||||
<el-card class="box-card">
|
||||
<div slot="header" class="clearfix">
|
||||
<span>Buttons</span>
|
||||
</div>
|
||||
<div style="margin-bottom:50px;">
|
||||
<el-col :span="4" class="text-center">
|
||||
<router-link class="pan-btn blue-btn" to="/components/index">Components</router-link>
|
||||
</el-col>
|
||||
<el-col :span="4" class="text-center">
|
||||
<router-link class="pan-btn light-blue-btn" to="/charts/index">Charts</router-link>
|
||||
</el-col>
|
||||
<el-col :span="4" class="text-center">
|
||||
<router-link class="pan-btn pink-btn" to="/excel/download">Excel</router-link>
|
||||
</el-col>
|
||||
<el-col :span="4" class="text-center">
|
||||
<router-link class="pan-btn green-btn" to="/example/table/complex-table">Table</router-link>
|
||||
</el-col>
|
||||
<el-col :span="4" class="text-center">
|
||||
<router-link class="pan-btn tiffany-btn" to="/form/edit-form">Form</router-link>
|
||||
</el-col>
|
||||
<el-col :span="4" class="text-center">
|
||||
<router-link class="pan-btn yellow-btn" to="/theme/index">Theme</router-link>
|
||||
</el-col>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" style="margin-top:50px;">
|
||||
<el-col :span="6">
|
||||
<el-card class="box-card">
|
||||
<div slot="header" class="clearfix">
|
||||
<span>Material Design 的input</span>
|
||||
</div>
|
||||
<div style="height:100px;">
|
||||
<el-form :model="demo" :rules="demoRules">
|
||||
<el-form-item prop="title">
|
||||
<md-input icon="search" name="title" placeholder="输入标题" v-model="demo.title">标题</md-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="6">
|
||||
<el-card class="box-card">
|
||||
<div slot="header" class="clearfix">
|
||||
<span>图片hover效果</span>
|
||||
</div>
|
||||
<div class="component-item">
|
||||
<pan-thumb width="100px" height="100px" image="https://wpimg.wallstcn.com/577965b9-bb9e-4e02-9f0c-095b41417191">
|
||||
vue-element-admin
|
||||
</pan-thumb>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="6">
|
||||
<el-card class="box-card">
|
||||
<div slot="header" class="clearfix">
|
||||
<span>水波纹 waves v-directive</span>
|
||||
</div>
|
||||
<div class="component-item">
|
||||
<el-button v-waves type="primary">水波纹效果</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="6">
|
||||
<el-card class="box-card">
|
||||
<div slot="header" class="clearfix">
|
||||
<span>hover text</span>
|
||||
</div>
|
||||
<div class="component-item">
|
||||
<mallki className="mallki-text" text="vue-element-admin"></mallki>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" style="margin-top:50px;">
|
||||
<el-col :span="6">
|
||||
<el-card class="box-card">
|
||||
<div slot="header" class="clearfix">
|
||||
<span>Share</span>
|
||||
</div>
|
||||
<div class="component-item" style="height:420px;">
|
||||
<dropdown-menu style="margin:0 auto;" title='系列文章' :items='articleList'></dropdown-menu>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PanThumb from '@/components/PanThumb'
|
||||
import MdInput from '@/components/MDinput'
|
||||
import Mallki from '@/components/TextHoverEffect/Mallki'
|
||||
import DropdownMenu from '@/components/Share/dropdownMenu'
|
||||
import waves from '@/directive/waves/index.js' // 水波纹指令
|
||||
|
||||
export default {
|
||||
name: 'componentMixin-demo',
|
||||
components: {
|
||||
PanThumb,
|
||||
MdInput,
|
||||
Mallki,
|
||||
DropdownMenu
|
||||
},
|
||||
directives: {
|
||||
waves
|
||||
},
|
||||
data() {
|
||||
const validate = (rule, value, callback) => {
|
||||
if (value.length !== 6) {
|
||||
callback(new Error('请输入六个字符'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
return {
|
||||
demo: {
|
||||
title: ''
|
||||
},
|
||||
demoRules: {
|
||||
title: [{ required: true, trigger: 'change', validator: validate }]
|
||||
},
|
||||
articleList: [
|
||||
{ title: '基础篇', href: 'https://segmentfault.com/a/1190000009275424' },
|
||||
{ title: '登录权限篇', href: 'https://segmentfault.com/a/1190000009506097' },
|
||||
{ title: '实战篇', href: 'https://segmentfault.com/a/1190000009762198' },
|
||||
{ title: 'vueAdmin-template 篇', href: 'https://segmentfault.com/a/1190000010043013' },
|
||||
{ title: '自行封装 component', href: 'https://segmentfault.com/a/1190000009090836' },
|
||||
{ title: '优雅的使用 icon', href: 'https://segmentfault.com/a/https://segmentfault.com/a/1190000012213278' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mixin-components-container {
|
||||
background-color: #f0f2f5;
|
||||
padding: 30px;
|
||||
min-height: calc(100vh - 84px);
|
||||
}
|
||||
.component-item{
|
||||
min-height: 100px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,67 +0,0 @@
|
||||
<template>
|
||||
<div class="components-container">
|
||||
<code><strong>SplitPane</strong> If you've used
|
||||
<a href="http://codepen.io/" target="_blank"> codepen</a>,
|
||||
<a href="https://jsfiddle.net/" target="_blank"> jsfiddle </a>will not be unfamiliar.
|
||||
<a href="https://github.com/PanJiaChen/vue-split-pane" target='_blank'> Github repository</a>
|
||||
</code>
|
||||
<split-pane v-on:resize="resize" split="vertical">
|
||||
<template slot="paneL">
|
||||
<div class="left-container"></div>
|
||||
</template>
|
||||
<template slot="paneR">
|
||||
<split-pane split="horizontal">
|
||||
<template slot="paneL">
|
||||
<div class="top-container"></div>
|
||||
</template>
|
||||
<template slot="paneR">
|
||||
<div class="bottom-container"></div>
|
||||
</template>
|
||||
</split-pane>
|
||||
</template>
|
||||
</split-pane>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import splitPane from 'vue-splitpane'
|
||||
|
||||
export default {
|
||||
name: 'splitpane-demo',
|
||||
components: { splitPane },
|
||||
methods: {
|
||||
resize() {
|
||||
console.log('resize')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.components-container {
|
||||
position: relative;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.left-container {
|
||||
background-color: #F38181;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.right-container {
|
||||
background-color: #FCE38A;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.top-container {
|
||||
background-color: #FCE38A;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.bottom-container {
|
||||
width: 100%;
|
||||
background-color: #95E1D3;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,130 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<sticky className="sub-navbar">
|
||||
<el-dropdown trigger="click">
|
||||
<el-button plain>
|
||||
Platform<i class="el-icon-caret-bottom el-icon--right"></i>
|
||||
</el-button>
|
||||
<el-dropdown-menu class="no-border" slot="dropdown">
|
||||
<el-checkbox-group v-model="platforms" style="padding: 5px 15px;">
|
||||
<el-checkbox v-for="item in platformsOptions" :label="item.key" :key="item.key">
|
||||
{{item.name}}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
|
||||
<el-dropdown trigger="click">
|
||||
<el-button plain>
|
||||
Link<i class="el-icon-caret-bottom el-icon--right"></i>
|
||||
</el-button>
|
||||
<el-dropdown-menu class="no-padding no-border" style="width:300px" slot="dropdown">
|
||||
<el-input placeholder="Please enter the content" v-model="url">
|
||||
<template slot="prepend">Url</template>
|
||||
</el-input>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
|
||||
<div class="time-container">
|
||||
<el-date-picker v-model="time" type="datetime" :picker-options="pickerOptions" format="yyyy-MM-dd HH:mm:ss" placeholder="Release time">
|
||||
</el-date-picker>
|
||||
</div>
|
||||
|
||||
<el-button style="margin-left: 10px;" type="success">publish
|
||||
</el-button>
|
||||
</sticky>
|
||||
|
||||
<div class="components-container">
|
||||
<code>Sticky header, {{$t('components.stickyTips')}}</code>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
<div>placeholder</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import Sticky from '@/components/Sticky'
|
||||
|
||||
export default {
|
||||
name: 'sticky-demo',
|
||||
components: { Sticky },
|
||||
data() {
|
||||
return {
|
||||
time: '',
|
||||
url: '',
|
||||
platforms: ['a-platform'],
|
||||
platformsOptions: [
|
||||
{ key: 'a-platform', name: 'platformA' },
|
||||
{ key: 'b-platform', name: 'platformB' },
|
||||
{ key: 'c-platform', name: 'platformC' }
|
||||
],
|
||||
pickerOptions: {
|
||||
disabledDate(time) {
|
||||
return time.getTime() > Date.now()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.components-container div {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.time-container {
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
@@ -1,41 +0,0 @@
|
||||
<template>
|
||||
<div class="components-container">
|
||||
<code>
|
||||
{{$t('components.tinymceTips')}}
|
||||
<a target="_blank" class="link-type" href="https://panjiachen.github.io/vue-element-admin-site/#/rich-editor"> {{$t('components.documentation')}}</a>
|
||||
</code>
|
||||
<div>
|
||||
<tinymce :height="300" v-model="content"></tinymce>
|
||||
</div>
|
||||
<div class="editor-content" v-html="content"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Tinymce from '@/components/Tinymce'
|
||||
|
||||
export default {
|
||||
name: 'tinymce-demo',
|
||||
components: { Tinymce },
|
||||
data() {
|
||||
return {
|
||||
content:
|
||||
`<h1 style="text-align: center;">Welcome to the TinyMCE demo!</h1>
|
||||
<ul>
|
||||
<li>Our <a href="//www.tinymce.com/docs/">documentation</a> is a great resource for learning how to configure TinyMCE.</li>
|
||||
<li>Have a specific question? Visit the <a href="https://community.tinymce.com/forum/">Community Forum</a>.</li>
|
||||
<li>We also offer enterprise grade support as part of <a href="https://tinymce.com/pricing">TinyMCE premium subscriptions</a>.</li>
|
||||
</ul>
|
||||
`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.editor-content{
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container documentation-container">
|
||||
<a class="document-btn" target='_blank' href="https://panjiachen.github.io/vue-element-admin-site/#/">{{$t('documentation.documentation')}}</a>
|
||||
<a class="document-btn" target='_blank' href="https://github.com/PanJiaChen/vue-element-admin/">{{$t('documentation.github')}}</a>
|
||||
<dropdown-menu style="float:left;margin-left:50px;" title='系列文章' :items='articleList'></dropdown-menu>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import DropdownMenu from '@/components/Share/dropdownMenu'
|
||||
|
||||
export default {
|
||||
name: 'documentation',
|
||||
components: { DropdownMenu },
|
||||
data() {
|
||||
return {
|
||||
articleList: [
|
||||
{ title: '基础篇', href: 'https://segmentfault.com/a/1190000009275424' },
|
||||
{ title: '登录权限篇', href: 'https://segmentfault.com/a/1190000009506097' },
|
||||
{ title: '实战篇', href: 'https://segmentfault.com/a/1190000009762198' },
|
||||
{ title: 'vueAdmin-template 篇', href: 'https://segmentfault.com/a/1190000010043013' },
|
||||
{ title: '自行封装 component', href: 'https://segmentfault.com/a/1190000009090836' },
|
||||
{ title: '优雅的使用 icon', href: 'https://segmentfault.com/a/https://segmentfault.com/a/1190000012213278' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.documentation-container {
|
||||
margin: 50px;
|
||||
.document-btn {
|
||||
float: left;
|
||||
margin-left: 50px;
|
||||
vertical-align: middle;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
background: black;
|
||||
color: white;
|
||||
height: 60px;
|
||||
width: 200px;
|
||||
line-height: 60px;
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,98 +0,0 @@
|
||||
<template>
|
||||
<el-table :data="list" border fit highlight-current-row style="width: 100%">
|
||||
|
||||
<el-table-column align="center" label="ID" width="65" v-loading="loading"
|
||||
element-loading-text="请给我点时间!">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.id}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column width="180px" align="center" label="Date">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}')}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column min-width="300px" label="Title">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.title}}</span>
|
||||
<el-tag>{{scope.row.type}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column width="110px" align="center" label="Author">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.author}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column width="120px" label="Importance">
|
||||
<template slot-scope="scope">
|
||||
<svg-icon v-for="n in +scope.row.importance" icon-class="star" :key="n"></svg-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column align="center" label="Readings" width="95">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.pageviews}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column class-name="status-col" label="Status" width="110">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.status | statusFilter">{{scope.row.status}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { fetchList } from '@/api/article'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'CN'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
list: null,
|
||||
listQuery: {
|
||||
page: 1,
|
||||
limit: 5,
|
||||
type: this.type,
|
||||
sort: '+id'
|
||||
},
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
statusFilter(status) {
|
||||
const statusMap = {
|
||||
published: 'success',
|
||||
draft: 'info',
|
||||
deleted: 'danger'
|
||||
}
|
||||
return statusMap[status]
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
this.loading = true
|
||||
this.$emit('create') // for test
|
||||
fetchList(this.listQuery).then(response => {
|
||||
this.list = response.data.items
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
<template>
|
||||
<div class="tab-container">
|
||||
<el-tag>mounted times :{{createdTimes}}</el-tag>
|
||||
<el-tabs style='margin-top:15px;' v-model="activeName" type="border-card">
|
||||
<el-tab-pane v-for="item in tabMapOptions" :label="item.label" :key='item.key' :name="item.key">
|
||||
<keep-alive>
|
||||
<tab-pane v-if='activeName==item.key' :type='item.key' @create='showCreatedTimes'></tab-pane>
|
||||
</keep-alive>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import tabPane from './components/tabPane'
|
||||
|
||||
export default {
|
||||
name: 'tab',
|
||||
components: { tabPane },
|
||||
data() {
|
||||
return {
|
||||
tabMapOptions: [
|
||||
{ label: 'China', key: 'CN' },
|
||||
{ label: 'USA', key: 'US' },
|
||||
{ label: 'Japan', key: 'JP' },
|
||||
{ label: 'Eurozone', key: 'EU' }
|
||||
],
|
||||
activeName: 'CN',
|
||||
createdTimes: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showCreatedTimes() {
|
||||
this.createdTimes = this.createdTimes + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tab-container{
|
||||
margin: 30px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,354 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container calendar-list-container">
|
||||
<div class="filter-container">
|
||||
<el-input @keyup.enter.native="handleFilter" style="width: 200px;" class="filter-item" :placeholder="$t('table.title')" v-model="listQuery.title">
|
||||
</el-input>
|
||||
<el-select clearable style="width: 90px" class="filter-item" v-model="listQuery.importance" :placeholder="$t('table.importance')">
|
||||
<el-option v-for="item in importanceOptions" :key="item" :label="item" :value="item">
|
||||
</el-option>
|
||||
</el-select>
|
||||
<el-select clearable class="filter-item" style="width: 130px" v-model="listQuery.type" :placeholder="$t('table.type')">
|
||||
<el-option v-for="item in calendarTypeOptions" :key="item.key" :label="item.display_name+'('+item.key+')'" :value="item.key">
|
||||
</el-option>
|
||||
</el-select>
|
||||
<el-select @change='handleFilter' style="width: 140px" class="filter-item" v-model="listQuery.sort">
|
||||
<el-option v-for="item in sortOptions" :key="item.key" :label="item.label" :value="item.key">
|
||||
</el-option>
|
||||
</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>
|
||||
<el-button class="filter-item" type="primary" :loading="downloadLoading" v-waves icon="el-icon-download" @click="handleDownload">{{$t('table.export')}}</el-button>
|
||||
<el-checkbox class="filter-item" style='margin-left:15px;' @change='tableKey=tableKey+1' v-model="showReviewer">{{$t('table.reviewer')}}</el-checkbox>
|
||||
</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="65">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.id}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="150px" align="center" :label="$t('table.date')">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}')}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column min-width="150px" :label="$t('table.title')">
|
||||
<template slot-scope="scope">
|
||||
<span class="link-type" @click="handleUpdate(scope.row)">{{scope.row.title}}</span>
|
||||
<el-tag>{{scope.row.type | typeFilter}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="110px" align="center" :label="$t('table.author')">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.author}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="110px" v-if='showReviewer' align="center" :label="$t('table.reviewer')">
|
||||
<template slot-scope="scope">
|
||||
<span style='color:red;'>{{scope.row.reviewer}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="80px" :label="$t('table.importance')">
|
||||
<template slot-scope="scope">
|
||||
<svg-icon v-for="n in +scope.row.importance" icon-class="star" class="meta-item__icon" :key="n"></svg-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.readings')" width="95">
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.pageviews" class="link-type" @click='handleFetchPv(scope.row.pageviews)'>{{scope.row.pageviews}}</span>
|
||||
<span v-else>0</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column class-name="status-col" :label="$t('table.status')" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.status | statusFilter">{{scope.row.status}}</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.status!='published'" size="mini" type="success" @click="handleModifyStatus(scope.row,'published')">{{$t('table.publish')}}
|
||||
</el-button>
|
||||
<el-button v-if="scope.row.status!='draft'" size="mini" @click="handleModifyStatus(scope.row,'draft')">{{$t('table.draft')}}
|
||||
</el-button>
|
||||
<el-button v-if="scope.row.status!='deleted'" size="mini" type="danger" @click="handleModifyStatus(scope.row,'deleted')">{{$t('table.delete')}}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pagination-container">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page.sync="listQuery.page"
|
||||
:page-sizes="[10,20,30, 50]" :page-size="listQuery.limit" 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.type')" prop="type">
|
||||
<el-select class="filter-item" v-model="temp.type" placeholder="Please select">
|
||||
<el-option v-for="item in calendarTypeOptions" :key="item.key" :label="item.display_name" :value="item.key">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('table.date')" prop="timestamp">
|
||||
<el-date-picker v-model="temp.timestamp" type="datetime" placeholder="Please pick a date">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('table.title')" prop="title">
|
||||
<el-input v-model="temp.title"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('table.status')">
|
||||
<el-select class="filter-item" v-model="temp.status" placeholder="Please select">
|
||||
<el-option v-for="item in statusOptions" :key="item" :label="item" :value="item">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('table.importance')">
|
||||
<el-rate style="margin-top:8px;" v-model="temp.importance" :colors="['#99A9BF', '#F7BA2A', '#FF9900']" :max='3'></el-rate>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('table.remark')">
|
||||
<el-input type="textarea" :autosize="{ minRows: 2, maxRows: 4}" placeholder="Please input" v-model="temp.remark">
|
||||
</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>
|
||||
</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, fetchPv, createArticle, updateArticle } from '@/api/article'
|
||||
import waves from '@/directive/waves' // 水波纹指令
|
||||
import { parseTime } from '@/utils'
|
||||
|
||||
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
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableKey: 0,
|
||||
list: null,
|
||||
total: null,
|
||||
listLoading: true,
|
||||
listQuery: {
|
||||
page: 1,
|
||||
limit: 20,
|
||||
importance: undefined,
|
||||
title: undefined,
|
||||
type: undefined,
|
||||
sort: '+id'
|
||||
},
|
||||
importanceOptions: [1, 2, 3],
|
||||
calendarTypeOptions,
|
||||
sortOptions: [{ label: 'ID Ascending', key: '+id' }, { label: 'ID Descending', key: '-id' }],
|
||||
statusOptions: ['published', 'draft', 'deleted'],
|
||||
showReviewer: false,
|
||||
temp: {
|
||||
id: undefined,
|
||||
importance: 1,
|
||||
remark: '',
|
||||
timestamp: new Date(),
|
||||
title: '',
|
||||
type: '',
|
||||
status: 'published'
|
||||
},
|
||||
dialogFormVisible: false,
|
||||
dialogStatus: '',
|
||||
textMap: {
|
||||
update: 'Edit',
|
||||
create: 'Create'
|
||||
},
|
||||
dialogPvVisible: false,
|
||||
pvData: [],
|
||||
rules: {
|
||||
type: [{ required: true, message: 'type is required', trigger: 'change' }],
|
||||
timestamp: [{ type: 'date', required: true, message: 'timestamp is required', trigger: 'change' }],
|
||||
title: [{ required: true, message: 'title is required', trigger: 'blur' }]
|
||||
},
|
||||
downloadLoading: false
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
statusFilter(status) {
|
||||
const statusMap = {
|
||||
published: 'success',
|
||||
draft: 'info',
|
||||
deleted: '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.items
|
||||
this.total = response.data.total
|
||||
this.listLoading = false
|
||||
})
|
||||
},
|
||||
handleFilter() {
|
||||
this.listQuery.page = 1
|
||||
this.getList()
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
this.listQuery.limit = val
|
||||
this.getList()
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.listQuery.page = val
|
||||
this.getList()
|
||||
},
|
||||
handleModifyStatus(row, status) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success'
|
||||
})
|
||||
row.status = status
|
||||
},
|
||||
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) {
|
||||
this.temp.id = parseInt(Math.random() * 100) + 1024 // mock a id
|
||||
this.temp.author = 'vue-element-admin'
|
||||
createArticle(this.temp).then(() => {
|
||||
this.list.unshift(this.temp)
|
||||
this.dialogFormVisible = false
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '创建成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
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)
|
||||
tempData.timestamp = +new Date(tempData.timestamp) // change Thu Nov 30 2017 16:41:05 GMT+0800 (CST) to 1512031311464
|
||||
updateArticle(tempData).then(() => {
|
||||
for (const v of this.list) {
|
||||
if (v.id === this.temp.id) {
|
||||
const index = this.list.indexOf(v)
|
||||
this.list.splice(index, 1, this.temp)
|
||||
break
|
||||
}
|
||||
}
|
||||
this.dialogFormVisible = false
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '更新成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
handleDelete(row) {
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '删除成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
const index = this.list.indexOf(row)
|
||||
this.list.splice(index, 1)
|
||||
},
|
||||
handleFetchPv(pv) {
|
||||
fetchPv(pv).then(response => {
|
||||
this.pvData = response.data.pvData
|
||||
this.dialogPvVisible = true
|
||||
})
|
||||
},
|
||||
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,147 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container calendar-list-container">
|
||||
<!-- Note that row-key is necessary to get a correct row order. -->
|
||||
<el-table :data="list" row-key="id" v-loading.body="listLoading" border fit highlight-current-row style="width: 100%">
|
||||
|
||||
<el-table-column align="center" label="ID" width="65">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.id}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column width="180px" align="center" label="Date">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}')}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column min-width="300px" label="Title">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.title}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column width="110px" align="center" label="Author">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.author}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column width="100px" label="Importance">
|
||||
<template slot-scope="scope">
|
||||
<svg-icon v-for="n in +scope.row.importance" icon-class="star" class="icon-star" :key="n"></svg-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column align="center" label="Readings" width="95">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.pageviews}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column class-name="status-col" label="Status" width="110">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.status | statusFilter">{{scope.row.status}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column align="center" label="Drag" width="80">
|
||||
<template slot-scope="scope">
|
||||
<svg-icon class='drag-handler' icon-class="drag"></svg-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
<!-- $t is vue-i18n global function to translate lang (lang in @/lang) -->
|
||||
<div class='show-d'>{{$t('table.dragTips1')}} : {{ oldList}}</div>
|
||||
<div class='show-d'>{{$t('table.dragTips2')}} : {{newList}}</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { fetchList } from '@/api/article'
|
||||
import Sortable from 'sortablejs'
|
||||
|
||||
export default {
|
||||
name: 'dragTable',
|
||||
data() {
|
||||
return {
|
||||
list: null,
|
||||
total: null,
|
||||
listLoading: true,
|
||||
listQuery: {
|
||||
page: 1,
|
||||
limit: 10
|
||||
},
|
||||
sortable: null,
|
||||
oldList: [],
|
||||
newList: []
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
statusFilter(status) {
|
||||
const statusMap = {
|
||||
published: 'success',
|
||||
draft: 'info',
|
||||
deleted: 'danger'
|
||||
}
|
||||
return statusMap[status]
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
this.listLoading = true
|
||||
fetchList(this.listQuery).then(response => {
|
||||
this.list = response.data.items
|
||||
this.total = response.data.total
|
||||
this.listLoading = false
|
||||
this.oldList = this.list.map(v => v.id)
|
||||
this.newList = this.oldList.slice()
|
||||
this.$nextTick(() => {
|
||||
this.setSort()
|
||||
})
|
||||
})
|
||||
},
|
||||
setSort() {
|
||||
const el = document.querySelectorAll('.el-table__body-wrapper > table > tbody')[0]
|
||||
this.sortable = Sortable.create(el, {
|
||||
ghostClass: 'sortable-ghost', // Class name for the drop placeholder,
|
||||
onEnd: evt => {
|
||||
const targetRow = this.list.splice(evt.oldIndex, 1)[0]
|
||||
this.list.splice(evt.newIndex, 0, targetRow)
|
||||
|
||||
// for show the changes, you can delete in you code
|
||||
const tempIndex = this.newList.splice(evt.oldIndex, 1)[0]
|
||||
this.newList.splice(evt.newIndex, 0, tempIndex)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.sortable-ghost{
|
||||
opacity: .8;
|
||||
color: #fff!important;
|
||||
background: #42b983!important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.icon-star{
|
||||
margin-right:2px;
|
||||
}
|
||||
.drag-handler{
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.show-d{
|
||||
margin-top: 15px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,58 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
|
||||
<div class="filter-container">
|
||||
<el-checkbox-group v-model="checkboxVal">
|
||||
<el-checkbox label="apple">apple</el-checkbox>
|
||||
<el-checkbox label="banana">banana</el-checkbox>
|
||||
<el-checkbox label="orange">orange</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
|
||||
<el-table :data="tableData" :key='key' border fit highlight-current-row style="width: 100%">
|
||||
<el-table-column prop="name" label="fruitName" width="180"></el-table-column>
|
||||
<el-table-column :key='fruit' v-for='fruit in formThead' :label="fruit">
|
||||
<template slot-scope="scope">
|
||||
{{scope.row[fruit]}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const defaultFormThead = ['apple', 'banana']
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
tableData: [
|
||||
{
|
||||
name: 'fruit-1',
|
||||
apple: 'apple-10',
|
||||
banana: 'banana-10',
|
||||
orange: 'orange-10'
|
||||
},
|
||||
{
|
||||
name: 'fruit-2',
|
||||
apple: 'apple-20',
|
||||
banana: 'banana-20',
|
||||
orange: 'orange-20'
|
||||
}
|
||||
],
|
||||
key: 1, // table key
|
||||
formTheadOptions: ['apple', 'banana', 'orange'],
|
||||
checkboxVal: defaultFormThead, // checkboxVal
|
||||
formThead: defaultFormThead // 默认表头 Default header
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
checkboxVal(valArr) {
|
||||
this.formThead = this.formTheadOptions.filter(i => valArr.indexOf(i) >= 0)
|
||||
this.key = this.key + 1// 为了保证table 每次都会重渲 In order to ensure the table will be re-rendered each time
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<div style='margin:0 0 5px 20px'>{{$t('table.dynamicTips1')}}</div>
|
||||
<fixed-thead></fixed-thead>
|
||||
|
||||
<div style='margin:30px 0 5px 20px'>{{$t('table.dynamicTips2')}}</div>
|
||||
<unfixed-thead></unfixed-thead>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import fixedThead from './fixedThead'
|
||||
import unfixedThead from './unfixedThead'
|
||||
|
||||
export default {
|
||||
name: 'dynamicTable',
|
||||
components: { fixedThead, unfixedThead }
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
|
||||
<div class="filter-container">
|
||||
<el-checkbox-group v-model="formThead">
|
||||
<el-checkbox label="apple">apple</el-checkbox>
|
||||
<el-checkbox label="banana">banana</el-checkbox>
|
||||
<el-checkbox label="orange">orange</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
|
||||
<el-table :data="tableData" border fit highlight-current-row style="width: 100%">
|
||||
<el-table-column prop="name" label="fruitName" width="180">
|
||||
</el-table-column>
|
||||
<el-table-column :key='fruit' v-for='fruit in formThead' :label="fruit">
|
||||
<template slot-scope="scope">
|
||||
{{scope.row[fruit]}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
tableData: [
|
||||
{
|
||||
name: 'fruit-1',
|
||||
apple: 'apple-10',
|
||||
banana: 'banana-10',
|
||||
orange: 'orange-10'
|
||||
},
|
||||
{
|
||||
name: 'fruit-2',
|
||||
apple: 'apple-20',
|
||||
banana: 'banana-20',
|
||||
orange: 'orange-20'
|
||||
}
|
||||
],
|
||||
formThead: ['apple', 'banana']
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,18 +0,0 @@
|
||||
<template>
|
||||
<transition name="fade" mode="out-in">
|
||||
<keep-alive :include='cachedViews'>
|
||||
<router-view></router-view>
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TableMain',
|
||||
computed: {
|
||||
cachedViews() {
|
||||
return this.$store.state.tagsView.cachedViews
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,129 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container calendar-list-container">
|
||||
|
||||
<el-table :data="list" v-loading.body="listLoading" border fit highlight-current-row style="width: 100%">
|
||||
|
||||
<el-table-column align="center" label="ID" width="80">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.id}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column width="180px" align="center" label="Date">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}')}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column width="120px" align="center" label="Author">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.author}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column width="100px" label="Importance">
|
||||
<template slot-scope="scope">
|
||||
<svg-icon v-for="n in +scope.row.importance" icon-class="star" class="meta-item__icon" :key="n"></svg-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column class-name="status-col" label="Status" width="110">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.status | statusFilter">{{scope.row.status}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column min-width="300px" label="Title">
|
||||
<template slot-scope="scope">
|
||||
<template v-if="scope.row.edit">
|
||||
<el-input class="edit-input" size="small" v-model="scope.row.title"></el-input>
|
||||
<el-button class='cancel-btn' size="small" icon="el-icon-refresh" type="warning" @click="cancelEdit(scope.row)">cancel</el-button>
|
||||
</template>
|
||||
<span v-else>{{ scope.row.title }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column align="center" label="Actions" width="120">
|
||||
<template slot-scope="scope">
|
||||
<el-button v-if="scope.row.edit" type="success" @click="confirmEdit(scope.row)" size="small" icon="el-icon-circle-check-outline">Ok</el-button>
|
||||
<el-button v-else type="primary" @click='scope.row.edit=!scope.row.edit' size="small" icon="el-icon-edit">Edit</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { fetchList } from '@/api/article'
|
||||
|
||||
export default {
|
||||
name: 'inlineEditTable',
|
||||
data() {
|
||||
return {
|
||||
list: null,
|
||||
listLoading: true,
|
||||
listQuery: {
|
||||
page: 1,
|
||||
limit: 10
|
||||
}
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
statusFilter(status) {
|
||||
const statusMap = {
|
||||
published: 'success',
|
||||
draft: 'info',
|
||||
deleted: 'danger'
|
||||
}
|
||||
return statusMap[status]
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
this.listLoading = true
|
||||
fetchList(this.listQuery).then(response => {
|
||||
const items = response.data.items
|
||||
this.list = items.map(v => {
|
||||
this.$set(v, 'edit', false) // https://vuejs.org/v2/guide/reactivity.html
|
||||
|
||||
v.originalTitle = v.title // will be used when user click the cancel botton
|
||||
|
||||
return v
|
||||
})
|
||||
this.listLoading = false
|
||||
})
|
||||
},
|
||||
cancelEdit(row) {
|
||||
row.title = row.originalTitle
|
||||
row.edit = false
|
||||
this.$message({
|
||||
message: 'The title has been restored to the original value',
|
||||
type: 'warning'
|
||||
})
|
||||
},
|
||||
confirmEdit(row) {
|
||||
row.edit = false
|
||||
row.originalTitle = row.title
|
||||
this.$message({
|
||||
message: 'The title has been edited',
|
||||
type: 'success'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.edit-input {
|
||||
padding-right: 100px;
|
||||
}
|
||||
.cancel-btn {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,84 +0,0 @@
|
||||
<template>
|
||||
<!-- $t is vue-i18n global function to translate lang -->
|
||||
<div class="app-container">
|
||||
<el-input style='width:340px;' :placeholder="$t('excel.placeholder')" prefix-icon="el-icon-document" v-model="filename"></el-input>
|
||||
<el-button style='margin-bottom:20px;' type="primary" icon="document" @click="handleDownload" :loading="downloadLoading">{{$t('excel.export')}} excel</el-button>
|
||||
<el-table :data="list" v-loading.body="listLoading" element-loading-text="拼命加载中" border fit highlight-current-row>
|
||||
<el-table-column align="center" label='Id' width="95">
|
||||
<template slot-scope="scope">
|
||||
{{scope.$index}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Title">
|
||||
<template slot-scope="scope">
|
||||
{{scope.row.title}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Author" width="110" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag>{{scope.row.author}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Readings" width="115" align="center">
|
||||
<template slot-scope="scope">
|
||||
{{scope.row.pageviews}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="Date" width="220">
|
||||
<template slot-scope="scope">
|
||||
<i class="el-icon-time"></i>
|
||||
<span>{{scope.row.timestamp | parseTime('{y}-{m}-{d} {h}:{i}')}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { fetchList } from '@/api/article'
|
||||
import { parseTime } from '@/utils'
|
||||
|
||||
export default {
|
||||
name: 'exportExcel',
|
||||
data() {
|
||||
return {
|
||||
list: null,
|
||||
listLoading: true,
|
||||
downloadLoading: false,
|
||||
filename: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
fetchData() {
|
||||
this.listLoading = true
|
||||
fetchList().then(response => {
|
||||
this.list = response.data.items
|
||||
this.listLoading = false
|
||||
})
|
||||
},
|
||||
handleDownload() {
|
||||
this.downloadLoading = true
|
||||
import('@/vendor/Export2Excel').then(excel => {
|
||||
const tHeader = ['Id', 'Title', 'Author', 'Readings', 'Date']
|
||||
const filterVal = ['id', 'title', 'author', 'pageviews', 'display_time']
|
||||
const list = this.list
|
||||
const data = this.formatJson(filterVal, list)
|
||||
excel.export_json_to_excel(tHeader, data, this.filename)
|
||||
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,91 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- $t is vue-i18n global function to translate lang -->
|
||||
<el-input style='width:340px;' :placeholder="$t('excel.placeholder')" prefix-icon="el-icon-document" v-model="filename"></el-input>
|
||||
<el-button style='margin-bottom:20px' type="primary" icon="document" @click="handleDownload" :loading="downloadLoading">{{$t('excel.selectedExport')}}</el-button>
|
||||
<el-table :data="list" v-loading.body="listLoading" element-loading-text="拼命加载中" border fit highlight-current-row @selection-change="handleSelectionChange"
|
||||
ref="multipleTable">
|
||||
<el-table-column type="selection" align="center"></el-table-column>
|
||||
<el-table-column align="center" label='Id' width="95">
|
||||
<template slot-scope="scope">
|
||||
{{scope.$index}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Title">
|
||||
<template slot-scope="scope">
|
||||
{{scope.row.title}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Author" width="110" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag>{{scope.row.author}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Readings" width="115" align="center">
|
||||
<template slot-scope="scope">
|
||||
{{scope.row.pageviews}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="PDate" width="220">
|
||||
<template slot-scope="scope">
|
||||
<i class="el-icon-time"></i>
|
||||
<span>{{scope.row.display_time}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { fetchList } from '@/api/article'
|
||||
|
||||
export default {
|
||||
name: 'selectExcel',
|
||||
data() {
|
||||
return {
|
||||
list: null,
|
||||
listLoading: true,
|
||||
multipleSelection: [],
|
||||
downloadLoading: false,
|
||||
filename: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
fetchData() {
|
||||
this.listLoading = true
|
||||
fetchList(this.listQuery).then(response => {
|
||||
this.list = response.data.items
|
||||
this.listLoading = false
|
||||
})
|
||||
},
|
||||
handleSelectionChange(val) {
|
||||
this.multipleSelection = val
|
||||
},
|
||||
handleDownload() {
|
||||
if (this.multipleSelection.length) {
|
||||
this.downloadLoading = true
|
||||
import('@/vendor/Export2Excel').then(excel => {
|
||||
const tHeader = ['Id', 'Title', 'Author', 'Readings', 'Date']
|
||||
const filterVal = ['id', 'title', 'author', 'pageviews', 'display_time']
|
||||
const list = this.multipleSelection
|
||||
const data = this.formatJson(filterVal, list)
|
||||
excel.export_json_to_excel(tHeader, data, this.filename)
|
||||
this.$refs.multipleTable.clearSelection()
|
||||
this.downloadLoading = false
|
||||
})
|
||||
} else {
|
||||
this.$message({
|
||||
message: 'Please select at least one item',
|
||||
type: 'warning'
|
||||
})
|
||||
}
|
||||
},
|
||||
formatJson(filterVal, jsonData) {
|
||||
return jsonData.map(v => filterVal.map(j => v[j]))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,30 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<upload-excel-component @on-selected-file='selected'></upload-excel-component>
|
||||
<el-table :data="tableData" border highlight-current-row style="width: 100%;margin-top:20px;">
|
||||
<el-table-column v-for='item of tableHeader' :prop="item" :label="item" :key='item'>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import UploadExcelComponent from '@/components/UploadExcel/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'uploadExcel',
|
||||
components: { UploadExcelComponent },
|
||||
data() {
|
||||
return {
|
||||
tableData: [],
|
||||
tableHeader: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selected(data) {
|
||||
this.tableData = data.results
|
||||
this.tableHeader = data.header
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,314 +0,0 @@
|
||||
<template>
|
||||
<div class="createPost-container">
|
||||
<el-form class="form-container" :model="postForm" :rules="rules" ref="postForm">
|
||||
|
||||
<sticky :className="'sub-navbar '+postForm.status">
|
||||
<template v-if="fetchSuccess">
|
||||
|
||||
<router-link style="margin-right:15px;" v-show='isEdit' :to="{ path:'create-form'}">
|
||||
<el-button type="info">创建form</el-button>
|
||||
</router-link>
|
||||
|
||||
<el-dropdown trigger="click">
|
||||
<el-button plain>{{!postForm.comment_disabled?'评论已打开':'评论已关闭'}}
|
||||
<i class="el-icon-caret-bottom el-icon--right"></i>
|
||||
</el-button>
|
||||
<el-dropdown-menu class="no-padding" slot="dropdown">
|
||||
<el-dropdown-item>
|
||||
<el-radio-group style="padding: 10px;" v-model="postForm.comment_disabled">
|
||||
<el-radio :label="true">关闭评论</el-radio>
|
||||
<el-radio :label="false">打开评论</el-radio>
|
||||
</el-radio-group>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
|
||||
<el-dropdown trigger="click">
|
||||
<el-button plain>平台
|
||||
<i class="el-icon-caret-bottom el-icon--right"></i>
|
||||
</el-button>
|
||||
<el-dropdown-menu class="no-border" slot="dropdown">
|
||||
<el-checkbox-group v-model="postForm.platforms" style="padding: 5px 15px;">
|
||||
<el-checkbox v-for="item in platformsOptions" :label="item.key" :key="item.key">
|
||||
{{item.name}}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
|
||||
<el-dropdown trigger="click">
|
||||
<el-button plain>
|
||||
外链
|
||||
<i class="el-icon-caret-bottom el-icon--right"></i>
|
||||
</el-button>
|
||||
<el-dropdown-menu class="no-padding no-border" style="width:300px" slot="dropdown">
|
||||
<el-form-item label-width="0px" style="margin-bottom: 0px" prop="source_uri">
|
||||
<el-input placeholder="请输入内容" v-model="postForm.source_uri">
|
||||
<template slot="prepend">填写url</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
|
||||
<el-button v-loading="loading" style="margin-left: 10px;" type="success" @click="submitForm()">发布
|
||||
</el-button>
|
||||
<el-button v-loading="loading" type="warning" @click="draftForm">草稿</el-button>
|
||||
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-tag>发送异常错误,刷新页面,或者联系程序员</el-tag>
|
||||
</template>
|
||||
|
||||
</sticky>
|
||||
|
||||
<div class="createPost-main-container">
|
||||
<el-row>
|
||||
<el-col :span="21">
|
||||
<el-form-item style="margin-bottom: 40px;" prop="title">
|
||||
<MDinput name="name" v-model="postForm.title" required :maxlength="100">
|
||||
标题
|
||||
</MDinput>
|
||||
<span v-show="postForm.title.length>=26" class='title-prompt'>app可能会显示不全</span>
|
||||
</el-form-item>
|
||||
|
||||
<div class="postInfo-container">
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<el-form-item label-width="45px" label="作者:" class="postInfo-container-item">
|
||||
<multiselect v-model="postForm.author" :options="userLIstOptions" @search-change="getRemoteUserList" placeholder="搜索用户" selectLabel="选择"
|
||||
deselectLabel="删除" track-by="key" :internalSearch="false" label="key">
|
||||
<span slot='noResult'>无结果</span>
|
||||
</multiselect>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="8">
|
||||
<el-tooltip class="item" effect="dark" content="将替换作者" placement="top">
|
||||
<el-form-item label-width="50px" label="来源:" class="postInfo-container-item">
|
||||
<el-input placeholder="将替换作者" style='min-width:150px;' v-model="postForm.source_name">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-tooltip>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="8">
|
||||
<el-form-item label-width="80px" label="发布时间:" class="postInfo-container-item">
|
||||
<el-date-picker v-model="postForm.display_time" type="datetime" format="yyyy-MM-dd HH:mm:ss" placeholder="选择日期时间">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item style="margin-bottom: 40px;" label-width="45px" label="摘要:">
|
||||
<el-input type="textarea" class="article-textarea" :rows="1" autosize placeholder="请输入内容" v-model="postForm.content_short">
|
||||
</el-input>
|
||||
<span class="word-counter" v-show="contentShortLength">{{contentShortLength}}字</span>
|
||||
</el-form-item>
|
||||
|
||||
<div class="editor-container">
|
||||
<tinymce :height=400 ref="editor" v-model="postForm.content"></tinymce>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 20px;">
|
||||
<Upload v-model="postForm.image_uri"></Upload>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Tinymce from '@/components/Tinymce'
|
||||
import Upload from '@/components/Upload/singleImage3'
|
||||
import MDinput from '@/components/MDinput'
|
||||
import Multiselect from 'vue-multiselect'// 使用的一个多选框组件,element-ui的select不能满足所有需求
|
||||
import 'vue-multiselect/dist/vue-multiselect.min.css'// 多选框组件css
|
||||
import Sticky from '@/components/Sticky' // 粘性header组件
|
||||
import { validateURL } from '@/utils/validate'
|
||||
import { fetchArticle } from '@/api/article'
|
||||
import { userSearch } from '@/api/remoteSearch'
|
||||
|
||||
const defaultForm = {
|
||||
status: 'draft',
|
||||
title: '', // 文章题目
|
||||
content: '', // 文章内容
|
||||
content_short: '', // 文章摘要
|
||||
source_uri: '', // 文章外链
|
||||
image_uri: '', // 文章图片
|
||||
source_name: '', // 文章外部作者
|
||||
display_time: undefined, // 前台展示时间
|
||||
id: undefined,
|
||||
platforms: ['a-platform'],
|
||||
comment_disabled: false
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'articleDetail',
|
||||
components: { Tinymce, MDinput, Upload, Multiselect, Sticky },
|
||||
props: {
|
||||
isEdit: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const validateRequire = (rule, value, callback) => {
|
||||
if (value === '') {
|
||||
this.$message({
|
||||
message: rule.field + '为必传项',
|
||||
type: 'error'
|
||||
})
|
||||
callback(null)
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
const validateSourceUri = (rule, value, callback) => {
|
||||
if (value) {
|
||||
if (validateURL(value)) {
|
||||
callback()
|
||||
} else {
|
||||
this.$message({
|
||||
message: '外链url填写不正确',
|
||||
type: 'error'
|
||||
})
|
||||
callback(null)
|
||||
}
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
return {
|
||||
postForm: Object.assign({}, defaultForm),
|
||||
fetchSuccess: true,
|
||||
loading: false,
|
||||
userLIstOptions: [],
|
||||
platformsOptions: [
|
||||
{ key: 'a-platform', name: 'a-platform' },
|
||||
{ key: 'b-platform', name: 'b-platform' },
|
||||
{ key: 'c-platform', name: 'c-platform' }
|
||||
],
|
||||
rules: {
|
||||
image_uri: [{ validator: validateRequire }],
|
||||
title: [{ validator: validateRequire }],
|
||||
content: [{ validator: validateRequire }],
|
||||
source_uri: [{ validator: validateSourceUri, trigger: 'blur' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
contentShortLength() {
|
||||
return this.postForm.content_short.length
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.isEdit) {
|
||||
this.fetchData()
|
||||
} else {
|
||||
this.postForm = Object.assign({}, defaultForm)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchData() {
|
||||
fetchArticle().then(response => {
|
||||
this.postForm = response.data
|
||||
}).catch(err => {
|
||||
this.fetchSuccess = false
|
||||
console.log(err)
|
||||
})
|
||||
},
|
||||
submitForm() {
|
||||
this.postForm.display_time = parseInt(this.display_time / 1000)
|
||||
console.log(this.postForm)
|
||||
this.$refs.postForm.validate(valid => {
|
||||
if (valid) {
|
||||
this.loading = true
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '发布文章成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
this.postForm.status = 'published'
|
||||
this.loading = false
|
||||
} else {
|
||||
console.log('error submit!!')
|
||||
return false
|
||||
}
|
||||
})
|
||||
},
|
||||
draftForm() {
|
||||
if (this.postForm.content.length === 0 || this.postForm.title.length === 0) {
|
||||
this.$message({
|
||||
message: '请填写必要的标题和内容',
|
||||
type: 'warning'
|
||||
})
|
||||
return
|
||||
}
|
||||
this.$message({
|
||||
message: '保存成功',
|
||||
type: 'success',
|
||||
showClose: true,
|
||||
duration: 1000
|
||||
})
|
||||
this.postForm.status = 'draft'
|
||||
},
|
||||
getRemoteUserList(query) {
|
||||
userSearch(query).then(response => {
|
||||
if (!response.data.items) return
|
||||
console.log(response)
|
||||
this.userLIstOptions = response.data.items.map(v => ({
|
||||
key: v.name
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
@import "src/styles/mixin.scss";
|
||||
.title-prompt{
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
font-size: 12px;
|
||||
top:10px;
|
||||
color:#ff4949;
|
||||
}
|
||||
.createPost-container {
|
||||
position: relative;
|
||||
.createPost-main-container {
|
||||
padding: 40px 45px 20px 50px;
|
||||
.postInfo-container {
|
||||
position: relative;
|
||||
@include clearfix;
|
||||
margin-bottom: 10px;
|
||||
.postInfo-container-item {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
.editor-container {
|
||||
min-height: 500px;
|
||||
margin: 0 0 30px;
|
||||
.editor-upload-btn-container {
|
||||
text-align: right;
|
||||
margin-right: 10px;
|
||||
.editor-upload-btn {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.word-counter {
|
||||
width: 40px;
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
top: 0px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<template>
|
||||
<article-detail :is-edit='false'></article-detail>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ArticleDetail from './components/ArticleDetail'
|
||||
|
||||
export default {
|
||||
name: 'createForm',
|
||||
components: { ArticleDetail }
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<template>
|
||||
<article-detail :is-edit='true'></article-detail>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ArticleDetail from './components/ArticleDetail'
|
||||
|
||||
export default {
|
||||
name: 'editForm',
|
||||
components: { ArticleDetail }
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<div :class="className" :id="chartId" :style="{height:height,width:width}"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import echarts from 'echarts'
|
||||
require('echarts/theme/macarons') // echarts theme
|
||||
|
||||
export default {
|
||||
props: {
|
||||
chartId: {
|
||||
type: String,
|
||||
default: 'daily-traffic-div'
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: 'chart'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '250px'
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
upload: 90, // 上行
|
||||
download: 100, // 下行
|
||||
text: '今日流量'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartDom: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initChart()
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
return
|
||||
}
|
||||
this.chartDom.dispose()
|
||||
this.chartDom = null
|
||||
},
|
||||
methods: {
|
||||
initChart() {
|
||||
this.chartDom = document.getElementById(this.chartId)
|
||||
this.myChart = echarts.init(this.chartDom)
|
||||
const option = {
|
||||
title: {
|
||||
text: this.data.text,
|
||||
subtext: '上行:' + this.data.upload + '\n\n' + '下行:' + this.data.download,
|
||||
textStyle: {
|
||||
fontSize: 18,
|
||||
fontWeight: 800,
|
||||
align: 'center'
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
data: ['上行', '下行'],
|
||||
orient: 'vertical',
|
||||
left: 'right'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: this.data.text,
|
||||
type: 'pie',
|
||||
radius: [45, 65],
|
||||
center: ['50%', '58%'],
|
||||
// 隐藏指示线
|
||||
labelLine: {
|
||||
normal: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
// 隐藏圆环上文字
|
||||
label: {
|
||||
normal: {
|
||||
show: false,
|
||||
position: 'center'
|
||||
}
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: this.data.upload,
|
||||
name: '上行',
|
||||
lineStyle: {
|
||||
normal: {
|
||||
color: '#2B81B1'
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: '#2B81B1'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
value: this.data.download,
|
||||
name: '下行',
|
||||
lineStyle: {
|
||||
normal: {
|
||||
color: '#dbebf7'
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: '#dbebf7'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
option && this.myChart.setOption(option)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<div>
|
||||
<div :class="className" id="license-div" :style="{height:height,width:width}"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import echarts from 'echarts'
|
||||
require('echarts/theme/macarons') // echarts theme
|
||||
|
||||
export default {
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: 'chart'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '250px'
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
onLine: 90, // 进度条最大值
|
||||
total: 100 // 当前进度
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartDom: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initChart()
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
return
|
||||
}
|
||||
this.chartDom.dispose()
|
||||
this.chartDom = null
|
||||
},
|
||||
methods: {
|
||||
initChart() {
|
||||
this.chartDom = document.getElementById('license-div')
|
||||
this.myChart = echarts.init(this.chartDom)
|
||||
const option = {
|
||||
title: {
|
||||
text: 'License统计',
|
||||
subtext: '在线数:' + this.data.onLine + '\n\n' + '离线数:' + (this.data.total - this.data.onLine),
|
||||
textStyle: {
|
||||
fontSize: 18,
|
||||
fontWeight: 800,
|
||||
align: 'center'
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
data: ['在线数', '离线数'],
|
||||
orient: 'vertical',
|
||||
left: 'right'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
// 第一张圆环
|
||||
name: 'License',
|
||||
type: 'pie',
|
||||
radius: [45, 65],
|
||||
center: ['50%', '58%'],
|
||||
avoidLabelOverlap: false,
|
||||
// 隐藏指示线
|
||||
labelLine: {
|
||||
normal: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
// 隐藏圆环上文字
|
||||
label: {
|
||||
normal: {
|
||||
show: false,
|
||||
position: 'center'
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 40,
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
}
|
||||
},
|
||||
data: [
|
||||
// value当前进度 + 颜色
|
||||
{
|
||||
name: '在线数',
|
||||
value: this.data.onLine,
|
||||
lineStyle: {
|
||||
normal: {
|
||||
color: '#2B81B1'
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: '#2B81B1'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '离线数',
|
||||
value: this.data.total - this.data.onLine,
|
||||
lineStyle: {
|
||||
normal: {
|
||||
color: '#dbebf7'
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: '#dbebf7'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
option && this.myChart.setOption(option)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<div :class="className" id="port-mapping-div" :style="{height:height,width:width}"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import echarts from 'echarts'
|
||||
require('echarts/theme/macarons') // echarts theme
|
||||
|
||||
export default {
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: 'chart'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '250px'
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
onLine: 90, // 进度条最大值
|
||||
total: 100 // 当前进度
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartDom: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initChart()
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
return
|
||||
}
|
||||
this.chartDom.dispose()
|
||||
this.chartDom = null
|
||||
},
|
||||
methods: {
|
||||
initChart() {
|
||||
this.chartDom = document.getElementById('port-mapping-div')
|
||||
this.myChart = echarts.init(this.chartDom)
|
||||
const option = {
|
||||
title: {
|
||||
text: '端口映射统计',
|
||||
subtext: '在线数:' + this.data.onLine + '\n\n' + '离线数:' + (this.data.total - this.data.onLine),
|
||||
textStyle: {
|
||||
fontSize: 18,
|
||||
fontWeight: 800,
|
||||
align: 'center'
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
data: ['在线数', '离线数'],
|
||||
orient: 'vertical',
|
||||
left: 'right'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
// 第一张圆环
|
||||
name: '端口映射',
|
||||
type: 'pie',
|
||||
radius: [45, 65],
|
||||
center: ['50%', '58%'],
|
||||
avoidLabelOverlap: false,
|
||||
// 隐藏指示线
|
||||
labelLine: {
|
||||
normal: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
// 隐藏圆环上文字
|
||||
label: {
|
||||
normal: {
|
||||
show: false,
|
||||
position: 'center'
|
||||
}
|
||||
},
|
||||
data: [
|
||||
// value当前进度 + 颜色
|
||||
{
|
||||
name: '在线数',
|
||||
value: this.data.onLine,
|
||||
lineStyle: {
|
||||
normal: {
|
||||
color: '#2B81B1'
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: '#2B81B1'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '离线数',
|
||||
value: this.data.total - this.data.onLine,
|
||||
lineStyle: {
|
||||
normal: {
|
||||
color: '#dbebf7'
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: '#dbebf7'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
option && this.myChart.setOption(option)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<div id="port-mapping-div" :style="{height:height+'px',width:width}">
|
||||
<el-table
|
||||
:data="tableChart"
|
||||
style="width: 100%"
|
||||
:default-sort = "{prop: 'date', order: 'descending'}"
|
||||
:height="height"
|
||||
>
|
||||
<el-table-column prop="date" label="日期" sortable />
|
||||
<el-table-column prop="name" label="姓名" sortable />
|
||||
<el-table-column prop="address" label="地址" sortable show-overflow-tooltip/>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '340'
|
||||
},
|
||||
tableChart: {
|
||||
type: Array,
|
||||
default: []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<div :class="className" :id="chartId" :style="{height:height,width:width}"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import echarts from 'echarts'
|
||||
require('echarts/theme/macarons') // echarts theme
|
||||
|
||||
export default {
|
||||
props: {
|
||||
chartId: {
|
||||
type: String,
|
||||
default: 'traffic-sum-div'
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: 'chart'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '380px'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartDom: null,
|
||||
colorList: ['#2B81B1', '#75ddfa', '#53a7e3', '#029fe8', '#015dae']
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initChart()
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (!this.chart) {
|
||||
return
|
||||
}
|
||||
this.chartDom.dispose()
|
||||
this.chartDom = null
|
||||
},
|
||||
methods: {
|
||||
initChart() {
|
||||
this.chartDom = document.getElementById(this.chartId)
|
||||
this.myChart = echarts.init(this.chartDom)
|
||||
const seriesList = []
|
||||
const legendList = []
|
||||
this.data.list.forEach((item, index) => {
|
||||
seriesList.push({
|
||||
name: item.name,
|
||||
type: 'line',
|
||||
stack: 'Total',
|
||||
data: item.value,
|
||||
areaStyle: {
|
||||
normal: {
|
||||
color: this.colorList[index] + '1f'
|
||||
}
|
||||
},
|
||||
lineStyle: {
|
||||
normal: {
|
||||
color: this.colorList[index]
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: this.colorList[index]
|
||||
}
|
||||
}
|
||||
})
|
||||
legendList.push(item.name)
|
||||
})
|
||||
const option = {
|
||||
title: {
|
||||
text: this.data.text + '折线图',
|
||||
subtext: this.data.subtext || ''
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
legend: {
|
||||
data: legendList,
|
||||
left: 'right'
|
||||
},
|
||||
grid: {
|
||||
left: '2%',
|
||||
right: '2%',
|
||||
bottom: '2%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: this.data.title
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: seriesList
|
||||
}
|
||||
option && this.myChart.setOption(option)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<div class="dashboard-editor-container">
|
||||
<github-corner></github-corner>
|
||||
|
||||
<panel-group @handleSetLineChartData="handleSetLineChartData"></panel-group>
|
||||
|
||||
<el-row style="background:#fff;padding:16px 16px 0;margin-bottom:32px;">
|
||||
<line-chart :chart-data="lineChartData"></line-chart>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="32">
|
||||
<el-col :xs="24" :sm="24" :lg="8">
|
||||
<div class="chart-wrapper">
|
||||
<raddar-chart></raddar-chart>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :lg="8">
|
||||
<div class="chart-wrapper">
|
||||
<pie-chart></pie-chart>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :lg="8">
|
||||
<div class="chart-wrapper">
|
||||
<bar-chart></bar-chart>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="8">
|
||||
<el-col :xs="{span: 24}" :sm="{span: 24}" :md="{span: 24}" :lg="{span: 12}" :xl="{span: 12}" style="padding-right:8px;margin-bottom:30px;">
|
||||
<transaction-table></transaction-table>
|
||||
</el-col>
|
||||
<el-col :xs="{span: 12}" :sm="{span: 12}" :md="{span: 12}" :lg="{span: 6}" :xl="{span: 5}">
|
||||
<todo-list></todo-list>
|
||||
</el-col>
|
||||
<el-col :xs="{span: 12}" :sm="{span: 12}" :md="{span: 12}" :lg="{span: 6}" :xl="{span: 5}">
|
||||
<box-card></box-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GithubCorner from '@/components/GithubCorner'
|
||||
import PanelGroup from './components/PanelGroup'
|
||||
import LineChart from './components/LineChart'
|
||||
import RaddarChart from './components/RaddarChart'
|
||||
import PieChart from './components/PieChart'
|
||||
import BarChart from './components/BarChart'
|
||||
import TransactionTable from './components/TransactionTable'
|
||||
import TodoList from './components/TodoList'
|
||||
import BoxCard from './components/BoxCard'
|
||||
|
||||
const lineChartData = {
|
||||
newVisitis: {
|
||||
expectedData: [100, 120, 161, 134, 105, 160, 165],
|
||||
actualData: [120, 82, 91, 154, 162, 140, 145]
|
||||
},
|
||||
messages: {
|
||||
expectedData: [200, 192, 120, 144, 160, 130, 140],
|
||||
actualData: [180, 160, 151, 106, 145, 150, 130]
|
||||
},
|
||||
purchases: {
|
||||
expectedData: [80, 100, 121, 104, 105, 90, 100],
|
||||
actualData: [120, 90, 100, 138, 142, 130, 130]
|
||||
},
|
||||
shoppings: {
|
||||
expectedData: [130, 140, 141, 142, 145, 150, 160],
|
||||
actualData: [120, 82, 91, 154, 162, 140, 130]
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'dashboard-admin',
|
||||
components: {
|
||||
GithubCorner,
|
||||
PanelGroup,
|
||||
LineChart,
|
||||
RaddarChart,
|
||||
PieChart,
|
||||
BarChart,
|
||||
TransactionTable,
|
||||
TodoList,
|
||||
BoxCard
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
lineChartData: lineChartData.newVisitis
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSetLineChartData(type) {
|
||||
this.lineChartData = lineChartData[type]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.dashboard-editor-container {
|
||||
padding: 32px;
|
||||
background-color: rgb(240, 242, 245);
|
||||
.chart-wrapper {
|
||||
background: #fff;
|
||||
padding: 16px 16px 0;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,110 +1,138 @@
|
||||
<template>
|
||||
<div class="dashboard-editor-container">
|
||||
<github-corner></github-corner>
|
||||
|
||||
<panel-group @handleSetLineChartData="handleSetLineChartData"></panel-group>
|
||||
|
||||
<el-row style="background:#fff;padding:16px 16px 0;margin-bottom:32px;">
|
||||
<line-chart :chart-data="lineChartData"></line-chart>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="32">
|
||||
<el-col :xs="24" :sm="24" :lg="8">
|
||||
<div class="chart-wrapper">
|
||||
<raddar-chart></raddar-chart>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :lg="8">
|
||||
<div class="chart-wrapper">
|
||||
<pie-chart></pie-chart>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :lg="8">
|
||||
<div class="chart-wrapper">
|
||||
<bar-chart></bar-chart>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="8">
|
||||
<el-col :xs="{span: 24}" :sm="{span: 24}" :md="{span: 24}" :lg="{span: 12}" :xl="{span: 12}" style="padding-right:8px;margin-bottom:30px;">
|
||||
<transaction-table></transaction-table>
|
||||
</el-col>
|
||||
<el-col :xs="{span: 12}" :sm="{span: 12}" :md="{span: 12}" :lg="{span: 6}" :xl="{span: 5}">
|
||||
<todo-list></todo-list>
|
||||
</el-col>
|
||||
<el-col :xs="{span: 12}" :sm="{span: 12}" :md="{span: 12}" :lg="{span: 6}" :xl="{span: 5}">
|
||||
<box-card></box-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<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"/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card class="box-card">
|
||||
<port-mapping-chart :data="echartsData.portMappingChart"/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card class="box-card">
|
||||
<daily-traffic-chart :data="echartsData.dailyTrafficChart" :chartId="'daily-traffic-div'"/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card class="box-card">
|
||||
<daily-traffic-chart :data="echartsData.historyTrafficChart" :chartId="'history-traffic-div'"/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GithubCorner from '@/components/GithubCorner'
|
||||
import PanelGroup from './components/PanelGroup'
|
||||
import LineChart from './components/LineChart'
|
||||
import RaddarChart from './components/RaddarChart'
|
||||
import PieChart from './components/PieChart'
|
||||
import BarChart from './components/BarChart'
|
||||
import TransactionTable from './components/TransactionTable'
|
||||
import TodoList from './components/TodoList'
|
||||
import BoxCard from './components/BoxCard'
|
||||
|
||||
const lineChartData = {
|
||||
newVisitis: {
|
||||
expectedData: [100, 120, 161, 134, 105, 160, 165],
|
||||
actualData: [120, 82, 91, 154, 162, 140, 145]
|
||||
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
|
||||
},
|
||||
messages: {
|
||||
expectedData: [200, 192, 120, 144, 160, 130, 140],
|
||||
actualData: [180, 160, 151, 106, 145, 150, 130]
|
||||
portMappingChart: {
|
||||
total: 100,
|
||||
onLine: 25
|
||||
},
|
||||
purchases: {
|
||||
expectedData: [80, 100, 121, 104, 105, 90, 100],
|
||||
actualData: [120, 90, 100, 138, 142, 130, 130]
|
||||
dailyTrafficChart: {
|
||||
upload: 300,
|
||||
download: 680,
|
||||
text: '今日流量'
|
||||
},
|
||||
shoppings: {
|
||||
expectedData: [130, 140, 141, 142, 145, 150, 160],
|
||||
actualData: [120, 82, 91, 154, 162, 140, 130]
|
||||
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]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'dashboard-admin',
|
||||
components: {
|
||||
GithubCorner,
|
||||
PanelGroup,
|
||||
LineChart,
|
||||
RaddarChart,
|
||||
PieChart,
|
||||
BarChart,
|
||||
TransactionTable,
|
||||
TodoList,
|
||||
BoxCard
|
||||
},
|
||||
components: { TrafficSumChart, DailyTrafficChart, PortMappingChart, LicenseChart },
|
||||
data() {
|
||||
return {
|
||||
lineChartData: lineChartData.newVisitis
|
||||
echartsData: echartsData
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
handleSetLineChartData(type) {
|
||||
this.lineChartData = lineChartData[type]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
.dashboard-editor-container {
|
||||
padding: 32px;
|
||||
background-color: rgb(240, 242, 245);
|
||||
.chart-wrapper {
|
||||
background: #fff;
|
||||
padding: 16px 16px 0;
|
||||
margin-bottom: 32px;
|
||||
.dashboard-editor{
|
||||
min-height: calc(100vh - 85px);
|
||||
padding: 16px;
|
||||
background-color: #f5f7fa;
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.line-chart, .pie-chart{
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.pie-chart{
|
||||
padding-top: 16px;
|
||||
.el-card{
|
||||
min-height: 300px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -23,9 +23,10 @@ export default {
|
||||
])
|
||||
},
|
||||
created() {
|
||||
if (!this.roles.includes('admin')) {
|
||||
this.currentRole = 'editorDashboard'
|
||||
}
|
||||
// 游客首页
|
||||
// if (!this.roles.includes('admin')) {
|
||||
// this.currentRole = 'editorDashboard'
|
||||
// }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-card class="box-card" style="margin-top:40px;">
|
||||
<div slot="header" class="clearfix">
|
||||
<svg-icon icon-class="international" />
|
||||
<span style='margin-left:10px;'>{{$t('i18nView.title')}}</span>
|
||||
</div>
|
||||
<div>
|
||||
<el-radio-group v-model="lang" size="small">
|
||||
<el-radio label="zh" border>简体中文</el-radio>
|
||||
<el-radio label="en" border>English</el-radio>
|
||||
</el-radio-group>
|
||||
<el-tag style='margin-top:15px;display:block;' type="info">{{$t('i18nView.note')}}</el-tag>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-row :gutter="20" style="margin:100px 15px 50px;">
|
||||
<el-col :span="12">
|
||||
<div class="block">
|
||||
<el-date-picker v-model="date" type="date" :placeholder="$t('i18nView.datePlaceholder')"></el-date-picker>
|
||||
</div>
|
||||
<div class="block">
|
||||
<el-pagination background :current-page="currentPage" :page-sizes="[100, 200, 300, 400]" :page-size="100" layout="total, sizes, prev, pager, next"
|
||||
:total="400">
|
||||
</el-pagination>
|
||||
</div>
|
||||
<div class="block">
|
||||
<el-button class="item-btn" size="small">{{$t('i18nView.default')}}</el-button>
|
||||
<el-button class="item-btn" size="small" type="primary">{{$t('i18nView.primary')}}</el-button>
|
||||
<el-button class="item-btn" size="small" type="success">{{$t('i18nView.success')}}</el-button>
|
||||
<el-button class="item-btn" size="small" type="info">{{$t('i18nView.info')}}</el-button>
|
||||
<el-button class="item-btn" size="small" type="warning">{{$t('i18nView.warning')}}</el-button>
|
||||
<el-button class="item-btn" size="small" type="danger">{{$t('i18nView.danger')}}</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-table :data="tableData" fit highlight-current-row border style="width: 100%">
|
||||
<el-table-column prop="name" :label="$t('i18nView.tableName')" width="100" align="center"></el-table-column>
|
||||
<el-table-column prop="date" :label="$t('i18nView.tableDate')" width="120" align="center"></el-table-column>
|
||||
<el-table-column prop="address" :label="$t('i18nView.tableAddress')"></el-table-column>
|
||||
</el-table>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import local from './local'
|
||||
const viewName = 'i18nView'
|
||||
|
||||
export default {
|
||||
name: 'i18n',
|
||||
data() {
|
||||
return {
|
||||
date: '',
|
||||
currentPage: 5,
|
||||
tableData: [{
|
||||
date: '2016-05-03',
|
||||
name: 'Tom',
|
||||
address: 'No. 189, Grove St, Los Angeles'
|
||||
},
|
||||
{
|
||||
date: '2016-05-02',
|
||||
name: 'Tom',
|
||||
address: 'No. 189, Grove St, Los Angeles'
|
||||
},
|
||||
{
|
||||
date: '2016-05-04',
|
||||
name: 'Tom',
|
||||
address: 'No. 189, Grove St, Los Angeles'
|
||||
},
|
||||
{
|
||||
date: '2016-05-01',
|
||||
name: 'Tom',
|
||||
address: 'No. 189, Grove St, Los Angeles'
|
||||
}]
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (!this.$i18n.getLocaleMessage('en')[viewName]) {
|
||||
this.$i18n.mergeLocaleMessage('en', local.en)
|
||||
this.$i18n.mergeLocaleMessage('zh', local.zh)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
lang: {
|
||||
get() {
|
||||
return this.$store.state.app.language
|
||||
},
|
||||
set(lang) {
|
||||
this.$i18n.locale = lang
|
||||
this.$store.dispatch('setLanguage', lang)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.box-card {
|
||||
width: 600px;
|
||||
margin: 20px auto;
|
||||
}
|
||||
.item-btn{
|
||||
margin-bottom: 15px;
|
||||
margin-left: 0px;
|
||||
}
|
||||
.block {
|
||||
padding: 25px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,36 +0,0 @@
|
||||
|
||||
export default {
|
||||
zh: {
|
||||
i18nView: {
|
||||
title: '切换语言',
|
||||
note: '目前只翻译了当前页面和侧边栏和导航,未完待续,敬请期待...',
|
||||
datePlaceholder: '请选择日期',
|
||||
tableDate: '日期',
|
||||
tableName: '姓名',
|
||||
tableAddress: '地址',
|
||||
default: '默认按钮',
|
||||
primary: '主要按钮',
|
||||
success: '成功按钮',
|
||||
info: '信息按钮',
|
||||
warning: '警告按钮',
|
||||
danger: '危险按钮'
|
||||
}
|
||||
|
||||
},
|
||||
en: {
|
||||
i18nView: {
|
||||
title: 'Switch Language',
|
||||
note: 'Currently only translated the i18n page and the sidebar and levelbar, please look forword to...',
|
||||
datePlaceholder: 'Pick a day',
|
||||
tableDate: 'tableDate',
|
||||
tableName: 'tableName',
|
||||
tableAddress: 'tableAddress',
|
||||
default: 'default:',
|
||||
primary: 'primary',
|
||||
success: 'success',
|
||||
info: 'info',
|
||||
warning: 'warning',
|
||||
danger: 'danger'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,12 +33,17 @@
|
||||
{{$t('navbar.github')}}
|
||||
</el-dropdown-item>
|
||||
</a>
|
||||
<el-dropdown-item>
|
||||
<span @click="updatePwdvisible = true">{{$t('navbar.updatePwd')}}</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item divided>
|
||||
<span @click="logout" style="display:block;">{{$t('navbar.logOut')}}</span>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
|
||||
<UpdatePwd :visible="updatePwdvisible" @cancel="handleCancel"></UpdatePwd>
|
||||
</el-menu>
|
||||
</template>
|
||||
|
||||
@@ -50,9 +55,11 @@ import ErrorLog from '@/components/ErrorLog'
|
||||
import Screenfull from '@/components/Screenfull'
|
||||
import LangSelect from '@/components/LangSelect'
|
||||
import ThemePicker from '@/components/ThemePicker'
|
||||
import UpdatePwd from '../../system/components/update-pwd'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
UpdatePwd,
|
||||
Breadcrumb,
|
||||
Hamburger,
|
||||
ErrorLog,
|
||||
@@ -68,6 +75,11 @@ export default {
|
||||
'loginName'
|
||||
])
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
updatePwdvisible: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleSideBar() {
|
||||
this.$store.dispatch('toggleSideBar')
|
||||
@@ -76,6 +88,9 @@ export default {
|
||||
this.$store.dispatch('LogOut').then(() => {
|
||||
location.reload()// In order to re-instantiate the vue-router object to avoid bugs
|
||||
})
|
||||
},
|
||||
handleCancel() {
|
||||
this.updatePwdvisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,12 +145,12 @@ export default {
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.el-icon-caret-bottom {
|
||||
/*.el-icon-caret-bottom {
|
||||
position: absolute;
|
||||
right: -20px;
|
||||
top: 25px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<scroll-bar>
|
||||
<div class="log-div">
|
||||
<img :src="logCross"/>
|
||||
</div>
|
||||
<el-menu mode="vertical" :default-active="$route.path" :collapse="isCollapse" background-color="#304156" text-color="#bfcbd9" active-text-color="#409EFF">
|
||||
<sidebar-item :routes="permission_routers"></sidebar-item>
|
||||
</el-menu>
|
||||
@@ -10,6 +13,7 @@
|
||||
import { mapGetters } from 'vuex'
|
||||
import SidebarItem from './SidebarItem'
|
||||
import ScrollBar from '@/components/ScrollBar'
|
||||
import logCross from '@/assets/logo/logo-text-cross.svg'
|
||||
|
||||
export default {
|
||||
components: { SidebarItem, ScrollBar },
|
||||
@@ -21,6 +25,20 @@ export default {
|
||||
isCollapse() {
|
||||
return !this.sidebar.opened
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
logCross
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.log-div{
|
||||
padding: 10px 0 5px 15px;
|
||||
img{
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<div class="app-container calendar-list-container">
|
||||
<div class="filter-container">
|
||||
<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.ip')" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.ip}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.type')" min-width="90">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.type | statusFilter">{{scope.row.type | statusName}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.msg')" min-width="200" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.msg}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.outcome')" min-width="90">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.code | statusFilter">{{scope.row.code | outcomeName}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.err')" min-width="200" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.err}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.happendTime')" min-width="150">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.createTime | parseTime('{y}-{m}-{d} {h}:{i}')}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination-container">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page.sync="listQuery.currentPage"
|
||||
:page-sizes="[10,20,30, 50]" :page-size="listQuery.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
title="调度日志"
|
||||
:visible.sync="dialogVisible"
|
||||
width="700px"
|
||||
:before-close="() => this.dialogVisible = false">
|
||||
<div class="job-msg-div">{{selectRow.msg}}</div>
|
||||
<div slot="footer" class="dialog-footer"></div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { fetchList } from '@/api/clientConnectLog'
|
||||
import waves from '@/directive/waves' // 水波纹指令
|
||||
|
||||
export default {
|
||||
name: 'clientConnectLog',
|
||||
directives: {
|
||||
waves
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableKey: 0,
|
||||
list: null,
|
||||
total: null,
|
||||
listLoading: false,
|
||||
listQuery: {
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
jobId: undefined
|
||||
},
|
||||
dialogVisible: false,
|
||||
selectRow: {}
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
statusName(status) {
|
||||
const statusMap = {
|
||||
1: '连接',
|
||||
2: '断开'
|
||||
}
|
||||
return statusMap[status]
|
||||
},
|
||||
statusFilter(status) {
|
||||
const statusMap = {
|
||||
1: 'success',
|
||||
2: 'danger'
|
||||
}
|
||||
return statusMap[status]
|
||||
},
|
||||
outcomeName(status) {
|
||||
const statusMap = {
|
||||
1: '成功',
|
||||
2: '失败'
|
||||
}
|
||||
return statusMap[status]
|
||||
}
|
||||
},
|
||||
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.currentPage = 1
|
||||
this.getList()
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
this.listQuery.pageSize = val
|
||||
this.listQuery.currentPage = 1
|
||||
this.getList()
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.listQuery.currentPage = val
|
||||
this.getList()
|
||||
},
|
||||
handleLookOver(row) {
|
||||
this.selectRow = row
|
||||
this.dialogVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.job-msg-div{
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -4,7 +4,7 @@
|
||||
<el-select v-model="listQuery.jobId" placeholder="请选择" clearable>
|
||||
<el-option v-for="item in jobList" :key="item.id" :label="item.desc" :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 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%">
|
||||
@@ -24,9 +24,9 @@
|
||||
<el-tag :type="scope.row.code | statusFilter">{{scope.row.code | statusName}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.jobLogMsg')" min-width="400">
|
||||
<el-table-column align="center" :label="$t('table.jobLogMsg')" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.msg}}</span>
|
||||
<el-button type="text" @click="handleLookOver(scope.row)">{{$t('button.lookOver')}}</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.alarmStatus')" min-width="120">
|
||||
@@ -34,7 +34,7 @@
|
||||
<el-tag :type="scope.row.alarmStatus | alarmStatusFilter">{{scope.row.alarmStatus | salarmStatusName}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.createTime')" min-width="150">
|
||||
<el-table-column align="center" :label="$t('table.jobLogTime')" min-width="150">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.createTime | parseTime('{y}-{m}-{d} {h}:{i}')}}</span>
|
||||
</template>
|
||||
@@ -50,6 +50,15 @@
|
||||
:page-sizes="[10,20,30, 50]" :page-size="listQuery.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
title="调度日志"
|
||||
:visible.sync="dialogVisible"
|
||||
width="700px"
|
||||
:before-close="() => this.dialogVisible = false">
|
||||
<div class="job-msg-div">{{selectRow.msg}}</div>
|
||||
<div slot="footer" class="dialog-footer"></div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -71,10 +80,12 @@ export default {
|
||||
listLoading: false,
|
||||
listQuery: {
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
pageSize: 10,
|
||||
jobId: undefined
|
||||
},
|
||||
jobList: []
|
||||
jobList: [],
|
||||
dialogVisible: false,
|
||||
selectRow: {}
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
@@ -119,7 +130,6 @@ export default {
|
||||
this.getJobList()
|
||||
if (this.$route.query.jobId) {
|
||||
this.listQuery.jobId = this.$route.query.jobId
|
||||
console.log(this.listQuery.jobId, this.$route.query.jobId)
|
||||
this.getList()
|
||||
}
|
||||
},
|
||||
@@ -143,6 +153,7 @@ export default {
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
this.listQuery.pageSize = val
|
||||
this.listQuery.currentPage = 1
|
||||
this.getList()
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
@@ -151,7 +162,18 @@ export default {
|
||||
},
|
||||
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-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 type="index" width="100" :label="$t('table.id')"></el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.userName')" min-width="200">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.userName}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.ip')" min-width="200">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.ip}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.type')" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.type | typeFilter">{{scope.row.type | typeName}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.happendTime')" min-width="150">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.createTime | parseTime('{y}-{m}-{d} {h}:{i}')}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination-container">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page.sync="listQuery.currentPage"
|
||||
:page-sizes="[10,20,30, 50]" :page-size="listQuery.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { fetchList } from '@/api/loginLog'
|
||||
import waves from '@/directive/waves' // 水波纹指令
|
||||
|
||||
export default {
|
||||
name: 'loginLog',
|
||||
directives: {
|
||||
waves
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableKey: 0,
|
||||
list: null,
|
||||
total: null,
|
||||
listLoading: false,
|
||||
listQuery: {
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
jobId: undefined
|
||||
}
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
statusName(status) {
|
||||
const statusMap = {
|
||||
0: '成功',
|
||||
1: '失败'
|
||||
}
|
||||
return statusMap[status]
|
||||
},
|
||||
statusFilter(status) {
|
||||
const statusMap = {
|
||||
0: 'success',
|
||||
1: 'danger'
|
||||
}
|
||||
return statusMap[status]
|
||||
},
|
||||
typeName(type) {
|
||||
const typeMap = {
|
||||
1: '登录',
|
||||
2: '登出'
|
||||
}
|
||||
return typeMap[type]
|
||||
},
|
||||
typeFilter(type) {
|
||||
const typeMap = {
|
||||
1: 'success',
|
||||
2: 'danger'
|
||||
}
|
||||
return typeMap[type]
|
||||
},
|
||||
alarmStatusFilter(status) {
|
||||
const statusMap = {
|
||||
0: 'success',
|
||||
1: 'warning',
|
||||
2: 'success',
|
||||
3: 'danger'
|
||||
}
|
||||
return statusMap[status]
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
},
|
||||
activated() {
|
||||
if (this.$route.query.jobId) {
|
||||
this.listQuery.jobId = this.$route.query.jobId
|
||||
console.log(this.listQuery.jobId, this.$route.query.jobId)
|
||||
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.currentPage = 1
|
||||
this.getList()
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
this.listQuery.pageSize = val
|
||||
this.listQuery.currentPage = 1
|
||||
this.getList()
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.listQuery.currentPage = val
|
||||
this.getList()
|
||||
},
|
||||
handleShowClick(row) {
|
||||
console.log(row)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -3,6 +3,9 @@
|
||||
<el-form class="login-form" autoComplete="on" :model="loginForm" :rules="loginRules" ref="loginForm" label-position="left">
|
||||
<div class="title-container">
|
||||
<!-- <h3 class="title">{{$t('login.title')}}</h3>-->
|
||||
<p align="center">
|
||||
<img width="30%" src="favicon.ico">
|
||||
</p>
|
||||
<h3 class="title">中微子代理</h3>
|
||||
</div>
|
||||
<el-form-item prop="username">
|
||||
|
||||
@@ -47,14 +47,14 @@
|
||||
<el-tag :type="scope.row.isOnline | statusFilter">{{scope.row.isOnline | isOnlineName}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.actions')" width="230" class-name="small-padding fixed-width">
|
||||
<el-table-column align="center" :label="$t('table.actions')" width="330" 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 size="mini" type="primary" @click="handleReset(scope.row)">{{$t('table.resetKey')}}</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"/>
|
||||
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -98,7 +98,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { fetchList, createLicense, updateLicense, updateEnableStatus, deleteLicense } from '@/api/license'
|
||||
import { fetchList, createLicense, updateLicense, updateEnableStatus, deleteLicense, resetLicense } from '@/api/license'
|
||||
import { userList } from '@/api/user'
|
||||
import waves from '@/directive/waves' // 水波纹指令
|
||||
import { parseTime } from '@/utils'
|
||||
@@ -133,7 +133,7 @@
|
||||
listLoading: true,
|
||||
listQuery: {
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
pageSize: 10,
|
||||
importance: undefined,
|
||||
title: undefined,
|
||||
type: undefined
|
||||
@@ -277,6 +277,25 @@
|
||||
this.$refs['dataForm'].clearValidate()
|
||||
})
|
||||
},
|
||||
handleReset(row) {
|
||||
this.$confirm('确定重置吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'info'
|
||||
}).then(() => {
|
||||
resetLicense(row.id).then(response => {
|
||||
if (response.data.code === 0) {
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '重置成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
updateData() {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
|
||||
@@ -172,7 +172,7 @@
|
||||
listLoading: true,
|
||||
listQuery: {
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
pageSize: 10,
|
||||
importance: undefined,
|
||||
title: undefined,
|
||||
type: undefined
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
<template>
|
||||
<el-upload action="https://upload.qbox.me" :data="dataObj" drag :multiple="true" :before-upload="beforeUpload">
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
</el-upload>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import { getToken } from '@/api/qiniu'
|
||||
// 获取七牛token 后端通过Access Key,Secret Key,bucket等生成token
|
||||
// 七牛官方sdk https://developer.qiniu.com/sdk#official-sdk
|
||||
|
||||
export default{
|
||||
data() {
|
||||
return {
|
||||
dataObj: { token: '', key: '' },
|
||||
image_uri: [],
|
||||
fileList: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
beforeUpload() {
|
||||
const _self = this
|
||||
return new Promise((resolve, reject) => {
|
||||
getToken().then(response => {
|
||||
const key = response.data.qiniu_key
|
||||
const token = response.data.qiniu_token
|
||||
_self._data.dataObj.token = token
|
||||
_self._data.dataObj.key = key
|
||||
resolve(true)
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
reject(false)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog title="修改密码"
|
||||
:visible.sync="visible"
|
||||
:close-on-click-modal="false"
|
||||
:before-close="onCancel"
|
||||
:width="width"
|
||||
>
|
||||
<el-form :rules="rules" ref='form' :model="formData" label-position="left" label-width="80px" style='padding:0 20px'>
|
||||
<el-form-item label="登录名" prop="loginName" v-if="row">
|
||||
<el-input v-model="formData.loginName" disabled/>
|
||||
</el-form-item>
|
||||
<el-form-item label="旧密码" prop="oldLoginPassword" v-if="!row">
|
||||
<el-input v-model="formData.oldLoginPassword" type="password" placeholder="请输入旧密码"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码" prop="loginPassword">
|
||||
<el-input v-model="formData.loginPassword" type="password" placeholder="请输入新密码"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" prop="confirmPassword" v-if="!row">
|
||||
<el-input v-model="formData.confirmPassword" type="password" placeholder="请输入确认密码"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="updatePwd">确定</el-button>
|
||||
<el-button @click="onCancel">取消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { updatePassword, updateUserPassword } from '@/api/user'
|
||||
|
||||
export default {
|
||||
name: 'updatePwd',
|
||||
props: {
|
||||
row: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
visible: Boolean,
|
||||
width: {
|
||||
type: String,
|
||||
default: '500px'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const validatorValue = (rule, value, callback) => {
|
||||
if (value === '' || !value) {
|
||||
callback(new Error('确认密码必填'))
|
||||
} else if (this.formData.loginPassword !== value) {
|
||||
callback(new Error('与新密码不一致'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
return {
|
||||
formData: {
|
||||
id: undefined,
|
||||
loginName: undefined,
|
||||
loginPassword: undefined,
|
||||
oldLoginPassword: undefined,
|
||||
confirmPassword: undefined
|
||||
},
|
||||
rules: {
|
||||
loginName: [
|
||||
{ required: true, message: '用户名', trigger: 'blur' }
|
||||
],
|
||||
loginPassword: [
|
||||
{ required: true, message: '新密码必填', trigger: 'blur' },
|
||||
{ min: 6, max: 20, message: '密码长度为 6 到 10 位之间', trigger: 'blur' }
|
||||
],
|
||||
oldLoginPassword: [
|
||||
{ required: true, message: '旧密码必填', trigger: 'blur' }
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, validator: validatorValue, trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(val) {
|
||||
if (val) {
|
||||
this.row && this.getRow()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getRow() {
|
||||
this.formData.id = this.row.id
|
||||
this.formData.loginName = this.row.loginName
|
||||
this.formData.loginPassword = ''
|
||||
},
|
||||
updatePwd() {
|
||||
this.$refs['form'].validate(valid => {
|
||||
if (valid) {
|
||||
if (this.row) {
|
||||
updatePassword(this.formData).then(res => {
|
||||
if (res.data.code === 0) {
|
||||
this.$message.success('修改成功')
|
||||
this.onCancel()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
updateUserPassword(this.formData).then(res => {
|
||||
if (res.data.code === 0) {
|
||||
this.$message.success('修改成功')
|
||||
this.onCancel()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
onCancel() {
|
||||
this.$emit('cancel')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -41,7 +41,7 @@
|
||||
<el-button size="mini" type="text" @click="handleLogClick(scope.row)">查看</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.actions')" width="230" class-name="small-padding fixed-width">
|
||||
<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="primary" @click="handleEditClick(scope.row)">编辑</el-button>
|
||||
<el-button size="mini" type="primary" @click="handleExecuteClick(scope.row)">执行</el-button>
|
||||
@@ -115,7 +115,7 @@
|
||||
listLoading: true,
|
||||
listQuery: {
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
pageSize: 10,
|
||||
importance: undefined,
|
||||
title: undefined,
|
||||
type: undefined
|
||||
@@ -225,7 +225,7 @@
|
||||
})
|
||||
},
|
||||
handleLogClick(row) {
|
||||
this.$router.push({ path: '/system/jobLog', query: { jobId: row.id }})
|
||||
this.$router.push({ path: '/log/jobLog', query: { jobId: row.id }})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<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="100">
|
||||
<el-table-column align="center" :label="$t('table.id')" width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.id}}</span>
|
||||
</template>
|
||||
@@ -17,22 +17,22 @@
|
||||
<span>{{scope.row.port}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="150px" align="center" :label="$t('table.createTime')">
|
||||
<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="150px" align="center" :label="$t('table.updateTime')">
|
||||
<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="100">
|
||||
<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="230" class-name="small-padding fixed-width">
|
||||
<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>
|
||||
@@ -108,7 +108,7 @@
|
||||
listLoading: true,
|
||||
listQuery: {
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
pageSize: 10,
|
||||
importance: undefined,
|
||||
title: undefined,
|
||||
type: undefined
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<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>
|
||||
<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
|
||||
@@ -37,14 +37,14 @@
|
||||
<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="230" class-name="small-padding fixed-width">
|
||||
<el-table-column align="center" :label="$t('table.actions')" :width="loginName === 'admin' ? 320 : 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 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="loginName === 'admin'" type="warning" size="mini" @click="handleUpdatePassword(scope.row)">{{$t('table.updatePwd')}}</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"/>
|
||||
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -56,7 +56,7 @@
|
||||
</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 :rules="rules" ref="dataForm" :model="temp" label-position="left" label-width="70px" style='width: 400px; margin-left:50px'>
|
||||
<el-form-item :label="$t('用户名')" prop="name">
|
||||
<el-input v-model="temp.name"></el-input>
|
||||
</el-form-item>
|
||||
@@ -81,6 +81,8 @@
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
<UpdatePwd :row="selectRow" :visible="updatePwdvisible" @cancel="handleCancel"></UpdatePwd>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -89,6 +91,8 @@
|
||||
import waves from '@/directive/waves' // 水波纹指令
|
||||
import { parseTime } from '@/utils'
|
||||
import ButtonPopover from '../../components/Button/buttonPopover'
|
||||
import UpdatePwd from './components/update-pwd'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
const calendarTypeOptions = [
|
||||
{ key: 'CN', display_name: 'China' },
|
||||
@@ -108,8 +112,14 @@
|
||||
directives: {
|
||||
waves
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'loginName'
|
||||
])
|
||||
},
|
||||
components: {
|
||||
ButtonPopover
|
||||
ButtonPopover,
|
||||
UpdatePwd
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -119,7 +129,7 @@
|
||||
listLoading: true,
|
||||
listQuery: {
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
pageSize: 10,
|
||||
importance: undefined,
|
||||
title: undefined,
|
||||
type: undefined
|
||||
@@ -150,7 +160,9 @@
|
||||
name: [{ required: true, message: '用户名必填', trigger: 'blur' }],
|
||||
loginName: [{ required: true, message: '登录名必填', trigger: 'blur' }]
|
||||
},
|
||||
downloadLoading: false
|
||||
downloadLoading: false,
|
||||
updatePwdvisible: false,
|
||||
selectRow: null
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
@@ -327,6 +339,14 @@
|
||||
return v[j]
|
||||
}
|
||||
}))
|
||||
},
|
||||
handleUpdatePassword(row) {
|
||||
this.selectRow = row
|
||||
this.updatePwdvisible = true
|
||||
},
|
||||
handleCancel() {
|
||||
this.updatePwdvisible = false
|
||||
this.selectRow = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- $t is vue-i18n global function to translate lang -->
|
||||
<el-input style='width:300px;' :placeholder="$t('zip.placeholder')" prefix-icon="el-icon-document" v-model="filename"></el-input>
|
||||
<el-button style='margin-bottom:20px;' type="primary" icon="document" @click="handleDownload" :loading="downloadLoading">{{$t('zip.export')}} zip</el-button>
|
||||
<el-table :data="list" v-loading.body="listLoading" element-loading-text="拼命加载中" border fit highlight-current-row>
|
||||
<el-table-column align="center" label='ID' width="95">
|
||||
<template slot-scope="scope">
|
||||
{{scope.$index}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Title">
|
||||
<template slot-scope="scope">
|
||||
{{scope.row.title}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Author" width="95" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag>{{scope.row.author}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Readings" width="115" align="center">
|
||||
<template slot-scope="scope">
|
||||
{{scope.row.pageviews}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="Date" width="220">
|
||||
<template slot-scope="scope">
|
||||
<i class="el-icon-time"></i>
|
||||
<span>{{scope.row.display_time}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { fetchList } from '@/api/article'
|
||||
|
||||
export default {
|
||||
name: 'exportZip',
|
||||
data() {
|
||||
return {
|
||||
list: null,
|
||||
listLoading: true,
|
||||
downloadLoading: false,
|
||||
filename: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
fetchData() {
|
||||
this.listLoading = true
|
||||
fetchList().then(response => {
|
||||
this.list = response.data.items
|
||||
this.listLoading = false
|
||||
})
|
||||
},
|
||||
handleDownload() {
|
||||
this.downloadLoading = true
|
||||
import('@/vendor/Export2Zip').then(zip => {
|
||||
const tHeader = ['Id', 'Title', 'Author', 'Readings', 'Date']
|
||||
const filterVal = ['id', 'title', 'author', 'pageviews', 'display_time']
|
||||
const list = this.list
|
||||
const data = this.formatJson(filterVal, list)
|
||||
zip.export_txt_to_zip(tHeader, data, this.filename, this.filename)
|
||||
this.downloadLoading = false
|
||||
})
|
||||
},
|
||||
formatJson(filterVal, jsonData) {
|
||||
return jsonData.map(v => filterVal.map(j => v[j]))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -39,6 +39,7 @@ public class ProxyConfig {
|
||||
private Protocol protocol;
|
||||
private Client client;
|
||||
private String licenseKey;
|
||||
private CustomConfig customConfig;
|
||||
public static volatile boolean authSuccess;
|
||||
|
||||
@Data
|
||||
|
||||
@@ -32,6 +32,7 @@ import fun.asgc.neutrino.core.constant.MetaDataConstant;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 应用生命周期事件监听器
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/10/10
|
||||
*/
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.client.core;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import fun.asgc.neutrino.core.annotation.Autowired;
|
||||
import fun.asgc.neutrino.core.annotation.Component;
|
||||
import fun.asgc.neutrino.core.annotation.NonIntercept;
|
||||
@@ -29,9 +30,12 @@ import fun.asgc.neutrino.core.context.Environment;
|
||||
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 java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
@@ -39,7 +43,7 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
*
|
||||
* license获取服务
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/9/4
|
||||
*/
|
||||
@@ -82,29 +86,90 @@ public class LicenseObtainService {
|
||||
}
|
||||
|
||||
public void process(String[] args) {
|
||||
String licenseKey = getLicenseKey(args);
|
||||
proxyClientService.start(licenseKey);
|
||||
CustomConfig customConfig = getCustomConfigByCliParams(args);
|
||||
proxyConfig.getClient().setJksPath(customConfig.getJksPath());
|
||||
proxyConfig.getClient().setServerIp(customConfig.getServerIp());
|
||||
proxyConfig.getClient().setServerPort(customConfig.getServerPort());
|
||||
proxyConfig.getClient().setSslEnable(customConfig.getSslEnable());
|
||||
proxyConfig.setLicenseKey(customConfig.getLicenseKey());
|
||||
proxyConfig.setCustomConfig(customConfig);
|
||||
proxyClientService.start();
|
||||
}
|
||||
|
||||
private String getLicenseKey(String[] args) {
|
||||
String license = "";
|
||||
if (null != args && ArrayUtil.notEmpty(args)) {
|
||||
for (String s : args) {
|
||||
if (s.startsWith("license=") && s.length() > 8) {
|
||||
license = s.substring(8).trim();
|
||||
break;
|
||||
private CustomConfig getCustomConfigByCliParams(String[] args) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (StringUtil.isEmpty(license)) {
|
||||
license = FileUtil.readContentAsString("./.neutrino-proxy.license");
|
||||
}
|
||||
|
||||
String license = "";
|
||||
while (StringUtil.isEmpty(license)) {
|
||||
System.out.print("请输入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 license;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,17 +22,16 @@
|
||||
|
||||
package fun.asgc.neutrino.proxy.client.core;
|
||||
|
||||
import fun.asgc.neutrino.core.annotation.Autowired;
|
||||
import fun.asgc.neutrino.core.annotation.Bean;
|
||||
import fun.asgc.neutrino.core.annotation.Component;
|
||||
import fun.asgc.neutrino.core.annotation.NonIntercept;
|
||||
import fun.asgc.neutrino.core.context.ApplicationRunner;
|
||||
import fun.asgc.neutrino.core.annotation.*;
|
||||
import fun.asgc.neutrino.core.base.CustomThreadFactory;
|
||||
import fun.asgc.neutrino.core.context.Environment;
|
||||
import fun.asgc.neutrino.core.util.FileUtil;
|
||||
import fun.asgc.neutrino.core.util.StringUtil;
|
||||
import fun.asgc.neutrino.proxy.client.config.ProxyConfig;
|
||||
import fun.asgc.neutrino.proxy.client.util.ProxyUtil;
|
||||
import fun.asgc.neutrino.proxy.core.*;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessage;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessageDecoder;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessageEncoder;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
@@ -48,10 +47,12 @@ import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import java.io.InputStream;
|
||||
import java.security.KeyStore;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
*
|
||||
* 客户端服务
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
@@ -69,23 +70,27 @@ public class ProxyClientService {
|
||||
@Autowired
|
||||
private Environment environment;
|
||||
private volatile Channel channel;
|
||||
|
||||
public void start(String licenseKey) {
|
||||
if (StringUtil.isEmpty(licenseKey)) {
|
||||
return;
|
||||
}
|
||||
proxyConfig.setLicenseKey(licenseKey);
|
||||
if (null == channel || !channel.isActive()) {
|
||||
connectProxyServer();
|
||||
} else {
|
||||
channel.writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getLicenseKey()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接代理服务器
|
||||
* 重连间隔(秒)
|
||||
*/
|
||||
private void connectProxyServer() {
|
||||
private static final long RECONNECT_INTERVAL_SECONDS = 5;
|
||||
/**
|
||||
* 重连次数
|
||||
*/
|
||||
private volatile int reconnectCount = 0;
|
||||
/**
|
||||
* 启用重连服务
|
||||
*/
|
||||
private volatile boolean reconnectServiceEnable = false;
|
||||
/**
|
||||
* 重连服务执行器
|
||||
*/
|
||||
private static final ScheduledExecutorService reconnectExecutor = Executors.newSingleThreadScheduledExecutor(new CustomThreadFactory("ClientReconnect"));
|
||||
|
||||
@Init
|
||||
public void init() {
|
||||
this.reconnectExecutor.scheduleWithFixedDelay(this::reconnect, 0, RECONNECT_INTERVAL_SECONDS, TimeUnit.SECONDS);
|
||||
|
||||
workerGroup = new NioEventLoopGroup();
|
||||
realServerBootstrap.group(workerGroup);
|
||||
realServerBootstrap.channel(NioSocketChannel.class);
|
||||
@@ -108,13 +113,34 @@ public class ProxyClientService {
|
||||
}
|
||||
|
||||
ch.pipeline().addLast(new ProxyMessageDecoder(proxyConfig.getProtocol().getMaxFrameLength(),
|
||||
proxyConfig.getProtocol().getLengthFieldOffset(), proxyConfig.getProtocol().getLengthFieldLength(),
|
||||
proxyConfig.getProtocol().getLengthAdjustment(), proxyConfig.getProtocol().getInitialBytesToStrip()));
|
||||
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());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (StringUtil.isEmpty(proxyConfig.getLicenseKey())) {
|
||||
return;
|
||||
}
|
||||
if (null == channel || !channel.isActive()) {
|
||||
try {
|
||||
connectProxyServer();
|
||||
} catch (Exception e) {
|
||||
log.error("启动异常", e);
|
||||
}
|
||||
} else {
|
||||
channel.writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getLicenseKey()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接代理服务器
|
||||
*/
|
||||
private void connectProxyServer() throws InterruptedException {
|
||||
bootstrap.connect(proxyConfig.getClient().getServerIp(), proxyConfig.getClient().getServerPort())
|
||||
.addListener(new ChannelFutureListener() {
|
||||
|
||||
@@ -126,12 +152,14 @@ public class ProxyClientService {
|
||||
ProxyUtil.setCmdChannel(future.channel());
|
||||
future.channel().writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getLicenseKey()));
|
||||
log.info("连接代理服务成功. channelId:{}", future.channel().id().asLongText());
|
||||
|
||||
reconnectServiceEnable = true;
|
||||
reconnectCount = 0;
|
||||
} else {
|
||||
log.info("连接代理服务失败!");
|
||||
System.exit(-1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}).sync();
|
||||
}
|
||||
|
||||
private ChannelHandler createSslHandler() {
|
||||
@@ -157,6 +185,21 @@ public class ProxyClientService {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected synchronized void reconnect() {
|
||||
if (!reconnectServiceEnable) {
|
||||
return;
|
||||
}
|
||||
if (null != channel && channel.isActive()) {
|
||||
return;
|
||||
}
|
||||
log.info("客户端重连 seq:{}", ++reconnectCount);
|
||||
try {
|
||||
connectProxyServer();
|
||||
} catch (Exception e) {
|
||||
log.error("重连异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Bootstrap bootstrap() {
|
||||
return new Bootstrap();
|
||||
|
||||
@@ -42,15 +42,15 @@ public class RealServerChannelHandler extends SimpleChannelInboundHandler<ByteBu
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) throws Exception {
|
||||
Channel realServerChannel = ctx.channel();
|
||||
Channel channel = realServerChannel.attr(Constants.NEXT_CHANNEL).get();
|
||||
if (channel == null) {
|
||||
Channel proxyChannel = realServerChannel.attr(Constants.NEXT_CHANNEL).get();
|
||||
if (null == proxyChannel) {
|
||||
// 代理客户端连接断开
|
||||
ctx.channel().close();
|
||||
} else {
|
||||
byte[] bytes = new byte[buf.readableBytes()];
|
||||
buf.readBytes(bytes);
|
||||
String visitorId = ProxyUtil.getRealServerChannelVisitorId(realServerChannel);
|
||||
channel.writeAndFlush(ProxyMessage.buildTransferMessage(visitorId, bytes));
|
||||
String visitorId = ProxyUtil.getVisitorIdByRealServerChannel(realServerChannel);
|
||||
proxyChannel.writeAndFlush(ProxyMessage.buildTransferMessage(visitorId, bytes));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ public class RealServerChannelHandler extends SimpleChannelInboundHandler<ByteBu
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
Channel realServerChannel = ctx.channel();
|
||||
String visitorId = ProxyUtil.getRealServerChannelVisitorId(realServerChannel);
|
||||
String visitorId = ProxyUtil.getVisitorIdByRealServerChannel(realServerChannel);
|
||||
ProxyUtil.removeRealServerChannel(visitorId);
|
||||
Channel channel = realServerChannel.attr(Constants.NEXT_CHANNEL).get();
|
||||
if (channel != null) {
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
package fun.asgc.neutrino.proxy.client.handler;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
import fun.asgc.neutrino.core.annotation.Autowired;
|
||||
import fun.asgc.neutrino.core.annotation.Component;
|
||||
import fun.asgc.neutrino.core.annotation.Match;
|
||||
@@ -37,7 +38,7 @@ import io.netty.channel.ChannelHandlerContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
*
|
||||
* 认证信息处理器
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/9/4
|
||||
*/
|
||||
@@ -48,17 +49,18 @@ import lombok.extern.slf4j.Slf4j;
|
||||
public class ProxyMessageAuthHandler implements ProxyMessageHandler {
|
||||
@Autowired
|
||||
private LicenseObtainService licenseObtainService;
|
||||
@Autowired
|
||||
private ProxyConfig proxyConfig;
|
||||
|
||||
@Override
|
||||
public void handle(ChannelHandlerContext context, ProxyMessage proxyMessage) {
|
||||
String info = proxyMessage.getInfo();
|
||||
JSONObject data = JSONObject.parseObject(info);
|
||||
Integer code = data.getInteger("code");
|
||||
String licenseKey = data.getString("licenseKey");
|
||||
log.info("认证结果:{}", info);
|
||||
if (ExceptionEnum.SUCCESS.getCode().equals(code)) {
|
||||
ProxyConfig.authSuccess = true;
|
||||
FileUtil.write("./.neutrino-proxy.license", licenseKey);
|
||||
FileUtil.write("./.neutrino-proxy-client.json", JSONObject.toJSONString(proxyConfig.getCustomConfig(), SerializerFeature.PrettyFormat));
|
||||
licenseObtainService.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* 连接信息处理器
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
@@ -54,6 +54,7 @@ public class ProxyMessageConnectHandler implements ProxyMessageHandler {
|
||||
String[] serverInfo = new String(proxyMessage.getData()).split(":");
|
||||
String ip = serverInfo[0];
|
||||
int port = Integer.parseInt(serverInfo[1]);
|
||||
// 连接真实的、被代理的服务
|
||||
realServerBootstrap.connect(ip, port).addListener(new ChannelFutureListener() {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -36,7 +36,7 @@ import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
|
||||
/**
|
||||
*
|
||||
* 断开连接信息处理器
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
|
||||
@@ -31,7 +31,7 @@ import io.netty.channel.ChannelHandlerContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
*
|
||||
* 异常信息处理器
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
|
||||
@@ -34,7 +34,7 @@ import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
|
||||
/**
|
||||
*
|
||||
* 传输信息处理器
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
|
||||
@@ -38,7 +38,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
/**
|
||||
*
|
||||
* 代理工具
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/8/31
|
||||
*/
|
||||
@@ -101,7 +101,7 @@ public class ProxyUtil {
|
||||
realServerChannel.attr(Constants.VISITOR_ID).set(visitorId);
|
||||
}
|
||||
|
||||
public static String getRealServerChannelVisitorId(Channel realServerChannel) {
|
||||
public static String getVisitorIdByRealServerChannel(Channel realServerChannel) {
|
||||
return realServerChannel.attr(Constants.VISITOR_ID).get();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<property name="LOG_FILE" value="/work/project/neutrino-proxy/client.log"/>
|
||||
<property name="LOG_FILE" value="/work/projects/neutrino-proxy-client/app.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" />
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -115,9 +114,9 @@ public class ProxyMessage {
|
||||
.setInfo(data.toJSONString());
|
||||
}
|
||||
|
||||
public static ProxyMessage buildConnectMessage(String info) {
|
||||
public static ProxyMessage buildConnectMessage(String visitorId) {
|
||||
return create().setType(TYPE_CONNECT)
|
||||
.setInfo(info);
|
||||
.setInfo(visitorId);
|
||||
}
|
||||
|
||||
public static ProxyMessage buildDisconnectMessage(String info) {
|
||||
@@ -125,9 +124,9 @@ public class ProxyMessage {
|
||||
.setInfo(info);
|
||||
}
|
||||
|
||||
public static ProxyMessage buildTransferMessage(String info, byte[] data) {
|
||||
public static ProxyMessage buildTransferMessage(String visitorId, byte[] data) {
|
||||
return create().setType(TYPE_TRANSFER)
|
||||
.setInfo(info)
|
||||
.setInfo(visitorId)
|
||||
.setData(data);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,11 @@
|
||||
<artifactId>druid</artifactId>
|
||||
<version>1.1.24</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.29</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<!-- <build>-->
|
||||
|
||||
@@ -24,7 +24,6 @@ package fun.asgc.neutrino.proxy.server;
|
||||
|
||||
import fun.asgc.neutrino.core.annotation.EnableJob;
|
||||
import fun.asgc.neutrino.core.annotation.NeutrinoApplication;
|
||||
import fun.asgc.neutrino.core.base.GlobalConfig;
|
||||
import fun.asgc.neutrino.core.context.NeutrinoLauncher;
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,8 +26,11 @@ import com.google.common.collect.Lists;
|
||||
import fun.asgc.neutrino.core.annotation.PreLoad;
|
||||
import fun.asgc.neutrino.core.db.template.JdbcTemplate;
|
||||
import fun.asgc.neutrino.core.util.*;
|
||||
import fun.asgc.neutrino.proxy.server.base.rest.config.SqliteConfig;
|
||||
import fun.asgc.neutrino.proxy.server.base.rest.config.DbConfig;
|
||||
import fun.asgc.neutrino.proxy.server.constant.DbTypeEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.sqlite.SQLiteConfig;
|
||||
import org.sqlite.SQLiteDataSource;
|
||||
|
||||
import java.sql.DriverManager;
|
||||
import java.util.List;
|
||||
@@ -41,11 +44,18 @@ import java.util.List;
|
||||
@PreLoad("init")
|
||||
public class DBInitialize {
|
||||
private static List<String> initDataTableNameList = Lists.newArrayList("user", "license", "port_pool", "port_mapping", "job_info");
|
||||
private static SqliteConfig sqliteConfig;
|
||||
private static DbConfig dbConfig;
|
||||
private static JdbcTemplate jdbcTemplate;
|
||||
|
||||
private static DbTypeEnum dbTypeEnum;
|
||||
|
||||
public static void init() throws Exception {
|
||||
sqliteConfig = ConfigUtil.getYmlConfig(SqliteConfig.class);
|
||||
dbConfig = ConfigUtil.getYmlConfig(DbConfig.class);
|
||||
Assert.notNull(dbConfig.getType(), "neutrino.data.db.type不能为空!");
|
||||
dbTypeEnum = DbTypeEnum.of(dbConfig.getType());
|
||||
Assert.notNull(dbTypeEnum, "neutrino.data.db.type取值异常!");
|
||||
|
||||
log.info("{}数据库初始化...", dbConfig.getType());
|
||||
jdbcTemplate = getJdbcTemplate();
|
||||
initDBStructure();
|
||||
initDBData();
|
||||
@@ -55,7 +65,7 @@ public class DBInitialize {
|
||||
* 初始化数据库结构
|
||||
*/
|
||||
private static void initDBStructure() throws Exception {
|
||||
List<String> lines = FileUtil.readContentAsStringList("classpath:/sql/init-structure.sql");
|
||||
List<String> lines = FileUtil.readContentAsStringList(String.format("classpath:/sql/%s/init-structure.sql", dbConfig.getType()));
|
||||
if (CollectionUtil.isEmpty(lines)) {
|
||||
return;
|
||||
}
|
||||
@@ -87,7 +97,7 @@ public class DBInitialize {
|
||||
if (count > 0) {
|
||||
continue;
|
||||
}
|
||||
List<String> lines = FileUtil.readContentAsStringList(String.format("classpath:/sql/%s.data.sql", tableName));
|
||||
List<String> lines = FileUtil.readContentAsStringList(String.format("classpath:/sql/%s/%s.data.sql", dbConfig.getType(), tableName));
|
||||
if (CollectionUtil.isEmpty(lines)) {
|
||||
return;
|
||||
}
|
||||
@@ -116,15 +126,30 @@ public class DBInitialize {
|
||||
() -> null == jdbcTemplate,
|
||||
DBInitialize.class,
|
||||
() -> {
|
||||
Class.forName(sqliteConfig.getDriverClass());
|
||||
//建立一个数据库名data.db的连接,如果不存在就在当前目录下创建之
|
||||
DriverManager.getConnection(sqliteConfig.getUrl());
|
||||
// 创建数据源
|
||||
DruidDataSource dataSource = new DruidDataSource();
|
||||
dataSource.setUrl(sqliteConfig.getUrl());
|
||||
dataSource.setDriverClassName(sqliteConfig.getDriverClass());
|
||||
// 创建jdbcTemplate
|
||||
jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
if (DbTypeEnum.SQLITE == dbTypeEnum) {
|
||||
//建立一个数据库名data.db的连接,如果不存在就在当前目录下创建之
|
||||
DriverManager.getConnection(dbConfig.getUrl());
|
||||
// 创建数据源
|
||||
SQLiteDataSource dataSource = new SQLiteDataSource();
|
||||
dataSource.setUrl(dbConfig.getUrl());
|
||||
dataSource.setJournalMode(SQLiteConfig.JournalMode.WAL.getValue());
|
||||
// 创建jdbcTemplate
|
||||
jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
} else if (DbTypeEnum.MYSQL == dbTypeEnum) {
|
||||
DruidDataSource dataSource = new DruidDataSource();
|
||||
dataSource.setDriverClassName(dbConfig.getDriverClass());
|
||||
dataSource.setUrl(dbConfig.getUrl());
|
||||
dataSource.setInitialSize(5);
|
||||
dataSource.setMinIdle(5);
|
||||
dataSource.setMaxActive(20);
|
||||
dataSource.setMaxWait(60000);
|
||||
dataSource.setPoolPreparedStatements(true);
|
||||
dataSource.setUsername(dbConfig.getUsername());
|
||||
dataSource.setPassword(dbConfig.getPassword());
|
||||
|
||||
// 创建jdbcTemplate
|
||||
jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
}
|
||||
},
|
||||
() -> jdbcTemplate
|
||||
);
|
||||
|
||||
@@ -40,7 +40,13 @@ public class SystemContextHolder {
|
||||
}
|
||||
|
||||
public static UserDO getUser() {
|
||||
return systemContextHolder.get().getUser();
|
||||
SystemContext context = getContext();
|
||||
return (null == context) ? null : context.getUser();
|
||||
}
|
||||
|
||||
public static Integer getUserId() {
|
||||
UserDO userDO = getUser();
|
||||
return (null == userDO) ? null : userDO.getId();
|
||||
}
|
||||
|
||||
public static String getToken() {
|
||||
|
||||
@@ -24,6 +24,7 @@ package fun.asgc.neutrino.proxy.server.base.rest.config;
|
||||
import fun.asgc.neutrino.core.annotation.Configuration;
|
||||
import fun.asgc.neutrino.core.annotation.NonIntercept;
|
||||
import fun.asgc.neutrino.core.annotation.Value;
|
||||
import fun.asgc.neutrino.proxy.server.constant.DbTypeEnum;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
@@ -33,8 +34,14 @@ import lombok.Data;
|
||||
*/
|
||||
@Data
|
||||
@NonIntercept
|
||||
@Configuration(prefix = "neutrino.data.sqlite")
|
||||
public class SqliteConfig {
|
||||
@Configuration(prefix = "neutrino.data.db")
|
||||
public class DbConfig {
|
||||
/**
|
||||
* 数据库类型
|
||||
* {@link DbTypeEnum}
|
||||
*/
|
||||
@Value("type")
|
||||
private String type;
|
||||
/**
|
||||
* 连接url
|
||||
*/
|
||||
@@ -45,4 +52,16 @@ public class SqliteConfig {
|
||||
*/
|
||||
@Value("driver-class")
|
||||
private String driverClass;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@Value("username")
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
@Value("password")
|
||||
private String password;
|
||||
}
|
||||
@@ -21,9 +21,12 @@
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.base.rest.config;
|
||||
|
||||
import com.alibaba.druid.pool.DruidDataSource;
|
||||
import fun.asgc.neutrino.core.annotation.*;
|
||||
import fun.asgc.neutrino.core.base.Ordered;
|
||||
import fun.asgc.neutrino.core.db.template.JdbcTemplate;
|
||||
import fun.asgc.neutrino.proxy.server.constant.DbTypeEnum;
|
||||
import org.sqlite.SQLiteConfig;
|
||||
import org.sqlite.SQLiteDataSource;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
@@ -37,14 +40,31 @@ import javax.sql.DataSource;
|
||||
@Component
|
||||
public class RestConfiguration {
|
||||
@Autowired
|
||||
private SqliteConfig sqliteConfig;
|
||||
private DbConfig dbConfig;
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
SQLiteDataSource dataSource = new SQLiteDataSource();
|
||||
dataSource.setUrl(sqliteConfig.getUrl());
|
||||
dataSource.setJournalMode("WAL");
|
||||
return dataSource;
|
||||
DbTypeEnum dbTypeEnum = DbTypeEnum.of(dbConfig.getType());
|
||||
if (DbTypeEnum.SQLITE == dbTypeEnum) {
|
||||
SQLiteDataSource dataSource = new SQLiteDataSource();
|
||||
dataSource.setUrl(dbConfig.getUrl());
|
||||
dataSource.setJournalMode(SQLiteConfig.JournalMode.WAL.getValue());
|
||||
return dataSource;
|
||||
} else if (DbTypeEnum.MYSQL == dbTypeEnum) {
|
||||
DruidDataSource dataSource = new DruidDataSource();
|
||||
dataSource.setDriverClassName(dbConfig.getDriverClass());
|
||||
dataSource.setUrl(dbConfig.getUrl());
|
||||
dataSource.setInitialSize(5);
|
||||
dataSource.setMinIdle(5);
|
||||
dataSource.setMaxActive(20);
|
||||
dataSource.setMaxWait(60000);
|
||||
dataSource.setPoolPreparedStatements(true);
|
||||
dataSource.setUsername(dbConfig.getUsername());
|
||||
dataSource.setPassword(dbConfig.getPassword());
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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.constant;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/23
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ClientConnectTypeEnum {
|
||||
CONNECT(1, "连接"),
|
||||
DISCONNECT(2, "断开连接");
|
||||
|
||||
private Integer type;
|
||||
private String desc;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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.constant;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 数据库类型美剧
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/25
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum DbTypeEnum {
|
||||
SQLITE("sqlite"),
|
||||
MYSQL("mysql");
|
||||
|
||||
private String type;
|
||||
|
||||
private static final Map<String, DbTypeEnum> cache = Stream.of(DbTypeEnum.values()).collect(Collectors.toMap(DbTypeEnum::getType, Function.identity()));
|
||||
|
||||
public static DbTypeEnum of(String type) {
|
||||
return cache.get(type);
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,11 @@ package fun.asgc.neutrino.proxy.server.constant;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 启用状态枚举
|
||||
* @author: aoshiguchen
|
||||
@@ -34,7 +39,11 @@ import lombok.Getter;
|
||||
public enum EnableStatusEnum {
|
||||
ENABLE(1, "启用"),
|
||||
DISABLE(2, "禁用");
|
||||
private static Map<Integer, EnableStatusEnum> CACHE = Stream.of(EnableStatusEnum.values()).collect(Collectors.toMap(EnableStatusEnum::getStatus, Function.identity()));
|
||||
|
||||
private Integer status;
|
||||
private String desc;
|
||||
public static EnableStatusEnum of(Integer status) {
|
||||
return CACHE.get(status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,9 +45,12 @@ public enum ExceptionConstant {
|
||||
// license管理(12000)
|
||||
LICENSE_NAME_CANNOT_REPEAT(12000, "license名称不能重复"),
|
||||
LICENSE_NOT_EXIST(12001, "license数据不存在"),
|
||||
ORIGIN_PASSWORD_CHECK_FAIL(12002, "原密码验证失败"),
|
||||
LOGIN_PASSWORD_LENGTH_CHECK_FAIL(12003, "登录密码不能小于6位数"),
|
||||
LOGIN_PASSWORD_NO_CHANGE_MODIFY_FAIL(12004, "密码没有变化,修改失败"),
|
||||
// 端口池管理(13000)
|
||||
PORT_CANNOT_REPEAT(13000,"端口不能重复"),
|
||||
PORT_NOT_EXIST(13001, "该端口在端口池中不存在,不允许映射"),
|
||||
PORT_NOT_EXIST(13001, "该端口在端口池中不存在"),
|
||||
// 端口映射管理(14000)
|
||||
PORT_MAPPING_NOT_EXIST(14000, "端口映射记录不存在"),
|
||||
PORT_CANNOT_REPEAT_MAPPING(14001, "服务端口[{}]不能重复映射"),
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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.constant;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/23
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum SuccessCodeEnum {
|
||||
SUCCESS(1, "成功"),
|
||||
FAIL(2, "失败");
|
||||
|
||||
private Integer code;
|
||||
private String desc;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import fun.asgc.neutrino.core.annotation.Autowired;
|
||||
import fun.asgc.neutrino.core.annotation.NonIntercept;
|
||||
import fun.asgc.neutrino.core.db.page.Page;
|
||||
import fun.asgc.neutrino.core.db.page.PageQuery;
|
||||
import fun.asgc.neutrino.core.web.annotation.GetMapping;
|
||||
import fun.asgc.neutrino.core.web.annotation.RequestMapping;
|
||||
import fun.asgc.neutrino.core.web.annotation.RestController;
|
||||
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 lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/26
|
||||
*/
|
||||
@Slf4j
|
||||
@NonIntercept
|
||||
@RequestMapping("client-connect-record")
|
||||
@RestController
|
||||
public class ClientConnectRecordController {
|
||||
@Autowired
|
||||
private ClientConnectRecordService clientConnectRecordService;
|
||||
|
||||
@GetMapping("page")
|
||||
public Page<ClientConnectRecordListRes> page(PageQuery pageQuery, ClientConnectRecordListReq req) {
|
||||
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
|
||||
return clientConnectRecordService.page(pageQuery, req);
|
||||
}
|
||||
}
|
||||
@@ -21,11 +21,20 @@
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller;
|
||||
|
||||
import fun.asgc.neutrino.core.annotation.Autowired;
|
||||
import fun.asgc.neutrino.core.annotation.NonIntercept;
|
||||
import fun.asgc.neutrino.core.db.page.Page;
|
||||
import fun.asgc.neutrino.core.db.page.PageQuery;
|
||||
import fun.asgc.neutrino.core.web.annotation.GetMapping;
|
||||
import fun.asgc.neutrino.core.web.annotation.RequestMapping;
|
||||
import fun.asgc.neutrino.core.web.annotation.RestController;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 报表管理
|
||||
@@ -37,6 +46,22 @@ import fun.asgc.neutrino.proxy.server.controller.res.ReportDataViewRes;
|
||||
@RestController
|
||||
public class ReportController {
|
||||
|
||||
@Autowired
|
||||
private ReportService reportService;
|
||||
|
||||
/**
|
||||
* 数据一览:
|
||||
* 在线用户数 查询token有效的用户ID数
|
||||
* 在线Client数 查询license表,状态为在线的数量
|
||||
* 今日流量 上行/下行/总计
|
||||
* 历史流量 上行/下行/总计
|
||||
*
|
||||
* 1、点击在线用户数:下方展示在线的用户列表,带分页
|
||||
* 2、点击在线client数:下方展示在线的license列表,带分页
|
||||
* 3、点击今日流量:下方展示今日的流量折线图(小时)。 筛选项:全部/用户列表
|
||||
* 4、点击历史流量:下方展示历史流量折线图。筛选项:日/月 日期选择:日(展示最近30天,最多跨30日),月(展示最近12个月,最多跨12个月)
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("data-view")
|
||||
public ReportDataViewRes dataView() {
|
||||
return new ReportDataViewRes()
|
||||
@@ -46,4 +71,30 @@ public class ReportController {
|
||||
.setTotalUpstreamFlow("23K").setTotalDownwardFlow("47M")
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户流量报表分页
|
||||
* @param pageQuery
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("user/flow-report/page")
|
||||
public Page<UserFlowReportRes> userFlowReportPage(PageQuery pageQuery, UserFlowReportReq req) {
|
||||
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
|
||||
|
||||
return reportService.userFlowReportPage(pageQuery, req);
|
||||
}
|
||||
|
||||
/**
|
||||
* license流量报表分页
|
||||
* @param pageQuery
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("license/flow-report/page")
|
||||
public Page<LicenseFlowReportRes> licenseFlowReportPage(PageQuery pageQuery, LicenseFlowReportReq req) {
|
||||
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
|
||||
|
||||
return reportService.licenseFlowReportPage(pageQuery, req);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,10 +26,15 @@ import fun.asgc.neutrino.core.annotation.NonIntercept;
|
||||
import fun.asgc.neutrino.core.db.page.Page;
|
||||
import fun.asgc.neutrino.core.db.page.PageQuery;
|
||||
import fun.asgc.neutrino.core.web.annotation.*;
|
||||
import fun.asgc.neutrino.proxy.server.base.rest.SystemContextHolder;
|
||||
import fun.asgc.neutrino.proxy.server.base.rest.annotation.OnlyAdmin;
|
||||
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 java.util.List;
|
||||
@@ -45,6 +50,8 @@ import java.util.List;
|
||||
public class UserController {
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
|
||||
@GetMapping("page")
|
||||
public Page<UserListRes> page(PageQuery pageQuery, UserListReq req) {
|
||||
@@ -101,6 +108,22 @@ public class UserController {
|
||||
ParamCheckUtil.checkNotNull(req, "req");
|
||||
ParamCheckUtil.checkNotNull(req.getId(), "id");
|
||||
ParamCheckUtil.checkNotEmpty(req.getLoginPassword(), "loginPassword");
|
||||
ParamCheckUtil.checkExpression(req.getLoginPassword().length() >= 6, ExceptionConstant.LOGIN_PASSWORD_LENGTH_CHECK_FAIL);
|
||||
|
||||
return userService.updatePassword(req);
|
||||
}
|
||||
|
||||
@PostMapping("current-user/update/password")
|
||||
public UserUpdatePasswordRes currentUserUpdatePassword(@RequestBody UserUpdatePasswordReq req) {
|
||||
ParamCheckUtil.checkNotNull(req, "req");
|
||||
ParamCheckUtil.checkNotEmpty(req.getOldLoginPassword(), "oldLoginPassword");
|
||||
ParamCheckUtil.checkNotEmpty(req.getLoginPassword(), "loginPassword");
|
||||
req.setId(SystemContextHolder.getUserId());
|
||||
ParamCheckUtil.checkExpression(req.getLoginPassword().length() >= 6, ExceptionConstant.LOGIN_PASSWORD_LENGTH_CHECK_FAIL);
|
||||
// 验证原密码
|
||||
Integer userId = req.getId();
|
||||
UserDO userDO = userMapper.findById(userId);
|
||||
ParamCheckUtil.checkExpression(Md5Util.encode(req.getOldLoginPassword()).equals(userDO.getLoginPassword()), ExceptionConstant.ORIGIN_PASSWORD_CHECK_FAIL);
|
||||
|
||||
return userService.updatePassword(req);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import fun.asgc.neutrino.core.annotation.Autowired;
|
||||
import fun.asgc.neutrino.core.annotation.NonIntercept;
|
||||
import fun.asgc.neutrino.core.db.page.Page;
|
||||
import fun.asgc.neutrino.core.db.page.PageQuery;
|
||||
import fun.asgc.neutrino.core.web.annotation.GetMapping;
|
||||
import fun.asgc.neutrino.core.web.annotation.RequestMapping;
|
||||
import fun.asgc.neutrino.core.web.annotation.RestController;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/10/20
|
||||
*/
|
||||
@NonIntercept
|
||||
@RequestMapping("user-login-record")
|
||||
@RestController
|
||||
public class UserLoginRecordController {
|
||||
@Autowired
|
||||
private UserLoginRecordService userLoginRecordService;
|
||||
|
||||
@GetMapping("page")
|
||||
public Page<UserLoginRecordListRes> page(PageQuery pageQuery, UserLoginRecordListReq req) {
|
||||
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
|
||||
|
||||
return userLoginRecordService.page(pageQuery, req);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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/11/26
|
||||
*/
|
||||
@Data
|
||||
public class ClientConnectRecordListReq {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 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/10/20
|
||||
*/
|
||||
@Data
|
||||
public class UserLoginRecordListReq {
|
||||
|
||||
}
|
||||
@@ -31,5 +31,6 @@ import lombok.Data;
|
||||
@Data
|
||||
public class UserUpdatePasswordReq {
|
||||
private Integer id;
|
||||
private String oldLoginPassword;
|
||||
private String loginPassword;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 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/11/26
|
||||
*/
|
||||
@Data
|
||||
public class ClientConnectRecordListRes {
|
||||
private Integer id;
|
||||
private String ip;
|
||||
private Integer licenseId;
|
||||
private Integer type;
|
||||
private Integer userId;
|
||||
private String userName;
|
||||
private String licenseName;
|
||||
private String msg;
|
||||
/**
|
||||
* 1、成功
|
||||
* 2、失败
|
||||
*/
|
||||
private Integer code;
|
||||
private String err;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 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 LicenseFlowReportRes {
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Integer userId;
|
||||
/**
|
||||
* 用户名称
|
||||
*/
|
||||
private String userName;
|
||||
/**
|
||||
* licenseId
|
||||
*/
|
||||
private Integer licenseId;
|
||||
/**
|
||||
* license名称
|
||||
*/
|
||||
private String licenseName;
|
||||
/**
|
||||
* 写入字节数
|
||||
*/
|
||||
private Long writeBytes;
|
||||
/**
|
||||
* 读取字节数
|
||||
*/
|
||||
private Long readBytes;
|
||||
/**
|
||||
* 写入流量描述
|
||||
*/
|
||||
private String writeFlowStr;
|
||||
/**
|
||||
* 读取流量描述
|
||||
*/
|
||||
private String readFlowStr;
|
||||
/**
|
||||
* 流量描述
|
||||
*/
|
||||
private String flowStr;
|
||||
/**
|
||||
* 报表时间
|
||||
*/
|
||||
private Date date;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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/10/20
|
||||
*/
|
||||
@Data
|
||||
public class UserLoginRecordListRes {
|
||||
private Integer id;
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Integer userId;
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String userName;
|
||||
/**
|
||||
* ip
|
||||
*/
|
||||
private String ip;
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private Integer type;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package fun.asgc.neutrino.proxy.server.dal;
|
||||
|
||||
import fun.asgc.neutrino.core.annotation.Component;
|
||||
import fun.asgc.neutrino.core.aop.Intercept;
|
||||
import fun.asgc.neutrino.core.db.annotation.ResultType;
|
||||
import fun.asgc.neutrino.core.db.annotation.Select;
|
||||
import fun.asgc.neutrino.core.db.mapper.SqlMapper;
|
||||
import fun.asgc.neutrino.core.db.page.Page;
|
||||
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.entity.ClientConnectRecordDO;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/23
|
||||
*/
|
||||
@Intercept(ignoreGlobal = true)
|
||||
@Component
|
||||
public interface ClientConnectRecordMapper extends SqlMapper {
|
||||
|
||||
void add(ClientConnectRecordDO clientConnectRecordDO);
|
||||
|
||||
@ResultType(ClientConnectRecordListRes.class)
|
||||
@Select("select * from client_connect_record order by id desc")
|
||||
void page(Page page, ClientConnectRecordListReq req);
|
||||
}
|
||||
@@ -1,3 +1,24 @@
|
||||
/**
|
||||
* 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 fun.asgc.neutrino.core.annotation.Component;
|
||||
@@ -16,6 +37,21 @@ import java.util.Date;
|
||||
public interface DataCleanMapper extends SqlMapper {
|
||||
|
||||
@Delete("delete from `job_log` where create_time < ?")
|
||||
void cleanJobLog(long date);
|
||||
void cleanJobLog(Date date);
|
||||
|
||||
@Delete("delete from `user_login_record` where create_time < ?")
|
||||
void cleanUserLoginRecord(Date date);
|
||||
|
||||
@Delete("delete from `client_connect_record` where create_time < ?")
|
||||
void cleanClientConnectRecord(Date date);
|
||||
|
||||
@Delete("delete from `flow_report_minute` where create_time < ?")
|
||||
void cleanFlowMinuteReport(Date date);
|
||||
|
||||
@Delete("delete from `flow_report_hour` where create_time < ?")
|
||||
void cleanFlowHourReport(Date date);
|
||||
|
||||
@Delete("delete from `flow_report_day` where create_time < ?")
|
||||
void cleanFlowDayReport(Date date);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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 fun.asgc.neutrino.core.annotation.Component;
|
||||
import fun.asgc.neutrino.core.annotation.Param;
|
||||
import fun.asgc.neutrino.core.aop.Intercept;
|
||||
import fun.asgc.neutrino.core.db.annotation.Delete;
|
||||
import fun.asgc.neutrino.core.db.annotation.Insert;
|
||||
import fun.asgc.neutrino.core.db.annotation.ResultType;
|
||||
import fun.asgc.neutrino.core.db.annotation.Select;
|
||||
import fun.asgc.neutrino.core.db.mapper.SqlMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.FlowReportDayDO;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/10/28
|
||||
*/
|
||||
@Intercept(ignoreGlobal = true)
|
||||
@Component
|
||||
public interface FlowReportDayMapper extends SqlMapper {
|
||||
@Select("select * from flow_report_day where license_id = :licenseId and date_str = :dateStr")
|
||||
FlowReportDayDO findOne(@Param("licenseId") Integer licenseId, @Param("dateStr") String dateStr);
|
||||
|
||||
@Insert("insert into flow_report_day(`user_id`,`license_id`,`write_bytes`,`read_bytes`,`date`,`date_str`,`create_time`) values(:userId,:licenseId,:writeBytes,:readBytes,:date,:dateStr,:createTime)")
|
||||
void add(FlowReportDayDO flowReportDayDO);
|
||||
|
||||
@Delete("delete from flow_report_day where date_str = :dateStr")
|
||||
void deleteByDateStr(@Param("dateStr") String dateStr);
|
||||
|
||||
@ResultType(FlowReportDayDO.class)
|
||||
@Select("select * from flow_report_day where date >= :startDate and date <= :endDate")
|
||||
List<FlowReportDayDO> findListByDateRange(@Param("startDate") Date startDate, @Param("endDate") Date endDate);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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 fun.asgc.neutrino.core.annotation.Component;
|
||||
import fun.asgc.neutrino.core.annotation.Param;
|
||||
import fun.asgc.neutrino.core.aop.Intercept;
|
||||
import fun.asgc.neutrino.core.db.annotation.Delete;
|
||||
import fun.asgc.neutrino.core.db.annotation.Insert;
|
||||
import fun.asgc.neutrino.core.db.annotation.ResultType;
|
||||
import fun.asgc.neutrino.core.db.annotation.Select;
|
||||
import fun.asgc.neutrino.core.db.mapper.SqlMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.FlowReportHourDO;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/10/28
|
||||
*/
|
||||
@Intercept(ignoreGlobal = true)
|
||||
@Component
|
||||
public interface FlowReportHourMapper extends SqlMapper {
|
||||
@Select("select * from flow_report_hour where license_id = :licenseId and date_str = :dateStr")
|
||||
FlowReportHourDO findOne(@Param("licenseId") Integer licenseId, @Param("dateStr") String dateStr);
|
||||
|
||||
@Insert("insert into flow_report_hour(`user_id`,`license_id`,`write_bytes`,`read_bytes`,`date`,`date_str`,`create_time`) values(:userId,:licenseId,:writeBytes,:readBytes,:date,:dateStr,:createTime)")
|
||||
void add(FlowReportHourDO flowReportHourDO);
|
||||
|
||||
@Delete("delete from flow_report_hour where date_str = :dateStr")
|
||||
void deleteByDateStr(@Param("dateStr") String dateStr);
|
||||
|
||||
@ResultType(FlowReportHourDO.class)
|
||||
@Select("select * from flow_report_hour where date >= :startDate and date <= :endDate")
|
||||
List<FlowReportHourDO> findListByDateRange(@Param("startDate") Date startDate, @Param("endDate") Date endDate);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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 fun.asgc.neutrino.core.annotation.Component;
|
||||
import fun.asgc.neutrino.core.annotation.Param;
|
||||
import fun.asgc.neutrino.core.aop.Intercept;
|
||||
import fun.asgc.neutrino.core.db.annotation.Insert;
|
||||
import fun.asgc.neutrino.core.db.annotation.ResultType;
|
||||
import fun.asgc.neutrino.core.db.annotation.Select;
|
||||
import fun.asgc.neutrino.core.db.mapper.SqlMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.FlowReportMinuteDO;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/10/24
|
||||
*/
|
||||
@Intercept(ignoreGlobal = true)
|
||||
@Component
|
||||
public interface FlowReportMinuteMapper extends SqlMapper {
|
||||
@Select("select * from flow_report_minute where license_id = :licenseId and date = :date")
|
||||
FlowReportMinuteDO findOne(@Param("licenseId") Integer licenseId, @Param("date") String date);
|
||||
|
||||
@ResultType(FlowReportMinuteDO.class)
|
||||
@Select("select * from flow_report_minute where license_id in (:licenseIds) and date = :date")
|
||||
List<FlowReportMinuteDO> findList(@Param("licenseIds") Set<Integer> licenseIds, @Param("date") String date);
|
||||
|
||||
@ResultType(FlowReportMinuteDO.class)
|
||||
@Select("select * from flow_report_minute where date >= :startDate and date <= :endDate")
|
||||
List<FlowReportMinuteDO> findListByDateRange(@Param("startDate") Date startDate, @Param("endDate") Date endDate);
|
||||
|
||||
@Insert("insert into flow_report_minute(`user_id`,`license_id`,`write_bytes`,`read_bytes`,`date`,`date_str`,`create_time`) values(:userId,:licenseId,:writeBytes,:readBytes,:date,:dateStr,:createTime)")
|
||||
void add(FlowReportMinuteDO flowReportMinuteDO);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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 fun.asgc.neutrino.core.annotation.Component;
|
||||
import fun.asgc.neutrino.core.annotation.Param;
|
||||
import fun.asgc.neutrino.core.aop.Intercept;
|
||||
import fun.asgc.neutrino.core.db.annotation.Delete;
|
||||
import fun.asgc.neutrino.core.db.annotation.Insert;
|
||||
import fun.asgc.neutrino.core.db.annotation.Select;
|
||||
import fun.asgc.neutrino.core.db.mapper.SqlMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.FlowReportMonthDO;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/10/28
|
||||
*/
|
||||
@Intercept(ignoreGlobal = true)
|
||||
@Component
|
||||
public interface FlowReportMonthMapper extends SqlMapper {
|
||||
@Select("select * from flow_report_month where license_id = :licenseId and date_str = :dateStr")
|
||||
FlowReportMonthDO findOne(@Param("licenseId") Integer licenseId, @Param("dateStr") String dateStr);
|
||||
|
||||
@Insert("insert into flow_report_month(`user_id`,`license_id`,`write_bytes`,`read_bytes`,`date`,`date_str`,`create_time`) values(:userId,:licenseId,:writeBytes,:readBytes,:date,:dateStr,:createTime)")
|
||||
void add(FlowReportMonthDO flowReportMonthDO);
|
||||
|
||||
@Delete("delete from flow_report_month where date_str = :dateStr")
|
||||
void deleteByDateStr(@Param("dateStr") String dateStr);
|
||||
}
|
||||
@@ -30,7 +30,6 @@ import fun.asgc.neutrino.core.db.annotation.Select;
|
||||
import fun.asgc.neutrino.core.db.annotation.Update;
|
||||
import fun.asgc.neutrino.core.db.mapper.SqlMapper;
|
||||
import fun.asgc.neutrino.core.db.page.Page;
|
||||
import fun.asgc.neutrino.core.db.page.PageQuery;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.LicenseListReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.LicenseListRes;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.LicenseDO;
|
||||
@@ -59,6 +58,14 @@ public interface LicenseMapper extends SqlMapper {
|
||||
@Select("select * from license where enable = 1")
|
||||
List<LicenseListRes> list();
|
||||
|
||||
@ResultType(LicenseDO.class)
|
||||
@Select("select * from license")
|
||||
List<LicenseDO> listAll();
|
||||
|
||||
@ResultType(LicenseDO.class)
|
||||
@Select("select * from `license` where user_id = :userId")
|
||||
List<LicenseDO> listByUserId(@Param("userId") Integer userId);
|
||||
|
||||
/**
|
||||
* 新增license
|
||||
* @param license
|
||||
@@ -71,7 +78,10 @@ public interface LicenseMapper extends SqlMapper {
|
||||
@Update("update `license` set is_online = :isOnline, update_time = :updateTime where id = :id")
|
||||
void updateOnlineStatus(@Param("id") Integer id, @Param("isOnline") Integer isOnline, @Param("updateTime") Date updateTime);
|
||||
|
||||
@Update("update `license` set key = :key,update_time = :updateTime where id = :id")
|
||||
@Update("update `license` set is_online = :isOnline, update_time = :updateTime")
|
||||
void updateOnlineStatus(@Param("isOnline") Integer isOnline, @Param("updateTime") Date updateTime);
|
||||
|
||||
@Update("update `license` set `key` = :key,update_time = :updateTime where id = :id")
|
||||
void reset(@Param("id") Integer id, @Param("key") String key, @Param("updateTime") Date updateTime);
|
||||
|
||||
@Delete("delete from `license` where id = ?")
|
||||
@@ -95,6 +105,6 @@ public interface LicenseMapper extends SqlMapper {
|
||||
@Select("select * from `license` where user_id = :userId and name =:name and id not in (:excludeIds) limit 0,1")
|
||||
LicenseDO checkRepeat(@Param("userId") Integer userId, @Param("name") String name, @Param("excludeIds") Set<Integer> excludeIds);
|
||||
|
||||
@Select("select * from `license` where key = ?")
|
||||
@Select("select * from `license` where `key` = ?")
|
||||
LicenseDO findByKey(String licenseKey);
|
||||
}
|
||||
|
||||
@@ -74,9 +74,20 @@ public interface PortMappingMapper extends SqlMapper {
|
||||
@Select("select * from port_mapping where license_id = ? and enable = 1")
|
||||
List<PortMappingDO> findEnableListByLicenseId(Integer licenseId);
|
||||
|
||||
@ResultType(PortMappingDO.class)
|
||||
@Select("select * from port_mapping where server_port = :serverPort")
|
||||
List<PortMappingDO> findListByServerPort(@Param("serverPort") Integer serverPort);
|
||||
|
||||
@ResultType(PortMappingDO.class)
|
||||
@Select("select * from port_mapping where license_id = ?")
|
||||
List<PortMappingDO> findListByLicenseId(Integer licenseId);
|
||||
|
||||
@Update("update `port_mapping` set is_online = :isOnline,update_time = :updateTime where license_id = :licenseId and server_port = :serverPort")
|
||||
void updateOnlineStatus(@Param("licenseId") Integer licenseId, @Param("serverPort") Integer serverPort, @Param("isOnline") Integer isOnline, @Param("updateTime") Date updateTime);
|
||||
|
||||
@Update("update `port_mapping` set is_online = :isOnline,update_time = :updateTime where license_id = :licenseId")
|
||||
void updateOnlineStatus(@Param("licenseId") Integer licenseId, @Param("isOnline") Integer isOnline, @Param("updateTime") Date updateTime);
|
||||
|
||||
@Update("update `port_mapping` set is_online = :isOnline,update_time = :updateTime")
|
||||
void updateOnlineStatus(@Param("isOnline") Integer isOnline, @Param("updateTime") Date updateTime);
|
||||
}
|
||||
|
||||
@@ -62,4 +62,7 @@ public interface PortPoolMapper extends SqlMapper {
|
||||
|
||||
@Select("select * from port_pool where port = ? limit 0,1")
|
||||
PortPoolDO findByPort(Integer port);
|
||||
|
||||
@Select("select * from port_pool where id = ?")
|
||||
PortPoolDO findById(Integer id);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,12 @@ package fun.asgc.neutrino.proxy.server.dal;
|
||||
import fun.asgc.neutrino.core.annotation.Component;
|
||||
import fun.asgc.neutrino.core.aop.Intercept;
|
||||
import fun.asgc.neutrino.core.db.annotation.Insert;
|
||||
import fun.asgc.neutrino.core.db.annotation.ResultType;
|
||||
import fun.asgc.neutrino.core.db.annotation.Select;
|
||||
import fun.asgc.neutrino.core.db.mapper.SqlMapper;
|
||||
import fun.asgc.neutrino.core.db.page.Page;
|
||||
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.entity.UserLoginRecordDO;
|
||||
|
||||
/**
|
||||
@@ -42,4 +47,8 @@ public interface UserLoginRecordMapper extends SqlMapper {
|
||||
*/
|
||||
@Insert("insert into `user_login_record`(`user_id`,`ip`,`token`,`type`,`create_time`) values(:userId,:ip,:token,:type,:createTime)")
|
||||
int add(UserLoginRecordDO userLoginRecord);
|
||||
|
||||
@ResultType(UserLoginRecordListRes.class)
|
||||
@Select("select * from user_login_record order by create_time desc")
|
||||
void page(Page page, UserLoginRecordListReq req);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ import fun.asgc.neutrino.core.db.mapper.SqlMapper;
|
||||
import fun.asgc.neutrino.core.db.page.Page;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.UserListReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.UserListRes;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.PortPoolDO;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package fun.asgc.neutrino.proxy.server.dal;
|
||||
|
||||
import fun.asgc.neutrino.core.annotation.Component;
|
||||
import fun.asgc.neutrino.core.annotation.Param;
|
||||
import fun.asgc.neutrino.core.aop.Intercept;
|
||||
import fun.asgc.neutrino.core.db.mapper.SqlMapper;
|
||||
import fun.asgc.neutrino.core.db.page.Page;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.UserFlowReportRes;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/1/18
|
||||
*/
|
||||
@Intercept(ignoreGlobal = true)
|
||||
@Component
|
||||
public interface UserReportMapper extends SqlMapper {
|
||||
|
||||
void userFlowReportPage(Page<UserFlowReportRes> page, @Param("todayBegin") Date todayBegin, @Param("todayEnd") Date todayEnd);
|
||||
|
||||
}
|
||||
@@ -25,8 +25,6 @@ import fun.asgc.neutrino.core.annotation.Component;
|
||||
import fun.asgc.neutrino.core.annotation.Param;
|
||||
import fun.asgc.neutrino.core.aop.Intercept;
|
||||
import fun.asgc.neutrino.core.db.annotation.Delete;
|
||||
import fun.asgc.neutrino.core.db.annotation.Insert;
|
||||
import fun.asgc.neutrino.core.db.annotation.Select;
|
||||
import fun.asgc.neutrino.core.db.annotation.Update;
|
||||
import fun.asgc.neutrino.core.db.mapper.SqlMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.UserTokenDO;
|
||||
@@ -58,7 +56,7 @@ public interface UserTokenMapper extends SqlMapper {
|
||||
* @return
|
||||
*/
|
||||
// @Select("select * from user_token where token = ? and expiration_time > ?")
|
||||
UserTokenDO findByAvailableToken(String token, Long time);
|
||||
UserTokenDO findByAvailableToken(String token, Date date);
|
||||
|
||||
/**
|
||||
* 根据token删除记录
|
||||
@@ -69,4 +67,11 @@ public interface UserTokenMapper extends SqlMapper {
|
||||
|
||||
@Update("update user_token set expiration_time = :expirationTime where token = :token")
|
||||
void updateTokenExpirationTime(@Param("token") String token, @Param("expirationTime") Date expirationTime);
|
||||
|
||||
/**
|
||||
* 根据userId删除token
|
||||
* @param userId
|
||||
*/
|
||||
@Delete("delete from user_token where user_id = ?")
|
||||
void deleteByUserId(Integer userId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package fun.asgc.neutrino.proxy.server.dal.entity;
|
||||
|
||||
import fun.asgc.neutrino.core.db.annotation.Id;
|
||||
import fun.asgc.neutrino.core.db.annotation.Table;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/11/23
|
||||
*/
|
||||
@ToString
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
@Table("client_connect_record")
|
||||
public class ClientConnectRecordDO {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String ip;
|
||||
private Integer licenseId;
|
||||
private Integer type;
|
||||
private String msg;
|
||||
/**
|
||||
* 1、成功
|
||||
* 2、失败
|
||||
*/
|
||||
private Integer code;
|
||||
private String err;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
}
|
||||
@@ -21,64 +21,52 @@
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.dal.entity;
|
||||
|
||||
import fun.asgc.neutrino.core.db.annotation.Id;
|
||||
import fun.asgc.neutrino.core.db.annotation.Table;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/8/31
|
||||
* @date: 2022/10/24
|
||||
*/
|
||||
@ToString
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
@Table("user_connect_record")
|
||||
public class UserConnectRecordDO {
|
||||
private Integer id;
|
||||
/**
|
||||
* 服务端端口号
|
||||
*/
|
||||
private Integer serverPort;
|
||||
/**
|
||||
* userIp
|
||||
*/
|
||||
private String userIp;
|
||||
/**
|
||||
* 客户端IP
|
||||
*/
|
||||
private String clientIp;
|
||||
/**
|
||||
* 客户端信息
|
||||
*/
|
||||
private String clientLanInfo;
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private String userId;
|
||||
/**
|
||||
* licenseId
|
||||
*/
|
||||
private Integer licenseId;
|
||||
/**
|
||||
* licenseKey
|
||||
*/
|
||||
private String licenseKey;
|
||||
/**
|
||||
* writeBytes
|
||||
*/
|
||||
private Integer writeBytes;
|
||||
/**
|
||||
* readBytes
|
||||
*/
|
||||
private Integer readBytes;
|
||||
/**
|
||||
* type
|
||||
*/
|
||||
private Integer type;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
@Table("flow_report_day")
|
||||
public class FlowReportDayDO {
|
||||
@Id
|
||||
private Integer id;
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Integer userId;
|
||||
/**
|
||||
* licenseId
|
||||
*/
|
||||
private Integer licenseId;
|
||||
/**
|
||||
* 写入字节数
|
||||
*/
|
||||
private Long writeBytes;
|
||||
/**
|
||||
* 读取字节数
|
||||
*/
|
||||
private Long readBytes;
|
||||
/**
|
||||
* 报表统计时间
|
||||
*/
|
||||
private Date date;
|
||||
/**
|
||||
* 报表统计时间
|
||||
* yyyy-MM-dd
|
||||
*/
|
||||
private String dateStr;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 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.entity;
|
||||
|
||||
import fun.asgc.neutrino.core.db.annotation.Id;
|
||||
import fun.asgc.neutrino.core.db.annotation.Table;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/10/24
|
||||
*/
|
||||
@ToString
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
@Table("flow_report_hour")
|
||||
public class FlowReportHourDO {
|
||||
@Id
|
||||
private Integer id;
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Integer userId;
|
||||
/**
|
||||
* licenseId
|
||||
*/
|
||||
private Integer licenseId;
|
||||
/**
|
||||
* 写入字节数
|
||||
*/
|
||||
private Long writeBytes;
|
||||
/**
|
||||
* 读取字节数
|
||||
*/
|
||||
private Long readBytes;
|
||||
/**
|
||||
* 报表统计时间
|
||||
*/
|
||||
private Date date;
|
||||
/**
|
||||
* 报表统计时间
|
||||
* yyyy-MM-dd HH
|
||||
*/
|
||||
private String dateStr;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 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.entity;
|
||||
|
||||
import fun.asgc.neutrino.core.db.annotation.Id;
|
||||
import fun.asgc.neutrino.core.db.annotation.Table;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/10/24
|
||||
*/
|
||||
@ToString
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
@Table("flow_report_minute")
|
||||
public class FlowReportMinuteDO {
|
||||
@Id
|
||||
private Integer id;
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Integer userId;
|
||||
/**
|
||||
* licenseId
|
||||
*/
|
||||
private Integer licenseId;
|
||||
/**
|
||||
* 写入字节数
|
||||
*/
|
||||
private Integer writeBytes;
|
||||
/**
|
||||
* 读取字节数
|
||||
*/
|
||||
private Integer readBytes;
|
||||
/**
|
||||
* 报表统计时间
|
||||
*/
|
||||
private Date date;
|
||||
/**
|
||||
* 报表统计时间
|
||||
* yyyy-MM-dd HH:mm
|
||||
*/
|
||||
private String dateStr;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 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.entity;
|
||||
|
||||
import fun.asgc.neutrino.core.db.annotation.Id;
|
||||
import fun.asgc.neutrino.core.db.annotation.Table;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/10/24
|
||||
*/
|
||||
@ToString
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
@Table("flow_report_month")
|
||||
public class FlowReportMonthDO {
|
||||
@Id
|
||||
private Integer id;
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Integer userId;
|
||||
/**
|
||||
* licenseId
|
||||
*/
|
||||
private Integer licenseId;
|
||||
/**
|
||||
* 写入字节数
|
||||
*/
|
||||
private Long writeBytes;
|
||||
/**
|
||||
* 读取字节数
|
||||
*/
|
||||
private Long readBytes;
|
||||
/**
|
||||
* 报表统计时间
|
||||
*/
|
||||
private Date date;
|
||||
/**
|
||||
* 报表统计时间
|
||||
* yyyy-MM
|
||||
*/
|
||||
private String dateStr;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
}
|
||||
@@ -1,3 +1,24 @@
|
||||
/**
|
||||
* 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.job;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
@@ -34,14 +55,67 @@ public class DataCleanJob implements IJobHandler {
|
||||
* Job日志保存天数
|
||||
*/
|
||||
private static final Integer JOB_LOG_KEEP_DAYS = 7;
|
||||
/**
|
||||
* 用户登录日志保留天数
|
||||
*/
|
||||
private static final Integer USER_LOGIN_RECORD_KEEP_DAYS = 30;
|
||||
/**
|
||||
* 客户端连接记录保留天数
|
||||
*/
|
||||
private static final Integer CLIENT_CONNECT_RECORD_KEEP_DAYS = 30;
|
||||
|
||||
/**
|
||||
* 流量统计分钟报表记录保留天数
|
||||
*/
|
||||
private static final Integer FLOW_MINUTE_REPORT_KEEP_DAYS = 2;
|
||||
/**
|
||||
* 流量统计小时报表记录保留天数
|
||||
*/
|
||||
private static final Integer FLOW_HOUR_REPORT_KEEP_DAYS = 90;
|
||||
/**
|
||||
* 流量统计天报表记录保留天数
|
||||
*/
|
||||
private static final Integer FLOW_DAY_REPORT_KEEP_DAYS = 400;
|
||||
|
||||
@Override
|
||||
public void execute(String s) throws Exception {
|
||||
JobParams jobParams = getParams(s);
|
||||
|
||||
Date date = DateUtil.addDate(new Date(), Calendar.DATE, -1 * jobParams.getJobLogKeepDays());
|
||||
log.info("清理调度管理日志 date:{}", sdf.format(date));
|
||||
dataCleanMapper.cleanJobLog(date.getTime());
|
||||
{
|
||||
Date date = DateUtil.addDate(new Date(), Calendar.DATE, -1 * jobParams.getJobLogKeepDays());
|
||||
log.info("清理调度管理日志 date:{}", sdf.format(date));
|
||||
dataCleanMapper.cleanJobLog(date);
|
||||
}
|
||||
|
||||
{
|
||||
Date date = DateUtil.addDate(new Date(), Calendar.DATE, -1 * jobParams.getUserLoginRecordKeepDays());
|
||||
log.info("清理用户登录日志 date:{}", sdf.format(date));
|
||||
dataCleanMapper.cleanUserLoginRecord(date);
|
||||
}
|
||||
|
||||
{
|
||||
Date date = DateUtil.addDate(new Date(), Calendar.DATE, -1 * jobParams.getClientConnectRecordKeepDays());
|
||||
log.info("清理客户端连接日志 date:{}", sdf.format(date));
|
||||
dataCleanMapper.cleanClientConnectRecord(date);
|
||||
}
|
||||
|
||||
{
|
||||
Date date = DateUtil.addDate(new Date(), Calendar.DATE, -1 * jobParams.getFlowMinuteReportKeepDays());
|
||||
log.info("清理流通统计分钟报表日志 date:{}", sdf.format(date));
|
||||
dataCleanMapper.cleanFlowMinuteReport(date);
|
||||
}
|
||||
|
||||
{
|
||||
Date date = DateUtil.addDate(new Date(), Calendar.DATE, -1 * jobParams.getFlowHourReportKeepDays());
|
||||
log.info("清理流通统计小时报表日志 date:{}", sdf.format(date));
|
||||
dataCleanMapper.cleanFlowHourReport(date);
|
||||
}
|
||||
|
||||
{
|
||||
Date date = DateUtil.addDate(new Date(), Calendar.DATE, -1 * jobParams.getFlowDayReportKeepDays());
|
||||
log.info("清理流通统计日报表日志 date:{}", sdf.format(date));
|
||||
dataCleanMapper.cleanFlowDayReport(date);
|
||||
}
|
||||
}
|
||||
|
||||
public static JobParams getParams(String s) {
|
||||
@@ -53,12 +127,22 @@ public class DataCleanJob implements IJobHandler {
|
||||
// ignore
|
||||
}
|
||||
return new JobParams()
|
||||
.setJobLogKeepDays(JOB_LOG_KEEP_DAYS);
|
||||
.setJobLogKeepDays(JOB_LOG_KEEP_DAYS)
|
||||
.setUserLoginRecordKeepDays(USER_LOGIN_RECORD_KEEP_DAYS)
|
||||
.setClientConnectRecordKeepDays(CLIENT_CONNECT_RECORD_KEEP_DAYS)
|
||||
.setFlowMinuteReportKeepDays(FLOW_MINUTE_REPORT_KEEP_DAYS)
|
||||
.setFlowHourReportKeepDays(FLOW_HOUR_REPORT_KEEP_DAYS)
|
||||
.setFlowDayReportKeepDays(FLOW_DAY_REPORT_KEEP_DAYS);
|
||||
}
|
||||
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
public static class JobParams {
|
||||
private Integer jobLogKeepDays;
|
||||
private Integer userLoginRecordKeepDays;
|
||||
private Integer clientConnectRecordKeepDays;
|
||||
private Integer flowMinuteReportKeepDays;
|
||||
private Integer flowHourReportKeepDays;
|
||||
private Integer flowDayReportKeepDays;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* 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.job;
|
||||
|
||||
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.quartz.IJobHandler;
|
||||
import fun.asgc.neutrino.core.quartz.annotation.JobHandler;
|
||||
import fun.asgc.neutrino.core.util.CollectionUtil;
|
||||
import fun.asgc.neutrino.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 lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/10/28
|
||||
*/
|
||||
@Slf4j
|
||||
@NonIntercept
|
||||
@Component
|
||||
@JobHandler(name = "FlowReportForDayJob", cron = "0 0 1 * * ?", param = "")
|
||||
public class FlowReportForDayJob implements IJobHandler {
|
||||
@Autowired
|
||||
private FlowReportService flowReportService;
|
||||
@Autowired
|
||||
private LicenseMapper licenseMapper;
|
||||
@Autowired
|
||||
private FlowReportMinuteMapper flowReportMinuteMapper;
|
||||
@Autowired
|
||||
private FlowReportHourMapper flowReportHourMapper;
|
||||
@Autowired
|
||||
private FlowReportDayMapper flowReportDayMapper;
|
||||
|
||||
@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");
|
||||
Date date = DateUtil.parse(dateStr, "yyyy-MM-dd");
|
||||
Date startHourDate = DateUtil.getDayBegin(date);
|
||||
Date endHourDate = DateUtil.getDayEnd(date);
|
||||
|
||||
// 删除原来的记录
|
||||
flowReportDayMapper.deleteByDateStr(dateStr);
|
||||
|
||||
// 查询前一天的小时级别统计数据
|
||||
List<FlowReportHourDO> flowReportHourDOList = flowReportHourMapper.findListByDateRange(startHourDate, endHourDate);
|
||||
if (CollectionUtil.isEmpty(flowReportHourDOList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 汇总前一个天的天级别统计数据
|
||||
Map<Integer, FlowReportDayDO> map = new HashMap<>();
|
||||
for (FlowReportHourDO item : flowReportHourDOList) {
|
||||
FlowReportDayDO report = map.get(item.getLicenseId());
|
||||
if (null == report) {
|
||||
report = new FlowReportDayDO();
|
||||
map.put(item.getLicenseId(), report);
|
||||
}
|
||||
Long writeBytes = report.getWriteBytes() == null ? 0 : report.getWriteBytes();
|
||||
Long readBytes = report.getReadBytes() == null ? 0 : report.getReadBytes();
|
||||
|
||||
report.setUserId(item.getUserId());
|
||||
report.setLicenseId(item.getLicenseId());
|
||||
report.setWriteBytes(writeBytes + item.getWriteBytes());
|
||||
report.setReadBytes(readBytes + item.getReadBytes());
|
||||
report.setDate(date);
|
||||
report.setDateStr(dateStr);
|
||||
report.setCreateTime(now);
|
||||
}
|
||||
|
||||
for (FlowReportDayDO item : map.values()) {
|
||||
flowReportDayMapper.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* 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.job;
|
||||
|
||||
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.quartz.IJobHandler;
|
||||
import fun.asgc.neutrino.core.quartz.annotation.JobHandler;
|
||||
import fun.asgc.neutrino.core.util.CollectionUtil;
|
||||
import fun.asgc.neutrino.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 lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/10/28
|
||||
*/
|
||||
@Slf4j
|
||||
@NonIntercept
|
||||
@Component
|
||||
@JobHandler(name = "FlowReportForHourJob", cron = "0 0 */1 * * ?", param = "")
|
||||
public class FlowReportForHourJob implements IJobHandler {
|
||||
@Autowired
|
||||
private FlowReportService flowReportService;
|
||||
@Autowired
|
||||
private LicenseMapper licenseMapper;
|
||||
@Autowired
|
||||
private FlowReportMinuteMapper flowReportMinuteMapper;
|
||||
@Autowired
|
||||
private FlowReportHourMapper flowReportHourMapper;
|
||||
|
||||
@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");
|
||||
Date date = DateUtil.parse(dateStr, "yyyy-MM-dd HH");
|
||||
Date startHourDate = DateUtil.getHourBegin(date);
|
||||
Date endHourDate = DateUtil.getHourEnd(date);
|
||||
|
||||
// 删除原来的记录
|
||||
flowReportHourMapper.deleteByDateStr(dateStr);
|
||||
|
||||
// 查询前一个小时的分钟级别统计数据
|
||||
List<FlowReportMinuteDO> flowReportMinuteDOList = flowReportMinuteMapper.findListByDateRange(startHourDate, endHourDate);
|
||||
if (CollectionUtil.isEmpty(flowReportMinuteDOList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 汇总前一个小时的小时级别统计数据
|
||||
Map<Integer, FlowReportHourDO> map = new HashMap<>();
|
||||
for (FlowReportMinuteDO item : flowReportMinuteDOList) {
|
||||
FlowReportHourDO report = map.get(item.getLicenseId());
|
||||
if (null == report) {
|
||||
report = new FlowReportHourDO();
|
||||
map.put(item.getLicenseId(), report);
|
||||
}
|
||||
Long writeBytes = report.getWriteBytes() == null ? 0 : report.getWriteBytes();
|
||||
Long readBytes = report.getReadBytes() == null ? 0 : report.getReadBytes();
|
||||
|
||||
report.setUserId(item.getUserId());
|
||||
report.setLicenseId(item.getLicenseId());
|
||||
report.setWriteBytes(writeBytes + item.getWriteBytes());
|
||||
report.setReadBytes(readBytes + item.getReadBytes());
|
||||
report.setDate(date);
|
||||
report.setDateStr(dateStr);
|
||||
report.setCreateTime(now);
|
||||
}
|
||||
|
||||
for (FlowReportHourDO item : map.values()) {
|
||||
flowReportHourMapper.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* 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.job;
|
||||
|
||||
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.quartz.IJobHandler;
|
||||
import fun.asgc.neutrino.core.quartz.annotation.JobHandler;
|
||||
import fun.asgc.neutrino.core.util.CollectionUtil;
|
||||
import fun.asgc.neutrino.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 lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 流量统计报表 - 分钟级别
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/10/24
|
||||
*/
|
||||
@Slf4j
|
||||
@NonIntercept
|
||||
@Component
|
||||
@JobHandler(name = "FlowReportForMinuteJob", cron = "0 */1 * * * ?", param = "")
|
||||
public class FlowReportForMinuteJob implements IJobHandler {
|
||||
|
||||
@Autowired
|
||||
private FlowReportService flowReportService;
|
||||
@Autowired
|
||||
private LicenseMapper licenseMapper;
|
||||
@Autowired
|
||||
private FlowReportMinuteMapper flowReportMinuteMapper;
|
||||
|
||||
@Override
|
||||
public void execute(String param) throws Exception {
|
||||
List<LicenseDO> list = licenseMapper.listAll();
|
||||
if (CollectionUtil.isEmpty(list)) {
|
||||
return;
|
||||
}
|
||||
Set<Integer> licenseIds = list.stream().map(LicenseDO::getId).collect(Collectors.toSet());
|
||||
Date now = new Date();
|
||||
String dateStr = DateUtil.format(DateUtil.addDate(now, Calendar.MINUTE, -1), "yyyy-MM-dd HH:mm");
|
||||
Date date = DateUtil.parse(dateStr, "yyyy-MM-dd HH:mm");
|
||||
List<FlowReportMinuteDO> oldList = flowReportMinuteMapper.findList(licenseIds, dateStr);
|
||||
Map<Integer, FlowReportMinuteDO> oldMap = CollectionUtil.isEmpty(oldList) ? new HashMap<>() :
|
||||
oldList.stream().collect(Collectors.toMap(FlowReportMinuteDO::getLicenseId, Function.identity(), (a,b) -> a));
|
||||
|
||||
for (LicenseDO item : list) {
|
||||
// 避免job重复执行导致数据重复
|
||||
if (oldMap.containsKey(item.getId())) {
|
||||
continue;
|
||||
}
|
||||
Integer writeBytes = flowReportService.getAndResetWriteByte(item.getId());
|
||||
Integer readBytes = flowReportService.getAndResetReadByte(item.getId());
|
||||
FlowReportMinuteDO flowReportMinuteDO = new FlowReportMinuteDO();
|
||||
flowReportMinuteDO.setUserId(item.getUserId());
|
||||
flowReportMinuteDO.setLicenseId(item.getId());
|
||||
flowReportMinuteDO.setWriteBytes(writeBytes);
|
||||
flowReportMinuteDO.setReadBytes(readBytes);
|
||||
flowReportMinuteDO.setDate(date);
|
||||
flowReportMinuteDO.setDateStr(dateStr);
|
||||
flowReportMinuteDO.setCreateTime(now);
|
||||
flowReportMinuteMapper.add(flowReportMinuteDO);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 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.job;
|
||||
|
||||
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.quartz.IJobHandler;
|
||||
import fun.asgc.neutrino.core.quartz.annotation.JobHandler;
|
||||
import fun.asgc.neutrino.core.util.CollectionUtil;
|
||||
import fun.asgc.neutrino.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 lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/10/28
|
||||
*/
|
||||
@Slf4j
|
||||
@NonIntercept
|
||||
@Component
|
||||
@JobHandler(name = "FlowReportForMonthJob", cron = "0 10 0 1 * ?", param = "")
|
||||
public class FlowReportForMonthJob implements IJobHandler {
|
||||
@Autowired
|
||||
private FlowReportService flowReportService;
|
||||
@Autowired
|
||||
private LicenseMapper licenseMapper;
|
||||
@Autowired
|
||||
private FlowReportMinuteMapper flowReportMinuteMapper;
|
||||
@Autowired
|
||||
private FlowReportHourMapper flowReportHourMapper;
|
||||
@Autowired
|
||||
private FlowReportDayMapper flowReportDayMapper;
|
||||
@Autowired
|
||||
private FlowReportMonthMapper flowReportMonthMapper;
|
||||
|
||||
@Override
|
||||
public void execute(String param) throws Exception {
|
||||
Date now = new Date();
|
||||
String dateStr = 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);
|
||||
|
||||
// 删除原来的记录
|
||||
flowReportMonthMapper.deleteByDateStr(dateStr);
|
||||
|
||||
// 查询上个月的天级别统计数据
|
||||
List<FlowReportDayDO> flowReportDayList = flowReportDayMapper.findListByDateRange(startDayDate, endEndDate);
|
||||
if (CollectionUtil.isEmpty(flowReportDayList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 汇总前一个天的天级别统计数据
|
||||
Map<Integer, FlowReportMonthDO> map = new HashMap<>();
|
||||
for (FlowReportDayDO item : flowReportDayList) {
|
||||
FlowReportMonthDO report = map.get(item.getLicenseId());
|
||||
if (null == report) {
|
||||
report = new FlowReportMonthDO();
|
||||
map.put(item.getLicenseId(), report);
|
||||
}
|
||||
Long writeBytes = report.getWriteBytes() == null ? 0 : report.getWriteBytes();
|
||||
Long readBytes = report.getReadBytes() == null ? 0 : report.getReadBytes();
|
||||
|
||||
report.setUserId(item.getUserId());
|
||||
report.setLicenseId(item.getLicenseId());
|
||||
report.setWriteBytes(writeBytes + item.getWriteBytes());
|
||||
report.setReadBytes(readBytes + item.getReadBytes());
|
||||
report.setDate(date);
|
||||
report.setDateStr(dateStr);
|
||||
report.setCreateTime(now);
|
||||
}
|
||||
|
||||
for (FlowReportMonthDO item : map.values()) {
|
||||
flowReportMonthMapper.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -43,7 +43,7 @@ public class BytesMetricsHandler extends ChannelDuplexHandler {
|
||||
MetricsCollector metricsCollector = MetricsCollector.getCollector(sa.getPort());
|
||||
metricsCollector.incrementReadBytes(((ByteBuf) msg).readableBytes());
|
||||
metricsCollector.incrementReadMsgs(1);
|
||||
System.out.println("字节数:" + metricsCollector.getMetrics().getReadBytes());
|
||||
// System.out.println("字节数:" + metricsCollector.getMetrics().getReadBytes());
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import fun.asgc.neutrino.core.annotation.Bean;
|
||||
import fun.asgc.neutrino.core.annotation.Component;
|
||||
import fun.asgc.neutrino.core.annotation.NonIntercept;
|
||||
import fun.asgc.neutrino.core.context.ApplicationRunner;
|
||||
import fun.asgc.neutrino.core.context.Environment;
|
||||
import fun.asgc.neutrino.core.util.FileUtil;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessageDecoder;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessageEncoder;
|
||||
@@ -60,6 +61,8 @@ public class ProxyServerRunner implements ApplicationRunner {
|
||||
private NioEventLoopGroup serverBossGroup;
|
||||
@Autowired("serverWorkerGroup")
|
||||
private NioEventLoopGroup serverWorkerGroup;
|
||||
@Autowired
|
||||
private Environment environment;
|
||||
|
||||
@Override
|
||||
public void run(String[] args) {
|
||||
@@ -80,15 +83,17 @@ public class ProxyServerRunner implements ApplicationRunner {
|
||||
}
|
||||
});
|
||||
try {
|
||||
bootstrap.bind(proxyConfig.getServer().getPort()).sync();
|
||||
log.info("代理服务启动,端口:{}", proxyConfig.getServer().getPort());
|
||||
Integer port = environment.getMainArgsForInteger("neutrino.proxy.server.port", proxyConfig.getServer().getPort());
|
||||
bootstrap.bind(port).sync();
|
||||
log.info("代理服务启动,端口:{}", port);
|
||||
} catch (Exception e) {
|
||||
log.error("代理服务异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void startProxyServerForSSL() {
|
||||
if (null == proxyConfig.getServer().getSslPort()) {
|
||||
Integer sslPort = environment.getMainArgsForInteger("neutrino.proxy.server.ssl-port", proxyConfig.getServer().getSslPort());
|
||||
if (null == sslPort) {
|
||||
return;
|
||||
}
|
||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||
@@ -101,8 +106,8 @@ public class ProxyServerRunner implements ApplicationRunner {
|
||||
}
|
||||
});
|
||||
try {
|
||||
bootstrap.bind(proxyConfig.getServer().getSslPort()).sync();
|
||||
log.info("代理服务启动,SSL端口: {}", proxyConfig.getServer().getSslPort());
|
||||
bootstrap.bind(sslPort).sync();
|
||||
log.info("代理服务启动,SSL端口: {}", sslPort);
|
||||
} catch (Exception e) {
|
||||
log.error("代理服务异常", e);
|
||||
}
|
||||
@@ -110,12 +115,16 @@ public class ProxyServerRunner implements ApplicationRunner {
|
||||
|
||||
private ChannelHandler createSslHandler() {
|
||||
try {
|
||||
InputStream jksInputStream = FileUtil.getInputStream(proxyConfig.getServer().getJksPath());
|
||||
String jksPath = environment.getMainArgsForString("neutrino.proxy.server.jks-path", proxyConfig.getServer().getJksPath());
|
||||
InputStream jksInputStream = FileUtil.getInputStream(jksPath);
|
||||
SSLContext serverContext = SSLContext.getInstance("TLS");
|
||||
final KeyStore ks = KeyStore.getInstance("JKS");
|
||||
ks.load(jksInputStream, proxyConfig.getServer().getKeyStorePassword().toCharArray());
|
||||
|
||||
String keyStorePassword = environment.getMainArgsForString("neutrino.proxy.server.key-store-password", proxyConfig.getServer().getKeyStorePassword());
|
||||
ks.load(jksInputStream, keyStorePassword.toCharArray());
|
||||
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
kmf.init(ks, proxyConfig.getServer().getKeyManagerPassword().toCharArray());
|
||||
String keyManagerPassword = environment.getMainArgsForString("neutrino.proxy.server.key-manager-password", proxyConfig.getServer().getKeyManagerPassword());
|
||||
kmf.init(ks, keyManagerPassword.toCharArray());
|
||||
TrustManager[] trustManagers = null;
|
||||
|
||||
serverContext.init(kmf.getKeyManagers(), trustManagers, null);
|
||||
|
||||
@@ -24,9 +24,14 @@ package fun.asgc.neutrino.proxy.server.proxy.core;
|
||||
|
||||
import fun.asgc.neutrino.core.base.Dispatcher;
|
||||
import fun.asgc.neutrino.core.util.BeanManager;
|
||||
import fun.asgc.neutrino.core.util.ChannelUtil;
|
||||
import fun.asgc.neutrino.proxy.core.Constants;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessage;
|
||||
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 io.netty.buffer.Unpooled;
|
||||
@@ -34,6 +39,8 @@ import io.netty.channel.*;
|
||||
import io.netty.handler.timeout.IdleStateEvent;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
@@ -81,6 +88,14 @@ public class ServerChannelHandler extends SimpleChannelInboundHandler<ProxyMessa
|
||||
CmdChannelAttachInfo cmdChannelAttachInfo = ProxyUtil.getAttachInfo(ctx.channel());
|
||||
if (null != cmdChannelAttachInfo) {
|
||||
BeanManager.getBean(ProxyMutualService.class).offline(cmdChannelAttachInfo);
|
||||
BeanManager.getBean(ClientConnectRecordService.class).add(new ClientConnectRecordDO()
|
||||
.setIp(ChannelUtil.getIP(ctx.channel()))
|
||||
.setLicenseId(cmdChannelAttachInfo.getLicenseId())
|
||||
.setType(ClientConnectTypeEnum.DISCONNECT.getType())
|
||||
.setMsg("")
|
||||
.setCode(SuccessCodeEnum.SUCCESS.getCode())
|
||||
.setCreateTime(new Date())
|
||||
);
|
||||
}
|
||||
ProxyUtil.removeCmdChannel(ctx.channel());
|
||||
}
|
||||
|
||||
@@ -22,8 +22,12 @@
|
||||
|
||||
package fun.asgc.neutrino.proxy.server.proxy.core;
|
||||
|
||||
import fun.asgc.neutrino.core.util.BeanManager;
|
||||
import fun.asgc.neutrino.core.util.StringUtil;
|
||||
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 io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
@@ -41,7 +45,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
*/
|
||||
public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
|
||||
|
||||
private static AtomicLong userIdProducer = new AtomicLong(0);
|
||||
private static AtomicLong visitorIdProducer = new AtomicLong(0);
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
@@ -63,27 +67,36 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
|
||||
} else {
|
||||
byte[] bytes = new byte[buf.readableBytes()];
|
||||
buf.readBytes(bytes);
|
||||
String userId = ProxyUtil.getVisitorChannelUserId(visitorChannel);
|
||||
proxyChannel.writeAndFlush(ProxyMessage.buildTransferMessage(userId, bytes));
|
||||
String visitorId = ProxyUtil.getVisitorIdByChannel(visitorChannel);
|
||||
proxyChannel.writeAndFlush(ProxyMessage.buildTransferMessage(visitorId, bytes));
|
||||
|
||||
// 增加流量计数
|
||||
VisitorChannelAttachInfo visitorChannelAttachInfo = ProxyUtil.getAttachInfo(visitorChannel);
|
||||
BeanManager.getBean(FlowReportService.class).addWriteByte(visitorChannelAttachInfo.getLicenseId(), bytes.length);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(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) {
|
||||
// 该端口还没有代理客户端
|
||||
ctx.channel().close();
|
||||
} else {
|
||||
String userId = newUserId();
|
||||
String visitorId = newVisitorId();
|
||||
String lanInfo = ProxyUtil.getClientLanInfoByServerPort(sa.getPort());
|
||||
// 用户连接到代理服务器时,设置用户连接不可读,等待代理后端服务器连接成功后再改变为可读状态
|
||||
userChannel.config().setOption(ChannelOption.AUTO_READ, false);
|
||||
ProxyUtil.addUserChannelToCmdChannel(cmdChannel, userId, userChannel);
|
||||
cmdChannel.writeAndFlush(ProxyMessage.buildConnectMessage(userId).setData(lanInfo.getBytes()));
|
||||
if (StringUtil.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()));
|
||||
}
|
||||
}
|
||||
|
||||
super.channelActive(ctx);
|
||||
@@ -104,7 +117,7 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
|
||||
} else {
|
||||
|
||||
// 用户连接断开,从控制连接中移除
|
||||
String userId = ProxyUtil.getVisitorChannelUserId(userChannel);
|
||||
String userId = ProxyUtil.getVisitorIdByChannel(userChannel);
|
||||
ProxyUtil.removeVisitorChannelFromCmdChannel(cmdChannel, userId);
|
||||
|
||||
Channel proxyChannel = userChannel.attr(Constants.NEXT_CHANNEL).get();
|
||||
@@ -126,17 +139,17 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
|
||||
public void channelWritabilityChanged(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) {
|
||||
// 该端口还没有代理客户端
|
||||
ctx.channel().close();
|
||||
} else {
|
||||
Channel proxyChannel = userChannel.attr(Constants.NEXT_CHANNEL).get();
|
||||
Channel proxyChannel = visitorChannel.attr(Constants.NEXT_CHANNEL).get();
|
||||
if (proxyChannel != null) {
|
||||
proxyChannel.config().setOption(ChannelOption.AUTO_READ, userChannel.isWritable());
|
||||
proxyChannel.config().setOption(ChannelOption.AUTO_READ, visitorChannel.isWritable());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,11 +157,11 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
|
||||
}
|
||||
|
||||
/**
|
||||
* 为用户连接产生ID
|
||||
* 为访问者连接产生ID
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static String newUserId() {
|
||||
return String.valueOf(userIdProducer.incrementAndGet());
|
||||
private static String newVisitorId() {
|
||||
return String.valueOf(visitorIdProducer.incrementAndGet());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,11 +52,15 @@ public class ProxyMapping {
|
||||
return list;
|
||||
}
|
||||
for (PortMappingDO portMapping : portMappingList) {
|
||||
list.add(new ProxyMapping()
|
||||
.setServerPort(portMapping.getServerPort())
|
||||
.setLanInfo(String.format("%s:%s", portMapping.getClientIp(), portMapping.getClientPort())));
|
||||
list.add(build(portMapping));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public static ProxyMapping build(PortMappingDO portMappingDO) {
|
||||
return new ProxyMapping()
|
||||
.setServerPort(portMappingDO.getServerPort())
|
||||
.setLanInfo(String.format("%s:%s", portMappingDO.getClientIp(), portMappingDO.getClientPort()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,11 @@ import lombok.experimental.Accessors;
|
||||
public class VisitorChannelAttachInfo {
|
||||
private String visitorId;
|
||||
private String lanInfo;
|
||||
private Integer serverPort;
|
||||
/**
|
||||
* licenseId
|
||||
*/
|
||||
private Integer licenseId;
|
||||
/**
|
||||
* ip地址
|
||||
*/
|
||||
|
||||
@@ -26,35 +26,25 @@ import fun.asgc.neutrino.core.annotation.Autowired;
|
||||
import fun.asgc.neutrino.core.annotation.Component;
|
||||
import fun.asgc.neutrino.core.annotation.Match;
|
||||
import fun.asgc.neutrino.core.annotation.NonIntercept;
|
||||
import fun.asgc.neutrino.core.util.CollectionUtil;
|
||||
import fun.asgc.neutrino.core.util.ChannelUtil;
|
||||
import fun.asgc.neutrino.core.util.StringUtil;
|
||||
import fun.asgc.neutrino.proxy.core.*;
|
||||
import fun.asgc.neutrino.proxy.server.constant.*;
|
||||
import fun.asgc.neutrino.proxy.server.base.proxy.ProxyConfig;
|
||||
import fun.asgc.neutrino.proxy.server.proxy.core.BytesMetricsHandler;
|
||||
import fun.asgc.neutrino.proxy.server.proxy.core.VisitorChannelHandler;
|
||||
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.PortMappingDO;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
|
||||
import fun.asgc.neutrino.proxy.server.proxy.domain.CmdChannelAttachInfo;
|
||||
import fun.asgc.neutrino.proxy.server.proxy.domain.ProxyMapping;
|
||||
import fun.asgc.neutrino.proxy.server.service.LicenseService;
|
||||
import fun.asgc.neutrino.proxy.server.service.PortMappingService;
|
||||
import fun.asgc.neutrino.proxy.server.service.ProxyMutualService;
|
||||
import fun.asgc.neutrino.proxy.server.service.UserService;
|
||||
import fun.asgc.neutrino.proxy.server.service.*;
|
||||
import fun.asgc.neutrino.proxy.server.util.ProxyUtil;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
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 java.net.BindException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -66,10 +56,6 @@ import java.util.stream.Collectors;
|
||||
@Match(type = Constants.ProxyDataTypeName.AUTH)
|
||||
@Component
|
||||
public class ProxyMessageAuthHandler implements ProxyMessageHandler {
|
||||
@Autowired("serverBossGroup")
|
||||
private NioEventLoopGroup serverBossGroup;
|
||||
@Autowired("serverWorkerGroup")
|
||||
private NioEventLoopGroup serverWorkerGroup;
|
||||
@Autowired
|
||||
private ProxyConfig proxyConfig;
|
||||
@Autowired
|
||||
@@ -80,79 +66,103 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
|
||||
private PortMappingService portMappingService;
|
||||
@Autowired
|
||||
private ProxyMutualService proxyMutualService;
|
||||
@Autowired
|
||||
private FlowReportService flowReportService;
|
||||
@Autowired
|
||||
private ClientConnectRecordService clientConnectRecordService;
|
||||
@Autowired
|
||||
private LicenseMapper licenseMapper;
|
||||
@Autowired
|
||||
private VisitorChannelService visitorChannelService;
|
||||
|
||||
@Override
|
||||
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
|
||||
String ip = ChannelUtil.getIP(ctx.channel());
|
||||
Date now = new Date();
|
||||
|
||||
String licenseKey = proxyMessage.getInfo();
|
||||
if (StringUtil.isEmpty(licenseKey)) {
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "license不能为空!", licenseKey));
|
||||
// ctx.channel().close();
|
||||
clientConnectRecordService.add(new ClientConnectRecordDO()
|
||||
.setIp(ip)
|
||||
.setType(ClientConnectTypeEnum.CONNECT.getType())
|
||||
.setMsg(licenseKey)
|
||||
.setCode(SuccessCodeEnum.FAIL.getCode())
|
||||
.setErr("license不能为空!")
|
||||
.setCreateTime(now)
|
||||
);
|
||||
return;
|
||||
}
|
||||
LicenseDO licenseDO = licenseService.findByKey(licenseKey);
|
||||
if (null == licenseDO) {
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "license不存在!", licenseKey));
|
||||
// ctx.channel().close();
|
||||
clientConnectRecordService.add(new ClientConnectRecordDO()
|
||||
.setIp(ip)
|
||||
.setType(ClientConnectTypeEnum.CONNECT.getType())
|
||||
.setMsg(licenseKey)
|
||||
.setCode(SuccessCodeEnum.FAIL.getCode())
|
||||
.setErr("license不存在!")
|
||||
.setCreateTime(now)
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (EnableStatusEnum.DISABLE.getStatus().equals(licenseDO.getEnable())) {
|
||||
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;
|
||||
}
|
||||
UserDO userDO = userService.findById(licenseDO.getId());
|
||||
UserDO userDO = userService.findById(licenseDO.getUserId());
|
||||
if (null == userDO || EnableStatusEnum.DISABLE.getStatus().equals(userDO.getEnable())) {
|
||||
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;
|
||||
}
|
||||
Channel cmdChannel = ProxyUtil.getCmdChannelByLicenseId(licenseDO.getId());
|
||||
if (null != cmdChannel) {
|
||||
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));
|
||||
|
||||
List<PortMappingDO> portMappingList = portMappingService.findEnableListByLicenseId(licenseDO.getId());
|
||||
// 没有端口映射仍然保持连接
|
||||
if (!CollectionUtil.isEmpty(portMappingList)) {
|
||||
ProxyUtil.initProxyInfo(licenseDO.getId(), ProxyMapping.buildList(portMappingList));
|
||||
clientConnectRecordService.add(new ClientConnectRecordDO()
|
||||
.setIp(ip)
|
||||
.setLicenseId(licenseDO.getId())
|
||||
.setType(ClientConnectTypeEnum.CONNECT.getType())
|
||||
.setMsg(licenseKey)
|
||||
.setCode(SuccessCodeEnum.SUCCESS.getCode())
|
||||
.setCreateTime(now));
|
||||
|
||||
ProxyUtil.addCmdChannel(licenseDO.getId(), ctx.channel(), portMappingList.stream().map(PortMappingDO::getServerPort).collect(Collectors.toSet()));
|
||||
|
||||
startUserPortServer(ProxyUtil.getAttachInfo(ctx.channel()), portMappingList);
|
||||
}
|
||||
// 更新license在线状态
|
||||
licenseMapper.updateOnlineStatus(licenseDO.getId(), OnlineStatusEnum.ONLINE.getStatus(), now);
|
||||
// 初始化VisitorChannel
|
||||
visitorChannelService.initVisitorChannel(licenseDO.getId(), ctx.channel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return ProxyDataTypeEnum.AUTH.getDesc();
|
||||
}
|
||||
|
||||
private void startUserPortServer(CmdChannelAttachInfo cmdChannelAttachInfo, List<PortMappingDO> portMappingList) {
|
||||
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) {
|
||||
try {
|
||||
proxyMutualService.bindServerPort(cmdChannelAttachInfo, portMapping.getServerPort());
|
||||
bootstrap.bind(portMapping.getServerPort()).get();
|
||||
log.info("绑定用户端口: {}", portMapping.getServerPort());
|
||||
} catch (Exception ex) {
|
||||
// BindException表示该端口已经绑定过
|
||||
if (!(ex.getCause() instanceof BindException)) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ public class ProxyMessageConnectHandler implements ProxyMessageHandler {
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
UserDO userDO = userService.findById(licenseDO.getId());
|
||||
UserDO userDO = userService.findById(licenseDO.getUserId());
|
||||
if (null == userDO || EnableStatusEnum.DISABLE.getStatus().equals(userDO.getEnable())) {
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "当前license无效!"));
|
||||
ctx.channel().close();
|
||||
@@ -97,14 +97,14 @@ public class ProxyMessageConnectHandler implements ProxyMessageHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
Channel userChannel = ProxyUtil.getUserChannel(cmdChannel, visitorId);
|
||||
if (userChannel != null) {
|
||||
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(userChannel);
|
||||
userChannel.attr(Constants.NEXT_CHANNEL).set(ctx.channel());
|
||||
ctx.channel().attr(Constants.NEXT_CHANNEL).set(visitorChannel);
|
||||
visitorChannel.attr(Constants.NEXT_CHANNEL).set(ctx.channel());
|
||||
// 代理客户端与后端服务器连接成功,修改用户连接为可读状态
|
||||
userChannel.config().setOption(ChannelOption.AUTO_READ, true);
|
||||
visitorChannel.config().setOption(ChannelOption.AUTO_READ, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,10 +25,14 @@ package fun.asgc.neutrino.proxy.server.proxy.handler;
|
||||
import fun.asgc.neutrino.core.annotation.Component;
|
||||
import fun.asgc.neutrino.core.annotation.Match;
|
||||
import fun.asgc.neutrino.core.annotation.NonIntercept;
|
||||
import fun.asgc.neutrino.core.util.BeanManager;
|
||||
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.server.proxy.domain.VisitorChannelAttachInfo;
|
||||
import fun.asgc.neutrino.proxy.server.service.FlowReportService;
|
||||
import fun.asgc.neutrino.proxy.server.util.ProxyUtil;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
@@ -45,11 +49,15 @@ public class ProxyMessageTransferHandler implements ProxyMessageHandler {
|
||||
|
||||
@Override
|
||||
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
|
||||
Channel userChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
|
||||
if (null != userChannel) {
|
||||
Channel visitorChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
|
||||
if (null != visitorChannel) {
|
||||
ByteBuf buf = ctx.alloc().buffer(proxyMessage.getData().length);
|
||||
buf.writeBytes(proxyMessage.getData());
|
||||
userChannel.writeAndFlush(buf);
|
||||
visitorChannel.writeAndFlush(buf);
|
||||
|
||||
// 增加流量计数
|
||||
VisitorChannelAttachInfo visitorChannelAttachInfo = ProxyUtil.getAttachInfo(visitorChannel);
|
||||
BeanManager.getBean(FlowReportService.class).addReadByte(visitorChannelAttachInfo.getLicenseId(), proxyMessage.getData().length);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* 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 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.db.page.Page;
|
||||
import fun.asgc.neutrino.core.db.page.PageQuery;
|
||||
import fun.asgc.neutrino.core.util.CollectionUtil;
|
||||
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 lombok.extern.slf4j.Slf4j;
|
||||
|
||||
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/11/23
|
||||
*/
|
||||
@Slf4j
|
||||
@NonIntercept
|
||||
@Component
|
||||
public class ClientConnectRecordService {
|
||||
@Autowired
|
||||
private ClientConnectRecordMapper clientConnectRecordMapper;
|
||||
@Autowired
|
||||
private LicenseMapper licenseMapper;
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
|
||||
public void add(ClientConnectRecordDO clientConnectRecordDO) {
|
||||
clientConnectRecordMapper.add(clientConnectRecordDO);
|
||||
}
|
||||
|
||||
public Page<ClientConnectRecordListRes> page(PageQuery pageQuery, ClientConnectRecordListReq req) {
|
||||
Page<ClientConnectRecordListRes> page = Page.create(pageQuery);
|
||||
clientConnectRecordMapper.page(page, req);
|
||||
if (CollectionUtil.isEmpty(page.getRecords())) {
|
||||
return page;
|
||||
}
|
||||
Set<Integer> licenseIds = page.getRecords().stream().map(ClientConnectRecordListRes::getLicenseId).collect(Collectors.toSet());
|
||||
if (CollectionUtil.isEmpty(licenseIds)) {
|
||||
return page;
|
||||
}
|
||||
List<LicenseDO> licenseList = licenseMapper.findByIds(licenseIds);
|
||||
if (CollectionUtil.isEmpty(licenseList)) {
|
||||
return page;
|
||||
}
|
||||
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()));
|
||||
boolean isAdmin = SystemContextHolder.isAdmin();
|
||||
page.getRecords().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 (!isAdmin) {
|
||||
// msg可能带有license等敏感信息,若登录者为游客,则不展示
|
||||
item.setMsg("******");
|
||||
}
|
||||
});
|
||||
return page;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* 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 fun.asgc.neutrino.core.annotation.Component;
|
||||
import fun.asgc.neutrino.core.annotation.NonIntercept;
|
||||
import fun.asgc.neutrino.core.util.LockUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* 流量报表服务
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/10/26
|
||||
*/
|
||||
@Slf4j
|
||||
@NonIntercept
|
||||
@Component
|
||||
public class FlowReportService {
|
||||
private Map<Integer/*licenseId*/, AtomicInteger/*writeByte*/> writeByteMap = new HashMap<>();
|
||||
private Map<Integer/*licenseId*/, AtomicInteger/*readByte*/> readByteMap = new HashMap<>();
|
||||
|
||||
private AtomicInteger getWriteByte(Integer licenseId) {
|
||||
return LockUtil.doubleCheckProcessForNoException(() -> !writeByteMap.containsKey(licenseId),
|
||||
licenseId,
|
||||
() -> {
|
||||
writeByteMap.put(licenseId, new AtomicInteger());
|
||||
},
|
||||
() -> writeByteMap.get(licenseId));
|
||||
}
|
||||
|
||||
private AtomicInteger getReadByte(Integer licenseId) {
|
||||
return LockUtil.doubleCheckProcessForNoException(() -> !readByteMap.containsKey(licenseId),
|
||||
licenseId,
|
||||
() -> {
|
||||
readByteMap.put(licenseId, new AtomicInteger());
|
||||
},
|
||||
() -> readByteMap.get(licenseId));
|
||||
}
|
||||
|
||||
public void addWriteByte(Integer licenseId, Integer writeByte) {
|
||||
getWriteByte(licenseId).addAndGet(writeByte);
|
||||
}
|
||||
|
||||
public void addReadByte(Integer licenseId, Integer readByte) {
|
||||
getReadByte(licenseId).addAndGet(readByte);
|
||||
}
|
||||
|
||||
public Integer getAndResetWriteByte(Integer licenseId) {
|
||||
return getWriteByte(licenseId).getAndSet(0);
|
||||
}
|
||||
|
||||
public Integer getAndResetReadByte(Integer licenseId) {
|
||||
return getReadByte(licenseId).getAndSet(0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,9 +22,7 @@
|
||||
package fun.asgc.neutrino.proxy.server.service;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
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.*;
|
||||
import fun.asgc.neutrino.core.db.page.Page;
|
||||
import fun.asgc.neutrino.core.db.page.PageQuery;
|
||||
import fun.asgc.neutrino.core.util.CollectionUtil;
|
||||
@@ -60,6 +58,8 @@ public class LicenseService {
|
||||
private LicenseMapper licenseMapper;
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
@Autowired
|
||||
private VisitorChannelService visitorChannelService;
|
||||
|
||||
public Page<LicenseListRes> page(PageQuery pageQuery, LicenseListReq req) {
|
||||
Page<LicenseListRes> page = Page.create(pageQuery);
|
||||
@@ -125,7 +125,7 @@ public class LicenseService {
|
||||
ParamCheckUtil.checkNotNull(oldLicenseDO, ExceptionConstant.LICENSE_NOT_EXIST);
|
||||
|
||||
LicenseDO licenseCheck = licenseMapper.checkRepeat(oldLicenseDO.getUserId(), req.getName(), Sets.newHashSet(oldLicenseDO.getId()));
|
||||
ParamCheckUtil.checkNotNull(licenseCheck, ExceptionConstant.LICENSE_NAME_CANNOT_REPEAT);
|
||||
ParamCheckUtil.checkMustNull(licenseCheck, ExceptionConstant.LICENSE_NAME_CANNOT_REPEAT);
|
||||
|
||||
licenseMapper.update(req.getId(), req.getName(), new Date());
|
||||
return new LicenseUpdateRes();
|
||||
@@ -161,7 +161,8 @@ public class LicenseService {
|
||||
*/
|
||||
public LicenseUpdateEnableStatusRes updateEnableStatus(LicenseUpdateEnableStatusReq req) {
|
||||
licenseMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
|
||||
|
||||
// 更新VisitorChannel
|
||||
visitorChannelService.updateVisitorChannelByLicenseId(req.getId(), req.getEnable());
|
||||
return new LicenseUpdateEnableStatusRes();
|
||||
}
|
||||
|
||||
@@ -171,6 +172,8 @@ public class LicenseService {
|
||||
*/
|
||||
public void delete(Integer id) {
|
||||
licenseMapper.delete(id);
|
||||
// 更新VisitorChannel
|
||||
visitorChannelService.updateVisitorChannelByLicenseId(id, EnableStatusEnum.DISABLE.getStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,4 +205,13 @@ public class LicenseService {
|
||||
}
|
||||
return licenseKey.substring(0, 10) + "****" + licenseKey.substring(licenseKey.length() - 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务端项目停止、启动时,更新在线状态为离线
|
||||
*/
|
||||
@Init
|
||||
@Destroy
|
||||
public void destroy() {
|
||||
licenseMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,7 @@
|
||||
package fun.asgc.neutrino.proxy.server.service;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
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.*;
|
||||
import fun.asgc.neutrino.core.db.page.Page;
|
||||
import fun.asgc.neutrino.core.db.page.PageQuery;
|
||||
import fun.asgc.neutrino.core.util.CollectionUtil;
|
||||
@@ -70,6 +68,8 @@ public class PortMappingService {
|
||||
private UserMapper userMapper;
|
||||
@Autowired
|
||||
private PortPoolMapper portPoolMapper;
|
||||
@Autowired
|
||||
private VisitorChannelService visitorChannelService;
|
||||
|
||||
public Page<PortMappingListRes> page(PageQuery pageQuery, PortMappingListReq req) {
|
||||
Page<PortMappingListRes> page = Page.create(pageQuery);
|
||||
@@ -128,6 +128,8 @@ public class PortMappingService {
|
||||
portMappingDO.setCreateTime(now);
|
||||
portMappingDO.setUpdateTime(now);
|
||||
portMappingMapper.add(portMappingDO);
|
||||
// 更新VisitorChannel
|
||||
visitorChannelService.addVisitorChannelByPortMapping(portMappingDO);
|
||||
return new PortMappingCreateRes();
|
||||
}
|
||||
|
||||
@@ -140,7 +142,11 @@ public class PortMappingService {
|
||||
}
|
||||
PortPoolDO portPoolDO = portPoolMapper.findByPort(req.getServerPort());
|
||||
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
|
||||
ParamCheckUtil.checkNotNull(portMappingMapper.findByPort(req.getServerPort(), Sets.newHashSet(req.getId())), ExceptionConstant.PORT_CANNOT_REPEAT_MAPPING, req.getServerPort());
|
||||
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());
|
||||
@@ -149,7 +155,10 @@ public class PortMappingService {
|
||||
portMappingDO.setClientIp(req.getClientIp());
|
||||
portMappingDO.setClientPort(req.getClientPort());
|
||||
portMappingDO.setUpdateTime(new Date());
|
||||
portMappingDO.setEnable(EnableStatusEnum.ENABLE.getStatus());
|
||||
portMappingMapper.update(portMappingDO);
|
||||
// 更新VisitorChannel
|
||||
visitorChannelService.updateVisitorChannelByPortMapping(oldPortMappingDO, portMappingDO);
|
||||
return new PortMappingUpdateRes();
|
||||
}
|
||||
|
||||
@@ -195,6 +204,14 @@ public class PortMappingService {
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -209,6 +226,9 @@ public class PortMappingService {
|
||||
}
|
||||
|
||||
portMappingMapper.delete(id);
|
||||
|
||||
// 更新VisitorChannel
|
||||
visitorChannelService.removeVisitorChannelByPortMapping(portMappingDO);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,4 +240,13 @@ public class PortMappingService {
|
||||
return portMappingMapper.findEnableListByLicenseId(licenseId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务端项目停止、启动时,更新在线状态为离线
|
||||
*/
|
||||
@Init
|
||||
@Destroy
|
||||
public void destroy() {
|
||||
portMappingMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ public class PortPoolService {
|
||||
|
||||
@Autowired
|
||||
private PortPoolMapper portPoolMapper;
|
||||
@Autowired
|
||||
private VisitorChannelService visitorChannelService;
|
||||
|
||||
public Page<PortPoolListRes> page(PageQuery pageQuery, PortPoolListReq req) {
|
||||
Page<PortPoolListRes> page = Page.create(pageQuery);
|
||||
@@ -65,7 +67,7 @@ public class PortPoolService {
|
||||
|
||||
public PortPoolCreateRes create(PortPoolCreateReq req) {
|
||||
PortPoolDO oldPortPoolDO = portPoolMapper.findByPort(req.getPort());
|
||||
ParamCheckUtil.checkNotNull(oldPortPoolDO, ExceptionConstant.PORT_CANNOT_REPEAT);
|
||||
ParamCheckUtil.checkMustNull(oldPortPoolDO, ExceptionConstant.PORT_CANNOT_REPEAT);
|
||||
|
||||
Date now = new Date();
|
||||
|
||||
@@ -75,19 +77,31 @@ public class PortPoolService {
|
||||
.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.delete(id);
|
||||
|
||||
// 更新visitorChannel
|
||||
visitorChannelService.updateVisitorChannelByPortPool(portPoolDO.getPort(), EnableStatusEnum.DISABLE.getStatus());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* 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 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.db.page.Page;
|
||||
import fun.asgc.neutrino.core.db.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 fun.asgc.neutrino.proxy.server.dal.UserReportMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/12/23
|
||||
*/
|
||||
@Slf4j
|
||||
@NonIntercept
|
||||
@Component
|
||||
public class ReportService {
|
||||
|
||||
@Autowired
|
||||
private UserReportMapper userReportMapper;
|
||||
|
||||
/**
|
||||
* 用户流量报表分页
|
||||
* @param pageQuery
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
public Page<UserFlowReportRes> userFlowReportPage(PageQuery pageQuery, UserFlowReportReq req) {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* license流量报表分页
|
||||
* @param pageQuery
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
public Page<LicenseFlowReportRes> licenseFlowReportPage(PageQuery pageQuery, LicenseFlowReportReq req) {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* 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 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.db.page.Page;
|
||||
import fun.asgc.neutrino.core.db.page.PageQuery;
|
||||
import fun.asgc.neutrino.core.util.CollectionUtil;
|
||||
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 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/10/20
|
||||
*/
|
||||
@NonIntercept
|
||||
@Component
|
||||
public class UserLoginRecordService {
|
||||
@Autowired
|
||||
private UserLoginRecordMapper userLoginRecordMapper;
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
|
||||
public Page<UserLoginRecordListRes> page(PageQuery pageQuery, UserLoginRecordListReq req) {
|
||||
Page<UserLoginRecordListRes> page = Page.create(pageQuery);
|
||||
userLoginRecordMapper.page(page, req);
|
||||
if (!CollectionUtil.isEmpty(page.getRecords())) {
|
||||
Set<Integer> userIds = page.getRecords().stream().map(UserLoginRecordListRes::getUserId).collect(Collectors.toSet());
|
||||
List<UserDO> userList = userMapper.findByIds(userIds);
|
||||
Map<Integer, UserDO> userMap = userList.stream().collect(Collectors.toMap(UserDO::getId, Function.identity()));
|
||||
for (UserLoginRecordListRes item : page.getRecords()) {
|
||||
UserDO userDO = userMap.get(item.getUserId());
|
||||
if (null != userDO) {
|
||||
item.setUserName(userDO.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
return page;
|
||||
}
|
||||
}
|
||||
@@ -27,10 +27,10 @@ import fun.asgc.neutrino.core.annotation.NonIntercept;
|
||||
import fun.asgc.neutrino.core.db.page.Page;
|
||||
import fun.asgc.neutrino.core.db.page.PageQuery;
|
||||
import fun.asgc.neutrino.core.util.DateUtil;
|
||||
import fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum;
|
||||
import fun.asgc.neutrino.proxy.server.constant.ExceptionConstant;
|
||||
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;
|
||||
@@ -40,6 +40,7 @@ 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 java.util.Calendar;
|
||||
import java.util.Date;
|
||||
@@ -61,6 +62,8 @@ public class UserService {
|
||||
private UserTokenMapper userTokenMapper;
|
||||
@Autowired
|
||||
private UserLoginRecordMapper userLoginRecordMapper;
|
||||
@Autowired
|
||||
private VisitorChannelService visitorChannelService;
|
||||
|
||||
public LoginRes login(LoginReq req) {
|
||||
UserDO userDO = userMapper.findByLoginName(req.getLoginName());
|
||||
@@ -114,7 +117,7 @@ public class UserService {
|
||||
|
||||
public UserDO findByToken(String token) {
|
||||
Date now = new Date();
|
||||
UserTokenDO userTokenDO = userTokenMapper.findByAvailableToken(token, now.getTime());
|
||||
UserTokenDO userTokenDO = userTokenMapper.findByAvailableToken(token, now);
|
||||
if (null == userTokenDO) {
|
||||
return null;
|
||||
}
|
||||
@@ -161,7 +164,8 @@ public class UserService {
|
||||
|
||||
public UserUpdateEnableStatusRes updateEnableStatus(UserUpdateEnableStatusReq req) {
|
||||
userMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
|
||||
|
||||
// 更新VisitorChannel
|
||||
visitorChannelService.updateVisitorChannelByUserId(req.getId(), req.getEnable());
|
||||
return new UserUpdateEnableStatusRes();
|
||||
}
|
||||
|
||||
@@ -192,12 +196,22 @@ public class UserService {
|
||||
}
|
||||
|
||||
public UserUpdatePasswordRes updatePassword(UserUpdatePasswordReq req) {
|
||||
UserDO userDO = userMapper.findById(req.getId());
|
||||
// 更新密码
|
||||
String loginPassword = Md5Util.encode(req.getLoginPassword());
|
||||
ParamCheckUtil.checkExpression(!userDO.getLoginPassword().equals(loginPassword), ExceptionConstant.LOGIN_PASSWORD_NO_CHANGE_MODIFY_FAIL);
|
||||
|
||||
userMapper.updateLoginPassword(req.getId(), loginPassword, new Date());
|
||||
|
||||
// 删除该用户所有token
|
||||
userTokenMapper.deleteByUserId(req.getId());
|
||||
|
||||
return new UserUpdatePasswordRes();
|
||||
}
|
||||
|
||||
public void delete(Integer id) {
|
||||
userMapper.delete(id);
|
||||
// 更新VisitorChannel
|
||||
visitorChannelService.updateVisitorChannelByUserId(id, EnableStatusEnum.DISABLE.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
/**
|
||||
* 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.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
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.util.CollectionUtil;
|
||||
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 io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
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 java.net.BindException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 访问者通道服务
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/2/5
|
||||
*/
|
||||
@Slf4j
|
||||
@NonIntercept
|
||||
@Component
|
||||
public class VisitorChannelService {
|
||||
@Autowired("serverBossGroup")
|
||||
private NioEventLoopGroup serverBossGroup;
|
||||
@Autowired("serverWorkerGroup")
|
||||
private NioEventLoopGroup serverWorkerGroup;
|
||||
@Autowired
|
||||
private ProxyMutualService proxyMutualService;
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
@Autowired
|
||||
private LicenseMapper licenseMapper;
|
||||
@Autowired
|
||||
private PortMappingMapper portMappingMapper;
|
||||
@Autowired
|
||||
private PortPoolMapper portPoolMapper;
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
* @param licenseId
|
||||
*/
|
||||
public void initVisitorChannel(Integer licenseId, Channel cmdChannel) {
|
||||
List<PortMappingDO> portMappingList = portMappingMapper.findEnableListByLicenseId(licenseId);
|
||||
// 没有端口映射仍然保持连接
|
||||
ProxyUtil.initProxyInfo(licenseId, ProxyMapping.buildList(portMappingList));
|
||||
|
||||
ProxyUtil.addCmdChannel(licenseId, cmdChannel, portMappingList.stream().map(PortMappingDO::getServerPort).collect(Collectors.toSet()));
|
||||
|
||||
startUserPortServer(ProxyUtil.getAttachInfo(cmdChannel), portMappingList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新
|
||||
* 触发时机:删除端口池、禁用端口池、启用端口池
|
||||
* @param serverPort
|
||||
* @param enable
|
||||
*/
|
||||
public void updateVisitorChannelByPortPool(Integer serverPort, Integer enable) {
|
||||
if (null == serverPort) {
|
||||
return;
|
||||
}
|
||||
List<PortMappingDO> portMappingDOList = portMappingMapper.findListByServerPort(serverPort);
|
||||
if (CollectionUtil.isEmpty(portMappingDOList)) {
|
||||
return;
|
||||
}
|
||||
EnableStatusEnum enableStatusEnum = EnableStatusEnum.of(enable);
|
||||
for (PortMappingDO portMappingDO : portMappingDOList) {
|
||||
if (EnableStatusEnum.DISABLE == enableStatusEnum) {
|
||||
removeVisitorChannelByPortMapping(portMappingDO);
|
||||
} else if (EnableStatusEnum.ENABLE == EnableStatusEnum.of(portMappingDO.getEnable())) {
|
||||
addVisitorChannelByPortMapping(portMappingDO);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新
|
||||
* 触发时机:删除用户、禁用用户、启用用户 (新增、修改用户不涉及VisitorChannel的变更)
|
||||
* @param userId
|
||||
*/
|
||||
public void updateVisitorChannelByUserId(Integer userId, Integer enable) {
|
||||
if (null == userId) {
|
||||
return;
|
||||
}
|
||||
List<LicenseDO> licenseDOList = licenseMapper.listByUserId(userId);
|
||||
if (CollectionUtil.isEmpty(licenseDOList)) {
|
||||
return;
|
||||
}
|
||||
for (LicenseDO licenseDO : licenseDOList) {
|
||||
updateVisitorChannelByLicenseId(licenseDO.getId(), enable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新
|
||||
* 触发时机:删除license、禁用license、启用license (新增、修改license不涉及VisitorChannel的变更)
|
||||
* 重置licenseKey,不会立即影响已经连接成功的license,如果想要立即影响,请先进行禁用
|
||||
* @param licenseId
|
||||
*/
|
||||
public void updateVisitorChannelByLicenseId(Integer licenseId, Integer enable) {
|
||||
if (null == licenseId) {
|
||||
return;
|
||||
}
|
||||
Channel cmdChannel = ProxyUtil.getCmdChannelByLicenseId(licenseId);
|
||||
if (null == cmdChannel) {
|
||||
// 如果不存在有效的cmdChannel,则无需更新VisitorChannel
|
||||
return;
|
||||
}
|
||||
EnableStatusEnum enableStatusEnum = EnableStatusEnum.of(enable);
|
||||
List<PortMappingDO> portMappingDOList = portMappingMapper.findListByLicenseId(licenseId);
|
||||
if (!CollectionUtil.isEmpty(portMappingDOList)) {
|
||||
for (PortMappingDO portMappingDO : portMappingDOList) {
|
||||
if (EnableStatusEnum.DISABLE == enableStatusEnum) {
|
||||
removeVisitorChannelByPortMapping(portMappingDO);
|
||||
} else if (EnableStatusEnum.ENABLE == EnableStatusEnum.of(portMappingDO.getEnable())) {
|
||||
addVisitorChannelByPortMapping(portMappingDO);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新
|
||||
* 触发时机:修改端口映射
|
||||
* @param oldPortMappingDO
|
||||
* @param newPortMappingDO
|
||||
*/
|
||||
public void updateVisitorChannelByPortMapping(PortMappingDO oldPortMappingDO, PortMappingDO newPortMappingDO) {
|
||||
if (null == oldPortMappingDO || null == newPortMappingDO) {
|
||||
return;
|
||||
}
|
||||
removeVisitorChannelByPortMapping(oldPortMappingDO);
|
||||
addVisitorChannelByPortMapping(newPortMappingDO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增VisitorChannel
|
||||
* 触发时机:新增端口映射、启用端口映射
|
||||
* @param portMappingDO
|
||||
*/
|
||||
public void addVisitorChannelByPortMapping(PortMappingDO portMappingDO) {
|
||||
if (null == portMappingDO) {
|
||||
return;
|
||||
}
|
||||
Channel cmdChannel = ProxyUtil.getCmdChannelByLicenseId(portMappingDO.getLicenseId());
|
||||
if (null == cmdChannel) {
|
||||
// 如果不存在有效的cmdChannel,则无需更新VisitorChannel
|
||||
return;
|
||||
}
|
||||
// 判断端口映射是否启用
|
||||
if (EnableStatusEnum.DISABLE != EnableStatusEnum.of(portMappingDO.getEnable())) {
|
||||
LicenseDO licenseDO = licenseMapper.findById(portMappingDO.getLicenseId());
|
||||
// 判断license是否启用
|
||||
if (null != licenseDO && EnableStatusEnum.ENABLE == EnableStatusEnum.of(licenseDO.getEnable())) {
|
||||
UserDO userDO = userMapper.findById(licenseDO.getUserId());
|
||||
// 判断用户是否启用
|
||||
if (null != userDO && EnableStatusEnum.ENABLE == EnableStatusEnum.of(userDO.getEnable())) {
|
||||
PortPoolDO portPoolDO = portPoolMapper.findByPort(portMappingDO.getServerPort());
|
||||
// 判断端口池是否启用
|
||||
if (null != portPoolDO && EnableStatusEnum.ENABLE == EnableStatusEnum.of(portPoolDO.getEnable())) {
|
||||
// 未删除且未禁用,则开启代理
|
||||
ProxyUtil.addProxyInfo(portMappingDO.getLicenseId(), ProxyMapping.build(portMappingDO));
|
||||
ProxyUtil.addCmdChannel(portMappingDO.getLicenseId(), cmdChannel, Sets.newHashSet(portMappingDO.getServerPort()));
|
||||
startUserPortServer(ProxyUtil.getAttachInfo(cmdChannel), Lists.newArrayList(portMappingDO));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除VisitorChannel
|
||||
* 触发时机:删除端口映射、禁用端口映射
|
||||
* @param portMappingDO
|
||||
*/
|
||||
public void removeVisitorChannelByPortMapping(PortMappingDO portMappingDO) {
|
||||
if (null == portMappingDO) {
|
||||
return;
|
||||
}
|
||||
Channel cmdChannel = ProxyUtil.getCmdChannelByLicenseId(portMappingDO.getLicenseId());
|
||||
if (null == cmdChannel) {
|
||||
// 如果不存在有效的cmdChannel,则无需更新VisitorChannel
|
||||
return;
|
||||
}
|
||||
Channel visitorChannel = ProxyUtil.getVisitorChannelByServerPort(portMappingDO.getServerPort());
|
||||
if (null != visitorChannel) {
|
||||
Channel proxyChannel = visitorChannel.attr(Constants.NEXT_CHANNEL).get();
|
||||
if (null != proxyChannel) {
|
||||
proxyChannel.close();
|
||||
}
|
||||
visitorChannel.close();
|
||||
}
|
||||
ProxyUtil.removeProxyInfo(portMappingDO.getServerPort());
|
||||
}
|
||||
|
||||
private void startUserPortServer(CmdChannelAttachInfo cmdChannelAttachInfo, List<PortMappingDO> portMappingList) {
|
||||
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) {
|
||||
try {
|
||||
proxyMutualService.bindServerPort(cmdChannelAttachInfo, portMapping.getServerPort());
|
||||
bootstrap.bind(portMapping.getServerPort()).get();
|
||||
log.info("绑定用户端口: {}", portMapping.getServerPort());
|
||||
} catch (Exception ex) {
|
||||
// BindException表示该端口已经绑定过
|
||||
if (!(ex.getCause() instanceof BindException)) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,13 @@ public class ParamCheckUtil {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.util;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import fun.asgc.neutrino.core.util.ChannelUtil;
|
||||
import fun.asgc.neutrino.core.util.CollectionUtil;
|
||||
import fun.asgc.neutrino.proxy.core.ChannelAttribute;
|
||||
@@ -50,7 +51,7 @@ public class ProxyUtil {
|
||||
/**
|
||||
* 代理信息映射
|
||||
*/
|
||||
private static final Map<Integer, String> proxyInfoMap = new HashMap<>();
|
||||
private static final Map<Integer, String> proxyInfoMap = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* 服务端口 -> 指令通道映射
|
||||
*/
|
||||
@@ -59,6 +60,10 @@ public class ProxyUtil {
|
||||
* license -> 指令通道映射
|
||||
*/
|
||||
private static Map<Integer, Channel> licenseToCmdChannelMap = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* 服务端口 -> 访问通道映射
|
||||
*/
|
||||
private static Map<Integer, Channel> serverPortToVisitorChannel = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* cmdChannelAttachInfo.getUserChannelMap() 读写锁
|
||||
@@ -72,12 +77,30 @@ public class ProxyUtil {
|
||||
*/
|
||||
public static void initProxyInfo(Integer licenseId, List<ProxyMapping> proxyMappingList) {
|
||||
licenseToServerPortMap.put(licenseId, new HashSet<>());
|
||||
for (ProxyMapping proxyMapping : proxyMappingList) {
|
||||
licenseToServerPortMap.get(licenseId).add(proxyMapping.getServerPort());
|
||||
proxyInfoMap.put(proxyMapping.getServerPort(), proxyMapping.getLanInfo());
|
||||
addProxyInfo(licenseId, proxyMappingList);
|
||||
}
|
||||
|
||||
public static void addProxyInfo(Integer licenseId, List<ProxyMapping> proxyMappingList) {
|
||||
if (!CollectionUtil.isEmpty(proxyMappingList)) {
|
||||
for (ProxyMapping proxyMapping : proxyMappingList) {
|
||||
licenseToServerPortMap.get(licenseId).add(proxyMapping.getServerPort());
|
||||
proxyInfoMap.put(proxyMapping.getServerPort(), proxyMapping.getLanInfo());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void addProxyInfo(Integer licenseId, ProxyMapping proxyMapping) {
|
||||
if (null == licenseId || null == proxyMapping) {
|
||||
return;
|
||||
}
|
||||
licenseToServerPortMap.get(licenseId).add(proxyMapping.getServerPort());
|
||||
proxyInfoMap.put(proxyMapping.getServerPort(), proxyMapping.getLanInfo());
|
||||
}
|
||||
|
||||
public static void removeProxyInfo(Integer serverPort) {
|
||||
proxyInfoMap.remove(serverPort);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据licenseId获取服务端端口集合
|
||||
* @param licenseId licenseId
|
||||
@@ -103,19 +126,25 @@ public class ProxyUtil {
|
||||
* @param serverPorts 服务端端口集合
|
||||
*/
|
||||
public static void addCmdChannel(Integer licenseId, Channel cmdChannel, Set<Integer> serverPorts) {
|
||||
if (CollectionUtil.isEmpty(serverPorts)) {
|
||||
return;
|
||||
if (!CollectionUtil.isEmpty(serverPorts)) {
|
||||
for (int port : serverPorts) {
|
||||
serverPortToCmdChannelMap.put(port, cmdChannel);
|
||||
}
|
||||
}
|
||||
CmdChannelAttachInfo cmdChannelAttachInfo = getAttachInfo(cmdChannel);
|
||||
if (null == cmdChannelAttachInfo) {
|
||||
cmdChannelAttachInfo = new CmdChannelAttachInfo()
|
||||
.setIp(ChannelUtil.getIP(cmdChannel))
|
||||
.setLicenseId(licenseId)
|
||||
.setVisitorChannelMap(new HashMap<>(16))
|
||||
.setServerPorts(Sets.newHashSet());
|
||||
setAttachInfo(cmdChannel, cmdChannelAttachInfo);
|
||||
}
|
||||
|
||||
for (int port : serverPorts) {
|
||||
serverPortToCmdChannelMap.put(port, cmdChannel);
|
||||
if (!CollectionUtil.isEmpty(serverPorts)) {
|
||||
cmdChannelAttachInfo.getServerPorts().addAll(serverPorts);
|
||||
}
|
||||
|
||||
setAttachInfo(cmdChannel, new CmdChannelAttachInfo()
|
||||
.setIp(ChannelUtil.getIP(cmdChannel))
|
||||
.setServerPorts(serverPorts)
|
||||
.setLicenseId(licenseId)
|
||||
.setVisitorChannelMap(new HashMap<>(16)));
|
||||
licenseToCmdChannelMap.put(licenseId, cmdChannel);
|
||||
}
|
||||
|
||||
@@ -171,23 +200,28 @@ public class ProxyUtil {
|
||||
/**
|
||||
* 增加用户连接与代理客户端连接关系
|
||||
*
|
||||
* @param userId
|
||||
* @param userChannel
|
||||
* @param visitorId
|
||||
* @param visitorChannel
|
||||
*/
|
||||
public static void addUserChannelToCmdChannel(Channel cmdChannel, String userId, Channel userChannel) {
|
||||
InetSocketAddress sa = (InetSocketAddress) userChannel.localAddress();
|
||||
public static void addVisitorChannelToCmdChannel(Channel cmdChannel, String visitorId, Channel visitorChannel, Integer serverPort) {
|
||||
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
|
||||
String lanInfo = getClientLanInfoByServerPort(sa.getPort());
|
||||
setAttachInfo(userChannel, new VisitorChannelAttachInfo()
|
||||
.setVisitorId(userId)
|
||||
CmdChannelAttachInfo cmdChannelAttachInfo = getAttachInfo(cmdChannel);
|
||||
|
||||
setAttachInfo(visitorChannel, new VisitorChannelAttachInfo()
|
||||
.setVisitorId(visitorId)
|
||||
.setLanInfo(lanInfo)
|
||||
.setIp(ChannelUtil.getIP(userChannel))
|
||||
.setServerPort(serverPort)
|
||||
.setLicenseId(cmdChannelAttachInfo.getLicenseId())
|
||||
.setIp(ChannelUtil.getIP(visitorChannel))
|
||||
);
|
||||
userChannelMapLock.writeLock().lock();
|
||||
try {
|
||||
((CmdChannelAttachInfo)getAttachInfo(cmdChannel)).getVisitorChannelMap().put(userId, userChannel);
|
||||
cmdChannelAttachInfo.getVisitorChannelMap().put(visitorId, visitorChannel);
|
||||
} finally {
|
||||
userChannelMapLock.writeLock().unlock();
|
||||
}
|
||||
serverPortToVisitorChannel.put(serverPort, visitorChannel);
|
||||
}
|
||||
|
||||
public static Channel removeVisitorChannelFromCmdChannel(Channel cmdChannel, String visitorId) {
|
||||
@@ -206,23 +240,32 @@ public class ProxyUtil {
|
||||
/**
|
||||
* 根据代理客户端连接与用户编号获取用户连接
|
||||
*
|
||||
* @param userId
|
||||
* @param visitorId
|
||||
* @return
|
||||
*/
|
||||
public static Channel getUserChannel(Channel cmdChannel, String userId) {
|
||||
public static Channel getVisitorChannel(Channel cmdChannel, String visitorId) {
|
||||
if (null == cmdChannel || null == getAttachInfo(cmdChannel)) {
|
||||
return null;
|
||||
}
|
||||
return ((CmdChannelAttachInfo)getAttachInfo(cmdChannel)).getVisitorChannelMap().get(userId);
|
||||
return ((CmdChannelAttachInfo)getAttachInfo(cmdChannel)).getVisitorChannelMap().get(visitorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户编号
|
||||
* 根据服务端口获取访问通道
|
||||
* @param serverPort
|
||||
* @return
|
||||
*/
|
||||
public static Channel getVisitorChannelByServerPort(Integer serverPort) {
|
||||
return serverPortToVisitorChannel.get(serverPort);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取访问者ID
|
||||
*
|
||||
* @param visitorChannel
|
||||
* @return
|
||||
*/
|
||||
public static String getVisitorChannelUserId(Channel visitorChannel) {
|
||||
public static String getVisitorIdByChannel(Channel visitorChannel) {
|
||||
if (null == visitorChannel || null == getAttachInfo(visitorChannel)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ neutrino:
|
||||
name: neutrino-proxy-server
|
||||
http:
|
||||
enable: true
|
||||
port: 8080
|
||||
port: 8888
|
||||
context-path: /
|
||||
max-content-length-desc: 128K
|
||||
static-resource:
|
||||
@@ -27,6 +27,12 @@ neutrino:
|
||||
key-manager-password: 123456
|
||||
jks-path: classpath:/test.jks
|
||||
data:
|
||||
sqlite:
|
||||
db:
|
||||
type: sqlite
|
||||
url: jdbc:sqlite:data.db
|
||||
driver-class: org.sqlite.JDBC
|
||||
# type: mysql
|
||||
# url: jdbc:mysql://103.163.47.16:3306/neutrino-proxy?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useAffectedRows=true&useSSL=false
|
||||
# driver-class: com.mysql.jdbc.Driver
|
||||
# username: "******"
|
||||
# password: "******"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<property name="LOG_FILE" value="/work/project/neutrino-proxy/server.log"/>
|
||||
<property name="LOG_FILE" value="/work/projects/neutrino-proxy-server/app.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" />
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="console"/>
|
||||
<appender-ref ref="file"/>
|
||||
</root>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<mapper namespace = "fun.asgc.neutrino.proxy.server.dal.ClientConnectRecordMapper">
|
||||
|
||||
<update id="add">
|
||||
insert into client_connect_record(`ip`,`license_id`,`type`, `msg`, `code`, `err`, `create_time`)
|
||||
values(:ip,:licenseId,:type,:msg,:code,:err,:createTime)
|
||||
</update>
|
||||
</mapper>
|
||||
@@ -0,0 +1,14 @@
|
||||
<mapper namespace = "fun.asgc.neutrino.proxy.server.dal.UserReportMapper">
|
||||
|
||||
<select id="userFlowReportPage" resultType="fun.asgc.neutrino.proxy.server.controller.res.UserFlowReportRes">
|
||||
SELECT
|
||||
u.id AS userId,
|
||||
u.NAME AS userName,
|
||||
IFNULL( SUM( frm.write_bytes ), 0 ) AS historyWriteBytes,
|
||||
IFNULL( SUM( frm.read_bytes ), 0 ) AS historyReadBytes
|
||||
FROM `user` u
|
||||
LEFT JOIN flow_report_month frm ON u.id = frm.user_id
|
||||
GROUP BY u.id
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -1,127 +0,0 @@
|
||||
#用户表
|
||||
CREATE TABLE IF NOT EXISTS `user` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`name` VARCHAR(50) NOT NULL,
|
||||
`login_name` VARCHAR(50) NOT NULL,
|
||||
`login_password` VARCHAR(255) NOT NULL,
|
||||
`enable` INTEGER(2) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL,
|
||||
`update_time` INTEGER(20) NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS I_login_name ON `user` (login_name ASC);
|
||||
|
||||
#license表
|
||||
CREATE TABLE IF NOT EXISTS `license` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`name` VARCHAR(50) NOT NULL,
|
||||
`key` VARCHAR(100) NOT NULL,
|
||||
`user_id` INTEGER NOT NULL,
|
||||
`is_online` INTEGER(2) NOT NULL,
|
||||
`enable` INTEGER(2) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL,
|
||||
`update_time` INTEGER(20) NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS I_key ON `license` (`key` ASC);
|
||||
|
||||
#用户token表
|
||||
CREATE TABLE IF NOT EXISTS `user_token` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`token` VARCHAR(50) NOT NULL,
|
||||
`user_id` INTEGER NOT NULL,
|
||||
`expiration_time` INTEGER NOT NULL,
|
||||
`create_time` INTEGER NOT NULL,
|
||||
`update_time` INTEGER NOT NULL
|
||||
);
|
||||
|
||||
#用户登录记录表
|
||||
CREATE TABLE IF NOT EXISTS `user_login_record` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`user_id` INTEGER NOT NULL,
|
||||
`ip` VARCHAR(50) NOT NULL,
|
||||
`token` VARCHAR(100) NOT NULL,
|
||||
`type` INTEGER(2) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL
|
||||
);
|
||||
|
||||
#端口池
|
||||
CREATE TABLE IF NOT EXISTS `port_pool` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`port` INTEGER NOT NULL,
|
||||
`enable` INTEGER(2) NOT NULL,
|
||||
`update_time` INTEGER(20) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS I_port ON port_pool (port ASC);
|
||||
|
||||
#端口映射表
|
||||
CREATE TABLE IF NOT EXISTS `port_mapping` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`license_id` INTEGER(20) NOT NULL,
|
||||
`server_port` INTEGER NOT NULL,
|
||||
`client_ip` VARCHAR(20) NOT NULL,
|
||||
`client_port` INTEGER NOT NULL,
|
||||
`is_online` INTEGER(2) NOT NULL,
|
||||
`enable` INTEGER(2) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL,
|
||||
`update_time` INTEGER(20) NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS I_server_port ON port_mapping (server_port ASC);
|
||||
|
||||
#客户端链接记录表
|
||||
CREATE TABLE IF NOT EXISTS `client_connect_record` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`user_id` INTEGER NOT NULL,
|
||||
`ip` VARCHAR(50) NOT NULL,
|
||||
`license_id` INTEGER(20) NOT NULL,
|
||||
`license_key` VARCHAR(100) NOT NULL,
|
||||
`write_bytes` INTEGER(20) DEFAULT NULL,
|
||||
`read_bytes` INTEGER(20) DEFAULT NULL,
|
||||
`type` INTEGER(2) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL
|
||||
);
|
||||
|
||||
#用户连接记录表
|
||||
CREATE TABLE IF NOT EXISTS `user_connect_record` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`server_port` INTEGER NOT NULL,
|
||||
`user_ip` VARCHAR(50) NOT NULL,
|
||||
`client_ip` VARCHAR(50) NOT NULL,
|
||||
`client_lan_info` VARCHAR(50) NOT NULL,
|
||||
`user_id` INTEGER NOT NULL,
|
||||
`license_id` INTEGER(20) NOT NULL,
|
||||
`license_key` VARCHAR(100) NOT NULL,
|
||||
`write_bytes` INTEGER(20) DEFAULT NULL,
|
||||
`read_bytes` INTEGER(20) DEFAULT NULL,
|
||||
`type` INTEGER(2) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL
|
||||
);
|
||||
|
||||
#############################调度管理相关表#############################
|
||||
#触发器信息表
|
||||
CREATE TABLE IF NOT EXISTS `job_info` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`desc` VARCHAR(255) NOT NULL,
|
||||
`handler` VARCHAR(255) NOT NULL,
|
||||
`cron` VARCHAR(128) NOT NULL ,
|
||||
`param` VARCHAR(512) DEFAULT NULL,
|
||||
`alarm_email` VARCHAR(255) DEFAULT NULL,
|
||||
`alarm_ding` VARCHAR(255) DEFAULT NULL,
|
||||
`enable` INTEGER(2) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL,
|
||||
`update_time` INTEGER(20) NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS I_handler ON `job_info` (`handler` ASC);
|
||||
|
||||
#触发器日志表
|
||||
CREATE TABLE IF NOT EXISTS `job_log` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`job_id` INTEGER(20) NOT NULL,
|
||||
`handler` VARCHAR(255) NOT NULL,
|
||||
`param` VARCHAR(512) DEFAULT NULL,
|
||||
`code` INTEGER(20) NOT NULL,
|
||||
`msg` TEXT DEFAULT NULL,
|
||||
`alarm_status` INTEGER(2) NOT NULL DEFAULT '0',
|
||||
`create_time` INTEGER(20) NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS I_create_time ON job_log(create_time);
|
||||
CREATE INDEX IF NOT EXISTS I_code ON job_log(code);
|
||||
@@ -1,5 +0,0 @@
|
||||
#job_qrtz_trigger_info
|
||||
insert into job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) values
|
||||
(1, '示例Job', 'DemoJob', '0/10 * * * * ?', '{"a":101}', 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
|
||||
insert into job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) values
|
||||
(2, '数据清理任务', 'DataCleanJob', '0 0 1 * * ?', '', 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
|
||||
@@ -0,0 +1,192 @@
|
||||
#############################系统管理相关表#############################
|
||||
#用户表
|
||||
CREATE TABLE IF NOT EXISTS `user` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(50) NOT NULL COMMENT '用户名',
|
||||
`login_name` varchar(50) NOT NULL COMMENT '登录名',
|
||||
`login_password` varchar(255) NOT NULL COMMENT '登录密码',
|
||||
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `I_user_login_name` (`login_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
#用户token表
|
||||
CREATE TABLE IF NOT EXISTS `user_token` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`token` varchar(50) NOT NULL COMMENT 'token',
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`expiration_time` datetime(3) NOT NULL COMMENT '过期时间',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `I_user_token_user_id` (`user_id`),
|
||||
KEY `I_user_token_token` (`token`),
|
||||
KEY `I_user_token_expiration_time` (`expiration_time`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
#端口池
|
||||
CREATE TABLE IF NOT EXISTS `port_pool` (
|
||||
`id` int NOT NULL AUTO_INCREMENT 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;
|
||||
|
||||
#############################代理配置相关表#############################
|
||||
#license表
|
||||
CREATE TABLE IF NOT EXISTS `license` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(50) NOT NULL COMMENT 'license名称',
|
||||
`key` varchar(100) NOT NULL COMMENT 'license key',
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`is_online` int NOT NULL COMMENT '是否在线(1、在线 2、离线)',
|
||||
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `I_license_key` (`key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
#端口映射表
|
||||
CREATE TABLE IF NOT EXISTS `port_mapping` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`license_id` int NOT NULL COMMENT 'licenseID',
|
||||
`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、离线)',
|
||||
`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;
|
||||
|
||||
#############################日志管理相关表#############################
|
||||
#用户登录记录表
|
||||
CREATE TABLE IF NOT EXISTS `user_login_record` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`ip` varchar(50) NOT NULL COMMENT 'IP',
|
||||
`token` varchar(100) NOT NULL COMMENT 'token',
|
||||
`type` int NOT NULL COMMENT '类型(1、登录 2、登出)',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
#客户端连接记录表
|
||||
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',
|
||||
`license_id` int NOT NULL COMMENT 'licenseId',
|
||||
`type` int NOT NULL COMMENT '类型(1、连接 2、断开连接)',
|
||||
`msg` varchar(512) DEFAULT NULL COMMENT '消息',
|
||||
`code` int NOT NULL COMMENT '结果 (1、成功 2、失败)',
|
||||
`err` text DEFAULT NULL COMMENT '异常信息',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
#############################调度管理相关表#############################
|
||||
#触发器信息表
|
||||
CREATE TABLE IF NOT EXISTS `job_info` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`desc` varchar(255) NOT NULL COMMENT '描述',
|
||||
`handler` varchar(255) NOT NULL COMMENT '处理器',
|
||||
`cron` varchar(128) NOT NULL COMMENT 'cron',
|
||||
`param` varchar(512) DEFAULT NULL COMMENT '参数',
|
||||
`alarm_email` varchar(255) DEFAULT NULL COMMENT '报警邮箱',
|
||||
`alarm_ding` varchar(255) DEFAULT NULL COMMENT '报警钉钉配置',
|
||||
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `I_job_info_handler` (`handler`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
#触发器日志表
|
||||
CREATE TABLE IF NOT EXISTS `job_log` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`job_id` int NOT NULL COMMENT 'JobId',
|
||||
`handler` varchar(255) NOT NULL COMMENT '处理器',
|
||||
`param` varchar(512) DEFAULT NULL COMMENT '参数',
|
||||
`code` int NOT NULL COMMENT '结果(1、成功 2、失败)',
|
||||
`msg` text COMMENT '消息',
|
||||
`alarm_status` int NOT NULL COMMENT '报警状态(1、未报警 2、已报警)',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `I_job_log_create_time` (`create_time`) USING BTREE,
|
||||
KEY `I_job_log_code` (`code`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
#############################报表管理相关表#############################
|
||||
#流量统计报表-分钟(保留24小时)
|
||||
CREATE TABLE IF NOT EXISTS `flow_report_minute` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`license_id` int NOT NULL COMMENT 'licenseId',
|
||||
`write_bytes` int NOT NULL COMMENT '写入流量',
|
||||
`read_bytes` int NOT NULL COMMENT '读取流量',
|
||||
`date` datetime(3) NOT NULL COMMENT '时间',
|
||||
`date_str` varchar(20) NOT NULL COMMENT '时间 yyyy-MM-dd HH:mm',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `I_flow_report_minute_create_time` (`create_time`) USING BTREE,
|
||||
KEY `I_flow_report_minute_date` (`date`) USING BTREE,
|
||||
KEY `I_flow_report_minute_user_id` (`user_id`),
|
||||
KEY `I_flow_report_minute_license_id` (`license_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
#流量统计报表-小时(保留60天)
|
||||
CREATE TABLE IF NOT EXISTS `flow_report_hour` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`license_id` int NOT NULL COMMENT 'licenseId',
|
||||
`write_bytes` int NOT NULL COMMENT '写入流量',
|
||||
`read_bytes` int NOT NULL COMMENT '读取流量',
|
||||
`date` datetime(3) NOT NULL COMMENT '时间',
|
||||
`date_str` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci 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;
|
||||
|
||||
#流量统计报表-天(保留1年)
|
||||
CREATE TABLE IF NOT EXISTS `flow_report_day` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`license_id` int NOT NULL COMMENT 'licenseId',
|
||||
`write_bytes` int NOT NULL COMMENT '写入流量',
|
||||
`read_bytes` int NOT NULL COMMENT '读取流量',
|
||||
`date` datetime(3) NOT NULL COMMENT '时间',
|
||||
`date_str` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci 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;
|
||||
|
||||
#流量统计报表-月(全量保留)
|
||||
CREATE TABLE IF NOT EXISTS `flow_report_month` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`license_id` int NOT NULL COMMENT 'licenseId',
|
||||
`write_bytes` int NOT NULL COMMENT '写入流量',
|
||||
`read_bytes` int NOT NULL COMMENT '读取流量',
|
||||
`date` datetime(3) NOT NULL COMMENT '时间',
|
||||
`date_str` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci 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;
|
||||
@@ -0,0 +1,13 @@
|
||||
#job_qrtz_trigger_info
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(1, '示例Job', 'DemoJob', '0/10 * * * * ?', '{"a":101}', 1, now(), now());
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(2, '数据清理任务', 'DataCleanJob', '0 0 1 * * ?', '', 1, now(), now());
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(3, '流量统计报表-分钟', 'FlowReportForMinuteJob', '0 */1 * * * ?', '', 1, now(), now());
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(4, '流量统计报表-小时', 'FlowReportForHourJob', '0 0 */1 * * ?', '', 1, now(), now());
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(5, '流量统计报表-天', 'FlowReportForDayJob', '0 0 1 * * ?', '', 1, now(), now());
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(6, '流量统计报表-月', 'FlowReportForMonthJob', '0 30 1 1 * ?', '', 1, now(), now());
|
||||
@@ -0,0 +1,3 @@
|
||||
#license
|
||||
INSERT INTO license(`id`, `name`, `key`, `user_id`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(1, '我的mac', 'b0a907332b474b25897c4dcb31fc7eb6', 1, 2, 1, now(), now());
|
||||
@@ -0,0 +1,5 @@
|
||||
#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());
|
||||
@@ -0,0 +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());
|
||||
@@ -0,0 +1,5 @@
|
||||
#用户表 6613b92b77056faeb72068f184ed4c4f
|
||||
INSERT INTO `user`(`id`, `name`,`login_name`,`login_password`,`enable`,`create_time`, `update_time`) VALUES
|
||||
(1, '管理员', 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, now(), now());
|
||||
INSERT INTO `user`(`id`, `name`,`login_name`,`login_password`,`enable`,`create_time`, `update_time`) VALUES
|
||||
(2, '游客', 'visitor', 'e10adc3949ba59abbe56e057f20f883e', 1, now(), now());
|
||||
@@ -1,41 +0,0 @@
|
||||
#端口池
|
||||
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'));
|
||||
@@ -0,0 +1,179 @@
|
||||
#############################系统管理相关表#############################
|
||||
#用户表
|
||||
CREATE TABLE IF NOT EXISTS `user` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`name` VARCHAR(50) NOT NULL,
|
||||
`login_name` VARCHAR(50) NOT NULL,
|
||||
`login_password` VARCHAR(255) NOT NULL,
|
||||
`enable` INTEGER(2) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL,
|
||||
`update_time` INTEGER(20) NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS I_user_login_name ON `user` (login_name ASC);
|
||||
|
||||
#用户token表
|
||||
CREATE TABLE IF NOT EXISTS `user_token` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`token` VARCHAR(50) NOT NULL,
|
||||
`user_id` INTEGER NOT NULL,
|
||||
`expiration_time` INTEGER NOT NULL,
|
||||
`create_time` INTEGER NOT NULL,
|
||||
`update_time` INTEGER NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS I_user_token_user_id ON user_token(user_id);
|
||||
CREATE INDEX IF NOT EXISTS I_user_token_token ON user_token(token);
|
||||
CREATE INDEX IF NOT EXISTS I_user_token_expiration_time ON user_token(expiration_time);
|
||||
|
||||
#端口池
|
||||
CREATE TABLE IF NOT EXISTS `port_pool` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`port` INTEGER NOT NULL,
|
||||
`enable` INTEGER(2) NOT NULL,
|
||||
`update_time` INTEGER(20) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS I_port_pool_port ON port_pool (port ASC);
|
||||
|
||||
#############################代理配置相关表#############################
|
||||
#license表
|
||||
CREATE TABLE IF NOT EXISTS `license` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`name` VARCHAR(50) NOT NULL,
|
||||
`key` VARCHAR(100) NOT NULL,
|
||||
`user_id` INTEGER NOT NULL,
|
||||
`is_online` INTEGER(2) NOT NULL,
|
||||
`enable` INTEGER(2) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL,
|
||||
`update_time` INTEGER(20) NOT NULL
|
||||
);
|
||||
CREATE 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,
|
||||
`server_port` INTEGER NOT NULL,
|
||||
`client_ip` VARCHAR(20) NOT NULL,
|
||||
`client_port` INTEGER NOT NULL,
|
||||
`is_online` INTEGER(2) NOT NULL,
|
||||
`enable` INTEGER(2) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL,
|
||||
`update_time` INTEGER(20) NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS I_port_mapping_server_port ON port_mapping (server_port ASC);
|
||||
|
||||
#############################日志管理相关表#############################
|
||||
#用户登录记录表
|
||||
CREATE TABLE IF NOT EXISTS `user_login_record` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`user_id` INTEGER NOT NULL,
|
||||
`ip` VARCHAR(50) NOT NULL,
|
||||
`token` VARCHAR(100) NOT NULL,
|
||||
`type` INTEGER(2) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL
|
||||
);
|
||||
#客户端连接记录表
|
||||
CREATE TABLE IF NOT EXISTS `client_connect_record` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`ip` VARCHAR(50) NOT NULL,
|
||||
`license_id` INTEGER(20) DEFAULT NULL,
|
||||
`type` INTEGER(2) NOT NULL,
|
||||
`msg` VARCHAR(512) DEFAULT NULL,
|
||||
`code` INTEGER(2) NOT NULL,
|
||||
`err` VARCHAR(512) DEFAULT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL
|
||||
);
|
||||
#############################调度管理相关表#############################
|
||||
#触发器信息表
|
||||
CREATE TABLE IF NOT EXISTS `job_info` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`desc` VARCHAR(255) NOT NULL,
|
||||
`handler` VARCHAR(255) NOT NULL,
|
||||
`cron` VARCHAR(128) NOT NULL ,
|
||||
`param` VARCHAR(512) DEFAULT NULL,
|
||||
`alarm_email` VARCHAR(255) DEFAULT NULL,
|
||||
`alarm_ding` VARCHAR(255) DEFAULT NULL,
|
||||
`enable` INTEGER(2) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL,
|
||||
`update_time` INTEGER(20) NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS I_job_info_handler ON `job_info` (`handler` ASC);
|
||||
|
||||
#触发器日志表
|
||||
CREATE TABLE IF NOT EXISTS `job_log` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`job_id` INTEGER(20) NOT NULL,
|
||||
`handler` VARCHAR(255) NOT NULL,
|
||||
`param` VARCHAR(512) DEFAULT NULL,
|
||||
`code` INTEGER(20) NOT NULL,
|
||||
`msg` TEXT DEFAULT NULL,
|
||||
`alarm_status` INTEGER(2) NOT NULL DEFAULT '0',
|
||||
`create_time` INTEGER(20) NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS I_job_log_create_time ON job_log(create_time);
|
||||
CREATE INDEX IF NOT EXISTS I_job_log_code ON job_log(code);
|
||||
|
||||
#############################报表管理相关表#############################
|
||||
#流量统计报表-分钟(保留24小时)
|
||||
CREATE TABLE IF NOT EXISTS `flow_report_minute` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`user_id` INTEGER(20) NOT NULL,
|
||||
`license_id` INTEGER(20) NOT NULL,
|
||||
`write_bytes` INTEGER(20) NOT NULL,
|
||||
`read_bytes` INTEGER(20) NOT NULL,
|
||||
`date` INTEGER(20) NOT NULL,
|
||||
`date_str` VARCHAR(20) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS I_flow_report_minute_create_time ON flow_report_minute(create_time);
|
||||
CREATE INDEX IF NOT EXISTS I_flow_report_minute_date ON flow_report_minute(`date`);
|
||||
CREATE INDEX IF NOT EXISTS I_flow_report_minute_user_id ON flow_report_minute(`user_id`);
|
||||
CREATE INDEX IF NOT EXISTS I_flow_report_minute_license_id ON flow_report_minute(`license_id`);
|
||||
|
||||
#流量统计报表-小时(保留60天)
|
||||
CREATE TABLE IF NOT EXISTS `flow_report_hour` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`user_id` INTEGER(20) NOT NULL,
|
||||
`license_id` INTEGER(20) NOT NULL,
|
||||
`write_bytes` INTEGER(20) NOT NULL,
|
||||
`read_bytes` INTEGER(20) NOT NULL,
|
||||
`date` INTEGER(20) NOT NULL,
|
||||
`date_str` VARCHAR(20) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS I_flow_report_hour_create_time ON flow_report_hour(create_time);
|
||||
CREATE INDEX IF NOT EXISTS I_flow_report_hour_date ON flow_report_hour(`date`);
|
||||
CREATE INDEX IF NOT EXISTS I_flow_report_hour_user_id ON flow_report_hour(`user_id`);
|
||||
CREATE INDEX IF NOT EXISTS I_flow_report_hour_license_id ON flow_report_hour(`license_id`);
|
||||
|
||||
#流量统计报表-天(保留1年)
|
||||
CREATE TABLE IF NOT EXISTS `flow_report_day` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`user_id` INTEGER(20) NOT NULL,
|
||||
`license_id` INTEGER(20) NOT NULL,
|
||||
`write_bytes` INTEGER(20) NOT NULL,
|
||||
`read_bytes` INTEGER(20) NOT NULL,
|
||||
`date` INTEGER(20) NOT NULL,
|
||||
`date_str` VARCHAR(20) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS I_flow_report_day_create_time ON flow_report_day(create_time);
|
||||
CREATE INDEX IF NOT EXISTS I_flow_report_day_date ON flow_report_day(`date`);
|
||||
CREATE INDEX IF NOT EXISTS I_flow_report_day_user_id ON flow_report_day(`user_id`);
|
||||
CREATE INDEX IF NOT EXISTS I_flow_report_day_license_id ON flow_report_day(`license_id`);
|
||||
|
||||
#流量统计报表-月(全量保留)
|
||||
CREATE TABLE IF NOT EXISTS `flow_report_month` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`user_id` INTEGER(20) NOT NULL,
|
||||
`license_id` INTEGER(20) NOT NULL,
|
||||
`write_bytes` INTEGER(20) NOT NULL,
|
||||
`read_bytes` INTEGER(20) NOT NULL,
|
||||
`date` INTEGER(20) NOT NULL,
|
||||
`date_str` VARCHAR(20) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS I_flow_report_month_create_time ON flow_report_month(create_time);
|
||||
CREATE INDEX IF NOT EXISTS I_flow_report_month_date ON flow_report_month(`date`);
|
||||
CREATE INDEX IF NOT EXISTS I_flow_report_month_user_id ON flow_report_month(`user_id`);
|
||||
CREATE INDEX IF NOT EXISTS I_flow_report_month_license_id ON flow_report_month(`license_id`);
|
||||
@@ -0,0 +1,13 @@
|
||||
#job_qrtz_trigger_info
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(1, '示例Job', 'DemoJob', '0/10 * * * * ?', '{"a":101}', 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(2, '数据清理任务', 'DataCleanJob', '0 0 1 * * ?', '', 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(3, '流量统计报表-分钟', 'FlowReportForMinuteJob', '0 */1 * * * ?', '', 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(4, '流量统计报表-小时', 'FlowReportForHourJob', '0 0 */1 * * ?', '', 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(5, '流量统计报表-天', 'FlowReportForDayJob', '0 0 1 * * ?', '', 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(6, '流量统计报表-月', 'FlowReportForMonthJob', '0 30 1 1 * ?', '', 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
|
||||
@@ -1,3 +1,3 @@
|
||||
#license
|
||||
insert into license(`id`, `name`, `key`, `user_id`, `is_online`, `enable`, `create_time`, `update_time`) values
|
||||
INSERT INTO license(`id`, `name`, `key`, `user_id`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(1, '我的mac', 'b0a907332b474b25897c4dcb31fc7eb6', 1, 2, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
|
||||
@@ -1,5 +1,5 @@
|
||||
#port_mapping
|
||||
insert into port_mapping(`id`, `license_id`, `server_port`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) values
|
||||
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
|
||||
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'));
|
||||
@@ -0,0 +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'));
|
||||
@@ -1,5 +1,5 @@
|
||||
#用户表 6613b92b77056faeb72068f184ed4c4f
|
||||
insert into `user`(`id`, `name`,`login_name`,`login_password`,`enable`,`create_time`, `update_time`) values
|
||||
INSERT INTO `user`(`id`, `name`,`login_name`,`login_password`,`enable`,`create_time`, `update_time`) VALUES
|
||||
(1, '管理员', 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
|
||||
insert into `user`(`id`, `name`,`login_name`,`login_password`,`enable`,`create_time`, `update_time`) values
|
||||
INSERT INTO `user`(`id`, `name`,`login_name`,`login_password`,`enable`,`create_time`, `update_time`) VALUES
|
||||
(2, '游客', 'visitor', 'e10adc3949ba59abbe56e057f20f883e', 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
|
||||
@@ -1,7 +1,37 @@
|
||||
- 测试及优化代理稳定性
|
||||
- 完成剩余的调度管理日志功能
|
||||
- 增加日志管理(登录日志、客户端连接日志、调度执行日志)
|
||||
- 增加简单的流量统计
|
||||
- 基于用户粒度的上下行流量累计
|
||||
- 完善补充代码文档
|
||||
- 优化底层框架
|
||||
# 功能点
|
||||
- 用户流量报表
|
||||
- 弹框展示月度明细
|
||||
- 弹框展示今日流量明细
|
||||
- License流量报表
|
||||
- 弹框展示月度明细
|
||||
- 弹框展示今日流量明细
|
||||
- 首页图表📈
|
||||
- 1、License在线数
|
||||
- 2、端口映射在线数
|
||||
- 3、今日流量(上行、下行)
|
||||
- 4、历史流量(上行、下行)
|
||||
- 点击1~4 切换列表
|
||||
- 在线License列表
|
||||
- 用户名
|
||||
- License名称
|
||||
- LicenseKey
|
||||
- 在线端口映射列表
|
||||
- 用户名
|
||||
- License名称
|
||||
- 服务端口
|
||||
- 代理客户端
|
||||
- 今日24小时流量列表(按小时到排序)
|
||||
- 用户名
|
||||
- License名称
|
||||
- 时间
|
||||
- 流量
|
||||
- 历史流量列表(取最近12个月按月到排序)
|
||||
- 用户名
|
||||
- License名称
|
||||
- 时间
|
||||
- 流量
|
||||
- 今日流量折线图(上行、下行、总流量,按分钟统计0~24小时)
|
||||
# Bug
|
||||
- windows环境下直接运行发布版的jar包,日志输出乱码
|
||||
- 部份用户windows环境下启动客户端,扫描类个数为0个
|
||||
- 代理mysql时,使用未开启远程访问的账号走代理访问mysql,代理客户端出现断开现象
|
||||