fix(voice): CloneModal 录音 5 分钟上限 + cloneCounter 改 useRef
Deploy / Build Production Runtime Images (push) Has been skipped
Deploy / Deploy Production (push) Has been skipped
Deploy / Production Browser E2E (push) Has been skipped
Deploy / Staging E2E Tests (push) Failing after 116h28m49s
Deploy / Deploy Staging (push) Failing after 116h30m30s
CI/CD Pipeline / Frontend Lint (push) Failing after 116h31m4s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 116h31m4s

P3 优化(代码审计建议):
1. 录音达到 5 分钟自动停止并提示用户,避免传超大文件
2. cloneCounter 从模块级变量改为组件内 useRef,避免多实例串号

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
灵应
2026-07-04 16:23:26 +08:00
parent 680a9c10c2
commit fa640ca537
+32 -12
View File
@@ -44,15 +44,8 @@ const ACCEPTED_EXTENSIONS = ["mp3", "wav", "m4a"];
const ACCEPTED_MIME = ".mp3,.wav,.m4a,audio/mpeg,audio/wav,audio/mp4";
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
/* ── 默认音色名称计数器 ─────────────────────────────────── */
let cloneCounter = 1;
const getNextDefaultName = (): string => {
const name = `我的声音 ${cloneCounter}`;
cloneCounter += 1;
return name;
};
/** 最长录制时长:5 分钟(秒) */
const MAX_RECORD_SECONDS = 5 * 60;
/* ── 组件 ───────────────────────────────────────────────── */
@@ -78,6 +71,15 @@ const CloneModal: React.FC<CloneModalProps> = ({
const recordTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
const mediaRecorderRef = useRef<MediaRecorder | null>(null);
const audioChunksRef = useRef<Blob[]>([]);
/** 默认音色名称计数器(组件级 ref,避免多实例串号) */
const cloneCounterRef = useRef(1);
/** 生成下一个默认音色名称 */
const getNextDefaultName = useCallback((): string => {
const name = `我的声音 ${cloneCounterRef.current}`;
cloneCounterRef.current += 1;
return name;
}, []);
/** 组件卸载时清理定时器和 MediaRecorder */
useEffect(() => {
@@ -110,7 +112,7 @@ const CloneModal: React.FC<CloneModalProps> = ({
mediaRecorderRef.current.stop();
}
mediaRecorderRef.current = null;
}, []);
}, [getNextDefaultName]);
/** 关闭弹窗 */
const handleClose = useCallback(() => {
@@ -238,7 +240,25 @@ const CloneModal: React.FC<CloneModalProps> = ({
setErrorMessage("");
recordTimerRef.current = setInterval(() => {
setRecordTime((prev) => prev + 1);
setRecordTime((prev) => {
const next = prev + 1;
if (next >= MAX_RECORD_SECONDS) {
// 达到 5 分钟上限,自动停止录制
setTimeout(() => {
setIsRecording(false);
if (recordTimerRef.current) {
clearInterval(recordTimerRef.current);
recordTimerRef.current = null;
}
if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
mediaRecorderRef.current.stop();
}
setErrorMessage("已达最长录制时长(5分钟),已自动停止");
}, 0);
return MAX_RECORD_SECONDS;
}
return next;
});
}, 1000);
} catch {
setErrorMessage("无法访问麦克风,请检查浏览器权限设置");
@@ -446,7 +466,7 @@ const CloneModal: React.FC<CloneModalProps> = ({
? `录制中 ${formatRecordTime(recordTime)}`
: recordedBlob
? `已录制 ${formatRecordTime(recordTime)}`
: "点击按钮开始录制你的声音"}
: "点击按钮开始录制(最长 5 分钟)"}
</p>
{isRecording && (
<div className="xx-clonemodal-record-wave">