Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cb98cd2bd | ||
|
|
b718c048ea | ||
|
|
d202caa499 | ||
|
|
8591e224dc | ||
|
|
5953256c11 | ||
|
|
8d5b7edf12 | ||
|
|
e74dc64a9a | ||
|
|
47b6a24a42 | ||
|
|
0095ffc65c | ||
|
|
299d75957b | ||
|
|
db13fb49b1 | ||
|
|
94cac52ad2 | ||
|
|
32780f34a4 | ||
|
|
8dd58035a6 | ||
|
|
ee55a4104a | ||
|
|
1651f2ca1c | ||
|
|
ba93dcbeac | ||
|
|
cbc7ea7d25 | ||
|
|
e0c6885b03 | ||
|
|
c6694b7dc8 | ||
|
|
a2a5beaf5b | ||
|
|
418a44a7c2 | ||
|
|
d78a1d7d9b | ||
|
|
bd4f4a056d | ||
|
|
364d77f38f | ||
|
|
798a61e905 | ||
|
|
f8e93a7eaa | ||
|
|
2716addc77 | ||
|
|
833bfe94a9 | ||
|
|
f07912a189 | ||
|
|
d3eb245db1 | ||
|
|
d06af96102 | ||
|
|
3bbc9a1193 | ||
|
|
f193dfc42c | ||
|
|
6f129cad30 | ||
|
|
6412c4c081 | ||
|
|
05c709e33a | ||
|
|
d32cfa5d51 | ||
|
|
7baf60e8a7 | ||
|
|
c40142321f | ||
|
|
18cbc3dced | ||
|
|
6de1bbc74a | ||
|
|
7dce301bef | ||
|
|
543ea25fb0 | ||
|
|
8587241714 | ||
|
|
38847902c8 | ||
|
|
4685a15d5a | ||
|
|
a9608a449d | ||
|
|
9c0b3f3086 | ||
|
|
446910fd44 | ||
|
|
c4e05aad9e | ||
|
|
4db971aabb | ||
|
|
4c975798d7 | ||
|
|
ddc52af4bb | ||
|
|
0cc4608983 | ||
|
|
99bfce9a31 | ||
|
|
cb0cbee532 | ||
|
|
268e17273e | ||
|
|
840dd2d344 | ||
|
|
e50c303437 | ||
|
|
3cf64596c6 | ||
|
|
35327cc9a5 | ||
|
|
4d8e4190ba | ||
|
|
2da08e1b55 | ||
|
|
8d9fc4cff6 | ||
|
|
4d380dbdf3 | ||
|
|
d5abf90954 | ||
|
|
e416c76ea8 | ||
|
|
33704b1694 | ||
|
|
491f042b2f | ||
|
|
fd306ce8c3 | ||
|
|
6880898321 | ||
|
|
5578874df7 | ||
|
|
67c362b423 | ||
|
|
b026b46647 | ||
|
|
bfe880a6c6 | ||
|
|
f46332db4a | ||
|
|
5b2fe447b2 | ||
|
|
0301f15628 | ||
|
|
4b278216e5 | ||
|
|
5fbf3684a6 | ||
|
|
85d3ed914f | ||
|
|
bf5028e056 | ||
|
|
3bd011b58d | ||
|
|
3610de97db | ||
|
|
40a03c1630 | ||
|
|
f1ea585cec | ||
|
|
9647006ed1 | ||
|
|
8c5a6b2f0d | ||
|
|
3991fcb91c | ||
|
|
93e8879bf5 | ||
|
|
0466710f73 | ||
|
|
ef8cab0a36 | ||
|
|
7a9e81c27a | ||
|
|
3f42b58471 | ||
|
|
323d86b49d | ||
|
|
e2226f7d0b | ||
|
|
5094700b66 | ||
|
|
b737535b0f | ||
|
|
7a95fbddbc | ||
|
|
f00d8d1479 | ||
|
|
4550a677db | ||
|
|
f604e8b011 | ||
|
|
a400fccf77 | ||
|
|
87e4af5ba5 | ||
|
|
1d11dd3f85 | ||
|
|
b3e85b6a65 | ||
|
|
c76c4d8257 | ||
|
|
64fcd43be5 | ||
|
|
a7b077af8c | ||
|
|
a4c2addb33 | ||
|
|
dd6493cc66 | ||
|
|
52ce6d265a | ||
|
|
eacc98d18f | ||
|
|
65af658be5 | ||
|
|
a7b2694204 | ||
|
|
abab45785b | ||
|
|
9da00e47bd | ||
|
|
4eea46fa46 | ||
|
|
b7518bfbeb | ||
|
|
a95b534701 | ||
|
|
04747296c5 | ||
|
|
0a5d50b84d | ||
|
|
22461066a4 | ||
|
|
cd4c9f2aae | ||
|
|
30b115610b | ||
|
|
c971bee96c | ||
|
|
7ea723b578 | ||
|
|
1625187bb1 | ||
|
|
934ceaadca | ||
|
|
bb7fe668b1 | ||
|
|
f78a74cd0b | ||
|
|
c385030fc4 | ||
|
|
767860643d | ||
|
|
c20a6db44a | ||
|
|
f423056361 | ||
|
|
86a3cf92b5 | ||
|
|
b28d476081 | ||
|
|
48c8df15e7 | ||
|
|
a31b0ebc95 | ||
|
|
fe86d786f4 | ||
|
|
c48404e3bb | ||
|
|
5daec7195f | ||
|
|
533675b367 | ||
|
|
e547895a9c | ||
|
|
42b7163f5b | ||
|
|
2ceacc6916 | ||
|
|
124273e8d7 | ||
|
|
5a36c88875 | ||
|
|
70e6d2ab45 | ||
|
|
2dc8965cdb | ||
|
|
0d997bbe92 | ||
|
|
f88bbb53cb | ||
|
|
2ae3267049 | ||
|
|
62bbd48b33 | ||
|
|
aa131b0705 | ||
|
|
1510e16e50 | ||
|
|
a3b324c72b | ||
|
|
c959ce13a3 | ||
|
|
4c828f814c | ||
|
|
5a65eebfd3 | ||
|
|
5a3375ce80 | ||
|
|
eb277b414a | ||
|
|
fc4097529f | ||
|
|
375687aa5f | ||
|
|
32504d33af | ||
|
|
bcd429d54f | ||
|
|
2ec87abda9 | ||
|
|
8c2e1b8f6c | ||
|
|
e2195ab966 | ||
|
|
2d0b9132e9 | ||
|
|
5a502115b1 | ||
|
|
a972e0f309 | ||
|
|
b52225937c | ||
|
|
98737f0e9d | ||
|
|
ea67c2bce0 | ||
|
|
7dda047d51 | ||
|
|
b518dbfd58 | ||
|
|
e715976726 | ||
|
|
f66791678c | ||
|
|
7054a1df0e | ||
|
|
da0d1946c6 | ||
|
|
afff036641 | ||
|
|
9944e60825 | ||
|
|
74ff0c3b44 | ||
|
|
5bb6a09ce2 | ||
|
|
3e9e7cb370 | ||
|
|
3cf6bcbe6e | ||
|
|
8f9375152e | ||
|
|
501012d9bc | ||
|
|
f2b560fa1f | ||
|
|
2b95967820 | ||
|
|
9b1fafd379 | ||
|
|
e41e346dff | ||
|
|
fff5c0121a | ||
|
|
406edaf172 | ||
|
|
b6cd8ab3a2 | ||
|
|
18653410f9 | ||
|
|
6d6e58af52 | ||
|
|
e9e9ce8c90 | ||
|
|
1e1b376883 | ||
|
|
9cb5eb3225 | ||
|
|
5b38bb6b22 | ||
|
|
2b28d4eff5 | ||
|
|
42f39ddb0f | ||
|
|
fabc575c9e | ||
|
|
3e1f14917b | ||
|
|
f17c341325 | ||
|
|
5bf21b7c2c | ||
|
|
252a6ac7c1 | ||
|
|
0b78367b9f | ||
|
|
bb91abae00 | ||
|
|
1e4312fcb1 | ||
|
|
f9009bb869 | ||
|
|
c4e1ae2a17 | ||
|
|
d2fc7b6d45 | ||
|
|
4d4c06e4b1 | ||
|
|
98cc7ead6e | ||
|
|
e1be4523ed | ||
|
|
99ddfa65f3 | ||
|
|
31e36d1939 | ||
|
|
f46ae98867 | ||
|
|
f87370397e | ||
|
|
ce6634d51c | ||
|
|
f43d447ea0 | ||
|
|
baafc9bf21 | ||
|
|
56226831f9 | ||
|
|
b3e15225b3 | ||
|
|
e725cdbb28 | ||
|
|
480867e655 | ||
|
|
a5c568392b | ||
|
|
bbb0e5dca3 | ||
|
|
2b80279256 | ||
|
|
b7b4432d09 | ||
|
|
a7269229b0 | ||
|
|
f84695d665 | ||
|
|
4258845985 | ||
|
|
87e6c066ed | ||
|
|
2ea8a5890f | ||
|
|
89ea802245 | ||
|
|
88387003e3 | ||
|
|
b1797686f1 | ||
|
|
9a08a7c72e | ||
|
|
0f15bbc94d | ||
|
|
e454510967 | ||
|
|
090c129f60 | ||
|
|
8b41038d13 | ||
|
|
980d6d1a46 | ||
|
|
6b805089ba | ||
|
|
d81cbb3f7b | ||
|
|
b6b91b69ab | ||
|
|
3185934ff9 | ||
|
|
238eb64c04 | ||
|
|
31e3d56f9b | ||
|
|
7b344d6c0c | ||
|
|
4b68f1925d | ||
|
|
62dcc43444 | ||
|
|
092fe74b78 | ||
|
|
99ecdc5cd2 | ||
|
|
d64d25254e | ||
|
|
b6e76ecc74 | ||
|
|
8a37ae2d4b | ||
|
|
1be2326873 | ||
|
|
22d9bb9d39 | ||
|
|
b07b7567e2 | ||
|
|
0a70d58817 | ||
|
|
e96083e996 | ||
|
|
cbbaca8590 | ||
|
|
bb4ca4daf5 | ||
|
|
751ddd744a | ||
|
|
07da864c09 | ||
|
|
0e315b00c9 | ||
|
|
96997a1f22 | ||
|
|
cbc4b2dced | ||
|
|
79a3170234 | ||
|
|
7033627a9f | ||
|
|
3500e8fca8 | ||
|
|
215412d5d2 | ||
|
|
5665426fdb | ||
|
|
ca48d9ffda | ||
|
|
b8d1a36395 | ||
|
|
9ca5273177 | ||
|
|
3f274a7e5c | ||
|
|
14ba030efe | ||
|
|
8d0a796706 | ||
|
|
32f038b6de | ||
|
|
bf432c0f44 | ||
|
|
ef8d1b0036 | ||
|
|
57117c3dd6 | ||
|
|
d0de78ce60 | ||
|
|
00d2918e76 | ||
|
|
950f679c79 | ||
|
|
41531a2ad9 | ||
|
|
5a24ff6aa4 | ||
|
|
76129db46f | ||
|
|
e9a2c7072d | ||
|
|
221b0ca52c | ||
|
|
ed5feee43b | ||
|
|
c3e7bf72fe | ||
|
|
01d67312cb | ||
|
|
a82829002a | ||
|
|
1f723b5d0f | ||
|
|
08a963c4b7 | ||
|
|
66ae66b8a9 | ||
|
|
71eb72d726 | ||
|
|
98ac3c67c1 | ||
|
|
7de1c37d6b | ||
|
|
07de9eec98 | ||
|
|
4b596e99bb | ||
|
|
9d36a4a32a | ||
|
|
c4726941ea | ||
|
|
144adfe73f | ||
|
|
247ec55b17 | ||
|
|
e2c5c6975f | ||
|
|
9fc6541e98 | ||
|
|
7dba9ac173 | ||
|
|
3f014418e1 | ||
|
|
43967bc23e | ||
|
|
f723545a4d | ||
|
|
1d58118f09 | ||
|
|
a500133701 | ||
|
|
bde25f0648 | ||
|
|
bf13f081c0 | ||
|
|
c6f757c4b6 | ||
|
|
5d32a3b922 | ||
|
|
ecb68c004c | ||
|
|
bbf1a316df | ||
|
|
13b4abe868 | ||
|
|
1b1cbbce1f | ||
|
|
2cb672e184 | ||
|
|
77868a7e89 | ||
|
|
bb766a434a | ||
|
|
58a02b8e60 | ||
|
|
8b560699b3 | ||
|
|
a2293e35a4 | ||
|
|
7fea324219 | ||
|
|
559c6bd5f5 | ||
|
|
8e1dfc48ca | ||
|
|
5807c4fbc4 | ||
|
|
e50176b3bb | ||
|
|
0f6625e1e1 | ||
|
|
ec95da42ec | ||
|
|
c46062d83b | ||
|
|
2e34bf9bb6 | ||
|
|
1fe4d032e6 | ||
|
|
5a5b336baf | ||
|
|
ae5140b543 | ||
|
|
4392f94d74 | ||
|
|
70ff088a9d | ||
|
|
26dc08a737 | ||
|
|
e2c152a569 | ||
|
|
983d5dde8c | ||
|
|
1e90bafe81 | ||
|
|
0130aeb741 | ||
|
|
9e8d1a7c74 | ||
|
|
0b4785cde6 | ||
|
|
50b34866ba | ||
|
|
4d16ea6543 | ||
|
|
626cdcf3d0 | ||
|
|
14df0da7f4 | ||
|
|
0beb1c114a | ||
|
|
97220987a2 | ||
|
|
a27438aa01 | ||
|
|
2f189d9ecd | ||
|
|
7a21a5dcf2 | ||
|
|
0e8ea85805 | ||
|
|
78cf5d95e5 | ||
|
|
3d6c83a11b | ||
|
|
df01e93c64 | ||
|
|
987eb9cd2a | ||
|
|
1be0e0d389 | ||
|
|
c4716c45e6 | ||
|
|
dffcf2932c | ||
|
|
97a248a16f | ||
|
|
ecf0b97493 | ||
|
|
5341f0d33a | ||
|
|
917ce765e5 | ||
|
|
db64f8b31d | ||
|
|
c70baad0f6 | ||
|
|
7fd6fd5313 | ||
|
|
f717fcc63a | ||
|
|
5b57cd9173 | ||
|
|
0d4c5597b5 | ||
|
|
132353e58c | ||
|
|
8cfd4fef2c |
@@ -0,0 +1,5 @@
|
||||
root=true
|
||||
|
||||
[*.{groovy,java,kt,xml}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
@@ -0,0 +1,83 @@
|
||||
name: jar release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# see: https://docs.github.com/zh/actions/using-jobs/choosing-the-runner-for-a-job
|
||||
# os: ['ubuntu-20.04', 'macos-12']
|
||||
os: ['ubuntu-20.04']
|
||||
name: build - ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
# https://github.com/softprops/action-gh-release/issues/236#issuecomment-1150530128
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: setup node v13.12.0
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 13.12.0
|
||||
# cache: 'npm' # 使用缓存需要把 package-lock.json 也上传到 git 上
|
||||
- name: build neutrino-proxy-admin
|
||||
run: |
|
||||
cd neutrino-proxy-admin
|
||||
npm install
|
||||
npm run build:dev
|
||||
cp -rf ./dist/ ./../neutrino-proxy-server/src/main/resources/static/
|
||||
- name: GitHub Action for GraalVM JDK 17
|
||||
uses: graalvm/setup-graalvm@v1
|
||||
with:
|
||||
java-version: '17.0.7' # for a specific JDK 17; or '17' for the latest JDK 17
|
||||
distribution: 'graalvm' # New 'distribution' option
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Cache local Maven repository
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-maven-
|
||||
- name: Build with Maven (neutrino-proxy-server)
|
||||
run: |
|
||||
mvn clean install -pl neutrino-proxy-core -am -DskipTests --no-transfer-progress
|
||||
cd neutrino-proxy-server
|
||||
mvn clean native:compile -P native --file pom.xml --no-transfer-progress
|
||||
cp target/neutrino-proxy-server ./../neutrino-proxy-server-${{ github.ref_name }}
|
||||
- name: Archive zip (neutrino-proxy-server)
|
||||
uses: thedoctor0/zip-release@master
|
||||
with:
|
||||
type: 'zip'
|
||||
path: 'neutrino-proxy-server-${{ github.ref_name }}'
|
||||
filename: neutrino-proxy-server-${{ matrix.os }}-${{ github.ref_name }}.zip
|
||||
- name: Build with Maven (neutrino-proxy-client)
|
||||
run: |
|
||||
cd neutrino-proxy-client
|
||||
mvn clean package --file pom.xml --no-transfer-progress
|
||||
mvn clean native:compile -P native --file pom.xml --no-transfer-progress
|
||||
cp target/neutrino-proxy-client ./../neutrino-proxy-client-${{ github.ref_name }}
|
||||
- name: Archive zip (neutrino-proxy-client)
|
||||
uses: thedoctor0/zip-release@master
|
||||
with:
|
||||
type: 'zip'
|
||||
path: 'neutrino-proxy-client-${{ github.ref_name }}'
|
||||
filename: neutrino-proxy-client-${{ matrix.os }}-${{ github.ref_name }}.zip
|
||||
# GitHub 上创建 release
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
files: |
|
||||
neutrino-proxy-server/target/neutrino-proxy-server.jar
|
||||
neutrino-proxy-client/target/neutrino-proxy-client.jar
|
||||
neutrino-proxy-server-${{ matrix.os }}-${{ github.ref_name }}.zip
|
||||
neutrino-proxy-client-${{ matrix.os }}-${{ github.ref_name }}.zip
|
||||
prerelease: true
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,83 @@
|
||||
name: jar release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- 'win*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# see: https://docs.github.com/zh/actions/using-jobs/choosing-the-runner-for-a-job
|
||||
# os: ['ubuntu-20.04', 'windows-latest', 'macos-latest']
|
||||
os: ['windows-2022']
|
||||
name: build - ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
# https://github.com/softprops/action-gh-release/issues/236#issuecomment-1150530128
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: setup node v13.12.0
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 13.12.0
|
||||
# cache: 'npm' # 使用缓存需要把 package-lock.json 也上传到 git 上
|
||||
- name: build neutrino-proxy-admin
|
||||
run: |
|
||||
cd neutrino-proxy-admin
|
||||
npm install
|
||||
npm run build:dev
|
||||
cp -r ./dist/ ./../neutrino-proxy-server/src/main/resources/static/
|
||||
- name: GitHub Action for GraalVM JDK 17
|
||||
uses: graalvm/setup-graalvm@v1
|
||||
with:
|
||||
java-version: '17.0.7' # for a specific JDK 17; or '17' for the latest JDK 17
|
||||
distribution: 'graalvm' # New 'distribution' option
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Cache local Maven repository
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-maven-
|
||||
- name: Build with Maven (neutrino-proxy-server)
|
||||
run: |
|
||||
mvn clean install -pl neutrino-proxy-core -am -DskipTests --no-transfer-progress
|
||||
cd neutrino-proxy-server
|
||||
mvn clean native:compile -P native --file pom.xml --no-transfer-progress
|
||||
cp target/neutrino-proxy-server.exe .
|
||||
- name: Archive zip (neutrino-proxy-server)
|
||||
uses: thedoctor0/zip-release@master
|
||||
with:
|
||||
type: 'zip'
|
||||
path: 'neutrino-proxy-server-${{ github.ref_name }}'
|
||||
filename: neutrino-proxy-server-${{ matrix.os }}-${{ github.ref_name }}.zip
|
||||
- name: Build with Maven (neutrino-proxy-client)
|
||||
run: |
|
||||
cd neutrino-proxy-client
|
||||
mvn clean package --file pom.xml --no-transfer-progress
|
||||
mvn clean native:compile -P native --file pom.xml --no-transfer-progress
|
||||
cp target/neutrino-proxy-client.exe .
|
||||
- name: Archive zip (neutrino-proxy-client)
|
||||
uses: thedoctor0/zip-release@master
|
||||
with:
|
||||
type: 'zip'
|
||||
path: 'neutrino-proxy-client-${{ github.ref_name }}'
|
||||
filename: neutrino-proxy-client-${{ matrix.os }}-${{ github.ref_name }}.zip
|
||||
# GitHub 上创建 release
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
files: |
|
||||
neutrino-proxy-server/target/neutrino-proxy-server.jar
|
||||
neutrino-proxy-client/target/neutrino-proxy-client.jar
|
||||
neutrino-proxy-server.exe
|
||||
neutrino-proxy-client.exe
|
||||
prerelease: true
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -52,4 +52,10 @@ hs_err_pid*
|
||||
|
||||
**/app-**.yml
|
||||
|
||||
**/memo/**
|
||||
**/memo/**
|
||||
|
||||
neutrino-proxy-vuepress/deploy.sh
|
||||
.NEUTRINO_PROXY_CLIENT_ID
|
||||
logs
|
||||
**/cert/**
|
||||
neutrino-proxy-server/src/main/resources/static/**
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,52 @@
|
||||
# 基础
|
||||
- [x] 服务端编译、启动成功
|
||||
- [x] 客户端编译、启动成功
|
||||
- [x] 配置
|
||||
- [x] 启动参数
|
||||
- [x] 外部配置文件
|
||||
- [x] 环境变量
|
||||
- [ ] 日志
|
||||
- [x] 控制台输出
|
||||
- [x] debug启动有netty错误日志
|
||||
- [x] 文件输出
|
||||
- [x] 修改日志级别
|
||||
- [x] 心跳日志
|
||||
- [x] 传输报文日志
|
||||
- [x] 连接
|
||||
- [x] 隧道SSL连接
|
||||
- [x] 隧道非SSL连接
|
||||
- [x] 隧道SSL连接自定义证书
|
||||
|
||||
# 服务端管理后台
|
||||
- [x] 登录成功
|
||||
- [x] 首页统计
|
||||
- [x] 代理配置
|
||||
- [x] license管理
|
||||
- [x] 端口映射
|
||||
- [x] 系统管理
|
||||
- [x] 用户管理
|
||||
- [x] 端口分组管理
|
||||
- [x] 端口池管理
|
||||
- [x] 协议管理
|
||||
- [x] 调度管理
|
||||
- [x] 报表管理
|
||||
- [x] 用户流量报表
|
||||
- [x] License流量报表
|
||||
- [x] 用户流量月度明细
|
||||
- [x] License流量月度明细
|
||||
- [x] 日志管理
|
||||
- [x] 调度日志
|
||||
- [x] 登录日志
|
||||
- [x] 客户端连接日志
|
||||
|
||||
|
||||
# 客户端
|
||||
- [x] 重连机制
|
||||
|
||||
# 代理
|
||||
- [x] TCP代理
|
||||
- [x] HTTP(S)代理
|
||||
- [x] 端口访问
|
||||
- [x] HTTP域名访问
|
||||
- [x] HTTPS域名访问
|
||||
- [x] UDP代理
|
||||
@@ -1,15 +1,24 @@
|
||||
<p align="center">
|
||||
<img src="https://i.imgtg.com/2023/02/10/crIMY.png" width="45%">
|
||||
<img src="assets/logo/logo-text-vertical.png" width="45%">
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href='https://gitee.com/dromara/neutrino-proxy/stargazers'><img src='https://gitee.com/dromara/neutrino-proxy/badge/star.svg?theme=dark' alt='star'></img></a>
|
||||
<a href='https://gitee.com/dromara/neutrino-proxy/members'><img src='https://gitee.com/dromara/neutrino-proxy/badge/fork.svg?theme=dark' alt='fork'></img></a>
|
||||
<a target="_blank" href="https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html">
|
||||
<img src="https://img.shields.io/badge/JDK-8+-red.svg" />
|
||||
</a>
|
||||
<a href="./LICENSE">
|
||||
<img src="https://img.shields.io/badge/license-MIT-red" alt="license MIT">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
# 1、简介
|
||||
- 中微子代理(neutrino-proxy)是一个基于netty的、开源的java内网穿透项目。
|
||||
- 技术栈:Solon、MybatisPlus
|
||||
- 技术栈:Solon、MybatisPlus、Netty
|
||||
- 遵循MIT许可,因此您可以对它进行复制、修改、传播并用于任何个人或商业行为。
|
||||
- 官网地址1:https://neutrino-proxy.dromara.org
|
||||
- 官网地址2:https://dromara.gitee.io/neutrino-proxy
|
||||
- 快速上手请[点击这里](https://dromara.gitee.io/neutrino-proxy/pages/793dcb)
|
||||
|
||||
# 2、名称由来
|
||||
中微子,是轻子的一种,是组成自然界的最基本的粒子之一。它十分微小、不带电,可自由穿过地球,以接近光速运动,与其他物质的相互作用十分微弱,号称宇宙间的“隐身人”。
|
||||
@@ -19,141 +28,74 @@
|
||||
因此以中微子命名,寓意着中微子代理拥有中微子"安全"、"快速"、"穿透力强"的特点。
|
||||
|
||||
# 3、运行示例
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
# 4、项目结构
|
||||
- neutrino-proxy
|
||||
- neutrino-proxy-core 与代理相关的公共常量、编解码器
|
||||
- neutrino-proxy-client 代理客户端项目
|
||||
- neutrino-proxy-server 代理服务端项目
|
||||
- neutrino-proxy-admin 代理监控项目(基于vue-element-admin开发)
|
||||
# 4、代理示意图
|
||||

|
||||
|
||||
# 5、运行
|
||||
## 5.1、使用keytool工具生成ssl证书, 若不需要ssl加密可跳过
|
||||
```shell
|
||||
keytool -genkey -alias test1 -keyalg RSA -keysize 1024 -validity 3650 -keypass 123456 -storepass 123456 -keystore "./test.jks"
|
||||
```
|
||||
|
||||
## 5.2、修改服务端配置(application.yml)
|
||||
```yml
|
||||
application:
|
||||
name: neutrino-proxy-server
|
||||
|
||||
proxy:
|
||||
protocol:
|
||||
max-frame-length: 2097152
|
||||
length-field-offset: 0
|
||||
length-field-length: 4
|
||||
initial-bytes-to-strip: 0
|
||||
length-adjustment: 0
|
||||
read-idle-time: 60
|
||||
write-idle-time: 40
|
||||
all-idle-time-seconds: 0
|
||||
server:
|
||||
# 服务端端口,用于保持与客户端的连接,非SSL
|
||||
port: 9000
|
||||
# 服务端端口,用于保持与客户端的连接,SSL,需要jks证书文件,若不需要ssl支持,可不配置
|
||||
ssl-port: 9002
|
||||
# 证书密码
|
||||
key-store-password: 123456
|
||||
key-manager-password: 123456
|
||||
# 证书存放路径,若不想打进jar包,可不带classpath:前缀
|
||||
jks-path: classpath:/test.jks
|
||||
data:
|
||||
# 数据库配置(支持mysql)
|
||||
type: sqlite
|
||||
url: jdbc:sqlite:data.db
|
||||
driver-class: org.sqlite.JDBC
|
||||
username:
|
||||
password:
|
||||
```
|
||||
|
||||
## 5.3、启动服务端
|
||||
> java -jar neutrino-proxy-server.jar
|
||||
|
||||
## 5.4、修改客户端配置
|
||||
```yml
|
||||
application:
|
||||
name: neutrino-proxy-client
|
||||
|
||||
proxy:
|
||||
protocol:
|
||||
max-frame-length: 2097152
|
||||
length-field-offset: 0
|
||||
length-field-length: 4
|
||||
initial-bytes-to-strip: 0
|
||||
length-adjustment: 0
|
||||
read-idle-time: 60
|
||||
write-idle-time: 30
|
||||
all-idle-time-seconds: 0
|
||||
client:
|
||||
# ssl证书密码
|
||||
key-store-password: 123456
|
||||
# ssl证书存放位置
|
||||
jks-path: classpath:/test.jks
|
||||
# 服务端ip,若部署到服务器,则配置服务器的ip
|
||||
server-ip: localhost
|
||||
# 服务端端口,若使用ssl,则需要配置为服务端的"ssl-port"
|
||||
server-port: 9000
|
||||
# 是否启用ssl,启用则必须配置ssl相关参数
|
||||
ssl-enable: false
|
||||
```
|
||||
## 5.5、代理示意图
|
||||

|
||||
|
||||
## 5.6、启动客户端
|
||||
### 5.6.1、启动参数直接指定配置
|
||||
> java -jar neutrino-proxy-client.jar serverIp=localhost serverPort=9000 licenseKey=b0a907332b474b25897c4dcb31fc7eb6
|
||||
|
||||
### 5.6.2、启动参数指定外部配置文件
|
||||
> java -jar neutrino-proxy-client.jar config=app.properties
|
||||
|
||||
配置文件格式如下:
|
||||
```
|
||||
neutrino.proxy.client.server-ip=localhost
|
||||
neutrino.proxy.client.server-port=9002
|
||||
neutrino.proxy.client.ssl-enable=true
|
||||
neutrino.proxy.client.key-store-password=123456
|
||||
neutrino.proxy.client.jks-path=classpath:/test.jks
|
||||
neutrino.proxy.client.license-key=b0a907332b474b25897c4dcb31fc7eb6
|
||||
```
|
||||
|
||||
## 5.7、Docker快速启动
|
||||
```shell script
|
||||
# 一键部署前端和后端,客户端按上方部署即可
|
||||
docker run -it -p 9000-9200:9000-9200/tcp -p 8888:8888 -d --name neutrino registry.cn-hangzhou.aliyuncs.com/asgc/aoshiguchen-docker-images:1.64
|
||||
# 如没docker环境,可以一键部署docker环境(centos 7.8)系统
|
||||
curl http://www.wangke666.cn/static/createDocker.sh | bash
|
||||
```
|
||||
|
||||
# 6、演示环境
|
||||
> 可使用分配好的游客license试用。服务器带宽较低,仅供学习使用!
|
||||
- 管理后台地址:http://103.163.47.16:9527
|
||||
- 游客账号:visitor/123456
|
||||
|
||||
# 7、未来迭代方向
|
||||
- 优化代码、增强稳定性
|
||||
- 服务端增加管理页面,提供报表、授权、限流等功能
|
||||
- 从项目中分离、孵化出另一个开源项目(neutrino-framework)
|
||||
|
||||
# 8、技术文档
|
||||
- [Aop](./docs/Aop.MD)
|
||||
- [Channel](./docs/Channel.MD)
|
||||
|
||||
# 9、联系我们
|
||||
# 5、联系我们
|
||||
- 微信: yuyunshize
|
||||
- Gitee: https://gitee.com/asgc/neutrino-proxy
|
||||
|
||||
# 6、贡献者列表
|
||||
<p>
|
||||
<a href="https://gitee.com/zcans" target="_blank">
|
||||
<img src="assets/developer/zcans.png" width="11%">
|
||||
</a>
|
||||
<a href="https://gitee.com/bmlt" target="_blank">
|
||||
<img src="assets/developer/jiuye.png" width="11%">
|
||||
</a>
|
||||
<a href="https://gitee.com/wangke666" target="_blank">
|
||||
<img src="assets/developer/keke.png" width="11%">
|
||||
</a>
|
||||
<a href="https://gitee.com/xue-fangming" target="_blank">
|
||||
<img src="assets/developer/fangming.png" width="11%">
|
||||
</a>
|
||||
<a href="https://gitee.com/y0hanes" target="_blank">
|
||||
<img src="assets/developer/yohanes.png" width="11%">
|
||||
</a>
|
||||
<a href="https://gitee.com/noear_admin" target="_blank">
|
||||
<img src="assets/developer/near.png" width="11%">
|
||||
</a>
|
||||
<a href="https://gitee.com/westboy" target="_blank">
|
||||
<img src="assets/developer/westboy.jpg" width="11%">
|
||||
</a>
|
||||
<a href="https://gitee.com/liugddx" target="_blank">
|
||||
<img src="assets/developer/liugddx.png" width="11%">
|
||||
</a>
|
||||
<a href="https://gitee.com/DianZiFaPiao" target="_blank">
|
||||
<img src="assets/developer/tan90.png" width="12%">
|
||||
</a>
|
||||
<a href="https://gitee.com/dearsny" target="_blank">
|
||||
<img src="assets/developer/pingon.png" width="11%">
|
||||
</a>
|
||||
<a href="https://gitee.com/ixxxk" target="_blank">
|
||||
<img src="assets/developer/tany.png" width="11%">
|
||||
</a>
|
||||
<a href="https://gitee.com/zoo-plus" target="_blank">
|
||||
<img src="assets/developer/zoo-plus.png" width="11%">
|
||||
</a>
|
||||
<a href="https://gitee.com/MetalXingxing" target="_blank">
|
||||
<img src="assets/developer/metal.png" width="11%">
|
||||
</a>
|
||||
<a href="https://gitee.com/click33" target="_blank">
|
||||
<img src="assets/developer/click33.png" width="11%">
|
||||
</a>
|
||||
<a href="https://gitee.com/songyinyin" target="_blank">
|
||||
<img src="assets/developer/songyinyin.png" width="11%">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
# ❤️ 感谢
|
||||
* [Solon](https://gitee.com/noear/solon?from=NeutrinoProxy)
|
||||
* [Hutool](https://hutool.cn?from=NeutrinoProxy)
|
||||
* [JetBrains](https://www.jetbrains.com?from=NeutrinoProxy)
|
||||
|
||||

|
||||

|
||||
|
||||
# 📚 Dromara 成员项目
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
安卓客户端项目
|
||||
@@ -1,39 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>solon-parent</artifactId>
|
||||
<version>2.2.2</version>
|
||||
</parent>
|
||||
|
||||
<groupId>fun.asgc</groupId>
|
||||
<artifactId>job-solon-plugin</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>solon</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<!--quartz-->
|
||||
<dependency>
|
||||
<groupId>org.quartz-scheduler</groupId>
|
||||
<artifactId>quartz</artifactId>
|
||||
<version>2.3.1</version>
|
||||
</dependency>
|
||||
<!--hutool-->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>5.8.15</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,34 +0,0 @@
|
||||
package fun.asgc.solon.extend.job;
|
||||
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/9/4
|
||||
*/
|
||||
public class CustomThreadFactory implements ThreadFactory {
|
||||
private final ThreadGroup group;
|
||||
private final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||
private final String namePrefix;
|
||||
|
||||
public CustomThreadFactory(String prefix) {
|
||||
SecurityManager s = System.getSecurityManager();
|
||||
group = (s != null) ? s.getThreadGroup() :
|
||||
Thread.currentThread().getThreadGroup();
|
||||
namePrefix = prefix + "-thread-";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
|
||||
if (t.isDaemon()) {
|
||||
t.setDaemon(false);
|
||||
}
|
||||
if (t.getPriority() != Thread.NORM_PRIORITY) {
|
||||
t.setPriority(Thread.NORM_PRIORITY);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package fun.asgc.solon.extend.job;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/9/4
|
||||
*/
|
||||
public interface IJobCallback {
|
||||
|
||||
/**
|
||||
* 执行日志
|
||||
* @param jobInfo
|
||||
* @param param
|
||||
* @param throwable
|
||||
*/
|
||||
void executeLog(JobInfo jobInfo, String param, Throwable throwable);
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package fun.asgc.solon.extend.job;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/9/4
|
||||
*/
|
||||
public interface IJobExecutor {
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
* @throws JobException
|
||||
*/
|
||||
void init() throws Exception;
|
||||
|
||||
/**
|
||||
* 新增job
|
||||
* @param jobInfo
|
||||
*/
|
||||
void add(JobInfo jobInfo);
|
||||
|
||||
/**
|
||||
* 删除job
|
||||
* @param jobName
|
||||
*/
|
||||
void remove(String jobName);
|
||||
|
||||
/**
|
||||
* 触发
|
||||
* @param jobName
|
||||
* @param param
|
||||
*/
|
||||
void trigger(String jobName, String param);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package fun.asgc.solon.extend.job;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/9/4
|
||||
*/
|
||||
public interface IJobHandler {
|
||||
|
||||
/**
|
||||
* job执行
|
||||
* @param param
|
||||
* @throws Exception
|
||||
*/
|
||||
void execute(String param) throws Exception;
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package fun.asgc.solon.extend.job;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/9/4
|
||||
*/
|
||||
public interface IJobSource {
|
||||
|
||||
/**
|
||||
* 获取所有job列表
|
||||
* @return
|
||||
*/
|
||||
List<JobInfo> sourceList();
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package fun.asgc.solon.extend.job;
|
||||
|
||||
import fun.asgc.solon.extend.job.impl.JobExecutor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.noear.solon.Solon;
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/9/4
|
||||
*/
|
||||
@Slf4j
|
||||
public class JobBean implements Job {
|
||||
|
||||
@Override
|
||||
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
|
||||
JobExecutor jobExecutor = Solon.context().getBean(JobExecutor.class);
|
||||
if (null != jobExecutor) {
|
||||
jobExecutor.execute(jobExecutionContext);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package fun.asgc.solon.extend.job;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/9/4
|
||||
*/
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
public class JobInfo {
|
||||
private String id;
|
||||
private String name;
|
||||
private String desc;
|
||||
private String cron;
|
||||
private String param;
|
||||
private boolean enable;
|
||||
private Map<String, Object> extension;
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package fun.asgc.solon.extend.job;
|
||||
|
||||
import fun.asgc.solon.extend.job.annotation.EnableJob;
|
||||
import fun.asgc.solon.extend.job.impl.DefaultJobCallback;
|
||||
import fun.asgc.solon.extend.job.impl.DefaultJobSource;
|
||||
import fun.asgc.solon.extend.job.impl.JobExecutor;
|
||||
import org.noear.solon.Solon;
|
||||
import org.noear.solon.core.AopContext;
|
||||
import org.noear.solon.core.Plugin;
|
||||
import org.noear.solon.core.event.AppLoadEndEvent;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/3/11
|
||||
*/
|
||||
public class XPluginImp implements Plugin {
|
||||
|
||||
@Override
|
||||
public void start(AopContext context) throws Throwable {
|
||||
EnableJob enableJob = Solon.app().source().getAnnotation(EnableJob.class);
|
||||
if (null == enableJob || !enableJob.value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
//应用加载完后,再启动任务
|
||||
Solon.app().onEvent(AppLoadEndEvent.class, e -> {
|
||||
IJobSource jobSource = context.getBean(IJobSource.class);
|
||||
IJobCallback jobCallback = context.getBean(IJobCallback.class);
|
||||
if (null == jobSource) {
|
||||
jobSource = new DefaultJobSource();
|
||||
}
|
||||
if (null == jobCallback) {
|
||||
jobCallback = new DefaultJobCallback();
|
||||
}
|
||||
|
||||
JobExecutor jobExecutor = new JobExecutor();
|
||||
jobExecutor.setJobSource(jobSource);
|
||||
jobExecutor.setJobCallback(jobCallback);
|
||||
jobExecutor.start();
|
||||
|
||||
context.wrapAndPut(JobExecutor.class, jobExecutor);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package fun.asgc.solon.extend.job.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/3/12
|
||||
*/
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface EnableJob {
|
||||
boolean value() default true;
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package fun.asgc.solon.extend.job.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/9/4
|
||||
*/
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface JobHandler {
|
||||
String name();
|
||||
String desc() default "";
|
||||
String cron();
|
||||
String param() default "";
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package fun.asgc.solon.extend.job.impl;
|
||||
|
||||
import fun.asgc.solon.extend.job.IJobCallback;
|
||||
import fun.asgc.solon.extend.job.JobInfo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/3/12
|
||||
*/
|
||||
@Slf4j
|
||||
public class DefaultJobCallback implements IJobCallback {
|
||||
|
||||
@Override
|
||||
public void executeLog(JobInfo jobInfo, String param, Throwable throwable) {
|
||||
if (null == throwable) {
|
||||
log.debug("[Solon Plugin Job] Job执行 id:{} name:{} desc:{} param:{}", jobInfo.getId(), jobInfo.getName(), jobInfo.getDesc(), param);
|
||||
} else {
|
||||
log.error("[Solon Plugin Job] Job执行 id:{} name:{} desc:{} param:{}", jobInfo.getId(), jobInfo.getName(), jobInfo.getDesc(), param, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package fun.asgc.solon.extend.job.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import fun.asgc.solon.extend.job.IJobHandler;
|
||||
import fun.asgc.solon.extend.job.IJobSource;
|
||||
import fun.asgc.solon.extend.job.annotation.JobHandler;
|
||||
import fun.asgc.solon.extend.job.JobInfo;
|
||||
import org.noear.solon.Solon;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/9/4
|
||||
*/
|
||||
public class DefaultJobSource implements IJobSource {
|
||||
|
||||
@Override
|
||||
public List<JobInfo> sourceList() {
|
||||
List<IJobHandler> jobHandlerList = Solon.context().getBeansOfType(IJobHandler.class);
|
||||
if (CollectionUtil.isEmpty(jobHandlerList)) {
|
||||
return CollectionUtil.newArrayList();
|
||||
}
|
||||
List<JobInfo> jobInfoList = CollectionUtil.newArrayList();
|
||||
for (IJobHandler jobHandler : jobHandlerList) {
|
||||
JobHandler handler = jobHandler.getClass().getAnnotation(JobHandler.class);
|
||||
if (null == handler || StrUtil.isEmpty(handler.name()) || StrUtil.isEmpty(handler.cron())) {
|
||||
continue;
|
||||
}
|
||||
jobInfoList.add(new JobInfo()
|
||||
.setId(handler.name())
|
||||
.setName(handler.name())
|
||||
.setDesc(handler.desc())
|
||||
.setCron(handler.cron())
|
||||
.setParam(handler.param())
|
||||
.setEnable(true)
|
||||
);
|
||||
}
|
||||
return jobInfoList;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
package fun.asgc.solon.extend.job.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import fun.asgc.solon.extend.job.*;
|
||||
import fun.asgc.solon.extend.job.annotation.JobHandler;
|
||||
import org.noear.solon.Solon;
|
||||
import org.quartz.*;
|
||||
import org.quartz.impl.StdSchedulerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Job执行器
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/9/4
|
||||
*/
|
||||
public class JobExecutor implements IJobExecutor {
|
||||
private IJobSource jobSource;
|
||||
private ThreadPoolExecutor threadPoolExecutor;
|
||||
private Map<String, JobInfo> jobInfoMap = new ConcurrentHashMap<>();
|
||||
private SchedulerFactory schedulerFactory;
|
||||
private Scheduler scheduler;
|
||||
private Map<String, IJobHandler> jobHandlerMap = new ConcurrentHashMap<>();
|
||||
private Set<String> runJobSet = CollectionUtil.newHashSet();
|
||||
private IJobCallback jobCallback;
|
||||
private Map<String, TriggerKey> triggerKeyMap = new ConcurrentHashMap<>();
|
||||
|
||||
public void start() {
|
||||
if (null == threadPoolExecutor) {
|
||||
threadPoolExecutor = new ThreadPoolExecutor(5, 20, 10L, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(), new CustomThreadFactory("SolonJob"));
|
||||
}
|
||||
|
||||
List<IJobHandler> jobHandlerList = Solon.context().getBeansOfType(IJobHandler.class);
|
||||
if (!CollectionUtil.isEmpty(jobHandlerList)) {
|
||||
for (IJobHandler item : jobHandlerList) {
|
||||
JobHandler jobHandler = item.getClass().getAnnotation(JobHandler.class);
|
||||
if (null == jobHandler) {
|
||||
continue;
|
||||
}
|
||||
jobHandlerMap.put(jobHandler.name(), item);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
this.schedulerFactory = new StdSchedulerFactory();
|
||||
this.scheduler = schedulerFactory.getScheduler();
|
||||
this.init();
|
||||
} catch (Exception e){
|
||||
throw new RuntimeException("job初始化异常");
|
||||
}
|
||||
}
|
||||
|
||||
public void setJobSource(IJobSource jobSource) {
|
||||
this.jobSource = jobSource;
|
||||
}
|
||||
|
||||
public void setThreadPoolExecutor(ThreadPoolExecutor threadPoolExecutor) {
|
||||
this.threadPoolExecutor = threadPoolExecutor;
|
||||
}
|
||||
|
||||
public void setJobCallback(IJobCallback jobCallback) {
|
||||
this.jobCallback = jobCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() throws Exception {
|
||||
List<JobInfo> jobInfoList = jobSource.sourceList();
|
||||
if (CollectionUtil.isEmpty(jobInfoList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (JobInfo jobInfo : jobInfoList) {
|
||||
add(jobInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(JobInfo jobInfo) {
|
||||
if (null == jobInfo || StrUtil.isEmpty(jobInfo.getId()) || StrUtil.isEmpty(jobInfo.getName()) ||
|
||||
StrUtil.isEmpty(jobInfo.getCron())) {
|
||||
return;
|
||||
}
|
||||
synchronized (jobInfo.getId()) {
|
||||
runJobSet.add(jobInfo.getName());
|
||||
jobInfoMap.put(jobInfo.getId(), jobInfo);
|
||||
|
||||
TriggerKey triggerKey = TriggerKey.triggerKey(jobInfo.getId());
|
||||
triggerKeyMap.put(jobInfo.getId(), triggerKey);
|
||||
JobKey jobKey = new JobKey(jobInfo.getName());
|
||||
|
||||
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobInfo.getCron()).withMisfireHandlingInstructionDoNothing();
|
||||
CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
|
||||
JobDetail jobDetail = JobBuilder.newJob(JobBean.class).withIdentity(jobKey).build();
|
||||
|
||||
try {
|
||||
if (jobInfo.isEnable()) {
|
||||
scheduler.scheduleJob(jobDetail, cronTrigger);
|
||||
scheduler.start();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(String.format("新增job[name=%s]异常", jobInfo.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(String jobId) {
|
||||
JobInfo jobInfo = jobInfoMap.get(jobId);
|
||||
if (null == jobInfo) {
|
||||
return;
|
||||
}
|
||||
synchronized (jobId) {
|
||||
runJobSet.remove(jobInfo.getName());
|
||||
unscheduleJob(jobId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trigger(String jobId, String param) {
|
||||
doExecute(jobId, param);
|
||||
}
|
||||
|
||||
public void execute(JobExecutionContext context) throws JobExecutionException {
|
||||
if (null == context || null == context.getTrigger()) {
|
||||
return;
|
||||
}
|
||||
String jobId = context.getTrigger().getKey().getName();
|
||||
JobInfo jobInfo = jobInfoMap.get(jobId);
|
||||
if (null == jobInfo) {
|
||||
unscheduleJob(jobId);
|
||||
return;
|
||||
}
|
||||
doExecute(jobId, jobInfo.getParam());
|
||||
}
|
||||
|
||||
private void unscheduleJob(String jobId) {
|
||||
TriggerKey triggerKey = triggerKeyMap.get(jobId);
|
||||
if (null != triggerKey) {
|
||||
try {
|
||||
scheduler.unscheduleJob(triggerKey);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void doExecute(String jobId, String param) {
|
||||
JobInfo jobInfo = jobInfoMap.get(jobId);
|
||||
if (null == jobInfo) {
|
||||
return;
|
||||
}
|
||||
IJobHandler jobHandler =jobHandlerMap.get(jobInfo.getName());
|
||||
if (null == jobHandler) {
|
||||
return;
|
||||
}
|
||||
threadPoolExecutor.submit(() -> {
|
||||
try {
|
||||
jobHandler.execute(param);
|
||||
if (null != jobCallback) {
|
||||
jobCallback.executeLog(jobInfo, param, null);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
jobCallback.executeLog(jobInfo, param, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package fun.asgc.solon.extend.job;
|
||||
@@ -1,2 +0,0 @@
|
||||
solon.plugin=fun.asgc.solon.extend.job.XPluginImp
|
||||
solon.plugin.priority=2
|
||||
@@ -1,30 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>solon-parent</artifactId>
|
||||
<version>2.2.2</version>
|
||||
</parent>
|
||||
|
||||
<groupId>fun.asgc</groupId>
|
||||
<artifactId>orika-solon-plugin</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>solon</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!--orika-->
|
||||
<dependency>
|
||||
<groupId>ma.glasnost.orika</groupId>
|
||||
<artifactId>orika-core</artifactId>
|
||||
<version>1.5.4</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,33 +0,0 @@
|
||||
package fun.asgc.solon.extend.orika;
|
||||
|
||||
import ma.glasnost.orika.CustomConverter;
|
||||
import ma.glasnost.orika.Mapper;
|
||||
import ma.glasnost.orika.MapperFacade;
|
||||
import ma.glasnost.orika.MapperFactory;
|
||||
import ma.glasnost.orika.impl.DefaultMapperFactory;
|
||||
import ma.glasnost.orika.metadata.ClassMapBuilder;
|
||||
import org.noear.solon.core.AopContext;
|
||||
import org.noear.solon.core.Plugin;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/3/11
|
||||
*/
|
||||
public class XPluginImp implements Plugin {
|
||||
@Override
|
||||
public void start(AopContext context) throws Throwable {
|
||||
DefaultMapperFactory factory = new DefaultMapperFactory.Builder().build();
|
||||
context.subBeansOfType(CustomConverter.class, bean -> {
|
||||
factory.getConverterFactory().registerConverter(bean);
|
||||
});
|
||||
context.subBeansOfType(Mapper.class, bean -> {
|
||||
factory.registerMapper(bean);
|
||||
});
|
||||
context.subBeansOfType(ClassMapBuilder.class, bean -> {
|
||||
factory.registerClassMap((ClassMapBuilder<? extends Object, ? extends Object>) bean);
|
||||
});
|
||||
context.wrapAndPut(MapperFactory.class, factory);
|
||||
context.wrapAndPut(MapperFacade.class, factory.getMapperFacade());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package fun.asgc.solon.extend.orika;
|
||||
@@ -1,2 +0,0 @@
|
||||
solon.plugin=fun.asgc.solon.extend.orika.XPluginImp
|
||||
solon.plugin.priority=1
|
||||
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 258 KiB |
|
Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 183 KiB |
|
After Width: | Height: | Size: 350 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 833 B |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 858 KiB |
|
After Width: | Height: | Size: 444 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 736 B |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 161 KiB |
|
After Width: | Height: | Size: 597 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 804 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 440 KiB |
|
Before Width: | Height: | Size: 521 KiB After Width: | Height: | Size: 521 KiB |
|
After Width: | Height: | Size: 502 KiB |
|
After Width: | Height: | Size: 441 KiB |
|
After Width: | Height: | Size: 435 KiB |
|
After Width: | Height: | Size: 449 KiB |
|
After Width: | Height: | Size: 394 KiB |
|
After Width: | Height: | Size: 327 KiB |
|
After Width: | Height: | Size: 427 KiB |
|
After Width: | Height: | Size: 312 KiB |
|
After Width: | Height: | Size: 217 KiB |
|
After Width: | Height: | Size: 495 KiB |
|
After Width: | Height: | Size: 328 KiB |
|
After Width: | Height: | Size: 315 KiB |
|
After Width: | Height: | Size: 311 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
@@ -52,3 +52,6 @@
|
||||
通过`ProxyChannel`向服务端发送`Transfer`指令,并携带响应数据。
|
||||
- 5.4、服务端通过`ProxyChannel`收到来自客户端的`Transfer`指令,找到`ProxyChannel`绑定的`VisitorChannel`,
|
||||
通过`VisitorChannel`向用户端写入响应数据。
|
||||
|
||||
# 请求流程图
|
||||

|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
## 1、关于作者
|
||||
- dromara开源组织成员,[dromara/neutrino-proxy](https://gitee.com/asgc/neutrino-proxy)项目作者
|
||||
- 名称:傲世孤尘、雨韵诗泽
|
||||
- 名言:
|
||||
- 扎根土壤,心向太阳。积蓄能量,绽放微光。
|
||||
- 拘浊酒邀明月,借赤日暖苍穹。喻万物为星斗,化沧海成诗篇。
|
||||
- 还生命以坦然,遗沧海以一笑。
|
||||
- 微信号:yuyunshize
|
||||
- Gitee: https://gitee.com/asgc
|
||||
|
||||
## 2、项目简介
|
||||
- [中微子代理(neutrino-proxy)](https://gitee.com/asgc/neutrino-proxy) 是一款基于netty的内网穿透神器。该项目采用最为宽松的MIT协议,因此您可以对它进行复制、修改、传播并用于任何个人或商业行为。
|
||||
- 演示环境:
|
||||
- 地址:http://103.163.47.16:9527
|
||||
- 账号:visitor/123456
|
||||
- 该开源版本针对开发者个人,不提供任何付费服务。上面提供的地址、账号仅用于演示,已提供现成的license供大家测试。
|
||||
- 市面上基于内网穿透的常见产品有:花生壳、TeamView、cpolar等。
|
||||
- 该项目的目标是打开内网穿透功能黑盒,让更多感兴趣的朋友能更简单、低成本的掌握内网穿透技术。
|
||||
- 学习、使用过程中,有任何问题都可以通过微信(通过上面的微信号,或扫描结尾的二维码)与笔者联系。同时,若该项目对您有所帮助,也请大家不吝转发推荐。
|
||||
|
||||
## 3、目录&文件说明
|
||||
```
|
||||
├── data.db 'sqlite数据库文件。若未配置mysql,默认使用sqlite,项目首次启动会自动初始化sqlite数据库。
|
||||
├── docs '项目相关的一些文档
|
||||
├── ├── Aop.MD '框架层Aop机制、使用说明
|
||||
├── └── Channel.MD '内网穿透实现原理、代理实现流程说明
|
||||
├── lib '项目开启了将自动生成的类保存到本地后,运行过程中动态生成的类自动保存到此处,方便学习、调试
|
||||
├── neutrino-core '一套手写的基于netty的框架,相当于简易版的SpringBoot + Mybatis + xxljob,计划后期分离为单独开源项目维护
|
||||
├── neutrino-proxy-admin '基于vue-element-admin开发的一个管理系统,用于可视化操作端口映射、代理数据实时监控
|
||||
├── neutrino-proxy-client '基于netty的代理客户端,用于和服务端交互、转发内网数据
|
||||
├── neutrino-proxy-core '代理相关的公共代码(协议、常量)
|
||||
├── neutrino-proxy-server '基于netty的代理服务端,用于和客户段交互,将客户端转发的内网数据转发至外网端口
|
||||
└── todolist.MD '近期的开发计划
|
||||
```
|
||||
|
||||
## 4、项目亮点
|
||||
- 性能:基于netty,天然拥有netty的高性能优势
|
||||
- 学习:除netty之外,只依赖了最基本的日志、数据库驱动/连接池、json等jar包,代理层代码量非常精简。极其适合java初学者用来学习和掌握Web框架底层原理、内网穿透实现机制。
|
||||
- 调试:只需要安装jdk8,甚至连数据库都无需考虑,即可直接在本地运行服务端+客户端,调试代理流程。尽可能减少前期步骤,确保大家能更快上手。
|
||||
- 测试:针对框架层封装(如:AOP、Web容器、ORM、调度管理等),提供了丰富的测试代码。
|
||||
|
||||
## 5、运行环境
|
||||
以下版本均为笔者开发时实际的使用版本
|
||||
|
||||
- `Node`:v13.12.0
|
||||
- `Java`:1.8.0_351
|
||||
- `Mysql`:8.0.31 (非必需,使用sqlite则无需考虑数据库)
|
||||
- `Nginx`:1.21.4 (非必需,可直接使用服务端项目提供的静态资源服务)
|
||||
|
||||
## 6、使用方法
|
||||
### 6.1、打包
|
||||
可直接前往[Gitee仓库发行版页面](https://gitee.com/dromara/neutrino-proxy/releases)下载所需版本已打好的包。
|
||||
若需手动打包,则可参照下面的执行命令:
|
||||
```sh
|
||||
# 服务端打包
|
||||
mvn clean install -U -pl neutrino-proxy-server -am -Dmaven.test.skip=true
|
||||
|
||||
# 客户端打包
|
||||
clean install -U -pl neutrino-proxy-client -am -Dmaven.test.skip=true
|
||||
|
||||
# 管理后台前端项目打包(本地环境,local改为dev则为dev环境,同时需要修改config目录下面的环境配置)
|
||||
npm run build:local
|
||||
```
|
||||
|
||||
### 6.2、部署
|
||||
#### 6.2.1、服务端部署
|
||||
使用常规的jar包部署方式即可,如:java -jar xxxx
|
||||
|
||||
#### 6.2.2、管理后台部署
|
||||
Nginx方式部署(推荐):
|
||||
|
||||
```
|
||||
server {
|
||||
listen 9527;
|
||||
server_name localhost;
|
||||
|
||||
#开启gzip
|
||||
gzip on;
|
||||
#低于1kb的资源不压缩
|
||||
gzip_min_length 1k;
|
||||
#压缩级别1-9,越大压缩率越高,同时消耗cpu资源也越多,建议设置在5左右。
|
||||
gzip_comp_level 5;
|
||||
#需要压缩哪些响应类型的资源,多个空格隔开。不建议压缩图片.
|
||||
gzip_types text/plain application/javascript application/x-javascript text/javascript text/xml text/css;
|
||||
#配置禁用gzip条件,支持正则。此处表示ie6及以下不启用gzip(因为ie低版本不支持)
|
||||
gzip_disable "MSIE [1-6]\.";
|
||||
#是否添加“Vary: Accept-Encoding”响应头
|
||||
gzip_vary on;
|
||||
|
||||
location / {
|
||||
root /work/projects/neutrino-proxy-server/neutrino-proxy-admin/dist;
|
||||
try_files $uri $uri/ /index.html;
|
||||
add_header Last-Modified $date_gmt;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在没有Nginx时,为了快速体验代理效果,可直接使用服务端项目提供的静态资源服务。
|
||||
直接将`neutrino-proxy-admin`打包后的文件解压放在`neutrino-proxy-server.jar`同级别目录下即可。
|
||||
例如:服务端配置的web端口为8080,则访问http://服务端IP:8080, 则会直接解析渲染`neutrino-proxy-server.jar`同级别目录下`neutrino-proxy-admin/dist/index.html`。
|
||||
|
||||
需要注意的是,使用服务端自带的静态资源服务时,由于框架目前未支持缓存、gzip压缩,所以访问速度没有使用nginx快,正式使用推荐用nginx。
|
||||
|
||||
### 6.3、配置
|
||||
#### 6.3.1、配置端口池
|
||||
端口池的作用是将代理服务需要对外暴露的端口进行集中管理,方便安全组设置时统一操作。
|
||||
|
||||
本项目为了简化首次配置,默认初始化数据会将9101~9120的所有端口加入端口池。
|
||||
可以在服务端项目sql配置(port_pool.data.sql文件)中自行修改初始化数据,也可以运行后在端口池管理页面手动维护。
|
||||
|
||||
首次使用如果为了快速体验,建议直接使用默认端口池,则无需任何配置。
|
||||
|
||||
#### 6.3.2、配置用户
|
||||
用户是登录管理后台的身份主体,同时也是持有license的身份主体,用于支持代理连接的合法性认证。
|
||||
|
||||
本项目为了简化首次配置,默认初始化admin、visitor两个用户,默认密码为123456。
|
||||
可以在服务端项目sql配置(user.data.sql文件)中自行修改初始化数据,也可以运行后在用户管理页面手动维护。
|
||||
|
||||
首次使用如果为了快速体验,建议直接使用默认用户,则无需任何配置。
|
||||
|
||||
#### 6.3.3、配置License
|
||||
License是客户端连接代理服务端时所需要的唯一合法凭证,一个License同时只能被一个客户端使用。
|
||||
|
||||
本项目为了简化首次配置,默认为每个用户初始化了一些license。
|
||||
可以在服务端项目sql配置(license.data.sql文件)中自行修改初始化数据,也可以运行后在License管理页面手动维护。
|
||||
|
||||
首次使用如果为了快速体验,建议直接使用默认License,则无需任何配置。
|
||||
|
||||
#### 6.3.4、配置端口映射
|
||||
端口映射是代理的基本单元,一个外网端口在同一时刻被唯一的映射到一个内网IP+端口。所有流出该外网端口的流量都转发自对应的内网端口,同理所有流入该外网端口的流量都会转发到对应的内网端口。
|
||||
|
||||
本项目为了简化首次配置,默认为每个用户初始化了一些端口映射。可以在服务端项目sql配置(port_mapping.data.sql文件)中自行修改初始化数据,也可以运行后在端口映射管理页面手动维护。
|
||||
|
||||
首次使用如果为了快速体验,建议直接使用默认端口映射,则无需任何配置。
|
||||
|
||||
|
||||
### 6.4、开启代理
|
||||
上述步骤完成后,就可以开始代理自己的局域网设备了。
|
||||
|
||||
比如现在有一个license:a123456,该license配置了一个或多个端口映射,其中包含9010到localhost:8080的映射(localhost表示代理客户端所在主机的端口,可以换成客户端所在局域网的任何IP)。此时修改客户端配置的服务端ip、license等启动客户端,就可以开启代理了,端口映射管理对应的记录在线状态为"在线"则证明代理成功建立。
|
||||
|
||||
该license配置也可以通过客户端启动参数、启动后引导式输入、外置配置提供,为了简化首次体验门槛,这里直接提供一个外置配置模版:
|
||||
```json
|
||||
{
|
||||
"jksPath":"classpath:/test.jks",
|
||||
"licenseKey":"b0a907332b474b25897c4dcb31fc7eb6",
|
||||
"serverIp":"localhost",
|
||||
"serverPort":9002,
|
||||
"sslEnable":true
|
||||
}
|
||||
```
|
||||
|
||||
修改上述json中的licenseKey,serverIp、serverPort、sslEnabled后,保存文件命名为`.neutrino-proxy-client.json`,放在客户端当前目录下(jar包启动时,放在jar包同级别目录下。idea启动时,放在项目根目录下),然后直接启动客户端。
|
||||
|
||||
此时通过访问外网ip+端口,可以成功访问内网服务。通过此方式,可以代理任何基于TCP之上的协议,如:socket、websocket、http、ftp、ssh等。
|
||||
|
||||
## 7、开发&调试
|
||||
篇幅所限,此处不便赘述管理后台相关的开发,有vue基础的童鞋基本都可以自行开发。服务端的初始化数据足够调试工作,开发过程无需单独在管理页面操作。
|
||||
|
||||
与SpringBoot项目类似,客户端、服务端项目均由一个入口类完成整个项目的启动,分别为`ProxyClient`、`ProxyServer`。
|
||||
|
||||
内置配置采用yml风格,主要涉及Http端口、静态资源路径、协议参数、代理服务端端口、jks证书、数据源、license等。
|
||||
整个项目基于`neutrino-core`,风格类似于SpringBoot。笔者不喜欢因过分炫技而引入过多花式操作,因为项目的定位是个人开发者,且满足使用的同时兼顾学习其原理的需求。基本使用方式与SpringBoot类似,尽可能降低首次学习成本。
|
||||
|
||||
`neutrino-core`项目test目录下包含众多核心封装的测试代码,通过调试这些代码,能尽可能减少大家学习的障碍。
|
||||
|
||||
## 8、运行截图
|
||||
License管理:
|
||||
<img src="./license1.png" width="100%"/>
|
||||
|
||||
端口映射管理:
|
||||
<img src="./port-mapping1.png" width="100%"/>
|
||||
|
||||
端口池管理:
|
||||
<img src="./port-pool1.png" width="100%"/>
|
||||
|
||||
用户管理:
|
||||
<img src="./user-manager1.png" width="100%"/>
|
||||
|
||||
## 9、迭代进度&未来计划
|
||||
从`2022-06-16` 第一个完整可用版本发布(无管理后台、无DB,纯配置实现)起,该项目已具备完整的内网穿透功能。经过后续的不断重构,初步完成了以下迭代:
|
||||
- 使用sqlite取代纯配置,增加管理后台界面。解决修改端口映射需要重新打包的问题。
|
||||
- 增加mysql支持。初学时,直接默认使用sqlite,可以专注于学习代理相关细节而不必额外安装、配置数据库。有了自己的服务器后,可配置使用mysql,完全兼容。
|
||||
- 增加端口池管理。方便服务器统一设置安全组,避免暴露的外网端口杂乱无章。
|
||||
- 集成调度管理。个人版项目自带调度管理,降低学习、使用成本。无需部署、接入其他调度服务。
|
||||
- 增加简单的游客权限限制。个人版项目无需权限管理,但是为了演示需要,同时避免游客篡改管理员数据。
|
||||
- 封装一套类似于SpringBoot + Mybatis的简化框架,用于支撑上层代理项目,减少外部依赖,为后期孵化一个开源框架做准备。
|
||||
|
||||
由于笔者工作之余才能进行开发,可投入时间不固定。截至目前(2023-02),该项目2.0版本仍未发布,基于此未来的计划如下:
|
||||
- 完成2.0版本发布(还剩下报表管理、首页图表功能)。
|
||||
- 底层框架全部重构。
|
||||
- 分离出一个开源框架项目,并持续维护。
|
||||
- 思考或着手基于内网穿透的产品开发,包括但不限于内网穿透SaaS服务、堡垒机。
|
||||
|
||||
## 10、联系我们
|
||||
笔者时间、能力有限,且开源项目非一朝一夕之事,存在众多问题亦在所难免。使用、学习过程中有任何问题欢迎大家与我联系。
|
||||
|
||||
对项目有什么想法或者建议,可以加我微信拉交流群,或者创建[issues](https://gitee.com/dromara/neutrino-proxy/issues),一起完善项目
|
||||
|
||||
<img src="MyWeChatQRCode.jpeg" width="100%"/>
|
||||
|
After Width: | Height: | Size: 123 KiB |
|
Before Width: | Height: | Size: 276 KiB After Width: | Height: | Size: 276 KiB |
|
Before Width: | Height: | Size: 216 KiB After Width: | Height: | Size: 216 KiB |
|
Before Width: | Height: | Size: 425 KiB After Width: | Height: | Size: 425 KiB |
|
Before Width: | Height: | Size: 202 KiB After Width: | Height: | Size: 202 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 502 KiB |
@@ -0,0 +1,113 @@
|
||||
# 内网穿透神器NeutrinoProxy 1.8.0版本发布
|
||||
|
||||
## 项目简介
|
||||
- [中微子代理(neutrino-proxy)](https://gitee.com/dromara/neutrino-proxy) 是一款基于netty的内网穿透神器。该项目采用最为宽松的MIT协议,因此您可以对它进行复制、修改、传播并用于任何个人或商业行为。
|
||||
- Gitee地址:https://gitee.com/dromara/neutrino-proxy
|
||||
- 官网地址:http://neutrino-proxy.dromara.org
|
||||
- 服务端管理后台截图:
|
||||
<img src="./home.png" width="100%"/>
|
||||
|
||||
## 主要特点:
|
||||
- 1、流量监控:首页图表、报表管理多维度流量监控。全方位掌握实时、历史代理数据。
|
||||
- 2、用户/License:支持多用户、多客户端使用。后台禁用实时生效。
|
||||
- 3、端口池:对外端口统一管理,支持用户、License独占端口。
|
||||
- 4、端口映射:新增、编辑、删除、禁用实时生效。
|
||||
- 5、Docker:服务端支持Docker一键部署。
|
||||
- 6、SSL证书:支持SSL,保护您的信息安全。
|
||||
- 7、域名映射:支持绑定子域名,方便本地调试三方回调
|
||||
- 8、采用最为宽松的MIT协议,免去你的后顾之忧
|
||||
|
||||
## 快速使用
|
||||
> 更多使用姿势、细节请通过官网或结尾微信二维码加我备注"中微子代理"入群交流。
|
||||
|
||||
### 1、 部署服务端
|
||||
#### 1.1、 Docker一键部署
|
||||
> 当前最新版本为1.8.0,下面的脚本中,可以使用:`registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:1.8.0` 指定版本安装,推荐使用`latest`直接安装最新版。
|
||||
|
||||
##### 使用默认sqlite数据库
|
||||
```shell
|
||||
docker run -it -p 9000-9200:9000-9200/tcp -p 8888:8888 \
|
||||
-d --restart=always --name neutrino-proxy \
|
||||
registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:latest
|
||||
```
|
||||
|
||||
##### 指定自己的mysql数据库
|
||||
- 在服务器上创建目录:/root/neutrino-proxy/config
|
||||
- 在该目录下创建`app.yml`文本文件,并配置如下内容:
|
||||
```yml
|
||||
neutrino:
|
||||
data:
|
||||
db:
|
||||
type: mysql
|
||||
# 自己的数据库实例,创建一个空的名为'neutrino-proxy'的数据库即可,首次启动服务端会自动初始化
|
||||
url: jdbc:mysql://xxxx:3306/neutrino-proxy?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useAffectedRows=true&useSSL=false
|
||||
driver-class: com.mysql.jdbc.Driver
|
||||
# 数据库帐号
|
||||
username: xxx
|
||||
# 数据库密码
|
||||
password: xxx
|
||||
```
|
||||
- 然后执行如下命令:
|
||||
```shell
|
||||
docker run -it -p 9000-9200:9000-9200/tcp -p 8888:8888 \
|
||||
-v /root/neutrino-proxy/config:/root/neutrino-proxy/config \
|
||||
-d --restart=always --name neutrino \
|
||||
registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:latest
|
||||
```
|
||||
|
||||
#### 1.2、使用jar包自行部署
|
||||
- 首先确保服务器上已安装java8运行环境
|
||||
- 打开[发行版页面](https://gitee.com/dromara/neutrino-proxy/releases),下载最新的release包:`neutrino-proxy-server.jar`、`neutrino-proxy-admin.zip`
|
||||
- 在服务器上新建部署目录:`/work/projects/neutrino-proxy-server`
|
||||
- 将` neutrino-proxy-server.jar`、`neutrino-proxy-admin.zip`上传至服务器部署目录。
|
||||
- 解压`neutrino-proxy-admin.zip`文件
|
||||
- 执行命令`java -jar neutrino-proxy-server.jar`启动服务端完成部署,默认使用sqlite数据库。
|
||||
- 若需要指定自己的mysql数据库,同样的需要在当前目录下新建`app.yml`文件,文件内容同上。执行命令`java -jar neutrino-proxy-server.jar config=app.yml`启动服务端完成部署
|
||||
- 可参照 https://gitee.com/dromara/neutrino-proxy/blob/master/bin/server_start.sh 使用shell脚本启动服务端。
|
||||
|
||||
### 2、管理后台配置
|
||||
- 服务端部署成功后,访问`http://{服务端IP}:8888`打开后台管理页面。
|
||||
- 使用默认的管理员帐号登录:admin/123456
|
||||
- 打开`代理配置>License管理`页面,可以看到系统已经自动为管理员初始化了一条License记录,复制该`LicenseKey`备用,后续客户端配置需要。
|
||||
- 打开`代理配置>端口映射`页面,可以看到系统已经自动为初始化了几条端口映射。可根据需要自行添加、修改。这里我们以`9101 -> 127.0.0.1:8080`映射为例
|
||||
|
||||
### 3、启动客户端
|
||||
- 首先确保本地已安装java8运行环境
|
||||
- 打开[发行版页面](https://gitee.com/dromara/neutrino-proxy/releases),下载最新的release包:`neutrino-proxy-client.jar`
|
||||
- 在本地`neutrino-proxy-client.jar`同级别目录下新建`app.yml`文件,并配置如下内容:
|
||||
```yml
|
||||
neutrino:
|
||||
proxy:
|
||||
client:
|
||||
# ssl证书密钥(使用jjar包内自带的证书,则此处无需修改)
|
||||
key-store-password: 123456
|
||||
# ssl证书管理密钥(使用jjar包内自带的证书,则此处无需修改。自定义证书,则此处配置对应的路径)
|
||||
jks-path: classpath:/test.jks
|
||||
# 代理服务端IP
|
||||
server-ip: localhost
|
||||
# 代理服务端IP, 若是非ssl端口,则ssl-enable需要配置为false
|
||||
server-port: 9002
|
||||
# 是否启用ssl
|
||||
ssl-enable: true
|
||||
# licenseKey,客户端凭证。此处需要配置刚刚从管理后台复制的LicenseKey
|
||||
license-key: xxxx
|
||||
```
|
||||
- 执行命令`java -jar neutrino-proxy-client.jar`启动客户端
|
||||
- 查看服务端License管理,刷新页面,对应的License在线状态为`在线`,则表明客户端已正常连接。
|
||||
|
||||
### 4、代理验证
|
||||
- 本地启动被代理服务,如:redis、本地web项目、本地mysql等等
|
||||
- 先确保本地能正常访问被代理服务,如果本地都不能访问,不用想代理更不可能!!!
|
||||
- 通过服务端IP+9101(上面License配置的端口映射重的服务端端口)访问本地被代理服务
|
||||
|
||||
## 联系我们
|
||||
笔者时间、能力有限,且开源项目非一朝一夕之事,存在众多问题亦在所难免。使用、学习过程中有任何问题欢迎大家与我联系。
|
||||
|
||||
对项目有什么想法或者建议,可以加我微信拉交流群,或者创建[issues](https://gitee.com/dromara/neutrino-proxy/issues),一起完善项目
|
||||
|
||||
- 微信号:yuyunshize
|
||||
- Email: aoshiguchen@dromara.org
|
||||
- 中微子代理官网:http://neutrino-proxy.dromara.org
|
||||
- 中微子代理仓库:https://gitee.com/dromara/neutrino-proxy
|
||||
- 微信二维码:
|
||||
<img src="MyWeChatQRCode.jpeg" width="100%"/>
|
||||
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 502 KiB |
@@ -0,0 +1,137 @@
|
||||
# 内网穿透神器NeutrinoProxy 1.9.0版本发布
|
||||
|
||||
## 更新内容
|
||||
> 与1.8.0版本对比,更新内容如下:
|
||||
- 核心功能
|
||||
- 域名映射支持HTTPS
|
||||
- 支持UDP协议代理
|
||||
- 客户端断开连接时,记录日志空指针异常问题修复
|
||||
- 基础优化
|
||||
- 客户端重连逻辑优化,支持配置文件指定重连间隔,是否开启无限重连
|
||||
- 增加对mariadb的支持
|
||||
- 服务端/客户端,支持配置文件、启动参数指定日志级别
|
||||
- 后台端口池管理支持批量删除
|
||||
- 后台下拉选择license、用户支持模糊搜索
|
||||
- 端口映射下拉选择端口支持搜索、分野
|
||||
- 客户端/服务端配置增加心跳日志开关,有需要时开启,方便排查问题
|
||||
- 端口映射HTTP(S)新增打开网页快捷操作,优先使用绑定域名打开
|
||||
|
||||
## 升级须知
|
||||
> 从1.9.0之前的版本升级需要注意:
|
||||
- 在项目`neutrino-proxy-server/src/main/resources/sql`目录下找到mysql/sqlite的增量sql文件`UPDATE-20230922.SQL`,执行更新自己的中微子代理数据库
|
||||
- 参照[官网服务端配置](https://dromara.gitee.io/neutrino-proxy/pages/f2d0f1)调整现有的服务端配置
|
||||
- 参照[官网客户端配置](https://dromara.gitee.io/neutrino-proxy/pages/50ce10)吊证现有的客户端配置
|
||||
- 从[仓库发行版](https://gitee.com/dromara/neutrino-proxy/releases)下载最新版本的客户端(neutrino-proxy-client.jar)、服务端(neutrino-proxy-server.jar、 neutrino-proxy-admin.zip)部署文件。若为服务端是docker镜像部署,则更新镜像重启即可
|
||||
|
||||
## 项目简介
|
||||
- [中微子代理(neutrino-proxy)](https://gitee.com/dromara/neutrino-proxy) 是一款基于netty的内网穿透神器。该项目采用最为宽松的MIT协议,因此您可以对它进行复制、修改、传播并用于任何个人或商业行为。
|
||||
- Gitee地址:https://gitee.com/dromara/neutrino-proxy
|
||||
- 官网地址:http://neutrino-proxy.dromara.org
|
||||
- 服务端管理后台截图:
|
||||
<img src="./home.png" width="100%"/>
|
||||
|
||||
## 主要特点:
|
||||
- 1、流量监控:首页图表、报表管理多维度流量监控。全方位掌握实时、历史代理数据。
|
||||
- 2、用户/License:支持多用户、多客户端使用。后台禁用实时生效。
|
||||
- 3、端口池:对外端口统一管理,支持用户、License独占端口。
|
||||
- 4、端口映射:新增、编辑、删除、禁用实时生效。
|
||||
- 5、Docker:服务端支持Docker一键部署。
|
||||
- 6、SSL证书:支持SSL,保护您的信息安全。
|
||||
- 7、域名映射:支持绑定子域名,方便本地调试三方回调
|
||||
- 8、多协议:支持代理TCP、HTTP(S)、UDP多种协议
|
||||
- 9、采用最为宽松的MIT协议,免去你的后顾之忧
|
||||
|
||||
## 快速使用
|
||||
> 更多使用姿势、细节请通过官网或结尾微信二维码加我备注"中微子代理"入群交流。
|
||||
|
||||
### 1、 部署服务端
|
||||
#### 1.1、 Docker一键部署
|
||||
> 当前最新版本为1.9.0,下面的脚本中,可以使用:`registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:1.9.0` 指定版本安装,推荐使用`latest`直接安装最新版。
|
||||
|
||||
##### 使用默认sqlite数据库
|
||||
```shell
|
||||
docker run -it -p 9000-9200:9000-9200/tcp -p 8888:8888 \
|
||||
-d --restart=always --name neutrino-proxy \
|
||||
registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:latest
|
||||
```
|
||||
|
||||
##### 指定自己的mysql数据库
|
||||
- 在服务器上创建目录:/root/neutrino-proxy/config
|
||||
- 在该目录下创建`app.yml`文本文件,并配置如下内容:
|
||||
```yml
|
||||
neutrino:
|
||||
data:
|
||||
db:
|
||||
type: mysql
|
||||
# 自己的数据库实例,创建一个空的名为'neutrino-proxy'的数据库即可,首次启动服务端会自动初始化
|
||||
url: jdbc:mysql://xxxx:3306/neutrino-proxy?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useAffectedRows=true&useSSL=false
|
||||
driver-class: com.mysql.jdbc.Driver
|
||||
# 数据库帐号
|
||||
username: xxx
|
||||
# 数据库密码
|
||||
password: xxx
|
||||
```
|
||||
- 然后执行如下命令:
|
||||
```shell
|
||||
docker run -it -p 9000-9200:9000-9200/tcp -p 8888:8888 \
|
||||
-v /root/neutrino-proxy/config:/root/neutrino-proxy/config \
|
||||
-d --restart=always --name neutrino \
|
||||
registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:latest
|
||||
```
|
||||
|
||||
#### 1.2、使用jar包自行部署
|
||||
- 首先确保服务器上已安装java8运行环境
|
||||
- 打开[发行版页面](https://gitee.com/dromara/neutrino-proxy/releases),下载最新的release包:`neutrino-proxy-server.jar`、`neutrino-proxy-admin.zip`
|
||||
- 在服务器上新建部署目录:`/work/projects/neutrino-proxy-server`
|
||||
- 将` neutrino-proxy-server.jar`、`neutrino-proxy-admin.zip`上传至服务器部署目录。
|
||||
- 解压`neutrino-proxy-admin.zip`文件
|
||||
- 执行命令`java -jar neutrino-proxy-server.jar`启动服务端完成部署,默认使用sqlite数据库。
|
||||
- 若需要指定自己的mysql数据库,同样的需要在当前目录下新建`app.yml`文件,文件内容同上。执行命令`java -jar neutrino-proxy-server.jar config=app.yml`启动服务端完成部署
|
||||
- 可参照 https://gitee.com/dromara/neutrino-proxy/blob/master/bin/server_start.sh 使用shell脚本启动服务端。
|
||||
|
||||
### 2、管理后台配置
|
||||
- 服务端部署成功后,访问`http://{服务端IP}:8888`打开后台管理页面。
|
||||
- 使用默认的管理员帐号登录:admin/123456
|
||||
- 打开`代理配置>License管理`页面,可以看到系统已经自动为管理员初始化了一条License记录,复制该`LicenseKey`备用,后续客户端配置需要。
|
||||
- 打开`代理配置>端口映射`页面,可以看到系统已经自动为初始化了几条端口映射。可根据需要自行添加、修改。这里我们以`9101 -> 127.0.0.1:8080`映射为例
|
||||
|
||||
### 3、启动客户端
|
||||
- 首先确保本地已安装java8运行环境
|
||||
- 打开[发行版页面](https://gitee.com/dromara/neutrino-proxy/releases),下载最新的release包:`neutrino-proxy-client.jar`
|
||||
- 在本地`neutrino-proxy-client.jar`同级别目录下新建`app.yml`文件,并配置如下内容:
|
||||
```yml
|
||||
neutrino:
|
||||
proxy:
|
||||
tunnel:
|
||||
# ssl证书密钥(使用jjar包内自带的证书,则此处无需修改)
|
||||
key-store-password: 123456
|
||||
# ssl证书管理密钥(使用jjar包内自带的证书,则此处无需修改。自定义证书,则此处配置对应的路径)
|
||||
jks-path: classpath:/test.jks
|
||||
# 代理服务端IP
|
||||
server-ip: localhost
|
||||
# 代理服务端IP, 若是非ssl端口,则ssl-enable需要配置为false
|
||||
server-port: 9002
|
||||
# 是否启用ssl
|
||||
ssl-enable: true
|
||||
# licenseKey,客户端凭证。此处需要配置刚刚从管理后台复制的LicenseKey
|
||||
license-key: xxxx
|
||||
```
|
||||
- 执行命令`java -jar neutrino-proxy-client.jar`启动客户端
|
||||
- 查看服务端License管理,刷新页面,对应的License在线状态为`在线`,则表明客户端已正常连接。
|
||||
|
||||
### 4、代理验证
|
||||
- 本地启动被代理服务,如:redis、本地web项目、本地mysql等等
|
||||
- 先确保本地能正常访问被代理服务,如果本地都不能访问,不用想代理更不可能!!!
|
||||
- 通过服务端IP+9101(上面License配置的端口映射重的服务端端口)访问本地被代理服务
|
||||
|
||||
## 联系我们
|
||||
笔者时间、能力有限,且开源项目非一朝一夕之事,存在众多问题亦在所难免。使用、学习过程中有任何问题欢迎大家与我联系。
|
||||
|
||||
对项目有什么想法或者建议,可以加我微信拉交流群,或者创建[issues](https://gitee.com/dromara/neutrino-proxy/issues),一起完善项目
|
||||
|
||||
- 微信号:yuyunshize
|
||||
- Email: aoshiguchen@dromara.org
|
||||
- 中微子代理官网:http://neutrino-proxy.dromara.org
|
||||
- 中微子代理仓库:https://gitee.com/dromara/neutrino-proxy
|
||||
- 微信二维码:
|
||||
<img src="MyWeChatQRCode.jpeg" width="100%"/>
|
||||
@@ -0,0 +1,37 @@
|
||||
# 🎋代码分支说明
|
||||
- dev:常规开发分支,日常的开发、Bug修复、提交PR都在此分支
|
||||
- feature/xxx:特征分支,一些试验性开发、提交PR都在此分支,xxx可自行取一个有意义的名称
|
||||
- release/xxx:发行版分支,作为阶段性成果、重大更新的版本固化分支。受保护,不允许任何提交。
|
||||
- master:主分支,定期同步最新代码,目前仅支持本人(傲世孤尘/雨韵诗泽)提交。
|
||||
|
||||
# ✊贡献的方式
|
||||
包括,但不限于以下形式:
|
||||
- 提交[issues](https://gitee.com/dromara/neutrino-proxy/issues)
|
||||
- 参与[issues](https://gitee.com/dromara/neutrino-proxy/issues)解决的必要性、解决方案的讨论、给出建设性的意见
|
||||
- 领取[issues](https://gitee.com/dromara/neutrino-proxy/issues) 并完成开发、提交PR
|
||||
- 为[本项目](https://gitee.com/dromara/neutrino-proxy)完善代码注释
|
||||
- 为[本项目](https://gitee.com/dromara/neutrino-proxy)增加测试代码
|
||||
- 为[本项目](https://gitee.com/dromara/neutrino-proxy)优化现有代码
|
||||
- 为[本项目](https://gitee.com/dromara/neutrino-proxy)优化操作体验
|
||||
- 通过公众号、博客、贴吧、论坛等形式分享/宣传中微子代理
|
||||
- 丰富项目文档,包括使用过程中踩过的坑、需要优化的点、具体问题的解决方法等
|
||||
|
||||
# 🧬贡献代码的步骤
|
||||
- 在Gitee上fork项目到自己的仓库
|
||||
- 将fork后的自己仓库的项目clone到本地
|
||||
- 拉取dev分支最新代码,并修改
|
||||
- commit并push到自己的仓库
|
||||
- 登录Gitee在的仓库上可以看到一个 pull request 按钮,点击它,填写一些说明信息,然后提交即可。
|
||||
- 等待合并
|
||||
|
||||
# 📐PR的规范
|
||||
> 你可能发现有的代码并不符合这个规范,但我们后续都会朝着这个方向迈进。后续我们会逐步全面规范化,请先保证新提交的代码满足以下要求:
|
||||
|
||||
- 注释完备,尤其每个新增的方法应按照Java文档规范标明方法说明、参数说明、返回值说明等信息,必要时请添加单元测试,如果愿意,也可以加上你的大名。
|
||||
- 原则上,不允许自行添加第三方依赖库。若确有必要,请先和项目负责人沟通达成一致。
|
||||
- 对现有代码的优化,请在注释中说明原因。
|
||||
- 如果涉及到数据库的变更,则至少需要做到以下绩点:
|
||||
- sqlite、mysql下均测试通过
|
||||
- 在`./neutrino-proxy-server/src/main/resource/sql`下维护更新对应的表结构sql文件
|
||||
- 对于新增的表,维护必要的初始化数据
|
||||
- 在对应的mysql、sqlite的update目录下,维护SQL变更记录,确保SQL执行测试通过
|
||||
@@ -0,0 +1,18 @@
|
||||
# native 打包
|
||||
```shell
|
||||
mvn clean install -pl neutrino-proxy-core -am -DskipTests
|
||||
|
||||
cd neutrino-proxy-server
|
||||
|
||||
mvn clean native:compile -P native -DskipTests
|
||||
|
||||
```
|
||||
|
||||
# 管理后台问题汇总
|
||||
## 问题1...
|
||||
> 描述、解决方式
|
||||
|
||||
# 代理服务端问题汇总
|
||||
|
||||
|
||||
# 代理客户端问题汇总
|
||||
@@ -0,0 +1,79 @@
|
||||
# 1、部署服务端
|
||||
## 1.1、Docker一键部署
|
||||
### 使用默认sqlite数据库
|
||||
```shell
|
||||
docker run -it -p 9000-9200:9000-9200/tcp -p 8888:8888 \
|
||||
-d --restart=always --name neutrino-proxy \
|
||||
registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:1.7.1
|
||||
```
|
||||
|
||||
### 指定自己的mysql数据库
|
||||
- 在服务器上创建目录:/root/neutrino-proxy/config
|
||||
- 在该目录下创建`app.yml`文本文件,并配置如下内容:
|
||||
```yml
|
||||
neutrino:
|
||||
data:
|
||||
db:
|
||||
type: mysql
|
||||
# 自己的数据库实例,创建一个空的名为'neutrino-proxy'的数据库即可,首次启动服务端会自动初始化
|
||||
url: jdbc:mysql://xxxx:3306/neutrino-proxy?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useAffectedRows=true&useSSL=false
|
||||
driver-class: com.mysql.jdbc.Driver
|
||||
# 数据库帐号
|
||||
username: xxx
|
||||
# 数据库密码
|
||||
password: wHCvf3@hmw^D*
|
||||
```
|
||||
- 然后执行如下命令:
|
||||
```shell
|
||||
docker run -it -p 9000-9200:9000-9200/tcp -p 8888:8888 \
|
||||
-v /root/neutrino-proxy/config:/root/neutrino-proxy/config \
|
||||
-d --restart=always --name neutrino \
|
||||
registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:1.7.1
|
||||
```
|
||||
|
||||
## 1.2、使用jar包自行部署
|
||||
- 首先确保服务器上已安装java8运行环境
|
||||
- 打开[发行版页面](https://gitee.com/dromara/neutrino-proxy/releases),下载最新的release包:`neutrino-proxy-server.jar`、`neutrino-proxy-admin.zip`
|
||||
- 在服务器上新建部署目录:`/work/projects/neutrino-proxy-server`
|
||||
- 将` neutrino-proxy-server.jar`、`neutrino-proxy-admin.zip`上传至服务器部署目录。
|
||||
- 解压`neutrino-proxy-admin.zip`文件
|
||||
- 执行命令`java -jar neutrino-proxy-server.jar`启动服务端完成部署,默认使用sqlite数据库。
|
||||
- 若需要指定自己的mysql数据库,同样的需要在当前目录下新建`app.yml`文件,文件内容同上。执行命令`java -jar neutrino-proxy-server.jar config=app.yml`启动服务端完成部署
|
||||
- 可参照 https://gitee.com/dromara/neutrino-proxy/blob/master/bin/server_start.sh 使用shell脚本启动服务端。
|
||||
|
||||
# 2、管理后台配置
|
||||
- 服务端部署成功后,访问`http://{服务端IP}:8888`打开后台管理页面。
|
||||
- 使用默认的管理员帐号登录:admin/123456
|
||||
- 打开`代理配置>License管理`页面,可以看到系统已经自动为管理员初始化了一条License记录,复制该`LicenseKey`备用,后续客户端配置需要。
|
||||
- 打开`代理配置>端口映射`页面,可以看到系统已经自动为初始化了几条端口映射。可根据需要自行添加、修改。这里我们以`9101 -> 127.0.0.1:8080`映射为例
|
||||
|
||||
# 3、启动客户端
|
||||
- 首先确保本地已安装java8运行环境
|
||||
- 打开[发行版页面](https://gitee.com/dromara/neutrino-proxy/releases),下载最新的release包:`neutrino-proxy-client.jar`
|
||||
- 在本地`neutrino-proxy-client.jar`同级别目录下新建`app.yml`文件,并配置如下内容:
|
||||
```yml
|
||||
neutrino:
|
||||
proxy:
|
||||
client:
|
||||
# ssl证书密钥(使用jjar包内自带的证书,则此处无需修改)
|
||||
key-store-password: 123456
|
||||
# ssl证书管理密钥(使用jjar包内自带的证书,则此处无需修改。自定义证书,则此处配置对应的路径)
|
||||
jks-path: classpath:/test.jks
|
||||
# 代理服务端IP
|
||||
server-ip: localhost
|
||||
# 代理服务端IP, 若是非ssl端口,则ssl-enable需要配置为false
|
||||
server-port: 9002
|
||||
# 是否启用ssl
|
||||
ssl-enable: true
|
||||
# licenseKey,客户端凭证。此处需要配置刚刚从管理后台复制的LicenseKey
|
||||
license-key: xxxx
|
||||
```
|
||||
- 执行命令`java -jar neutrino-proxy-client.jar`启动客户端
|
||||
- 查看服务端License管理,刷新页面,对应的License在线状态为`在线`,则表明客户端已正常连接。
|
||||
|
||||
# 4、代理验证
|
||||
- 本地启动被代理服务,如:redis、本地web项目、本地mysql等等
|
||||
- 先确保本地能正常访问被代理服务,如果本地都不能访问,不用想代理更不可能!!!
|
||||
- 通过服务端IP+9101(上面License配置的端口映射重的服务端端口)访问本地被代理服务
|
||||
|
||||
访问成功,至此首次完整的内网穿透体验完成。开源不易,请速至[Gitee仓库](https://gitee.com/dromara/neutrino-proxy)一键三连🤝
|
||||
@@ -0,0 +1,38 @@
|
||||
# 本地启动SpringBoot,提供hello接口以供测试:
|
||||
|
||||
## 1、10线程 100连接直接测试
|
||||
> wrk -t 10 -c 100 -d 1s http://localhost:8080/hello
|
||||
测试结果:
|
||||
```
|
||||
10 threads and 100 connections
|
||||
Thread Stats Avg Stdev Max +/- Stdev
|
||||
Latency 10.51ms 5.82ms 53.28ms 83.61%
|
||||
Req/Sec 0.99k 123.01 1.21k 81.82%
|
||||
10797 requests in 1.10s, 1.35MB read
|
||||
Requests/sec: 9799.85
|
||||
Transfer/sec: 1.23MB
|
||||
```
|
||||
## 2、10线程 100连接走端口转发测试
|
||||
> wrk -t 10 -c 100 -d 1s http://localhost:9105/hello
|
||||
测试结果:
|
||||
```
|
||||
10 threads and 100 connections
|
||||
Thread Stats Avg Stdev Max +/- Stdev
|
||||
Latency 126.37ms 163.69ms 971.32ms 81.43%
|
||||
Req/Sec 24.04 21.06 80.00 84.62%
|
||||
70 requests in 1.02s, 8.96KB read
|
||||
Requests/sec: 68.46
|
||||
Transfer/sec: 8.76K
|
||||
```
|
||||
## 3、10线程 100连接走域名转发测试
|
||||
> wrk -t 10 -c 100 -d 1s http://test1.neutrino-proxy.asgc.fun/hello
|
||||
测试结果:
|
||||
```
|
||||
10 threads and 100 connections
|
||||
Thread Stats Avg Stdev Max +/- Stdev
|
||||
Latency 327.86ms 218.93ms 903.28ms 56.76%
|
||||
Req/Sec 30.65 20.46 88.00 62.67%
|
||||
259 requests in 1.02s, 44.77KB read
|
||||
Requests/sec: 253.75
|
||||
Transfer/sec: 43.86KB
|
||||
```
|
||||
@@ -0,0 +1,30 @@
|
||||
# 首页
|
||||
- License统计:统计License相关数量指标
|
||||
- 端口映射统计:统计端口映射相关数量指标
|
||||
- 今日流量:统计当天的服务端上行/下行流量数据
|
||||
- 流量汇总:统计所有(包含当天)的服务端上行/下行流量数据
|
||||
- 流量监控:按天统计最近15天(可能会动态调整)每天的上行流量、下行流量、汇总流量
|
||||
|
||||
# 代理配置
|
||||
- License管理:License是客户端连接服务端的唯一合法凭证。一个License同时只能被一个客户端使用,一个License可以维护多条端口映射
|
||||
- 端口映射:服务端IP+端口 -> 客户端IP+端口的四元组映射(因目前服务端单节点只有一个公网IP,所以不体现出来),是内网穿透的基本单元。
|
||||
|
||||
# 系统管理
|
||||
- 用户管理:支持多用户,一个用户可持有多个License。由于项目目前主推个人版,所以暂是没有权限这一套,管理员之外的所有用户都属于游客。对于绝大多数操作,游客仅有只读权限。
|
||||
- 端口池管理:用于统一管理服务器内网穿透端口,方便统一设置安全组。
|
||||
- 端口池分组:对端口池的一个分组。
|
||||
- 全局分组:该分组下的端口全局通用。
|
||||
- 用户分组:该分组下的端口由分组绑定的用户独占。
|
||||
- License分组:该分组下的端口由分组绑定的License独占。
|
||||
- 调度管理:维护服务端定时任务。方便开发、调试。正常使用时无需关心。
|
||||
|
||||
# 报表管理
|
||||
- 用户流量报表:基于用户维度的流量统计
|
||||
- License流量报表:基于License的流量统计
|
||||
- 用户流量月度明细:基于用户的流量月度统计
|
||||
- License流量月度明细:基于License的流量月度统计
|
||||
|
||||
# 日志管理
|
||||
- 调度日志:服务端定时任务执行日志
|
||||
- 登录日志:管理后端登录、退出登录的日志
|
||||
- 客户端连接日志:客户端连接、断开的日志
|
||||
@@ -40,7 +40,8 @@ module.exports = {
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
...(config.dev.useEslint ? [createLintingRule()] : []),
|
||||
// 关闭EsLint校验
|
||||
// ...(config.dev.useEslint ? [createLintingRule()] : []),
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader',
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
module.exports = {
|
||||
NODE_ENV: '"development"',
|
||||
ENV_CONFIG: '"dev"',
|
||||
BASE_API: '"http://103.163.47.16:8888"'
|
||||
BASE_API: '"https://neutrino-proxy.asgc.fun/neutrino-proxy-server"',
|
||||
USER_NAME: '"visitor"',
|
||||
USER_PWD: '"123456"'
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
NODE_ENV: '"development"',
|
||||
ENV_CONFIG: '"docker"',
|
||||
BASE_API: '"/"'
|
||||
BASE_API: '"/neutrino-proxy-server"'
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 3.1 KiB |
@@ -0,0 +1,9 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function domainNameBindInfo(query) {
|
||||
return request({
|
||||
url: '/domain-name/bind-info',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
@@ -14,6 +14,12 @@ export function licenseList() {
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
export function licenseAuthList() {
|
||||
return request({
|
||||
url: '/license/auth-list',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function createLicense(data) {
|
||||
return request({
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function fetchList(query) {
|
||||
return request({
|
||||
url: '/port-group/page',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function portGroupList() {
|
||||
return request({
|
||||
url: '/port-group/list',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function updateEnableStatus(id, enable) {
|
||||
return request({
|
||||
url: '/port-group/update/enable-status',
|
||||
method: 'post',
|
||||
data: {
|
||||
id: id,
|
||||
enable: enable
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function createPortGroup(data) {
|
||||
return request({
|
||||
url: '/port-group/create',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteGroup(id) {
|
||||
return request({
|
||||
url: '/port-group/delete',
|
||||
method: 'post',
|
||||
params: {
|
||||
id: id
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -14,6 +14,13 @@ export function portPoolList() {
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
export function availablePortList(query) {
|
||||
return request({
|
||||
url: '/port-pool/get-available-port-list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function updateEnableStatus(id, enable) {
|
||||
return request({
|
||||
@@ -34,6 +41,14 @@ export function createPortPool(data) {
|
||||
})
|
||||
}
|
||||
|
||||
export function updatePortPool(data) {
|
||||
return request({
|
||||
url: '/port-pool/update',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function deletePortPool(id) {
|
||||
return request({
|
||||
url: '/port-pool/delete',
|
||||
@@ -43,3 +58,21 @@ export function deletePortPool(id) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteBatchPortPool(ids) {
|
||||
return request({
|
||||
url: '/port-pool/delete-batch',
|
||||
method: 'post',
|
||||
data: {
|
||||
ids: ids
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function portAvailable(query) {
|
||||
return request({
|
||||
url: '/port-pool/port-available',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function fetchList(query) {
|
||||
return request({
|
||||
url: '/protocal/page',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function protocalList(query) {
|
||||
return request({
|
||||
url: '/protocal/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import request from '@/utils/request'
|
||||
export function fetchUserFlowReportList(query) {
|
||||
return request({
|
||||
url: '/report/user/flow-report/page',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchLicenseFlowReportList(query) {
|
||||
return request({
|
||||
url: '/report/license/flow-report/page',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchUserFlowMonthReportList(query) {
|
||||
return request({
|
||||
url: '/report/user/flow-month-report/page',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchLicenseFlowMonthReportList(query) {
|
||||
return request({
|
||||
url: '/report/license/flow-month-report/page',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function homeData(query) {
|
||||
return request({
|
||||
url: '/report/home/data-view',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
@@ -8,60 +8,68 @@
|
||||
<el-button size="mini" @click="handleCancelClick">{{cancelText}}</el-button>
|
||||
<el-button type="primary" size="mini" @click="handleCommitClick">{{okText}}</el-button>
|
||||
</div>
|
||||
<el-button slot="reference" :type="type" :size="size">{{buttonText}}</el-button>
|
||||
<el-button slot="reference" :type="type" :size="size" :icon="icon" :disabled="disabled">{{buttonText}}</el-button>
|
||||
</el-popover>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'deleteButton',
|
||||
props: {
|
||||
width: {
|
||||
type: Number,
|
||||
default: 160
|
||||
export default {
|
||||
name: 'deleteButton',
|
||||
props: {
|
||||
width: {
|
||||
type: Number,
|
||||
default: 160
|
||||
},
|
||||
buttonText: {
|
||||
type: String,
|
||||
default: '删除'
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'danger'
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'mini'
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '确定删除吗?'
|
||||
},
|
||||
okText: {
|
||||
type: String,
|
||||
default: '确定'
|
||||
},
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取消'
|
||||
}
|
||||
},
|
||||
buttonText: {
|
||||
type: String,
|
||||
default: '删除'
|
||||
data() {
|
||||
return {
|
||||
visible: false
|
||||
}
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'danger'
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'mini'
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '确定删除吗?'
|
||||
},
|
||||
okText: {
|
||||
type: String,
|
||||
default: '确定'
|
||||
},
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取消'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleCancelClick() {
|
||||
this.visible = false
|
||||
this.$emit('handleCancelClick')
|
||||
},
|
||||
handleCommitClick() {
|
||||
this.visible = false
|
||||
this.$emit('handleCommitClick')
|
||||
methods: {
|
||||
handleCancelClick() {
|
||||
this.visible = false
|
||||
this.$emit('handleCancelClick')
|
||||
},
|
||||
handleCommitClick() {
|
||||
this.visible = false
|
||||
this.$emit('handleCommitClick')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
<!--
|
||||
采用github上面的开源组件https://github.com/johnhom1024/vue-load-select
|
||||
不过有个bug,就是没办法展示下拉的箭头,目前修复的方式:等dom加载完毕后,补充箭头的class
|
||||
参考:https://blog.csdn.net/weixin_42381896/article/details/122258563
|
||||
-->
|
||||
<template>
|
||||
<el-select
|
||||
ref="selectLoadMore"
|
||||
:value="value"
|
||||
v-loadmore="loadMore"
|
||||
@focus="focus"
|
||||
@clear="clear"
|
||||
filterable
|
||||
remote
|
||||
:filter-method="handleSearch"
|
||||
:loading="loading"
|
||||
clearable
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<el-option
|
||||
v-for="option in data"
|
||||
:label="option[dictLabel]"
|
||||
:value="option[dictValue]"
|
||||
:key="option.value"
|
||||
></el-option>
|
||||
<!-- 此处加载中的value可以随便设置,只要不与其他数据重复即可 -->
|
||||
<el-option
|
||||
v-if="hasMore"
|
||||
disabled
|
||||
label="加载中..."
|
||||
value="-1" />
|
||||
<!--补充页面无数据时,显示无数据的效果-->
|
||||
<el-option
|
||||
v-if="!hasMore && data.length==0"
|
||||
disabled
|
||||
label="暂无数据"
|
||||
value="-2" />
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "SelectLoadMore",
|
||||
props: {
|
||||
value: {
|
||||
default: null
|
||||
},
|
||||
// 列表数据
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
dictLabel: {
|
||||
type: String,
|
||||
default: "label"
|
||||
},
|
||||
dictValue: {
|
||||
type: String,
|
||||
default: "value"
|
||||
},
|
||||
// 调用页数的接口
|
||||
request: {
|
||||
type: Function,
|
||||
default: () => {
|
||||
}
|
||||
},
|
||||
// 传入的页码
|
||||
page: {
|
||||
type: [Number, String],
|
||||
default: 1
|
||||
},
|
||||
// 是否还有更多数据
|
||||
hasMore: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
directives: {
|
||||
// 这里实现一个组件内部的自定义指令
|
||||
loadmore: {
|
||||
// 指令的定义
|
||||
bind(el, binding) {
|
||||
const SELECTWRAP = el.querySelector(
|
||||
".el-select-dropdown .el-select-dropdown__wrap"
|
||||
);
|
||||
if (!SELECTWRAP) {
|
||||
throw new Error('获取不到"el-select-dropdown__wrap"节点');
|
||||
}
|
||||
SELECTWRAP.addEventListener("scroll", () => {
|
||||
// scrollTop 这里可能因为浏览器缩放存在小数点的情况,导致了滚动到底部时
|
||||
// scrollHeight 减去滚动到底部时的scrollTop ,依然大于clientHeight 导致无法请求更多数据
|
||||
// 这里将scrollTop向上取整 保证滚到底部时,触发调用
|
||||
const CONDITION =
|
||||
SELECTWRAP.scrollHeight -
|
||||
Math.ceil(SELECTWRAP.scrollTop) <=
|
||||
SELECTWRAP.clientHeight;
|
||||
// el.scrollTop !== 0 当输入时,如果搜索结果很少,以至于没看到滚动条,那么此时的CONDITION计算结果是true,会执行bind.value(),此时不应该执行,否则搜索结果不匹配
|
||||
if (CONDITION && SELECTWRAP.scrollTop !== 0) {
|
||||
binding.value();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
keyword: "", // 存储关键字用
|
||||
loading: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 请求下一页的数据
|
||||
loadMore() {
|
||||
// 如果没有更多数据,则不请求
|
||||
if (!this.hasMore) {
|
||||
return;
|
||||
}
|
||||
// 如果intercept属性为true则不请求数据,
|
||||
if (this.loadMore.intercept) {
|
||||
return;
|
||||
}
|
||||
this.loadMore.intercept = true;
|
||||
this.request({
|
||||
page: this.page + 1,
|
||||
more: true,
|
||||
keyword: this.keyword
|
||||
}).then(() => {
|
||||
this.loadMore.intercept = false;
|
||||
});
|
||||
},
|
||||
// 选中下拉框没有数据时,自动请求第一页的数据
|
||||
focus() {
|
||||
if (!this.data.length) {
|
||||
this.request({page: 1});
|
||||
}
|
||||
},
|
||||
handleSearch(keyword) {
|
||||
this.keyword = keyword;
|
||||
this.loading = true;
|
||||
console.log(keyword);
|
||||
this.request({page: 1, keyword: keyword}).then(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
// 删除选中时,如果请求了关键字,则清除关键字再请求第一页的数据
|
||||
clear() {
|
||||
if (this.keyword) {
|
||||
this.keyword = "";
|
||||
this.request({page: 1});
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(function () {
|
||||
// github代码有个bug,下拉的箭头无法加载的问题,需要等dom页面加载完毕后,添加箭头的class
|
||||
let rulesDom = this.$refs["selectLoadMore"].$el.querySelector(
|
||||
".el-input .el-input__suffix .el-input__suffix-inner .el-input__icon"
|
||||
);// 找到dom
|
||||
rulesDom.classList.add("el-icon-arrow-up");// 对dom新增class
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -97,12 +97,15 @@ export default {
|
||||
readings: 'Readings',
|
||||
status: 'Status',
|
||||
actions: 'Actions',
|
||||
access: 'Access',
|
||||
edit: 'Edit',
|
||||
publish: 'Publish',
|
||||
draft: 'Draft',
|
||||
delete: 'Delete',
|
||||
deleteBatch: 'DeleteBatch',
|
||||
cancel: 'Cancel',
|
||||
confirm: 'Confirm'
|
||||
confirm: 'Confirm',
|
||||
portOrRange: 'example: 3306 or 1000-2000'
|
||||
},
|
||||
errorLog: {
|
||||
tips: 'Please click the bug icon in the upper right corner',
|
||||
|
||||
@@ -48,6 +48,8 @@ export default {
|
||||
user: '用户管理',
|
||||
system: '系统管理',
|
||||
portPool: '端口池管理',
|
||||
portGroup: '端口分组管理',
|
||||
protocal: '协议管理',
|
||||
proxy: '代理配置',
|
||||
license: 'License管理',
|
||||
portMapping: '端口映射',
|
||||
@@ -55,7 +57,12 @@ export default {
|
||||
jobLog: '调度日志',
|
||||
log: '日志管理',
|
||||
loginLog: '登录日志',
|
||||
clientConnectLog: '客户端连接日志'
|
||||
clientConnectLog: '客户端连接日志',
|
||||
report: '报表管理',
|
||||
userFlowReport: '用户流量报表',
|
||||
licenseFlowReport: 'License流量报表',
|
||||
userFlowMonthReport: '用户流量月度明细',
|
||||
licenseFlowMonthReport: 'License流量月度明细'
|
||||
},
|
||||
navbar: {
|
||||
logOut: '退出登录',
|
||||
@@ -110,14 +117,22 @@ export default {
|
||||
readings: '阅读数',
|
||||
status: '状态',
|
||||
actions: '操作',
|
||||
access: '访问',
|
||||
openWebPage: '打开网页',
|
||||
edit: '编辑',
|
||||
publish: '发布',
|
||||
draft: '草稿',
|
||||
delete: '删除',
|
||||
deleteBatch: '批量删除',
|
||||
cancel: '取 消',
|
||||
confirm: '确 定',
|
||||
userId: '用户ID',
|
||||
userName: '用户名',
|
||||
group: '分组',
|
||||
groupName: '分组名称',
|
||||
groupPossessor: '分组所属',
|
||||
possessorType: '所属类',
|
||||
possessorId: '分组所属',
|
||||
license: 'License',
|
||||
createTime: '创建时间',
|
||||
updateTime: '更新时间',
|
||||
@@ -125,6 +140,7 @@ export default {
|
||||
enable: '启用',
|
||||
loginName: '登录名',
|
||||
port: '端口',
|
||||
portOrRange: '端口或范围,如: 3306或1000-2000',
|
||||
licenseName: 'License名称',
|
||||
licenseKey: 'License Key',
|
||||
isOnline: '在线状态',
|
||||
@@ -147,7 +163,13 @@ export default {
|
||||
msg: '消息',
|
||||
outcome: '结果',
|
||||
err: '异常信息',
|
||||
resetKey: '重置Key'
|
||||
resetKey: '重置Key',
|
||||
upFlow: '上行流量',
|
||||
downFlow: '下行流量',
|
||||
totalFlow: '总流量',
|
||||
protocalName: '协议',
|
||||
supportStatus: '支持状态',
|
||||
domainName: '域名'
|
||||
},
|
||||
button: {
|
||||
lookOver: '查看'
|
||||
|
||||
@@ -16,6 +16,7 @@ import './icons' // icon
|
||||
import './errorLog'// error log
|
||||
import './permission' // permission control
|
||||
import './mock' // simulation data
|
||||
import './plugins/baiduhm' // 百度统计
|
||||
|
||||
import * as filters from './filters' // global filters
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
const _hmt = _hmt || [];
|
||||
(function() {
|
||||
const hm = document.createElement("script");
|
||||
hm.src = "https://hm.baidu.com/hm.js?173e771eef816c412396d2cb4fe2d632";
|
||||
const s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
@@ -75,10 +75,28 @@ export const asyncRouterMap = [
|
||||
},
|
||||
children: [
|
||||
{ path: 'user', component: _import('system/user'), name: 'user', meta: { title: 'user' }},
|
||||
{ path: 'portGroup', component: _import('system/portGroup'), name: 'portGroup', meta: { title: 'portGroup' }},
|
||||
{ path: 'portPool', component: _import('system/portPool'), name: 'portPool', meta: { title: 'portPool' }},
|
||||
{ path: 'protocal', component: _import('system/protocal'), name: 'protocal', meta: { title: 'protocal' }},
|
||||
{ path: 'jobManager', component: _import('system/jobManager'), name: 'jobManager', meta: { title: 'jobManager' }}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/report',
|
||||
component: Layout,
|
||||
redirect: 'noredirect',
|
||||
name: 'report',
|
||||
meta: {
|
||||
title: 'report',
|
||||
icon: 'component'
|
||||
},
|
||||
children: [
|
||||
{ path: 'userFlowReport', component: _import('report/userFlowReport'), name: 'userFlowReport', meta: { title: 'userFlowReport' }},
|
||||
{ path: 'licenseFlowReport', component: _import('report/licenseFlowReport'), name: 'licenseFlowReport', meta: { title: 'licenseFlowReport' }},
|
||||
{ path: 'userFlowMonthReport', component: _import('report/userFlowMonthReport'), name: 'userFlowMonthReport', meta: { title: 'userFlowMonthReport' }},
|
||||
{ path: 'licenseFlowMonthReport', component: _import('report/licenseFlowMonthReport'), name: 'licenseFlowMonthReport', meta: { title: 'licenseFlowMonthReport' }}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/log',
|
||||
component: Layout,
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
const SIZE_UNINTS = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||
const SIZE_SYSTEM = 1024
|
||||
/**
|
||||
* 根据字节数获取大小描述
|
||||
* 1、小于1024字节的以B为单位
|
||||
* 2、小于1024KB的以KB为单位
|
||||
* 3、小于1024M的以MB为单位
|
||||
* 4、小于1024G的以GB为单位
|
||||
* 5、其他以TB为单位
|
||||
*/
|
||||
export function getSizeDescByByteCount(byteCount) {
|
||||
if (byteCount <= 0) {
|
||||
return '0B'
|
||||
}
|
||||
|
||||
let res = byteCount
|
||||
let index = 0
|
||||
while (index < SIZE_UNINTS.length && res >= SIZE_SYSTEM) {
|
||||
res /= SIZE_SYSTEM
|
||||
index++
|
||||
}
|
||||
|
||||
if (index >= SIZE_UNINTS.length) {
|
||||
index = SIZE_UNINTS.length - 1
|
||||
res *= 1024
|
||||
}
|
||||
|
||||
return parseFloat(res.toFixed(2)) + SIZE_UNINTS[index]
|
||||
}
|
||||
@@ -22,14 +22,14 @@ export default {
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '250px'
|
||||
default: '220px'
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
upload: 90, // 上行
|
||||
download: 100, // 下行
|
||||
upFlowBytes: 0, // 上行
|
||||
downFlowBytes: 0, // 下行
|
||||
text: '今日流量'
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@ export default {
|
||||
const option = {
|
||||
title: {
|
||||
text: this.data.text,
|
||||
subtext: '上行:' + this.data.upload + '\n\n' + '下行:' + this.data.download,
|
||||
subtext: '上行:' + this.data.upFlowDesc + '\n\n' + '下行:' + this.data.downFlowDesc + '\n\n' + '汇总:' + this.data.totalFlowDesc,
|
||||
textStyle: {
|
||||
fontSize: 18,
|
||||
fontWeight: 800,
|
||||
@@ -65,7 +65,10 @@ export default {
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
trigger: 'item',
|
||||
formatter: (value) => {
|
||||
return `${value.seriesName} <br/>${value.marker} : ${value.name} ${value.name === '上行' ? this.data.upFlowDesc : this.data.downFlowDesc}`
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: ['上行', '下行'],
|
||||
@@ -77,7 +80,7 @@ export default {
|
||||
name: this.data.text,
|
||||
type: 'pie',
|
||||
radius: [45, 65],
|
||||
center: ['50%', '58%'],
|
||||
center: ['60%', '60%'],
|
||||
// 隐藏指示线
|
||||
labelLine: {
|
||||
normal: {
|
||||
@@ -93,30 +96,30 @@ export default {
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: this.data.upload,
|
||||
value: this.data.upFlowBytes,
|
||||
name: '上行',
|
||||
lineStyle: {
|
||||
normal: {
|
||||
color: '#2B81B1'
|
||||
color: '#63b2ee'
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: '#2B81B1'
|
||||
color: '#63b2ee'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
value: this.data.download,
|
||||
value: this.data.downFlowBytes,
|
||||
name: '下行',
|
||||
lineStyle: {
|
||||
normal: {
|
||||
color: '#dbebf7'
|
||||
color: '#76da91'
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: '#dbebf7'
|
||||
color: '#76da91'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,14 +20,14 @@ export default {
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '250px'
|
||||
default: '220px'
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
onLine: 90, // 进度条最大值
|
||||
total: 100 // 当前进度
|
||||
onlineCount: 0, // 进度条最大值
|
||||
totalCount: 0 // 当前进度
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,7 @@ export default {
|
||||
const option = {
|
||||
title: {
|
||||
text: 'License统计',
|
||||
subtext: '在线数:' + this.data.onLine + '\n\n' + '离线数:' + (this.data.total - this.data.onLine),
|
||||
subtext: '在线数量:' + this.data.onlineCount + '\n\n' + '离线数量:' + (this.data.totalCount - this.data.onlineCount) + '\n\n' + '汇总数量:' + this.data.totalCount,
|
||||
textStyle: {
|
||||
fontSize: 18,
|
||||
fontWeight: 800,
|
||||
@@ -75,7 +75,7 @@ export default {
|
||||
name: 'License',
|
||||
type: 'pie',
|
||||
radius: [45, 65],
|
||||
center: ['50%', '58%'],
|
||||
center: ['60%', '60%'],
|
||||
avoidLabelOverlap: false,
|
||||
// 隐藏指示线
|
||||
labelLine: {
|
||||
@@ -103,29 +103,29 @@ export default {
|
||||
// value当前进度 + 颜色
|
||||
{
|
||||
name: '在线数',
|
||||
value: this.data.onLine,
|
||||
value: this.data.onlineCount,
|
||||
lineStyle: {
|
||||
normal: {
|
||||
color: '#2B81B1'
|
||||
color: '#63b2ee'
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: '#2B81B1'
|
||||
color: '#63b2ee'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '离线数',
|
||||
value: this.data.total - this.data.onLine,
|
||||
value: this.data.totalCount - this.data.onlineCount,
|
||||
lineStyle: {
|
||||
normal: {
|
||||
color: '#dbebf7'
|
||||
color: '#76da91'
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: '#dbebf7'
|
||||
color: '#76da91'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,14 +18,14 @@ export default {
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '250px'
|
||||
default: '220px'
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
onLine: 90, // 进度条最大值
|
||||
total: 100 // 当前进度
|
||||
onlineCount: 0, // 进度条最大值
|
||||
totalCount: 0 // 当前进度
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ export default {
|
||||
const option = {
|
||||
title: {
|
||||
text: '端口映射统计',
|
||||
subtext: '在线数:' + this.data.onLine + '\n\n' + '离线数:' + (this.data.total - this.data.onLine),
|
||||
subtext: '在线数量:' + this.data.onlineCount + '\n\n' + '离线数量:' + (this.data.totalCount - this.data.onlineCount) + '\n\n' + '汇总数量:' + this.data.totalCount,
|
||||
textStyle: {
|
||||
fontSize: 18,
|
||||
fontWeight: 800,
|
||||
@@ -73,7 +73,7 @@ export default {
|
||||
name: '端口映射',
|
||||
type: 'pie',
|
||||
radius: [45, 65],
|
||||
center: ['50%', '58%'],
|
||||
center: ['60%', '60%'],
|
||||
avoidLabelOverlap: false,
|
||||
// 隐藏指示线
|
||||
labelLine: {
|
||||
@@ -92,29 +92,29 @@ export default {
|
||||
// value当前进度 + 颜色
|
||||
{
|
||||
name: '在线数',
|
||||
value: this.data.onLine,
|
||||
value: this.data.onlineCount,
|
||||
lineStyle: {
|
||||
normal: {
|
||||
color: '#2B81B1'
|
||||
color: '#63b2ee'
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: '#2B81B1'
|
||||
color: '#63b2ee'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '离线数',
|
||||
value: this.data.total - this.data.onLine,
|
||||
value: this.data.totalCount - this.data.onlineCount,
|
||||
lineStyle: {
|
||||
normal: {
|
||||
color: '#dbebf7'
|
||||
color: '#76da91'
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: '#dbebf7'
|
||||
color: '#76da91'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<script>
|
||||
import echarts from 'echarts'
|
||||
require('echarts/theme/macarons') // echarts theme
|
||||
|
||||
import { getSizeDescByByteCount } from '@/utils/utils'
|
||||
export default {
|
||||
props: {
|
||||
chartId: {
|
||||
@@ -26,13 +26,13 @@ export default {
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '450px'
|
||||
default: '500px'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartDom: null,
|
||||
colorList: ['#2B81B1', '#75ddfa', '#53a7e3', '#029fe8', '#015dae']
|
||||
colorList: ['#63b2ee', '#76da91', '#f8cb7f', '#f89588', '#7cd6cf', '#9192ab', '#7898e1', '#7898e1']
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -50,12 +50,11 @@ export default {
|
||||
this.chartDom = document.getElementById(this.chartId)
|
||||
this.myChart = echarts.init(this.chartDom)
|
||||
const seriesList = []
|
||||
const legendList = []
|
||||
this.data.list.forEach((item, index) => {
|
||||
this.data.list && this.data.list.forEach((item, index) => {
|
||||
seriesList.push({
|
||||
name: item.name,
|
||||
type: 'line',
|
||||
stack: 'Total',
|
||||
// stack: 'Total',
|
||||
data: item.value,
|
||||
areaStyle: {
|
||||
normal: {
|
||||
@@ -71,25 +70,33 @@ export default {
|
||||
normal: {
|
||||
color: this.colorList[index]
|
||||
}
|
||||
}
|
||||
},
|
||||
smooth: true
|
||||
})
|
||||
legendList.push(item.name)
|
||||
})
|
||||
|
||||
const option = {
|
||||
title: {
|
||||
text: this.data.text + '折线图',
|
||||
text: this.data.text,
|
||||
subtext: this.data.subtext || ''
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
trigger: 'axis',
|
||||
formatter: (value) => {
|
||||
let title = this.data.text + '<br/>'
|
||||
value.forEach(item => {
|
||||
title = title + item.marker + item.seriesName + ' : ' + getSizeDescByByteCount(item.data) + '<br/>'
|
||||
})
|
||||
return title
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: legendList,
|
||||
data: this.data.legendList,
|
||||
left: 'right'
|
||||
},
|
||||
grid: {
|
||||
left: '2%',
|
||||
right: '2%',
|
||||
left: '0',
|
||||
right: '3%',
|
||||
bottom: '2%',
|
||||
containLabel: true
|
||||
},
|
||||
@@ -99,7 +106,12 @@ export default {
|
||||
data: this.data.title
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
formatter: (value) => {
|
||||
return getSizeDescByByteCount(value)
|
||||
}
|
||||
}
|
||||
},
|
||||
series: seriesList
|
||||
}
|
||||
|
||||
@@ -1,40 +1,32 @@
|
||||
<template>
|
||||
<div class="dashboard-editor">
|
||||
<div class="log-div">
|
||||
|
||||
</div>
|
||||
<div class="container">
|
||||
<el-row class="line-chart" :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-card>
|
||||
<traffic-sum-chart :data="echartsData.trafficSumChart" :chartId="'traffic-sum-div1'"/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card>
|
||||
<traffic-sum-chart :data="echartsData.trafficSumChart2"/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="pie-chart" :gutter="16">
|
||||
<el-col :span="6">
|
||||
<el-card class="box-card">
|
||||
<license-chart :data="echartsData.licenseChart"/>
|
||||
<license-chart :data="licenseChart" v-if="licenseChart"/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card class="box-card">
|
||||
<port-mapping-chart :data="echartsData.portMappingChart"/>
|
||||
<port-mapping-chart :data="portMappingChart" v-if="portMappingChart"/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card class="box-card">
|
||||
<daily-traffic-chart :data="echartsData.dailyTrafficChart" :chartId="'daily-traffic-div'"/>
|
||||
<daily-traffic-chart :data="dailyTrafficChart" :chartId="'daily-traffic-div'" v-if="dailyTrafficChart"/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card class="box-card">
|
||||
<daily-traffic-chart :data="echartsData.historyTrafficChart" :chartId="'history-traffic-div'"/>
|
||||
<daily-traffic-chart :data="historyTrafficChart" :chartId="'history-traffic-div'" v-if="historyTrafficChart"/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="line-chart" :gutter="16">
|
||||
<el-col :span="24">
|
||||
<el-card>
|
||||
<traffic-sum-chart :data="trafficSumChart" :chartId="'traffic-sum-div1'" v-if="trafficSumChart"/>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -48,69 +40,51 @@ import LicenseChart from './components/LicenseChart'
|
||||
import PortMappingChart from './components/PortMappingChart'
|
||||
import DailyTrafficChart from './components/DailyTrafficChart'
|
||||
import TrafficSumChart from './components/TrafficSumChart'
|
||||
|
||||
const echartsData = {
|
||||
licenseChart: {
|
||||
total: 100,
|
||||
onLine: 80
|
||||
},
|
||||
portMappingChart: {
|
||||
total: 100,
|
||||
onLine: 25
|
||||
},
|
||||
dailyTrafficChart: {
|
||||
upload: 300,
|
||||
download: 680,
|
||||
text: '今日流量'
|
||||
},
|
||||
historyTrafficChart: {
|
||||
upload: 4100,
|
||||
download: 9520,
|
||||
text: '历史流量'
|
||||
},
|
||||
trafficSumChart: {
|
||||
text: '今日流量',
|
||||
subtext: '当日0-24时',
|
||||
title: ['1:00', '2:00', '3:00', '4:00', '5:00', '6:00', '7:00'],
|
||||
list: [
|
||||
{
|
||||
name: '上行',
|
||||
value: [120, 132, 101, 134, 90, 230, 210]
|
||||
},
|
||||
{
|
||||
name: '下行',
|
||||
value: [112, 333, 150, 60, 99, 50, 102]
|
||||
}
|
||||
]
|
||||
},
|
||||
trafficSumChart2: {
|
||||
text: '历史流量',
|
||||
title: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
|
||||
list: [
|
||||
{
|
||||
name: '上行',
|
||||
value: [132, 101, 134, 90, 120, 132, 101, 134, 90, 230, 210, 120]
|
||||
},
|
||||
{
|
||||
name: '下行',
|
||||
value: [111, 0, 50, 101, 134, 90, 230, 120, 132, 101, 60, 20]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
import { homeData } from '@/api/report'
|
||||
|
||||
export default {
|
||||
name: 'dashboard-admin',
|
||||
components: { TrafficSumChart, DailyTrafficChart, PortMappingChart, LicenseChart },
|
||||
data() {
|
||||
return {
|
||||
echartsData: echartsData
|
||||
licenseChart: undefined,
|
||||
portMappingChart: undefined,
|
||||
dailyTrafficChart: undefined,
|
||||
historyTrafficChart: undefined,
|
||||
trafficSumChart: undefined
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getHomeData()
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
|
||||
getHomeData() {
|
||||
homeData().then(res => {
|
||||
this.licenseChart = res.data.data.license
|
||||
this.portMappingChart = res.data.data.portMapping
|
||||
this.dailyTrafficChart = Object.assign(res.data.data.todayFlow, { text: '今日流量' })
|
||||
this.historyTrafficChart = Object.assign(res.data.data.totalFlow, { text: '流量汇总' })
|
||||
this.trafficSumChart = this.getChartData(res.data.data.last7dFlow)
|
||||
})
|
||||
},
|
||||
getChartData(last7dFlow) {
|
||||
const list = []
|
||||
last7dFlow.seriesList.forEach(item => {
|
||||
list.push({
|
||||
name: item.seriesName,
|
||||
value: item.seriesData
|
||||
})
|
||||
})
|
||||
return {
|
||||
text: '流量监控',
|
||||
subtext: `最近${last7dFlow.dataList.length || 0}天流量监控`,
|
||||
title: last7dFlow.xDate,
|
||||
list: list,
|
||||
legendList: last7dFlow.legendData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -127,9 +101,9 @@ export default {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.pie-chart{
|
||||
padding-top: 16px;
|
||||
padding-bottom: 16px;
|
||||
.el-card{
|
||||
min-height: 300px;
|
||||
min-height: 250px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<template>
|
||||
<div class="app-container calendar-list-container">
|
||||
<div class="filter-container">
|
||||
<el-select v-model="listQuery.licenseId" placeholder="请选择license" clearable style="margin-right:10px">
|
||||
<el-option v-for="item in licenseList" :key="item.key" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
<el-button type="primary" v-waves icon="el-icon-search" @click="handleFilter">{{$t('table.search')}}</el-button>
|
||||
</div>
|
||||
|
||||
@@ -48,8 +51,8 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination-container">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.currentPage"
|
||||
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.current"
|
||||
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.size" layout="total, sizes, prev, pager, next, jumper" :total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
|
||||
@@ -66,6 +69,7 @@
|
||||
|
||||
<script>
|
||||
import { fetchList } from '@/api/clientConnectLog'
|
||||
import { licenseList } from '@/api/license'
|
||||
import waves from '@/directive/waves' // 水波纹指令
|
||||
|
||||
export default {
|
||||
@@ -80,12 +84,13 @@ export default {
|
||||
total: null,
|
||||
listLoading: false,
|
||||
listQuery: {
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
current: 1,
|
||||
size: 10,
|
||||
jobId: undefined
|
||||
},
|
||||
dialogVisible: false,
|
||||
selectRow: {}
|
||||
selectRow: {},
|
||||
licenseList: []
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
@@ -112,6 +117,7 @@ export default {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getLicenseList()
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
@@ -123,6 +129,11 @@ export default {
|
||||
this.listLoading = false
|
||||
})
|
||||
},
|
||||
getLicenseList() {
|
||||
licenseList().then(response => {
|
||||
this.licenseList = response.data.data
|
||||
})
|
||||
},
|
||||
handleFilter() {
|
||||
this.listQuery.current = 1
|
||||
this.getList()
|
||||
|
||||
@@ -46,8 +46,8 @@
|
||||
</el-table-column>-->
|
||||
</el-table>
|
||||
<div class="pagination-container">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.currentPage"
|
||||
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.current"
|
||||
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.size" layout="total, sizes, prev, pager, next, jumper" :total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
|
||||
@@ -79,8 +79,8 @@ export default {
|
||||
total: null,
|
||||
listLoading: false,
|
||||
listQuery: {
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
current: 1,
|
||||
size: 10,
|
||||
jobId: undefined
|
||||
},
|
||||
jobList: [],
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<template>
|
||||
<div class="app-container calendar-list-container">
|
||||
<div class="filter-container">
|
||||
<el-select v-model="listQuery.userId" placeholder="请选择用户" clearable>
|
||||
<el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id"/>
|
||||
</el-select>
|
||||
<el-button class="filter-item" type="primary" v-waves icon="el-icon-search" @click="handleFilter">{{$t('table.search')}}</el-button>
|
||||
</div>
|
||||
|
||||
@@ -28,8 +31,8 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination-container">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.currentPage"
|
||||
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.current"
|
||||
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.size" layout="total, sizes, prev, pager, next, jumper" :total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
@@ -37,6 +40,7 @@
|
||||
|
||||
<script>
|
||||
import { fetchList } from '@/api/loginLog'
|
||||
import { userList } from '@/api/user'
|
||||
import waves from '@/directive/waves' // 水波纹指令
|
||||
|
||||
export default {
|
||||
@@ -51,10 +55,11 @@ export default {
|
||||
total: null,
|
||||
listLoading: false,
|
||||
listQuery: {
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
jobId: undefined
|
||||
}
|
||||
current: 1,
|
||||
size: 10,
|
||||
userId: null
|
||||
},
|
||||
userList: []
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
@@ -98,13 +103,11 @@ export default {
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
this.getDataList()
|
||||
},
|
||||
activated() {
|
||||
if (this.$route.query.jobId) {
|
||||
this.listQuery.jobId = this.$route.query.jobId
|
||||
console.log(this.listQuery.jobId, this.$route.query.jobId)
|
||||
this.getList()
|
||||
}
|
||||
this.getUserList()
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
@@ -115,6 +118,22 @@ export default {
|
||||
this.listLoading = false
|
||||
})
|
||||
},
|
||||
getUserList() {
|
||||
userList().then(response => {
|
||||
this.userList = response.data.data
|
||||
})
|
||||
},
|
||||
getDataList() {
|
||||
const loginName = this.$store.state.user.loginName
|
||||
userList().then(response => {
|
||||
this.userList = response.data.data
|
||||
const curUser = this.userList.find((val) => val.loginName === loginName)
|
||||
if (curUser) {
|
||||
this.listQuery.userId = curUser.id
|
||||
}
|
||||
this.getList()
|
||||
})
|
||||
},
|
||||
handleFilter() {
|
||||
this.listQuery.current = 1
|
||||
this.getList()
|
||||
|
||||
@@ -27,9 +27,9 @@
|
||||
|
||||
<el-button type="primary" style="width:100%;margin-bottom:30px;" :loading="loading" @click.native.prevent="handleLogin">{{$t('login.logIn')}}</el-button>
|
||||
|
||||
<div class="tips">
|
||||
<span>{{$t('login.username')}} : visitor</span>
|
||||
<span>{{$t('login.password')}} : 123456</span>
|
||||
<div class="tips" v-if="user_name && user_pwd">
|
||||
<span>{{$t('login.username')}} : {{ user_name }}</span>
|
||||
<span>{{$t('login.password')}} : {{ user_pwd }}</span>
|
||||
</div>
|
||||
|
||||
</el-form>
|
||||
@@ -60,8 +60,8 @@ export default {
|
||||
}
|
||||
return {
|
||||
loginForm: {
|
||||
username: 'visitor',
|
||||
password: '123456'
|
||||
username: '',
|
||||
password: ''
|
||||
},
|
||||
loginRules: {
|
||||
username: [{ required: true, trigger: 'blur', validator: validateUsername }],
|
||||
@@ -69,7 +69,9 @@ export default {
|
||||
},
|
||||
passwordType: 'password',
|
||||
loading: false,
|
||||
showDialog: false
|
||||
showDialog: false,
|
||||
user_name: undefined,
|
||||
user_pwd: undefined
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -115,6 +117,14 @@ export default {
|
||||
// }
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (process.env.USER_NAME) {
|
||||
this.user_name = process.env.USER_NAME
|
||||
this.user_pwd = process.env.USER_PWD
|
||||
this.loginForm.username = process.env.USER_NAME
|
||||
this.loginForm.password = process.env.USER_PWD
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// window.addEventListener('hashchange', this.afterQRScan)
|
||||
},
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
<template>
|
||||
<div class="app-container calendar-list-container">
|
||||
<div class="filter-container">
|
||||
<el-select v-model="listQuery.userId" placeholder="请选择用户" filterable clearable>
|
||||
<el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id"/>
|
||||
</el-select>
|
||||
<el-select v-model="listQuery.isOnline" placeholder="请选择在线状态" clearable>
|
||||
<el-option v-for="item in selectObj.onlineOptions" :key="item.value" :label="item.label" :value="item.value"/>
|
||||
</el-select>
|
||||
<el-select v-model="listQuery.enable" placeholder="请选择启用状态" clearable>
|
||||
<el-option v-for="item in selectObj.statusOptions" :key="item.value" :label="item.label" :value="item.value"/>
|
||||
</el-select>
|
||||
<el-button class="filter-item" type="primary" v-waves icon="el-icon-search" @click="handleFilter">{{$t('table.search')}}</el-button>
|
||||
<el-button class="filter-item" style="margin-left: 10px;" @click="handleCreate" type="primary" icon="el-icon-edit">{{$t('table.add')}}</el-button>
|
||||
</div>
|
||||
@@ -60,8 +69,8 @@
|
||||
</el-table>
|
||||
|
||||
<div class="pagination-container">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.currentPage"
|
||||
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.current"
|
||||
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.size" layout="total, sizes, prev, pager, next, jumper" :total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
|
||||
@@ -132,11 +141,11 @@
|
||||
total: null,
|
||||
listLoading: true,
|
||||
listQuery: {
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
importance: undefined,
|
||||
title: undefined,
|
||||
type: undefined
|
||||
current: 1,
|
||||
size: 10,
|
||||
enable: undefined,
|
||||
isOnline: undefined,
|
||||
userId: null
|
||||
},
|
||||
importanceOptions: [1, 2, 3],
|
||||
calendarTypeOptions,
|
||||
@@ -165,7 +174,11 @@
|
||||
userId: [{ required: true, message: '请选择用户', trigger: 'blur' }],
|
||||
name: [{ required: true, message: 'License名称不能为空', trigger: 'blur' }]
|
||||
},
|
||||
downloadLoading: false
|
||||
downloadLoading: false,
|
||||
selectObj: {
|
||||
statusOptions: [{ label: '启用', value: 1 }, { label: '禁用', value: 2 }],
|
||||
onlineOptions: [{ label: '在线', value: 1 }, { label: '离线', value: 2 }]
|
||||
}
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
@@ -195,8 +208,7 @@
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
this.getUserList()
|
||||
this.getDataList()
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
@@ -212,6 +224,17 @@
|
||||
this.userList = response.data.data
|
||||
})
|
||||
},
|
||||
getDataList() {
|
||||
const loginName = this.$store.state.user.loginName
|
||||
userList().then(response => {
|
||||
this.userList = response.data.data
|
||||
const curUser = this.userList.find((val) => val.loginName === loginName)
|
||||
if (curUser) {
|
||||
this.listQuery.userId = curUser.id
|
||||
}
|
||||
this.getList()
|
||||
})
|
||||
},
|
||||
handleFilter() {
|
||||
this.listQuery.current = 1
|
||||
this.getList()
|
||||
|
||||
@@ -1,95 +1,134 @@
|
||||
<template>
|
||||
<div class="app-container calendar-list-container">
|
||||
<div class="filter-container">
|
||||
<el-button class="filter-item" type="primary" v-waves icon="el-icon-search" @click="handleFilter">{{$t('table.search')}}</el-button>
|
||||
<el-button class="filter-item" style="margin-left: 10px;" @click="handleCreate" type="primary" icon="el-icon-edit">{{$t('table.add')}}</el-button>
|
||||
<div class="filter-container" style="display:flex">
|
||||
<el-select v-model="listQuery.userId" placeholder="请选择用户" filterable clearable style="margin-right:10px;width: 120px;">
|
||||
<el-option v-for="item in userList" :key="item.loginName" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
<el-select v-model="listQuery.licenseId" placeholder="请选择license" filterable clearable style="margin-right:10px;width: 135px;">
|
||||
<el-option v-for="item in licenseList" :key="item.key" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
<el-select v-model="listQuery.protocal" placeholder="请选择协议" clearable style="margin-right:10px;width: 120px;">
|
||||
<el-option v-for="item in protocalList" :key="item.name" :label="item.name" :value="item.name"
|
||||
:disabled="!item.enable" />
|
||||
</el-select>
|
||||
<el-input v-model="listQuery.serverPort" type="text" style="width:145px;margin-right:10px" class="filter-item"
|
||||
placeholder="请输入服务端端口" :maxlength="5" show-word-limit />
|
||||
<el-select v-model="listQuery.isOnline" placeholder="请选择在线状态" clearable style="width:145px;margin-right:10px">
|
||||
<el-option v-for="item in selectObj.onlineOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
<el-select v-model="listQuery.enable" placeholder="请选择启用状态" clearable style="width:145px;margin-right:10px">
|
||||
<el-option v-for="item in selectObj.statusOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
<el-input v-model="listQuery.description" type="text" style="width:145px;margin-right:10px" class="filter-item"
|
||||
placeholder="请输入描述详情" show-word-limit />
|
||||
<el-button class="filter-item" type="primary" v-waves icon="el-icon-search" @click="handleFilter">{{
|
||||
$t('table.search') }}</el-button>
|
||||
<el-button class="filter-item" style="margin-left: 10px;" @click="handleCreate" type="primary"
|
||||
icon="el-icon-edit">{{ $t('table.add') }}</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :key='tableKey' :data="list" v-loading="listLoading" element-loading-text="给我一点时间" border fit highlight-current-row
|
||||
style="width: 100%">
|
||||
<el-table :key='tableKey' :data="list" v-loading="listLoading" element-loading-text="给我一点时间" border fit
|
||||
highlight-current-row style="width: 100%">
|
||||
<el-table-column align="center" :label="$t('table.id')" width="50">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.id}}</span>
|
||||
<span>{{ scope.row.id }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.userName')" width="100">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.userName}}</span>
|
||||
<span>{{ scope.row.userName }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.licenseName')" width="150">
|
||||
<el-table-column align="center" :label="$t('table.licenseName')" width="130">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.licenseName}}</span>
|
||||
<span>{{ scope.row.licenseName }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.serverPort')" width="200">
|
||||
<el-table-column align="center" :label="$t('table.protocalName')" width="100">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.serverPort}}</span>
|
||||
<span>{{ scope.row.protocal }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.proxyClient')" width="200">
|
||||
<el-table-column align="center" :label="$t('table.domainName')" width="200">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.clientIp}}:{{scope.row.clientPort}}</span>
|
||||
<span>{{ scope.row.domain }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.serverPort')" width="80">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.serverPort }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.proxyClient')" width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.clientIp }}:{{ scope.row.clientPort }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.desc')" width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.description }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column width="150px" align="center" :label="$t('table.createTime')">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.createTime | parseTime('{y}-{m}-{d} {h}:{i}')}}</span>
|
||||
<span>{{ scope.row.createTime | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="150px" align="center" :label="$t('table.updateTime')">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.updateTime | parseTime('{y}-{m}-{d} {h}:{i}')}}</span>
|
||||
<span>{{ scope.row.updateTime | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column class-name="status-col" :label="$t('table.enableStatus')" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.enable | statusFilter">{{scope.row.enable | statusName}}</el-tag>
|
||||
<el-tag :type="scope.row.enable | statusFilter">{{ scope.row.enable | statusName }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column class-name="status-col" :label="$t('table.isOnline')" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.isOnline | statusFilter">{{scope.row.isOnline | isOnlineName}}</el-tag>
|
||||
<el-tag :type="scope.row.isOnline | statusFilter">{{ scope.row.isOnline | isOnlineName }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.actions')" width="230" class-name="small-padding fixed-width">
|
||||
<el-table-column class-name="status-col" :label="$t('table.access')" width="120">
|
||||
<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 size="mini" v-if="(scope.row.protocal === 'HTTP' || scope.row.protocal === 'HTTP(S)')" @click="handleOpenWebPage(scope.row)">{{$t('table.openWebPage')}}</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.actions')" width="320" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="primary" size="mini" @click="handleUpdate(scope.row)">{{ $t('table.edit') }}</el-button>
|
||||
<el-button v-if="scope.row.enable == '1'" size="mini" type="danger" @click="handleModifyStatus(scope.row, 2)">{{$t('table.disable')}}</el-button>
|
||||
<el-button v-if="scope.row.enable == '2'" size="mini" type="success"
|
||||
@click="handleModifyStatus(scope.row, 1)">{{ $t('table.enable') }}</el-button>
|
||||
<!-- <el-button size="mini" type="danger" @click="handleDelete(scope.row,'deleted')">{{$t('table.delete')}}</el-button>-->
|
||||
<ButtonPopover @handleCommitClick="handleDelete2(scope.row)" style="margin-left: 10px"/>
|
||||
<ButtonPopover @handleCommitClick="handleDelete2(scope.row)" style="margin-left: 10px" />
|
||||
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pagination-container">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.currentPage"
|
||||
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange"
|
||||
:current-pageInfo.sync="listQuery.current" :pageInfo-sizes="[10, 20, 30, 50]" :pageInfo-size="listQuery.size"
|
||||
layout="total, sizes, prev, pager, next, jumper" :total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
|
||||
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
|
||||
<el-form :rules="rules" ref="dataForm" :model="temp" label-position="left" label-width="120px" style='width: 400px; margin-left:50px;'>
|
||||
<!-- <el-form-item :label="$t('License')" prop="licenseId">-->
|
||||
<!-- <el-select style="width: 280px;" class="filter-item" v-model="temp.licenseId" placeholder="请选择" :disabled="dialogStatus=='update'">-->
|
||||
<!-- <el-option v-for="item in licenseList" :key="item.id" :label="item.name" :value="item.id">-->
|
||||
<!-- </el-option>-->
|
||||
<!-- </el-select>-->
|
||||
<!-- </el-form-item>-->
|
||||
<el-form :rules="rules" ref="dataForm" :model="temp" label-position="left" label-width="120px"
|
||||
style='width: 400px; margin-left:50px;'>
|
||||
<!-- <el-form-item :label="$t('License')" prop="licenseId">-->
|
||||
<!-- <el-select style="width: 280px;" class="filter-item" v-model="temp.licenseId" placeholder="请选择" :disabled="dialogStatus=='update'">-->
|
||||
<!-- <el-option v-for="item in licenseList" :key="item.id" :label="item.name" :value="item.id">-->
|
||||
<!-- </el-option>-->
|
||||
<!-- </el-select>-->
|
||||
<!-- </el-form-item>-->
|
||||
|
||||
<el-form-item :label="$t('License')" prop="licenseId">
|
||||
<DropdownTable
|
||||
v-model="temp.licenseId"
|
||||
:name.sync="temp.licenseName"
|
||||
:tableData= "licenseList"
|
||||
@selectedData="selectedFeeItem"
|
||||
placeholder="请选择"
|
||||
:width="280"
|
||||
:disabled="dialogStatus==='update'"
|
||||
/>
|
||||
<!-- <DropdownTable
|
||||
<DropdownTable v-model="temp.licenseId" :name.sync="temp.licenseName" :tableData="licenseAuthList"
|
||||
@selectedData="selectedFeeItem" placeholder="请选择" :width="280" :disabled="dialogStatus === 'update'" />
|
||||
<!-- <DropdownTable
|
||||
:columns="countryColumns"
|
||||
:data="licenseList"
|
||||
v-model="temp.licenseId"
|
||||
@@ -100,23 +139,53 @@
|
||||
style="width: 280px"
|
||||
/>-->
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('服务端端口')" prop="serverPort">
|
||||
<el-select style="width: 280px;" class="filter-item" v-model="temp.serverPort" placeholder="请选择">
|
||||
<el-option v-for="item in serverPortList" :key="item.port" :label="item.port" :value="item.port">
|
||||
<el-form-item :label="$t('协议')" prop="protocal">
|
||||
<el-select style="width: 280px;" class="filter-item" v-model="temp.protocal" placeholder="请选择">
|
||||
<el-option v-for="item in protocalList" :key="item.name" :label="item.name" :value="item.name"
|
||||
:disabled="!item.enable">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('服务端端口')" prop="serverPort" >
|
||||
<load-select style="width: 280px;" class="filter-item"
|
||||
v-model="temp.serverPort"
|
||||
:data="serverPortList"
|
||||
:page="loadServerPortQuery.page"
|
||||
:hasMore="more"
|
||||
:clearable="false"
|
||||
:dictLabel="'port'"
|
||||
:dictValue="'port'"
|
||||
:request="loadServerPort"/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('客户端IP')" prop="clientIp">
|
||||
<el-input v-model="temp.clientIp"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('客户端端口')" prop="clientPort">
|
||||
<el-input v-model="temp.clientPort"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('域名')" prop="subdomain" v-if="(temp.protocal === 'HTTP' || temp.protocal === 'HTTP(S)') && domainName && domainName != ''">
|
||||
<el-input v-model="temp.subdomain">
|
||||
<template slot="append">.{{ domainName }}</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('响应数量')" prop="proxyResponses" v-if="temp.protocal === 'UDP'">
|
||||
<el-input v-model="temp.proxyResponses"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('超时时间')" prop="proxyTimeoutMs" v-if="temp.protocal === 'UDP'">
|
||||
<el-input v-model="temp.proxyTimeoutMs">
|
||||
<template slot="append">毫秒</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('描述')" prop="description">
|
||||
<el-input v-model="temp.description"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogFormVisible = false">{{$t('table.cancel')}}</el-button>
|
||||
<el-button v-if="dialogStatus=='create'" type="primary" @click="createData">{{$t('table.confirm')}}</el-button>
|
||||
<el-button v-else type="primary" @click="updateData">{{$t('table.confirm')}}</el-button>
|
||||
<el-button @click="
|
||||
dialogFormVisible = false">{{ $t('table.cancel') }}</el-button>
|
||||
<el-button v-if="dialogStatus == 'create'" type="primary" @click="createData">{{ $t('table.confirm')
|
||||
}}</el-button>
|
||||
<el-button v-else type="primary" @click="updateData">{{ $t('table.confirm') }}</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
@@ -126,7 +195,7 @@
|
||||
<el-table-column prop="pv" label="Pv"> </el-table-column>
|
||||
</el-table>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="dialogPvVisible = false">{{$t('table.confirm')}}</el-button>
|
||||
<el-button type="primary" @click="dialogPvVisible = false">{{ $t('table.confirm') }}</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
@@ -134,251 +203,332 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { fetchList, createUserPortMapping, updateUserPortMapping, updateEnableStatus, deletePortMapping } from '@/api/portMapping'
|
||||
import { portPoolList } from '@/api/portPool'
|
||||
import { licenseList } from '@/api/license'
|
||||
import waves from '@/directive/waves' // 水波纹指令
|
||||
import { parseTime } from '@/utils'
|
||||
import ButtonPopover from '../../components/Button/buttonPopover'
|
||||
import DropdownTable from '../../components/Dropdown/DropdownTable'
|
||||
import { fetchList, createUserPortMapping, updateUserPortMapping, updateEnableStatus, deletePortMapping } from '@/api/portMapping'
|
||||
import { availablePortList, portAvailable } from '@/api/portPool'
|
||||
import { licenseList, licenseAuthList } from '@/api/license'
|
||||
import { protocalList } from '@/api/protocal'
|
||||
import { userList } from '@/api/user'
|
||||
import { domainNameBindInfo } from '@/api/domain'
|
||||
import waves from '@/directive/waves' // 水波纹指令
|
||||
import { parseTime } from '@/utils'
|
||||
import ButtonPopover from '../../components/Button/buttonPopover'
|
||||
import DropdownTable from '../../components/Dropdown/DropdownTable'
|
||||
// 下拉选择加载组件
|
||||
import loadSelect from "@/components/Select/SelectLoadMore";
|
||||
|
||||
const calendarTypeOptions = [
|
||||
{ key: 'CN', display_name: 'China' },
|
||||
{ key: 'US', display_name: 'USA' },
|
||||
{ key: 'JP', display_name: 'Japan' },
|
||||
{ key: 'EU', display_name: 'Eurozone' }
|
||||
]
|
||||
const calendarTypeOptions = [
|
||||
{ key: 'CN', display_name: 'China' },
|
||||
{ key: 'US', display_name: 'USA' },
|
||||
{ key: 'JP', display_name: 'Japan' },
|
||||
{ key: 'EU', display_name: 'Eurozone' }
|
||||
]
|
||||
|
||||
// arr to obj ,such as { CN : "China", US : "USA" }
|
||||
const calendarTypeKeyValue = calendarTypeOptions.reduce((acc, cur) => {
|
||||
acc[cur.key] = cur.display_name
|
||||
return acc
|
||||
}, {})
|
||||
// arr to obj ,such as { CN : "China", US : "USA" }
|
||||
const calendarTypeKeyValue = calendarTypeOptions.reduce((acc, cur) => {
|
||||
acc[cur.key] = cur.display_name
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
export default {
|
||||
name: 'complexTable',
|
||||
directives: {
|
||||
waves
|
||||
},
|
||||
components: {
|
||||
DropdownTable,
|
||||
ButtonPopover
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableKey: 0,
|
||||
list: null,
|
||||
total: null,
|
||||
listLoading: true,
|
||||
listQuery: {
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
importance: undefined,
|
||||
title: undefined,
|
||||
type: undefined
|
||||
},
|
||||
importanceOptions: [1, 2, 3],
|
||||
calendarTypeOptions,
|
||||
sortOptions: [{ label: 'ID Ascending', key: '+id' }, { label: 'ID Descending', key: '-id' }],
|
||||
statusOptions: ['published', 'draft', 'deleted'],
|
||||
licenseList: [],
|
||||
serverPortList: [],
|
||||
showReviewer: false,
|
||||
temp: {
|
||||
id: undefined,
|
||||
licenseId: undefined,
|
||||
licenseName: undefined,
|
||||
serverPort: undefined,
|
||||
clientIp: undefined,
|
||||
clientPort: undefined
|
||||
},
|
||||
dialogFormVisible: false,
|
||||
dialogStatus: '',
|
||||
textMap: {
|
||||
update: '编辑',
|
||||
create: '新建'
|
||||
},
|
||||
dialogPvVisible: false,
|
||||
pvData: [],
|
||||
rules: {
|
||||
licenseId: [{ required: true, message: '请选择License', trigger: 'blur,change' }],
|
||||
serverPort: [{ required: true, message: '请输入服务端端口', trigger: 'blur' }],
|
||||
clientIp: [{ required: true, message: '请输入客户端IP', trigger: 'blur' }],
|
||||
clientPort: [{ required: true, message: '请输入客户端端口', trigger: 'blur' }]
|
||||
},
|
||||
downloadLoading: false,
|
||||
countryColumns: [
|
||||
{ prop: 'userName', label: '用户名', align: 'center' },
|
||||
{ prop: 'name', label: 'License', align: 'center' }
|
||||
]
|
||||
export default {
|
||||
name: 'complexTable',
|
||||
directives: {
|
||||
waves
|
||||
},
|
||||
components: {
|
||||
DropdownTable,
|
||||
ButtonPopover,
|
||||
loadSelect
|
||||
},
|
||||
data() {
|
||||
const isPortAvailable = (rule, value, callback) => {
|
||||
if (value != null) {
|
||||
const param = { port: value, portMappingId: this.temp.id }
|
||||
portAvailable(param).then(res => {
|
||||
if (!res.data.data) {
|
||||
return callback(new Error('该端口被占用'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
statusName(status) {
|
||||
const statusMap = {
|
||||
1: '启用',
|
||||
2: '禁用'
|
||||
}
|
||||
return statusMap[status]
|
||||
}
|
||||
return {
|
||||
tableKey: 0,
|
||||
list: null,
|
||||
total: null,
|
||||
listLoading: true,
|
||||
listQuery: {
|
||||
current: 1,
|
||||
size: 10,
|
||||
importance: undefined,
|
||||
title: undefined,
|
||||
type: undefined,
|
||||
userId: undefined,
|
||||
license: undefined,
|
||||
port: undefined,
|
||||
isOnline: undefined,
|
||||
enable: undefined,
|
||||
description: undefined
|
||||
|
||||
},
|
||||
isOnlineName(isOnline) {
|
||||
const isOnlineMap = {
|
||||
1: '在线',
|
||||
2: '离线'
|
||||
}
|
||||
return isOnlineMap[isOnline]
|
||||
importanceOptions: [1, 2, 3],
|
||||
calendarTypeOptions,
|
||||
sortOptions: [{ label: 'ID Ascending', key: '+id' }, { label: 'ID Descending', key: '-id' }],
|
||||
statusOptions: ['published', 'draft', 'deleted'],
|
||||
userList: [],
|
||||
licenseList: [],
|
||||
protocalList: [],
|
||||
licenseAuthList: [],
|
||||
serverPortList: [],
|
||||
domainName: '',
|
||||
showReviewer: false,
|
||||
temp: {
|
||||
id: undefined,
|
||||
licenseId: undefined,
|
||||
licenseName: undefined,
|
||||
serverPort: undefined,
|
||||
clientIp: undefined,
|
||||
clientPort: undefined,
|
||||
protocal: undefined,
|
||||
proxyResponses: undefined,
|
||||
proxyTimeoutMs: undefined
|
||||
},
|
||||
statusFilter(status) {
|
||||
const statusMap = {
|
||||
1: 'success',
|
||||
2: 'danger'
|
||||
}
|
||||
return statusMap[status]
|
||||
selectObj: {
|
||||
statusOptions: [{ label: '启用', value: 1 }, { label: '禁用', value: 2 }],
|
||||
onlineOptions: [{ label: '在线', value: 1 }, { label: '离线', value: 2 }]
|
||||
},
|
||||
typeFilter(type) {
|
||||
return calendarTypeKeyValue[type]
|
||||
dialogFormVisible: false,
|
||||
dialogStatus: '',
|
||||
textMap: {
|
||||
update: '编辑',
|
||||
create: '新建'
|
||||
},
|
||||
dialogPvVisible: false,
|
||||
pvData: [],
|
||||
rules: {
|
||||
licenseId: [{ required: true, message: '请选择License', trigger: 'blur,change' }],
|
||||
serverPort: [{ required: true, message: '请输入服务端端口', trigger: 'blur' },
|
||||
// { validator: isPortAvailable, trigger: 'change' }
|
||||
],
|
||||
clientIp: [{ required: true, message: '请输入客户端IP', trigger: 'blur' }],
|
||||
clientPort: [{ required: true, message: '请输入客户端端口', trigger: 'blur' }],
|
||||
protocal: [{ required: true, message: '请选择协议', trigger: 'blur' }]
|
||||
},
|
||||
downloadLoading: false,
|
||||
countryColumns: [
|
||||
{ prop: 'userName', label: '用户名', align: 'center' },
|
||||
{ prop: 'name', label: 'License', align: 'center' }
|
||||
],
|
||||
loadServerPortQuery:{ //下拉框加载数据请求参数
|
||||
page:1,
|
||||
size:50,
|
||||
licenseId:null,
|
||||
},
|
||||
more: true,
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
statusName(status) {
|
||||
const statusMap = {
|
||||
1: '启用',
|
||||
2: '禁用'
|
||||
}
|
||||
return statusMap[status]
|
||||
},
|
||||
created() {
|
||||
isOnlineName(isOnline) {
|
||||
const isOnlineMap = {
|
||||
1: '在线',
|
||||
2: '离线'
|
||||
}
|
||||
return isOnlineMap[isOnline]
|
||||
},
|
||||
statusFilter(status) {
|
||||
const statusMap = {
|
||||
1: 'success',
|
||||
2: 'danger'
|
||||
}
|
||||
return statusMap[status]
|
||||
},
|
||||
typeFilter(type) {
|
||||
return calendarTypeKeyValue[type]
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getDomainNameBindInfo()
|
||||
this.getDataList()
|
||||
this.getLicenseList()
|
||||
this.getLicenseAuthList()
|
||||
this.getProtocalList()
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
this.listLoading = true
|
||||
fetchList(this.listQuery).then(response => {
|
||||
this.list = response.data.data.records
|
||||
this.total = response.data.data.total
|
||||
this.listLoading = false
|
||||
})
|
||||
},
|
||||
getDataList() {
|
||||
const loginName = this.$store.state.user.loginName
|
||||
userList().then(response => {
|
||||
this.userList = response.data.data
|
||||
const curUser = this.userList.find((val) => val.loginName === loginName)
|
||||
if (curUser) {
|
||||
this.listQuery.userId = curUser.id
|
||||
}
|
||||
this.getList()
|
||||
})
|
||||
},
|
||||
getDomainNameBindInfo() {
|
||||
domainNameBindInfo().then(response => {
|
||||
this.domainName = response.data.data
|
||||
})
|
||||
},
|
||||
getAvailablePortList(licenseId) {
|
||||
this.loadServerPortQuery.licenseId = licenseId;
|
||||
this.serverPortList = []; //清掉数据
|
||||
},
|
||||
getAllUserList() {
|
||||
userList().then(response => {
|
||||
this.userList = response.data.data
|
||||
})
|
||||
},
|
||||
getLicenseList() {
|
||||
licenseList().then(response => {
|
||||
this.licenseList = response.data.data
|
||||
})
|
||||
},
|
||||
getProtocalList() {
|
||||
protocalList().then(response => {
|
||||
this.protocalList = response.data.data
|
||||
})
|
||||
},
|
||||
getLicenseAuthList() {
|
||||
licenseAuthList().then(response => {
|
||||
this.licenseAuthList = response.data.data
|
||||
})
|
||||
},
|
||||
handleFilter() {
|
||||
this.listQuery.current = 1
|
||||
this.getList()
|
||||
this.getPortPoolList()
|
||||
this.getLicenseList()
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
this.listLoading = true
|
||||
fetchList(this.listQuery).then(response => {
|
||||
this.list = response.data.data.records
|
||||
this.total = response.data.data.total
|
||||
this.listLoading = false
|
||||
})
|
||||
},
|
||||
getPortPoolList() {
|
||||
portPoolList().then(response => {
|
||||
this.serverPortList = response.data.data
|
||||
})
|
||||
},
|
||||
getLicenseList() {
|
||||
licenseList().then(response => {
|
||||
this.licenseList = response.data.data
|
||||
})
|
||||
},
|
||||
handleFilter() {
|
||||
this.listQuery.current = 1
|
||||
this.getList()
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
this.listQuery.size = val
|
||||
this.getList()
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.listQuery.current = val
|
||||
this.getList()
|
||||
},
|
||||
handleModifyStatus(row, enable) {
|
||||
console.log('route', this.$route)
|
||||
updateEnableStatus(row.id, enable).then(response => {
|
||||
if (response.data.data.code === 0) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success'
|
||||
})
|
||||
}
|
||||
this.getList()
|
||||
})
|
||||
},
|
||||
resetTemp() {
|
||||
this.temp = {
|
||||
id: undefined,
|
||||
licenseId: undefined,
|
||||
licenseName: undefined,
|
||||
serverPort: undefined,
|
||||
clientIp: undefined,
|
||||
clientPort: undefined
|
||||
handleSizeChange(val) {
|
||||
this.listQuery.size = val
|
||||
this.getList()
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.listQuery.current = val
|
||||
this.getList()
|
||||
},
|
||||
handleModifyStatus(row, enable) {
|
||||
console.log('route', this.$route)
|
||||
updateEnableStatus(row.id, enable).then(response => {
|
||||
if (response.data.data.code === 0) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success'
|
||||
})
|
||||
}
|
||||
},
|
||||
handleCreate() {
|
||||
this.resetTemp()
|
||||
this.dialogStatus = 'create'
|
||||
this.dialogFormVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs['dataForm'].clearValidate()
|
||||
})
|
||||
},
|
||||
createData() {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
createUserPortMapping(this.temp).then(response => {
|
||||
if (response.data.code === 0) {
|
||||
this.dialogFormVisible = false
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '创建成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
handleUpdate(row) {
|
||||
this.temp = Object.assign({}, row) // copy obj
|
||||
this.temp.timestamp = new Date(this.temp.timestamp)
|
||||
this.dialogStatus = 'update'
|
||||
this.dialogFormVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs['dataForm'].clearValidate()
|
||||
})
|
||||
},
|
||||
updateData() {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
const tempData = Object.assign({}, this.temp)
|
||||
updateUserPortMapping(tempData).then(response => {
|
||||
if (response.data.code === 0) {
|
||||
// this.$message({
|
||||
// message: '操作成功',
|
||||
// type: 'success'
|
||||
// })
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '更新成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
this.dialogFormVisible = false
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
selectedFeeItem(row, list) {
|
||||
this.temp.licenseId = row.id
|
||||
this.temp.licenseName = row.name
|
||||
},
|
||||
handleDelete(row) {
|
||||
this.$confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
deletePortMapping(row.id).then(response => {
|
||||
this.getList()
|
||||
})
|
||||
},
|
||||
resetTemp() {
|
||||
this.temp = {
|
||||
id: undefined,
|
||||
licenseId: undefined,
|
||||
licenseName: undefined,
|
||||
serverPort: undefined,
|
||||
clientIp: '127.0.0.1',
|
||||
clientPort: undefined,
|
||||
userId: undefined,
|
||||
proxyResponses: undefined,
|
||||
proxyTimeoutMs: undefined
|
||||
}
|
||||
this.serverPortList = []
|
||||
this.loadServerPortQuery.licenseId = null;
|
||||
this.more = true;
|
||||
},
|
||||
handleCreate() {
|
||||
this.resetTemp()
|
||||
this.dialogStatus = 'create'
|
||||
this.dialogFormVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs['dataForm'].clearValidate()
|
||||
})
|
||||
},
|
||||
createData() {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
createUserPortMapping(this.temp).then(response => {
|
||||
if (response.data.code === 0) {
|
||||
this.dialogFormVisible = false
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '删除成功',
|
||||
message: '创建成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
handleDelete2(row) {
|
||||
}
|
||||
})
|
||||
},
|
||||
handleOpenWebPage(row) {
|
||||
let url = location.protocol + '//' + location.hostname + ':' + row.serverPort
|
||||
if (row.domain) {
|
||||
url = location.protocol + '//' + row.domain
|
||||
}
|
||||
open(url)
|
||||
},
|
||||
handleUpdate(row) {
|
||||
this.temp = Object.assign({}, row) // copy obj
|
||||
this.temp.timestamp = new Date(this.temp.timestamp)
|
||||
this.dialogStatus = 'update'
|
||||
this.dialogFormVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs['dataForm'].clearValidate()
|
||||
})
|
||||
this.getAvailablePortList(row.licenseId)
|
||||
},
|
||||
updateData() {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
const tempData = Object.assign({}, this.temp)
|
||||
updateUserPortMapping(tempData).then(response => {
|
||||
if (response.data.code === 0) {
|
||||
// this.$message({
|
||||
// message: '操作成功',
|
||||
// type: 'success'
|
||||
// })
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '更新成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
this.dialogFormVisible = false
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
selectedFeeItem(row, list) {
|
||||
if (this.temp.licenseId !== row.id) {
|
||||
this.temp.licenseId = row.id
|
||||
this.temp.licenseName = row.name
|
||||
this.getAvailablePortList(row.id)
|
||||
this.temp.serverPort = null
|
||||
}
|
||||
},
|
||||
handleDelete(row) {
|
||||
this.$confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
deletePortMapping(row.id).then(response => {
|
||||
if (response.data.code === 0) {
|
||||
this.$notify({
|
||||
@@ -390,26 +540,70 @@
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
},
|
||||
handleDownload() {
|
||||
this.downloadLoading = true
|
||||
import('@/vendor/Export2Excel').then(excel => {
|
||||
const tHeader = ['timestamp', 'title', 'type', 'importance', 'status']
|
||||
const filterVal = ['timestamp', 'title', 'type', 'importance', 'status']
|
||||
const data = this.formatJson(filterVal, this.list)
|
||||
excel.export_json_to_excel(tHeader, data, 'table-list')
|
||||
this.downloadLoading = false
|
||||
}).catch(() => { })
|
||||
},
|
||||
handleDelete2(row) {
|
||||
deletePortMapping(row.id).then(response => {
|
||||
if (response.data.code === 0) {
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '删除成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
},
|
||||
handleDownload() {
|
||||
this.downloadLoading = true
|
||||
import('@/vendor/Export2Excel').then(excel => {
|
||||
const tHeader = ['timestamp', 'title', 'type', 'importance', 'status']
|
||||
const filterVal = ['timestamp', 'title', 'type', 'importance', 'status']
|
||||
const data = this.formatJson(filterVal, this.list)
|
||||
excel.export_json_to_excel(tHeader, data, 'table-list')
|
||||
this.downloadLoading = false
|
||||
})
|
||||
},
|
||||
formatJson(filterVal, jsonData) {
|
||||
return jsonData.map(v => filterVal.map(j => {
|
||||
if (j === 'timestamp') {
|
||||
return parseTime(v[j])
|
||||
} else {
|
||||
return v[j]
|
||||
}
|
||||
}))
|
||||
},
|
||||
// 传入给load-select组件的函数
|
||||
loadServerPort({page = 1, more = false, keyword = ""} = {}) {
|
||||
if(this.loadServerPortQuery.licenseId==null || this.loadServerPortQuery.licenseId==''){
|
||||
this.more = false;
|
||||
this.$message({
|
||||
message: '请先选择License',
|
||||
type: 'warning'
|
||||
})
|
||||
},
|
||||
formatJson(filterVal, jsonData) {
|
||||
return jsonData.map(v => filterVal.map(j => {
|
||||
if (j === 'timestamp') {
|
||||
return parseTime(v[j])
|
||||
} else {
|
||||
return v[j]
|
||||
}
|
||||
}))
|
||||
return ;
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
this.loadServerPortQuery.page = page;
|
||||
this.loadServerPortQuery.keyword = keyword;
|
||||
// 访问后端接口API
|
||||
availablePortList(this.loadServerPortQuery).then(res => {
|
||||
let result = res.data;
|
||||
if (more) {
|
||||
this.serverPortList = [...this.serverPortList, ...result.data.records];
|
||||
} else {
|
||||
this.serverPortList = result.data.records;
|
||||
}
|
||||
|
||||
// this.loadServerPortQuery.page = result.data.current;
|
||||
let {total, current, size} = result.data;
|
||||
this.more = page * size < total;
|
||||
this.loadServerPortQuery.page = current;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<div class="app-container calendar-list-container">
|
||||
<div class="filter-container">
|
||||
<el-select v-model="listQuery.userId" placeholder="请选择用户" clearable>
|
||||
<el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id"/>
|
||||
</el-select>
|
||||
<el-select v-model="listQuery.licenseId" placeholder="请选择License" clearable>
|
||||
<el-option v-for="item in licenseList" :key="item.id" :label="item.name" :value="item.id"/>
|
||||
</el-select>
|
||||
<el-button type="primary" v-waves icon="el-icon-search" @click="handleFilter">{{$t('table.search')}}</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :key='tableKey' :data="list" v-loading="listLoading" element-loading-text="给我一点时间" border fit highlight-current-row style="width: 100%">
|
||||
<el-table-column type="index" width="100" :label="$t('table.id')"></el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.userName')" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.userName}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.licenseName')" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.licenseName}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.upFlow')" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.upFlowDesc}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.downFlow')" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.downFlowDesc}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.totalFlow')" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.totalFlowDesc}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="150px" align="center" :label="$t('table.date')">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.date | parseTime('{y}-{m}')}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination-container">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.current"
|
||||
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.size" layout="total, sizes, prev, pager, next, jumper" :total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { fetchLicenseFlowMonthReportList } from '@/api/report'
|
||||
import { userList } from '@/api/user'
|
||||
import waves from '@/directive/waves'
|
||||
import { licenseList } from '@/api/license'
|
||||
|
||||
export default {
|
||||
name: 'licenseFlowMonthReport',
|
||||
directives: {
|
||||
waves
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableKey: 0,
|
||||
list: null,
|
||||
total: null,
|
||||
listLoading: false,
|
||||
listQuery: {
|
||||
current: 1,
|
||||
size: 10,
|
||||
userId: undefined,
|
||||
licenseId: undefined
|
||||
},
|
||||
userList: [],
|
||||
licenseList: [],
|
||||
dialogVisible: false,
|
||||
selectRow: {}
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
this.getUserList()
|
||||
this.getLicenseList()
|
||||
},
|
||||
activated() {
|
||||
this.getUserList()
|
||||
this.getLicenseList()
|
||||
if (this.$route.query.userId) {
|
||||
this.listQuery.userId = this.$route.query.userId
|
||||
}
|
||||
if (this.$route.query.licenseId) {
|
||||
this.listQuery.licenseId = this.$route.query.licenseId
|
||||
}
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
this.listLoading = true
|
||||
fetchLicenseFlowMonthReportList(this.listQuery).then(response => {
|
||||
this.list = response.data.data.records
|
||||
this.total = response.data.data.total
|
||||
this.listLoading = false
|
||||
})
|
||||
},
|
||||
getUserList() {
|
||||
userList().then(response => {
|
||||
this.userList = response.data.data
|
||||
})
|
||||
},
|
||||
getLicenseList() {
|
||||
licenseList().then(response => {
|
||||
this.licenseList = response.data.data
|
||||
})
|
||||
},
|
||||
handleFilter() {
|
||||
this.listQuery.current = 1
|
||||
this.getList()
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
this.listQuery.size = val
|
||||
this.listQuery.current = 1
|
||||
this.getList()
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.listQuery.current = val
|
||||
this.getList()
|
||||
},
|
||||
handleShowClick(row) {
|
||||
console.log(row)
|
||||
},
|
||||
handleLookOver(row) {
|
||||
this.selectRow = row
|
||||
this.dialogVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.job-msg-div{
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<div class="app-container calendar-list-container">
|
||||
<div class="filter-container">
|
||||
<el-select v-model="listQuery.userId" placeholder="请选择用户" clearable>
|
||||
<el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id"/>
|
||||
</el-select>
|
||||
<el-button type="primary" v-waves icon="el-icon-search" @click="handleFilter">{{$t('table.search')}}</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :key='tableKey' :data="list" v-loading="listLoading" element-loading-text="给我一点时间" border fit highlight-current-row style="width: 100%">
|
||||
<el-table-column type="index" width="100" :label="$t('table.id')"></el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.userName')" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.userName}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.licenseName')" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.licenseName}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.upFlow')" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.upFlowDesc}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.downFlow')" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.downFlowDesc}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.totalFlow')" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.totalFlowDesc}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.actions')" min-width="230" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" @click="handleFlowMonthReportClick(scope.row)">流量月度明细</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination-container">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.current"
|
||||
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.size" layout="total, sizes, prev, pager, next, jumper" :total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { fetchLicenseFlowReportList } from '@/api/report'
|
||||
import { userList } from '@/api/user'
|
||||
import waves from '@/directive/waves'
|
||||
|
||||
export default {
|
||||
name: 'licenseFlowReport',
|
||||
directives: {
|
||||
waves
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableKey: 0,
|
||||
list: null,
|
||||
total: null,
|
||||
listLoading: false,
|
||||
listQuery: {
|
||||
current: 1,
|
||||
size: 10,
|
||||
jobId: undefined
|
||||
},
|
||||
userList: [],
|
||||
dialogVisible: false,
|
||||
selectRow: {}
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
this.getUserList()
|
||||
},
|
||||
activated() {
|
||||
this.getUserList()
|
||||
if (this.$route.query.jobId) {
|
||||
this.listQuery.jobId = this.$route.query.jobId
|
||||
this.getList()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
this.listLoading = true
|
||||
fetchLicenseFlowReportList(this.listQuery).then(response => {
|
||||
this.list = response.data.data.records
|
||||
this.total = response.data.data.total
|
||||
this.listLoading = false
|
||||
})
|
||||
},
|
||||
getUserList() {
|
||||
userList().then(response => {
|
||||
this.userList = response.data.data
|
||||
})
|
||||
},
|
||||
handleFilter() {
|
||||
this.listQuery.current = 1
|
||||
this.getList()
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
this.listQuery.size = val
|
||||
this.listQuery.current = 1
|
||||
this.getList()
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.listQuery.current = val
|
||||
this.getList()
|
||||
},
|
||||
handleShowClick(row) {
|
||||
console.log(row)
|
||||
},
|
||||
handleLookOver(row) {
|
||||
this.selectRow = row
|
||||
this.dialogVisible = true
|
||||
},
|
||||
handleFlowMonthReportClick(row) {
|
||||
this.$router.push({ path: '/report/licenseFlowMonthReport', query: { userId: row.userId, licenseId: row.licenseId }})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.job-msg-div{
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,128 @@
|
||||
<template>
|
||||
<div class="app-container calendar-list-container">
|
||||
<div class="filter-container">
|
||||
<el-select v-model="listQuery.userId" placeholder="请选择用户" clearable>
|
||||
<el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id"/>
|
||||
</el-select>
|
||||
<el-button type="primary" v-waves icon="el-icon-search" @click="handleFilter">{{$t('table.search')}}</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :key='tableKey' :data="list" v-loading="listLoading" element-loading-text="给我一点时间" border fit highlight-current-row style="width: 100%">
|
||||
<el-table-column type="index" width="100" :label="$t('table.id')"></el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.userName')" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.userName}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.upFlow')" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.upFlowDesc}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.downFlow')" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.downFlowDesc}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.totalFlow')" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.totalFlowDesc}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="150px" align="center" :label="$t('table.date')">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.date | parseTime('{y}-{m}')}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination-container">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.current"
|
||||
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.size" layout="total, sizes, prev, pager, next, jumper" :total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { fetchUserFlowMonthReportList } from '@/api/report'
|
||||
import { userList } from '@/api/user'
|
||||
import waves from '@/directive/waves' // 水波纹指令
|
||||
|
||||
export default {
|
||||
name: 'userFlowMonthReport',
|
||||
directives: {
|
||||
waves
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableKey: 0,
|
||||
list: null,
|
||||
total: null,
|
||||
listLoading: false,
|
||||
listQuery: {
|
||||
current: 1,
|
||||
size: 10,
|
||||
userId: undefined
|
||||
},
|
||||
userList: [],
|
||||
dialogVisible: false,
|
||||
selectRow: {}
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
this.getUserList()
|
||||
},
|
||||
activated() {
|
||||
this.getUserList()
|
||||
if (this.$route.query.userId) {
|
||||
this.listQuery.userId = this.$route.query.userId
|
||||
this.getList()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
this.listLoading = true
|
||||
fetchUserFlowMonthReportList(this.listQuery).then(response => {
|
||||
this.list = response.data.data.records
|
||||
this.total = response.data.data.total
|
||||
this.listLoading = false
|
||||
})
|
||||
},
|
||||
getUserList() {
|
||||
userList().then(response => {
|
||||
this.userList = response.data.data
|
||||
})
|
||||
},
|
||||
handleFilter() {
|
||||
this.listQuery.current = 1
|
||||
this.getList()
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
this.listQuery.size = val
|
||||
this.listQuery.current = 1
|
||||
this.getList()
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.listQuery.current = val
|
||||
this.getList()
|
||||
},
|
||||
handleShowClick(row) {
|
||||
console.log(row)
|
||||
},
|
||||
handleLookOver(row) {
|
||||
this.selectRow = row
|
||||
this.dialogVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.job-msg-div{
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<div class="app-container calendar-list-container">
|
||||
<div class="filter-container">
|
||||
<el-select v-model="listQuery.userId" placeholder="请选择用户" clearable>
|
||||
<el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id"/>
|
||||
</el-select>
|
||||
<el-button type="primary" v-waves icon="el-icon-search" @click="handleFilter">{{$t('table.search')}}</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :key='tableKey' :data="list" v-loading="listLoading" element-loading-text="给我一点时间" border fit highlight-current-row style="width: 100%">
|
||||
<el-table-column type="index" width="100" :label="$t('table.id')"></el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.userName')" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.userName}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.upFlow')" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.upFlowDesc}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.downFlow')" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.downFlowDesc}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.totalFlow')" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.totalFlowDesc}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.actions')" min-width="230" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" @click="handleFlowMonthReportClick(scope.row)">流量月度明细</el-button>
|
||||
<el-button size="mini" type="text" @click="handleLicenseFlowMonthReportClick(scope.row)">License流量月度明细</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination-container">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.current"
|
||||
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.size" layout="total, sizes, prev, pager, next, jumper" :total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { fetchUserFlowReportList } from '@/api/report'
|
||||
import { userList } from '@/api/user'
|
||||
import waves from '@/directive/waves' // 水波纹指令
|
||||
|
||||
export default {
|
||||
name: 'userFlowReport',
|
||||
directives: {
|
||||
waves
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableKey: 0,
|
||||
list: null,
|
||||
total: null,
|
||||
listLoading: false,
|
||||
listQuery: {
|
||||
current: 1,
|
||||
size: 10,
|
||||
userId: undefined
|
||||
},
|
||||
userList: [],
|
||||
dialogVisible: false,
|
||||
selectRow: {}
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
this.getUserList()
|
||||
},
|
||||
activated() {
|
||||
this.getUserList()
|
||||
if (this.$route.query.userId) {
|
||||
this.listQuery.userId = this.$route.query.userId
|
||||
this.getList()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
this.listLoading = true
|
||||
fetchUserFlowReportList(this.listQuery).then(response => {
|
||||
this.list = response.data.data.records
|
||||
this.total = response.data.data.total
|
||||
this.listLoading = false
|
||||
})
|
||||
},
|
||||
getUserList() {
|
||||
userList().then(response => {
|
||||
this.userList = response.data.data
|
||||
})
|
||||
},
|
||||
handleFilter() {
|
||||
this.listQuery.current = 1
|
||||
this.getList()
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
this.listQuery.size = val
|
||||
this.listQuery.current = 1
|
||||
this.getList()
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.listQuery.current = val
|
||||
this.getList()
|
||||
},
|
||||
handleShowClick(row) {
|
||||
console.log(row)
|
||||
},
|
||||
handleLookOver(row) {
|
||||
this.selectRow = row
|
||||
this.dialogVisible = true
|
||||
},
|
||||
handleFlowMonthReportClick(row) {
|
||||
this.$router.push({ path: '/report/userFlowMonthReport', query: { userId: row.userId }})
|
||||
},
|
||||
handleLicenseFlowMonthReportClick(row) {
|
||||
this.$router.push({ path: '/report/licenseFlowMonthReport', query: { userId: row.userId }})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.job-msg-div{
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -51,8 +51,8 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination-container">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.currentPage"
|
||||
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.current"
|
||||
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.size" layout="total, sizes, prev, pager, next, jumper" :total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
|
||||
@@ -114,8 +114,8 @@
|
||||
total: null,
|
||||
listLoading: true,
|
||||
listQuery: {
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
current: 1,
|
||||
size: 10,
|
||||
importance: undefined,
|
||||
title: undefined,
|
||||
type: undefined
|
||||
|
||||
@@ -0,0 +1,346 @@
|
||||
<template>
|
||||
<div class="app-container calendar-list-container">
|
||||
<div class="filter-container">
|
||||
<el-button class="filter-item" type="primary" v-waves icon="el-icon-search" @click="handleFilter">
|
||||
{{ $t('table.search') }}
|
||||
</el-button>
|
||||
<el-button class="filter-item" style="margin-left: 10px;" @click="handleCreate" type="primary"
|
||||
icon="el-icon-edit">{{ $t('table.add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :key='tableKey' :data="list" v-loading="listLoading" element-loading-text="给我一点时间" border fit
|
||||
highlight-current-row
|
||||
style="width: 100%">
|
||||
<el-table-column align="center" :label="$t('table.id')" width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.id }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.groupName')" width="200">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.name }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.groupPossessor')" width="200">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.possessorType == 1 ? "用户:" : (scope.row.possessorType == 2 ? "License:" : "全局") }} {{ scope.row.possessor }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="200" align="center" :label="$t('table.createTime')">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.createTime | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="200" align="center" :label="$t('table.updateTime')">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.updateTime | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- <el-table-column class-name="status-col" :label="$t('table.enableStatus')" width="150">-->
|
||||
<!-- <template slot-scope="scope">-->
|
||||
<!-- <el-tag :type="scope.row.enable | statusFilter">{{ scope.row.enable | statusName }}</el-tag>-->
|
||||
<!-- </template>-->
|
||||
<!-- </el-table-column>-->
|
||||
<el-table-column align="center" :label="$t('table.actions')" width="250" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<!-- <el-button v-if="scope.row.enable =='1'" size="mini" type="danger" @click="handleModifyStatus(scope.row,2)">-->
|
||||
<!-- {{ $t('table.disable') }}-->
|
||||
<!-- </el-button>-->
|
||||
<!-- <el-button v-if="scope.row.enable =='2'" size="mini" type="success" @click="handleModifyStatus(scope.row,1)">-->
|
||||
<!-- {{ $t('table.enable') }}-->
|
||||
<!-- </el-button>-->
|
||||
<!-- <el-button v-if="scope.row.status!='deleted'" size="mini" type="danger" @click="handleDelete(scope.row,'deleted')">{{$t('table.delete')}}</el-button>-->
|
||||
<ButtonPopover @handleCommitClick="handleDelete(scope.row)" style="margin-left: 10px"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pagination-container">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange"
|
||||
:current-pageInfo.sync="listQuery.current"
|
||||
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.size"
|
||||
layout="total, sizes, prev, pager, next, jumper" :total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
|
||||
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
|
||||
<el-form :rules="rules" ref="dataForm" :model="temp" label-position="left" label-width="70px"
|
||||
style='width: 400px; margin-left:50px;'>
|
||||
<el-form-item style="width: 350px" class="filter-item" :label="$t('table.groupName')" prop="name">
|
||||
<el-input v-model="temp.name"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('table.possessorType')" prop="possessorType">
|
||||
<el-select style="width: 280px" class="filter-item" v-model="temp.possessorType" placeholder="请选择"
|
||||
:disabled="dialogStatus=='update'">
|
||||
<el-option v-for="item in possessorTypeList" :key="item.id" :label="item.name" :value="item.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
<el-form-item v-show="temp.possessorType==1" :label="$t('table.userName')" prop="possessorId">
|
||||
<el-select style="width: 280px" class="filter-item" v-model="temp.possessorId" placeholder="请选择"
|
||||
:disabled="dialogStatus=='update'">
|
||||
<el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-show="temp.possessorType==2" :label="$t('table.license')" prop="possessorId">
|
||||
<el-select style="width: 280px" class="filter-item" v-model="temp.possessorId" placeholder="请选择"
|
||||
:disabled="dialogStatus=='update'">
|
||||
<el-option v-for="item in licenseList" :key="item.id" :label="item.name" :value="item.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogFormVisible = false">{{ $t('table.cancel') }}</el-button>
|
||||
<el-button v-if="dialogStatus=='create'" type="primary" @click="createData">{{ $t('table.confirm') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="Reading statistics" :visible.sync="dialogPvVisible">
|
||||
<el-table :data="pvData" border fit highlight-current-row style="width: 100%">
|
||||
<el-table-column prop="key" label="Channel"></el-table-column>
|
||||
<el-table-column prop="pv" label="Pv"></el-table-column>
|
||||
</el-table>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="dialogPvVisible = false">{{ $t('table.confirm') }}</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { fetchList, updateEnableStatus, createPortGroup, deleteGroup } from '@/api/portGroup'
|
||||
import waves from '@/directive/waves' // 水波纹指令
|
||||
import { parseTime } from '@/utils'
|
||||
import { userList } from '@/api/user'
|
||||
import ButtonPopover from '../../components/Button/buttonPopover'
|
||||
import { licenseList } from '../../api/license'
|
||||
|
||||
const calendarTypeOptions = [
|
||||
{ key: 'CN', display_name: 'China' },
|
||||
{ key: 'US', display_name: 'USA' },
|
||||
{ key: 'JP', display_name: 'Japan' },
|
||||
{ key: 'EU', display_name: 'Eurozone' }
|
||||
]
|
||||
|
||||
// arr to obj ,such as { CN : "China", US : "USA" }
|
||||
const calendarTypeKeyValue = calendarTypeOptions.reduce((acc, cur) => {
|
||||
acc[cur.key] = cur.display_name
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
export default {
|
||||
name: 'complexTable',
|
||||
directives: {
|
||||
waves
|
||||
},
|
||||
components: {
|
||||
ButtonPopover
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableKey: 0,
|
||||
list: null,
|
||||
total: null,
|
||||
listLoading: true,
|
||||
listQuery: {
|
||||
current: 1,
|
||||
size: 10,
|
||||
importance: undefined,
|
||||
title: undefined,
|
||||
type: undefined
|
||||
},
|
||||
importanceOptions: [1, 2, 3],
|
||||
calendarTypeOptions,
|
||||
sortOptions: [{ label: 'ID Ascending', key: '+id' }, { label: 'ID Descending', key: '-id' }],
|
||||
statusOptions: ['published', 'draft', 'deleted'],
|
||||
showReviewer: false,
|
||||
userList: [],
|
||||
licenseList: [],
|
||||
possessorTypeList: [
|
||||
{
|
||||
'id': 1,
|
||||
'name': '用户'
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'name': '通道'
|
||||
}
|
||||
],
|
||||
temp: {
|
||||
name: '',
|
||||
possessorType: 1,
|
||||
possessorId: 1
|
||||
|
||||
},
|
||||
dialogFormVisible: false,
|
||||
dialogStatus: '',
|
||||
textMap: {
|
||||
update: '编辑',
|
||||
create: '新建'
|
||||
},
|
||||
dialogPvVisible: false,
|
||||
pvData: [],
|
||||
rules: {
|
||||
port: [{ required: true, message: '端口必填', trigger: 'blur' }]
|
||||
},
|
||||
downloadLoading: false
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
statusName(status) {
|
||||
const statusMap = {
|
||||
1: '启用',
|
||||
2: '禁用'
|
||||
}
|
||||
return statusMap[status]
|
||||
},
|
||||
statusFilter(status) {
|
||||
const statusMap = {
|
||||
1: 'success',
|
||||
2: 'danger'
|
||||
}
|
||||
return statusMap[status]
|
||||
},
|
||||
typeFilter(type) {
|
||||
return calendarTypeKeyValue[type]
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
this.getUserList()
|
||||
this.getLicenseList()
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
this.listLoading = true
|
||||
fetchList(this.listQuery).then(response => {
|
||||
this.list = response.data.data.records
|
||||
this.total = response.data.data.total
|
||||
this.listLoading = false
|
||||
})
|
||||
},
|
||||
getUserList() {
|
||||
userList().then(response => {
|
||||
this.userList = response.data.data
|
||||
})
|
||||
},
|
||||
getLicenseList() {
|
||||
licenseList().then(response => {
|
||||
this.licenseList = response.data.data
|
||||
})
|
||||
},
|
||||
handleFilter() {
|
||||
this.listQuery.current = 1
|
||||
this.getList()
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
this.listQuery.size = val
|
||||
this.getList()
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.listQuery.current = val
|
||||
this.getList()
|
||||
},
|
||||
handleModifyStatus(row, enable) {
|
||||
console.log('route', this.$route)
|
||||
updateEnableStatus(row.id, enable).then(response => {
|
||||
if (response.data.data.code === 0) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success'
|
||||
})
|
||||
}
|
||||
this.getList()
|
||||
})
|
||||
},
|
||||
resetTemp() {
|
||||
this.temp = {
|
||||
id: undefined,
|
||||
importance: 1,
|
||||
remark: '',
|
||||
timestamp: new Date(),
|
||||
title: '',
|
||||
status: 'published',
|
||||
type: ''
|
||||
}
|
||||
},
|
||||
handleCreate() {
|
||||
this.resetTemp()
|
||||
this.dialogStatus = 'create'
|
||||
this.dialogFormVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs['dataForm'].clearValidate()
|
||||
})
|
||||
},
|
||||
createData() {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
createPortGroup(this.temp).then(response => {
|
||||
if (response.data.code === 0) {
|
||||
this.dialogFormVisible = false
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '创建成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
handleUpdate(row) {
|
||||
this.temp = Object.assign({}, row) // copy obj
|
||||
this.temp.timestamp = new Date(this.temp.timestamp)
|
||||
this.dialogStatus = 'update'
|
||||
this.dialogFormVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs['dataForm'].clearValidate()
|
||||
})
|
||||
},
|
||||
handleDelete(row) {
|
||||
deleteGroup(row.id).then(response => {
|
||||
if (response.data.code === 0) {
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '删除成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
},
|
||||
handleDownload() {
|
||||
this.downloadLoading = true
|
||||
import('@/vendor/Export2Excel').then(excel => {
|
||||
const tHeader = ['timestamp', 'title', 'type', 'importance', 'status']
|
||||
const filterVal = ['timestamp', 'title', 'type', 'importance', 'status']
|
||||
const data = this.formatJson(filterVal, this.list)
|
||||
excel.export_json_to_excel(tHeader, data, 'table-list')
|
||||
this.downloadLoading = false
|
||||
})
|
||||
},
|
||||
formatJson(filterVal, jsonData) {
|
||||
return jsonData.map(v => filterVal.map(j => {
|
||||
if (j === 'timestamp') {
|
||||
return parseTime(v[j])
|
||||
} else {
|
||||
return v[j]
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,12 +1,18 @@
|
||||
<template>
|
||||
<div class="app-container calendar-list-container">
|
||||
<div class="filter-container">
|
||||
<el-select v-model="listQuery.groupId" placeholder="请选择端口池分组" clearable>
|
||||
<el-option v-for="item in portGroupList" :key="item.id" :label="item.name" :value="item.id"/>
|
||||
</el-select>
|
||||
<el-button class="filter-item" type="primary" v-waves icon="el-icon-search" @click="handleFilter">{{$t('table.search')}}</el-button>
|
||||
<el-button class="filter-item" style="margin-left: 10px;" @click="handleCreate" type="primary" icon="el-icon-edit">{{$t('table.add')}}</el-button>
|
||||
<button-popover class="filter-item" style="margin-left: 10px;" size="medium" icon="el-icon-delete"
|
||||
:buttonText="$t('table.deleteBatch')" :disabled="checkBoxData.length==0" @handleCommitClick="handleDeleteBatch" />
|
||||
</div>
|
||||
|
||||
<el-table :key='tableKey' :data="list" v-loading="listLoading" element-loading-text="给我一点时间" border fit highlight-current-row
|
||||
style="width: 100%">
|
||||
style="width: 100%" @selection-change="onTableRowChange">
|
||||
<el-table-column align="center" width="40" type="selection" />
|
||||
<el-table-column align="center" :label="$t('table.id')" width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.id}}</span>
|
||||
@@ -17,6 +23,11 @@
|
||||
<span>{{scope.row.port}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.groupName')" width="200">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.groupName}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="200" align="center" :label="$t('table.createTime')">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.createTime | parseTime('{y}-{m}-{d} {h}:{i}')}}</span>
|
||||
@@ -34,6 +45,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column align="center" :label="$t('table.actions')" width="250" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="primary" size="mini" @click="handleUpdate(scope.row)">{{$t('table.edit')}}</el-button>
|
||||
<el-button v-if="scope.row.enable =='1'" size="mini" type="danger" @click="handleModifyStatus(scope.row,2)">{{$t('table.disable')}}</el-button>
|
||||
<el-button v-if="scope.row.enable =='2'" size="mini" type="success" @click="handleModifyStatus(scope.row,1)">{{$t('table.enable')}}</el-button>
|
||||
<!-- <el-button v-if="scope.row.status!='deleted'" size="mini" type="danger" @click="handleDelete(scope.row,'deleted')">{{$t('table.delete')}}</el-button>-->
|
||||
@@ -43,20 +55,29 @@
|
||||
</el-table>
|
||||
|
||||
<div class="pagination-container">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.currentPage"
|
||||
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.current"
|
||||
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.size" layout="total, sizes, prev, pager, next, jumper" :total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
|
||||
<el-dialog :title="textMap[dialogStatus]" :visible.sync="dialogFormVisible">
|
||||
<el-form :rules="rules" ref="dataForm" :model="temp" label-position="left" label-width="70px" style='width: 400px; margin-left:50px;'>
|
||||
<el-form-item :label="$t('table.port')" prop="port">
|
||||
<el-input v-model="temp.port"></el-input>
|
||||
<el-input :placeholder="$t('table.portOrRange')" v-model="temp.port" :disabled="dialogStatus=='update'"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('table.group')" prop="group">
|
||||
<el-select style="width: 330px" class="filter-item" v-model="temp.groupId">
|
||||
<el-option v-for="item in portGroupList" :key="item.id" :label="item.name" :value="item.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogFormVisible = false">{{$t('table.cancel')}}</el-button>
|
||||
<el-button v-if="dialogStatus=='create'" type="primary" @click="createData">{{$t('table.confirm')}}</el-button>
|
||||
<el-button v-else type="primary" @click="updateData">{{$t('table.confirm')}}</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
@@ -74,12 +95,13 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { fetchList, updateEnableStatus, createPortPool, deletePortPool } from '@/api/portPool'
|
||||
import waves from '@/directive/waves' // 水波纹指令
|
||||
import { parseTime } from '@/utils'
|
||||
import ButtonPopover from '../../components/Button/buttonPopover'
|
||||
import { createPortPool, deletePortPool, deleteBatchPortPool, fetchList, updateEnableStatus, updatePortPool } from '@/api/portPool'
|
||||
import { portGroupList } from '@/api/portGroup'
|
||||
import waves from '@/directive/waves' // 水波纹指令
|
||||
import { parseTime } from '@/utils'
|
||||
import ButtonPopover from '../../components/Button/buttonPopover'
|
||||
|
||||
const calendarTypeOptions = [
|
||||
const calendarTypeOptions = [
|
||||
{ key: 'CN', display_name: 'China' },
|
||||
{ key: 'US', display_name: 'USA' },
|
||||
{ key: 'JP', display_name: 'Japan' },
|
||||
@@ -107,11 +129,9 @@
|
||||
total: null,
|
||||
listLoading: true,
|
||||
listQuery: {
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
importance: undefined,
|
||||
title: undefined,
|
||||
type: undefined
|
||||
current: 1,
|
||||
size: 10,
|
||||
groupId: undefined
|
||||
},
|
||||
importanceOptions: [1, 2, 3],
|
||||
calendarTypeOptions,
|
||||
@@ -119,6 +139,7 @@
|
||||
statusOptions: ['published', 'draft', 'deleted'],
|
||||
showReviewer: false,
|
||||
temp: {
|
||||
groupId: 1,
|
||||
id: undefined,
|
||||
importance: 1,
|
||||
remark: '',
|
||||
@@ -133,12 +154,14 @@
|
||||
update: '编辑',
|
||||
create: '新建'
|
||||
},
|
||||
portGroupList: [],
|
||||
dialogPvVisible: false,
|
||||
pvData: [],
|
||||
rules: {
|
||||
port: [{ required: true, message: '端口必填', trigger: 'blur' }]
|
||||
},
|
||||
downloadLoading: false
|
||||
downloadLoading: false,
|
||||
checkBoxData:[], //表单勾选的行
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
@@ -161,7 +184,9 @@
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
// eslint-disable-next-line no-sequences
|
||||
this.getList(),
|
||||
this.getPortGroupList()
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
@@ -172,6 +197,11 @@
|
||||
this.listLoading = false
|
||||
})
|
||||
},
|
||||
getPortGroupList() {
|
||||
portGroupList().then(response => {
|
||||
this.portGroupList = response.data.data
|
||||
})
|
||||
},
|
||||
handleFilter() {
|
||||
this.listQuery.current = 1
|
||||
this.getList()
|
||||
@@ -204,13 +234,15 @@
|
||||
timestamp: new Date(),
|
||||
title: '',
|
||||
status: 'published',
|
||||
type: ''
|
||||
type: '',
|
||||
groupId: 1
|
||||
}
|
||||
},
|
||||
handleCreate() {
|
||||
this.resetTemp()
|
||||
this.dialogStatus = 'create'
|
||||
this.dialogFormVisible = true
|
||||
this.temp.groupId = 1
|
||||
this.$nextTick(() => {
|
||||
this.$refs['dataForm'].clearValidate()
|
||||
})
|
||||
@@ -242,6 +274,25 @@
|
||||
this.$refs['dataForm'].clearValidate()
|
||||
})
|
||||
},
|
||||
updateData() {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
const tempData = Object.assign({}, this.temp)
|
||||
updatePortPool(tempData).then(response => {
|
||||
if (response.data.code === 0) {
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '更新成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
this.dialogFormVisible = false
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
handleDelete(row) {
|
||||
deletePortPool(row.id).then(response => {
|
||||
if (response.data.code === 0) {
|
||||
@@ -273,6 +324,24 @@
|
||||
return v[j]
|
||||
}
|
||||
}))
|
||||
},
|
||||
onTableRowChange(val){ //表单勾选后赋值
|
||||
this.checkBoxData = val
|
||||
},
|
||||
handleDeleteBatch(){ //批量删除操作
|
||||
let ids = []
|
||||
this.checkBoxData.forEach(item => {ids.push(item.id)})
|
||||
deleteBatchPortPool(ids).then(response => {
|
||||
if (response.data.code === 0) {
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '删除成功',
|
||||
type: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<div class="app-container calendar-list-container">
|
||||
<div class="filter-container">
|
||||
<el-button class="filter-item" type="primary" v-waves icon="el-icon-search" @click="handleFilter">
|
||||
{{ $t('table.search') }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :key='tableKey' :data="list" v-loading="listLoading" element-loading-text="给我一点时间" border fit
|
||||
highlight-current-row
|
||||
style="width: 100%">
|
||||
<el-table-column align="center" :label="$t('table.protocalName')" width="150">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.name }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column class-name="status-col" :label="$t('table.supportStatus')" width="150">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.enable | statusFilter">{{scope.row.enable | statusName}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" :label="$t('table.remark')" width="500">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.remark }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pagination-container">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange"
|
||||
:current-pageInfo.sync="listQuery.current"
|
||||
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.size"
|
||||
layout="total, sizes, prev, pager, next, jumper" :total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { fetchList } from '@/api/protocal'
|
||||
import waves from '@/directive/waves' // 水波纹指令
|
||||
import { parseTime } from '@/utils'
|
||||
import ButtonPopover from '../../components/Button/buttonPopover'
|
||||
|
||||
const calendarTypeOptions = [
|
||||
{ key: 'CN', display_name: 'China' },
|
||||
{ key: 'US', display_name: 'USA' },
|
||||
{ key: 'JP', display_name: 'Japan' },
|
||||
{ key: 'EU', display_name: 'Eurozone' }
|
||||
]
|
||||
|
||||
// arr to obj ,such as { CN : "China", US : "USA" }
|
||||
const calendarTypeKeyValue = calendarTypeOptions.reduce((acc, cur) => {
|
||||
acc[cur.key] = cur.display_name
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
export default {
|
||||
name: 'complexTable',
|
||||
directives: {
|
||||
waves
|
||||
},
|
||||
components: {
|
||||
ButtonPopover
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableKey: 0,
|
||||
list: null,
|
||||
total: null,
|
||||
listLoading: true,
|
||||
listQuery: {
|
||||
current: 1,
|
||||
size: 10,
|
||||
importance: undefined,
|
||||
title: undefined,
|
||||
type: undefined
|
||||
},
|
||||
importanceOptions: [1, 2, 3],
|
||||
calendarTypeOptions,
|
||||
sortOptions: [{ label: 'ID Ascending', key: '+id' }, { label: 'ID Descending', key: '-id' }],
|
||||
statusOptions: ['published', 'draft', 'deleted'],
|
||||
showReviewer: false,
|
||||
possessorTypeList: [
|
||||
{
|
||||
'id': 1,
|
||||
'name': '用户'
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'name': '通道'
|
||||
}
|
||||
],
|
||||
temp: {
|
||||
name: '',
|
||||
possessorType: 1,
|
||||
possessorId: 1
|
||||
|
||||
},
|
||||
dialogFormVisible: false,
|
||||
dialogStatus: '',
|
||||
textMap: {
|
||||
update: '编辑',
|
||||
create: '新建'
|
||||
},
|
||||
dialogPvVisible: false,
|
||||
pvData: [],
|
||||
rules: {
|
||||
port: [{ required: true, message: '端口必填', trigger: 'blur' }]
|
||||
},
|
||||
downloadLoading: false
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
statusName(status) {
|
||||
const statusMap = {
|
||||
true: '支持',
|
||||
false: '不支持'
|
||||
}
|
||||
return statusMap[status]
|
||||
},
|
||||
statusFilter(status) {
|
||||
const statusMap = {
|
||||
true: 'success',
|
||||
false: 'danger'
|
||||
}
|
||||
return statusMap[status]
|
||||
},
|
||||
typeFilter(type) {
|
||||
return calendarTypeKeyValue[type]
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
this.listLoading = true
|
||||
fetchList(this.listQuery).then(response => {
|
||||
this.list = response.data.data.records
|
||||
this.total = response.data.data.total
|
||||
this.listLoading = false
|
||||
})
|
||||
},
|
||||
handleFilter() {
|
||||
this.listQuery.current = 1
|
||||
this.getList()
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
this.listQuery.size = val
|
||||
this.getList()
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.listQuery.current = val
|
||||
this.getList()
|
||||
},
|
||||
formatJson(filterVal, jsonData) {
|
||||
return jsonData.map(v => filterVal.map(j => {
|
||||
if (j === 'timestamp') {
|
||||
return parseTime(v[j])
|
||||
} else {
|
||||
return v[j]
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -50,8 +50,8 @@
|
||||
</el-table>
|
||||
|
||||
<div class="pagination-container">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.currentPage"
|
||||
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-pageInfo.sync="listQuery.current"
|
||||
:pageInfo-sizes="[10,20,30, 50]" :pageInfo-size="listQuery.size" layout="total, sizes, prev, pager, next, jumper" :total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
|
||||
@@ -128,8 +128,8 @@
|
||||
total: null,
|
||||
listLoading: true,
|
||||
listQuery: {
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
current: 1,
|
||||
size: 10,
|
||||
importance: undefined,
|
||||
title: undefined,
|
||||
type: undefined
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
FROM openjdk:8-jdk-alpine
|
||||
#同步时间
|
||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
|
||||
apk update && apk add wget unzip vim && apk add -U tzdata && \
|
||||
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
|
||||
RUN mkdir -p /root/neutrino-proxy/config
|
||||
WORKDIR /root/neutrino-proxy
|
||||
COPY ../neutrino-proxy-client/target/neutrino-proxy-client.jar /root/neutrino-proxy/neutrino-proxy-client.jar
|
||||
COPY ../neutrino-proxy-client/src/main/resources/app.yml /root/neutrino-proxy/config
|
||||
#VOLUME ["/root/neutrino-proxy"]
|
||||
ENTRYPOINT ["java","-jar","neutrino-proxy-client.jar","config=./config/app.yml"]
|
||||
|
||||
#docker run -it -d --restart=always --name np_client -e SERVER_IP=127.0.0.1
|
||||
@@ -4,7 +4,7 @@
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>neutrino-proxy</artifactId>
|
||||
<groupId>fun.asgc.neutrino</groupId>
|
||||
<groupId>org.dromara.neutrino-proxy</groupId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
@@ -14,13 +14,8 @@
|
||||
<artifactId>neutrino-proxy-client</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>fun.asgc.neutrino</groupId>-->
|
||||
<!-- <artifactId>neutrino-core</artifactId>-->
|
||||
<!-- <version>${revision}</version>-->
|
||||
<!-- </dependency>-->
|
||||
<dependency>
|
||||
<groupId>fun.asgc.neutrino</groupId>
|
||||
<groupId>org.dromara.neutrino-proxy</groupId>
|
||||
<artifactId>neutrino-proxy-core</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
@@ -30,52 +25,23 @@
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<!-- <build>-->
|
||||
<!-- <finalName>${artifactId}</finalName>-->
|
||||
<!-- <plugins>-->
|
||||
<!-- <plugin>-->
|
||||
<!-- <groupId>org.springframework.boot</groupId>-->
|
||||
<!-- <artifactId>spring-boot-maven-plugin</artifactId>-->
|
||||
<!-- <version>2.1.3.RELEASE</version>-->
|
||||
<!-- <configuration>-->
|
||||
<!-- <mainClass>fun.asgc.neutrino.proxy.client.ProxyClient</mainClass>-->
|
||||
<!-- </configuration>-->
|
||||
<!-- <executions>-->
|
||||
<!-- <execution>-->
|
||||
<!-- <goals>-->
|
||||
<!-- <goal>repackage</goal>-->
|
||||
<!-- </goals>-->
|
||||
<!-- </execution>-->
|
||||
<!-- </executions>-->
|
||||
<!-- </plugin>-->
|
||||
<!-- </plugins>-->
|
||||
<!-- </build>-->
|
||||
<build>
|
||||
<finalName>${project.artifactId}</finalName>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>${project.basedir}/src/main/resources</directory>
|
||||
<filtering>false</filtering>
|
||||
|
||||
<excludes>
|
||||
<exclude>app-dev.yml</exclude>
|
||||
</excludes>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<!-- 配置打包插件(并打包成胖包) -->
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<configuration>
|
||||
<appendAssemblyId>false</appendAssemblyId>
|
||||
<finalName>${artifactId}</finalName>
|
||||
<archive>
|
||||
<manifest>
|
||||
<!--这里指定要运行的main类-->
|
||||
<mainClass>fun.asgc.neutrino.proxy.client.ProxyClient</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
<descriptorRefs>
|
||||
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||
</descriptorRefs>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-assembly</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>solon-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.client.config;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/10/18
|
||||
*/
|
||||
@Data
|
||||
public class CustomConfig {
|
||||
private String jksPath;
|
||||
private String serverIp;
|
||||
private Integer serverPort;
|
||||
private Boolean sslEnable;
|
||||
private String licenseKey;
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package fun.asgc.neutrino.proxy.client.config;
|
||||
|
||||
import fun.asgc.neutrino.proxy.core.ProxyDataTypeEnum;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessage;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessageHandler;
|
||||
import fun.asgc.neutrino.proxy.core.dispatcher.DefaultDispatcher;
|
||||
import fun.asgc.neutrino.proxy.core.dispatcher.Dispatcher;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import org.noear.solon.Solon;
|
||||
import org.noear.solon.annotation.Bean;
|
||||
import org.noear.solon.annotation.Configuration;
|
||||
import org.noear.solon.core.bean.LifecycleBean;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 代理配置
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/10/8
|
||||
*/
|
||||
@Configuration
|
||||
public class ProxyConfiguration implements LifecycleBean {
|
||||
|
||||
@Override
|
||||
public void start() throws Throwable {
|
||||
List<ProxyMessageHandler> list = Solon.context().getBeansOfType(ProxyMessageHandler.class);
|
||||
Dispatcher<ChannelHandlerContext, ProxyMessage> dispatcher = new DefaultDispatcher<>("消息调度器", list,
|
||||
proxyMessage -> ProxyDataTypeEnum.of((int)proxyMessage.getType()) == null ?
|
||||
null : ProxyDataTypeEnum.of((int)proxyMessage.getType()).getName());
|
||||
Solon.context().wrapAndPut(Dispatcher.class, dispatcher);
|
||||
}
|
||||
|
||||
@Bean("bootstrap")
|
||||
public Bootstrap bootstrap() {
|
||||
return new Bootstrap();
|
||||
}
|
||||
|
||||
@Bean("realServerBootstrap")
|
||||
public Bootstrap realServerBootstrap() {
|
||||
return new Bootstrap();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
///**
|
||||
// * Copyright (c) 2022 aoshiguchen
|
||||
// *
|
||||
// * Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// * of this software and associated documentation files (the "Software"), to deal
|
||||
// * in the Software without restriction, including without limitation the rights
|
||||
// * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// * copies of the Software, and to permit persons to whom the Software is
|
||||
// * furnished to do so, subject to the following conditions:
|
||||
// *
|
||||
// * The above copyright notice and this permission notice shall be included in all
|
||||
// * copies or substantial portions of the Software.
|
||||
// *
|
||||
// * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// * SOFTWARE.
|
||||
// */
|
||||
//package fun.asgc.neutrino.proxy.client.core;
|
||||
//
|
||||
//import fun.asgc.neutrino.core.annotation.Autowired;
|
||||
//import fun.asgc.neutrino.core.annotation.Component;
|
||||
//import fun.asgc.neutrino.core.annotation.NonIntercept;
|
||||
//import fun.asgc.neutrino.core.annotation.Subscribe;
|
||||
//import fun.asgc.neutrino.core.base.event.ApplicationEvent;
|
||||
//import fun.asgc.neutrino.core.base.event.ApplicationEventReceiver;
|
||||
//import fun.asgc.neutrino.core.constant.AppLifeCycleStatusEnum;
|
||||
//import fun.asgc.neutrino.core.constant.MetaDataConstant;
|
||||
//import lombok.extern.slf4j.Slf4j;
|
||||
//
|
||||
///**
|
||||
// * 应用生命周期事件监听器
|
||||
// * @author: aoshiguchen
|
||||
// * @date: 2022/10/10
|
||||
// */
|
||||
//@Slf4j
|
||||
//@NonIntercept
|
||||
//@Component
|
||||
//@Subscribe(topic = MetaDataConstant.TOPIC_APP_LIFE_CYCLE)
|
||||
//public class ApplicationLifeCycleListener extends ApplicationEventReceiver<AppLifeCycleStatusEnum> {
|
||||
// @Autowired
|
||||
// private LicenseObtainService licenseObtainService;
|
||||
//
|
||||
// @Override
|
||||
// public void receive(ApplicationEvent<AppLifeCycleStatusEnum> msg) {
|
||||
// log.debug("ApplicationLifeCycleListener:{}", msg.data().getDesc());
|
||||
// if (msg.data() == AppLifeCycleStatusEnum.APP_STARTUP) {
|
||||
// licenseObtainService.start();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//}
|
||||
@@ -1,166 +0,0 @@
|
||||
///**
|
||||
// * Copyright (c) 2022 aoshiguchen
|
||||
// *
|
||||
// * Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// * of this software and associated documentation files (the "Software"), to deal
|
||||
// * in the Software without restriction, including without limitation the rights
|
||||
// * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// * copies of the Software, and to permit persons to whom the Software is
|
||||
// * furnished to do so, subject to the following conditions:
|
||||
// *
|
||||
// * The above copyright notice and this permission notice shall be included in all
|
||||
// * copies or substantial portions of the Software.
|
||||
// *
|
||||
// * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// * SOFTWARE.
|
||||
// */
|
||||
//package fun.asgc.neutrino.proxy.client.core;
|
||||
//
|
||||
//import com.alibaba.fastjson.JSONObject;
|
||||
//import fun.asgc.neutrino.core.base.CustomThreadFactory;
|
||||
//import fun.asgc.neutrino.core.util.ArrayUtil;
|
||||
//import fun.asgc.neutrino.core.util.FileUtil;
|
||||
//import fun.asgc.neutrino.core.util.StringUtil;
|
||||
//import fun.asgc.neutrino.proxy.client.config.CustomConfig;
|
||||
//import fun.asgc.neutrino.proxy.client.config.ProxyConfig;
|
||||
//import lombok.extern.slf4j.Slf4j;
|
||||
//import org.noear.solon.annotation.Component;
|
||||
//import org.noear.solon.annotation.Inject;
|
||||
//
|
||||
//import java.util.HashMap;
|
||||
//import java.util.Map;
|
||||
//import java.util.Scanner;
|
||||
//import java.util.concurrent.Executors;
|
||||
//import java.util.concurrent.ScheduledExecutorService;
|
||||
//import java.util.concurrent.TimeUnit;
|
||||
//import java.util.concurrent.locks.ReentrantLock;
|
||||
//
|
||||
///**
|
||||
// * license获取服务
|
||||
// * @author: aoshiguchen
|
||||
// * @date: 2022/9/4
|
||||
// */
|
||||
//@Slf4j
|
||||
//@Component
|
||||
//public class LicenseObtainService {
|
||||
// /**
|
||||
// * 调度器
|
||||
// */
|
||||
// private static final ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor(new CustomThreadFactory("LicenseObtain"));
|
||||
// private ReentrantLock runLock = new ReentrantLock();
|
||||
// private Scanner scanner = new Scanner(System.in);
|
||||
// @Inject
|
||||
// private ProxyConfig proxyConfig;
|
||||
// @Inject
|
||||
// private ProxyClientService proxyClientService;
|
||||
//
|
||||
// public void start() {
|
||||
// scheduledExecutor.scheduleWithFixedDelay(() -> {
|
||||
// boolean lock = runLock.tryLock();
|
||||
// try {
|
||||
// if (lock) {
|
||||
// this.process();
|
||||
// }
|
||||
// } finally {
|
||||
// if (runLock.isHeldByCurrentThread()){
|
||||
// runLock.unlock();
|
||||
// }
|
||||
// }
|
||||
// }, 0, proxyConfig.getClient().getObtainLicenseInterval(), TimeUnit.SECONDS);
|
||||
// }
|
||||
//
|
||||
// public void stop() {
|
||||
// scheduledExecutor.shutdown();
|
||||
// log.info("licenseKey获取任务停止");
|
||||
// }
|
||||
//
|
||||
// public void process() {
|
||||
//
|
||||
//
|
||||
// proxyClientService.start();
|
||||
// }
|
||||
//
|
||||
// private String
|
||||
//
|
||||
// private CustomConfig getCustomConfigByCliParams() {
|
||||
// CustomConfig customConfig = new CustomConfig();
|
||||
// // 默认无需输入
|
||||
// customConfig.setJksPath(environment.getMainArgsForString("jksPath", proxyConfig.getClient().getJksPath()));
|
||||
// customConfig.setServerIp(environment.getMainArgsForString("serverIp", proxyConfig.getClient().getServerIp()));
|
||||
// customConfig.setServerPort(environment.getMainArgsForInteger("serverPort", proxyConfig.getClient().getServerPort()));
|
||||
// customConfig.setSslEnable(environment.getMainArgsForBoolean("sslEnable", proxyConfig.getClient().getSslEnable()));
|
||||
// customConfig.setLicenseKey(environment.getMainArgsForString("licenseKey"));
|
||||
// // 启动参数指定了license,则无需后续处理
|
||||
// if (StringUtil.notEmpty(customConfig.getLicenseKey())) {
|
||||
//// FileUtil.write("./.neutrino-proxy-client.json", JSONObject.toJSONString(customConfig, SerializerFeature.PrettyFormat));
|
||||
// return customConfig;
|
||||
// }
|
||||
//
|
||||
// // 启动参数未指定license,则优先读取外部配置
|
||||
// String config = FileUtil.readContentAsString("./.neutrino-proxy-client.json");
|
||||
// if (StringUtil.notEmpty(config)) {
|
||||
// try {
|
||||
// CustomConfig tmp = JSONObject.parseObject(config, CustomConfig.class);
|
||||
// if (!StringUtil.isEmpty(tmp.getJksPath())) {
|
||||
// customConfig.setJksPath(tmp.getJksPath());
|
||||
// }
|
||||
// if (!StringUtil.isEmpty(tmp.getServerIp())) {
|
||||
// customConfig.setServerIp(tmp.getServerIp());
|
||||
// }
|
||||
// if (null != tmp.getServerPort()) {
|
||||
// customConfig.setServerPort(tmp.getServerPort());
|
||||
// }
|
||||
// if (null != tmp.getSslEnable()) {
|
||||
// customConfig.setSslEnable(tmp.getSslEnable());
|
||||
// }
|
||||
// if (!StringUtil.isEmpty(tmp.getLicenseKey())) {
|
||||
// customConfig.setLicenseKey(tmp.getLicenseKey());
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// log.error("file '.neutrino-proxy-client.json' config exception!", e);
|
||||
// }
|
||||
// if (StringUtil.notEmpty(customConfig.getLicenseKey())) {
|
||||
//// FileUtil.write("./.neutrino-proxy-client.json", JSONObject.toJSONString(customConfig, SerializerFeature.PrettyFormat));
|
||||
// return customConfig;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// String license = "";
|
||||
// while (StringUtil.isEmpty(license)) {
|
||||
// System.out.print("Please input license:");
|
||||
// license = scanner.next();
|
||||
// }
|
||||
// customConfig.setLicenseKey(license);
|
||||
//// FileUtil.write("./.neutrino-proxy-client.json", JSONObject.toJSONString(customConfig, SerializerFeature.PrettyFormat));
|
||||
//
|
||||
// return customConfig;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 获取命令行参数
|
||||
// * @param args
|
||||
// * @return
|
||||
// */
|
||||
// private Map<String, String> getCliParams(String[] args) {
|
||||
// Map<String, String> res = new HashMap<>();
|
||||
// if (ArrayUtil.notEmpty(args)) {
|
||||
// for (String item : args) {
|
||||
// if (StringUtil.isEmpty(item) || !item.contains("=")) {
|
||||
// continue;
|
||||
// }
|
||||
// int index = item.indexOf("=");
|
||||
// if (index <= 0 || index == item.length() - 1) {
|
||||
// continue;
|
||||
// }
|
||||
// res.put(item.substring(0, index), item.substring(index + 1));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return res;
|
||||
// }
|
||||
//}
|
||||
@@ -1,196 +0,0 @@
|
||||
package fun.asgc.neutrino.proxy.client.core;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import fun.asgc.neutrino.proxy.client.config.ProxyConfig;
|
||||
import fun.asgc.neutrino.proxy.client.util.ProxyUtil;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessage;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessageDecoder;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessageEncoder;
|
||||
import fun.asgc.neutrino.proxy.core.util.FileUtil;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
import io.netty.handler.timeout.IdleStateHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.noear.solon.Solon;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Init;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import java.io.InputStream;
|
||||
import java.security.KeyStore;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 客户端服务
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ProxyClientService {
|
||||
@Inject
|
||||
private ProxyConfig proxyConfig;
|
||||
@Inject("bootstrap")
|
||||
private Bootstrap bootstrap;
|
||||
@Inject("realServerBootstrap")
|
||||
private Bootstrap realServerBootstrap;
|
||||
private volatile Channel channel;
|
||||
/**
|
||||
* 重连间隔(秒)
|
||||
*/
|
||||
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);
|
||||
|
||||
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
|
||||
realServerBootstrap.group(workerGroup);
|
||||
realServerBootstrap.channel(NioSocketChannel.class);
|
||||
realServerBootstrap.handler(new ChannelInitializer<SocketChannel>() {
|
||||
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
ch.pipeline().addLast(new RealServerChannelHandler());
|
||||
}
|
||||
});
|
||||
|
||||
bootstrap.group(workerGroup);
|
||||
bootstrap.channel(NioSocketChannel.class);
|
||||
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
|
||||
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
if (proxyConfig.getClient().getSslEnable()) {
|
||||
ch.pipeline().addLast(createSslHandler());
|
||||
}
|
||||
|
||||
ch.pipeline().addLast(new ProxyMessageDecoder(proxyConfig.getProtocol().getMaxFrameLength(),
|
||||
proxyConfig.getProtocol().getLengthFieldOffset(), proxyConfig.getProtocol().getLengthFieldLength(),
|
||||
proxyConfig.getProtocol().getLengthAdjustment(), proxyConfig.getProtocol().getInitialBytesToStrip()));
|
||||
ch.pipeline().addLast(new ProxyMessageEncoder());
|
||||
ch.pipeline().addLast(new IdleStateHandler(proxyConfig.getProtocol().getReadIdleTime(), proxyConfig.getProtocol().getWriteIdleTime(), proxyConfig.getProtocol().getAllIdleTimeSeconds()));
|
||||
ch.pipeline().addLast(new ClientChannelHandler());
|
||||
}
|
||||
});
|
||||
this.start();
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (StrUtil.isEmpty(proxyConfig.getClient().getServerIp())) {
|
||||
log.error("not found server-ip config.");
|
||||
Solon.stop();
|
||||
return;
|
||||
}
|
||||
if (null == proxyConfig.getClient().getServerPort()) {
|
||||
log.error("not found server-port config.");
|
||||
Solon.stop();
|
||||
return;
|
||||
}
|
||||
if (null != proxyConfig.getClient().getSslEnable() && proxyConfig.getClient().getSslEnable()
|
||||
&& StrUtil.isEmpty(proxyConfig.getClient().getJksPath())) {
|
||||
log.error("not found jks-path config.");
|
||||
Solon.stop();
|
||||
return;
|
||||
}
|
||||
if (StrUtil.isEmpty(proxyConfig.getClient().getLicenseKey())) {
|
||||
log.error("not found license-key config.");
|
||||
Solon.stop();
|
||||
return;
|
||||
}
|
||||
if (null == channel || !channel.isActive()) {
|
||||
try {
|
||||
connectProxyServer();
|
||||
} catch (Exception e) {
|
||||
log.error("client start error", e);
|
||||
}
|
||||
} else {
|
||||
channel.writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getClient().getLicenseKey()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接代理服务器
|
||||
*/
|
||||
private void connectProxyServer() throws InterruptedException {
|
||||
bootstrap.connect(proxyConfig.getClient().getServerIp(), proxyConfig.getClient().getServerPort())
|
||||
.addListener(new ChannelFutureListener() {
|
||||
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
channel = future.channel();
|
||||
// 连接成功,向服务器发送客户端认证信息(licenseKey)
|
||||
ProxyUtil.setCmdChannel(future.channel());
|
||||
future.channel().writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getClient().getLicenseKey()));
|
||||
log.info("连接代理服务成功. channelId:{}", future.channel().id().asLongText());
|
||||
|
||||
reconnectServiceEnable = true;
|
||||
reconnectCount = 0;
|
||||
} else {
|
||||
log.info("连接代理服务失败!");
|
||||
}
|
||||
}
|
||||
}).sync();
|
||||
}
|
||||
|
||||
private ChannelHandler createSslHandler() {
|
||||
try {
|
||||
InputStream jksInputStream = FileUtil.getInputStream(proxyConfig.getClient().getJksPath());
|
||||
|
||||
SSLContext clientContext = SSLContext.getInstance("TLS");
|
||||
final KeyStore ks = KeyStore.getInstance("JKS");
|
||||
ks.load(jksInputStream, proxyConfig.getClient().getKeyStorePassword().toCharArray());
|
||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
tmf.init(ks);
|
||||
TrustManager[] trustManagers = tmf.getTrustManagers();
|
||||
clientContext.init(null, trustManagers, null);
|
||||
|
||||
SSLEngine sslEngine = clientContext.createSSLEngine();
|
||||
sslEngine.setUseClientMode(true);
|
||||
|
||||
return new SslHandler(sslEngine);
|
||||
} catch (Exception e) {
|
||||
log.error("创建SSL处理器失败", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected synchronized void reconnect() {
|
||||
if (!reconnectServiceEnable) {
|
||||
return;
|
||||
}
|
||||
if (null != channel && channel.isActive()) {
|
||||
return;
|
||||
}
|
||||
log.info("客户端重连 seq:{}", ++reconnectCount);
|
||||
try {
|
||||
connectProxyServer();
|
||||
} catch (Exception e) {
|
||||
log.error("重连异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package fun.asgc.neutrino.proxy.client.handler;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import fun.asgc.neutrino.proxy.core.Constants;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessage;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessageHandler;
|
||||
import fun.asgc.neutrino.proxy.core.dispatcher.Match;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.noear.solon.annotation.Component;
|
||||
|
||||
/**
|
||||
* 认证信息处理器
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/9/4
|
||||
*/
|
||||
@Slf4j
|
||||
@Match(type = Constants.ProxyDataTypeName.AUTH)
|
||||
@Component
|
||||
public class ProxyMessageAuthHandler implements ProxyMessageHandler {
|
||||
@Override
|
||||
public void handle(ChannelHandlerContext context, ProxyMessage proxyMessage) {
|
||||
String info = proxyMessage.getInfo();
|
||||
JSONObject data = JSONObject.parseObject(info);
|
||||
Integer code = data.getInteger("code");
|
||||
log.info("认证结果:{}", info);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package fun.asgc.neutrino.proxy.client;
|
||||
package org.dromara.neutrinoproxy.client;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.noear.solon.Solon;
|
||||
import org.noear.solon.SolonApp;
|
||||
import org.noear.solon.annotation.SolonMain;
|
||||
|
||||
/**
|
||||
@@ -10,17 +10,18 @@ import org.noear.solon.annotation.SolonMain;
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
@Slf4j
|
||||
@SolonMain
|
||||
public class ProxyClient {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Solon.start(ProxyClient.class, args, app -> {
|
||||
setAlias("neutrino.proxy.client.serverIp", "serverIp");
|
||||
setAlias("neutrino.proxy.client.serverPort", "serverPort");
|
||||
setAlias("neutrino.proxy.client.sslEnable", "sslEnable");
|
||||
setAlias("neutrino.proxy.client.jksPath", "jksPath");
|
||||
setAlias("neutrino.proxy.client.keyStorePassword", "keyStorePassword");
|
||||
setAlias("neutrino.proxy.client.licenseKey", "licenseKey");
|
||||
setAlias("neutrino.proxy.tunnel.serverIp", "serverIp");
|
||||
setAlias("neutrino.proxy.tunnel.serverPort", "serverPort");
|
||||
setAlias("neutrino.proxy.tunnel.sslEnable", "sslEnable");
|
||||
setAlias("neutrino.proxy.tunnel.jksPath", "jksPath");
|
||||
setAlias("neutrino.proxy.tunnel.keyStorePassword", "keyStorePassword");
|
||||
setAlias("neutrino.proxy.tunnel.licenseKey", "licenseKey");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -35,4 +36,5 @@ public class ProxyClient {
|
||||
Solon.cfg().put(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.dromara.neutrinoproxy.client.config;
|
||||
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.aot.RuntimeNativeMetadata;
|
||||
import org.noear.solon.aot.RuntimeNativeRegistrar;
|
||||
import org.noear.solon.aot.hint.MemberCategory;
|
||||
import org.noear.solon.core.AppContext;
|
||||
|
||||
/**
|
||||
* @author songyinyin
|
||||
* @since 2023/10/21 22:50
|
||||
*/
|
||||
@Component
|
||||
public class NeutrinoClientRuntimeNativeRegistrar implements RuntimeNativeRegistrar {
|
||||
@Override
|
||||
public void register(AppContext context, RuntimeNativeMetadata metadata) {
|
||||
metadata.registerResourceInclude("test.jks");
|
||||
|
||||
metadata.registerReflection(ProxyConfig.Protocol.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
|
||||
metadata.registerReflection(ProxyConfig.Client.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
|
||||
metadata.registerReflection(ProxyConfig.Tunnel.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
|
||||
metadata.registerReflection(ProxyConfig.Tcp.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
|
||||
metadata.registerReflection(ProxyConfig.Udp.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
|
||||
metadata.registerReflection(ProxyConfig.Reconnection.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package fun.asgc.neutrino.proxy.client.config;
|
||||
package org.dromara.neutrinoproxy.client.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.noear.solon.annotation.Component;
|
||||
@@ -14,6 +14,8 @@ import org.noear.solon.annotation.Inject;
|
||||
public class ProxyConfig {
|
||||
@Inject("${neutrino.proxy.protocol}")
|
||||
private Protocol protocol;
|
||||
@Inject("${neutrino.proxy.tunnel}")
|
||||
private Tunnel tunnel;
|
||||
@Inject("${neutrino.proxy.client}")
|
||||
private Client client;
|
||||
|
||||
@@ -30,7 +32,7 @@ public class ProxyConfig {
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Client {
|
||||
public static class Tunnel {
|
||||
private String keyStorePassword;
|
||||
private String jksPath;
|
||||
private String serverIp;
|
||||
@@ -38,5 +40,35 @@ public class ProxyConfig {
|
||||
private Boolean sslEnable;
|
||||
private Integer obtainLicenseInterval;
|
||||
private String licenseKey;
|
||||
private Integer threadCount;
|
||||
private String clientId;
|
||||
private Boolean transferLogEnable;
|
||||
private Boolean heartbeatLogEnable;
|
||||
private Reconnection reconnection;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Client {
|
||||
// private Tcp tcp;
|
||||
private Udp udp;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Reconnection {
|
||||
private Integer intervalSeconds;
|
||||
private Boolean unlimited;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Tcp {
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Udp {
|
||||
private Integer bossThreadCount;
|
||||
private Integer workThreadCount;
|
||||
private String puppetPortRange;
|
||||
private Boolean transferLogEnable;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
package org.dromara.neutrinoproxy.client.config;
|
||||
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioDatagramChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import io.netty.handler.timeout.IdleStateHandler;
|
||||
import org.dromara.neutrinoproxy.client.core.*;
|
||||
import org.dromara.neutrinoproxy.client.util.ProxyUtil;
|
||||
import org.dromara.neutrinoproxy.core.*;
|
||||
import org.dromara.neutrinoproxy.core.aot.NeutrinoCoreRuntimeNativeRegistrar;
|
||||
import org.dromara.neutrinoproxy.core.dispatcher.DefaultDispatcher;
|
||||
import org.dromara.neutrinoproxy.core.dispatcher.Dispatcher;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import org.noear.solon.Solon;
|
||||
import org.noear.solon.annotation.Bean;
|
||||
import org.noear.solon.annotation.Configuration;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
import org.noear.solon.core.bean.LifecycleBean;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 代理配置
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/10/8
|
||||
*/
|
||||
@Configuration
|
||||
public class ProxyConfiguration implements LifecycleBean {
|
||||
|
||||
@Override
|
||||
public void start() throws Throwable {
|
||||
List<ProxyMessageHandler> list = Solon.context().getBeansOfType(ProxyMessageHandler.class);
|
||||
Dispatcher<ChannelHandlerContext, ProxyMessage> dispatcher = new DefaultDispatcher<>("MessageDispatcher", list,
|
||||
proxyMessage -> ProxyDataTypeEnum.of((int)proxyMessage.getType()) == null ?
|
||||
null : ProxyDataTypeEnum.of((int)proxyMessage.getType()).getName());
|
||||
Solon.context().wrapAndPut(Dispatcher.class, dispatcher);
|
||||
}
|
||||
|
||||
@Bean("tunnelWorkGroup")
|
||||
public NioEventLoopGroup tunnelWorkGroup(@Inject ProxyConfig proxyConfig) {
|
||||
return new NioEventLoopGroup(proxyConfig.getTunnel().getThreadCount());
|
||||
}
|
||||
|
||||
@Bean("tcpRealServerWorkGroup")
|
||||
public NioEventLoopGroup tcpRealServerWorkGroup(@Inject ProxyConfig proxyConfig) {
|
||||
// 暂时先公用此配置
|
||||
return new NioEventLoopGroup(proxyConfig.getTunnel().getThreadCount());
|
||||
}
|
||||
|
||||
@Bean("udpServerGroup")
|
||||
public NioEventLoopGroup udpServerGroup(@Inject ProxyConfig proxyConfig) {
|
||||
// 暂时先公用此配置
|
||||
return new NioEventLoopGroup(proxyConfig.getClient().getUdp().getBossThreadCount());
|
||||
}
|
||||
|
||||
@Bean("udpWorkGroup")
|
||||
public NioEventLoopGroup udpWorkGroup(@Inject ProxyConfig proxyConfig) {
|
||||
// 暂时先公用此配置
|
||||
return new NioEventLoopGroup(proxyConfig.getClient().getUdp().getWorkThreadCount());
|
||||
}
|
||||
|
||||
@Bean("cmdTunnelBootstrap")
|
||||
public Bootstrap cmdTunnelBootstrap(@Inject ProxyConfig proxyConfig,
|
||||
@Inject("tunnelWorkGroup") NioEventLoopGroup tunnelWorkGroup) {
|
||||
Bootstrap bootstrap = new Bootstrap();
|
||||
bootstrap.group(tunnelWorkGroup);
|
||||
bootstrap.channel(NioSocketChannel.class);
|
||||
// bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000);
|
||||
// bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
|
||||
// /**
|
||||
// * TCP/IP协议中,无论发送多少数据,总是要在数据前面加上协议头,同时,对方接收到数据,也需要发送ACK表示确认。为了尽可能的利用网络带宽,TCP总是希望尽可能的发送足够大的数据。(一个连接会设置MSS参数,因此,TCP/IP希望每次都能够以MSS尺寸的数据块来发送数据)。
|
||||
// * Nagle算法就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。
|
||||
// */
|
||||
// bootstrap.option(ChannelOption.TCP_NODELAY, true);
|
||||
bootstrap.remoteAddress(InetSocketAddress.createUnresolved(proxyConfig.getTunnel().getServerIp(), proxyConfig.getTunnel().getServerPort()));
|
||||
|
||||
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
|
||||
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
if (proxyConfig.getTunnel().getSslEnable()) {
|
||||
ch.pipeline().addLast(ProxyUtil.createSslHandler(proxyConfig));
|
||||
}
|
||||
if (null != proxyConfig.getTunnel().getTransferLogEnable() && proxyConfig.getTunnel().getTransferLogEnable()) {
|
||||
ch.pipeline().addFirst(new LoggingHandler(CmdChannelHandler.class));
|
||||
}
|
||||
ch.pipeline().addLast(new ProxyMessageDecoder(proxyConfig.getProtocol().getMaxFrameLength(),
|
||||
proxyConfig.getProtocol().getLengthFieldOffset(), proxyConfig.getProtocol().getLengthFieldLength(),
|
||||
proxyConfig.getProtocol().getLengthAdjustment(), proxyConfig.getProtocol().getInitialBytesToStrip()));
|
||||
ch.pipeline().addLast(new ProxyMessageEncoder());
|
||||
ch.pipeline().addLast(new IdleStateHandler(proxyConfig.getProtocol().getReadIdleTime(), proxyConfig.getProtocol().getWriteIdleTime(), proxyConfig.getProtocol().getAllIdleTimeSeconds()));
|
||||
ch.pipeline().addLast(new CmdChannelHandler());
|
||||
}
|
||||
});
|
||||
return bootstrap;
|
||||
}
|
||||
|
||||
@Bean("tcpProxyTunnelBootstrap")
|
||||
public Bootstrap tcpProxyTunnelBootstrap(@Inject ProxyConfig proxyConfig,
|
||||
@Inject("tunnelWorkGroup") NioEventLoopGroup tunnelWorkGroup) {
|
||||
Bootstrap bootstrap = new Bootstrap();
|
||||
bootstrap.group(tunnelWorkGroup);
|
||||
bootstrap.channel(NioSocketChannel.class);
|
||||
bootstrap.remoteAddress(InetSocketAddress.createUnresolved(proxyConfig.getTunnel().getServerIp(), proxyConfig.getTunnel().getServerPort()));
|
||||
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
|
||||
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
if (proxyConfig.getTunnel().getSslEnable()) {
|
||||
ch.pipeline().addLast(ProxyUtil.createSslHandler(proxyConfig));
|
||||
}
|
||||
if (null != proxyConfig.getTunnel().getTransferLogEnable() && proxyConfig.getTunnel().getTransferLogEnable()) {
|
||||
ch.pipeline().addFirst(new LoggingHandler(TcpProxyChannelHandler.class));
|
||||
}
|
||||
ch.pipeline().addLast(new ProxyMessageDecoder(proxyConfig.getProtocol().getMaxFrameLength(),
|
||||
proxyConfig.getProtocol().getLengthFieldOffset(), proxyConfig.getProtocol().getLengthFieldLength(),
|
||||
proxyConfig.getProtocol().getLengthAdjustment(), proxyConfig.getProtocol().getInitialBytesToStrip()));
|
||||
ch.pipeline().addLast(new ProxyMessageEncoder());
|
||||
ch.pipeline().addLast(new IdleStateHandler(proxyConfig.getProtocol().getReadIdleTime(), proxyConfig.getProtocol().getWriteIdleTime(), proxyConfig.getProtocol().getAllIdleTimeSeconds()));
|
||||
ch.pipeline().addLast(new TcpProxyChannelHandler());
|
||||
}
|
||||
});
|
||||
return bootstrap;
|
||||
}
|
||||
|
||||
@Bean("udpProxyTunnelBootstrap")
|
||||
public Bootstrap udpProxyTunnelBootstrap(@Inject ProxyConfig proxyConfig,
|
||||
@Inject("tunnelWorkGroup") NioEventLoopGroup tunnelWorkGroup) {
|
||||
Bootstrap bootstrap = new Bootstrap();
|
||||
bootstrap.group(tunnelWorkGroup);
|
||||
bootstrap.channel(NioSocketChannel.class);
|
||||
bootstrap.remoteAddress(InetSocketAddress.createUnresolved(proxyConfig.getTunnel().getServerIp(), proxyConfig.getTunnel().getServerPort()));
|
||||
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
|
||||
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
if (proxyConfig.getTunnel().getSslEnable()) {
|
||||
ch.pipeline().addLast(ProxyUtil.createSslHandler(proxyConfig));
|
||||
}
|
||||
if (null != proxyConfig.getTunnel().getTransferLogEnable() && proxyConfig.getTunnel().getTransferLogEnable()) {
|
||||
ch.pipeline().addFirst(new LoggingHandler(TcpProxyChannelHandler.class));
|
||||
}
|
||||
ch.pipeline().addLast(new ProxyMessageDecoder(proxyConfig.getProtocol().getMaxFrameLength(),
|
||||
proxyConfig.getProtocol().getLengthFieldOffset(), proxyConfig.getProtocol().getLengthFieldLength(),
|
||||
proxyConfig.getProtocol().getLengthAdjustment(), proxyConfig.getProtocol().getInitialBytesToStrip()));
|
||||
ch.pipeline().addLast(new ProxyMessageEncoder());
|
||||
ch.pipeline().addLast(new IdleStateHandler(proxyConfig.getProtocol().getReadIdleTime(), proxyConfig.getProtocol().getWriteIdleTime(), proxyConfig.getProtocol().getAllIdleTimeSeconds()));
|
||||
ch.pipeline().addLast(new UdpProxyChannelHandler());
|
||||
}
|
||||
});
|
||||
return bootstrap;
|
||||
}
|
||||
|
||||
@Bean("realServerBootstrap")
|
||||
public Bootstrap realServerBootstrap(@Inject ProxyConfig proxyConfig,
|
||||
@Inject("tcpRealServerWorkGroup") NioEventLoopGroup tcpRealServerWorkGroup
|
||||
) {
|
||||
Bootstrap bootstrap = new Bootstrap();
|
||||
bootstrap.group(tcpRealServerWorkGroup);
|
||||
bootstrap.channel(NioSocketChannel.class);
|
||||
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
|
||||
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
if (null != proxyConfig.getTunnel().getTransferLogEnable() && proxyConfig.getTunnel().getTransferLogEnable()) {
|
||||
ch.pipeline().addFirst(new LoggingHandler(RealServerChannelHandler.class));
|
||||
}
|
||||
ch.pipeline().addLast(new RealServerChannelHandler());
|
||||
}
|
||||
});
|
||||
return bootstrap;
|
||||
}
|
||||
|
||||
@Bean("udpServerBootstrap")
|
||||
public Bootstrap udpServerBootstrap(@Inject ProxyConfig proxyConfig,
|
||||
@Inject("udpServerGroup") NioEventLoopGroup udpServerGroup,
|
||||
@Inject("udpWorkGroup") NioEventLoopGroup udpWorkGroup) {
|
||||
Bootstrap bootstrap = new Bootstrap();
|
||||
bootstrap.group(udpServerGroup)
|
||||
// 主线程处理
|
||||
.channel(NioDatagramChannel.class)
|
||||
// 广播
|
||||
.option(ChannelOption.SO_BROADCAST, true)
|
||||
// 设置读缓冲区为2M
|
||||
.option(ChannelOption.SO_RCVBUF, 2048 * 1024)
|
||||
// 设置写缓冲区为1M
|
||||
.option(ChannelOption.SO_SNDBUF, 1024 * 1024)
|
||||
.handler(new ChannelInitializer<NioDatagramChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioDatagramChannel ch) {
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
if (null != proxyConfig.getClient().getUdp().getTransferLogEnable() && proxyConfig.getClient().getUdp().getTransferLogEnable()) {
|
||||
ch.pipeline().addFirst(new LoggingHandler(UdpRealServerHandler.class));
|
||||
}
|
||||
pipeline.addLast(udpWorkGroup, new UdpRealServerHandler());
|
||||
}
|
||||
});
|
||||
return bootstrap;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public NeutrinoCoreRuntimeNativeRegistrar neutrinoCoreRuntimeNativeRegistrar() {
|
||||
return new NeutrinoCoreRuntimeNativeRegistrar();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.dromara.neutrinoproxy.client.constant;
|
||||
|
||||
import io.netty.util.AttributeKey;
|
||||
import org.dromara.neutrinoproxy.client.util.UdpChannelBindInfo;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/9/21
|
||||
*/
|
||||
public interface Constants {
|
||||
AttributeKey<UdpChannelBindInfo> UDP_CHANNEL_BIND_KEY = AttributeKey.newInstance("udpChannelBindKey");
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package org.dromara.neutrinoproxy.client.core;
|
||||
|
||||
import org.dromara.neutrinoproxy.client.config.ProxyConfig;
|
||||
import org.dromara.neutrinoproxy.client.util.ProxyUtil;
|
||||
import org.dromara.neutrinoproxy.core.Constants;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
import org.dromara.neutrinoproxy.core.dispatcher.Dispatcher;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.timeout.IdleStateEvent;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.noear.solon.Solon;
|
||||
|
||||
/**
|
||||
* 处理与服务端之间的数据传输
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
@Slf4j
|
||||
public class CmdChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
|
||||
private static volatile Boolean transferLogEnable = Boolean.FALSE;
|
||||
|
||||
public CmdChannelHandler() {
|
||||
ProxyConfig proxyConfig = Solon.context().getBean(ProxyConfig.class);
|
||||
if (null != proxyConfig.getClient() && null != proxyConfig.getTunnel().getHeartbeatLogEnable()) {
|
||||
transferLogEnable = proxyConfig.getTunnel().getHeartbeatLogEnable();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception {
|
||||
if (ProxyMessage.TYPE_HEARTBEAT != proxyMessage.getType() || transferLogEnable) {
|
||||
log.debug("[CMD Channel]Client CmdChannel recieved proxy message, type is {}", proxyMessage.getType());
|
||||
}
|
||||
Solon.context().getBean(Dispatcher.class).dispatch(ctx, proxyMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
|
||||
Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
|
||||
if (realServerChannel != null) {
|
||||
realServerChannel.config().setOption(ChannelOption.AUTO_READ, ctx.channel().isWritable());
|
||||
}
|
||||
|
||||
super.channelWritabilityChanged(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
log.info("[CMD Channel]Client CmdChannel disconnect");
|
||||
ProxyUtil.setCmdChannel(null);
|
||||
ProxyUtil.clearRealServerChannels();
|
||||
|
||||
super.channelInactive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
log.error("[CMD Channel]Client CmdChannel Error channelId:{}", ctx.channel().id().asLongText(), cause);
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||
if(evt instanceof IdleStateEvent) {
|
||||
IdleStateEvent event = (IdleStateEvent)evt;
|
||||
switch (event.state()) {
|
||||
case READER_IDLE:
|
||||
// 读超时,断开连接
|
||||
log.error("[CMD Channel] Read timeout disconnect");
|
||||
ctx.channel().close();
|
||||
break;
|
||||
case WRITER_IDLE:
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildHeartbeatMessage());
|
||||
break;
|
||||
case ALL_IDLE:
|
||||
log.error("[CMD Channel] ReadWrite timeout disconnect");
|
||||
ctx.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.client.core;
|
||||
package org.dromara.neutrinoproxy.client.core;
|
||||
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
@@ -20,7 +20,7 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package fun.asgc.neutrino.proxy.client.core;
|
||||
package org.dromara.neutrinoproxy.client.core;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
package org.dromara.neutrinoproxy.client.core;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.dromara.neutrinoproxy.client.config.ProxyConfig;
|
||||
import org.dromara.neutrinoproxy.client.util.ProxyUtil;
|
||||
import org.dromara.neutrinoproxy.client.util.UdpServerUtil;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.noear.solon.Solon;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Init;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 代理客户端服务
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ProxyClientService {
|
||||
@Inject
|
||||
private ProxyConfig proxyConfig;
|
||||
@Inject("cmdTunnelBootstrap")
|
||||
private Bootstrap cmdTunnelBootstrap;
|
||||
@Inject("udpServerBootstrap")
|
||||
private Bootstrap udpServerBootstrap;
|
||||
private volatile Channel channel;
|
||||
/**
|
||||
* 重连次数
|
||||
*/
|
||||
private volatile int reconnectCount = 0;
|
||||
/**
|
||||
* 重连服务执行器
|
||||
*/
|
||||
private static final ScheduledExecutorService reconnectExecutor = Executors.newSingleThreadScheduledExecutor(new CustomThreadFactory("ClientReconnect"));
|
||||
|
||||
@Init
|
||||
public void init() {
|
||||
this.reconnectExecutor.scheduleWithFixedDelay(this::reconnect, 10, proxyConfig.getTunnel().getReconnection().getIntervalSeconds(), TimeUnit.SECONDS);
|
||||
|
||||
try {
|
||||
this.start();
|
||||
UdpServerUtil.initCache(proxyConfig, udpServerBootstrap);
|
||||
} catch (Exception e) {
|
||||
// 启动连不上也做一下重连,因此先catch异常
|
||||
log.error("[CmdChannel] start error", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (StrUtil.isEmpty(proxyConfig.getTunnel().getServerIp())) {
|
||||
log.error("not found server-ip config.");
|
||||
Solon.stop();
|
||||
return;
|
||||
}
|
||||
if (null == proxyConfig.getTunnel().getServerPort()) {
|
||||
log.error("not found server-port config.");
|
||||
Solon.stop();
|
||||
return;
|
||||
}
|
||||
if (null != proxyConfig.getTunnel().getSslEnable() && proxyConfig.getTunnel().getSslEnable()
|
||||
&& StrUtil.isEmpty(proxyConfig.getTunnel().getJksPath())) {
|
||||
log.error("not found jks-path config.");
|
||||
Solon.stop();
|
||||
return;
|
||||
}
|
||||
if (StrUtil.isEmpty(proxyConfig.getTunnel().getLicenseKey())) {
|
||||
log.error("not found license-key config.");
|
||||
Solon.stop();
|
||||
return;
|
||||
}
|
||||
if (null == channel || !channel.isActive()) {
|
||||
try {
|
||||
connectProxyServer();
|
||||
} catch (Exception e) {
|
||||
log.error("client start error", e);
|
||||
}
|
||||
} else {
|
||||
channel.writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getTunnel().getLicenseKey(), ProxyUtil.getClientId()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接代理服务器
|
||||
*/
|
||||
private void connectProxyServer() throws InterruptedException {
|
||||
cmdTunnelBootstrap.connect()
|
||||
.addListener(new ChannelFutureListener() {
|
||||
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
channel = future.channel();
|
||||
// 连接成功,向服务器发送客户端认证信息(licenseKey)
|
||||
ProxyUtil.setCmdChannel(future.channel());
|
||||
future.channel().writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getTunnel().getLicenseKey(), ProxyUtil.getClientId()));
|
||||
log.info("[CmdChannel] connect proxy server success. channelId:{}", future.channel().id().asLongText());
|
||||
|
||||
// reconnectServiceEnable = true;
|
||||
reconnectCount = 0;
|
||||
} else {
|
||||
log.info("[CmdChannel] connect proxy server failed!");
|
||||
}
|
||||
}
|
||||
}).sync();
|
||||
}
|
||||
|
||||
protected synchronized void reconnect() {
|
||||
if (null != channel) {
|
||||
if (channel.isActive()) {
|
||||
return;
|
||||
}
|
||||
channel.close();
|
||||
}
|
||||
|
||||
log.info("[CmdChannel] client reconnect seq:{}", ++reconnectCount);
|
||||
try {
|
||||
connectProxyServer();
|
||||
} catch (Exception e) {
|
||||
log.error("[CmdChannel] reconnect error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,11 +20,12 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package fun.asgc.neutrino.proxy.client.core;
|
||||
package org.dromara.neutrinoproxy.client.core;
|
||||
|
||||
import fun.asgc.neutrino.proxy.client.util.ProxyUtil;
|
||||
import fun.asgc.neutrino.proxy.core.Constants;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessage;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.neutrinoproxy.client.util.ProxyUtil;
|
||||
import org.dromara.neutrinoproxy.core.Constants;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
@@ -36,6 +37,7 @@ import io.netty.channel.SimpleChannelInboundHandler;
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
@Slf4j
|
||||
public class RealServerChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
|
||||
|
||||
|
||||
@@ -47,6 +49,17 @@ public class RealServerChannelHandler extends SimpleChannelInboundHandler<ByteBu
|
||||
// 代理客户端连接断开
|
||||
ctx.channel().close();
|
||||
} else {
|
||||
|
||||
if (proxyChannel.isWritable()) {
|
||||
if (!realServerChannel.config().isAutoRead()) {
|
||||
realServerChannel.config().setAutoRead(true);
|
||||
}
|
||||
} else {
|
||||
if (realServerChannel.config().isAutoRead()) {
|
||||
realServerChannel.config().setAutoRead(false);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] bytes = new byte[buf.readableBytes()];
|
||||
buf.readBytes(bytes);
|
||||
String visitorId = ProxyUtil.getVisitorIdByRealServerChannel(realServerChannel);
|
||||
@@ -85,6 +98,6 @@ public class RealServerChannelHandler extends SimpleChannelInboundHandler<ByteBu
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
super.exceptionCaught(ctx, cause);
|
||||
log.error("Client ProxyChannel Error", cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package org.dromara.neutrinoproxy.client.core;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.timeout.IdleStateEvent;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.neutrinoproxy.client.util.ProxyUtil;
|
||||
import org.dromara.neutrinoproxy.core.Constants;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
import org.dromara.neutrinoproxy.core.dispatcher.Dispatcher;
|
||||
import org.noear.solon.Solon;
|
||||
|
||||
/**
|
||||
* 处理与服务端之间的数据传输
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
@Slf4j
|
||||
public class TcpProxyChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
|
||||
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception {
|
||||
if (ProxyMessage.TYPE_HEARTBEAT != proxyMessage.getType()) {
|
||||
log.debug("[TCP Proxy Channel]Client ProxyChannel recieved proxy message, type is {}", proxyMessage.getType());
|
||||
}
|
||||
Solon.context().getBean(Dispatcher.class).dispatch(ctx, proxyMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
|
||||
Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
|
||||
if (realServerChannel != null) {
|
||||
realServerChannel.config().setOption(ChannelOption.AUTO_READ, ctx.channel().isWritable());
|
||||
}
|
||||
|
||||
super.channelWritabilityChanged(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
// 数据传输连接
|
||||
Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
|
||||
if (realServerChannel != null && realServerChannel.isActive()) {
|
||||
realServerChannel.close();
|
||||
}
|
||||
|
||||
ProxyUtil.removeTcpProxyChanel(ctx.channel());
|
||||
super.channelInactive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
log.error("[TCP Proxy Channel]Client ProxyChannel Error channelId:{}", ctx.channel().id().asLongText(), cause);
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||
if(evt instanceof IdleStateEvent) {
|
||||
IdleStateEvent event = (IdleStateEvent)evt;
|
||||
switch (event.state()) {
|
||||
case READER_IDLE:
|
||||
if (ctx.channel().isWritable()) {
|
||||
// 读超时,断开连接
|
||||
log.info("[TCP Proxy Channel]Read timeout");
|
||||
ctx.channel().close();
|
||||
}
|
||||
break;
|
||||
case WRITER_IDLE:
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildHeartbeatMessage());
|
||||
break;
|
||||
case ALL_IDLE:
|
||||
// log.debug("[TCP Proxy Channel]ReadWrite timeout");
|
||||
// ctx.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
package fun.asgc.neutrino.proxy.client.core;
|
||||
package org.dromara.neutrinoproxy.client.core;
|
||||
|
||||
import fun.asgc.neutrino.proxy.client.util.ProxyUtil;
|
||||
import fun.asgc.neutrino.proxy.core.Constants;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessage;
|
||||
import fun.asgc.neutrino.proxy.core.dispatcher.Dispatcher;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.timeout.IdleStateEvent;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.neutrinoproxy.client.util.ProxyUtil;
|
||||
import org.dromara.neutrinoproxy.core.Constants;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
import org.dromara.neutrinoproxy.core.dispatcher.Dispatcher;
|
||||
import org.noear.solon.Solon;
|
||||
|
||||
/**
|
||||
@@ -18,13 +18,13 @@ import org.noear.solon.Solon;
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
@Slf4j
|
||||
public class ClientChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
|
||||
public class UdpProxyChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
|
||||
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception {
|
||||
if (ProxyMessage.TYPE_HEARTBEAT != proxyMessage.getType()) {
|
||||
log.info("recieved proxy message, type is {}", proxyMessage.getType());
|
||||
log.debug("[UDP Proxy Channel]Client ProxyChannel recieved proxy message, type is {}", proxyMessage.getType());
|
||||
}
|
||||
Solon.context().getBean(Dispatcher.class).dispatch(ctx, proxyMessage);
|
||||
}
|
||||
@@ -41,27 +41,20 @@ public class ClientChannelHandler extends SimpleChannelInboundHandler<ProxyMessa
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
// 控制连接
|
||||
if (ProxyUtil.getCmdChannel() == ctx.channel()) {
|
||||
log.info("与服务端断开连接");
|
||||
ProxyUtil.setCmdChannel(null);
|
||||
ProxyUtil.clearRealServerChannels();
|
||||
} else {
|
||||
// 数据传输连接
|
||||
Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
|
||||
if (realServerChannel != null && realServerChannel.isActive()) {
|
||||
realServerChannel.close();
|
||||
}
|
||||
// 数据传输连接
|
||||
Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
|
||||
if (realServerChannel != null && realServerChannel.isActive()) {
|
||||
realServerChannel.close();
|
||||
}
|
||||
|
||||
ProxyUtil.removeProxyChanel(ctx.channel());
|
||||
ProxyUtil.removeTcpProxyChanel(ctx.channel());
|
||||
super.channelInactive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
super.exceptionCaught(ctx, cause);
|
||||
cause.printStackTrace();
|
||||
log.error("[UDP Proxy Channel]Client ProxyChannel Error channelId:{}", ctx.channel().id().asLongText(), cause);
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -71,13 +64,15 @@ public class ClientChannelHandler extends SimpleChannelInboundHandler<ProxyMessa
|
||||
switch (event.state()) {
|
||||
case READER_IDLE:
|
||||
// 读超时,断开连接
|
||||
log.info("读超时");
|
||||
log.info("[UDP Proxy Channel]Read timeout");
|
||||
ctx.channel().close();
|
||||
break;
|
||||
case WRITER_IDLE:
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildHeartbeatMessage());
|
||||
break;
|
||||
case ALL_IDLE:
|
||||
log.debug("[UDP Proxy Channel]ReadWrite timeout");
|
||||
ctx.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.dromara.neutrinoproxy.client.core;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.channel.socket.DatagramPacket;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.neutrinoproxy.client.constant.Constants;
|
||||
import org.dromara.neutrinoproxy.client.util.UdpChannelBindInfo;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/9/21
|
||||
*/
|
||||
@Slf4j
|
||||
public class UdpRealServerHandler extends SimpleChannelInboundHandler<DatagramPacket> {
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket datagramPacket) throws Exception {
|
||||
log.debug("chid---<:{} port:{}", ctx.channel().id().asLongText(), ((InetSocketAddress)ctx.channel().localAddress()).getPort());
|
||||
UdpChannelBindInfo udpChannelBindInfo = ctx.channel().attr(Constants.UDP_CHANNEL_BIND_KEY).get();
|
||||
if (null != udpChannelBindInfo) {
|
||||
byte[] bytes = new byte[datagramPacket.content().readableBytes()];
|
||||
datagramPacket.content().readBytes(bytes);
|
||||
|
||||
udpChannelBindInfo.getTunnelChannel().writeAndFlush(ProxyMessage.buildUdpTransferMessage(new ProxyMessage.UdpBaseInfo()
|
||||
.setVisitorId(udpChannelBindInfo.getVisitorId())
|
||||
.setVisitorIp(udpChannelBindInfo.getVisitorIp())
|
||||
.setVisitorPort(udpChannelBindInfo.getVisitorPort())
|
||||
.setServerPort(udpChannelBindInfo.getServerPort())
|
||||
.setTargetIp(udpChannelBindInfo.getTargetIp())
|
||||
.setTargetPort(udpChannelBindInfo.getTargetPort()))
|
||||
.setData(bytes)
|
||||
);
|
||||
|
||||
udpChannelBindInfo.getLockChannel().setResponseCount(udpChannelBindInfo.getLockChannel().getResponseCount() + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.dromara.neutrinoproxy.client.handler;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.neutrinoproxy.client.config.ProxyConfig;
|
||||
import org.dromara.neutrinoproxy.core.Constants;
|
||||
import org.dromara.neutrinoproxy.core.ExceptionEnum;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessageHandler;
|
||||
import org.dromara.neutrinoproxy.core.dispatcher.Match;
|
||||
import org.noear.snack.ONode;
|
||||
import org.noear.solon.Solon;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
|
||||
/**
|
||||
* 认证信息处理器
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/9/4
|
||||
*/
|
||||
@Slf4j
|
||||
@Match(type = Constants.ProxyDataTypeName.AUTH)
|
||||
@Component
|
||||
public class ProxyMessageAuthHandler implements ProxyMessageHandler {
|
||||
@Inject
|
||||
private ProxyConfig proxyConfig;
|
||||
@Override
|
||||
public void handle(ChannelHandlerContext context, ProxyMessage proxyMessage) {
|
||||
String info = proxyMessage.getInfo();
|
||||
ONode load = ONode.load(info);
|
||||
Integer code = load.get("code").getInt();
|
||||
log.info("Auth result:{}", info);
|
||||
if (ExceptionEnum.AUTH_FAILED.getCode().equals(code)) {
|
||||
// 客户端认证失败,直接停止服务
|
||||
log.info("client auth failed , client stop.");
|
||||
context.channel().close();
|
||||
if (!proxyConfig.getTunnel().getReconnection().getUnlimited()) {
|
||||
Solon.stop();
|
||||
}
|
||||
} else if (ExceptionEnum.CONNECT_FAILED.getCode().equals(code) ||
|
||||
ExceptionEnum.LICENSE_CANNOT_REPEAT_CONNECT.getCode().equals(code)
|
||||
){
|
||||
context.channel().close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
package fun.asgc.neutrino.proxy.client.handler;
|
||||
package org.dromara.neutrinoproxy.client.handler;
|
||||
|
||||
import fun.asgc.neutrino.proxy.client.config.ProxyConfig;
|
||||
import fun.asgc.neutrino.proxy.client.core.ProxyChannelBorrowListener;
|
||||
import fun.asgc.neutrino.proxy.client.util.ProxyUtil;
|
||||
import fun.asgc.neutrino.proxy.core.Constants;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyDataTypeEnum;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessage;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessageHandler;
|
||||
import fun.asgc.neutrino.proxy.core.dispatcher.Match;
|
||||
import org.dromara.neutrinoproxy.client.config.ProxyConfig;
|
||||
import org.dromara.neutrinoproxy.client.core.ProxyChannelBorrowListener;
|
||||
import org.dromara.neutrinoproxy.client.util.ProxyUtil;
|
||||
import org.dromara.neutrinoproxy.core.Constants;
|
||||
import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessageHandler;
|
||||
import org.dromara.neutrinoproxy.core.dispatcher.Match;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.*;
|
||||
import org.noear.solon.annotation.Component;
|
||||
@@ -21,8 +21,8 @@ import org.noear.solon.annotation.Inject;
|
||||
@Match(type = Constants.ProxyDataTypeName.CONNECT)
|
||||
@Component
|
||||
public class ProxyMessageConnectHandler implements ProxyMessageHandler {
|
||||
@Inject("bootstrap")
|
||||
private Bootstrap bootstrap;
|
||||
@Inject("tcpProxyTunnelBootstrap")
|
||||
private Bootstrap tcpProxyTunnelBootstrap;
|
||||
@Inject("realServerBootstrap")
|
||||
private Bootstrap realServerBootstrap;
|
||||
@Inject
|
||||
@@ -48,7 +48,7 @@ public class ProxyMessageConnectHandler implements ProxyMessageHandler {
|
||||
realServerChannel.config().setOption(ChannelOption.AUTO_READ, false);
|
||||
|
||||
// 获取连接
|
||||
ProxyUtil.borrowProxyChanel(bootstrap, new ProxyChannelBorrowListener() {
|
||||
ProxyUtil.borrowTcpProxyChanel(tcpProxyTunnelBootstrap, new ProxyChannelBorrowListener() {
|
||||
|
||||
@Override
|
||||
public void success(Channel channel) {
|
||||
@@ -57,7 +57,7 @@ public class ProxyMessageConnectHandler implements ProxyMessageHandler {
|
||||
realServerChannel.attr(Constants.NEXT_CHANNEL).set(channel);
|
||||
|
||||
// 远程绑定
|
||||
channel.writeAndFlush(ProxyMessage.buildConnectMessage(visitorId + "@" + proxyConfig.getClient().getLicenseKey()));
|
||||
channel.writeAndFlush(ProxyMessage.buildConnectMessage(visitorId + "@" + proxyConfig.getTunnel().getLicenseKey()));
|
||||
|
||||
realServerChannel.config().setOption(ChannelOption.AUTO_READ, true);
|
||||
ProxyUtil.addRealServerChannel(visitorId, realServerChannel);
|
||||
@@ -1,11 +1,11 @@
|
||||
package fun.asgc.neutrino.proxy.client.handler;
|
||||
package org.dromara.neutrinoproxy.client.handler;
|
||||
|
||||
import fun.asgc.neutrino.proxy.client.util.ProxyUtil;
|
||||
import fun.asgc.neutrino.proxy.core.Constants;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyDataTypeEnum;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessage;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessageHandler;
|
||||
import fun.asgc.neutrino.proxy.core.dispatcher.Match;
|
||||
import org.dromara.neutrinoproxy.client.util.ProxyUtil;
|
||||
import org.dromara.neutrinoproxy.core.Constants;
|
||||
import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessageHandler;
|
||||
import org.dromara.neutrinoproxy.core.dispatcher.Match;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
@@ -26,9 +26,10 @@ public class ProxyMessageDisconnectHandler implements ProxyMessageHandler {
|
||||
Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
|
||||
if (null != realServerChannel) {
|
||||
ctx.channel().attr(Constants.NEXT_CHANNEL).remove();
|
||||
ProxyUtil.returnProxyChanel(ctx.channel());
|
||||
ProxyUtil.returnTcpProxyChanel(ctx.channel());
|
||||
realServerChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1,10 +1,14 @@
|
||||
package fun.asgc.neutrino.proxy.client.handler;
|
||||
package org.dromara.neutrinoproxy.client.handler;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import fun.asgc.neutrino.proxy.core.*;
|
||||
import fun.asgc.neutrino.proxy.core.dispatcher.Match;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.neutrinoproxy.core.Constants;
|
||||
import org.dromara.neutrinoproxy.core.ExceptionEnum;
|
||||
import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessageHandler;
|
||||
import org.dromara.neutrinoproxy.core.dispatcher.Match;
|
||||
import org.noear.snack.ONode;
|
||||
import org.noear.solon.annotation.Component;
|
||||
|
||||
/**
|
||||
@@ -19,9 +23,9 @@ public class ProxyMessageErrorHandler implements ProxyMessageHandler {
|
||||
|
||||
@Override
|
||||
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
|
||||
log.info("异常信息: {}", proxyMessage.getInfo());
|
||||
JSONObject data = JSONObject.parseObject(proxyMessage.getInfo());
|
||||
Integer code = data.getInteger("code");
|
||||
log.info("error: {}", proxyMessage.getInfo());
|
||||
ONode load = ONode.load(proxyMessage.getInfo());
|
||||
Integer code = load.get("code").getInt();
|
||||
if (ExceptionEnum.AUTH_FAILED.getCode().equals(code)) {
|
||||
System.exit(0);
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package fun.asgc.neutrino.proxy.client.handler;
|
||||
package org.dromara.neutrinoproxy.client.handler;
|
||||
|
||||
import fun.asgc.neutrino.proxy.core.Constants;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyDataTypeEnum;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessage;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessageHandler;
|
||||
import fun.asgc.neutrino.proxy.core.dispatcher.Match;
|
||||
import org.dromara.neutrinoproxy.core.Constants;
|
||||
import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessageHandler;
|
||||
import org.dromara.neutrinoproxy.core.dispatcher.Match;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
@@ -23,6 +23,10 @@ public class ProxyMessageTransferHandler implements ProxyMessageHandler {
|
||||
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
|
||||
Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
|
||||
if (realServerChannel != null) {
|
||||
|
||||
// 自己可写,则设置来源可读。自己不可写,则设置来源不可读
|
||||
ctx.channel().config().setAutoRead(realServerChannel.isWritable());
|
||||
|
||||
ByteBuf buf = ctx.alloc().buffer(proxyMessage.getData().length);
|
||||
buf.writeBytes(proxyMessage.getData());
|
||||
realServerChannel.writeAndFlush(buf);
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.dromara.neutrinoproxy.client.handler;
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.neutrinoproxy.client.config.ProxyConfig;
|
||||
import org.dromara.neutrinoproxy.client.core.ProxyChannelBorrowListener;
|
||||
import org.dromara.neutrinoproxy.client.util.ProxyUtil;
|
||||
import org.dromara.neutrinoproxy.core.Constants;
|
||||
import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessageHandler;
|
||||
import org.dromara.neutrinoproxy.core.dispatcher.Match;
|
||||
import org.noear.snack.ONode;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/9/19
|
||||
*/
|
||||
@Slf4j
|
||||
@Match(type = Constants.ProxyDataTypeName.UDP_CONNECT)
|
||||
@Component
|
||||
public class UdpProxyMessageConnectHandler implements ProxyMessageHandler {
|
||||
@Inject
|
||||
private ProxyConfig proxyConfig;
|
||||
@Inject("udpProxyTunnelBootstrap")
|
||||
private Bootstrap udpProxyTunnelBootstrap;
|
||||
|
||||
@Override
|
||||
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
|
||||
final Channel cmdChannel = ctx.channel();
|
||||
final ProxyMessage.UdpBaseInfo udpBaseInfo = ONode.deserialize(proxyMessage.getInfo(), ProxyMessage.UdpBaseInfo.class);
|
||||
log.info("[UDP connect]info:{}", proxyMessage.getInfo());
|
||||
|
||||
// 获取连接
|
||||
ProxyUtil.borrowUdpProxyChanel(udpProxyTunnelBootstrap, new ProxyChannelBorrowListener() {
|
||||
|
||||
@Override
|
||||
public void success(Channel channel) {
|
||||
channel.writeAndFlush(ProxyMessage.buildUdpConnectMessage(new ProxyMessage.UdpBaseInfo()
|
||||
.setVisitorId(udpBaseInfo.getVisitorId())
|
||||
.setServerPort(udpBaseInfo.getServerPort())
|
||||
.setTargetIp(udpBaseInfo.getTargetIp())
|
||||
.setTargetPort(udpBaseInfo.getTargetPort())
|
||||
).setData(proxyConfig.getTunnel().getLicenseKey().getBytes()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(Throwable cause) {
|
||||
cmdChannel.writeAndFlush(ProxyMessage.buildDisconnectMessage(udpBaseInfo.toJsonString()));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return ProxyDataTypeEnum.UDP_CONNECT.getDesc();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.dromara.neutrinoproxy.client.handler;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.socket.DatagramPacket;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.neutrinoproxy.client.util.UdpServerUtil;
|
||||
import org.dromara.neutrinoproxy.core.Constants;
|
||||
import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessageHandler;
|
||||
import org.dromara.neutrinoproxy.core.dispatcher.Match;
|
||||
import org.noear.snack.ONode;
|
||||
import org.noear.solon.annotation.Component;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/9/20
|
||||
*/
|
||||
@Slf4j
|
||||
@Match(type = Constants.ProxyDataTypeName.UDP_TRANSFER)
|
||||
@Component
|
||||
public class UdpProxyMessageTransferHandler implements ProxyMessageHandler {
|
||||
|
||||
@Override
|
||||
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
|
||||
final ProxyMessage.UdpBaseInfo udpBaseInfo = ONode.deserialize(proxyMessage.getInfo(), ProxyMessage.UdpBaseInfo.class);
|
||||
log.debug("[UDP transfer]info:{} data:{}", proxyMessage.getInfo(), new String(proxyMessage.getData()));
|
||||
Channel channel = UdpServerUtil.takeChannel(udpBaseInfo, ctx.channel());
|
||||
if (null == channel) {
|
||||
log.error("[UDP transfer] take udp channel failed.");
|
||||
return;
|
||||
}
|
||||
log.debug("chid--->:{} port:{}", ctx.channel().id().asLongText(), ((InetSocketAddress)channel.localAddress()).getPort());
|
||||
InetSocketAddress address = new InetSocketAddress(udpBaseInfo.getTargetIp(), udpBaseInfo.getTargetPort());
|
||||
ByteBuf byteBuf = Unpooled.copiedBuffer(proxyMessage.getData());
|
||||
channel.writeAndFlush(new DatagramPacket(byteBuf, address));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return ProxyDataTypeEnum.UDP_TRANSFER.getDesc();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.dromara.neutrinoproxy.client.util;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/9/21
|
||||
*/
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
public class LockChannel {
|
||||
// 端口号
|
||||
private int port;
|
||||
// 通道
|
||||
private Channel channel;
|
||||
// 期望的响应次数
|
||||
private int proxyResponses;
|
||||
// 超时时间(毫秒)
|
||||
private long proxyTimeoutMs;
|
||||
// 被获取的时间
|
||||
private Date takeTime;
|
||||
// 已经响应的次数
|
||||
private int responseCount;
|
||||
}
|
||||
@@ -19,22 +19,33 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.client.util;
|
||||
package org.dromara.neutrinoproxy.client.util;
|
||||
|
||||
import fun.asgc.neutrino.proxy.client.config.ProxyConfig;
|
||||
import fun.asgc.neutrino.proxy.client.core.ProxyChannelBorrowListener;
|
||||
import fun.asgc.neutrino.proxy.core.Constants;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dromara.neutrinoproxy.client.config.ProxyConfig;
|
||||
import org.dromara.neutrinoproxy.client.core.ProxyChannelBorrowListener;
|
||||
import org.dromara.neutrinoproxy.core.Constants;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.util.AttributeKey;
|
||||
import org.dromara.neutrinoproxy.core.util.FileUtil;
|
||||
import org.noear.solon.Solon;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import java.io.InputStream;
|
||||
import java.security.KeyStore;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
@@ -43,6 +54,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/8/31
|
||||
*/
|
||||
@Slf4j
|
||||
public class ProxyUtil {
|
||||
private static final AttributeKey<Boolean> USER_CHANNEL_WRITEABLE = AttributeKey.newInstance("user_channel_writeable");
|
||||
|
||||
@@ -52,20 +64,22 @@ public class ProxyUtil {
|
||||
|
||||
private static Map<String, Channel> realServerChannels = new ConcurrentHashMap<String, Channel>();
|
||||
|
||||
private static ConcurrentLinkedQueue<Channel> proxyChannelPool = new ConcurrentLinkedQueue<Channel>();
|
||||
private static ConcurrentLinkedQueue<Channel> tcpProxyChannelPool = new ConcurrentLinkedQueue<Channel>();
|
||||
private static ConcurrentLinkedQueue<Channel> udpProxyChannelPool = new ConcurrentLinkedQueue<>();
|
||||
|
||||
private static volatile Channel cmdChannel;
|
||||
|
||||
public static void borrowProxyChanel(Bootstrap bootstrap, final ProxyChannelBorrowListener borrowListener) {
|
||||
Channel channel = proxyChannelPool.poll();
|
||||
private static String clientId;
|
||||
private static final String CLIENT_ID_FILE = ".NEUTRINO_PROXY_CLIENT_ID";
|
||||
|
||||
public static void borrowTcpProxyChanel(Bootstrap tcpProxyTunnelBootstrap, final ProxyChannelBorrowListener borrowListener) {
|
||||
Channel channel = tcpProxyChannelPool.poll();
|
||||
if (null != channel) {
|
||||
borrowListener.success(channel);
|
||||
return;
|
||||
}
|
||||
|
||||
String serverIp = Solon.cfg().get("neutrino.proxy.client.server-ip");
|
||||
Integer serverPort = Solon.cfg().getInt("neutrino.proxy.client.server-port", 9000);
|
||||
bootstrap.connect(serverIp, serverPort).addListener((ChannelFutureListener) future -> {
|
||||
tcpProxyTunnelBootstrap.connect().addListener((ChannelFutureListener) future -> {
|
||||
if (future.isSuccess()) {
|
||||
borrowListener.success(future.channel());
|
||||
} else {
|
||||
@@ -74,18 +88,52 @@ public class ProxyUtil {
|
||||
});
|
||||
}
|
||||
|
||||
public static void returnProxyChanel(Channel proxyChanel) {
|
||||
if (proxyChannelPool.size() > MAX_POOL_SIZE) {
|
||||
public static void returnTcpProxyChanel(Channel proxyChanel) {
|
||||
if (tcpProxyChannelPool.size() > MAX_POOL_SIZE) {
|
||||
proxyChanel.close();
|
||||
} else {
|
||||
proxyChanel.config().setOption(ChannelOption.AUTO_READ, true);
|
||||
proxyChanel.attr(Constants.NEXT_CHANNEL).remove();
|
||||
proxyChannelPool.offer(proxyChanel);
|
||||
tcpProxyChannelPool.offer(proxyChanel);
|
||||
}
|
||||
}
|
||||
|
||||
public static void removeProxyChanel(Channel proxyChanel) {
|
||||
proxyChannelPool.remove(proxyChanel);
|
||||
|
||||
|
||||
public static void removeTcpProxyChanel(Channel proxyChanel) {
|
||||
tcpProxyChannelPool.remove(proxyChanel);
|
||||
}
|
||||
|
||||
public static void borrowUdpProxyChanel(Bootstrap tcpProxyTunnelBootstrap, final ProxyChannelBorrowListener borrowListener) {
|
||||
Channel channel = udpProxyChannelPool.poll();
|
||||
if (null != channel) {
|
||||
borrowListener.success(channel);
|
||||
return;
|
||||
}
|
||||
|
||||
tcpProxyTunnelBootstrap.connect().addListener((ChannelFutureListener) future -> {
|
||||
if (future.isSuccess()) {
|
||||
borrowListener.success(future.channel());
|
||||
} else {
|
||||
borrowListener.error(future.cause());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void returnUdpProxyChanel(Channel proxyChanel) {
|
||||
if (udpProxyChannelPool.size() > MAX_POOL_SIZE) {
|
||||
proxyChanel.close();
|
||||
} else {
|
||||
proxyChanel.config().setOption(ChannelOption.AUTO_READ, true);
|
||||
proxyChanel.attr(Constants.NEXT_CHANNEL).remove();
|
||||
udpProxyChannelPool.offer(proxyChanel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static void removeUdpProxyChanel(Channel proxyChanel) {
|
||||
udpProxyChannelPool.remove(proxyChanel);
|
||||
}
|
||||
|
||||
public static void setCmdChannel(Channel cmdChannel) {
|
||||
@@ -131,4 +179,48 @@ public class ProxyUtil {
|
||||
|
||||
realServerChannels.clear();
|
||||
}
|
||||
|
||||
public static String getClientId() {
|
||||
if (StringUtils.isNotBlank(clientId)) {
|
||||
return clientId;
|
||||
}
|
||||
ProxyConfig proxyConfig = Solon.context().getBean(ProxyConfig.class);
|
||||
if (StringUtils.isNotBlank(proxyConfig.getTunnel().getClientId())) {
|
||||
clientId = proxyConfig.getTunnel().getClientId();
|
||||
return clientId;
|
||||
}
|
||||
String id = FileUtil.readContentAsString(CLIENT_ID_FILE);
|
||||
if (StringUtils.isNotBlank(id)) {
|
||||
clientId = id;
|
||||
return id;
|
||||
}
|
||||
id = UUID.randomUUID().toString().replace("-", "");
|
||||
FileUtil.write(CLIENT_ID_FILE, id);
|
||||
clientId = id;
|
||||
return id;
|
||||
}
|
||||
|
||||
public static ChannelHandler createSslHandler(ProxyConfig proxyConfig) {
|
||||
try {
|
||||
InputStream jksInputStream = FileUtil.getInputStream(proxyConfig.getTunnel().getJksPath());
|
||||
|
||||
SSLContext clientContext = SSLContext.getInstance("TLS");
|
||||
final KeyStore ks = KeyStore.getInstance("JKS");
|
||||
ks.load(jksInputStream, proxyConfig.getTunnel().getKeyStorePassword().toCharArray());
|
||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
tmf.init(ks);
|
||||
TrustManager[] trustManagers = tmf.getTrustManagers();
|
||||
clientContext.init(null, trustManagers, null);
|
||||
|
||||
SSLEngine sslEngine = clientContext.createSSLEngine();
|
||||
sslEngine.setUseClientMode(true);
|
||||
|
||||
return new SslHandler(sslEngine);
|
||||
} catch (Exception e) {
|
||||
log.error("create SSL handler failed", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.dromara.neutrinoproxy.client.util;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/9/21
|
||||
*/
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
public class UdpChannelBindInfo {
|
||||
private Channel tunnelChannel;
|
||||
private LockChannel lockChannel;
|
||||
private String visitorId;
|
||||
private String visitorIp;
|
||||
private int visitorPort;
|
||||
private int serverPort;
|
||||
private String targetIp;
|
||||
private int targetPort;
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package org.dromara.neutrinoproxy.client.util;
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dromara.neutrinoproxy.client.config.ProxyConfig;
|
||||
import org.dromara.neutrinoproxy.client.constant.Constants;
|
||||
import org.dromara.neutrinoproxy.client.core.CustomThreadFactory;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
import org.noear.solon.core.runtime.NativeDetector;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/9/21
|
||||
*/
|
||||
@Slf4j
|
||||
public class UdpServerUtil {
|
||||
private static final Boolean isSupportUdp = Boolean.FALSE;
|
||||
private static int udpServerPortMin = 0;
|
||||
private static int udpServerPortMax = 0;
|
||||
private static int nextUdpServerPort = 0;
|
||||
private static Bootstrap udpServerBootstrap;
|
||||
private static int defaultUdpServerPort;
|
||||
private static Channel defaultUdpServerChannel;
|
||||
private static Map<Integer, Channel> portToChannelMap = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* udp服务空闲端口池
|
||||
*/
|
||||
private static ConcurrentLinkedQueue<Integer> udpServerFreePortPool = new ConcurrentLinkedQueue<>();
|
||||
private static List<LockChannel> lockChannelList = new ArrayList<>();
|
||||
/**
|
||||
* lockChannel扫描器
|
||||
*/
|
||||
private static final ScheduledExecutorService lockChannelScanner = Executors.newSingleThreadScheduledExecutor(new CustomThreadFactory("lockChannelScanner"));
|
||||
|
||||
/**
|
||||
* 初始化UDP缓存
|
||||
* 1、初始化一个基础UDP服务,用于不需要响应的UDP转发
|
||||
* 2、维护一个UDP服务池,用于需要响应的UDP转发
|
||||
* @param proxyConfig
|
||||
*/
|
||||
public static void initCache(ProxyConfig proxyConfig, Bootstrap udpServerBootstrap) {
|
||||
// aot 阶段,不初始化UDP服务
|
||||
if (NativeDetector.isAotRuntime()) {
|
||||
return;
|
||||
}
|
||||
if (null == proxyConfig.getClient().getUdp() || StringUtils.isEmpty(proxyConfig.getClient().getUdp().getPuppetPortRange())) {
|
||||
return;
|
||||
}
|
||||
ProxyConfig.Udp udpConfig = proxyConfig.getClient().getUdp();
|
||||
if (StringUtils.isEmpty(udpConfig.getPuppetPortRange())) {
|
||||
return;
|
||||
}
|
||||
String[] tmp = udpConfig.getPuppetPortRange().split("-");
|
||||
if (null == tmp || tmp.length != 2) {
|
||||
log.error("client udp config error!");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
udpServerPortMin = Integer.parseInt(tmp[0]);
|
||||
udpServerPortMax = Integer.parseInt(tmp[1]);
|
||||
if (udpServerPortMax <= udpServerPortMin) {
|
||||
// 至少得给1个udp端口,一个用于基础无响应UDP转发
|
||||
throw new RuntimeException("client udp config error!");
|
||||
}
|
||||
nextUdpServerPort = udpServerPortMin;
|
||||
UdpServerUtil.udpServerBootstrap = udpServerBootstrap;
|
||||
log.info("udp proxy server port: {} ~ {}", udpServerPortMin, udpServerPortMax);
|
||||
// 初始化udp服务
|
||||
initUdpServer();
|
||||
// 初始化lockChannel扫描器
|
||||
lockChannelScanner.scheduleWithFixedDelay(UdpServerUtil::lockChannelScan, 5, 3, TimeUnit.SECONDS);
|
||||
} catch (Exception e) {
|
||||
log.error("client udp config error!", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化udp服务
|
||||
*/
|
||||
private static void initUdpServer() {
|
||||
defaultUdpServerPort = nextUdpServerPort();
|
||||
defaultUdpServerChannel = bindPort(defaultUdpServerPort);
|
||||
// 初始化默认最多额外开启5个udp服务,其他的需要时再启动
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (!hasNextUdpServerPort()) {
|
||||
return;
|
||||
}
|
||||
int port = nextUdpServerPort();
|
||||
Channel ch = bindPort(port);
|
||||
portToChannelMap.put(port, ch);
|
||||
udpServerFreePortPool.offer(port);
|
||||
}
|
||||
while (hasNextUdpServerPort()) {
|
||||
udpServerFreePortPool.offer(nextUdpServerPort());
|
||||
}
|
||||
}
|
||||
|
||||
private static Channel bindPort(int port) {
|
||||
try {
|
||||
ChannelFuture channelFuture = udpServerBootstrap.bind(port).sync();
|
||||
log.info("[udp server] bind port:{} success!", port);
|
||||
return channelFuture.channel();
|
||||
} catch (InterruptedException e) {
|
||||
log.error("[udp server] bind port:{} error!", port);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Boolean hasNextUdpServerPort() {
|
||||
return nextUdpServerPort <= udpServerPortMax;
|
||||
}
|
||||
|
||||
public static synchronized int nextUdpServerPort() {
|
||||
return nextUdpServerPort++;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个可用的udp通道
|
||||
* 1、如果期待的响应为0,或者超时时间<=0,则认为不需要响应,直接返回默认的udp服务,否则继续下一步
|
||||
* 2、从可用端口队列中找到一个可用端口,若不存在可用端口,则降级为不需要响应,返回默认的udp服务。否则继续下一步
|
||||
* 3、根据该端口找到udp服务通道,找不到则绑定端口开启一个通道并返回。将该端口添加到锁定列表
|
||||
* 4、维护一个定时器的,定时扫描锁定列表,及时释放锁定的端口
|
||||
* @param info
|
||||
* @return
|
||||
*/
|
||||
public static synchronized Channel takeChannel(ProxyMessage.UdpBaseInfo info, Channel tunnelChannel) {
|
||||
if (info.getProxyResponses() <= 0 || info.getProxyTimeoutMs() <= 0) {
|
||||
return defaultUdpServerChannel;
|
||||
}
|
||||
Integer port = udpServerFreePortPool.poll();
|
||||
if (null == port) {
|
||||
return defaultUdpServerChannel;
|
||||
}
|
||||
Channel channel = portToChannelMap.get(port);
|
||||
if (null == channel) {
|
||||
channel = bindPort(port);
|
||||
portToChannelMap.put(port, channel);
|
||||
}
|
||||
// 添加到锁定队列
|
||||
LockChannel lockChannel = new LockChannel()
|
||||
.setPort(port)
|
||||
.setChannel(channel)
|
||||
.setProxyResponses(info.getProxyResponses())
|
||||
.setProxyTimeoutMs(info.getProxyTimeoutMs())
|
||||
.setTakeTime(new Date())
|
||||
.setResponseCount(0);
|
||||
lockChannelList.add(lockChannel);
|
||||
channel.attr(Constants.UDP_CHANNEL_BIND_KEY).set(new UdpChannelBindInfo()
|
||||
.setTunnelChannel(tunnelChannel)
|
||||
.setVisitorId(info.getVisitorId())
|
||||
.setVisitorIp(info.getVisitorIp())
|
||||
.setVisitorPort(info.getVisitorPort())
|
||||
.setServerPort(info.getServerPort())
|
||||
.setTargetIp(info.getTargetIp())
|
||||
.setTargetPort(info.getTargetPort())
|
||||
.setLockChannel(lockChannel)
|
||||
);
|
||||
return channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* lockChannel扫描
|
||||
*/
|
||||
public static synchronized void lockChannelScan() {
|
||||
if (lockChannelList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Iterator<LockChannel> iter = lockChannelList.iterator();
|
||||
if (iter.hasNext()) {
|
||||
LockChannel lockChannel = iter.next();
|
||||
if (lockChannel.getResponseCount() >= lockChannel.getProxyResponses() ||
|
||||
System.currentTimeMillis() - lockChannel.getTakeTime().getTime() >= lockChannel.getProxyTimeoutMs()
|
||||
) {
|
||||
iter.remove();
|
||||
UdpChannelBindInfo udpChannelBindInfo = lockChannel.getChannel().attr(Constants.UDP_CHANNEL_BIND_KEY).get();
|
||||
// 此处必须释放代理隧道
|
||||
closeChannel(udpChannelBindInfo.getTunnelChannel());
|
||||
lockChannel.getChannel().attr(Constants.UDP_CHANNEL_BIND_KEY).set(null);
|
||||
udpServerFreePortPool.offer(lockChannel.getPort());
|
||||
log.debug("[udp channel]release udp channel port:{}", lockChannel.getPort());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void closeChannel(Channel channel) {
|
||||
try {
|
||||
channel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,16 @@
|
||||
# 日志级别
|
||||
solon.logging.appender:
|
||||
console:
|
||||
pattern: "%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %magenta(${PID:-}) --- %-15([%15.15thread]) %-56(%cyan(%-40.40logger{39}%L)) : %msg%n"
|
||||
file:
|
||||
enable: false
|
||||
pattern: "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level ${PID:-} --- %-15([%15.15thread]) %-56(%-40.40logger{39}%L) : %msg%n"
|
||||
name: "logs/${neutrino.application.name}"
|
||||
rolling: "logs/${neutrino.application.name}_%d{yyyy-MM-dd}_%i.log.gz"
|
||||
solon.logging.logger:
|
||||
"root":
|
||||
# level: ${LOG_LEVEL:info}
|
||||
level: info
|
||||
neutrino:
|
||||
application:
|
||||
name: neutrino-proxy-client
|
||||
@@ -9,14 +22,41 @@ neutrino:
|
||||
length-field-length: 4
|
||||
initial-bytes-to-strip: 0
|
||||
length-adjustment: 0
|
||||
read-idle-time: 40
|
||||
write-idle-time: 8
|
||||
read-idle-time: 120
|
||||
write-idle-time: 20
|
||||
all-idle-time-seconds: 0
|
||||
tunnel:
|
||||
# 线程池相关配置,用于技术调优,可忽略
|
||||
thread-count: 50
|
||||
# 隧道SSL证书配置
|
||||
key-store-password: ${STORE_PASS:123456}
|
||||
jks-path: ${JKS_PATH:classpath:/test.jks}
|
||||
# 服务端IP
|
||||
server-ip: ${SERVER_IP:localhost}
|
||||
# 服务端端口(对应服务端app.yml中的tunnel.port、tunnel.ssl-port)
|
||||
server-port: ${SERVER_PORT:9002}
|
||||
# 是否启用SSL(注意:该配置必须和server-port对应上)
|
||||
ssl-enable: ${SSL_ENABLE:true}
|
||||
# 客户端连接唯一凭证
|
||||
license-key: ${LICENSE_KEY:}
|
||||
# 客户端唯一身份标识(可忽略,若不设置首次启动会自动生成)
|
||||
client-id: ${CLIENT_ID:}
|
||||
# 是否开启隧道传输报文日志(日志级别为debug时开启才有效)
|
||||
transfer-log-enable: ${CLIENT_LOG:false}
|
||||
# 是否开启心跳日志
|
||||
heartbeat-log-enable: ${HEARTBEAT_LOG:false}
|
||||
# 重连设置
|
||||
reconnection:
|
||||
# 重连间隔(秒)
|
||||
interval-seconds: 10
|
||||
# 是否开启无限重连(未开启时,客户端license不合法会自动停止应用,开启了则不会,请谨慎开启)
|
||||
unlimited: false
|
||||
client:
|
||||
key-store-password: 123456
|
||||
jks-path: classpath:/test.jks
|
||||
server-ip: localhost
|
||||
server-port: 9002
|
||||
ssl-enable: true
|
||||
obtain-license-interval: 5
|
||||
license-key:
|
||||
udp:
|
||||
# 线程池相关配置,用于技术调优,可忽略
|
||||
boss-thread-count: 5
|
||||
work-thread-count: 20
|
||||
# udp傀儡端口范围
|
||||
puppet-port-range: 10000-10500
|
||||
# 是否开启隧道传输报文日志(日志级别为debug时开启才有效)
|
||||
transfer-log-enable: ${CLIENT_LOG:false}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<property name="LOG_FILE" value="/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" />
|
||||
|
||||
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>DEBUG</level>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
<charset>${ENCODE}</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_FILE}</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.zip</fileNamePattern>
|
||||
<maxHistory>7</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
<charset>${ENCODE}</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="console"/>
|
||||
<appender-ref ref="file"/>
|
||||
</root>
|
||||
|
||||
<logger name="com.example.logback" level="warn"/>
|
||||
<logger name="io.netty" level="warn"/>
|
||||
</configuration>
|
||||
@@ -4,7 +4,7 @@
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>neutrino-proxy</artifactId>
|
||||
<groupId>fun.asgc.neutrino</groupId>
|
||||
<groupId>org.dromara.neutrino-proxy</groupId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
@@ -13,6 +13,13 @@
|
||||
<artifactId>neutrino-proxy-core</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<!--solon aot start(用于 aot 时注册 native 元信息)-->
|
||||
<dependency>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>solon.aot</artifactId>
|
||||
</dependency>
|
||||
<!--solon aot end-->
|
||||
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
@@ -21,7 +28,7 @@
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${hutool.ver}</version>
|
||||
<version>${hutool.version}</version>
|
||||
</dependency>
|
||||
<!--lombok-->
|
||||
<dependency>
|
||||
@@ -30,4 +37,17 @@
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
|
||||
<includes>
|
||||
<include>META-INF/native-image/neutrino-proxy-core/*.json</include>
|
||||
</includes>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -19,10 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.core;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.util.AttributeKey;
|
||||
package org.dromara.neutrinoproxy.core;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
@@ -20,11 +20,13 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package fun.asgc.neutrino.proxy.core;
|
||||
package org.dromara.neutrinoproxy.core;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
@@ -38,6 +40,16 @@ public interface Constants {
|
||||
|
||||
AttributeKey<Integer> LICENSE_ID = AttributeKey.newInstance("license_id");
|
||||
|
||||
AttributeKey<String> TARGET_IP = AttributeKey.newInstance("targetIp");
|
||||
AttributeKey<Integer> TARGET_PORT = AttributeKey.newInstance("targetPort");
|
||||
AttributeKey<Integer> PROXY_RESPONSES = AttributeKey.newInstance("proxyResponses");
|
||||
AttributeKey<Long> PROXY_TIMEOUT_MS = AttributeKey.newInstance("proxyTimeoutMs");
|
||||
|
||||
// 临时解决 udp channel被close的问题
|
||||
AttributeKey<Boolean> IS_UDP_KEY = AttributeKey.newInstance("isUdp");
|
||||
AttributeKey<InetSocketAddress> SENDER = AttributeKey.newInstance("sender");
|
||||
|
||||
|
||||
int HEADER_SIZE = 4;
|
||||
int TYPE_SIZE = 1;
|
||||
int SERIAL_NUMBER_SIZE = 8;
|
||||
@@ -49,6 +61,9 @@ public interface Constants {
|
||||
String CONNECT = "CONNECT";
|
||||
String DISCONNECT = "DISCONNECT";
|
||||
String TRANSFER = "TRANSFER";
|
||||
String UDP_CONNECT = "UDP_CONNECT";
|
||||
String UDP_DISCONNECT = "UDP_DISCONNECT";
|
||||
String UDP_TRANSFER = "UDP_TRANSFER";
|
||||
String ERROR = "ERROR";
|
||||
String PORT_MAPPING_SYNC = "PORT_MAPPING_SYNC";
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package fun.asgc.neutrino.proxy.core;
|
||||
package org.dromara.neutrinoproxy.core;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@@ -33,9 +33,12 @@ import lombok.Getter;
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ExceptionEnum {
|
||||
SUCCESS(0, "成功"),
|
||||
AUTH_FAILED(1, "认证失败"),
|
||||
CONNECT_FAILED(2, "连接失败");
|
||||
SUCCESS(0, "success"),
|
||||
AUTH_FAILED(1, "auth failed"),
|
||||
CONNECT_FAILED(2, "connect failed"),
|
||||
@Deprecated
|
||||
LICENSE_CANNOT_REPEAT_CONNECT(3, "license cannot multiple client simultaneous use"),
|
||||
;
|
||||
|
||||
private Integer code;
|
||||
private String msg;
|
||||
@@ -20,7 +20,7 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package fun.asgc.neutrino.proxy.core;
|
||||
package org.dromara.neutrinoproxy.core;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.timeout.IdleStateEvent;
|
||||
@@ -20,7 +20,7 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package fun.asgc.neutrino.proxy.core;
|
||||
package org.dromara.neutrinoproxy.core;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package fun.asgc.neutrino.proxy.core;
|
||||
package org.dromara.neutrinoproxy.core;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@@ -38,13 +38,16 @@ import java.util.stream.Stream;
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ProxyDataTypeEnum {
|
||||
HEARTBEAT(0x01, Constants.ProxyDataTypeName.HEARTBEAT, "心跳"),
|
||||
AUTH(0x02, Constants.ProxyDataTypeName.AUTH,"认证"),
|
||||
CONNECT(0x03, Constants.ProxyDataTypeName.CONNECT,"连接"),
|
||||
DISCONNECT(0x04, Constants.ProxyDataTypeName.DISCONNECT,"断开连接"),
|
||||
TRANSFER(0x05, Constants.ProxyDataTypeName.TRANSFER,"数据传输"),
|
||||
ERROR(0x06, Constants.ProxyDataTypeName.ERROR,"异常"),
|
||||
PORT_MAPPING_SYNC(0x07, Constants.ProxyDataTypeName.PORT_MAPPING_SYNC, "端口映射同步");
|
||||
HEARTBEAT(0x01, Constants.ProxyDataTypeName.HEARTBEAT, "HEARTBEAT"),
|
||||
AUTH(0x02, Constants.ProxyDataTypeName.AUTH,"AUTH"),
|
||||
CONNECT(0x03, Constants.ProxyDataTypeName.CONNECT,"CONNECT"),
|
||||
DISCONNECT(0x04, Constants.ProxyDataTypeName.DISCONNECT,"DISCONNECT"),
|
||||
TRANSFER(0x05, Constants.ProxyDataTypeName.TRANSFER,"TRANSFER"),
|
||||
ERROR(0x06, Constants.ProxyDataTypeName.ERROR,"ERROR"),
|
||||
PORT_MAPPING_SYNC(0x07, Constants.ProxyDataTypeName.PORT_MAPPING_SYNC, "PORT_MAPPING_SYNC"),
|
||||
UDP_CONNECT(0x08, Constants.ProxyDataTypeName.UDP_CONNECT,"UDP_CONNECT"),
|
||||
UDP_DISCONNECT(0x09, Constants.ProxyDataTypeName.UDP_DISCONNECT,"UDP_DISCONNECT"),
|
||||
UDP_TRANSFER(0x10, Constants.ProxyDataTypeName.UDP_TRANSFER,"UDP_TRANSFER");
|
||||
private static Map<Integer,ProxyDataTypeEnum> cache = Stream.of(values()).collect(Collectors.toMap(ProxyDataTypeEnum::getType, Function.identity()));
|
||||
|
||||
private int type;
|
||||
@@ -20,11 +20,11 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package fun.asgc.neutrino.proxy.core;
|
||||
package org.dromara.neutrinoproxy.core;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.noear.snack.ONode;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -66,6 +66,18 @@ public class ProxyMessage {
|
||||
* 通用异常信息
|
||||
*/
|
||||
public static final byte TYPE_ERROR = 0x06;
|
||||
/**
|
||||
* UDP代理隧道连接
|
||||
*/
|
||||
public static final byte TYPE_UDP_CONNECT = 0x08;
|
||||
/**
|
||||
* UDP代理隧道断开连接
|
||||
*/
|
||||
public static final byte TYPE_UDP_DISCONNECT = 0x09;
|
||||
/**
|
||||
* UDP数据传输
|
||||
*/
|
||||
public static final byte TYPE_UDP_TRANSFER = 0x10;
|
||||
|
||||
/**
|
||||
* 消息类型
|
||||
@@ -100,18 +112,18 @@ public class ProxyMessage {
|
||||
return create().setType(TYPE_HEARTBEAT);
|
||||
}
|
||||
|
||||
public static ProxyMessage buildAuthMessage(String info) {
|
||||
public static ProxyMessage buildAuthMessage(String info, String clientId) {
|
||||
return create().setType(TYPE_AUTH)
|
||||
.setInfo(info);
|
||||
.setInfo(info + "," + clientId);
|
||||
}
|
||||
|
||||
public static ProxyMessage buildAuthResultMessage(Integer code, String msg, String licenseKey) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("code", code);
|
||||
data.put("msg", msg);
|
||||
data.put("licenseKey", licenseKey);
|
||||
ONode data = ONode.newObject();
|
||||
data.set("code", code);
|
||||
data.set("msg", msg);
|
||||
data.set("licenseKey", licenseKey);
|
||||
return create().setType(TYPE_AUTH)
|
||||
.setInfo(data.toJSONString());
|
||||
.setInfo(data.toJson());
|
||||
}
|
||||
|
||||
public static ProxyMessage buildConnectMessage(String visitorId) {
|
||||
@@ -130,16 +142,53 @@ public class ProxyMessage {
|
||||
.setData(data);
|
||||
}
|
||||
|
||||
|
||||
public static ProxyMessage buildUdpConnectMessage(UdpBaseInfo info) {
|
||||
return create().setType(TYPE_UDP_CONNECT)
|
||||
.setInfo(info.toJsonString());
|
||||
}
|
||||
|
||||
public static ProxyMessage buildUdpDisconnectMessage() {
|
||||
return create().setType(TYPE_UDP_DISCONNECT);
|
||||
}
|
||||
|
||||
public static ProxyMessage buildUdpTransferMessage(UdpBaseInfo info) {
|
||||
return create().setType(TYPE_UDP_TRANSFER)
|
||||
.setInfo(info.toJsonString());
|
||||
}
|
||||
|
||||
public static ProxyMessage buildErrMessage(ExceptionEnum exceptionEnum, String info) {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("code", exceptionEnum.getCode());
|
||||
data.put("msg", exceptionEnum.getMsg());
|
||||
data.put("info", info);
|
||||
ONode data = ONode.newObject();
|
||||
data.set("code", exceptionEnum.getCode());
|
||||
data.set("msg", exceptionEnum.getMsg());
|
||||
data.set("info", info);
|
||||
return create().setType(TYPE_ERROR)
|
||||
.setInfo(data.toJSONString());
|
||||
.setInfo(data.toJson());
|
||||
}
|
||||
|
||||
public static ProxyMessage buildErrMessage(ExceptionEnum exceptionEnum) {
|
||||
return buildErrMessage(exceptionEnum, null);
|
||||
}
|
||||
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
public static class UdpBaseInfo {
|
||||
private String visitorId;
|
||||
private String visitorIp;
|
||||
private int visitorPort;
|
||||
private int serverPort;
|
||||
private String targetIp;
|
||||
private int targetPort;
|
||||
/**
|
||||
* 期待的响应数
|
||||
*/
|
||||
private int proxyResponses;
|
||||
/**
|
||||
* 超时时间(<=0时,相当于不需要响应)
|
||||
*/
|
||||
private long proxyTimeoutMs;
|
||||
public String toJsonString() {
|
||||
return ONode.serialize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,12 +20,12 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package fun.asgc.neutrino.proxy.core;
|
||||
package org.dromara.neutrinoproxy.core;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
|
||||
import static fun.asgc.neutrino.proxy.core.Constants.*;
|
||||
import static org.dromara.neutrinoproxy.core.Constants.*;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -20,12 +20,12 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package fun.asgc.neutrino.proxy.core;
|
||||
package org.dromara.neutrinoproxy.core;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
import static fun.asgc.neutrino.proxy.core.Constants.*;
|
||||
import static org.dromara.neutrinoproxy.core.Constants.*;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -1,6 +1,6 @@
|
||||
package fun.asgc.neutrino.proxy.core;
|
||||
package org.dromara.neutrinoproxy.core;
|
||||
|
||||
import fun.asgc.neutrino.proxy.core.dispatcher.Handler;
|
||||
import org.dromara.neutrinoproxy.core.dispatcher.Handler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
|
||||
/**
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.dromara.neutrinoproxy.core.aot;
|
||||
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
import org.noear.solon.aot.RuntimeNativeMetadata;
|
||||
import org.noear.solon.aot.RuntimeNativeRegistrar;
|
||||
import org.noear.solon.aot.hint.MemberCategory;
|
||||
import org.noear.solon.core.AppContext;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author songyinyin
|
||||
* @since 2023/10/23 21:33
|
||||
*/
|
||||
public class NeutrinoCoreRuntimeNativeRegistrar implements RuntimeNativeRegistrar {
|
||||
@Override
|
||||
public void register(AppContext context, RuntimeNativeMetadata metadata) {
|
||||
Set<Class<?>> channelInboundClasses = ClassUtil.scanPackageBySuper("org.dromara.neutrinoproxy", SimpleChannelInboundHandler.class);
|
||||
for (Class<?> clazz : channelInboundClasses) {
|
||||
metadata.registerReflection(clazz, MemberCategory.INVOKE_DECLARED_METHODS);
|
||||
}
|
||||
|
||||
metadata.registerReflection(ProxyMessage.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
|
||||
metadata.registerReflection(ProxyMessage.UdpBaseInfo.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
|
||||
|
||||
metadata.registerArg("--add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-exports=java.base/jdk.internal.misc=ALL-UNNAMED");
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package fun.asgc.neutrino.proxy.core.base;
|
||||
package org.dromara.neutrinoproxy.core.base;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
|
||||
package fun.asgc.neutrino.proxy.core.base;
|
||||
package org.dromara.neutrinoproxy.core.base;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -1,4 +1,4 @@
|
||||
package fun.asgc.neutrino.proxy.core.base;
|
||||
package org.dromara.neutrinoproxy.core.base;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -1,11 +1,11 @@
|
||||
package fun.asgc.neutrino.proxy.core.dispatcher;
|
||||
package org.dromara.neutrinoproxy.core.dispatcher;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import fun.asgc.neutrino.proxy.core.util.TypeUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.neutrinoproxy.core.util.TypeUtil;
|
||||
import org.noear.snack.ONode;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -35,10 +35,10 @@ public class DefaultDispatcher<Context, Data> implements Dispatcher<Context, Dat
|
||||
private Function<Data,String> matcher;
|
||||
|
||||
public DefaultDispatcher(String name, List<? extends Handler<Context,Data>> handlerList, Function<Data,String> matcher) {
|
||||
Assert.notNull(name, "名称不能为空!");
|
||||
Assert.notNull(matcher, "匹配器不能为空!");
|
||||
Assert.notNull(name, "name cannot empty!");
|
||||
Assert.notNull(matcher, "matcher cannot empty!");
|
||||
if (CollectionUtil.isEmpty(handlerList)) {
|
||||
log.error("{} 处理器列表为空.", name);
|
||||
log.error("{} handler list empty.", name);
|
||||
return;
|
||||
}
|
||||
this.name = name == null ? "" : name;
|
||||
@@ -48,20 +48,20 @@ public class DefaultDispatcher<Context, Data> implements Dispatcher<Context, Dat
|
||||
for (Handler handler : handlerList) {
|
||||
Match match = handler.getClass().getAnnotation(Match.class);
|
||||
if (null == match) {
|
||||
log.warn("{} 类: {} 缺失Match注解", this.name, handler.getClass().getName());
|
||||
log.warn("{} class: {} notfound Match annotation", this.name, handler.getClass().getName());
|
||||
continue;
|
||||
}
|
||||
if (StrUtil.isEmpty(match.type())) {
|
||||
log.warn("{} 类: {} match注解缺失type参数!", this.name, handler.getClass().getName());
|
||||
log.warn("{} class: {} match annotation notfound type param!", this.name, handler.getClass().getName());
|
||||
continue;
|
||||
}
|
||||
if (handlerMap.containsKey(match.type())) {
|
||||
log.warn("{} 类: {} match注解type值{} 存在重复!", this.name, handler.getClass().getName(), match.type());
|
||||
log.warn("{} class: {} match annotation type value {} repeat!", this.name, handler.getClass().getName(), match.type());
|
||||
continue;
|
||||
}
|
||||
handlerMap.put(match.type(), handler);
|
||||
}
|
||||
log.info("{} 处理器初始化完成", this.name);
|
||||
log.info("{} dispatcher init success", this.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -71,19 +71,18 @@ public class DefaultDispatcher<Context, Data> implements Dispatcher<Context, Dat
|
||||
}
|
||||
String type = matcher.apply(data);
|
||||
if (null == type) {
|
||||
log.warn("{} 获取匹配类型失败 data:{}", this.name, JSONObject.toJSONString(data));
|
||||
log.warn("{} get match type failed data:{}", this.name, ONode.serialize(data));
|
||||
return;
|
||||
}
|
||||
Handler<Context,Data> handler = handlerMap.get(type);
|
||||
if (null == handler) {
|
||||
// log.debug("{} 找不到匹配的处理器 type:{}", this.name, type);
|
||||
return;
|
||||
}
|
||||
String handlerName = handler.name();
|
||||
if (StrUtil.isEmpty(handlerName)) {
|
||||
handlerName = TypeUtil.getSimpleName(handler.getClass());
|
||||
}
|
||||
log.debug("{} 处理器[{}]执行.", this.name, handlerName);
|
||||
log.debug("{} handler[{}]execute.", this.name, handlerName);
|
||||
handler.handle(context, data);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package fun.asgc.neutrino.proxy.core.dispatcher;
|
||||
package org.dromara.neutrinoproxy.core.dispatcher;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -20,7 +20,7 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package fun.asgc.neutrino.proxy.core.dispatcher;
|
||||
package org.dromara.neutrinoproxy.core.dispatcher;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -20,7 +20,7 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package fun.asgc.neutrino.proxy.core.dispatcher;
|
||||
package org.dromara.neutrinoproxy.core.dispatcher;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package fun.asgc.neutrino.proxy.core.type;
|
||||
package org.dromara.neutrinoproxy.core.type;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -1,6 +1,6 @@
|
||||
package fun.asgc.neutrino.proxy.core.type;
|
||||
package org.dromara.neutrinoproxy.core.type;
|
||||
|
||||
import fun.asgc.neutrino.proxy.core.util.TypeUtil;
|
||||
import org.dromara.neutrinoproxy.core.util.TypeUtil;
|
||||
|
||||
/**
|
||||
* 默认的类型匹配器组
|
||||
@@ -1,4 +1,4 @@
|
||||
package fun.asgc.neutrino.proxy.core.type;
|
||||
package org.dromara.neutrinoproxy.core.type;
|
||||
|
||||
/**
|
||||
* 类型转换器
|
||||
@@ -1,4 +1,4 @@
|
||||
package fun.asgc.neutrino.proxy.core.type;
|
||||
package org.dromara.neutrinoproxy.core.type;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
@@ -1,4 +1,4 @@
|
||||
package fun.asgc.neutrino.proxy.core.type;
|
||||
package org.dromara.neutrinoproxy.core.type;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
package fun.asgc.neutrino.proxy.core.type;
|
||||
package org.dromara.neutrinoproxy.core.type;
|
||||
|
||||
/**
|
||||
* 值类型匹配器
|
||||
@@ -1,4 +1,4 @@
|
||||
package fun.asgc.neutrino.proxy.core.type;
|
||||
package org.dromara.neutrinoproxy.core.type;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -1,29 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.core.type;
|
||||
package org.dromara.neutrinoproxy.core.type;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import fun.asgc.neutrino.proxy.core.type.extension.*;
|
||||
import fun.asgc.neutrino.proxy.core.util.Assert;
|
||||
import org.dromara.neutrinoproxy.core.type.extension.*;
|
||||
import org.dromara.neutrinoproxy.core.util.Assert;
|
||||
import org.dromara.neutrinoproxy.core.type.extension.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
@@ -1,8 +1,8 @@
|
||||
package fun.asgc.neutrino.proxy.core.type.extension;
|
||||
package org.dromara.neutrinoproxy.core.type.extension;
|
||||
|
||||
import fun.asgc.neutrino.proxy.core.type.AbstractMatcherGroup;
|
||||
import fun.asgc.neutrino.proxy.core.type.TypeMatchInfo;
|
||||
import fun.asgc.neutrino.proxy.core.type.TypeMatcher;
|
||||
import org.dromara.neutrinoproxy.core.type.AbstractMatcherGroup;
|
||||
import org.dromara.neutrinoproxy.core.type.TypeMatchInfo;
|
||||
import org.dromara.neutrinoproxy.core.type.TypeMatcher;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@@ -1,9 +1,9 @@
|
||||
package fun.asgc.neutrino.proxy.core.type.extension;
|
||||
package org.dromara.neutrinoproxy.core.type.extension;
|
||||
|
||||
import fun.asgc.neutrino.proxy.core.type.AbstractMatcherGroup;
|
||||
import fun.asgc.neutrino.proxy.core.type.TypeMatchInfo;
|
||||
import fun.asgc.neutrino.proxy.core.type.TypeMatcher;
|
||||
import fun.asgc.neutrino.proxy.core.util.TypeUtil;
|
||||
import org.dromara.neutrinoproxy.core.type.AbstractMatcherGroup;
|
||||
import org.dromara.neutrinoproxy.core.type.TypeMatchInfo;
|
||||
import org.dromara.neutrinoproxy.core.type.TypeMatcher;
|
||||
import org.dromara.neutrinoproxy.core.util.TypeUtil;
|
||||
|
||||
/**
|
||||
* 布尔值匹配器组
|
||||
@@ -1,9 +1,9 @@
|
||||
package fun.asgc.neutrino.proxy.core.type.extension;
|
||||
package org.dromara.neutrinoproxy.core.type.extension;
|
||||
|
||||
import fun.asgc.neutrino.proxy.core.type.AbstractMatcherGroup;
|
||||
import fun.asgc.neutrino.proxy.core.type.TypeMatchInfo;
|
||||
import fun.asgc.neutrino.proxy.core.type.TypeMatcher;
|
||||
import fun.asgc.neutrino.proxy.core.util.TypeUtil;
|
||||
import org.dromara.neutrinoproxy.core.type.AbstractMatcherGroup;
|
||||
import org.dromara.neutrinoproxy.core.type.TypeMatchInfo;
|
||||
import org.dromara.neutrinoproxy.core.type.TypeMatcher;
|
||||
import org.dromara.neutrinoproxy.core.util.TypeUtil;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -1,8 +1,8 @@
|
||||
package fun.asgc.neutrino.proxy.core.type.extension;
|
||||
package org.dromara.neutrinoproxy.core.type.extension;
|
||||
|
||||
import fun.asgc.neutrino.proxy.core.type.AbstractMatcherGroup;
|
||||
import fun.asgc.neutrino.proxy.core.type.TypeMatchInfo;
|
||||
import fun.asgc.neutrino.proxy.core.type.TypeMatcher;
|
||||
import org.dromara.neutrinoproxy.core.type.AbstractMatcherGroup;
|
||||
import org.dromara.neutrinoproxy.core.type.TypeMatchInfo;
|
||||
import org.dromara.neutrinoproxy.core.type.TypeMatcher;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -1,6 +1,6 @@
|
||||
package fun.asgc.neutrino.proxy.core.type.extension;
|
||||
package org.dromara.neutrinoproxy.core.type.extension;
|
||||
|
||||
import fun.asgc.neutrino.proxy.core.type.AbstractMatcherGroup;
|
||||
import org.dromara.neutrinoproxy.core.type.AbstractMatcherGroup;
|
||||
|
||||
/**
|
||||
* Map匹配器组
|
||||
@@ -1,9 +1,9 @@
|
||||
package fun.asgc.neutrino.proxy.core.type.extension;
|
||||
package org.dromara.neutrinoproxy.core.type.extension;
|
||||
|
||||
import fun.asgc.neutrino.proxy.core.type.AbstractMatcherGroup;
|
||||
import fun.asgc.neutrino.proxy.core.type.TypeMatchInfo;
|
||||
import fun.asgc.neutrino.proxy.core.type.TypeMatcher;
|
||||
import fun.asgc.neutrino.proxy.core.util.TypeUtil;
|
||||
import org.dromara.neutrinoproxy.core.type.AbstractMatcherGroup;
|
||||
import org.dromara.neutrinoproxy.core.type.TypeMatchInfo;
|
||||
import org.dromara.neutrinoproxy.core.type.TypeMatcher;
|
||||
import org.dromara.neutrinoproxy.core.util.TypeUtil;
|
||||
|
||||
/**
|
||||
* 字符串匹配器组
|
||||
@@ -1,8 +1,8 @@
|
||||
package fun.asgc.neutrino.proxy.core.type.extension;
|
||||
package org.dromara.neutrinoproxy.core.type.extension;
|
||||
|
||||
import fun.asgc.neutrino.proxy.core.type.AbstractMatcherGroup;
|
||||
import fun.asgc.neutrino.proxy.core.type.TypeMatchInfo;
|
||||
import fun.asgc.neutrino.proxy.core.type.TypeMatcher;
|
||||
import org.dromara.neutrinoproxy.core.type.AbstractMatcherGroup;
|
||||
import org.dromara.neutrinoproxy.core.type.TypeMatchInfo;
|
||||
import org.dromara.neutrinoproxy.core.type.TypeMatcher;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
@@ -1,9 +1,9 @@
|
||||
package fun.asgc.neutrino.proxy.core.type.extension;
|
||||
package org.dromara.neutrinoproxy.core.type.extension;
|
||||
|
||||
import fun.asgc.neutrino.proxy.core.type.AbstractMatcherGroup;
|
||||
import fun.asgc.neutrino.proxy.core.type.TypeMatchInfo;
|
||||
import fun.asgc.neutrino.proxy.core.type.TypeMatcher;
|
||||
import fun.asgc.neutrino.proxy.core.util.TypeUtil;
|
||||
import org.dromara.neutrinoproxy.core.type.AbstractMatcherGroup;
|
||||
import org.dromara.neutrinoproxy.core.type.TypeMatchInfo;
|
||||
import org.dromara.neutrinoproxy.core.type.TypeMatcher;
|
||||
import org.dromara.neutrinoproxy.core.util.TypeUtil;
|
||||
|
||||
/**
|
||||
* 字符串匹配器组
|
||||
@@ -1,4 +1,4 @@
|
||||
package fun.asgc.neutrino.proxy.core.util;
|
||||
package org.dromara.neutrinoproxy.core.util;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
@@ -1,4 +1,4 @@
|
||||
package fun.asgc.neutrino.proxy.core.util;
|
||||
package org.dromara.neutrinoproxy.core.util;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@@ -286,7 +286,7 @@ public class DateUtil {
|
||||
public static Date getHourBegin(Date date) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(date);
|
||||
setCalender(calendar, calendar.get(Calendar.HOUR), 0, 0, 0);
|
||||
setCalender(calendar, calendar.get(Calendar.HOUR_OF_DAY), 0, 0, 0);
|
||||
return calendar.getTime();
|
||||
}
|
||||
|
||||
@@ -298,7 +298,7 @@ public class DateUtil {
|
||||
public static Date getHourEnd(Date date) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(date);
|
||||
setCalender(calendar, calendar.get(Calendar.HOUR), 59, 59, 999);
|
||||
setCalender(calendar, calendar.get(Calendar.HOUR_OF_DAY), 59, 59, 999);
|
||||
return calendar.getTime();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package fun.asgc.neutrino.proxy.core.util;
|
||||
package org.dromara.neutrinoproxy.core.util;
|
||||
|
||||
import fun.asgc.neutrino.proxy.core.base.InternalException;
|
||||
import fun.asgc.neutrino.proxy.core.base.MetaDataConstant;
|
||||
import org.dromara.neutrinoproxy.core.base.InternalException;
|
||||
import org.dromara.neutrinoproxy.core.base.MetaDataConstant;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.*;
|
||||
@@ -1,6 +1,6 @@
|
||||
package fun.asgc.neutrino.proxy.core.util;
|
||||
package org.dromara.neutrinoproxy.core.util;
|
||||
|
||||
import fun.asgc.neutrino.proxy.core.base.CodeBlock;
|
||||
import org.dromara.neutrinoproxy.core.base.CodeBlock;
|
||||
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Supplier;
|
||||
@@ -1,11 +1,11 @@
|
||||
package fun.asgc.neutrino.proxy.core.util;
|
||||
package org.dromara.neutrinoproxy.core.util;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.lang.Pair;
|
||||
import com.google.common.collect.Lists;
|
||||
import fun.asgc.neutrino.proxy.core.type.TypeMatchInfo;
|
||||
import fun.asgc.neutrino.proxy.core.type.TypeMatchers;
|
||||
import org.dromara.neutrinoproxy.core.type.TypeMatchInfo;
|
||||
import org.dromara.neutrinoproxy.core.type.TypeMatchers;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
@@ -0,0 +1,18 @@
|
||||
[
|
||||
{
|
||||
"name": "java.nio.channels.spi.SelectorProvider",
|
||||
"allDeclaredMethods": true
|
||||
},
|
||||
{
|
||||
"name": "sun.nio.ch.KQueueSelectorProvider",
|
||||
"allDeclaredMethods": true
|
||||
},
|
||||
{
|
||||
"name": "sun.nio.ch.PollSelectorProvider",
|
||||
"allDeclaredMethods": true
|
||||
},
|
||||
{
|
||||
"name": "sun.nio.ch.SelectorProviderImpl",
|
||||
"allDeclaredMethods": true
|
||||
}
|
||||
]
|
||||
@@ -6,9 +6,9 @@ RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
|
||||
RUN mkdir -p /root/neutrino-proxy/config
|
||||
RUN mkdir -p /root/neutrino-proxy/neutrino-proxy-admin
|
||||
WORKDIR /root/neutrino-proxy
|
||||
COPY neutrino-proxy-server/target/neutrino-proxy-server.jar /root/neutrino-proxy/neutrino-proxy-server.jar
|
||||
COPY neutrino-proxy-server/src/main/resources/app.yml /root/neutrino-proxy/config
|
||||
COPY neutrino-proxy-admin/dist /root/neutrino-proxy/neutrino-proxy-admin/dist
|
||||
COPY ../neutrino-proxy-server/target/neutrino-proxy-server.jar /root/neutrino-proxy/neutrino-proxy-server.jar
|
||||
COPY ../neutrino-proxy-server/src/main/resources/app.yml /root/neutrino-proxy/config
|
||||
COPY ../neutrino-proxy-admin/dist /root/neutrino-proxy/neutrino-proxy-admin/dist
|
||||
#VOLUME ["/root/neutrino-proxy"]
|
||||
ENTRYPOINT ["java","-jar","neutrino-proxy-server.jar","config=./config/app.yml"]
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
version: '3.1'
|
||||
|
||||
services:
|
||||
|
||||
neutrino-proxy11:
|
||||
container_name: 'np-server'
|
||||
restart: always
|
||||
image: registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:1.8.0
|
||||
ports:
|
||||
- "9000-9200:9000-9200"
|
||||
- "8888:8888"
|
||||
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
volumes:
|
||||
- /root/neutrino-proxy/config:/root/neutrino-proxy/config
|
||||
|
||||
privileged: true
|
||||
@@ -4,7 +4,7 @@
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>neutrino-proxy</artifactId>
|
||||
<groupId>fun.asgc.neutrino</groupId>
|
||||
<groupId>org.dromara.neutrino-proxy</groupId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
@@ -24,90 +24,57 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>mybatis-pagehelper-solon-plugin</artifactId>
|
||||
<artifactId>mybatis-plus-extension-solon-plugin</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>activerecord-solon-plugin</artifactId>
|
||||
<artifactId>wood-solon-plugin</artifactId>
|
||||
</dependency>
|
||||
<!--orika-->
|
||||
<dependency>
|
||||
<groupId>fun.asgc</groupId>
|
||||
<artifactId>orika-solon-plugin</artifactId>
|
||||
<version>2.2.2</version>
|
||||
</dependency>
|
||||
<!--job-->
|
||||
<dependency>
|
||||
<groupId>fun.asgc</groupId>
|
||||
<groupId>org.dromara.solon-plugins</groupId>
|
||||
<artifactId>job-solon-plugin</artifactId>
|
||||
<version>2.2.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>fun.asgc.neutrino</groupId>
|
||||
<groupId>org.dromara.neutrino-proxy</groupId>
|
||||
<artifactId>neutrino-proxy-core</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mariadb.jdbc</groupId>
|
||||
<artifactId>mariadb-java-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<!-- <build>-->
|
||||
<!-- <finalName>${artifactId}</finalName>-->
|
||||
<!-- <plugins>-->
|
||||
<!-- <plugin>-->
|
||||
<!-- <groupId>org.springframework.boot</groupId>-->
|
||||
<!-- <artifactId>spring-boot-maven-plugin</artifactId>-->
|
||||
<!-- <version>2.1.3.RELEASE</version>-->
|
||||
<!-- <configuration>-->
|
||||
<!-- <mainClass>fun.asgc.neutrino.proxy.server.ProxyServer</mainClass>-->
|
||||
<!-- </configuration>-->
|
||||
<!-- <executions>-->
|
||||
<!-- <execution>-->
|
||||
<!-- <goals>-->
|
||||
<!-- <goal>repackage</goal>-->
|
||||
<!-- </goals>-->
|
||||
<!-- </execution>-->
|
||||
<!-- </executions>-->
|
||||
<!-- </plugin>-->
|
||||
<!-- </plugins>-->
|
||||
<!-- </build>-->
|
||||
|
||||
<build>
|
||||
<finalName>${project.artifactId}</finalName>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>${project.basedir}/src/main/resources</directory>
|
||||
<filtering>false</filtering>
|
||||
|
||||
<excludes>
|
||||
<exclude>app-dev.yml</exclude>
|
||||
</excludes>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<!-- 配置打包插件(并打包成胖包) -->
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<configuration>
|
||||
<appendAssemblyId>false</appendAssemblyId>
|
||||
<finalName>${artifactId}</finalName>
|
||||
<archive>
|
||||
<manifest>
|
||||
<!--这里指定要运行的main类-->
|
||||
<mainClass>fun.asgc.neutrino.proxy.server.ProxyServer</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
<descriptorRefs>
|
||||
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||
</descriptorRefs>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-assembly</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>solon-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
package fun.asgc.neutrino.proxy.server.base.db;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.MybatisSqlSessionFactoryBuilder;
|
||||
import com.baomidou.mybatisplus.core.config.GlobalConfig;
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import fun.asgc.neutrino.proxy.server.constant.DbTypeEnum;
|
||||
import org.apache.ibatis.solon.annotation.Db;
|
||||
import org.noear.solon.annotation.Bean;
|
||||
import org.noear.solon.annotation.Configuration;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
import org.sqlite.SQLiteConfig;
|
||||
import org.sqlite.SQLiteDataSource;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/3/10
|
||||
*/
|
||||
@Configuration
|
||||
public class DbConfiguration {
|
||||
|
||||
@Bean(value = "db", typed = true)
|
||||
public DataSource dataSource(@Inject DbConfig dbConfig) {
|
||||
DbTypeEnum dbTypeEnum = DbTypeEnum.of(dbConfig.getType());
|
||||
if (DbTypeEnum.SQLITE == dbTypeEnum) {
|
||||
SQLiteDataSource dataSource = new SQLiteDataSource();
|
||||
dataSource.setUrl(dbConfig.getUrl());
|
||||
dataSource.setJournalMode(SQLiteConfig.JournalMode.WAL.getValue());
|
||||
return dataSource;
|
||||
} else if (DbTypeEnum.MYSQL == dbTypeEnum) {
|
||||
HikariDataSource dataSource = new HikariDataSource();
|
||||
dataSource.setDriverClassName(dbConfig.getDriverClass());
|
||||
dataSource.setJdbcUrl(dbConfig.getUrl());
|
||||
dataSource.setMinimumIdle(5);
|
||||
dataSource.setMaximumPoolSize(20);
|
||||
dataSource.setMaxLifetime(60000);
|
||||
// dataSource.setInitialSize(5);
|
||||
// dataSource.setMinIdle(5);
|
||||
// dataSource.setMaxActive(20);
|
||||
// dataSource.setMaxWait(60000);
|
||||
// dataSource.setPoolPreparedStatements(true);
|
||||
dataSource.setUsername(dbConfig.getUsername());
|
||||
dataSource.setPassword(dbConfig.getPassword());
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public void db1_ext(@Db("db") GlobalConfig globalConfig) {
|
||||
MetaObjectHandler metaObjectHandler = new MetaObjectHandlerImpl();
|
||||
globalConfig.setMetaObjectHandler(metaObjectHandler);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public void db1_ext2(@Db("db") MybatisConfiguration config){
|
||||
config.getTypeHandlerRegistry().register("fun.asgc.neutrino.proxy.server.dal");
|
||||
config.setDefaultEnumTypeHandler(null);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MybatisSqlSessionFactoryBuilder factoryBuilderNew(){
|
||||
return new MybatisSqlSessionFactoryBuilderImpl();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package fun.asgc.neutrino.proxy.server.base.proxy;
|
||||
|
||||
import fun.asgc.neutrino.proxy.core.ProxyDataTypeEnum;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessage;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessageHandler;
|
||||
import fun.asgc.neutrino.proxy.core.dispatcher.DefaultDispatcher;
|
||||
import fun.asgc.neutrino.proxy.core.dispatcher.Dispatcher;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import org.noear.solon.Solon;
|
||||
import org.noear.solon.annotation.Bean;
|
||||
import org.noear.solon.annotation.Configuration;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
import org.noear.solon.core.AopContext;
|
||||
import org.noear.solon.core.bean.LifecycleBean;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 代理配置
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/10/8
|
||||
*/
|
||||
@Configuration
|
||||
public class ProxyConfiguration implements LifecycleBean {
|
||||
|
||||
@Override
|
||||
public void start() throws Throwable {
|
||||
List<ProxyMessageHandler> list = Solon.context().getBeansOfType(ProxyMessageHandler.class);
|
||||
Dispatcher<ChannelHandlerContext, ProxyMessage> dispatcher = new DefaultDispatcher<>("消息调度器", list,
|
||||
proxyMessage -> ProxyDataTypeEnum.of((int)proxyMessage.getType()) == null ?
|
||||
null : ProxyDataTypeEnum.of((int)proxyMessage.getType()).getName());
|
||||
|
||||
Solon.context().wrapAndPut(Dispatcher.class, dispatcher);
|
||||
}
|
||||
|
||||
@Bean("serverBossGroup")
|
||||
public NioEventLoopGroup serverBossGroup() {
|
||||
return new NioEventLoopGroup();
|
||||
}
|
||||
|
||||
@Bean("serverWorkerGroup")
|
||||
public NioEventLoopGroup serverWorkerGroup() {
|
||||
return new NioEventLoopGroup();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package fun.asgc.neutrino.proxy.server.controller;
|
||||
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.*;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.*;
|
||||
import fun.asgc.neutrino.proxy.server.service.PortMappingService;
|
||||
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
|
||||
import org.noear.solon.annotation.*;
|
||||
|
||||
/**
|
||||
* 端口映射
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/8/8
|
||||
*/
|
||||
@Mapping("/port-mapping")
|
||||
@Controller
|
||||
public class PortMappingController {
|
||||
@Inject
|
||||
private PortMappingService portMappingService;
|
||||
|
||||
@Get
|
||||
@Mapping("/page")
|
||||
public PageInfo<PortMappingListRes> page(PageQuery pageQuery, PortMappingListReq req) {
|
||||
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
|
||||
|
||||
return portMappingService.page(pageQuery, req);
|
||||
}
|
||||
|
||||
@Post
|
||||
@Mapping("/create")
|
||||
public PortMappingCreateRes create(PortMappingCreateReq req) {
|
||||
ParamCheckUtil.checkNotNull(req, "req");
|
||||
ParamCheckUtil.checkNotNull(req.getLicenseId(), "licenseId");
|
||||
ParamCheckUtil.checkNotNull(req.getServerPort(), "serverPort");
|
||||
ParamCheckUtil.checkNotEmpty(req.getClientIp(), "clientIp");
|
||||
ParamCheckUtil.checkNotNull(req.getClientPort(), "clientPort");
|
||||
|
||||
return portMappingService.create(req);
|
||||
}
|
||||
|
||||
@Post
|
||||
@Mapping("/update")
|
||||
public PortMappingUpdateRes update(PortMappingUpdateReq req) {
|
||||
ParamCheckUtil.checkNotNull(req, "req");
|
||||
|
||||
return portMappingService.update(req);
|
||||
}
|
||||
|
||||
@Get
|
||||
@Mapping("/detail")
|
||||
public PortMappingDetailRes detail(PortMappingDetailReq req) {
|
||||
ParamCheckUtil.checkNotNull(req, "req");
|
||||
ParamCheckUtil.checkNotNull(req.getId(), "id");
|
||||
|
||||
return portMappingService.detail(req.getId());
|
||||
}
|
||||
|
||||
@Post
|
||||
@Mapping("/update/enable-status")
|
||||
public PortMappingUpdateEnableStatusRes updateEnableStatus(PortMappingUpdateEnableStatusReq req) {
|
||||
ParamCheckUtil.checkNotNull(req, "req");
|
||||
ParamCheckUtil.checkNotNull(req.getId(), "id");
|
||||
ParamCheckUtil.checkNotNull(req.getEnable(), "enable");
|
||||
|
||||
return portMappingService.updateEnableStatus(req);
|
||||
}
|
||||
|
||||
@Post
|
||||
@Mapping("/delete")
|
||||
public void delete(PortMappingDeleteReq req) {
|
||||
ParamCheckUtil.checkNotNull(req, "req");
|
||||
ParamCheckUtil.checkNotNull(req.getId(), "id");
|
||||
|
||||
portMappingService.delete(req.getId());
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package fun.asgc.neutrino.proxy.server.controller;
|
||||
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.LicenseFlowReportReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.UserFlowReportReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.LicenseFlowReportRes;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.ReportDataViewRes;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.UserFlowReportRes;
|
||||
import fun.asgc.neutrino.proxy.server.service.ReportService;
|
||||
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
|
||||
import org.noear.solon.annotation.Controller;
|
||||
import org.noear.solon.annotation.Get;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
import org.noear.solon.annotation.Mapping;
|
||||
|
||||
/**
|
||||
* 报表管理
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/9/12
|
||||
*/
|
||||
@Mapping("/report")
|
||||
@Controller
|
||||
public class ReportController {
|
||||
|
||||
@Inject
|
||||
private ReportService reportService;
|
||||
|
||||
/**
|
||||
* 数据一览:
|
||||
* 在线用户数 查询token有效的用户ID数
|
||||
* 在线Client数 查询license表,状态为在线的数量
|
||||
* 今日流量 上行/下行/总计
|
||||
* 历史流量 上行/下行/总计
|
||||
*
|
||||
* 1、点击在线用户数:下方展示在线的用户列表,带分页
|
||||
* 2、点击在线client数:下方展示在线的license列表,带分页
|
||||
* 3、点击今日流量:下方展示今日的流量折线图(小时)。 筛选项:全部/用户列表
|
||||
* 4、点击历史流量:下方展示历史流量折线图。筛选项:日/月 日期选择:日(展示最近30天,最多跨30日),月(展示最近12个月,最多跨12个月)
|
||||
* @return
|
||||
*/
|
||||
@Get
|
||||
@Mapping("/data-view")
|
||||
public ReportDataViewRes dataView() {
|
||||
return new ReportDataViewRes()
|
||||
.setUserOnlineNumber(2).setEnableUserNumber(3).setUserNumber(5)
|
||||
.setLicenseNumber(3).setEnableLicenseNumber(6).setLicenseNumber(6)
|
||||
.setServerPortOnlineNumber(3).setEnableServerPortNumber(5).setServerPortNumber(6)
|
||||
.setTotalUpstreamFlow("23K").setTotalDownwardFlow("47M")
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户流量报表分页
|
||||
* @param pageQuery
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
@Get
|
||||
@Mapping("/user/flow-report/page")
|
||||
public PageInfo<UserFlowReportRes> userFlowReportPage(PageQuery pageQuery, UserFlowReportReq req) {
|
||||
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
|
||||
|
||||
return reportService.userFlowReportPage(pageQuery, req);
|
||||
}
|
||||
|
||||
/**
|
||||
* license流量报表分页
|
||||
* @param pageQuery
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
@Get
|
||||
@Mapping("/license/flow-report/page")
|
||||
public PageInfo<LicenseFlowReportRes> licenseFlowReportPage(PageQuery pageQuery, LicenseFlowReportReq req) {
|
||||
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
|
||||
|
||||
return reportService.licenseFlowReportPage(pageQuery, req);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/12/21
|
||||
*/
|
||||
@Data
|
||||
public class LicenseFlowReportReq {
|
||||
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 端口映射列表请求
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/8/8
|
||||
*/
|
||||
@Data
|
||||
public class PortMappingListReq {
|
||||
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/12/21
|
||||
*/
|
||||
@Data
|
||||
public class UserFlowReportReq {
|
||||
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/9/12
|
||||
*/
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
public class ReportDataViewRes {
|
||||
/**
|
||||
* 在线用户数
|
||||
*/
|
||||
private Integer userOnlineNumber;
|
||||
/**
|
||||
* 启用用户数
|
||||
*/
|
||||
private Integer enableUserNumber;
|
||||
/**
|
||||
* 用户总数
|
||||
*/
|
||||
private Integer userNumber;
|
||||
/**
|
||||
* 在线license数
|
||||
*/
|
||||
private Integer licenseOnlineNumber;
|
||||
/**
|
||||
* 启用license数
|
||||
*/
|
||||
private Integer enableLicenseNumber;
|
||||
/**
|
||||
* license总数
|
||||
*/
|
||||
private Integer licenseNumber;
|
||||
/**
|
||||
* 服务端口在线数
|
||||
*/
|
||||
private Integer serverPortOnlineNumber;
|
||||
/**
|
||||
* 启用服务端口数
|
||||
*/
|
||||
private Integer enableServerPortNumber;
|
||||
/**
|
||||
* 总的服务端口数
|
||||
*/
|
||||
private Integer serverPortNumber;
|
||||
/**
|
||||
* 累计上行流量
|
||||
*/
|
||||
private String totalUpstreamFlow;
|
||||
/**
|
||||
* 累计下行流量
|
||||
*/
|
||||
private String totalDownwardFlow;
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/12/21
|
||||
*/
|
||||
@Data
|
||||
public class UserFlowReportRes {
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Integer userId;
|
||||
/**
|
||||
* 用户名称
|
||||
*/
|
||||
private String userName;
|
||||
/**
|
||||
* 历史写入字节数
|
||||
*/
|
||||
private Long historyWriteBytes;
|
||||
/**
|
||||
* 历史读取字节数
|
||||
*/
|
||||
private Long historyReadBytes;
|
||||
/**
|
||||
* 写入字节数
|
||||
*/
|
||||
private Long writeBytes;
|
||||
/**
|
||||
* 读取字节数
|
||||
*/
|
||||
private Long readBytes;
|
||||
/**
|
||||
* 写入流量描述
|
||||
*/
|
||||
private String writeFlowStr;
|
||||
/**
|
||||
* 读取流量描述
|
||||
*/
|
||||
private String readFlowStr;
|
||||
/**
|
||||
* 流量描述
|
||||
*/
|
||||
private String flowStr;
|
||||
/**
|
||||
* 报表时间
|
||||
*/
|
||||
private Date date;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.dal;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.LicenseDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/8/6
|
||||
*/
|
||||
@Mapper
|
||||
public interface LicenseMapper extends BaseMapper<LicenseDO> {
|
||||
|
||||
default List<LicenseDO> listAll() {
|
||||
return this.selectList(new LambdaQueryWrapper<>());
|
||||
}
|
||||
|
||||
default List<LicenseDO> listByUserId(Integer userId) {
|
||||
return this.selectList(new LambdaQueryWrapper<LicenseDO>()
|
||||
.eq(LicenseDO::getUserId, userId)
|
||||
);
|
||||
}
|
||||
|
||||
default void updateEnableStatus(Integer id, Integer enable, Date updateTime) {
|
||||
this.update(null, new LambdaUpdateWrapper<LicenseDO>()
|
||||
.eq(LicenseDO::getId, id)
|
||||
.set(LicenseDO::getEnable, enable)
|
||||
.set(LicenseDO::getUpdateTime, updateTime)
|
||||
);
|
||||
}
|
||||
|
||||
default void updateOnlineStatus(Integer id, Integer isOnline, Date updateTime) {
|
||||
this.update(null, new LambdaUpdateWrapper<LicenseDO>()
|
||||
.eq(LicenseDO::getId, id)
|
||||
.set(LicenseDO::getIsOnline, isOnline)
|
||||
.set(LicenseDO::getUpdateTime, updateTime)
|
||||
);
|
||||
}
|
||||
|
||||
default void updateOnlineStatus(Integer isOnline, Date updateTime) {
|
||||
this.update(null, new LambdaUpdateWrapper<LicenseDO>()
|
||||
.set(LicenseDO::getIsOnline, updateTime)
|
||||
.set(LicenseDO::getUpdateTime, updateTime)
|
||||
);
|
||||
}
|
||||
|
||||
default void reset(Integer id, String key, Date updateTime) {
|
||||
this.update(null, new LambdaUpdateWrapper<LicenseDO>()
|
||||
.eq(LicenseDO::getId, id)
|
||||
.set(LicenseDO::getKey, key)
|
||||
.set(LicenseDO::getUpdateTime, updateTime)
|
||||
);
|
||||
}
|
||||
|
||||
default LicenseDO findById(Integer id) {
|
||||
return this.selectById(id);
|
||||
}
|
||||
|
||||
default void update(Integer id, String name, Date updateTime) {
|
||||
this.update(null, new LambdaUpdateWrapper<LicenseDO>()
|
||||
.eq(LicenseDO::getId, id)
|
||||
.set(LicenseDO::getName, name)
|
||||
.set(LicenseDO::getUpdateTime, updateTime)
|
||||
);
|
||||
}
|
||||
|
||||
default List<LicenseDO> findByIds(Set<Integer> ids) {
|
||||
return selectBatchIds(ids);
|
||||
}
|
||||
|
||||
default LicenseDO checkRepeat(Integer userId, String name) {
|
||||
return this.selectOne(new LambdaQueryWrapper<LicenseDO>()
|
||||
.eq(LicenseDO::getUserId, userId)
|
||||
.eq(LicenseDO::getName, name)
|
||||
.last("limit 1")
|
||||
);
|
||||
}
|
||||
|
||||
default LicenseDO checkRepeat(Integer userId, String name, Set<Integer> excludeIds) {
|
||||
return this.selectOne(new LambdaQueryWrapper<LicenseDO>()
|
||||
.eq(LicenseDO::getUserId, userId)
|
||||
.eq(LicenseDO::getName, name)
|
||||
.notIn(LicenseDO::getId, excludeIds)
|
||||
.last("limit 1")
|
||||
);
|
||||
}
|
||||
|
||||
default LicenseDO findByKey(String licenseKey) {
|
||||
return selectOne(new LambdaQueryWrapper<LicenseDO>()
|
||||
.eq(LicenseDO::getKey, licenseKey)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package fun.asgc.neutrino.proxy.server.proxy.core;
|
||||
|
||||
import fun.asgc.neutrino.proxy.core.Constants;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessage;
|
||||
import fun.asgc.neutrino.proxy.core.dispatcher.Dispatcher;
|
||||
import fun.asgc.neutrino.proxy.server.constant.ClientConnectTypeEnum;
|
||||
import fun.asgc.neutrino.proxy.server.constant.SuccessCodeEnum;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.ClientConnectRecordDO;
|
||||
import fun.asgc.neutrino.proxy.server.proxy.domain.CmdChannelAttachInfo;
|
||||
import fun.asgc.neutrino.proxy.server.service.ClientConnectRecordService;
|
||||
import fun.asgc.neutrino.proxy.server.service.ProxyMutualService;
|
||||
import fun.asgc.neutrino.proxy.server.util.ProxyUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.handler.timeout.IdleStateEvent;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.noear.solon.Solon;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
@Slf4j
|
||||
public class ServerChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
|
||||
private static volatile Dispatcher<ChannelHandlerContext, ProxyMessage> dispatcher;
|
||||
|
||||
public ServerChannelHandler() {
|
||||
dispatcher = Solon.context().getBean(Dispatcher.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception {
|
||||
dispatcher.dispatch(ctx, proxyMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
|
||||
Channel userChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
|
||||
if (userChannel != null) {
|
||||
userChannel.config().setOption(ChannelOption.AUTO_READ, ctx.channel().isWritable());
|
||||
}
|
||||
|
||||
super.channelWritabilityChanged(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
Channel userChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
|
||||
if (userChannel != null && userChannel.isActive()) {
|
||||
Integer licenseId = ctx.channel().attr(Constants.LICENSE_ID).get();
|
||||
String visitorId = ctx.channel().attr(Constants.VISITOR_ID).get();
|
||||
Channel cmdChannel = ProxyUtil.getCmdChannelByLicenseId(licenseId);
|
||||
|
||||
if (cmdChannel != null) {
|
||||
ProxyUtil.removeVisitorChannelFromCmdChannel(cmdChannel, visitorId);
|
||||
}
|
||||
|
||||
// 数据发送完成后再关闭连接,解决http1.0数据传输问题
|
||||
userChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
|
||||
userChannel.close();
|
||||
} else {
|
||||
CmdChannelAttachInfo cmdChannelAttachInfo = ProxyUtil.getAttachInfo(ctx.channel());
|
||||
if (null != cmdChannelAttachInfo) {
|
||||
Solon.context().getBean(ProxyMutualService.class).offline(cmdChannelAttachInfo);
|
||||
Solon.context().getBean(ClientConnectRecordService.class).add(new ClientConnectRecordDO()
|
||||
.setIp(((InetSocketAddress)ctx.channel().remoteAddress()).getAddress().getHostAddress())
|
||||
.setLicenseId(cmdChannelAttachInfo.getLicenseId())
|
||||
.setType(ClientConnectTypeEnum.DISCONNECT.getType())
|
||||
.setMsg("")
|
||||
.setCode(SuccessCodeEnum.SUCCESS.getCode())
|
||||
.setCreateTime(new Date())
|
||||
);
|
||||
}
|
||||
ProxyUtil.removeCmdChannel(ctx.channel());
|
||||
}
|
||||
|
||||
super.channelInactive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
super.exceptionCaught(ctx, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||
if(evt instanceof IdleStateEvent) {
|
||||
IdleStateEvent event = (IdleStateEvent)evt;
|
||||
switch (event.state()) {
|
||||
case READER_IDLE:
|
||||
// 读超时,断开连接
|
||||
log.info("读超时");
|
||||
ctx.channel().close();
|
||||
break;
|
||||
case WRITER_IDLE:
|
||||
log.info("写超时");
|
||||
break;
|
||||
case ALL_IDLE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package fun.asgc.neutrino.proxy.server.proxy.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
@Data
|
||||
public class Metrics implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private int port;
|
||||
private long readBytes;
|
||||
private long wroteBytes;
|
||||
private long readMsgs;
|
||||
private long wroteMsgs;
|
||||
private int channels;
|
||||
private long timestamp;
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package fun.asgc.neutrino.proxy.server.proxy.handler;
|
||||
|
||||
import fun.asgc.neutrino.proxy.core.Constants;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyDataTypeEnum;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessage;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessageHandler;
|
||||
import fun.asgc.neutrino.proxy.core.dispatcher.Match;
|
||||
import fun.asgc.neutrino.proxy.server.util.ProxyUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import org.noear.solon.annotation.Component;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
@Match(type = Constants.ProxyDataTypeName.DISCONNECT)
|
||||
@Component
|
||||
public class ProxyMessageDisconnectHandler implements ProxyMessageHandler {
|
||||
|
||||
@Override
|
||||
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
|
||||
Integer licenseId = ctx.channel().attr(Constants.LICENSE_ID).get();
|
||||
// licenseId为空,说明访问者通道已经关闭,无需处理
|
||||
if (null == licenseId) {
|
||||
return;
|
||||
}
|
||||
// 代理连接没有连上服务器由控制连接发送用户端断开连接消息
|
||||
String visitorId = proxyMessage.getInfo();
|
||||
Channel userChannel = ProxyUtil.removeVisitorChannelFromCmdChannel(ctx.channel(), visitorId);
|
||||
if (null != userChannel) {
|
||||
// 数据发送完成后再关闭连接,解决http1.0数据传输问题
|
||||
userChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return ProxyDataTypeEnum.DISCONNECT.getDesc();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
package fun.asgc.neutrino.proxy.server.service;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.github.pagehelper.Page;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.google.common.collect.Lists;
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
|
||||
import fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum;
|
||||
import fun.asgc.neutrino.proxy.server.constant.ExceptionConstant;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.JobInfoExecuteReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.JobInfoListReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.JobInfoUpdateEnableStatusReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.JobInfoUpdateReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.JobInfoExecuteRes;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.JobInfoListRes;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.JobInfoUpdateEnableStatusRes;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.JobInfoUpdateRes;
|
||||
import fun.asgc.neutrino.proxy.server.dal.JobInfoMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.JobInfoDO;
|
||||
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
|
||||
import fun.asgc.solon.extend.job.IJobSource;
|
||||
import fun.asgc.solon.extend.job.JobInfo;
|
||||
import fun.asgc.solon.extend.job.impl.JobExecutor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import ma.glasnost.orika.MapperFacade;
|
||||
import org.apache.ibatis.solon.annotation.Db;
|
||||
import org.noear.solon.Solon;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/9/5
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class JobInfoService implements IJobSource {
|
||||
@Inject
|
||||
private MapperFacade mapperFacade;
|
||||
@Db
|
||||
private JobInfoMapper jobInfoMapper;
|
||||
|
||||
public PageInfo<JobInfoListRes> page(PageQuery pageQuery, JobInfoListReq req) {
|
||||
Page<JobInfoListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
|
||||
List<JobInfoDO> list = jobInfoMapper.selectList(new LambdaQueryWrapper<JobInfoDO>()
|
||||
.orderByAsc(JobInfoDO::getId)
|
||||
);
|
||||
List<JobInfoListRes> respList = mapperFacade.mapAsList(list, JobInfoListRes.class);
|
||||
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
|
||||
}
|
||||
|
||||
public List<JobInfoDO> findList() {
|
||||
List<JobInfoDO> jobInfoDOList = jobInfoMapper.findList();
|
||||
return jobInfoDOList;
|
||||
}
|
||||
|
||||
public JobInfoUpdateEnableStatusRes updateEnableStatus(JobInfoUpdateEnableStatusReq req) {
|
||||
JobInfoDO jobInfoDO = jobInfoMapper.findById(req.getId());
|
||||
ParamCheckUtil.checkNotNull(jobInfoDO, ExceptionConstant.JOB_INFO_NOT_EXIST);
|
||||
jobInfoMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
|
||||
if (EnableStatusEnum.ENABLE.getStatus().equals(req.getEnable())) {
|
||||
Solon.context().getBean(JobExecutor.class).add(new JobInfo()
|
||||
.setId(String.valueOf(jobInfoDO.getId()))
|
||||
.setName(jobInfoDO.getHandler())
|
||||
.setDesc(jobInfoDO.getDesc())
|
||||
.setCron(jobInfoDO.getCron())
|
||||
.setParam(jobInfoDO.getParam())
|
||||
.setEnable(true)
|
||||
);
|
||||
} else {
|
||||
Solon.context().getBean(JobExecutor.class).remove(String.valueOf(req.getId()));
|
||||
}
|
||||
return new JobInfoUpdateEnableStatusRes();
|
||||
}
|
||||
|
||||
public JobInfoExecuteRes execute(JobInfoExecuteReq req) {
|
||||
Solon.context().getBean(JobExecutor.class).trigger(String.valueOf(req.getId()), req.getParam());
|
||||
return new JobInfoExecuteRes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JobInfo> sourceList() {
|
||||
List<JobInfo> jobInfoList = Lists.newArrayList();
|
||||
List<JobInfoDO> jobInfoDOList = jobInfoMapper.findList();
|
||||
if (CollectionUtil.isEmpty(jobInfoDOList)) {
|
||||
return jobInfoList;
|
||||
}
|
||||
for (JobInfoDO item : jobInfoDOList) {
|
||||
jobInfoList.add(new JobInfo()
|
||||
.setId(String.valueOf(item.getId()))
|
||||
.setName(item.getHandler())
|
||||
.setDesc(item.getDesc())
|
||||
.setCron(item.getCron())
|
||||
.setParam(item.getParam())
|
||||
.setEnable(EnableStatusEnum.ENABLE.getStatus().equals(item.getEnable()))
|
||||
);
|
||||
}
|
||||
|
||||
return jobInfoList;
|
||||
}
|
||||
|
||||
public JobInfoUpdateRes update(JobInfoUpdateReq req) {
|
||||
JobInfoDO jobInfoDO = jobInfoMapper.findById(req.getId());
|
||||
ParamCheckUtil.checkNotNull( jobInfoDO, ExceptionConstant.PORT_MAPPING_NOT_EXIST);
|
||||
JobInfoDO jobInfo = new JobInfoDO();
|
||||
jobInfo.setId(req.getId());
|
||||
jobInfo.setCron(req.getCron());
|
||||
jobInfo.setDesc(req.getDesc());
|
||||
jobInfo.setAlarmEmail(req.getAlarmEmail());
|
||||
jobInfo.setAlarmDing(req.getAlarmDing());
|
||||
jobInfo.setParam(req.getParam());
|
||||
jobInfo.setUpdateTime(new Date());
|
||||
|
||||
jobInfoMapper.updateById(jobInfo);
|
||||
return new JobInfoUpdateRes();
|
||||
}
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
package fun.asgc.neutrino.proxy.server.service;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||
import com.github.pagehelper.Page;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.google.common.collect.Sets;
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
|
||||
import fun.asgc.neutrino.proxy.server.base.rest.SystemContextHolder;
|
||||
import fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum;
|
||||
import fun.asgc.neutrino.proxy.server.constant.ExceptionConstant;
|
||||
import fun.asgc.neutrino.proxy.server.constant.OnlineStatusEnum;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.LicenseCreateReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.LicenseListReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.LicenseUpdateEnableStatusReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.LicenseUpdateReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.*;
|
||||
import fun.asgc.neutrino.proxy.server.dal.LicenseMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.UserMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.LicenseDO;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
|
||||
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
|
||||
import ma.glasnost.orika.MapperFacade;
|
||||
import org.apache.ibatis.solon.annotation.Db;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
import org.noear.solon.core.Lifecycle;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* license服务
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/8/6
|
||||
*/
|
||||
@Component
|
||||
public class LicenseService implements Lifecycle {
|
||||
@Inject
|
||||
private MapperFacade mapperFacade;
|
||||
@Db
|
||||
private LicenseMapper licenseMapper;
|
||||
@Db
|
||||
private UserMapper userMapper;
|
||||
@Inject
|
||||
private VisitorChannelService visitorChannelService;
|
||||
|
||||
public PageInfo<LicenseListRes> page(PageQuery pageQuery, LicenseListReq req) {
|
||||
Page<LicenseListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
|
||||
List<LicenseDO> list = licenseMapper.selectList(new LambdaQueryWrapper<LicenseDO>()
|
||||
.orderByAsc(LicenseDO::getId)
|
||||
);
|
||||
List<LicenseListRes> respList = mapperFacade.mapAsList(list, LicenseListRes.class);
|
||||
if (CollectionUtils.isEmpty(list)) {
|
||||
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
|
||||
}
|
||||
if (!CollectionUtil.isEmpty(respList)) {
|
||||
Set<Integer> userIds = respList.stream().map(LicenseListRes::getUserId).collect(Collectors.toSet());
|
||||
List<UserDO> userList = userMapper.findByIds(userIds);
|
||||
Map<Integer, UserDO> userMap = userList.stream().collect(Collectors.toMap(UserDO::getId, Function.identity()));
|
||||
for (LicenseListRes item : respList) {
|
||||
UserDO userDO = userMap.get(item.getUserId());
|
||||
if (null != userDO) {
|
||||
item.setUserName(userDO.getName());
|
||||
}
|
||||
item.setKey(desensitization(item.getUserId(), item.getKey()));
|
||||
}
|
||||
}
|
||||
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
|
||||
}
|
||||
|
||||
public List<LicenseListRes> list(LicenseListReq req) {
|
||||
List<LicenseDO> list = licenseMapper.selectList(new LambdaQueryWrapper<LicenseDO>()
|
||||
.eq(LicenseDO::getEnable, EnableStatusEnum.ENABLE.getStatus())
|
||||
);
|
||||
List<LicenseListRes> licenseList = mapperFacade.mapAsList(list, LicenseListRes.class);
|
||||
if (!CollectionUtil.isEmpty(licenseList)) {
|
||||
Set<Integer> userIds = licenseList.stream().map(LicenseListRes::getUserId).collect(Collectors.toSet());
|
||||
List<UserDO> userList = userMapper.findByIds(userIds);
|
||||
Map<Integer, UserDO> userMap = userList.stream().collect(Collectors.toMap(UserDO::getId, Function.identity()));
|
||||
for (LicenseListRes item : licenseList) {
|
||||
UserDO userDO = userMap.get(item.getUserId());
|
||||
if (null != userDO) {
|
||||
item.setUserName(userDO.getName());
|
||||
}
|
||||
item.setKey(desensitization(item.getUserId(), item.getKey()));
|
||||
}
|
||||
}
|
||||
return licenseList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建license
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
public LicenseCreateRes create(LicenseCreateReq req) {
|
||||
LicenseDO licenseDO = licenseMapper.checkRepeat(req.getUserId(), req.getName());
|
||||
ParamCheckUtil.checkExpression(null == licenseDO, ExceptionConstant.LICENSE_NAME_CANNOT_REPEAT);
|
||||
|
||||
String key = UUID.randomUUID().toString().replaceAll("-", "");
|
||||
Date now = new Date();
|
||||
|
||||
licenseMapper.insert(new LicenseDO()
|
||||
.setName(req.getName())
|
||||
.setKey(key)
|
||||
.setUserId(req.getUserId())
|
||||
.setIsOnline(OnlineStatusEnum.OFFLINE.getStatus())
|
||||
.setEnable(EnableStatusEnum.ENABLE.getStatus())
|
||||
.setCreateTime(now)
|
||||
.setUpdateTime(now)
|
||||
);
|
||||
return new LicenseCreateRes();
|
||||
}
|
||||
|
||||
public LicenseUpdateRes update(LicenseUpdateReq req) {
|
||||
LicenseDO oldLicenseDO = licenseMapper.findById(req.getId());
|
||||
ParamCheckUtil.checkNotNull(oldLicenseDO, ExceptionConstant.LICENSE_NOT_EXIST);
|
||||
|
||||
LicenseDO licenseCheck = licenseMapper.checkRepeat(oldLicenseDO.getUserId(), req.getName(), Sets.newHashSet(oldLicenseDO.getId()));
|
||||
ParamCheckUtil.checkMustNull(licenseCheck, ExceptionConstant.LICENSE_NAME_CANNOT_REPEAT);
|
||||
|
||||
licenseMapper.update(req.getId(), req.getName(), new Date());
|
||||
return new LicenseUpdateRes();
|
||||
}
|
||||
|
||||
public LicenseDetailRes detail(Integer id) {
|
||||
LicenseDO licenseDO = licenseMapper.findById(id);
|
||||
if (null == licenseDO) {
|
||||
return null;
|
||||
}
|
||||
UserDO userDO = userMapper.findById(licenseDO.getUserId());
|
||||
String userName = "";
|
||||
if (null != userDO) {
|
||||
userName = userDO.getName();
|
||||
}
|
||||
return new LicenseDetailRes()
|
||||
.setId(licenseDO.getId())
|
||||
.setName(licenseDO.getName())
|
||||
.setKey(desensitization(licenseDO.getUserId(), licenseDO.getKey()))
|
||||
.setUserId(licenseDO.getUserId())
|
||||
.setUserName(userName)
|
||||
.setIsOnline(licenseDO.getIsOnline())
|
||||
.setEnable(licenseDO.getEnable())
|
||||
.setCreateTime(licenseDO.getCreateTime())
|
||||
.setUpdateTime(licenseDO.getUpdateTime())
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新license启用状态
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
public LicenseUpdateEnableStatusRes updateEnableStatus(LicenseUpdateEnableStatusReq req) {
|
||||
licenseMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
|
||||
// 更新VisitorChannel
|
||||
visitorChannelService.updateVisitorChannelByLicenseId(req.getId(), req.getEnable());
|
||||
return new LicenseUpdateEnableStatusRes();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除license
|
||||
* @param id
|
||||
*/
|
||||
public void delete(Integer id) {
|
||||
licenseMapper.deleteById(id);
|
||||
// 更新VisitorChannel
|
||||
visitorChannelService.updateVisitorChannelByLicenseId(id, EnableStatusEnum.DISABLE.getStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置license
|
||||
* @param id
|
||||
*/
|
||||
public void reset(Integer id) {
|
||||
String key = UUID.randomUUID().toString().replaceAll("-", "");
|
||||
Date now = new Date();
|
||||
|
||||
licenseMapper.reset(id, key, now);
|
||||
}
|
||||
|
||||
public LicenseDO findByKey(String license) {
|
||||
return licenseMapper.findByKey(license);
|
||||
}
|
||||
|
||||
/**
|
||||
* 脱敏处理
|
||||
* 非当前登录人的license,一律脱敏
|
||||
* @param userId
|
||||
* @param licenseKey
|
||||
* @return
|
||||
*/
|
||||
private String desensitization(Integer userId, String licenseKey) {
|
||||
Integer currentUserId = SystemContextHolder.getUser().getId();
|
||||
if (currentUserId.equals(userId)) {
|
||||
return licenseKey;
|
||||
}
|
||||
return licenseKey.substring(0, 10) + "****" + licenseKey.substring(licenseKey.length() - 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务端项目停止、启动时,更新在线状态为离线
|
||||
*/
|
||||
@Override
|
||||
public void start() throws Throwable {
|
||||
licenseMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务端项目停止、启动时,更新在线状态为离线
|
||||
*/
|
||||
@Override
|
||||
public void stop() throws Throwable {
|
||||
licenseMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
|
||||
}
|
||||
}
|
||||
@@ -1,245 +0,0 @@
|
||||
package fun.asgc.neutrino.proxy.server.service;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||
import com.github.pagehelper.Page;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.google.common.collect.Sets;
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
|
||||
import fun.asgc.neutrino.proxy.server.base.rest.SystemContextHolder;
|
||||
import fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum;
|
||||
import fun.asgc.neutrino.proxy.server.constant.ExceptionConstant;
|
||||
import fun.asgc.neutrino.proxy.server.constant.OnlineStatusEnum;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.PortMappingCreateReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.PortMappingListReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.PortMappingUpdateEnableStatusReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.PortMappingUpdateReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.*;
|
||||
import fun.asgc.neutrino.proxy.server.dal.LicenseMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.PortMappingMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.PortPoolMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.UserMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.LicenseDO;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.PortMappingDO;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.PortPoolDO;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
|
||||
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
|
||||
import ma.glasnost.orika.MapperFacade;
|
||||
import org.apache.ibatis.solon.annotation.Db;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
import org.noear.solon.core.Lifecycle;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/8/8
|
||||
*/
|
||||
@Component
|
||||
public class PortMappingService implements Lifecycle {
|
||||
@Inject
|
||||
private MapperFacade mapperFacade;
|
||||
@Db
|
||||
private PortMappingMapper portMappingMapper;
|
||||
@Db
|
||||
private LicenseMapper licenseMapper;
|
||||
@Db
|
||||
private UserMapper userMapper;
|
||||
@Db
|
||||
private PortPoolMapper portPoolMapper;
|
||||
@Inject
|
||||
private VisitorChannelService visitorChannelService;
|
||||
|
||||
public PageInfo<PortMappingListRes> page(PageQuery pageQuery, PortMappingListReq req) {
|
||||
Page<PortMappingListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
|
||||
List<PortMappingDO> list = portMappingMapper.selectList(new LambdaQueryWrapper<PortMappingDO>()
|
||||
.orderByAsc(PortMappingDO::getId)
|
||||
);
|
||||
List<PortMappingListRes> respList = mapperFacade.mapAsList(list, PortMappingListRes.class);
|
||||
if (CollectionUtils.isEmpty(list)) {
|
||||
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
|
||||
}
|
||||
Set<Integer> licenseIds = respList.stream().map(PortMappingListRes::getLicenseId).collect(Collectors.toSet());
|
||||
List<LicenseDO> licenseList = licenseMapper.findByIds(licenseIds);
|
||||
if (CollectionUtil.isEmpty(licenseList)) {
|
||||
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
|
||||
}
|
||||
Set<Integer> userIds = licenseList.stream().map(LicenseDO::getUserId).collect(Collectors.toSet());
|
||||
List<UserDO> userList = userMapper.findByIds(userIds);
|
||||
Map<Integer, LicenseDO> licenseMap = licenseList.stream().collect(Collectors.toMap(LicenseDO::getId, Function.identity()));
|
||||
Map<Integer, UserDO> userMap = userList.stream().collect(Collectors.toMap(UserDO::getId, Function.identity()));
|
||||
respList.forEach(item -> {
|
||||
LicenseDO license = licenseMap.get(item.getLicenseId());
|
||||
if (null == license) {
|
||||
return;
|
||||
}
|
||||
item.setLicenseName(license.getName());
|
||||
item.setUserId(license.getUserId());
|
||||
UserDO user = userMap.get(license.getUserId());
|
||||
if (null == user) {
|
||||
return;
|
||||
}
|
||||
item.setUserName(user.getName());
|
||||
});
|
||||
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
|
||||
}
|
||||
|
||||
public PortMappingCreateRes create(PortMappingCreateReq req) {
|
||||
LicenseDO licenseDO = licenseMapper.findById(req.getLicenseId());
|
||||
ParamCheckUtil.checkNotNull(licenseDO, ExceptionConstant.LICENSE_NOT_EXIST);
|
||||
if (!SystemContextHolder.isAdmin()) {
|
||||
// 临时处理,如果当前用户不是管理院,则操作userId不能为1
|
||||
ParamCheckUtil.checkExpression(!licenseDO.getUserId().equals(1), ExceptionConstant.NO_PERMISSION_VISIT);
|
||||
}
|
||||
PortPoolDO portPoolDO = portPoolMapper.findByPort(req.getServerPort());
|
||||
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
|
||||
ParamCheckUtil.checkExpression(null == portMappingMapper.findByPort(req.getServerPort(), null), ExceptionConstant.PORT_CANNOT_REPEAT_MAPPING, req.getServerPort());
|
||||
|
||||
|
||||
Date now = new Date();
|
||||
PortMappingDO portMappingDO = new PortMappingDO();
|
||||
portMappingDO.setLicenseId(req.getLicenseId());
|
||||
portMappingDO.setServerPort(req.getServerPort());
|
||||
portMappingDO.setClientIp(req.getClientIp());
|
||||
portMappingDO.setClientPort(req.getClientPort());
|
||||
portMappingDO.setIsOnline(OnlineStatusEnum.OFFLINE.getStatus());
|
||||
portMappingDO.setEnable(EnableStatusEnum.ENABLE.getStatus());
|
||||
portMappingDO.setCreateTime(now);
|
||||
portMappingDO.setUpdateTime(now);
|
||||
portMappingMapper.insert(portMappingDO);
|
||||
// 更新VisitorChannel
|
||||
visitorChannelService.addVisitorChannelByPortMapping(portMappingDO);
|
||||
return new PortMappingCreateRes();
|
||||
}
|
||||
|
||||
public PortMappingUpdateRes update(PortMappingUpdateReq req) {
|
||||
LicenseDO licenseDO = licenseMapper.findById(req.getLicenseId());
|
||||
ParamCheckUtil.checkNotNull(licenseDO, ExceptionConstant.LICENSE_NOT_EXIST);
|
||||
if (!SystemContextHolder.isAdmin()) {
|
||||
// 临时处理,如果当前用户不是管理员,则操作userId不能为1
|
||||
ParamCheckUtil.checkExpression(!licenseDO.getUserId().equals(1), ExceptionConstant.NO_PERMISSION_VISIT);
|
||||
}
|
||||
PortPoolDO portPoolDO = portPoolMapper.findByPort(req.getServerPort());
|
||||
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
|
||||
ParamCheckUtil.checkExpression(null == portMappingMapper.findByPort(req.getServerPort(), Sets.newHashSet(req.getId())), ExceptionConstant.PORT_CANNOT_REPEAT_MAPPING, req.getServerPort());
|
||||
|
||||
// 查询原端口映射
|
||||
PortMappingDO oldPortMappingDO = portMappingMapper.findById(req.getId());
|
||||
ParamCheckUtil.checkNotNull(oldPortMappingDO, ExceptionConstant.PORT_MAPPING_NOT_EXIST);
|
||||
|
||||
PortMappingDO portMappingDO = new PortMappingDO();
|
||||
portMappingDO.setId(req.getId());
|
||||
portMappingDO.setLicenseId(req.getLicenseId());
|
||||
portMappingDO.setServerPort(req.getServerPort());
|
||||
portMappingDO.setClientIp(req.getClientIp());
|
||||
portMappingDO.setClientPort(req.getClientPort());
|
||||
portMappingDO.setUpdateTime(new Date());
|
||||
portMappingDO.setEnable(EnableStatusEnum.ENABLE.getStatus());
|
||||
portMappingMapper.updateById(portMappingDO);
|
||||
// 更新VisitorChannel
|
||||
visitorChannelService.updateVisitorChannelByPortMapping(oldPortMappingDO, portMappingDO);
|
||||
return new PortMappingUpdateRes();
|
||||
}
|
||||
|
||||
public PortMappingDetailRes detail(Integer id) {
|
||||
PortMappingDO portMappingDO = portMappingMapper.findById(id);
|
||||
if (null == portMappingDO) {
|
||||
return null;
|
||||
}
|
||||
PortMappingDetailRes res = new PortMappingDetailRes()
|
||||
.setId(portMappingDO.getId())
|
||||
.setLicenseId(portMappingDO.getLicenseId())
|
||||
.setServerPort(portMappingDO.getServerPort())
|
||||
.setClientIp(portMappingDO.getClientIp())
|
||||
.setClientPort(portMappingDO.getClientPort())
|
||||
.setIsOnline(portMappingDO.getIsOnline())
|
||||
.setEnable(portMappingDO.getEnable())
|
||||
.setCreateTime(portMappingDO.getCreateTime())
|
||||
.setUpdateTime(portMappingDO.getUpdateTime());
|
||||
|
||||
LicenseDO license = licenseMapper.findById(portMappingDO.getLicenseId());
|
||||
if (null != license) {
|
||||
res.setLicenseName(license.getName());
|
||||
res.setUserId(license.getUserId());
|
||||
UserDO user = userMapper.findById(license.getUserId());
|
||||
if (null != user) {
|
||||
res.setUserName(user.getName());
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public PortMappingUpdateEnableStatusRes updateEnableStatus(PortMappingUpdateEnableStatusReq req) {
|
||||
PortMappingDO portMappingDO = portMappingMapper.findById(req.getId());
|
||||
ParamCheckUtil.checkNotNull(portMappingDO, ExceptionConstant.PORT_MAPPING_NOT_EXIST);
|
||||
|
||||
LicenseDO licenseDO = licenseMapper.findById(portMappingDO.getLicenseId());
|
||||
ParamCheckUtil.checkNotNull(licenseDO, ExceptionConstant.LICENSE_NOT_EXIST);
|
||||
if (!SystemContextHolder.isAdmin()) {
|
||||
ParamCheckUtil.checkExpression(!licenseDO.getUserId().equals(1), ExceptionConstant.NO_PERMISSION_VISIT);
|
||||
}
|
||||
|
||||
portMappingMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
|
||||
|
||||
// 更新VisitorChannel
|
||||
portMappingDO.setEnable(req.getEnable());
|
||||
if (EnableStatusEnum.ENABLE == EnableStatusEnum.of(req.getEnable())) {
|
||||
visitorChannelService.addVisitorChannelByPortMapping(portMappingDO);
|
||||
} else {
|
||||
visitorChannelService.removeVisitorChannelByPortMapping(portMappingDO);
|
||||
}
|
||||
|
||||
return new PortMappingUpdateEnableStatusRes();
|
||||
}
|
||||
|
||||
public void delete(Integer id) {
|
||||
PortMappingDO portMappingDO = portMappingMapper.findById(id);
|
||||
ParamCheckUtil.checkNotNull(portMappingDO, ExceptionConstant.PORT_MAPPING_NOT_EXIST);
|
||||
|
||||
LicenseDO licenseDO = licenseMapper.findById(portMappingDO.getLicenseId());
|
||||
if (null != licenseDO && !SystemContextHolder.isAdmin()) {
|
||||
// 临时处理,如果当前用户不是管理员,则操作userId不能为1
|
||||
ParamCheckUtil.checkExpression(!licenseDO.getUserId().equals(1), ExceptionConstant.NO_PERMISSION_VISIT);
|
||||
}
|
||||
|
||||
portMappingMapper.deleteById(id);
|
||||
|
||||
// 更新VisitorChannel
|
||||
visitorChannelService.removeVisitorChannelByPortMapping(portMappingDO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据license查询可用的端口映射列表
|
||||
* @param licenseId
|
||||
* @return
|
||||
*/
|
||||
public List<PortMappingDO> findEnableListByLicenseId(Integer licenseId) {
|
||||
return portMappingMapper.findEnableListByLicenseId(licenseId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务端项目停止、启动时,更新在线状态为离线
|
||||
*/
|
||||
@Override
|
||||
public void start() throws Throwable {
|
||||
portMappingMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务端项目停止、启动时,更新在线状态为离线
|
||||
*/
|
||||
@Override
|
||||
public void stop() throws Throwable {
|
||||
portMappingMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.github.pagehelper.Page;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
|
||||
import fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum;
|
||||
import fun.asgc.neutrino.proxy.server.constant.ExceptionConstant;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.PortPoolCreateReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.PortPoolListReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.PortPoolUpdateEnableStatusReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.PortPoolCreateRes;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.PortPoolListRes;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.PortPoolUpdateEnableStatusRes;
|
||||
import fun.asgc.neutrino.proxy.server.dal.PortPoolMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.PortPoolDO;
|
||||
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
|
||||
import ma.glasnost.orika.MapperFacade;
|
||||
import org.apache.ibatis.solon.annotation.Db;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/8/7
|
||||
*/
|
||||
@Component
|
||||
public class PortPoolService {
|
||||
@Inject
|
||||
private MapperFacade mapperFacade;
|
||||
@Db
|
||||
private PortPoolMapper portPoolMapper;
|
||||
@Inject
|
||||
private VisitorChannelService visitorChannelService;
|
||||
|
||||
public PageInfo<PortPoolListRes> page(PageQuery pageQuery, PortPoolListReq req) {
|
||||
Page<PortPoolListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
|
||||
List<PortPoolDO> list = portPoolMapper.selectList(new LambdaQueryWrapper<PortPoolDO>()
|
||||
.orderByAsc(PortPoolDO::getId)
|
||||
);
|
||||
List<PortPoolListRes> respList = mapperFacade.mapAsList(list, PortPoolListRes.class);
|
||||
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
|
||||
}
|
||||
|
||||
public List<PortPoolListRes> list(PortPoolListReq req) {
|
||||
List<PortPoolDO> list = portPoolMapper.selectList(new LambdaQueryWrapper<PortPoolDO>()
|
||||
.eq(PortPoolDO::getEnable, EnableStatusEnum.ENABLE.getStatus())
|
||||
);
|
||||
return mapperFacade.mapAsList(list, PortPoolListRes.class);
|
||||
}
|
||||
|
||||
public PortPoolCreateRes create(PortPoolCreateReq req) {
|
||||
PortPoolDO oldPortPoolDO = portPoolMapper.findByPort(req.getPort());
|
||||
ParamCheckUtil.checkMustNull(oldPortPoolDO, ExceptionConstant.PORT_CANNOT_REPEAT);
|
||||
|
||||
Date now = new Date();
|
||||
|
||||
portPoolMapper.insert(new PortPoolDO()
|
||||
.setPort(req.getPort())
|
||||
.setEnable(EnableStatusEnum.ENABLE.getStatus())
|
||||
.setCreateTime(now)
|
||||
.setUpdateTime(now)
|
||||
);
|
||||
// 更新visitorChannel
|
||||
visitorChannelService.updateVisitorChannelByPortPool(req.getPort(), EnableStatusEnum.ENABLE.getStatus());
|
||||
|
||||
return new PortPoolCreateRes();
|
||||
}
|
||||
|
||||
public PortPoolUpdateEnableStatusRes updateEnableStatus(PortPoolUpdateEnableStatusReq req) {
|
||||
PortPoolDO portPoolDO = portPoolMapper.findById(req.getId());
|
||||
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
|
||||
portPoolMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
|
||||
|
||||
// 更新visitorChannel
|
||||
visitorChannelService.updateVisitorChannelByPortPool(portPoolDO.getPort(), req.getEnable());
|
||||
|
||||
return new PortPoolUpdateEnableStatusRes();
|
||||
}
|
||||
|
||||
public void delete(Integer id) {
|
||||
PortPoolDO portPoolDO = portPoolMapper.findById(id);
|
||||
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
|
||||
|
||||
portPoolMapper.deleteById(id);
|
||||
|
||||
// 更新visitorChannel
|
||||
visitorChannelService.updateVisitorChannelByPortPool(portPoolDO.getPort(), EnableStatusEnum.DISABLE.getStatus());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package fun.asgc.neutrino.proxy.server.service;
|
||||
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.LicenseFlowReportReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.UserFlowReportReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.LicenseFlowReportRes;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.UserFlowReportRes;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.noear.solon.annotation.Component;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/12/23
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ReportService {
|
||||
|
||||
/**
|
||||
* 用户流量报表分页
|
||||
* @param pageQuery
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
public PageInfo<UserFlowReportRes> userFlowReportPage(PageQuery pageQuery, UserFlowReportReq req) {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* license流量报表分页
|
||||
* @param pageQuery
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
public PageInfo<LicenseFlowReportRes> licenseFlowReportPage(PageQuery pageQuery, LicenseFlowReportReq req) {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package fun.asgc.neutrino.proxy.server.service;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||
import com.github.pagehelper.Page;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.UserLoginRecordListReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.UserLoginRecordListRes;
|
||||
import fun.asgc.neutrino.proxy.server.dal.UserLoginRecordMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.UserMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.UserLoginRecordDO;
|
||||
import ma.glasnost.orika.MapperFacade;
|
||||
import org.apache.ibatis.solon.annotation.Db;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 用户登录日志
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/10/20
|
||||
*/
|
||||
@Component
|
||||
public class UserLoginRecordService {
|
||||
@Inject
|
||||
private MapperFacade mapperFacade;
|
||||
@Db
|
||||
private UserLoginRecordMapper userLoginRecordMapper;
|
||||
@Db
|
||||
private UserMapper userMapper;
|
||||
|
||||
public PageInfo<UserLoginRecordListRes> page(PageQuery pageQuery, UserLoginRecordListReq req) {
|
||||
Page<UserLoginRecordListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
|
||||
List<UserLoginRecordDO> list = userLoginRecordMapper.selectList(new LambdaQueryWrapper<UserLoginRecordDO>()
|
||||
.orderByDesc(UserLoginRecordDO::getCreateTime)
|
||||
);
|
||||
List<UserLoginRecordListRes> respList = mapperFacade.mapAsList(list, UserLoginRecordListRes.class);
|
||||
if (CollectionUtils.isEmpty(list)) {
|
||||
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
|
||||
}
|
||||
|
||||
if (!CollectionUtil.isEmpty(respList)) {
|
||||
Set<Integer> userIds = respList.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 : respList) {
|
||||
UserDO userDO = userMap.get(item.getUserId());
|
||||
if (null != userDO) {
|
||||
item.setUserName(userDO.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package fun.asgc.neutrino.proxy.server.util;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import fun.asgc.neutrino.proxy.server.constant.ExceptionConstant;
|
||||
import fun.asgc.neutrino.proxy.server.base.rest.ServiceException;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/8/1
|
||||
*/
|
||||
public class ParamCheckUtil {
|
||||
|
||||
public static void checkNotNull(Object obj, String name) {
|
||||
if (null == obj) {
|
||||
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_NULL, name);
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkNotEmpty(String str, String name) {
|
||||
if (StrUtil.isEmpty(str)) {
|
||||
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_EMPTY, name);
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkNotEmpty(Collection collection, String name) {
|
||||
if (null == collection || collection.isEmpty()) {
|
||||
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_EMPTY, name);
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkNotEmpty(Map map, String name) {
|
||||
if (null == map || map.isEmpty()) {
|
||||
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_EMPTY, name);
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkNotEmpty(Set set, String name) {
|
||||
if (null == set || set.isEmpty()) {
|
||||
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_EMPTY, name);
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkMustNull(Object obj, ExceptionConstant constant, Object... params) {
|
||||
if (null != obj) {
|
||||
throw ServiceException.create(constant, params);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void checkNotNull(Object obj, ExceptionConstant constant, Object... params) {
|
||||
if (null == obj) {
|
||||
throw ServiceException.create(constant, params);
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkNotEmpty(String str, ExceptionConstant constant, Object... params) {
|
||||
if (StrUtil.isEmpty(str)) {
|
||||
throw ServiceException.create(constant, params);
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkNotEmpty(Collection collection, ExceptionConstant constant, Object... params) {
|
||||
if (null == collection || collection.isEmpty()) {
|
||||
throw ServiceException.create(constant, params);
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkNotEmpty(Map map, ExceptionConstant constant, Object... params) {
|
||||
if (null == map || map.isEmpty()) {
|
||||
throw ServiceException.create(constant, params);
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkNotEmpty(Set set, ExceptionConstant constant, Object... params) {
|
||||
if (null == set || set.isEmpty()) {
|
||||
throw ServiceException.create(constant, params);
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkExpression(boolean expression, ExceptionConstant constant, Object... params) {
|
||||
if (!expression) {
|
||||
throw ServiceException.create(constant, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package fun.asgc.neutrino.proxy.server;
|
||||
package org.dromara.neutrinoproxy.server;
|
||||
|
||||
import fun.asgc.solon.extend.job.annotation.EnableJob;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.solonplugins.job.annotation.EnableJob;
|
||||
import org.noear.solon.Solon;
|
||||
import org.noear.solon.annotation.SolonMain;
|
||||
import org.noear.solon.web.cors.CrossFilter;
|
||||
@@ -10,6 +11,7 @@ import org.noear.solon.web.cors.CrossFilter;
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
@Slf4j
|
||||
@EnableJob
|
||||
@SolonMain
|
||||
public class ProxyServer {
|
||||
@@ -0,0 +1,95 @@
|
||||
package org.dromara.neutrinoproxy.server.base;
|
||||
|
||||
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
|
||||
import org.dromara.neutrinoproxy.server.base.rest.ResponseBody;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.report.HomeDataView;
|
||||
import org.dromara.neutrinoproxy.server.dal.ClientConnectRecordMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.FlowReportDayMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.FlowReportHourMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.FlowReportMinuteMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.FlowReportMonthMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.JobInfoMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.LicenseMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.PortMappingMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.PortPoolMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.UserLoginRecordMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.UserMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.UserTokenMapper;
|
||||
import org.dromara.neutrinoproxy.server.service.ClientConnectRecordService;
|
||||
import org.dromara.neutrinoproxy.server.service.JobInfoService;
|
||||
import org.dromara.neutrinoproxy.server.service.JobLogService;
|
||||
import org.dromara.neutrinoproxy.server.service.LicenseService;
|
||||
import org.dromara.neutrinoproxy.server.service.PortGroupService;
|
||||
import org.dromara.neutrinoproxy.server.service.PortMappingService;
|
||||
import org.dromara.neutrinoproxy.server.service.PortPoolService;
|
||||
import org.dromara.neutrinoproxy.server.service.ReportService;
|
||||
import org.dromara.neutrinoproxy.server.service.UserLoginRecordService;
|
||||
import org.dromara.neutrinoproxy.server.service.UserService;
|
||||
import org.dromara.solonplugins.job.JobBean;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.aot.RuntimeNativeMetadata;
|
||||
import org.noear.solon.aot.RuntimeNativeRegistrar;
|
||||
import org.noear.solon.aot.hint.MemberCategory;
|
||||
import org.noear.solon.core.AppContext;
|
||||
|
||||
/**
|
||||
* native 打包
|
||||
*
|
||||
* @author songyinyin
|
||||
* @since 2023/10/20 11:24
|
||||
*/
|
||||
@Component
|
||||
public class NeutrinoServerRuntimeNativeRegistrar implements RuntimeNativeRegistrar {
|
||||
@Override
|
||||
public void register(AppContext context, RuntimeNativeMetadata metadata) {
|
||||
metadata.registerResourceInclude("test.jks");
|
||||
metadata.registerResourceInclude("sql/.*");
|
||||
|
||||
// 使用 MP lambda 的类,需要注册序列化
|
||||
metadata.registerLambdaSerialization(ClientConnectRecordService.class);
|
||||
metadata.registerLambdaSerialization(JobInfoService.class);
|
||||
metadata.registerLambdaSerialization(JobLogService.class);
|
||||
metadata.registerLambdaSerialization(LicenseService.class);
|
||||
metadata.registerLambdaSerialization(PortGroupService.class);
|
||||
metadata.registerLambdaSerialization(PortMappingService.class);
|
||||
metadata.registerLambdaSerialization(PortPoolService.class);
|
||||
metadata.registerLambdaSerialization(ReportService.class);
|
||||
metadata.registerLambdaSerialization(UserLoginRecordService.class);
|
||||
metadata.registerLambdaSerialization(UserService.class);
|
||||
|
||||
metadata.registerLambdaSerialization(LicenseMapper.class);
|
||||
metadata.registerLambdaSerialization(ClientConnectRecordMapper.class);
|
||||
metadata.registerLambdaSerialization(FlowReportDayMapper.class);
|
||||
metadata.registerLambdaSerialization(FlowReportHourMapper.class);
|
||||
metadata.registerLambdaSerialization(FlowReportMinuteMapper.class);
|
||||
metadata.registerLambdaSerialization(FlowReportMonthMapper.class);
|
||||
metadata.registerLambdaSerialization(JobInfoMapper.class);
|
||||
metadata.registerLambdaSerialization(PortMappingMapper.class);
|
||||
metadata.registerLambdaSerialization(PortPoolMapper.class);
|
||||
metadata.registerLambdaSerialization(UserLoginRecordMapper.class);
|
||||
metadata.registerLambdaSerialization(UserMapper.class);
|
||||
metadata.registerLambdaSerialization(UserTokenMapper.class);
|
||||
|
||||
metadata.registerReflection(HomeDataView.class, MemberCategory.DECLARED_FIELDS);
|
||||
metadata.registerReflection(HomeDataView.Last7dFlow.class, MemberCategory.DECLARED_FIELDS);
|
||||
metadata.registerReflection(HomeDataView.License.class, MemberCategory.DECLARED_FIELDS);
|
||||
metadata.registerReflection(HomeDataView.PortMapping.class, MemberCategory.DECLARED_FIELDS);
|
||||
metadata.registerReflection(HomeDataView.Series.class, MemberCategory.DECLARED_FIELDS);
|
||||
metadata.registerReflection(HomeDataView.SingleDayFlow.class, MemberCategory.DECLARED_FIELDS);
|
||||
metadata.registerReflection(HomeDataView.TodayFlow.class, MemberCategory.DECLARED_FIELDS);
|
||||
metadata.registerReflection(HomeDataView.TotalFlow.class, MemberCategory.DECLARED_FIELDS);
|
||||
|
||||
metadata.registerReflection(ResponseBody.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
|
||||
metadata.registerSerialization(ResponseBody.class);
|
||||
|
||||
metadata.registerReflection(ProxyConfig.Protocol.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
|
||||
metadata.registerReflection(ProxyConfig.Server.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
|
||||
metadata.registerReflection(ProxyConfig.Tunnel.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
|
||||
metadata.registerReflection(ProxyConfig.Tcp.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
|
||||
metadata.registerReflection(ProxyConfig.Udp.class, MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS);
|
||||
|
||||
metadata.registerReflection(JobBean.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,21 +19,22 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.base.db;
|
||||
package org.dromara.neutrinoproxy.server.base.db;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.jfinal.plugin.activerecord.Db;
|
||||
import fun.asgc.neutrino.proxy.core.util.Assert;
|
||||
import fun.asgc.neutrino.proxy.core.util.FileUtil;
|
||||
import fun.asgc.neutrino.proxy.server.constant.DbTypeEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.neutrinoproxy.core.util.Assert;
|
||||
import org.dromara.neutrinoproxy.core.util.FileUtil;
|
||||
import org.dromara.neutrinoproxy.server.constant.DbTypeEnum;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Init;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
import org.noear.solon.core.event.AppLoadEndEvent;
|
||||
import org.noear.solon.core.event.EventListener;
|
||||
import org.noear.wood.DbContext;
|
||||
import org.noear.wood.annotation.Db;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -45,27 +46,29 @@ import java.util.List;
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DBInitialize implements EventListener<AppLoadEndEvent> {
|
||||
private static List<String> initDataTableNameList = Lists.newArrayList("user", "license", "port_pool", "port_mapping", "job_info");
|
||||
private static List<String> initDataTableNameList = Lists.newArrayList("user", "license", "port_group", "port_pool", "port_mapping", "job_info");
|
||||
@Inject
|
||||
private DbConfig dbConfig;
|
||||
|
||||
private DbTypeEnum dbTypeEnum;
|
||||
|
||||
@Db
|
||||
DbContext dbContext;
|
||||
|
||||
@Init
|
||||
public void init() throws Throwable {
|
||||
Assert.notNull(dbConfig.getType(), "neutrino.data.db.type不能为空!");
|
||||
dbTypeEnum = DbTypeEnum.of(dbConfig.getType());
|
||||
Assert.notNull(dbTypeEnum, "neutrino.data.db.type取值异常!");
|
||||
|
||||
log.info("{}数据库初始化...", dbConfig.getType());
|
||||
log.info("{} database init...", dbConfig.getType());
|
||||
initDBStructure();
|
||||
initDBData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(AppLoadEndEvent appLoadEndEvent) throws Throwable {
|
||||
// TODO 该事件有50%的概率不触发
|
||||
System.out.println("11");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,8 +86,8 @@ public class DBInitialize implements EventListener<AppLoadEndEvent> {
|
||||
}
|
||||
sql += "\r\n" + line.trim();
|
||||
if (sql.endsWith(";")) {
|
||||
log.debug("初始化数据库表 sql:{}", sql);
|
||||
Db.update(sql);
|
||||
log.debug("init database table sql:{}", sql);
|
||||
dbContext.exe(sql);
|
||||
sql = "";
|
||||
}
|
||||
}
|
||||
@@ -102,8 +105,8 @@ public class DBInitialize implements EventListener<AppLoadEndEvent> {
|
||||
for (String tableName : initDataTableNameList) {
|
||||
// 表里没有数据的时候,才进行初始化操作
|
||||
|
||||
int count = Db.queryInt(String.format("select count(1) from `%s`", tableName));
|
||||
if (count > 0) {
|
||||
Object result = dbContext.exe(String.format("select count(1) from `%s`", tableName));
|
||||
if (result != null && ((Number) result).intValue() > 0) {
|
||||
continue;
|
||||
}
|
||||
List<String> lines = FileUtil.readContentAsStringList(String.format("classpath:/sql/%s/%s.data.sql", dbConfig.getType(), tableName));
|
||||
@@ -117,8 +120,8 @@ public class DBInitialize implements EventListener<AppLoadEndEvent> {
|
||||
}
|
||||
sql += "\r\n" + line.trim();
|
||||
if (sql.endsWith(";")) {
|
||||
log.debug("初始化数据[table={}] sql:{}", tableName, sql);
|
||||
Db.update(sql);
|
||||
log.debug("init database data[table={}] sql:{}", tableName, sql);
|
||||
dbContext.exe(sql);
|
||||
sql = "";
|
||||
}
|
||||
}
|
||||
@@ -19,9 +19,9 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.base.db;
|
||||
package org.dromara.neutrinoproxy.server.base.db;
|
||||
|
||||
import fun.asgc.neutrino.proxy.server.constant.DbTypeEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.DbTypeEnum;
|
||||
import lombok.Data;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
@@ -45,11 +45,6 @@ public class DbConfig {
|
||||
*/
|
||||
@Inject("${neutrino.data.db.url}")
|
||||
private String url;
|
||||
/**
|
||||
* 驱动类
|
||||
*/
|
||||
@Inject("${neutrino.data.db.driver-class}")
|
||||
private String driverClass;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
@@ -0,0 +1,79 @@
|
||||
package org.dromara.neutrinoproxy.server.base.db;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.MybatisSqlSessionFactoryBuilder;
|
||||
import com.baomidou.mybatisplus.core.config.GlobalConfig;
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import com.baomidou.mybatisplus.solon.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.solon.plugins.inner.PaginationInnerInterceptor;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import org.apache.ibatis.solon.annotation.Db;
|
||||
import org.dromara.neutrinoproxy.server.constant.DbTypeEnum;
|
||||
import org.noear.solon.annotation.Bean;
|
||||
import org.noear.solon.annotation.Configuration;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/3/10
|
||||
*/
|
||||
@Configuration
|
||||
public class DbConfiguration {
|
||||
|
||||
@Bean(value = "db", typed = true)
|
||||
public DataSource dataSource(@Inject DbConfig dbConfig) {
|
||||
DbTypeEnum dbTypeEnum = DbTypeEnum.of(dbConfig.getType());
|
||||
if (DbTypeEnum.H2 == dbTypeEnum) {
|
||||
return newHikariDataSource(dbConfig, "org.h2.Driver");
|
||||
} else if (DbTypeEnum.MYSQL == dbTypeEnum) {
|
||||
String driver = "com.mysql.cj.jdbc.Driver";
|
||||
try {
|
||||
Class.forName(driver);
|
||||
} catch (ClassNotFoundException e) {
|
||||
// 对类名的判断,异常则说明不存在:
|
||||
driver = "com.mysql.jdbc.Driver";
|
||||
}
|
||||
return newHikariDataSource(dbConfig, driver);
|
||||
} else if (DbTypeEnum.MARIADB == dbTypeEnum) {
|
||||
return newHikariDataSource(dbConfig, "org.mariadb.jdbc.Driver");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private HikariDataSource newHikariDataSource(DbConfig dbConfig, String driverClass) {
|
||||
HikariDataSource dataSource = new HikariDataSource();
|
||||
dataSource.setDriverClassName(driverClass);
|
||||
dataSource.setJdbcUrl(dbConfig.getUrl());
|
||||
dataSource.setMinimumIdle(5);
|
||||
dataSource.setMaximumPoolSize(20);
|
||||
dataSource.setMaxLifetime(60000);
|
||||
dataSource.setUsername(dbConfig.getUsername());
|
||||
dataSource.setPassword(dbConfig.getPassword());
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public void db1_ext(@Db("db") GlobalConfig globalConfig) {
|
||||
MetaObjectHandler metaObjectHandler = new MetaObjectHandlerImpl();
|
||||
globalConfig.setMetaObjectHandler(metaObjectHandler);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public void db1_ext2(@Db("db") MybatisConfiguration config) {
|
||||
config.getTypeHandlerRegistry().register("org.dromara.neutrinoproxy.server.dal.entity");
|
||||
config.setDefaultEnumTypeHandler(null);
|
||||
|
||||
MybatisPlusInterceptor plusInterceptor = new MybatisPlusInterceptor();
|
||||
plusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
|
||||
config.addInterceptor(plusInterceptor);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MybatisSqlSessionFactoryBuilder factoryBuilderNew() {
|
||||
return new MybatisSqlSessionFactoryBuilderImpl();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package fun.asgc.neutrino.proxy.server.base.db;
|
||||
package org.dromara.neutrinoproxy.server.base.db;
|
||||
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
@@ -1,4 +1,4 @@
|
||||
package fun.asgc.neutrino.proxy.server.base.db;
|
||||
package org.dromara.neutrinoproxy.server.base.db;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisSqlSessionFactoryBuilder;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package fun.asgc.neutrino.proxy.server.base.page;
|
||||
package org.dromara.neutrinoproxy.server.base.page;
|
||||
|
||||
import com.baomidou.mybatisplus.solon.plugins.pagination.Page;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
@@ -101,11 +102,20 @@ public class PageInfo<T> implements Serializable {
|
||||
}
|
||||
|
||||
public static <T> PageInfo<T> of(List<T> records, Long total, Integer current, Integer size) {
|
||||
PageInfo<T> pageInfo = new PageInfo();
|
||||
PageInfo<T> pageInfo = new PageInfo<>();
|
||||
pageInfo.setRecords(records);
|
||||
pageInfo.setTotal(total);
|
||||
pageInfo.setCurrent(current);
|
||||
pageInfo.setSize(size);
|
||||
return pageInfo;
|
||||
}
|
||||
|
||||
public static <T> PageInfo<T> of(List<T> records, Page<?> page) {
|
||||
PageInfo<T> pageInfo = new PageInfo<>();
|
||||
pageInfo.setRecords(records);
|
||||
pageInfo.setTotal(page.getTotal());
|
||||
pageInfo.setCurrent((int) page.getCurrent());
|
||||
pageInfo.setSize((int) page.getSize());
|
||||
return pageInfo;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package fun.asgc.neutrino.proxy.server.base.page;
|
||||
package org.dromara.neutrinoproxy.server.base.page;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package fun.asgc.neutrino.proxy.server.base.proxy;
|
||||
package org.dromara.neutrinoproxy.server.base.proxy;
|
||||
|
||||
import lombok.Data;
|
||||
import org.noear.solon.annotation.Component;
|
||||
@@ -18,10 +18,15 @@ public class ProxyConfig {
|
||||
@Inject("${neutrino.proxy.protocol}")
|
||||
private Protocol protocol;
|
||||
/**
|
||||
* 服务端配置
|
||||
* 代理服务配置
|
||||
*/
|
||||
@Inject("${neutrino.proxy.server}")
|
||||
private Server server;
|
||||
/**
|
||||
* 代理隧道配置
|
||||
*/
|
||||
@Inject("${neutrino.proxy.tunnel}")
|
||||
private Tunnel tunnel;
|
||||
|
||||
@Data
|
||||
public static class Protocol {
|
||||
@@ -37,11 +42,39 @@ public class ProxyConfig {
|
||||
|
||||
@Data
|
||||
public static class Server {
|
||||
private Tcp tcp;
|
||||
private Udp udp;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Tunnel {
|
||||
private Integer bossThreadCount;
|
||||
private Integer workThreadCount;
|
||||
private Integer port;
|
||||
private Integer sslPort;
|
||||
private String keyStorePassword;
|
||||
private String keyManagerPassword;
|
||||
private String jksPath;
|
||||
private Boolean transferLogEnable;
|
||||
private Boolean heartbeatLogEnable;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Tcp {
|
||||
private Integer bossThreadCount;
|
||||
private Integer workThreadCount;
|
||||
private String domainName;
|
||||
private Integer httpProxyPort;
|
||||
private Integer httpsProxyPort;
|
||||
private String keyStorePassword;
|
||||
private String jksPath;
|
||||
private Boolean transferLogEnable;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Udp {
|
||||
private Integer bossThreadCount;
|
||||
private Integer workThreadCount;
|
||||
private Boolean transferLogEnable;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package org.dromara.neutrinoproxy.server.base.proxy;
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioDatagramChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessageHandler;
|
||||
import org.dromara.neutrinoproxy.core.aot.NeutrinoCoreRuntimeNativeRegistrar;
|
||||
import org.dromara.neutrinoproxy.core.dispatcher.DefaultDispatcher;
|
||||
import org.dromara.neutrinoproxy.core.dispatcher.Dispatcher;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import org.dromara.neutrinoproxy.server.proxy.core.BytesMetricsHandler;
|
||||
import org.dromara.neutrinoproxy.server.proxy.core.TcpVisitorChannelHandler;
|
||||
import org.dromara.neutrinoproxy.server.proxy.core.UdpVisitorChannelHandler;
|
||||
import org.noear.solon.Solon;
|
||||
import org.noear.solon.annotation.Bean;
|
||||
import org.noear.solon.annotation.Configuration;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
import org.noear.solon.core.bean.LifecycleBean;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 代理配置
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/10/8
|
||||
*/
|
||||
@Configuration
|
||||
public class ProxyConfiguration implements LifecycleBean {
|
||||
|
||||
@Override
|
||||
public void start() throws Throwable {
|
||||
List<ProxyMessageHandler> list = Solon.context().getBeansOfType(ProxyMessageHandler.class);
|
||||
Dispatcher<ChannelHandlerContext, ProxyMessage> dispatcher = new DefaultDispatcher<>("MessageDispatcher", list,
|
||||
proxyMessage -> ProxyDataTypeEnum.of((int)proxyMessage.getType()) == null ?
|
||||
null : ProxyDataTypeEnum.of((int)proxyMessage.getType()).getName());
|
||||
|
||||
Solon.context().wrapAndPut(Dispatcher.class, dispatcher);
|
||||
}
|
||||
|
||||
@Bean("tcpServerBossGroup")
|
||||
public NioEventLoopGroup tcpServerBossGroup(@Inject ProxyConfig proxyConfig) {
|
||||
ProxyConfig.Server server = proxyConfig.getServer();
|
||||
ProxyConfig.Tcp tcp = server.getTcp();
|
||||
return new NioEventLoopGroup(tcp.getBossThreadCount());
|
||||
}
|
||||
|
||||
@Bean("tcpServerWorkerGroup")
|
||||
public NioEventLoopGroup tcpServerWorkerGroup(@Inject ProxyConfig proxyConfig) {
|
||||
return new NioEventLoopGroup(proxyConfig.getServer().getTcp().getWorkThreadCount());
|
||||
}
|
||||
|
||||
@Bean("tcpServerBootstrap")
|
||||
public ServerBootstrap tcpServerBootstrap(@Inject("tcpServerBossGroup") NioEventLoopGroup tcpServerBossGroup,
|
||||
@Inject("tcpServerWorkerGroup") NioEventLoopGroup tcpServerWorkerGroup,
|
||||
@Inject ProxyConfig proxyConfig
|
||||
) {
|
||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||
bootstrap.group(tcpServerBossGroup, tcpServerWorkerGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
if (null != proxyConfig.getServer().getTcp().getTransferLogEnable() && proxyConfig.getServer().getTcp().getTransferLogEnable()) {
|
||||
ch.pipeline().addFirst(new LoggingHandler(TcpVisitorChannelHandler.class));
|
||||
}
|
||||
ch.pipeline().addFirst(new BytesMetricsHandler());
|
||||
// ch.pipeline().addLast(new ChannelTrafficShapingHandler(1024 * 1024 * 20, 1024 * 1024 * 20, 100, 20000));
|
||||
ch.pipeline().addLast(new TcpVisitorChannelHandler());
|
||||
}
|
||||
});
|
||||
return bootstrap;
|
||||
}
|
||||
|
||||
@Bean("udpServerBossGroup")
|
||||
public NioEventLoopGroup udpServerBossGroup(@Inject ProxyConfig proxyConfig) {
|
||||
return new NioEventLoopGroup(proxyConfig.getServer().getUdp().getBossThreadCount());
|
||||
}
|
||||
|
||||
@Bean("udpServerWorkerGroup")
|
||||
public NioEventLoopGroup udpServerWorkerGroup(@Inject ProxyConfig proxyConfig) {
|
||||
return new NioEventLoopGroup(proxyConfig.getServer().getUdp().getWorkThreadCount());
|
||||
}
|
||||
|
||||
@Bean("udpServerBootstrap")
|
||||
public Bootstrap udpServerBootstrap(@Inject("udpServerBossGroup") NioEventLoopGroup udpServerBossGroup,
|
||||
@Inject("udpServerWorkerGroup") NioEventLoopGroup udpServerWorkerGroup,
|
||||
@Inject ProxyConfig proxyConfig) {
|
||||
Bootstrap bootstrap = new Bootstrap();
|
||||
bootstrap.group(udpServerBossGroup)
|
||||
// 主线程处理
|
||||
.channel(NioDatagramChannel.class)
|
||||
// 广播
|
||||
.option(ChannelOption.SO_BROADCAST, true)
|
||||
// 设置读缓冲区为2M
|
||||
.option(ChannelOption.SO_RCVBUF, 2048 * 1024)
|
||||
// 设置写缓冲区为1M
|
||||
.option(ChannelOption.SO_SNDBUF, 1024 * 1024)
|
||||
.handler(new ChannelInitializer<NioDatagramChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioDatagramChannel ch) {
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
if (null != proxyConfig.getServer().getUdp().getTransferLogEnable() && proxyConfig.getServer().getUdp().getTransferLogEnable()) {
|
||||
ch.pipeline().addFirst(new LoggingHandler(UdpVisitorChannelHandler.class));
|
||||
}
|
||||
pipeline.addLast(udpServerWorkerGroup, new UdpVisitorChannelHandler());
|
||||
}
|
||||
});
|
||||
return bootstrap;
|
||||
}
|
||||
|
||||
@Bean("tunnelBossGroup")
|
||||
public NioEventLoopGroup tunnelBossGroup(@Inject ProxyConfig proxyConfig) {
|
||||
return new NioEventLoopGroup(proxyConfig.getTunnel().getBossThreadCount());
|
||||
}
|
||||
|
||||
@Bean("tunnelWorkerGroup")
|
||||
public NioEventLoopGroup tunnelWorkerGroup(@Inject ProxyConfig proxyConfig) {
|
||||
return new NioEventLoopGroup(proxyConfig.getTunnel().getWorkThreadCount());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public NeutrinoCoreRuntimeNativeRegistrar neutrinoCoreRuntimeNativeRegistrar() {
|
||||
return new NeutrinoCoreRuntimeNativeRegistrar();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.base.rest;
|
||||
package org.dromara.neutrinoproxy.server.base.rest;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.base.rest;
|
||||
package org.dromara.neutrinoproxy.server.base.rest;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
@@ -19,9 +19,9 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.base.rest;
|
||||
package org.dromara.neutrinoproxy.server.base.rest;
|
||||
|
||||
import fun.asgc.neutrino.proxy.server.constant.ExceptionConstant;
|
||||
import org.dromara.neutrinoproxy.server.constant.ExceptionConstant;
|
||||
import lombok.Getter;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@@ -42,6 +42,7 @@ public class ServiceException extends RuntimeException {
|
||||
private String msg;
|
||||
|
||||
public ServiceException(int code, String msg) {
|
||||
super(msg);
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
}
|
||||
@@ -19,9 +19,9 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.base.rest;
|
||||
package org.dromara.neutrinoproxy.server.base.rest;
|
||||
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.noear.solon.core.handle.Action;
|
||||
@@ -19,9 +19,9 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.base.rest;
|
||||
package org.dromara.neutrinoproxy.server.base.rest;
|
||||
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
|
||||
|
||||
/**
|
||||
* 系统上下文持有者
|
||||
@@ -1,13 +1,14 @@
|
||||
package fun.asgc.neutrino.proxy.server.base.rest.interceptor;
|
||||
package org.dromara.neutrinoproxy.server.base.rest.interceptor;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import fun.asgc.neutrino.proxy.server.base.rest.*;
|
||||
import fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum;
|
||||
import fun.asgc.neutrino.proxy.server.constant.ExceptionConstant;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
|
||||
import fun.asgc.neutrino.proxy.server.service.UserService;
|
||||
import org.dromara.neutrinoproxy.server.base.rest.*;
|
||||
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.ExceptionConstant;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
|
||||
import org.dromara.neutrinoproxy.server.service.UserService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.dromara.neutrinoproxy.server.base.rest.*;
|
||||
import org.noear.solon.Solon;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.core.handle.Action;
|
||||
@@ -73,7 +74,7 @@ public class BaseAuthInterceptor implements RouterInterceptor {
|
||||
}
|
||||
|
||||
if (result instanceof Throwable) {
|
||||
log.error("全局异常", (Throwable) result);
|
||||
log.error("global error", (Throwable) result);
|
||||
|
||||
if (result instanceof ServiceException) {
|
||||
ServiceException exception = (ServiceException) result;
|
||||
@@ -1,8 +1,8 @@
|
||||
package fun.asgc.neutrino.proxy.server.base.rest.interceptor;
|
||||
package org.dromara.neutrinoproxy.server.base.rest.interceptor;
|
||||
|
||||
import fun.asgc.neutrino.proxy.server.base.rest.ResponseBody;
|
||||
import fun.asgc.neutrino.proxy.server.base.rest.ServiceException;
|
||||
import fun.asgc.neutrino.proxy.server.constant.ExceptionConstant;
|
||||
import org.dromara.neutrinoproxy.server.base.rest.ResponseBody;
|
||||
import org.dromara.neutrinoproxy.server.base.rest.ServiceException;
|
||||
import org.dromara.neutrinoproxy.server.constant.ExceptionConstant;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.noear.solon.annotation.Component;
|
||||
@@ -23,7 +23,7 @@ public class GlobalExceptionFilter implements Filter {
|
||||
try {
|
||||
chain.doFilter(ctx);
|
||||
} catch (Throwable e) {
|
||||
log.error("全局异常", e);
|
||||
log.error("global error", e);
|
||||
|
||||
if (e instanceof ServiceException) {
|
||||
ServiceException serviceException = (ServiceException) e;
|
||||
@@ -1,7 +1,7 @@
|
||||
package fun.asgc.neutrino.proxy.server.base.rest.interceptor;
|
||||
package org.dromara.neutrinoproxy.server.base.rest.interceptor;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.noear.snack.ONode;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.core.handle.Action;
|
||||
import org.noear.solon.core.handle.Context;
|
||||
@@ -31,10 +31,10 @@ public class VisitLogInterceptor implements RouterInterceptor {
|
||||
} finally {
|
||||
Date now = new Date();
|
||||
long elapsedTime = now.getTime() - startTime.getTime();
|
||||
log.info("\n-----------------------------------------------------------------接口请求日志:\n{} url:{} 执行耗时:{}\n请求体参数:{}\n响应结果:{}\n客户端IP:{}\n",
|
||||
log.debug("\n-----------------------------------------------------------------interface request log:\n{} url:{} elapsed time:{}\nrequest:{}\nresponse:{}\nclient ip:{}\n",
|
||||
ctx.method(), ctx.path(), getElapsedTimeStr(elapsedTime),
|
||||
JSONObject.toJSONString(ctx.paramMap()),
|
||||
JSONObject.toJSONString(JSONObject.toJSONString(ctx.result)),
|
||||
ONode.serialize(ctx.paramMap()),
|
||||
ONode.serialize(ctx.result),
|
||||
ctx.realIp()
|
||||
);
|
||||
}
|
||||
@@ -47,11 +47,11 @@ public class VisitLogInterceptor implements RouterInterceptor {
|
||||
*/
|
||||
private static String getElapsedTimeStr(long elapsedTime) {
|
||||
if (elapsedTime < 1000) {
|
||||
return String.format("%s毫秒", elapsedTime);
|
||||
return String.format("%sms", elapsedTime);
|
||||
} else if (elapsedTime < 60000) {
|
||||
return String.format("%.2f秒", (elapsedTime * 1.0) / 1000);
|
||||
return String.format("%.2fs", (elapsedTime * 1.0) / 1000);
|
||||
}
|
||||
return String.format("%.2f分钟", (elapsedTime * 1.0) / 1000 / 60);
|
||||
return String.format("%.2fm", (elapsedTime * 1.0) / 1000 / 60);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.constant;
|
||||
package org.dromara.neutrinoproxy.server.constant;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.constant;
|
||||
package org.dromara.neutrinoproxy.server.constant;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.constant;
|
||||
package org.dromara.neutrinoproxy.server.constant;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.dromara.neutrinoproxy.server.constant;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/3/18
|
||||
*/
|
||||
public interface Constants {
|
||||
/**
|
||||
* 默认的端口分组ID
|
||||
*/
|
||||
int DEFAULT_PORT_GROUP_ID = 1;
|
||||
/**
|
||||
* 首页流量监控展示天数
|
||||
*/
|
||||
int HOME_FLOW_DAYS = 15;
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.constant;
|
||||
package org.dromara.neutrinoproxy.server.constant;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@@ -37,8 +37,10 @@ import java.util.stream.Stream;
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum DbTypeEnum {
|
||||
SQLITE("sqlite"),
|
||||
MYSQL("mysql");
|
||||
H2("h2"),
|
||||
MYSQL("mysql"),
|
||||
MARIADB("mariadb"),
|
||||
;
|
||||
|
||||
private String type;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.constant;
|
||||
package org.dromara.neutrinoproxy.server.constant;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.constant;
|
||||
package org.dromara.neutrinoproxy.server.constant;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@@ -41,6 +41,8 @@ public enum ExceptionConstant {
|
||||
PARAMS_NOT_NULL(10, "参数[{}]不能为空"),
|
||||
PARAMS_NOT_EMPTY(11, "参数[{}]不能为空"),
|
||||
|
||||
FILED_LENGTH_OUT(12 ,"{}不能超出长度{}"),
|
||||
|
||||
// 用户管理(11000)
|
||||
// license管理(12000)
|
||||
LICENSE_NAME_CANNOT_REPEAT(12000, "license名称不能重复"),
|
||||
@@ -48,15 +50,24 @@ public enum ExceptionConstant {
|
||||
ORIGIN_PASSWORD_CHECK_FAIL(12002, "原密码验证失败"),
|
||||
LOGIN_PASSWORD_LENGTH_CHECK_FAIL(12003, "登录密码不能小于6位数"),
|
||||
LOGIN_PASSWORD_NO_CHANGE_MODIFY_FAIL(12004, "密码没有变化,修改失败"),
|
||||
LICENSE_CANNOT_BE_DELETED(12005, "license下存在[{}]个端口映射!"),
|
||||
// 端口池管理(13000)
|
||||
PORT_CANNOT_REPEAT(13000,"端口不能重复"),
|
||||
PORT_NOT_EXIST(13001, "该端口在端口池中不存在"),
|
||||
PORT_RANGE_FAIL(13002, "端口值范围为MIN-MAX"),
|
||||
// 端口映射管理(14000)
|
||||
PORT_MAPPING_NOT_EXIST(14000, "端口映射记录不存在"),
|
||||
PORT_CANNOT_REPEAT_MAPPING(14001, "服务端口[{}]不能重复映射"),
|
||||
AN_UNSUPPORTED_PROTOCOL(14002, "不支持的协议[{}]!"),
|
||||
PORT_MAPPING_SUBDONAME_CONNOT_REPEAT(14003, "子域名不能重复使用!"),
|
||||
|
||||
// 调度管理(15000)
|
||||
JOB_INFO_NOT_EXIST(15000, "调度管理记录不存在"),
|
||||
SYSTEM_ERROR(500, "系统异常"),
|
||||
|
||||
PORT_GROUP_NAME_ALREADY_EXIST(16000,"端口分组名称[{}]已经存在"),
|
||||
PORT_GROUP_NAME_DOES_NOT_EXIST(16001,"端口分组不存在"),
|
||||
DEFAULT_GROUP_FORBID_DELETE(16002,"默认分组禁止删除")
|
||||
;
|
||||
|
||||
private int code;
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.dromara.neutrinoproxy.server.constant;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/4/2
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum NetworkProtocolEnum {
|
||||
TCP("TCP", "TCP"),
|
||||
UDP("UDP", "UDP"),
|
||||
HTTP("HTTP", "TCP"),
|
||||
;
|
||||
private String desc;
|
||||
private String baseProtocol;
|
||||
private static final Map<String, NetworkProtocolEnum> map = Stream.of(NetworkProtocolEnum.values()).collect(Collectors.toMap(NetworkProtocolEnum::getDesc, Function.identity()));
|
||||
|
||||
public static NetworkProtocolEnum of(String desc) {
|
||||
if (StringUtils.isBlank(desc)) {
|
||||
return null;
|
||||
}
|
||||
if (desc.startsWith("HTTP")) {
|
||||
return NetworkProtocolEnum.HTTP;
|
||||
}
|
||||
return map.get(desc);
|
||||
}
|
||||
|
||||
public static Boolean isHttp(String desc) {
|
||||
NetworkProtocolEnum networkProtocolEnum = of(desc);
|
||||
return NetworkProtocolEnum.HTTP == networkProtocolEnum;
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.constant;
|
||||
package org.dromara.neutrinoproxy.server.constant;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.dromara.neutrinoproxy.server.constant;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 端口池分组枚举类型
|
||||
* @author Yohanes
|
||||
* @date 2023/03/17
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum PoolGroupEnum {
|
||||
GLOBAL(0, "全局"),
|
||||
USER(1, "用户"),
|
||||
LICENSE(2, "License");
|
||||
|
||||
private Integer status;
|
||||
private String desc;
|
||||
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.constant;
|
||||
package org.dromara.neutrinoproxy.server.constant;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@@ -19,14 +19,14 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller;
|
||||
package org.dromara.neutrinoproxy.server.controller;
|
||||
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.ClientConnectRecordListReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.ClientConnectRecordListRes;
|
||||
import fun.asgc.neutrino.proxy.server.service.ClientConnectRecordService;
|
||||
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.log.ClientConnectRecordListReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.log.ClientConnectRecordListRes;
|
||||
import org.dromara.neutrinoproxy.server.service.ClientConnectRecordService;
|
||||
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.noear.solon.annotation.Controller;
|
||||
import org.noear.solon.annotation.Get;
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.dromara.neutrinoproxy.server.controller;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
|
||||
import org.noear.solon.annotation.Controller;
|
||||
import org.noear.solon.annotation.Get;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
import org.noear.solon.annotation.Mapping;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/4/2
|
||||
*/
|
||||
@Slf4j
|
||||
@Mapping("/domain-name")
|
||||
@Controller
|
||||
public class DomainNameController {
|
||||
@Inject
|
||||
private ProxyConfig proxyConfig;
|
||||
|
||||
@Get
|
||||
@Mapping("/bind-info")
|
||||
public String bindInfo () {
|
||||
return proxyConfig.getServer().getTcp().getDomainName();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,13 +19,13 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller;
|
||||
package org.dromara.neutrinoproxy.server.controller;
|
||||
|
||||
import fun.asgc.neutrino.proxy.server.base.rest.Authorization;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.LoginReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.LoginRes;
|
||||
import fun.asgc.neutrino.proxy.server.service.UserService;
|
||||
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
|
||||
import org.dromara.neutrinoproxy.server.base.rest.Authorization;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.LoginReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.LoginRes;
|
||||
import org.dromara.neutrinoproxy.server.service.UserService;
|
||||
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
|
||||
import org.noear.solon.annotation.*;
|
||||
import org.noear.solon.core.handle.Context;
|
||||
|
||||
@@ -19,22 +19,22 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller;
|
||||
package org.dromara.neutrinoproxy.server.controller;
|
||||
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
|
||||
import fun.asgc.neutrino.proxy.server.base.rest.Authorization;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.JobInfoExecuteReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.JobInfoListReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.JobInfoUpdateEnableStatusReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.JobInfoUpdateReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.JobInfoExecuteRes;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.JobInfoListRes;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.JobInfoUpdateEnableStatusRes;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.JobInfoUpdateRes;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.JobInfoDO;
|
||||
import fun.asgc.neutrino.proxy.server.service.JobInfoService;
|
||||
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
|
||||
import org.dromara.neutrinoproxy.server.base.rest.Authorization;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.JobInfoExecuteReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.JobInfoListReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.JobInfoUpdateEnableStatusReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.JobInfoUpdateReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.JobInfoExecuteRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.JobInfoListRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.JobInfoUpdateEnableStatusRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.JobInfoUpdateRes;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.JobInfoDO;
|
||||
import org.dromara.neutrinoproxy.server.service.JobInfoService;
|
||||
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.noear.solon.annotation.*;
|
||||
|
||||
@@ -19,14 +19,14 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller;
|
||||
package org.dromara.neutrinoproxy.server.controller;
|
||||
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.JobLogListReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.JobLogListRes;
|
||||
import fun.asgc.neutrino.proxy.server.service.JobLogService;
|
||||
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.log.JobLogListReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.log.JobLogListRes;
|
||||
import org.dromara.neutrinoproxy.server.service.JobLogService;
|
||||
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.noear.solon.annotation.Controller;
|
||||
import org.noear.solon.annotation.Get;
|
||||
@@ -19,15 +19,17 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller;
|
||||
package org.dromara.neutrinoproxy.server.controller;
|
||||
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
|
||||
import fun.asgc.neutrino.proxy.server.base.rest.Authorization;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.*;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.*;
|
||||
import fun.asgc.neutrino.proxy.server.service.LicenseService;
|
||||
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
|
||||
import org.dromara.neutrinoproxy.server.base.rest.Authorization;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.proxy.*;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.proxy.*;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.proxy.*;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.proxy.*;
|
||||
import org.dromara.neutrinoproxy.server.service.LicenseService;
|
||||
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
|
||||
import org.noear.solon.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
@@ -56,6 +58,11 @@ public class LicenseController {
|
||||
return licenseService.list(req);
|
||||
}
|
||||
|
||||
@Mapping("/auth-list")
|
||||
public List<LicenseListRes> queryCurUserLicense(LicenseListReq req) {
|
||||
return licenseService.queryCurUserLicense(req);
|
||||
}
|
||||
|
||||
@Post
|
||||
@Mapping("/create")
|
||||
@Authorization(onlyAdmin = true)
|
||||
@@ -0,0 +1,76 @@
|
||||
package org.dromara.neutrinoproxy.server.controller;
|
||||
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
|
||||
import org.dromara.neutrinoproxy.server.base.rest.Authorization;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.PortGroupCreateReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.PortGroupDeleteReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.PortGroupListReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.PortGroupUpdateEnableStatusReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.PortGroupCreateRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.PortGroupListRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.PortGroupUpdateEnableStatusRes;
|
||||
import org.dromara.neutrinoproxy.server.service.PortGroupService;
|
||||
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
|
||||
import org.noear.solon.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 端口分组控制层
|
||||
*/
|
||||
@Mapping("/port-group")
|
||||
@Controller
|
||||
public class PortGroupController {
|
||||
|
||||
@Inject
|
||||
private PortGroupService portGroupService;
|
||||
|
||||
@Post
|
||||
@Mapping("/create")
|
||||
public PortGroupCreateRes create(PortGroupCreateReq req) {
|
||||
ParamCheckUtil.checkNotNull(req, "req");
|
||||
ParamCheckUtil.checkNotEmpty(req.getName(), "name");
|
||||
ParamCheckUtil.checkNotNull(req.getPossessorType(), "possessorType");
|
||||
ParamCheckUtil.checkNotNull(req.getPossessorId(), "possessorId");
|
||||
|
||||
return portGroupService.create(req);
|
||||
}
|
||||
|
||||
@Get
|
||||
@Mapping("/page")
|
||||
public PageInfo<PortGroupListRes> page(PageQuery pageQuery, PortGroupListReq req) {
|
||||
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
|
||||
return portGroupService.page(pageQuery, req);
|
||||
}
|
||||
|
||||
@Get
|
||||
@Mapping("/list")
|
||||
public List<PortGroupListRes> list(PortGroupListReq req) {
|
||||
return portGroupService.list(req);
|
||||
}
|
||||
|
||||
@Post
|
||||
@Mapping("/update/enable-status")
|
||||
public PortGroupUpdateEnableStatusRes updateEnableStatus(PortGroupUpdateEnableStatusReq req) {
|
||||
ParamCheckUtil.checkNotNull(req, "req");
|
||||
ParamCheckUtil.checkNotNull(req.getId(), "id");
|
||||
ParamCheckUtil.checkNotNull(req.getEnable(), "enable");
|
||||
|
||||
return portGroupService.updateEnableStatus(req);
|
||||
}
|
||||
|
||||
@Post
|
||||
@Mapping("/delete")
|
||||
@Authorization(onlyAdmin = true)
|
||||
public void delete(PortGroupDeleteReq req) {
|
||||
ParamCheckUtil.checkNotNull(req, "req");
|
||||
ParamCheckUtil.checkNotNull(req.getId(), "id");
|
||||
|
||||
portGroupService.delete(req.getId());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package org.dromara.neutrinoproxy.server.controller;
|
||||
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
|
||||
import org.dromara.neutrinoproxy.server.constant.ExceptionConstant;
|
||||
import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.proxy.*;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.proxy.*;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.proxy.*;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.proxy.*;
|
||||
import org.dromara.neutrinoproxy.server.service.PortMappingService;
|
||||
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.noear.solon.annotation.*;
|
||||
|
||||
/**
|
||||
* 端口映射
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/8/8
|
||||
*/
|
||||
@Mapping("/port-mapping")
|
||||
@Controller
|
||||
public class PortMappingController {
|
||||
@Inject
|
||||
private PortMappingService portMappingService;
|
||||
|
||||
@Get
|
||||
@Mapping("/page")
|
||||
public PageInfo<PortMappingListRes> page(PageQuery pageQuery, PortMappingListReq req) {
|
||||
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
|
||||
|
||||
return portMappingService.page(pageQuery, req);
|
||||
}
|
||||
|
||||
@Post
|
||||
@Mapping("/create")
|
||||
public PortMappingCreateRes create(PortMappingCreateReq req) {
|
||||
ParamCheckUtil.checkNotNull(req, "req");
|
||||
ParamCheckUtil.checkNotNull(req.getLicenseId(), "licenseId");
|
||||
ParamCheckUtil.checkNotNull(req.getServerPort(), "serverPort");
|
||||
ParamCheckUtil.checkNotNull(req.getClientPort(), "clientPort");
|
||||
ParamCheckUtil.checkNotEmpty(req.getProtocal(), "protocal");
|
||||
ParamCheckUtil.checkMaxLength(req.getDescription(), 50, "描述", "50");
|
||||
if (StringUtils.isBlank(req.getClientIp())) {
|
||||
// 没传客户端ip,默认为127.0.0.1
|
||||
req.setClientIp("127.0.0.1");
|
||||
}
|
||||
NetworkProtocolEnum networkProtocolEnum = NetworkProtocolEnum.of(req.getProtocal());
|
||||
ParamCheckUtil.checkNotNull(networkProtocolEnum, ExceptionConstant.AN_UNSUPPORTED_PROTOCOL, req.getProtocal());
|
||||
if (networkProtocolEnum != NetworkProtocolEnum.HTTP) {
|
||||
// 目前仅HTTP支持绑定域名
|
||||
req.setSubdomain(null);
|
||||
}
|
||||
req.setProtocal(networkProtocolEnum.getDesc());
|
||||
if (null == req.getProxyResponses()) {
|
||||
req.setProxyResponses(0);
|
||||
}
|
||||
if (null == req.getProxyTimeoutMs()) {
|
||||
req.setProxyTimeoutMs(0L);
|
||||
}
|
||||
|
||||
return portMappingService.create(req);
|
||||
}
|
||||
|
||||
@Post
|
||||
@Mapping("/update")
|
||||
public PortMappingUpdateRes update(PortMappingUpdateReq req) {
|
||||
ParamCheckUtil.checkNotNull(req, "req");
|
||||
ParamCheckUtil.checkNotNull(req.getLicenseId(), "licenseId");
|
||||
ParamCheckUtil.checkNotNull(req.getServerPort(), "serverPort");
|
||||
ParamCheckUtil.checkNotNull(req.getClientPort(), "clientPort");
|
||||
ParamCheckUtil.checkNotEmpty(req.getProtocal(), "protocal");
|
||||
ParamCheckUtil.checkMaxLength(req.getDescription(), 50, "描述", "50");
|
||||
if (StringUtils.isBlank(req.getClientIp())) {
|
||||
// 没传客户端ip,默认为127.0.0.1
|
||||
req.setClientIp("127.0.0.1");
|
||||
}
|
||||
NetworkProtocolEnum networkProtocolEnum = NetworkProtocolEnum.of(req.getProtocal());
|
||||
ParamCheckUtil.checkNotNull(networkProtocolEnum, ExceptionConstant.AN_UNSUPPORTED_PROTOCOL, req.getProtocal());
|
||||
if (networkProtocolEnum != NetworkProtocolEnum.HTTP) {
|
||||
// 目前仅HTTP支持绑定域名
|
||||
req.setSubdomain(null);
|
||||
}
|
||||
req.setProtocal(networkProtocolEnum.getDesc());
|
||||
if (null == req.getProxyResponses()) {
|
||||
req.setProxyResponses(0);
|
||||
}
|
||||
if (null == req.getProxyTimeoutMs()) {
|
||||
req.setProxyTimeoutMs(0L);
|
||||
}
|
||||
|
||||
return portMappingService.update(req);
|
||||
}
|
||||
|
||||
@Get
|
||||
@Mapping("/detail")
|
||||
public PortMappingDetailRes detail(PortMappingDetailReq req) {
|
||||
ParamCheckUtil.checkNotNull(req, "req");
|
||||
ParamCheckUtil.checkNotNull(req.getId(), "id");
|
||||
|
||||
return portMappingService.detail(req.getId());
|
||||
}
|
||||
|
||||
@Post
|
||||
@Mapping("/update/enable-status")
|
||||
public PortMappingUpdateEnableStatusRes updateEnableStatus(PortMappingUpdateEnableStatusReq req) {
|
||||
ParamCheckUtil.checkNotNull(req, "req");
|
||||
ParamCheckUtil.checkNotNull(req.getId(), "id");
|
||||
ParamCheckUtil.checkNotNull(req.getEnable(), "enable");
|
||||
|
||||
return portMappingService.updateEnableStatus(req);
|
||||
}
|
||||
|
||||
@Post
|
||||
@Mapping("/delete")
|
||||
public void delete(PortMappingDeleteReq req) {
|
||||
ParamCheckUtil.checkNotNull(req, "req");
|
||||
ParamCheckUtil.checkNotNull(req.getId(), "id");
|
||||
|
||||
portMappingService.delete(req.getId());
|
||||
}
|
||||
}
|
||||
@@ -19,20 +19,17 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller;
|
||||
package org.dromara.neutrinoproxy.server.controller;
|
||||
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
|
||||
import fun.asgc.neutrino.proxy.server.base.rest.Authorization;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.PortPoolCreateReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.PortPoolDeleteReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.PortPoolListReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.PortPoolUpdateEnableStatusReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.PortPoolCreateRes;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.PortPoolListRes;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.PortPoolUpdateEnableStatusRes;
|
||||
import fun.asgc.neutrino.proxy.server.service.PortPoolService;
|
||||
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
|
||||
import org.dromara.neutrinoproxy.server.base.rest.Authorization;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.*;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.*;
|
||||
import org.dromara.neutrinoproxy.server.service.PortPoolService;
|
||||
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.*;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.*;
|
||||
import org.noear.solon.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
@@ -56,6 +53,15 @@ public class PortPoolController {
|
||||
return portPoolService.page(pageQuery, req);
|
||||
}
|
||||
|
||||
@Post
|
||||
@Mapping("/update")
|
||||
public PortPoolUpdateRes update(PortPoolUpdateReq req) {
|
||||
ParamCheckUtil.checkNotNull(req, "req");
|
||||
ParamCheckUtil.checkNotNull(req.getId(), "id");
|
||||
ParamCheckUtil.checkNotNull(req.getGroupId(), "groupId");
|
||||
return portPoolService.update(req);
|
||||
}
|
||||
|
||||
@Get
|
||||
@Mapping("/list")
|
||||
public List<PortPoolListRes> list(PortPoolListReq req) {
|
||||
@@ -67,7 +73,8 @@ public class PortPoolController {
|
||||
@Authorization(onlyAdmin = true)
|
||||
public PortPoolCreateRes create(PortPoolCreateReq req) {
|
||||
ParamCheckUtil.checkNotNull(req, "req");
|
||||
ParamCheckUtil.checkNotNull(req.getPort(), "port");
|
||||
ParamCheckUtil.checkNotEmpty(req.getPort(), "port");
|
||||
ParamCheckUtil.checkNotNull(req.getGroupId(), "groupId");
|
||||
|
||||
return portPoolService.create(req);
|
||||
}
|
||||
@@ -92,4 +99,46 @@ public class PortPoolController {
|
||||
|
||||
portPoolService.delete(req.getId());
|
||||
}
|
||||
|
||||
@Get
|
||||
@Mapping("/get-available-port-list")
|
||||
public PageInfo<PortPoolListRes> getAvailablePortList(AvailablePortListReq req) {
|
||||
ParamCheckUtil.checkNotNull(req, "req");
|
||||
ParamCheckUtil.checkNotNull(req.getLicenseId(), "licenseId");
|
||||
return portPoolService.getAvailablePortList(req);
|
||||
}
|
||||
|
||||
|
||||
@Get
|
||||
@Mapping("/get-by-group")
|
||||
public List<PortPoolListRes> portListByGroupId(String groupId) {
|
||||
return portPoolService.portListByGroupId(groupId);
|
||||
}
|
||||
|
||||
@Put
|
||||
@Mapping("/update-group")
|
||||
public PortPoolUpdateGroupRes updateGroup(PortPoolUpdateGroupReq req) {
|
||||
ParamCheckUtil.checkNotNull(req, "req");
|
||||
ParamCheckUtil.checkNotNull(req.getGroupId(), "groupId");
|
||||
ParamCheckUtil.checkNotEmpty(req.getPortIdList(), "portIdList");
|
||||
return portPoolService.updateGroup(req);
|
||||
}
|
||||
|
||||
@Post
|
||||
@Mapping("/delete-batch")
|
||||
@Authorization(onlyAdmin = true)
|
||||
public void deleteBatch(PortPoolDeleteBatchReq req) {
|
||||
ParamCheckUtil.checkNotNull(req, "req");
|
||||
ParamCheckUtil.checkNotNull(req.getIds(), "ids");
|
||||
|
||||
portPoolService.deleteBatch(req.getIds());
|
||||
}
|
||||
|
||||
@Get
|
||||
@Mapping("/port-available")
|
||||
public boolean portAvailable(Integer port, Integer portMappingId) {
|
||||
ParamCheckUtil.checkNotNull(port, "port");
|
||||
|
||||
return portPoolService.portAvailable(port, portMappingId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.dromara.neutrinoproxy.server.controller;
|
||||
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.ProtocalListRes;
|
||||
import org.dromara.neutrinoproxy.server.service.ProtocalService;
|
||||
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
|
||||
import org.noear.solon.annotation.Controller;
|
||||
import org.noear.solon.annotation.Get;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
import org.noear.solon.annotation.Mapping;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/4/2
|
||||
*/
|
||||
@Mapping("/protocal")
|
||||
@Controller
|
||||
public class ProtocalController {
|
||||
|
||||
@Inject
|
||||
private ProtocalService protocalService;
|
||||
|
||||
@Get
|
||||
@Mapping("/page")
|
||||
public PageInfo<ProtocalListRes> page(PageQuery pageQuery) {
|
||||
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
|
||||
|
||||
List<ProtocalListRes> list = protocalService.list();
|
||||
return PageInfo.of(protocalService.list(), (long)list.size(), pageQuery.getCurrent(), pageQuery.getSize());
|
||||
}
|
||||
|
||||
@Get
|
||||
@Mapping("/list")
|
||||
public List<ProtocalListRes> list() {
|
||||
return protocalService.list();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package org.dromara.neutrinoproxy.server.controller;
|
||||
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.report.LicenseFlowMonthReportReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.report.LicenseFlowReportReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.report.UserFlowMonthReportReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.report.UserFlowReportReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.report.*;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.report.*;
|
||||
import org.dromara.neutrinoproxy.server.service.ReportService;
|
||||
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
|
||||
import org.noear.solon.annotation.Controller;
|
||||
import org.noear.solon.annotation.Get;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
import org.noear.solon.annotation.Mapping;
|
||||
|
||||
/**
|
||||
* 报表管理
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/9/12
|
||||
*/
|
||||
@Mapping("/report")
|
||||
@Controller
|
||||
public class ReportController {
|
||||
|
||||
@Inject
|
||||
private ReportService reportService;
|
||||
|
||||
/**
|
||||
* 首页数据一览
|
||||
* @return
|
||||
*/
|
||||
@Get
|
||||
@Mapping("/home/data-view")
|
||||
public HomeDataView homeDataView() {
|
||||
return reportService.homeDataView();
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户流量报表分页
|
||||
* @param pageQuery
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
@Get
|
||||
@Mapping("/user/flow-report/page")
|
||||
public PageInfo<UserFlowReportRes> userFlowReportPage(PageQuery pageQuery, UserFlowReportReq req) {
|
||||
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
|
||||
|
||||
return reportService.userFlowReportPage(pageQuery, req);
|
||||
}
|
||||
|
||||
/**
|
||||
* license流量报表分页
|
||||
* @param pageQuery
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
@Get
|
||||
@Mapping("/license/flow-report/page")
|
||||
public PageInfo<LicenseFlowReportRes> licenseFlowReportPage(PageQuery pageQuery, LicenseFlowReportReq req) {
|
||||
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
|
||||
|
||||
return reportService.licenseFlowReportPage(pageQuery, req);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户流量报表月度明细
|
||||
* @param pageQuery
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
@Get
|
||||
@Mapping("/user/flow-month-report/page")
|
||||
public PageInfo<UserFlowMonthReportRes> userFlowMonthReportPage(PageQuery pageQuery, UserFlowMonthReportReq req) {
|
||||
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
|
||||
|
||||
return reportService.userFlowMonthReportPage(pageQuery, req);
|
||||
}
|
||||
|
||||
/**
|
||||
* license流量报表月度明细
|
||||
* @param pageQuery
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
@Get
|
||||
@Mapping("/license/flow-month-report/page")
|
||||
public PageInfo<LicenseFlowMonthReportRes> licenseFlowMonthReportPage(PageQuery pageQuery, LicenseFlowMonthReportReq req) {
|
||||
ParamCheckUtil.checkNotNull(pageQuery, "pageQuery");
|
||||
|
||||
return reportService.licenseFlowMonthReportPage(pageQuery, req);
|
||||
}
|
||||
}
|
||||
@@ -19,20 +19,22 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller;
|
||||
package org.dromara.neutrinoproxy.server.controller;
|
||||
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
|
||||
import fun.asgc.neutrino.proxy.server.base.rest.Authorization;
|
||||
import fun.asgc.neutrino.proxy.server.base.rest.SystemContextHolder;
|
||||
import fun.asgc.neutrino.proxy.server.constant.ExceptionConstant;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.*;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.*;
|
||||
import fun.asgc.neutrino.proxy.server.dal.UserMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
|
||||
import fun.asgc.neutrino.proxy.server.service.UserService;
|
||||
import fun.asgc.neutrino.proxy.server.util.Md5Util;
|
||||
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
|
||||
import org.dromara.neutrinoproxy.server.base.rest.Authorization;
|
||||
import org.dromara.neutrinoproxy.server.base.rest.SystemContextHolder;
|
||||
import org.dromara.neutrinoproxy.server.constant.ExceptionConstant;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.*;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.*;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.*;
|
||||
import org.dromara.neutrinoproxy.server.dal.UserMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
|
||||
import org.dromara.neutrinoproxy.server.service.UserService;
|
||||
import org.dromara.neutrinoproxy.server.util.Md5Util;
|
||||
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.*;
|
||||
import org.noear.solon.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
@@ -19,14 +19,14 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller;
|
||||
package org.dromara.neutrinoproxy.server.controller;
|
||||
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.UserLoginRecordListReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.UserLoginRecordListRes;
|
||||
import fun.asgc.neutrino.proxy.server.service.UserLoginRecordService;
|
||||
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.log.UserLoginRecordListReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.log.UserLoginRecordListRes;
|
||||
import org.dromara.neutrinoproxy.server.service.UserLoginRecordService;
|
||||
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
|
||||
import org.noear.solon.annotation.Controller;
|
||||
import org.noear.solon.annotation.Get;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.log;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -29,5 +29,8 @@ import lombok.Data;
|
||||
*/
|
||||
@Data
|
||||
public class ClientConnectRecordListReq {
|
||||
|
||||
/**
|
||||
* licenseId
|
||||
*/
|
||||
private Integer licenseId;
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.log;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.log;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -30,5 +30,8 @@ import lombok.Data;
|
||||
*/
|
||||
@Data
|
||||
public class UserLoginRecordListReq {
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Integer userId;
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.proxy;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.proxy;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.proxy;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.proxy;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -30,5 +30,16 @@ import lombok.Data;
|
||||
*/
|
||||
@Data
|
||||
public class LicenseListReq {
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Integer userId;
|
||||
/**
|
||||
* 是否在线
|
||||
*/
|
||||
private Integer isOnline;
|
||||
/**
|
||||
* 启动状态 1启用 2禁用
|
||||
*/
|
||||
private Integer enable;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.proxy;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -19,9 +19,10 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.proxy;
|
||||
|
||||
import lombok.Data;
|
||||
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
|
||||
|
||||
/**
|
||||
* 更新启用状态请求
|
||||
@@ -36,7 +37,7 @@ public class LicenseUpdateEnableStatusReq {
|
||||
private Integer id;
|
||||
/**
|
||||
* 启用状态
|
||||
* {@link fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum}
|
||||
* {@link EnableStatusEnum}
|
||||
*/
|
||||
private Integer enable;
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.proxy;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.proxy;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -34,6 +34,14 @@ public class PortMappingCreateReq {
|
||||
* licenseId
|
||||
*/
|
||||
private Integer licenseId;
|
||||
/**
|
||||
* 协议
|
||||
*/
|
||||
private String protocal;
|
||||
/**
|
||||
* 子域名
|
||||
*/
|
||||
private String subdomain;
|
||||
/**
|
||||
* 服务端端口
|
||||
*/
|
||||
@@ -46,4 +54,16 @@ public class PortMappingCreateReq {
|
||||
* 客户端端口
|
||||
*/
|
||||
private Integer clientPort;
|
||||
/**
|
||||
* 代理响应数量(响应数据包数量,如果没有拆包则等于数据条数)
|
||||
*/
|
||||
private Integer proxyResponses;
|
||||
/**
|
||||
* 代理超时时间
|
||||
*/
|
||||
private Long proxyTimeoutMs;
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.proxy;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.proxy;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -19,60 +19,49 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.proxy;
|
||||
|
||||
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 端口映射列表请求
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/12/21
|
||||
* @date: 2022/8/8
|
||||
*/
|
||||
@Data
|
||||
public class LicenseFlowReportRes {
|
||||
public class PortMappingListReq {
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Integer userId;
|
||||
/**
|
||||
* 用户名称
|
||||
* 协议
|
||||
*/
|
||||
private String userName;
|
||||
private String protocal;
|
||||
/**
|
||||
* licenseId
|
||||
*/
|
||||
private Integer licenseId;
|
||||
/**
|
||||
* license名称
|
||||
* 服务端口号
|
||||
*/
|
||||
private String licenseName;
|
||||
private Integer serverPort;
|
||||
/**
|
||||
* 写入字节数
|
||||
* 描述
|
||||
*/
|
||||
private Long writeBytes;
|
||||
private String description;
|
||||
|
||||
|
||||
/**
|
||||
* 读取字节数
|
||||
* 是否在线
|
||||
* {@link OnlineStatusEnum}
|
||||
*/
|
||||
private Long readBytes;
|
||||
private Integer isOnline;
|
||||
/**
|
||||
* 写入流量描述
|
||||
* 启用状态
|
||||
* {@link EnableStatusEnum}
|
||||
*/
|
||||
private String writeFlowStr;
|
||||
/**
|
||||
* 读取流量描述
|
||||
*/
|
||||
private String readFlowStr;
|
||||
/**
|
||||
* 流量描述
|
||||
*/
|
||||
private String flowStr;
|
||||
/**
|
||||
* 报表时间
|
||||
*/
|
||||
private Date date;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
private Integer enable;
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.proxy;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.proxy;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -38,6 +38,14 @@ public class PortMappingUpdateReq {
|
||||
* licenseId
|
||||
*/
|
||||
private Integer licenseId;
|
||||
/**
|
||||
* 协议
|
||||
*/
|
||||
private String protocal;
|
||||
/**
|
||||
* 子域名
|
||||
*/
|
||||
private String subdomain;
|
||||
/**
|
||||
* 服务端端口
|
||||
*/
|
||||
@@ -50,4 +58,16 @@ public class PortMappingUpdateReq {
|
||||
* 客户端端口
|
||||
*/
|
||||
private Integer clientPort;
|
||||
/**
|
||||
* 代理响应数量(响应数据包数量,如果没有拆包则等于数据条数)
|
||||
*/
|
||||
private Integer proxyResponses;
|
||||
/**
|
||||
* 代理超时时间
|
||||
*/
|
||||
private Long proxyTimeoutMs;
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.dromara.neutrinoproxy.server.controller.req.report;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/12/21
|
||||
*/
|
||||
@Data
|
||||
public class LicenseFlowMonthReportReq {
|
||||
private Integer userId;
|
||||
private Integer licenseId;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.dromara.neutrinoproxy.server.controller.req.report;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/12/21
|
||||
*/
|
||||
@Data
|
||||
public class LicenseFlowReportReq {
|
||||
private Integer userId;
|
||||
private Integer licenseId;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.dromara.neutrinoproxy.server.controller.req.report;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/12/21
|
||||
*/
|
||||
@Data
|
||||
public class UserFlowMonthReportReq {
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Integer userId;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.dromara.neutrinoproxy.server.controller.req.report;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/12/21
|
||||
*/
|
||||
@Data
|
||||
public class UserFlowReportReq {
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Integer userId;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.dromara.neutrinoproxy.server.controller.req.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 获取可用端口请求
|
||||
*/
|
||||
@Data
|
||||
public class AvailablePortListReq {
|
||||
|
||||
/**
|
||||
* licenseId
|
||||
*/
|
||||
private Integer licenseId;
|
||||
|
||||
/**
|
||||
* 当前页
|
||||
*/
|
||||
private int page = 1;
|
||||
/**
|
||||
* 分页大小
|
||||
*/
|
||||
private int size = 10;
|
||||
|
||||
/**
|
||||
* 搜索关键字
|
||||
*/
|
||||
private String keyword;
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -19,9 +19,10 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.system;
|
||||
|
||||
import lombok.Data;
|
||||
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@@ -59,7 +60,7 @@ public class JobInfoUpdateReq {
|
||||
private String alarmDing;
|
||||
/**
|
||||
* 启用状态
|
||||
* {@link fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum}
|
||||
* {@link EnableStatusEnum}
|
||||
*/
|
||||
private Integer enable;
|
||||
/**
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.dromara.neutrinoproxy.server.controller.req.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 端口分组创建请求
|
||||
*
|
||||
*
|
||||
*/
|
||||
@Data
|
||||
public class PortGroupCreateReq {
|
||||
|
||||
/**
|
||||
* 分组名称
|
||||
*/
|
||||
private String name ;
|
||||
|
||||
/**
|
||||
* 所有者类型 (0、全局共享 1、用户所有 2License所有)
|
||||
*/
|
||||
private Integer possessorType ;
|
||||
|
||||
/**
|
||||
* 所有者id(当type为0时 固定为-1、当type为1时为用户id 、当type为2时为licenseid)
|
||||
*/
|
||||
private Integer possessorId ;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.dromara.neutrinoproxy.server.controller.req.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 删除端口分组请求
|
||||
*/
|
||||
@Data
|
||||
public class PortGroupDeleteReq {
|
||||
private Integer id;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.dromara.neutrinoproxy.server.controller.req.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 端口分组查询请求
|
||||
*
|
||||
*
|
||||
*/
|
||||
@Data
|
||||
public class PortGroupListReq {
|
||||
|
||||
/**
|
||||
* 分组名称
|
||||
*/
|
||||
private String name ;
|
||||
|
||||
/**
|
||||
* 所有者类型 (0、全局共享 1、用户所有 2License所有)
|
||||
*/
|
||||
private Integer possessorType ;
|
||||
|
||||
/**
|
||||
* 所有者id(当type为0时 固定为-1、当type为1时为用户id 、当type为2时为licenseid)
|
||||
*/
|
||||
private Integer possessorId ;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.dromara.neutrinoproxy.server.controller.req.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 修改端口分组请求
|
||||
*/
|
||||
@Data
|
||||
public class PortGroupUpdateEnableStatusReq {
|
||||
|
||||
private Integer id;
|
||||
private Integer enable;
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -30,5 +30,7 @@ import lombok.Data;
|
||||
*/
|
||||
@Data
|
||||
public class PortPoolCreateReq {
|
||||
private Integer port;
|
||||
private String port;
|
||||
|
||||
private Integer groupId;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.dromara.neutrinoproxy.server.controller.req.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author: Metal
|
||||
* @date: 2023/7/3
|
||||
*/
|
||||
@Data
|
||||
public class PortPoolDeleteBatchReq {
|
||||
private List<Integer> ids;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -30,5 +30,8 @@ import lombok.Data;
|
||||
*/
|
||||
@Data
|
||||
public class PortPoolListReq {
|
||||
|
||||
/**
|
||||
* 分组ID
|
||||
*/
|
||||
private Integer groupId;
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.dromara.neutrinoproxy.server.controller.req.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 批量修改端口分组请求
|
||||
*/
|
||||
@Data
|
||||
public class PortPoolUpdateGroupReq {
|
||||
|
||||
private String groupId;
|
||||
|
||||
private List<Integer> portIdList;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.dromara.neutrinoproxy.server.controller.req.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/3/25
|
||||
*/
|
||||
@Data
|
||||
public class PortPoolUpdateReq {
|
||||
private Integer id;
|
||||
|
||||
private Integer groupId;
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.system;
|
||||
|
||||
/**
|
||||
* 用户列表请求
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.req;
|
||||
package org.dromara.neutrinoproxy.server.controller.req.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.log;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -19,9 +19,10 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.log;
|
||||
|
||||
import lombok.Data;
|
||||
import org.dromara.neutrinoproxy.server.constant.AlarmStatusEnum;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@@ -55,7 +56,7 @@ public class JobLogListRes {
|
||||
private String msg;
|
||||
/**
|
||||
* 报警状态
|
||||
* {@link fun.asgc.neutrino.proxy.server.constant.AlarmStatusEnum}
|
||||
* {@link AlarmStatusEnum}
|
||||
*/
|
||||
private Integer alarmStatus;
|
||||
/**
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.log;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.proxy;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -19,9 +19,10 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.proxy;
|
||||
|
||||
import fun.asgc.neutrino.proxy.server.constant.OnlineStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@@ -59,7 +60,7 @@ public class LicenseDetailRes {
|
||||
private Integer isOnline;
|
||||
/**
|
||||
* 启用状态
|
||||
* {@link fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum}
|
||||
* {@link EnableStatusEnum}
|
||||
*/
|
||||
private Integer enable;
|
||||
/**
|
||||
@@ -1,6 +1,7 @@
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.proxy;
|
||||
|
||||
import fun.asgc.neutrino.proxy.server.constant.OnlineStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@@ -38,7 +39,7 @@ public class LicenseListRes {
|
||||
private Integer isOnline;
|
||||
/**
|
||||
* 启用状态
|
||||
* {@link fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum}
|
||||
* {@link EnableStatusEnum}
|
||||
*/
|
||||
private Integer enable;
|
||||
/**
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.proxy;
|
||||
|
||||
/**
|
||||
* 更新启用状态响应
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.proxy;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.proxy;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.proxy;
|
||||
|
||||
import fun.asgc.neutrino.proxy.server.constant.OnlineStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@@ -48,9 +49,17 @@ public class PortMappingDetailRes {
|
||||
* {@link OnlineStatusEnum}
|
||||
*/
|
||||
private Integer isOnline;
|
||||
/**
|
||||
* 代理响应数量(响应数据包数量,如果没有拆包则等于数据条数)
|
||||
*/
|
||||
private Integer proxyResponses;
|
||||
/**
|
||||
* 代理超时时间
|
||||
*/
|
||||
private Long proxyTimeoutMs;
|
||||
/**
|
||||
* 启用状态
|
||||
* {@link fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum}
|
||||
* {@link EnableStatusEnum}
|
||||
*/
|
||||
private Integer enable;
|
||||
/**
|
||||
@@ -19,9 +19,10 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.proxy;
|
||||
|
||||
import fun.asgc.neutrino.proxy.server.constant.OnlineStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
@@ -38,6 +39,18 @@ public class PortMappingListRes {
|
||||
* licenseId
|
||||
*/
|
||||
private Integer licenseId;
|
||||
/**
|
||||
* 协议
|
||||
*/
|
||||
private String protocal;
|
||||
/**
|
||||
* 子域名
|
||||
*/
|
||||
private String subdomain;
|
||||
/**
|
||||
* 域名
|
||||
*/
|
||||
private String domain;
|
||||
/**
|
||||
* license名称
|
||||
*/
|
||||
@@ -73,9 +86,21 @@ public class PortMappingListRes {
|
||||
private Integer isOnline;
|
||||
/**
|
||||
* 启用状态
|
||||
* {@link fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum}
|
||||
* {@link EnableStatusEnum}
|
||||
*/
|
||||
private Integer enable;
|
||||
/**
|
||||
* 代理响应数量(响应数据包数量,如果没有拆包则等于数据条数)
|
||||
*/
|
||||
private Integer proxyResponses;
|
||||
/**
|
||||
* 代理超时时间
|
||||
*/
|
||||
private Long proxyTimeoutMs;
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.proxy;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.proxy;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
package org.dromara.neutrinoproxy.server.controller.res.report;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/3/26
|
||||
*/
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
public class HomeDataView {
|
||||
|
||||
/**
|
||||
* licenses数据
|
||||
*/
|
||||
private License license;
|
||||
|
||||
/**
|
||||
* 端口映射
|
||||
*/
|
||||
private PortMapping portMapping;
|
||||
|
||||
/**
|
||||
* 今日流量
|
||||
*/
|
||||
private TodayFlow todayFlow;
|
||||
|
||||
/**
|
||||
* 总流量
|
||||
*/
|
||||
private TotalFlow totalFlow;
|
||||
|
||||
/**
|
||||
* 最近7日流量
|
||||
*/
|
||||
private Last7dFlow last7dFlow;
|
||||
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
public static class License {
|
||||
/**
|
||||
* 总数
|
||||
*/
|
||||
private Integer totalCount;
|
||||
/**
|
||||
* 在线数
|
||||
*/
|
||||
private Integer onlineCount;
|
||||
/**
|
||||
* 离线数
|
||||
*/
|
||||
private Integer offlineCount;
|
||||
}
|
||||
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
public static class PortMapping {
|
||||
/**
|
||||
* 总数
|
||||
*/
|
||||
private Integer totalCount;
|
||||
/**
|
||||
* 在线数
|
||||
*/
|
||||
private Integer onlineCount;
|
||||
/**
|
||||
* 离线数
|
||||
*/
|
||||
private Integer offlineCount;
|
||||
}
|
||||
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
public static class TodayFlow {
|
||||
/**
|
||||
* 上行流量字节数
|
||||
*/
|
||||
private Long upFlowBytes;
|
||||
/**
|
||||
* 下行流量字节数
|
||||
*/
|
||||
private Long downFlowBytes;
|
||||
/**
|
||||
* 总流量字节数
|
||||
*/
|
||||
private Long totalFlowBytes;
|
||||
/**
|
||||
* 上行流量描述
|
||||
*/
|
||||
private String upFlowDesc;
|
||||
/**
|
||||
* 下行流量描述
|
||||
*/
|
||||
private String downFlowDesc;
|
||||
/**
|
||||
* 总流量描述
|
||||
*/
|
||||
private String totalFlowDesc;
|
||||
}
|
||||
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
public static class TotalFlow {
|
||||
/**
|
||||
* 上行流量字节数
|
||||
*/
|
||||
private Long upFlowBytes;
|
||||
/**
|
||||
* 下行流量字节数
|
||||
*/
|
||||
private Long downFlowBytes;
|
||||
/**
|
||||
* 总流量字节数
|
||||
*/
|
||||
private Long totalFlowBytes;
|
||||
/**
|
||||
* 上行流量描述
|
||||
*/
|
||||
private String upFlowDesc;
|
||||
/**
|
||||
* 下行流量描述
|
||||
*/
|
||||
private String downFlowDesc;
|
||||
/**
|
||||
* 总流量描述
|
||||
*/
|
||||
private String totalFlowDesc;
|
||||
}
|
||||
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
public static class Last7dFlow {
|
||||
/**
|
||||
* 最近7日流量数据
|
||||
*/
|
||||
private List<SingleDayFlow> dataList;
|
||||
/**
|
||||
* x轴日期
|
||||
*/
|
||||
private List<String> xDate;
|
||||
/**
|
||||
* 图例(上行流量、下行流量、总流量)
|
||||
*/
|
||||
private List<String> legendData;
|
||||
/**
|
||||
* 折线列表
|
||||
*/
|
||||
private List<Series> seriesList;
|
||||
}
|
||||
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
public static class SingleDayFlow {
|
||||
/**
|
||||
* 日期字符串
|
||||
*/
|
||||
private String dateStr;
|
||||
/**
|
||||
* 日期
|
||||
*/
|
||||
private Date date;
|
||||
/**
|
||||
* 上行流量字节数
|
||||
*/
|
||||
private Long upFlowBytes;
|
||||
/**
|
||||
* 下行流量字节数
|
||||
*/
|
||||
private Long downFlowBytes;
|
||||
/**
|
||||
* 总流量字节数
|
||||
*/
|
||||
private Long totalFlowBytes;
|
||||
/**
|
||||
* 上行流量描述
|
||||
*/
|
||||
private String upFlowDesc;
|
||||
/**
|
||||
* 下行流量描述
|
||||
*/
|
||||
private String downFlowDesc;
|
||||
/**
|
||||
* 总流量描述
|
||||
*/
|
||||
private String totalFlowDesc;
|
||||
}
|
||||
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
public static class Series {
|
||||
/**
|
||||
* 此处目前固定为:line
|
||||
*/
|
||||
private String seriesType;
|
||||
/**
|
||||
* 名称:上行流量、下行流量、总流量
|
||||
*/
|
||||
private String seriesName;
|
||||
/**
|
||||
* y值序列
|
||||
*/
|
||||
private List<Long> seriesData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.dromara.neutrinoproxy.server.controller.res.report;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/12/21
|
||||
*/
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
public class LicenseFlowMonthReportRes {
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Integer userId;
|
||||
/**
|
||||
* 用户名称
|
||||
*/
|
||||
private String userName;
|
||||
/**
|
||||
* licenseId
|
||||
*/
|
||||
private Integer licenseId;
|
||||
/**
|
||||
* license名称
|
||||
*/
|
||||
private String licenseName;
|
||||
/**
|
||||
* 上行流量字节数
|
||||
*/
|
||||
private Long upFlowBytes;
|
||||
/**
|
||||
* 下行流量字节数
|
||||
*/
|
||||
private Long downFlowBytes;
|
||||
/**
|
||||
* 总流量字节数
|
||||
*/
|
||||
private Long totalFlowBytes;
|
||||
/**
|
||||
* 上行流量描述
|
||||
*/
|
||||
private String upFlowDesc;
|
||||
/**
|
||||
* 下行流量描述
|
||||
*/
|
||||
private String downFlowDesc;
|
||||
/**
|
||||
* 总流量描述
|
||||
*/
|
||||
private String totalFlowDesc;
|
||||
/**
|
||||
* 日期
|
||||
*/
|
||||
private Date date;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package org.dromara.neutrinoproxy.server.controller.res.report;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/12/21
|
||||
*/
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
public class LicenseFlowReportRes {
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Integer userId;
|
||||
/**
|
||||
* 用户名称
|
||||
*/
|
||||
private String userName;
|
||||
/**
|
||||
* licenseId
|
||||
*/
|
||||
private Integer licenseId;
|
||||
/**
|
||||
* license名称
|
||||
*/
|
||||
private String licenseName;
|
||||
/**
|
||||
* 上行流量字节数
|
||||
*/
|
||||
private Long upFlowBytes;
|
||||
/**
|
||||
* 下行流量字节数
|
||||
*/
|
||||
private Long downFlowBytes;
|
||||
/**
|
||||
* 总流量字节数
|
||||
*/
|
||||
private Long totalFlowBytes;
|
||||
/**
|
||||
* 上行流量描述
|
||||
*/
|
||||
private String upFlowDesc;
|
||||
/**
|
||||
* 下行流量描述
|
||||
*/
|
||||
private String downFlowDesc;
|
||||
/**
|
||||
* 总流量描述
|
||||
*/
|
||||
private String totalFlowDesc;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.dromara.neutrinoproxy.server.controller.res.report;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/12/21
|
||||
*/
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
public class UserFlowMonthReportRes {
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Integer userId;
|
||||
/**
|
||||
* 用户名称
|
||||
*/
|
||||
private String userName;
|
||||
/**
|
||||
* 上行流量字节数
|
||||
*/
|
||||
private Long upFlowBytes;
|
||||
/**
|
||||
* 下行流量字节数
|
||||
*/
|
||||
private Long downFlowBytes;
|
||||
/**
|
||||
* 总流量字节数
|
||||
*/
|
||||
private Long totalFlowBytes;
|
||||
/**
|
||||
* 上行流量描述
|
||||
*/
|
||||
private String upFlowDesc;
|
||||
/**
|
||||
* 下行流量描述
|
||||
*/
|
||||
private String downFlowDesc;
|
||||
/**
|
||||
* 总流量描述
|
||||
*/
|
||||
private String totalFlowDesc;
|
||||
/**
|
||||
* 日期
|
||||
*/
|
||||
private Date date;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.dromara.neutrinoproxy.server.controller.res.report;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/12/21
|
||||
*/
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
public class UserFlowReportRes {
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Integer userId;
|
||||
/**
|
||||
* 用户名称
|
||||
*/
|
||||
private String userName;
|
||||
/**
|
||||
* 上行流量字节数
|
||||
*/
|
||||
private Long upFlowBytes;
|
||||
/**
|
||||
* 下行流量字节数
|
||||
*/
|
||||
private Long downFlowBytes;
|
||||
/**
|
||||
* 总流量字节数
|
||||
*/
|
||||
private Long totalFlowBytes;
|
||||
/**
|
||||
* 上行流量描述
|
||||
*/
|
||||
private String upFlowDesc;
|
||||
/**
|
||||
* 下行流量描述
|
||||
*/
|
||||
private String downFlowDesc;
|
||||
/**
|
||||
* 总流量描述
|
||||
*/
|
||||
private String totalFlowDesc;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.dromara.neutrinoproxy.server.controller.res.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class AvailablePortListRes {
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -19,9 +19,11 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.system;
|
||||
|
||||
import lombok.Data;
|
||||
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
@@ -58,7 +60,7 @@ public class JobInfoListRes {
|
||||
private String alarmDing;
|
||||
/**
|
||||
* 启用状态
|
||||
* {@link fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum}
|
||||
* {@link EnableStatusEnum}
|
||||
*/
|
||||
private Integer enable;
|
||||
/**
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.system;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.system;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
@@ -0,0 +1,4 @@
|
||||
package org.dromara.neutrinoproxy.server.controller.res.system;
|
||||
|
||||
public class PortGroupCreateRes {
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.dromara.neutrinoproxy.server.controller.res.system;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class PortGroupListRes {
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* 分组名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 所有者类型 (0、全局共享 1、用户所有 2License所有)
|
||||
*/
|
||||
private Integer possessorType;
|
||||
|
||||
/**
|
||||
* 所有者id(当type为0时 固定为-1、当type为1时为用户id 、当type为2时为licenseid)
|
||||
*/
|
||||
private Integer possessorId;
|
||||
|
||||
/**
|
||||
* 是否启用(1、启用 2、禁用)
|
||||
*/
|
||||
private Integer enable;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* 来源
|
||||
*/
|
||||
private String possessor;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.dromara.neutrinoproxy.server.controller.res.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class PortGroupUpdateEnableStatusRes {
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -19,9 +19,10 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.system;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@@ -30,6 +31,7 @@ import java.util.Date;
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/8/7
|
||||
*/
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
public class PortPoolListRes {
|
||||
private Integer id;
|
||||
@@ -49,4 +51,18 @@ public class PortPoolListRes {
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* 分组类型
|
||||
*/
|
||||
private Integer possessorType;
|
||||
|
||||
/**
|
||||
* 分组
|
||||
*/
|
||||
private String groupName;
|
||||
/**
|
||||
* 分组ID
|
||||
*/
|
||||
private Integer groupId;
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
package org.dromara.neutrinoproxy.server.controller.res.system;
|
||||
|
||||
public class PortPoolUpdateGroupRes {
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.dromara.neutrinoproxy.server.controller.res.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/3/25
|
||||
*/
|
||||
@Data
|
||||
public class PortPoolUpdateRes {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.dromara.neutrinoproxy.server.controller.res.system;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 协议列表响应
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/4/2
|
||||
*/
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
public class ProtocalListRes {
|
||||
/**
|
||||
* 协议名称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 启用状态
|
||||
*/
|
||||
private Boolean enable;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.system;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -1,4 +1,4 @@
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.system;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.system;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.system;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.controller.res;
|
||||
package org.dromara.neutrinoproxy.server.controller.res.system;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -1,8 +1,8 @@
|
||||
package fun.asgc.neutrino.proxy.server.dal;
|
||||
package org.dromara.neutrinoproxy.server.dal;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.ClientConnectRecordDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.ClientConnectRecordDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Date;
|
||||
@@ -19,11 +19,11 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.dal;
|
||||
package org.dromara.neutrinoproxy.server.dal;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.FlowReportDayDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportDayDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Date;
|
||||
@@ -33,7 +33,7 @@ import java.util.List;
|
||||
public interface FlowReportDayMapper extends BaseMapper<FlowReportDayDO> {
|
||||
default void clean(Date date) {
|
||||
this.delete(new LambdaQueryWrapper<FlowReportDayDO>()
|
||||
.lt(FlowReportDayDO::getCreateTime, date)
|
||||
.lt(FlowReportDayDO::getDate, date)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -45,8 +45,8 @@ public interface FlowReportDayMapper extends BaseMapper<FlowReportDayDO> {
|
||||
|
||||
default List<FlowReportDayDO> findListByDateRange(Date startDate, Date endDate) {
|
||||
return this.selectList(new LambdaQueryWrapper<FlowReportDayDO>()
|
||||
.le(FlowReportDayDO::getDate, startDate)
|
||||
.gt(FlowReportDayDO::getDate, endDate)
|
||||
.ge(FlowReportDayDO::getDate, startDate)
|
||||
.le(FlowReportDayDO::getDate, endDate)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -19,11 +19,11 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.dal;
|
||||
package org.dromara.neutrinoproxy.server.dal;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.FlowReportHourDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportHourDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Date;
|
||||
@@ -33,7 +33,7 @@ import java.util.List;
|
||||
public interface FlowReportHourMapper extends BaseMapper<FlowReportHourDO> {
|
||||
default void clean(Date date) {
|
||||
this.delete(new LambdaQueryWrapper<FlowReportHourDO>()
|
||||
.lt(FlowReportHourDO::getCreateTime, date)
|
||||
.lt(FlowReportHourDO::getDate, date)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.dal;
|
||||
package org.dromara.neutrinoproxy.server.dal;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.FlowReportMinuteDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportMinuteDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Date;
|
||||
@@ -34,7 +34,7 @@ import java.util.Set;
|
||||
public interface FlowReportMinuteMapper extends BaseMapper<FlowReportMinuteDO> {
|
||||
default void clean(Date date) {
|
||||
this.delete(new LambdaQueryWrapper<FlowReportMinuteDO>()
|
||||
.lt(FlowReportMinuteDO::getCreateTime, date)
|
||||
.lt(FlowReportMinuteDO::getDate, date)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.dal;
|
||||
package org.dromara.neutrinoproxy.server.dal;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.FlowReportMonthDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportMonthDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
@@ -19,12 +19,12 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.dal;
|
||||
package org.dromara.neutrinoproxy.server.dal;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.JobInfoDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.JobInfoDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Date;
|
||||
@@ -19,11 +19,11 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.dal;
|
||||
package org.dromara.neutrinoproxy.server.dal;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.JobLogDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.JobLogDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Date;
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
* <p>
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
* <p>
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
* <p>
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package org.dromara.neutrinoproxy.server.dal;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.LicenseDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/8/6
|
||||
*/
|
||||
@Mapper
|
||||
public interface LicenseMapper extends BaseMapper<LicenseDO> {
|
||||
|
||||
default List<LicenseDO> listAll() {
|
||||
return this.selectList(new LambdaQueryWrapper<>());
|
||||
}
|
||||
|
||||
default List<LicenseDO> listByUserId(Integer userId) {
|
||||
return this.selectList(new LambdaQueryWrapper<LicenseDO>()
|
||||
.eq(LicenseDO::getUserId, userId)
|
||||
);
|
||||
}
|
||||
|
||||
default LicenseDO queryById(Integer licenseId) {
|
||||
return this.selectOne(new LambdaQueryWrapper<LicenseDO>()
|
||||
.eq(LicenseDO::getId, licenseId));
|
||||
}
|
||||
|
||||
default void updateEnableStatus(Integer id, Integer enable, Date updateTime) {
|
||||
this.update(null, new LambdaUpdateWrapper<LicenseDO>()
|
||||
.eq(LicenseDO::getId, id)
|
||||
.set(LicenseDO::getEnable, enable)
|
||||
.set(LicenseDO::getUpdateTime, updateTime)
|
||||
);
|
||||
}
|
||||
|
||||
default void updateOnlineStatus(Integer id, Integer isOnline, Date updateTime) {
|
||||
this.update(null, new LambdaUpdateWrapper<LicenseDO>()
|
||||
.eq(LicenseDO::getId, id)
|
||||
.set(LicenseDO::getIsOnline, isOnline)
|
||||
.set(LicenseDO::getUpdateTime, updateTime)
|
||||
);
|
||||
}
|
||||
|
||||
default void updateOnlineStatus(Integer isOnline, Date updateTime) {
|
||||
this.update(null, new LambdaUpdateWrapper<LicenseDO>()
|
||||
.set(LicenseDO::getIsOnline, isOnline)
|
||||
.set(LicenseDO::getUpdateTime, updateTime)
|
||||
);
|
||||
}
|
||||
|
||||
default void reset(Integer id, String key, Date updateTime) {
|
||||
this.update(null, new LambdaUpdateWrapper<LicenseDO>()
|
||||
.eq(LicenseDO::getId, id)
|
||||
.set(LicenseDO::getKey, key)
|
||||
.set(LicenseDO::getUpdateTime, updateTime)
|
||||
);
|
||||
}
|
||||
|
||||
default LicenseDO findById(Integer id) {
|
||||
return this.selectById(id);
|
||||
}
|
||||
|
||||
default void update(Integer id, String name, Date updateTime) {
|
||||
this.update(null, new LambdaUpdateWrapper<LicenseDO>()
|
||||
.eq(LicenseDO::getId, id)
|
||||
.set(LicenseDO::getName, name)
|
||||
.set(LicenseDO::getUpdateTime, updateTime)
|
||||
);
|
||||
}
|
||||
|
||||
default List<LicenseDO> findByIds(Set<Integer> ids) {
|
||||
return selectBatchIds(ids);
|
||||
}
|
||||
|
||||
default LicenseDO checkRepeat(Integer userId, String name) {
|
||||
return this.selectOne(new LambdaQueryWrapper<LicenseDO>()
|
||||
.eq(LicenseDO::getUserId, userId)
|
||||
.eq(LicenseDO::getName, name)
|
||||
.last("limit 1")
|
||||
);
|
||||
}
|
||||
|
||||
default LicenseDO checkRepeat(Integer userId, String name, Set<Integer> excludeIds) {
|
||||
return this.selectOne(new LambdaQueryWrapper<LicenseDO>()
|
||||
.eq(LicenseDO::getUserId, userId)
|
||||
.eq(LicenseDO::getName, name)
|
||||
.notIn(LicenseDO::getId, excludeIds)
|
||||
.last("limit 1")
|
||||
);
|
||||
}
|
||||
|
||||
default LicenseDO findByKey(String licenseKey) {
|
||||
return selectOne(new LambdaQueryWrapper<LicenseDO>()
|
||||
.eq(LicenseDO::getKey, licenseKey)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.dromara.neutrinoproxy.server.dal;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.PortGroupListReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.PortGroupListRes;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.PortGroupDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Mapper
|
||||
public interface PortGroupMapper extends BaseMapper<PortGroupDO> {
|
||||
|
||||
List<PortGroupListRes> selectPortGroupListResList(IPage<PortGroupListRes> page, PortGroupListReq res);
|
||||
|
||||
|
||||
default void updateEnableStatus(Integer id, Integer enable, Date now){
|
||||
this.update(null, Wrappers.lambdaUpdate(PortGroupDO.class)
|
||||
.eq(PortGroupDO::getId,id)
|
||||
.set(PortGroupDO::getEnable,enable)
|
||||
.set(PortGroupDO::getUpdateTime,now)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
package fun.asgc.neutrino.proxy.server.dal;
|
||||
package org.dromara.neutrinoproxy.server.dal;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.PortMappingDO;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.proxy.PortMappingListReq;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.PortMappingDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@@ -40,6 +44,18 @@ public interface PortMappingMapper extends BaseMapper<PortMappingDO> {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验子域名是否重复
|
||||
* @param subdomain
|
||||
* @return
|
||||
*/
|
||||
default Boolean checkRepeatBySubdomain(String subdomain, Set<Integer> excludeIds) {
|
||||
return StringUtils.isEmpty(subdomain)? Boolean.FALSE : this.selectCount(new LambdaQueryWrapper<PortMappingDO>()
|
||||
.eq(PortMappingDO::getSubdomain, subdomain)
|
||||
.notIn(!CollectionUtil.isEmpty(excludeIds), PortMappingDO::getId, excludeIds)
|
||||
).intValue() > 0;
|
||||
}
|
||||
|
||||
default List<PortMappingDO> findEnableListByLicenseId(Integer licenseId) {
|
||||
return this.selectList(new LambdaQueryWrapper<PortMappingDO>()
|
||||
.eq(PortMappingDO::getLicenseId, licenseId)
|
||||
@@ -59,6 +75,14 @@ public interface PortMappingMapper extends BaseMapper<PortMappingDO> {
|
||||
);
|
||||
}
|
||||
|
||||
default PortMappingDO findByLicenseIdAndServerPort(Integer licenseId, Integer serverPort) {
|
||||
return this.selectOne(new LambdaQueryWrapper<PortMappingDO>()
|
||||
.eq(PortMappingDO::getLicenseId, licenseId)
|
||||
.eq(PortMappingDO::getServerPort, serverPort)
|
||||
.last("limit 1")
|
||||
);
|
||||
}
|
||||
|
||||
default void updateOnlineStatus(Integer licenseId,Integer serverPort, Integer isOnline, Date updateTime) {
|
||||
this.update(null, new LambdaUpdateWrapper<PortMappingDO>()
|
||||
.eq(PortMappingDO::getLicenseId, licenseId)
|
||||
@@ -82,4 +106,6 @@ public interface PortMappingMapper extends BaseMapper<PortMappingDO> {
|
||||
.set(PortMappingDO::getUpdateTime, updateTime)
|
||||
);
|
||||
}
|
||||
|
||||
List<PortMappingDO> selectPortMappingByCondition(IPage<PortMappingDO> page, @Param("req") PortMappingListReq req);
|
||||
}
|
||||
@@ -19,15 +19,20 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.dal;
|
||||
package org.dromara.neutrinoproxy.server.dal;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.PortPoolDO;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.PortPoolListReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.PortPoolListRes;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.PortPoolDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -53,4 +58,15 @@ public interface PortPoolMapper extends BaseMapper<PortPoolDO> {
|
||||
default PortPoolDO findById(Integer id) {
|
||||
return this.selectById(id);
|
||||
}
|
||||
|
||||
default List<PortPoolDO> getByGroupId(String groupId){
|
||||
return this.selectList(
|
||||
new LambdaQueryWrapper<PortPoolDO>()
|
||||
.eq(PortPoolDO::getGroupId, groupId)
|
||||
);
|
||||
}
|
||||
|
||||
List<PortPoolListRes> selectResList(IPage<PortPoolListRes> page, @Param("req") PortPoolListReq req);
|
||||
|
||||
List<PortPoolListRes> getAvailablePortList(IPage<PortPoolListRes> page, @Param("licenseId") Integer licenseId,@Param("userId") Integer userId, @Param("keyword") String keyword);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package org.dromara.neutrinoproxy.server.dal;
|
||||
|
||||
import com.baomidou.mybatisplus.solon.plugins.pagination.Page;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.report.*;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.report.LicenseFlowMonthReportRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.report.LicenseFlowReportRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.report.UserFlowMonthReportRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.report.UserFlowReportRes;
|
||||
import org.dromara.neutrinoproxy.server.service.bo.FlowBO;
|
||||
import org.dromara.neutrinoproxy.server.service.bo.SingleDayFlowBO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/3/19
|
||||
*/
|
||||
@Mapper
|
||||
public interface ReportMapper {
|
||||
/**
|
||||
* 基于用户维度的流量报表
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
List<UserFlowReportRes> userFlowReportList(Page<UserFlowReportRes> page, @Param("userId") Integer userId, @Param("curMonthBeginDate") Date curMonthBeginDate, @Param("curDayBeginDate") Date curDayBeginDate, @Param("curDate") Date curDate);
|
||||
/**
|
||||
* 基于License维度的流量报表
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
List<LicenseFlowReportRes> licenseFLowReportList(Page<LicenseFlowReportRes> page, @Param("userId") Integer userId, @Param("curMonthBeginDate") Date curMonthBeginDate, @Param("curDayBeginDate") Date curDayBeginDate, @Param("curDate") Date curDate);
|
||||
|
||||
/**
|
||||
* 用户流量月度明细
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
List<UserFlowMonthReportRes> userFlowMonthReportList(Page<UserFlowMonthReportRes> page, @Param("userId") Integer userId, @Param("curMonthBeginDate") Date curMonthBeginDate, @Param("curDayBeginDate") Date curDayBeginDate, @Param("curDate") Date curDate);
|
||||
/**
|
||||
* License流量月度明细
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
List<LicenseFlowMonthReportRes> licenseFLowMonthReportList(Page<LicenseFlowMonthReportRes> page, @Param("userId") Integer userId, @Param("licenseId") Integer licenseId, @Param("curMonthBeginDate") Date curMonthBeginDate, @Param("curDayBeginDate") Date curDayBeginDate, @Param("curDate") Date curDate);
|
||||
|
||||
/**
|
||||
* 首页 - 今日流量
|
||||
* @param curDayBeginDate
|
||||
* @param curDate
|
||||
* @return
|
||||
*/
|
||||
FlowBO homeTodayFlow(@Param("curDayBeginDate") Date curDayBeginDate, @Param("curDate") Date curDate);
|
||||
|
||||
/**
|
||||
* 首页 - 总流量
|
||||
* @param curMonthBeginDate
|
||||
* @param curDayBeginDate
|
||||
* @param curDate
|
||||
* @return
|
||||
*/
|
||||
FlowBO homeTotalFlow(@Param("curMonthBeginDate") Date curMonthBeginDate, @Param("curDayBeginDate") Date curDayBeginDate, @Param("curDate") Date curDate);
|
||||
|
||||
/**
|
||||
* 首页最近7日流量
|
||||
* @param beginDate
|
||||
* @param curDayBeginDate
|
||||
* @param curDate
|
||||
* @return
|
||||
*/
|
||||
List<SingleDayFlowBO> homeLast7dFlowList(@Param("beginDate") Date beginDate, @Param("curDayBeginDate") Date curDayBeginDate, @Param("curDate") Date curDate);
|
||||
}
|
||||
@@ -19,11 +19,11 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.dal;
|
||||
package org.dromara.neutrinoproxy.server.dal;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.UserLoginRecordDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.UserLoginRecordDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Date;
|
||||
@@ -1,9 +1,9 @@
|
||||
package fun.asgc.neutrino.proxy.server.dal;
|
||||
package org.dromara.neutrinoproxy.server.dal;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
|
||||
import org.apache.ibatis.annotations.Insert;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@@ -19,12 +19,12 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.dal;
|
||||
package org.dromara.neutrinoproxy.server.dal;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.UserTokenDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.UserTokenDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Date;
|
||||
@@ -1,4 +1,4 @@
|
||||
package fun.asgc.neutrino.proxy.server.dal.entity;
|
||||
package org.dromara.neutrinoproxy.server.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.log.ClientConnectRecordListRes;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@@ -34,4 +35,17 @@ public class ClientConnectRecordDO {
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
public ClientConnectRecordListRes toRes() {
|
||||
ClientConnectRecordListRes res = new ClientConnectRecordListRes();
|
||||
res.setId(id);
|
||||
res.setIp(ip);
|
||||
res.setLicenseId(licenseId);
|
||||
res.setType(type);
|
||||
res.setMsg(msg);
|
||||
res.setCode(code);
|
||||
res.setErr(err);
|
||||
res.setCreateTime(createTime);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.dal.entity;
|
||||
package org.dromara.neutrinoproxy.server.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.dal.entity;
|
||||
package org.dromara.neutrinoproxy.server.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.dal.entity;
|
||||
package org.dromara.neutrinoproxy.server.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.dal.entity;
|
||||
package org.dromara.neutrinoproxy.server.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.dal.entity;
|
||||
package org.dromara.neutrinoproxy.server.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
@@ -28,6 +28,8 @@ import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.JobInfoListRes;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@@ -52,7 +54,7 @@ public class JobInfoDO {
|
||||
private String param;
|
||||
/**
|
||||
* 启用状态
|
||||
* {@link fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum}
|
||||
* {@link EnableStatusEnum}
|
||||
*/
|
||||
private Integer enable;
|
||||
/**
|
||||
@@ -63,4 +65,19 @@ public class JobInfoDO {
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
|
||||
public JobInfoListRes toRes() {
|
||||
JobInfoListRes res = new JobInfoListRes();
|
||||
res.setId(id);
|
||||
res.setCron(cron);
|
||||
res.setDesc(desc);
|
||||
res.setAlarmEmail(alarmEmail);
|
||||
res.setAlarmDing(alarmDing);
|
||||
res.setHandler(handler);
|
||||
res.setParam(param);
|
||||
res.setEnable(enable);
|
||||
res.setCreateTime(createTime);
|
||||
res.setUpdateTime(updateTime);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.dal.entity;
|
||||
package org.dromara.neutrinoproxy.server.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
@@ -27,6 +27,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.log.JobLogListRes;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@@ -52,4 +53,17 @@ public class JobLogDO {
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
public JobLogListRes toRes() {
|
||||
JobLogListRes res = new JobLogListRes();
|
||||
res.setId(id);
|
||||
res.setJobId(jobId);
|
||||
res.setHandler(handler);
|
||||
res.setParam(param);
|
||||
res.setCode(code);
|
||||
res.setMsg(msg);
|
||||
res.setAlarmStatus(alarmStatus);
|
||||
res.setCreateTime(createTime);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -19,16 +19,18 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.dal.entity;
|
||||
package org.dromara.neutrinoproxy.server.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import fun.asgc.neutrino.proxy.server.constant.OnlineStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.proxy.LicenseListRes;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@@ -64,7 +66,7 @@ public class LicenseDO {
|
||||
private Integer isOnline;
|
||||
/**
|
||||
* 启用状态
|
||||
* {@link fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum}
|
||||
* {@link EnableStatusEnum}
|
||||
*/
|
||||
private Integer enable;
|
||||
/**
|
||||
@@ -75,4 +77,17 @@ public class LicenseDO {
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
|
||||
public LicenseListRes toRes() {
|
||||
LicenseListRes res = new LicenseListRes();
|
||||
res.setId(id);
|
||||
res.setName(name);
|
||||
res.setKey(key);
|
||||
res.setUserId(userId);
|
||||
res.setIsOnline(isOnline);
|
||||
res.setEnable(enable);
|
||||
res.setCreateTime(createTime);
|
||||
res.setUpdateTime(updateTime);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.dromara.neutrinoproxy.server.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 端口组
|
||||
*/
|
||||
@ToString
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
@TableName("port_group")
|
||||
public class PortGroupDO {
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* 分组名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 所有者类型 (0、全局共享 1、用户所有 2License所有)
|
||||
*/
|
||||
private Integer possessorType;
|
||||
|
||||
/**
|
||||
* 所有者id(当type为0时 固定为-1、当type为1时为用户id 、当type为2时为licenseid)
|
||||
*/
|
||||
private Integer possessorId;
|
||||
|
||||
/**
|
||||
* 是否启用(1、启用 2、禁用)
|
||||
*/
|
||||
private Integer enable;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
|
||||
}
|
||||
@@ -19,15 +19,17 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.dal.entity;
|
||||
package org.dromara.neutrinoproxy.server.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import fun.asgc.neutrino.proxy.server.constant.OnlineStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.proxy.PortMappingListRes;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@@ -47,6 +49,14 @@ public class PortMappingDO {
|
||||
* licenseId
|
||||
*/
|
||||
private Integer licenseId;
|
||||
/**
|
||||
* 协议
|
||||
*/
|
||||
private String protocal;
|
||||
/**
|
||||
* 子域名
|
||||
*/
|
||||
private String subdomain;
|
||||
/**
|
||||
* 服务端端口
|
||||
*/
|
||||
@@ -59,14 +69,27 @@ public class PortMappingDO {
|
||||
* 客户端端口
|
||||
*/
|
||||
private Integer clientPort;
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 是否在线
|
||||
* {@link OnlineStatusEnum}
|
||||
*/
|
||||
private Integer isOnline;
|
||||
/**
|
||||
* 代理响应数量(响应数据包数量,如果没有拆包则等于数据条数)
|
||||
*/
|
||||
private Integer proxyResponses;
|
||||
/**
|
||||
* 代理超时时间
|
||||
*/
|
||||
private Long proxyTimeoutMs;
|
||||
/**
|
||||
* 启用状态
|
||||
* {@link fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum}
|
||||
* {@link EnableStatusEnum}
|
||||
*/
|
||||
private Integer enable;
|
||||
/**
|
||||
@@ -77,4 +100,24 @@ public class PortMappingDO {
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
|
||||
|
||||
public PortMappingListRes toRes() {
|
||||
PortMappingListRes res = new PortMappingListRes();
|
||||
res.setId(id);
|
||||
res.setLicenseId(licenseId);
|
||||
res.setProtocal(protocal);
|
||||
res.setSubdomain(subdomain);
|
||||
res.setServerPort(serverPort);
|
||||
res.setClientIp(clientIp);
|
||||
res.setClientPort(clientPort);
|
||||
res.setDescription(description);
|
||||
res.setIsOnline(isOnline);
|
||||
res.setProxyResponses(proxyResponses);
|
||||
res.setProxyTimeoutMs(proxyTimeoutMs);
|
||||
res.setEnable(enable);
|
||||
res.setCreateTime(createTime);
|
||||
res.setUpdateTime(updateTime);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.dal.entity;
|
||||
package org.dromara.neutrinoproxy.server.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
@@ -27,6 +27,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.PortPoolListRes;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@@ -42,6 +43,12 @@ import java.util.Date;
|
||||
public class PortPoolDO {
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* 分组id
|
||||
*/
|
||||
private Integer groupId;
|
||||
|
||||
/**
|
||||
* 端口
|
||||
*/
|
||||
@@ -58,4 +65,15 @@ public class PortPoolDO {
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
|
||||
public PortPoolListRes toRes() {
|
||||
PortPoolListRes res = new PortPoolListRes();
|
||||
res.setId(id);
|
||||
res.setGroupId(groupId);
|
||||
res.setPort(port);
|
||||
res.setEnable(enable);
|
||||
res.setCreateTime(createTime);
|
||||
res.setUpdateTime(updateTime);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -19,13 +19,14 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.dal.entity;
|
||||
package org.dromara.neutrinoproxy.server.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.UserListRes;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@@ -36,7 +37,7 @@ import java.util.Date;
|
||||
*/
|
||||
@ToString
|
||||
@Data
|
||||
@TableName("user")
|
||||
@TableName("`user`")
|
||||
public class UserDO {
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Integer id;
|
||||
@@ -64,4 +65,15 @@ public class UserDO {
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
|
||||
public UserListRes toRes() {
|
||||
UserListRes res = new UserListRes();
|
||||
res.setId(id);
|
||||
res.setName(name);
|
||||
res.setLoginName(loginName);
|
||||
res.setEnable(enable);
|
||||
res.setCreateTime(createTime);
|
||||
res.setUpdateTime(updateTime);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
* in accordance with the terms of the contract agreement
|
||||
* you entered into with Zeyi inc.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.dal.entity;
|
||||
package org.dromara.neutrinoproxy.server.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
@@ -18,6 +18,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.log.UserLoginRecordListRes;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@@ -62,4 +63,14 @@ public class UserLoginRecordDO {
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
public UserLoginRecordListRes toRes() {
|
||||
UserLoginRecordListRes res = new UserLoginRecordListRes();
|
||||
res.setId(id);
|
||||
res.setUserId(userId);
|
||||
res.setIp(ip);
|
||||
res.setType(type);
|
||||
res.setCreateTime(createTime);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.dal.entity;
|
||||
package org.dromara.neutrinoproxy.server.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
@@ -1,14 +1,19 @@
|
||||
package fun.asgc.neutrino.proxy.server.job;
|
||||
package org.dromara.neutrinoproxy.server.job;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import fun.asgc.neutrino.proxy.core.util.DateUtil;
|
||||
import fun.asgc.neutrino.proxy.server.dal.*;
|
||||
import fun.asgc.solon.extend.job.IJobHandler;
|
||||
import fun.asgc.solon.extend.job.annotation.JobHandler;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dromara.neutrinoproxy.core.util.DateUtil;
|
||||
import org.dromara.neutrinoproxy.server.dal.ClientConnectRecordMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.FlowReportDayMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.FlowReportHourMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.FlowReportMinuteMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.JobLogMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.UserLoginRecordMapper;
|
||||
import org.dromara.solonplugins.job.IJobHandler;
|
||||
import org.dromara.solonplugins.job.annotation.JobHandler;
|
||||
import org.noear.snack.ONode;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
|
||||
@@ -70,37 +75,37 @@ public class DataCleanJob implements IJobHandler {
|
||||
|
||||
{
|
||||
Date date = DateUtil.addDate(new Date(), Calendar.DATE, -1 * jobParams.getJobLogKeepDays());
|
||||
log.info("清理调度管理日志 date:{}", sdf.format(date));
|
||||
log.info("clean job log date:{}", sdf.format(date));
|
||||
jobLogMapper.clean(date);
|
||||
}
|
||||
|
||||
{
|
||||
Date date = DateUtil.addDate(new Date(), Calendar.DATE, -1 * jobParams.getUserLoginRecordKeepDays());
|
||||
log.info("清理用户登录日志 date:{}", sdf.format(date));
|
||||
log.info("clean user login record date:{}", sdf.format(date));
|
||||
userLoginRecordMapper.clean(date);
|
||||
}
|
||||
|
||||
{
|
||||
Date date = DateUtil.addDate(new Date(), Calendar.DATE, -1 * jobParams.getClientConnectRecordKeepDays());
|
||||
log.info("清理客户端连接日志 date:{}", sdf.format(date));
|
||||
log.info("clean client connect record date:{}", sdf.format(date));
|
||||
clientConnectRecordMapper.clean(date);
|
||||
}
|
||||
|
||||
{
|
||||
Date date = DateUtil.addDate(new Date(), Calendar.DATE, -1 * jobParams.getFlowMinuteReportKeepDays());
|
||||
log.info("清理流通统计分钟报表日志 date:{}", sdf.format(date));
|
||||
log.info("clean flow report minute record date:{}", sdf.format(date));
|
||||
flowReportMinuteMapper.clean(date);
|
||||
}
|
||||
|
||||
{
|
||||
Date date = DateUtil.addDate(new Date(), Calendar.DATE, -1 * jobParams.getFlowHourReportKeepDays());
|
||||
log.info("清理流通统计小时报表日志 date:{}", sdf.format(date));
|
||||
log.info("clean flow report hour record date:{}", sdf.format(date));
|
||||
flowReportHourMapper.clean(date);
|
||||
}
|
||||
|
||||
{
|
||||
Date date = DateUtil.addDate(new Date(), Calendar.DATE, -1 * jobParams.getFlowDayReportKeepDays());
|
||||
log.info("清理流通统计日报表日志 date:{}", sdf.format(date));
|
||||
log.info("clean flow report day record date:{}", sdf.format(date));
|
||||
flowReportDayMapper.clean(date);
|
||||
}
|
||||
}
|
||||
@@ -108,7 +113,7 @@ public class DataCleanJob implements IJobHandler {
|
||||
public static JobParams getParams(String s) {
|
||||
try {
|
||||
if (StringUtils.isNotBlank(s)) {
|
||||
return JSONObject.parseObject(s, JobParams.class);
|
||||
return ONode.deserialize(s, JobParams.class);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
@@ -1,8 +1,8 @@
|
||||
package fun.asgc.neutrino.proxy.server.job;
|
||||
package org.dromara.neutrinoproxy.server.job;
|
||||
|
||||
import fun.asgc.solon.extend.job.IJobHandler;
|
||||
import fun.asgc.solon.extend.job.annotation.JobHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.solonplugins.job.IJobHandler;
|
||||
import org.dromara.solonplugins.job.annotation.JobHandler;
|
||||
import org.noear.solon.annotation.Component;
|
||||
|
||||
/**
|
||||
@@ -17,6 +17,6 @@ public class DemoJob implements IJobHandler {
|
||||
|
||||
@Override
|
||||
public void execute(String param) throws Exception {
|
||||
System.out.println("DemoJob execute param:" + param);
|
||||
log.debug("DemoJob execute param: {}", param);
|
||||
}
|
||||
}
|
||||
@@ -19,20 +19,21 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.job;
|
||||
package org.dromara.neutrinoproxy.server.job;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import fun.asgc.neutrino.proxy.core.util.DateUtil;
|
||||
import fun.asgc.neutrino.proxy.server.dal.FlowReportDayMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.FlowReportHourMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.FlowReportMinuteMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.LicenseMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.FlowReportDayDO;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.FlowReportHourDO;
|
||||
import fun.asgc.neutrino.proxy.server.service.FlowReportService;
|
||||
import fun.asgc.solon.extend.job.IJobHandler;
|
||||
import fun.asgc.solon.extend.job.annotation.JobHandler;
|
||||
import org.dromara.neutrinoproxy.core.util.DateUtil;
|
||||
import org.dromara.neutrinoproxy.server.dal.FlowReportDayMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.FlowReportHourMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.FlowReportMinuteMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.LicenseMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportDayDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportHourDO;
|
||||
import org.dromara.neutrinoproxy.server.service.FlowReportService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dromara.solonplugins.job.IJobHandler;
|
||||
import org.dromara.solonplugins.job.annotation.JobHandler;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
|
||||
@@ -60,7 +61,7 @@ public class FlowReportForDayJob implements IJobHandler {
|
||||
@Override
|
||||
public void execute(String param) throws Exception {
|
||||
Date now = new Date();
|
||||
String dateStr = DateUtil.format(DateUtil.addDate(now, Calendar.DATE, -1), "yyyy-MM-dd");
|
||||
String dateStr = getDateStr(now, param); // DateUtil.format(DateUtil.addDate(now, Calendar.DATE, -1), "yyyy-MM-dd");
|
||||
Date date = DateUtil.parse(dateStr, "yyyy-MM-dd");
|
||||
Date startHourDate = DateUtil.getDayBegin(date);
|
||||
Date endHourDate = DateUtil.getDayEnd(date);
|
||||
@@ -99,4 +100,16 @@ public class FlowReportForDayJob implements IJobHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private String getDateStr(Date now, String params) {
|
||||
if (StringUtils.isNotBlank(params)) {
|
||||
try {
|
||||
// 参数格式错误则取当前时间
|
||||
DateUtil.parse(params, "yyyy-MM-dd");
|
||||
return params;
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return DateUtil.format(DateUtil.addDate(now, Calendar.DATE, -1), "yyyy-MM-dd");
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,17 @@
|
||||
package fun.asgc.neutrino.proxy.server.job;
|
||||
package org.dromara.neutrinoproxy.server.job;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import fun.asgc.neutrino.proxy.core.util.DateUtil;
|
||||
import fun.asgc.neutrino.proxy.server.dal.FlowReportHourMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.FlowReportMinuteMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.LicenseMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.FlowReportHourDO;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.FlowReportMinuteDO;
|
||||
import fun.asgc.neutrino.proxy.server.service.FlowReportService;
|
||||
import fun.asgc.solon.extend.job.IJobHandler;
|
||||
import fun.asgc.solon.extend.job.annotation.JobHandler;
|
||||
import org.dromara.neutrinoproxy.core.util.DateUtil;
|
||||
import org.dromara.neutrinoproxy.server.dal.FlowReportHourMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.FlowReportMinuteMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.LicenseMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportHourDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportMinuteDO;
|
||||
import org.dromara.neutrinoproxy.server.service.FlowReportService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dromara.solonplugins.job.IJobHandler;
|
||||
import org.dromara.solonplugins.job.annotation.JobHandler;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
|
||||
@@ -36,7 +37,7 @@ public class FlowReportForHourJob implements IJobHandler {
|
||||
@Override
|
||||
public void execute(String param) throws Exception {
|
||||
Date now = new Date();
|
||||
String dateStr = DateUtil.format(DateUtil.addDate(now, Calendar.HOUR, -1), "yyyy-MM-dd HH");
|
||||
String dateStr = getDateStr(now, param); // DateUtil.format(DateUtil.addDate(now, Calendar.HOUR, -1), "yyyy-MM-dd HH");
|
||||
Date date = DateUtil.parse(dateStr, "yyyy-MM-dd HH");
|
||||
Date startHourDate = DateUtil.getHourBegin(date);
|
||||
Date endHourDate = DateUtil.getHourEnd(date);
|
||||
@@ -75,4 +76,17 @@ public class FlowReportForHourJob implements IJobHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private String getDateStr(Date now, String params) {
|
||||
if (StringUtils.isNotBlank(params)) {
|
||||
try {
|
||||
// 参数格式错误则取当前时间
|
||||
DateUtil.parse(params, "yyyy-MM-dd HH");
|
||||
return params;
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return DateUtil.format(DateUtil.addDate(now, Calendar.HOUR, -1), "yyyy-MM-dd HH");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
package fun.asgc.neutrino.proxy.server.job;
|
||||
package org.dromara.neutrinoproxy.server.job;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import fun.asgc.neutrino.proxy.core.util.DateUtil;
|
||||
import fun.asgc.neutrino.proxy.server.dal.FlowReportMinuteMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.LicenseMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.FlowReportMinuteDO;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.LicenseDO;
|
||||
import fun.asgc.neutrino.proxy.server.service.FlowReportService;
|
||||
import fun.asgc.solon.extend.job.IJobHandler;
|
||||
import fun.asgc.solon.extend.job.annotation.JobHandler;
|
||||
import org.dromara.neutrinoproxy.core.util.DateUtil;
|
||||
import org.dromara.neutrinoproxy.server.dal.FlowReportMinuteMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.LicenseMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportMinuteDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.LicenseDO;
|
||||
import org.dromara.neutrinoproxy.server.service.FlowReportService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.solonplugins.job.IJobHandler;
|
||||
import org.dromara.solonplugins.job.annotation.JobHandler;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
package fun.asgc.neutrino.proxy.server.job;
|
||||
package org.dromara.neutrinoproxy.server.job;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import fun.asgc.neutrino.proxy.core.util.DateUtil;
|
||||
import fun.asgc.neutrino.proxy.server.dal.*;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.FlowReportDayDO;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.FlowReportMonthDO;
|
||||
import fun.asgc.neutrino.proxy.server.service.FlowReportService;
|
||||
import fun.asgc.solon.extend.job.IJobHandler;
|
||||
import fun.asgc.solon.extend.job.annotation.JobHandler;
|
||||
import org.dromara.neutrinoproxy.core.util.DateUtil;
|
||||
import org.dromara.neutrinoproxy.server.dal.*;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportDayDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.FlowReportMonthDO;
|
||||
import org.dromara.neutrinoproxy.server.service.FlowReportService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dromara.solonplugins.job.IJobHandler;
|
||||
import org.dromara.solonplugins.job.annotation.JobHandler;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
|
||||
@@ -38,7 +39,7 @@ public class FlowReportForMonthJob implements IJobHandler {
|
||||
@Override
|
||||
public void execute(String param) throws Exception {
|
||||
Date now = new Date();
|
||||
String dateStr = DateUtil.format(DateUtil.addDate(now, Calendar.MONTH, -1), "yyyy-MM");
|
||||
String dateStr = getDateStr(now, param); // DateUtil.format(DateUtil.addDate(now, Calendar.MONTH, -1), "yyyy-MM");
|
||||
Date date = DateUtil.parse(dateStr, "yyyy-MM");
|
||||
Date startDayDate = DateUtil.getMonthBegin(date);
|
||||
Date endEndDate = DateUtil.getMonthEnd(date);
|
||||
@@ -77,4 +78,16 @@ public class FlowReportForMonthJob implements IJobHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private String getDateStr(Date now, String params) {
|
||||
if (StringUtils.isNotBlank(params)) {
|
||||
try {
|
||||
// 参数格式错误则取当前时间
|
||||
DateUtil.parse(params, "yyyy-MM");
|
||||
return params;
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return DateUtil.format(DateUtil.addDate(now, Calendar.MONTH, -1), "yyyy-MM");
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,9 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package fun.asgc.neutrino.proxy.server.proxy.core;
|
||||
package org.dromara.neutrinoproxy.server.proxy.core;
|
||||
|
||||
import fun.asgc.neutrino.proxy.server.proxy.domain.MetricsCollector;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.MetricsCollector;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* Copyright (c) 2022 aoshiguchen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package org.dromara.neutrinoproxy.server.proxy.core;
|
||||
|
||||
import org.dromara.neutrinoproxy.core.Constants;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
import org.dromara.neutrinoproxy.core.dispatcher.Dispatcher;
|
||||
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
|
||||
import org.dromara.neutrinoproxy.server.constant.ClientConnectTypeEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.SuccessCodeEnum;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.ClientConnectRecordDO;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.CmdChannelAttachInfo;
|
||||
import org.dromara.neutrinoproxy.server.service.ClientConnectRecordService;
|
||||
import org.dromara.neutrinoproxy.server.service.ProxyMutualService;
|
||||
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.handler.timeout.IdleStateEvent;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.noear.solon.Solon;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
@Slf4j
|
||||
public class ProxyTunnelChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {
|
||||
private static volatile Dispatcher<ChannelHandlerContext, ProxyMessage> dispatcher;
|
||||
private static volatile Boolean transferLogEnable = Boolean.FALSE;
|
||||
|
||||
public ProxyTunnelChannelHandler() {
|
||||
dispatcher = Solon.context().getBean(Dispatcher.class);
|
||||
ProxyConfig proxyConfig = Solon.context().getBean(ProxyConfig.class);
|
||||
if (null != proxyConfig.getTunnel() && null != proxyConfig.getTunnel().getHeartbeatLogEnable()) {
|
||||
transferLogEnable = proxyConfig.getTunnel().getHeartbeatLogEnable();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception {
|
||||
if (ProxyMessage.TYPE_HEARTBEAT != proxyMessage.getType() || transferLogEnable) {
|
||||
log.debug("Server CmdChannel recieved proxy message, type is {}", proxyMessage.getType());
|
||||
}
|
||||
dispatcher.dispatch(ctx, proxyMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
|
||||
Channel visitorChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
|
||||
if (visitorChannel != null) {
|
||||
visitorChannel.config().setOption(ChannelOption.AUTO_READ, ctx.channel().isWritable());
|
||||
}
|
||||
|
||||
super.channelWritabilityChanged(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
Channel visitorChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
|
||||
if (null != visitorChannel) {
|
||||
Integer licenseId = ctx.channel().attr(Constants.LICENSE_ID).get();
|
||||
String visitorId = ctx.channel().attr(Constants.VISITOR_ID).get();
|
||||
Channel cmdChannel = ProxyUtil.getCmdChannelByLicenseId(licenseId);
|
||||
|
||||
if (null != cmdChannel) {
|
||||
ProxyUtil.removeVisitorChannelFromCmdChannel(cmdChannel, visitorId);
|
||||
}
|
||||
ProxyUtil.remoteProxyConnectAttachment(visitorId);
|
||||
|
||||
// 此处如果时UDP的 visitorChannel,则不能close,先临时判断一下
|
||||
Boolean isUdp = visitorChannel.attr(Constants.IS_UDP_KEY).get();
|
||||
if (visitorChannel.isActive() && null == isUdp) {
|
||||
// 数据发送完成后再关闭连接,解决http1.0数据传输问题
|
||||
visitorChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
|
||||
visitorChannel.close();
|
||||
}
|
||||
} else {
|
||||
CmdChannelAttachInfo cmdChannelAttachInfo = ProxyUtil.getAttachInfo(ctx.channel());
|
||||
if (null != cmdChannelAttachInfo) {
|
||||
Channel curCmdChannel = ProxyUtil.getCmdChannelByLicenseId(cmdChannelAttachInfo.getLicenseId());
|
||||
// 客户端切换网络后,连接断开,但服务端还未触发断开事件。此时客户端重连上了,然后服务端触发了断开,此时不应该更新在线状态
|
||||
if (curCmdChannel == ctx.channel()) {
|
||||
Solon.context().getBean(ProxyMutualService.class).offline(cmdChannelAttachInfo);
|
||||
ProxyUtil.removeCmdChannel(ctx.channel());
|
||||
// 防止下次换一个客户端,无法连接的情况
|
||||
ProxyUtil.removeClientIdByLicenseId(cmdChannelAttachInfo.getLicenseId());
|
||||
}
|
||||
// 即便是因为上述原因断开,断开的日志依然要记录,方便排查问题
|
||||
Solon.context().getBean(ClientConnectRecordService.class).add(new ClientConnectRecordDO()
|
||||
.setIp(((InetSocketAddress)ctx.channel().remoteAddress()).getAddress().getHostAddress())
|
||||
.setLicenseId(cmdChannelAttachInfo.getLicenseId())
|
||||
.setType(ClientConnectTypeEnum.DISCONNECT.getType())
|
||||
.setMsg("")
|
||||
.setCode(SuccessCodeEnum.SUCCESS.getCode())
|
||||
.setCreateTime(new Date())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
super.channelInactive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
// super.exceptionCaught(ctx, cause);
|
||||
// if (ctx.channel().isActive()) {
|
||||
// ctx.channel().close();
|
||||
// }
|
||||
log.error("[Tunnel Channel] error", cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||
if(evt instanceof IdleStateEvent) {
|
||||
IdleStateEvent event = (IdleStateEvent)evt;
|
||||
switch (event.state()) {
|
||||
case READER_IDLE:
|
||||
if (ctx.channel().isWritable()) {
|
||||
// 读超时,断开连接
|
||||
log.warn("[Tunnel Channel]Read timeout");
|
||||
ctx.channel().close();
|
||||
}
|
||||
break;
|
||||
case WRITER_IDLE:
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildHeartbeatMessage());
|
||||
break;
|
||||
case ALL_IDLE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
package fun.asgc.neutrino.proxy.server.proxy.core;
|
||||
package org.dromara.neutrinoproxy.server.proxy.core;
|
||||
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessageDecoder;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessageEncoder;
|
||||
import fun.asgc.neutrino.proxy.core.util.FileUtil;
|
||||
import fun.asgc.neutrino.proxy.server.base.proxy.ProxyConfig;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessageDecoder;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessageEncoder;
|
||||
import org.dromara.neutrinoproxy.core.util.FileUtil;
|
||||
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
@@ -17,6 +18,7 @@ import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
import org.noear.solon.core.event.AppLoadEndEvent;
|
||||
import org.noear.solon.core.event.EventListener;
|
||||
import org.noear.solon.core.runtime.NativeDetector;
|
||||
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
@@ -26,33 +28,26 @@ import java.io.InputStream;
|
||||
import java.security.KeyStore;
|
||||
|
||||
/**
|
||||
*
|
||||
* 代理隧道服务
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ProxyServerRunner implements EventListener<AppLoadEndEvent> {
|
||||
public class ProxyTunnelServer implements EventListener<AppLoadEndEvent> {
|
||||
@Inject
|
||||
private ProxyConfig proxyConfig;
|
||||
@Inject("serverBossGroup")
|
||||
@Inject("tunnelBossGroup")
|
||||
private NioEventLoopGroup serverBossGroup;
|
||||
@Inject("serverWorkerGroup")
|
||||
@Inject("tunnelWorkerGroup")
|
||||
private NioEventLoopGroup serverWorkerGroup;
|
||||
@Inject("${neutrino.proxy.server.port}")
|
||||
private Integer port;
|
||||
@Inject("${neutrino.proxy.server.ssl-port}")
|
||||
private Integer sslPort;
|
||||
@Inject("${neutrino.proxy.server.jks-path}")
|
||||
private String jksPath;
|
||||
@Inject("${neutrino.proxy.server.key-store-password}")
|
||||
private String keyStorePassword;
|
||||
@Inject("${neutrino.proxy.server.key-manager-password}")
|
||||
private String keyManagerPassword;
|
||||
@Override
|
||||
public void onEvent(AppLoadEndEvent appLoadEndEvent) throws Throwable {
|
||||
startProxyServer();
|
||||
startProxyServerForSSL();
|
||||
// aot 阶段,不启动代理服务
|
||||
if (NativeDetector.isNotAotRuntime()) {
|
||||
startProxyServer();
|
||||
startProxyServerForSSL();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 启动代理服务
|
||||
@@ -67,15 +62,15 @@ public class ProxyServerRunner implements EventListener<AppLoadEndEvent> {
|
||||
}
|
||||
});
|
||||
try {
|
||||
bootstrap.bind(port).sync();
|
||||
log.info("代理服务启动,端口:{}", port);
|
||||
bootstrap.bind(proxyConfig.getTunnel().getPort()).sync();
|
||||
log.info("proxy server started,port:{}", proxyConfig.getTunnel().getPort());
|
||||
} catch (Exception e) {
|
||||
log.error("代理服务异常", e);
|
||||
log.error("proxy server error", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void startProxyServerForSSL() {
|
||||
if (null == sslPort) {
|
||||
if (null == proxyConfig.getTunnel().getSslPort()) {
|
||||
return;
|
||||
}
|
||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||
@@ -88,22 +83,22 @@ public class ProxyServerRunner implements EventListener<AppLoadEndEvent> {
|
||||
}
|
||||
});
|
||||
try {
|
||||
bootstrap.bind(sslPort).sync();
|
||||
log.info("代理服务启动,SSL端口: {}", sslPort);
|
||||
bootstrap.bind(proxyConfig.getTunnel().getSslPort()).sync();
|
||||
log.info("proxy server started,SSL port: {}", proxyConfig.getTunnel().getSslPort());
|
||||
} catch (Exception e) {
|
||||
log.error("代理服务异常", e);
|
||||
log.error("proxy server error", e);
|
||||
}
|
||||
}
|
||||
|
||||
private ChannelHandler createSslHandler() {
|
||||
try {
|
||||
InputStream jksInputStream = FileUtil.getInputStream(jksPath);
|
||||
InputStream jksInputStream = FileUtil.getInputStream(proxyConfig.getTunnel().getJksPath());
|
||||
SSLContext serverContext = SSLContext.getInstance("TLS");
|
||||
final KeyStore ks = KeyStore.getInstance("JKS");
|
||||
|
||||
ks.load(jksInputStream, keyStorePassword.toCharArray());
|
||||
ks.load(jksInputStream, proxyConfig.getTunnel().getKeyStorePassword().toCharArray());
|
||||
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
kmf.init(ks, keyManagerPassword.toCharArray());
|
||||
kmf.init(ks, proxyConfig.getTunnel().getKeyManagerPassword().toCharArray());
|
||||
TrustManager[] trustManagers = null;
|
||||
|
||||
serverContext.init(kmf.getKeyManagers(), trustManagers, null);
|
||||
@@ -114,18 +109,21 @@ public class ProxyServerRunner implements EventListener<AppLoadEndEvent> {
|
||||
|
||||
return new SslHandler(sslEngine);
|
||||
} catch (Exception e) {
|
||||
log.error("创建SSL处理器失败", e);
|
||||
log.error("create SSL handler error", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void proxyServerCommonInitHandler(SocketChannel ch) {
|
||||
if (null != proxyConfig.getTunnel().getTransferLogEnable() && proxyConfig.getTunnel().getTransferLogEnable()) {
|
||||
ch.pipeline().addFirst(new LoggingHandler(ProxyTunnelServer.class));
|
||||
}
|
||||
ch.pipeline().addLast(new ProxyMessageDecoder(proxyConfig.getProtocol().getMaxFrameLength(),
|
||||
proxyConfig.getProtocol().getLengthFieldOffset(), proxyConfig.getProtocol().getLengthFieldLength(),
|
||||
proxyConfig.getProtocol().getLengthAdjustment(), proxyConfig.getProtocol().getInitialBytesToStrip()));
|
||||
ch.pipeline().addLast(new ProxyMessageEncoder());
|
||||
ch.pipeline().addLast(new IdleStateHandler(proxyConfig.getProtocol().getReadIdleTime(), proxyConfig.getProtocol().getWriteIdleTime(), proxyConfig.getProtocol().getAllIdleTimeSeconds()));
|
||||
ch.pipeline().addLast(new ServerChannelHandler());
|
||||
ch.pipeline().addLast(new ProxyTunnelChannelHandler());
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
package fun.asgc.neutrino.proxy.server.proxy.core;
|
||||
package org.dromara.neutrinoproxy.server.proxy.core;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import fun.asgc.neutrino.proxy.core.Constants;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessage;
|
||||
import fun.asgc.neutrino.proxy.server.proxy.domain.VisitorChannelAttachInfo;
|
||||
import fun.asgc.neutrino.proxy.server.service.FlowReportService;
|
||||
import fun.asgc.neutrino.proxy.server.util.ProxyUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.neutrinoproxy.core.Constants;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.VisitorChannelAttachInfo;
|
||||
import org.dromara.neutrinoproxy.server.service.FlowReportService;
|
||||
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
@@ -14,44 +16,46 @@ import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import org.noear.solon.Solon;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
|
||||
|
||||
private static AtomicLong visitorIdProducer = new AtomicLong(0);
|
||||
@Slf4j
|
||||
public class TcpVisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
|
||||
// 当出现异常就关闭连接
|
||||
ctx.close();
|
||||
log.error("VisitorChannel error", cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) throws Exception {
|
||||
|
||||
// 通知代理客户端
|
||||
Channel visitorChannel = ctx.channel();
|
||||
Channel proxyChannel = visitorChannel.attr(Constants.NEXT_CHANNEL).get();
|
||||
if (proxyChannel == null) {
|
||||
|
||||
if (null == proxyChannel) {
|
||||
// 该端口还没有代理客户端
|
||||
ctx.channel().close();
|
||||
} else {
|
||||
byte[] bytes = new byte[buf.readableBytes()];
|
||||
buf.readBytes(bytes);
|
||||
String visitorId = ProxyUtil.getVisitorIdByChannel(visitorChannel);
|
||||
proxyChannel.writeAndFlush(ProxyMessage.buildTransferMessage(visitorId, bytes));
|
||||
|
||||
// 增加流量计数
|
||||
VisitorChannelAttachInfo visitorChannelAttachInfo = ProxyUtil.getAttachInfo(visitorChannel);
|
||||
Solon.context().getBean(FlowReportService.class).addWriteByte(visitorChannelAttachInfo.getLicenseId(), bytes.length);
|
||||
return;
|
||||
}
|
||||
|
||||
// 代理通道可写,则设置访问通道可读。代理通道不可写,则设置访问通道不可读
|
||||
visitorChannel.config().setAutoRead(proxyChannel.isWritable());
|
||||
|
||||
// 转发代理数据
|
||||
byte[] bytes = new byte[buf.readableBytes()];
|
||||
buf.readBytes(bytes);
|
||||
String visitorId = ProxyUtil.getVisitorIdByChannel(visitorChannel);
|
||||
proxyChannel.writeAndFlush(ProxyMessage.buildTransferMessage(visitorId, bytes));
|
||||
|
||||
// 增加流量计数
|
||||
VisitorChannelAttachInfo visitorChannelAttachInfo = ProxyUtil.getAttachInfo(visitorChannel);
|
||||
Solon.context().getBean(FlowReportService.class).addWriteByte(visitorChannelAttachInfo.getLicenseId(), bytes.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -60,23 +64,26 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
|
||||
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
|
||||
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
|
||||
|
||||
if (cmdChannel == null) {
|
||||
if (null == cmdChannel) {
|
||||
// 该端口还没有代理客户端
|
||||
ctx.channel().close();
|
||||
} else {
|
||||
String visitorId = newVisitorId();
|
||||
String lanInfo = ProxyUtil.getClientLanInfoByServerPort(sa.getPort());
|
||||
if (StrUtil.isEmpty(lanInfo)) {
|
||||
ctx.channel().close();
|
||||
} else {
|
||||
// 用户连接到代理服务器时,设置用户连接不可读,等待代理后端服务器连接成功后再改变为可读状态
|
||||
visitorChannel.config().setOption(ChannelOption.AUTO_READ, false);
|
||||
|
||||
ProxyUtil.addVisitorChannelToCmdChannel(cmdChannel, visitorId, visitorChannel, sa.getPort());
|
||||
cmdChannel.writeAndFlush(ProxyMessage.buildConnectMessage(visitorId).setData(lanInfo.getBytes()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据代理服务端端口,获取被代理客户端局域网连接信息
|
||||
String lanInfo = ProxyUtil.getClientLanInfoByServerPort(sa.getPort());
|
||||
if (StrUtil.isEmpty(lanInfo)) {
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
|
||||
// 用户连接到代理服务器时,设置用户连接不可读,等待代理后端服务器连接成功后再改变为可读状态
|
||||
visitorChannel.config().setOption(ChannelOption.AUTO_READ, false);
|
||||
|
||||
String visitorId = ProxyUtil.newVisitorId();
|
||||
ProxyUtil.addVisitorChannelToCmdChannel(NetworkProtocolEnum.TCP, cmdChannel, visitorId, visitorChannel, sa.getPort());
|
||||
cmdChannel.writeAndFlush(ProxyMessage.buildConnectMessage(visitorId).setData(lanInfo.getBytes()));
|
||||
|
||||
super.channelActive(ctx);
|
||||
}
|
||||
|
||||
@@ -84,21 +91,23 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
|
||||
// 通知代理客户端
|
||||
Channel userChannel = ctx.channel();
|
||||
InetSocketAddress sa = (InetSocketAddress) userChannel.localAddress();
|
||||
Channel visitorChannel = ctx.channel();
|
||||
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
|
||||
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
|
||||
|
||||
if (cmdChannel == null) {
|
||||
|
||||
if (null == cmdChannel) {
|
||||
// 该端口还没有代理客户端
|
||||
ctx.channel().close();
|
||||
} else {
|
||||
|
||||
// 用户连接断开,从控制连接中移除
|
||||
String userId = ProxyUtil.getVisitorIdByChannel(userChannel);
|
||||
ProxyUtil.removeVisitorChannelFromCmdChannel(cmdChannel, userId);
|
||||
String visitorId = ProxyUtil.getVisitorIdByChannel(visitorChannel);
|
||||
ProxyUtil.removeVisitorChannelFromCmdChannel(cmdChannel, visitorId);
|
||||
|
||||
Channel proxyChannel = userChannel.attr(Constants.NEXT_CHANNEL).get();
|
||||
// 删除代理附加对象
|
||||
ProxyUtil.remoteProxyConnectAttachment(visitorId);
|
||||
|
||||
Channel proxyChannel = visitorChannel.attr(Constants.NEXT_CHANNEL).get();
|
||||
if (proxyChannel != null && proxyChannel.isActive()) {
|
||||
proxyChannel.attr(Constants.NEXT_CHANNEL).remove();
|
||||
proxyChannel.attr(Constants.LICENSE_ID).remove();
|
||||
@@ -106,7 +115,7 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
|
||||
|
||||
proxyChannel.config().setOption(ChannelOption.AUTO_READ, true);
|
||||
// 通知客户端,用户连接已经断开
|
||||
proxyChannel.writeAndFlush(ProxyMessage.buildDisconnectMessage(userId));
|
||||
proxyChannel.writeAndFlush(ProxyMessage.buildDisconnectMessage(visitorId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,12 +130,13 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
|
||||
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
|
||||
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
|
||||
|
||||
if (cmdChannel == null) {
|
||||
if (null == cmdChannel) {
|
||||
// 该端口还没有代理客户端
|
||||
ctx.channel().close();
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
Channel proxyChannel = visitorChannel.attr(Constants.NEXT_CHANNEL).get();
|
||||
if (proxyChannel != null) {
|
||||
if (null != proxyChannel) {
|
||||
proxyChannel.config().setOption(ChannelOption.AUTO_READ, visitorChannel.isWritable());
|
||||
}
|
||||
}
|
||||
@@ -134,12 +144,4 @@ public class VisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf>
|
||||
super.channelWritabilityChanged(ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为访问者连接产生ID
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static String newVisitorId() {
|
||||
return String.valueOf(visitorIdProducer.incrementAndGet());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package org.dromara.neutrinoproxy.server.proxy.core;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.channel.socket.DatagramPacket;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dromara.neutrinoproxy.core.Constants;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyAttachment;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.VisitorChannelAttachInfo;
|
||||
import org.dromara.neutrinoproxy.server.service.FlowReportService;
|
||||
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
|
||||
import org.noear.solon.Solon;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/9/16
|
||||
*/
|
||||
@Slf4j
|
||||
public class UdpVisitorChannelHandler extends SimpleChannelInboundHandler<DatagramPacket> {
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket datagramPacket) throws Exception {
|
||||
log.debug("chid>>>{}", ctx.channel().id().asLongText());
|
||||
byte[] bytes = new byte[datagramPacket.content().readableBytes()];
|
||||
datagramPacket.content().readBytes(bytes);
|
||||
datagramPacket.content().resetReaderIndex();
|
||||
ProxyAttachment proxyAttachment = new ProxyAttachment(ctx.channel(), bytes, (channel, buf) -> {
|
||||
Channel proxyChannel = channel.attr(Constants.NEXT_CHANNEL).get();
|
||||
|
||||
if (null == proxyChannel) {
|
||||
// // 该端口还没有代理客户端
|
||||
// ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
|
||||
proxyChannel.attr(Constants.SENDER).set(datagramPacket.sender());
|
||||
String targetIp = proxyChannel.attr(Constants.TARGET_IP).get();
|
||||
int targetPort = proxyChannel.attr(Constants.TARGET_PORT).get();
|
||||
Integer proxyResponses = proxyChannel.attr(Constants.PROXY_RESPONSES).get();
|
||||
Long proxyTimeoutMs = proxyChannel.attr(Constants.PROXY_TIMEOUT_MS).get();
|
||||
|
||||
// 转发代理数据
|
||||
String visitorId = ProxyUtil.getVisitorIdByChannel(channel);
|
||||
proxyChannel.writeAndFlush(ProxyMessage.buildUdpTransferMessage(new ProxyMessage.UdpBaseInfo()
|
||||
.setVisitorId(visitorId)
|
||||
.setVisitorIp(datagramPacket.sender().getAddress().getHostAddress())
|
||||
.setVisitorPort(datagramPacket.sender().getPort())
|
||||
.setTargetIp(targetIp)
|
||||
.setTargetPort(targetPort)
|
||||
.setProxyTimeoutMs(proxyTimeoutMs)
|
||||
.setProxyResponses(proxyResponses)
|
||||
).setData(bytes));
|
||||
|
||||
// 增加流量计数
|
||||
VisitorChannelAttachInfo visitorChannelAttachInfo = ProxyUtil.getAttachInfo(channel);
|
||||
Solon.context().getBean(FlowReportService.class).addWriteByte(visitorChannelAttachInfo.getLicenseId(), bytes.length);
|
||||
});
|
||||
|
||||
// String visitorId = ProxyUtil.getVisitorIdByChannel(ctx.channel());
|
||||
// if (StringUtils.isNotBlank(visitorId)) {
|
||||
// // UDP代理隧道已就绪,直接转发
|
||||
// proxyAttachment.execute();
|
||||
// return;
|
||||
// }
|
||||
Channel proxyChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
|
||||
if (null != proxyChannel && proxyChannel.isActive()) {
|
||||
// UDP代理隧道已就绪,直接转发
|
||||
proxyAttachment.execute();
|
||||
return;
|
||||
}
|
||||
|
||||
Channel visitorChannel = ctx.channel();
|
||||
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
|
||||
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
|
||||
|
||||
// 没有指令通道,直接结束
|
||||
if (null == cmdChannel) {
|
||||
// 该端口还没有代理客户端
|
||||
// ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据代理服务端端口,获取被代理客户端局域网连接信息
|
||||
String lanInfo = ProxyUtil.getClientLanInfoByServerPort(sa.getPort());
|
||||
if (StrUtil.isEmpty(lanInfo)) {
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
|
||||
String[] targetInfo = lanInfo.split(":");
|
||||
String targetIp = targetInfo[0];
|
||||
int targetPort = Integer.parseInt(targetInfo[1]);
|
||||
|
||||
// // 用户连接到代理服务器时,设置用户连接不可读,等待代理后端服务器连接成功后再改变为可读状态
|
||||
// visitorChannel.config().setOption(ChannelOption.AUTO_READ, false);
|
||||
|
||||
// TODO UDP此处叫visitor似有不妥,与TCP不同,2.x重构思考
|
||||
String visitorId = ProxyUtil.newVisitorId();
|
||||
// 此处需要和tcp分开
|
||||
ProxyUtil.addVisitorChannelToCmdChannel(NetworkProtocolEnum.UDP, cmdChannel, visitorId, visitorChannel, sa.getPort());
|
||||
ProxyUtil.addProxyConnectAttachment(visitorId, proxyAttachment);
|
||||
cmdChannel.writeAndFlush(ProxyMessage.buildUdpConnectMessage(new ProxyMessage.UdpBaseInfo()
|
||||
.setVisitorId(visitorId)
|
||||
.setServerPort(sa.getPort())
|
||||
.setTargetIp(targetIp)
|
||||
.setTargetPort(targetPort)
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
super.channelActive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
// 当出现异常就关闭连接
|
||||
ctx.close();
|
||||
log.error("[UDP Visitor Channel]VisitorChannel error", cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
|
||||
|
||||
// 通知代理客户端
|
||||
Channel visitorChannel = ctx.channel();
|
||||
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
|
||||
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
|
||||
|
||||
if (null == cmdChannel) {
|
||||
// 该端口还没有代理客户端
|
||||
ctx.channel().close();
|
||||
}
|
||||
else {
|
||||
Channel proxyChannel = visitorChannel.attr(Constants.NEXT_CHANNEL).get();
|
||||
if (null != proxyChannel) {
|
||||
proxyChannel.config().setOption(ChannelOption.AUTO_READ, visitorChannel.isWritable());
|
||||
}
|
||||
}
|
||||
|
||||
super.channelWritabilityChanged(ctx);
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.proxy.domain;
|
||||
package org.dromara.neutrinoproxy.server.proxy.domain;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Data;
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.dromara.neutrinoproxy.server.proxy.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
@Data
|
||||
public class Metrics implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private int port;
|
||||
private long readBytes;
|
||||
private long wroteBytes;
|
||||
private long readMsgs;
|
||||
private long wroteMsgs;
|
||||
private int channels;
|
||||
private long timestamp;
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package fun.asgc.neutrino.proxy.server.proxy.domain;
|
||||
package org.dromara.neutrinoproxy.server.proxy.domain;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.dromara.neutrinoproxy.server.proxy.domain;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* 代理连接附件
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/4/2
|
||||
*/
|
||||
public class ProxyAttachment {
|
||||
private Channel channel;
|
||||
private byte[] bytes;
|
||||
private BiConsumer<Channel, byte[]> executor;
|
||||
|
||||
public ProxyAttachment(Channel channel, byte[] bytes, BiConsumer<Channel, byte[]> executor) {
|
||||
this.channel = channel;
|
||||
this.bytes = bytes;
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
public void execute() {
|
||||
if (null != executor) {
|
||||
this.executor.accept(channel, bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package fun.asgc.neutrino.proxy.server.proxy.domain;
|
||||
package org.dromara.neutrinoproxy.server.proxy.domain;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.PortMappingDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.PortMappingDO;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@@ -19,10 +19,11 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.proxy.domain;
|
||||
package org.dromara.neutrinoproxy.server.proxy.domain;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -32,6 +33,7 @@ import lombok.experimental.Accessors;
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
public class VisitorChannelAttachInfo {
|
||||
private NetworkProtocolEnum protocol;
|
||||
private String visitorId;
|
||||
private String lanInfo;
|
||||
private Integer serverPort;
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.dromara.neutrinoproxy.server.proxy.enhance;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
|
||||
import org.dromara.neutrinoproxy.server.proxy.core.BytesMetricsHandler;
|
||||
import org.dromara.neutrinoproxy.server.proxy.core.ProxyTunnelServer;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
import org.noear.solon.core.event.AppLoadEndEvent;
|
||||
import org.noear.solon.core.event.EventListener;
|
||||
|
||||
/**
|
||||
* HTTP代理
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/4/2
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class HttpProxy implements EventListener<AppLoadEndEvent> {
|
||||
@Inject
|
||||
private ProxyConfig proxyConfig;
|
||||
@Override
|
||||
public void onEvent(AppLoadEndEvent appLoadEndEvent) throws Throwable {
|
||||
if (StrUtil.isBlank(proxyConfig.getServer().getTcp().getDomainName()) || null == proxyConfig.getServer().getTcp().getHttpProxyPort()) {
|
||||
log.info("no config domain name,nonsupport http proxy.");
|
||||
return;
|
||||
}
|
||||
this.start();
|
||||
}
|
||||
|
||||
private void start() {
|
||||
try {
|
||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||
bootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup())
|
||||
.channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
if (null != proxyConfig.getServer().getTcp().getTransferLogEnable() && proxyConfig.getServer().getTcp().getTransferLogEnable()) {
|
||||
ch.pipeline().addFirst(new LoggingHandler(HttpProxy.class));
|
||||
}
|
||||
ch.pipeline().addFirst(new BytesMetricsHandler());
|
||||
ch.pipeline().addLast(new HttpVisitorChannelHandler(proxyConfig.getServer().getTcp().getDomainName()));
|
||||
}
|
||||
});
|
||||
bootstrap.bind("0.0.0.0", proxyConfig.getServer().getTcp().getHttpProxyPort()).sync();
|
||||
log.info("Http proxy server start success!port:{}", proxyConfig.getServer().getTcp().getHttpProxyPort());
|
||||
} catch (Exception e) {
|
||||
log.error("http proxy start err!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package org.dromara.neutrinoproxy.server.proxy.enhance;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dromara.neutrinoproxy.core.Constants;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyAttachment;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.VisitorChannelAttachInfo;
|
||||
import org.dromara.neutrinoproxy.server.service.FlowReportService;
|
||||
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
|
||||
import org.noear.solon.Solon;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/5/27
|
||||
*/
|
||||
@Slf4j
|
||||
public class HttpVisitorChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
|
||||
/**
|
||||
* 域名
|
||||
*/
|
||||
private String domainName;
|
||||
|
||||
public HttpVisitorChannelHandler(String domainName) {
|
||||
this.domainName = domainName;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception {
|
||||
if (StrUtil.isBlank(domainName)) {
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] bytes = new byte[byteBuf.readableBytes()];
|
||||
byteBuf.readBytes(bytes);
|
||||
byteBuf.resetReaderIndex();
|
||||
ProxyAttachment proxyAttachment = new ProxyAttachment(ctx.channel(), bytes, (channel, buf) -> {
|
||||
Channel proxyChannel = channel.attr(Constants.NEXT_CHANNEL).get();
|
||||
if (null == proxyChannel) {
|
||||
// 该端口还没有代理客户端
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
|
||||
proxyChannel.writeAndFlush(ProxyMessage.buildTransferMessage(ProxyUtil.getVisitorIdByChannel(channel), bytes));
|
||||
|
||||
// 增加流量计数
|
||||
VisitorChannelAttachInfo visitorChannelAttachInfo = ProxyUtil.getAttachInfo(channel);
|
||||
Solon.context().getBean(FlowReportService.class).addWriteByte(visitorChannelAttachInfo.getLicenseId(), bytes.length);
|
||||
});
|
||||
|
||||
String visitorId = ProxyUtil.getVisitorIdByChannel(ctx.channel());
|
||||
if (StringUtils.isNotBlank(visitorId)) {
|
||||
proxyAttachment.execute();
|
||||
return;
|
||||
}
|
||||
|
||||
// 用户连接到代理服务器时,设置用户连接不可读,等待代理后端服务器连接成功后再改变为可读状态
|
||||
ctx.channel().config().setOption(ChannelOption.AUTO_READ, false);
|
||||
|
||||
String host = getHost(bytes);
|
||||
log.debug("HttpProxy host: {}", host);
|
||||
if (StringUtils.isBlank(host)) {
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
if (!host.endsWith(domainName)) {
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
int index = host.lastIndexOf("." + domainName);
|
||||
String subdomain = host.substring(0, index);
|
||||
|
||||
// 根据域名拿到绑定的映射对应的cmdChannel
|
||||
Integer serverPort = ProxyUtil.getServerPortBySubdomain(subdomain);
|
||||
if (null == serverPort) {
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(serverPort);
|
||||
if (null == cmdChannel) {
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
String lanInfo = ProxyUtil.getClientLanInfoByServerPort(serverPort);
|
||||
if (StringUtils.isBlank(lanInfo)) {
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
|
||||
visitorId = ProxyUtil.newVisitorId();
|
||||
ProxyUtil.addVisitorChannelToCmdChannel(NetworkProtocolEnum.HTTP, cmdChannel, visitorId, ctx.channel(), serverPort);
|
||||
ProxyUtil.addProxyConnectAttachment(visitorId, proxyAttachment);
|
||||
cmdChannel.writeAndFlush(ProxyMessage.buildConnectMessage(visitorId).setData(lanInfo.getBytes()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
|
||||
// 通知代理客户端
|
||||
Channel visitorChannel = ctx.channel();
|
||||
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
|
||||
Channel cmdChannel = ProxyUtil.getCmdChannelByServerPort(sa.getPort());
|
||||
|
||||
if (cmdChannel == null) {
|
||||
// 该端口还没有代理客户端
|
||||
ctx.channel().close();
|
||||
} else {
|
||||
|
||||
// 用户连接断开,从控制连接中移除
|
||||
String visitorId = ProxyUtil.getVisitorIdByChannel(visitorChannel);
|
||||
ProxyUtil.removeVisitorChannelFromCmdChannel(cmdChannel, visitorId);
|
||||
|
||||
// 删除代理附加对象
|
||||
ProxyUtil.remoteProxyConnectAttachment(visitorId);
|
||||
|
||||
Channel proxyChannel = visitorChannel.attr(Constants.NEXT_CHANNEL).get();
|
||||
if (proxyChannel != null && proxyChannel.isActive()) {
|
||||
proxyChannel.attr(Constants.NEXT_CHANNEL).remove();
|
||||
proxyChannel.attr(Constants.LICENSE_ID).remove();
|
||||
proxyChannel.attr(Constants.VISITOR_ID).remove();
|
||||
|
||||
proxyChannel.config().setOption(ChannelOption.AUTO_READ, true);
|
||||
// 通知客户端,用户连接已经断开
|
||||
proxyChannel.writeAndFlush(ProxyMessage.buildDisconnectMessage(visitorId));
|
||||
}
|
||||
}
|
||||
|
||||
super.channelInactive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
super.channelActive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
// 当出现异常就关闭连接
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
private String getHost(byte[] buf) {
|
||||
String req = new String(buf);
|
||||
String[] lines = req.split("\r\n");
|
||||
String firstLine = lines[0];
|
||||
if (!(firstLine.endsWith("HTTP/1.1") || firstLine.endsWith("HTTP/1.0"))) {
|
||||
return null;
|
||||
}
|
||||
for (int i = 1; i < lines.length; i++) {
|
||||
String line = lines[i];
|
||||
if (!line.startsWith("Host: ")) {
|
||||
continue;
|
||||
}
|
||||
// 域名
|
||||
String domain = line.substring(6);
|
||||
// 去掉域名后面的端口号
|
||||
return domain.replaceAll(":.*", "");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package org.dromara.neutrinoproxy.server.proxy.enhance;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dromara.neutrinoproxy.core.util.FileUtil;
|
||||
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
|
||||
import org.dromara.neutrinoproxy.server.proxy.core.BytesMetricsHandler;
|
||||
import org.dromara.neutrinoproxy.server.proxy.core.ProxyTunnelServer;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
import org.noear.solon.core.event.AppLoadEndEvent;
|
||||
import org.noear.solon.core.event.EventListener;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
import java.io.InputStream;
|
||||
import java.security.KeyStore;
|
||||
|
||||
/**
|
||||
* HTTPS代理
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/4/2
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class HttpsProxy implements EventListener<AppLoadEndEvent> {
|
||||
@Inject
|
||||
private ProxyConfig proxyConfig;
|
||||
@Override
|
||||
public void onEvent(AppLoadEndEvent appLoadEndEvent) throws Throwable {
|
||||
if (StrUtil.isBlank(proxyConfig.getServer().getTcp().getDomainName()) || null == proxyConfig.getServer().getTcp().getHttpsProxyPort() ||
|
||||
StringUtils.isEmpty(proxyConfig.getServer().getTcp().getJksPath()) || StringUtils.isEmpty(proxyConfig.getServer().getTcp().getKeyStorePassword())) {
|
||||
log.info("no config domain name,nonsupport https proxy.");
|
||||
return;
|
||||
}
|
||||
this.start();
|
||||
}
|
||||
|
||||
private void start() {
|
||||
try {
|
||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||
bootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup())
|
||||
.channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
if (null != proxyConfig.getServer().getTcp().getTransferLogEnable() && proxyConfig.getServer().getTcp().getTransferLogEnable()) {
|
||||
ch.pipeline().addFirst(new LoggingHandler(HttpsProxy.class));
|
||||
}
|
||||
ch.pipeline().addLast(createSslHandler());
|
||||
ch.pipeline().addFirst(new BytesMetricsHandler());
|
||||
ch.pipeline().addLast(new HttpVisitorChannelHandler(proxyConfig.getServer().getTcp().getDomainName()));
|
||||
}
|
||||
});
|
||||
bootstrap.bind("0.0.0.0", proxyConfig.getServer().getTcp().getHttpsProxyPort()).sync();
|
||||
log.info("Https proxy server started!port:{}", proxyConfig.getServer().getTcp().getHttpsProxyPort());
|
||||
} catch (Exception e) {
|
||||
log.error("https proxy start err!", e);
|
||||
}
|
||||
}
|
||||
|
||||
private ChannelHandler createSslHandler() {
|
||||
try {
|
||||
InputStream jksInputStream = FileUtil.getInputStream(proxyConfig.getServer().getTcp().getJksPath());
|
||||
SSLContext serverContext = SSLContext.getInstance("TLS");
|
||||
final KeyStore ks = KeyStore.getInstance("JKS");
|
||||
|
||||
ks.load(jksInputStream, proxyConfig.getServer().getTcp().getKeyStorePassword().toCharArray());
|
||||
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
kmf.init(ks, proxyConfig.getServer().getTcp().getKeyStorePassword().toCharArray());
|
||||
TrustManager[] trustManagers = null;
|
||||
|
||||
serverContext.init(kmf.getKeyManagers(), trustManagers, null);
|
||||
|
||||
SSLEngine sslEngine = serverContext.createSSLEngine();
|
||||
sslEngine.setUseClientMode(false);
|
||||
sslEngine.setNeedClientAuth(false);
|
||||
|
||||
return new SslHandler(sslEngine);
|
||||
} catch (Exception e) {
|
||||
log.error("create SSL handler failed", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -20,22 +20,23 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package fun.asgc.neutrino.proxy.server.proxy.handler;
|
||||
package org.dromara.neutrinoproxy.server.proxy.handler;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import fun.asgc.neutrino.proxy.core.*;
|
||||
import fun.asgc.neutrino.proxy.core.dispatcher.Match;
|
||||
import fun.asgc.neutrino.proxy.server.base.proxy.ProxyConfig;
|
||||
import fun.asgc.neutrino.proxy.server.constant.ClientConnectTypeEnum;
|
||||
import fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum;
|
||||
import fun.asgc.neutrino.proxy.server.constant.OnlineStatusEnum;
|
||||
import fun.asgc.neutrino.proxy.server.constant.SuccessCodeEnum;
|
||||
import fun.asgc.neutrino.proxy.server.dal.LicenseMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.ClientConnectRecordDO;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.LicenseDO;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
|
||||
import fun.asgc.neutrino.proxy.server.service.*;
|
||||
import fun.asgc.neutrino.proxy.server.util.ProxyUtil;
|
||||
import org.dromara.neutrinoproxy.core.*;
|
||||
import org.dromara.neutrinoproxy.core.*;
|
||||
import org.dromara.neutrinoproxy.core.dispatcher.Match;
|
||||
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
|
||||
import org.dromara.neutrinoproxy.server.constant.ClientConnectTypeEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.SuccessCodeEnum;
|
||||
import org.dromara.neutrinoproxy.server.dal.LicenseMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.ClientConnectRecordDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.LicenseDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
|
||||
import org.dromara.neutrinoproxy.server.service.*;
|
||||
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -78,81 +79,107 @@ public class ProxyMessageAuthHandler implements ProxyMessageHandler {
|
||||
String ip = ((InetSocketAddress)ctx.channel().remoteAddress()).getAddress().getHostAddress();
|
||||
Date now = new Date();
|
||||
|
||||
String info = proxyMessage.getInfo();
|
||||
String[] tmp = info.split(",");
|
||||
String licenseKey = proxyMessage.getInfo();
|
||||
String clientId = "";
|
||||
if (tmp.length == 2) {
|
||||
licenseKey = tmp[0];
|
||||
clientId = tmp[1];
|
||||
}
|
||||
|
||||
if (StrUtil.isEmpty(licenseKey)) {
|
||||
log.warn("[client connection] license cannot empty info:{} ", info);
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "license不能为空!", licenseKey));
|
||||
ctx.channel().close();
|
||||
clientConnectRecordService.add(new ClientConnectRecordDO()
|
||||
.setIp(ip)
|
||||
.setType(ClientConnectTypeEnum.CONNECT.getType())
|
||||
.setMsg(licenseKey)
|
||||
.setCode(SuccessCodeEnum.FAIL.getCode())
|
||||
.setErr("license不能为空!")
|
||||
.setErr("license cannot empty!")
|
||||
.setCreateTime(now)
|
||||
);
|
||||
return;
|
||||
}
|
||||
LicenseDO licenseDO = licenseService.findByKey(licenseKey);
|
||||
if (null == licenseDO) {
|
||||
log.warn("[client connection] license notfound info:{} ", info);
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "license不存在!", licenseKey));
|
||||
ctx.channel().close();
|
||||
clientConnectRecordService.add(new ClientConnectRecordDO()
|
||||
.setIp(ip)
|
||||
.setType(ClientConnectTypeEnum.CONNECT.getType())
|
||||
.setMsg(licenseKey)
|
||||
.setCode(SuccessCodeEnum.FAIL.getCode())
|
||||
.setErr("license不存在!")
|
||||
.setErr("license notfound!")
|
||||
.setCreateTime(now)
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (EnableStatusEnum.DISABLE.getStatus().equals(licenseDO.getEnable())) {
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "当前license已被禁用!", licenseKey));
|
||||
log.warn("[client connection] the license disabled info:{} ", info);
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "the license disabled!", licenseKey));
|
||||
ctx.channel().close();
|
||||
clientConnectRecordService.add(new ClientConnectRecordDO()
|
||||
.setIp(ip)
|
||||
.setLicenseId(licenseDO.getId())
|
||||
.setType(ClientConnectTypeEnum.CONNECT.getType())
|
||||
.setMsg(licenseKey)
|
||||
.setCode(SuccessCodeEnum.FAIL.getCode())
|
||||
.setErr("当前license已被禁用!")
|
||||
.setErr("the license disabled!")
|
||||
.setCreateTime(now));
|
||||
return;
|
||||
}
|
||||
UserDO userDO = userService.findById(licenseDO.getUserId());
|
||||
if (null == userDO || EnableStatusEnum.DISABLE.getStatus().equals(userDO.getEnable())) {
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "当前license无效!", licenseKey));
|
||||
log.warn("[client connection] the license invalid info:{} ", info);
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "the license invalid!", licenseKey));
|
||||
ctx.channel().close();
|
||||
clientConnectRecordService.add(new ClientConnectRecordDO()
|
||||
.setIp(ip)
|
||||
.setLicenseId(licenseDO.getId())
|
||||
.setType(ClientConnectTypeEnum.CONNECT.getType())
|
||||
.setMsg(licenseKey)
|
||||
.setCode(SuccessCodeEnum.FAIL.getCode())
|
||||
.setErr("当前license无效!")
|
||||
.setErr("the license invalid!")
|
||||
.setCreateTime(now));
|
||||
return;
|
||||
}
|
||||
Channel cmdChannel = ProxyUtil.getCmdChannelByLicenseId(licenseDO.getId());
|
||||
if (null != cmdChannel) {
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "当前license已被另一节点使用!", licenseKey));
|
||||
clientConnectRecordService.add(new ClientConnectRecordDO()
|
||||
.setIp(ip)
|
||||
.setLicenseId(licenseDO.getId())
|
||||
.setType(ClientConnectTypeEnum.CONNECT.getType())
|
||||
.setMsg(licenseKey)
|
||||
.setCode(SuccessCodeEnum.FAIL.getCode())
|
||||
.setErr("当前license已被另一节点使用!")
|
||||
.setCreateTime(now));
|
||||
return;
|
||||
String _clientId = ProxyUtil.getClientIdByLicenseId(licenseDO.getId());
|
||||
if (!clientId.equals(_clientId)) {
|
||||
log.warn("[client connection] the license on another no used info:{} _clientId:{}", info, _clientId);
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.AUTH_FAILED.getCode(), "the license on another no used!", licenseKey));
|
||||
ctx.channel().close();
|
||||
clientConnectRecordService.add(new ClientConnectRecordDO()
|
||||
.setIp(ip)
|
||||
.setLicenseId(licenseDO.getId())
|
||||
.setType(ClientConnectTypeEnum.CONNECT.getType())
|
||||
.setMsg(licenseKey)
|
||||
.setCode(SuccessCodeEnum.FAIL.getCode())
|
||||
.setErr("the license on another no used!")
|
||||
.setCreateTime(now));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 发送认证成功消息
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.SUCCESS.getCode(), "认证成功!", licenseKey));
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildAuthResultMessage(ExceptionEnum.SUCCESS.getCode(), "auth success!", licenseKey));
|
||||
|
||||
clientConnectRecordService.add(new ClientConnectRecordDO()
|
||||
.setIp(ip)
|
||||
.setLicenseId(licenseDO.getId())
|
||||
.setType(ClientConnectTypeEnum.CONNECT.getType())
|
||||
.setMsg(licenseKey)
|
||||
.setMsg(info)
|
||||
.setCode(SuccessCodeEnum.SUCCESS.getCode())
|
||||
.setCreateTime(now));
|
||||
|
||||
// 设置当前licenseId对应的客户端ID
|
||||
ProxyUtil.setLicenseIdToClientIdMap(licenseDO.getId(), clientId);
|
||||
|
||||
log.warn("[client connection] auth success info:{} ", info);
|
||||
|
||||
// 更新license在线状态
|
||||
licenseMapper.updateOnlineStatus(licenseDO.getId(), OnlineStatusEnum.ONLINE.getStatus(), now);
|
||||
// 初始化VisitorChannel
|
||||
@@ -1,14 +1,16 @@
|
||||
package fun.asgc.neutrino.proxy.server.proxy.handler;
|
||||
package org.dromara.neutrinoproxy.server.proxy.handler;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import fun.asgc.neutrino.proxy.core.*;
|
||||
import fun.asgc.neutrino.proxy.core.dispatcher.Match;
|
||||
import fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.LicenseDO;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
|
||||
import fun.asgc.neutrino.proxy.server.service.LicenseService;
|
||||
import fun.asgc.neutrino.proxy.server.service.UserService;
|
||||
import fun.asgc.neutrino.proxy.server.util.ProxyUtil;
|
||||
import org.dromara.neutrinoproxy.core.*;
|
||||
import org.dromara.neutrinoproxy.core.*;
|
||||
import org.dromara.neutrinoproxy.core.dispatcher.Match;
|
||||
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.LicenseDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyAttachment;
|
||||
import org.dromara.neutrinoproxy.server.service.LicenseService;
|
||||
import org.dromara.neutrinoproxy.server.service.UserService;
|
||||
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelOption;
|
||||
@@ -32,14 +34,14 @@ public class ProxyMessageConnectHandler implements ProxyMessageHandler {
|
||||
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
|
||||
String info = proxyMessage.getInfo();
|
||||
if (StrUtil.isEmpty(info)) {
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "info不能为空!"));
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "info cannot be empty!"));
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
|
||||
String[] tokens = info.split("@");
|
||||
if (tokens.length != 2) {
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "info格式有误!"));
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "info format error!"));
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
@@ -48,18 +50,18 @@ public class ProxyMessageConnectHandler implements ProxyMessageHandler {
|
||||
|
||||
LicenseDO licenseDO = licenseService.findByKey(licenseKey);
|
||||
if (null == licenseDO) {
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "license不存在!"));
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "the license notfound!"));
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
if (EnableStatusEnum.DISABLE.getStatus().equals(licenseDO.getEnable())) {
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "当前license已被禁用!"));
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "the license invalid!"));
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
UserDO userDO = userService.findById(licenseDO.getUserId());
|
||||
if (null == userDO || EnableStatusEnum.DISABLE.getStatus().equals(userDO.getEnable())) {
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "当前license无效!"));
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "the license invalid!"));
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
@@ -67,19 +69,28 @@ public class ProxyMessageConnectHandler implements ProxyMessageHandler {
|
||||
Channel cmdChannel = ProxyUtil.getCmdChannelByLicenseId(licenseDO.getId());
|
||||
|
||||
if (null == cmdChannel) {
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "服务端异常,指令通道不存在!"));
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "server error,cmd channel notfound!"));
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
|
||||
Channel visitorChannel = ProxyUtil.getVisitorChannel(cmdChannel, visitorId);
|
||||
if (visitorChannel != null) {
|
||||
ctx.channel().attr(Constants.VISITOR_ID).set(visitorId);
|
||||
ctx.channel().attr(Constants.LICENSE_ID).set(licenseDO.getId());
|
||||
ctx.channel().attr(Constants.NEXT_CHANNEL).set(visitorChannel);
|
||||
visitorChannel.attr(Constants.NEXT_CHANNEL).set(ctx.channel());
|
||||
// 代理客户端与后端服务器连接成功,修改用户连接为可读状态
|
||||
visitorChannel.config().setOption(ChannelOption.AUTO_READ, true);
|
||||
if (null == visitorChannel) {
|
||||
return;
|
||||
}
|
||||
ctx.channel().attr(Constants.VISITOR_ID).set(visitorId);
|
||||
ctx.channel().attr(Constants.LICENSE_ID).set(licenseDO.getId());
|
||||
ctx.channel().attr(Constants.NEXT_CHANNEL).set(visitorChannel);
|
||||
visitorChannel.attr(Constants.NEXT_CHANNEL).set(ctx.channel());
|
||||
// 代理客户端与后端服务器连接成功,修改用户连接为可读状态
|
||||
visitorChannel.config().setOption(ChannelOption.AUTO_READ, true);
|
||||
|
||||
// 获取代理附加对象
|
||||
ProxyAttachment proxyAttachment = ProxyUtil.getProxyConnectAttachment(visitorId);
|
||||
if (null != proxyAttachment) {
|
||||
// 及时释放
|
||||
ProxyUtil.remoteProxyConnectAttachment(visitorId);
|
||||
proxyAttachment.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.dromara.neutrinoproxy.server.proxy.handler;
|
||||
|
||||
import org.checkerframework.checker.units.qual.C;
|
||||
import org.dromara.neutrinoproxy.core.Constants;
|
||||
import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessageHandler;
|
||||
import org.dromara.neutrinoproxy.core.dispatcher.Match;
|
||||
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import org.noear.solon.annotation.Component;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/6/16
|
||||
*/
|
||||
@Match(type = Constants.ProxyDataTypeName.DISCONNECT)
|
||||
@Component
|
||||
public class ProxyMessageDisconnectHandler implements ProxyMessageHandler {
|
||||
|
||||
@Override
|
||||
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
|
||||
Integer licenseId = ctx.channel().attr(Constants.LICENSE_ID).get();
|
||||
// licenseId为空,说明访问者通道已经关闭,无需处理
|
||||
if (null == licenseId) {
|
||||
return;
|
||||
}
|
||||
// 代理连接没有连上服务器由控制连接发送用户端断开连接消息
|
||||
Channel visitorChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
|
||||
if (null != visitorChannel) {
|
||||
// 数据发送完成后再关闭连接,解决http1.0数据传输问题
|
||||
visitorChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
// String visitorId = proxyMessage.getInfo();
|
||||
// Channel visitorChannel = ProxyUtil.removeVisitorChannelFromCmdChannel(ctx.channel(), visitorId);
|
||||
// if (null != visitorChannel) {
|
||||
// // 数据发送完成后再关闭连接,解决http1.0数据传输问题
|
||||
// visitorChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return ProxyDataTypeEnum.DISCONNECT.getDesc();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package fun.asgc.neutrino.proxy.server.proxy.handler;
|
||||
package org.dromara.neutrinoproxy.server.proxy.handler;
|
||||
|
||||
import fun.asgc.neutrino.proxy.core.Constants;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyDataTypeEnum;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessage;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessageHandler;
|
||||
import fun.asgc.neutrino.proxy.core.dispatcher.Match;
|
||||
import org.dromara.neutrinoproxy.core.Constants;
|
||||
import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessageHandler;
|
||||
import org.dromara.neutrinoproxy.core.dispatcher.Match;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import org.noear.solon.annotation.Component;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package fun.asgc.neutrino.proxy.server.proxy.handler;
|
||||
package org.dromara.neutrinoproxy.server.proxy.handler;
|
||||
|
||||
import fun.asgc.neutrino.proxy.core.Constants;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyDataTypeEnum;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessage;
|
||||
import fun.asgc.neutrino.proxy.core.ProxyMessageHandler;
|
||||
import fun.asgc.neutrino.proxy.core.dispatcher.Match;
|
||||
import fun.asgc.neutrino.proxy.server.proxy.domain.VisitorChannelAttachInfo;
|
||||
import fun.asgc.neutrino.proxy.server.service.FlowReportService;
|
||||
import fun.asgc.neutrino.proxy.server.util.ProxyUtil;
|
||||
import org.dromara.neutrinoproxy.core.Constants;
|
||||
import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessageHandler;
|
||||
import org.dromara.neutrinoproxy.core.dispatcher.Match;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.VisitorChannelAttachInfo;
|
||||
import org.dromara.neutrinoproxy.server.service.FlowReportService;
|
||||
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
@@ -27,10 +27,24 @@ public class ProxyMessageTransferHandler implements ProxyMessageHandler {
|
||||
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
|
||||
Channel visitorChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
|
||||
if (null != visitorChannel) {
|
||||
if (!visitorChannel.isWritable()) {
|
||||
//自己不可写,通道可以读,让通道关闭读
|
||||
//自己可写,通道不可以读,让通道打开读
|
||||
if (ctx.channel().config().isAutoRead()) {
|
||||
ctx.channel().config().setAutoRead(false);
|
||||
}
|
||||
} else {
|
||||
if (!ctx.channel().config().isAutoRead()) {
|
||||
ctx.channel().config().setAutoRead(true);
|
||||
}
|
||||
}
|
||||
ByteBuf buf = ctx.alloc().buffer(proxyMessage.getData().length);
|
||||
buf.writeBytes(proxyMessage.getData());
|
||||
visitorChannel.writeAndFlush(buf);
|
||||
|
||||
// 关闭http响应通道
|
||||
// ProxyUtil.closeHttpProxyResponseChannel(visitorChannel);
|
||||
|
||||
// 增加流量计数
|
||||
VisitorChannelAttachInfo visitorChannelAttachInfo = ProxyUtil.getAttachInfo(visitorChannel);
|
||||
Solon.context().getBean(FlowReportService.class).addReadByte(visitorChannelAttachInfo.getLicenseId(), proxyMessage.getData().length);
|
||||
@@ -0,0 +1,107 @@
|
||||
package org.dromara.neutrinoproxy.server.proxy.handler;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.neutrinoproxy.core.Constants;
|
||||
import org.dromara.neutrinoproxy.core.ExceptionEnum;
|
||||
import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessageHandler;
|
||||
import org.dromara.neutrinoproxy.core.dispatcher.Match;
|
||||
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.dal.PortMappingMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.LicenseDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.PortMappingDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyAttachment;
|
||||
import org.dromara.neutrinoproxy.server.service.LicenseService;
|
||||
import org.dromara.neutrinoproxy.server.service.UserService;
|
||||
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
|
||||
import org.noear.snack.ONode;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/9/19
|
||||
*/
|
||||
@Slf4j
|
||||
@Match(type = Constants.ProxyDataTypeName.UDP_CONNECT)
|
||||
@Component
|
||||
public class UdpProxyMessageConnectHandler implements ProxyMessageHandler {
|
||||
@Inject
|
||||
private LicenseService licenseService;
|
||||
@Inject
|
||||
private UserService userService;
|
||||
@Inject
|
||||
private PortMappingMapper portMappingMapper;
|
||||
|
||||
@Override
|
||||
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
|
||||
final ProxyMessage.UdpBaseInfo udpBaseInfo = ONode.deserialize(proxyMessage.getInfo(), ProxyMessage.UdpBaseInfo.class);
|
||||
final String licenseKey = new String(proxyMessage.getData());
|
||||
log.info("[UDP connect]info:{} licenseKey:{}", proxyMessage.getInfo(), licenseKey);
|
||||
|
||||
LicenseDO licenseDO = licenseService.findByKey(licenseKey);
|
||||
if (null == licenseDO) {
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "the license notfound!"));
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
if (EnableStatusEnum.DISABLE.getStatus().equals(licenseDO.getEnable())) {
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "the license disabled!"));
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
UserDO userDO = userService.findById(licenseDO.getUserId());
|
||||
if (null == userDO || EnableStatusEnum.DISABLE.getStatus().equals(userDO.getEnable())) {
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "the license invalid!"));
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
|
||||
Channel cmdChannel = ProxyUtil.getCmdChannelByLicenseId(licenseDO.getId());
|
||||
|
||||
if (null == cmdChannel) {
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "server error,cmd channel notfound!"));
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
|
||||
Channel visitorChannel = ProxyUtil.getVisitorChannel(cmdChannel, udpBaseInfo.getVisitorId());
|
||||
if (null == visitorChannel) {
|
||||
return;
|
||||
}
|
||||
PortMappingDO portMappingDO = portMappingMapper.findByLicenseIdAndServerPort(licenseDO.getId(), udpBaseInfo.getServerPort());
|
||||
if (null == portMappingDO || !EnableStatusEnum.ENABLE.getStatus().equals(portMappingDO.getEnable())) {
|
||||
ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "server error, port mapping notfound!"));
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.channel().attr(Constants.VISITOR_ID).set(udpBaseInfo.getVisitorId());
|
||||
ctx.channel().attr(Constants.LICENSE_ID).set(licenseDO.getId());
|
||||
ctx.channel().attr(Constants.NEXT_CHANNEL).set(visitorChannel);
|
||||
ctx.channel().attr(Constants.TARGET_IP).set(portMappingDO.getClientIp());
|
||||
ctx.channel().attr(Constants.TARGET_PORT).set(portMappingDO.getClientPort());
|
||||
ctx.channel().attr(Constants.PROXY_RESPONSES).set(portMappingDO.getProxyResponses());
|
||||
ctx.channel().attr(Constants.PROXY_TIMEOUT_MS).set(portMappingDO.getProxyTimeoutMs());
|
||||
visitorChannel.attr(Constants.NEXT_CHANNEL).set(ctx.channel());
|
||||
visitorChannel.attr(Constants.IS_UDP_KEY).set(Boolean.TRUE);
|
||||
// // 代理客户端与后端服务器连接成功,修改用户连接为可读状态
|
||||
// visitorChannel.config().setOption(ChannelOption.AUTO_READ, true);
|
||||
// 获取代理附加对象
|
||||
ProxyAttachment proxyAttachment = ProxyUtil.getProxyConnectAttachment(udpBaseInfo.getVisitorId());
|
||||
if (null != proxyAttachment) {
|
||||
// 及时释放
|
||||
ProxyUtil.remoteProxyConnectAttachment(udpBaseInfo.getVisitorId());
|
||||
proxyAttachment.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return ProxyDataTypeEnum.UDP_CONNECT.getDesc();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package org.dromara.neutrinoproxy.server.proxy.handler;
|
||||
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.socket.DatagramPacket;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.neutrinoproxy.core.Constants;
|
||||
import org.dromara.neutrinoproxy.core.ProxyDataTypeEnum;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessage;
|
||||
import org.dromara.neutrinoproxy.core.ProxyMessageHandler;
|
||||
import org.dromara.neutrinoproxy.core.dispatcher.Match;
|
||||
import org.dromara.neutrinoproxy.server.service.FlowReportService;
|
||||
import org.noear.snack.ONode;
|
||||
import org.noear.solon.Solon;
|
||||
import org.noear.solon.annotation.Component;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/9/21
|
||||
*/
|
||||
@Slf4j
|
||||
@Match(type = Constants.ProxyDataTypeName.UDP_TRANSFER)
|
||||
@Component
|
||||
public class UdpProxyMessageTransferHandler implements ProxyMessageHandler {
|
||||
@Override
|
||||
public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
|
||||
final ProxyMessage.UdpBaseInfo udpBaseInfo = ONode.deserialize(proxyMessage.getInfo(), ProxyMessage.UdpBaseInfo.class);
|
||||
log.debug("[UDP transfer]info:{} data:{}", proxyMessage.getInfo(), new String(proxyMessage.getData()));
|
||||
|
||||
Channel visitorChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
|
||||
if (null != visitorChannel) {
|
||||
|
||||
if (!visitorChannel.isWritable()) {
|
||||
//自己不可写,通道可以读,让通道关闭读
|
||||
//自己可写,通道不可以读,让通道打开读
|
||||
if (ctx.channel().config().isAutoRead()) {
|
||||
ctx.channel().config().setAutoRead(false);
|
||||
}
|
||||
} else {
|
||||
if (ctx.channel().config().isAutoRead()) {
|
||||
ctx.channel().config().setAutoRead(true);
|
||||
}
|
||||
}
|
||||
|
||||
// InetSocketAddress address = new InetSocketAddress(udpBaseInfo.getVisitorIp(), udpBaseInfo.getVisitorPort());
|
||||
// ByteBuf byteBuf = Unpooled.copiedBuffer(proxyMessage.getData());
|
||||
// visitorChannel.writeAndFlush(new DatagramPacket(byteBuf, address));
|
||||
InetSocketAddress address = ctx.channel().attr(Constants.SENDER).get();
|
||||
if (null != address) {
|
||||
visitorChannel.writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer(proxyMessage.getData()), address));
|
||||
}
|
||||
|
||||
// 增加流量计数(TODO 如果UDP映射服务端端口修改,这个似乎不准)
|
||||
Integer licenseId = visitorChannel.attr(Constants.LICENSE_ID).get();
|
||||
if (null != licenseId) {
|
||||
Solon.context().getBean(FlowReportService.class).addReadByte(licenseId, proxyMessage.getData().length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return ProxyDataTypeEnum.UDP_TRANSFER.getDesc();
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,23 @@
|
||||
package fun.asgc.neutrino.proxy.server.service;
|
||||
package org.dromara.neutrinoproxy.server.service;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||
import com.github.pagehelper.Page;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
|
||||
import fun.asgc.neutrino.proxy.server.base.rest.SystemContextHolder;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.ClientConnectRecordListReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.ClientConnectRecordListRes;
|
||||
import fun.asgc.neutrino.proxy.server.dal.ClientConnectRecordMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.LicenseMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.UserMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.ClientConnectRecordDO;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.LicenseDO;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
|
||||
import com.baomidou.mybatisplus.solon.plugins.pagination.Page;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import ma.glasnost.orika.MapperFacade;
|
||||
import org.apache.ibatis.solon.annotation.Db;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
|
||||
import org.dromara.neutrinoproxy.server.base.rest.SystemContextHolder;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.log.ClientConnectRecordListReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.log.ClientConnectRecordListRes;
|
||||
import org.dromara.neutrinoproxy.server.dal.ClientConnectRecordMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.LicenseMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.UserMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.ClientConnectRecordDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.LicenseDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -35,8 +32,6 @@ import java.util.stream.Collectors;
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ClientConnectRecordService {
|
||||
@Inject
|
||||
private MapperFacade mapperFacade;
|
||||
@Db
|
||||
private ClientConnectRecordMapper clientConnectRecordMapper;
|
||||
@Db
|
||||
@@ -49,18 +44,18 @@ public class ClientConnectRecordService {
|
||||
}
|
||||
|
||||
public PageInfo<ClientConnectRecordListRes> page(PageQuery pageQuery, ClientConnectRecordListReq req) {
|
||||
Page<ClientConnectRecordListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
|
||||
List<ClientConnectRecordDO> list = clientConnectRecordMapper.selectList(new LambdaQueryWrapper<ClientConnectRecordDO>()
|
||||
.orderByDesc(ClientConnectRecordDO::getId)
|
||||
Page<ClientConnectRecordDO> pageResult = clientConnectRecordMapper.selectPage(new Page<>(pageQuery.getCurrent(), pageQuery.getSize()), new LambdaQueryWrapper<ClientConnectRecordDO>()
|
||||
.eq(null != req.getLicenseId(), ClientConnectRecordDO::getLicenseId, req.getLicenseId())
|
||||
.orderByDesc(ClientConnectRecordDO::getId)
|
||||
);
|
||||
List<ClientConnectRecordListRes> respList = mapperFacade.mapAsList(list, ClientConnectRecordListRes.class);
|
||||
if (CollectionUtils.isEmpty(list)) {
|
||||
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
|
||||
List<ClientConnectRecordListRes> respList = pageResult.getRecords().stream().map(ClientConnectRecordDO::toRes).collect(Collectors.toList());
|
||||
if (CollectionUtils.isEmpty(pageResult.getRecords())) {
|
||||
return PageInfo.of(respList, pageResult.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
|
||||
}
|
||||
Set<Integer> licenseIds = respList.stream().map(ClientConnectRecordListRes::getLicenseId).collect(Collectors.toSet());
|
||||
List<LicenseDO> licenseList = licenseMapper.findByIds(licenseIds);
|
||||
if (CollectionUtil.isEmpty(licenseList)) {
|
||||
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
|
||||
return PageInfo.of(respList, pageResult.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
|
||||
}
|
||||
Set<Integer> userIds = licenseList.stream().map(LicenseDO::getUserId).collect(Collectors.toSet());
|
||||
List<UserDO> userList = userMapper.findByIds(userIds);
|
||||
@@ -84,6 +79,6 @@ public class ClientConnectRecordService {
|
||||
item.setMsg("******");
|
||||
}
|
||||
});
|
||||
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
|
||||
return PageInfo.of(respList, pageResult);
|
||||
}
|
||||
}
|
||||
@@ -19,9 +19,9 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.service;
|
||||
package org.dromara.neutrinoproxy.server.service;
|
||||
|
||||
import fun.asgc.neutrino.proxy.core.util.LockUtil;
|
||||
import org.dromara.neutrinoproxy.core.util.LockUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.noear.solon.annotation.Component;
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
package org.dromara.neutrinoproxy.server.service;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.solon.plugins.pagination.Page;
|
||||
import com.google.common.collect.Lists;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.solon.annotation.Db;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
|
||||
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.ExceptionConstant;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.JobInfoExecuteReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.JobInfoListReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.JobInfoUpdateEnableStatusReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.JobInfoUpdateReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.JobInfoExecuteRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.JobInfoListRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.JobInfoUpdateEnableStatusRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.JobInfoUpdateRes;
|
||||
import org.dromara.neutrinoproxy.server.dal.JobInfoMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.JobInfoDO;
|
||||
import org.dromara.neutrinoproxy.server.job.DataCleanJob;
|
||||
import org.dromara.neutrinoproxy.server.job.DemoJob;
|
||||
import org.dromara.neutrinoproxy.server.job.FlowReportForDayJob;
|
||||
import org.dromara.neutrinoproxy.server.job.FlowReportForHourJob;
|
||||
import org.dromara.neutrinoproxy.server.job.FlowReportForMinuteJob;
|
||||
import org.dromara.neutrinoproxy.server.job.FlowReportForMonthJob;
|
||||
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
|
||||
import org.dromara.solonplugins.job.IJobHandler;
|
||||
import org.dromara.solonplugins.job.IJobSource;
|
||||
import org.dromara.solonplugins.job.JobInfo;
|
||||
import org.dromara.solonplugins.job.impl.JobExecutor;
|
||||
import org.noear.solon.Solon;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Init;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/9/5
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class JobInfoService implements IJobSource {
|
||||
@Db
|
||||
private JobInfoMapper jobInfoMapper;
|
||||
@Inject
|
||||
private DataCleanJob dataCleanJob;
|
||||
@Inject
|
||||
private DemoJob demoJob;
|
||||
@Inject
|
||||
private FlowReportForDayJob flowReportForDayJob;
|
||||
@Inject
|
||||
private FlowReportForHourJob flowReportForHourJob;
|
||||
@Inject
|
||||
private FlowReportForMinuteJob flowReportForMinuteJob;
|
||||
@Inject
|
||||
private FlowReportForMonthJob flowReportForMonthJob;
|
||||
private Map<String, IJobHandler> jobHandlerMap = new HashMap<>();
|
||||
|
||||
@Init
|
||||
public void init() {
|
||||
jobHandlerMap.put("DataCleanJob", dataCleanJob);
|
||||
jobHandlerMap.put("DemoJob", demoJob);
|
||||
jobHandlerMap.put("FlowReportForDayJob", flowReportForDayJob);
|
||||
jobHandlerMap.put("FlowReportForHourJob", flowReportForHourJob);
|
||||
jobHandlerMap.put("FlowReportForMinuteJob", flowReportForMinuteJob);
|
||||
jobHandlerMap.put("FlowReportForMonthJob", flowReportForMonthJob);
|
||||
}
|
||||
|
||||
public PageInfo<JobInfoListRes> page(PageQuery pageQuery, JobInfoListReq req) {
|
||||
Page<JobInfoDO> page = jobInfoMapper.selectPage(new Page<>(pageQuery.getCurrent(), pageQuery.getSize()), new LambdaQueryWrapper<JobInfoDO>()
|
||||
.orderByAsc(JobInfoDO::getId)
|
||||
);
|
||||
List<JobInfoListRes> respList = page.getRecords().stream().map(JobInfoDO::toRes).collect(Collectors.toList());
|
||||
return PageInfo.of(respList, page);
|
||||
}
|
||||
|
||||
public List<JobInfoDO> findList() {
|
||||
List<JobInfoDO> jobInfoDOList = jobInfoMapper.findList();
|
||||
return jobInfoDOList;
|
||||
}
|
||||
|
||||
public JobInfoUpdateEnableStatusRes updateEnableStatus(JobInfoUpdateEnableStatusReq req) {
|
||||
JobInfoDO jobInfoDO = jobInfoMapper.findById(req.getId());
|
||||
ParamCheckUtil.checkNotNull(jobInfoDO, ExceptionConstant.JOB_INFO_NOT_EXIST);
|
||||
jobInfoMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
|
||||
if (EnableStatusEnum.ENABLE.getStatus().equals(req.getEnable())) {
|
||||
Solon.context().getBean(JobExecutor.class).startById(String.valueOf(req.getId()));
|
||||
} else {
|
||||
Solon.context().getBean(JobExecutor.class).stopById(String.valueOf(req.getId()));
|
||||
}
|
||||
return new JobInfoUpdateEnableStatusRes();
|
||||
}
|
||||
|
||||
public JobInfoExecuteRes execute(JobInfoExecuteReq req) {
|
||||
Solon.context().getBean(JobExecutor.class).triggerById(String.valueOf(req.getId()), req.getParam());
|
||||
return new JobInfoExecuteRes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JobInfo> sourceList() {
|
||||
List<JobInfo> jobInfoList = Lists.newArrayList();
|
||||
List<JobInfoDO> jobInfoDOList = jobInfoMapper.findList();
|
||||
if (CollectionUtil.isEmpty(jobInfoDOList)) {
|
||||
return jobInfoList;
|
||||
}
|
||||
for (JobInfoDO item : jobInfoDOList) {
|
||||
jobInfoList.add(new JobInfo()
|
||||
.setId(String.valueOf(item.getId()))
|
||||
.setName(item.getHandler())
|
||||
.setDesc(item.getDesc())
|
||||
.setCron(item.getCron())
|
||||
.setParam(item.getParam())
|
||||
.setEnable(EnableStatusEnum.ENABLE.getStatus().equals(item.getEnable()))
|
||||
.setJobHandler(jobHandlerMap.get(item.getHandler()))
|
||||
);
|
||||
}
|
||||
|
||||
return jobInfoList;
|
||||
}
|
||||
|
||||
public JobInfoUpdateRes update(JobInfoUpdateReq req) {
|
||||
JobInfoDO jobInfoDO = jobInfoMapper.findById(req.getId());
|
||||
ParamCheckUtil.checkNotNull(jobInfoDO, ExceptionConstant.PORT_MAPPING_NOT_EXIST);
|
||||
JobInfoDO jobInfo = new JobInfoDO();
|
||||
jobInfo.setId(req.getId());
|
||||
jobInfo.setCron(req.getCron());
|
||||
jobInfo.setDesc(req.getDesc());
|
||||
jobInfo.setAlarmEmail(req.getAlarmEmail());
|
||||
jobInfo.setAlarmDing(req.getAlarmDing());
|
||||
jobInfo.setParam(req.getParam());
|
||||
jobInfo.setUpdateTime(new Date());
|
||||
|
||||
jobInfoMapper.updateById(jobInfo);
|
||||
return new JobInfoUpdateRes();
|
||||
}
|
||||
}
|
||||
@@ -19,28 +19,26 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.service;
|
||||
package org.dromara.neutrinoproxy.server.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.github.pagehelper.Page;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.JobLogListReq;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.JobLogListRes;
|
||||
import fun.asgc.neutrino.proxy.server.dal.JobLogMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.JobLogDO;
|
||||
import fun.asgc.solon.extend.job.IJobCallback;
|
||||
import fun.asgc.solon.extend.job.JobInfo;
|
||||
import com.baomidou.mybatisplus.solon.plugins.pagination.Page;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import ma.glasnost.orika.MapperFacade;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.ibatis.solon.annotation.Db;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.log.JobLogListReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.log.JobLogListRes;
|
||||
import org.dromara.neutrinoproxy.server.dal.JobLogMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.JobLogDO;
|
||||
import org.dromara.solonplugins.job.IJobCallback;
|
||||
import org.dromara.solonplugins.job.JobInfo;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -50,8 +48,6 @@ import java.util.List;
|
||||
@Slf4j
|
||||
@Component
|
||||
public class JobLogService implements IJobCallback {
|
||||
@Inject
|
||||
private MapperFacade mapperFacade;
|
||||
@Db
|
||||
private JobLogMapper jobLogMapper;
|
||||
|
||||
@@ -60,11 +56,11 @@ public class JobLogService implements IJobCallback {
|
||||
Integer code = 0;
|
||||
String msg = "";
|
||||
if (null == throwable) {
|
||||
msg = "执行成功";
|
||||
log.info("job[id={},name={}]执行完毕", jobInfo.getId(), jobInfo.getName());
|
||||
msg = "execute success";
|
||||
log.debug("job[id={},name={}]execute success", jobInfo.getId(), jobInfo.getName());
|
||||
} else {
|
||||
log.error("job[id={},name={}]执行异常", jobInfo.getId(), jobInfo.getName(), throwable);
|
||||
msg = "执行异常:\r\n" + ExceptionUtils.getStackTrace(throwable);
|
||||
log.error("job[id={},name={}]execute error", jobInfo.getId(), jobInfo.getName(), throwable);
|
||||
msg = "execute error:\r\n" + ExceptionUtils.getStackTrace(throwable);
|
||||
code = -1;
|
||||
}
|
||||
jobLogMapper.insert(new JobLogDO()
|
||||
@@ -79,13 +75,12 @@ public class JobLogService implements IJobCallback {
|
||||
}
|
||||
|
||||
public PageInfo<JobLogListRes> page(PageQuery pageQuery, JobLogListReq req) {
|
||||
Page<JobLogListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
|
||||
List<JobLogDO> list = jobLogMapper.selectList(new LambdaQueryWrapper<JobLogDO>()
|
||||
.eq(null != req.getJobId(), JobLogDO::getJobId, req.getJobId())
|
||||
.orderByDesc(JobLogDO::getId)
|
||||
);
|
||||
List<JobLogListRes> respList = mapperFacade.mapAsList(list, JobLogListRes.class);
|
||||
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
|
||||
Page<JobLogDO> page = jobLogMapper.selectPage(new Page<>(pageQuery.getCurrent(), pageQuery.getSize()), new LambdaQueryWrapper<JobLogDO>()
|
||||
.eq(null != req.getJobId(), JobLogDO::getJobId, req.getJobId())
|
||||
.orderByDesc(JobLogDO::getId)
|
||||
);
|
||||
List<JobLogListRes> respList = page.getRecords().stream().map(JobLogDO::toRes).collect(Collectors.toList());
|
||||
return PageInfo.of(respList, page);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
package org.dromara.neutrinoproxy.server.service;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||
import com.baomidou.mybatisplus.solon.plugins.pagination.Page;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.apache.ibatis.solon.annotation.Db;
|
||||
import org.dromara.neutrinoproxy.server.base.db.DBInitialize;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
|
||||
import org.dromara.neutrinoproxy.server.base.rest.ServiceException;
|
||||
import org.dromara.neutrinoproxy.server.base.rest.SystemContextHolder;
|
||||
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.ExceptionConstant;
|
||||
import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.proxy.LicenseCreateReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.proxy.LicenseListReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.proxy.LicenseUpdateEnableStatusReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.proxy.LicenseUpdateReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.proxy.LicenseCreateRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.proxy.LicenseDetailRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.proxy.LicenseListRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.proxy.LicenseUpdateEnableStatusRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.proxy.LicenseUpdateRes;
|
||||
import org.dromara.neutrinoproxy.server.dal.LicenseMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.PortMappingMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.UserMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.LicenseDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.PortMappingDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
|
||||
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Init;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
import org.noear.solon.core.bean.LifecycleBean;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* license服务
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/8/6
|
||||
*/
|
||||
@Component
|
||||
public class LicenseService implements LifecycleBean {
|
||||
@Db
|
||||
private LicenseMapper licenseMapper;
|
||||
@Db
|
||||
private PortMappingMapper portMappingMapper;
|
||||
@Db
|
||||
private UserMapper userMapper;
|
||||
@Inject
|
||||
private VisitorChannelService visitorChannelService;
|
||||
@Inject
|
||||
private DBInitialize dbInitialize;
|
||||
|
||||
public PageInfo<LicenseListRes> page(PageQuery pageQuery, LicenseListReq req) {
|
||||
Page<LicenseDO> page = licenseMapper.selectPage(new Page<>(pageQuery.getCurrent(), pageQuery.getSize()), new LambdaQueryWrapper<LicenseDO>()
|
||||
.eq(req.getUserId() != null, LicenseDO::getUserId, req.getUserId())
|
||||
.eq(req.getIsOnline() != null, LicenseDO::getIsOnline, req.getIsOnline())
|
||||
.eq(req.getEnable() != null, LicenseDO::getEnable, req.getEnable())
|
||||
.orderByAsc(Arrays.asList(LicenseDO::getUserId, LicenseDO::getId))
|
||||
);
|
||||
List<LicenseListRes> respList = page.getRecords().stream().map(LicenseDO::toRes).collect(Collectors.toList());
|
||||
if (CollectionUtils.isEmpty(page.getRecords())) {
|
||||
return PageInfo.of(respList, page);
|
||||
}
|
||||
if (!CollectionUtil.isEmpty(respList)) {
|
||||
Set<Integer> userIds = respList.stream().map(LicenseListRes::getUserId).collect(Collectors.toSet());
|
||||
List<UserDO> userList = userMapper.findByIds(userIds);
|
||||
Map<Integer, UserDO> userMap = userList.stream().collect(Collectors.toMap(UserDO::getId, Function.identity()));
|
||||
for (LicenseListRes item : respList) {
|
||||
UserDO userDO = userMap.get(item.getUserId());
|
||||
if (null != userDO) {
|
||||
item.setUserName(userDO.getName());
|
||||
}
|
||||
item.setKey(desensitization(item.getUserId(), item.getKey()));
|
||||
}
|
||||
}
|
||||
return PageInfo.of(respList, page);
|
||||
}
|
||||
|
||||
public List<LicenseListRes> list(LicenseListReq req) {
|
||||
List<LicenseDO> list = licenseMapper.selectList(new LambdaQueryWrapper<LicenseDO>()
|
||||
.eq(null != req.getEnable(), LicenseDO::getEnable, req.getEnable())
|
||||
);
|
||||
List<LicenseListRes> licenseList = assembleConvertLicenses(list);
|
||||
return licenseList;
|
||||
}
|
||||
|
||||
private List<LicenseListRes> assembleConvertLicenses(List<LicenseDO> list) {
|
||||
List<LicenseListRes> licenseList = list.stream().map(LicenseDO::toRes).collect(Collectors.toList());
|
||||
if (!CollectionUtil.isEmpty(licenseList)) {
|
||||
Set<Integer> userIds = licenseList.stream().map(LicenseListRes::getUserId).collect(Collectors.toSet());
|
||||
List<UserDO> userList = userMapper.findByIds(userIds);
|
||||
Map<Integer, UserDO> userMap = userList.stream().collect(Collectors.toMap(UserDO::getId, Function.identity()));
|
||||
for (LicenseListRes item : licenseList) {
|
||||
UserDO userDO = userMap.get(item.getUserId());
|
||||
if (null != userDO) {
|
||||
item.setUserName(userDO.getName());
|
||||
}
|
||||
item.setKey(desensitization(item.getUserId(), item.getKey()));
|
||||
}
|
||||
}
|
||||
return licenseList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建license
|
||||
*
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
public LicenseCreateRes create(LicenseCreateReq req) {
|
||||
LicenseDO licenseDO = licenseMapper.checkRepeat(req.getUserId(), req.getName());
|
||||
ParamCheckUtil.checkExpression(null == licenseDO, ExceptionConstant.LICENSE_NAME_CANNOT_REPEAT);
|
||||
|
||||
String key = UUID.randomUUID().toString().replaceAll("-", "");
|
||||
Date now = new Date();
|
||||
|
||||
licenseMapper.insert(new LicenseDO()
|
||||
.setName(req.getName())
|
||||
.setKey(key)
|
||||
.setUserId(req.getUserId())
|
||||
.setIsOnline(OnlineStatusEnum.OFFLINE.getStatus())
|
||||
.setEnable(EnableStatusEnum.ENABLE.getStatus())
|
||||
.setCreateTime(now)
|
||||
.setUpdateTime(now)
|
||||
);
|
||||
return new LicenseCreateRes();
|
||||
}
|
||||
|
||||
public LicenseUpdateRes update(LicenseUpdateReq req) {
|
||||
LicenseDO oldLicenseDO = licenseMapper.findById(req.getId());
|
||||
ParamCheckUtil.checkNotNull(oldLicenseDO, ExceptionConstant.LICENSE_NOT_EXIST);
|
||||
|
||||
LicenseDO licenseCheck = licenseMapper.checkRepeat(oldLicenseDO.getUserId(), req.getName(), Sets.newHashSet(oldLicenseDO.getId()));
|
||||
ParamCheckUtil.checkMustNull(licenseCheck, ExceptionConstant.LICENSE_NAME_CANNOT_REPEAT);
|
||||
|
||||
licenseMapper.update(req.getId(), req.getName(), new Date());
|
||||
return new LicenseUpdateRes();
|
||||
}
|
||||
|
||||
public LicenseDetailRes detail(Integer id) {
|
||||
LicenseDO licenseDO = licenseMapper.findById(id);
|
||||
if (null == licenseDO) {
|
||||
return null;
|
||||
}
|
||||
UserDO userDO = userMapper.findById(licenseDO.getUserId());
|
||||
String userName = "";
|
||||
if (null != userDO) {
|
||||
userName = userDO.getName();
|
||||
}
|
||||
return new LicenseDetailRes()
|
||||
.setId(licenseDO.getId())
|
||||
.setName(licenseDO.getName())
|
||||
.setKey(desensitization(licenseDO.getUserId(), licenseDO.getKey()))
|
||||
.setUserId(licenseDO.getUserId())
|
||||
.setUserName(userName)
|
||||
.setIsOnline(licenseDO.getIsOnline())
|
||||
.setEnable(licenseDO.getEnable())
|
||||
.setCreateTime(licenseDO.getCreateTime())
|
||||
.setUpdateTime(licenseDO.getUpdateTime())
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新license启用状态
|
||||
*
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
public LicenseUpdateEnableStatusRes updateEnableStatus(LicenseUpdateEnableStatusReq req) {
|
||||
licenseMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
|
||||
// 更新VisitorChannel
|
||||
visitorChannelService.updateVisitorChannelByLicenseId(req.getId(), req.getEnable());
|
||||
return new LicenseUpdateEnableStatusRes();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除license
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
public void delete(Integer id) {
|
||||
List<PortMappingDO> portMappingDOList = portMappingMapper.findListByLicenseId(id);
|
||||
if (CollectionUtil.isNotEmpty(portMappingDOList)) {
|
||||
throw ServiceException.create(ExceptionConstant.LICENSE_CANNOT_BE_DELETED, portMappingDOList.size());
|
||||
}
|
||||
licenseMapper.deleteById(id);
|
||||
// 更新VisitorChannel
|
||||
visitorChannelService.updateVisitorChannelByLicenseId(id, EnableStatusEnum.DISABLE.getStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置license
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
public void reset(Integer id) {
|
||||
String key = UUID.randomUUID().toString().replaceAll("-", "");
|
||||
Date now = new Date();
|
||||
|
||||
licenseMapper.reset(id, key, now);
|
||||
}
|
||||
|
||||
public LicenseDO findByKey(String license) {
|
||||
return licenseMapper.findByKey(license);
|
||||
}
|
||||
|
||||
/**
|
||||
* 脱敏处理
|
||||
* 非当前登录人的license,一律脱敏
|
||||
*
|
||||
* @param userId
|
||||
* @param licenseKey
|
||||
* @return
|
||||
*/
|
||||
private String desensitization(Integer userId, String licenseKey) {
|
||||
Integer currentUserId = SystemContextHolder.getUser().getId();
|
||||
if (currentUserId.equals(userId)) {
|
||||
return licenseKey;
|
||||
}
|
||||
return licenseKey.substring(0, 10) + "****" + licenseKey.substring(licenseKey.length() - 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务端项目停止、启动时,更新在线状态为离线
|
||||
*/
|
||||
@Init
|
||||
public void init() {
|
||||
licenseMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void start() throws Throwable {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务端项目停止、启动时,更新在线状态为离线
|
||||
*/
|
||||
@Override
|
||||
public void stop() throws Throwable {
|
||||
licenseMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前角色下的license,若为管理员 则返回全部license
|
||||
*/
|
||||
public List<LicenseListRes> queryCurUserLicense(LicenseListReq req) {
|
||||
if (SystemContextHolder.isAdmin()) {
|
||||
return this.list(req);
|
||||
}
|
||||
|
||||
List<LicenseDO> list = licenseMapper.selectList(new LambdaQueryWrapper<LicenseDO>()
|
||||
.eq(LicenseDO::getEnable, EnableStatusEnum.ENABLE.getStatus())
|
||||
.eq(LicenseDO::getUserId, SystemContextHolder.getUserId())
|
||||
);
|
||||
List<LicenseListRes> licenseList = assembleConvertLicenses(list);
|
||||
return licenseList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package org.dromara.neutrinoproxy.server.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.solon.plugins.pagination.Page;
|
||||
import org.apache.ibatis.solon.annotation.Db;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
|
||||
import org.dromara.neutrinoproxy.server.base.rest.ServiceException;
|
||||
import org.dromara.neutrinoproxy.server.base.rest.SystemContextHolder;
|
||||
import org.dromara.neutrinoproxy.server.constant.Constants;
|
||||
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.ExceptionConstant;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.PortGroupCreateReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.PortGroupListReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.PortGroupUpdateEnableStatusReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.PortGroupCreateRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.PortGroupListRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.PortGroupUpdateEnableStatusRes;
|
||||
import org.dromara.neutrinoproxy.server.dal.PortGroupMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.PortPoolMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.PortGroupDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.PortPoolDO;
|
||||
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
|
||||
import org.noear.solon.annotation.Component;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 端口分组服务
|
||||
*/
|
||||
@Component
|
||||
public class PortGroupService {
|
||||
|
||||
@Db
|
||||
private PortGroupMapper portGroupMapper;
|
||||
|
||||
@Db
|
||||
private PortPoolMapper portPoolMapper;
|
||||
|
||||
public PortGroupCreateRes create(PortGroupCreateReq req) {
|
||||
PortGroupDO portGroupDO = portGroupMapper.selectOne(Wrappers.lambdaQuery(PortGroupDO.class)
|
||||
.eq(PortGroupDO::getName, req.getName()));
|
||||
if (Objects.nonNull(portGroupDO)) {
|
||||
throw ServiceException.create(ExceptionConstant.PORT_GROUP_NAME_ALREADY_EXIST, req.getName());
|
||||
}
|
||||
Date now = new Date();
|
||||
portGroupDO = new PortGroupDO();
|
||||
portGroupDO.setName(req.getName());
|
||||
portGroupDO.setPossessorType(req.getPossessorType());
|
||||
portGroupDO.setPossessorId(req.getPossessorId());
|
||||
portGroupDO.setEnable(EnableStatusEnum.ENABLE.getStatus());
|
||||
portGroupDO.setCreateTime(now);
|
||||
portGroupDO.setUpdateTime(now);
|
||||
portGroupMapper.insert(portGroupDO);
|
||||
return new PortGroupCreateRes();
|
||||
}
|
||||
|
||||
public PageInfo<PortGroupListRes> page(PageQuery pageQuery, PortGroupListReq req) {
|
||||
Page<PortGroupListRes> page = new Page<>(pageQuery.getCurrent(), pageQuery.getSize());
|
||||
List<PortGroupListRes> list = portGroupMapper.selectPortGroupListResList(page, req);
|
||||
|
||||
return PageInfo.of(list, page.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
|
||||
}
|
||||
|
||||
public List<PortGroupListRes> list(PortGroupListReq req) {
|
||||
List<PortGroupListRes> list = portGroupMapper.selectPortGroupListResList(null, req);
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
public PortGroupUpdateEnableStatusRes updateEnableStatus(PortGroupUpdateEnableStatusReq req) {
|
||||
PortGroupDO portGroupDO = portGroupMapper.selectById(req.getId());
|
||||
ParamCheckUtil.checkNotNull(portGroupDO, ExceptionConstant.PORT_GROUP_NAME_DOES_NOT_EXIST);
|
||||
if (!SystemContextHolder.isAdmin()) {
|
||||
ParamCheckUtil.checkExpression(false, ExceptionConstant.NO_PERMISSION_VISIT);
|
||||
}
|
||||
portGroupMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
|
||||
return new PortGroupUpdateEnableStatusRes();
|
||||
}
|
||||
|
||||
public void delete(Integer id) {
|
||||
if (id == Constants.DEFAULT_PORT_GROUP_ID) {
|
||||
throw ServiceException.create(ExceptionConstant.DEFAULT_GROUP_FORBID_DELETE);
|
||||
}
|
||||
PortGroupDO portGroupDO = portGroupMapper.selectById(id);
|
||||
//检验分组是否存在
|
||||
ParamCheckUtil.checkNotNull(portGroupDO, ExceptionConstant.PORT_GROUP_NAME_DOES_NOT_EXIST);
|
||||
//删除
|
||||
portGroupMapper.deleteById(id);
|
||||
//修改绑定此分组的端口到默认分组
|
||||
portPoolMapper.update(null, Wrappers.lambdaUpdate(PortPoolDO.class)
|
||||
.eq(PortPoolDO::getGroupId, portGroupDO.getId())
|
||||
.set(PortPoolDO::getGroupId, Constants.DEFAULT_PORT_GROUP_ID)
|
||||
.set(PortPoolDO::getUpdateTime, new Date())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
package org.dromara.neutrinoproxy.server.service;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||
import com.baomidou.mybatisplus.solon.plugins.pagination.Page;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.apache.ibatis.solon.annotation.Db;
|
||||
import org.dromara.neutrinoproxy.server.base.db.DBInitialize;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
|
||||
import org.dromara.neutrinoproxy.server.base.proxy.ProxyConfig;
|
||||
import org.dromara.neutrinoproxy.server.base.rest.SystemContextHolder;
|
||||
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.ExceptionConstant;
|
||||
import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.proxy.PortMappingCreateReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.proxy.PortMappingListReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.proxy.PortMappingUpdateEnableStatusReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.proxy.PortMappingUpdateReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.proxy.PortMappingCreateRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.proxy.PortMappingDetailRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.proxy.PortMappingListRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.proxy.PortMappingUpdateEnableStatusRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.proxy.PortMappingUpdateRes;
|
||||
import org.dromara.neutrinoproxy.server.dal.LicenseMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.PortMappingMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.PortPoolMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.UserMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.LicenseDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.PortMappingDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.PortPoolDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
|
||||
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
|
||||
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Init;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
import org.noear.solon.core.bean.LifecycleBean;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/8/8
|
||||
*/
|
||||
@Component
|
||||
public class PortMappingService implements LifecycleBean {
|
||||
@Db
|
||||
private PortMappingMapper portMappingMapper;
|
||||
@Db
|
||||
private LicenseMapper licenseMapper;
|
||||
@Db
|
||||
private UserMapper userMapper;
|
||||
@Db
|
||||
private PortPoolMapper portPoolMapper;
|
||||
@Inject
|
||||
private VisitorChannelService visitorChannelService;
|
||||
|
||||
@Inject
|
||||
private PortPoolService portPoolService;
|
||||
@Inject
|
||||
private ProxyConfig proxyConfig;
|
||||
@Inject
|
||||
private DBInitialize dbInitialize;
|
||||
|
||||
public PageInfo<PortMappingListRes> page(PageQuery pageQuery, PortMappingListReq req) {
|
||||
if (StringUtils.isNotEmpty(req.getDescription())) {
|
||||
//描述字段为模糊查询,在应用层处理,否则sqlite不支持
|
||||
req.setDescription("%" + req.getDescription() + "%");
|
||||
}
|
||||
|
||||
// 协议名称转换
|
||||
if (StringUtils.isNotBlank(req.getProtocal())) {
|
||||
NetworkProtocolEnum networkProtocolEnum = NetworkProtocolEnum.of(req.getProtocal());
|
||||
req.setProtocal(networkProtocolEnum.getDesc());
|
||||
}
|
||||
|
||||
Page<PortMappingDO> page = new Page<>(pageQuery.getCurrent(), pageQuery.getSize());
|
||||
List<PortMappingDO> list = portMappingMapper.selectPortMappingByCondition(page, req);
|
||||
List<PortMappingListRes> respList = list.stream().map(PortMappingDO::toRes).collect(Collectors.toList());
|
||||
if (CollectionUtils.isEmpty(list)) {
|
||||
return PageInfo.of(respList, page.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
|
||||
}
|
||||
|
||||
Set<Integer> licenseIds = respList.stream().map(PortMappingListRes::getLicenseId).collect(Collectors.toSet());
|
||||
List<LicenseDO> licenseList = licenseMapper.findByIds(licenseIds);
|
||||
if (CollectionUtil.isEmpty(licenseList)) {
|
||||
return PageInfo.of(respList, page.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
|
||||
}
|
||||
Set<Integer> userIds = licenseList.stream().map(LicenseDO::getUserId).collect(Collectors.toSet());
|
||||
List<UserDO> userList = userMapper.findByIds(userIds);
|
||||
Map<Integer, LicenseDO> licenseMap = licenseList.stream().collect(Collectors.toMap(LicenseDO::getId, Function.identity()));
|
||||
Map<Integer, UserDO> userMap = userList.stream().collect(Collectors.toMap(UserDO::getId, Function.identity()));
|
||||
|
||||
respList.forEach(item -> {
|
||||
LicenseDO license = licenseMap.get(item.getLicenseId());
|
||||
if (null == license) {
|
||||
return;
|
||||
}
|
||||
item.setLicenseName(license.getName());
|
||||
item.setUserId(license.getUserId());
|
||||
UserDO user = userMap.get(license.getUserId());
|
||||
if (null == user) {
|
||||
return;
|
||||
}
|
||||
item.setUserName(user.getName());
|
||||
if (StrUtil.isNotBlank(proxyConfig.getServer().getTcp().getDomainName()) && StrUtil.isNotBlank(item.getSubdomain())) {
|
||||
item.setDomain(item.getSubdomain() + "." + proxyConfig.getServer().getTcp().getDomainName());
|
||||
}
|
||||
if (NetworkProtocolEnum.HTTP.getDesc().equals(item.getProtocal())) {
|
||||
item.setProtocal("HTTP(S)");
|
||||
}
|
||||
});
|
||||
//sorted [userId asc] [licenseId asc] [createTime asc]
|
||||
respList = respList.stream().sorted(Comparator.comparing(PortMappingListRes::getUserId).thenComparing(PortMappingListRes::getLicenseId).thenComparing(PortMappingListRes::getCreateTime)).collect(Collectors.toList());
|
||||
return PageInfo.of(respList, page.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
|
||||
}
|
||||
|
||||
public PortMappingCreateRes create(PortMappingCreateReq req) {
|
||||
LicenseDO licenseDO = licenseMapper.findById(req.getLicenseId());
|
||||
ParamCheckUtil.checkNotNull(licenseDO, ExceptionConstant.LICENSE_NOT_EXIST);
|
||||
if (!SystemContextHolder.isAdmin()) {
|
||||
// 临时处理,如果当前用户不是管理员,则操作userId不能为1
|
||||
ParamCheckUtil.checkExpression(!licenseDO.getUserId().equals(1), ExceptionConstant.NO_PERMISSION_VISIT);
|
||||
}
|
||||
PortPoolDO portPoolDO = portPoolMapper.findByPort(req.getServerPort());
|
||||
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
|
||||
ParamCheckUtil.checkExpression(null == portMappingMapper.findByPort(req.getServerPort(), null), ExceptionConstant.PORT_CANNOT_REPEAT_MAPPING, req.getServerPort());
|
||||
ParamCheckUtil.checkExpression(!portMappingMapper.checkRepeatBySubdomain(req.getSubdomain(), null), ExceptionConstant.PORT_MAPPING_SUBDONAME_CONNOT_REPEAT);
|
||||
|
||||
Date now = new Date();
|
||||
PortMappingDO portMappingDO = new PortMappingDO();
|
||||
portMappingDO.setLicenseId(req.getLicenseId());
|
||||
portMappingDO.setProtocal(req.getProtocal());
|
||||
portMappingDO.setSubdomain(req.getSubdomain());
|
||||
portMappingDO.setServerPort(req.getServerPort());
|
||||
portMappingDO.setClientIp(req.getClientIp());
|
||||
portMappingDO.setClientPort(req.getClientPort());
|
||||
portMappingDO.setProxyResponses(req.getProxyResponses());
|
||||
portMappingDO.setProxyTimeoutMs(req.getProxyTimeoutMs());
|
||||
portMappingDO.setDescription(req.getDescription());
|
||||
portMappingDO.setIsOnline(OnlineStatusEnum.OFFLINE.getStatus());
|
||||
portMappingDO.setEnable(EnableStatusEnum.ENABLE.getStatus());
|
||||
portMappingDO.setCreateTime(now);
|
||||
portMappingDO.setUpdateTime(now);
|
||||
portMappingMapper.insert(portMappingDO);
|
||||
// 更新VisitorChannel
|
||||
visitorChannelService.addVisitorChannelByPortMapping(portMappingDO);
|
||||
// 更新域名映射
|
||||
if (NetworkProtocolEnum.isHttp(portMappingDO.getProtocal()) && StrUtil.isNotBlank(proxyConfig.getServer().getTcp().getDomainName()) && StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
|
||||
ProxyUtil.setSubdomainToServerPort(portMappingDO.getSubdomain(), portMappingDO.getServerPort());
|
||||
}
|
||||
return new PortMappingCreateRes();
|
||||
}
|
||||
|
||||
public PortMappingUpdateRes update(PortMappingUpdateReq req) {
|
||||
LicenseDO licenseDO = licenseMapper.findById(req.getLicenseId());
|
||||
ParamCheckUtil.checkNotNull(licenseDO, ExceptionConstant.LICENSE_NOT_EXIST);
|
||||
if (!SystemContextHolder.isAdmin()) {
|
||||
// 临时处理,如果当前用户不是管理员,则操作userId不能为1
|
||||
ParamCheckUtil.checkExpression(!licenseDO.getUserId().equals(1), ExceptionConstant.NO_PERMISSION_VISIT);
|
||||
}
|
||||
PortPoolDO portPoolDO = portPoolMapper.findByPort(req.getServerPort());
|
||||
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
|
||||
ParamCheckUtil.checkExpression(null == portMappingMapper.findByPort(req.getServerPort(), Sets.newHashSet(req.getId())), ExceptionConstant.PORT_CANNOT_REPEAT_MAPPING, req.getServerPort());
|
||||
ParamCheckUtil.checkExpression(!portMappingMapper.checkRepeatBySubdomain(req.getSubdomain(), Sets.newHashSet(req.getId())), ExceptionConstant.PORT_MAPPING_SUBDONAME_CONNOT_REPEAT);
|
||||
|
||||
// 查询原端口映射
|
||||
PortMappingDO oldPortMappingDO = portMappingMapper.findById(req.getId());
|
||||
ParamCheckUtil.checkNotNull(oldPortMappingDO, ExceptionConstant.PORT_MAPPING_NOT_EXIST);
|
||||
|
||||
PortMappingDO portMappingDO = new PortMappingDO();
|
||||
portMappingDO.setId(req.getId());
|
||||
portMappingDO.setProtocal(req.getProtocal());
|
||||
portMappingDO.setSubdomain(req.getSubdomain());
|
||||
portMappingDO.setLicenseId(req.getLicenseId());
|
||||
portMappingDO.setServerPort(req.getServerPort());
|
||||
portMappingDO.setClientIp(req.getClientIp());
|
||||
portMappingDO.setClientPort(req.getClientPort());
|
||||
portMappingDO.setProxyResponses(req.getProxyResponses());
|
||||
portMappingDO.setProxyTimeoutMs(req.getProxyTimeoutMs());
|
||||
portMappingDO.setDescription(req.getDescription());
|
||||
portMappingDO.setUpdateTime(new Date());
|
||||
portMappingDO.setEnable(EnableStatusEnum.ENABLE.getStatus());
|
||||
portMappingMapper.updateById(portMappingDO);
|
||||
// 更新VisitorChannel
|
||||
visitorChannelService.updateVisitorChannelByPortMapping(oldPortMappingDO, portMappingDO);
|
||||
// 删除老的域名映射
|
||||
if (NetworkProtocolEnum.isHttp(oldPortMappingDO.getProtocal()) && StrUtil.isNotBlank(oldPortMappingDO.getSubdomain())) {
|
||||
ProxyUtil.removeSubdomainToServerPort(oldPortMappingDO.getSubdomain());
|
||||
}
|
||||
// 更新域名映射
|
||||
if (NetworkProtocolEnum.isHttp(portMappingDO.getProtocal()) && StrUtil.isNotBlank(proxyConfig.getServer().getTcp().getDomainName()) && StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
|
||||
ProxyUtil.setSubdomainToServerPort(portMappingDO.getSubdomain(), portMappingDO.getServerPort());
|
||||
}
|
||||
return new PortMappingUpdateRes();
|
||||
}
|
||||
|
||||
public PortMappingDetailRes detail(Integer id) {
|
||||
PortMappingDO portMappingDO = portMappingMapper.findById(id);
|
||||
if (null == portMappingDO) {
|
||||
return null;
|
||||
}
|
||||
PortMappingDetailRes res = new PortMappingDetailRes()
|
||||
.setId(portMappingDO.getId())
|
||||
.setLicenseId(portMappingDO.getLicenseId())
|
||||
.setServerPort(portMappingDO.getServerPort())
|
||||
.setClientIp(portMappingDO.getClientIp())
|
||||
.setClientPort(portMappingDO.getClientPort())
|
||||
.setIsOnline(portMappingDO.getIsOnline())
|
||||
.setProxyTimeoutMs(portMappingDO.getProxyTimeoutMs())
|
||||
.setProxyResponses(portMappingDO.getProxyResponses())
|
||||
.setEnable(portMappingDO.getEnable())
|
||||
.setCreateTime(portMappingDO.getCreateTime())
|
||||
.setUpdateTime(portMappingDO.getUpdateTime());
|
||||
|
||||
LicenseDO license = licenseMapper.findById(portMappingDO.getLicenseId());
|
||||
if (null != license) {
|
||||
res.setLicenseName(license.getName());
|
||||
res.setUserId(license.getUserId());
|
||||
UserDO user = userMapper.findById(license.getUserId());
|
||||
if (null != user) {
|
||||
res.setUserName(user.getName());
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public PortMappingUpdateEnableStatusRes updateEnableStatus(PortMappingUpdateEnableStatusReq req) {
|
||||
PortMappingDO portMappingDO = portMappingMapper.findById(req.getId());
|
||||
ParamCheckUtil.checkNotNull(portMappingDO, ExceptionConstant.PORT_MAPPING_NOT_EXIST);
|
||||
|
||||
LicenseDO licenseDO = licenseMapper.findById(portMappingDO.getLicenseId());
|
||||
ParamCheckUtil.checkNotNull(licenseDO, ExceptionConstant.LICENSE_NOT_EXIST);
|
||||
if (!SystemContextHolder.isAdmin()) {
|
||||
ParamCheckUtil.checkExpression(!licenseDO.getUserId().equals(1), ExceptionConstant.NO_PERMISSION_VISIT);
|
||||
}
|
||||
|
||||
portMappingMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
|
||||
|
||||
// 更新VisitorChannel
|
||||
portMappingDO.setEnable(req.getEnable());
|
||||
if (EnableStatusEnum.ENABLE == EnableStatusEnum.of(req.getEnable())) {
|
||||
visitorChannelService.addVisitorChannelByPortMapping(portMappingDO);
|
||||
} else {
|
||||
visitorChannelService.removeVisitorChannelByPortMapping(portMappingDO);
|
||||
}
|
||||
|
||||
return new PortMappingUpdateEnableStatusRes();
|
||||
}
|
||||
|
||||
public void delete(Integer id) {
|
||||
PortMappingDO portMappingDO = portMappingMapper.findById(id);
|
||||
ParamCheckUtil.checkNotNull(portMappingDO, ExceptionConstant.PORT_MAPPING_NOT_EXIST);
|
||||
|
||||
LicenseDO licenseDO = licenseMapper.findById(portMappingDO.getLicenseId());
|
||||
if (null != licenseDO && !SystemContextHolder.isAdmin()) {
|
||||
// 临时处理,如果当前用户不是管理员,则操作userId不能为1
|
||||
ParamCheckUtil.checkExpression(!licenseDO.getUserId().equals(1), ExceptionConstant.NO_PERMISSION_VISIT);
|
||||
}
|
||||
|
||||
portMappingMapper.deleteById(id);
|
||||
|
||||
// 更新VisitorChannel
|
||||
visitorChannelService.removeVisitorChannelByPortMapping(portMappingDO);
|
||||
// 更新域名映射
|
||||
if (NetworkProtocolEnum.isHttp(portMappingDO.getProtocal()) && StrUtil.isNotBlank(portMappingDO.getSubdomain())) {
|
||||
ProxyUtil.removeSubdomainToServerPort(portMappingDO.getSubdomain());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据license查询可用的端口映射列表
|
||||
*
|
||||
* @param licenseId
|
||||
* @return
|
||||
*/
|
||||
public List<PortMappingDO> findEnableListByLicenseId(Integer licenseId) {
|
||||
return portMappingMapper.findEnableListByLicenseId(licenseId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务端项目停止、启动时,更新在线状态为离线
|
||||
*/
|
||||
@Init
|
||||
public void init() {
|
||||
portMappingMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
|
||||
|
||||
// 未配置域名,则不需要处理域名映射逻辑
|
||||
if (StrUtil.isBlank(proxyConfig.getServer().getTcp().getDomainName())) {
|
||||
return;
|
||||
}
|
||||
List<PortMappingDO> portMappingDOList = portMappingMapper.selectList(new LambdaQueryWrapper<PortMappingDO>().eq(PortMappingDO::getProtocal, NetworkProtocolEnum.HTTP.getDesc()).isNotNull(PortMappingDO::getSubdomain));
|
||||
if (CollectionUtil.isEmpty(portMappingDOList)) {
|
||||
return;
|
||||
}
|
||||
portMappingDOList.forEach(item -> {
|
||||
if (StrUtil.isBlank(item.getSubdomain())) {
|
||||
return;
|
||||
}
|
||||
ProxyUtil.setSubdomainToServerPort(item.getSubdomain(), item.getServerPort());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws Throwable {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务端项目停止、启动时,更新在线状态为离线
|
||||
*/
|
||||
@Override
|
||||
public void stop() throws Throwable {
|
||||
portMappingMapper.updateOnlineStatus(OnlineStatusEnum.OFFLINE.getStatus(), new Date());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
package org.dromara.neutrinoproxy.server.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.solon.plugins.pagination.Page;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.ibatis.solon.annotation.Db;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
|
||||
import org.dromara.neutrinoproxy.server.base.rest.ServiceException;
|
||||
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.ExceptionConstant;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.AvailablePortListReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.PortPoolCreateReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.PortPoolListReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.PortPoolUpdateEnableStatusReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.PortPoolUpdateGroupReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.PortPoolUpdateReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.PortPoolCreateRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.PortPoolListRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.PortPoolUpdateEnableStatusRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.PortPoolUpdateGroupRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.PortPoolUpdateRes;
|
||||
import org.dromara.neutrinoproxy.server.dal.LicenseMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.PortGroupMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.PortMappingMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.PortPoolMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.LicenseDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.PortGroupDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.PortMappingDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.PortPoolDO;
|
||||
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
|
||||
import org.dromara.neutrinoproxy.server.util.PortAvailableUtil;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.dromara.neutrinoproxy.server.constant.ExceptionConstant.PORT_CANNOT_REPEAT;
|
||||
import static org.dromara.neutrinoproxy.server.constant.ExceptionConstant.PORT_GROUP_NAME_DOES_NOT_EXIST;
|
||||
import static org.dromara.neutrinoproxy.server.constant.ExceptionConstant.PORT_RANGE_FAIL;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/8/7
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class PortPoolService {
|
||||
@Db
|
||||
private PortPoolMapper portPoolMapper;
|
||||
@Inject
|
||||
private VisitorChannelService visitorChannelService;
|
||||
|
||||
@Db
|
||||
private PortGroupMapper portGroupMapper;
|
||||
@Db
|
||||
private PortMappingMapper portMappingMapper;
|
||||
@Db
|
||||
private LicenseMapper licenseMapper;
|
||||
|
||||
public PageInfo<PortPoolListRes> page(PageQuery pageQuery, PortPoolListReq req) {
|
||||
Page<PortPoolListRes> page = new Page<>(pageQuery.getCurrent(), pageQuery.getSize());
|
||||
List<PortPoolListRes> list = portPoolMapper.selectResList(page, req);
|
||||
return PageInfo.of(list, page.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
|
||||
}
|
||||
|
||||
public List<PortPoolListRes> list(PortPoolListReq req) {
|
||||
List<PortPoolDO> list = portPoolMapper.selectList(new LambdaQueryWrapper<PortPoolDO>()
|
||||
.eq(PortPoolDO::getEnable, EnableStatusEnum.ENABLE.getStatus())
|
||||
);
|
||||
|
||||
List<PortPoolDO> resultList = this.filterUsedPorts(list);
|
||||
return resultList.stream().map(PortPoolDO::toRes).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<PortPoolDO> filterUsedPorts(List<PortPoolDO> list) {
|
||||
//Gets the used ports
|
||||
List<PortMappingDO> usePorts = portMappingMapper.selectList(new LambdaQueryWrapper<PortMappingDO>().orderByAsc(PortMappingDO::getId));
|
||||
|
||||
List<Integer> serverPorts = usePorts.stream().map(item -> item.getServerPort()).collect(Collectors.toList());
|
||||
|
||||
return list.stream().filter(item -> !serverPorts.contains(item.getPort())).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public PortPoolUpdateRes update(PortPoolUpdateReq req) {
|
||||
portPoolMapper.update(null, new LambdaUpdateWrapper<PortPoolDO>()
|
||||
.eq(PortPoolDO::getId, req.getId())
|
||||
.set(PortPoolDO::getGroupId, req.getGroupId())
|
||||
.set(PortPoolDO::getUpdateTime, new Date())
|
||||
);
|
||||
|
||||
return new PortPoolUpdateRes();
|
||||
}
|
||||
|
||||
public PortPoolCreateRes create(PortPoolCreateReq req) {
|
||||
Consumer<Integer> consumer = port -> {
|
||||
PortPoolDO oldPortPoolDO = portPoolMapper.findByPort(port);
|
||||
ParamCheckUtil.checkMustNull(oldPortPoolDO, PORT_CANNOT_REPEAT);
|
||||
PortGroupDO portGroupDO = portGroupMapper.selectById(req.getGroupId());
|
||||
ParamCheckUtil.checkNotNull(portGroupDO, PORT_GROUP_NAME_DOES_NOT_EXIST);
|
||||
Date now = new Date();
|
||||
portPoolMapper.insert(new PortPoolDO()
|
||||
.setPort(port)
|
||||
.setGroupId(req.getGroupId())
|
||||
.setEnable(EnableStatusEnum.ENABLE.getStatus())
|
||||
.setCreateTime(now)
|
||||
.setUpdateTime(now)
|
||||
);
|
||||
// 更新visitorChannel
|
||||
visitorChannelService.updateVisitorChannelByPortPool(port, EnableStatusEnum.ENABLE.getStatus());
|
||||
};
|
||||
String[] portArr = StringUtils.split(req.getPort(), "-");
|
||||
if(portArr.length == 1){
|
||||
Integer port = Integer.valueOf(portArr[0]);
|
||||
consumer.accept(port);
|
||||
}else if(portArr.length == 2){
|
||||
int min = Integer.parseInt(portArr[0]),max = Integer.parseInt(portArr[1]);
|
||||
for (int i = min; i <= max; i++) {
|
||||
try {
|
||||
consumer.accept(i);
|
||||
} catch (Exception e) {
|
||||
log.warn("bulk add port err:{}",e.getMessage());
|
||||
}
|
||||
}
|
||||
}else{
|
||||
throw ServiceException.create(PORT_RANGE_FAIL);
|
||||
}
|
||||
return new PortPoolCreateRes();
|
||||
}
|
||||
|
||||
public PortPoolUpdateEnableStatusRes updateEnableStatus(PortPoolUpdateEnableStatusReq req) {
|
||||
PortPoolDO portPoolDO = portPoolMapper.findById(req.getId());
|
||||
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
|
||||
portPoolMapper.updateEnableStatus(req.getId(), req.getEnable(), new Date());
|
||||
|
||||
// 更新visitorChannel
|
||||
visitorChannelService.updateVisitorChannelByPortPool(portPoolDO.getPort(), req.getEnable());
|
||||
|
||||
return new PortPoolUpdateEnableStatusRes();
|
||||
}
|
||||
|
||||
public void delete(Integer id) {
|
||||
PortPoolDO portPoolDO = portPoolMapper.findById(id);
|
||||
ParamCheckUtil.checkNotNull(portPoolDO, ExceptionConstant.PORT_NOT_EXIST);
|
||||
|
||||
portPoolMapper.deleteById(id);
|
||||
|
||||
// 更新visitorChannel
|
||||
visitorChannelService.updateVisitorChannelByPortPool(portPoolDO.getPort(), EnableStatusEnum.DISABLE.getStatus());
|
||||
}
|
||||
|
||||
public List<PortPoolListRes> portListByGroupId(String groupId) {
|
||||
List<PortPoolDO> portPoolDOList = portPoolMapper.getByGroupId(groupId);
|
||||
List<PortPoolListRes> portPoolListReList = portPoolDOList.stream().map(PortPoolDO::toRes).collect(Collectors.toList());
|
||||
return portPoolListReList;
|
||||
}
|
||||
|
||||
public PortPoolUpdateGroupRes updateGroup(PortPoolUpdateGroupReq req) {
|
||||
PortGroupDO portGroupDO = portGroupMapper.selectById(req.getGroupId());
|
||||
if (Objects.isNull(portGroupDO)) {
|
||||
throw ServiceException.create(ExceptionConstant.PARAMS_INVALID);
|
||||
}
|
||||
portPoolMapper.update(null, Wrappers.lambdaUpdate(PortPoolDO.class)
|
||||
.in(PortPoolDO::getId, req.getPortIdList())
|
||||
.set(PortPoolDO::getGroupId, req.getGroupId())
|
||||
.set(PortPoolDO::getUpdateTime, new Date())
|
||||
);
|
||||
return new PortPoolUpdateGroupRes();
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员: 全局端口 + 当前选择的用户独占端口 + 当前选择license独占端口
|
||||
* 游客:全局端口 + 当前选择用户独占端口 + 当前选择license独占端口
|
||||
* 非管理员身份时:下拉选择license,只能选当前用户下的LICENSE
|
||||
*/
|
||||
public PageInfo<PortPoolListRes> getAvailablePortList(AvailablePortListReq req) {
|
||||
|
||||
LicenseDO licenseDO = licenseMapper.queryById(req.getLicenseId());
|
||||
if(StringUtils.isNotEmpty(req.getKeyword())){
|
||||
req.setKeyword(req.getKeyword()+"%");
|
||||
}
|
||||
|
||||
Page<PortPoolListRes> page = new Page<>(req.getPage(), req.getSize());
|
||||
List<PortPoolListRes> portList = portPoolMapper.getAvailablePortList(page, req.getLicenseId(), licenseDO.getUserId(), req.getKeyword());
|
||||
|
||||
return PageInfo.of(portList, page.getTotal(), req.getPage(), req.getSize());
|
||||
}
|
||||
|
||||
public void deleteBatch(List<Integer> ids) {
|
||||
List<PortPoolDO> portPoolDOList = portPoolMapper.selectBatchIds(ids);
|
||||
ParamCheckUtil.checkNotNull(portPoolDOList, ExceptionConstant.PORT_NOT_EXIST);
|
||||
|
||||
portPoolMapper.deleteBatchIds(ids);
|
||||
// 更新visitorChannel
|
||||
portPoolDOList.stream().forEach(portPoolDO -> {
|
||||
visitorChannelService.updateVisitorChannelByPortPool(portPoolDO.getPort(), EnableStatusEnum.DISABLE.getStatus());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查端口是否被占用
|
||||
* 端口映射编辑时,如果端口号没有变动,则不验证。避免出现端口映射正在使用时,无法更新端口映射其他信息的问题
|
||||
* @param port
|
||||
* @param portMappingId
|
||||
* @return
|
||||
*/
|
||||
public boolean portAvailable(Integer port, Integer portMappingId) {
|
||||
if (null != portMappingId) {
|
||||
PortMappingDO portMappingDO = portMappingMapper.findById(portMappingId);
|
||||
if (null != portMappingDO && portMappingDO.getServerPort().equals(port)) {
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
}
|
||||
return PortAvailableUtil.isPortAvailable(port);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.dromara.neutrinoproxy.server.service;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.ProtocalListRes;
|
||||
import org.noear.solon.annotation.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/4/2
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ProtocalService {
|
||||
|
||||
/**
|
||||
* 获取协议列表
|
||||
* @return
|
||||
*/
|
||||
public List<ProtocalListRes> list() {
|
||||
return Lists.newArrayList(
|
||||
new ProtocalListRes().setName("TCP").setEnable(Boolean.TRUE).setRemark("支持一切TCP之上的协议"),
|
||||
new ProtocalListRes().setName("HTTP(S)").setEnable(Boolean.TRUE).setRemark("支持绑定子域名,未绑定时等价于时使用TCP。 若配置了证书,则同时支持HTTPS。"),
|
||||
new ProtocalListRes().setName("UDP").setEnable(Boolean.TRUE).setRemark("支持UDP转发、响应。需要配合响应数、超时时间使用")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,12 +19,12 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.service;
|
||||
package org.dromara.neutrinoproxy.server.service;
|
||||
|
||||
import fun.asgc.neutrino.proxy.server.constant.OnlineStatusEnum;
|
||||
import fun.asgc.neutrino.proxy.server.dal.LicenseMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.PortMappingMapper;
|
||||
import fun.asgc.neutrino.proxy.server.proxy.domain.CmdChannelAttachInfo;
|
||||
import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.dal.LicenseMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.PortMappingMapper;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.CmdChannelAttachInfo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.solon.annotation.Db;
|
||||
import org.noear.solon.annotation.Component;
|
||||
@@ -53,7 +53,7 @@ public class ProxyMutualService {
|
||||
Date now = new Date();
|
||||
portMappingMapper.updateOnlineStatus(attachInfo.getLicenseId(), serverPort, OnlineStatusEnum.ONLINE.getStatus(), now);
|
||||
licenseMapper.updateOnlineStatus(attachInfo.getLicenseId(), OnlineStatusEnum.ONLINE.getStatus(), now);
|
||||
log.info("绑定服务端端口 licenseId:{},ip:{},serverPort:{}", attachInfo.getLicenseId(), attachInfo.getIp(), serverPort);
|
||||
log.info("bind server port licenseId:{},ip:{},serverPort:{}", attachInfo.getLicenseId(), attachInfo.getIp(), serverPort);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,7 +64,7 @@ public class ProxyMutualService {
|
||||
Date now = new Date();
|
||||
portMappingMapper.updateOnlineStatus(attachInfo.getLicenseId(), OnlineStatusEnum.OFFLINE.getStatus(), now);
|
||||
licenseMapper.updateOnlineStatus(attachInfo.getLicenseId(), OnlineStatusEnum.OFFLINE.getStatus(), now);
|
||||
log.info("客户端下线 licenseId:{},ip:{}", attachInfo.getLicenseId(), attachInfo.getIp());
|
||||
log.info("client offline licenseId:{},ip:{}", attachInfo.getLicenseId(), attachInfo.getIp());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
package org.dromara.neutrinoproxy.server.service;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.solon.plugins.pagination.Page;
|
||||
import com.google.common.collect.Lists;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.solon.annotation.Db;
|
||||
import org.dromara.neutrinoproxy.core.util.DateUtil;
|
||||
import org.dromara.neutrinoproxy.server.base.db.DbConfig;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
|
||||
import org.dromara.neutrinoproxy.server.constant.Constants;
|
||||
import org.dromara.neutrinoproxy.server.constant.OnlineStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.report.LicenseFlowMonthReportReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.report.LicenseFlowReportReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.report.UserFlowMonthReportReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.report.UserFlowReportReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.report.HomeDataView;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.report.LicenseFlowMonthReportRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.report.LicenseFlowReportRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.report.UserFlowMonthReportRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.report.UserFlowReportRes;
|
||||
import org.dromara.neutrinoproxy.server.dal.LicenseMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.PortMappingMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.ReportMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.LicenseDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.PortMappingDO;
|
||||
import org.dromara.neutrinoproxy.server.service.bo.FlowBO;
|
||||
import org.dromara.neutrinoproxy.server.service.bo.SingleDayFlowBO;
|
||||
import org.dromara.neutrinoproxy.server.util.FormatUtil;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/12/23
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ReportService {
|
||||
@Db
|
||||
private ReportMapper reportMapper;
|
||||
@Db
|
||||
private LicenseMapper licenseMapper;
|
||||
@Db
|
||||
private PortMappingMapper portMappingMapper;
|
||||
@Inject
|
||||
private DbConfig dbConfig;
|
||||
|
||||
/**
|
||||
* 首页图表
|
||||
* @return
|
||||
*/
|
||||
public HomeDataView homeDataView() {
|
||||
HomeDataView homeDataView = new HomeDataView();
|
||||
homeDataView.setLicense(new HomeDataView.License().setTotalCount(0).setOnlineCount(0));
|
||||
homeDataView.setPortMapping(new HomeDataView.PortMapping().setTotalCount(0).setOnlineCount(0));
|
||||
homeDataView.setTodayFlow(new HomeDataView.TodayFlow().setUpFlowBytes(0L).setDownFlowBytes(0L));
|
||||
homeDataView.setTotalFlow(new HomeDataView.TotalFlow().setUpFlowBytes(0L).setDownFlowBytes(0L));
|
||||
|
||||
// 查询license列表
|
||||
List<LicenseDO> licenseDOList = licenseMapper.listAll();
|
||||
if (CollectionUtil.isNotEmpty(licenseDOList)) {
|
||||
homeDataView.getLicense().setTotalCount(licenseDOList.size());
|
||||
homeDataView.getLicense().setOnlineCount((int)licenseDOList.stream().filter(e -> OnlineStatusEnum.ONLINE.getStatus().equals(e.getIsOnline())).count());
|
||||
}
|
||||
// 查询端口映射列表
|
||||
List<PortMappingDO> portMappingDOList = portMappingMapper.selectList(new LambdaQueryWrapper<>());
|
||||
if (CollectionUtil.isNotEmpty(portMappingDOList)) {
|
||||
homeDataView.getPortMapping().setTotalCount(portMappingDOList.size());
|
||||
homeDataView.getPortMapping().setOnlineCount((int)portMappingDOList.stream().filter(e -> OnlineStatusEnum.ONLINE.getStatus().equals(e.getIsOnline())).count());
|
||||
}
|
||||
// 今日流量
|
||||
Date now = new Date();
|
||||
FlowBO todayFlow = reportMapper.homeTodayFlow(DateUtil.getDayBegin(now), now);
|
||||
homeDataView.setTodayFlow(todayFlow.todayFlow());
|
||||
|
||||
// 总流量
|
||||
FlowBO totalFlow = reportMapper.homeTotalFlow(DateUtil.getMonthBegin(now), DateUtil.getDayBegin(now), now);
|
||||
homeDataView.setTotalFlow(totalFlow.totalFlow());
|
||||
|
||||
// 最近n日流量
|
||||
Integer days = Constants.HOME_FLOW_DAYS;
|
||||
List<SingleDayFlowBO> last7dFlowList = reportMapper.homeLast7dFlowList(DateUtil.getDayBegin(DateUtil.addDate(now, Calendar.DATE, -(days - 1))), DateUtil.getDayBegin(now), now);
|
||||
homeDataView.setLast7dFlow(new HomeDataView.Last7dFlow());
|
||||
homeDataView.getLast7dFlow().setDataList(last7dFlowList.stream().map(SingleDayFlowBO::toSingleDayFlow).collect(Collectors.toList()));
|
||||
|
||||
// 数据处理
|
||||
fillHomeDataView(homeDataView, now);
|
||||
return homeDataView;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户流量报表分页
|
||||
* @param pageQuery
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
public PageInfo<UserFlowReportRes> userFlowReportPage(PageQuery pageQuery, UserFlowReportReq req) {
|
||||
Page<UserFlowReportRes> page = new Page<>(pageQuery.getCurrent(), pageQuery.getSize());
|
||||
Date now = new Date();
|
||||
List<UserFlowReportRes> list = reportMapper.userFlowReportList(page, req.getUserId(), DateUtil.getMonthBegin(now), DateUtil.getDayBegin(now), now);
|
||||
fillUserFlowReport(list);
|
||||
return PageInfo.of(list, page);
|
||||
}
|
||||
|
||||
/**
|
||||
* license流量报表分页
|
||||
* @param pageQuery
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
public PageInfo<LicenseFlowReportRes> licenseFlowReportPage(PageQuery pageQuery, LicenseFlowReportReq req) {
|
||||
Page<LicenseFlowReportRes> page = new Page<>(pageQuery.getCurrent(), pageQuery.getSize());
|
||||
Date now = new Date();
|
||||
List<LicenseFlowReportRes> list = reportMapper.licenseFLowReportList(page, req.getUserId(), DateUtil.getMonthBegin(now), DateUtil.getDayBegin(now), now);
|
||||
fillLicenseFlowReport(list);
|
||||
return PageInfo.of(list, page);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户流量月度明细
|
||||
* @param pageQuery
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
public PageInfo<UserFlowMonthReportRes> userFlowMonthReportPage(PageQuery pageQuery, UserFlowMonthReportReq req) {
|
||||
Page<UserFlowMonthReportRes> page = new Page<>(pageQuery.getCurrent(), pageQuery.getSize());
|
||||
Date now = new Date();
|
||||
List<UserFlowMonthReportRes> list = reportMapper.userFlowMonthReportList(page, req.getUserId(), DateUtil.getMonthBegin(now), DateUtil.getDayBegin(now), now);
|
||||
fillUserFlowMonthReport(list);
|
||||
return PageInfo.of(list, page);
|
||||
}
|
||||
|
||||
/**
|
||||
* license流量月度明细
|
||||
* @param pageQuery
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
public PageInfo<LicenseFlowMonthReportRes> licenseFlowMonthReportPage(PageQuery pageQuery, LicenseFlowMonthReportReq req) {
|
||||
Page<LicenseFlowMonthReportRes> page = new Page<>(pageQuery.getCurrent(), pageQuery.getSize());
|
||||
Date now = new Date();
|
||||
List<LicenseFlowMonthReportRes> list = reportMapper.licenseFLowMonthReportList(page, req.getUserId(), req.getLicenseId(), DateUtil.getMonthBegin(now), DateUtil.getDayBegin(now), now);
|
||||
fillLicenseFlowMonthReport(list);
|
||||
return PageInfo.of(list, page);
|
||||
}
|
||||
|
||||
private void fillUserFlowReport(List<UserFlowReportRes> list) {
|
||||
if (CollectionUtil.isEmpty(list)) {
|
||||
return;
|
||||
}
|
||||
for (UserFlowReportRes item : list) {
|
||||
long upFlowBytes = (null == item.getUpFlowBytes()) ? 0 : item.getUpFlowBytes();
|
||||
long downFlowBytes = (null == item.getDownFlowBytes()) ? 0 : item.getDownFlowBytes();
|
||||
long totalFlowBytes = upFlowBytes + downFlowBytes;
|
||||
item.setUpFlowBytes(upFlowBytes);
|
||||
item.setDownFlowBytes(downFlowBytes);
|
||||
item.setTotalFlowBytes(totalFlowBytes);
|
||||
item.setUpFlowDesc(FormatUtil.getSizeDescByByteCount(upFlowBytes));
|
||||
item.setDownFlowDesc(FormatUtil.getSizeDescByByteCount(downFlowBytes));
|
||||
item.setTotalFlowDesc(FormatUtil.getSizeDescByByteCount(totalFlowBytes));
|
||||
}
|
||||
}
|
||||
|
||||
private void fillLicenseFlowReport(List<LicenseFlowReportRes> list) {
|
||||
if (CollectionUtil.isEmpty(list)) {
|
||||
return;
|
||||
}
|
||||
for (LicenseFlowReportRes item : list) {
|
||||
long upFlowBytes = (null == item.getUpFlowBytes()) ? 0 : item.getUpFlowBytes();
|
||||
long downFlowBytes = (null == item.getDownFlowBytes()) ? 0 : item.getDownFlowBytes();
|
||||
long totalFlowBytes = upFlowBytes + downFlowBytes;
|
||||
item.setUpFlowBytes(upFlowBytes);
|
||||
item.setDownFlowBytes(downFlowBytes);
|
||||
item.setTotalFlowBytes(totalFlowBytes);
|
||||
item.setUpFlowDesc(FormatUtil.getSizeDescByByteCount(upFlowBytes));
|
||||
item.setDownFlowDesc(FormatUtil.getSizeDescByByteCount(downFlowBytes));
|
||||
item.setTotalFlowDesc(FormatUtil.getSizeDescByByteCount(totalFlowBytes));
|
||||
}
|
||||
}
|
||||
|
||||
private void fillUserFlowMonthReport(List<UserFlowMonthReportRes> list) {
|
||||
if (CollectionUtil.isEmpty(list)) {
|
||||
return;
|
||||
}
|
||||
for (UserFlowMonthReportRes item : list) {
|
||||
long upFlowBytes = (null == item.getUpFlowBytes()) ? 0 : item.getUpFlowBytes();
|
||||
long downFlowBytes = (null == item.getDownFlowBytes()) ? 0 : item.getDownFlowBytes();
|
||||
long totalFlowBytes = upFlowBytes + downFlowBytes;
|
||||
item.setUpFlowBytes(upFlowBytes);
|
||||
item.setDownFlowBytes(downFlowBytes);
|
||||
item.setTotalFlowBytes(totalFlowBytes);
|
||||
item.setUpFlowDesc(FormatUtil.getSizeDescByByteCount(upFlowBytes));
|
||||
item.setDownFlowDesc(FormatUtil.getSizeDescByByteCount(downFlowBytes));
|
||||
item.setTotalFlowDesc(FormatUtil.getSizeDescByByteCount(totalFlowBytes));
|
||||
}
|
||||
}
|
||||
|
||||
private void fillLicenseFlowMonthReport(List<LicenseFlowMonthReportRes> list) {
|
||||
if (CollectionUtil.isEmpty(list)) {
|
||||
return;
|
||||
}
|
||||
for (LicenseFlowMonthReportRes item : list) {
|
||||
long upFlowBytes = (null == item.getUpFlowBytes()) ? 0 : item.getUpFlowBytes();
|
||||
long downFlowBytes = (null == item.getDownFlowBytes()) ? 0 : item.getDownFlowBytes();
|
||||
long totalFlowBytes = upFlowBytes + downFlowBytes;
|
||||
item.setUpFlowBytes(upFlowBytes);
|
||||
item.setDownFlowBytes(downFlowBytes);
|
||||
item.setTotalFlowBytes(totalFlowBytes);
|
||||
item.setUpFlowDesc(FormatUtil.getSizeDescByByteCount(upFlowBytes));
|
||||
item.setDownFlowDesc(FormatUtil.getSizeDescByByteCount(downFlowBytes));
|
||||
item.setTotalFlowDesc(FormatUtil.getSizeDescByByteCount(totalFlowBytes));
|
||||
}
|
||||
}
|
||||
|
||||
private void fillHomeDataView(HomeDataView homeDataView, Date now) {
|
||||
if (null == homeDataView) {
|
||||
return;
|
||||
}
|
||||
// License
|
||||
if (null == homeDataView.getLicense()) {
|
||||
homeDataView.setLicense(new HomeDataView.License());
|
||||
}
|
||||
if (null == homeDataView.getLicense().getTotalCount()) {
|
||||
homeDataView.getLicense().setTotalCount(0);
|
||||
}
|
||||
if (null == homeDataView.getLicense().getOnlineCount()) {
|
||||
homeDataView.getLicense().setOnlineCount(0);
|
||||
}
|
||||
homeDataView.getLicense().setOfflineCount(homeDataView.getLicense().getTotalCount() - homeDataView.getLicense().getOnlineCount());
|
||||
|
||||
// PortMapping
|
||||
if (null == homeDataView.getPortMapping()) {
|
||||
homeDataView.setPortMapping(new HomeDataView.PortMapping());
|
||||
}
|
||||
if (null == homeDataView.getPortMapping().getTotalCount()) {
|
||||
homeDataView.getPortMapping().setTotalCount(0);
|
||||
}
|
||||
if (null == homeDataView.getPortMapping().getOnlineCount()) {
|
||||
homeDataView.getPortMapping().setOnlineCount(0);
|
||||
}
|
||||
homeDataView.getPortMapping().setOfflineCount(homeDataView.getPortMapping().getTotalCount() - homeDataView.getPortMapping().getOnlineCount());
|
||||
|
||||
// TodayFlow
|
||||
if (null == homeDataView.getTodayFlow()) {
|
||||
homeDataView.setTodayFlow(new HomeDataView.TodayFlow());
|
||||
}
|
||||
if (null == homeDataView.getTodayFlow().getUpFlowBytes()) {
|
||||
homeDataView.getTodayFlow().setUpFlowBytes(0L);
|
||||
}
|
||||
if (null == homeDataView.getTodayFlow().getDownFlowBytes()) {
|
||||
homeDataView.getTodayFlow().setDownFlowBytes(0L);
|
||||
}
|
||||
homeDataView.getTodayFlow().setTotalFlowBytes(homeDataView.getTodayFlow().getUpFlowBytes() + homeDataView.getTodayFlow().getDownFlowBytes());
|
||||
homeDataView.getTodayFlow().setUpFlowDesc(FormatUtil.getSizeDescByByteCount(homeDataView.getTodayFlow().getUpFlowBytes()));
|
||||
homeDataView.getTodayFlow().setDownFlowDesc(FormatUtil.getSizeDescByByteCount(homeDataView.getTodayFlow().getDownFlowBytes()));
|
||||
homeDataView.getTodayFlow().setTotalFlowDesc(FormatUtil.getSizeDescByByteCount(homeDataView.getTodayFlow().getTotalFlowBytes()));
|
||||
|
||||
// TotalFlow
|
||||
if (null == homeDataView.getTotalFlow()) {
|
||||
homeDataView.setTotalFlow(new HomeDataView.TotalFlow());
|
||||
}
|
||||
if (null == homeDataView.getTotalFlow().getUpFlowBytes()) {
|
||||
homeDataView.getTotalFlow().setUpFlowBytes(0L);
|
||||
}
|
||||
if (null == homeDataView.getTotalFlow().getDownFlowBytes()) {
|
||||
homeDataView.getTotalFlow().setDownFlowBytes(0L);
|
||||
}
|
||||
homeDataView.getTotalFlow().setTotalFlowBytes(homeDataView.getTotalFlow().getUpFlowBytes() + homeDataView.getTotalFlow().getDownFlowBytes());
|
||||
homeDataView.getTotalFlow().setUpFlowDesc(FormatUtil.getSizeDescByByteCount(homeDataView.getTotalFlow().getUpFlowBytes()));
|
||||
homeDataView.getTotalFlow().setDownFlowDesc(FormatUtil.getSizeDescByByteCount(homeDataView.getTotalFlow().getDownFlowBytes()));
|
||||
homeDataView.getTotalFlow().setTotalFlowDesc(FormatUtil.getSizeDescByByteCount(homeDataView.getTotalFlow().getTotalFlowBytes()));
|
||||
|
||||
// 最近N日流量
|
||||
Integer days = Constants.HOME_FLOW_DAYS;
|
||||
HomeDataView.Last7dFlow last7dFlow = homeDataView.getLast7dFlow();
|
||||
if (null == last7dFlow) {
|
||||
last7dFlow = new HomeDataView.Last7dFlow();
|
||||
homeDataView.setLast7dFlow(last7dFlow);
|
||||
}
|
||||
if (null == last7dFlow.getDataList()) {
|
||||
last7dFlow.setDataList(Collections.emptyList());
|
||||
}
|
||||
// 数据列表
|
||||
Set<String> existDateStrList = new HashSet<>();
|
||||
if (CollectionUtil.isNotEmpty(last7dFlow.getDataList())) {
|
||||
for (HomeDataView.SingleDayFlow singleDayFlow : last7dFlow.getDataList()) {
|
||||
if (null == singleDayFlow.getUpFlowBytes()) {
|
||||
singleDayFlow.setUpFlowBytes(0L);
|
||||
}
|
||||
if (null == singleDayFlow.getDownFlowBytes()) {
|
||||
singleDayFlow.setDownFlowBytes(0L);
|
||||
}
|
||||
singleDayFlow.setTotalFlowBytes(singleDayFlow.getUpFlowBytes() + singleDayFlow.getDownFlowBytes());
|
||||
singleDayFlow.setUpFlowDesc(FormatUtil.getSizeDescByByteCount(singleDayFlow.getUpFlowBytes()));
|
||||
singleDayFlow.setDownFlowDesc(FormatUtil.getSizeDescByByteCount(singleDayFlow.getDownFlowBytes()));
|
||||
singleDayFlow.setTotalFlowDesc(FormatUtil.getSizeDescByByteCount(singleDayFlow.getTotalFlowBytes()));
|
||||
singleDayFlow.setDateStr(DateUtil.format(singleDayFlow.getDate(), "yyyy-MM-dd"));
|
||||
existDateStrList.add(singleDayFlow.getDateStr());
|
||||
}
|
||||
}
|
||||
// 获取最近7天的日期字符串列表。防止因统计数据缺失,造成图表展示错误的问题,缺失的日期数据,自动填充0
|
||||
List<String> dateList = DateUtil.getBetweenTimes(DateUtil.format(DateUtil.addDate(now, Calendar.DATE, -(days - 1)), "yyyy-MM-dd"), DateUtil.format(now, "yyyy-MM-dd"));
|
||||
for (String dateStr : dateList) {
|
||||
if (existDateStrList.contains(dateStr)) {
|
||||
continue;
|
||||
}
|
||||
last7dFlow.getDataList().add(new HomeDataView.SingleDayFlow()
|
||||
.setDate(DateUtil.parse(dateStr, "yyyy-MM-dd"))
|
||||
.setDateStr(dateStr)
|
||||
.setUpFlowBytes(0L)
|
||||
.setUpFlowDesc("0B")
|
||||
.setDownFlowBytes(0L)
|
||||
.setDownFlowDesc("0B")
|
||||
.setTotalFlowBytes(0L)
|
||||
.setTotalFlowDesc("0B")
|
||||
);
|
||||
}
|
||||
|
||||
// 按日期升序
|
||||
Collections.sort(last7dFlow.getDataList(), Comparator.comparing(HomeDataView.SingleDayFlow::getDate));
|
||||
// x轴日期
|
||||
last7dFlow.setXDate(last7dFlow.getDataList().stream().map(HomeDataView.SingleDayFlow::getDateStr).collect(Collectors.toList()));
|
||||
// 图例
|
||||
last7dFlow.setLegendData(Lists.newArrayList("上行流量", "下行流量", "总流量"));
|
||||
// 折线图
|
||||
List<HomeDataView.Series> seriesList = Lists.newArrayList();
|
||||
last7dFlow.setSeriesList(seriesList);
|
||||
// 上行流量折线
|
||||
seriesList.add(new HomeDataView.Series()
|
||||
.setSeriesType("line")
|
||||
.setSeriesName(last7dFlow.getLegendData().get(0))
|
||||
.setSeriesData(last7dFlow.getDataList().stream().map(HomeDataView.SingleDayFlow::getUpFlowBytes).collect(Collectors.toList()))
|
||||
);
|
||||
// 下行流量折线
|
||||
seriesList.add(new HomeDataView.Series()
|
||||
.setSeriesType("line")
|
||||
.setSeriesName(last7dFlow.getLegendData().get(1))
|
||||
.setSeriesData(last7dFlow.getDataList().stream().map(HomeDataView.SingleDayFlow::getDownFlowBytes).collect(Collectors.toList()))
|
||||
);
|
||||
// 总流量折线
|
||||
seriesList.add(new HomeDataView.Series()
|
||||
.setSeriesType("line")
|
||||
.setSeriesName(last7dFlow.getLegendData().get(2))
|
||||
.setSeriesData(last7dFlow.getDataList().stream().map(HomeDataView.SingleDayFlow::getTotalFlowBytes).collect(Collectors.toList()))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.dromara.neutrinoproxy.server.service;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||
import com.baomidou.mybatisplus.solon.plugins.pagination.Page;
|
||||
import org.apache.ibatis.solon.annotation.Db;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.log.UserLoginRecordListReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.log.UserLoginRecordListRes;
|
||||
import org.dromara.neutrinoproxy.server.dal.UserLoginRecordMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.UserMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.UserLoginRecordDO;
|
||||
import org.noear.solon.annotation.Component;
|
||||
|
||||
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
|
||||
*/
|
||||
@Component
|
||||
public class UserLoginRecordService {
|
||||
@Db
|
||||
private UserLoginRecordMapper userLoginRecordMapper;
|
||||
@Db
|
||||
private UserMapper userMapper;
|
||||
|
||||
public PageInfo<UserLoginRecordListRes> page(PageQuery pageQuery, UserLoginRecordListReq req) {
|
||||
Page<UserLoginRecordDO> page = userLoginRecordMapper.selectPage(new Page<>(pageQuery.getCurrent(), pageQuery.getSize()), new LambdaQueryWrapper<UserLoginRecordDO>()
|
||||
.eq(null != req.getUserId(), UserLoginRecordDO::getUserId, req.getUserId())
|
||||
.orderByDesc(UserLoginRecordDO::getCreateTime)
|
||||
);
|
||||
List<UserLoginRecordListRes> respList = page.getRecords().stream().map(UserLoginRecordDO::toRes).collect(Collectors.toList());
|
||||
if (CollectionUtils.isEmpty(page.getRecords())) {
|
||||
return PageInfo.of(respList, page);
|
||||
}
|
||||
|
||||
if (!CollectionUtil.isEmpty(respList)) {
|
||||
Set<Integer> userIds = respList.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 : respList) {
|
||||
UserDO userDO = userMap.get(item.getUserId());
|
||||
if (null != userDO) {
|
||||
item.setUserName(userDO.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
return PageInfo.of(respList, page);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,38 @@
|
||||
package fun.asgc.neutrino.proxy.server.service;
|
||||
package org.dromara.neutrinoproxy.server.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.github.pagehelper.Page;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import fun.asgc.neutrino.proxy.core.util.DateUtil;
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageInfo;
|
||||
import fun.asgc.neutrino.proxy.server.base.page.PageQuery;
|
||||
import fun.asgc.neutrino.proxy.server.base.rest.ServiceException;
|
||||
import fun.asgc.neutrino.proxy.server.base.rest.SystemContextHolder;
|
||||
import fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum;
|
||||
import fun.asgc.neutrino.proxy.server.constant.ExceptionConstant;
|
||||
import fun.asgc.neutrino.proxy.server.controller.req.*;
|
||||
import fun.asgc.neutrino.proxy.server.controller.res.*;
|
||||
import fun.asgc.neutrino.proxy.server.dal.UserLoginRecordMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.UserMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.UserTokenMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.UserLoginRecordDO;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.UserTokenDO;
|
||||
import fun.asgc.neutrino.proxy.server.util.Md5Util;
|
||||
import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil;
|
||||
import ma.glasnost.orika.MapperFacade;
|
||||
import com.baomidou.mybatisplus.solon.plugins.pagination.Page;
|
||||
import org.apache.ibatis.solon.annotation.Db;
|
||||
import org.dromara.neutrinoproxy.core.util.DateUtil;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageInfo;
|
||||
import org.dromara.neutrinoproxy.server.base.page.PageQuery;
|
||||
import org.dromara.neutrinoproxy.server.base.rest.ServiceException;
|
||||
import org.dromara.neutrinoproxy.server.base.rest.SystemContextHolder;
|
||||
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.ExceptionConstant;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.LoginReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.UserCreateReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.UserInfoReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.UserListReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.UserUpdateEnableStatusReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.UserUpdatePasswordReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.req.system.UserUpdateReq;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.LoginRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.UserCreateRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.UserInfoRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.UserListRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.UserUpdateEnableStatusRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.UserUpdatePasswordRes;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.system.UserUpdateRes;
|
||||
import org.dromara.neutrinoproxy.server.dal.UserLoginRecordMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.UserMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.UserTokenMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.UserLoginRecordDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.UserTokenDO;
|
||||
import org.dromara.neutrinoproxy.server.util.Md5Util;
|
||||
import org.dromara.neutrinoproxy.server.util.ParamCheckUtil;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
|
||||
@@ -30,6 +40,7 @@ import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -39,8 +50,7 @@ import java.util.UUID;
|
||||
@Component
|
||||
public class UserService {
|
||||
private static final String DEFAULT_PASSWORD = "123456";
|
||||
@Inject
|
||||
private MapperFacade mapperFacade;
|
||||
|
||||
@Db
|
||||
private UserMapper userMapper;
|
||||
@Db
|
||||
@@ -120,12 +130,11 @@ public class UserService {
|
||||
}
|
||||
|
||||
public PageInfo<UserListRes> page(PageQuery pageQuery, UserListReq req) {
|
||||
Page<UserListRes> result = PageHelper.startPage(pageQuery.getCurrent(), pageQuery.getSize());
|
||||
List<UserDO> list = userMapper.selectList(new LambdaQueryWrapper<UserDO>()
|
||||
.orderByAsc(UserDO::getId)
|
||||
);
|
||||
List<UserListRes> respList = mapperFacade.mapAsList(list, UserListRes.class);
|
||||
return PageInfo.of(respList, result.getTotal(), pageQuery.getCurrent(), pageQuery.getSize());
|
||||
Page<UserDO> page = userMapper.selectPage(new Page<>(pageQuery.getCurrent(), pageQuery.getSize()), new LambdaQueryWrapper<UserDO>()
|
||||
.orderByAsc(UserDO::getId)
|
||||
);
|
||||
List<UserListRes> respList = page.getRecords().stream().map(UserDO::toRes).collect(Collectors.toList());
|
||||
return PageInfo.of(respList, page);
|
||||
}
|
||||
|
||||
public List<UserListRes> list(UserListReq req) {
|
||||
@@ -133,7 +142,7 @@ public class UserService {
|
||||
.eq(UserDO::getEnable, EnableStatusEnum.ENABLE.getStatus())
|
||||
.orderByAsc(UserDO::getId)
|
||||
);
|
||||
return mapperFacade.mapAsList(userDOList, UserListRes.class);
|
||||
return userDOList.stream().map(UserDO::toRes).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public UserInfoRes info(UserInfoReq req) {
|
||||
@@ -1,23 +1,25 @@
|
||||
package fun.asgc.neutrino.proxy.server.service;
|
||||
package org.dromara.neutrinoproxy.server.service;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import fun.asgc.neutrino.proxy.core.Constants;
|
||||
import fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum;
|
||||
import fun.asgc.neutrino.proxy.server.dal.LicenseMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.PortMappingMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.PortPoolMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.UserMapper;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.LicenseDO;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.PortMappingDO;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.PortPoolDO;
|
||||
import fun.asgc.neutrino.proxy.server.dal.entity.UserDO;
|
||||
import fun.asgc.neutrino.proxy.server.proxy.core.BytesMetricsHandler;
|
||||
import fun.asgc.neutrino.proxy.server.proxy.core.VisitorChannelHandler;
|
||||
import fun.asgc.neutrino.proxy.server.proxy.domain.CmdChannelAttachInfo;
|
||||
import fun.asgc.neutrino.proxy.server.proxy.domain.ProxyMapping;
|
||||
import fun.asgc.neutrino.proxy.server.util.ProxyUtil;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import org.dromara.neutrinoproxy.core.Constants;
|
||||
import org.dromara.neutrinoproxy.server.constant.EnableStatusEnum;
|
||||
import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum;
|
||||
import org.dromara.neutrinoproxy.server.dal.LicenseMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.PortMappingMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.PortPoolMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.UserMapper;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.LicenseDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.PortMappingDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.PortPoolDO;
|
||||
import org.dromara.neutrinoproxy.server.dal.entity.UserDO;
|
||||
import org.dromara.neutrinoproxy.server.proxy.core.BytesMetricsHandler;
|
||||
import org.dromara.neutrinoproxy.server.proxy.core.TcpVisitorChannelHandler;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.CmdChannelAttachInfo;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyMapping;
|
||||
import org.dromara.neutrinoproxy.server.util.ProxyUtil;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
@@ -41,10 +43,10 @@ import java.util.stream.Collectors;
|
||||
@Slf4j
|
||||
@Component
|
||||
public class VisitorChannelService {
|
||||
@Inject("serverBossGroup")
|
||||
private NioEventLoopGroup serverBossGroup;
|
||||
@Inject("serverWorkerGroup")
|
||||
private NioEventLoopGroup serverWorkerGroup;
|
||||
@Inject("tcpServerBootstrap")
|
||||
private ServerBootstrap tcpServerBootstrap;
|
||||
@Inject("udpServerBootstrap")
|
||||
private Bootstrap udpServerBootstrap;
|
||||
@Inject
|
||||
private ProxyMutualService proxyMutualService;
|
||||
@Db
|
||||
@@ -209,7 +211,8 @@ public class VisitorChannelService {
|
||||
if (null != proxyChannel) {
|
||||
proxyChannel.close();
|
||||
}
|
||||
visitorChannel.close();
|
||||
// TODO 此处如果时UDP的visitorChannel,则不能close,后续重构考虑
|
||||
// visitorChannel.close();
|
||||
}
|
||||
ProxyUtil.removeProxyInfo(portMappingDO.getServerPort());
|
||||
}
|
||||
@@ -218,21 +221,23 @@ public class VisitorChannelService {
|
||||
if (CollectionUtil.isEmpty(portMappingList)) {
|
||||
return;
|
||||
}
|
||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||
bootstrap.group(serverBossGroup, serverWorkerGroup)
|
||||
.channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
ch.pipeline().addFirst(new BytesMetricsHandler());
|
||||
ch.pipeline().addLast(new VisitorChannelHandler());
|
||||
}
|
||||
});
|
||||
|
||||
for (PortMappingDO portMapping : portMappingList) {
|
||||
if (EnableStatusEnum.DISABLE.getStatus().equals(portMapping.getEnable())) {
|
||||
// 端口映射被禁用了,忽略 TODO 映射没被禁用,但端口被禁用了也需要处理
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
proxyMutualService.bindServerPort(cmdChannelAttachInfo, portMapping.getServerPort());
|
||||
bootstrap.bind(portMapping.getServerPort()).get();
|
||||
log.info("绑定用户端口: {}", portMapping.getServerPort());
|
||||
|
||||
NetworkProtocolEnum networkProtocolEnum = NetworkProtocolEnum.of(portMapping.getProtocal());
|
||||
if (networkProtocolEnum == NetworkProtocolEnum.UDP) {
|
||||
udpServerBootstrap.bind(portMapping.getServerPort()).get();
|
||||
log.info("bind UDP user port: {}", portMapping.getServerPort());
|
||||
} else {
|
||||
tcpServerBootstrap.bind(portMapping.getServerPort()).get();
|
||||
log.info("bind TCP user port: {}", portMapping.getServerPort());
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
// BindException表示该端口已经绑定过
|
||||
if (!(ex.getCause() instanceof BindException)) {
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.dromara.neutrinoproxy.server.service.bo;
|
||||
|
||||
import lombok.Data;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.report.HomeDataView;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/3/26
|
||||
*/
|
||||
@Data
|
||||
public class FlowBO {
|
||||
/**
|
||||
* 上行流量字节数
|
||||
*/
|
||||
private Long upFlowBytes;
|
||||
/**
|
||||
* 下行流量字节数
|
||||
*/
|
||||
private Long downFlowBytes;
|
||||
|
||||
public HomeDataView.TodayFlow todayFlow() {
|
||||
HomeDataView.TodayFlow todayFlow = new HomeDataView.TodayFlow();
|
||||
todayFlow.setUpFlowBytes(upFlowBytes);
|
||||
todayFlow.setDownFlowBytes(downFlowBytes);
|
||||
return todayFlow;
|
||||
}
|
||||
|
||||
public HomeDataView.TotalFlow totalFlow() {
|
||||
HomeDataView.TotalFlow totalFlow = new HomeDataView.TotalFlow();
|
||||
totalFlow.setUpFlowBytes(upFlowBytes);
|
||||
totalFlow.setDownFlowBytes(downFlowBytes);
|
||||
return totalFlow;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.dromara.neutrinoproxy.server.service.bo;
|
||||
|
||||
import lombok.Data;
|
||||
import org.dromara.neutrinoproxy.server.controller.res.report.HomeDataView;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 单日流量
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/3/26
|
||||
*/
|
||||
@Data
|
||||
public class SingleDayFlowBO {
|
||||
/**
|
||||
* 日期
|
||||
*/
|
||||
private Date date;
|
||||
/**
|
||||
* 上行流量字节数
|
||||
*/
|
||||
private Long upFlowBytes;
|
||||
/**
|
||||
* 下行流量字节数
|
||||
*/
|
||||
private Long downFlowBytes;
|
||||
|
||||
public HomeDataView.SingleDayFlow toSingleDayFlow() {
|
||||
HomeDataView.SingleDayFlow singleDayFlow = new HomeDataView.SingleDayFlow();
|
||||
singleDayFlow.setDate(date);
|
||||
singleDayFlow.setUpFlowBytes(upFlowBytes);
|
||||
singleDayFlow.setDownFlowBytes(downFlowBytes);
|
||||
return singleDayFlow;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.dromara.neutrinoproxy.server.util;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2023/3/19
|
||||
*/
|
||||
public class FormatUtil {
|
||||
private static final String[] SIZE_UNINTS = {"B", "KB", "MB", "GB", "TB"};
|
||||
private static final int SIZE_SYSTEM = 1024;
|
||||
/**
|
||||
* 根据字节数获取大小描述
|
||||
* 1、小于1024字节的以B为单位
|
||||
* 2、小于1024KB的以KB为单位
|
||||
* 3、小于1024M的以MB为单位
|
||||
* 4、小于1024G的以GB为单位
|
||||
* 5、其他以TB为单位
|
||||
* @param byteCount
|
||||
* @return
|
||||
*/
|
||||
public static String getSizeDescByByteCount(long byteCount){
|
||||
if(byteCount <= 0){
|
||||
return "0B";
|
||||
}
|
||||
|
||||
double res = byteCount;
|
||||
int index = 0;
|
||||
while (index < SIZE_UNINTS.length && res >= SIZE_SYSTEM){
|
||||
res /= SIZE_SYSTEM;
|
||||
index++;
|
||||
}
|
||||
|
||||
if(index >= SIZE_UNINTS.length){
|
||||
index = SIZE_UNINTS.length - 1;
|
||||
res *= 1024;
|
||||
}
|
||||
|
||||
return trimZero(String.format("%.2f", res)) + SIZE_UNINTS[index];
|
||||
}
|
||||
|
||||
private static String trimZero(String s) {
|
||||
if (s.indexOf(".") > 0) {
|
||||
// 去掉多余的0
|
||||
s = s.replaceAll("0+?$", "");
|
||||
// 如最后一位是.则去掉
|
||||
s = s.replaceAll("[.]$", "");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package fun.asgc.neutrino.proxy.server.util;
|
||||
package org.dromara.neutrinoproxy.server.util;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.MessageDigest;
|
||||
@@ -0,0 +1,106 @@
|
||||
package org.dromara.neutrinoproxy.server.util;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.dromara.neutrinoproxy.server.constant.ExceptionConstant;
|
||||
import org.dromara.neutrinoproxy.server.base.rest.ServiceException;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author: aoshiguchen
|
||||
* @date: 2022/8/1
|
||||
*/
|
||||
public class ParamCheckUtil {
|
||||
|
||||
public static void checkNotNull(Object obj, String name) {
|
||||
if (null == obj) {
|
||||
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_NULL, name);
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkNotEmpty(String str, String name) {
|
||||
if (StrUtil.isEmpty(str)) {
|
||||
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_EMPTY, name);
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkNotEmpty(Collection collection, String name) {
|
||||
if (null == collection || collection.isEmpty()) {
|
||||
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_EMPTY, name);
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkNotEmpty(Map map, String name) {
|
||||
if (null == map || map.isEmpty()) {
|
||||
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_EMPTY, name);
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkNotEmpty(Set set, String name) {
|
||||
if (null == set || set.isEmpty()) {
|
||||
throw ServiceException.create(ExceptionConstant.PARAMS_NOT_EMPTY, name);
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkMustNull(Object obj, ExceptionConstant constant, Object... params) {
|
||||
if (null != obj) {
|
||||
throw ServiceException.create(constant, params);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void checkNotNull(Object obj, ExceptionConstant constant, Object... params) {
|
||||
if (null == obj) {
|
||||
throw ServiceException.create(constant, params);
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkNotEmpty(String str, ExceptionConstant constant, Object... params) {
|
||||
if (StrUtil.isEmpty(str)) {
|
||||
throw ServiceException.create(constant, params);
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkNotEmpty(Collection collection, ExceptionConstant constant, Object... params) {
|
||||
if (null == collection || collection.isEmpty()) {
|
||||
throw ServiceException.create(constant, params);
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkNotEmpty(Map map, ExceptionConstant constant, Object... params) {
|
||||
if (null == map || map.isEmpty()) {
|
||||
throw ServiceException.create(constant, params);
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkNotEmpty(Set set, ExceptionConstant constant, Object... params) {
|
||||
if (null == set || set.isEmpty()) {
|
||||
throw ServiceException.create(constant, params);
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkExpression(boolean expression, ExceptionConstant constant, Object... params) {
|
||||
if (!expression) {
|
||||
throw ServiceException.create(constant, params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check max length , if the string length exceeded ,an exception is thrown
|
||||
* if the string is empty , it's not checked
|
||||
* 检查最大长度,超出长度,则抛出异常 . 如果字符串本身为空,则不进行检查
|
||||
*
|
||||
* @param str String
|
||||
* @param maxLength Maximum length
|
||||
* @param params Exception parameters
|
||||
* @throws {@link ServiceException}
|
||||
*/
|
||||
public static void checkMaxLength(String str, int maxLength, Object... params) {
|
||||
if (!StrUtil.isEmpty(str) && maxLength < str.length()) {
|
||||
throw ServiceException.create(ExceptionConstant.FILED_LENGTH_OUT, params);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.dromara.neutrinoproxy.server.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
|
||||
/**
|
||||
* 检查端口是否被占用
|
||||
* 文章参考:https://blog.csdn.net/xingluxiaoseng/article/details/40148527
|
||||
*/
|
||||
public class PortAvailableUtil {
|
||||
|
||||
private static void bindPort(String host, int port) throws IOException {
|
||||
Socket s = new Socket();
|
||||
s.bind(new InetSocketAddress(host, port));
|
||||
s.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* 端口占用判断,若是端口被占用,则会抛出IOException异常,表示端口被占用
|
||||
* @param port
|
||||
* @return
|
||||
*/
|
||||
public static boolean isPortAvailable(int port) {
|
||||
try {
|
||||
bindPort("0.0.0.0", port);
|
||||
bindPort(InetAddress.getLocalHost().getHostAddress(), port);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("端口被占用:"+isPortAvailable(9527));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,17 +1,22 @@
|
||||
package fun.asgc.neutrino.proxy.server.util;
|
||||
package org.dromara.neutrinoproxy.server.util;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.google.common.collect.Sets;
|
||||
import fun.asgc.neutrino.proxy.core.ChannelAttribute;
|
||||
import fun.asgc.neutrino.proxy.server.proxy.domain.CmdChannelAttachInfo;
|
||||
import fun.asgc.neutrino.proxy.server.proxy.domain.ProxyMapping;
|
||||
import fun.asgc.neutrino.proxy.server.proxy.domain.VisitorChannelAttachInfo;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.dromara.neutrinoproxy.core.ChannelAttribute;
|
||||
import org.dromara.neutrinoproxy.core.Constants;
|
||||
import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.CmdChannelAttachInfo;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyAttachment;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.ProxyMapping;
|
||||
import org.dromara.neutrinoproxy.server.proxy.domain.VisitorChannelAttachInfo;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
@@ -47,6 +52,22 @@ public class ProxyUtil {
|
||||
* cmdChannelAttachInfo.getUserChannelMap() 读写锁
|
||||
*/
|
||||
private static final ReadWriteLock userChannelMapLock = new ReentrantReadWriteLock();
|
||||
/**
|
||||
* 访问者ID生成器
|
||||
*/
|
||||
private static AtomicLong visitorIdProducer = new AtomicLong(0);
|
||||
/**
|
||||
* 代理 - connect附加映射
|
||||
*/
|
||||
private static Map<String, ProxyAttachment> proxyConnectAttachmentMap = new HashMap<>();
|
||||
/**
|
||||
* 子域名 - 服务端端口映射
|
||||
*/
|
||||
private static Map<String, Integer> subdomainToServerPort = new HashMap<>();
|
||||
/**
|
||||
* licenseId - 客户端Id映射
|
||||
*/
|
||||
private static Map<Integer, String> licenseIdToClientIdMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 初始化代理信息
|
||||
@@ -181,18 +202,21 @@ public class ProxyUtil {
|
||||
* @param visitorId
|
||||
* @param visitorChannel
|
||||
*/
|
||||
public static void addVisitorChannelToCmdChannel(Channel cmdChannel, String visitorId, Channel visitorChannel, Integer serverPort) {
|
||||
public static void addVisitorChannelToCmdChannel(NetworkProtocolEnum protocol, Channel cmdChannel, String visitorId, Channel visitorChannel, Integer serverPort) {
|
||||
InetSocketAddress sa = (InetSocketAddress) visitorChannel.localAddress();
|
||||
String lanInfo = getClientLanInfoByServerPort(sa.getPort());
|
||||
CmdChannelAttachInfo cmdChannelAttachInfo = getAttachInfo(cmdChannel);
|
||||
|
||||
setAttachInfo(visitorChannel, new VisitorChannelAttachInfo()
|
||||
.setVisitorId(visitorId)
|
||||
.setLanInfo(lanInfo)
|
||||
.setServerPort(serverPort)
|
||||
.setLicenseId(cmdChannelAttachInfo.getLicenseId())
|
||||
.setIp(((InetSocketAddress)visitorChannel.remoteAddress()).getAddress().getHostAddress())
|
||||
);
|
||||
VisitorChannelAttachInfo attachInfo = new VisitorChannelAttachInfo()
|
||||
.setProtocol(protocol)
|
||||
.setVisitorId(visitorId)
|
||||
.setLanInfo(lanInfo)
|
||||
.setServerPort(serverPort)
|
||||
.setLicenseId(cmdChannelAttachInfo.getLicenseId());
|
||||
if (NetworkProtocolEnum.UDP != protocol) {
|
||||
attachInfo.setIp(((InetSocketAddress)visitorChannel.remoteAddress()).getAddress().getHostAddress());
|
||||
}
|
||||
setAttachInfo(visitorChannel, attachInfo);
|
||||
userChannelMapLock.writeLock().lock();
|
||||
try {
|
||||
cmdChannelAttachInfo.getVisitorChannelMap().put(visitorId, visitorChannel);
|
||||
@@ -278,4 +302,123 @@ public class ProxyUtil {
|
||||
}
|
||||
return channel.attr(CHANNEL_ATTR_KEY).get().get("attachInfo");
|
||||
}
|
||||
|
||||
/**
|
||||
* 为访问者连接产生ID
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String newVisitorId() {
|
||||
return String.valueOf(visitorIdProducer.incrementAndGet());
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加代理附加对象
|
||||
* @param visitorId
|
||||
* @param proxyAttachment
|
||||
*/
|
||||
public static void addProxyConnectAttachment(String visitorId, ProxyAttachment proxyAttachment) {
|
||||
proxyConnectAttachmentMap.put(visitorId, proxyAttachment);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代理附加对象
|
||||
* @param visitorId
|
||||
* @return
|
||||
*/
|
||||
public static ProxyAttachment getProxyConnectAttachment(String visitorId) {
|
||||
return proxyConnectAttachmentMap.get(visitorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除代理附加对象
|
||||
* @param visitorId
|
||||
*/
|
||||
public static void remoteProxyConnectAttachment(String visitorId) {
|
||||
proxyConnectAttachmentMap.remove(visitorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置子域名到服务端端口的映射
|
||||
* @param subdomain
|
||||
* @param serverPort
|
||||
*/
|
||||
public static void setSubdomainToServerPort(String subdomain, Integer serverPort) {
|
||||
subdomainToServerPort.put(subdomain, serverPort);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除子域名到服务端端口的映射
|
||||
* @param subdomain
|
||||
*/
|
||||
public static void removeSubdomainToServerPort(String subdomain) {
|
||||
subdomainToServerPort.remove(subdomain);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据子域名获取外网端口
|
||||
* @param subdomain
|
||||
* @return
|
||||
*/
|
||||
public static Integer getServerPortBySubdomain(String subdomain) {
|
||||
return subdomainToServerPort.get(subdomain);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭http响应channel
|
||||
* @param channel
|
||||
* @return
|
||||
*/
|
||||
public static void closeHttpProxyResponseChannel(Channel channel) {
|
||||
if (null == channel) {
|
||||
return;
|
||||
}
|
||||
|
||||
String visitorId = getVisitorIdByChannel(channel); // channel.attr(Constants.VISITOR_ID).get();
|
||||
if (StringUtils.isBlank(visitorId)) {
|
||||
return;
|
||||
}
|
||||
ProxyAttachment proxyAttachment = ProxyUtil.getProxyConnectAttachment(visitorId);
|
||||
if (null != proxyAttachment) {
|
||||
tryClose(channel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭channel
|
||||
* @param channel
|
||||
*/
|
||||
public static void tryClose(Channel channel) {
|
||||
try {
|
||||
channel.close();
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置licenseId - clientId映射
|
||||
* @param licenseId
|
||||
* @param clientId
|
||||
*/
|
||||
public static void setLicenseIdToClientIdMap(Integer licenseId, String clientId) {
|
||||
licenseIdToClientIdMap.put(licenseId, clientId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据licenseId获取clientId
|
||||
* @param licenseId
|
||||
* @return
|
||||
*/
|
||||
public static String getClientIdByLicenseId(Integer licenseId) {
|
||||
return licenseIdToClientIdMap.get(licenseId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据licenseId删除clientId
|
||||
* @param licenseId
|
||||
*/
|
||||
public static void removeClientIdByLicenseId(Integer licenseId) {
|
||||
licenseIdToClientIdMap.remove(licenseId);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,87 @@
|
||||
server:
|
||||
port: 8888
|
||||
# 服务端web端口,用于支持HTTP接口,管理后台页面访问
|
||||
port: ${WEB_PORT:8888}
|
||||
context-path: /neutrino-proxy-server
|
||||
solon:
|
||||
app:
|
||||
name: neutrino-proxy-server
|
||||
# aot:
|
||||
# jvmArguments: --add-opens java.base/java.lang=ALL-UNNAMED
|
||||
# 日志级别
|
||||
solon.logging.appender:
|
||||
console:
|
||||
pattern: "%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %magenta(${PID:-}) --- %-15([%15.15thread]) %-56(%cyan(%-40.40logger{39}%L)) : %msg%n"
|
||||
file:
|
||||
enable: false
|
||||
pattern: "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level ${PID:-} --- %-15([%15.15thread]) %-56(%-40.40logger{39}%L) : %msg%n"
|
||||
name: "logs/${solon.app.name}"
|
||||
rolling: "logs/${solon.app.name}_%d{yyyy-MM-dd}_%i.log"
|
||||
solon.logging.logger:
|
||||
"root":
|
||||
# level: ${LOG_LEVEL:info}
|
||||
level: info
|
||||
|
||||
|
||||
neutrino:
|
||||
proxy:
|
||||
protocol:
|
||||
max-frame-length: 2097152
|
||||
max-frame-length: ${MAX_FRAME_LENGTH:2097152}
|
||||
length-field-offset: 0
|
||||
length-field-length: 4
|
||||
initial-bytes-to-strip: 0
|
||||
length-adjustment: 0
|
||||
read-idle-time: 40
|
||||
write-idle-time: 10
|
||||
read-idle-time: 120
|
||||
write-idle-time: 20
|
||||
all-idle-time-seconds: 0
|
||||
# 隧道相关配置-用于维持服务端与客户端的通信
|
||||
tunnel:
|
||||
# 线程池相关配置,用于技术调优,可忽略
|
||||
boss-thread-count: 2
|
||||
work-thread-count: 10
|
||||
# 隧道非SSL端口
|
||||
port: ${OPEN_PORT:9000}
|
||||
# 隧道SSL端口
|
||||
ssl-port: ${SSL_PORT:9002}
|
||||
# 隧道SSL证书配置
|
||||
key-store-password: ${STORE_PASS:123456}
|
||||
key-manager-password: ${MGR_PASS:123456}
|
||||
jks-path: ${JKS_PATH:classpath:/test.jks}
|
||||
# 是否开启隧道传输报文日志(日志级别为debug时开启才有效)
|
||||
transfer-log-enable: ${TUNNEL_LOG:false}
|
||||
# 是否开启心跳日志
|
||||
heartbeat-log-enable: ${HEARTBEAT_LOG:false}
|
||||
server:
|
||||
port: 9000
|
||||
ssl-port: 9002
|
||||
key-store-password: 123456
|
||||
key-manager-password: 123456
|
||||
jks-path: classpath:/test.jks
|
||||
tcp:
|
||||
# 线程池相关配置,用于技术调优,可忽略
|
||||
boss-thread-count: 5
|
||||
work-thread-count: 20
|
||||
# http代理端口,默认80
|
||||
http-proxy-port: ${HTTP_PROXY_PORT:80}
|
||||
# https代理端口,默认443 (需要配置域名、证书)
|
||||
https-proxy-port: ${HTTPS_PROXY_PORT:443}
|
||||
# 如果不配置,则不支持域名映射
|
||||
domain-name: ${DOMAIN_NAME:}
|
||||
# https证书配置
|
||||
key-store-password: ${HTTPS_STORE_PASS:}
|
||||
jks-path: ${HTTPS_JKS_PATH:}
|
||||
# 是否开启代理服务报文日志(日志级别为debug时开启才有效)
|
||||
transfer-log-enable: ${SERVER_LOG:false}
|
||||
udp:
|
||||
# 线程池相关配置,用于技术调优,可忽略
|
||||
boss-thread-count: 5
|
||||
work-thread-count: 20
|
||||
# 是否开启代理服务报文日志(日志级别为debug时开启才有效)
|
||||
transfer-log-enable: ${SERVER_LOG:false}
|
||||
data:
|
||||
db:
|
||||
type: sqlite
|
||||
url: jdbc:sqlite:data.db
|
||||
driver-class: org.sqlite.JDBC
|
||||
username:
|
||||
password:
|
||||
# 数据库类型,目前支持h2、mysql、mariadb
|
||||
type: ${DB_TYPE:h2}
|
||||
# 数据库连接URL
|
||||
url: ${DB_URL:jdbc:h2:file:~/.neutrino-proxy/data/db;MODE=MySQL;AUTO_SERVER=TRUE}
|
||||
# 数据库用户名
|
||||
username: ${DB_USER:}
|
||||
# 数据库密码
|
||||
password: ${DB_PASSWORD:}
|
||||
|
||||
#添加MIME印射(如果有需要?)
|
||||
#是否启用静态文件服务。(可不配,默认为启用)
|
||||
@@ -38,12 +95,12 @@ solon.staticfiles.mappings:
|
||||
|
||||
mybatis.db:
|
||||
typeAliases: #支持包名 或 类名(大写开头 或 *)//支持 ** 或 * 占位符
|
||||
- "fun.asgc.neutrino.proxy.server.dal.entity"
|
||||
- "org.dromara.neutrinoproxy.server.dal.entity"
|
||||
mappers: #支持包名 或 类名(大写开头 或 *)或 xml(.xml结尾)//支持 ** 或 * 占位符
|
||||
- "classpath:mapper/*.xml"
|
||||
- "fun.asgc.neutrino.proxy.server.dal"
|
||||
- "org.dromara.neutrinoproxy.server.dal"
|
||||
configuration: #扩展配置(要与 MybatisConfiguration 类的属性一一对应)
|
||||
cacheEnabled: false
|
||||
mapUnderscoreToCamelCase: true
|
||||
globalConfig: #全局配置(要与 GlobalConfig 类的属性一一对应)
|
||||
banner: true
|
||||
banner: true
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<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" />
|
||||
|
||||
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>DEBUG</level>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
<charset>${ENCODE}</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_FILE}</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.zip</fileNamePattern>
|
||||
<maxHistory>7</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
<charset>${ENCODE}</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="console"/>
|
||||
<appender-ref ref="file"/>
|
||||
</root>
|
||||
|
||||
<logger name="com.example.logback" level="warn"/>
|
||||
<logger name="io.netty" level="warn"/>
|
||||
</configuration>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.dromara.neutrinoproxy.server.dal.PortGroupMapper">
|
||||
|
||||
<select id="selectPortGroupListResList" resultType="org.dromara.neutrinoproxy.server.controller.res.system.PortGroupListRes">
|
||||
SELECT g.*,
|
||||
CASE
|
||||
WHEN g.possessor_type = 1 THEN
|
||||
u.`name`
|
||||
WHEN g.possessor_type = 2 THEN
|
||||
l.`name`
|
||||
ELSE ''
|
||||
END possessor
|
||||
FROM port_group g
|
||||
LEFT JOIN `user` u ON g.possessor_id = u.id
|
||||
LEFT JOIN license l ON g.possessor_id = l.id
|
||||
ORDER BY g.id
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.dromara.neutrinoproxy.server.dal.PortMappingMapper">
|
||||
|
||||
<select id="selectPortMappingByCondition" resultType="org.dromara.neutrinoproxy.server.dal.entity.PortMappingDO">
|
||||
SELECT pm.* from port_mapping pm left join license on pm.license_id = license.`id`
|
||||
<where>
|
||||
<if test="req.userId != null">
|
||||
AND license.user_id = #{req.userId}
|
||||
</if>
|
||||
<if test="req.licenseId != null">
|
||||
AND pm.license_id = #{req.licenseId}
|
||||
</if>
|
||||
<if test="req.serverPort != null">
|
||||
AND pm.server_port = #{req.serverPort}
|
||||
</if>
|
||||
<if test="req.isOnline != null">
|
||||
AND pm.is_online = #{req.isOnline}
|
||||
</if>
|
||||
<if test="req.enable != null">
|
||||
AND pm.enable = #{req.enable}
|
||||
</if>
|
||||
<if test="req.protocal != null and req.protocal != ''">
|
||||
AND pm.protocal = #{req.protocal}
|
||||
</if>
|
||||
<if test="req.description != null and req.description != '' ">
|
||||
AND pm.description like #{req.description}
|
||||
</if>
|
||||
</where>
|
||||
order by pm.id asc
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.dromara.neutrinoproxy.server.dal.PortPoolMapper">
|
||||
|
||||
<select id="selectResList" resultType="org.dromara.neutrinoproxy.server.controller.res.system.PortPoolListRes">
|
||||
SELECT p.*,
|
||||
g.possessor_type,
|
||||
g.`name` group_name
|
||||
FROM port_pool p
|
||||
LEFT JOIN port_group g ON p.group_id = g.id
|
||||
<where>
|
||||
<if test="req.groupId != null">
|
||||
AND p.group_id = #{req.groupId}
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY p.id
|
||||
</select>
|
||||
|
||||
<select id="getAvailablePortList" resultType="org.dromara.neutrinoproxy.server.controller.res.system.PortPoolListRes">
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
port_pool
|
||||
WHERE
|
||||
`port` NOT IN ( SELECT server_port FROM port_mapping )
|
||||
AND group_id IN (
|
||||
SELECT
|
||||
id
|
||||
FROM
|
||||
port_group
|
||||
WHERE
|
||||
possessor_type = 0
|
||||
OR ( possessor_type = 1 AND possessor_id = #{userId,jdbcType=INTEGER} )
|
||||
OR ( possessor_type = 2 AND possessor_id = #{licenseId,jdbcType=INTEGER} )
|
||||
)
|
||||
<if test="keyword!=null and keyword!=''">
|
||||
AND `port` LIKE #{keyword}
|
||||
</if>
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -0,0 +1,253 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.dromara.neutrinoproxy.server.dal.ReportMapper">
|
||||
|
||||
<select id="userFlowReportList" resultType="org.dromara.neutrinoproxy.server.controller.res.report.UserFlowReportRes">
|
||||
SELECT
|
||||
u.id AS userId,
|
||||
u.name AS userName,
|
||||
SUM(COALESCE(frm.write_bytes,0) + COALESCE(frd.write_bytes,0) + COALESCE(frm2.write_bytes,0)) AS upFlowBytes,
|
||||
SUM(COALESCE(frm.read_bytes,0) + COALESCE(frd.read_bytes,0) + COALESCE(frm2.read_bytes,0)) AS downFlowBytes,
|
||||
COALESCE(SUM(frm.write_bytes),0) monthWriteBytes,
|
||||
COALESCE(SUM(frm.read_bytes),0) monthReadBytes,
|
||||
COALESCE(SUM(frd.write_bytes),0) dayWriteBytes,
|
||||
COALESCE(SUM(frd.read_bytes),0) dayReadBytes,
|
||||
COALESCE(SUM(frm2.write_bytes),0) minuteWriteBytes,
|
||||
COALESCE(SUM(frm2.read_bytes),0) minuteReadBytes
|
||||
FROM `user` u
|
||||
LEFT JOIN (SELECT user_id,sum(write_bytes) write_bytes,sum(read_bytes) read_bytes from flow_report_month GROUP BY user_id) frm ON u.id = frm.user_id
|
||||
LEFT JOIN (SELECT user_id,sum(write_bytes) write_bytes,sum(read_bytes) read_bytes from flow_report_day
|
||||
WHERE date >= #{curMonthBeginDate} AND date <= #{curDayBeginDate}
|
||||
GROUP BY user_id) frd ON u.id = frd.user_id
|
||||
LEFT JOIN (SELECT user_id,sum(write_bytes) write_bytes,sum(read_bytes) read_bytes from flow_report_minute
|
||||
WHERE date >= #{curDayBeginDate} AND date <= #{curDate}
|
||||
GROUP BY user_id) frm2 ON u.id = frm2.user_id
|
||||
<where>
|
||||
<if test="userId != null">
|
||||
AND u.id = #{userId}
|
||||
</if>
|
||||
</where>
|
||||
GROUP BY u.id
|
||||
</select>
|
||||
|
||||
<select id="licenseFLowReportList" resultType="org.dromara.neutrinoproxy.server.controller.res.report.LicenseFlowReportRes">
|
||||
SELECT
|
||||
l.id AS licenseId,
|
||||
l.name AS licenseName,
|
||||
u.id AS userId,
|
||||
u.name AS userName,
|
||||
SUM(COALESCE(frm.write_bytes,0) + COALESCE(frd.write_bytes,0) + COALESCE(frm2.write_bytes,0)) AS upFlowBytes,
|
||||
SUM(COALESCE(frm.read_bytes,0) + COALESCE(frd.read_bytes,0) + COALESCE(frm2.read_bytes,0)) AS downFlowBytes,
|
||||
COALESCE(SUM(frm.write_bytes),0) monthWriteBytes,
|
||||
COALESCE(SUM(frm.read_bytes),0) monthReadBytes,
|
||||
COALESCE(SUM(frd.write_bytes),0) dayWriteBytes,
|
||||
COALESCE(SUM(frd.read_bytes),0) dayReadBytes,
|
||||
COALESCE(SUM(frm2.write_bytes),0) minuteWriteBytes,
|
||||
COALESCE(SUM(frm2.read_bytes),0) minuteReadBytes
|
||||
FROM `license` l
|
||||
LEFT JOIN `user` u ON l.user_id = u.id
|
||||
LEFT JOIN (SELECT license_id,sum(write_bytes) write_bytes,sum(read_bytes) read_bytes from flow_report_month GROUP BY license_id) frm ON l.id = frm.license_id
|
||||
LEFT JOIN (SELECT license_id,sum(write_bytes) write_bytes,sum(read_bytes) read_bytes from flow_report_day
|
||||
WHERE date >= #{curMonthBeginDate} AND #{curDayBeginDate}
|
||||
GROUP BY license_id) frd ON l.id = frd.license_id
|
||||
LEFT JOIN (SELECT license_id,sum(write_bytes) write_bytes,sum(read_bytes) read_bytes from flow_report_minute
|
||||
WHERE date >= #{curDayBeginDate} AND date <= #{curDate}
|
||||
GROUP BY license_id)frm2 ON l.id = frm2.license_id
|
||||
<where>
|
||||
<if test="userId != null">
|
||||
AND u.id = #{userId}
|
||||
</if>
|
||||
</where>
|
||||
GROUP BY l.id
|
||||
</select>
|
||||
|
||||
<select id="userFlowMonthReportList" resultType="org.dromara.neutrinoproxy.server.controller.res.report.UserFlowMonthReportRes">
|
||||
SELECT
|
||||
T2.user_id,
|
||||
u.name AS userName,
|
||||
T2.date,
|
||||
T2.upFlowBytes,
|
||||
T2.downFlowBytes
|
||||
FROM (
|
||||
|
||||
SELECT
|
||||
frm.user_id,
|
||||
frm.date,
|
||||
COALESCE(SUM(frm.write_bytes),0) AS upFlowBytes,
|
||||
COALESCE(SUM(frm.read_bytes),0) AS downFlowBytes
|
||||
FROM flow_report_month frm
|
||||
GROUP BY frm.user_id,frm.date
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
T1.user_id,
|
||||
#{curMonthBeginDate} AS date,
|
||||
COALESCE(SUM(T1.upFlowBytes),0) AS upFlowBytes,
|
||||
COALESCE(SUM(T1.downFlowBytes),0) AS downFlowBytes
|
||||
FROM (
|
||||
|
||||
SELECT
|
||||
frd.user_id,
|
||||
frd.license_id,
|
||||
frd.write_bytes AS upFlowBytes,
|
||||
frd.read_bytes AS downFlowBytes,
|
||||
frd.date
|
||||
FROM flow_report_day frd
|
||||
WHERE frd.date >= #{curMonthBeginDate} AND frd.date <= #{curDayBeginDate}
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
frm.user_id,
|
||||
frm.license_id,
|
||||
COALESCE(SUM(frm.write_bytes),0) AS upFlowBytes,
|
||||
COALESCE(SUM(frm.read_bytes),0) AS downFlowBytes,
|
||||
#{curDayBeginDate} AS date
|
||||
FROM flow_report_minute frm
|
||||
WHERE frm.date >= #{curDayBeginDate} AND frm.date <= #{curDate}
|
||||
GROUP BY frm.user_id,frm.license_id
|
||||
|
||||
) T1
|
||||
GROUP BY T1.user_id
|
||||
|
||||
) T2
|
||||
LEFT JOIN `user` u ON u.id = T2.user_id
|
||||
<where>
|
||||
<if test="userId != null">
|
||||
AND u.id = #{userId}
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY T2.user_id ASC,T2.date DESC
|
||||
</select>
|
||||
|
||||
<select id="licenseFLowMonthReportList" resultType="org.dromara.neutrinoproxy.server.controller.res.report.LicenseFlowMonthReportRes">
|
||||
SELECT
|
||||
T2.user_id,
|
||||
u.name AS userName,
|
||||
T2.license_id,
|
||||
l.`name` AS licenseName,
|
||||
T2.date,
|
||||
T2.upFlowBytes,
|
||||
T2.downFlowBytes
|
||||
FROM (
|
||||
|
||||
SELECT
|
||||
frm.user_id,
|
||||
frm.license_id,
|
||||
frm.date,
|
||||
COALESCE(SUM(frm.write_bytes),0) AS upFlowBytes,
|
||||
COALESCE(SUM(frm.read_bytes),0) AS downFlowBytes
|
||||
FROM flow_report_month frm
|
||||
GROUP BY frm.user_id,frm.license_id,frm.date
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
T1.user_id,
|
||||
T1.license_id,
|
||||
#{curMonthBeginDate} AS date,
|
||||
COALESCE(SUM(T1.upFlowBytes),0) AS upFlowBytes,
|
||||
COALESCE(SUM(T1.downFlowBytes),0) AS downFlowBytes
|
||||
FROM (
|
||||
|
||||
SELECT
|
||||
frd.user_id,
|
||||
frd.license_id,
|
||||
frd.write_bytes AS upFlowBytes,
|
||||
frd.read_bytes AS downFlowBytes,
|
||||
frd.date
|
||||
FROM flow_report_day frd
|
||||
WHERE frd.date >= #{curMonthBeginDate} AND frd.date <= #{curDayBeginDate}
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
frm.user_id,
|
||||
frm.license_id,
|
||||
COALESCE(SUM(frm.write_bytes),0) AS upFlowBytes,
|
||||
COALESCE(SUM(frm.read_bytes),0) AS downFlowBytes,
|
||||
#{curDayBeginDate} AS date
|
||||
FROM flow_report_minute frm
|
||||
WHERE frm.date >= #{curDayBeginDate} AND frm.date <= #{curDate}
|
||||
GROUP BY frm.user_id,frm.license_id
|
||||
|
||||
) T1
|
||||
GROUP BY T1.user_id,T1.license_id
|
||||
|
||||
) T2
|
||||
LEFT JOIN `user` u ON u.id = T2.user_id
|
||||
LEFT JOIN `license` l ON l.id = T2.license_id
|
||||
<where>
|
||||
<if test="userId != null">
|
||||
AND u.id = #{userId}
|
||||
</if>
|
||||
<if test="licenseId != null">
|
||||
AND l.id = #{licenseId}
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY T2.user_id ASC,T2.license_id,T2.date DESC
|
||||
</select>
|
||||
|
||||
<select id="homeTodayFlow" resultType="org.dromara.neutrinoproxy.server.service.bo.FlowBO">
|
||||
SELECT
|
||||
COALESCE(SUM(frm.write_bytes),0) AS upFlowBytes,
|
||||
COALESCE(SUM(frm.read_bytes),0) AS downFlowBytes
|
||||
FROM flow_report_minute frm
|
||||
WHERE frm.date >= #{curDayBeginDate} AND frm.date <= #{curDate}
|
||||
</select>
|
||||
|
||||
<select id="homeTotalFlow" resultType="org.dromara.neutrinoproxy.server.service.bo.FlowBO">
|
||||
SELECT
|
||||
COALESCE(SUM(T.upFlowBytes),0) AS upFlowBytes,
|
||||
COALESCE(SUM(T.downFlowBytes),0) AS downFlowBytes
|
||||
FROM (
|
||||
|
||||
SELECT
|
||||
COALESCE(SUM(frm.write_bytes),0) AS upFlowBytes,
|
||||
COALESCE(SUM(frm.read_bytes),0) AS downFlowBytes
|
||||
FROM flow_report_month frm
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
COALESCE(SUM(frd.write_bytes),0) AS upFlowBytes,
|
||||
COALESCE(SUM(frd.read_bytes),0) AS downFlowBytes
|
||||
FROM flow_report_day frd
|
||||
WHERE frd.date >= #{curMonthBeginDate} AND frd.date <= #{curDayBeginDate}
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
COALESCE(SUM(frm.write_bytes),0) AS upFlowBytes,
|
||||
COALESCE(SUM(frm.read_bytes),0) AS downFlowBytes
|
||||
FROM flow_report_minute frm
|
||||
WHERE frm.date >= #{curDayBeginDate} AND frm.date <= #{curDate}
|
||||
|
||||
) T
|
||||
</select>
|
||||
|
||||
<select id="homeLast7dFlowList" resultType="org.dromara.neutrinoproxy.server.service.bo.SingleDayFlowBO">
|
||||
SELECT
|
||||
T.*
|
||||
FROM (
|
||||
|
||||
SELECT
|
||||
frd.date,
|
||||
COALESCE(SUM(frd.write_bytes),0) AS upFlowBytes,
|
||||
COALESCE(SUM(frd.read_bytes),0) AS downFlowBytes
|
||||
FROM flow_report_day frd
|
||||
WHERE frd.date >= #{beginDate} AND frd.date <= #{curDayBeginDate}
|
||||
GROUP BY frd.date
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
#{curDayBeginDate} AS date,
|
||||
COALESCE(SUM(frm.write_bytes),0) AS upFlowBytes,
|
||||
COALESCE(SUM(frm.read_bytes),0) AS downFlowBytes
|
||||
FROM flow_report_minute frm
|
||||
WHERE frm.date >= #{curDayBeginDate} AND frm.date <= #{curDate}
|
||||
|
||||
) T
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -1,24 +1,24 @@
|
||||
#############################系统管理相关表#############################
|
||||
#用户表
|
||||
CREATE TABLE IF NOT EXISTS `user` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`name` VARCHAR(50) NOT NULL,
|
||||
`login_name` VARCHAR(50) NOT NULL,
|
||||
`login_password` VARCHAR(255) NOT NULL,
|
||||
`enable` INTEGER(2) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL,
|
||||
`update_time` INTEGER(20) NOT NULL
|
||||
`create_time` TIMESTAMP NOT NULL,
|
||||
`update_time` TIMESTAMP NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS I_user_login_name ON `user` (login_name ASC);
|
||||
|
||||
#用户token表
|
||||
CREATE TABLE IF NOT EXISTS `user_token` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`token` VARCHAR(50) NOT NULL,
|
||||
`user_id` INTEGER NOT NULL,
|
||||
`expiration_time` INTEGER NOT NULL,
|
||||
`create_time` INTEGER NOT NULL,
|
||||
`update_time` INTEGER NOT NULL
|
||||
`expiration_time` TIMESTAMP NOT NULL,
|
||||
`create_time` TIMESTAMP NOT NULL,
|
||||
`update_time` TIMESTAMP NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS I_user_token_user_id ON user_token(user_id);
|
||||
CREATE INDEX IF NOT EXISTS I_user_token_token ON user_token(token);
|
||||
@@ -26,67 +26,83 @@ CREATE INDEX IF NOT EXISTS I_user_token_expiration_time ON user_token(expiration
|
||||
|
||||
#端口池
|
||||
CREATE TABLE IF NOT EXISTS `port_pool` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`group_id` INTEGER NOT NULL DEFAULT 1,
|
||||
`port` INTEGER NOT NULL,
|
||||
`enable` INTEGER(2) NOT NULL,
|
||||
`update_time` INTEGER(20) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL
|
||||
`update_time` TIMESTAMP NOT NULL,
|
||||
`create_time` TIMESTAMP NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS I_port_pool_port ON port_pool (port ASC);
|
||||
|
||||
#端口分组
|
||||
CREATE TABLE IF NOT EXISTS `port_group` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`name` VARCHAR(255) NOT NULL,
|
||||
`possessor_type` INTEGER NOT NULL DEFAULT '0',
|
||||
`possessor_id` INTEGER NOT NULL DEFAULT '-1',
|
||||
`enable` INTEGER NOT NULL,
|
||||
`create_time` TIMESTAMP NOT NULL,
|
||||
`update_time` TIMESTAMP NOT NULL
|
||||
);
|
||||
#############################代理配置相关表#############################
|
||||
#license表
|
||||
CREATE TABLE IF NOT EXISTS `license` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`name` VARCHAR(50) NOT NULL,
|
||||
`key` VARCHAR(100) NOT NULL,
|
||||
`user_id` INTEGER NOT NULL,
|
||||
`is_online` INTEGER(2) NOT NULL,
|
||||
`enable` INTEGER(2) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL,
|
||||
`update_time` INTEGER(20) NOT NULL
|
||||
`create_time` TIMESTAMP NOT NULL,
|
||||
`update_time` TIMESTAMP NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS I_license_key ON `license` (`key` ASC);
|
||||
|
||||
#端口映射表
|
||||
CREATE TABLE IF NOT EXISTS `port_mapping` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`license_id` INTEGER(20) NOT NULL,
|
||||
`protocal` VARCHAR(10) NOT NULL DEFAULT 'TCP',
|
||||
`subdomain` VARCHAR(50) DEFAULT NULL,
|
||||
`server_port` INTEGER NOT NULL,
|
||||
`client_ip` VARCHAR(20) NOT NULL,
|
||||
`client_port` INTEGER NOT NULL,
|
||||
`is_online` INTEGER(2) NOT NULL,
|
||||
`description` VARCHAR(100) DEFAULT NULL,
|
||||
`proxy_responses` INTEGER(20) NOT NULL DEFAULT 0,
|
||||
`proxy_timeout_ms` INTEGER(20) NOT NULL DEFAULT 0,
|
||||
`enable` INTEGER(2) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL,
|
||||
`update_time` INTEGER(20) NOT NULL
|
||||
`create_time` TIMESTAMP NOT NULL,
|
||||
`update_time` TIMESTAMP NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS I_port_mapping_server_port ON port_mapping (server_port ASC);
|
||||
|
||||
#############################日志管理相关表#############################
|
||||
#用户登录记录表
|
||||
CREATE TABLE IF NOT EXISTS `user_login_record` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`user_id` INTEGER NOT NULL,
|
||||
`ip` VARCHAR(50) NOT NULL,
|
||||
`token` VARCHAR(100) NOT NULL,
|
||||
`type` INTEGER(2) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL
|
||||
`create_time` TIMESTAMP NOT NULL
|
||||
);
|
||||
#客户端连接记录表
|
||||
CREATE TABLE IF NOT EXISTS `client_connect_record` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`ip` VARCHAR(50) NOT NULL,
|
||||
`license_id` INTEGER(20) DEFAULT NULL,
|
||||
`type` INTEGER(2) NOT NULL,
|
||||
`msg` VARCHAR(512) DEFAULT NULL,
|
||||
`code` INTEGER(2) NOT NULL,
|
||||
`err` VARCHAR(512) DEFAULT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL
|
||||
`create_time` TIMESTAMP NOT NULL
|
||||
);
|
||||
#############################调度管理相关表#############################
|
||||
#触发器信息表
|
||||
CREATE TABLE IF NOT EXISTS `job_info` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`desc` VARCHAR(255) NOT NULL,
|
||||
`handler` VARCHAR(255) NOT NULL,
|
||||
`cron` VARCHAR(128) NOT NULL ,
|
||||
@@ -94,21 +110,21 @@ CREATE TABLE IF NOT EXISTS `job_info` (
|
||||
`alarm_email` VARCHAR(255) DEFAULT NULL,
|
||||
`alarm_ding` VARCHAR(255) DEFAULT NULL,
|
||||
`enable` INTEGER(2) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL,
|
||||
`update_time` INTEGER(20) NOT NULL
|
||||
`create_time` TIMESTAMP NOT NULL,
|
||||
`update_time` TIMESTAMP NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS I_job_info_handler ON `job_info` (`handler` ASC);
|
||||
|
||||
#触发器日志表
|
||||
CREATE TABLE IF NOT EXISTS `job_log` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`job_id` INTEGER(20) NOT NULL,
|
||||
`handler` VARCHAR(255) NOT NULL,
|
||||
`param` VARCHAR(512) DEFAULT NULL,
|
||||
`code` INTEGER(20) NOT NULL,
|
||||
`msg` TEXT DEFAULT NULL,
|
||||
`alarm_status` INTEGER(2) NOT NULL DEFAULT '0',
|
||||
`create_time` INTEGER(20) NOT NULL
|
||||
`create_time` TIMESTAMP NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS I_job_log_create_time ON job_log(create_time);
|
||||
CREATE INDEX IF NOT EXISTS I_job_log_code ON job_log(code);
|
||||
@@ -116,14 +132,14 @@ CREATE INDEX IF NOT EXISTS I_job_log_code ON job_log(code);
|
||||
#############################报表管理相关表#############################
|
||||
#流量统计报表-分钟(保留24小时)
|
||||
CREATE TABLE IF NOT EXISTS `flow_report_minute` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`user_id` INTEGER(20) NOT NULL,
|
||||
`license_id` INTEGER(20) NOT NULL,
|
||||
`write_bytes` INTEGER(20) NOT NULL,
|
||||
`read_bytes` INTEGER(20) NOT NULL,
|
||||
`date` INTEGER(20) NOT NULL,
|
||||
`date` TIMESTAMP NOT NULL,
|
||||
`date_str` VARCHAR(20) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL
|
||||
`create_time` TIMESTAMP NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS I_flow_report_minute_create_time ON flow_report_minute(create_time);
|
||||
CREATE INDEX IF NOT EXISTS I_flow_report_minute_date ON flow_report_minute(`date`);
|
||||
@@ -132,14 +148,14 @@ CREATE INDEX IF NOT EXISTS I_flow_report_minute_license_id ON flow_report_minute
|
||||
|
||||
#流量统计报表-小时(保留60天)
|
||||
CREATE TABLE IF NOT EXISTS `flow_report_hour` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`user_id` INTEGER(20) NOT NULL,
|
||||
`license_id` INTEGER(20) NOT NULL,
|
||||
`write_bytes` INTEGER(20) NOT NULL,
|
||||
`read_bytes` INTEGER(20) NOT NULL,
|
||||
`date` INTEGER(20) NOT NULL,
|
||||
`date` TIMESTAMP NOT NULL,
|
||||
`date_str` VARCHAR(20) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL
|
||||
`create_time` TIMESTAMP NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS I_flow_report_hour_create_time ON flow_report_hour(create_time);
|
||||
CREATE INDEX IF NOT EXISTS I_flow_report_hour_date ON flow_report_hour(`date`);
|
||||
@@ -148,14 +164,14 @@ CREATE INDEX IF NOT EXISTS I_flow_report_hour_license_id ON flow_report_hour(`li
|
||||
|
||||
#流量统计报表-天(保留1年)
|
||||
CREATE TABLE IF NOT EXISTS `flow_report_day` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`user_id` INTEGER(20) NOT NULL,
|
||||
`license_id` INTEGER(20) NOT NULL,
|
||||
`write_bytes` INTEGER(20) NOT NULL,
|
||||
`read_bytes` INTEGER(20) NOT NULL,
|
||||
`date` INTEGER(20) NOT NULL,
|
||||
`date` TIMESTAMP NOT NULL,
|
||||
`date_str` VARCHAR(20) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL
|
||||
`create_time` TIMESTAMP NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS I_flow_report_day_create_time ON flow_report_day(create_time);
|
||||
CREATE INDEX IF NOT EXISTS I_flow_report_day_date ON flow_report_day(`date`);
|
||||
@@ -164,16 +180,16 @@ CREATE INDEX IF NOT EXISTS I_flow_report_day_license_id ON flow_report_day(`lice
|
||||
|
||||
#流量统计报表-月(全量保留)
|
||||
CREATE TABLE IF NOT EXISTS `flow_report_month` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
`user_id` INTEGER(20) NOT NULL,
|
||||
`license_id` INTEGER(20) NOT NULL,
|
||||
`write_bytes` INTEGER(20) NOT NULL,
|
||||
`read_bytes` INTEGER(20) NOT NULL,
|
||||
`date` INTEGER(20) NOT NULL,
|
||||
`date` TIMESTAMP NOT NULL,
|
||||
`date_str` VARCHAR(20) NOT NULL,
|
||||
`create_time` INTEGER(20) NOT NULL
|
||||
`create_time` TIMESTAMP NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS I_flow_report_month_create_time ON flow_report_month(create_time);
|
||||
CREATE INDEX IF NOT EXISTS I_flow_report_month_date ON flow_report_month(`date`);
|
||||
CREATE INDEX IF NOT EXISTS I_flow_report_month_user_id ON flow_report_month(`user_id`);
|
||||
CREATE INDEX IF NOT EXISTS I_flow_report_month_license_id ON flow_report_month(`license_id`);
|
||||
CREATE INDEX IF NOT EXISTS I_flow_report_month_license_id ON flow_report_month(`license_id`);
|
||||
@@ -1,13 +1,13 @@
|
||||
#job_qrtz_trigger_info
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(1, '示例Job', 'DemoJob', '0/10 * * * * ?', '{"a":101}', 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
|
||||
(1, '示例Job', 'DemoJob', '0/10 * * * * ?', '{"a":101}', 1, now(), now());
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(2, '数据清理任务', 'DataCleanJob', '0 0 1 * * ?', '', 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
|
||||
(2, '数据清理任务', 'DataCleanJob', '0 0 1 * * ?', '', 1, now(), now());
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(3, '流量统计报表-分钟', 'FlowReportForMinuteJob', '0 */1 * * * ?', '', 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
|
||||
(3, '流量统计报表-分钟', 'FlowReportForMinuteJob', '0 */1 * * * ?', '', 1, now(), now());
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(4, '流量统计报表-小时', 'FlowReportForHourJob', '0 0 */1 * * ?', '', 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
|
||||
(4, '流量统计报表-小时', 'FlowReportForHourJob', '0 0 */1 * * ?', '', 1, now(), now());
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(5, '流量统计报表-天', 'FlowReportForDayJob', '0 0 1 * * ?', '', 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
|
||||
(5, '流量统计报表-天', 'FlowReportForDayJob', '0 0 1 * * ?', '', 1, now(), now());
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(6, '流量统计报表-月', 'FlowReportForMonthJob', '0 30 1 1 * ?', '', 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
|
||||
(6, '流量统计报表-月', 'FlowReportForMonthJob', '0 30 1 1 * ?', '', 1, now(), now());
|
||||
@@ -1,3 +1,3 @@
|
||||
#license
|
||||
INSERT INTO license(`id`, `name`, `key`, `user_id`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(1, '我的mac', 'b0a907332b474b25897c4dcb31fc7eb6', 1, 2, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
|
||||
(1, '我的mac', 'b0a907332b474b25897c4dcb31fc7eb6', 1, 2, 1, now(), now());
|
||||
@@ -0,0 +1,3 @@
|
||||
#port_group
|
||||
INSERT INTO `port_group`(`id`,`name`,`possessor_type`,`possessor_id`,`enable`,`create_time`,`update_time`) VALUES
|
||||
(1, '全局(默认)', 0, -1, 1, now(), now());
|
||||
@@ -0,0 +1,7 @@
|
||||
#port_mapping
|
||||
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`,`client_ip`, `client_port`, `is_online`, `enable`,`description`,`create_time`, `update_time`) VALUES
|
||||
(1, 1, 9101, 'HTTP', 'test1', '127.0.0.1', 8080, 2, 1,'test1', now(), now());
|
||||
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`,`description`, `create_time`, `update_time`) VALUES
|
||||
(2, 1, 9102, 'TCP', '', '127.0.0.1', 3306, 2, 1,'test2', now(), now());
|
||||
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`,`description`, `create_time`, `update_time`) VALUES
|
||||
(3, 1, 9103, 'HTTP', 'test2', '127.0.0.1', 8081, 2, 1,'test3', now(), now());
|
||||
@@ -0,0 +1,41 @@
|
||||
#端口池
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(1, 1, 9101, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(2, 1, 9102, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(3, 1, 9103, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(4, 1, 9104, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(5, 1, 9105, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(6, 1, 9106, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(7, 1, 9107, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(8, 1, 9108, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(9, 1, 9109, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(10, 1, 9110, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(11, 1, 9111, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(12, 1, 9112, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(13, 1, 9113, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(14, 1, 9114, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(15, 1, 9115, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(16, 1, 9116, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(17, 1, 9117, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(18, 1, 9118, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(19, 1, 9119, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(20, 1, 9120, 1, now(), now());
|
||||
@@ -0,0 +1,12 @@
|
||||
ALTER TABLE port_pool ADD group_id INTEGER NOT NULL DEFAULT 1;
|
||||
|
||||
#端口分组
|
||||
CREATE TABLE IF NOT EXISTS `port_group` (
|
||||
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
`name` VARCHAR(255) NOT NULL,
|
||||
`possessor_type` INTEGER NOT NULL DEFAULT '0',
|
||||
`possessor_id` INTEGER NOT NULL DEFAULT '-1',
|
||||
`enable` INTEGER NOT NULL,
|
||||
`create_time` datetime(3) NOT NULL,
|
||||
`update_time` datetime(3) NOT NULL
|
||||
);
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE port_mapping ADD `protocal` varchar(10) NOT NULL DEFAULT 'TCP';
|
||||
ALTER TABLE port_mapping ADD `subdomain` varchar(50) DEFAULT NULL;
|
||||
ALTER TABLE port_mapping ADD `description` varchar(100);
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE port_mapping ADD `proxy_responses` INTEGER(20) NOT NULL DEFAULT 0;
|
||||
ALTER TABLE port_mapping ADD `proxy_timeout_ms` INTEGER(20) NOT NULL DEFAULT 0;
|
||||
@@ -1,5 +1,5 @@
|
||||
#用户表 6613b92b77056faeb72068f184ed4c4f
|
||||
INSERT INTO `user`(`id`, `name`,`login_name`,`login_password`,`enable`,`create_time`, `update_time`) VALUES
|
||||
(1, '管理员', 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
|
||||
(1, '管理员', 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, now(), now());
|
||||
INSERT INTO `user`(`id`, `name`,`login_name`,`login_password`,`enable`,`create_time`, `update_time`) VALUES
|
||||
(2, '游客', 'visitor', 'e10adc3949ba59abbe56e057f20f883e', 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
|
||||
(2, '游客', 'visitor', 'e10adc3949ba59abbe56e057f20f883e', 1, now(), now());
|
||||
@@ -0,0 +1,209 @@
|
||||
#############################系统管理相关表#############################
|
||||
#用户表
|
||||
CREATE TABLE IF NOT EXISTS `user` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(50) NOT NULL COMMENT '用户名',
|
||||
`login_name` varchar(50) NOT NULL COMMENT '登录名',
|
||||
`login_password` varchar(255) NOT NULL COMMENT '登录密码',
|
||||
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `I_user_login_name` (`login_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
#用户token表
|
||||
CREATE TABLE IF NOT EXISTS `user_token` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`token` varchar(50) NOT NULL COMMENT 'token',
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`expiration_time` datetime(3) NOT NULL COMMENT '过期时间',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `I_user_token_user_id` (`user_id`),
|
||||
KEY `I_user_token_token` (`token`),
|
||||
KEY `I_user_token_expiration_time` (`expiration_time`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
#端口池
|
||||
CREATE TABLE IF NOT EXISTS `port_pool` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`group_id` int NOT NULL DEFAULT 1 COMMENT '分组ID',
|
||||
`port` int NOT NULL COMMENT '端口',
|
||||
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `I_port_pool_port` (`port`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
#端口分组
|
||||
CREATE TABLE IF NOT EXISTS `port_group` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(255) NOT NULL COMMENT '分组名称',
|
||||
`possessor_type` int NOT NULL DEFAULT '0' COMMENT '所有者类型 (0、全局共享 1、用户所有 2License所有) ',
|
||||
`possessor_id` int NOT NULL DEFAULT '-1' COMMENT '所有者id(当type为0时 固定为-1、当type为1时为用户id 、当type为2时为licenseid)',
|
||||
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
#############################代理配置相关表#############################
|
||||
#license表
|
||||
CREATE TABLE IF NOT EXISTS `license` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(50) NOT NULL COMMENT 'license名称',
|
||||
`key` varchar(100) NOT NULL COMMENT 'license key',
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`is_online` int NOT NULL COMMENT '是否在线(1、在线 2、离线)',
|
||||
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `I_license_key` (`key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
#端口映射表
|
||||
CREATE TABLE IF NOT EXISTS `port_mapping` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`license_id` int NOT NULL COMMENT 'licenseID',
|
||||
`protocal` varchar(10) NOT NULL DEFAULT 'TCP' COMMENT '协议',
|
||||
`subdomain` varchar(50) DEFAULT NULL COMMENT '子域名(仅HTTP时有效)',
|
||||
`server_port` int NOT NULL COMMENT '服务端端口',
|
||||
`client_ip` varchar(20) NOT NULL COMMENT '客户端IP',
|
||||
`client_port` int NOT NULL COMMENT '客户端端口',
|
||||
`is_online` int NOT NULL COMMENT '是否在线(1、在线 2、离线)',
|
||||
`description` varchar(100) DEFAULT NULL COMMENT '描述',
|
||||
`proxy_responses` int NOT NULL DEFAULT 0 COMMENT '代理响应数据包数量',
|
||||
`proxy_timeout_ms` int NOT NULL DEFAULT 0 COMMENT '代理超时毫秒数',
|
||||
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `I_port_mapping_server_port` (`server_port`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
#############################日志管理相关表#############################
|
||||
#用户登录记录表
|
||||
CREATE TABLE IF NOT EXISTS `user_login_record` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`ip` varchar(50) NOT NULL COMMENT 'IP',
|
||||
`token` varchar(100) NOT NULL COMMENT 'token',
|
||||
`type` int NOT NULL COMMENT '类型(1、登录 2、登出)',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
#客户端连接记录表
|
||||
CREATE TABLE IF NOT EXISTS `client_connect_record` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`ip` varchar(50) NOT NULL COMMENT 'IP',
|
||||
`license_id` int NOT NULL COMMENT 'licenseId',
|
||||
`type` int NOT NULL COMMENT '类型(1、连接 2、断开连接)',
|
||||
`msg` varchar(512) DEFAULT NULL COMMENT '消息',
|
||||
`code` int NOT NULL COMMENT '结果 (1、成功 2、失败)',
|
||||
`err` text DEFAULT NULL COMMENT '异常信息',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
#############################调度管理相关表#############################
|
||||
#触发器信息表
|
||||
CREATE TABLE IF NOT EXISTS `job_info` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`desc` varchar(255) NOT NULL COMMENT '描述',
|
||||
`handler` varchar(255) NOT NULL COMMENT '处理器',
|
||||
`cron` varchar(128) NOT NULL COMMENT 'cron',
|
||||
`param` varchar(512) DEFAULT NULL COMMENT '参数',
|
||||
`alarm_email` varchar(255) DEFAULT NULL COMMENT '报警邮箱',
|
||||
`alarm_ding` varchar(255) DEFAULT NULL COMMENT '报警钉钉配置',
|
||||
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `I_job_info_handler` (`handler`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
#触发器日志表
|
||||
CREATE TABLE IF NOT EXISTS `job_log` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`job_id` int NOT NULL COMMENT 'JobId',
|
||||
`handler` varchar(255) NOT NULL COMMENT '处理器',
|
||||
`param` varchar(512) DEFAULT NULL COMMENT '参数',
|
||||
`code` int NOT NULL COMMENT '结果(1、成功 2、失败)',
|
||||
`msg` text COMMENT '消息',
|
||||
`alarm_status` int NOT NULL COMMENT '报警状态(1、未报警 2、已报警)',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `I_job_log_create_time` (`create_time`) USING BTREE,
|
||||
KEY `I_job_log_code` (`code`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
#############################报表管理相关表#############################
|
||||
#流量统计报表-分钟(保留24小时)
|
||||
CREATE TABLE IF NOT EXISTS `flow_report_minute` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`license_id` int NOT NULL COMMENT 'licenseId',
|
||||
`write_bytes` int NOT NULL COMMENT '写入流量',
|
||||
`read_bytes` int NOT NULL COMMENT '读取流量',
|
||||
`date` datetime(3) NOT NULL COMMENT '时间',
|
||||
`date_str` varchar(20) NOT NULL COMMENT '时间 yyyy-MM-dd HH:mm',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `I_flow_report_minute_create_time` (`create_time`) USING BTREE,
|
||||
KEY `I_flow_report_minute_date` (`date`) USING BTREE,
|
||||
KEY `I_flow_report_minute_user_id` (`user_id`),
|
||||
KEY `I_flow_report_minute_license_id` (`license_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
#流量统计报表-小时(保留60天)
|
||||
CREATE TABLE IF NOT EXISTS `flow_report_hour` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`license_id` int NOT NULL COMMENT 'licenseId',
|
||||
`write_bytes` int NOT NULL COMMENT '写入流量',
|
||||
`read_bytes` int NOT NULL COMMENT '读取流量',
|
||||
`date` datetime(3) NOT NULL COMMENT '时间',
|
||||
`date_str` varchar(20) NOT NULL COMMENT '时间 yyyy-MM-dd HH',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `I_flow_report_hour_create_time` (`create_time`) USING BTREE,
|
||||
KEY `I_flow_report_hour_date` (`date`) USING BTREE,
|
||||
KEY `I_flow_report_hour_user_id` (`user_id`),
|
||||
KEY `I_flow_report_hour_license_id` (`license_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
#流量统计报表-天(保留1年)
|
||||
CREATE TABLE IF NOT EXISTS `flow_report_day` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`license_id` int NOT NULL COMMENT 'licenseId',
|
||||
`write_bytes` int NOT NULL COMMENT '写入流量',
|
||||
`read_bytes` int NOT NULL COMMENT '读取流量',
|
||||
`date` datetime(3) NOT NULL COMMENT '时间',
|
||||
`date_str` varchar(20) NOT NULL COMMENT '时间 yyyy-MM-dd',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `I_flow_report_day_create_time` (`create_time`) USING BTREE,
|
||||
KEY `I_flow_report_day_date` (`date`) USING BTREE,
|
||||
KEY `I_flow_report_day_user_id` (`user_id`),
|
||||
KEY `I_flow_report_day_license_id` (`license_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
#流量统计报表-月(全量保留)
|
||||
CREATE TABLE IF NOT EXISTS `flow_report_month` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`license_id` int NOT NULL COMMENT 'licenseId',
|
||||
`write_bytes` int NOT NULL COMMENT '写入流量',
|
||||
`read_bytes` int NOT NULL COMMENT '读取流量',
|
||||
`date` datetime(3) NOT NULL COMMENT '时间',
|
||||
`date_str` varchar(20) NOT NULL COMMENT '时间 yyyy-MM',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `I_flow_report_month_create_time` (`create_time`) USING BTREE,
|
||||
KEY `I_flow_report_month_date` (`date`) USING BTREE,
|
||||
KEY `I_flow_report_month_user_id` (`user_id`),
|
||||
KEY `I_flow_report_month_license_id` (`license_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
@@ -0,0 +1,13 @@
|
||||
#job_qrtz_trigger_info
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(1, '示例Job', 'DemoJob', '0/10 * * * * ?', '{"a":101}', 1, now(), now());
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(2, '数据清理任务', 'DataCleanJob', '0 0 1 * * ?', '', 1, now(), now());
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(3, '流量统计报表-分钟', 'FlowReportForMinuteJob', '0 */1 * * * ?', '', 1, now(), now());
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(4, '流量统计报表-小时', 'FlowReportForHourJob', '0 0 */1 * * ?', '', 1, now(), now());
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(5, '流量统计报表-天', 'FlowReportForDayJob', '0 0 1 * * ?', '', 1, now(), now());
|
||||
INSERT INTO job_info(`id`, `desc`, `handler`, `cron`, `param`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(6, '流量统计报表-月', 'FlowReportForMonthJob', '0 30 1 1 * ?', '', 1, now(), now());
|
||||
@@ -0,0 +1,3 @@
|
||||
#license
|
||||
INSERT INTO license(`id`, `name`, `key`, `user_id`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(1, '我的mac', 'b0a907332b474b25897c4dcb31fc7eb6', 1, 2, 1, now(), now());
|
||||
@@ -0,0 +1,3 @@
|
||||
#port_group
|
||||
INSERT INTO `port_group`(`id`,`name`,`possessor_type`,`possessor_id`,`enable`,`create_time`,`update_time`) VALUES
|
||||
(1, '全局(默认)', 0, -1, 1, now(), now());
|
||||
@@ -0,0 +1,7 @@
|
||||
#port_mapping
|
||||
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(1, 1, 9101, 'HTTP', 'test1', '127.0.0.1', 8080, 2, 1, now(), now());
|
||||
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(2, 1, 9102, 'TCP', '', '127.0.0.1', 3306, 2, 1, now(), now());
|
||||
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(3, 1, 9103, 'HTTP', 'test2', '127.0.0.1', 8081, 2, 1, now(), now());
|
||||
@@ -0,0 +1,41 @@
|
||||
#端口池
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(1, 1, 9101, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(2, 1, 9102, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(3, 1, 9103, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(4, 1, 9104, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(5, 1, 9105, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(6, 1, 9106, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(7, 1, 9107, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(8, 1, 9108, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(9, 1, 9109, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(10, 1, 9110, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(11, 1, 9111, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(12, 1, 9112, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(13, 1, 9113, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(14, 1, 9114, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(15, 1, 9115, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(16, 1, 9116, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(17, 1, 9117, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(18, 1, 9118, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(19, 1, 9119, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(20, 1, 9120, 1, now(), now());
|
||||
@@ -0,0 +1,5 @@
|
||||
#用户表 6613b92b77056faeb72068f184ed4c4f
|
||||
INSERT INTO `user`(`id`, `name`,`login_name`,`login_password`,`enable`,`create_time`, `update_time`) VALUES
|
||||
(1, '管理员', 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, now(), now());
|
||||
INSERT INTO `user`(`id`, `name`,`login_name`,`login_password`,`enable`,`create_time`, `update_time`) VALUES
|
||||
(2, '游客', 'visitor', 'e10adc3949ba59abbe56e057f20f883e', 1, now(), now());
|
||||
@@ -10,7 +10,7 @@ CREATE TABLE IF NOT EXISTS `user` (
|
||||
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `I_user_login_name` (`login_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
#用户token表
|
||||
CREATE TABLE IF NOT EXISTS `user_token` (
|
||||
@@ -24,18 +24,31 @@ CREATE TABLE IF NOT EXISTS `user_token` (
|
||||
KEY `I_user_token_user_id` (`user_id`),
|
||||
KEY `I_user_token_token` (`token`),
|
||||
KEY `I_user_token_expiration_time` (`expiration_time`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
#端口池
|
||||
CREATE TABLE IF NOT EXISTS `port_pool` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`group_id` int NOT NULL DEFAULT 1 COMMENT '分组ID',
|
||||
`port` int NOT NULL COMMENT '端口',
|
||||
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `I_port_pool_port` (`port`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
#端口分组
|
||||
CREATE TABLE IF NOT EXISTS `port_group` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(255) NOT NULL COMMENT '分组名称',
|
||||
`possessor_type` int NOT NULL DEFAULT '0' COMMENT '所有者类型 (0、全局共享 1、用户所有 2License所有) ',
|
||||
`possessor_id` int NOT NULL DEFAULT '-1' COMMENT '所有者id(当type为0时 固定为-1、当type为1时为用户id 、当type为2时为licenseid)',
|
||||
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
#############################代理配置相关表#############################
|
||||
#license表
|
||||
@@ -50,23 +63,27 @@ CREATE TABLE IF NOT EXISTS `license` (
|
||||
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `I_license_key` (`key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
#端口映射表
|
||||
CREATE TABLE IF NOT EXISTS `port_mapping` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`license_id` int NOT NULL COMMENT 'licenseID',
|
||||
`protocal` varchar(10) NOT NULL DEFAULT 'TCP' COMMENT '协议',
|
||||
`subdomain` varchar(50) DEFAULT NULL COMMENT '子域名(仅HTTP时有效)',
|
||||
`server_port` int NOT NULL COMMENT '服务端端口',
|
||||
`client_ip` varchar(20) NOT NULL COMMENT '客户端IP',
|
||||
`client_port` int NOT NULL COMMENT '客户端端口',
|
||||
`is_online` int NOT NULL COMMENT '是否在线(1、在线 2、离线)',
|
||||
`description` varchar(100) DEFAULT NULL COMMENT '描述',
|
||||
`proxy_responses` int NOT NULL DEFAULT 0 COMMENT '代理响应数据包数量',
|
||||
`proxy_timeout_ms` int NOT NULL DEFAULT 0 COMMENT '代理超时毫秒数',
|
||||
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `I_port_mapping_server_port` (`server_port`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
#############################日志管理相关表#############################
|
||||
#用户登录记录表
|
||||
CREATE TABLE IF NOT EXISTS `user_login_record` (
|
||||
@@ -77,11 +94,11 @@ CREATE TABLE IF NOT EXISTS `user_login_record` (
|
||||
`type` int NOT NULL COMMENT '类型(1、登录 2、登出)',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
#客户端连接记录表
|
||||
CREATE TABLE IF NOT EXISTS `client_connect_record` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'IP',
|
||||
`ip` varchar(50) NOT NULL COMMENT 'IP',
|
||||
`license_id` int NOT NULL COMMENT 'licenseId',
|
||||
`type` int NOT NULL COMMENT '类型(1、连接 2、断开连接)',
|
||||
`msg` varchar(512) DEFAULT NULL COMMENT '消息',
|
||||
@@ -89,7 +106,7 @@ CREATE TABLE IF NOT EXISTS `client_connect_record` (
|
||||
`err` text DEFAULT NULL COMMENT '异常信息',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
#############################调度管理相关表#############################
|
||||
#触发器信息表
|
||||
CREATE TABLE IF NOT EXISTS `job_info` (
|
||||
@@ -105,7 +122,7 @@ CREATE TABLE IF NOT EXISTS `job_info` (
|
||||
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `I_job_info_handler` (`handler`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
#触发器日志表
|
||||
CREATE TABLE IF NOT EXISTS `job_log` (
|
||||
@@ -120,7 +137,7 @@ CREATE TABLE IF NOT EXISTS `job_log` (
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `I_job_log_create_time` (`create_time`) USING BTREE,
|
||||
KEY `I_job_log_code` (`code`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
#############################报表管理相关表#############################
|
||||
#流量统计报表-分钟(保留24小时)
|
||||
@@ -138,7 +155,7 @@ CREATE TABLE IF NOT EXISTS `flow_report_minute` (
|
||||
KEY `I_flow_report_minute_date` (`date`) USING BTREE,
|
||||
KEY `I_flow_report_minute_user_id` (`user_id`),
|
||||
KEY `I_flow_report_minute_license_id` (`license_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
#流量统计报表-小时(保留60天)
|
||||
CREATE TABLE IF NOT EXISTS `flow_report_hour` (
|
||||
@@ -148,14 +165,14 @@ CREATE TABLE IF NOT EXISTS `flow_report_hour` (
|
||||
`write_bytes` int NOT NULL COMMENT '写入流量',
|
||||
`read_bytes` int NOT NULL COMMENT '读取流量',
|
||||
`date` datetime(3) NOT NULL COMMENT '时间',
|
||||
`date_str` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '时间 yyyy-MM-dd HH',
|
||||
`date_str` varchar(20) NOT NULL COMMENT '时间 yyyy-MM-dd HH',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `I_flow_report_hour_create_time` (`create_time`) USING BTREE,
|
||||
KEY `I_flow_report_hour_date` (`date`) USING BTREE,
|
||||
KEY `I_flow_report_hour_user_id` (`user_id`),
|
||||
KEY `I_flow_report_hour_license_id` (`license_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
#流量统计报表-天(保留1年)
|
||||
CREATE TABLE IF NOT EXISTS `flow_report_day` (
|
||||
@@ -165,14 +182,14 @@ CREATE TABLE IF NOT EXISTS `flow_report_day` (
|
||||
`write_bytes` int NOT NULL COMMENT '写入流量',
|
||||
`read_bytes` int NOT NULL COMMENT '读取流量',
|
||||
`date` datetime(3) NOT NULL COMMENT '时间',
|
||||
`date_str` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '时间 yyyy-MM-dd',
|
||||
`date_str` varchar(20) NOT NULL COMMENT '时间 yyyy-MM-dd',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `I_flow_report_day_create_time` (`create_time`) USING BTREE,
|
||||
KEY `I_flow_report_day_date` (`date`) USING BTREE,
|
||||
KEY `I_flow_report_day_user_id` (`user_id`),
|
||||
KEY `I_flow_report_day_license_id` (`license_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
#流量统计报表-月(全量保留)
|
||||
CREATE TABLE IF NOT EXISTS `flow_report_month` (
|
||||
@@ -182,11 +199,11 @@ CREATE TABLE IF NOT EXISTS `flow_report_month` (
|
||||
`write_bytes` int NOT NULL COMMENT '写入流量',
|
||||
`read_bytes` int NOT NULL COMMENT '读取流量',
|
||||
`date` datetime(3) NOT NULL COMMENT '时间',
|
||||
`date_str` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '时间 yyyy-MM',
|
||||
`date_str` varchar(20) NOT NULL COMMENT '时间 yyyy-MM',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `I_flow_report_month_create_time` (`create_time`) USING BTREE,
|
||||
KEY `I_flow_report_month_date` (`date`) USING BTREE,
|
||||
KEY `I_flow_report_month_user_id` (`user_id`),
|
||||
KEY `I_flow_report_month_license_id` (`license_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
#port_group
|
||||
INSERT INTO `port_group`(`id`,`name`,`possessor_type`,`possessor_id`,`enable`,`create_time`,`update_time`) VALUES
|
||||
(1, '全局(默认)', 0, -1, 1, now(), now());
|
||||
@@ -1,5 +1,7 @@
|
||||
#port_mapping
|
||||
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(1, 1, 9101, '127.0.0.1', 8080, 2, 1, now(), now());
|
||||
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(2, 1, 9102, '127.0.0.1', 3306, 2, 1, now(), now());
|
||||
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(1, 1, 9101, 'HTTP', 'test1', '127.0.0.1', 8080, 2, 1, now(), now());
|
||||
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(2, 1, 9102, 'TCP', '', '127.0.0.1', 3306, 2, 1, now(), now());
|
||||
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `protocal`, `subdomain`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(3, 1, 9103, 'HTTP', 'test2', '127.0.0.1', 8081, 2, 1, now(), now());
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
#端口池
|
||||
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(1, 9101, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(2, 9102, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(3, 9103, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(4, 9104, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(5, 9105, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(6, 9106, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(7, 9107, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(8, 9108, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(9, 9109, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(10, 9110, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(11, 9111, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(12, 9112, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(13, 9113, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(14, 9114, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(15, 9115, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(16, 9116, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(17, 9117, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(18, 9118, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(19, 9119, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(20, 9120, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(1, 1, 9101, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(2, 1, 9102, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(3, 1, 9103, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(4, 1, 9104, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(5, 1, 9105, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(6, 1, 9106, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(7, 1, 9107, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(8, 1, 9108, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(9, 1, 9109, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(10, 1, 9110, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(11, 1, 9111, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(12, 1, 9112, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(13, 1, 9113, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(14, 1, 9114, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(15, 1, 9115, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(16, 1, 9116, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(17, 1, 9117, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(18, 1, 9118, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(19, 1, 9119, 1, now(), now());
|
||||
INSERT INTO port_pool(`id`, `group_id`, `port`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(20, 1, 9120, 1, now(), now());
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
ALTER TABLE port_pool ADD group_id INT NOT NULL DEFAULT 1 COMMENT "分组ID";
|
||||
|
||||
#端口分组
|
||||
CREATE TABLE IF NOT EXISTS `port_group` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(255) NOT NULL COMMENT '分组名称',
|
||||
`possessor_type` int NOT NULL DEFAULT '0' COMMENT '所有者类型 (0、全局共享 1、用户所有 2License所有) ',
|
||||
`possessor_id` int NOT NULL DEFAULT '-1' COMMENT '所有者id(当type为0时 固定为-1、当type为1时为用户id 、当type为2时为licenseid)',
|
||||
`enable` int NOT NULL COMMENT '是否启用(1、启用 2、禁用)',
|
||||
`create_time` datetime(3) NOT NULL COMMENT '创建时间',
|
||||
`update_time` datetime(3) NOT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COMMENT='端口分组';
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE port_mapping ADD `protocal` varchar(10) NOT NULL DEFAULT 'TCP' COMMENT '协议';
|
||||
ALTER TABLE port_mapping ADD `subdomain` varchar(50) DEFAULT NULL COMMENT '子域名(仅HTTP时有效)';
|
||||
ALTER TABLE port_mapping ADD `description` varchar(100) DEFAULT NULL COMMENT '描述';
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE port_mapping ADD `proxy_responses` int NOT NULL DEFAULT 0 COMMENT '代理响应数据包数量';
|
||||
ALTER TABLE port_mapping ADD `proxy_timeout_ms` int NOT NULL DEFAULT 0 COMMENT '代理超时毫秒数';
|
||||
@@ -1,5 +0,0 @@
|
||||
#port_mapping
|
||||
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(1, 1, 9101, '127.0.0.1', 8080, 2, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
|
||||
INSERT INTO port_mapping(`id`, `license_id`, `server_port`, `client_ip`, `client_port`, `is_online`, `enable`, `create_time`, `update_time`) VALUES
|
||||
(2, 1, 9102, '127.0.0.1', 3306, 2, 1, STRFTIME('%s000', 'NOW'), STRFTIME('%s000', 'NOW'));
|
||||
@@ -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 @@
|
||||
neutrino-proxy.dromara.org
|
||||
@@ -4,236 +4,177 @@ const htmlModules = require('./config/htmlModules.js');
|
||||
|
||||
module.exports = {
|
||||
|
||||
theme: 'vdoing', // 使用依赖包主题
|
||||
// theme: require.resolve('../../vdoing'), // 使用本地主题 (先将vdoing主题文件下载到本地:https://github.com/xugaoyi/vuepress-theme-vdoing)
|
||||
theme: 'vdoing', // 使用依赖包主题
|
||||
// theme: require.resolve('../../vdoing'), // 使用本地主题 (先将vdoing主题文件下载到本地:https://github.com/xugaoyi/vuepress-theme-vdoing)
|
||||
|
||||
title: "中微子代理",
|
||||
description: '一个基于VuePress的 知识管理&博客 主题',
|
||||
// base: '/', // 默认'/'。如果你想将你的网站部署到如 https://foo.github.io/bar/,那么 base 应该被设置成 "/bar/",(否则页面将失去样式等文件)
|
||||
head: [ // 注入到页面<head> 中的标签,格式[tagName, { attrName: attrValue }, innerHTML?]
|
||||
['link', { rel: 'icon', href: '/img/favicon.ico' }], //favicons,资源放在public文件夹
|
||||
['meta', { name: 'keywords', content: 'vuepress,theme,blog,vdoing' }],
|
||||
['meta', { name: 'theme-color', content: '#11a8cd' }], // 移动浏览器主题颜色
|
||||
title: "中微子代理",
|
||||
description: '一个基于Netty的开源内网穿透神器',
|
||||
base: '/neutrino-proxy/', // 默认'/'。如果你想将你的网站部署到如 https://foo.github.io/bar/,那么 base 应该被设置成 "/bar/",(否则页面将失去样式等文件)
|
||||
head: [ // 注入到页面<head> 中的标签,格式[tagName, { attrName: attrValue }, innerHTML?]
|
||||
['link', {rel: 'icon', href: '/img/favicon.ico'}], //favicons,资源放在public文件夹
|
||||
['meta', {name: 'keywords', content: 'vuepress,theme,blog,vdoing'}],
|
||||
['meta', {name: 'theme-color', content: '#11a8cd'}], // 移动浏览器主题颜色
|
||||
['meta', {name: 'referrer', content: 'no-referrer-when-downgrade'}], //统计在 Chrome 85 版本中,为了保护用户的隐私,默认的 Referrer Policy 则变成了 strict-origin-when-cross-origin
|
||||
|
||||
['meta', { name: 'wwads-cn-verify', content: '6c4b761a28b734fe93831e3fb400ce87' }], // 广告相关,你可以去掉
|
||||
['script', { src: 'https://cdn.wwads.cn/js/makemoney.js', type: 'text/javascript' }], // 广告相关,你可以去掉
|
||||
],
|
||||
// ['meta', { name: 'wwads-cn-verify', content: '6c4b761a28b734fe93831e3fb400ce87' }], // 广告相关,你可以去掉
|
||||
// ['script', { src: 'https://cdn.wwads.cn/js/makemoney.js', type: 'text/javascript' }], // 广告相关,你可以去掉
|
||||
],
|
||||
|
||||
// 主题配置
|
||||
themeConfig: {
|
||||
// nav: [
|
||||
// { text: '首页', link: '/' },
|
||||
// {
|
||||
// text: '指南', link: '/pages/a2f161/', items: [
|
||||
// { text: '主题初衷与诞生', link: '/pages/52d5c3/' },
|
||||
// { text: '介绍', link: '/pages/a2f161/' },
|
||||
// { text: '快速上手', link: '/pages/793dcb/' },
|
||||
// { text: '目录结构', link: '/pages/2f674a/' },
|
||||
// { text: '核心配置和约定', link: '/pages/33d574/' },
|
||||
// { text: '自动生成front matter', link: '/pages/088c16/' },
|
||||
// { text: 'Markdown 容器', link: '/pages/d0d7eb/' },
|
||||
// { text: 'Markdown 中使用组件', link: '/pages/197691/' },
|
||||
// {
|
||||
// text: '相关文章', items: [
|
||||
// { text: '使目录栏支持h2~h6标题', link: '/pages/8dfab5/' },
|
||||
// { text: '如何让你的笔记更有表现力', link: '/pages/dd027d/' },
|
||||
// { text: '批量操作front matter工具', link: '/pages/2b8e22/' },
|
||||
// { text: '部署', link: '/pages/0fc1d2/' },
|
||||
// { text: '关于写文章和H1标题', link: '/pages/9ae0bd/' },
|
||||
// { text: '关于博客搭建与管理', link: '/pages/26997d/' },
|
||||
// { text: '在线编辑和新增文章的方法', link: '/pages/c5a54d/' },
|
||||
// ]
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
// {
|
||||
// text: '配置', link: '/pages/a20ce8/', items: [
|
||||
// { text: '主题配置', link: '/pages/a20ce8/' },
|
||||
// { text: '首页配置', link: '/pages/f14bdb/' },
|
||||
// { text: 'front matter配置', link: '/pages/3216b0/' },
|
||||
// { text: '目录页配置', link: '/pages/54651a/' },
|
||||
// { text: '添加摘要', link: '/pages/1cc523/' },
|
||||
// { text: '修改主题颜色和样式', link: '/pages/f51918/' },
|
||||
// { text: '评论栏', link: '/pages/ce175c/' },
|
||||
// ]
|
||||
// },
|
||||
// { text: '资源', link: '/pages/db78e2/' },
|
||||
// { text: '案例', link: '/pages/5d571c/' },
|
||||
// { text: '问答', link: '/pages/9cc27d/' },
|
||||
// { text: '赞助', link: '/pages/1b12ed/' },
|
||||
// ],
|
||||
nav: [
|
||||
{ text: '首页', link: '/' },
|
||||
{
|
||||
text: '使用教程', link: '/pages/a2f161/', items: [
|
||||
{ text: '主题初衷与诞生', link: '/pages/52d5c3/' },
|
||||
{ text: '介绍', link: '/pages/a2f161/' },
|
||||
{ text: '快速上手', link: '/pages/793dcb/' },
|
||||
{ text: '目录结构', link: '/pages/2f674a/' },
|
||||
{ text: '核心配置和约定', link: '/pages/33d574/' },
|
||||
{ text: '自动生成front matter', link: '/pages/088c16/' },
|
||||
{ text: 'Markdown 容器', link: '/pages/d0d7eb/' },
|
||||
{ text: 'Markdown 中使用组件', link: '/pages/197691/' }
|
||||
// 主题配置
|
||||
themeConfig: {
|
||||
nav: [
|
||||
{text: '首页', link: '/'},
|
||||
{
|
||||
text: '文档', link: '/pages/793dcb/', items: [
|
||||
{text: '快速上手', link: '/pages/793dcb/'},
|
||||
{text: '后台操作指南', link: '/pages/793dcc/'},
|
||||
]
|
||||
},
|
||||
{text: '演示', link: '/pages/db78e2/'},
|
||||
{text: 'Gitee', link: 'https://gitee.com/dromara/neutrino-proxy'},
|
||||
],
|
||||
sidebarDepth: 2, // 侧边栏显示深度,默认1,最大2(显示到h3标题)
|
||||
logo: '/img/logo.png', // 导航栏logo
|
||||
repo: 'https://github.com/dromara/neutrino-proxy', // 导航栏右侧生成Github链接
|
||||
repoLabel: 'Github',
|
||||
searchMaxSuggestions: 10, // 搜索结果显示最大数
|
||||
lastUpdated: '上次更新', // 更新的时间,及前缀文字 string | boolean (取值为git提交时间)
|
||||
|
||||
// docsDir: 'docs', // 编辑的文件夹
|
||||
// editLinks: true, // 编辑链接
|
||||
// editLinkText: '编辑',
|
||||
|
||||
// 以下配置是Vdoing主题改动的和新增的配置
|
||||
sidebar: {mode: 'structuring', collapsable: false}, // 侧边栏 'structuring' | { mode: 'structuring', collapsable: Boolean} | 'auto' | 自定义 温馨提示:目录页数据依赖于结构化的侧边栏数据,如果你不设置为'structuring',将无法使用目录页
|
||||
|
||||
// sidebarOpen: false, // 初始状态是否打开侧边栏,默认true
|
||||
updateBar: { // 最近更新栏
|
||||
showToArticle: false, // 显示到文章页底部,默认true
|
||||
// moreArticle: '/archives' // “更多文章”跳转的页面,默认'/archives'
|
||||
},
|
||||
// titleBadge: false, // 文章标题前的图标是否显示,默认true
|
||||
// titleBadgeIcons: [ // 文章标题前图标的地址,默认主题内置图标
|
||||
// '图标地址1',
|
||||
// '图标地址2'
|
||||
// ],
|
||||
|
||||
pageStyle: 'line', // 页面风格,可选值:'card'卡片 | 'line' 线(未设置bodyBgImg时才生效), 默认'card'。 说明:card时背景显示灰色衬托出卡片样式,line时背景显示纯色,并且部分模块带线条边框
|
||||
|
||||
// contentBgStyle: 1,
|
||||
|
||||
category: false, // 是否打开分类功能,默认true。 如打开,会做的事情有:1. 自动生成的frontmatter包含分类字段 2.页面中显示与分类相关的信息和模块 3.自动生成分类页面(在@pages文件夹)。如关闭,则反之。
|
||||
tag: false, // 是否打开标签功能,默认true。 如打开,会做的事情有:1. 自动生成的frontmatter包含标签字段 2.页面中显示与标签相关的信息和模块 3.自动生成标签页面(在@pages文件夹)。如关闭,则反之。
|
||||
// archive: false, // 是否打开归档功能,默认true。 如打开,会做的事情有:1.自动生成归档页面(在@pages文件夹)。如关闭,则反之。
|
||||
|
||||
author: { // 文章默认的作者信息,可在md文件中单独配置此信息 String | {name: String, href: String}
|
||||
name: '傲世孤尘', // 必需
|
||||
href: 'https://gitee.com/dromara/neutrino-proxy' // 可选的
|
||||
},
|
||||
social: { // 社交图标,显示于博主信息栏和页脚栏
|
||||
// iconfontCssFile: '//at.alicdn.com/t/font_1678482_u4nrnp8xp6g.css', // 可选,阿里图标库在线css文件地址,对于主题没有的图标可自由添加
|
||||
icons: [
|
||||
{
|
||||
iconClass: 'icon-youjian',
|
||||
title: '发邮件',
|
||||
link: 'mailto:[email protected]'
|
||||
},
|
||||
{
|
||||
iconClass: 'icon-gitee',
|
||||
title: 'Gitee',
|
||||
link: 'https://gitee.com/dromara/neutrino-proxy'
|
||||
},
|
||||
{
|
||||
iconClass: 'icon-github',
|
||||
title: 'Github',
|
||||
link: 'https://github.com/aoshiguchen/neutrino-proxy'
|
||||
}
|
||||
]
|
||||
},
|
||||
footer: { // 页脚信息
|
||||
createYear: 2023, // 博客创建年份
|
||||
copyrightInfo: '傲世孤尘 | MIT License', // 博客版权信息,支持a标签
|
||||
},
|
||||
htmlModules,
|
||||
},
|
||||
|
||||
// 插件
|
||||
plugins: [
|
||||
// [require('./plugins/love-me'), { // 鼠标点击爱心特效
|
||||
// color: '#11a8cd', // 爱心颜色,默认随机色
|
||||
// excludeClassName: 'theme-vdoing-content' // 要排除元素的class, 默认空''
|
||||
// }],
|
||||
|
||||
['fulltext-search'], // 全文搜索
|
||||
|
||||
// ['thirdparty-search', { // 可以添加第三方搜索链接的搜索框(原官方搜索框的参数仍可用)
|
||||
// thirdparty: [ // 可选,默认 []
|
||||
// {
|
||||
// title: '在GitHub中搜索',
|
||||
// frontUrl: 'https://github.com/search?q=', // 搜索链接的前面部分
|
||||
// behindUrl: '' // 搜索链接的后面部分,可选,默认 ''
|
||||
// },
|
||||
// {
|
||||
// title: '在npm中搜索',
|
||||
// frontUrl: 'https://www.npmjs.com/search?q=',
|
||||
// },
|
||||
// {
|
||||
// title: '在Bing中搜索',
|
||||
// frontUrl: 'https://cn.bing.com/search?q='
|
||||
// }
|
||||
// ]
|
||||
// }],
|
||||
|
||||
[
|
||||
'vuepress-plugin-baidu-tongji', // 百度统计
|
||||
{
|
||||
hm: baiduCode || 'f5901e55fecee5322d9f613841c717b8'
|
||||
}
|
||||
],
|
||||
|
||||
['one-click-copy', { // 代码块复制按钮
|
||||
copySelector: ['div[class*="language-"] pre', 'div[class*="aside-code"] aside'], // String or Array
|
||||
copyMessage: '复制成功', // default is 'Copy successfully and then paste it for use.'
|
||||
duration: 1000, // prompt message display time.
|
||||
showInMobile: false // whether to display on the mobile side, default: false.
|
||||
}],
|
||||
['demo-block', { // demo演示模块 https://github.com/xiguaxigua/vuepress-plugin-demo-block
|
||||
settings: {
|
||||
// jsLib: ['http://xxx'], // 在线示例(jsfiddle, codepen)中的js依赖
|
||||
// cssLib: ['http://xxx'], // 在线示例中的css依赖
|
||||
// vue: 'https://fastly.jsdelivr.net/npm/vue/dist/vue.min.js', // 在线示例中的vue依赖
|
||||
jsfiddle: false, // 是否显示 jsfiddle 链接
|
||||
codepen: true, // 是否显示 codepen 链接
|
||||
horizontal: false // 是否展示为横向样式
|
||||
}
|
||||
}],
|
||||
[
|
||||
'vuepress-plugin-zooming', // 放大图片
|
||||
{
|
||||
selector: '.theme-vdoing-content img:not(.no-zoom)',
|
||||
options: {
|
||||
bgColor: 'rgba(0,0,0,0.6)'
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'@vuepress/last-updated', // "上次更新"时间格式
|
||||
{
|
||||
transformer: (timestamp, lang) => {
|
||||
const dayjs = require('dayjs') // https://day.js.org/
|
||||
return dayjs(timestamp).format('YYYY/MM/DD, HH:mm:ss')
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '常见问题', link: '/pages/a20ce8/', items: [
|
||||
{ text: '主题配置', link: '/pages/a20ce8/' },
|
||||
{ text: '首页配置', link: '/pages/f14bdb/' },
|
||||
{ text: 'front matter配置', link: '/pages/3216b0/' },
|
||||
{ text: '目录页配置', link: '/pages/54651a/' },
|
||||
{ text: '添加摘要', link: '/pages/1cc523/' },
|
||||
{ text: '修改主题颜色和样式', link: '/pages/f51918/' },
|
||||
{ text: '评论栏', link: '/pages/ce175c/' },
|
||||
]
|
||||
},
|
||||
{ text: '演示', link: '/pages/db78e2/' },
|
||||
{ text: '案例', link: '/pages/5d571c/' },
|
||||
{ text: '最近更新', link: '/pages/9cc27d/' },
|
||||
{ text: '仓库地址', link: '/pages/1b12ed/' },
|
||||
],
|
||||
sidebarDepth: 2, // 侧边栏显示深度,默认1,最大2(显示到h3标题)
|
||||
logo: '/img/logo.png', // 导航栏logo
|
||||
repo: 'xugaoyi/vuepress-theme-vdoing', // 导航栏右侧生成Github链接
|
||||
// repo: 'https://gitee.com/dromara/neutrino-proxy', // 导航栏右侧生成Github链接
|
||||
searchMaxSuggestions: 10, // 搜索结果显示最大数
|
||||
lastUpdated: '上次更新', // 更新的时间,及前缀文字 string | boolean (取值为git提交时间)
|
||||
|
||||
// docsDir: 'docs', // 编辑的文件夹
|
||||
// editLinks: true, // 编辑链接
|
||||
// editLinkText: '编辑',
|
||||
|
||||
// 以下配置是Vdoing主题改动的和新增的配置
|
||||
sidebar: { mode: 'structuring', collapsable: false }, // 侧边栏 'structuring' | { mode: 'structuring', collapsable: Boolean} | 'auto' | 自定义 温馨提示:目录页数据依赖于结构化的侧边栏数据,如果你不设置为'structuring',将无法使用目录页
|
||||
|
||||
// sidebarOpen: false, // 初始状态是否打开侧边栏,默认true
|
||||
updateBar: { // 最近更新栏
|
||||
showToArticle: false, // 显示到文章页底部,默认true
|
||||
// moreArticle: '/archives' // “更多文章”跳转的页面,默认'/archives'
|
||||
},
|
||||
// titleBadge: false, // 文章标题前的图标是否显示,默认true
|
||||
// titleBadgeIcons: [ // 文章标题前图标的地址,默认主题内置图标
|
||||
// '图标地址1',
|
||||
// '图标地址2'
|
||||
// ],
|
||||
|
||||
pageStyle: 'line', // 页面风格,可选值:'card'卡片 | 'line' 线(未设置bodyBgImg时才生效), 默认'card'。 说明:card时背景显示灰色衬托出卡片样式,line时背景显示纯色,并且部分模块带线条边框
|
||||
|
||||
// contentBgStyle: 1,
|
||||
|
||||
category: false, // 是否打开分类功能,默认true。 如打开,会做的事情有:1. 自动生成的frontmatter包含分类字段 2.页面中显示与分类相关的信息和模块 3.自动生成分类页面(在@pages文件夹)。如关闭,则反之。
|
||||
tag: false, // 是否打开标签功能,默认true。 如打开,会做的事情有:1. 自动生成的frontmatter包含标签字段 2.页面中显示与标签相关的信息和模块 3.自动生成标签页面(在@pages文件夹)。如关闭,则反之。
|
||||
// archive: false, // 是否打开归档功能,默认true。 如打开,会做的事情有:1.自动生成归档页面(在@pages文件夹)。如关闭,则反之。
|
||||
|
||||
author: { // 文章默认的作者信息,可在md文件中单独配置此信息 String | {name: String, href: String}
|
||||
name: 'Evan Xu', // 必需
|
||||
href: 'https://github.com/xugaoyi' // 可选的
|
||||
},
|
||||
social: { // 社交图标,显示于博主信息栏和页脚栏
|
||||
// iconfontCssFile: '//at.alicdn.com/t/font_1678482_u4nrnp8xp6g.css', // 可选,阿里图标库在线css文件地址,对于主题没有的图标可自由添加
|
||||
icons: [
|
||||
{
|
||||
iconClass: 'icon-youjian',
|
||||
title: '发邮件',
|
||||
link: 'mailto:[email protected]'
|
||||
},
|
||||
{
|
||||
iconClass: 'icon-gitee',
|
||||
title: 'Gitee',
|
||||
link: 'https://gitee.com/dromara/neutrino-proxy'
|
||||
},
|
||||
{
|
||||
iconClass: 'icon-github',
|
||||
title: 'Github',
|
||||
link: 'https://github.com/aoshiguchen/neutrino-proxy'
|
||||
}
|
||||
]
|
||||
},
|
||||
footer: { // 页脚信息
|
||||
createYear: 2023, // 博客创建年份
|
||||
copyrightInfo: '傲世孤尘 | MIT License', // 博客版权信息,支持a标签
|
||||
},
|
||||
htmlModules,
|
||||
},
|
||||
|
||||
// 插件
|
||||
plugins: [
|
||||
// [require('./plugins/love-me'), { // 鼠标点击爱心特效
|
||||
// color: '#11a8cd', // 爱心颜色,默认随机色
|
||||
// excludeClassName: 'theme-vdoing-content' // 要排除元素的class, 默认空''
|
||||
// }],
|
||||
|
||||
['fulltext-search'], // 全文搜索
|
||||
|
||||
// ['thirdparty-search', { // 可以添加第三方搜索链接的搜索框(原官方搜索框的参数仍可用)
|
||||
// thirdparty: [ // 可选,默认 []
|
||||
// {
|
||||
// title: '在GitHub中搜索',
|
||||
// frontUrl: 'https://github.com/search?q=', // 搜索链接的前面部分
|
||||
// behindUrl: '' // 搜索链接的后面部分,可选,默认 ''
|
||||
// },
|
||||
// {
|
||||
// title: '在npm中搜索',
|
||||
// frontUrl: 'https://www.npmjs.com/search?q=',
|
||||
// },
|
||||
// {
|
||||
// title: '在Bing中搜索',
|
||||
// frontUrl: 'https://cn.bing.com/search?q='
|
||||
// }
|
||||
// ]
|
||||
// }],
|
||||
|
||||
[
|
||||
'vuepress-plugin-baidu-tongji', // 百度统计
|
||||
{
|
||||
hm: baiduCode || '01293bffa6c3962016c08ba685c79d78'
|
||||
}
|
||||
],
|
||||
|
||||
['one-click-copy', { // 代码块复制按钮
|
||||
copySelector: ['div[class*="language-"] pre', 'div[class*="aside-code"] aside'], // String or Array
|
||||
copyMessage: '复制成功', // default is 'Copy successfully and then paste it for use.'
|
||||
duration: 1000, // prompt message display time.
|
||||
showInMobile: false // whether to display on the mobile side, default: false.
|
||||
}],
|
||||
['demo-block', { // demo演示模块 https://github.com/xiguaxigua/vuepress-plugin-demo-block
|
||||
settings: {
|
||||
// jsLib: ['http://xxx'], // 在线示例(jsfiddle, codepen)中的js依赖
|
||||
// cssLib: ['http://xxx'], // 在线示例中的css依赖
|
||||
// vue: 'https://fastly.jsdelivr.net/npm/vue/dist/vue.min.js', // 在线示例中的vue依赖
|
||||
jsfiddle: false, // 是否显示 jsfiddle 链接
|
||||
codepen: true, // 是否显示 codepen 链接
|
||||
horizontal: false // 是否展示为横向样式
|
||||
}
|
||||
}],
|
||||
[
|
||||
'vuepress-plugin-zooming', // 放大图片
|
||||
{
|
||||
selector: '.theme-vdoing-content img:not(.no-zoom)',
|
||||
options: {
|
||||
bgColor: 'rgba(0,0,0,0.6)'
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'@vuepress/last-updated', // "上次更新"时间格式
|
||||
{
|
||||
transformer: (timestamp, lang) => {
|
||||
const dayjs = require('dayjs') // https://day.js.org/
|
||||
return dayjs(timestamp).format('YYYY/MM/DD, HH:mm:ss')
|
||||
},
|
||||
}
|
||||
markdown: {
|
||||
// lineNumbers: true,
|
||||
extractHeaders: ['h2', 'h3', 'h4', 'h5', 'h6'], // 提取标题到侧边栏的级别,默认['h2', 'h3']
|
||||
},
|
||||
|
||||
// 监听文件变化并重新构建
|
||||
extraWatchFiles: [
|
||||
'.vuepress/config.js',
|
||||
'.vuepress/config/htmlModules.js',
|
||||
]
|
||||
],
|
||||
|
||||
markdown: {
|
||||
// lineNumbers: true,
|
||||
extractHeaders: ['h2', 'h3', 'h4', 'h5', 'h6'], // 提取标题到侧边栏的级别,默认['h2', 'h3']
|
||||
},
|
||||
|
||||
// 监听文件变化并重新构建
|
||||
extraWatchFiles: [
|
||||
'.vuepress/config.js',
|
||||
'.vuepress/config/htmlModules.js',
|
||||
]
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ module.exports = {
|
||||
</style>
|
||||
`,
|
||||
// 赞助商广告
|
||||
sidebarT: `<a href="http://apifox.cn/a103xugaoyi" target="_blank"><img src="https://cdn.staticaly.com/gh/xugaoyi/blog-gitalk-comment@master/img/431669861564_.2470ykdcpbds.jpg" alt="npm" class="no-zoom" style="width: 100%;border-radius: 2px;"></a>`,
|
||||
// sidebarT: `<a href="http://apifox.cn/a103xugaoyi" target="_blank"><img src="https://cdn.staticaly.com/gh/xugaoyi/blog-gitalk-comment@master/img/431669861564_.2470ykdcpbds.jpg" alt="npm" class="no-zoom" style="width: 100%;border-radius: 2px;"></a>`,
|
||||
|
||||
|
||||
// windowRB: `
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
/**
|
||||
* to主题使用者:你可以去掉本文件的所有代码
|
||||
*/
|
||||
export default ({
|
||||
Vue, // VuePress 正在使用的 Vue 构造函数
|
||||
options, // 附加到根实例的一些选项
|
||||
router, // 当前应用的路由实例
|
||||
siteData, // 站点元数据
|
||||
isServer // 当前应用配置是处于 服务端渲染 还是 客户端
|
||||
}) => {
|
||||
|
||||
// 用于监控在路由变化时检查广告拦截器 (to主题使用者:你可以去掉本文件的所有代码)
|
||||
if (!isServer) {
|
||||
router.afterEach(() => {
|
||||
//check if wwads' fire function was blocked after document is ready with 3s timeout (waiting the ad loading)
|
||||
docReady(function () {
|
||||
setTimeout(function () {
|
||||
if (window._AdBlockInit === undefined) {
|
||||
ABDetected();
|
||||
}
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
// 删除事件改为隐藏事件
|
||||
setTimeout(() => {
|
||||
const pageAD = document.querySelector('.page-wwads');
|
||||
if (!pageAD) return;
|
||||
const btnEl = pageAD.querySelector('.wwads-hide');
|
||||
if (btnEl) {
|
||||
btnEl.onclick = () => {
|
||||
pageAD.style.display = 'none';
|
||||
}
|
||||
}
|
||||
// 显示广告模块
|
||||
if (pageAD.style.display === 'none') {
|
||||
pageAD.style.display = 'flex';
|
||||
}
|
||||
}, 900);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function ABDetected() {
|
||||
const h = "<style>.wwads-horizontal,.wwads-vertical{background-color:#f4f8fa;padding:5px;min-height:120px;margin-top:20px;box-sizing:border-box;border-radius:3px;font-family:sans-serif;display:flex;min-width:150px;position:relative;overflow:hidden;}.wwads-horizontal{flex-wrap:wrap;justify-content:center}.wwads-vertical{flex-direction:column;align-items:center;padding-bottom:32px}.wwads-horizontal a,.wwads-vertical a{text-decoration:none}.wwads-horizontal .wwads-img,.wwads-vertical .wwads-img{margin:5px}.wwads-horizontal .wwads-content,.wwads-vertical .wwads-content{margin:5px}.wwads-horizontal .wwads-content{flex:130px}.wwads-vertical .wwads-content{margin-top:10px}.wwads-horizontal .wwads-text,.wwads-content .wwads-text{font-size:14px;line-height:1.4;color:#0e1011;-webkit-font-smoothing:antialiased}.wwads-horizontal .wwads-poweredby,.wwads-vertical .wwads-poweredby{display:block;font-size:11px;color:#a6b7bf;margin-top:1em}.wwads-vertical .wwads-poweredby{position:absolute;left:10px;bottom:10px}.wwads-horizontal .wwads-poweredby span,.wwads-vertical .wwads-poweredby span{transition:all 0.2s ease-in-out;margin-left:-1em}.wwads-horizontal .wwads-poweredby span:first-child,.wwads-vertical .wwads-poweredby span:first-child{opacity:0}.wwads-horizontal:hover .wwads-poweredby span,.wwads-vertical:hover .wwads-poweredby span{opacity:1;margin-left:0}.wwads-horizontal .wwads-hide,.wwads-vertical .wwads-hide{position:absolute;right:-23px;bottom:-23px;width:46px;height:46px;border-radius:23px;transition:all 0.3s ease-in-out;cursor:pointer;}.wwads-horizontal .wwads-hide:hover,.wwads-vertical .wwads-hide:hover{background:rgb(0 0 0 /0.05)}.wwads-horizontal .wwads-hide svg,.wwads-vertical .wwads-hide svg{position:absolute;left:10px;top:10px;fill:#a6b7bf}.wwads-horizontal .wwads-hide:hover svg,.wwads-vertical .wwads-hide:hover svg{fill:#3E4546}</style><a href='https://wwads.cn/page/whitelist-wwads' class='wwads-img' target='_blank' rel='nofollow'><img src='https://fastly.jsdelivr.net/gh/xugaoyi/image_store@master/blog/wwads.2a3pidhlh4ys.webp' width='130'></a><div class='wwads-content'><a href='https://wwads.cn/page/whitelist-wwads' class='wwads-text' target='_blank' rel='nofollow'>为了本站的长期运营,请将我们的网站加入广告拦截器的白名单,感谢您的支持!<span style='color: #11a8cd'>如何添加白名单?</span></a><a href='https://wwads.cn/page/end-user-privacy' class='wwads-poweredby' title='万维广告 ~ 让广告更优雅,且有用' target='_blank'><span>广告</span></a></div><a class='wwads-hide' onclick='parentNode.remove()' title='隐藏广告'><svg xmlns='http://www.w3.org/2000/svg' width='6' height='7'><path d='M.879.672L3 2.793 5.121.672a.5.5 0 11.707.707L3.708 3.5l2.12 2.121a.5.5 0 11-.707.707l-2.12-2.12-2.122 2.12a.5.5 0 11-.707-.707l2.121-2.12L.172 1.378A.5.5 0 01.879.672z'></path></svg></a>";
|
||||
const wwadsEl = document.getElementsByClassName("wwads-cn");
|
||||
const wwadsContentEl = document.querySelector('.wwads-content');
|
||||
if (wwadsEl[0] && !wwadsContentEl) {
|
||||
wwadsEl[0].innerHTML = h;
|
||||
}
|
||||
};
|
||||
|
||||
//check document ready
|
||||
function docReady(t) {
|
||||
"complete" === document.readyState ||
|
||||
"interactive" === document.readyState
|
||||
? setTimeout(t, 1)
|
||||
: document.addEventListener("DOMContentLoaded", t);
|
||||
}
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 301 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 456 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,11 @@
|
||||
<svg width="206" height="53" viewBox="0 0 206 53" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_501_120)">
|
||||
<path d="M59.0087 36H54.5243V13.5938H59.0087V22.5625H67.9618V13.5938H72.4462V36H67.9618V27.0469H59.0087V36ZM82.5712 31.9062C82.7378 31.9583 82.9045 31.9948 83.0712 32.0156C83.2378 32.026 83.4045 32.0312 83.5712 32.0312C83.9878 32.0312 84.3889 31.974 84.7743 31.8594C85.1597 31.7448 85.5191 31.5833 85.8524 31.375C86.1962 31.1562 86.4983 30.8958 86.7587 30.5938C87.0295 30.2812 87.2483 29.9375 87.4149 29.5625L90.5399 32.7031C90.1441 33.2656 89.6858 33.7708 89.1649 34.2188C88.6545 34.6667 88.0972 35.0469 87.493 35.3594C86.8993 35.6719 86.2691 35.9062 85.6024 36.0625C84.9462 36.2292 84.2691 36.3125 83.5712 36.3125C82.3941 36.3125 81.2847 36.0938 80.243 35.6562C79.2118 35.2188 78.3055 34.6094 77.5243 33.8281C76.7535 33.0469 76.1441 32.1198 75.6962 31.0469C75.2483 29.9635 75.0243 28.776 75.0243 27.4844C75.0243 26.1615 75.2483 24.9531 75.6962 23.8594C76.1441 22.7656 76.7535 21.8333 77.5243 21.0625C78.3055 20.2917 79.2118 19.6927 80.243 19.2656C81.2847 18.8385 82.3941 18.625 83.5712 18.625C84.2691 18.625 84.9514 18.7083 85.618 18.875C86.2847 19.0417 86.9149 19.2812 87.5087 19.5938C88.1128 19.9062 88.6753 20.2917 89.1962 20.75C89.717 21.1979 90.1753 21.7031 90.5712 22.2656L82.5712 31.9062ZM84.7587 23.1094C84.5608 23.0365 84.3628 22.9896 84.1649 22.9688C83.9774 22.9479 83.7795 22.9375 83.5712 22.9375C82.9878 22.9375 82.4358 23.0469 81.9149 23.2656C81.4045 23.474 80.9566 23.776 80.5712 24.1719C80.1962 24.5677 79.8993 25.0469 79.6805 25.6094C79.4618 26.1615 79.3524 26.7865 79.3524 27.4844C79.3524 27.6406 79.3576 27.8177 79.368 28.0156C79.3889 28.2135 79.4149 28.4167 79.4462 28.625C79.4878 28.8229 79.5347 29.0156 79.5868 29.2031C79.6389 29.3906 79.7066 29.5573 79.7899 29.7031L84.7587 23.1094ZM96.993 36H92.7274V19.2656H93.7587L95.1649 21.25C95.8524 20.625 96.6337 20.1458 97.5087 19.8125C98.3837 19.4688 99.2899 19.2969 100.227 19.2969H103.993V23.5469H100.227C99.7795 23.5469 99.3576 23.6302 98.9618 23.7969C98.566 23.9635 98.2222 24.1927 97.9305 24.4844C97.6389 24.776 97.4097 25.1198 97.243 25.5156C97.0764 25.9115 96.993 26.3333 96.993 26.7812V36ZM114.368 36C113.337 36 112.368 35.8073 111.462 35.4219C110.556 35.026 109.759 34.4896 109.071 33.8125C108.394 33.125 107.858 32.3281 107.462 31.4219C107.076 30.5156 106.884 29.5469 106.884 28.5156V23.5469H104.806V19.2969H106.884V12.6094H111.134V19.2969H117.602V23.5469H111.134V28.5156C111.134 28.9635 111.217 29.3854 111.384 29.7812C111.55 30.1667 111.78 30.5052 112.071 30.7969C112.363 31.0885 112.707 31.3229 113.102 31.5C113.498 31.6667 113.92 31.75 114.368 31.75H117.602V36H114.368ZM133.071 36H119.118L126.165 23.5469H119.118V19.2969H133.071L126.024 31.75H133.071V36ZM153.821 29.2812C153.821 30.2083 153.644 31.0781 153.29 31.8906C152.936 32.7031 152.451 33.4167 151.837 34.0312C151.233 34.6354 150.524 35.1146 149.712 35.4688C148.899 35.8229 148.03 36 147.102 36H135.899V13.5938H147.102C148.03 13.5938 148.899 13.7708 149.712 14.125C150.524 14.4792 151.233 14.9635 151.837 15.5781C152.451 16.1823 152.936 16.8906 153.29 17.7031C153.644 18.5156 153.821 19.3854 153.821 20.3125C153.821 20.7292 153.764 21.1562 153.649 21.5938C153.535 22.0312 153.373 22.4531 153.165 22.8594C152.957 23.2656 152.707 23.6354 152.415 23.9688C152.123 24.3021 151.8 24.5781 151.446 24.7969C151.811 24.9948 152.139 25.2656 152.431 25.6094C152.722 25.9427 152.972 26.3125 153.181 26.7188C153.389 27.125 153.545 27.5521 153.649 28C153.764 28.4375 153.821 28.8646 153.821 29.2812ZM140.384 31.5156H147.102C147.415 31.5156 147.707 31.4583 147.977 31.3438C148.248 31.2292 148.483 31.0729 148.681 30.875C148.889 30.6667 149.05 30.4271 149.165 30.1562C149.28 29.8854 149.337 29.5938 149.337 29.2812C149.337 28.9688 149.28 28.6771 149.165 28.4062C149.05 28.1354 148.889 27.901 148.681 27.7031C148.483 27.4948 148.248 27.3333 147.977 27.2188C147.707 27.1042 147.415 27.0469 147.102 27.0469H140.384V31.5156ZM140.384 22.5625H147.102C147.415 22.5625 147.707 22.5052 147.977 22.3906C148.248 22.276 148.483 22.1198 148.681 21.9219C148.889 21.7135 149.05 21.474 149.165 21.2031C149.28 20.9219 149.337 20.625 149.337 20.3125C149.337 20 149.28 19.7083 149.165 19.4375C149.05 19.1667 148.889 18.9323 148.681 18.7344C148.483 18.526 148.248 18.3646 147.977 18.25C147.707 18.1354 147.415 18.0781 147.102 18.0781H140.384V22.5625ZM163.415 31.9062C163.582 31.9583 163.748 31.9948 163.915 32.0156C164.082 32.026 164.248 32.0312 164.415 32.0312C164.832 32.0312 165.233 31.974 165.618 31.8594C166.003 31.7448 166.363 31.5833 166.696 31.375C167.04 31.1562 167.342 30.8958 167.602 30.5938C167.873 30.2812 168.092 29.9375 168.259 29.5625L171.384 32.7031C170.988 33.2656 170.53 33.7708 170.009 34.2188C169.498 34.6667 168.941 35.0469 168.337 35.3594C167.743 35.6719 167.113 35.9062 166.446 36.0625C165.79 36.2292 165.113 36.3125 164.415 36.3125C163.238 36.3125 162.128 36.0938 161.087 35.6562C160.056 35.2188 159.149 34.6094 158.368 33.8281C157.597 33.0469 156.988 32.1198 156.54 31.0469C156.092 29.9635 155.868 28.776 155.868 27.4844C155.868 26.1615 156.092 24.9531 156.54 23.8594C156.988 22.7656 157.597 21.8333 158.368 21.0625C159.149 20.2917 160.056 19.6927 161.087 19.2656C162.128 18.8385 163.238 18.625 164.415 18.625C165.113 18.625 165.795 18.7083 166.462 18.875C167.128 19.0417 167.759 19.2812 168.352 19.5938C168.957 19.9062 169.519 20.2917 170.04 20.75C170.561 21.1979 171.019 21.7031 171.415 22.2656L163.415 31.9062ZM165.602 23.1094C165.405 23.0365 165.207 22.9896 165.009 22.9688C164.821 22.9479 164.623 22.9375 164.415 22.9375C163.832 22.9375 163.28 23.0469 162.759 23.2656C162.248 23.474 161.8 23.776 161.415 24.1719C161.04 24.5677 160.743 25.0469 160.524 25.6094C160.306 26.1615 160.196 26.7865 160.196 27.4844C160.196 27.6406 160.201 27.8177 160.212 28.0156C160.233 28.2135 160.259 28.4167 160.29 28.625C160.332 28.8229 160.378 29.0156 160.431 29.2031C160.483 29.3906 160.55 29.5573 160.634 29.7031L165.602 23.1094ZM189.649 36H188.618L186.962 33.7031C186.556 34.0677 186.123 34.4115 185.665 34.7344C185.217 35.0469 184.743 35.3229 184.243 35.5625C183.743 35.7917 183.227 35.974 182.696 36.1094C182.175 36.2448 181.644 36.3125 181.102 36.3125C179.925 36.3125 178.816 36.1146 177.774 35.7188C176.743 35.3229 175.837 34.75 175.056 34C174.285 33.2396 173.675 32.3125 173.227 31.2188C172.78 30.125 172.556 28.8802 172.556 27.4844C172.556 26.1823 172.78 24.9896 173.227 23.9062C173.675 22.8125 174.285 21.875 175.056 21.0938C175.837 20.3125 176.743 19.7083 177.774 19.2812C178.816 18.8438 179.925 18.625 181.102 18.625C181.644 18.625 182.181 18.6927 182.712 18.8281C183.243 18.9635 183.759 19.151 184.259 19.3906C184.759 19.6302 185.233 19.9115 185.681 20.2344C186.139 20.5573 186.566 20.9062 186.962 21.2812L188.618 19.2969H189.649V36ZM185.352 27.4844C185.352 26.901 185.238 26.3385 185.009 25.7969C184.79 25.2448 184.488 24.7604 184.102 24.3438C183.717 23.9167 183.264 23.5781 182.743 23.3281C182.233 23.0677 181.686 22.9375 181.102 22.9375C180.519 22.9375 179.967 23.0365 179.446 23.2344C178.936 23.4323 178.488 23.724 178.102 24.1094C177.727 24.4948 177.431 24.974 177.212 25.5469C176.993 26.1094 176.884 26.7552 176.884 27.4844C176.884 28.2135 176.993 28.8646 177.212 29.4375C177.431 30 177.727 30.474 178.102 30.8594C178.488 31.2448 178.936 31.5365 179.446 31.7344C179.967 31.9323 180.519 32.0312 181.102 32.0312C181.686 32.0312 182.233 31.9062 182.743 31.6562C183.264 31.3958 183.717 31.0573 184.102 30.6406C184.488 30.2135 184.79 29.7292 185.009 29.1875C185.238 28.6354 185.352 28.0677 185.352 27.4844ZM201.306 36C200.274 36 199.306 35.8073 198.399 35.4219C197.493 35.026 196.696 34.4896 196.009 33.8125C195.332 33.125 194.795 32.3281 194.399 31.4219C194.014 30.5156 193.821 29.5469 193.821 28.5156V23.5469H191.743V19.2969H193.821V12.6094H198.071V19.2969H204.54V23.5469H198.071V28.5156C198.071 28.9635 198.155 29.3854 198.321 29.7812C198.488 30.1667 198.717 30.5052 199.009 30.7969C199.3 31.0885 199.644 31.3229 200.04 31.5C200.436 31.6667 200.858 31.75 201.306 31.75H204.54V36H201.306Z" fill="#B421CC"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.8453 0C11.1261 0 0 10.745 0 24.001C0 33.826 6.11788 42.271 14.8744 45.982C14.804 44.308 14.862 42.293 15.304 40.47C15.7823 38.522 17.7077 27.5 17.7077 27.5C17.7077 27.5 17.7077 25.861 17.7077 23.595C17.7077 20.039 20.1176 21.819 22.7739 21.819L26.397 23C33.1256 23 43.995 23 43.995 23C43.995 23 34.1018 15.5 27.4322 15.5C19.8971 15.5 19.1508 15.5 19.1508 15.5C19.1508 17.594 13.9749 18 13.9749 18L11.9045 15.5L9.31658 14.5C9.31658 7.684 13.9956 8.069 25.7987 8.069C35.285 8.069 49.6884 14.5 43.995 23C43.995 23 34.3875 23 26.397 23C23.718 23 29.3327 31.09 28.4673 29.5C28.4673 29.5 28.9849 33 22.7739 37C22.248 38.85 18.7481 45.575 17.805 47.015C20.04 47.653 22.4012 48 24.8453 48C38.5655 48 49.6884 37.255 49.6884 24.001C49.6884 10.745 38.5655 0 24.8453 0Z" fill="#B421CC"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_501_120">
|
||||
<rect width="206" height="53" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 38 KiB |
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="380px" height="120px" viewBox="0 0 380 120" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>jpom-logo</title>
|
||||
<defs>
|
||||
<linearGradient x1="0.0077378419%" y1="0%" x2="99.9922622%" y2="100%" id="linearGradient-1">
|
||||
<stop stop-color="#5988FF" offset="0%"></stop>
|
||||
<stop stop-color="#0D58FF" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
<path d="M97.0595063,0 C98.6792225,0 99.9922619,1.31303939 99.9922619,2.93275555 L99.9922619,83.9820475 C98.0456091,82.5565127 95.6960926,81.7868903 93.2832934,81.7844154 L92.9319967,81.7897766 C90.0097544,81.8790045 87.225876,83.0788415 85.1523588,85.1523588 C82.9959008,87.3088167 81.7844154,90.2336013 81.7844154,93.2832934 C81.7887374,95.7018607 82.5669783,98.0555651 84.0052619,100 L2.93275555,100 C1.31303939,100 0,98.6869606 0,97.0672444 L0,23.3072816 C0,21.6875654 1.31303939,20.374526 2.93275555,20.374526 L40.9038149,20.374526 C41.6822604,20.3724639 42.429417,20.6807875 42.979863,21.2312335 C43.530309,21.7816795 43.8386326,22.5288361 43.8365705,23.3072816 L43.8365705,78.9367794 L30.5114911,78.9367794 C29.733676,78.9367794 28.9877182,78.6277935 28.4377198,78.0777952 C27.8877214,77.5277968 27.5787356,76.781839 27.5787356,76.0040238 L27.5787356,34.4192525 C27.5787412,32.7982798 26.2669471,31.4830245 24.64598,31.4787588 L19.113209,31.4787588 C18.3340513,31.4787561 17.5869184,31.7888039 17.036697,32.3404771 C16.4864757,32.8921502 16.1783976,33.6400975 16.1804535,34.4192525 L16.1804535,73.9456782 C16.1783993,77.9092655 17.7520185,81.7111045 20.5546982,84.5137843 C23.357378,87.3164641 27.1592171,88.8900833 31.1228043,88.8880291 L68.8849338,88.8880291 C72.8478917,88.8880291 76.6485371,87.3137502 79.4507714,84.5115159 C82.2530058,81.7092815 83.8272847,77.9086361 83.8272847,73.9456782 L83.8272847,26.0543218 C83.8230153,17.8036584 77.1355972,11.1162403 68.8849338,11.1119709 L2.93275555,11.1119709 C1.31303939,11.1119709 0,9.79893152 0,8.17921535 L0,2.93275555 C0,1.31303939 1.31303939,0 2.93275555,0 Z M69.5271996,20.4054786 C71.1481666,20.4097443 72.4599551,21.7249996 72.4599551,23.3459723 L72.4599551,76.0427145 C72.4599551,76.8205297 72.1509693,77.5664875 71.6009709,78.1164859 C71.0509726,78.6664842 70.3050147,78.9754701 69.5271996,78.9754701 L59.1967809,78.9754701 C57.5758082,78.9754701 56.260553,77.6636816 56.2562872,76.0427145 L56.2562872,34.3573474 C56.2583431,33.5781924 55.950265,32.830245 55.4000437,32.2785719 C54.8498223,31.7268988 54.1026894,31.416851 53.3235317,31.4168537 L49.2842219,31.4168537 C47.6645058,31.4168537 46.3514664,30.1038143 46.3514664,28.4840981 L46.3514664,23.3459723 C46.3514664,21.7249996 47.6632549,20.4097443 49.2842219,20.4054786 Z" id="path-2"></path>
|
||||
<filter x="-6.5%" y="-5.5%" width="113.0%" height="113.0%" filterUnits="objectBoundingBox" id="filter-3">
|
||||
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
|
||||
<feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
|
||||
<feColorMatrix values="0 0 0 0 0.239215686 0 0 0 0 0.466666667 0 0 0 0 1 0 0 0 0.4 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
|
||||
</filter>
|
||||
<linearGradient x1="0%" y1="41.21675%" x2="100%" y2="58.78325%" id="linearGradient-4">
|
||||
<stop stop-color="#5988FF" offset="0%"></stop>
|
||||
<stop stop-color="#0D58FF" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
<path d="M321.177106,22 C326.648252,22 330.951915,23.8568225 334.001288,27.5645829 C337.018271,31.2329589 338.5054,36.5562047 338.5054,43.5075594 L338.5054,72.2764579 C338.5054,73.5037494 337.841444,74.4996824 336.640847,75.1857377 C335.575623,75.7944374 334.335668,76.0950324 332.937365,76.0950324 C331.544467,76.0950324 330.325067,75.8136323 329.292975,75.240248 C328.109924,74.5829978 327.466523,73.558321 327.466523,72.2764579 L327.466523,45.8401728 C327.466523,40.8955189 326.694202,37.2501639 325.194838,34.9111564 C323.764436,32.6797287 321.623311,31.5809935 318.650108,31.5809935 C316.79872,31.5809935 314.959692,32.2377892 313.114393,33.5771188 C311.238478,34.9386702 309.704728,36.7913184 308.510889,39.1483861 C307.319542,41.5005314 306.723542,44.0831986 306.723542,46.9092873 L306.723542,72.2764579 C306.723542,73.558321 306.08014,74.5829978 304.89709,75.240248 C303.864998,75.8136323 302.645598,76.0950324 301.2527,76.0950324 C299.859802,76.0950324 298.640401,75.8136323 297.60831,75.240248 C296.425259,74.5829978 295.781857,73.558321 295.781857,72.2764579 L295.781857,45.8401728 C295.781857,40.8955189 295.009536,37.2501639 293.510173,34.9111564 C292.080954,32.6815753 289.908011,31.5809935 286.868251,31.5809935 C285.088109,31.5809935 283.283388,32.2504868 281.43818,33.6195127 C279.556179,35.0158357 278.019002,36.8726239 276.823821,39.2016947 C275.633386,41.5215173 275.038877,44.0215023 275.038877,46.7149028 L275.038877,72.2764579 C275.038877,73.5037494 274.374922,74.4996824 273.174325,75.1857377 C272.111504,75.793064 270.90363,76.0950324 269.568035,76.0950324 C268.169731,76.0950324 266.929777,75.7944374 265.864552,75.1857377 C264.663955,74.4996824 264,73.5037494 264,72.2764579 L264,26.4989201 C264,25.2077738 264.668286,24.181477 265.88847,23.5278073 C266.948186,22.9601019 268.179114,22.6803456 269.568035,22.6803456 C270.71541,22.6803456 271.794876,22.969488 272.789139,23.5431017 C273.934643,24.2039694 274.552916,25.2264969 274.552916,26.4989201 L274.552,30.281 L274.790224,29.9647489 C276.112461,28.2407943 277.63116,26.7395118 279.345238,25.4631222 L279.777828,25.1487113 C282.731605,23.0535904 285.978736,22 289.492441,22 C293.59303,22 297.017667,23.0550649 299.730507,25.1766448 C301.838091,26.8248839 303.45357,29.0787041 304.580218,31.9217647 L304.664,32.141 L304.894304,31.758482 C306.256617,29.541499 308.04526,27.5894747 310.255056,25.9061867 L310.674327,25.5937203 C313.951412,23.204179 317.460734,22 321.177106,22 Z M231.214173,22.2721382 C235.842656,22.2721382 240.114809,23.2813081 244.017009,25.2996873 C247.949275,27.3336181 251.102191,30.4187296 253.463829,34.534728 C255.824475,38.648996 256.99819,43.6792053 256.99819,49.6112311 C256.99819,55.477913 255.824553,60.4910223 253.465574,64.636085 C251.105109,68.7837598 247.953791,71.9011925 244.023018,73.968237 C240.119404,76.0209992 235.845055,77.0475162 231.214173,77.0475162 C226.582371,77.0475162 222.323058,76.0205671 218.450763,73.9665673 C215.78895,72.5546487 213.481953,70.6522464 211.530874,68.2601777 C209.80805,70.6713158 207.747453,72.5853025 205.353205,73.9936837 C201.893057,76.0290653 198.056892,77.0475162 193.863464,77.0475162 C189.54284,77.0475162 185.913019,76.1613627 182.990636,74.373552 C181.090035,73.2108313 179.432511,71.7008619 178.019516,69.8490491 L177.854,69.627 L177.854825,92.1814255 C177.854825,93.3365234 177.266684,94.2866808 176.196546,94.966436 L175.990272,95.0907053 C174.927451,95.6980316 173.719578,96 172.383982,96 C170.985679,96 169.745724,95.699405 168.6805,95.0907053 C167.479903,94.40465 166.815948,93.408717 166.815948,92.1814255 L166.815948,26.7710583 C166.815948,25.479912 167.484234,24.4536152 168.704417,23.7999455 C169.764134,23.2322402 170.995062,22.9524838 172.383982,22.9524838 C173.531358,22.9524838 174.610823,23.2416263 175.605087,23.8152399 C176.750591,24.4761076 177.368863,25.4986352 177.368863,26.7710583 L177.368,30.16 L177.461941,30.0361727 C178.783959,28.309545 180.389965,26.8166359 182.276634,25.5604571 L182.685215,25.2949004 C185.864442,23.2779717 189.597959,22.2721382 193.863464,22.2721382 C198.128773,22.2721382 202.004283,23.3430028 205.46887,25.4828953 C207.771196,26.9049197 209.757739,28.7668286 211.429887,31.0693813 C213.399057,28.641774 215.743808,26.7167895 218.456791,25.3013201 C222.327675,23.2817281 226.584779,22.2721382 231.214173,22.2721382 Z M191.530851,31.6587473 C187.243462,31.6587473 183.896417,33.3099564 181.39759,36.6517607 C178.844201,40.0665344 177.563248,44.3106557 177.563248,49.4168467 C177.563248,55.109921 178.816344,59.5808448 181.296842,62.8575524 C183.727136,66.0679395 187.106609,67.6609071 191.530851,67.6609071 C195.643176,67.6609071 198.975743,66.1216499 201.603835,63.0102305 C203.930389,60.255805 205.247205,56.4902582 205.534777,51.6932527 L205.559596,51.2685058 L205.545125,50.8489568 C205.533272,50.4407123 205.527348,50.0281371 205.527348,49.6112311 C205.527348,49.189798 205.533269,48.7729418 205.545271,48.355724 L205.556796,48.0938945 L205.551214,47.9513662 C205.38458,44.9043393 204.749828,42.2339124 203.652128,39.9339693 C202.366962,37.2412414 200.654503,35.195383 198.511261,33.7766173 C196.374155,32.3619136 194.055203,31.6587473 191.530851,31.6587473 Z M231.214173,31.6587473 C226.836455,31.6587473 223.357717,33.2031872 220.700173,36.3137216 C218.430591,38.9701643 217.111936,42.5417911 216.759905,47.0508924 L216.700355,47.9290956 L216.714085,48.3122437 C216.725839,48.7404962 216.731714,49.1734918 216.731714,49.6112311 C216.731714,50.0470061 216.726182,50.47794 216.71489,50.911505 L216.700355,51.2251958 L216.724381,51.660711 C217.015654,56.4203676 218.347416,60.1767886 220.703261,62.9609692 C223.36111,66.1020631 226.838531,67.6609071 231.214173,67.6609071 C235.591349,67.6609071 239.086753,66.1007268 241.777295,62.9567219 C244.491519,59.7850452 245.862121,55.3498364 245.862121,49.6112311 C245.862121,43.8739162 244.492208,39.456332 241.78041,36.317958 C239.090148,33.2045095 235.593424,31.6587473 231.214173,31.6587473 Z M150.712743,4 C149.259053,4 147.963783,4.29606168 146.840427,4.89518486 C145.554497,5.58101431 144.853132,6.6564405 144.853132,8.01295896 L144.853132,50.3887689 C144.853132,54.9604969 144.458747,58.4784132 143.684829,60.9239931 C142.944236,63.264268 141.87171,64.8087043 140.491802,65.6090509 C139.040305,66.4509193 137.071912,66.8833693 134.578834,66.8833693 C133.004999,66.8833693 131.710962,66.6900075 130.699158,66.3141946 C129.625877,65.9155473 128.398225,65.3094909 127.020608,64.495445 C126.731999,64.3493116 126.45579,64.2036746 126.173893,64.0486339 L125.744519,63.8077828 C125.260149,63.5309997 124.711978,63.2569143 124.099443,62.9846766 C123.527077,62.7889863 123.145105,62.7041037 122.818575,62.7041037 C121.483129,62.7041037 120.469249,63.6183631 119.75338,65.2384268 L119.527332,65.7673253 C119.172059,66.6629279 119,67.5887714 119,68.5637149 C119,70.7152148 119.984373,72.4418969 121.873536,73.6494261 L122.114388,73.7976565 C125.574241,75.9689259 130.225662,77.0475162 136.036717,77.0475162 C140.933982,77.0475162 144.881961,76.1664222 147.872655,74.3788806 C150.905083,72.5663953 153.107534,69.6529168 154.47669,65.6823622 C155.813792,61.8047679 156.475162,56.6460815 156.475162,50.1943844 L156.475162,8.01295896 C156.475162,6.66509921 155.798106,5.59114857 154.548952,4.90196011 C153.45357,4.29761162 152.17012,4 150.712743,4 Z" id="path-5"></path>
|
||||
<filter x="-3.0%" y="-6.0%" width="105.9%" height="114.1%" filterUnits="objectBoundingBox" id="filter-6">
|
||||
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
|
||||
<feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
|
||||
<feColorMatrix values="0 0 0 0 0.239215686 0 0 0 0 0.466666667 0 0 0 0 1 0 0 0 0.4 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
|
||||
</filter>
|
||||
</defs>
|
||||
<g id="jpom-logo" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="1" transform="translate(12.000000, 10.000000)" fill-rule="nonzero">
|
||||
<g id="2">
|
||||
<use fill="black" fill-opacity="1" filter="url(#filter-3)" xlink:href="#path-2"></use>
|
||||
<use fill="url(#linearGradient-1)" xlink:href="#path-2"></use>
|
||||
</g>
|
||||
<rect id="4" fill="#F74C00" x="345" y="64" width="12" height="12" rx="6"></rect>
|
||||
<g id="5">
|
||||
<use fill="black" fill-opacity="1" filter="url(#filter-6)" xlink:href="#path-5"></use>
|
||||
<use fill="url(#linearGradient-4)" xlink:href="#path-5"></use>
|
||||
</g>
|
||||
<rect id="3" fill="#F74C00" x="88" y="88" width="12" height="12" rx="6"></rect>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 142 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="_图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 240">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #ff9045;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #333;
|
||||
}
|
||||
|
||||
.cls-3 {
|
||||
fill: #0079dd;
|
||||
}
|
||||
|
||||
.cls-4 {
|
||||
fill: #595959;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g>
|
||||
<g>
|
||||
<circle class="cls-1" cx="145.28" cy="62.13" r="13.63"/>
|
||||
<path class="cls-1" d="M154.29,83.78h-18.09c-4.56,0-8.26,3.7-8.26,8.26v92.19c0,4.02,3.26,7.28,7.28,7.28h.07c4.02,0,7.28-3.26,7.28-7.28v-54.05c2.96,0,5.36,2.4,5.36,5.36v16.41c0,4.02,3.26,7.28,7.28,7.28h.07c4.02,0,7.28-3.26,7.28-7.28v-21.16s0-38.75,0-38.75c0-4.56-3.7-8.26-8.26-8.26Z"/>
|
||||
</g>
|
||||
<path class="cls-3" d="M117.19,125.56h-1.98c-3.07,0-5.56-2.49-5.56-5.56s2.49-5.56,5.56-5.56h1.98s.04,0,.06,0v-25.71c0-4.77-2.32-9.24-6.21-11.98L50.81,34.3c-4.86-3.42-11.55,.05-11.55,5.99V199.71c0,5.94,6.7,9.41,11.55,5.99l60.23-42.45c3.9-2.75,6.21-7.22,6.21-11.98v-25.71s-.04,0-.06,0Z"/>
|
||||
<path class="cls-3" d="M239.76,34.3l-60.23,42.45c-3.9,2.75-6.21,7.22-6.21,11.98v25.71s.04,0,.06,0h1.98c3.07,0,5.56,2.49,5.56,5.56s-2.49,5.56-5.56,5.56h-1.98s-.04,0-.06,0v25.71c0,4.77,2.32,9.24,6.21,11.98l60.23,42.45c4.86,3.42,11.55-.05,11.55-5.99V40.29c0-5.94-6.7-9.41-11.55-5.99Z"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="cls-2" d="M344.63,60.54v-7.61h8.13v7.61h20.8c4.41,0,7.8,.88,10.16,2.64,3.06,2.31,4.58,5.45,4.58,9.43l-.04,32.47c0,3.86-.78,6.82-2.34,8.87-2.42,3.2-6.01,4.8-10.77,4.8h-22.4l.04,7.52-8.17-.04v-7.48h-20.54c-4.15,0-7.24-.59-9.25-1.77-3.66-2.13-5.49-6.18-5.49-12.15v-30.57c0-4.44,1.12-7.77,3.37-9.99,2.51-2.48,5.89-3.72,10.16-3.72h21.75Zm0,50.07v-41.94h-22.01c-2.05,0-3.47,.55-4.28,1.64-.58,.81-.87,2.08-.87,3.8v30.05c0,1.96,.07,3.13,.22,3.5,.72,1.99,2.31,2.98,4.76,2.98l22.18-.04Zm8.13,0l20.93,.04c4.32,.03,6.49-1.79,6.49-5.45v-30.48c0-2.48-.49-4.11-1.47-4.89-.92-.72-2.48-1.1-4.67-1.12l-21.27-.09v41.99Z"/>
|
||||
<path class="cls-2" d="M401.72,75.41v-8.78c2.13-1.73,3.62-3.34,4.45-4.84,1.12-1.93,2-4.94,2.64-9.04h8.13c-.46,2.19-.79,3.84-.99,4.93-.81,4.12-2.12,7.46-3.93,10.03-1.87,2.62-4.08,4.64-6.62,6.05-.81,.43-2.03,.98-3.67,1.64Zm0,20.67v-8.86c2.36-1.9,3.91-3.56,4.63-4.97,.89-1.67,1.7-4.63,2.42-8.86h8.17l-.91,4.15v48.86h-8.09v-33.77c-1.93,1.44-4.01,2.59-6.23,3.46Zm17.25,30.31v-8.47c1.35-.78,2.03-1.9,2.03-3.37v-10.59c0-2.6,.88-4.79,2.64-6.57,1.59-1.58,4.53-2.38,8.82-2.38h6.87c6.72,0,10.08,3.19,10.08,9.56v8.86c0,1.73,.23,2.87,.69,3.42,.26,.32,.72,.68,1.38,1.08v8.47c-2.88-.43-5.16-1.3-6.83-2.59-.87-.66-1.59-1.56-2.16-2.68-.81-1.53-1.21-3.07-1.21-4.63v-9.95c0-2.39-1.47-3.59-4.41-3.59h-3.5c-1.47,0-2.51,.32-3.11,.95-.81,.81-1.21,2.05-1.21,3.72v8.87c0,2.79-.88,5.07-2.64,6.83-1.39,1.41-3.86,2.44-7.44,3.07Zm.91-73.68h7.22v15.52c0,1.21,.23,2.02,.69,2.42,.52,.43,1.41,.65,2.68,.65h1.08v-18.59h7.74v18.59h1.12c1.07,0,1.84-.29,2.34-.86s.74-1.17,.74-1.77v-15.96h7.05v17.99c0,3.08-1.11,5.35-3.33,6.79-1.21,.78-3.37,1.17-6.49,1.17h-11.68c-3.2,0-5.59-.81-7.18-2.42-.69-.72-1.14-1.37-1.34-1.95-.43-1.21-.65-3.01-.65-5.4v-16.17Zm0,38.18v-7.22h30.66v7.22h-30.66Zm33.12,35.11v-7.74c2.36-.92,4.08-1.72,5.15-2.38,1.1-.69,2.62-1.92,4.58-3.67-2.02-3.83-3.39-7.16-4.11-9.99-.89-3.34-1.34-6.76-1.34-10.25v-14.87c-1.12,.75-2.45,1.37-3.98,1.86v-8.39c1.38-1.53,2.35-3.56,2.9-6.1,.63-3.08,1.08-7,1.34-11.76h7.26v4.89h17.08v8h-4.2v27.46c0,6.05-1.83,12.44-5.49,19.16,1.44,1.38,2.61,2.41,3.5,3.07,1.27,.89,3.33,1.89,6.18,2.98v7.74c-3.98-1.1-6.99-2.28-9.04-3.55-1.5-.89-3.27-2.29-5.32-4.19-3.08,2.54-5.49,4.24-7.22,5.1-1.53,.81-3.96,1.69-7.31,2.64Zm4.67-49.12h6.66l-.13,12.8c-.03,4.56,1.05,9.7,3.24,15.44,2.02-4.44,3.03-8.62,3.03-12.54v-26.98h-6.74c-1.15,4.24-2.38,7.18-3.68,8.82-.52,.69-1.31,1.51-2.38,2.47Z"/>
|
||||
<path class="cls-2" d="M494.58,91.89v-8h41.21l21.32-17.81c1.21-1.01,1.82-1.9,1.82-2.68,0-.46-.16-.89-.47-1.3-.66-.83-1.75-1.25-3.24-1.25h-56.56v-8h58.98c3.6,0,6.36,.68,8.26,2.03,2.36,1.67,3.55,3.99,3.55,6.96,0,2.56-1.04,4.97-3.11,7.22-1.21,1.3-2.8,2.75-4.76,4.37l-12.93,10.46h26.12v8h-29.45l.04,21.53c0,3.92-1.21,7.1-3.63,9.56-2.25,2.31-5.61,3.46-10.07,3.46h-16.73v-8.04h15.52c2.45,.03,4.12-.47,5.01-1.51,.81-.95,1.21-2.39,1.21-4.33v-20.71l-42.07,.04Z"/>
|
||||
<path class="cls-2" d="M587.86,86.78v-10.29c3-2.13,5.17-4.28,6.53-6.44,2.88-4.58,4.96-10.29,6.23-17.12h9.08c-1.24,6.26-2.96,11.93-5.15,17.04v56.38h-8.3v-44.88c-2.02,1.62-3.34,2.61-3.98,2.98-.61,.4-2.08,1.18-4.41,2.34Zm21.02-8.78v-8.13h19.46l-5.06-17.08h8.69l4.8,17.08h30.61v8.13h-28.32l5.1,18.81c1.01,3.72,2.31,7.28,3.89,10.68,1.47,3.14,3.66,5.69,6.57,7.65,3.29,2.22,6.69,3.33,10.2,3.33,.46,0,1.17-.04,2.12-.13v8.09c-4.7-.09-8.53-.68-11.5-1.77-5.51-2.07-9.71-5.65-12.63-10.72-1.87-3.23-3.72-7.91-5.53-14.05l-6.53-21.88h-21.88Zm40.99-16.78v-8h17.51v8h-17.51Z"/>
|
||||
<path class="cls-2" d="M680.84,124.06v-8h7.05v-24.95l-7.09,.04,.04-7.87h7.05v-20.41h-7.05v-8h22.4v8h-7.35v20.41h7.35v7.83h-7.35v24.95h7.35v8h-22.4Zm26.59,2.2v-8h22.92v-9.25h-20.76v-8h20.76v-7.05l-14.01-.04c-2.48,0-4.44-.88-5.88-2.64-.89-1.12-1.41-2.31-1.56-3.55-.09-.72-.13-1.7-.13-2.94v-23.44c0-2.91,.74-5.1,2.2-6.57,1.38-1.38,3.24-2.08,5.58-2.08h33.25c3.52,0,6.04,.72,7.57,2.16,1.33,1.27,1.99,3.17,1.99,5.71v25.47c0,2.42-.71,4.35-2.12,5.79-1.35,1.38-3.06,2.07-5.1,2.07l-13.79,.04v7.05h20.8v8h-20.8v9.25h22.4v8h-53.31Zm9.34-56.99h13.58v-8.56h-10.25c-1.21,0-2.11,.29-2.68,.86-.43,.43-.65,1.25-.65,2.47v5.23Zm13.58,16.56v-8.6h-13.58v4.71c0,1.56,.23,2.6,.69,3.11,.43,.52,1.24,.78,2.42,.78h10.46Zm8-16.56h13.19v-5.23c.03-2.28-1.41-3.42-4.32-3.42l-8.86,.09v8.56Zm0,16.56h9.64c1.41,0,2.35-.26,2.81-.78,.52-.58,.76-1.54,.74-2.9v-4.93h-13.19v8.6Z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="cls-4" d="M315.77,154.13l16.1,23.16h.12v-23.16h3.27v28.46h-3.35l-16.18-23.4h-.12v23.4h-3.27v-28.46h3.43Z"/>
|
||||
<path class="cls-4" d="M370.12,164.77c1.51,1.99,2.31,4.82,2.39,8.41h-16.26c.16,2.31,.76,4.11,1.87,5.38,1.12,1.28,2.67,1.91,4.63,1.91,1.67,0,3.07-.44,4.1-1.28,.88-.72,1.56-1.79,2.07-3.23h3.19c-.48,1.99-1.39,3.59-2.79,4.86-1.71,1.52-3.91,2.31-6.58,2.31-2.95,0-5.34-1-7.1-2.91-1.83-1.99-2.71-4.62-2.71-7.97,0-3.03,.84-5.58,2.59-7.61,1.75-2.15,4.11-3.23,7.1-3.23,3.31,0,5.82,1.12,7.49,3.35Zm-11.84,1.12c-1.12,1.2-1.76,2.79-1.95,4.82h12.88c-.4-4.42-2.59-6.62-6.5-6.62-1.83,0-3.31,.6-4.42,1.79Z"/>
|
||||
<path class="cls-4" d="M392.99,161.98v12.6c0,1.95,.4,3.43,1.24,4.38,.84,.96,2.19,1.48,4.07,1.48,1.35,0,2.59-.56,3.67-1.59,1.12-1.12,1.79-2.55,2.03-4.3v-12.56h3.19v20.61h-3.19v-3.19c-1.68,2.47-3.91,3.75-6.7,3.75-5.02,0-7.5-2.83-7.5-8.41v-12.76h3.19Z"/>
|
||||
<path class="cls-4" d="M431.17,161.98h5.1v2.67h-5.1v13.11c0,.76,.12,1.31,.44,1.63,.28,.32,.8,.52,1.51,.52h2.63v2.67h-3.11c-1.67,0-2.91-.44-3.63-1.31-.68-.8-1-1.95-1-3.51v-13.11h-4.15v-2.67h4.15v-5.34l3.15-1.31v6.66Z"/>
|
||||
<path class="cls-4" d="M465.24,161.9v3.19c-1.04-.32-2.07-.44-3.07-.44-1.59,0-2.95,.68-4.03,2.11-1.12,1.35-1.64,2.99-1.64,4.9v10.92h-3.15v-20.61h3.15v3.59c.56-1.19,1.28-2.15,2.15-2.83,1.08-.88,2.31-1.32,3.75-1.32,1.12,0,2.07,.16,2.83,.48Z"/>
|
||||
<path class="cls-4" d="M485.8,154.88c.48,.4,.72,.96,.72,1.68,0,.68-.24,1.24-.72,1.71-.48,.44-1.04,.68-1.71,.68s-1.24-.24-1.71-.68c-.48-.48-.68-1.04-.68-1.71,0-.72,.2-1.28,.68-1.68,.48-.48,1.04-.68,1.71-.68s1.24,.2,1.71,.68Zm-.12,7.1v20.61h-3.19v-20.61h3.19Z"/>
|
||||
<path class="cls-4" d="M522.54,169.91v12.67h-3.19v-12.44c0-4.03-1.83-6.02-5.42-6.02-1.43,0-2.67,.52-3.71,1.56-1.12,1.12-1.71,2.59-1.83,4.46v12.44h-3.19v-20.61h3.19v3.03c.72-1.16,1.64-2.03,2.75-2.63,1.12-.64,2.35-.96,3.71-.96,5.1,0,7.69,2.83,7.69,8.49Z"/>
|
||||
<path class="cls-4" d="M557.61,164.61c1.75,2.03,2.63,4.58,2.63,7.69s-.88,5.62-2.59,7.61c-1.87,2.15-4.35,3.23-7.41,3.23s-5.54-1.08-7.37-3.23c-1.75-1.99-2.63-4.54-2.63-7.61s.88-5.66,2.63-7.69c1.83-2.15,4.27-3.19,7.37-3.19s5.54,1.04,7.38,3.19Zm-12.48,1.99c-1.12,1.51-1.63,3.39-1.63,5.7s.52,4.15,1.63,5.66c1.2,1.68,2.91,2.51,5.1,2.51s3.87-.84,5.14-2.51c1.07-1.51,1.63-3.43,1.63-5.66s-.56-4.19-1.63-5.7c-1.28-1.68-2.99-2.51-5.14-2.51s-3.91,.84-5.1,2.51Z"/>
|
||||
<path class="cls-4" d="M616.79,154.13c6.54,0,9.8,2.79,9.8,8.37s-3.31,8.45-9.84,8.45h-8.33v11.64h-3.27v-28.46h11.64Zm-8.37,13.99h8.17c2.31,0,3.99-.48,5.1-1.39,1.07-.92,1.63-2.31,1.63-4.23s-.56-3.31-1.67-4.15c-1.12-.96-2.79-1.4-5.06-1.4h-8.17v11.16Z"/>
|
||||
<path class="cls-4" d="M655.76,161.9v3.19c-1.04-.32-2.07-.44-3.07-.44-1.59,0-2.95,.68-4.03,2.11-1.11,1.35-1.63,2.99-1.63,4.9v10.92h-3.15v-20.61h3.15v3.59c.56-1.19,1.28-2.15,2.15-2.83,1.07-.88,2.31-1.32,3.75-1.32,1.12,0,2.07,.16,2.83,.48Z"/>
|
||||
<path class="cls-4" d="M688.55,164.61c1.76,2.03,2.63,4.58,2.63,7.69s-.88,5.62-2.59,7.61c-1.87,2.15-4.34,3.23-7.41,3.23s-5.54-1.08-7.37-3.23c-1.75-1.99-2.63-4.54-2.63-7.61s.88-5.66,2.63-7.69c1.83-2.15,4.27-3.19,7.37-3.19s5.54,1.04,7.37,3.19Zm-12.48,1.99c-1.12,1.51-1.63,3.39-1.63,5.7s.52,4.15,1.63,5.66c1.2,1.68,2.91,2.51,5.1,2.51s3.87-.84,5.14-2.51c1.08-1.51,1.64-3.43,1.64-5.66s-.56-4.19-1.64-5.7c-1.28-1.68-2.99-2.51-5.14-2.51s-3.91,.84-5.1,2.51Z"/>
|
||||
<path class="cls-4" d="M711.23,161.98l5.34,7.33,5.3-7.33h3.75l-7.29,9.69,8.17,10.92h-3.79l-6.14-8.57-6.18,8.57h-3.79l8.17-10.92-7.29-9.69h3.75Z"/>
|
||||
<path class="cls-4" d="M743.9,161.98l6.3,16.26,6.1-16.26h3.51l-11.28,28.5h-3.47l3.55-8.37-8.17-20.13h3.47Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 213.97 125.78"><defs><style>.cls-1{fill:#231815;}</style></defs><title>资源 6</title><g id="图层_2" data-name="图层 2"><g id="图层_1-2" data-name="图层 1"><path class="cls-1" d="M103.07,10C104.83,4.87,101.58-.51,95.14,0A11.77,11.77,0,0,0,90,1.83C58.43,21,59.5,62.37,90.42,82.43A10.38,10.38,0,0,0,95,84.08c7.4.71,12-6.51,7.31-12a10.5,10.5,0,0,0-2.89-2.23c-23.85-13.2-24.7-41.33-.38-55.2A8.85,8.85,0,0,0,103.07,10Z"/><path class="cls-1" d="M124.06,14.2l10.52,6.9a8.84,8.84,0,0,0,1,.56c7.18,3.31,13.75-3.74,9.77-9.95A8.46,8.46,0,0,0,143,9.36L131.66,1.8A8.44,8.44,0,0,0,125.37.4c-4.29.82-6.12,4.75-4.94,9A8.54,8.54,0,0,0,124.06,14.2Z"/><path class="cls-1" d="M132,51.62V62.88a4.06,4.06,0,0,1-2,3.5l-5.34,3.14a7.59,7.59,0,0,0-4.13,8.69c.77,2.74,3.12,5,7.16,5.25a7.37,7.37,0,0,0,2.75-.43,35.87,35.87,0,0,0,13.38-8.55,9.57,9.57,0,0,0,2.76-6.75V42.06h0a6.78,6.78,0,0,0-6.78-6.78H132"/><path class="cls-1" d="M124.17,37.57V48.93a2.3,2.3,0,0,1-2.29,2.29h-7.11a8.48,8.48,0,0,1-7.79-5,7.39,7.39,0,0,1-.33-5.41,8.39,8.39,0,0,1,8-5.57h7.18A2.3,2.3,0,0,1,124.17,37.57Z"/><path class="cls-1" d="M11.91,100.28A2.38,2.38,0,0,0,9.31,97a3.89,3.89,0,0,0-1.68.59c-10.37,6.28-10,19.87.13,26.45a3.4,3.4,0,0,0,1.5.54c2.43.24,3.94-2.14,2.4-3.93a3.26,3.26,0,0,0-.95-.73c-7.82-4.33-8.1-13.57-.12-18.12A2.87,2.87,0,0,0,11.91,100.28Z"/><path class="cls-1" d="M18.8,101.68l3.46,2.26a3.37,3.37,0,0,0,.32.19c2.36,1.08,4.51-1.23,3.21-3.27a2.86,2.86,0,0,0-.78-.77L21.3,97.61a2.78,2.78,0,0,0-2.07-.46,2.18,2.18,0,0,0-1.62,2.95A2.81,2.81,0,0,0,18.8,101.68Z"/><path class="cls-1" d="M39.24,98.82V123a2.3,2.3,0,0,0,0,.37c.37,2.57,3.48,3.12,4.48.91a2.44,2.44,0,0,0,.22-1.07L44,98.69a2.78,2.78,0,0,0-.74-2,2.19,2.19,0,0,0-3.36.25A2.86,2.86,0,0,0,39.24,98.82Z"/><path class="cls-1" d="M71.05,108.91,71,123c0,.12,0,.25,0,.37.38,2.57,3.49,3.12,4.49.91a2.6,2.6,0,0,0,.22-1.07l.06-14.47a2.81,2.81,0,0,0-.73-2,2.2,2.2,0,0,0-3.37.25A2.85,2.85,0,0,0,71.05,108.91Z"/><path class="cls-1" d="M126.44,95.88l-10.13,0a2.42,2.42,0,0,0-.38,0c-2.57.37-3.12,3.49-.91,4.49a2.86,2.86,0,0,0,1.08.22l10.46.06a2.76,2.76,0,0,0,2-.74,2.18,2.18,0,0,0-.25-3.36A2.75,2.75,0,0,0,126.44,95.88Z"/><path class="cls-1" d="M126.44,121l-10.13,0-.38,0c-2.57.38-3.12,3.49-.91,4.49a2.86,2.86,0,0,0,1.08.22l10.46.06a2.8,2.8,0,0,0,2-.73,2.19,2.19,0,0,0-.25-3.37A2.81,2.81,0,0,0,126.44,121Z"/><path class="cls-1" d="M172.85,98.18a2.86,2.86,0,0,1-.91,1.92l-12.34,12v10.86a2.81,2.81,0,0,1-.68,1.86,2.19,2.19,0,0,1-3.36.25,2.85,2.85,0,0,1-.74-2l0-6.36,0-6.61,4.76-4.62,8.91-8.68a2.82,2.82,0,0,1,1.8-.81A2.19,2.19,0,0,1,172.85,98.18Z"/><path class="cls-1" d="M121.44,108.44h-5.13a2.42,2.42,0,0,0-.38,0c-2.57.37-3.12,3.48-.91,4.48a2.68,2.68,0,0,0,1.08.22l5.46.07a2.76,2.76,0,0,0,2-.74,2.18,2.18,0,0,0-.25-3.36A2.81,2.81,0,0,0,121.44,108.44Z"/><path class="cls-1" d="M211.18,95.79H201.05a2.3,2.3,0,0,0-.37,0c-2.57.37-3.12,3.48-.92,4.48a2.68,2.68,0,0,0,1.08.22l10.46.07a2.85,2.85,0,0,0,2-.74,2.19,2.19,0,0,0-.25-3.36A2.86,2.86,0,0,0,211.18,95.79Z"/><path class="cls-1" d="M211.18,120.93l-10.13,0a2.3,2.3,0,0,0-.37,0c-2.57.37-3.12,3.49-.92,4.49a2.86,2.86,0,0,0,1.08.22l10.46.06a2.8,2.8,0,0,0,2-.74,2.19,2.19,0,0,0-.25-3.36A2.8,2.8,0,0,0,211.18,120.93Z"/><path class="cls-1" d="M206.18,108.36l-5.13,0-.37,0c-2.57.38-3.12,3.49-.92,4.49a2.86,2.86,0,0,0,1.08.22l5.46.06a2.84,2.84,0,0,0,2-.73A2.2,2.2,0,0,0,208,109,2.85,2.85,0,0,0,206.18,108.36Z"/><path class="cls-1" d="M151.22,100.83l-3.88-3.36a2.37,2.37,0,0,0-.3-.23c-2.19-1.39-4.63.62-3.61,2.81a2.48,2.48,0,0,0,.67.87l4.1,3.61a2.83,2.83,0,0,0,2,.73,2.18,2.18,0,0,0,2-2.71A2.8,2.8,0,0,0,151.22,100.83Z"/><path class="cls-1" d="M61.45,100.75l24.13-.24.38,0c2.56-.4,3.07-3.52.86-4.49a2.67,2.67,0,0,0-1.08-.21L61.28,96a2.82,2.82,0,0,0-2,.76,2.19,2.19,0,0,0,.29,3.36A2.78,2.78,0,0,0,61.45,100.75Z"/><path class="cls-1" d="M102.73,98.84,103,123a2.42,2.42,0,0,0,0,.38c.4,2.56,3.52,3.08,4.5.86a2.67,2.67,0,0,0,.21-1.08l-.21-24.46a2.76,2.76,0,0,0-.76-2,2.18,2.18,0,0,0-3.35.28A2.81,2.81,0,0,0,102.73,98.84Z"/><path class="cls-1" d="M187.27,98.84l.24,24.13a2.43,2.43,0,0,0,0,.38c.4,2.56,3.52,3.08,4.49.86a2.67,2.67,0,0,0,.21-1.08L192,98.67a2.8,2.8,0,0,0-.76-2,2.19,2.19,0,0,0-3.36.28A2.8,2.8,0,0,0,187.27,98.84Z"/><path class="cls-1" d="M26.18,110.82v8.42a3.13,3.13,0,0,1-.91,2.22,11.64,11.64,0,0,1-4.39,2.8,2.3,2.3,0,0,1-.9.15,2.4,2.4,0,0,1-2.35-1.73A2.49,2.49,0,0,1,19,119.83l1.75-1a1.33,1.33,0,0,0,.65-1.15V114a.23.23,0,0,0,0-.14h-5.6a.86.86,0,0,1-.23,0,2.76,2.76,0,0,1-2.33-1.6,2.43,2.43,0,0,1-.1-1.78,2.74,2.74,0,0,1,2.43-1.82h8.6a2.18,2.18,0,0,1,1.41.64A2.26,2.26,0,0,1,26.18,110.82Z"/><path class="cls-1" d="M15.73,108.59h0Z"/><path class="cls-1" d="M15.52,113.8a.86.86,0,0,0,.23,0h-.23Z"/><path class="cls-1" d="M24.12,108.59h0Z"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 20 KiB |
@@ -0,0 +1,15 @@
|
||||
<svg width="1053" height="170" viewBox="0 0 1053 170" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M73.9973 35.0579C54.1616 69.8286 42.9756 83.2381 35.8964 77.1848C30.9901 72.9896 47.7987 41.7206 58.7276 21.3898C62.1781 14.9707 65.0426 9.64207 66.4524 6.60013C66.7845 5.88338 66.1105 5.51842 65.5618 6.11847C40.0106 34.0615 -21.3347 135.895 7.5775 162.983C8.93141 164.252 11.0512 163.631 11.7989 161.933L33.1473 113.459C34.035 111.443 36.5449 110.432 38.7533 111.201L48.8546 114.714C51.063 115.483 52.1337 117.739 51.246 119.755L31.2362 165.189C30.3407 167.223 31.8971 169.466 34.0878 169.095C74.8305 162.188 98.0507 93.0493 117.5 36.5C124 14.5 124 14.5 128 0C119.318 15.677 82.7367 82.7294 70.4985 81.4891C61.0082 80.5273 63.8387 71.85 70.8524 50.3486C72.2789 45.9755 73.8785 41.072 75.5826 35.595C75.865 34.6874 74.4765 34.218 73.9973 35.0579Z" fill="#FF5C00"/>
|
||||
<path d="M200.782 129.157H178.611L171.579 108.423H138.084L131.052 129.157H109L144.163 32.6779H165.5L200.782 129.157ZM154.533 60.2434L144.759 88.7678H164.904L155.249 60.2434H154.533Z" fill="black"/>
|
||||
<path d="M208.662 159V57.6067H228.806V66.1161H229.522C231.667 62.9201 234.409 60.4432 237.746 58.6854C241.084 56.8477 244.62 55.9288 248.355 55.9288C253.997 55.9288 258.924 57.4469 263.135 60.4832C267.426 63.4395 270.724 67.6742 273.029 73.1873C275.333 78.7004 276.485 85.1723 276.485 92.603C276.485 100.353 275.333 107.105 273.029 112.858C270.724 118.61 267.426 123.045 263.135 126.161C258.844 129.277 253.838 130.835 248.116 130.835C244.302 130.835 240.766 129.916 237.508 128.079C234.329 126.321 231.667 123.844 229.522 120.648H228.806V159H208.662ZM228.33 93.4419C228.33 99.035 229.561 103.509 232.025 106.865C234.488 110.141 237.746 111.779 241.799 111.779C246.09 111.779 249.507 110.141 252.05 106.865C254.593 103.509 255.864 99.035 255.864 93.4419C255.864 87.769 254.633 83.2946 252.169 80.0187C249.706 76.6629 246.408 74.985 242.276 74.985C238.064 74.985 234.687 76.6629 232.144 80.0187C229.601 83.2946 228.33 87.769 228.33 93.4419Z" fill="black"/>
|
||||
<path d="M306.184 130.835C299.907 130.835 294.702 129.037 290.569 125.442C286.517 121.767 284.49 116.733 284.49 110.341C284.49 105.387 285.881 101.032 288.662 97.2772C291.443 93.5218 295.377 90.6454 300.463 88.6479C305.628 86.5705 311.628 85.5318 318.462 85.5318H324.898V83.7341C324.898 80.2185 323.905 77.5418 321.918 75.7041C320.011 73.7865 317.309 72.8277 313.813 72.8277C308.807 72.8277 304.277 75.3046 300.224 80.2584L287.113 70.191C294.821 60.6829 304.595 55.9288 316.435 55.9288C322.077 55.9288 327.084 57.0874 331.454 59.4045C335.825 61.6417 339.202 64.7978 341.586 68.8727C343.97 72.9476 345.162 77.6617 345.162 83.015V129.157H324.898V121.007H324.183C322.753 124.203 320.448 126.64 317.27 128.318C314.091 129.996 310.396 130.835 306.184 130.835ZM312.502 114.176C316.078 114.176 319.018 113.377 321.322 111.779C323.706 110.101 324.898 107.824 324.898 104.948V102.071H320.488C315.323 102.071 311.469 102.591 308.926 103.629C306.383 104.668 305.111 106.426 305.111 108.903C305.111 110.421 305.747 111.699 307.019 112.738C308.37 113.697 310.197 114.176 312.502 114.176Z" fill="black"/>
|
||||
<path d="M391.139 130.835C383.829 130.835 377.471 129.317 372.068 126.281C366.744 123.245 362.611 118.93 359.671 113.337C356.811 107.664 355.38 101.032 355.38 93.4419C355.38 85.8514 356.811 79.2197 359.671 73.5468C362.611 67.8739 366.744 63.5194 372.068 60.4832C377.471 57.4469 383.829 55.9288 391.139 55.9288C397.656 55.9288 403.576 57.8464 408.9 61.6816C414.224 65.5169 418.118 70.7104 420.581 77.2622L401.867 84.3333C400.834 81.377 399.205 79.0999 396.98 77.5019C394.835 75.9039 392.451 75.1049 389.828 75.1049C385.617 75.1049 382.239 76.7828 379.696 80.1386C377.233 83.4145 376.001 87.8489 376.001 93.4419C376.001 99.035 377.233 103.469 379.696 106.745C382.239 110.021 385.617 111.659 389.828 111.659C392.451 111.659 394.835 110.86 396.98 109.262C399.205 107.584 400.834 105.307 401.867 102.431L420.581 109.502C418.118 116.054 414.224 121.247 408.9 125.082C403.576 128.918 397.656 130.835 391.139 130.835Z" fill="black"/>
|
||||
<path d="M430.993 129.157V32.6779H451.138V66.9551H451.734C453.8 63.5194 456.502 60.8427 459.839 58.9251C463.256 56.9276 467.15 55.9288 471.52 55.9288C478.593 55.9288 484.076 58.2459 487.97 62.8801C491.943 67.4345 493.93 73.9463 493.93 82.4157V129.157H473.785V87.0899C473.785 83.4145 472.871 80.5381 471.044 78.4607C469.216 76.3034 466.713 75.2247 463.534 75.2247C459.799 75.2247 456.78 76.4232 454.475 78.8202C452.25 81.1373 451.138 84.3333 451.138 88.4082V129.157H430.993Z" fill="black"/>
|
||||
<path d="M571.491 88.2884C571.491 93.3221 570.974 97.6367 569.941 101.232H525.361C526.871 109.861 531.719 114.176 539.903 114.176C542.764 114.176 545.347 113.337 547.651 111.659C549.956 109.981 551.783 107.664 553.134 104.708L569.703 111.899C567.319 117.891 563.505 122.566 558.26 125.921C553.095 129.197 546.936 130.835 539.784 130.835C532.632 130.835 526.355 129.357 520.951 126.401C515.547 123.365 511.376 119.09 508.435 113.577C505.575 107.984 504.144 101.472 504.144 94.0412C504.144 86.2909 505.575 79.5793 508.435 73.9064C511.296 68.1536 515.309 63.7191 520.474 60.603C525.719 57.4869 531.838 55.9288 538.831 55.9288C545.347 55.9288 551.068 57.2472 555.995 59.8839C560.922 62.5206 564.736 66.2759 567.438 71.1498C570.14 76.0237 571.491 81.7366 571.491 88.2884ZM550.989 84.573C550.989 80.9775 549.876 78.0612 547.651 75.824C545.506 73.5868 542.565 72.4682 538.831 72.4682C531.997 72.4682 527.626 76.5031 525.719 84.573H550.989Z" fill="black"/>
|
||||
<path d="M658.286 130.835C649.545 130.835 641.916 128.678 635.4 124.363C628.884 119.969 623.997 114.176 620.739 106.985L638.857 98.4757C640.526 102.79 643.069 106.066 646.486 108.303C649.903 110.541 653.876 111.659 658.405 111.659C663.094 111.659 666.829 110.78 669.61 109.022C672.391 107.265 673.782 104.988 673.782 102.191C673.782 100.194 673.265 98.5556 672.232 97.2772C671.199 95.9189 669.411 94.6804 666.868 93.5618C664.325 92.4432 660.63 91.2047 655.783 89.8464C649.426 88.0087 644.141 86.171 639.93 84.3333C635.718 82.4956 631.983 79.6592 628.725 75.824C625.546 71.9089 623.957 66.7553 623.957 60.3633C623.957 54.7703 625.507 49.7366 628.606 45.2622C631.784 40.7878 636.076 37.3121 641.479 34.8352C646.962 32.2784 653.002 31 659.597 31C676.205 31 688.165 38.5905 695.476 53.7715L677.596 62.5206C675.53 58.206 672.987 55.0899 669.967 53.1723C667.027 51.1748 663.491 50.176 659.359 50.176C655.386 50.176 652.048 51.0549 649.346 52.8127C646.644 54.5705 645.294 56.7278 645.294 59.2846C645.294 61.4419 645.85 63.1997 646.962 64.5581C648.075 65.8365 649.863 67.035 652.326 68.1536C654.869 69.1923 658.604 70.3908 663.531 71.7491C669.808 73.5069 675.053 75.3046 679.265 77.1423C683.476 78.9001 687.172 81.7366 690.35 85.6517C693.529 89.4869 695.118 94.6005 695.118 100.993C695.118 106.985 693.529 112.258 690.35 116.813C687.251 121.287 682.92 124.723 677.358 127.12C671.795 129.597 665.438 130.835 658.286 130.835Z" fill="black"/>
|
||||
<path d="M706.521 129.157V32.6779H726.665V66.9551H727.261C729.327 63.5194 732.029 60.8427 735.367 58.9251C738.784 56.9276 742.678 55.9288 747.048 55.9288C754.12 55.9288 759.604 58.2459 763.497 62.8801C767.471 67.4345 769.457 73.9463 769.457 82.4157V129.157H749.313V87.0899C749.313 83.4145 748.399 80.5381 746.571 78.4607C744.744 76.3034 742.24 75.2247 739.062 75.2247C735.327 75.2247 732.307 76.4232 730.003 78.8202C727.778 81.1373 726.665 84.3333 726.665 88.4082V129.157H706.521Z" fill="black"/>
|
||||
<path d="M847.018 88.2884C847.018 93.3221 846.502 97.6367 845.469 101.232H800.889C802.399 109.861 807.246 114.176 815.431 114.176C818.292 114.176 820.874 113.337 823.179 111.659C825.483 109.981 827.311 107.664 828.662 104.708L845.231 111.899C842.847 117.891 839.032 122.566 833.788 125.921C828.622 129.197 822.464 130.835 815.312 130.835C808.16 130.835 801.882 129.357 796.479 126.401C791.075 123.365 786.903 119.09 783.963 113.577C781.102 107.984 779.672 101.472 779.672 94.0412C779.672 86.2909 781.102 79.5793 783.963 73.9064C786.824 68.1536 790.837 63.7191 796.002 60.603C801.247 57.4869 807.365 55.9288 814.358 55.9288C820.874 55.9288 826.596 57.2472 831.523 59.8839C836.45 62.5206 840.264 66.2759 842.966 71.1498C845.668 76.0237 847.018 81.7366 847.018 88.2884ZM826.516 84.573C826.516 80.9775 825.404 78.0612 823.179 75.824C821.033 73.5868 818.093 72.4682 814.358 72.4682C807.524 72.4682 803.154 76.5031 801.247 84.573H826.516Z" fill="black"/>
|
||||
<path d="M857.962 129.157V57.6067H878.107V66.9551H878.703C880.769 63.5194 883.471 60.8427 886.808 58.9251C890.225 56.9276 894.119 55.9288 898.489 55.9288C905.562 55.9288 911.045 58.2459 914.939 62.8801C918.912 67.4345 920.899 73.9463 920.899 82.4157V129.157H900.754V87.0899C900.754 83.4145 899.84 80.5381 898.013 78.4607C896.185 76.3034 893.682 75.2247 890.503 75.2247C886.768 75.2247 883.749 76.4232 881.444 78.8202C879.219 81.1373 878.107 84.3333 878.107 88.4082V129.157H857.962Z" fill="black"/>
|
||||
<path d="M944.073 91.4045L909.625 32.6779H934.776L954.205 73.1873H954.92L974.349 32.6779H999.619L965.052 91.4045V129.157H944.073V91.4045Z" fill="black"/>
|
||||
<path d="M1053 57.6067V129.157H1032.86V120.049H1032.14C1030.15 123.484 1027.49 126.121 1024.15 127.959C1020.9 129.876 1017.08 130.835 1012.71 130.835C1005.64 130.835 1000.08 128.558 996.024 124.004C992.05 119.37 990.064 112.938 990.064 104.708V57.6067H1010.21V100.034C1010.21 103.629 1011.16 106.506 1013.07 108.663C1014.98 110.82 1017.48 111.899 1020.58 111.899C1024.31 111.899 1027.29 110.74 1029.52 108.423C1031.74 106.026 1032.86 102.83 1032.86 98.8352V57.6067H1053Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 440 KiB |
|
After Width: | Height: | Size: 521 KiB |
|
After Width: | Height: | Size: 502 KiB |
|
After Width: | Height: | Size: 441 KiB |
|
After Width: | Height: | Size: 435 KiB |
|
After Width: | Height: | Size: 449 KiB |
|
After Width: | Height: | Size: 394 KiB |
|
After Width: | Height: | Size: 327 KiB |
|
After Width: | Height: | Size: 427 KiB |
|
After Width: | Height: | Size: 183 KiB |
|
After Width: | Height: | Size: 312 KiB |
|
After Width: | Height: | Size: 354 KiB |
|
After Width: | Height: | Size: 495 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 328 KiB |
|
After Width: | Height: | Size: 315 KiB |
|
After Width: | Height: | Size: 311 KiB |
|
After Width: | Height: | Size: 104 KiB |
@@ -1,24 +1,25 @@
|
||||
// .home-wrapper .banner .banner-conent .hero h1{
|
||||
// font-size 2.8rem!important
|
||||
// }
|
||||
// // 文档中适配
|
||||
// table
|
||||
// width auto
|
||||
// .page >*:not(.footer),.card-box
|
||||
// box-shadow: none!important
|
||||
.home-wrapper .banner .banner-conent .hero h1{
|
||||
font-size 2.8rem!important
|
||||
}
|
||||
|
||||
// .page
|
||||
// @media (min-width $contentWidth + 80)
|
||||
// padding-top $navbarHeight!important
|
||||
// .home-wrapper .banner .banner-conent
|
||||
// padding 0 2.9rem
|
||||
// box-sizing border-box
|
||||
// .home-wrapper .banner .slide-banner .slide-banner-wrapper .slide-item a
|
||||
// h2
|
||||
// margin-top 2rem
|
||||
// font-size 1.2rem!important
|
||||
// p
|
||||
// padding 0 1rem
|
||||
// 文档中适配
|
||||
table
|
||||
width auto
|
||||
.page >*:not(.footer),.card-box
|
||||
box-shadow: none!important
|
||||
|
||||
.page
|
||||
@media (min-width $contentWidth + 80)
|
||||
padding-top $navbarHeight!important
|
||||
.home-wrapper .banner .banner-conent
|
||||
padding 0 2.9rem
|
||||
box-sizing border-box
|
||||
.home-wrapper .banner .slide-banner .slide-banner-wrapper .slide-item a
|
||||
h2
|
||||
margin-top 2rem
|
||||
font-size 1.2rem!important
|
||||
p
|
||||
padding 0 1rem
|
||||
|
||||
// 评论区颜色重置
|
||||
.gt-container
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
// .home-wrapper .banner .banner-conent .hero h1{
|
||||
// font-size 2.8rem!important
|
||||
// }
|
||||
// // 文档中适配
|
||||
// table
|
||||
// width auto
|
||||
// .page >*:not(.footer),.card-box
|
||||
// box-shadow: none!important
|
||||
|
||||
// .page
|
||||
// @media (min-width $contentWidth + 80)
|
||||
// padding-top $navbarHeight!important
|
||||
// .home-wrapper .banner .banner-conent
|
||||
// padding 0 2.9rem
|
||||
// box-sizing border-box
|
||||
// .home-wrapper .banner .slide-banner .slide-banner-wrapper .slide-item a
|
||||
// h2
|
||||
// margin-top 2rem
|
||||
// font-size 1.2rem!important
|
||||
// p
|
||||
// padding 0 1rem
|
||||
|
||||
// 评论区颜色重置
|
||||
.gt-container
|
||||
.gt-ico-tip
|
||||
&::after
|
||||
content: '。( Win + . ) or ( ⌃ + ⌘ + ␣ ) open Emoji'
|
||||
color: #999
|
||||
.gt-meta
|
||||
border-color var(--borderColor)!important
|
||||
.gt-comments-null
|
||||
color var(--textColor)
|
||||
opacity .5
|
||||
.gt-header-textarea
|
||||
color var(--textColor)
|
||||
background rgba(180,180,180,0.1)!important
|
||||
.gt-btn
|
||||
border-color $accentColor!important
|
||||
background-color $accentColor!important
|
||||
.gt-btn-preview
|
||||
background-color rgba(255,255,255,0)!important
|
||||
color $accentColor!important
|
||||
a
|
||||
color $accentColor!important
|
||||
.gt-svg svg
|
||||
fill $accentColor!important
|
||||
.gt-comment-content,.gt-comment-admin .gt-comment-content
|
||||
background-color rgba(150,150,150,0.1)!important
|
||||
&:hover
|
||||
box-shadow 0 0 25px rgba(150,150,150,.5)!important
|
||||
.gt-comment-body
|
||||
color var(--textColor)!important
|
||||
|
||||
|
||||
// qq徽章
|
||||
.qq
|
||||
position: relative;
|
||||
.qq::after
|
||||
content: "可撩";
|
||||
background: $accentColor;
|
||||
color:#fff;
|
||||
padding: 0 5px;
|
||||
border-radius: 10px;
|
||||
font-size:12px;
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: -35px;
|
||||
transform:scale(0.85);
|
||||
|
||||
// demo模块图标颜色
|
||||
body .vuepress-plugin-demo-block__wrapper
|
||||
&,.vuepress-plugin-demo-block__display
|
||||
border-color rgba(160,160,160,.3)
|
||||
.vuepress-plugin-demo-block__footer:hover
|
||||
.vuepress-plugin-demo-block__expand::before
|
||||
border-top-color: $accentColor !important;
|
||||
border-bottom-color: $accentColor !important;
|
||||
svg
|
||||
fill: $accentColor !important;
|
||||
|
||||
|
||||
// 全文搜索框
|
||||
.suggestions
|
||||
overflow: auto
|
||||
max-height: calc(100vh - 6rem)
|
||||
@media (max-width: 719px) {
|
||||
width: 90vw;
|
||||
min-width: 90vw!important;
|
||||
margin-right: -20px;
|
||||
}
|
||||
.highlight
|
||||
color: $accentColor
|
||||
font-weight: bold
|
||||
@@ -4,9 +4,9 @@
|
||||
//***vdoing主题-变量***//
|
||||
|
||||
// // 颜色
|
||||
|
||||
$nprogressColor = #4274F4
|
||||
// $bannerTextColor = #fff // 首页banner区(博客标题)文本颜色
|
||||
// $accentColor = #11A8CD
|
||||
$accentColor = #4274F4
|
||||
// $arrowBgColor = #ccc
|
||||
// $badgeTipColor = #42b983
|
||||
// $badgeWarningColor = darken(#ffe564, 35%)
|
||||
@@ -23,40 +23,40 @@
|
||||
// $lineNumbersWrapperWidth = 2.5rem
|
||||
|
||||
// 浅色模式
|
||||
// .theme-mode-light
|
||||
// --bodyBg: rgba(255,255,255,1)
|
||||
// --mainBg: rgba(255,255,255,1)
|
||||
// --sidebarBg: rgba(255,255,255,.8)
|
||||
// --blurBg: rgba(255,255,255,.9)
|
||||
// // --textColor: #004050
|
||||
// --textLightenColor: #0085AD
|
||||
// --borderColor: rgba(0,0,0,.15)
|
||||
// --codeBg: #f6f6f6
|
||||
// --codeColor: #525252
|
||||
// codeThemeLight()
|
||||
.theme-mode-light
|
||||
--bodyBg: #f4f4f4
|
||||
--mainBg: #fff
|
||||
--sidebarBg: #fff
|
||||
--blurBg: #fff
|
||||
// --textColor: #004050
|
||||
--textLightenColor: #4274F4
|
||||
--borderColor: rgba(0,0,0,.15)
|
||||
--codeBg: #282C34
|
||||
--codeColor: #fff
|
||||
codeThemeDark()
|
||||
|
||||
// // 深色模式
|
||||
// .theme-mode-dark
|
||||
// --bodyBg: rgba(30,30,34,1)
|
||||
// --mainBg: rgba(30,30,34,1)
|
||||
// --sidebarBg: rgba(30,30,34,.8)
|
||||
// --blurBg: rgba(30,30,34,.8)
|
||||
// --textColor: rgb(140,140,150)
|
||||
// --textLightenColor: #0085AD
|
||||
// --borderColor: #2C2C3A
|
||||
// --codeBg: #252526
|
||||
// --codeColor: #fff
|
||||
// codeThemeDark()
|
||||
// 深色模式
|
||||
.theme-mode-dark
|
||||
--bodyBg: #22272E
|
||||
--mainBg: #22272E
|
||||
--sidebarBg: #22272E
|
||||
--blurBg: #22272E
|
||||
--textColor: #ADBAC7
|
||||
--textLightenColor: #4274F4
|
||||
--borderColor: #2C2C3A
|
||||
--codeBg: #282C34
|
||||
--codeColor: #fff
|
||||
codeThemeDark()
|
||||
|
||||
// // 阅读模式
|
||||
// .theme-mode-read
|
||||
// --bodyBg: rgba(245,245,213,1)
|
||||
// --mainBg: rgba(245,245,213,1)
|
||||
// --sidebarBg: rgba(245,245,213,.8)
|
||||
// --blurBg: rgba(245,245,213,.9)
|
||||
// --textColor: #004050
|
||||
// --textLightenColor: #0085AD
|
||||
// --borderColor: rgba(0,0,0,.15)
|
||||
// --codeBg: #282c34
|
||||
// --codeColor: #fff
|
||||
// codeThemeDark()
|
||||
// 阅读模式
|
||||
.theme-mode-read
|
||||
--bodyBg: rgba(245,245,213,1)
|
||||
--mainBg: rgba(245,245,213,1)
|
||||
--sidebarBg: rgba(245,245,213,.8)
|
||||
--blurBg: rgba(245,245,213,.9)
|
||||
--textColor: #004050
|
||||
--textLightenColor: #4274F4
|
||||
--borderColor: rgba(0,0,0,.15)
|
||||
--codeBg: #282C34
|
||||
--codeColor: #fff
|
||||
codeThemeDark()
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
|
||||
// 原主题变量已弃用,以下是vdoing使用的变量,你可以在这个文件内修改它们。
|
||||
|
||||
//***vdoing主题-变量***//
|
||||
|
||||
// // 颜色
|
||||
|
||||
// $bannerTextColor = #fff // 首页banner区(博客标题)文本颜色
|
||||
// $accentColor = #11A8CD
|
||||
// $arrowBgColor = #ccc
|
||||
// $badgeTipColor = #42b983
|
||||
// $badgeWarningColor = darken(#ffe564, 35%)
|
||||
// $badgeErrorColor = #DA5961
|
||||
|
||||
// // 布局
|
||||
// $navbarHeight = 3.6rem
|
||||
// $sidebarWidth = 18rem
|
||||
// $contentWidth = 860px
|
||||
// $homePageWidth = 1100px
|
||||
// $rightMenuWidth = 230px // 右侧菜单
|
||||
|
||||
// // 代码块
|
||||
// $lineNumbersWrapperWidth = 2.5rem
|
||||
|
||||
// 浅色模式
|
||||
// .theme-mode-light
|
||||
// --bodyBg: rgba(255,255,255,1)
|
||||
// --mainBg: rgba(255,255,255,1)
|
||||
// --sidebarBg: rgba(255,255,255,.8)
|
||||
// --blurBg: rgba(255,255,255,.9)
|
||||
// // --textColor: #004050
|
||||
// --textLightenColor: #0085AD
|
||||
// --borderColor: rgba(0,0,0,.15)
|
||||
// --codeBg: #f6f6f6
|
||||
// --codeColor: #525252
|
||||
// codeThemeLight()
|
||||
|
||||
// // 深色模式
|
||||
// .theme-mode-dark
|
||||
// --bodyBg: rgba(30,30,34,1)
|
||||
// --mainBg: rgba(30,30,34,1)
|
||||
// --sidebarBg: rgba(30,30,34,.8)
|
||||
// --blurBg: rgba(30,30,34,.8)
|
||||
// --textColor: rgb(140,140,150)
|
||||
// --textLightenColor: #0085AD
|
||||
// --borderColor: #2C2C3A
|
||||
// --codeBg: #252526
|
||||
// --codeColor: #fff
|
||||
// codeThemeDark()
|
||||
|
||||
// // 阅读模式
|
||||
// .theme-mode-read
|
||||
// --bodyBg: rgba(245,245,213,1)
|
||||
// --mainBg: rgba(245,245,213,1)
|
||||
// --sidebarBg: rgba(245,245,213,.8)
|
||||
// --blurBg: rgba(245,245,213,.9)
|
||||
// --textColor: #004050
|
||||
// --textLightenColor: #0085AD
|
||||
// --borderColor: rgba(0,0,0,.15)
|
||||
// --codeBg: #282c34
|
||||
// --codeColor: #fff
|
||||
// codeThemeDark()
|
||||
@@ -0,0 +1,123 @@
|
||||
---
|
||||
title: 快速上手
|
||||
date: 2023-03-28 20:30:00
|
||||
permalink: /pages/793dcb
|
||||
article: false
|
||||
---
|
||||
|
||||
## 1、 部署服务端
|
||||
### 1.1、 Docker一键部署
|
||||
> 当前最新版本为1.9.0,下面的脚本中,可以使用:`registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:1.9.0` 指定版本安装,推荐使用`latest`直接安装最新版。
|
||||
|
||||
#### 使用默认sqlite数据库
|
||||
```shell
|
||||
docker run -it -p 9000-9200:9000-9200/tcp -p 8888:8888 \
|
||||
-d --restart=always --name neutrino-proxy \
|
||||
registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:latest
|
||||
```
|
||||
|
||||
#### 指定自己的mysql数据库
|
||||
- 在服务器上创建目录:/root/neutrino-proxy/config
|
||||
- 在该目录下创建`app.yml`文本文件,并配置如下内容:
|
||||
```yml
|
||||
neutrino:
|
||||
data:
|
||||
db:
|
||||
type: mysql
|
||||
# 自己的数据库实例,创建一个空的名为'neutrino-proxy'的数据库即可,首次启动服务端会自动初始化
|
||||
url: jdbc:mysql://xxxx:3306/neutrino-proxy?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useAffectedRows=true&useSSL=false
|
||||
driver-class: com.mysql.jdbc.Driver
|
||||
# 数据库帐号
|
||||
username: xxx
|
||||
# 数据库密码
|
||||
password: xxx
|
||||
```
|
||||
- 然后执行如下命令:
|
||||
```shell
|
||||
docker run -it -p 9000-9200:9000-9200/tcp -p 8888:8888 \
|
||||
-v /root/neutrino-proxy/config:/root/neutrino-proxy/config \
|
||||
-d --restart=always --name neutrino \
|
||||
registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy:latest
|
||||
```
|
||||
|
||||
### 1.2、使用jar包自行部署
|
||||
- 首先确保服务器上已安装java8运行环境
|
||||
- 打开[发行版页面](https://gitee.com/dromara/neutrino-proxy/releases),下载最新的release包:`neutrino-proxy-server.jar`、`neutrino-proxy-admin.zip`
|
||||
- 在服务器上新建部署目录:`/work/projects/neutrino-proxy-server`
|
||||
- 将` neutrino-proxy-server.jar`、`neutrino-proxy-admin.zip`上传至服务器部署目录。
|
||||
- 解压`neutrino-proxy-admin.zip`文件
|
||||
- 执行命令`java -Dfile.encoding=utf-8 -jar neutrino-proxy-server.jar`启动服务端完成部署,默认使用sqlite数据库。
|
||||
- 若需要指定自己的mysql数据库,同样的需要在当前目录下新建`app.yml`文件,文件内容同上。执行命令`java -Dfile.encoding=utf-8 -jar neutrino-proxy-server.jar config=app.yml`启动服务端完成部署
|
||||
- 可参照 https://gitee.com/dromara/neutrino-proxy/blob/master/scripts/unix/server_start.sh 使用shell脚本启动服务端。
|
||||
|
||||
## 2、管理后台配置
|
||||
- 服务端部署成功后,访问`http://{服务端IP}:8888`打开后台管理页面。
|
||||
- 使用默认的管理员帐号登录:admin/123456
|
||||
- 打开`代理配置>License管理`页面,可以看到系统已经自动为管理员初始化了一条License记录,复制该`LicenseKey`备用,后续客户端配置需要。
|
||||
- 打开`代理配置>端口映射`页面,可以看到系统已经自动为初始化了几条端口映射。可根据需要自行添加、修改。这里我们以`9101 -> 127.0.0.1:8080`映射为例
|
||||
|
||||
## 3、启动客户端
|
||||
- 首先确保本地已安装java8运行环境
|
||||
- 打开[发行版页面](https://gitee.com/dromara/neutrino-proxy/releases),下载最新的release包:`neutrino-proxy-client.jar`
|
||||
- 在本地`neutrino-proxy-client.jar`同级别目录下新建`app.yml`文件,并配置如下内容:
|
||||
```yml
|
||||
neutrino:
|
||||
proxy:
|
||||
tunnel:
|
||||
# ssl证书密钥(使用jjar包内自带的证书,则此处无需修改)
|
||||
key-store-password: 123456
|
||||
# ssl证书管理密钥(使用jjar包内自带的证书,则此处无需修改。自定义证书,则此处配置对应的路径)
|
||||
jks-path: classpath:/test.jks
|
||||
# 代理服务端IP
|
||||
server-ip: localhost
|
||||
# 代理服务端IP, 若是非ssl端口,则ssl-enable需要配置为false
|
||||
server-port: 9002
|
||||
# 是否启用ssl
|
||||
ssl-enable: true
|
||||
# licenseKey,客户端凭证。此处需要配置刚刚从管理后台复制的LicenseKey
|
||||
license-key: xxxx
|
||||
```
|
||||
- 执行命令`java -jar neutrino-proxy-client.jar config=app.yml`启动客户端
|
||||
- 查看服务端License管理,刷新页面,对应的License在线状态为`在线`,则表明客户端已正常连接。
|
||||
|
||||
## 4、代理验证
|
||||
- 本地启动被代理服务,如:redis、本地web项目、本地mysql等等
|
||||
- 先确保本地能正常访问被代理服务,如果本地都不能访问,不用想代理更不可能!!!
|
||||
- 通过服务端IP+9101(上面License配置的端口映射重的服务端端口)访问本地被代理服务
|
||||
|
||||
访问成功,至此首次完整的内网穿透体验完成。开源不易,请速至[Gitee仓库](https://gitee.com/dromara/neutrino-proxy)一键三连🤝
|
||||
|
||||
## 5、客户端Docker部署
|
||||
> 因为一些原因,官方不推荐客户端使用Docker方式部署,但有不少朋友希望采用这种方式,因此从1.9.0版本开始维护了客户端镜像,版本号同服务端,也可使用`latest`直接安装最新版。
|
||||
- 客户端镜像地址:registry.cn-hangzhou.aliyuncs.com/asgc/neutrino-proxy-client:latest
|
||||
- 注意:客户端采用docker部署时,服务端端口映射客户端ip请不要配置127.0.0.1应该配置为容器访问宿主机的ip或者hostname,具体请自行百度
|
||||
|
||||
<!--
|
||||
## 4.开发&调试
|
||||
|
||||
::: tip
|
||||
1. 不建议在原默认vuepress项目上单独安装使用本主题包,而是clone我的整个项目再替换你自己的内容即可。
|
||||
2. 修改`config.js`配置后需要重新启动项目才会生效。
|
||||
3. 更多关于项目上手的问题,请查阅 [问答](/pages/9cc27d/)。
|
||||
:::
|
||||
|
||||
|
||||
## 版本升级
|
||||
|
||||
主题的版本会不定期更新,你只需更新npm主题包即可:
|
||||
```sh
|
||||
npm update vuepress-theme-vdoing
|
||||
```
|
||||
|
||||
::: tip
|
||||
1. 如更新后没起作用或报错,尝试把`node_modules`文件夹删除再`npm i`重新安装。
|
||||
2. 在.vuepress/config.js中,设置`theme: 'vdoing'`才是使用npm主题依赖包:
|
||||
```js
|
||||
// config.js
|
||||
module.exports = {
|
||||
theme: 'vdoing', // npm主题依赖包
|
||||
// theme: require.resolve('../../vdoing'), // 使用本地主题包
|
||||
}
|
||||
```
|
||||
:::
|
||||
-->
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
title: 后台操作指南
|
||||
date: 2023-03-28 20:30:00
|
||||
permalink: /pages/793dcc
|
||||
article: false
|
||||
---
|
||||
|
||||
# 首页
|
||||
- License统计:统计License相关数量指标
|
||||
- 端口映射统计:统计端口映射相关数量指标
|
||||
- 今日流量:统计当天的服务端上行/下行流量数据
|
||||
- 流量汇总:统计所有(包含当天)的服务端上行/下行流量数据
|
||||
- 流量监控:按天统计最近15天(可能会动态调整)每天的上行流量、下行流量、汇总流量
|
||||
|
||||
<img :src="$withBase('/img/run-example/home.png')"></img>
|
||||
|
||||
# 代理配置
|
||||
- License管理:License是客户端连接服务端的唯一合法凭证。一个License同时只能被一个客户端使用,一个License可以维护多条端口映射
|
||||
- 端口映射:服务端IP+端口 -> 客户端IP+端口的四元组映射(因目前服务端单节点只有一个公网IP,所以不体现出来),是内网穿透的基本单元。
|
||||
|
||||
<img :src="$withBase('/img/run-example/license1.png')"></img>
|
||||
|
||||
# 系统管理
|
||||
- 用户管理:支持多用户,一个用户可持有多个License。由于项目目前主推个人版,所以暂是没有权限这一套,管理员之外的所有用户都属于游客。对于绝大多数操作,游客仅有只读权限。
|
||||
- 端口池管理:用于统一管理服务器内网穿透端口,方便统一设置安全组。
|
||||
- 端口池分组:对端口池的一个分组。
|
||||
- 全局分组:该分组下的端口全局通用。
|
||||
- 用户分组:该分组下的端口由分组绑定的用户独占。
|
||||
- License分组:该分组下的端口由分组绑定的License独占。
|
||||
- 调度管理:维护服务端定时任务。方便开发、调试。正常使用时无需关心。
|
||||
|
||||
<img :src="$withBase('/img/run-example/user-manager1.png')"></img>
|
||||
|
||||
# 报表管理
|
||||
- 用户流量报表:基于用户维度的流量统计
|
||||
- License流量报表:基于License的流量统计
|
||||
- 用户流量月度明细:基于用户的流量月度统计
|
||||
- License流量月度明细:基于License的流量月度统计
|
||||
|
||||
<img :src="$withBase('/img/run-example/user-flow1.png')"></img>
|
||||
|
||||
# 日志管理
|
||||
- 调度日志:服务端定时任务执行日志
|
||||
- 登录日志:管理后端登录、退出登录的日志
|
||||
- 客户端连接日志:客户端连接、断开的日志
|
||||
|
||||
<img :src="$withBase('/img/run-example/login-log1.png')"></img>
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
title: 关于端口
|
||||
date: 2023-04-05 18:20:18
|
||||
permalink: /pages/68badb/
|
||||
---
|
||||
|
||||
# 端口池
|
||||
> 端口池用于统一、集中管理服务端代理对外开放的代理端口。
|
||||
|
||||
- 1、这里不包含服务端启动本身所需要的一些特殊端口,如:WEB端口、对客户端开放的端口(9000、9002)、HTTP代理端口。
|
||||
- 2、端口映射里所有的服务端端口,都包含在端口池内。方便了统一管理、安全组/防火墙设置。
|
||||
|
||||
# 端口池分组
|
||||
> 顾名思义,它是对端口池下的端口做了一个划分。
|
||||
|
||||
- 1、允许同一个分组内的端口拥有特定的归属。比如:全局分组下的端口,进行代理时所有用户共享,这里极端情况下涉及到端口抢占问题。而用户分组下的端口,归属于某一个特定的用户。
|
||||
License分组下的端口,归属于某一个特定的License。
|
||||
- 2、该划分,使得特定的用户、License能够独占一部分端口。为后续更多场景下代理,避免端口被占用提供了支持。
|
||||
|
||||
# 端口映射
|
||||
> 当前版本代理功能的基本单元。
|
||||
|
||||
- 1、该映射由一个四元组唯一标识(代理服务端IP、代理服务端端口、被代理服务局域网IP、被代理服务端口)。
|
||||
- 2、由于本项目为个人版,代理服务端仅支持一个节点,所以四元组中的服务端IP可以省略,不在端口映射管理中体现。
|
||||
- 3、代理成功的情况下,通过访问代理服务端IP+端口,可以达到访问内网被代理服务IP+端口的同等效果
|
||||
- 4、例如:本地启动一个socket服务,需要和异地的同事联调。可以通过中微子代理jetbrains客户端插件(后续会推出)将本地socket端口代理至公网。同事通过访问代理服务端公网IP+端口进行调试。
|
||||
|
||||
# 基础端口说明
|
||||
- WEB端口:服务端API、后台管理访问端口,默认为:8888
|
||||
- 服务端等待客户端连接的端口,非SSL:默认9000,SSL端口:默认9002(若不需要SSL支持,可不配置SSL端口)
|
||||
- HTTP代理端口:默认80,用于域名映射,若无需域名映射,可以忽略。
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
title: 域名映射
|
||||
date: 2023-04-05 18:46:12
|
||||
permalink: /pages/269a2e/
|
||||
---
|
||||
|
||||
# 什么是域名映射?
|
||||
域名映射是将指定端口映射绑定到某一个子域名上,通过直接访问域名的方式,达到访问被代理服务的目的。
|
||||
|
||||
# 域名映射能解决什么问题?
|
||||
- 1、当服务端端口需要经常变动的时候,可以通过固定域名的方式,使得开发调试能够长期稳定的进行,不用每次更改访问地址。
|
||||
- 2、使用域名可以使用有意义的名称方便记忆
|
||||
- 3、调试某些第三方回调时,回调地址不能填写IP+端口的形式,此时需要域名来完成
|
||||
|
||||
# 域名映射的大致流程?
|
||||
> 例如你有一个已备案的域名:asgc.fun,以下是使用域名映射的大致流程
|
||||
|
||||
## 情况1:代理服务端使用80端口
|
||||
- 1、DNS解析配置泛域名解析 proxy.asgc.fun 指向代理服务端IP
|
||||
- 2、代理服务端app.yml配置域名:proxy.asgc.fun,部署
|
||||
- 3、服务端管理后台新建端口映射,协议选择:HTTP,设置好子域名,如:test1
|
||||
- 4、配置好客户端并启动,代理映射完成
|
||||
- 5、通过访问: http://test1.proxy.asgc.fun 实现访问被代理服务
|
||||
|
||||
## 情况2:代理服务端不使用80端口,80端口被nginx占用
|
||||
> 假设代理服务端使用82作为HTTP代理端口
|
||||
|
||||
- 1、DNS解析配置泛域名解析 proxy.asgc.fun 指向代理服务端IP
|
||||
- 2、nginx 80端口下将 *.proxy.asgc.fun 请求转到82端口,并附带原有请求头(特别是`Host`)
|
||||
- 3、代理服务端app.yml配置域名:proxy.asgc.fun,部署
|
||||
- 4、服务端管理后台新建端口映射,协议选择:HTTP,设置好子域名,如:test1
|
||||
- 5、配置好客户端并启动,代理映射完成
|
||||
- 6、通过访问: http://test1.proxy.asgc.fun 实现访问被代理服务
|
||||
|
||||
# 所有的代理映射都能绑定域名吗?
|
||||
不行的。之所以能实现域名绑定,原因是HTTP请求附带了Host请求头,包含了请求的域名信息,代理服务端能根据请求的域名做分发。而TCP协议本身并不包含域名,因此无法实现。
|
||||
所以,目前只有HTTP(涵盖HTTPS)协议能支持。
|
||||
|
||||
# 代理服务端使用非80端口时,nginx配置示例
|
||||
```
|
||||
server {
|
||||
listen 80;
|
||||
# 此处配不配貌似不影响
|
||||
server_name localhost *.neutrino-proxy.asgc.fun;
|
||||
|
||||
location / {
|
||||
if ($http_host ~* "(.*?).neutrino-proxy.asgc.fun") {
|
||||
# 转发到代理服务端HTTP代理端口
|
||||
proxy_pass http://localhost:8899;
|
||||
}
|
||||
|
||||
tcp_nodelay on;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
#以上三行,目的是将代理服务器收到的用户的信息传到真实服务器上
|
||||
|
||||
root html;
|
||||
index index.html index.htm;
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: HTTPS配置
|
||||
date: 2023-05-27 11:40:12
|
||||
permalink: /pages/99a300/
|
||||
---
|
||||
|
||||
## 基础说明
|
||||
- 在中微子代理中,HTTP的定位是:为ip+端口的访问形式增加域名访问的支持。因此:
|
||||
- 如果端口映射正确配置了域名,那么该映射同时支持IP+端口、域名2种访问形式。
|
||||
- 如果端口映射未配置域名,那么只能以ip+端口的形式访问,此时等价于选择TCP协议。
|
||||
|
||||
- 在中微子代理中,HTTPS作为HTTP的一种增强行为,不作为一个单独的协议。因此:
|
||||
- 在端口映射中只需要选择HTTP即可
|
||||
- 若端口映射未正确配置域名,那么同理,此时等价于选择TCP协议。
|
||||
- 若端口映射配置了域名,但是未正确配置HTTPS端口、证书,那么此时可以通过域名访问,但不支持HTTPS
|
||||
- 若端口映射配置了域名,且正确配置了HTTPS端口、证书,那么此时可以通过IP+端口、http域名、https域名3种形式访问
|
||||
|
||||
|
||||
## HTTPS配置流程
|
||||
- 按照域名映射流程,完成域名的配置
|
||||
- 在服务端配置域名的HTTPS证书信息(此处应该是通配符证书)
|
||||
|
||||
## HTTPS端口问题
|
||||
- 与HTTP类似,HTTPS也有一个默认端口:443,默认情况下需要占用服务端443端口
|
||||
- 如果代理服务端不能独占443端口,此处可以指定任意端口,但需要在nginx中配置443端口的转发规则。此处与域名映射中代理服务端不使用80端口的情况类似
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
title: 服务端配置
|
||||
date: 2023-05-27 11:41:44
|
||||
permalink: /pages/f2d0f1/
|
||||
---
|
||||
|
||||
::: tip
|
||||
|
||||
1.9.0版本,服务端配置格式有所调整。从之前的版本升级的,需要注意
|
||||
|
||||
:::
|
||||
|
||||
|
||||
# 以下是最新的服务端配置格式(app.yml)
|
||||
```yml
|
||||
server:
|
||||
# 服务端web端口,用于支持HTTP接口,管理后台页面访问
|
||||
port: ${WEB_PORT:8888}
|
||||
|
||||
neutrino:
|
||||
proxy:
|
||||
logger:
|
||||
# 日志级别
|
||||
level: ${LOG_LEVEL:info}
|
||||
# 隧道相关配置-用于维持服务端与客户端的通信
|
||||
tunnel:
|
||||
# 线程池相关配置,用于技术调优,可忽略
|
||||
boss-thread-count: 2
|
||||
work-thread-count: 10
|
||||
# 隧道非SSL端口
|
||||
port: ${OPEN_PORT:9000}
|
||||
# 隧道SSL端口
|
||||
ssl-port: ${SSL_PORT:9002}
|
||||
# 隧道SSL证书配置
|
||||
key-store-password: ${STORE_PASS:123456}
|
||||
key-manager-password: ${MGR_PASS:123456}
|
||||
jks-path: ${JKS_PATH:classpath:/test.jks}
|
||||
# 是否开启隧道传输报文日志(日志级别为debug时开启才有效)
|
||||
transfer-log-enable: ${TUNNEL_LOG:false}
|
||||
server:
|
||||
tcp:
|
||||
# 线程池相关配置,用于技术调优,可忽略
|
||||
boss-thread-count: 5
|
||||
work-thread-count: 20
|
||||
# http代理端口,默认80
|
||||
http-proxy-port: ${HTTP_PROXY_PORT:80}
|
||||
# https代理端口,默认443 (需要配置域名、证书)
|
||||
https-proxy-port: ${HTTPS_PROXY_PORT:443}
|
||||
# 如果不配置,则不支持域名映射
|
||||
domain-name: ${DOMAIN_NAME:}
|
||||
# https证书配置
|
||||
key-store-password: ${HTTPS_STORE_PASS:}
|
||||
jks-path: ${HTTPS_JKS_PATH:}
|
||||
# 是否开启代理服务报文日志(日志级别为debug时开启才有效)
|
||||
transfer-log-enable: ${SERVER_LOG:false}
|
||||
udp:
|
||||
# 线程池相关配置,用于技术调优,可忽略
|
||||
boss-thread-count: 5
|
||||
work-thread-count: 20
|
||||
# 是否开启代理服务报文日志(日志级别为debug时开启才有效)
|
||||
transfer-log-enable: ${SERVER_LOG:false}
|
||||
data:
|
||||
db:
|
||||
# 数据库类型,目前支持sqlite、mysql、mariadb
|
||||
type: ${DB_TYPE:sqlite}
|
||||
# 数据库连接URL
|
||||
url: ${DB_URL:jdbc:sqlite:data.db}
|
||||
# 数据库用户名
|
||||
username: ${DB_USER:}
|
||||
# 数据库密码
|
||||
password: ${DB_PASSWORD:}
|
||||
```
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
title: 客户端配置
|
||||
date: 2023-06-09 21:15:35
|
||||
permalink: /pages/50ce10/
|
||||
---
|
||||
|
||||
::: tip
|
||||
|
||||
1.9.0版本,客户端配置格式有所调整。从之前的版本升级的,需要注意
|
||||
|
||||
:::
|
||||
|
||||
# 以下是最新的客户端配置格式(app.yml)
|
||||
```yml
|
||||
neutrino:
|
||||
proxy:
|
||||
logger:
|
||||
# 日志级别
|
||||
level: ${LOG_LEVEL:info}
|
||||
tunnel:
|
||||
# 线程池相关配置,用于技术调优,可忽略
|
||||
thread-count: 50
|
||||
# 隧道SSL证书配置
|
||||
key-store-password: ${STORE_PASS:123456}
|
||||
jks-path: ${JKS_PATH:classpath:/test.jks}
|
||||
# 服务端IP
|
||||
server-ip: ${SERVER_IP:localhost}
|
||||
# 服务端端口(对应服务端app.yml中的tunnel.port、tunnel.ssl-port)
|
||||
server-port: ${SERVER_PORT:9002}
|
||||
# 是否启用SSL(注意:该配置必须和server-port对应上)
|
||||
ssl-enable: ${SSL_ENABLE:true}
|
||||
# 客户端连接唯一凭证
|
||||
license-key: ${LICENSE_KEY:}
|
||||
# 客户端唯一身份标识(可忽略,若不设置首次启动会自动生成)
|
||||
client-id: ${CLIENT_ID:}
|
||||
# 是否开启隧道传输报文日志(日志级别为debug时开启才有效)
|
||||
transfer-log-enable: ${CLIENT_LOG:false}
|
||||
# 重连设置
|
||||
reconnection:
|
||||
# 重连间隔(秒)
|
||||
interval-seconds: 10
|
||||
# 是否开启无限重连(未开启时,客户端license不合法会自动停止应用,开启了则不会,请谨慎开启)
|
||||
unlimited: false
|
||||
client:
|
||||
udp:
|
||||
# 线程池相关配置,用于技术调优,可忽略
|
||||
boss-thread-count: 5
|
||||
work-thread-count: 20
|
||||
# udp傀儡端口范围
|
||||
puppet-port-range: 10000-10500
|
||||
# 是否开启隧道传输报文日志(日志级别为debug时开启才有效)
|
||||
transfer-log-enable: ${CLIENT_LOG:false}
|
||||
```
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
title: 常见问题汇总
|
||||
date: 2023-07-01 13:35:00
|
||||
permalink: /pages/a00001/
|
||||
---
|
||||
|
||||
## 1. 客户端启动后连不上服务端,一直在重连
|
||||
### 1.1. 启动日志包含`not found license-key config`相关字样
|
||||
出现这种情况,说明客户端没获取到配置的license,可按如下检查:
|
||||
- 客户端是否有配置app.yml,app.yml中是否有配置`license-key`
|
||||
- 客户端启动参数是否指定app.yml配置,若未指定客户端不会加载任何jar外部的配置文件
|
||||
- 客户端启动参数指定的配置文件路径是否正确?如:`java -jar neutrino-proxy-client.jar config=app.yml`,代表`app.yml`文件与jar在同级别目录下,如果不在同级别目录,请使用绝对/相对路径指定
|
||||
- 除此之外license还可以用以下方法直接在启动参数中指定:
|
||||
- `java -jar -DLICENSE_KEY=xxxx -DSERVER_IP=x.x.x.x -DSERVER_PORT=9000 -DSSL_ENABLE=false neutrino-proxy-client.jar`
|
||||
- `java -jar neutrino-proxy-client.jar serverIp=x.x.x.x serverPort=9000 sslEnable=false licenseKey=xxxx`
|
||||
|
||||
### 1.2. 配置文件确定都已经配置,且启动参数正确指定了,但是没有出现`认证成功日志`
|
||||
- 确认客户端配置的`server-ip`,必须是服务端所在机器的ip,且保证客户端所在机器能正常ping通
|
||||
- 确认客户端配置的`server-port`与`ssl-enable`,默认情况应配置为9000、false或9002、true,除非服务端app.yml有改动隧道对外端口
|
||||
- 确认服务端对外的9000、9002端口是否已经放开
|
||||
- 服务端如果采用docker部署,需要确保容器的9000、9002端口映射到宿主机
|
||||
|
||||
## 2. 关于中微子代理涉及的2类SSL证书
|
||||
### 2.1. HTTPS证书
|
||||
该证书用于服务端支持HTTPS,若需要在服务端`app.yml`中配置即可
|
||||
|
||||
### 2.2. 隧道SSL证书
|
||||
该证书用于对客户端-服务端之间的数据通信进行加密,因此客户端/服务端均需要配置该证书。可参考如下命令生成:
|
||||
```
|
||||
keytool -genkey -alias test1 -keyalg RSA -keysize 1024 -validity 3650 -keypass 123456 -storepass 123456 -keystore "./test.jks"
|
||||
```
|
||||
|
||||
## 3. 1.9.0 版本udp代理无效
|
||||
### 问题描述
|
||||
代理本地udp端口9231,公网访问代理端口无效,代理端口测试发送成功,但是本地服务没有收到,使用路由器的ddns功能也可以成功代理。
|
||||
### 解决方案
|
||||
+ 防火墙开放端口的时候一定要指定udp协议
|
||||
+ 还有如果想收到回复需要配置两个响应参数
|
||||
+ 响应数量根据实际响应数量配置,可以配置的大一些,但是不能比实际小
|
||||
+ 超时时间根据实际响应时间配置,比如正常响应时间是10s,这里就可以配置15000毫秒等
|
||||
<img :src="$withBase('/img/run-example/question_3_1.png')"></img>
|
||||
+ 相关链接:[1.9.0版本udp代理无效 · Issue #I83XOI · dromara/neutrino-proxy - Gitee.com](https://gitee.com/dromara/neutrino-proxy/issues/I83XOI)
|
||||
+ 贡献者:gitee:眉黛如画
|
||||
|
||||
## 4. Vue项目出现Invalid Host header 的解决方案
|
||||
### 问题描述
|
||||
内网穿透 Vue项目出现Invalid Host header 的解决方案
|
||||
### 解决方案
|
||||
manifest.json中配置
|
||||
```
|
||||
"h5":{
|
||||
"devServer":{
|
||||
"disableHostCheck":true
|
||||
}
|
||||
},
|
||||
```
|
||||
+ 相关链接:[内网穿透 Vue项目出现Invalid Host header 的解决方案](https://blog.csdn.net/weixin_41549915/article/details/96132925)
|
||||
+ 贡献者:wx:明天会更好
|
||||
@@ -1,39 +0,0 @@
|
||||
---
|
||||
title: 主题初衷与诞生
|
||||
date: 2020-05-11 13:59:38
|
||||
permalink: /pages/52d5c3
|
||||
article: false
|
||||
---
|
||||
|
||||
这个主题的初衷是打造一个好用的、面向程序员的`知识管理工具`。
|
||||
|
||||
对于程序员来说,繁杂的知识体系难免会有遗忘的地方。如果有一个方便好用的知识管理工具,可以帮助我们很好的管理知识,并能够快速地把遗忘的知识点找回来。
|
||||
|
||||
## Markdown
|
||||
最初接触[Markdown](https://xugaoyi.com/pages/ad247c4332211551/)的时候,我就被它简洁的语法干净的文本结构吸引住,它的代码块和兼容`html`标签的能力更是让我爱上它,很高兴找到了一个高效记录学习笔记的工具。
|
||||
|
||||
## 知识管理
|
||||
在一段学习的日子里,我尝试过用`txt`记录笔记、云笔记、`Markdown`笔记,并把`Markdown`文件上传到`github`进行管理,但总感觉还是不够方便...直到我发现了`VuePress`,它似乎可以管理我的学习笔记,并且把站点部署到`github pages`不就是一个在线的云笔记网站了吗
|
||||
|
||||
## VuePress
|
||||
[VuePress](https://vuepress.vuejs.org/zh/)是一个 Vue 驱动的静态网站生成器,正是以`Markdown`为中心的项目结构,它简洁至上的理念正合我心。对于我这个对Vue还算有一些了解的前端,迫不及待的想去使用它来搭建一个我的云笔记网站。
|
||||
|
||||
## 知识管理&博客主题-Vdoing的诞生
|
||||
我以前的一个领导和我们说过一个好的知识管理可以帮助我们提高开发质量和开发效率,下面这张图就是他想传达的,我表示赞同:
|
||||

|
||||
|
||||
什么是一个好用的知识管理工具呢?其实我也在不断的寻找和思考中。不过我觉得它至少要结构清晰、管理方便,在查找知识点的过程中可以快速的找到,正像上图所说的要在15秒内找到一个知识点,在添加内容的时候可以很方便的添加,并且保证结构清晰。
|
||||
|
||||
VuePress的官方默认主题是专门为写文档而生的,和我心目中的知识库有一些差距,比如在满足结构清晰这点上,每添加一个内容就得手动的添加侧边栏数据,还有在满足15秒内找到一个知识点上还是不够好,因此我觉得需要做一些改造。
|
||||
|
||||
在知识管理方面,有三种典型的知识形态:
|
||||
|
||||
> * 结构化:看重知识的条理性和实用性,有一定专业背景才能看懂
|
||||
> * 碎片化:描述具体的知识点,通俗易懂
|
||||
> * 体系化:对某一领域的完整解读,通常是某类知识的专业之作
|
||||
|
||||
从这三种知识形态出发,我给主题添加了自动生成结构化侧边栏、目录页、面包屑等等。在满足了结构化之后,写博客应该算得上是一种碎片化的形态,我就给主题添加了分类、标签、归档等等。在满足更方便添加内容上,有了自动生成front matter、批量操作front matter等,更多关于主题的知识从开始使用它去探索吧~~
|
||||
|
||||
### 主题名称
|
||||
|
||||
Vdoing,中文大概是维度的意思。起这个名的原因,首先是我看过一个视频《尤雨溪创立vue的心路历程》[B站传送](https://b23.tv/xI9ONW) [youtube传送](https://www.youtube.com/watch?v=OrxmtDw4pVI),里面有讲到vue起名的故事,一开始想起的名其实是Seed.js,但是在npm上被占用了,然后发现vue是一个挺酷的一个词,才决定用vue。发现大佬起名可以这么酷。再者,这个主题我想它可以多维度的快速寻找一个知识点,要么就叫维度吧,那英文名就用Vdoing好了...
|
||||
@@ -1,73 +0,0 @@
|
||||
---
|
||||
title: 介绍
|
||||
date: 2020-05-11 13:54:03
|
||||
permalink: /pages/a2f161
|
||||
article: false
|
||||
---
|
||||
|
||||
|
||||
Vdoing是[VuePress v1.x](https://vuepress.vuejs.org/zh/)的一个主题,是在[默认主题](https://vuepress.vuejs.org/zh/theme/option-api.html)基础上做的修改和扩展,很多配置仍然沿用[官方配置](https://vuepress.vuejs.org/zh/config/)。使用本主题可以很方便的搭建一个结构化的知识库或博客。
|
||||
|
||||
这个主题的初衷是打造一个好用的、面向程序员的知识管理工具:
|
||||
[**主题初衷与诞生**。](/pages/52d5c3/)
|
||||
|
||||
::: warning 注意
|
||||
1. Node请使用`v14.17.x`或以上版本
|
||||
2. 在使用本主题前,要求你至少会VuePress v1.x的基本使用和默认主题的基本配置,然后再查看本文档。
|
||||
3. 本文档仅负责介绍Vdoing主题对默认主题的扩展部分,更多配置请移步 [VuePress v1.x文档](https://vuepress.vuejs.org/zh/)
|
||||
:::
|
||||
|
||||
## 特性
|
||||
* **知识管理**
|
||||
|
||||
包含三种典型的知识管理形态:结构化、碎片化、体系化。轻松打造属于你自己的知识管理平台。
|
||||
|
||||
* **结构化**
|
||||
|
||||
自动生成侧边栏、目录页、索引页、面包屑等,轻松构建一个结构化知识库。
|
||||
|
||||
* **碎片化&个性化**
|
||||
|
||||
博客功能提供一种知识的碎片化形态,并提供个性化的博客配置。
|
||||
|
||||
* **简洁高效**
|
||||
|
||||
以 Markdown 为中心的项目结构,内置自动化工具,以更少的配置完成更多的事。配合多维索引快速定位每个知识点。
|
||||
|
||||
* **沉浸式阅读体验**
|
||||
|
||||
专为阅读设计的UI,配合多种颜色模式、可关闭的侧边栏和导航栏,带给你一种沉浸式阅读体验。
|
||||
|
||||
|
||||
|
||||
## 扩展功能
|
||||
相较于默认主题,添加的功能内容主要有:
|
||||
|
||||
* 添加方便管理学习笔记和技术文档的`自动生成结构化侧边栏`、`自动生成front matter`、`目录页`、`扩展的搜索框插件`、`面包屑`、`快捷翻页按钮` 等,让你快速定位到任何你想要找的内容。
|
||||
|
||||
* 添加博客相关的 `文章信息栏(作者与创建时间)`、`最近更新栏` 、`博主信息栏`、`页脚版权栏`、`分类功能+分类页`、`标签功能+标签页`、`归档页`、`评论插件`等。
|
||||
|
||||
* 方便好用的 `Markdown 容器`。
|
||||
|
||||
* 首页`文章列表`、`个性化配置`和`样式美化`等。
|
||||
|
||||
* 多种颜色模式供用户选择:`跟随系统`、`浅色模式`、`深色模式`、`阅读模式`
|
||||
|
||||
* 提高搬砖效率的辅助工具: `批量操作front matter工具`
|
||||
|
||||
* ...
|
||||
|
||||
## 安利
|
||||
* 拥有它你就同时拥有了一个专属你个人的在线知识库(云笔记)、博客、文档库、Demo库、一站式技术搜索工具,内容全部采用Markdown编写,简单高效,各种代码随便贴。
|
||||
* 你可以在`.md`文件中写html、css、js、甚至是vue组件代码,[markdown天然的就支持vue组件](https://v1.vuepress.vuejs.org/zh/guide/using-vue.html),魔改页面什么的不要太简单。
|
||||
* 相当多的程序员喜欢深色模式,还有的视力也不太好(🤓),我们有浅色、深色和阅读模式,更有跟随系统自动响应深浅色模式功能,想怎么换就怎么换。
|
||||
|
||||
> 点击右下角换肤按钮
|
||||
* 当你习惯用vdoing主题后,在别处看文档发现是markdown编写的,但所在站点的目录、导航、主题等某个地方用起来不是很爽,你都可以把文档拷贝或把整个专栏下载(如支持下载的话)下来放到vdoing主题,vdoing的自动化工具助你生成一个结构清晰的、拥有目录、页面导航的,而且有多种颜色模式的文档站。让你更专注于内容的学习。
|
||||
|
||||
> 参考我博客中的[文档专栏](https://xugaoyi.com/note/typescript-axios/)
|
||||
* 如果你想和更多的人分享你的文章,那么这款seo友好的主题是一个很不错的选择,更有为了加快百度收录而定制的每天定时[百度推送程序](https://xugaoyi.com/pages/f44d2f9ad04ab8d3/)。
|
||||
|
||||
> 参考我的博客[收录情况](https://www.baidu.com/s?word=site%3Axugaoyi.com)。
|
||||
|
||||
别犹豫了,赶快上手吧
|
||||
@@ -1,110 +0,0 @@
|
||||
---
|
||||
title: 快速上手
|
||||
date: 2020-05-11 13:54:40
|
||||
permalink: /pages/793dcb
|
||||
article: false
|
||||
---
|
||||
|
||||
## 安装和启动
|
||||
|
||||
<code-group>
|
||||
<code-block title="知识库兼博客风格预设配置" active>
|
||||
|
||||
```bash
|
||||
# clone the project
|
||||
git clone https://github.com/xugaoyi/vuepress-theme-vdoing.git
|
||||
|
||||
# enter the project directory
|
||||
cd vuepress-theme-vdoing
|
||||
|
||||
# install dependency 注意:如安装不成功请关闭淘宝源。
|
||||
npm install # or yarn install
|
||||
|
||||
# develop
|
||||
npm run dev # or yarn dev
|
||||
```
|
||||
</code-block>
|
||||
|
||||
<code-block title="文档风格预设配置">
|
||||
|
||||
```bash
|
||||
# clone the project
|
||||
git clone https://github.com/xugaoyi/vuepress-theme-vdoing-doc.git
|
||||
|
||||
# enter the project directory
|
||||
cd vuepress-theme-vdoing-doc
|
||||
|
||||
# install dependency 注意:如安装不成功请关闭淘宝源。
|
||||
npm install # or yarn install
|
||||
|
||||
# develop
|
||||
npm run dev # or yarn dev
|
||||
```
|
||||
> 仓库地址: <https://github.com/xugaoyi/vuepress-theme-vdoing-doc>
|
||||
|
||||
</code-block>
|
||||
|
||||
<code-block title="简洁模板预设配置(社区提供)">
|
||||
|
||||
```bash
|
||||
# clone the project
|
||||
git clone https://github.com/u2sb/vuepress-theme-vdoing-template.git
|
||||
|
||||
# enter the project directory
|
||||
cd vuepress-theme-vdoing-template
|
||||
|
||||
# install dependency 注意:如安装不成功请关闭淘宝源。
|
||||
npm install # or yarn install
|
||||
|
||||
# develop
|
||||
npm run dev # or yarn dev
|
||||
```
|
||||
> 仓库地址: <https://github.com/u2sb/vuepress-theme-vdoing-template>
|
||||
|
||||
</code-block>
|
||||
</code-group>
|
||||
|
||||
::: warning
|
||||
Node请使用`v14.17.x`或以上版本
|
||||
:::
|
||||
|
||||
## 使用Vdoing主题
|
||||
|
||||
1. 安装最新的Vdoing主题包:
|
||||
```sh
|
||||
npm install vuepress-theme-vdoing -D
|
||||
```
|
||||
|
||||
2. 在`.vuepress/config.js`中配置使用主题:
|
||||
```js
|
||||
// config.js
|
||||
module.exports = {
|
||||
theme: 'vdoing'
|
||||
}
|
||||
```
|
||||
|
||||
::: tip
|
||||
1. 不建议在原默认vuepress项目上单独安装使用本主题包,而是clone我的整个项目再替换你自己的内容即可。
|
||||
2. 修改`config.js`配置后需要重新启动项目才会生效。
|
||||
3. 更多关于项目上手的问题,请查阅 [问答](/pages/9cc27d/)。
|
||||
:::
|
||||
|
||||
|
||||
## 版本升级
|
||||
|
||||
主题的版本会不定期更新,你只需更新npm主题包即可:
|
||||
```sh
|
||||
npm update vuepress-theme-vdoing
|
||||
```
|
||||
|
||||
::: tip
|
||||
1. 如更新后没起作用或报错,尝试把`node_modules`文件夹删除再`npm i`重新安装。
|
||||
2. 在.vuepress/config.js中,设置`theme: 'vdoing'`才是使用npm主题依赖包:
|
||||
```js
|
||||
// config.js
|
||||
module.exports = {
|
||||
theme: 'vdoing', // npm主题依赖包
|
||||
// theme: require.resolve('../../vdoing'), // 使用本地主题包
|
||||
}
|
||||
```
|
||||
:::
|
||||
@@ -1,75 +0,0 @@
|
||||
---
|
||||
title: 目录结构
|
||||
date: 2020-05-11 13:54:56
|
||||
permalink: /pages/2f674a
|
||||
article: false
|
||||
---
|
||||
|
||||
```
|
||||
.
|
||||
├── .github (可选,GitHub 相关文件)
|
||||
│ ├── workflows
|
||||
│ │ ├── baiduPush.yml (可选,百度定时自动推送)
|
||||
│ │ └── ci.yml (可选,自动部署)
|
||||
├── docs (必须,不要修改文件夹名称)
|
||||
│ ├── .vuepress (同官方,查看:https://vuepress.vuejs.org/zh/guide/directory-structure.html#目录结构)
|
||||
│ ├── @pages (可选,自动生成的文件夹,存放分类页、标签页、归档页)
|
||||
│ ├── _posts (可选,专门存放碎片化博客文章的文件夹)
|
||||
│ ├── <结构化目录>
|
||||
│ └── index.md (首页)
|
||||
├── vdoing (可选,本地的vdoing主题)
|
||||
├── utils (可选,vdoing主题使用的node工具)
|
||||
│ ├── modules
|
||||
│ ├── config.yml (可选,批量操作front matter配置)
|
||||
│ ├── editFrontmatter.js (可选,批量操作front matter工具)
|
||||
├── baiduPush.sh (可选,百度推送命令脚本)
|
||||
├── deploy.sh (可选,部署命令脚本)
|
||||
│
|
||||
└── package.json
|
||||
|
||||
```
|
||||
* `docs` 文件夹名称请不要修改
|
||||
|
||||
* `docs/.vuepress` 用于存放全局的配置、样式、静态资源等,同官方,查看 [详情](https://vuepress.vuejs.org/zh/guide/directory-structure.html#目录结构)
|
||||
* `docs/@pages` 此文件夹是自动生成的,存放分类页、标签页、归档页对应的`.md`文件,一般不需要改动
|
||||
* `docs/_posts` 专门用于存放碎片化博客文章,里面的`.md`文件不需要遵循命名约定,不会生成结构化侧边栏和目录页。
|
||||
|
||||
* `docs/<结构化目录>` 请查看[《构建结构化站点的核心配置和约定》](/pages/33d574/)。
|
||||
|
||||
* `docs/index.md` 首页
|
||||
* `vdoing` 存放在本地的vdoing主题文件,如果你想深度的修改主题,首先要在`docs/.vuepress/config.js`中配置使用的主题指向这个文件。
|
||||
|
||||
<code-group>
|
||||
<code-block title="config.js" active>
|
||||
|
||||
``` js
|
||||
module.exports = {
|
||||
// theme: 'vdoing', // npm主题依赖包
|
||||
theme: require.resolve('../../vdoing'), // 使用本地主题包
|
||||
}
|
||||
```
|
||||
|
||||
</code-block>
|
||||
|
||||
<code-block title="config.ts">
|
||||
|
||||
``` typescript
|
||||
import { resolve } from 'path'
|
||||
import { defineConfig4CustomTheme } from 'vuepress/config'
|
||||
import { VdoingThemeConfig } from 'vuepress-theme-vdoing/types'
|
||||
export default defineConfig4CustomTheme<VdoingThemeConfig>({
|
||||
// theme: 'vdoing', // 使用npm主题包
|
||||
theme: resolve(__dirname, '../../vdoing'), // 使用本地主题包
|
||||
})
|
||||
```
|
||||
|
||||
</code-block>
|
||||
</code-group>
|
||||
|
||||
**注意**:主题的后续维护升级只对npm主题包负责,就是说你使用本地主题就等于放弃了后续的升级服务。因此,建议能在`docs/.vuepress/`内配置和修改的,就尽量不要改动主题内部代码。
|
||||
|
||||
---
|
||||
|
||||
::: tip 提示
|
||||
为了方便您更快的学习和使用本主题,我在代码当中添加了比较多的注释说明。
|
||||
:::
|
||||
@@ -1,107 +0,0 @@
|
||||
---
|
||||
title: 构建结构化站点的核心配置和约定
|
||||
date: 2020-05-12 11:01:21
|
||||
permalink: /pages/33d574
|
||||
article: false
|
||||
---
|
||||
|
||||
本主题遵循“**约定优于配置**”原则,如果你想构建一个结构化的站点,需要遵循下面这些约定,约定可以为你省去很多配置,轻松拥有一个结构清晰的站点。
|
||||
|
||||
|
||||
|
||||
你需要在`config.js`中有如下配置:
|
||||
|
||||
```js
|
||||
// config.js
|
||||
module.exports = {
|
||||
themeConfig: {
|
||||
sidebar: 'structuring' // 'structuring' | { mode: 'structuring', collapsable: Boolean} | 'auto' | 自定义
|
||||
}
|
||||
}
|
||||
```
|
||||
在运行开发服务`npm run dev`或打包`npm run build`时主题内部将会按照目录约定自动生成一个结构化的**侧边栏、目录页、面包屑等**数据。
|
||||
|
||||
在源目录(一般是`docs`)文件夹中,除了`.vuepress`、`@pages`、`_posts`、``index.md 或 README.md``之外的**文件夹**将会为其生成对应的侧边栏。生成的顺序取自序号,标题取自文件(夹)名称。
|
||||
|
||||
### 命名约定
|
||||
|
||||
* 无论是**文件**还是**文件夹**,请为其名称添加上正确的**正整数序号**和`.`,从`00`或`01`开始累计,如`01.文件夹`、`02.文件.md`,我们将会按照序号的顺序来决定其在侧边栏当中的顺序。
|
||||
* 同一级别目录别内即使只有一个文件或文件夹也要为其加上序号。
|
||||
<!-- * 文件或文件夹名称中间不能出现多余的点`.`,如`01.我是.名称.md`中间出现`.`将会导致解析错误。 -->
|
||||
|
||||
::: tip 提示
|
||||
|
||||
序号只是用于决定先后顺序,并不一定需要连着,如`01、02、03...`,实际工作中可能会在两个文章中间插入一篇新的文章,因此为了方便可以采用间隔序号`10、20、30...`,后面如果需要在`10`和`20`中间插入一篇新文章,可以给定序号`15`。
|
||||
|
||||
:::
|
||||
|
||||
### 级别说明
|
||||
|
||||
源目录(一般是`docs`)底下的级别现在我们称之为`一级目录`,`一级目录`的下一级为`二级目录`,以此类推,最多只能到`四级目录`。
|
||||
|
||||
* **一级目录**
|
||||
1. `.vuepress`、`@pages`、`_posts`、`index.md 或 README.md` 这些文件(文件夹)不参与数据生成。
|
||||
2. 序号非必须。(如一些专栏,可以不用序号)
|
||||
* **二级目录**
|
||||
1. 该级别下可以同时放文件夹和`.md`文件,但是两者序号要连贯(参考下面的例子中的`其他`)。
|
||||
2. 必须有序号
|
||||
* **三级目录**
|
||||
- (同上)
|
||||
* **四级目录** <Badge text="v1.6.0 +"/>
|
||||
1. 该级别下**只能**放`.md`文件。
|
||||
2. 必须有序号
|
||||
|
||||
所有级别内至少有一个文件或文件夹。
|
||||
|
||||
### 目录结构例子
|
||||
|
||||
```html
|
||||
.
|
||||
├── docs
|
||||
│ │ (不参与数据生成)
|
||||
│ ├── .vuepress
|
||||
│ ├── @pages
|
||||
│ ├── _posts
|
||||
│ ├── index.md
|
||||
│ │
|
||||
│ │ (以下部分参与数据生成)
|
||||
│ ├── 《JavaScript教程》专栏 (一级目录)
|
||||
│ │ ├── 01.章节1 (二级目录)
|
||||
│ │ | ├── 01.js1.md (三级目录-文件)
|
||||
│ │ | ├── 02.js2.md
|
||||
│ │ | └── 03.js3.md
|
||||
│ │ └── 02.章节2 (二级目录)
|
||||
│ │ | ├── 01.jsa.md
|
||||
│ │ | ├── 02.小节 (三级目录)
|
||||
│ │ | | └── 01.jsxx.md (四级目录-文件)
|
||||
│ ├── 01.前端
|
||||
│ │ ├── 01.JavaScript
|
||||
│ │ | ├── 01.js1.md
|
||||
│ │ | ├── 02.js2.md
|
||||
│ │ | └── 03.js3.md
|
||||
│ │ └── 02.vue
|
||||
│ │ | ├── 01.vue1.md
|
||||
│ │ | └── 02.vue2.md
|
||||
│ ├── 02.其他
|
||||
│ │ ├── 01.学习
|
||||
│ │ | ├── 01.xxa.md
|
||||
│ │ | └── 02.xxb.md
|
||||
│ │ ├── 02.学习笔记
|
||||
│ │ | ├── 01.xxa.md
|
||||
│ │ | └── 02.xxb.md
|
||||
│ │ ├── 03.文件x.md
|
||||
│ │ └── 04.文件xx.md
|
||||
│ └── 03.关于我
|
||||
│ │ └── 01.关于我.md
|
||||
. .
|
||||
```
|
||||
|
||||
### 如何知道侧边栏数据有没有正确生成?
|
||||
在运行开发服务时(`npm run dev`),在命令行查看打印记录,如果正确生成会有这样的`绿色`提示记录:
|
||||
```bash
|
||||
tip: add sidebar data. 侧边栏数据添加成功。
|
||||
```
|
||||
如果有未按约定的文件,会有`黄色`警告记录,如:
|
||||
```bash
|
||||
warning: 该文件'xxx'序号出错,请填写正确的序号。
|
||||
```
|
||||
@@ -1,108 +0,0 @@
|
||||
---
|
||||
title: 自动生成front matter
|
||||
date: 2020-05-12 11:46:37
|
||||
permalink: /pages/088c16
|
||||
article: false
|
||||
---
|
||||
|
||||
当你没有给`.md`文件的[front matter](https://vuepress.vuejs.org/zh/guide/frontmatter.html)指定标题(`title`)、时间(`date`)、永久链接(`permalink`)、分类(`categories`)、标签(`tags`)、主题配置中[extendFrontmatter](/pages/a20ce8/#extendfrontmatte)配置的字段时,在运行开发服务`npm run dev`或打包`npm run build`时将自动为你生成这些数据,你也可以自己手动设置这些数据,当你手动设置之后,相应的数据就不会再自动生成。
|
||||
|
||||
|
||||
|
||||
### 生成示例
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: 《JavaScript教程》笔记
|
||||
date: 2020-01-12 11:51:53
|
||||
permalink: /pages/d8cae9
|
||||
categories:
|
||||
- 前端
|
||||
- JavaScript
|
||||
tags:
|
||||
-
|
||||
---
|
||||
```
|
||||
|
||||
### title
|
||||
|
||||
* 类型: `string`
|
||||
|
||||
* 默认:`.md`文件的名称
|
||||
|
||||
当前页面的标题
|
||||
|
||||
|
||||
|
||||
### date
|
||||
* 类型: `string`
|
||||
* 格式:`YYYY-MM-DD HH:MM:SS`
|
||||
* 默认:`.md`文件在系统中创建的时间
|
||||
|
||||
当前页面的创建时间,如需手动添加或修改该字段时请按照格式添加或修改
|
||||
|
||||
|
||||
|
||||
### permalink
|
||||
* 类型: `string`
|
||||
* 默认:`/pages/`+ 6位字母加数字的随机码
|
||||
|
||||
当前页面的永久链接
|
||||
|
||||
> Q:自动生成front matter为什么要包含永久链接?
|
||||
>
|
||||
> A:使用永久链接是出于以下几点考虑:
|
||||
>
|
||||
> * 在config.js配置nav时使用永久链接,就不会因为文件的路径或名称的改变而改变。
|
||||
>* 对于博客而言,当别人收藏了你的文章,在未来的时间里都可以通过永久链接来访问到。
|
||||
>* 主题中的目录页需要通过永久链接来访问文章。
|
||||
|
||||
|
||||
### categories
|
||||
|
||||
* 类型: `array`
|
||||
* 默认:
|
||||
* `.md`所在的文件夹名称。
|
||||
* 如果`.md`文件所在的目录是`三级目录`,则会有两个分类值,分别是`二级目录`和`一级目录`的文件夹名称。如果在`四级目录`,则再多一个`三级目录`的文件夹名称分类。([级别说明](/pages/33d574/#级别说明))
|
||||
* 如果`.md`文件所在的目录是`_posts`,则默认值是`随笔`,这个默认值可以在`config.js`中修改,参考:[config.js配置](/pages/a20ce8/#碎片化博文默认分类值)
|
||||
* 如果在 [config.js配置](/pages/a20ce8/#category) 设置了`category: false` 将不会自动生成该字段
|
||||
|
||||
当前页面的分类
|
||||
|
||||
|
||||
|
||||
### tags
|
||||
|
||||
* 类型: `array`
|
||||
* 默认:空数组
|
||||
* 如果在 [config.js配置]() 设置了`tag: false` 将不会自动生成该字段
|
||||
|
||||
当前页面的标签,默认值是空数组,自动生成该字段只是为了方便后面添加标签值。
|
||||
|
||||
|
||||
### 扩展自动生成front matter
|
||||
|
||||
当在主题配置中配置了`extendFrontmatter`时,将在自动生成front matter时添加相应配置的字段和数据。详见:[extendFrontmatter](/pages/a20ce8/#extendfrontmatter)
|
||||
|
||||
### 碎片化文章‘分类’的自动生成规则 <Badge text="v1.12.5+"/>
|
||||
|
||||
> 碎片化文章即放在_posts文件夹的文章,里面的`.md`文件不需要遵循命名约定,不会生成结构化侧边栏和目录页。
|
||||
|
||||
当文章在_posts根目录时,分类获取 `themeConfig.categoryText` 的值,如`_posts/foo.md` ,则`foo.md`文件的分类会生成为:
|
||||
|
||||
```yaml
|
||||
categories:
|
||||
- 随笔
|
||||
```
|
||||
|
||||
> categoryText的默认值是‘随笔’,可在themeConfig修改,详见[categorytext](/pages/a20ce8/#categorytext)。
|
||||
|
||||
当文章在非_posts根目录时,获取父文件夹的名称作为分类,如
|
||||
|
||||
`_posts/想法/奇思妙想/foo.md` ,则`foo.md`文件的分类会生成为:
|
||||
|
||||
```yaml
|
||||
categories:
|
||||
- 想法
|
||||
- 奇思妙想
|
||||
```
|
||||
@@ -1,373 +0,0 @@
|
||||
---
|
||||
title: Markdown 容器
|
||||
date: 2020-05-29 11:16:18
|
||||
permalink: /pages/d0d7eb/
|
||||
article: false
|
||||
---
|
||||
|
||||
Markdown 容器是对 Markdown 语法的一个扩展,使用简单的语法就可以在页面中呈现丰富的效果。
|
||||
|
||||
除了原默认主题自带的容器外,本主题还新增了一些好用的自定义容器。
|
||||
|
||||
## 信息框容器
|
||||
**输入**
|
||||
```` md
|
||||
::: tip
|
||||
这是一条提示
|
||||
:::
|
||||
|
||||
::: warning
|
||||
这是一条注意
|
||||
:::
|
||||
|
||||
::: danger
|
||||
这是一条警告
|
||||
:::
|
||||
|
||||
::: note
|
||||
这是笔记容器,在 <Badge text="v1.5.0 +" /> 版本才支持哦~
|
||||
:::
|
||||
````
|
||||
|
||||
**输出**
|
||||
::: tip
|
||||
这是一条提示
|
||||
:::
|
||||
|
||||
::: warning
|
||||
这是一条注意
|
||||
:::
|
||||
|
||||
::: danger
|
||||
这是一条警告
|
||||
:::
|
||||
|
||||
::: note
|
||||
这是笔记容器,在 <Badge text="v1.5.0 +" /> 以上版本才支持哦~
|
||||
:::
|
||||
|
||||
以上容器均可自定义标题,如:
|
||||
````
|
||||
::: tip 我的提示
|
||||
自定义标题的提示框
|
||||
:::
|
||||
````
|
||||
::: tip 我的提示
|
||||
自定义标题的提示框
|
||||
:::
|
||||
|
||||
## 布局容器 <Badge text="v1.3.3 +" />
|
||||
**输入**
|
||||
```` md
|
||||
::: center
|
||||
### 我是居中的内容
|
||||
(可用于标题、图片等的居中)
|
||||
:::
|
||||
|
||||
::: right
|
||||
[我是右浮动的内容](https://zh.wikipedia.org/wiki/%E7%89%9B%E9%A1%BF%E8%BF%90%E5%8A%A8%E5%AE%9A%E5%BE%8B)
|
||||
:::
|
||||
|
||||
::: details
|
||||
这是一个详情块,在 IE / Edge 中不生效
|
||||
```js
|
||||
console.log('这是一个详情块')
|
||||
```
|
||||
:::
|
||||
|
||||
::: theorem 牛顿第一定律
|
||||
假若施加于某物体的外力为零,则该物体的运动速度不变。
|
||||
::: right
|
||||
来自 [维基百科](https://zh.wikipedia.org/wiki/%E7%89%9B%E9%A1%BF%E8%BF%90%E5%8A%A8%E5%AE%9A%E5%BE%8B)
|
||||
:::
|
||||
````
|
||||
|
||||
**输出**
|
||||
::: center
|
||||
### 我是居中的内容
|
||||
(可用于标题、图片等的居中)
|
||||
:::
|
||||
|
||||
::: right
|
||||
[我是右浮动的内容](https://zh.wikipedia.org/wiki/%E7%89%9B%E9%A1%BF%E8%BF%90%E5%8A%A8%E5%AE%9A%E5%BE%8B)
|
||||
:::
|
||||
|
||||
::: details
|
||||
这是一个详情块,在 IE / Edge 中不生效
|
||||
```js
|
||||
console.log('这是一个详情块')
|
||||
```
|
||||
:::
|
||||
|
||||
::: theorem 牛顿第一定律
|
||||
假若施加于某物体的外力为零,则该物体的运动速度不变。
|
||||
|
||||
::: right
|
||||
来自 [维基百科](https://zh.wikipedia.org/wiki/%E7%89%9B%E9%A1%BF%E8%BF%90%E5%8A%A8%E5%AE%9A%E5%BE%8B)
|
||||
:::
|
||||
|
||||
> 注意:`right`、`details`、`theorem`这三个容器在`v1.3.0 +`版本才支持。`center`容器在`v1.3.3 +`版本才支持。
|
||||
|
||||
|
||||
## 普通卡片列表 <Badge text="v1.1.0 +"/>
|
||||
|
||||
普通卡片列表容器,可用于`友情链接`、`项目推荐`、`诗词展示`等。
|
||||
|
||||
先来看看效果:
|
||||
|
||||
**输出**
|
||||
::: cardList
|
||||
```yaml
|
||||
- name: 麋鹿鲁哟
|
||||
desc: 大道至简,知易行难
|
||||
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200122153807.jpg # 可选
|
||||
link: https://www.cnblogs.com/miluluyo/ # 可选
|
||||
bgColor: '#CBEAFA' # 可选,默认var(--bodyBg)。颜色值有#号时请添加单引号
|
||||
textColor: '#6854A1' # 可选,默认var(--textColor)
|
||||
- name: XAOXUU
|
||||
desc: '#IOS #Volantis主题作者'
|
||||
avatar: https://fastly.jsdelivr.net/gh/xaoxuu/assets@master/avatar/avatar.png
|
||||
link: https://xaoxuu.com
|
||||
bgColor: '#718971'
|
||||
textColor: '#fff'
|
||||
- name: 平凡的你我
|
||||
desc: 理想成为大牛的小陈同学
|
||||
avatar: https://reinness.com/avatar.png
|
||||
link: https://reinness.com
|
||||
bgColor: '#FCDBA0'
|
||||
textColor: '#A05F2C'
|
||||
```
|
||||
:::
|
||||
|
||||
上面效果在Markdown中的代码是这样的:
|
||||
|
||||
**输入**
|
||||
```` md
|
||||
::: cardList
|
||||
```yaml
|
||||
- name: 麋鹿鲁哟
|
||||
desc: 大道至简,知易行难
|
||||
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200122153807.jpg # 可选
|
||||
link: https://www.cnblogs.com/miluluyo/ # 可选
|
||||
bgColor: '#CBEAFA' # 可选,默认var(--bodyBg)。颜色值有#号时请添加单引号
|
||||
textColor: '#6854A1' # 可选,默认var(--textColor)
|
||||
- name: XAOXUU
|
||||
desc: '#IOS #Volantis主题作者'
|
||||
avatar: https://fastly.jsdelivr.net/gh/xaoxuu/assets@master/avatar/avatar.png
|
||||
link: https://xaoxuu.com
|
||||
bgColor: '#718971'
|
||||
textColor: '#fff'
|
||||
- name: 平凡的你我
|
||||
desc: 理想成为大牛的小陈同学
|
||||
avatar: https://reinness.com/avatar.png
|
||||
link: https://reinness.com
|
||||
bgColor: '#FCDBA0'
|
||||
textColor: '#A05F2C'
|
||||
```
|
||||
:::
|
||||
````
|
||||
|
||||
|
||||
### 语法
|
||||
````md
|
||||
::: cardList <每行显示数量>
|
||||
``` yaml
|
||||
- name: 名称
|
||||
desc: 描述
|
||||
avatar: https://xxx.jpg # 头像,可选
|
||||
link: https://xxx/ # 链接,可选
|
||||
bgColor: '#CBEAFA' # 背景色,可选,默认var(--bodyBg)。颜色值有#号时请添加引号
|
||||
textColor: '#6854A1' # 文本色,可选,默认var(--textColor)
|
||||
```
|
||||
:::
|
||||
````
|
||||
|
||||
* `<每行显示数量>` 数字,表示每行最多显示多少个,选值范围1~4,默认3。在小屏时会根据屏幕宽度减少每行显示数量。
|
||||
* 代码块需指定语言为`yaml`
|
||||
* 代码块内是一个`yaml`格式的数组列表
|
||||
* 数组成员的属性有:
|
||||
* `name`名称
|
||||
* `desc`描述
|
||||
* `avatar`头像,可选
|
||||
* `link`链接,可选
|
||||
* `bgColor`背景色,可选,默认`var(--bodyBg)`。颜色值有`#`号时请添加引号
|
||||
* `textColor`文本色,可选,默认`var(--textColor)`
|
||||
|
||||
下面再来看另外一个示例:
|
||||
|
||||
**输入**
|
||||
```` md
|
||||
::: cardList 2
|
||||
```yaml
|
||||
- name: 《静夜思》
|
||||
desc: 床前明月光,疑是地上霜。举头望明月,低头思故乡。
|
||||
bgColor: '#F0DFB1'
|
||||
textColor: '#242A38'
|
||||
- name: Vdoing
|
||||
desc: 🚀一款简洁高效的VuePress 知识管理&博客(blog) 主题
|
||||
link: https://github.com/xugaoyi/vuepress-theme-vdoing
|
||||
bgColor: '#DFEEE7'
|
||||
textColor: '#2A3344'
|
||||
```
|
||||
:::
|
||||
````
|
||||
|
||||
|
||||
**输出**
|
||||
::: cardList 2
|
||||
```yaml
|
||||
- name: 《静夜思》
|
||||
desc: 床前明月光,疑是地上霜。举头望明月,低头思故乡。
|
||||
bgColor: '#F0DFB1'
|
||||
textColor: '#242A38'
|
||||
- name: Vdoing
|
||||
desc: 🚀一款简洁高效的VuePress 知识管理&博客(blog) 主题
|
||||
link: https://github.com/xugaoyi/vuepress-theme-vdoing
|
||||
bgColor: '#DFEEE7'
|
||||
textColor: '#2A3344'
|
||||
```
|
||||
:::
|
||||
|
||||
|
||||
|
||||
|
||||
## 图文卡片列表 <Badge text="v1.1.0 +" />
|
||||
|
||||
图文卡片列表容器,可用于`项目展示`、`产品展示`等。
|
||||
|
||||
先看效果:
|
||||
|
||||
**输出**
|
||||
::: cardImgList
|
||||
```yaml
|
||||
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200529162253.jpg
|
||||
link: https://xugaoyi.com/
|
||||
name: 标题
|
||||
desc: 描述内容描述内容描述内容描述内容描述内容描述内容描述内容描述内容 # 描述,可选
|
||||
author: Evan Xu # 作者,可选
|
||||
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200103123203.jpg # 头像,可选
|
||||
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200530100256.jpg
|
||||
link: https://xugaoyi.com/
|
||||
name: 标题
|
||||
desc: 描述内容描述内容描述内容描述内容描述内容描述内容描述内容描述内容
|
||||
author: Evan Xu
|
||||
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200103123203.jpg
|
||||
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200530100257.jpg
|
||||
link: https://xugaoyi.com/
|
||||
name: 标题
|
||||
desc: 描述内容描述内容描述内容描述内容描述内容描述内容描述内容描述内容
|
||||
author: Evan Xu
|
||||
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200103123203.jpg
|
||||
```
|
||||
:::
|
||||
|
||||
**输入**
|
||||
````md
|
||||
::: cardImgList
|
||||
```yaml
|
||||
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200529162253.jpg
|
||||
link: https://xugaoyi.com/
|
||||
name: 标题
|
||||
desc: 描述内容描述内容描述内容描述内容描述内容描述内容描述内容描述内容 # 描述,可选
|
||||
author: Evan Xu # 作者,可选
|
||||
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200103123203.jpg # 头像,可选
|
||||
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200530100256.jpg
|
||||
link: https://xugaoyi.com/
|
||||
name: 标题
|
||||
desc: 描述内容描述内容描述内容描述内容描述内容描述内容描述内容描述内容
|
||||
author: Evan Xu
|
||||
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200103123203.jpg
|
||||
- img: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200530100257.jpg
|
||||
link: https://xugaoyi.com/
|
||||
name: 标题
|
||||
desc: 描述内容描述内容描述内容描述内容描述内容描述内容描述内容描述内容
|
||||
author: Evan Xu
|
||||
avatar: https://fastly.jsdelivr.net/gh/xugaoyi/image_store/blog/20200103123203.jpg
|
||||
```
|
||||
:::
|
||||
````
|
||||
|
||||
|
||||
### 语法
|
||||
````md
|
||||
::: cardImgList <每行显示数量>
|
||||
``` yaml
|
||||
- img: https://xxx.jpg # 图片地址
|
||||
link: https://xxx.com # 链接地址
|
||||
name: 标题
|
||||
desc: 描述 # 可选
|
||||
author: 作者名称 # 可选
|
||||
avatar: https://xxx.jpg # 作者头像,可选
|
||||
```
|
||||
:::
|
||||
````
|
||||
* `<每行显示数量>` 数字,表示每行最多显示多少个,选值范围1~4,默认3。在小屏时会根据屏幕宽度减少每行显示数量。
|
||||
* 代码块需指定语言为`yaml`
|
||||
* 代码块内是一个`yaml`格式的数组列表
|
||||
* 数组成员的属性有:
|
||||
* `img`图片地址
|
||||
* `link`链接地址
|
||||
* `name`标题
|
||||
* `desc`描述,可选
|
||||
* `author`作者名称,可选
|
||||
* `avatar`作者头像,可选
|
||||
|
||||
|
||||
## 增强配置 <Badge text="v1.9.0 +"/>
|
||||
为了适应更多需求场景,`v1.9.0+`版本的普通卡片和图文卡片容器添加了一些新的配置:
|
||||
|
||||
### 1. 普通卡片和图文卡片容器
|
||||
|
||||
#### target
|
||||
- 链接的打开方式,默认`_blank`
|
||||
|
||||
- `_self` 当前页面
|
||||
|
||||
- `_blank` 新窗口打开
|
||||
|
||||
|
||||
|
||||
### 2. 图文卡片容器
|
||||
|
||||
#### imgHeight
|
||||
- 设置图片高度,默认 `auto`
|
||||
|
||||
- 带单位
|
||||
|
||||
|
||||
#### objectFit
|
||||
- 设置图片的填充方式(object-fit),默认 `cover`
|
||||
|
||||
- `fill` 拉伸 (会改变宽高比)
|
||||
- `contain` 缩放 (保持宽高比,会留空)
|
||||
- `cover` 填充 (会裁剪)
|
||||
- `none` 保持原有尺寸 (会留空或裁剪)
|
||||
- `scale-down` 保证显示完整图片 (保持宽高比,会留空)
|
||||
|
||||
|
||||
#### lineClamp
|
||||
- 描述文本超出多少行显示省略号,默认`1`
|
||||
|
||||
|
||||
|
||||
### 3. 配置示例:
|
||||
|
||||
````yaml
|
||||
::: cardImgList
|
||||
``` yaml
|
||||
config:
|
||||
target: _blank
|
||||
imgHeight: auto
|
||||
objectFit: cover
|
||||
lineClamp: 1
|
||||
|
||||
data:
|
||||
- img: https://xxx.jpg
|
||||
link: https://xugaoyi.com/
|
||||
name: 标题
|
||||
desc: 描述内容
|
||||
author: Evan Xu
|
||||
avatar: https://xxx.jpg
|
||||
```
|
||||
:::
|
||||
````
|
||||
@@ -1,70 +0,0 @@
|
||||
---
|
||||
title: Markdown 中使用组件
|
||||
date: 2020-11-10 18:56:22
|
||||
permalink: /pages/197691/
|
||||
article: false
|
||||
---
|
||||
|
||||
主题的内置组件可以直接在`Markdown`文件中以类似html标签的方式使用。
|
||||
|
||||
## 标记
|
||||
- **Props:**
|
||||
- `text`- string
|
||||
- `type` - string, 可选值: `tip | warning | error`,默认: `tip`
|
||||
- `vertical` - string, 可选值: `top | middle`,默认: `top`
|
||||
|
||||
- **Usage:**
|
||||
|
||||
你可以在标题或其他内容中使用标记:
|
||||
```md
|
||||
#### 《沁园春·雪》 <Badge text="摘"/>
|
||||
北国风光<Badge text="注释" type="warning"/>,千里冰封,万里雪飘。
|
||||
|
||||
> <Badge text="译文" type="error" vertical="middle"/>: 北方的风光。
|
||||
```
|
||||
**效果:**
|
||||
#### 《沁园春·雪》 <Badge text="摘"/>
|
||||
北国风光<Badge text="注释" type="warning"/>,千里冰封,万里雪飘。
|
||||
|
||||
> <Badge text="译文" type="error" vertical="middle"/>: 北方的风光。
|
||||
|
||||
## 代码块选项卡 <Badge text="v1.8.0 +"/>
|
||||
|
||||
在`<code-group>`中嵌套`<code-block>`来配合使用。在`<code-block>`标签添加`title`来指定tab标题,`active`指定当前tab:
|
||||
|
||||
````md
|
||||
<code-group>
|
||||
<code-block title="YARN" active>
|
||||
```bash
|
||||
yarn add vuepress-theme-vdoing -D
|
||||
```
|
||||
</code-block>
|
||||
|
||||
<code-block title="NPM">
|
||||
```bash
|
||||
npm install vuepress-theme-vdoing -D
|
||||
```
|
||||
</code-block>
|
||||
</code-group>
|
||||
````
|
||||
|
||||
**效果:**
|
||||
|
||||
<code-group>
|
||||
<code-block title="YARN" active>
|
||||
```bash
|
||||
yarn add vuepress-theme-vdoing -D
|
||||
```
|
||||
</code-block>
|
||||
|
||||
<code-block title="NPM">
|
||||
```bash
|
||||
npm install vuepress-theme-vdoing -D
|
||||
```
|
||||
</code-block>
|
||||
</code-group>
|
||||
|
||||
::: warning
|
||||
- 请在`<code-group>`标签与markdown内容之间使用空行隔开,否则可能会解析不出来。
|
||||
- 该组件只适用于放置代码块,放其他内容在体验上并不友好。如您确实需要放置其他内容的选项卡,推荐使用[vuepress-plugin-tabs](https://superbiger.github.io/vuepress-plugin-tabs)插件。
|
||||
:::
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
title: 项目结构
|
||||
date: 2020-05-11 13:54:56
|
||||
permalink: /pages/2f674a
|
||||
article: false
|
||||
---
|
||||
|
||||
# 主要目录、文件
|
||||
|
||||
```
|
||||
├── data.db (为配置mysql时,项目启动默认初始化生成的本地sqlite数据库文件)
|
||||
├── docs (项目相关的一些文档)
|
||||
├── neutrino-proxy-admin (管理后台前端项目,基于vue-element-admin)
|
||||
├── neutrino-proxy-client (基于netty的代理客户端,用于和服务端交互、转发内网数据)
|
||||
├── neutrino-proxy-core (代理相关的公共代码(协议、常量))
|
||||
├── neutrino-proxy-server (基于netty的代理服务端,用于和客户段交互,将客户端转发的内网数据转发至外网端口)
|
||||
└── todolist.MD (近期的开发计划)
|
||||
```
|
||||
|
||||
# 代理服务端 `neutrino-proxy-server`
|
||||
- base:基础的配置、初始化代码
|
||||
- constant:服务端常量、枚举定义
|
||||
- dal:持久层mapper、实体
|
||||
- job:定时任务执行入口
|
||||
- proxy:代理核心实现
|
||||
- service:基础业务
|
||||
- util:基础工具封装
|
||||
- resource/mapper:mybatis SQL文件
|
||||
- resource/sql:维护mysql/sqlite的初始化SQL、SQL变更记录
|
||||
- resource/app.yml:项目默认配置
|
||||
- resource/test.jks:默认的SSL证书
|
||||
|
||||
# 代理客户端 `neutrino-proxy-client`
|
||||
- config:基础配置
|
||||
- core:客户端代理核心入口
|
||||
- handler:客户端代理数据处理逻辑
|
||||
- util:基础工具封装
|
||||
- resource/app.yml:项目默认配置
|
||||
- resource/test.jks:默认的SSL证书
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
title: 实现原理
|
||||
date: 2020-09-26 21:13:59
|
||||
permalink: /pages/dd027d/
|
||||
article: false
|
||||
---
|
||||
|
||||
# 代理实现中涉及的几类Channel
|
||||
|
||||
## 指令通道(CmdChannel)
|
||||
- 该channel负责维护客户端与服务端之间的指令通讯,由客户端启动后向服务端发起连接请求,服务端验证license成功后连接建立。
|
||||
- 服务端维护第一个CmdChannel的映射表,key为licenseId。服务端可以根据licenseId,向指定的客户端指令通道发布指令。
|
||||
- 该通道建立完成后,服务端会将该license授权的外网映射端口打开,等待用户访问。正常情况下,只要客户端不下线,该通道一直可用。
|
||||
- 然后服务端维护第二个CmdChannel的映射表,key为服务端外网端口。服务端可以根据指定的外网端口,向指定的客户端指令通道发布指令。
|
||||
|
||||
## 用户访问通道(VisitorChannel)
|
||||
- 该channel负责维护访问者与服务端端口之间的通讯,由访问者向服务端映射的外网端口发起请求开始建立,具体断开时机由实际的被代理的
|
||||
协议决定。
|
||||
- 如HTTP/1.0下,用户向该端口发起请求,响应结束后,该通道随之关闭,下一次发起请求后,重新建立新的连接。
|
||||
- 该连接建立后,服务端会向该外网端口映射的指令通道发送`Connect`指令,传输该外网端口需要代理的内网信息,
|
||||
如:`127.0.0.1:3306`。
|
||||
|
||||
## 被代理服务的实际通道(RealServerChannel)
|
||||
- 该channel负责维护客户端与实际被代理服务之间的通讯,当客户端接收到服务端的`Connect`指令后,客户端便会建立该通道。
|
||||
|
||||
## 代理数据传输的通道(ProxyChannel)
|
||||
- 该channel负责完成内网被代理服务与代理服务端之间的数据转发任务。
|
||||
- 每个客户端维护一个`ProxyChannel`的缓存队列,需要时从该队列中取,当取不到时,直接新建一个`ProxyChannel`返回。
|
||||
当一个`ProxyChannel`实例用完后,需要归还到缓存队列中(`ProxyChannel`收到`DisConnect`指令时)。
|
||||
- 当`RealServerChannel`建立完成后,就会获取相关联的`ProxyChannel`,并与其绑定。设置`RealServerChannel`为
|
||||
可读状态。然后通过`ProxyChannel`向服务端发送`Connect`指令。
|
||||
|
||||
|
||||
# 代理实现流程
|
||||
## 1、服务连接阶段
|
||||
- 1.1、客户端根据是否需要使用SSL,选择对应的服务端端口发起连接,建立`CmdChannel`。
|
||||
- 1.2、客户端根据用户输入或配置文件获取`license`,并携带`license`通过`CmdChannel`向服务端发送`Auth`指令。
|
||||
- 1.3、服务端通过`CmdChannel`接收到来自客户端的`Auth`指令。若验证`license`有效,则建立`licenseId`与`CmdChannel`的映射缓存、
|
||||
外网端口与`CmdChannel`的映射缓存。并启动服务端代理端口,等待用户连接。
|
||||
## 2、用户连接阶段
|
||||
- 2.1、用户向服务端代理的外网端口发起请求,服务端建立`VisitorChannel`。
|
||||
- 2.2、根据外网端口查找`CmdChannel`,若不存在有效的`CmdChannel`,则关闭该`VisitorChannel`。否则,
|
||||
设置`VisitorChannel`为不可读,并携带内网映射信息(如:`127.0.0.1:3306`)通过`CmdChannel`向客户端发送`Connect`指令。
|
||||
## 3、实际被代理服务连接阶段
|
||||
- 3.1、客户端通过`CmdChannel`接收到服务端的`Connect`指令。拿到需要代理的内网IP、端口号,向实际被
|
||||
代理服务发起连接请求,若连接失败,则通过`CmdChannel`向服务端发送`DisConnect`指令。建立`RealServerChannel`成功,设置`RealServerChannel`为不可读
|
||||
状态,并进入4.1阶段
|
||||
## 4、代理通道连接阶段
|
||||
- 4.1、客户端通过`ProxyChannelQueue`获取或新建一个`ProxyChannel`,并将`RealServerChannel`与`ProxyChannel`进行绑定。
|
||||
- 4.2、客户端通过`ProxyChannel`向服务端发送`Connect`指令。
|
||||
- 4.3、服务端通过`ProxyChannel`通道收到来自客户端的`Connect`指令后,将`ProxyChannel`与对应的`VisitorChannel`进行绑定,并
|
||||
设置`VisitorChannel`为可读状态。
|
||||
## 5、数据传输阶段
|
||||
- 5.1、服务端通过`VisitorChannel`收到来自用户的请求数据,然后找到`VisitorChannel`绑定的`ProxyChannel`,
|
||||
通过`ProxyChannel`发送`Transfer`指令,并携带用户请求数据。
|
||||
- 5.2、客户端通过`ProxyChannel`收到来自服务端的`Transfer`指令,取出用户请求数据。找到`ProxyChannel`绑定的`RealServerChannel`,
|
||||
通过`RealServerChannel`向被代理服务写入用户请求数据。
|
||||
- 5.3、客户端通过`RealServerChannel`收到被代理服务响应的数据,找到`RealServerChannel`绑定的`ProxyChannel`,
|
||||
通过`ProxyChannel`向服务端发送`Transfer`指令,并携带响应数据。
|
||||
- 5.4、服务端通过`ProxyChannel`收到来自客户端的`Transfer`指令,找到`ProxyChannel`绑定的`VisitorChannel`,
|
||||
通过`VisitorChannel`向用户端写入响应数据。
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
title: 使目录栏支持h2~h6标题
|
||||
date: 2022-03-18 15:02:52
|
||||
permalink: /pages/8dfab5/
|
||||
article: false
|
||||
---
|
||||
|
||||
在`.vuepress/config.js`添加如下配置即可使 VuePress 提取相应标题级别的数据,并应用到主题的右侧目录栏中<Badge text="v1.10.0 +"/>。
|
||||
|
||||
|
||||
## markdown.extractHeaders
|
||||
|
||||
- 类型: Array
|
||||
- 默认: ['h2', 'h3']
|
||||
|
||||
Markdown 文件的 headers (标题 & 小标题) 会在准备阶段被提取出来,并存储在 this.$page.headers 中。默认情况下,VuePress 会提取 h2 和 h3 标题。你可以通过这个选项来修改提取出的标题级别。
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
markdown: {
|
||||
extractHeaders: [ 'h2', 'h3', 'h4', 'h5', 'h6' ]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
注:此配置来自 [VuePress官方文档](https://vuepress.vuejs.org/zh/config/#markdown-extractheaders)
|
||||
@@ -1,230 +0,0 @@
|
||||
---
|
||||
title: 如何让你的笔记更有表现力
|
||||
date: 2020-09-26 21:13:59
|
||||
permalink: /pages/dd027d/
|
||||
article: false
|
||||
---
|
||||
|
||||
你的知识笔记枯燥无味没有重点?基于本主题,配合各种骚操作,让你的知识笔记表现力爆棚~~
|
||||
|
||||
::: note
|
||||
Markdown的基本语法就不再重复啦 (对Markdown不了解的可以看 [这里](https://xugaoyi.com/pages/ad247c4332211551/)),
|
||||
下面将介绍一些可以在本主题Markdown中使用的骚操作~
|
||||
:::
|
||||
|
||||
## 1. 文本高亮
|
||||
使用`<mark>`标签让文本高亮
|
||||
|
||||
```text
|
||||
Vdoing是一款简洁高效的 <mark>知识管理&博客</mark> 主题
|
||||
```
|
||||
Vdoing是一款简洁高效的 <mark> 知识管理&博客 </mark> 主题
|
||||
|
||||
## 2. 标记
|
||||
### 内置标记
|
||||
主题内置的[Badge组件](https://vuepress.vuejs.org/zh/guide/using-vue.html#badge),直接在 Markdown 文件中使用
|
||||
|
||||
```html
|
||||
<Badge text="beta" type="warning"/>
|
||||
<Badge text="Vdoing主题"/>
|
||||
```
|
||||
<Badge text="beta" type="warning"/>
|
||||
<Badge text="Vdoing主题"/>
|
||||
|
||||
### 外部标记
|
||||
使用 [shields](https://shields.io/) 生成标记,在Markdown中使用
|
||||
```markdown
|
||||

|
||||

|
||||
```
|
||||

|
||||

|
||||
|
||||
> 这类标记图标可以生成动态统计数据。
|
||||
|
||||
## 3. 折叠列表
|
||||
主题内置的容器,直接在 Markdown 文件中使用
|
||||
````html
|
||||
::: details
|
||||
这是一个详情块
|
||||
```js
|
||||
console.log('这是一个详情块')
|
||||
```
|
||||
:::
|
||||
````
|
||||
::: details
|
||||
这是一个详情块
|
||||
```js
|
||||
console.log('这是一个详情块')
|
||||
```
|
||||
:::
|
||||
|
||||
> 更多:[Markdown 容器](/pages/d0d7eb/)
|
||||
|
||||
## 4. 思维导图 & 流程图
|
||||
### 方法一:
|
||||
1. 使用[Markmap](https://markmap.js.org/)生成思维导图html文件
|
||||
2. 将html文件放在`docs/.vuepress/public/markmap/`
|
||||
3. 通过`<iframe>`插入到Markdown
|
||||
|
||||
``` html
|
||||
<iframe :src="$withBase('/markmap/01.html')" width="100%" height="400" frameborder="0" scrolling="No" leftmargin="0" topmargin="0"></iframe>
|
||||
```
|
||||
|
||||
<iframe :src="$withBase('/markmap/01.html')" width="100%" height="400" frameborder="0" scrolling="No" leftmargin="0" topmargin="0"></iframe>
|
||||
|
||||
### 方法二:
|
||||
通过`<iframe>`标签引入[processon](https://www.processon.com/)或其他在线作图工具生成的链接。
|
||||
```html
|
||||
<iframe src="https://www.processon.com/view/link/5e718942e4b015182028682c" width="100%" height="500" frameborder="0" scrolling="No" leftmargin="0" topmargin="0"></iframe>
|
||||
```
|
||||
<iframe src="https://www.processon.com/view/link/5e718942e4b015182028682c" width="100%" height="500" frameborder="0" scrolling="No" leftmargin="0" topmargin="0"></iframe>
|
||||
|
||||
### 方法三:
|
||||
使用流程图插件:
|
||||
* [vuepress-plugin-flowchart](https://www.npmjs.com/package/vuepress-plugin-flowchart)
|
||||
* [vuepress-plugin-mermaidjs](https://github.com/eFrane/vuepress-plugin-mermaidjs)
|
||||
|
||||
## 5.Demo演示框
|
||||
### 方法一:
|
||||
1. 安装 [vuepress-plugin-demo-block](https://www.npmjs.com/package/vuepress-plugin-demo-block)或其他同类插件,使用方法看插件文档
|
||||
2. 在`.vuepress/config.js`配置插件
|
||||
3. Markdown中使用
|
||||
|
||||
> 同类插件:[vuepress-plugin-demo-container](https://github.com/calebman/vuepress-plugin-demo-container)
|
||||
|
||||
|
||||
::: demo [vanilla]
|
||||
```html
|
||||
<html>
|
||||
<div class="animationBox">
|
||||
<div class="rotate">旋转动画1</div>
|
||||
<div class="play">
|
||||
<div class="img">旋转动画2</div>
|
||||
<span><p class="p2"></p></span>
|
||||
<span><p></p></span>
|
||||
<span><p></p></span>
|
||||
<span><p class="p2"></p></span>
|
||||
</div>
|
||||
<div class="elasticity">弹性动画</div>
|
||||
<div class="elasticity2">曲线弹性</div>
|
||||
</div>
|
||||
</html>
|
||||
|
||||
<style>
|
||||
.animationBox{overflow: hidden;}
|
||||
.animationBox>div{
|
||||
width: 100px;height: 100px;background: #eee;border-radius: 50%;text-align: center;line-height: 100px;margin: 30px;float:left;
|
||||
}
|
||||
.rotate{
|
||||
animation: rotate 5s linear infinite
|
||||
}
|
||||
.rotate:hover{ animation-play-state: paused}
|
||||
@keyframes rotate {
|
||||
0%{transform: rotate(0);}
|
||||
100%{transform: rotate(360deg);}
|
||||
}
|
||||
.animationBox>.play {
|
||||
position: relative;
|
||||
margin: 50px 30px;
|
||||
background:none;
|
||||
}
|
||||
.play .img{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left:0;
|
||||
z-index: 1;
|
||||
width: 100px;height: 100px; background: #eee;
|
||||
border-radius: 50%;
|
||||
|
||||
animation: rotate 5s linear infinite
|
||||
}
|
||||
.play span {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left:1px;
|
||||
z-index: 0;
|
||||
display: block;
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
border: 1px solid #999;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.play span p{display: block;width: 4px;height: 4px;background: #000;margin: -2px 0 0 50%;border-radius: 50%;opacity: 0.5;}
|
||||
.play span .p2{margin: 50% 0 0 -2px;}
|
||||
.play span{
|
||||
animation: wave 5s linear infinite
|
||||
}
|
||||
.play>span:nth-child(3){
|
||||
/* 延迟时间 */
|
||||
animation-delay:1s;
|
||||
}
|
||||
.play>span:nth-child(4){
|
||||
animation-delay:2.2s;
|
||||
}
|
||||
.play>span:nth-child(5){
|
||||
animation-delay:3.8s;
|
||||
}
|
||||
|
||||
@keyframes wave {
|
||||
0%
|
||||
{
|
||||
transform:scale(1) rotate(360deg);
|
||||
opacity: 0.8;
|
||||
}
|
||||
100%
|
||||
{
|
||||
transform:scale(1.8) rotate(0deg);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.elasticity{
|
||||
animation: elasticity 1s linear 2s infinite
|
||||
}
|
||||
|
||||
@keyframes elasticity{
|
||||
0%{
|
||||
transform: scale(0);
|
||||
}
|
||||
60%{
|
||||
transform: scale(1.1);
|
||||
}
|
||||
90%{
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.elasticity2{
|
||||
animation: elasticity2 1s cubic-bezier(.39,.62,.74,1.39) 2s infinite
|
||||
}
|
||||
@keyframes elasticity2{
|
||||
0%{
|
||||
transform: scale(0);
|
||||
}
|
||||
90%{
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
:::
|
||||
|
||||
### 方法二:
|
||||
嵌入[codepen](https://codepen.io/)
|
||||
|
||||
```html
|
||||
<iframe height="400" style="width: 100%;" scrolling="no" title="【CSS:行为】使用:hover和attr()定制悬浮提示" src="https://codepen.io/xugaoyi/embed/vYNKNaq?height=400&theme-id=light&default-tab=css,result" frameborder="no" allowtransparency="true" allowfullscreen="true" loading="lazy">
|
||||
See the Pen <a href='https://codepen.io/xugaoyi/pen/vYNKNaq'>【CSS:行为】使用:hover和attr()定制悬浮提示</a> by xugaoyi
|
||||
(<a href='https://codepen.io/xugaoyi'>@xugaoyi</a>) on <a href='https://codepen.io'>CodePen</a>.
|
||||
</iframe>
|
||||
```
|
||||
<iframe height="400" style="width: 100%;" scrolling="no" title="【CSS:行为】使用:hover和attr()定制悬浮提示" src="https://codepen.io/xugaoyi/embed/vYNKNaq?height=400&theme-id=light&default-tab=css,result" frameborder="no" allowtransparency="true" allowfullscreen="true" loading="lazy">
|
||||
See the Pen <a href='https://codepen.io/xugaoyi/pen/vYNKNaq'>【CSS:行为】使用:hover和attr()定制悬浮提示</a> by xugaoyi
|
||||
(<a href='https://codepen.io/xugaoyi'>@xugaoyi</a>) on <a href='https://codepen.io'>CodePen</a>.
|
||||
</iframe>
|
||||
|
||||
::: note
|
||||
`<iframe>`标签还可以嵌入其他任何外部网页,如视频、地图等
|
||||
:::
|
||||
@@ -1,67 +0,0 @@
|
||||
---
|
||||
title: 批量操作front matter工具
|
||||
date: 2020-05-13 11:52:45
|
||||
permalink: /pages/2b8e22
|
||||
article: false
|
||||
---
|
||||
|
||||
当你想为某个文件夹下的所有`.md`文件添加、修改、删除某些front matter字段时,这个工具可以快速的为你批量操作。
|
||||
|
||||
首先,你需要在`package.json`的`scripts`中写入脚本:
|
||||
```json
|
||||
// package.json
|
||||
{
|
||||
"scripts": {
|
||||
"editFm": "node utils/editFrontmatter.js",
|
||||
}
|
||||
}
|
||||
```
|
||||
在`utils/config.yml`配置要批量操作的文件夹和要编辑的字段,示例:
|
||||
```yaml
|
||||
# utils/config.yml
|
||||
#批量添加和修改、删除front matter配置文件
|
||||
|
||||
# 需要批量处理的路径,docs文件夹内的文件夹 (数组。映射路径:docs/arr[1]/arr[2] ... )
|
||||
path:
|
||||
- docs # 第一个成员必须是docs
|
||||
- 《JS教程》专辑
|
||||
- 第一章节
|
||||
|
||||
# 要删除的字段 (数组)
|
||||
delete:
|
||||
- article
|
||||
|
||||
# 要添加、修改front matter的数据 (front matter中没有的数据则添加,已有的数据则覆盖)
|
||||
data:
|
||||
author: 齐天大圣
|
||||
sidebar: false
|
||||
```
|
||||
|
||||
比如你要操作的文件夹路径是`docs/《JS教程》专辑/第一章节`,你需要这样配置路径:
|
||||
```yaml
|
||||
path:
|
||||
- docs # 第一个成员必须是docs
|
||||
- 《JS教程》专辑
|
||||
- 第一章节
|
||||
```
|
||||
`path`数组的第一个成员必须是`docs`,如果你想操作`docs`底下除了首页之外所有的`.md`文件,只需保留第一个成员`docs`即可。
|
||||
|
||||
你想删除`article`字段:
|
||||
```yaml
|
||||
delete:
|
||||
- article
|
||||
```
|
||||
|
||||
你想为这个文件夹下的所有`.md`文件添加作者`author`和隐藏侧边栏`sidebar`:
|
||||
```yaml
|
||||
data:
|
||||
author: 齐天大圣
|
||||
sidebar: false
|
||||
```
|
||||
|
||||
最后,执行`npm run editFm`命令,为了防止误操作,会有一个询问过程:
|
||||
```sh
|
||||
npm run editFm
|
||||
? 批量操作frontmatter有修改数据的风险,确定要继续吗? (Y/n)
|
||||
...
|
||||
```
|
||||
@@ -1,84 +0,0 @@
|
||||
---
|
||||
title: 部署
|
||||
date: 2020-05-13 12:10:53
|
||||
permalink: /pages/0fc1d2
|
||||
article: false
|
||||
---
|
||||
|
||||
|
||||
::: warning
|
||||
目前作者使用的部署方式已改为 [vercel](https://vercel.com/),部署方法参考 [这里](https://zhuanlan.zhihu.com/p/347990778)。(2022.01.01)</br>
|
||||
更多 [部署方式](https://vuepress.vuejs.org/zh/guide/deploy.html#%E9%83%A8%E7%BD%B2)
|
||||
:::
|
||||
|
||||
本项目内置了两种`自动部署`脚本,用于一键部署到 GitHub Pages 或 国内访问速度更快的Coding Pages
|
||||
|
||||
## 1.使用deploy.sh脚本部署
|
||||
|
||||
第一步,修改`deploy.sh`脚本内的仓库地址为你的仓库,如有自定义域名则一并修改,没有则注释
|
||||
|
||||
```bash
|
||||
# 如果是发布到自定义域名
|
||||
echo 'xugaoyi.com' > CNAME
|
||||
|
||||
# 如果发布到 https://<USERNAME>.github.io
|
||||
# git push -f git@github.com:<USERNAME>/<USERNAME>.github.io.git master
|
||||
|
||||
# 如果发布到 https://<USERNAME>.github.io/<REPO>
|
||||
# git push -f git@github.com:<USERNAME>/<REPO>.git master:gh-pages
|
||||
```
|
||||
|
||||
第二步,一键部署命令
|
||||
|
||||
```bash
|
||||
npm run deploy
|
||||
```
|
||||
|
||||
> windows系统下使用bash命令窗
|
||||
|
||||
第三步,设置仓库的GitHub Pages。
|
||||
|
||||
> `deploy.sh`文件看 [这里](https://github.com/xugaoyi/vuepress-theme-vdoing/blob/master/deploy.sh)
|
||||
> 看不懂shell代码? 参考:[shell教程](https://ipcmen.com/)
|
||||
|
||||
|
||||
## 2. 使用GitHub Action自动持续集成
|
||||
|
||||
第一步,按照[官方文档](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line),生成一个github token (令牌)。
|
||||
|
||||
第二步,将这个密钥储存到当前仓库的`Settings/Secrets`里面。
|
||||
|
||||
> `Settings/Secrets`是储存秘密的环境变量的地方。环境变量的名字可以随便起,这里用的是`ACCESS_TOKEN`。如果你不用这个名字,`.github/workflows/ci.yml`脚本里的变量名也要跟着改。
|
||||
|
||||
第三步,push提交代码到GitHub仓库master分支。
|
||||
|
||||
第四步,设置仓库的GitHub Pages
|
||||
|
||||
> 参考 [GitHub Action实现自动部署静态博客](http://xugaoyi.com/pages/6b9d359ec5aa5019/)
|
||||
> `ci.yml`文件看 [这里](https://github.com/xugaoyi/blog/blob/master/.github/workflows/ci.yml)
|
||||
> 看不懂yaml代码? 参考:[yaml教程](https://xugaoyi.com/pages/4e8444e2d534d14f/)
|
||||
|
||||
|
||||
## 部署升级:同时部署到github和coding
|
||||
|
||||
为了让博客站能够让百度收录,因此我把博客部署到了coding。相关[文章](https://xugaoyi.com/pages/41f87d890d0a02af/)。
|
||||
|
||||
在原有部署方式上做了升级,主要修改代码文件有下面这两个:
|
||||
|
||||
|
||||
使用前先将[github token (令牌) ](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line)和[coding token (令牌)](https://dev.tencent.com/help/doc/account/access-token) 同时设置到github仓库的`Settings/Secrets`位置。
|
||||

|
||||
|
||||
### 部署方式有两种:
|
||||
|
||||
#### 1) 、使用如下命令一键部署到github和coding
|
||||
|
||||
```sh
|
||||
npm run deploy
|
||||
```
|
||||
|
||||
#### 2)、使用GitHub Action自动部署到github和coding
|
||||
|
||||
只需要push提交代码到github仓库master分支即可。
|
||||
|
||||
> 参考 [《GitHub Action实现自动部署静态博客》](http://xugaoyi.com/pages/6b9d359ec5aa5019/)
|
||||
@@ -1,30 +0,0 @@
|
||||
---
|
||||
title: 关于写文章和一级标题
|
||||
date: 2020-05-13 12:07:32
|
||||
permalink: /pages/9ae0bd
|
||||
article: false
|
||||
---
|
||||
|
||||
写文章时,大概的流程是这样的:
|
||||
|
||||
1.首先是创建`.md`文件,如果在结构化目录下则**需要**添加相应的序号,在碎片化博文目录`_posts`下**不需要**添加序号,文件名填写文章的标题。
|
||||
|
||||
2.如果你想手动指定文章的分类、标签,则需要在头部front matter填写相应的字段。一般情况下不需要再填写其他字段。不想手动指定分类和标签也可以省略这一步。
|
||||
``` yaml
|
||||
---
|
||||
categories:
|
||||
- 分类1
|
||||
tags:
|
||||
- 标签1
|
||||
- 标签2
|
||||
---
|
||||
```
|
||||
|
||||
3.**关于一级标题**,即markdown中的一级标题(`# 标题`),你可以写也可以不写,在页面中实际显示的文章标题是取自front matter中的`title`,然后把`.md`中的一级标题隐藏了。
|
||||
|
||||
> 这样做即可以保留本地文件的一级标题,也可以兼容线上页面的标题。
|
||||
> 同时,对于在新建`.md`文件时已经输入了一次标题在文件名,不想在文档中重复输入一次标题的,也是可以实现的。
|
||||
|
||||
4.**关于文章摘要**,你想在首先文章列表中显示摘要时可以在合适的位置添加一个`<!-- more -->`注释,参考:[添加摘要](/pages/1cc523/)
|
||||
|
||||
5.最后,就可以正式开始写作啦。
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
title: 关于博客搭建与管理
|
||||
date: 2020-05-13 12:12:33
|
||||
permalink: /pages/26997d
|
||||
article: false
|
||||
---
|
||||
|
||||
这里是我在搭建和管理博客过程中写的一些文章和小技巧。
|
||||
|
||||
1. 评论模块的搭建
|
||||
|
||||
[使用Gitalk实现静态博客无后台评论系统](https://xugaoyi.com/pages/1da0bf9a988eafe5/)
|
||||
|
||||
2. 自定义域名及解析,[详情](https://github.com/xugaoyi/vuepress-theme-vdoing/issues/326)
|
||||
|
||||
3. SEO相关
|
||||
|
||||
```js
|
||||
// config.js
|
||||
module.exports = {
|
||||
description: '填写网站描述', // 以 <meta> 标签渲染到页面html中
|
||||
head: [ // 注入到页面<head> 中的标签,[tagName, { attrName: attrValue }]
|
||||
['meta', { name: 'keywords', content: '填写关键字'}]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
4. 图床
|
||||
|
||||
[GitHub + jsDelivr + TinyPNG+ PicGo 打造稳定快速、高效免费图床](https://xugaoyi.com/pages/a5f73af5185fdf0a/)
|
||||
|
||||
5. 结合GitHub Actions开发的每天定时百度推送,加快收录
|
||||
|
||||
[GitHub Actions 定时运行代码:每天定时百度链接推送](https://xugaoyi.com/pages/f44d2f9ad04ab8d3/)
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
---
|
||||
title: 在线编辑和新增文章的方法
|
||||
date: 2020-05-19 11:17:58
|
||||
permalink: /pages/c5a54d
|
||||
article: false
|
||||
---
|
||||
::: warning 说明
|
||||
以下方法实现的前提是把博客源码上传到github仓库,并配置好 [GitHub Actions](https://github.com/features/actions) 自动部署。
|
||||
:::
|
||||
|
||||
### 在线编辑原有的文章
|
||||
首先,在`config.js`启用页面的编辑按钮,并配置好github仓库和`.md`文件所在根目录等,详见 [官方文档](https://vuepress.vuejs.org/zh/theme/default-theme-config.html#git-%E4%BB%93%E5%BA%93%E5%92%8C%E7%BC%96%E8%BE%91%E9%93%BE%E6%8E%A5)。
|
||||
|
||||
示例:
|
||||
```js
|
||||
// config.js
|
||||
module.exports = {
|
||||
themeConfig: {
|
||||
repo: 'xugaoyi/vuepress-theme-vdoing', // Github仓库地址
|
||||
docsDir: 'docs', // .md文件放在了docs目录下
|
||||
editLinks: true, // 启用编辑链接
|
||||
editLinkText: '编辑',
|
||||
}
|
||||
}
|
||||
```
|
||||
配置好之后,每个文章页面底下都会有一个编辑按钮,点击即可跳到github在线编辑,编辑完成后提交就会自动触发GitHub Actions自动部署。
|
||||
|
||||
### 在线新增文章
|
||||
在github博客源码仓库相应的文章目录下,新建`.md`文件,编辑好文章并提交后会触发GitHub Actions自动部署。
|
||||
|
||||
> 提示:当使用过在线编辑或新增后,下次在本地编辑之前先拉取代码。
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: 演示环境
|
||||
date: 2020-05-12 15:10:15
|
||||
permalink: /pages/db78e2
|
||||
article: false
|
||||
---
|
||||
|
||||
### 演示环境
|
||||
> 由于部分用户使用演示环境代理非法网站导致演示环境服务器被封禁,因此不再提供演示服务。
|
||||
|
||||
可使用分配好的游客license试用。服务器带宽较低,仅供学习使用!
|
||||
|
||||
管理后台地址:<a href="http://103.163.47.16:9527" target="_blank">http://103.163.47.16:9527</a>
|
||||
|
||||
游客账号:visitor/123456
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
title: 如何贡献
|
||||
date: 2023-03-29 23:34:07
|
||||
permalink: /pages/a3f096/
|
||||
---
|
||||
|
||||
# 🎋代码分支说明
|
||||
- dev:常规开发分支,日常的开发、Bug修复、提交PR都在此分支
|
||||
- feature/xxx:特征分支,一些试验性开发、提交PR都在此分支,xxx可自行取一个有意义的名称
|
||||
- release/xxx:发行版分支,作为阶段性成果、重大更新的版本固化分支。受保护,不允许任何提交。
|
||||
- master:主分支,定期同步最新代码,目前仅支持本人(傲世孤尘/雨韵诗泽)提交。
|
||||
|
||||
# ✊贡献的方式
|
||||
包括,但不限于以下形式:
|
||||
- 提交[issues](https://gitee.com/dromara/neutrino-proxy/issues)
|
||||
- 参与[issues](https://gitee.com/dromara/neutrino-proxy/issues)解决的必要性、解决方案的讨论、给出建设性的意见
|
||||
- 领取[issues](https://gitee.com/dromara/neutrino-proxy/issues) 并完成开发、提交PR
|
||||
- 为[本项目](https://gitee.com/dromara/neutrino-proxy)完善代码注释
|
||||
- 为[本项目](https://gitee.com/dromara/neutrino-proxy)增加测试代码
|
||||
- 为[本项目](https://gitee.com/dromara/neutrino-proxy)优化现有代码
|
||||
- 为[本项目](https://gitee.com/dromara/neutrino-proxy)优化操作体验
|
||||
- 通过公众号、博客、贴吧、论坛等形式分享/宣传中微子代理
|
||||
- 丰富项目文档,包括使用过程中踩过的坑、需要优化的点、具体问题的解决方法等
|
||||
|
||||
# 🧬贡献代码的步骤
|
||||
- 在Gitee上fork项目到自己的仓库
|
||||
- 将fork后的自己仓库的项目clone到本地
|
||||
- 拉取dev分支最新代码,并修改
|
||||
- commit并push到自己的仓库
|
||||
- 登录Gitee在的仓库上可以看到一个 pull request 按钮,点击它,填写一些说明信息,然后提交即可。
|
||||
- 等待合并
|
||||
|
||||
# 📐PR的规范
|
||||
> 你可能发现有的代码并不符合这个规范,但我们后续都会朝着这个方向迈进。后续我们会逐步全面规范化,请先保证新提交的代码满足以下要求:
|
||||
|
||||
- 注释完备,尤其每个新增的方法应按照Java文档规范标明方法说明、参数说明、返回值说明等信息,必要时请添加单元测试,如果愿意,也可以加上你的大名。
|
||||
- 原则上,不允许自行添加第三方依赖库。若确有必要,请先和项目负责人沟通达成一致。
|
||||
- 对现有代码的优化,请在注释中说明原因。
|
||||
- 如果涉及到数据库的变更,则至少需要做到以下绩点:
|
||||
- sqlite、mysql下均测试通过
|
||||
- 在`./neutrino-proxy-server/src/main/resource/sql`下维护更新对应的表结构sql文件
|
||||
- 对于新增的表,维护必要的初始化数据
|
||||
- 在对应的mysql、sqlite的update目录下,维护SQL变更记录,确保SQL执行测试通过
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
title: 2023年
|
||||
date: 2023-06-09 21:33:17
|
||||
permalink: /pages/cded59/
|
||||
---
|
||||
|
||||
| 日期 | 渠道 | 金额 |昵称| 备注 |
|
||||
|:-----------|:---|:-----|:-|:-----------------|
|
||||
|2023-10-11|微信红包|50|喜鸽小宝||
|
||||
|2023-10-10|微信红包|20|海洋||
|
||||
| 2023-07-31 |Gitee捐助| 50 |失败女神| 感谢您的开源项目! |
|
||||
| 2023-07-28 |微信转账| 50 |AdrianPteLtd.com-咨询| |
|
||||
| 2023-07-12 |微信红包| 100 |MaxKeyTop| 请大佬抽包烟 |
|
||||
| 2023-07-04 |微信红包| 50 |姫野永遠| |
|
||||
| 2023-06-29 |微信红包| 16.8 |迟迟🌱| 加油 |
|
||||
| 2023-06-27 |微信红包| 30 |Arno| |
|
||||
| 2023-06-25 |Gitee捐助| 10 |zhujue888| 感谢您的开源项目! |
|
||||
| 2023-06-16 |微信红包| 50 |小跟班| |
|
||||
| 2023-06-10 |Gitee捐助| 10 |失败女神| 感谢您的开源项目! |
|
||||
| 2023-06-09 |Gitee捐助| 50 |Admin| 感谢您的开源项目! |
|
||||
| 2023-06-09 |微信红包| 50 |李阳| 开源无限好 |
|
||||
| 2023-06-09 |Gitee捐助| 50 |罗宾| 感谢您的开源项目! |
|
||||
| 2023-06-07 |Gitee捐助| 50 |罗宾| 感谢您的开源项目! |
|
||||
| 2023-06-03 |Gitee捐助| 50 |Admin| 感谢您的开源项目! |
|
||||
| 2023-05-29 |微信红包| 20 |TYY| |
|
||||
| 2023-05-29 |Gitee捐助| 10 |笑看| 感谢您的开源项目! |
|
||||
| 2023-05-26 |微信红包| 50 |至少还有满天星光照耀你| |
|
||||
| 2023-02-23 |Gitee捐助| 10 |Yohanes| 感谢您的开源项目! |
|
||||
| 2023-02-23 |Gitee捐助| 20 |jam_lee| 感谢您的开源项目!希望能支持域名映射|
|
||||
| 2023-02-10 |Gitee捐助| 5 |实习两年半| 感谢您的开源项目! |
|
||||
| 2023-02-02 |Gitee捐助| 10 |阳光很暖| 感谢您的开源项目! |
|
||||
@@ -1,208 +0,0 @@
|
||||
---
|
||||
title: 资源
|
||||
date: 2020-05-12 15:10:15
|
||||
permalink: /pages/db78e2
|
||||
article: false
|
||||
---
|
||||
|
||||
## 插件推荐
|
||||
|
||||
* [vuepress-plugin-fulltext-search](https://github.com/leo-buneev/vuepress-plugin-fulltext-search) 全文搜索
|
||||
|
||||
* [vuepress-plugin-thirdparty-search](https://github.com/xugaoyi/vuepress-plugin-thirdparty-search) 可以添加第三方搜索链接的搜索框
|
||||
|
||||
* [vuepress-plugin-one-click-copy](https://www.npmjs.com/package/vuepress-plugin-one-click-copy) 代码块一键复制
|
||||
|
||||
* [vuepress-plugin-comment](https://github.com/dongyuanxin/vuepress-plugin-comment) 评论区
|
||||
|
||||
* [vuepress-plugin-vssue](https://vssue.js.org/) 评论区(单页)
|
||||
|
||||
* [vuepress-plugin-vssue-global](https://github.com/u2sb/vuepress-plugin-vssue-global) 评论区(全局)
|
||||
|
||||
* [vuepress-plugin-smplayer](https://github.com/u2sb/vuepress-plugin-smplayer) 播放器
|
||||
|
||||
* [vuepress-plugin-flowchart](https://www.npmjs.com/package/vuepress-plugin-flowchart) 流程图
|
||||
|
||||
* [vuepress-plugin-mathjax](https://www.npmjs.com/package/vuepress-plugin-mathjax) 数学公式
|
||||
|
||||
* [vuepress-plugin-tabs](https://www.npmjs.com/package/vuepress-plugin-tabs/) 选项卡
|
||||
|
||||
* [vuepress-plugin-element-ui](https://www.npmjs.com/package/vuepress-plugin-element-ui/) Element UI
|
||||
|
||||
* [花里胡哨的插件](https://moefyit.github.io/moefy-vuepress/) 鼠标点击特效、背景彩带、音乐播放器等花里胡哨的插件
|
||||
|
||||
**更多插件...**
|
||||
|
||||
- [Awesome VuePress](https://github.com/vuepressjs/awesome-vuepress)
|
||||
|
||||
- [在npm中搜索"vuepress–plugin"](https://www.npmjs.com/search?q=vuepress%E2%80%93plugin)
|
||||
|
||||
|
||||
## 社区优秀解决方案
|
||||
|
||||
### [1. 站点信息模块](https://notes.youngkbt.cn/about/website/info/)
|
||||
在首页添加`站点信息模块`,效果:
|
||||
|
||||

|
||||
|
||||
在文章页添加`文章字数`、`阅读时间`、`浏览量`,效果:
|
||||
|
||||

|
||||
|
||||
### [2. 私密文章功能](https://notes.youngkbt.cn/about/website/private/)
|
||||
|
||||
当大家想要「云端备份」文章到博客时,又不希望别人看到,该功能能满足你。
|
||||
|
||||
### [3. 首页大图模块](https://notes.youngkbt.cn/about/website/index-big-img/)
|
||||
|
||||
喜欢首页大图模式的朋友可以参考此教程。
|
||||
|
||||
### [4. 优雅的全文搜索方案](https://wiki.eryajf.net/pages/dfc792/#%E5%89%8D%E8%A8%80)
|
||||
|
||||
|
||||
|
||||
## 文章管理和发布
|
||||
|
||||
使用 [此插件 src-sy-post-publisher](https://github.com/terwer/src-sy-post-publisher) 发布[思源笔记](https://b3log.org/siyuan/)的文章到Vuepress等平台。
|
||||
|
||||
## 图标&配图
|
||||
|
||||
说明:以下图标**非主题内置**,你可以选择喜欢的图标右键保存或到图标库下载。更多图标:[阿里图标库](https://www.iconfont.cn/home/index)
|
||||
|
||||
### 静态图标
|
||||
<table class="icons-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<img src="/img/png/编程.png" width=60>
|
||||
<p class="name">编程</p>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<img src="/img/png/服务器.png" width=60>
|
||||
<p class="name">服务器</p>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<img src="/img/png/机器学习.png" width=60>
|
||||
<p class="name">机器学习</p>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<img src="/img/png/计算机网络.png" width=60>
|
||||
<p class="name">计算机网络</p>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<img src="/img/png/面向对象.png" width=60>
|
||||
<p class="name">面向对象</p>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<img src="/img/png/软件开发.png" width=60>
|
||||
<p class="name">软件开发</p>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<img src="/img/png/数据结构.png" width=60>
|
||||
<p class="name">数据结构</p>
|
||||
</td>
|
||||
</tr><tr></tr>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<img src="/img/png/数据库.png" width=60>
|
||||
<p class="name">数据库</p>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<img src="/img/png/思维导图.png" width=60>
|
||||
<p class="name">思维导图</p>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<img src="/img/png/算法.png" width=60>
|
||||
<p class="name">算法</p>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<img src="/img/png/网络技术.png" width=60>
|
||||
<p class="name">网络技术</p>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<img src="/img/png/系统.png" width=60>
|
||||
<p class="name">系统</p>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<img src="/img/png/系统分析.png" width=60>
|
||||
<p class="name">系统分析</p>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<img src="/img/png/项目管理.png" width=60>
|
||||
<p class="name">项目管理</p>
|
||||
</td>
|
||||
</tr><tr></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<style>
|
||||
.icons-table td{
|
||||
padding: 1em;
|
||||
}
|
||||
.icons-table p.name{
|
||||
font-size: 14px;
|
||||
margin: 10px 0 0 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
### 萌系图标
|
||||
|
||||
- [猫咪系列](https://www.iconfont.cn/collections/detail?spm=a313x.7781069.1998910419.dc64b3430&cid=37776)
|
||||
</br>
|
||||
- [数码宝贝](https://www.iconfont.cn/collections/detail?spm=a313x.7781069.1998910419.dc64b3430&cid=38124)
|
||||

|
||||
- [水果系列](https://www.iconfont.cn/collections/detail?spm=a313x.7781069.1998910419.dc64b3430&cid=38124)
|
||||

|
||||
|
||||
|
||||
### 动态图标
|
||||
<table class="icons-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<img src="/img/gif/猫1.gif" width=60>
|
||||
<p class="name">猫1</p>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<img src="/img/gif/猫2.gif" width=60>
|
||||
<p class="name">猫2</p>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<img src="/img/gif/猫3.gif" width=60>
|
||||
<p class="name">猫3</p>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<img src="/img/gif/猫4.gif" width=60>
|
||||
<p class="name">猫4</p>
|
||||
</td>
|
||||
</tr><tr></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### 插画
|
||||
[免费插画](https://undraw.co/illustrations)</br>
|
||||
[2D/3D/手绘插画](https://storytale.io/)</br>
|
||||
[阿里插画库](https://www.iconfont.cn/illustrations/index)</br>
|
||||
|
||||
### 配图
|
||||
[可画-文章配图](https://www.canva.cn/)
|
||||
|
||||
### Logo
|
||||
[logo生成1](https://www.designevo.com/logo-maker/)</br>
|
||||
[logo生成2](https://instantlogodesign.com/)
|
||||
|
||||
### Emoji表情
|
||||
[Emoji百科](https://emojipedia.org/)
|
||||
|
||||
::: tip 小技巧
|
||||
在任意输入框快速打开emoji表情方法:</br>
|
||||
Windows系统下按<kbd>Win</kbd> + <kbd>.</kbd></br>
|
||||
Mac系统下按<kbd>Control</kbd> + <kbd>Command</kbd> + <kbd>空格</kbd>
|
||||
:::
|
||||
|
||||
## 共享资源
|
||||
如果您有不错的资源,欢迎在 [资源分享区](https://github.com/xugaoyi/vuepress-theme-vdoing/discussions/categories/%E8%B5%84%E6%BA%90%E5%88%86%E4%BA%AB) 留言。
|
||||
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
<a href="http://apifox.cn/a103xugaoyi" target="_blank"><img src="/img/Apifox-860x320.png" alt="npm" class="no-zoom" style="width: 100%;border-radius: 2px;"></a>
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
title: 最近更新
|
||||
date: 2020-05-25 12:01:52
|
||||
permalink: /pages/9cc27d
|
||||
# sidebar: false
|
||||
article: false
|
||||
---
|
||||
|
||||
# TODO
|
||||
|
||||
### 1.x剩余规划
|
||||
- [ ] 参考鹊桥,增加对域名的支持
|
||||
- [ ] 官网上线
|
||||
|
||||
#### Bug
|
||||
- windows环境下直接运行发布版的jar包,日志输出乱码
|
||||
- 代理mysql时,使用未开启远程访问的账号走代理访问mysql,代理客户端出现断开现象
|
||||
|
||||
### 2.x规划
|
||||
- 插件开发
|
||||
- [ ] neutrino-proxy-solon-plugin
|
||||
- [ ] neutrino-proxy-jetbrains-plugin
|
||||
- [ ] neutrino-proxy-starter (SpringBoot)
|
||||
- 基础优化
|
||||
- [ ] 支持批量端口映射
|
||||
- [ ] 代理协议规范化,方便后续更好扩展、支持不同语言客户端接入
|
||||
- 监控运维
|
||||
- [ ] 监控服务端状态
|
||||
- [ ] 支持调整服务端端口、证书
|
||||
- [ ] 服务端版本、协议版本、客户端版本管理
|
||||
- [ ] 服务端日志
|
||||
- client+计划启动
|
||||
- [ ] 安卓客户端
|
||||
- [ ] 基于electron-egg的多平台客户端
|
||||
|
||||
### 3.x规划
|
||||
- [ ] 支持针对用户限速、限流
|
||||
- [ ] 支持P2P穿透
|
||||
- [ ] 支持原生编译
|
||||
@@ -1,48 +0,0 @@
|
||||
---
|
||||
title: 问答
|
||||
date: 2020-05-25 12:01:52
|
||||
permalink: /pages/9cc27d
|
||||
# sidebar: false
|
||||
article: false
|
||||
---
|
||||
|
||||
## 我是一个小白,想使用这个主题搭建博客(知识库)需要做哪些工作?
|
||||
答:使用这个主题前需要你掌握下面这些知识:
|
||||
* 掌握 [markdown](https://xugaoyi.com/pages/ad247c4332211551/)、[yaml](https://xugaoyi.com/pages/4e8444e2d534d14f/) 语法
|
||||
* 会使用终端(命令行),会使用 git
|
||||
* 会阅读文档、搜索文档
|
||||
* 会[VuePress](https://vuepress.vuejs.org/zh/)的基本使用和默认主题的基本配置
|
||||
|
||||
以上知识都掌握之后,再查看本文档。你也可以运行我的主题项目,一边看代码,一边查看文档。主题项目内写了比较多的注释代码,方便你的学习和使用。
|
||||
|
||||
## clone 项目后需要修改哪些地方?
|
||||
答:大致的修改流程是这样的:
|
||||
1. 首先让项目正常的跑起来
|
||||
2. 根据需求构建和替换 [docs/<结构化目录>](/pages/2f674a/) 的目录及内容
|
||||
3. 根据需求修改 [config.js](/pages/a20ce8/) 配置
|
||||
4. 修改 [首页配置](/pages/f14bdb/)
|
||||
5. 修改 [主题颜色和样式](/pages/f51918/)(如果你想修改的话)
|
||||
|
||||
|
||||
## 可减少项目冗余的地方有哪些?
|
||||
答:在完成项目的搭建之后,对于没有使用到的一些文件和代码,可以看情况删减,可删减的地方有:
|
||||
* 卸载`config.js`中未使用的插件依赖包
|
||||
* 参照 [目录结构](/pages/2f674a/),删除未使用到的 `可选` 文件
|
||||
* 删除`.vusepress`目录内未使用到的 `可选` 文件
|
||||
* 删除各文件内的注释代码
|
||||
|
||||
## 我可以不使用永久链接吗?
|
||||
答:**不可以**。当你没有在front matter指定永久链接时,主题会[自动生成永久链接](/pages/088c16/)到front matter,你可以修改永久链接的值。
|
||||
使用永久链接是出于以下几点考虑:
|
||||
1. 在`config.js`配置nav时使用永久链接,就不会因为文件的路径或名称的改变而改变。
|
||||
2. 对于博客而言,当别人收藏了你的文章,在未来的时间里都可以通过永久链接来访问到。
|
||||
3. 主题中的`目录页`需要通过永久链接来访问文章。
|
||||
|
||||
## 是否支持国际化?
|
||||
答:暂不支持。目前的国际化方案,只能是使用不同项目来处理,比如:一个项目是中文,一个项目是英文,在导航中添加中英文站点的链接。
|
||||
|
||||
## 打包后在本地预览怎么没有样式?
|
||||
答:因vuepress打包后的CSS、JS等资源链接采用绝对路径引入,在本地预览时的引入链接是不正确的,部署到服务器即可正常引入和预览。如果你确实需要本地预览,可以使用[vuepress-plugin-serve](https://vuepress.github.io/zh/plugins/serve/)插件,使用方法看相应文档。
|
||||
|
||||
## 在`build`时出现错误,但是在`dev`时是正常的
|
||||
答:这种情况多半是因为你在`created`或`beforeCreate`生命周期调用了浏览器API的原因导致,详情查看:[浏览器的 API 访问限制](https://vuepress.vuejs.org/zh/guide/using-vue.html#%E6%B5%8F%E8%A7%88%E5%99%A8%E7%9A%84-api-%E8%AE%BF%E9%97%AE%E9%99%90%E5%88%B6)
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: 如何贡献
|
||||
date: 2023-03-29 12:55:44
|
||||
permalink: /pages/69a632/
|
||||
---
|
||||
# 🎋代码分支说明
|
||||
- dev:常规开发分支,日常的开发、Bug修复、提交PR都在此分支
|
||||
- feature/xxx:特征分支,一些试验性开发、提交PR都在此分支,xxx可自行取一个有意义的名称
|
||||
- release/xxx:发行版分支,作为阶段性成果、重大更新的版本固化分支。受保护,不允许任何提交。
|
||||
- master:主分支,定期同步最新代码,目前仅支持本人(傲世孤尘/雨韵诗泽)提交。
|
||||
|
||||
|
||||
# ✊贡献的方式
|
||||
包括,但不限于以下形式:
|
||||
- 提交[issues](https://gitee.com/dromara/neutrino-proxy/issues)
|
||||
- 参与[issues](https://gitee.com/dromara/neutrino-proxy/issues)解决的必要性、解决方案的讨论、给出建设性的意见
|
||||
- 领取[issues](https://gitee.com/dromara/neutrino-proxy/issues) 并完成开发、提交PR
|
||||
- 为[本项目](https://gitee.com/dromara/neutrino-proxy)完善代码注释
|
||||
- 为[本项目](https://gitee.com/dromara/neutrino-proxy)增加测试代码
|
||||
- 为[本项目](https://gitee.com/dromara/neutrino-proxy)优化现有代码
|
||||
- 为[本项目](https://gitee.com/dromara/neutrino-proxy)优化操作体验
|
||||
- 通过公众号、博客、贴吧、论坛等形式分享/宣传中微子代理
|
||||
- 丰富项目文档,包括使用过程中踩过的坑、需要优化的点、具体问题的解决方法等
|
||||
|
||||
# 🧬贡献代码的步骤
|
||||
- 在Gitee或者Github上fork项目到自己的repo,fork,一定要把项目fork一份。
|
||||
- 把fork过去的项目也就是你的项目clone到你的本地
|
||||
- 同步feature/1.7.1最新代码
|
||||
- 修改代码
|
||||
- 开发完成后,不忙着提PR,再拉一遍最新代码,如果有冲突、解决冲突
|
||||
- commit后push到自己的库
|
||||
- 登录Gitee在你首页可以看到一个 pull request 按钮,点击它,填写一些说明信息,然后提交即可。
|
||||
- 等待维护者合并
|
||||
|
||||
# 📐PR的规范
|
||||
> 你可能发现有的代码并不符合这个规范,但我们后续都会朝着这个方向迈进。后续我们会逐步全面规范化,请先保证新提交的代码满足以下要求:
|
||||
|
||||
- 注释完备,尤其每个新增的方法应按照Java文档规范标明方法说明、参数说明、返回值说明等信息,必要时请添加单元测试,如果愿意,也可以加上你的大名。
|
||||
- 原则上,不允许自行添加第三方依赖库。若确有必要,请先和项目负责人沟通达成一致。
|
||||
- 对现有代码的优化,请在注释中说明原因。
|
||||
- 如果涉及到数据库的变更,则至少需要做到以下绩点:
|
||||
- sqlite、mysql下均测试通过
|
||||
- 在`./neutrino-proxy-server/src/main/resource/sql`下维护更新对应的表结构sql文件
|
||||
- 对于新增的表,维护必要的初始化数据
|
||||
- 在对应的mysql、sqlite的update目录下,维护SQL变更记录,确保SQL执行测试通过
|
||||
@@ -8,12 +8,15 @@ article: false
|
||||
|
||||
如果您正在使用这个项目并感觉良好,或者是想支持我继续开发,您可以通过如下`任意`方式支持我:
|
||||
|
||||
1. Star并分享 [vuepress-theme-vdoing](https://github.com/xugaoyi/vuepress-theme-vdoing) :rocket:
|
||||
1. Star并分享 [neutrino-proxy](https://gitee.com/dromara/neutrino-proxy) :rocket:
|
||||
谢谢! :heart:
|
||||
|
||||
<!--
|
||||
2. 轻轻点击一次上面的广告 :D
|
||||
2. 进入下面赞助商花十几秒钟扫码注册一下, 赞助商就会代您赞助一笔小钱。 :D
|
||||
3. 通过以下二维码 一次性捐款,打赏作者一杯茶。:tea:
|
||||
|
||||
谢谢! :heart:
|
||||
|
||||
|
||||
| 微信赞赏 | 微信 | 支付宝 |
|
||||
| :---: | :---: | :---: |
|
||||
@@ -22,7 +25,8 @@ article: false
|
||||
## 赞助商
|
||||
> 如果您想支持本项目,奈何囊中羞涩,您可以花十几秒点击下方赞助商进入注册(只需微信扫码就可以),赞助商就会代您赞助一笔小钱。
|
||||
|
||||
<!-- <a href="http://apifox.cn/a103xugaoyi" target="_blank"><img src="/img/Apifox-860x320.png" class="no-zoom" style="width: 400px;border-radius: 2px;"></a> -->
|
||||
|
||||
<a href="http://apifox.cn/a103xugaoyi" target="_blank"><img src="/img/Apifox-860x320.png" class="no-zoom" style="width: 400px;border-radius: 2px;"></a>
|
||||
<a href="http://apifox.cn/a103xugaoyi" target="_blank"><img src="https://cdn.staticaly.com/gh/xugaoyi/blog-gitalk-comment@master/img/431669861564_.2470ykdcpbds.jpg" class="no-zoom" style="border-radius: 2px;"></a>
|
||||
|
||||
|
||||
@@ -47,5 +51,6 @@ article: false
|
||||
|
||||
<img :src="$withBase('/img/qrcode/gzh.jpg')" style="width:180px;" />
|
||||
|
||||
-->
|
||||
## 致谢
|
||||
感谢给予支持的朋友,您的支持是我前进的动力 🎉
|
||||
@@ -1,26 +1,38 @@
|
||||
---
|
||||
home: true
|
||||
|
||||
heroImage: /img/logo.png
|
||||
heroImage: /img/logo-0.75x.png
|
||||
heroText: Neutrino-Proxy
|
||||
tagline: 🚀一个基于 netty 的、开源的 java 内网穿透项目
|
||||
tagline: 🚀一个基于Netty的开源内网穿透神器
|
||||
actionText: 开始使用 →
|
||||
actionLink: /pages/a2f161/
|
||||
actionLink: /pages/793dcb/
|
||||
bannerBg: none # auto => 网格纹背景(有bodyBgImg时无背景),默认 | none => 无 | '大图地址' | background: 自定义背景样式 提示:如发现文本颜色不适应你的背景时可以到palette.styl修改$bannerTextColor变量
|
||||
|
||||
features: # 可选的
|
||||
- title: 安全
|
||||
details: 内外网传输数据。
|
||||
- title: 快速
|
||||
details: 快速部署。
|
||||
- title: 穿透力强
|
||||
details: 穿透力。
|
||||
- title: 流量监控
|
||||
details: 首页图表、报表管理多维度流量监控。全方位掌握实时、历史代理数据。
|
||||
- title: 域名映射
|
||||
details: HTTP代理绑定子域名,开发调试三方回调更方便。
|
||||
- title: 用户/License
|
||||
details: 支持多用户、多客户端使用。后台禁用实时生效。
|
||||
- title: 端口池
|
||||
details: 对外端口统一管理,支持用户、License独占端口。
|
||||
- title: 端口映射
|
||||
details: 新增、编辑、删除、禁用实时生效。
|
||||
- title: Docker
|
||||
details: 服务端支持Docker一键部署。
|
||||
- title: 隧道SSL加密
|
||||
details: 隧道通信支持SSL,保护您的数据安全
|
||||
- title: HTTPS
|
||||
details: 支持HTTPS
|
||||
- title: 多客户端支持
|
||||
details: 暂未支持
|
||||
|
||||
# 文章列表显示方式: detailed 默认,显示详细版文章列表(包括作者、分类、标签、摘要、分页等)| simple => 显示简约版文章列表(仅标题和日期)| none 不显示文章列表
|
||||
postList: none
|
||||
---
|
||||
<p align="center">
|
||||
<a class="become-sponsor" href="/pages/1b12ed/">支持这个项目</a>
|
||||
<a class="become-sponsor" href="https://gitee.com/dromara/neutrino-proxy">支持这个项目</a>
|
||||
</p>
|
||||
|
||||
<style>
|
||||
@@ -36,9 +48,7 @@ postList: none
|
||||
|
||||
<br/>
|
||||
<p align="center">
|
||||
<a href="https://www.npmjs.com/package/vuepress-theme-vdoing" target="_blank"><img src="https://img.shields.io/npm/v/vuepress-theme-vdoing" alt="npm" class="no-zoom"></a>
|
||||
<a href="https://www.npmjs.com/package/vuepress-theme-vdoing" target="_blank"><img src="https://img.shields.io/npm/dt/vuepress-theme-vdoing" alt="npm" class="no-zoom"></a>
|
||||
<a href="https://gitee.com/dromara/neutrino-proxy" target="_blank"><img src='https://gitee.com/dromara/neutrino-proxy/badge/star.svg?theme=dark' alt='star' class="no-zoom"></img></a>
|
||||
<a href="https://gitee.com/dromara/neutrino-proxy" target="_blank"><img src='https://gitee.com/dromara/neutrino-proxy/badge/star.svg?theme=dark' alt='star' class="no-zoom"></a>
|
||||
<a href="https://gitee.com/dromara/neutrino-proxy" target="_blank"><img src='https://gitee.com/dromara/neutrino-proxy/badge/fork.svg?theme=dark' alt='forks' class="no-zoom"></a>
|
||||
</p>
|
||||
|
||||
@@ -79,7 +89,7 @@ postList: none
|
||||
:::
|
||||
-->
|
||||
<br/>
|
||||
|
||||
<!--
|
||||
## 🎉上新推荐
|
||||
* `v1.7.0`:项目重构、底层框架更换为Solon。
|
||||
* `v1.6.4`:代理使用细节优化。
|
||||
@@ -93,6 +103,7 @@ postList: none
|
||||
更多上新请查阅:[**更新日志**](https://gitee.com/dromara/neutrino-proxy/releases)
|
||||
|
||||
<br/>
|
||||
-->
|
||||
|
||||
<!-- ## ⚡️未来...
|
||||
* `v1.5.0`:新增配置文件对TypeScript的支持,参考[config.ts](https://github.com/xugaoyi/vuepress-theme-vdoing/blob/master/docs/.vuepress/config.ts)。新增[标题标记](/pages/3216b0/#titletag)。
|
||||
@@ -124,118 +135,205 @@ postList: none
|
||||
你也可以在Issue查看别人提的问题和给出解决方案。
|
||||
|
||||
或者加入我们的交流群:
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<img src="https://cdn.staticaly.com/gh/xugaoyi/blog-gitalk-comment@master/img/0.4pp7r95mdai0.jpeg" class="no-zoom" style="width:120px;margin: 10px;">
|
||||
<p>vdoing微信群(添加我微信备注"进群")</p>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<img :src="$withBase('/img/qrcode/qqq.webp')" alt="群号: 694387113" class="no-zoom" style="width:120px;margin: 10px;">
|
||||
<p>vdoing QQ群: 694387113</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<br/>
|
||||
|
||||
## 🧬贡献代码的步骤
|
||||
|
||||
贡献代码注意事项:
|
||||
1. 在Gitee或者Github上fork项目到自己的repo,fork,一定要把项目fork一份。
|
||||
2. 把fork过去的项目也就是你的项目clone到你的本地
|
||||
3. 同步feature/1.7.1最新代码
|
||||
4. 修改代码
|
||||
5. 开发完成后,不忙着提PR,再拉一遍最新代码,如果有冲突、解决冲突
|
||||
6. commit后push到自己的库
|
||||
7. 登录Gitee在你首页可以看到一个 pull request 按钮,点击它,填写一些说明信息,然后提交即可。
|
||||
8. 等待维护者合并
|
||||
|
||||
<div align="center">
|
||||
<img :src="$withBase('/img/qrcode/wxq.png')" class="no-zoom" style="width:200px;margin: 10px;">
|
||||
<p>中微子 微信群(添加我微信备注"进群")</p>
|
||||
</div>
|
||||
<br/>
|
||||
|
||||
## 📚 Dromara 成员项目
|
||||
<p align="center">
|
||||
<a href="https://gitee.com/dromara/TLog" target="_blank">
|
||||
<img src="https://oss.dev33.cn/sa-token/link/tlog2.png" alt="一个轻量级的分布式日志标记追踪神器,10分钟即可接入,自动对日志打标签完成微服务的链路追踪" width="15%">
|
||||
<h2 id="🌈特别赞助"><a href="#🌈特别赞助" class="header-anchor">#</a> 🌈特别赞助</h2>
|
||||
<!-- <p align="center">等待有缘人赞助</p> -->
|
||||
|
||||
<div class="cardListContainer">
|
||||
<div class="card-list">
|
||||
<a href="https://www.xigexb.com" target="_blank" class="card-item row-3" style="background-color:#102863;--random-color:#102863;color:#FFFFFF;">
|
||||
<img src="img/sponsor/xigexiaobao.jpg" class="no-zoom">
|
||||
<div><p class="name">喜鸽小宝</p> <p class="desc">一个爱好写代码的同学</p></div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!--
|
||||
<div class="cardListContainer">
|
||||
<div class="card-list">
|
||||
<a href="https://datayi.cn/w/nPN4JJe9" target="_blank" class="card-item row-3" style="background-color:#102863;--random-color:#102863;color:#FFFFFF;">
|
||||
<img src="/images/friends/ad/postcat.png" class="no-zoom">
|
||||
<div><p class="name">Postcat</p> <p class="desc">开源 API 管理工具,接口文档、接口测试、Mock...</p></div>
|
||||
</a>
|
||||
<a href="https://gitee.com/dromara/MaxKey" target="_blank" class="card-item row-3" style="background-color:#9ed3e5;--random-color:#9ed3e5;color:#1f2328;">
|
||||
<img src="/images/friends/ad/maxkey.png" class="no-zoom">
|
||||
<div><p class="name">MaxKey</p> <p class="desc"><br>业界领先的单点登录</p></div>
|
||||
</a>
|
||||
<a href="https://www.xiaonuo.vip" target="_blank" class="card-item row-3" style="background-color:#1373CC;--random-color:#1373CC;color:#ffffff;">
|
||||
<img src="/images/friends/ad/snowy.png" class="no-zoom">
|
||||
<div><p class="name">Snowy</p> <p class="desc">国内首个国密前后端分离快速开发平台,中国式工作流、独家双模式租户</p></div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="language-yaml line-numbers-mode"><pre class="language-yaml codecopy-enabled">
|
||||
<code>
|
||||
<span class="token punctuation">-</span>
|
||||
<span class="token key atrule">name</span>
|
||||
<span class="token punctuation">:</span> Postcat
|
||||
<span class="token key atrule">desc</span>
|
||||
<span class="token punctuation">:</span>
|
||||
开源 API 管理工具,接口文档、接口测试、Mock<span class="token punctuation">...</span>
|
||||
<span class="token key atrule">avatar</span><span class="token punctuation">:</span> /images/friends/ad/postcat.png
|
||||
<span class="token key atrule">link</span><span class="token punctuation">:</span> https<span class="token punctuation">:</span>//datayi.cn/w/nPN4JJe9
|
||||
<span class="token key atrule">bgColor</span><span class="token punctuation">:</span> <span class="token string">'#102863'</span>
|
||||
<span class="token key atrule">textColor</span><span class="token punctuation">:</span> <span class="token string">'#FFFFFF'</span>
|
||||
<span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> MaxKey
|
||||
<span class="token key atrule">desc</span><span class="token punctuation">:</span> </br<span class="token punctuation">></span>业界领先的单点登录
|
||||
<span class="token key atrule">avatar</span><span class="token punctuation">:</span> /images/friends/ad/maxkey.png
|
||||
<span class="token key atrule">link</span><span class="token punctuation">:</span> https<span class="token punctuation">:</span>//gitee.com/dromara/MaxKey
|
||||
<span class="token key atrule">bgColor</span><span class="token punctuation">:</span> <span class="token string">'#9ed3e5'</span>
|
||||
<span class="token key atrule">textColor</span><span class="token punctuation">:</span> <span class="token string">'#1f2328'</span>
|
||||
<span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Snowy
|
||||
<span class="token key atrule">desc</span><span class="token punctuation">:</span> 国内首个国密前后端分离快速开发平台,中国式工作流、独家双模式租户
|
||||
<span class="token key atrule">avatar</span><span class="token punctuation">:</span> /images/friends/ad/snowy.png
|
||||
<span class="token key atrule">link</span><span class="token punctuation">:</span> https<span class="token punctuation">:</span>//www.xiaonuo.vip
|
||||
<span class="token key atrule">bgColor</span><span class="token punctuation">:</span> <span class="token string">'#1373CC'</span>
|
||||
<span class="token key atrule">textColor</span><span class="token punctuation">:</span> <span class="token string">'#ffffff'</span>
|
||||
</code><i class="code-copy" title="Copy to clipboard"><svg style="color:#aaa;font-size:14px" t="1572422231464" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3201" width="14" height="14"><path d="M866.461538 39.384615H354.461538c-43.323077 0-78.769231 35.446154-78.76923 78.769231v39.384616h472.615384c43.323077 0 78.769231 35.446154 78.769231 78.76923v551.384616h39.384615c43.323077 0 78.769231-35.446154 78.769231-78.769231V118.153846c0-43.323077-35.446154-78.769231-78.769231-78.769231z m-118.153846 275.692308c0-43.323077-35.446154-78.769231-78.76923-78.769231H157.538462c-43.323077 0-78.769231 35.446154-78.769231 78.769231v590.769231c0 43.323077 35.446154 78.769231 78.769231 78.769231h512c43.323077 0 78.769231-35.446154 78.76923-78.769231V315.076923z m-354.461538 137.846154c0 11.815385-7.876923 19.692308-19.692308 19.692308h-157.538461c-11.815385 0-19.692308-7.876923-19.692308-19.692308v-39.384615c0-11.815385 7.876923-19.692308 19.692308-19.692308h157.538461c11.815385 0 19.692308 7.876923 19.692308 19.692308v39.384615z m157.538461 315.076923c0 11.815385-7.876923 19.692308-19.692307 19.692308H216.615385c-11.815385 0-19.692308-7.876923-19.692308-19.692308v-39.384615c0-11.815385 7.876923-19.692308 19.692308-19.692308h315.076923c11.815385 0 19.692308 7.876923 19.692307 19.692308v39.384615z m78.769231-157.538462c0 11.815385-7.876923 19.692308-19.692308 19.692308H216.615385c-11.815385 0-19.692308-7.876923-19.692308-19.692308v-39.384615c0-11.815385 7.876923-19.692308 19.692308-19.692308h393.846153c11.815385 0 19.692308 7.876923 19.692308 19.692308v39.384615z" p-id="3202"></path></svg></i></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br><span class="line-number">15</span><br><span class="line-number">16</span><br><span class="line-number">17</span><br><span class="line-number">18</span><br></div>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
|
||||
<h2 id="🤝-dromara-组织项目"><a href="#🤝-dromara-组织项目" class="header-anchor">#</a> 🤝 dromara 组织项目</h2>
|
||||
<p align="center"><b><a href="https://dromara.org/zh/projects/?from=neutrino-proxy" target="_blank">为往圣继绝学,一个人或许能走的更快,但一群人会走的更远。</a></b></p>
|
||||
<p>
|
||||
<a href="https://gitee.com/dromara/TLog?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/tlog.png" msg="一个轻量级的分布式日志标记追踪神器,10分钟即可接入,自动对日志打标签完成微服务的链路追踪">
|
||||
</a>
|
||||
<a href="https://gitee.com/dromara/liteFlow" target="_blank">
|
||||
<img src="https://oss.dev33.cn/sa-token/link/liteflow.png" alt="轻量,快速,稳定,可编排的组件式流程引擎" width="15%">
|
||||
<a href="https://gitee.com/dromara/liteFlow?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/liteflow.png" msg="轻量,快速,稳定,可编排的组件式流程引擎">
|
||||
</a>
|
||||
<a href="https://hutool.cn/" target="_blank">
|
||||
<img src="https://oss.dev33.cn/sa-token/link/hutool.jpg" alt="小而全的Java工具类库,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。" width="15%">
|
||||
<a href="https://hutool.cn/?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/hutool.jpg" msg="🍬小而全的Java工具类库,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。">
|
||||
</a>
|
||||
<a href="https://sa-token.dev33.cn/" target="_blank">
|
||||
<img src="https://oss.dev33.cn/sa-token/link/sa-token.png" alt="一个轻量级 java 权限认证框架,让鉴权变得简单、优雅!" width="15%">
|
||||
<a href="https://sa-token.cc/?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/sa-token.png" msg="一个轻量级 java 权限认证框架,让鉴权变得简单、优雅!">
|
||||
</a>
|
||||
<a href="https://gitee.com/dromara/hmily" target="_blank">
|
||||
<img src="https://oss.dev33.cn/sa-token/link/hmily.png" alt="高性能一站式分布式事务解决方案。" width="15%">
|
||||
<a href="https://gitee.com/dromara/hmily?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/hmily.png" msg="高性能一站式分布式事务解决方案。">
|
||||
</a>
|
||||
<a href="https://gitee.com/dromara/Raincat" target="_blank">
|
||||
<img src="https://oss.dev33.cn/sa-token/link/raincat.png" alt="强一致性分布式事务解决方案。" width="15%">
|
||||
<a href="https://gitee.com/dromara/Raincat?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/raincat.png" msg="强一致性分布式事务解决方案。">
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://gitee.com/dromara/myth" target="_blank">
|
||||
<img src="https://oss.dev33.cn/sa-token/link/myth.png" alt="可靠消息分布式事务解决方案。" width="15%">
|
||||
<a href="https://gitee.com/dromara/myth?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/myth.png" msg="可靠消息分布式事务解决方案。">
|
||||
</a>
|
||||
<a href="https://cubic.jiagoujishu.com/" target="_blank">
|
||||
<img src="https://oss.dev33.cn/sa-token/link/cubic.png" alt="一站式问题定位平台,以agent的方式无侵入接入应用,完整集成arthas功能模块,致力于应用级监控,帮助开发人员快速定位问题" width="15%">
|
||||
<a href="https://cubic.jiagoujishu.com/?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/cubic.png" msg="一站式问题定位平台,以agent的方式无侵入接入应用,完整集成arthas功能模块,致力于应用级监控,帮助开发人员快速定位问题">
|
||||
</a>
|
||||
<a href="https://maxkey.top/" target="_blank">
|
||||
<img src="https://oss.dev33.cn/sa-token/link/maxkey.png" alt="业界领先的身份管理和认证产品" width="15%">
|
||||
<a href="https://maxkey.top/?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/maxkey.png" msg="业界领先的身份管理和认证产品">
|
||||
</a>
|
||||
<a href="http://forest.dtflyx.com/" target="_blank">
|
||||
<img src="https://oss.dev33.cn/sa-token/link/forest-logo.png" alt="Forest能够帮助您使用更简单的方式编写Java的HTTP客户端" width="15%">
|
||||
<a href="http://forest.dtflyx.com/?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/forest-logo.png" msg="Forest能够帮助您使用更简单的方式编写Java的HTTP客户端" nf="">
|
||||
</a>
|
||||
<a href="https://jpom.io/" target="_blank">
|
||||
<img src="https://oss.dev33.cn/sa-token/link/jpom.png" alt="一款简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件" width="15%">
|
||||
<a href="https://jpom.top/?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/jpom.svg" msg="一款简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件">
|
||||
</a>
|
||||
<a href="https://su.usthe.com/" target="_blank">
|
||||
<img src="https://oss.dev33.cn/sa-token/link/sureness.png" alt="面向 REST API 的高性能认证鉴权框架" width="15%">
|
||||
<a href="https://su.usthe.com/?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/sureness.png" msg="面向 REST API 的高性能认证鉴权框架">
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://easy-es.cn/" target="_blank">
|
||||
<img src="https://oss.dev33.cn/sa-token/link/easy-es2.png" alt="傻瓜级ElasticSearch搜索引擎ORM框架" width="15%">
|
||||
<a href="https://easy-es.cn/?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/easy-es2.png" msg="🚀傻瓜级ElasticSearch搜索引擎ORM框架">
|
||||
</a>
|
||||
<a href="https://gitee.com/dromara/northstar" target="_blank">
|
||||
<img src="https://oss.dev33.cn/sa-token/link/northstar_logo.png" alt="Northstar盈富量化交易平台" width="15%">
|
||||
<a href="https://gitee.com/dromara/northstar?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/northstar_logo.png" msg="Northstar盈富量化交易平台">
|
||||
</a>
|
||||
<a href="https://hertzbeat.com/" target="_blank">
|
||||
<img src="https://oss.dev33.cn/sa-token/link/hertzbeat_brand.jpg" alt="易用友好的云监控系统" width="15%">
|
||||
<a href="https://hertzbeat.com/?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/hertzbeat-brand.svg" msg="易用友好的云监控系统">
|
||||
</a>
|
||||
<a href="https://plugins.sheng90.wang/fast-request/" target="_blank">
|
||||
<img src="https://oss.dev33.cn/sa-token/link/fast-request.gif" alt="Idea 版 Postman,为简化调试API而生" width="15%">
|
||||
<a href="https://dromara.gitee.io/fast-request/?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/fast-request.gif" msg="Idea 版 Postman,为简化调试API而生">
|
||||
</a>
|
||||
<a href="https://www.jeesuite.com/" target="_blank">
|
||||
<img src="https://oss.dev33.cn/sa-token/link/mendmix.png" alt="开源分布式云原生架构一站式解决方案" width="15%">
|
||||
<a href="https://www.jeesuite.com/?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/mendmix.png" msg="开源分布式云原生架构一站式解决方案">
|
||||
</a>
|
||||
<a href="https://gitee.com/dromara/koalas-rpc" target="_blank">
|
||||
<img src="https://oss.dev33.cn/sa-token/link/koalas-rpc2.png" alt="企业生产级百亿日PV高可用可拓展的RPC框架。" width="15%">
|
||||
<a href="https://gitee.com/dromara/koalas-rpc?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/koalas-rpc2.png" msg="企业生产级百亿日PV高可用可拓展的RPC框架。">
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://async.sizegang.cn/" target="_blank">
|
||||
<img src="https://oss.dev33.cn/sa-token/link/gobrs-async.png" alt="配置极简功能强大的异步任务动态编排框架" width="15%">
|
||||
<a href="https://async.sizegang.cn/?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/gobrs-async.png" msg="🔥 配置极简功能强大的异步任务动态编排框架">
|
||||
</a>
|
||||
<a href="https://dynamictp.cn/?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/dynamic-tp.png" msg="🔥🔥🔥 基于配置中心的轻量级动态可监控线程池">
|
||||
</a>
|
||||
<a href="https://www.x-easypdf.cn?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/x-easypdf.png" msg="一个用搭积木的方式构建pdf的框架(基于pdfbox)">
|
||||
</a>
|
||||
<a href="http://dromara.gitee.io/image-combiner?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/image-combiner.png" msg="一个专门用于图片合成的工具,没有很复杂的功能,简单实用,却不失强大">
|
||||
</a>
|
||||
<a href="https://www.herodotus.cn/?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/dante-cloud.jpg" msg="Dante-Cloud 是一款企业级微服务架构和服务能力开发平台。">
|
||||
</a>
|
||||
<a href="http://www.mtruning.club?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/go-view.png" msg="低代码数据可视化开发平台">
|
||||
</a>
|
||||
<a href="https://tangyh.top/?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/lamp-cloud.png" msg="微服务中后台快速开发平台,支持租户(SaaS)模式、非租户模式">
|
||||
</a>
|
||||
<a href="https://www.redisfront.com/?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/redis-front.png" msg="RedisFront 是一款开源免费的跨平台 Redis 桌面客户端工具, 支持单机模式, 集群模式, 哨兵模式以及 SSH 隧道连接, 可轻松管理Redis缓存数据.">
|
||||
</a>
|
||||
<a href="https://dynamictp.cn/" target="_blank">
|
||||
<img src="https://oss.dev33.cn/sa-token/link/dynamic-tp.png" alt="基于配置中心的轻量级动态可监控线程池" width="15%">
|
||||
<a href="https://www.yuque.com/u34495/mivcfg?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/electron-egg.png" msg="一个入门简单、跨平台、企业级桌面软件开发框架">
|
||||
</a>
|
||||
<a href="https://www.x-easypdf.cn" target="_blank">
|
||||
<img src="https://oss.dev33.cn/sa-token/link/x-easypdf.png" alt="一个用搭积木的方式构建pdf的框架(基于pdfbox)" width="15%">
|
||||
<a href="https://gitee.com/dromara/open-capacity-platform?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/open-capacity-platform.jpg" msg="简称ocp是基于Spring Cloud的企业级微服务框架(用户权限管理,配置中心管理,应用管理,....)">
|
||||
</a>
|
||||
<a href="http://dromara.gitee.io/image-combiner" target="_blank">
|
||||
<img src="https://oss.dev33.cn/sa-token/link/image-combiner.png" alt="一个专门用于图片合成的工具,没有很复杂的功能,简单实用,却不失强大" width="15%">
|
||||
<a href="http://easy-trans.fhs-opensource.top/?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/easy_trans.png" msg="Easy-Trans 一个注解搞定数据翻译,减少30%SQL代码量">
|
||||
</a>
|
||||
<a href="https://www.herodotus.cn/" target="_blank">
|
||||
<img src="https://oss.dev33.cn/sa-token/link/dante-cloud2.png" alt="Dante-Cloud 是一款企业级微服务架构和服务能力开发平台。" width="15%">
|
||||
<a href="https://gitee.com/dromara/neutrino-proxy?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/neutrino-proxy.svg" msg="一款基于 Netty 的、开源的内网穿透神器。">
|
||||
</a>
|
||||
<a href="https://dromara.org/zh/projects/" target="_blank">
|
||||
<img src="https://oss.dev33.cn/sa-token/link/dromara.png" alt="让每一位开源爱好者,体会到开源的快乐。" width="15%">
|
||||
<a href="https://chatgpt.cn.obiscr.com/?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/chatgpt.png" msg="一个支持在 JetBrains 系列 IDE 上运行的 ChatGPT 的插件。">
|
||||
</a>
|
||||
<a href="https://gitee.com/dromara/zyplayer-doc?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/zyplayer-doc.png" msg="zyplayer-doc是一款适合团队和个人使用的WIKI文档管理工具,同时还包含数据库文档、Api接口文档。">
|
||||
</a>
|
||||
<a href="https://gitee.com/dromara/payment-spring-boot?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/payment-spring-boot.png" msg="最全最好用的微信支付V3 Spring Boot 组件。">
|
||||
</a>
|
||||
<a href="https://www.j2eefast.com/?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/j2eefast.png" msg="J2eeFAST 是一个致力于中小企业 Java EE 企业级快速开发平台,我们永久开源!">
|
||||
</a>
|
||||
<a href="https://gitee.com/dromara/data-compare?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/dataCompare.png" msg="数据库比对工具:hive 表数据比对,mysql、Doris 数据比对,实现自动化配置进行数据比对,避免频繁写sql 进行处理,低代码(Low-Code) 平台">
|
||||
</a>
|
||||
<a href="https://gitee.com/dromara/open-giteye-api?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/open-giteye-api.svg" msg="giteye.net 是专为开源作者设计的数据图表服务工具类站点,提供了包括 Star 趋势图、贡献者列表、Gitee指数等数据图表服务。">
|
||||
</a>
|
||||
<a href="https://gitee.com/dromara/RuoYi-Vue-Plus?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/RuoYi-Vue-Plus.png" msg="后台管理系统 重写 RuoYi-Vue 所有功能 集成 Sa-Token + Mybatis-Plus + Jackson + Xxl-Job + SpringDoc + Hutool + OSS 定期同步">
|
||||
</a>
|
||||
<a href="https://gitee.com/dromara/RuoYi-Cloud-Plus?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/RuoYi-Cloud-Plus.png" msg="微服务管理系统 重写RuoYi-Cloud所有功能 整合 SpringCloudAlibaba Dubbo3.0 Sa-Token Mybatis-Plus MQ OSS ES Xxl-Job Docker 全方位升级 定期同步">
|
||||
</a>
|
||||
<a href="https://gitee.com/dromara/stream-query?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/stream-query.png" msg="允许完全摆脱 Mapper 的 mybatis-plus 体验!封装 stream 和 lambda 操作进行数据返回处理。">
|
||||
</a>
|
||||
<a href="https://dromara.org/zh/projects/?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" src="img/logo/dromara.png" msg="让每一位开源爱好者,体会到开源的快乐。">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<h2>🤝 友情开源项目</h2>
|
||||
<p>
|
||||
<a href="https://gitee.com/noear/solon?from=neutrino-proxy" target="_blank" class="friends-item" >
|
||||
<img class="no-zoom friends-item-img hover-alt" :src="$withBase('img/logo/solon_logo_500_150.png')" msg="一个高效的应用开发框架:更快、更小、更简单。" />
|
||||
</a>
|
||||
<a href="https://gitee.com/xiaonuobase/snowy?from=neutrino-proxy" target="_blank" class="friends-item">
|
||||
<img class="no-zoom friends-item-img hover-alt" :src="$withBase('img/logo/xiaonuo.png')" msg="国内首个国密前后端分离快速开发平台" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<!-- AD -->
|
||||
<div class="wwads-cn wwads-horizontal page-wwads" data-id="136"></div>
|
||||
<style>
|
||||
@@ -258,4 +356,32 @@ postList: none
|
||||
padding-top: 5px;
|
||||
display: block;
|
||||
}
|
||||
.friends-item {
|
||||
width: 10em;
|
||||
height: 3em;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
margin: 5px;
|
||||
border: 2px solid #ebeef5;
|
||||
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
|
||||
border-radius: 8px;
|
||||
padding: 10px
|
||||
}
|
||||
.friends-item-img {
|
||||
-o-object-fit: contain;
|
||||
object-fit: contain;
|
||||
max-width: 150px!important;
|
||||
height: 100%
|
||||
}
|
||||
/*a {*/
|
||||
/* font-weight: 500;*/
|
||||
/* text-decoration: none*/
|
||||
/*}*/
|
||||
/*a,p a code {*/
|
||||
/*color: #4274f4*/
|
||||
/*}*/
|
||||
/*p a code {*/
|
||||
/*font-weight: 400*/
|
||||
/*}*/
|
||||
</style>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"scripts": {
|
||||
"dev": "vuepress dev docs",
|
||||
"build": "vuepress build docs",
|
||||
"buildAndDeploy": "vuepress build docs && bash deploy.sh",
|
||||
"deploy": "bash deploy.sh",
|
||||
"updateTheme": "yarn remove vuepress-theme-vdoing && rm -rf node_modules && yarn && yarn add vuepress-theme-vdoing -D",
|
||||
"editFm": "node utils/editFrontmatter.js"
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
<parent>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>solon-parent</artifactId>
|
||||
<version>2.2.2</version>
|
||||
<version>2.5.11</version>
|
||||
<relativePath />
|
||||
</parent>
|
||||
|
||||
<groupId>fun.asgc.neutrino</groupId>
|
||||
<groupId>org.dromara.neutrino-proxy</groupId>
|
||||
<artifactId>neutrino-proxy</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>${revision}</version>
|
||||
@@ -20,20 +20,19 @@
|
||||
<module>neutrino-proxy-core</module>
|
||||
<module>neutrino-proxy-client</module>
|
||||
<module>neutrino-proxy-server</module>
|
||||
<module>_solon_plugin/job-solon-plugin</module>
|
||||
<module>_solon_plugin/orika-solon-plugin</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
|
||||
<revision>1.0-SNAPSHOT</revision>
|
||||
<revision>1.1-SNAPSHOT</revision>
|
||||
|
||||
<java.version>1.8</java.version>
|
||||
<native.version>0.9.28</native.version>
|
||||
|
||||
<java.version>17</java.version>
|
||||
<maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>
|
||||
<maven-flatten.version>1.1.0</maven-flatten.version>
|
||||
<org.projectlombok.version>1.18.4</org.projectlombok.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@@ -41,22 +40,7 @@
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
<version>4.1.22.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.7.25</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.2.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${org.projectlombok.version}</version>
|
||||
<version>4.1.100.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
@@ -74,11 +58,6 @@
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.9</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>1.2.83</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
@@ -90,35 +69,43 @@
|
||||
<version>1.3.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>3.39.2.1</version>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>2.2.224</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>5.1.49</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mariadb.jdbc</groupId>
|
||||
<artifactId>mariadb-java-client</artifactId>
|
||||
<version>2.7.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
<version>4.0.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.dromara.solon-plugins</groupId>
|
||||
<artifactId>job-solon-plugin</artifactId>
|
||||
<version>0.1.1</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>solon.logging.logback</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
@@ -128,14 +115,16 @@
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -157,7 +146,7 @@
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${org.projectlombok.version}</version>
|
||||
<version>${lombok.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
@@ -190,5 +179,83 @@
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>tencent</id>
|
||||
<url>https://mirrors.cloud.tencent.com/nexus/repository/maven-public/</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>sonatype-nexus-snapshots</id>
|
||||
<name>Sonatype Nexus Snapshots</name>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
</repository>
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>sonatype-nexus-snapshots</id>
|
||||
<name>Sonatype Nexus Snapshots</name>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>native</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>solon-maven-plugin</artifactId>
|
||||
<version>${solon-maven-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>process-aot</id>
|
||||
<goals>
|
||||
<goal>process-aot</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.plexus</groupId>
|
||||
<artifactId>plexus-utils</artifactId>
|
||||
<version>3.5.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.graalvm.buildtools</groupId>
|
||||
<artifactId>native-maven-plugin</artifactId>
|
||||
<version>${native.version}</version>
|
||||
<!-- 使用graalvm提供的可达性元数据,很多第三方库就直接可以构建成可执行文件了 -->
|
||||
<configuration>
|
||||
<metadataRepository>
|
||||
<enabled>true</enabled>
|
||||
</metadataRepository>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>add-reachability-metadata</id>
|
||||
<goals>
|
||||
<goal>add-reachability-metadata</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
# 中微子代理管理后台编译打包脚本,基础参数请自行修改
|
||||
|
||||
#环境
|
||||
env=docker
|
||||
env=dev
|
||||
nvmDir=$HOME/.nvm
|
||||
nodeVersion=v13.12.0
|
||||
deployDir="deploy"
|
||||
serverDeployDir=$deployDir"/server"
|
||||
adminDeployDir=$serverDeployDir"/neutrino-proxy-admin"
|
||||
giteePagesDir=$deployDir"/gitee-pages"
|
||||
|
||||
#设置nvm生效
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
@@ -16,7 +17,7 @@ export NVM_DIR="$HOME/.nvm"
|
||||
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
|
||||
|
||||
#初始化文件夹
|
||||
cd ..
|
||||
cd ../..
|
||||
rm -rf $adminDeployDir
|
||||
if [ ! -d "$deployDir" ];then
|
||||
mkdir $deployDir
|
||||
@@ -39,4 +40,7 @@ npm i
|
||||
npm run build:$env
|
||||
#拷贝
|
||||
cd ..
|
||||
cp -rf ./neutrino-proxy-admin/dist $adminDeployDir
|
||||
cp -rf ./neutrino-proxy-admin/dist $adminDeployDir/
|
||||
cp -rf ./neutrino-proxy-admin/dist/ $giteePagesDir
|
||||
cd $serverDeployDir
|
||||
zip -r neutrino-proxy-admin.zip "neutrino-proxy-admin/"
|
||||
@@ -0,0 +1,52 @@
|
||||
#!/bin/sh
|
||||
# 中微子代理管理后台编译打包脚本,基础参数请自行修改
|
||||
|
||||
#环境
|
||||
env=docker
|
||||
nvmDir=$HOME/.nvm
|
||||
nodeVersion=v13.12.0
|
||||
deployDir="deploy"
|
||||
serverDeployDir=$deployDir"/server"
|
||||
adminDeployDir=$serverDeployDir"/neutrino-proxy-admin"
|
||||
giteePagesDir=$deployDir"/gitee-pages"
|
||||
|
||||
#设置nvm生效
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
||||
# This loads nvm
|
||||
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
|
||||
|
||||
#初始化文件夹
|
||||
cd ../..
|
||||
rm -rf $adminDeployDir
|
||||
if [ ! -d "$deployDir" ];then
|
||||
mkdir $deployDir
|
||||
fi
|
||||
if [ ! -d "$serverDeployDir" ];then
|
||||
mkdir $serverDeployDir
|
||||
fi
|
||||
if [ ! -d "$adminDeployDir" ];then
|
||||
mkdir $adminDeployDir
|
||||
fi
|
||||
#切node版本
|
||||
nvm use $nodeVersion
|
||||
#进入admin项目目录
|
||||
cd ./neutrino-proxy-admin
|
||||
#删除之前的build
|
||||
rm -rf ./dist
|
||||
#安装依赖
|
||||
npm i
|
||||
#编译
|
||||
npm run build:$env
|
||||
#拷贝
|
||||
cd ..
|
||||
cp -rf ./neutrino-proxy-admin/dist $adminDeployDir/
|
||||
cp -rf ./neutrino-proxy-admin/dist/ $giteePagesDir
|
||||
cd $serverDeployDir
|
||||
zip -r neutrino-proxy-admin.zip "neutrino-proxy-admin/"
|
||||
|
||||
#拷贝到neutrino-proxy-server项目静态资源目录下
|
||||
cd ../..
|
||||
rm -rf ./neutrino-proxy-server/src/main/resources/static
|
||||
mkdir -p neutrino-proxy-server/src/main/resources/static
|
||||
cp -rf ./neutrino-proxy-admin/dist/ ./neutrino-proxy-server/src/main/resources/static
|
||||
@@ -2,22 +2,22 @@
|
||||
# 中微子代理客户端编译打包脚本,基础参数请自行修改
|
||||
|
||||
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home
|
||||
export MAVANE_HOME=/Users/yangwen/my/service/maven/apache-maven-3.8.1
|
||||
export PATH=:$PATH:$JAVA_HOME/bin:$MAVANE_HOME/bin
|
||||
export MAVEN_HOME=/Users/yangwen/my/service/maven/apache-maven-3.8.1
|
||||
export PATH=:$PATH:$JAVA_HOME/bin:$MAVEN_HOME/bin
|
||||
|
||||
deployDir="deploy"
|
||||
clientDeployDir=$deployDir"/client"
|
||||
|
||||
#切到项目根目录
|
||||
cd ..
|
||||
cd ../..
|
||||
#初始化文件夹
|
||||
rm -rf $clientDeployDir
|
||||
if [ ! -d "$deployDir" ];then
|
||||
mkdir $deployDir
|
||||
fi
|
||||
if [ ! -d "$clientDeployDir" ];then
|
||||
mkdir $clientDeployDir
|
||||
fi
|
||||
rm -rf $clientDeployDir/neutrino-proxy-client.jar
|
||||
#客户端打包
|
||||
mvn clean install -U -pl neutrino-proxy-client -am -Dmaven.test.skip=true
|
||||
# 拷贝到deploy目录下
|
||||
@@ -0,0 +1,29 @@
|
||||
#!/bin/sh
|
||||
|
||||
# 镜像版本,每次更新版本时需要调整
|
||||
ImageVer=1.8.5
|
||||
ImageName=neutrino-proxy-client
|
||||
DockerFilePath=$PWD/../../neutrino-proxy-client/Dockerfile
|
||||
|
||||
deployDir=$PWD/../../"deploy"
|
||||
clientDeployDir=$deployDir"/client"
|
||||
|
||||
#切到项目根目录
|
||||
#初始化文件夹
|
||||
if [ ! -d "$deployDir" ];then
|
||||
mkdir $deployDir
|
||||
fi
|
||||
if [ ! -d "$clientDeployDir" ];then
|
||||
mkdir $clientDeployDir
|
||||
fi
|
||||
rm -rf $clientDeployDir/$ImageName.tar
|
||||
|
||||
#echo '打包jar...'
|
||||
sh ./client_build.sh
|
||||
|
||||
# 删除老的本地镜像
|
||||
docker rmi -f $(docker images | grep $ImageName | awk '{print $3}')
|
||||
# 构建镜像
|
||||
docker build -t $ImageName:$ImageVer -t $ImageName:latest -f $DockerFilePath $PWD/../..
|
||||
# 保存镜像到本地
|
||||
docker save -o $clientDeployDir/$ImageName.tar $ImageName:$ImageVer
|
||||
@@ -0,0 +1,29 @@
|
||||
#!/bin/sh
|
||||
# 中微子代理客户端编译打包脚本,基础参数请自行修改
|
||||
|
||||
export JAVA_HOME=/Users/yangwen/my/service/graalvm/graalvm-community-openjdk-17.0.8+7.1/Contents/Home
|
||||
export MAVEN_HOME=/Users/yangwen/my/service/maven/apache-maven-3.8.1
|
||||
export PATH=:$PATH:$JAVA_HOME/bin:$MAVEN_HOME/bin
|
||||
|
||||
deployDir="deploy"
|
||||
clientDeployDir=$deployDir"/client"
|
||||
|
||||
#切到项目根目录
|
||||
cd ../..
|
||||
#初始化文件夹
|
||||
mkdir -p $clientDeployDir
|
||||
|
||||
# 删除原来的编译文件
|
||||
rm -rf $clientDeployDir/neutrino-proxy-client.jar
|
||||
rm -rf $clientDeployDir/neutrino-proxy-client
|
||||
|
||||
#客户端打包
|
||||
mvn clean install -U -pl neutrino-proxy-client -am -Dmaven.test.skip=true
|
||||
cd neutrino-proxy-client
|
||||
mvn clean native:compile -P native -DskipTests
|
||||
cd ..
|
||||
|
||||
# 拷贝到deploy目录下
|
||||
cp ./neutrino-proxy-client/target/neutrino-proxy-client.jar $clientDeployDir/neutrino-proxy-client.jar
|
||||
cp ./neutrino-proxy-client/target/neutrino-proxy-client $clientDeployDir/neutrino-proxy-client
|
||||
|
||||
@@ -8,7 +8,7 @@ export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/li
|
||||
mkdir -p /work/$NAME/heapError/
|
||||
|
||||
NAME=neutrino-proxy-client
|
||||
WORK=$PWD/../deploy/client
|
||||
WORK=$PWD/../../deploy/client
|
||||
OUT=$WORK/$NAME.out
|
||||
JAR_PATH=$WORK
|
||||
|
||||
@@ -37,7 +37,7 @@ cp $OUT $JAR_PATH/logs/back_$time.out
|
||||
fi
|
||||
rm -f $OUT
|
||||
cd $JAR_PATH
|
||||
nohup java $JAVA_OPS -jar $NAME.jar $startupParams > $OUT 2>&1 &
|
||||
nohup java -Dfile.encoding=utf-8 $JAVA_OPS -jar $NAME.jar $startupParams > $OUT 2>&1 &
|
||||
echo "sleep 15s wating service start"
|
||||
sleep 15
|
||||
tail -200 $OUT
|
||||
@@ -1,24 +1,25 @@
|
||||
!/bin/sh
|
||||
# 中微子代理客户端编译打包脚本,基础参数请自行修改
|
||||
#!/bin/sh
|
||||
# 中微子代理服务端编译打包脚本,基础参数请自行修改
|
||||
|
||||
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home
|
||||
export MAVANE_HOME=/Users/yangwen/my/service/maven/apache-maven-3.8.1
|
||||
export PATH=:$PATH:$JAVA_HOME/bin:$MAVANE_HOME/bin
|
||||
export MAVEN_HOME=/Users/yangwen/my/service/maven/apache-maven-3.8.1
|
||||
export PATH=:$PATH:$JAVA_HOME/bin:$MAVEN_HOME/bin
|
||||
|
||||
deployDir="deploy"
|
||||
serverDeployDir=$deployDir"/server"
|
||||
|
||||
#切到项目根目录
|
||||
cd ..
|
||||
cd ../..
|
||||
#初始化文件夹
|
||||
rm -rf $serverDeployDir
|
||||
if [ ! -d "$deployDir" ];then
|
||||
mkdir $deployDir
|
||||
fi
|
||||
if [ ! -d "$serverDeployDir" ];then
|
||||
mkdir $serverDeployDir
|
||||
fi
|
||||
#客户端打包
|
||||
rm -rf $serverDeployDir/neutrino-proxy-server.jar
|
||||
|
||||
#服务端打包
|
||||
mvn clean install -U -pl neutrino-proxy-server -am -Dmaven.test.skip=true
|
||||
# 拷贝到deploy目录下
|
||||
cp ./neutrino-proxy-server/target/neutrino-proxy-server.jar $serverDeployDir/neutrino-proxy-server.jar
|
||||
@@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
|
||||
# 镜像版本,每次更新版本时需要调整
|
||||
ImageVer=1.0.0
|
||||
ImageName=neutrino-proxy
|
||||
DockerFilePath=$PWD/../..
|
||||
|
||||
#echo '打包jar...'
|
||||
sh ./server_build.sh
|
||||
#echo '打包管理后台...'
|
||||
sh ./admin_build_docker.sh
|
||||
|
||||
# 删除老的本地镜像
|
||||
docker rmi -f $(docker images | grep $ImageName | awk '{print $3}')
|
||||
# 构建镜像
|
||||
docker build -t $ImageName:$ImageVer -t $ImageName:latest $DockerFilePath
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/bin/sh
|
||||
# 中微子代理服务端编译打包脚本,基础参数请自行修改
|
||||
|
||||
export JAVA_HOME=/Users/yangwen/my/service/graalvm/graalvm-community-openjdk-17.0.8+7.1/Contents/Home
|
||||
export MAVEN_HOME=/Users/yangwen/my/service/maven/apache-maven-3.8.1
|
||||
export PATH=:$PATH:$JAVA_HOME/bin:$MAVEN_HOME/bin
|
||||
|
||||
deployDir="deploy"
|
||||
serverDeployDir=$deployDir"/server"
|
||||
|
||||
#切到项目根目录
|
||||
cd ../..
|
||||
#初始化文件夹
|
||||
mkdir -p $serverDeployDir
|
||||
|
||||
# 删除原来的编译文件
|
||||
rm -rf $serverDeployDir/neutrino-proxy-server.jar
|
||||
rm -rf $serverDeployDir/neutrino-proxy-server
|
||||
|
||||
# 不需要每次都重新编译一次前端代码,如果前端代码有变更,编译前需要手动执行`admin_build_docker.sh`
|
||||
##前端页面打包
|
||||
#rm -rf ./neutrino-proxy-server/src/main/resources/static
|
||||
#cd ./scripts/unix
|
||||
#sh admin_build_docker.sh
|
||||
#cd ../..
|
||||
#mkdir -p neutrino-proxy-server/src/main/resources/static
|
||||
#cp -rf ./neutrino-proxy-admin/dist/ ./neutrino-proxy-server/src/main/resources/static
|
||||
|
||||
#服务端打包
|
||||
mvn clean install -U -pl neutrino-proxy-server -am -Dmaven.test.skip=true
|
||||
cd neutrino-proxy-server
|
||||
mvn clean native:compile -P native -DskipTests
|
||||
cd ..
|
||||
|
||||
# 拷贝到deploy目录下
|
||||
cp ./neutrino-proxy-server/target/neutrino-proxy-server.jar $serverDeployDir/neutrino-proxy-server.jar
|
||||
cp ./neutrino-proxy-server/target/neutrino-proxy-server $serverDeployDir/neutrino-proxy-server
|
||||
|
||||
@@ -8,7 +8,7 @@ export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/li
|
||||
mkdir -p /work/$NAME/heapError/
|
||||
|
||||
NAME=neutrino-proxy-server
|
||||
WORK=$PWD/../deploy/server
|
||||
WORK=$PWD/../../deploy/server
|
||||
OUT=$WORK/$NAME.out
|
||||
JAR_PATH=$WORK
|
||||
|
||||
@@ -29,7 +29,7 @@ cp $OUT $JAR_PATH/logs/back_$time.out
|
||||
fi
|
||||
rm -f $OUT
|
||||
cd $JAR_PATH
|
||||
nohup java $JAVA_OPS -jar $NAME.jar > $OUT 2>&1 &
|
||||
nohup java -Dfile.encoding=utf-8 $JAVA_OPS -jar $NAME.jar > $OUT 2>&1 &
|
||||
echo "sleep 15s wating service start"
|
||||
sleep 15
|
||||
tail -200 $OUT
|
||||
@@ -0,0 +1,29 @@
|
||||
@echo off
|
||||
@rem basic params
|
||||
set ENV=dev
|
||||
set NODE_VERSION=v13.12.0
|
||||
set MODULE_NAME=neutrino-proxy-admin
|
||||
@rem 判断当前node版本是否符合,如果不符合切换node版本
|
||||
for /f "tokens=1" %%v in ('node -v') do set v=%%v
|
||||
if not "%v%" == "%NODE_VERSION%" (
|
||||
echo Node.js version is not %NODE_VERSION%.
|
||||
@rem 判断是否安装nvm
|
||||
set "nvm_home=%NVM_HOME%"
|
||||
set "nvm_symlink=%NVM_SYMLINK%"
|
||||
if not defined nvm_home (
|
||||
echo nvm is not installed,then use nvm to install node %NODE_VERSION%.
|
||||
) else if not defined nvm_symlink (
|
||||
echo nvm is not installed,then use nvm to install node %NODE_VERSION%.
|
||||
) else (
|
||||
echo nvm is installed.
|
||||
nvm use %NODE_VERSION%
|
||||
)
|
||||
)
|
||||
cd %~dp0 && cd ../../%MODULE_NAME%
|
||||
@rem 检查dist文件夹是否存在,存在就删除,/s选项表示将目录及其所有子目录一起删除,/q选项表示不需要确认操作
|
||||
if exist dist (
|
||||
rmdir /s /q dist
|
||||
)
|
||||
@rem 等待npm执行完毕
|
||||
call npm install
|
||||
npm run build:%ENV% && pause
|
||||
@@ -0,0 +1,29 @@
|
||||
@echo off
|
||||
@rem basic params
|
||||
set ENV=docker
|
||||
set NODE_VERSION=v13.12.0
|
||||
set MODULE_NAME=neutrino-proxy-admin
|
||||
@rem 判断当前node版本是否符合,如果不符合切换node版本
|
||||
for /f "tokens=1" %%v in ('node -v') do set v=%%v
|
||||
if not "%v%" == "%NODE_VERSION%" (
|
||||
echo Node.js version is not %NODE_VERSION%.
|
||||
@rem 判断是否安装nvm
|
||||
set "nvm_home=%NVM_HOME%"
|
||||
set "nvm_symlink=%NVM_SYMLINK%"
|
||||
if not defined nvm_home (
|
||||
echo nvm is not installed,then use nvm to install node %NODE_VERSION%.
|
||||
) else if not defined nvm_symlink (
|
||||
echo nvm is not installed,then use nvm to install node %NODE_VERSION%.
|
||||
) else (
|
||||
echo nvm is installed.
|
||||
nvm use %NODE_VERSION%
|
||||
)
|
||||
)
|
||||
cd %~dp0 && cd ../../%MODULE_NAME%
|
||||
@rem 检查dist文件夹是否存在,存在就删除,/s选项表示将目录及其所有子目录一起删除,/q选项表示不需要确认操作
|
||||
if exist dist (
|
||||
rmdir /s /q dist
|
||||
)
|
||||
@rem 等待npm执行完毕
|
||||
call npm install
|
||||
npm run build:%ENV% && pause
|
||||
@@ -0,0 +1,8 @@
|
||||
@echo off
|
||||
@rem console output code changed to UTF-8
|
||||
chcp 65001
|
||||
@rem basic params
|
||||
set MODULE_NAME="neutrino-proxy-client"
|
||||
@rem step
|
||||
cd %~dp0 && cd ../../
|
||||
call mvn clean package -pl %MODULE_NAME% -am -Dmaven.test.skip=true
|
||||
@@ -0,0 +1,10 @@
|
||||
@echo off
|
||||
@rem console output code changed to UTF-8
|
||||
chcp 65001
|
||||
@rem basic params
|
||||
set MODULE_NAME="neutrino-proxy-client"
|
||||
set JAR_NAME="neutrino-proxy-client.jar"
|
||||
set JVM_OPTS="-Xmx256m" "-Xms256m"
|
||||
@rem step
|
||||
cd %~dp0 && cd ../../%MODULE_NAME%/target
|
||||
java -jar %JVM_OPTS% %JAR_NAME%
|
||||
@@ -0,0 +1,8 @@
|
||||
@echo off
|
||||
@rem console output code changed to UTF-8
|
||||
chcp 65001
|
||||
@rem basic params
|
||||
set MODULE_NAME="neutrino-proxy-server"
|
||||
@rem step
|
||||
cd %~dp0 && cd ../../
|
||||
call mvn clean package -pl %MODULE_NAME% -am -Dmaven.test.skip=true
|
||||
@@ -0,0 +1,10 @@
|
||||
@echo off
|
||||
@rem console output code changed to UTF-8
|
||||
chcp 65001
|
||||
@rem basic params
|
||||
set MODULE_NAME="neutrino-proxy-server"
|
||||
set JAR_NAME="neutrino-proxy-server.jar"
|
||||
set JVM_OPTS="-Xmx256m" "-Xms256m"
|
||||
@rem step
|
||||
cd %~dp0 && cd ../../%MODULE_NAME%/target
|
||||
java -jar %JVM_OPTS% %JAR_NAME%
|
||||
@@ -1,51 +1,66 @@
|
||||
# 功能点
|
||||
- 用户流量报表
|
||||
- 弹框展示月度明细
|
||||
- 弹框展示今日流量明细
|
||||
- License流量报表
|
||||
- 弹框展示月度明细
|
||||
- 弹框展示今日流量明细
|
||||
- 首页图表📈
|
||||
- 1、License在线数
|
||||
- 2、端口映射在线数
|
||||
- 3、今日流量(上行、下行)
|
||||
- 4、历史流量(上行、下行)
|
||||
- 点击1~4 切换列表
|
||||
- 在线License列表
|
||||
- 用户名
|
||||
- License名称
|
||||
- LicenseKey
|
||||
- 在线端口映射列表
|
||||
- 用户名
|
||||
- License名称
|
||||
- 服务端口
|
||||
- 代理客户端
|
||||
- 今日24小时流量列表(按小时到排序)
|
||||
- 用户名
|
||||
- License名称
|
||||
- 时间
|
||||
- 流量
|
||||
- 历史流量列表(取最近12个月按月到排序)
|
||||
- 用户名
|
||||
- License名称
|
||||
- 时间
|
||||
- 流量
|
||||
- 今日流量折线图(上行、下行、总流量,按分钟统计0~24小时)
|
||||
# 1.x规划
|
||||
- Bug
|
||||
- 优化
|
||||
- 添加端口映射时,验证端口是否被其他服务占用
|
||||
- 拉下搜索license,支持模糊搜索
|
||||
- license下拉用户搜索,支持模糊搜索
|
||||
- UDP支持
|
||||
- 官网文档完善
|
||||
- HTTPS配置说明
|
||||
- 协议重构
|
||||
- 代码重构
|
||||
|
||||
# 1.8.6
|
||||
- [x] 端口映射选择端口支持分页
|
||||
- [x] 新增/更新端口映射,增加端口占用检测
|
||||
- [x] 拉下搜索license,支持模糊搜索
|
||||
- [x] license下拉用户搜索,支持模糊搜索
|
||||
- [x] 端口映射编辑时,如果端口号没有变动,则不验证。避免出现端口映射正在使用时,无法更新端口映射其他信息的问题
|
||||
- [x] 端口映射HTTP(S)新增打开网页按钮
|
||||
- [x] 客户端断开连接时,记录日志空指针异常问题修复
|
||||
|
||||
# 1.9.0
|
||||
- [x] 支持UDP
|
||||
- 服务端
|
||||
- 原`neutrino.proxy.server`配置移到`neutrino.proxy.server.tcp`下
|
||||
- 客户端
|
||||
- 原`neutrino.proxy.client`配置移到`neutrino.proxy.tunnel`下
|
||||
- [x] 端口映射HTTP(S)新增打开网页按钮,优先使用域名打开
|
||||
- [x] 新增/编辑端口映射时端口占用检测功能屏蔽,解决docker下使用的各种问题
|
||||
|
||||
# 1.9.1
|
||||
- [ ] 如果UDP映射服务端端口变动,流量统计似乎不准,转发功能或许也受到影响,需要评估、测试、思考如何优化
|
||||
- [ ] 增加服务端/客户端jar式一键部署脚本
|
||||
- [ ] 排查解决问题:https://gitee.com/dromara/neutrino-proxy/issues/I7LGLB
|
||||
- [ ] 访问白名单
|
||||
- [ ] 端口映射分组
|
||||
|
||||
# Bug
|
||||
- 指令通达被close的问题,org.dromara.neutrinoproxy.server.proxy.core.ProxyTunnelChannelHandler.channelInactive
|
||||
- windows环境下直接运行发布版的jar包,日志输出乱码
|
||||
- 部份用户windows环境下启动客户端,扫描类个数为0个
|
||||
- 代理mysql时,使用未开启远程访问的账号走代理访问mysql,代理客户端出现断开现象
|
||||
- 客户端连接映射某个端口以后,如果在服务器端禁用了,没有立刻反映出来,要灯客户端重连以后才会屏蔽被禁用的端口。
|
||||
|
||||
# 2.x规划
|
||||
- 全面重构:底层更换为Solon + Mybatis Plus
|
||||
- 规范协议:代理协议规范化,方便后续更好扩展、支持不同语言客户端接入
|
||||
- 端口池优化:支持为license设置独占端口。方便后续开发jetbrains插件、solon插件
|
||||
- 精细化控制:支持针对用户限速、限流
|
||||
- 参考鹊桥,增加对域名的支持
|
||||
- MGT增加服务端维护
|
||||
- 支持调整服务端端口
|
||||
- 支持重启
|
||||
- 支持查看服务端日志
|
||||
- 支持证书上传下载
|
||||
- 支持客户端版本管理
|
||||
- 支持原生编译
|
||||
- 插件开发
|
||||
- [ ] neutrino-proxy-solon-plugin
|
||||
- [ ] neutrino-proxy-jetbrains-plugin
|
||||
- [ ] neutrino-proxy-starter (SpringBoot)
|
||||
- 基础优化
|
||||
- [ ] 支持批量端口映射
|
||||
- [ ] 代理协议规范化,方便后续更好扩展、支持不同语言客户端接入
|
||||
- 监控运维
|
||||
- [ ] 监控服务端状态
|
||||
- [ ] 支持调整服务端端口、证书
|
||||
- [ ] 服务端版本、协议版本、客户端版本管理
|
||||
- [ ] 服务端日志
|
||||
- [ ] 支持持管理后台动态调整日志级别。
|
||||
- [ ] 动态调整转发、报文、心跳输出
|
||||
- client+计划启动
|
||||
- [ ] 安卓客户端
|
||||
- [ ] 基于electron-egg的多平台客户端
|
||||
|
||||
# 3.x规划
|
||||
- [ ] 支持针对用户限速、限流
|
||||
- [ ] 支持P2P穿透
|
||||
- [ ] 支持原生编译
|
||||
|
||||