Skip to content

Commit

Permalink
🚀 Enhance file upload and configuration management
Browse files Browse the repository at this point in the history
- Add file upload progress tracking
- Implement file size limit validation
- Update environment configuration for file uploads
- Add file type icons in file upload component
- Improve localization for file upload messages
- Bump version to 1.0.15
  • Loading branch information
yeongpin committed Feb 21, 2025
1 parent 6c444f7 commit 7076fe3
Show file tree
Hide file tree
Showing 11 changed files with 262 additions and 35 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ PUBLIC_HOST=0.0.0.0
VITE_SERVER_PORT=13050
VITE_NAME_LIMIT=20
VITE_MESSAGE_SALT=mysecretkey123
MAX_FILE_SIZE=500
# 歷史記錄和上傳文件的保留時間 (0d0h0m0s 表示立即清理)
CLEANUP_INTERVAL=1h # 清理檢查間隔
HISTORY_RETENTION=7d # 歷史記錄保留時間
Expand Down
23 changes: 16 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,16 @@ npm run dev
### 4️⃣ 修改 .env 文件
請把 .env.example 複製一份,改名為 .env,並修改以下內容
```
SERVER_PORT=13050 # 服務器端口
PUBLIC_PORT=5173 # 前端端口
HOST=0.0.0.0 # 服務器地址
PUBLIC_HOST=0.0.0.0 # 前端地址
VITE_SERVER_PORT=13050 # 服務器端口
VITE_NAME_LIMIT=20 # 用戶名長度限制
SERVER_PORT=13050
PUBLIC_PORT=5173
HOST=0.0.0.0
PUBLIC_HOST=0.0.0.0
VITE_SERVER_PORT=13050
VITE_NAME_LIMIT=20
VITE_MESSAGE_SALT=mysecretkey123
MAX_FILE_SIZE=500
# 歷史記錄和上傳文件的保留時間 (0d0h0m0s 表示立即清理)
CLEANUP_INTERVAL=1h # 清理檢查間隔
CLEANUP_INTERVAL=1h # 清理檢查間隔
HISTORY_RETENTION=7d # 歷史記錄保留時間
UPLOADS_RETENTION=1d # 上傳文件保留時間
```
Expand Down Expand Up @@ -167,6 +169,13 @@ docker run -p 13050:13050 yeongpin/lanlocalchat:latest

## 📝 更新日誌

### v1.0.15

- 🔄 新增文件上傳進度顯示
- 🔄 修復文件上傳問題
- 🎨 界面優化
- 修復已知問題

### v1.0.14

