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