✨
This commit is contained in:
parent
0d0b24de75
commit
bcfbe0d6b3
11
CHANGELOG.md
11
CHANGELOG.md
@ -1,3 +1,14 @@
|
|||||||
|
## [1.2.1](https://github.com/chaos-zhu/easynode/releases) (2022-12-12)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 新增支持终端长命令输入模式 ✔
|
||||||
|
* 新增前端静态文件缓存 ✔
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* v1.2的若干bug...
|
||||||
|
|
||||||
## [1.2.0](https://github.com/chaos-zhu/easynode/releases) (2022-09-12)
|
## [1.2.0](https://github.com/chaos-zhu/easynode/releases) (2022-09-12)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
10
Q&A.md
10
Q&A.md
@ -1,14 +1,12 @@
|
|||||||
# 用于收集一些疑难杂症
|
# Q&A
|
||||||
|
|
||||||
- **欢迎pr~**
|
## CentOS7/8启动服务失败
|
||||||
|
|
||||||
## 甲骨文CentOS7/8启动服务失败
|
|
||||||
|
|
||||||
> 先关闭SELinux
|
> 先关闭SELinux
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
vi /etc/selinux/config
|
vi /etc/selinux/config
|
||||||
SELINUX=enforcing
|
SELINUX=enforcing
|
||||||
// 修改为禁用
|
// 修改为禁用
|
||||||
SELINUX=disabled
|
SELINUX=disabled
|
||||||
```
|
```
|
||||||
@ -17,6 +15,6 @@ SELINUX=disabled
|
|||||||
|
|
||||||
> 查看SELinux状态:sestatus
|
> 查看SELinux状态:sestatus
|
||||||
|
|
||||||
## 甲骨文ubuntu20.04客户端服务启动成功,无法连接?
|
## 客户端服务启动成功,无法连接?
|
||||||
|
|
||||||
> 端口未开放:`iptables -I INPUT -s 0.0.0.0/0 -p tcp --dport 22022 -j ACCEPT` 或者 `rm -rf /etc/iptables && reboot`
|
> 端口未开放:`iptables -I INPUT -s 0.0.0.0/0 -p tcp --dport 22022 -j ACCEPT` 或者 `rm -rf /etc/iptables && reboot`
|
||||||
|
10
README.md
10
README.md
@ -42,12 +42,10 @@
|
|||||||
|
|
||||||
- 依赖Node.js环境
|
- 依赖Node.js环境
|
||||||
|
|
||||||
- 占用端口:8082(http端口)、8083(https端口)、22022(客户端端口)
|
- 占用端口:8082(http端口)、22022(客户端端口)
|
||||||
|
|
||||||
- 建议使用**境外服务器**(最好延迟低)安装服务端,客户端信息监控与webssh功能都将以`该服务器作为跳板机`
|
- 建议使用**境外服务器**(最好延迟低)安装服务端,客户端信息监控与webssh功能都将以`该服务器作为跳板机`
|
||||||
|
|
||||||
- https服务需自行配置证书,或者使用`nginx反代`解决(推荐)
|
|
||||||
|
|
||||||
#### Docker镜像
|
#### Docker镜像
|
||||||
|
|
||||||
> 注意:网速统计功能可能受限,docker网络将使用host模式(与宿主机共享端口,占用: 8082、22022)
|
> 注意:网速统计功能可能受限,docker网络将使用host模式(与宿主机共享端口,占用: 8082、22022)
|
||||||
@ -88,12 +86,6 @@ wget -qO- --no-check-certificate https://ghproxy.com/https://raw.githubuserconte
|
|||||||
|
|
||||||
- 默认登录密码:admin(首次部署完成后请及时修改).
|
- 默认登录密码:admin(首次部署完成后请及时修改).
|
||||||
|
|
||||||
6. 部署https服务
|
|
||||||
- 部署https服务需要自己上传域名证书至`\server\app\config\pem`,并且证书和私钥分别命名:`key.pem`和`cert.pem`
|
|
||||||
- 配置域名:vim server/app/config/index.js 在domain字段中填写你解析到服务器的域名
|
|
||||||
- pm2 restart easynode-server
|
|
||||||
- 不出意外你就可以访问https服务:https://domain:8083
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 客户端安装
|
### 客户端安装
|
||||||
|
0
client/bin/www
Normal file → Executable file
0
client/bin/www
Normal file → Executable file
@ -73,9 +73,16 @@ mv ${FILE_PATH}/${SERVER_NAME}.service ${SERVICE_PATH}
|
|||||||
# echo "***********************daemon-reload***********************"
|
# echo "***********************daemon-reload***********************"
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
|
|
||||||
echo "***********************启动服务***********************"
|
echo "***********************准备启动服务***********************"
|
||||||
systemctl start ${SERVER_NAME}
|
systemctl start ${SERVER_NAME}
|
||||||
|
|
||||||
|
if [ $? != 0 ]
|
||||||
|
then
|
||||||
|
echo "***********************${SERVER_NAME}.service启动失败***********************"
|
||||||
|
echo "***********************可能是服务器开启了SELinux, 参见Q&A***********************"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "***********************服务启动成功***********************"
|
||||||
|
|
||||||
# echo "***********************设置开机启动***********************"
|
# echo "***********************设置开机启动***********************"
|
||||||
systemctl enable ${SERVER_NAME}
|
systemctl enable ${SERVER_NAME}
|
||||||
|
@ -1,25 +1,11 @@
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const fs = require('fs')
|
|
||||||
|
|
||||||
const getCertificate =() => {
|
|
||||||
try {
|
|
||||||
return {
|
|
||||||
cert: fs.readFileSync(path.join(__dirname, './pem/cert.pem')),
|
|
||||||
key: fs.readFileSync(path.join(__dirname, './pem/key.pem'))
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
domain: 'xxx.com', // https域名, 可不配置
|
|
||||||
httpPort: 8082,
|
httpPort: 8082,
|
||||||
httpsPort: 8083,
|
|
||||||
clientPort: 22022, // 勿更改
|
clientPort: 22022, // 勿更改
|
||||||
certificate: getCertificate(),
|
|
||||||
uploadDir: path.join(process.cwd(),'app/static/upload'),
|
uploadDir: path.join(process.cwd(),'app/static/upload'),
|
||||||
staticDir: path.join(process.cwd(),'app/static'),
|
staticDir: path.join(process.cwd(),'app/static'),
|
||||||
sftpCacheDir: path.join(process.cwd(),'app/socket/.sftp-cache'),
|
sftpCacheDir: path.join(process.cwd(),'app/socket/sftp-cache'),
|
||||||
sshRecordPath: path.join(process.cwd(),'app/storage/ssh-record.json'),
|
sshRecordPath: path.join(process.cwd(),'app/storage/ssh-record.json'),
|
||||||
keyPath: path.join(process.cwd(),'app/storage/key.json'),
|
keyPath: path.join(process.cwd(),'app/storage/key.json'),
|
||||||
hostListPath: path.join(process.cwd(),'app/storage/host-list.json'),
|
hostListPath: path.join(process.cwd(),'app/storage/host-list.json'),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const consola = require('consola')
|
const consola = require('consola')
|
||||||
global.consola = consola
|
global.consola = consola
|
||||||
const { httpServer, httpsServer, clientHttpServer } = require('./server')
|
const { httpServer, clientHttpServer } = require('./server')
|
||||||
const initLocal = require('./init')
|
const initLocal = require('./init')
|
||||||
const scheduleJob = require('./schedule')
|
const scheduleJob = require('./schedule')
|
||||||
|
|
||||||
@ -10,6 +10,4 @@ initLocal()
|
|||||||
|
|
||||||
httpServer()
|
httpServer()
|
||||||
|
|
||||||
httpsServer()
|
|
||||||
|
|
||||||
clientHttpServer()
|
clientHttpServer()
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
const koaBody = require('koa-body')
|
const koaBody = require('koa-body')
|
||||||
const { uploadDir } = require('../config')
|
const { uploadDir } = require('../config')
|
||||||
|
|
||||||
module.exports = koaBody({
|
module.exports = koaBody({
|
||||||
multipart: true, // 支持 multipart-formdate 的表单
|
multipart: true, // 支持 multipart-formdate 的表单
|
||||||
formidable: {
|
formidable: {
|
||||||
uploadDir, // 上传目录
|
uploadDir, // 上传目录
|
||||||
keepExtensions: true, // 保持文件的后缀
|
keepExtensions: true, // 保持文件的后缀
|
||||||
multipart: true, // 多文件上传
|
multipart: true, // 多文件上传
|
||||||
maxFieldsSize: 2 * 1024 * 1024 // 文件上传大小 单位:B
|
maxFieldsSize: 2 * 1024 * 1024 // 文件上传大小 单位:B
|
||||||
}
|
}
|
||||||
})
|
})
|
@ -1,10 +1,8 @@
|
|||||||
const cors = require('@koa/cors')
|
const cors = require('@koa/cors')
|
||||||
// const { domain } = require('../config')
|
|
||||||
|
|
||||||
// 跨域处理
|
// 跨域处理
|
||||||
const useCors = cors({
|
const useCors = cors({
|
||||||
origin: ({ req }) => {
|
origin: ({ req }) => {
|
||||||
// return domain || req.headers.origin
|
|
||||||
return req.headers.origin
|
return req.headers.origin
|
||||||
},
|
},
|
||||||
credentials: true,
|
credentials: true,
|
||||||
|
@ -1,58 +1,58 @@
|
|||||||
const log4js = require('log4js')
|
const log4js = require('log4js')
|
||||||
const { outDir, recordLog } = require('../config').logConfig
|
const { outDir, recordLog } = require('../config').logConfig
|
||||||
|
|
||||||
log4js.configure({
|
log4js.configure({
|
||||||
appenders: {
|
appenders: {
|
||||||
// 控制台输出
|
// 控制台输出
|
||||||
out: {
|
out: {
|
||||||
type: 'stdout',
|
type: 'stdout',
|
||||||
layout: {
|
layout: {
|
||||||
type: 'colored'
|
type: 'colored'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 保存日志文件
|
// 保存日志文件
|
||||||
cheese: {
|
cheese: {
|
||||||
type: 'file',
|
type: 'file',
|
||||||
maxLogSize: 512*1024, // unit: bytes 1KB = 1024bytes
|
maxLogSize: 512*1024, // unit: bytes 1KB = 1024bytes
|
||||||
filename: `${ outDir }/receive.log`
|
filename: `${ outDir }/receive.log`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
categories: {
|
categories: {
|
||||||
default: {
|
default: {
|
||||||
appenders: [ 'out', 'cheese' ], // 配置
|
appenders: [ 'out', 'cheese' ], // 配置
|
||||||
level: 'info' // 只输出info以上级别的日志
|
level: 'info' // 只输出info以上级别的日志
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// pm2: true
|
// pm2: true
|
||||||
})
|
})
|
||||||
|
|
||||||
const logger = log4js.getLogger()
|
const logger = log4js.getLogger()
|
||||||
|
|
||||||
const useLog = () => {
|
const useLog = () => {
|
||||||
return async (ctx, next) => {
|
return async (ctx, next) => {
|
||||||
const { method, path, origin, query, body, headers, ip } = ctx.request
|
const { method, path, origin, query, body, headers, ip } = ctx.request
|
||||||
const data = {
|
const data = {
|
||||||
method,
|
method,
|
||||||
path,
|
path,
|
||||||
origin,
|
origin,
|
||||||
query,
|
query,
|
||||||
body,
|
body,
|
||||||
ip,
|
ip,
|
||||||
headers
|
headers
|
||||||
}
|
}
|
||||||
await next() // 等待路由处理完成,再开始记录日志
|
await next() // 等待路由处理完成,再开始记录日志
|
||||||
// 是否记录日志
|
// 是否记录日志
|
||||||
if (recordLog) {
|
if (recordLog) {
|
||||||
const { status, params } = ctx
|
const { status, params } = ctx
|
||||||
data.status = status
|
data.status = status
|
||||||
data.params = params
|
data.params = params
|
||||||
data.result = ctx.body || 'no content'
|
data.result = ctx.body || 'no content'
|
||||||
if (String(status).startsWith(4) || String(status).startsWith(5))
|
if (String(status).startsWith(4) || String(status).startsWith(5))
|
||||||
logger.error(JSON.stringify(data))
|
logger.error(JSON.stringify(data))
|
||||||
else
|
else
|
||||||
logger.info(JSON.stringify(data))
|
logger.info(JSON.stringify(data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = useLog()
|
module.exports = useLog()
|
@ -1,13 +1,13 @@
|
|||||||
const router = require('../router')
|
const router = require('../router')
|
||||||
|
|
||||||
// 路由中间件
|
// 路由中间件
|
||||||
const useRoutes = router.routes()
|
const useRoutes = router.routes()
|
||||||
// 优化错误提示中间件
|
// 优化错误提示中间件
|
||||||
// 原先如果请求方法错误响应404
|
// 原先如果请求方法错误响应404
|
||||||
// 使用该中间件后请求方法错误会提示405 Method Not Allowed【get list ✔200 post /list ❌405】
|
// 使用该中间件后请求方法错误会提示405 Method Not Allowed【get list ✔200 post /list ❌405】
|
||||||
const useAllowedMethods = router.allowedMethods()
|
const useAllowedMethods = router.allowedMethods()
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
useRoutes,
|
useRoutes,
|
||||||
useAllowedMethods
|
useAllowedMethods
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
const koaStatic = require('koa-static')
|
const koaStatic = require('koa-static')
|
||||||
const { staticDir } = require('../config')
|
const { staticDir } = require('../config')
|
||||||
|
|
||||||
const useStatic = koaStatic(staticDir, {
|
const useStatic = koaStatic(staticDir, {
|
||||||
maxage: 1000 * 60 * 60 * 24 * 30,
|
maxage: 1000 * 60 * 60 * 24 * 30,
|
||||||
gzip: true,
|
gzip: true,
|
||||||
setHeaders: (res, path) => {
|
setHeaders: (res, path) => {
|
||||||
if(path && path.endsWith('.html')) {
|
if(path && path.endsWith('.html')) {
|
||||||
res.setHeader('Cache-Control', 'max-age=0')
|
res.setHeader('Cache-Control', 'max-age=0')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
module.exports = useStatic
|
module.exports = useStatic
|
||||||
|
@ -1,68 +1,55 @@
|
|||||||
const Koa = require('koa')
|
const Koa = require('koa')
|
||||||
const compose = require('koa-compose') // 组合中间件,简化写法
|
const compose = require('koa-compose') // 组合中间件,简化写法
|
||||||
const http = require('http')
|
const http = require('http')
|
||||||
const https = require('https')
|
const { clientPort } = require('./config')
|
||||||
const { clientPort } = require('./config')
|
const { httpPort } = require('./config')
|
||||||
const { domain, httpPort, httpsPort, certificate } = require('./config')
|
const middlewares = require('./middlewares')
|
||||||
const middlewares = require('./middlewares')
|
const wsMonitorOsInfo = require('./socket/monitor')
|
||||||
const wsMonitorOsInfo = require('./socket/monitor')
|
const wsTerminal = require('./socket/terminal')
|
||||||
const wsTerminal = require('./socket/terminal')
|
const wsSftp = require('./socket/sftp')
|
||||||
const wsSftp = require('./socket/sftp')
|
const wsHostStatus = require('./socket/host-status')
|
||||||
const wsHostStatus = require('./socket/host-status')
|
const wsClientInfo = require('./socket/clients')
|
||||||
const wsClientInfo = require('./socket/clients')
|
const { throwError } = require('./utils')
|
||||||
const { throwError } = require('./utils')
|
|
||||||
|
const httpServer = () => {
|
||||||
const httpServer = () => {
|
const app = new Koa()
|
||||||
const app = new Koa()
|
const server = http.createServer(app.callback())
|
||||||
const server = http.createServer(app.callback())
|
serverHandler(app, server)
|
||||||
serverHandler(app, server)
|
// ws一直报跨域的错误:参照官方文档使用createServer API创建服务
|
||||||
// ws一直报跨域的错误:参照官方文档使用createServer API创建服务
|
server.listen(httpPort, () => {
|
||||||
server.listen(process.env.PORT || httpPort, () => {
|
consola.success(`Server(http) is running on: http://localhost:${ httpPort }`)
|
||||||
consola.success(`Server(http) is running on: http://localhost:${ httpPort }`)
|
})
|
||||||
})
|
}
|
||||||
}
|
|
||||||
|
const clientHttpServer = () => {
|
||||||
const httpsServer = () => {
|
const app = new Koa()
|
||||||
if(!certificate) return consola.error('未上传证书, 创建https服务失败')
|
const server = http.createServer(app.callback())
|
||||||
const app = new Koa()
|
wsMonitorOsInfo(server) // 监控本机信息
|
||||||
const server = https.createServer(certificate, app.callback())
|
server.listen(clientPort, () => {
|
||||||
serverHandler(app, server)
|
consola.success(`Client(http) is running on: http://localhost:${ clientPort }`)
|
||||||
server.listen(httpsPort, (err) => {
|
})
|
||||||
if (err) return consola.error('https server error: ', err)
|
}
|
||||||
consola.success(`Server(https) is running: https://${ domain }:${ httpsPort }`)
|
|
||||||
})
|
// 服务
|
||||||
}
|
function serverHandler(app, server) {
|
||||||
|
app.proxy = true // 用于nginx反代时获取真实客户端ip
|
||||||
const clientHttpServer = () => {
|
wsTerminal(server) // 终端
|
||||||
const app = new Koa()
|
wsSftp(server) // sftp
|
||||||
const server = http.createServer(app.callback())
|
wsHostStatus(server) // 终端侧边栏host信息
|
||||||
wsMonitorOsInfo(server) // 监控本机信息
|
wsClientInfo(server) // 客户端信息
|
||||||
server.listen(clientPort, () => {
|
app.context.throwError = throwError // 常用方法挂载全局ctx上
|
||||||
consola.success(`Client(http) is running on: http://localhost:${ clientPort }`)
|
app.use(compose(middlewares))
|
||||||
})
|
// 捕获error.js模块抛出的服务错误
|
||||||
}
|
app.on('error', (err, ctx) => {
|
||||||
|
ctx.status = 500
|
||||||
// 服务
|
ctx.body = {
|
||||||
function serverHandler(app, server) {
|
status: ctx.status,
|
||||||
app.proxy = true // 用于nginx反代时获取真实客户端ip
|
message: `Program Error:${ err.message }`
|
||||||
wsTerminal(server) // 终端
|
}
|
||||||
wsSftp(server) // sftp
|
})
|
||||||
wsHostStatus(server) // 终端侧边栏host信息
|
}
|
||||||
wsClientInfo(server) // 客户端信息
|
|
||||||
app.context.throwError = throwError // 常用方法挂载全局ctx上
|
module.exports = {
|
||||||
app.use(compose(middlewares))
|
httpServer,
|
||||||
// 捕获error.js模块抛出的服务错误
|
clientHttpServer
|
||||||
app.on('error', (err, ctx) => {
|
}
|
||||||
ctx.status = 500
|
|
||||||
ctx.body = {
|
|
||||||
status: ctx.status,
|
|
||||||
message: `Program Error:${ err.message }`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
httpServer,
|
|
||||||
httpsServer,
|
|
||||||
clientHttpServer
|
|
||||||
}
|
|
@ -1,74 +1,74 @@
|
|||||||
const { Server: ServerIO } = require('socket.io')
|
const { Server: ServerIO } = require('socket.io')
|
||||||
const { io: ClientIO } = require('socket.io-client')
|
const { io: ClientIO } = require('socket.io-client')
|
||||||
const { clientPort } = require('../config')
|
const { clientPort } = require('../config')
|
||||||
const { verifyAuth } = require('../utils')
|
const { verifyAuth } = require('../utils')
|
||||||
|
|
||||||
let hostSockets = {}
|
let hostSockets = {}
|
||||||
|
|
||||||
function getHostInfo(serverSocket, host) {
|
function getHostInfo(serverSocket, host) {
|
||||||
let hostSocket = ClientIO(`http://${ host }:${ clientPort }`, {
|
let hostSocket = ClientIO(`http://${ host }:${ clientPort }`, {
|
||||||
path: '/client/os-info',
|
path: '/client/os-info',
|
||||||
forceNew: false,
|
forceNew: false,
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
reconnectionDelay: 3000,
|
reconnectionDelay: 3000,
|
||||||
reconnectionAttempts: 100
|
reconnectionAttempts: 100
|
||||||
})
|
})
|
||||||
// 将与客户端连接的socket实例保存起来,web端断开时关闭与客户端的连接
|
// 将与客户端连接的socket实例保存起来,web端断开时关闭与客户端的连接
|
||||||
hostSockets[serverSocket.id] = hostSocket
|
hostSockets[serverSocket.id] = hostSocket
|
||||||
|
|
||||||
hostSocket
|
hostSocket
|
||||||
.on('connect', () => {
|
.on('connect', () => {
|
||||||
consola.success('host-status-socket连接成功:', host)
|
consola.success('host-status-socket连接成功:', host)
|
||||||
hostSocket.on('client_data', (data) => {
|
hostSocket.on('client_data', (data) => {
|
||||||
serverSocket.emit('host_data', data)
|
serverSocket.emit('host_data', data)
|
||||||
})
|
})
|
||||||
hostSocket.on('client_error', () => {
|
hostSocket.on('client_error', () => {
|
||||||
serverSocket.emit('host_data', null)
|
serverSocket.emit('host_data', null)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.on('connect_error', (error) => {
|
.on('connect_error', (error) => {
|
||||||
consola.error('host-status-socket连接[失败]:', host, error.message)
|
consola.error('host-status-socket连接[失败]:', host, error.message)
|
||||||
serverSocket.emit('host_data', null)
|
serverSocket.emit('host_data', null)
|
||||||
})
|
})
|
||||||
.on('disconnect', () => {
|
.on('disconnect', () => {
|
||||||
consola.info('host-status-socket连接[断开]:', host)
|
consola.info('host-status-socket连接[断开]:', host)
|
||||||
serverSocket.emit('host_data', null)
|
serverSocket.emit('host_data', null)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = (httpServer) => {
|
module.exports = (httpServer) => {
|
||||||
const serverIo = new ServerIO(httpServer, {
|
const serverIo = new ServerIO(httpServer, {
|
||||||
path: '/host-status',
|
path: '/host-status',
|
||||||
cors: {
|
cors: {
|
||||||
origin: '*' // 需配置跨域
|
origin: '*' // 需配置跨域
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
serverIo.on('connection', (serverSocket) => {
|
serverIo.on('connection', (serverSocket) => {
|
||||||
// 前者兼容nginx反代, 后者兼容nodejs自身服务
|
// 前者兼容nginx反代, 后者兼容nodejs自身服务
|
||||||
let clientIp = serverSocket.handshake.headers['x-forwarded-for'] || serverSocket.handshake.address
|
let clientIp = serverSocket.handshake.headers['x-forwarded-for'] || serverSocket.handshake.address
|
||||||
serverSocket.on('init_host_data', ({ token, host }) => {
|
serverSocket.on('init_host_data', ({ token, host }) => {
|
||||||
// 校验登录态
|
// 校验登录态
|
||||||
const { code, msg } = verifyAuth(token, clientIp)
|
const { code, msg } = verifyAuth(token, clientIp)
|
||||||
if(code !== 1) {
|
if(code !== 1) {
|
||||||
serverSocket.emit('token_verify_fail', msg || '鉴权失败')
|
serverSocket.emit('token_verify_fail', msg || '鉴权失败')
|
||||||
serverSocket.disconnect()
|
serverSocket.disconnect()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取客户端数据
|
// 获取客户端数据
|
||||||
getHostInfo(serverSocket, host)
|
getHostInfo(serverSocket, host)
|
||||||
|
|
||||||
consola.info('host-status-socket连接socketId: ', serverSocket.id, 'host-status-socket已连接数: ', Object.keys(hostSockets).length)
|
consola.info('host-status-socket连接socketId: ', serverSocket.id, 'host-status-socket已连接数: ', Object.keys(hostSockets).length)
|
||||||
|
|
||||||
// 关闭连接
|
// 关闭连接
|
||||||
serverSocket.on('disconnect', () => {
|
serverSocket.on('disconnect', () => {
|
||||||
// 当web端与服务端断开连接时, 服务端与每个客户端的socket也应该断开连接
|
// 当web端与服务端断开连接时, 服务端与每个客户端的socket也应该断开连接
|
||||||
let socket = hostSockets[serverSocket.id]
|
let socket = hostSockets[serverSocket.id]
|
||||||
socket.close && socket.close()
|
socket.close && socket.close()
|
||||||
delete hostSockets[serverSocket.id]
|
delete hostSockets[serverSocket.id]
|
||||||
consola.info('host-status-socket剩余连接数: ', Object.keys(hostSockets).length)
|
consola.info('host-status-socket剩余连接数: ', Object.keys(hostSockets).length)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,71 +1,71 @@
|
|||||||
const { Server } = require('socket.io')
|
const { Server } = require('socket.io')
|
||||||
const schedule = require('node-schedule')
|
const schedule = require('node-schedule')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
let getOsData = require('../utils/os-data')
|
let getOsData = require('../utils/os-data')
|
||||||
const consola = require('consola')
|
const consola = require('consola')
|
||||||
|
|
||||||
let serverSockets = {}, ipInfo = {}, osData = {}
|
let serverSockets = {}, ipInfo = {}, osData = {}
|
||||||
|
|
||||||
async function getIpInfo() {
|
async function getIpInfo() {
|
||||||
try {
|
try {
|
||||||
let { data } = await axios.get('http://ip-api.com/json?lang=zh-CN')
|
let { data } = await axios.get('http://ip-api.com/json?lang=zh-CN')
|
||||||
consola.success('getIpInfo Success: ', new Date())
|
consola.success('getIpInfo Success: ', new Date())
|
||||||
ipInfo = data
|
ipInfo = data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
consola.error('getIpInfo Error: ', new Date(), error)
|
consola.error('getIpInfo Error: ', new Date(), error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ipSchedule() {
|
function ipSchedule() {
|
||||||
let rule1 = new schedule.RecurrenceRule()
|
let rule1 = new schedule.RecurrenceRule()
|
||||||
rule1.second = [0, 10, 20, 30, 40, 50]
|
rule1.second = [0, 10, 20, 30, 40, 50]
|
||||||
schedule.scheduleJob(rule1, () => {
|
schedule.scheduleJob(rule1, () => {
|
||||||
let { query, country, city } = ipInfo || {}
|
let { query, country, city } = ipInfo || {}
|
||||||
if(query && country && city) return
|
if(query && country && city) return
|
||||||
consola.success('Task: start getIpInfo', new Date())
|
consola.success('Task: start getIpInfo', new Date())
|
||||||
getIpInfo()
|
getIpInfo()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 每日凌晨两点整,刷新ip信息(兼容动态ip服务器)
|
// 每日凌晨两点整,刷新ip信息(兼容动态ip服务器)
|
||||||
let rule2 = new schedule.RecurrenceRule()
|
let rule2 = new schedule.RecurrenceRule()
|
||||||
rule2.hour = 2
|
rule2.hour = 2
|
||||||
rule2.minute = 0
|
rule2.minute = 0
|
||||||
rule2.second = 0
|
rule2.second = 0
|
||||||
schedule.scheduleJob(rule2, () => {
|
schedule.scheduleJob(rule2, () => {
|
||||||
consola.info('Task: refresh ip info', new Date())
|
consola.info('Task: refresh ip info', new Date())
|
||||||
getIpInfo()
|
getIpInfo()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
ipSchedule()
|
ipSchedule()
|
||||||
|
|
||||||
module.exports = (httpServer) => {
|
module.exports = (httpServer) => {
|
||||||
const serverIo = new Server(httpServer, {
|
const serverIo = new Server(httpServer, {
|
||||||
path: '/client/os-info',
|
path: '/client/os-info',
|
||||||
cors: {
|
cors: {
|
||||||
origin: '*'
|
origin: '*'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
serverIo.on('connection', (socket) => {
|
serverIo.on('connection', (socket) => {
|
||||||
// 存储对应websocket连接的定时器
|
// 存储对应websocket连接的定时器
|
||||||
serverSockets[socket.id] = setInterval(async () => {
|
serverSockets[socket.id] = setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
osData = await getOsData()
|
osData = await getOsData()
|
||||||
socket && socket.emit('client_data', Object.assign(osData, { ipInfo }))
|
socket && socket.emit('client_data', Object.assign(osData, { ipInfo }))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
consola.error('客户端错误:', error)
|
consola.error('客户端错误:', error)
|
||||||
socket && socket.emit('client_error', { error })
|
socket && socket.emit('client_error', { error })
|
||||||
}
|
}
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
socket.on('disconnect', () => {
|
socket.on('disconnect', () => {
|
||||||
// 断开时清楚对应的websocket连接
|
// 断开时清楚对应的websocket连接
|
||||||
if(serverSockets[socket.id]) clearInterval(serverSockets[socket.id])
|
if(serverSockets[socket.id]) clearInterval(serverSockets[socket.id])
|
||||||
delete serverSockets[socket.id]
|
delete serverSockets[socket.id]
|
||||||
socket.close && socket.close()
|
socket.close && socket.close()
|
||||||
socket = null
|
socket = null
|
||||||
// console.log('断开socketId: ', socket.id, '剩余链接数: ', Object.keys(serverSockets).length)
|
// console.log('断开socketId: ', socket.id, '剩余链接数: ', Object.keys(serverSockets).length)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -109,21 +109,21 @@ function listenInput(sftpClient, socket) {
|
|||||||
socket.emit('sftp_error', error.message)
|
socket.emit('sftp_error', error.message)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// socket.on('up_file', async ({ targetPath, fullPath, name, file }) => {
|
socket.on('up_file', async ({ targetPath, fullPath, name, file }) => {
|
||||||
// console.log({ targetPath, fullPath, name, file })
|
console.log({ targetPath, fullPath, name, file })
|
||||||
// const exists = await sftpClient.exists(targetPath)
|
const exists = await sftpClient.exists(targetPath)
|
||||||
// if(!exists) return socket.emit('not_exists_dir', '文件夹不存在或当前不可访问')
|
if(!exists) return socket.emit('not_exists_dir', '文件夹不存在或当前不可访问')
|
||||||
// try {
|
try {
|
||||||
// const localPath = rawPath.join(sftpCacheDir, name)
|
const localPath = rawPath.join(sftpCacheDir, name)
|
||||||
// fs.writeFileSync(localPath, file)
|
fs.writeFileSync(localPath, file)
|
||||||
// let res = await sftpClient.fastPut(localPath, fullPath)
|
let res = await sftpClient.fastPut(localPath, fullPath)
|
||||||
// consola.success('sftp上传成功: ', res)
|
consola.success('sftp上传成功: ', res)
|
||||||
// socket.emit('up_file_success', res)
|
socket.emit('up_file_success', res)
|
||||||
// } catch (error) {
|
} catch (error) {
|
||||||
// consola.error('up_file Error', error.message)
|
consola.error('up_file Error', error.message)
|
||||||
// socket.emit('sftp_error', error.message)
|
socket.emit('sftp_error', error.message)
|
||||||
// }
|
}
|
||||||
// })
|
})
|
||||||
|
|
||||||
/** 分片上传 */
|
/** 分片上传 */
|
||||||
// 1. 创建本地缓存文件夹
|
// 1. 创建本地缓存文件夹
|
||||||
@ -186,7 +186,7 @@ function listenInput(sftpClient, socket) {
|
|||||||
clearDir(resultDirPath, true) // 传服务器后移除文件夹及其文件
|
clearDir(resultDirPath, true) // 传服务器后移除文件夹及其文件
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
consola.error('sftp上传失败: ', error.message)
|
consola.error('sftp上传失败: ', error.message)
|
||||||
socket.emit('sftp_error', error.message)
|
socket.emit('up_file_fail', error.message)
|
||||||
clearDir(resultDirPath, true) // 传服务器后移除文件夹及其文件
|
clearDir(resultDirPath, true) // 传服务器后移除文件夹及其文件
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
File diff suppressed because one or more lines are too long
BIN
server/app/static/assets/index.5de3ed69.js.gz
Normal file
BIN
server/app/static/assets/index.5de3ed69.js.gz
Normal file
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
BIN
server/app/static/assets/index.cf7d36d4.css.gz
Normal file
BIN
server/app/static/assets/index.cf7d36d4.css.gz
Normal file
Binary file not shown.
Binary file not shown.
@ -1,17 +1,17 @@
|
|||||||
<html lang="zh">
|
<html lang="zh">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0,user-scalable=yes">
|
<meta name="viewport" content="width=device-width, initial-scale=0,user-scalable=yes">
|
||||||
<title>EasyNode</title>
|
<title>EasyNode</title>
|
||||||
<script src="//at.alicdn.com/t/c/font_3309550_flid0kihu36.js"></script>
|
<script src="//at.alicdn.com/t/c/font_3309550_flid0kihu36.js"></script>
|
||||||
<script type="module" crossorigin src="/assets/index.eb5f280e.js"></script>
|
<script type="module" crossorigin src="/assets/index.5de3ed69.js"></script>
|
||||||
<link rel="stylesheet" href="/assets/index.a9194a35.css">
|
<link rel="stylesheet" href="/assets/index.cf7d36d4.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,2 +1 @@
|
|||||||
[
|
[]
|
||||||
]
|
|
@ -63,7 +63,7 @@ const getNetIPInfo = async (searchIp = '') => {
|
|||||||
let { origip: ip, location: country, city = '', regionName = '' } = res || {}
|
let { origip: ip, location: country, city = '', regionName = '' } = res || {}
|
||||||
searchResult.push({ ip, country, city: `${ regionName } ${ city }`, date })
|
searchResult.push({ ip, country, city: `${ regionName } ${ city }`, date })
|
||||||
}
|
}
|
||||||
console.log(searchResult)
|
// console.log(searchResult)
|
||||||
let validInfo = searchResult.find(item => Boolean(item.country))
|
let validInfo = searchResult.find(item => Boolean(item.country))
|
||||||
consola.info('查询IP信息:', validInfo)
|
consola.info('查询IP信息:', validInfo)
|
||||||
return validInfo || { ip: '获取IP信息API出错,请排查或更新API', country: '未知', city: '未知', date }
|
return validInfo || { ip: '获取IP信息API出错,请排查或更新API', country: '未知', city: '未知', date }
|
||||||
|
0
server/bin/www
Normal file → Executable file
0
server/bin/www
Normal file → Executable file
Loading…
x
Reference in New Issue
Block a user