- 🔄 新增Docker Hub 部署
Expand Down
29 changes: 21 additions & 8 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,31 @@ services:
build:
context: .
args:
- SERVER_PORT=${SERVER_PORT:-13050}
- PUBLIC_PORT=${PUBLIC_PORT:-5173}
- HOST=${HOST:-0.0.0.0}
- PUBLIC_HOST=${PUBLIC_HOST:-0.0.0.0}
- VITE_SERVER_PORT=${VITE_SERVER_PORT:-13050}
- VITE_NAME_LIMIT=${VITE_NAME_LIMIT:-20}
- VITE_MESSAGE_SALT=${VITE_MESSAGE_SALT:-mysecretkey123}
- MAX_FILE_SIZE=${MAX_FILE_SIZE:-500}
- CLEANUP_INTERVAL=${CLEANUP_INTERVAL:-1h}
- HISTORY_RETENTION=${HISTORY_RETENTION:-7d}
- UPLOADS_RETENTION=${UPLOADS_RETENTION:-1d}
ports:
- "${SERVER_PORT:-13050}:13050"
environment:
- SERVER_PORT=${SERVER_PORT:-13050}
- HOST=${HOST:-0.0.0.0}
- VITE_SERVER_PORT=${VITE_SERVER_PORT:-13050}
- VITE_NAME_LIMIT=${VITE_NAME_LIMIT:-20}
- VITE_MESSAGE_SALT=${VITE_MESSAGE_SALT:-mysecretkey123}
- CLEANUP_INTERVAL=${CLEANUP_INTERVAL:-1h}
- HISTORY_RETENTION=${HISTORY_RETENTION:-7d}
- UPLOADS_RETENTION=${UPLOADS_RETENTION:-1d}
- SERVER_PORT=${SERVER_PORT:-13050}
- PUBLIC_PORT=${PUBLIC_PORT:-5173}
- HOST=${HOST:-0.0.0.0}
- PUBLIC_HOST=${PUBLIC_HOST:-0.0.0.0}
- VITE_SERVER_PORT=${VITE_SERVER_PORT:-13050}
- VITE_NAME_LIMIT=${VITE_NAME_LIMIT:-20}
- VITE_MESSAGE_SALT=${VITE_MESSAGE_SALT:-mysecretkey123}
- MAX_FILE_SIZE=${MAX_FILE_SIZE:-500}
- CLEANUP_INTERVAL=${CLEANUP_INTERVAL:-1h}
- HISTORY_RETENTION=${HISTORY_RETENTION:-7d}
- UPLOADS_RETENTION=${UPLOADS_RETENTION:-1d}
volumes:
- uploads:/app/server/uploads

Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "lan-chat",
"version": "1.0.14",
"version": "1.0.15",
"description": "LAN Chat Application",
"main": "server/server.js",
"scripts": {
Expand All @@ -13,10 +13,11 @@
},
"dependencies": {
"axios": "^0.26.1",
"busboy": "^1.6.0",
"cors": "^2.8.5",
"crypto-js": "^4.1.1",
"dotenv": "^16.0.3",
"express": "^4.17.1",
"crypto-js": "^4.1.1",
"multer": "^1.4.4",
"socket.io": "^4.4.1",
"socket.io-client": "^4.4.1",
Expand Down
3 changes: 2 additions & 1 deletion public/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"mention": "Mention user",
"fileLimit": "You can only upload 3 files at once",
"uploadFailed": "File upload failed",
"loadHistory": "Load chat history"
"loadHistory": "Load chat history",
"fileTooLarge": "File size exceeds the limit (max {size}MB)"
},
"user": {
"join": "joined the chat",
Expand Down
3 changes: 2 additions & 1 deletion public/locale/zh_cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"mention": "提及用户",
"fileLimit": "最多只能同时上传3个文件",
"uploadFailed": "文件上传失败",
"loadHistory": "加载历史消息"
"loadHistory": "加载历史消息",
"fileTooLarge": "文件大小超过限制(最大 {size}MB)"
},
"user": {
"join": "加入了聊天室",
Expand Down
3 changes: 2 additions & 1 deletion public/locale/zh_tw.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"mention": "提及用戶",
"fileLimit": "最多只能同時上傳3個文件",
"uploadFailed": "文件上傳失敗",
"loadHistory": "載入歷史消息"
"loadHistory": "載入歷史消息",
"fileTooLarge": "文件大小超過限制(最大 {size}MB)"
},
"user": {
"join": "加入了聊天室",
Expand Down
61 changes: 53 additions & 8 deletions server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const readdir = promisify(fs.readdir);
const os = require('os');
const net = require('net');
const CryptoJS = require('crypto-js');
const busboy = require('busboy');
dotenv.config();

// 存儲最近的消息
Expand Down Expand Up @@ -160,7 +161,7 @@ const storage = multer.diskStorage({

const upload = multer({
storage: storage,
limits: { fileSize: 50 * 1024 * 1024 } // 50MB limit
limits: { fileSize: (parseInt(process.env.MAX_FILE_SIZE) || 500) * 1024 * 1024 } // MB limit
});

// 檢查並清理過期的用戶
Expand Down Expand Up @@ -667,14 +668,58 @@ io.on('connection', (socket) => {
});

// 文件上傳路由
app.post('/upload', upload.single('file'), (req, res) => {
if (!req.file) {
return res.status(400).send('No file uploaded.');
}
res.json({
filename: req.file.filename,
path: `/uploads/${req.file.filename}`
app.post('/upload', (req, res) => {
const bb = busboy({
headers: req.headers,
limits: {
fileSize: (parseInt(process.env.MAX_FILE_SIZE) || 500) * 1024 * 1024
}
});
let totalSize = 0;
let processedSize = 0;
let filePath = '';
let fileName = '';

bb.on('file', (name, file, info) => {
fileName = info.filename;
filePath = path.join(uploadDir, fileName);
const writeStream = fs.createWriteStream(filePath);

// 獲取文件大小
file.on('data', data => {
processedSize += data.length;
if (totalSize > 0) {
const progress = Math.round((processedSize / totalSize) * 100);
res.write(JSON.stringify({ progress }) + '\n');
}
});

file.pipe(writeStream);
});

bb.on('field', (name, val) => {
if (name === 'totalSize') {
totalSize = parseInt(val);
if (totalSize > (parseInt(process.env.MAX_FILE_SIZE) || 500) * 1024 * 1024) {
res.status(413).json({
error: `File size exceeds limit of ${process.env.MAX_FILE_SIZE}MB`
});
req.unpipe(bb);
return;
}
}
});

bb.on('finish', () => {
res.write(JSON.stringify({
progress: 100,
filename: fileName,
path: `/uploads/${fileName}`
}) + '\n');
res.end();
});

req.pipe(bb);
});

// 所有請求都返回 index.html
Expand Down
90 changes: 85 additions & 5 deletions src/components/ChatInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
v-for="(file, index) in uploadingFiles"
:key="index"
:file="file"
:progress="uploadProgress[file.name] || 0"
>
<template #action>
<button class="action-btn" @click="removeFile(index)">
Expand Down Expand Up @@ -56,6 +57,7 @@ import axios from 'axios';
import FileCard from './FileCard.vue';
import { t } from '../utils/i18n';
import CryptoJS from 'crypto-js';
import { ref, reactive } from 'vue';
export default {
name: 'ChatInput',
Expand All @@ -77,7 +79,8 @@ export default {
attach: '附加文件',
mention: '提及用戶',
fileLimit: '最多只能同時上傳3個文件',
uploadFailed: '文件上傳失敗'
uploadFailed: '文件上傳失敗',
fileTooLarge: '文件大小超過限制,最大為 {size}MB'
}
})
}
Expand All @@ -93,6 +96,12 @@ export default {
salt: import.meta.env.VITE_MESSAGE_SALT
}
},
setup() {
const uploadProgress = reactive({});
return {
uploadProgress
}
},
computed: {
filteredUsers() {
if (!this.mentionFilter) return this.users;
Expand Down Expand Up @@ -121,10 +130,15 @@ export default {
});
},
methods: {
t(path) {
t(path, params = {}) {
try {
const value = path.split('.').reduce((acc, part) => acc && acc[part], this.localeData);
return value || path;
if (!value) return path;
// 替換參數
return value.replace(/\{(\w+)\}/g, (match, key) => {
return params[key] !== undefined ? params[key] : match;
});
} catch (error) {
console.warn(`Translation not found for: ${path}`);
return path;
Expand All @@ -141,13 +155,23 @@ export default {
const file = event.target.files[0];
if (!file) return;
// 檢查文件大小
const maxSize = (parseInt(import.meta.env.MAX_FILE_SIZE) || 500) * 1024 * 1024; // MB to bytes
if (file.size > maxSize) {
const maxSizeMB = parseInt(import.meta.env.MAX_FILE_SIZE) || 500;
alert(this.t('chat.fileTooLarge', { size: maxSizeMB }));
this.$refs.fileInput.value = '';
return;
}
if (this.uploadingFiles.length >= 3) {
alert(this.t('chat.fileLimit'));
return;
}
// 添加到待上傳文件列表
this.uploadingFiles.push(file);
this.uploadProgress[file.name] = 0;
// 清空文件輸入
this.$refs.fileInput.value = '';
Expand Down Expand Up @@ -199,25 +223,81 @@ export default {
for (const file of this.uploadingFiles) {
const formData = new FormData();
formData.append('file', file);
formData.append('totalSize', file.size.toString());
try {
const response = await axios.post('/upload', formData);
const response = await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', '/upload');
xhr.onload = () => {
try {
// 分割響應並過濾空行
const responses = xhr.responseText.split('\n').filter(line => line.trim());
// 獲取最後一個有效的 JSON 響應
const lastResponse = responses[responses.length - 1];
resolve(JSON.parse(lastResponse));
} catch (e) {
console.error('Parse error:', e);
console.log('Response text:', xhr.responseText);
reject(e);
}
};
xhr.onerror = () => reject(new Error('Upload failed'));
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
const percentCompleted = Math.round((e.loaded * 100) / e.total);
this.uploadProgress[file.name] = percentCompleted;
}
};
xhr.onreadystatechange = () => {
if (xhr.readyState === 3) { // 接收到部分響應
try {
// 分割響應並過濾空行
const responses = xhr.responseText.split('\n').filter(line => line.trim());
// 處理每個進度更新
responses.forEach(response => {
if (!response.trim()) return;
const data = JSON.parse(response);
if (data.progress) {
this.uploadProgress[file.name] = data.progress;
}
});
} catch (e) {
// 忽略部分響應的解析錯誤
}
}
};
xhr.send(formData);
});
this.$emit('send-message', {
type: 'file',
content: {
type: 'file',
name: file.name,
size: file.size,
path: response.data.path
path: response.path
},
timestamp: Date.now(),
mentions: [],
user: ''
});
// 上傳完成後清除進度
delete this.uploadProgress[file.name];
} catch (error) {
console.error('File upload failed:', error);
alert(this.t('chat.uploadFailed'));
// 上傳失敗也清除進度
delete this.uploadProgress[file.name];
}
}
// 清空上傳文件列表
this.uploadingFiles = [];
}
},
Expand Down
Loading

0 comments on commit 7076fe3

Please sign in to comment.