Compare commits
12 Commits
f0468539dc
...
main
Author | SHA1 | Date | |
---|---|---|---|
02c505c83a | |||
e59484e3c7 | |||
6a4ff85683 | |||
b3c2587903 | |||
79a9568a87 | |||
a9afc2ffea | |||
5c91ac8b8d | |||
73118f8454 | |||
42e32adf86 | |||
175e685480 | |||
6b5eec89ed | |||
c92f817d66 |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -5,4 +5,9 @@ coverage
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
upload
|
upload
|
||||||
|
|
||||||
app.config.json5
|
app.config.json5
|
||||||
|
|
||||||
|
release/*
|
||||||
|
!release/.gitkeep
|
||||||
|
|
||||||
|
/*.tgz
|
5
.npmrc
5
.npmrc
@@ -1 +1,4 @@
|
|||||||
@abearxiong:registry=https://npm.pkg.github.com
|
//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN}
|
||||||
|
@abearxiong:registry=https://npm.pkg.github.com
|
||||||
|
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||||
|
@kevisual:registry=https://npm.xiongxiao.me
|
14
README.md
Normal file
14
README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# page proxy
|
||||||
|
|
||||||
|
# 部署方案
|
||||||
|
|
||||||
|
```sh
|
||||||
|
envision pack -p -u
|
||||||
|
envision pack-deploy 330bc5f8-1ae7-4be5-a44c-0ea0b3da184b page-proxy # key和id是人设置的
|
||||||
|
# 会复制到对应的文件夹里面了,现在。启动时后台启动
|
||||||
|
# 需要调用类似,但需要token
|
||||||
|
# token ev token会显示当前登陆的用户的token
|
||||||
|
# https://kevisual.xiongxiao.me/api/router?path=local-apps&key=updateStatus&appKey=page-proxy&status=start&token=******
|
||||||
|
```
|
||||||
|
|
||||||
|

|
@@ -1,10 +1,12 @@
|
|||||||
{
|
{
|
||||||
port: 3005,
|
|
||||||
api: {
|
api: {
|
||||||
host: 'localhost:4000', // 后台代理
|
host: 'localhost:4002', // 后台代理
|
||||||
path: '/api/router',
|
path: '/api/router',
|
||||||
},
|
},
|
||||||
allowedOrigins: ['localhost', 'xiongxiao.me', 'zxj.im'],
|
proxy: {
|
||||||
domain: 'kevisual.xiongxiao.me',
|
port: 3005,
|
||||||
resources: 'minio.xiongxiao.me/resources',
|
domain: 'kevisual.xiongxiao.me',
|
||||||
|
resources: 'https://minio.xiongxiao.me/resources',
|
||||||
|
allowedOrigins: ['localhost', 'xiongxiao.me', 'zxj.im', 'silkyai.cn'],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
10
app.config.json5.example
Normal file
10
app.config.json5.example
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
port: 3005,
|
||||||
|
api: {
|
||||||
|
host: 'localhost:3000', // 后台代理
|
||||||
|
path: '/api/router',
|
||||||
|
},
|
||||||
|
allowedOrigins: ['localhost', 'xiongxiao.me', 'zxj.im'],
|
||||||
|
domain: 'demo.kevisual.xiongxiao.me',
|
||||||
|
resources: 'localhost:9000/resources',
|
||||||
|
}
|
BIN
docs/after-pub-deploy.png
Normal file
BIN
docs/after-pub-deploy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 51 KiB |
49
package.json
49
package.json
@@ -1,38 +1,49 @@
|
|||||||
{
|
{
|
||||||
"name": "var-proxy",
|
"name": "page-proxy",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"app": {
|
||||||
|
"key": "page-proxy",
|
||||||
|
"entry": "dist/app.mjs",
|
||||||
|
"type": "pm2-system-app",
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "cross-env NODE_ENV=development nodemon --exec tsx src/index.ts",
|
"dev": "cross-env NODE_ENV=development nodemon --ignore upload --exec tsx src/index.ts",
|
||||||
"build": "rimraf dist && rollup -c",
|
"build": "rimraf dist && rollup -c",
|
||||||
"deploy": "rsync -avz dist/ light:~/apps/var-proxy/backend",
|
"start": "pm2 start dist/app.mjs --name page-proxy",
|
||||||
"reload": "ssh light pm2 restart proxy",
|
"release": "node ./scripts/release/index.mjs",
|
||||||
"pub": "npm run build && npm run deploy && npm run reload"
|
"deploy": "envision switch root && envision pack -p -u",
|
||||||
|
"pub": "npm run build && npm run deploy"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-commonjs": "^28.0.0",
|
"@rollup/plugin-commonjs": "^28.0.2",
|
||||||
"@rollup/plugin-json": "^6.1.0",
|
"@rollup/plugin-json": "^6.1.0",
|
||||||
"@rollup/plugin-node-resolve": "^15.3.0",
|
"@rollup/plugin-node-resolve": "^16.0.0",
|
||||||
"@rollup/plugin-typescript": "^12.1.0",
|
"@rollup/plugin-typescript": "^12.1.2",
|
||||||
"@types/node": "^22.7.5",
|
"@types/http-proxy": "^1.17.16",
|
||||||
|
"@types/node": "^22.13.4",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"nodemon": "^3.1.7",
|
"nodemon": "^3.1.9",
|
||||||
"rollup": "^4.24.0",
|
"rollup": "^4.34.8",
|
||||||
"ts-lib": "^0.0.5",
|
"tslib": "^2.8.1",
|
||||||
"tslib": "^2.7.0",
|
"typescript": "^5.7.3"
|
||||||
"typescript": "^5.6.3"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@abearxiong/router": "0.0.1-alpha.40",
|
"@kevisual/router": "0.0.6-alpha-5",
|
||||||
"@abearxiong/use-config": "^0.0.2",
|
"@kevisual/use-config": "^1.0.7",
|
||||||
"@abearxiong/use-file-store": "^0.0.1",
|
"ioredis": "^5.5.0",
|
||||||
"ioredis": "^5.4.1",
|
"nanoid": "^5.1.0"
|
||||||
"nanoid": "^5.0.7"
|
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"picomatch": "^4.0.2"
|
"picomatch": "^4.0.2"
|
||||||
|
75
pnpm-lock.yaml
generated
75
pnpm-lock.yaml
generated
@@ -11,7 +11,10 @@ importers:
|
|||||||
|
|
||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@abearxiong/use-config':
|
'@abearxiong/router':
|
||||||
|
specifier: 0.0.1-alpha.43
|
||||||
|
version: 0.0.1-alpha.43
|
||||||
|
'@kevisual/use-config':
|
||||||
specifier: ^0.0.2
|
specifier: ^0.0.2
|
||||||
version: 0.0.2
|
version: 0.0.2
|
||||||
'@abearxiong/use-file-store':
|
'@abearxiong/use-file-store':
|
||||||
@@ -60,8 +63,11 @@ importers:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
'@abearxiong/use-config@0.0.2':
|
'@abearxiong/router@0.0.1-alpha.43':
|
||||||
resolution: {integrity: sha512-IBOmeP46ykbDlkplFS65UsAHjyPDKnvS2oqbkpLWhbSwDbF5zhBnD4ibsFZKPCyc3lMlPeRqYva4x6puX3E/qQ==, tarball: https://npm.pkg.github.com/download/@abearxiong/use-config/0.0.2/59fbeec8c8e086ec48e55024fe39020b079e6fa5}
|
resolution: {integrity: sha512-umwi4T5s54Zb8ItseGw3uB7PDG8BqnHyAughlXH2dhub4f49fO+rwR+Zth7scUONkmc21Z27/lPgLBHXrYOkYw==, tarball: https://npm.pkg.github.com/download/@abearxiong/router/0.0.1-alpha.43/287c6e2597b36c5e3ed351b473e38f468b5f49ea}
|
||||||
|
|
||||||
|
'@kevisual/use-config@0.0.2':
|
||||||
|
resolution: {integrity: sha512-IBOmeP46ykbDlkplFS65UsAHjyPDKnvS2oqbkpLWhbSwDbF5zhBnD4ibsFZKPCyc3lMlPeRqYva4x6puX3E/qQ==, tarball: https://npm.pkg.github.com/download/@kevisual/use-config/0.0.2/59fbeec8c8e086ec48e55024fe39020b079e6fa5}
|
||||||
|
|
||||||
'@abearxiong/use-file-store@0.0.1':
|
'@abearxiong/use-file-store@0.0.1':
|
||||||
resolution: {integrity: sha512-65ZQBHxwr76sAFG+Xd4IQstx8dERhkaX5MLqtqJ0f9m+2NnS/klNe0t4q9tgjMWAEWQxHjnPShpHWzkCENaDnQ==, tarball: https://npm.pkg.github.com/download/@abearxiong/use-file-store/0.0.1/f171e398c078d4940c1ddedf5ad529d17b0eec32}
|
resolution: {integrity: sha512-65ZQBHxwr76sAFG+Xd4IQstx8dERhkaX5MLqtqJ0f9m+2NnS/klNe0t4q9tgjMWAEWQxHjnPShpHWzkCENaDnQ==, tarball: https://npm.pkg.github.com/download/@abearxiong/use-file-store/0.0.1/f171e398c078d4940c1ddedf5ad529d17b0eec32}
|
||||||
@@ -231,6 +237,9 @@ packages:
|
|||||||
'@types/estree@1.0.6':
|
'@types/estree@1.0.6':
|
||||||
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
|
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
|
||||||
|
|
||||||
|
'@types/http-proxy@1.17.15':
|
||||||
|
resolution: {integrity: sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==}
|
||||||
|
|
||||||
'@types/json-schema@7.0.15':
|
'@types/json-schema@7.0.15':
|
||||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||||
|
|
||||||
@@ -433,6 +442,9 @@ packages:
|
|||||||
estree-walker@2.0.2:
|
estree-walker@2.0.2:
|
||||||
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
||||||
|
|
||||||
|
eventemitter3@4.0.7:
|
||||||
|
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
|
||||||
|
|
||||||
events@3.3.0:
|
events@3.3.0:
|
||||||
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
|
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
|
||||||
engines: {node: '>=0.8.x'}
|
engines: {node: '>=0.8.x'}
|
||||||
@@ -455,6 +467,15 @@ packages:
|
|||||||
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
follow-redirects@1.15.9:
|
||||||
|
resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
|
||||||
|
engines: {node: '>=4.0'}
|
||||||
|
peerDependencies:
|
||||||
|
debug: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
debug:
|
||||||
|
optional: true
|
||||||
|
|
||||||
fsevents@2.3.3:
|
fsevents@2.3.3:
|
||||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||||
@@ -485,6 +506,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
http-proxy@1.18.1:
|
||||||
|
resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==}
|
||||||
|
engines: {node: '>=8.0.0'}
|
||||||
|
|
||||||
ignore-by-default@1.0.1:
|
ignore-by-default@1.0.1:
|
||||||
resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==}
|
resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==}
|
||||||
|
|
||||||
@@ -626,6 +651,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==}
|
resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
|
requires-port@1.0.0:
|
||||||
|
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
|
||||||
|
|
||||||
resolve@1.22.8:
|
resolve@1.22.8:
|
||||||
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
|
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -781,9 +809,28 @@ packages:
|
|||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
ws@8.18.0:
|
||||||
|
resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==}
|
||||||
|
engines: {node: '>=10.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
bufferutil: ^4.0.1
|
||||||
|
utf-8-validate: '>=5.0.2'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
bufferutil:
|
||||||
|
optional: true
|
||||||
|
utf-8-validate:
|
||||||
|
optional: true
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
'@abearxiong/use-config@0.0.2': {}
|
'@abearxiong/router@0.0.1-alpha.43':
|
||||||
|
dependencies:
|
||||||
|
ws: 8.18.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bufferutil
|
||||||
|
- utf-8-validate
|
||||||
|
|
||||||
|
'@kevisual/use-config@0.0.2': {}
|
||||||
|
|
||||||
'@abearxiong/use-file-store@0.0.1(typescript@5.6.3)':
|
'@abearxiong/use-file-store@0.0.1(typescript@5.6.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -916,6 +963,10 @@ snapshots:
|
|||||||
|
|
||||||
'@types/estree@1.0.6': {}
|
'@types/estree@1.0.6': {}
|
||||||
|
|
||||||
|
'@types/http-proxy@1.17.15':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 22.7.5
|
||||||
|
|
||||||
'@types/json-schema@7.0.15': {}
|
'@types/json-schema@7.0.15': {}
|
||||||
|
|
||||||
'@types/node@22.7.5':
|
'@types/node@22.7.5':
|
||||||
@@ -1133,6 +1184,8 @@ snapshots:
|
|||||||
|
|
||||||
estree-walker@2.0.2: {}
|
estree-walker@2.0.2: {}
|
||||||
|
|
||||||
|
eventemitter3@4.0.7: {}
|
||||||
|
|
||||||
events@3.3.0: {}
|
events@3.3.0: {}
|
||||||
|
|
||||||
fast-deep-equal@3.1.3: {}
|
fast-deep-equal@3.1.3: {}
|
||||||
@@ -1147,6 +1200,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
to-regex-range: 5.0.1
|
to-regex-range: 5.0.1
|
||||||
|
|
||||||
|
follow-redirects@1.15.9: {}
|
||||||
|
|
||||||
fsevents@2.3.3:
|
fsevents@2.3.3:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -1168,6 +1223,14 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
function-bind: 1.1.2
|
function-bind: 1.1.2
|
||||||
|
|
||||||
|
http-proxy@1.18.1:
|
||||||
|
dependencies:
|
||||||
|
eventemitter3: 4.0.7
|
||||||
|
follow-redirects: 1.15.9
|
||||||
|
requires-port: 1.0.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- debug
|
||||||
|
|
||||||
ignore-by-default@1.0.1: {}
|
ignore-by-default@1.0.1: {}
|
||||||
|
|
||||||
ioredis@5.4.1:
|
ioredis@5.4.1:
|
||||||
@@ -1296,6 +1359,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
redis-errors: 1.2.0
|
redis-errors: 1.2.0
|
||||||
|
|
||||||
|
requires-port@1.0.0: {}
|
||||||
|
|
||||||
resolve@1.22.8:
|
resolve@1.22.8:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-core-module: 2.15.1
|
is-core-module: 2.15.1
|
||||||
@@ -1467,3 +1532,5 @@ snapshots:
|
|||||||
which@2.0.2:
|
which@2.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
isexe: 2.0.0
|
isexe: 2.0.0
|
||||||
|
|
||||||
|
ws@8.18.0: {}
|
||||||
|
0
release/.gitkeep
Normal file
0
release/.gitkeep
Normal file
@@ -11,7 +11,7 @@ import json from '@rollup/plugin-json';
|
|||||||
export default {
|
export default {
|
||||||
input: 'src/index.ts', // TypeScript 入口文件
|
input: 'src/index.ts', // TypeScript 入口文件
|
||||||
output: {
|
output: {
|
||||||
file: 'dist/app.js', // 输出文件
|
file: 'dist/app.mjs', // 输出文件
|
||||||
format: 'es', // 输出格式设置为 ES 模块
|
format: 'es', // 输出格式设置为 ES 模块
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
@@ -25,5 +25,5 @@ export default {
|
|||||||
declaration: false,
|
declaration: false,
|
||||||
}), // 使用 @rollup/plugin-typescript 处理 TypeScript 文件
|
}), // 使用 @rollup/plugin-typescript 处理 TypeScript 文件
|
||||||
],
|
],
|
||||||
external: ['ws'],
|
external: ['ioredis', '@kevisual/router', '@kevisual/use-config'],
|
||||||
};
|
};
|
||||||
|
114
scripts/release/index.mjs
Normal file
114
scripts/release/index.mjs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import archiver from 'archiver';
|
||||||
|
import { exec } from 'child_process';
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
|
||||||
|
const cwd = process.cwd();
|
||||||
|
const pkgPath = path.join(cwd, 'package.json');
|
||||||
|
|
||||||
|
export const checkFileExistsSync = (filePath) => {
|
||||||
|
try {
|
||||||
|
// 使用 F_OK 检查文件或目录是否存在
|
||||||
|
fs.accessSync(filePath, fs.constants.F_OK);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
||||||
|
|
||||||
|
const releasePath = path.join(cwd, 'release');
|
||||||
|
|
||||||
|
const distPath = path.join(cwd, 'dist');
|
||||||
|
|
||||||
|
const zip = archiver('zip', {
|
||||||
|
zlib: { level: 9 },
|
||||||
|
});
|
||||||
|
const zipName = `page-proxy-${pkg.version}.zip`;
|
||||||
|
const zipCache = path.join(releasePath, `page-proxy-${pkg.version}.zip`);
|
||||||
|
|
||||||
|
const getZip = async () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const output = fs.createWriteStream(zipCache);
|
||||||
|
const startTime = (new Date().getTime() / 1000).toFixed(0);
|
||||||
|
// 监听事件
|
||||||
|
output.on('close', async () => {
|
||||||
|
const bytes = zip.pointer();
|
||||||
|
const size = bytes < 1024 ? `${bytes} bytes` : `${(bytes / 1024).toFixed(2)} KB`;
|
||||||
|
console.log(`Zip file has been created successfully. Total size: ${size} bytes.`);
|
||||||
|
let time = (new Date().getTime() / 1000).toFixed(0);
|
||||||
|
console.log('time', time - startTime);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
output.on('end', () => {
|
||||||
|
console.log('Data has been drained.'); // 数据已被耗尽
|
||||||
|
throw new CustomError('Data has been drained.');
|
||||||
|
});
|
||||||
|
|
||||||
|
zip.on('warning', (err) => {
|
||||||
|
if (err.code === 'ENOENT') {
|
||||||
|
console.warn('File not found:', err);
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
zip.on('error', (err) => {
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 通过管道将 zip 数据流输出到指定文件
|
||||||
|
zip.pipe(output);
|
||||||
|
|
||||||
|
// 添加 sh 字符串作为文件到 zip 中
|
||||||
|
const sh = `#!/bin/bash
|
||||||
|
npm i -g pnpm
|
||||||
|
pnpm install --prod
|
||||||
|
`;
|
||||||
|
zip.append(sh, { name: 'start.sh' });
|
||||||
|
// 把dist目录下的文件添加到zip中
|
||||||
|
zip.directory(distPath, 'dist');
|
||||||
|
// 把README.md添加到zip中
|
||||||
|
zip.file(path.join(cwd, 'README.md'), { name: 'README.md' });
|
||||||
|
// 把package.json添加到zip中
|
||||||
|
zip.file(pkgPath, { name: 'package.json' });
|
||||||
|
const ecosystemContent = `module.exports = {
|
||||||
|
apps: [
|
||||||
|
{
|
||||||
|
name: 'page-proxy', // 应用名称
|
||||||
|
script: './dist/app.mjs', // 入口文件
|
||||||
|
// cwd: '.', // 设置当前工作目录
|
||||||
|
output: './logs/page-proxy.log',
|
||||||
|
error: './logs/page-proxy.log',
|
||||||
|
log_date_format: 'YYYY-MM-DD HH:mm:ss',
|
||||||
|
// watch: true, // 自动监控文件变化
|
||||||
|
watch: ['dist'], // 监控的文件夹
|
||||||
|
ignore_watch: ['node_modules', 'logs'], // 忽略的文件夹
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
zip.append(ecosystemContent, { name: 'ecosystem.config.cjs' });
|
||||||
|
const json5Content = fs.readFileSync(path.join(cwd, 'app.config.json5.example'), 'utf8');
|
||||||
|
// tokenSecret 是一个随机字符串,用于生成 token
|
||||||
|
const tokenSecret = 'XX' + nanoid(39);
|
||||||
|
json5Content.replace('<TOKEN_SECRET>', tokenSecret);
|
||||||
|
// tokenSecret
|
||||||
|
// 把app.config.json5.example添加到zip中
|
||||||
|
// zip.file(path.join(cwd, 'app.config.json5.example'), { name: 'app.config.json5.example' });
|
||||||
|
zip.append(json5Content, { name: 'app.config.json5.example' });
|
||||||
|
|
||||||
|
// 结束归档(必须调用,否则 zip 文件无法完成)
|
||||||
|
zip.finalize();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
getZip().then(() => {
|
||||||
|
console.log('zip success');
|
||||||
|
console.log(`envision switchOrg system && envision deploy ./release/${zipName} -v 1.0.0 -k page-proxy -y y -u`);
|
||||||
|
|
||||||
|
console.log(`download zip: https://kevisual.xiongxiao.me/system/page-proxy/${zipName}`);
|
||||||
|
});
|
9
src/app.ts
Normal file
9
src/app.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { App } from '@kevisual/router';
|
||||||
|
|
||||||
|
export const app = new App({
|
||||||
|
serverOptions: {
|
||||||
|
path: '/api/proxy',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// app.server.on(callback);
|
29
src/index.ts
29
src/index.ts
@@ -1,14 +1,21 @@
|
|||||||
import http from 'http';
|
|
||||||
import { handleRequest } from './module/index.ts';
|
import { handleRequest } from './module/index.ts';
|
||||||
import { useConfig } from '@abearxiong/use-config';
|
import { config } from './module/config.ts';
|
||||||
const { port } = useConfig<{ port: number }>();
|
import { app } from './app.ts';
|
||||||
const server = http.createServer((req, res) => {
|
import './route/route.ts';
|
||||||
// res.writeHead(200, { 'Content-Type': 'text/plain' });
|
const port = config?.proxy?.port || 3005;
|
||||||
// const pathname = new URL(req.url, `http://${dns.hostName}`).pathname;
|
|
||||||
handleRequest(req, res);
|
app
|
||||||
// res.write(`Request from ${dns.hostName} with IP: ${dns.ip}\n`);
|
.route({
|
||||||
// res.end('Hello World\n');
|
path: 'hello',
|
||||||
});
|
key: '0',
|
||||||
server.listen(port, () => {
|
})
|
||||||
|
.define(async (ctx) => {
|
||||||
|
ctx.body = 'hello world';
|
||||||
|
})
|
||||||
|
.addTo(app);
|
||||||
|
|
||||||
|
app.listen(port, () => {
|
||||||
console.log(`Server running at http://localhost:${port}/`);
|
console.log(`Server running at http://localhost:${port}/`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.server.on(handleRequest);
|
||||||
|
33
src/module/config.ts
Normal file
33
src/module/config.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { useConfig } from '@kevisual/use-config';
|
||||||
|
import { useFileStore } from '@kevisual/use-config/file-store';
|
||||||
|
export const fileStore = useFileStore('proxy-upload');
|
||||||
|
|
||||||
|
type ConfigType = {
|
||||||
|
api: {
|
||||||
|
/**
|
||||||
|
* API host address
|
||||||
|
*/
|
||||||
|
host: string;
|
||||||
|
path?: string;
|
||||||
|
port?: number;
|
||||||
|
};
|
||||||
|
proxy: {
|
||||||
|
port?: number;
|
||||||
|
/**
|
||||||
|
* self domain kevisual.xiongxiao.me
|
||||||
|
*/
|
||||||
|
domain: string;
|
||||||
|
/**
|
||||||
|
* resources path
|
||||||
|
* https://minio.xiongxiao.me/resources
|
||||||
|
*/
|
||||||
|
resources: string;
|
||||||
|
/**
|
||||||
|
* allow origin xiongxiao.me zxj.im silkyai.cn
|
||||||
|
* 允许跨域访问的地址
|
||||||
|
*/
|
||||||
|
allowOrigin: string[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const config = useConfig<ConfigType>();
|
@@ -1,16 +1,15 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { redis, subscriber } from './redis/redis.ts';
|
import { redis, subscriber } from './redis/redis.ts';
|
||||||
import { useFileStore } from '@abearxiong/use-file-store';
|
import { config, fileStore } from '../module/config.ts';
|
||||||
import { useConfig } from '@abearxiong/use-config';
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { pipeline } from 'stream';
|
import { pipeline } from 'stream';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
|
import { fetchApp, fetchDomain, fetchTest } from './query/get-router.ts';
|
||||||
const pipelineAsync = promisify(pipeline);
|
const pipelineAsync = promisify(pipeline);
|
||||||
|
|
||||||
const { resources, api } = useConfig<{ resources: string; api: { host: string; path: string } }>();
|
const { resources } = config?.proxy || { resources: 'https://minio.xiongxiao.me/resources' };
|
||||||
const fileStore = useFileStore('upload');
|
|
||||||
const status: { [key: string]: boolean } = {};
|
const status: { [key: string]: boolean } = {};
|
||||||
const demoData = {
|
const demoData = {
|
||||||
user: 'root',
|
user: 'root',
|
||||||
@@ -44,9 +43,13 @@ type UserAppOptions = {
|
|||||||
export class UserApp {
|
export class UserApp {
|
||||||
user: string;
|
user: string;
|
||||||
app: string;
|
app: string;
|
||||||
|
isTest: boolean;
|
||||||
constructor(options: UserAppOptions) {
|
constructor(options: UserAppOptions) {
|
||||||
this.user = options.user;
|
this.user = options.user;
|
||||||
this.app = options.app;
|
this.app = options.app;
|
||||||
|
if (this.user === 'test') {
|
||||||
|
this.isTest = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
async getExist() {
|
async getExist() {
|
||||||
const app = this.app;
|
const app = this.app;
|
||||||
@@ -63,6 +66,7 @@ export class UserApp {
|
|||||||
if (!value) {
|
if (!value) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
return JSON.parse(value);
|
||||||
}
|
}
|
||||||
async getFile(appFileUrl: string) {
|
async getFile(appFileUrl: string) {
|
||||||
const app = this.app;
|
const app = this.app;
|
||||||
@@ -83,20 +87,12 @@ export class UserApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取域名对应的用户和应用
|
// 获取域名对应的用户和应用
|
||||||
const fetchUrl = 'http://' + api.host + api.path;
|
const fetchRes = await fetchDomain(domain).catch((err) => {
|
||||||
const fetchRes = await fetch(fetchUrl, {
|
return {
|
||||||
method: 'POST',
|
code: 500,
|
||||||
headers: {
|
message: err,
|
||||||
'Content-Type': 'application/json',
|
};
|
||||||
},
|
});
|
||||||
body: JSON.stringify({
|
|
||||||
path: 'app',
|
|
||||||
key: 'getDomainApp',
|
|
||||||
data: {
|
|
||||||
domain,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}).then((res) => res.json());
|
|
||||||
if (fetchRes?.code !== 200) {
|
if (fetchRes?.code !== 200) {
|
||||||
console.log('fetchRes is error', fetchRes);
|
console.log('fetchRes is error', fetchRes);
|
||||||
return null;
|
return null;
|
||||||
@@ -121,31 +117,25 @@ export class UserApp {
|
|||||||
status[key] = false;
|
status[key] = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async setCacheData() {
|
async getLoaded() {
|
||||||
const app = this.app;
|
const app = this.app;
|
||||||
const user = this.user;
|
const user = this.user;
|
||||||
const key = 'user:app:' + app + ':' + user;
|
const key = 'user:app:' + app + ':' + user;
|
||||||
const fetchUrl = 'http://' + api.host + api.path;
|
return status[key];
|
||||||
|
}
|
||||||
|
async setCacheData() {
|
||||||
|
const app = this.app;
|
||||||
|
const user = this.user;
|
||||||
|
const isTest = this.isTest;
|
||||||
|
const key = 'user:app:' + app + ':' + user;
|
||||||
if (status[key]) {
|
if (status[key]) {
|
||||||
return {
|
return {
|
||||||
loading: true,
|
loading: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
status[key] = true;
|
status[key] = true;
|
||||||
const fetchRes = await fetch(fetchUrl, {
|
|
||||||
method: 'POST',
|
const fetchRes = isTest ? await fetchTest(app) : await fetchApp({ user, app });
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
path: 'app',
|
|
||||||
key: 'getApp',
|
|
||||||
data: {
|
|
||||||
user,
|
|
||||||
key: app,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}).then((res) => res.json());
|
|
||||||
if (fetchRes?.code !== 200) {
|
if (fetchRes?.code !== 200) {
|
||||||
console.log('fetchRes is error', fetchRes);
|
console.log('fetchRes is error', fetchRes);
|
||||||
this.setLoaded();
|
this.setLoaded();
|
||||||
@@ -214,6 +204,9 @@ export class UserApp {
|
|||||||
// 删除所有文件
|
// 删除所有文件
|
||||||
deleteUserAppFiles(user, app);
|
deleteUserAppFiles(user, app);
|
||||||
}
|
}
|
||||||
|
fileCheck(file: string) {
|
||||||
|
return checkFileExistsSync(file);
|
||||||
|
}
|
||||||
async close() {
|
async close() {
|
||||||
// 关闭连接
|
// 关闭连接
|
||||||
await redis.quit();
|
await redis.quit();
|
||||||
@@ -229,28 +222,38 @@ export const downloadUserAppFiles = async (user: string, app: string, data: type
|
|||||||
fs.mkdirSync(uploadFiles, { recursive: true });
|
fs.mkdirSync(uploadFiles, { recursive: true });
|
||||||
}
|
}
|
||||||
const newFiles = [];
|
const newFiles = [];
|
||||||
if (data.type === 'local') {
|
try {
|
||||||
// local copy file
|
if (data.type === 'local') {
|
||||||
for (let i = 0; i < files.length; i++) {
|
// local copy file
|
||||||
const file = files[i];
|
for (let i = 0; i < files.length; i++) {
|
||||||
const copyFile = path.join(fileStore, file.path);
|
const file = files[i];
|
||||||
const destFile = path.join(uploadFiles, file.name);
|
const copyFile = path.join(fileStore, file.path);
|
||||||
const destDir = path.dirname(destFile); // 获取目标文件所在的目录路径
|
const destFile = path.join(uploadFiles, file.name);
|
||||||
// 检查目录是否存在,如果不存在则创建
|
const destDir = path.dirname(destFile); // 获取目标文件所在的目录路径
|
||||||
if (!checkFileExistsSync(destDir)) {
|
// 检查目录是否存在,如果不存在则创建
|
||||||
fs.mkdirSync(destDir, { recursive: true }); // 递归创建目录
|
if (!checkFileExistsSync(destDir)) {
|
||||||
|
fs.mkdirSync(destDir, { recursive: true }); // 递归创建目录
|
||||||
|
}
|
||||||
|
fs.copyFileSync(copyFile, destFile);
|
||||||
|
// const etag = await setEtag(fs.readFileSync(destFile, 'utf-8'));
|
||||||
|
const etag = nanoid();
|
||||||
|
newFiles.push({
|
||||||
|
name: file.name,
|
||||||
|
path: destFile.replace(fileStore, '') + '||' + etag,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
fs.copyFileSync(copyFile, destFile);
|
|
||||||
// const etag = await setEtag(fs.readFileSync(destFile, 'utf-8'));
|
|
||||||
const etag = nanoid();
|
|
||||||
newFiles.push({
|
|
||||||
name: file.name,
|
|
||||||
path: destFile.replace(fileStore, '') + '||' + etag,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
const userApp = new UserApp({ user, app });
|
||||||
|
userApp.clearCacheData();
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
files: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if (data.type === 'oss') {
|
if (data.type === 'oss') {
|
||||||
const serverPath = 'https://' + resources + '/';
|
let serverPath = new URL(resources).href + '/';
|
||||||
// server download file
|
// server download file
|
||||||
for (let i = 0; i < files.length; i++) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
const file = files[i];
|
const file = files[i];
|
||||||
@@ -292,7 +295,11 @@ export const deleteUserAppFiles = async (user: string, app: string) => {
|
|||||||
try {
|
try {
|
||||||
fs.rmSync(uploadFiles, { recursive: true });
|
fs.rmSync(uploadFiles, { recursive: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('deleteUserAppFiles', err);
|
if (err.code === 'ENOENT') {
|
||||||
|
// 文件不存在
|
||||||
|
} else {
|
||||||
|
console.error('deleteUserAppFiles', err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// console.log('deleteUserAppFiles', res);
|
// console.log('deleteUserAppFiles', res);
|
||||||
};
|
};
|
||||||
|
@@ -1,25 +1,74 @@
|
|||||||
import { getDNS, isLocalhost } from '@/utils/dns.ts';
|
import { getDNS, isLocalhost } from '@/utils/dns.ts';
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
import { UserApp } from './get-user-app.ts';
|
import { UserApp } from './get-user-app.ts';
|
||||||
import { useFileStore } from '@abearxiong/use-file-store';
|
import { config, fileStore } from '../module/config.ts';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { useConfig } from '@abearxiong/use-config';
|
|
||||||
import { redis } from './redis/redis.ts';
|
|
||||||
import { getContentType } from './get-content-type.ts';
|
import { getContentType } from './get-content-type.ts';
|
||||||
import { sleep } from '@/utils/sleep.ts';
|
import { sleep } from '@/utils/sleep.ts';
|
||||||
const { api, domain, allowedOrigins } = useConfig<{
|
|
||||||
api: {
|
|
||||||
host: string;
|
|
||||||
};
|
|
||||||
domain: string;
|
|
||||||
allowedOrigins: string[];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const fileStore = useFileStore('upload');
|
const api = config?.api || { host: 'kevisual.xiongxiao.me', path: '/api/router' };
|
||||||
console.log('filePath', fileStore);
|
const domain = config?.proxy?.domain || 'kevisual.xiongxiao.me';
|
||||||
|
const allowedOrigins = config?.proxy?.allowOrigin || [];
|
||||||
|
|
||||||
|
|
||||||
const noProxyUrl = ['/', '/favicon.ico'];
|
const noProxyUrl = ['/', '/favicon.ico'];
|
||||||
export const handleRequest = async (req: http.IncomingMessage, res: http.ServerResponse) => {
|
export const handleRequest = async (req: http.IncomingMessage, res: http.ServerResponse) => {
|
||||||
|
if (req.url === '/favicon.ico') {
|
||||||
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
||||||
|
res.write('proxy no favicon.ico\n');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (req.url.startsWith('/api/proxy')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (req.url.startsWith('/api')) {
|
||||||
|
// 代理到 http://codeflow.xiongxiao.me/api
|
||||||
|
const _u = new URL(req.url, `http://${api.host}`);
|
||||||
|
// 设置代理请求的目标 URL 和请求头
|
||||||
|
let header: any = {};
|
||||||
|
if (req.headers?.['Authorization']) {
|
||||||
|
header.authorization = req.headers['Authorization'];
|
||||||
|
} else if (req.headers?.['authorization']) {
|
||||||
|
header.authorization = req.headers['authorization'];
|
||||||
|
}
|
||||||
|
if (req.headers?.['Content-Type']) {
|
||||||
|
header['Content-Type'] = req.headers?.['Content-Type'];
|
||||||
|
}
|
||||||
|
const options = {
|
||||||
|
host: _u.hostname,
|
||||||
|
path: req.url,
|
||||||
|
method: req.method,
|
||||||
|
headers: {
|
||||||
|
...header,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (_u.port) {
|
||||||
|
// @ts-ignore
|
||||||
|
options.port = _u.port;
|
||||||
|
}
|
||||||
|
// 创建代理请求
|
||||||
|
const proxyReq = http.request(options, (proxyRes) => {
|
||||||
|
// 将代理服务器的响应头和状态码返回给客户端
|
||||||
|
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
||||||
|
// 将代理响应流写入客户端响应
|
||||||
|
proxyRes.pipe(res, { end: true });
|
||||||
|
});
|
||||||
|
// 处理代理请求的错误事件
|
||||||
|
proxyReq.on('error', (err) => {
|
||||||
|
console.error(`Proxy request error: ${err.message}`);
|
||||||
|
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
||||||
|
res.write(`Proxy request error: ${err.message}`);
|
||||||
|
});
|
||||||
|
// 处理 POST 请求的请求体(传递数据到目标服务器)
|
||||||
|
req.pipe(proxyReq, { end: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (req.url.startsWith('/api')) {
|
||||||
|
res.end('not catch api');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const dns = getDNS(req);
|
const dns = getDNS(req);
|
||||||
// 配置可以跨域
|
// 配置可以跨域
|
||||||
// 配置可以访问的域名 localhost, xiongxiao.me
|
// 配置可以访问的域名 localhost, xiongxiao.me
|
||||||
@@ -42,7 +91,6 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR
|
|||||||
// app = 'codeflow';
|
// app = 'codeflow';
|
||||||
// domainApp = true;
|
// domainApp = true;
|
||||||
} else {
|
} else {
|
||||||
// 生产环境
|
|
||||||
// 验证域名
|
// 验证域名
|
||||||
if (dns.hostName !== domain) {
|
if (dns.hostName !== domain) {
|
||||||
// redis获取域名对应的用户和应用
|
// redis获取域名对应的用户和应用
|
||||||
@@ -62,8 +110,10 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR
|
|||||||
app = data.app;
|
app = data.app;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const url = req.url;
|
// const url = req.url;
|
||||||
if (!domainApp && noProxyUrl.includes(req.url)) {
|
const pathname = new URL(req.url, `http://${dns.hostName}`).pathname;
|
||||||
|
const url = pathname;
|
||||||
|
if (!domainApp && noProxyUrl.includes(url)) {
|
||||||
res.write('No proxy for this URL\n');
|
res.write('No proxy for this URL\n');
|
||||||
return res.end();
|
return res.end();
|
||||||
}
|
}
|
||||||
@@ -89,42 +139,7 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR
|
|||||||
user = _user;
|
user = _user;
|
||||||
app = _app;
|
app = _app;
|
||||||
}
|
}
|
||||||
const [_, _api] = req.url.split('/');
|
|
||||||
if (_api === 'api') {
|
|
||||||
// 代理到 http://codeflow.xiongxiao.me/api
|
|
||||||
// 设置代理请求的目标 URL 和请求头
|
|
||||||
let header: any = {};
|
|
||||||
if (req.headers?.['Authroization']) {
|
|
||||||
header.Authorization = req.headers?.['Authroization'];
|
|
||||||
}
|
|
||||||
if (req.headers?.['Content-Type']) {
|
|
||||||
header['Content-Type'] = req.headers?.['Content-Type'];
|
|
||||||
}
|
|
||||||
const options = {
|
|
||||||
host: api.host,
|
|
||||||
path: req.url,
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
...header,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
// 创建代理请求
|
|
||||||
const proxyReq = http.request(options, (proxyRes) => {
|
|
||||||
// 将代理服务器的响应头和状态码返回给客户端
|
|
||||||
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
|
||||||
// 将代理响应流写入客户端响应
|
|
||||||
proxyRes.pipe(res, { end: true });
|
|
||||||
});
|
|
||||||
// 处理代理请求的错误事件
|
|
||||||
proxyReq.on('error', (err) => {
|
|
||||||
console.error(`Proxy request error: ${err.message}`);
|
|
||||||
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
||||||
res.write(`Proxy request error: ${err.message}`);
|
|
||||||
});
|
|
||||||
// 处理 POST 请求的请求体(传递数据到目标服务器)
|
|
||||||
req.pipe(proxyReq, { end: true });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const userApp = new UserApp({ user, app });
|
const userApp = new UserApp({ user, app });
|
||||||
let isExist = await userApp.getExist();
|
let isExist = await userApp.getExist();
|
||||||
if (!isExist) {
|
if (!isExist) {
|
||||||
@@ -172,6 +187,14 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR
|
|||||||
const [indexFilePath, etag] = indexFile.split('||');
|
const [indexFilePath, etag] = indexFile.split('||');
|
||||||
const contentType = getContentType(indexFilePath);
|
const contentType = getContentType(indexFilePath);
|
||||||
const isHTML = contentType.includes('html');
|
const isHTML = contentType.includes('html');
|
||||||
|
const filePath = path.join(fileStore, indexFilePath);
|
||||||
|
if (!userApp.fileCheck(filePath)) {
|
||||||
|
res.writeHead(500, { 'Content-Type': 'text/html' });
|
||||||
|
res.write('File expired, Not Found\n');
|
||||||
|
res.end();
|
||||||
|
await userApp.clearCacheData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
// 如果 content是 'application/octet-stream' 会下载文件, 添加文件后缀
|
// 如果 content是 'application/octet-stream' 会下载文件, 添加文件后缀
|
||||||
if (contentType === 'application/octet-stream') {
|
if (contentType === 'application/octet-stream') {
|
||||||
// 提取文件名,只保留文件名而不是整个路径
|
// 提取文件名,只保留文件名而不是整个路径
|
||||||
@@ -181,9 +204,9 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR
|
|||||||
// 不存在的文件,返回indexFile的文件
|
// 不存在的文件,返回indexFile的文件
|
||||||
res.writeHead(200, { 'Content-Type': contentType, 'Cache-Control': isHTML ? 'no-cache' : 'public, max-age=3600' });
|
res.writeHead(200, { 'Content-Type': contentType, 'Cache-Control': isHTML ? 'no-cache' : 'public, max-age=3600' });
|
||||||
|
|
||||||
const filePath = path.join(fileStore, indexFilePath);
|
|
||||||
const readStream = fs.createReadStream(filePath);
|
const readStream = fs.createReadStream(filePath);
|
||||||
readStream.pipe(res);
|
readStream.pipe(res);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
const [appFilePath, eTag] = appFile.split('||');
|
const [appFilePath, eTag] = appFile.split('||');
|
||||||
@@ -207,8 +230,17 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR
|
|||||||
'Cache-Control': isHTML ? 'no-cache' : 'public, max-age=3600', // 设置缓存时间为 1 小时
|
'Cache-Control': isHTML ? 'no-cache' : 'public, max-age=3600', // 设置缓存时间为 1 小时
|
||||||
ETag: eTag,
|
ETag: eTag,
|
||||||
});
|
});
|
||||||
|
if (!userApp.fileCheck(filePath)) {
|
||||||
|
console.error('File expired', filePath);
|
||||||
|
res.writeHead(500, { 'Content-Type': 'text/html' });
|
||||||
|
res.write('File expired\n');
|
||||||
|
res.end();
|
||||||
|
await userApp.clearCacheData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const readStream = fs.createReadStream(filePath);
|
const readStream = fs.createReadStream(filePath);
|
||||||
readStream.pipe(res);
|
readStream.pipe(res);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
56
src/module/query/get-router.ts
Normal file
56
src/module/query/get-router.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { config } from '../config.ts';
|
||||||
|
|
||||||
|
const api = config?.api || { host: 'kevisual.xiongxiao.me', path: '/api/router' };
|
||||||
|
const apiPath = api.path || '/api/router';
|
||||||
|
export const fetchTest = async (id: string) => {
|
||||||
|
const fetchUrl = 'http://' + api.host + apiPath;
|
||||||
|
const fetchRes = await fetch(fetchUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
path: 'user-app',
|
||||||
|
key: 'test',
|
||||||
|
id: id,
|
||||||
|
}),
|
||||||
|
}).then((res) => res.json());
|
||||||
|
return fetchRes;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchDomain = async (domain: string) => {
|
||||||
|
const fetchUrl = 'http://' + api.host + apiPath;
|
||||||
|
const fetchRes = await fetch(fetchUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
path: 'app',
|
||||||
|
key: 'getDomainApp',
|
||||||
|
data: {
|
||||||
|
domain,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}).then((res) => res.json());
|
||||||
|
return fetchRes;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchApp = async ({ user, app }) => {
|
||||||
|
const fetchUrl = 'http://' + api.host + apiPath;
|
||||||
|
const fetchRes = await fetch(fetchUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
path: 'app',
|
||||||
|
key: 'getApp',
|
||||||
|
data: {
|
||||||
|
user,
|
||||||
|
key: app,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}).then((res) => res.json());
|
||||||
|
return fetchRes;
|
||||||
|
};
|
@@ -1,5 +1,5 @@
|
|||||||
import { Redis } from 'ioredis';
|
import { Redis } from 'ioredis';
|
||||||
import { useConfig } from '@abearxiong/use-config';
|
import { useConfig } from '@kevisual/use-config';
|
||||||
|
|
||||||
const config = useConfig<{
|
const config = useConfig<{
|
||||||
redis: ConstructorParameters<typeof Redis>;
|
redis: ConstructorParameters<typeof Redis>;
|
||||||
|
1
src/route/app/index.ts
Normal file
1
src/route/app/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
import './list.ts'
|
97
src/route/app/list.ts
Normal file
97
src/route/app/list.ts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import { UserApp } from '@/module/get-user-app.ts';
|
||||||
|
import { app } from '../../app.ts';
|
||||||
|
import { redis } from '@/module/redis/redis.ts';
|
||||||
|
import fs from 'fs';
|
||||||
|
import { fileStore } from '../../module/config.ts';
|
||||||
|
|
||||||
|
app
|
||||||
|
.route({
|
||||||
|
path: 'app',
|
||||||
|
key: 'list',
|
||||||
|
})
|
||||||
|
.define(async (ctx) => {
|
||||||
|
const keys = await redis.keys('user:app:*');
|
||||||
|
// const keys = await redis.keys('user:app:exist:*');
|
||||||
|
// const data = await redis.mget(...keys);
|
||||||
|
ctx.body = {
|
||||||
|
// data: data,
|
||||||
|
keys,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.addTo(app);
|
||||||
|
|
||||||
|
app
|
||||||
|
.route({
|
||||||
|
path: 'app',
|
||||||
|
key: 'delete',
|
||||||
|
})
|
||||||
|
.define(async (ctx) => {
|
||||||
|
const { user, app } = ctx.query;
|
||||||
|
try {
|
||||||
|
const userApp = new UserApp({ user, app });
|
||||||
|
await userApp.clearCacheData();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
ctx.throw('删除失败');
|
||||||
|
}
|
||||||
|
ctx.body = 'successfully';
|
||||||
|
})
|
||||||
|
.addTo(app);
|
||||||
|
|
||||||
|
app
|
||||||
|
.route({
|
||||||
|
path: 'app',
|
||||||
|
key: 'deleteAll',
|
||||||
|
})
|
||||||
|
.define(async (ctx) => {
|
||||||
|
const keys = await redis.keys('user:app:*');
|
||||||
|
for (const key of keys) {
|
||||||
|
await redis.set(key, '', 'EX', 1);
|
||||||
|
}
|
||||||
|
ctx.body = {
|
||||||
|
keys,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.addTo(app);
|
||||||
|
|
||||||
|
app
|
||||||
|
.route({
|
||||||
|
path: 'app',
|
||||||
|
key: 'clear',
|
||||||
|
})
|
||||||
|
.define(async (ctx) => {
|
||||||
|
const keys = await redis.keys('user:app:*');
|
||||||
|
if (keys.length > 0) {
|
||||||
|
await redis.del(...keys);
|
||||||
|
}
|
||||||
|
fs.rmSync(fileStore, { recursive: true });
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
keys,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.addTo(app);
|
||||||
|
|
||||||
|
app
|
||||||
|
.route({
|
||||||
|
path: 'app',
|
||||||
|
key: 'get',
|
||||||
|
})
|
||||||
|
.define(async (ctx) => {
|
||||||
|
const { user, app } = ctx.query.data || {};
|
||||||
|
if (!user || !app) {
|
||||||
|
if (!user) {
|
||||||
|
ctx.throw('user is required');
|
||||||
|
}
|
||||||
|
if (!app) {
|
||||||
|
ctx.throw('app is required');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const userApp = new UserApp({ user, app });
|
||||||
|
const cache = await userApp.getCache();
|
||||||
|
if (!cache) {
|
||||||
|
ctx.throw('Not Found App');
|
||||||
|
}
|
||||||
|
ctx.body = cache;
|
||||||
|
})
|
||||||
|
.addTo(app);
|
1
src/route/route.ts
Normal file
1
src/route/route.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
import './app/index.ts'
|
@@ -1,8 +1,7 @@
|
|||||||
import { UserApp, clearAllUserApp } from '../module/get-user-app.ts';
|
import { UserApp, clearAllUserApp } from '../module/get-user-app.ts';
|
||||||
import { redis } from '../module/redis/redis.ts';
|
import { redis } from '../module/redis/redis.ts';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { useFileStore } from '@abearxiong/use-file-store';
|
import { config, fileStore } from '../module/config.ts';
|
||||||
const filePath = useFileStore('upload');
|
|
||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
const userApp = new UserApp({ user: 'root', app: 'codeflow' });
|
const userApp = new UserApp({ user: 'root', app: 'codeflow' });
|
||||||
@@ -33,7 +32,6 @@ const clearData = async () => {
|
|||||||
// clearData();
|
// clearData();
|
||||||
// clearAllUserApp();
|
// clearAllUserApp();
|
||||||
|
|
||||||
|
|
||||||
const expireData = async () => {
|
const expireData = async () => {
|
||||||
await redis.set('user:app:exist:' + 'codeflow:root', 'value', 'EX', 2);
|
await redis.set('user:app:exist:' + 'codeflow:root', 'value', 'EX', 2);
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
@@ -45,5 +43,5 @@ const keysData = async () => {
|
|||||||
const keys = await redis.keys('user:app:exist:*');
|
const keys = await redis.keys('user:app:exist:*');
|
||||||
console.log('keys', keys);
|
console.log('keys', keys);
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
};
|
||||||
keysData();
|
keysData();
|
||||||
|
@@ -7,5 +7,5 @@ export const getDNS = (req: http.IncomingMessage) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const isLocalhost = (hostName: string) => {
|
export const isLocalhost = (hostName: string) => {
|
||||||
return hostName.includes('localhost');
|
return hostName.includes('localhost') || hostName.includes('192.168');
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user