@lppx/lanshare 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +121 -0
- package/dist/scripts/gitsync.js +100 -0
- package/dist/src/app.js +46 -0
- package/dist/src/cli/cli.js +131 -0
- package/dist/src/cli/index.js +30 -0
- package/dist/src/cli/network.js +29 -0
- package/dist/src/cli.js +19 -0
- package/dist/src/index.js +26 -0
- package/dist/src/routers/files.js +142 -0
- package/dist/src/routers/upload.js +47 -0
- package/dist/src/socket/index.js +65 -0
- package/dist/src/socket/types.js +2 -0
- package/dist/src/utils/logger.js +48 -0
- package/package.json +45 -0
- package/public/css/base.css +44 -0
- package/public/css/device-list.css +76 -0
- package/public/css/drop-zone.css +39 -0
- package/public/css/file-list.css +167 -0
- package/public/css/header.css +48 -0
- package/public/css/responsive.css +9 -0
- package/public/index.html +92 -0
- package/public/js/api.js +54 -0
- package/public/js/device-list.js +23 -0
- package/public/js/file-list.js +62 -0
- package/public/js/main.js +18 -0
- package/public/js/socket.js +63 -0
- package/public/js/upload.js +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# LanShare - 局域网文件分享工具
|
|
2
|
+
|
|
3
|
+
一个简单易用的局域网文件分享工具,支持通过浏览器上传和下载文件。
|
|
4
|
+
|
|
5
|
+
## 功能特性
|
|
6
|
+
|
|
7
|
+
- 🚀 交互式命令行界面
|
|
8
|
+
- 🌐 选择网卡和 IP 地址
|
|
9
|
+
- 🔧 自定义端口号
|
|
10
|
+
- 📤 文件上传
|
|
11
|
+
- 📥 文件下载
|
|
12
|
+
- 🎨 简洁的 Web 界面
|
|
13
|
+
- 📝 完整的日志记录
|
|
14
|
+
|
|
15
|
+
## 安装
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g @lppx/lanshare
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 使用方法
|
|
22
|
+
|
|
23
|
+
### 启动服务器
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
lanshare
|
|
27
|
+
# 或
|
|
28
|
+
lsh
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 交互式配置
|
|
32
|
+
|
|
33
|
+
启动后会依次提示:
|
|
34
|
+
|
|
35
|
+
1. 选择网卡
|
|
36
|
+
2. 输入 IP 地址(默认为选中网卡的 IP)
|
|
37
|
+
3. 输入端口号(默认 3000)
|
|
38
|
+
|
|
39
|
+
### 访问服务
|
|
40
|
+
|
|
41
|
+
启动成功后,在浏览器中访问显示的地址,例如:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
http://192.168.1.100:3000
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
局域网内的其他设备也可以通过该地址访问。
|
|
48
|
+
|
|
49
|
+
## 日志功能
|
|
50
|
+
|
|
51
|
+
应用会自动记录运行日志,方便调试和问题排查。
|
|
52
|
+
|
|
53
|
+
### 日志位置
|
|
54
|
+
|
|
55
|
+
日志文件保存在用户主目录下:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
~/.lanshare/logs/
|
|
59
|
+
├── lanshare.log # 所有日志
|
|
60
|
+
└── error.log # 仅错误日志
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 日志级别
|
|
64
|
+
|
|
65
|
+
默认日志级别为 `info`,可通过环境变量 `LOG_LEVEL` 调整:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# 设置为 debug 级别查看更详细的日志
|
|
69
|
+
LOG_LEVEL=debug lanshare
|
|
70
|
+
|
|
71
|
+
# 可选级别: error, warn, info, debug
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 日志内容
|
|
75
|
+
|
|
76
|
+
日志会记录以下信息:
|
|
77
|
+
|
|
78
|
+
- 服务器启动/停止
|
|
79
|
+
- HTTP 请求(IP、方法、路径)
|
|
80
|
+
- 文件上传(文件名、大小)
|
|
81
|
+
- 文件下载
|
|
82
|
+
- 错误信息和堆栈跟踪
|
|
83
|
+
|
|
84
|
+
### 日志轮转
|
|
85
|
+
|
|
86
|
+
- 单个日志文件最大 5MB
|
|
87
|
+
- 最多保留 5 个历史文件
|
|
88
|
+
- 超出限制会自动清理旧日志
|
|
89
|
+
|
|
90
|
+
## 开发
|
|
91
|
+
|
|
92
|
+
### 安装依赖
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
npm install
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 构建
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npm run build
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 本地测试
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
npm link
|
|
108
|
+
lanshare
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## 技术栈
|
|
112
|
+
|
|
113
|
+
- TypeScript
|
|
114
|
+
- Express
|
|
115
|
+
- Commander
|
|
116
|
+
- Prompts
|
|
117
|
+
- Multer
|
|
118
|
+
|
|
119
|
+
## 许可证
|
|
120
|
+
|
|
121
|
+
MIT
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env ts-node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const path_1 = require("path");
|
|
7
|
+
// #region 颜色定义
|
|
8
|
+
const colors = {
|
|
9
|
+
red: '\x1b[0;31m',
|
|
10
|
+
green: '\x1b[0;32m',
|
|
11
|
+
yellow: '\x1b[1;33m',
|
|
12
|
+
reset: '\x1b[0m',
|
|
13
|
+
};
|
|
14
|
+
// #endregion
|
|
15
|
+
// #region 辅助函数
|
|
16
|
+
function printSuccess(message) {
|
|
17
|
+
console.log(`${colors.green}✓ ${message}${colors.reset}`);
|
|
18
|
+
}
|
|
19
|
+
function printError(message) {
|
|
20
|
+
console.error(`${colors.red}✗ ${message}${colors.reset}`);
|
|
21
|
+
}
|
|
22
|
+
function printWarning(message) {
|
|
23
|
+
console.log(`${colors.yellow}⚠ ${message}${colors.reset}`);
|
|
24
|
+
}
|
|
25
|
+
// #endregion
|
|
26
|
+
// #region Git 操作
|
|
27
|
+
function execGit(command) {
|
|
28
|
+
try {
|
|
29
|
+
return (0, child_process_1.execSync)(`git ${command}`, {
|
|
30
|
+
encoding: 'utf-8',
|
|
31
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
32
|
+
}).trim();
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
throw new Error(error.stderr || error.message);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function syncRepo() {
|
|
39
|
+
const dir = process.cwd();
|
|
40
|
+
console.log('开始同步仓库...');
|
|
41
|
+
// 检查是否在 git 仓库中
|
|
42
|
+
if (!(0, fs_1.existsSync)((0, path_1.join)(dir, '.git'))) {
|
|
43
|
+
printError('当前目录不是 git 仓库');
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
// 执行 git pull
|
|
47
|
+
console.log('正在拉取远程更改...');
|
|
48
|
+
try {
|
|
49
|
+
execGit('pull');
|
|
50
|
+
printSuccess('成功拉取远程更改');
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
printError('拉取失败,可能存在冲突');
|
|
54
|
+
printWarning('请手动解决冲突后再运行此脚本');
|
|
55
|
+
console.error(error.message);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
// 检查是否有更改需要提交
|
|
59
|
+
const status = execGit('status --porcelain');
|
|
60
|
+
if (!status) {
|
|
61
|
+
printWarning('没有需要提交的更改');
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}
|
|
64
|
+
// 添加所有更改
|
|
65
|
+
console.log('正在添加所有更改...');
|
|
66
|
+
execGit('add -A');
|
|
67
|
+
printSuccess('已添加所有更改');
|
|
68
|
+
// 生成 commit message(当前日期和时间)
|
|
69
|
+
const now = new Date();
|
|
70
|
+
const commitMsg = now.toLocaleString('zh-CN', {
|
|
71
|
+
year: 'numeric',
|
|
72
|
+
month: '2-digit',
|
|
73
|
+
day: '2-digit',
|
|
74
|
+
hour: '2-digit',
|
|
75
|
+
minute: '2-digit',
|
|
76
|
+
second: '2-digit',
|
|
77
|
+
hour12: false,
|
|
78
|
+
}).replace(/\//g, '-');
|
|
79
|
+
console.log('正在提交更改...');
|
|
80
|
+
execGit(`commit -m "${commitMsg}"`);
|
|
81
|
+
printSuccess(`提交成功: ${commitMsg}`);
|
|
82
|
+
// 推送到远程仓库
|
|
83
|
+
console.log('正在推送到远程仓库...');
|
|
84
|
+
try {
|
|
85
|
+
execGit('push');
|
|
86
|
+
printSuccess('成功推送到远程仓库');
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
printError('推送失败');
|
|
90
|
+
console.error(error.message);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
printSuccess('仓库同步完成!');
|
|
94
|
+
}
|
|
95
|
+
// #endregion
|
|
96
|
+
// 执行主函数
|
|
97
|
+
syncRepo().catch((error) => {
|
|
98
|
+
printError(`发生错误: ${error.message}`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
});
|
package/dist/src/app.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createServer = createServer;
|
|
7
|
+
const express_1 = __importDefault(require("express"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const os_1 = __importDefault(require("os"));
|
|
11
|
+
const http_1 = require("http");
|
|
12
|
+
const upload_1 = require("./routers/upload");
|
|
13
|
+
const files_1 = require("./routers/files");
|
|
14
|
+
const logger_1 = __importDefault(require("./utils/logger"));
|
|
15
|
+
const socket_1 = require("./socket");
|
|
16
|
+
function createServer(config) {
|
|
17
|
+
const app = (0, express_1.default)();
|
|
18
|
+
const httpServer = (0, http_1.createServer)(app);
|
|
19
|
+
const { io, connectedDevices } = (0, socket_1.setupSocketIO)(httpServer); // 初始化 Socket.IO
|
|
20
|
+
const uploadDir = config.uploadDir || path_1.default.join(os_1.default.homedir(), 'lanshare-uploads');
|
|
21
|
+
// #region 确保上传目录存在
|
|
22
|
+
if (!fs_1.default.existsSync(uploadDir)) {
|
|
23
|
+
fs_1.default.mkdirSync(uploadDir, { recursive: true });
|
|
24
|
+
logger_1.default.info(`创建上传目录: ${uploadDir}`);
|
|
25
|
+
}
|
|
26
|
+
// #endregion
|
|
27
|
+
// #region 中间件
|
|
28
|
+
app.use(express_1.default.json());
|
|
29
|
+
app.use(express_1.default.urlencoded({ extended: true }));
|
|
30
|
+
app.use(express_1.default.static(path_1.default.join(__dirname, '../../public')));
|
|
31
|
+
// 请求日志中间件
|
|
32
|
+
app.use((req, _res, next) => {
|
|
33
|
+
logger_1.default.info(`${req.method} ${req.path} - ${req.ip}`);
|
|
34
|
+
next();
|
|
35
|
+
});
|
|
36
|
+
// #endregion
|
|
37
|
+
// #region 路由
|
|
38
|
+
app.use((0, upload_1.createUploadRouter)(uploadDir));
|
|
39
|
+
app.use((0, files_1.createFilesRouter)(uploadDir));
|
|
40
|
+
// 获取已连接设备列表
|
|
41
|
+
app.get('/api/devices', (_req, res) => {
|
|
42
|
+
res.json(Array.from(connectedDevices.values()));
|
|
43
|
+
});
|
|
44
|
+
// #endregion
|
|
45
|
+
return { app, httpServer, io, uploadDir, connectedDevices };
|
|
46
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.handleStartCommand = handleStartCommand;
|
|
40
|
+
const prompts_1 = __importDefault(require("prompts"));
|
|
41
|
+
const index_1 = require("../index");
|
|
42
|
+
const logger_1 = __importStar(require("../utils/logger"));
|
|
43
|
+
const network_1 = require("./network");
|
|
44
|
+
/**
|
|
45
|
+
* 处理 start 命令
|
|
46
|
+
*/
|
|
47
|
+
async function handleStartCommand() {
|
|
48
|
+
try {
|
|
49
|
+
logger_1.default.info('启动 lanshare 服务');
|
|
50
|
+
const networkInterfaces = (0, network_1.getNetworkInterfaces)();
|
|
51
|
+
if (networkInterfaces.length === 0) {
|
|
52
|
+
logger_1.default.error('未找到可用的网络接口');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
// #region 选择网卡
|
|
56
|
+
const interfaceChoices = networkInterfaces.map(iface => ({
|
|
57
|
+
title: `${iface.name} (${iface.address})`,
|
|
58
|
+
value: iface
|
|
59
|
+
}));
|
|
60
|
+
const interfaceResponse = await (0, prompts_1.default)({
|
|
61
|
+
type: 'select',
|
|
62
|
+
name: 'interface',
|
|
63
|
+
message: '请选择网卡',
|
|
64
|
+
choices: interfaceChoices,
|
|
65
|
+
initial: 0
|
|
66
|
+
});
|
|
67
|
+
if (!interfaceResponse.interface) {
|
|
68
|
+
logger_1.default.info('用户取消操作');
|
|
69
|
+
process.exit(0);
|
|
70
|
+
}
|
|
71
|
+
const selectedInterface = interfaceResponse.interface;
|
|
72
|
+
// #endregion
|
|
73
|
+
// #region 输入 IP 地址
|
|
74
|
+
const ipResponse = await (0, prompts_1.default)({
|
|
75
|
+
type: 'text',
|
|
76
|
+
name: 'ip',
|
|
77
|
+
message: '请输入 IP 地址',
|
|
78
|
+
initial: selectedInterface.address,
|
|
79
|
+
validate: (value) => {
|
|
80
|
+
const ipRegex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
81
|
+
if (!ipRegex.test(value)) {
|
|
82
|
+
return 'IP 地址格式不正确';
|
|
83
|
+
}
|
|
84
|
+
const parts = value.split('.');
|
|
85
|
+
if (parts.some((part) => parseInt(part) > 255)) {
|
|
86
|
+
return 'IP 地址范围不正确';
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
if (!ipResponse.ip) {
|
|
92
|
+
logger_1.default.info('用户取消操作');
|
|
93
|
+
process.exit(0);
|
|
94
|
+
}
|
|
95
|
+
// #endregion
|
|
96
|
+
// #region 输入端口号
|
|
97
|
+
const portResponse = await (0, prompts_1.default)({
|
|
98
|
+
type: 'text',
|
|
99
|
+
name: 'port',
|
|
100
|
+
message: '请输入端口号',
|
|
101
|
+
initial: '3001',
|
|
102
|
+
validate: (value) => {
|
|
103
|
+
if (!value || value.trim() === '') {
|
|
104
|
+
return '端口号不能为空';
|
|
105
|
+
}
|
|
106
|
+
const port = Number(value);
|
|
107
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
108
|
+
return '端口号必须在 1-65535 之间';
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
if (!portResponse.port) {
|
|
114
|
+
logger_1.default.info('用户取消操作');
|
|
115
|
+
process.exit(0);
|
|
116
|
+
}
|
|
117
|
+
// #endregion
|
|
118
|
+
const port = Number(portResponse.port);
|
|
119
|
+
logger_1.default.info(`配置完成 - IP: ${ipResponse.ip}, Port: ${port}`);
|
|
120
|
+
// 启动服务器
|
|
121
|
+
logger_1.default.info(`日志目录: ${logger_1.logDir}`);
|
|
122
|
+
await (0, index_1.startServer)({
|
|
123
|
+
ip: ipResponse.ip,
|
|
124
|
+
port: port
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
logger_1.default.error('启动失败', error);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.createProgram = createProgram;
|
|
5
|
+
const commander_1 = require("commander");
|
|
6
|
+
const cli_1 = require("./cli");
|
|
7
|
+
/**
|
|
8
|
+
* 创建并配置命令行程序
|
|
9
|
+
*/
|
|
10
|
+
function createProgram() {
|
|
11
|
+
const program = new commander_1.Command();
|
|
12
|
+
program
|
|
13
|
+
.name('lanshare')
|
|
14
|
+
.description('局域网文件分享工具')
|
|
15
|
+
.version('1.0.0');
|
|
16
|
+
program
|
|
17
|
+
.command('start')
|
|
18
|
+
.description('启动文件分享服务器')
|
|
19
|
+
.action(cli_1.handleStartCommand);
|
|
20
|
+
return program;
|
|
21
|
+
}
|
|
22
|
+
async function main() {
|
|
23
|
+
const program = createProgram();
|
|
24
|
+
// 默认执行 start 命令
|
|
25
|
+
if (process.argv.length === 2) {
|
|
26
|
+
process.argv.push('start');
|
|
27
|
+
}
|
|
28
|
+
program.parse();
|
|
29
|
+
}
|
|
30
|
+
main();
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getNetworkInterfaces = getNetworkInterfaces;
|
|
7
|
+
const os_1 = __importDefault(require("os"));
|
|
8
|
+
/**
|
|
9
|
+
* 获取所有可用的 IPv4 网络接口
|
|
10
|
+
*/
|
|
11
|
+
function getNetworkInterfaces() {
|
|
12
|
+
const interfaces = os_1.default.networkInterfaces();
|
|
13
|
+
const result = [];
|
|
14
|
+
for (const [name, addresses] of Object.entries(interfaces)) {
|
|
15
|
+
if (!addresses)
|
|
16
|
+
continue;
|
|
17
|
+
for (const addr of addresses) {
|
|
18
|
+
if (addr.family === 'IPv4' && !addr.internal) {
|
|
19
|
+
result.push({
|
|
20
|
+
name,
|
|
21
|
+
address: addr.address,
|
|
22
|
+
family: addr.family,
|
|
23
|
+
internal: addr.internal
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
15
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
16
|
+
};
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
// 此文件已废弃,请使用 src/cli/index.ts
|
|
19
|
+
__exportStar(require("./cli/index"), exports);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.startServer = startServer;
|
|
7
|
+
const app_1 = require("./app");
|
|
8
|
+
const logger_1 = __importDefault(require("./utils/logger"));
|
|
9
|
+
function startServer(config) {
|
|
10
|
+
const { httpServer, uploadDir } = (0, app_1.createServer)(config);
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
try {
|
|
13
|
+
httpServer.listen(config.port, config.ip, () => {
|
|
14
|
+
logger_1.default.info(`服务器启动成功✅ - http://${config.ip}:${config.port}`);
|
|
15
|
+
logger_1.default.info(`上传目录: ${uploadDir}`);
|
|
16
|
+
logger_1.default.info('Socket.IO 已启用,等待设备连接...');
|
|
17
|
+
logger_1.default.info('按 Ctrl+C 停止服务器');
|
|
18
|
+
resolve();
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
logger_1.default.error('服务器启动失败', error);
|
|
23
|
+
reject(error);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createFilesRouter = createFilesRouter;
|
|
7
|
+
const express_1 = require("express");
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const os_1 = __importDefault(require("os"));
|
|
11
|
+
const logger_1 = __importDefault(require("../utils/logger"));
|
|
12
|
+
// 存储文件下载次数
|
|
13
|
+
const downloadCounts = {};
|
|
14
|
+
// 存储已连接设备
|
|
15
|
+
const connectedDevices = new Map();
|
|
16
|
+
function createFilesRouter(uploadDir) {
|
|
17
|
+
const router = (0, express_1.Router)();
|
|
18
|
+
// #region 获取文件列表
|
|
19
|
+
router.get('/files', (_req, res) => {
|
|
20
|
+
fs_1.default.readdir(uploadDir, (err, files) => {
|
|
21
|
+
if (err) {
|
|
22
|
+
logger_1.default.error('读取文件列表失败', err);
|
|
23
|
+
return res.status(500).json({ error: '读取文件列表失败' });
|
|
24
|
+
}
|
|
25
|
+
const fileDetails = files.map(filename => {
|
|
26
|
+
const filepath = path_1.default.join(uploadDir, filename);
|
|
27
|
+
const stats = fs_1.default.statSync(filepath);
|
|
28
|
+
return {
|
|
29
|
+
name: filename,
|
|
30
|
+
size: stats.size,
|
|
31
|
+
downloads: downloadCounts[filename] || 0,
|
|
32
|
+
sharedBy: os_1.default.hostname(),
|
|
33
|
+
ip: getLocalIP(),
|
|
34
|
+
createdAt: stats.birthtime
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
logger_1.default.debug(`获取文件列表: ${files.length} 个文件`);
|
|
38
|
+
res.json(fileDetails);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
// #endregion
|
|
42
|
+
// #region 下载文件
|
|
43
|
+
router.get('/download/:filename', (_req, res) => {
|
|
44
|
+
const filename = _req.params.filename;
|
|
45
|
+
const filepath = path_1.default.join(uploadDir, filename);
|
|
46
|
+
if (!fs_1.default.existsSync(filepath)) {
|
|
47
|
+
logger_1.default.warn(`下载失败: 文件不存在 - ${filename}`);
|
|
48
|
+
return res.status(404).json({ error: '文件不存在' });
|
|
49
|
+
}
|
|
50
|
+
// 记录下载次数
|
|
51
|
+
downloadCounts[filename] = (downloadCounts[filename] || 0) + 1;
|
|
52
|
+
// 记录设备信息
|
|
53
|
+
const clientIP = _req.ip || _req.socket.remoteAddress || 'unknown';
|
|
54
|
+
const userAgent = _req.get('user-agent') || 'Unknown Device';
|
|
55
|
+
const deviceName = parseDeviceName(userAgent);
|
|
56
|
+
connectedDevices.set(clientIP, {
|
|
57
|
+
name: deviceName,
|
|
58
|
+
ip: clientIP,
|
|
59
|
+
lastSeen: Date.now()
|
|
60
|
+
});
|
|
61
|
+
logger_1.default.info(`文件下载: ${filename} - ${clientIP}`);
|
|
62
|
+
res.download(filepath);
|
|
63
|
+
});
|
|
64
|
+
// #endregion
|
|
65
|
+
// #region 删除文件
|
|
66
|
+
router.delete('/delete/:filename', (_req, res) => {
|
|
67
|
+
const filename = _req.params.filename;
|
|
68
|
+
const filepath = path_1.default.join(uploadDir, filename);
|
|
69
|
+
if (!fs_1.default.existsSync(filepath)) {
|
|
70
|
+
logger_1.default.warn(`删除失败: 文件不存在 - ${filename}`);
|
|
71
|
+
return res.status(404).json({ error: '文件不存在' });
|
|
72
|
+
}
|
|
73
|
+
fs_1.default.unlink(filepath, (err) => {
|
|
74
|
+
if (err) {
|
|
75
|
+
logger_1.default.error(`删除文件失败: ${filename}`, err);
|
|
76
|
+
return res.status(500).json({ error: '删除文件失败' });
|
|
77
|
+
}
|
|
78
|
+
// 清除下载计数
|
|
79
|
+
delete downloadCounts[filename];
|
|
80
|
+
logger_1.default.info(`文件删除成功: ${filename}`);
|
|
81
|
+
res.json({ message: '文件删除成功' });
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
// #endregion
|
|
85
|
+
// #region 获取已连接设备
|
|
86
|
+
router.get('/devices', (_req, res) => {
|
|
87
|
+
// 清理超过5分钟未活动的设备
|
|
88
|
+
const now = Date.now();
|
|
89
|
+
const timeout = 5 * 60 * 1000; // 5分钟
|
|
90
|
+
for (const [ip, device] of connectedDevices.entries()) {
|
|
91
|
+
if (now - device.lastSeen > timeout) {
|
|
92
|
+
connectedDevices.delete(ip);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const devices = Array.from(connectedDevices.values());
|
|
96
|
+
res.json(devices);
|
|
97
|
+
});
|
|
98
|
+
// #endregion
|
|
99
|
+
// #region 获取服务器信息
|
|
100
|
+
router.get('/info', (_req, res) => {
|
|
101
|
+
res.json({
|
|
102
|
+
ip: getLocalIP(),
|
|
103
|
+
hostname: os_1.default.hostname(),
|
|
104
|
+
platform: os_1.default.platform()
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
// #endregion
|
|
108
|
+
return router;
|
|
109
|
+
}
|
|
110
|
+
// #region 辅助函数
|
|
111
|
+
function getLocalIP() {
|
|
112
|
+
const interfaces = os_1.default.networkInterfaces();
|
|
113
|
+
for (const name of Object.keys(interfaces)) {
|
|
114
|
+
const iface = interfaces[name];
|
|
115
|
+
if (!iface)
|
|
116
|
+
continue;
|
|
117
|
+
for (const addr of iface) {
|
|
118
|
+
if (addr.family === 'IPv4' && !addr.internal) {
|
|
119
|
+
return addr.address;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return '127.0.0.1';
|
|
124
|
+
}
|
|
125
|
+
function parseDeviceName(userAgent) {
|
|
126
|
+
if (userAgent.includes('Android')) {
|
|
127
|
+
const match = userAgent.match(/Android.*?;\s*([^)]+)/);
|
|
128
|
+
return match ? match[1].trim() : 'Android Device';
|
|
129
|
+
}
|
|
130
|
+
if (userAgent.includes('iPhone'))
|
|
131
|
+
return 'iPhone';
|
|
132
|
+
if (userAgent.includes('iPad'))
|
|
133
|
+
return 'iPad';
|
|
134
|
+
if (userAgent.includes('Mac'))
|
|
135
|
+
return 'MacBook Pro';
|
|
136
|
+
if (userAgent.includes('Windows'))
|
|
137
|
+
return 'Windows PC';
|
|
138
|
+
if (userAgent.includes('Linux'))
|
|
139
|
+
return 'Linux PC';
|
|
140
|
+
return 'Unknown Device';
|
|
141
|
+
}
|
|
142
|
+
// #endregion
|