From f18bf32d33b033e2b7114b35484fef85f3e9ea54 Mon Sep 17 00:00:00 2001
From: liu <362165265@qq.com>
Date: Sat, 31 Jan 2026 01:17:51 +0800
Subject: [PATCH] Update file upload limits to 2GB in backend and frontend
components; refactor avatar utility functions for improved name handling.
---
backend/src/main/resources/application.yml | 6 +--
frontend/src/components/FileDropZone.vue | 2 +-
frontend/src/stores/wsStore.ts | 12 +++--
frontend/src/utils/avatar.ts | 51 ++-----------------
frontend/src/utils/chineseName.ts | 57 ++++++++++++++++++++++
frontend/src/views/HomeView.vue | 2 +-
frontend/src/views/RoomView.vue | 16 +++---
7 files changed, 80 insertions(+), 66 deletions(-)
create mode 100644 frontend/src/utils/chineseName.ts
diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml
index 6890f65..844564d 100644
--- a/backend/src/main/resources/application.yml
+++ b/backend/src/main/resources/application.yml
@@ -8,8 +8,8 @@ spring:
name: datatool-backend
servlet:
multipart:
- max-file-size: 100MB
- max-request-size: 100MB
+ max-file-size: 2GB
+ max-request-size: 2GB
# WebSocket / STOMP 基础配置占位,后续可扩展
datatool:
@@ -20,7 +20,7 @@ datatool:
# 文件上传存储与限制(大文件走 HTTP 上传/下载,避免 WebSocket 断连)
transfer:
upload-dir: ./data/uploads
- max-file-size: 104857600 # 100MB
+ max-file-size: 2147483648 # 2GB
room-expire-hours: 24
cleanup-interval-ms: 3600000 # 定时过期清理间隔(毫秒),默认 1 小时
diff --git a/frontend/src/components/FileDropZone.vue b/frontend/src/components/FileDropZone.vue
index 44d708a..693349e 100644
--- a/frontend/src/components/FileDropZone.vue
+++ b/frontend/src/components/FileDropZone.vue
@@ -39,7 +39,7 @@
/>
拖拽文件到此处,或点击 / Ctrl+V 粘贴
- · 单文件最大 50MB
+ · 单文件最大 2GB
diff --git a/frontend/src/stores/wsStore.ts b/frontend/src/stores/wsStore.ts
index 26242cb..03c7a19 100644
--- a/frontend/src/stores/wsStore.ts
+++ b/frontend/src/stores/wsStore.ts
@@ -6,6 +6,7 @@ import type { RoomMessagePayload, SessionInfo } from '@/types/room';
import { isImageMimeType } from '@/types/room';
import { uploadRoomFile } from '@/api/room';
import { mergeChunksToBlob } from '@/utils/fileChunker';
+import { generateChineseName } from '@/utils/chineseName';
const HISTORY_KEY_PREFIX = 'DataTool-history-';
/** 历史记录最大条数,超出则淘汰最旧(doc10 容量) */
@@ -19,8 +20,8 @@ const CHUNK_SIZE = 32 * 1024;
const CHUNK_CACHE_TTL = 60 * 1000;
/** 小图直发阈值(doc07:200KB 以下直接发送 base64) */
const IMAGE_INLINE_THRESHOLD = 200 * 1024;
-/** 单文件大小上限(100MB),与后端 transfer.max-file-size 一致 */
-const MAX_FILE_SIZE = 100 * 1024 * 1024;
+/** 单文件大小上限(2GB),与后端 transfer.max-file-size 一致 */
+const MAX_FILE_SIZE = 2 * 1024 * 1024 * 1024;
interface JoinRoomPayload {
roomCode: string;
@@ -67,7 +68,7 @@ export const useWsStore = defineStore('ws', () => {
const status = ref('disconnected');
const client = ref(null);
const myUserId = ref(randomUserId());
- const myNickname = ref('匿名用户');
+ const myNickname = ref(generateChineseName());
const currentRoomCode = ref(null);
const userList = ref([]);
const roomMessages = ref([]);
@@ -478,9 +479,12 @@ export const useWsStore = defineStore('ws', () => {
if (!client.value) return { ok: false, error: '未连接' };
if (file.size > MAX_FILE_SIZE) {
const limitMB = MAX_FILE_SIZE / (1024 * 1024);
+ const limitStr = limitMB >= 1024 ? `${limitMB / 1024}GB` : `${limitMB}MB`;
+ const sizeMB = file.size / (1024 * 1024);
+ const sizeStr = sizeMB >= 1024 ? `${(sizeMB / 1024).toFixed(1)}GB` : `${sizeMB.toFixed(1)}MB`;
return {
ok: false,
- error: `文件过大(${(file.size / (1024 * 1024)).toFixed(1)}MB),当前最大支持 ${limitMB}MB`,
+ error: `文件过大(${sizeStr}),当前最大支持 ${limitStr}`,
};
}
diff --git a/frontend/src/utils/avatar.ts b/frontend/src/utils/avatar.ts
index dd0406b..419c40b 100644
--- a/frontend/src/utils/avatar.ts
+++ b/frontend/src/utils/avatar.ts
@@ -1,6 +1,6 @@
/**
* 根据显示名生成头像文字与背景色,用于消息/用户列表等。
- * IP 时取最后一段(如 192.168.100.166 → 166)作为头像与显示名。
+ * 取名字第一个字作为头像。
*/
const AVATAR_COLORS = [
@@ -22,62 +22,19 @@ function hashString(s: string): number {
return n;
}
-/** 简单判断是否为 IPv4 字符串(如 192.168.100.166) */
-export function isIPv4(s: string): boolean {
- if (!s || typeof s !== 'string') return false;
- const trimmed = s.trim();
- return /^\d{1,3}(\.\d{1,3}){3}$/.test(trimmed);
-}
-
-/** 匹配纯 IPv4 或带后缀的 IPv4(如 192.168.100.166-2) */
-const IP_OPTIONAL_SUFFIX = /^(\d{1,3}(?:\.\d{1,3}){3})(-\d+)?$/;
-
-/** 取 IPv4 最后一段,如 192.168.100.166 → 166;非 IP 返回原字符串 */
-export function getLastOctet(name: string): string {
- const trimmed = (name ?? '').trim();
- if (!trimmed) return trimmed;
- const m = trimmed.match(IP_OPTIONAL_SUFFIX);
- if (m) {
- const parts = m[1]!.split('.');
- return parts[parts.length - 1] ?? trimmed;
- }
- if (isIPv4(trimmed)) {
- const parts = trimmed.split('.');
- return parts[parts.length - 1] ?? trimmed;
- }
- return trimmed;
-}
-
-/** 是否为 IP 或 IP-后缀 形式 */
-function isIPOrWithSuffix(s: string): boolean {
- return IP_OPTIONAL_SUFFIX.test((s ?? '').trim());
-}
-
/**
- * 显示用短名:IP 只显示最后一段(166、128),带后缀则 166-2;非 IP 原样。
+ * 获取短显示名(直接返回原名称)
*/
export function getShortDisplayName(name: string): string {
- const trimmed = (name ?? '').trim();
- if (!trimmed) return trimmed;
- const m = trimmed.match(IP_OPTIONAL_SUFFIX);
- if (m) {
- const octet = m[1]!.split('.').pop() ?? m[1];
- return m[2] ? `${octet}${m[2]}` : octet;
- }
- if (isIPv4(trimmed)) return getLastOctet(trimmed);
- return trimmed;
+ return (name ?? '').trim() || '未知';
}
/**
- * 头像文字:IP 取最后一段(166、128),否则取首字。
+ * 头像文字:取名字第一个字
*/
export function getAvatarLetter(name: string): string {
const trimmed = (name ?? '').trim();
if (!trimmed) return '?';
- if (isIPOrWithSuffix(trimmed)) {
- const octet = getLastOctet(trimmed);
- return octet.length > 0 ? octet : '?';
- }
return trimmed[0] ?? '?';
}
diff --git a/frontend/src/utils/chineseName.ts b/frontend/src/utils/chineseName.ts
new file mode 100644
index 0000000..036662a
--- /dev/null
+++ b/frontend/src/utils/chineseName.ts
@@ -0,0 +1,57 @@
+/**
+ * 中国人名随机生成器
+ * 从常见姓氏和名字中随机组合生成中国人名
+ */
+
+// 常见姓氏(百家姓高频姓氏)
+const SURNAMES = [
+ '王', '李', '张', '刘', '陈', '杨', '黄', '赵', '吴', '周',
+ '徐', '孙', '马', '朱', '胡', '郭', '何', '林', '高', '罗',
+ '郑', '梁', '谢', '宋', '唐', '许', '邓', '冯', '韩', '曹',
+ '曾', '彭', '萧', '蔡', '潘', '田', '董', '袁', '于', '余',
+ '叶', '蒋', '杜', '苏', '魏', '程', '吕', '丁', '沈', '任',
+ '姚', '卢', '傅', '钟', '姜', '崔', '谭', '廖', '范', '汪',
+ '陆', '金', '石', '戴', '贾', '韦', '夏', '邱', '方', '侯',
+ '邹', '熊', '孟', '秦', '白', '江', '阎', '薛', '尹', '段',
+ '雷', '黎', '史', '龙', '陶', '贺', '顾', '毛', '郝', '龚',
+ '邵', '万', '钱', '严', '赖', '覃', '洪', '武', '莫', '孔',
+];
+
+// 常见名字用字(男女通用/偏中性)
+const NAME_CHARS = [
+ '伟', '芳', '娜', '敏', '静', '丽', '强', '磊', '军', '洋',
+ '勇', '艳', '杰', '娟', '涛', '明', '超', '秀', '英', '华',
+ '慧', '巧', '美', '雪', '倩', '玲', '红', '春', '辉', '霞',
+ '浩', '建', '平', '刚', '峰', '鹏', '宇', '飞', '林', '波',
+ '文', '云', '龙', '凤', '琳', '萍', '晨', '晓', '阳', '婷',
+ '欣', '怡', '佳', '嘉', '瑞', '祥', '博', '俊', '航', '鑫',
+ '昊', '轩', '睿', '泽', '豪', '子', '梓', '一', '宁', '乐',
+ '天', '雨', '诗', '琪', '雯', '萱', '颖', '悦', '淼', '然',
+ '思', '远', '哲', '皓', '逸', '安', '宏', '志', '国', '正',
+ '新', '海', '旭', '亮', '清', '冰', '健', '蕾', '燕', '菲',
+];
+
+/**
+ * 生成随机中国人名
+ * @returns 随机生成的中国人名(2-3个字)
+ */
+export function generateChineseName(): string {
+ const surname = SURNAMES[Math.floor(Math.random() * SURNAMES.length)];
+ // 50% 概率生成单字名,50% 概率生成双字名
+ const nameLength = Math.random() < 0.5 ? 1 : 2;
+ let name = '';
+ for (let i = 0; i < nameLength; i++) {
+ name += NAME_CHARS[Math.floor(Math.random() * NAME_CHARS.length)];
+ }
+ return surname + name;
+}
+
+/**
+ * 获取姓名的第一个字(用于头像显示)
+ * @param name 姓名
+ * @returns 第一个字
+ */
+export function getFirstChar(name: string): string {
+ const trimmed = (name ?? '').trim();
+ return trimmed.length > 0 ? trimmed[0] : '?';
+}
diff --git a/frontend/src/views/HomeView.vue b/frontend/src/views/HomeView.vue
index 3008a04..046feb5 100644
--- a/frontend/src/views/HomeView.vue
+++ b/frontend/src/views/HomeView.vue
@@ -80,7 +80,7 @@ import axios from 'axios';
import BaseButton from '@/components/ui/BaseButton.vue';
// 开发时用空字符串走同源,由 Vite 代理到后端,便于其它设备通过本机 IP:5173 访问
-const API_BASE = import.meta.env.VITE_API_BASE ?? (import.meta.env.DEV ? '' : 'http://localhost:8080');
+const API_BASE = import.meta.env.VITE_API_BASE ?? '';
const router = useRouter();
diff --git a/frontend/src/views/RoomView.vue b/frontend/src/views/RoomView.vue
index d410131..08b25a1 100644
--- a/frontend/src/views/RoomView.vue
+++ b/frontend/src/views/RoomView.vue
@@ -95,13 +95,13 @@ import MessageItem from '@/components/MessageItem.vue';
import FileMessage from '@/components/FileMessage.vue';
import ImageMessage from '@/components/ImageMessage.vue';
import MessageInput from '@/components/MessageInput.vue';
-import { getFileDownloadUrl, downloadWithProgress, getMyIp } from '@/api/room';
+import { getFileDownloadUrl, downloadWithProgress } from '@/api/room';
import { useWsStore } from '@/stores/wsStore';
import type { RoomMessagePayload } from '@/types/room';
import { isImageMimeType } from '@/types/room';
-// 开发时用空字符串走同源,由 Vite 代理到后端,便于其它设备通过本机 IP:5173 访问
-const API_BASE = import.meta.env.VITE_API_BASE ?? (import.meta.env.DEV ? '' : 'http://localhost:8080');
+// 未设置 VITE_API_BASE 时使用同源:开发时走 Vite 代理,Docker/生产时与页面同 host:port,避免硬编码 localhost 导致部署后连不上
+const API_BASE = import.meta.env.VITE_API_BASE ?? '';
const route = useRoute();
const router = useRouter();
@@ -128,14 +128,10 @@ onMounted(() => {
watch(
() => wsStore.status,
- async (status) => {
+ (status) => {
if (status === 'connected' && roomCode) {
- try {
- const ip = await getMyIp(API_BASE);
- wsStore.joinRoom({ roomCode, nickname: ip?.trim() || '访客' });
- } catch {
- wsStore.joinRoom({ roomCode, nickname: '访客' });
- }
+ // 使用 store 中已生成的随机中国人名
+ wsStore.joinRoom({ roomCode, nickname: wsStore.myNickname });
}
},
{ immediate: true },