Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
34930a3992 | ||
|
ca434c918b | ||
|
f8372b8de8 |
@ -25,8 +25,7 @@ COPY --from=frontend-builder /app/frontend/dist /usr/share/nginx/html
|
|||||||
COPY --from=backend-builder /app/ushare /usr/local/bin/ushare
|
COPY --from=backend-builder /app/ushare /usr/local/bin/ushare
|
||||||
|
|
||||||
# 配置 Nginx
|
# 配置 Nginx
|
||||||
RUN rm /etc/nginx/conf.d/default.conf
|
COPY deployment/nginx.conf /etc/nginx/nginx.conf
|
||||||
COPY deployment/nginx.conf /etc/nginx/conf.d
|
|
||||||
COPY deployment/entrypoint.sh /usr/local/bin/entrypoint.sh
|
COPY deployment/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||||
|
|
||||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||||
|
@ -1,3 +1,26 @@
|
|||||||
|
user root;
|
||||||
|
worker_processes auto;
|
||||||
|
error_log /var/log/nginx/error.log warn;
|
||||||
|
pid /var/run/nginx.pid;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
|
||||||
|
access_log /var/log/nginx/access.log main;
|
||||||
|
|
||||||
|
sendfile on;
|
||||||
|
|
||||||
|
keepalive_timeout 65;
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
|
|
||||||
@ -13,11 +36,25 @@ server {
|
|||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /ushare {
|
location /api/ulocal/ws {
|
||||||
proxy_pass http://localhost:9119;
|
proxy_pass http://localhost:9119;
|
||||||
const rtc = new RTCPeerConnection({iceServers: [{urls: "stun:stun.qq.com:3478"}]})
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location /ushare {
|
||||||
|
proxy_pass http://localhost:9119;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_max_temp_file_size 0;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
proxy_buffering off;
|
||||||
|
client_max_body_size 5M;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -16,7 +16,6 @@ export const useFileUpload = () => {
|
|||||||
setProgress(0);
|
setProgress(0);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(`[D] api.Upload: upload file = ${file.name}, size = ${file.size}`, file);
|
|
||||||
const url = `/api/ushare/${file.name}`;
|
const url = `/api/ushare/${file.name}`;
|
||||||
|
|
||||||
// 1. 初始化上传
|
// 1. 初始化上传
|
||||||
@ -26,7 +25,7 @@ export const useFileUpload = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!res1.ok) {
|
if (!res1.ok) {
|
||||||
console.log(`[D] upload: put file not ok, status = ${res1.status}, res = ${await res1.text()}`)
|
console.log(`[W] upload: put file not ok, status = ${res1.status}, res = ${await res1.text()}`)
|
||||||
if (res1.status === 401) {
|
if (res1.status === 401) {
|
||||||
window.location.href = "/login?next=/share"
|
window.location.href = "/login?next=/share"
|
||||||
return ""
|
return ""
|
||||||
|
@ -17,7 +17,8 @@ const useClass = createUseStyles({
|
|||||||
background: "rgba(0, 0, 0, 0.5)",
|
background: "rgba(0, 0, 0, 0.5)",
|
||||||
backdropFilter: "blur(2px)"
|
backdropFilter: "blur(2px)"
|
||||||
},
|
},
|
||||||
background: "rgba(255, 255, 255, 0.4)",
|
background: "rgba(212,212,212,0.85)",
|
||||||
|
backdropFilter: "blur(8px)",
|
||||||
},
|
},
|
||||||
dialog_content: {
|
dialog_content: {
|
||||||
padding: "1.5rem",
|
padding: "1.5rem",
|
||||||
|
@ -50,7 +50,6 @@ export const Drawer: React.FC<DrawerProps> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleEscape = (e: KeyboardEvent) => {
|
const handleEscape = (e: KeyboardEvent) => {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
console.log('[D] escape close:', close)
|
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,87 @@
|
|||||||
import React, {useState} from 'react';
|
import React, {useState} from 'react';
|
||||||
import {Dialog} from "../../../component/dialog/dialog.tsx";
|
import {Dialog} from "../../../component/dialog/dialog.tsx";
|
||||||
import {ReceivedMessage} from "./types.ts";
|
import {ReceivedMessage} from "./types.ts";
|
||||||
|
import {createUseStyles} from "react-jss";
|
||||||
|
|
||||||
|
const useStyles = createUseStyles({
|
||||||
|
root: {
|
||||||
|
marginBottom: '1rem',
|
||||||
|
},
|
||||||
|
sender: {
|
||||||
|
margin: '0 0 0.5rem 0',
|
||||||
|
fontSize: '0.9rem',
|
||||||
|
color: '#666',
|
||||||
|
},
|
||||||
|
msgBox: {
|
||||||
|
background: 'rgba(255,255,255,0.96)',
|
||||||
|
color: '#222',
|
||||||
|
padding: '1rem',
|
||||||
|
borderRadius: '4px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
maxHeight: '400px',
|
||||||
|
minHeight: '80px',
|
||||||
|
overflowY: 'auto',
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
},
|
||||||
|
downloadLink: {
|
||||||
|
color: '#007aff',
|
||||||
|
textDecoration: 'underline',
|
||||||
|
},
|
||||||
|
btnRow: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: '0.5rem',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: '8px',
|
||||||
|
},
|
||||||
|
copySuccess: {
|
||||||
|
color: '#28a745',
|
||||||
|
fontSize: '0.9rem',
|
||||||
|
marginRight: '0.5rem',
|
||||||
|
animation: 'fadeIn 0.3s ease-in',
|
||||||
|
},
|
||||||
|
copyBtn: {
|
||||||
|
padding: '8px 16px',
|
||||||
|
background: '#007aff',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '0.9rem',
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
'&:hover': {
|
||||||
|
background: '#0056b3',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
copyBtnSuccess: {
|
||||||
|
background: '#28a745',
|
||||||
|
},
|
||||||
|
closeBtn: {
|
||||||
|
padding: '8px 16px',
|
||||||
|
background: '#6c757d',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '0.9rem',
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
},
|
||||||
|
progressBar: {
|
||||||
|
width: '100%',
|
||||||
|
height: 8,
|
||||||
|
background: '#eee',
|
||||||
|
borderRadius: 4,
|
||||||
|
margin: '12px 0',
|
||||||
|
overflow: 'hidden',
|
||||||
|
},
|
||||||
|
progressInner: {
|
||||||
|
height: '100%',
|
||||||
|
background: '#4dabf7',
|
||||||
|
transition: 'width 0.3s',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
interface MessageDialogProps {
|
interface MessageDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -10,16 +91,14 @@ interface MessageDialogProps {
|
|||||||
|
|
||||||
export const MessageDialog: React.FC<MessageDialogProps> = ({open, message, onClose}) => {
|
export const MessageDialog: React.FC<MessageDialogProps> = ({open, message, onClose}) => {
|
||||||
const [copySuccess, setCopySuccess] = useState(false);
|
const [copySuccess, setCopySuccess] = useState(false);
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
const handleCopyMessage = () => {
|
const handleCopyMessage = () => {
|
||||||
if (message) {
|
if (message) {
|
||||||
navigator.clipboard.writeText(message.text || '').then(() => {
|
navigator.clipboard.writeText(message.text || '').then(() => {
|
||||||
console.log('消息已复制到剪贴板');
|
|
||||||
setCopySuccess(true);
|
setCopySuccess(true);
|
||||||
// 2秒后隐藏成功提示
|
|
||||||
setTimeout(() => setCopySuccess(false), 2000);
|
setTimeout(() => setCopySuccess(false), 2000);
|
||||||
}).catch(err => {
|
}).catch(() => {
|
||||||
console.error('复制失败:', err);
|
|
||||||
alert('复制失败,请手动复制');
|
alert('复制失败,请手动复制');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -32,82 +111,49 @@ export const MessageDialog: React.FC<MessageDialogProps> = ({open, message, onCl
|
|||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
footer={false}
|
footer={false}
|
||||||
>
|
>
|
||||||
<div style={{ marginBottom: '1rem' }}>
|
<div className={classes.root}>
|
||||||
<p style={{ margin: '0 0 0.5rem 0', fontSize: '0.9rem', color: '#666' }}>
|
<p className={classes.sender}>
|
||||||
来自: {message?.sender}
|
来自: {message?.sender}
|
||||||
</p>
|
</p>
|
||||||
<div style={{
|
<div className={classes.msgBox}>
|
||||||
background: '#f5f5f5',
|
|
||||||
padding: '1rem',
|
|
||||||
borderRadius: '4px',
|
|
||||||
border: '1px solid #ddd',
|
|
||||||
maxHeight: '200px',
|
|
||||||
overflowY: 'auto',
|
|
||||||
whiteSpace: 'pre-wrap',
|
|
||||||
wordBreak: 'break-word'
|
|
||||||
}}>
|
|
||||||
{message?.isFile ? (
|
{message?.isFile ? (
|
||||||
<>
|
<>
|
||||||
<div>📎 文件: {message.fileName} ({message.fileSize ? (message.fileSize/1024).toFixed(1) : ''} KB)</div>
|
<div>📎 文件: {message.fileName} ({message.fileSize ? (message.fileSize/1024).toFixed(1) : ''} KB)</div>
|
||||||
<a href={message.fileBlobUrl} download={message.fileName} style={{color:'#007aff',textDecoration:'underline'}}>
|
{message.receiving ? (
|
||||||
|
<>
|
||||||
|
<div style={{fontSize: '0.95em', color: '#888'}}>正在接收... {Math.round((message.progress||0)*100)}%</div>
|
||||||
|
<div className={classes.progressBar}>
|
||||||
|
<div className={classes.progressInner} style={{width: `${Math.round((message.progress||0)*100)}%`}} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<a href={message.fileBlobUrl} download={message.fileName} className={classes.downloadLink}>
|
||||||
点击下载
|
点击下载
|
||||||
</a>
|
</a>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
message?.text
|
message?.text
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'row', gap: '0.5rem', justifyContent: 'flex-end', alignItems: 'center', marginTop: '8px' }}>
|
<div className={classes.btnRow}>
|
||||||
{copySuccess && (
|
{copySuccess && (
|
||||||
<span style={{
|
<span className={classes.copySuccess}>
|
||||||
color: '#28a745',
|
|
||||||
fontSize: '0.9rem',
|
|
||||||
marginRight: '0.5rem',
|
|
||||||
animation: 'fadeIn 0.3s ease-in'
|
|
||||||
}}>
|
|
||||||
✓ 已复制
|
✓ 已复制
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{!message?.isFile && (
|
{!message?.isFile && (
|
||||||
<button
|
<button
|
||||||
onClick={handleCopyMessage}
|
onClick={handleCopyMessage}
|
||||||
style={{
|
className={copySuccess ? `${classes.copyBtn} ${classes.copyBtnSuccess}` : classes.copyBtn}
|
||||||
padding: '8px 16px',
|
|
||||||
background: copySuccess ? '#28a745' : '#007aff',
|
|
||||||
color: 'white',
|
|
||||||
border: 'none',
|
|
||||||
borderRadius: '4px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
fontSize: '0.9rem',
|
|
||||||
transition: 'all 0.2s'
|
|
||||||
}}
|
|
||||||
onMouseEnter={(e) => {
|
|
||||||
if (!copySuccess) {
|
|
||||||
e.currentTarget.style.backgroundColor = '#0056b3';
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onMouseLeave={(e) => {
|
|
||||||
if (!copySuccess) {
|
|
||||||
e.currentTarget.style.backgroundColor = '#007aff';
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{copySuccess ? '已复制' : '复制消息'}
|
{copySuccess ? '已复制' : '复制消息'}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
style={{
|
className={classes.closeBtn}
|
||||||
padding: '8px 16px',
|
|
||||||
background: '#6c757d',
|
|
||||||
color: 'white',
|
|
||||||
border: 'none',
|
|
||||||
borderRadius: '4px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
fontSize: '0.9rem',
|
|
||||||
transition: 'all 0.2s'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
关闭
|
关闭
|
||||||
</button>
|
</button>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import {Resp} from "../../../interface/response.ts";
|
import { WSMessage, ReceivedMessage} from "./types.ts";
|
||||||
import {Client, WSMessage, ReceivedMessage} from "./types.ts";
|
|
||||||
import {useLocalStore} from "../../../store/local.ts";
|
import {useLocalStore} from "../../../store/local.ts";
|
||||||
|
|
||||||
// 文件接收缓存
|
// 文件接收缓存
|
||||||
@ -9,7 +8,7 @@ export const handleFileChunk = (chunk: any, onFileReceived: (msg: import("./type
|
|||||||
if (chunk.type === 'file') {
|
if (chunk.type === 'file') {
|
||||||
// 文件元信息,初始化缓存
|
// 文件元信息,初始化缓存
|
||||||
fileReceiveCache[chunk.name + '_' + chunk.timestamp] = {
|
fileReceiveCache[chunk.name + '_' + chunk.timestamp] = {
|
||||||
chunks: [],
|
chunks: new Array(chunk.totalChunks),
|
||||||
total: chunk.totalChunks,
|
total: chunk.totalChunks,
|
||||||
received: 0,
|
received: 0,
|
||||||
name: chunk.name,
|
name: chunk.name,
|
||||||
@ -17,6 +16,16 @@ export const handleFileChunk = (chunk: any, onFileReceived: (msg: import("./type
|
|||||||
sender: chunk.sender,
|
sender: chunk.sender,
|
||||||
timestamp: chunk.timestamp
|
timestamp: chunk.timestamp
|
||||||
};
|
};
|
||||||
|
// 首次弹出进度
|
||||||
|
onFileReceived({
|
||||||
|
sender: chunk.sender,
|
||||||
|
timestamp: chunk.timestamp,
|
||||||
|
fileName: chunk.name,
|
||||||
|
fileSize: chunk.size,
|
||||||
|
isFile: true,
|
||||||
|
progress: 0,
|
||||||
|
receiving: true
|
||||||
|
});
|
||||||
} else if (chunk.type === 'file-chunk') {
|
} else if (chunk.type === 'file-chunk') {
|
||||||
const key = chunk.name + '_' + chunk.timestamp;
|
const key = chunk.name + '_' + chunk.timestamp;
|
||||||
const cache = fileReceiveCache[key];
|
const cache = fileReceiveCache[key];
|
||||||
@ -25,8 +34,23 @@ export const handleFileChunk = (chunk: any, onFileReceived: (msg: import("./type
|
|||||||
const uint8 = new Uint8Array(chunk.data);
|
const uint8 = new Uint8Array(chunk.data);
|
||||||
cache.chunks[chunk.chunkIndex] = uint8.buffer;
|
cache.chunks[chunk.chunkIndex] = uint8.buffer;
|
||||||
cache.received++;
|
cache.received++;
|
||||||
// 全部收到
|
// 实时回调进度
|
||||||
if (cache.received === cache.total) {
|
if (cache.received < cache.total) {
|
||||||
|
onFileReceived({
|
||||||
|
sender: cache.sender,
|
||||||
|
timestamp: cache.timestamp,
|
||||||
|
fileName: cache.name,
|
||||||
|
fileSize: cache.size,
|
||||||
|
isFile: true,
|
||||||
|
progress: cache.received / cache.total,
|
||||||
|
receiving: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (chunk.type === 'file-end') {
|
||||||
|
const key = chunk.name + '_' + chunk.timestamp;
|
||||||
|
const cache = fileReceiveCache[key];
|
||||||
|
if (cache && cache.received === cache.total) {
|
||||||
const blob = new Blob(cache.chunks);
|
const blob = new Blob(cache.chunks);
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
onFileReceived({
|
onFileReceived({
|
||||||
@ -35,10 +59,23 @@ export const handleFileChunk = (chunk: any, onFileReceived: (msg: import("./type
|
|||||||
fileName: cache.name,
|
fileName: cache.name,
|
||||||
fileSize: cache.size,
|
fileSize: cache.size,
|
||||||
fileBlobUrl: url,
|
fileBlobUrl: url,
|
||||||
isFile: true
|
isFile: true,
|
||||||
|
progress: 1,
|
||||||
|
receiving: false
|
||||||
});
|
});
|
||||||
delete fileReceiveCache[key];
|
delete fileReceiveCache[key];
|
||||||
}
|
} else if (cache) {
|
||||||
|
// 分块未齐全,提示异常
|
||||||
|
onFileReceived({
|
||||||
|
sender: cache.sender,
|
||||||
|
timestamp: cache.timestamp,
|
||||||
|
fileName: cache.name,
|
||||||
|
fileSize: cache.size,
|
||||||
|
isFile: true,
|
||||||
|
progress: cache.received / cache.total,
|
||||||
|
receiving: true,
|
||||||
|
text: '文件分块未齐全,接收失败'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -60,21 +97,18 @@ export class RTCHandler {
|
|||||||
|
|
||||||
// 更新回调函数的方法
|
// 更新回调函数的方法
|
||||||
updateCallbacks = (newCallbacks: RTCHandlerCallbacks) => {
|
updateCallbacks = (newCallbacks: RTCHandlerCallbacks) => {
|
||||||
console.log('[D] Updating RTC handler callbacks');
|
|
||||||
this.callbacks = newCallbacks;
|
this.callbacks = newCallbacks;
|
||||||
};
|
};
|
||||||
|
|
||||||
setupDataChannel = async (ch: RTCDataChannel, type: 'sender' | 'receiver') => {
|
setupDataChannel = async (ch: RTCDataChannel, type: 'sender' | 'receiver') => {
|
||||||
console.log(`[D] Setting up data channel for type: ${type}`);
|
|
||||||
ch.onopen = () => {
|
ch.onopen = () => {
|
||||||
console.log(`[D] 通道已打开!类型: ${type}`);
|
console.log(`[D] 通道已打开!类型: ${type}`);
|
||||||
console.log('[D] Calling onChannelOpen callback with type:', type);
|
|
||||||
this.callbacks.onChannelOpen(type);
|
this.callbacks.onChannelOpen(type);
|
||||||
useLocalStore.getState().setChannel(ch);
|
useLocalStore.getState().setChannel(ch);
|
||||||
};
|
};
|
||||||
|
|
||||||
ch.onmessage = (e) => {
|
ch.onmessage = (e) => {
|
||||||
console.log('[D] Received message:', e.data);
|
// console.log('[D] Received message:', e.data);
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
if (data.type === 'message') {
|
if (data.type === 'message') {
|
||||||
@ -84,10 +118,9 @@ export class RTCHandler {
|
|||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
sender: data.sender || '未知用户'
|
sender: data.sender || '未知用户'
|
||||||
};
|
};
|
||||||
console.log('[D] Calling onMessageReceived callback with message:', message);
|
|
||||||
this.callbacks.onMessageReceived(message);
|
this.callbacks.onMessageReceived(message);
|
||||||
} else if (data.type === 'file') {
|
} else if (data.type === 'file' || data.type === 'file-chunk' || data.type === 'file-end') {
|
||||||
// 处理文件消息
|
// 处理文件相关消息
|
||||||
handleFileChunk(data, this.callbacks.onMessageReceived);
|
handleFileChunk(data, this.callbacks.onMessageReceived);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -97,27 +130,23 @@ export class RTCHandler {
|
|||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
sender: '未知用户'
|
sender: '未知用户'
|
||||||
};
|
};
|
||||||
console.log('[D] Calling onMessageReceived callback with plain text message:', message);
|
|
||||||
this.callbacks.onMessageReceived(message);
|
this.callbacks.onMessageReceived(message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ch.onclose = () => {
|
ch.onclose = () => {
|
||||||
console.log('[D] 通道关闭');
|
console.log('[D] 通道关闭');
|
||||||
console.log('[D] Calling onChannelClose callback');
|
|
||||||
this.callbacks.onChannelClose();
|
this.callbacks.onChannelClose();
|
||||||
useLocalStore.getState().setChannel();
|
useLocalStore.getState().setChannel();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
handleBubbleClick = async (bubbleId: string, currentUserId: string) => {
|
handleBubbleClick = async (bubbleId: string, currentUserId: string) => {
|
||||||
console.log(`[D] click id = ${bubbleId}`);
|
|
||||||
const current_rtc = this.rtcRef.current;
|
const current_rtc = this.rtcRef.current;
|
||||||
if (!current_rtc) return;
|
if (!current_rtc) return;
|
||||||
|
|
||||||
current_rtc.onnegotiationneeded = async () => {
|
current_rtc.onnegotiationneeded = async () => {
|
||||||
const offer = await current_rtc.createOffer();
|
const offer = await current_rtc.createOffer();
|
||||||
console.log('[D] offer created', offer);
|
|
||||||
await current_rtc.setLocalDescription(offer);
|
await current_rtc.setLocalDescription(offer);
|
||||||
const data = {
|
const data = {
|
||||||
id: bubbleId,
|
id: bubbleId,
|
||||||
@ -132,7 +161,6 @@ export class RTCHandler {
|
|||||||
};
|
};
|
||||||
|
|
||||||
current_rtc.onicecandidate = async (e) => {
|
current_rtc.onicecandidate = async (e) => {
|
||||||
console.log('[D] on candidate ', e);
|
|
||||||
await fetch('/api/ulocal/candidate', {
|
await fetch('/api/ulocal/candidate', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {"Content-Type": "application/json"},
|
headers: {"Content-Type": "application/json"},
|
||||||
@ -148,7 +176,7 @@ export class RTCHandler {
|
|||||||
let current_id: string;
|
let current_id: string;
|
||||||
let current_rtc: RTCPeerConnection | null;
|
let current_rtc: RTCPeerConnection | null;
|
||||||
const msg = JSON.parse(e.data) as WSMessage;
|
const msg = JSON.parse(e.data) as WSMessage;
|
||||||
console.log('[D] ws event msg =', msg);
|
// console.log('[D] ws event msg =', msg);
|
||||||
|
|
||||||
switch (msg.type) {
|
switch (msg.type) {
|
||||||
case "enter":
|
case "enter":
|
||||||
@ -213,7 +241,6 @@ export class RTCHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!candidate_data.candidate) {
|
if (!candidate_data.candidate) {
|
||||||
console.log('[W] candidate data null');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await current_rtc.addIceCandidate(candidate_data.candidate);
|
await current_rtc.addIceCandidate(candidate_data.candidate);
|
||||||
@ -223,10 +250,8 @@ export class RTCHandler {
|
|||||||
|
|
||||||
sendMessage = (msg: string, files: File[], senderName: string) => {
|
sendMessage = (msg: string, files: File[], senderName: string) => {
|
||||||
const ch = useLocalStore.getState().channel;
|
const ch = useLocalStore.getState().channel;
|
||||||
console.log('[D] ready to send:', msg, files, ch);
|
|
||||||
const CHUNK_SIZE = 64 * 1024; // 64KB
|
const CHUNK_SIZE = 64 * 1024; // 64KB
|
||||||
const BUFFERED_AMOUNT_THRESHOLD = 1 * 1024 * 1024; // 1MB
|
const BUFFERED_AMOUNT_THRESHOLD = 1 * 1024 * 1024; // 1MB
|
||||||
|
|
||||||
if (ch && ch.readyState === 'open') {
|
if (ch && ch.readyState === 'open') {
|
||||||
if (msg.trim()) {
|
if (msg.trim()) {
|
||||||
// 发送文本消息
|
// 发送文本消息
|
||||||
@ -254,23 +279,28 @@ export class RTCHandler {
|
|||||||
};
|
};
|
||||||
ch.send(JSON.stringify(fileData));
|
ch.send(JSON.stringify(fileData));
|
||||||
|
|
||||||
ch.bufferedAmountLowThreshold = BUFFERED_AMOUNT_THRESHOLD;
|
|
||||||
|
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
let chunkIndex = 0;
|
let chunkIndex = 0;
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
|
|
||||||
function sendNextChunk() {
|
const sendNextChunk = () => {
|
||||||
if (offset >= file.size) return;
|
if (offset >= file.size) {
|
||||||
if (ch && ch.bufferedAmount && ch.bufferedAmount > BUFFERED_AMOUNT_THRESHOLD) {
|
// 分块全部发送完毕,发送 file-end
|
||||||
// 等待缓冲区变低
|
ch.send(JSON.stringify({
|
||||||
|
type: 'file-end',
|
||||||
|
name: file.name,
|
||||||
|
timestamp,
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ch.bufferedAmount > BUFFERED_AMOUNT_THRESHOLD) {
|
||||||
ch.addEventListener('bufferedamountlow', sendNextChunk, { once: true });
|
ch.addEventListener('bufferedamountlow', sendNextChunk, { once: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const slice = file.slice(offset, offset + CHUNK_SIZE);
|
const slice = file.slice(offset, offset + CHUNK_SIZE);
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
if (e.target?.result) {
|
if (e.target?.result) {
|
||||||
ch?.send(JSON.stringify({
|
ch.send(JSON.stringify({
|
||||||
type: 'file-chunk',
|
type: 'file-chunk',
|
||||||
name: file.name,
|
name: file.name,
|
||||||
timestamp,
|
timestamp,
|
||||||
@ -284,7 +314,7 @@ export class RTCHandler {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
reader.readAsArrayBuffer(slice);
|
reader.readAsArrayBuffer(slice);
|
||||||
}
|
};
|
||||||
sendNextChunk();
|
sendNextChunk();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,12 @@ interface SendDialogProps {
|
|||||||
open: boolean;
|
open: boolean;
|
||||||
onSend: (msg: string, files: File[]) => void;
|
onSend: (msg: string, files: File[]) => void;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SendDialog: React.FC<SendDialogProps> = ({open, onSend, onClose}) => {
|
export const SendDialog: React.FC<SendDialogProps> = ({open, onSend, onClose, name}) => {
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} title="发送消息" onClose={onClose}>
|
<Dialog open={open} title={`发送消息给${name}`} onClose={onClose}>
|
||||||
<Sender onSend={onSend} />
|
<Sender onSend={onSend} />
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
@ -150,17 +150,10 @@ export const Sender: React.FC<SenderProps> = ({onSend}) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className={classes.buttons}>
|
<div className={classes.buttons}>
|
||||||
<input type="file" ref={fileInputRef} hidden onChange={handleFileSelect}/>
|
<input type="file" ref={fileInputRef} hidden onChange={handleFileSelect}/>
|
||||||
<button
|
<button className={classes.action_btn} onClick={() => {
|
||||||
className={classes.action_btn}
|
|
||||||
style={{ opacity: 0.5, cursor: 'not-allowed' }}
|
|
||||||
onClick={() => { message.warning('暂不支持文件发送') }}
|
|
||||||
>
|
|
||||||
📁 选择文件
|
|
||||||
</button>
|
|
||||||
{/* <button className={classes.action_btn} onClick={() => {
|
|
||||||
fileInputRef.current && fileInputRef.current.click()
|
fileInputRef.current && fileInputRef.current.click()
|
||||||
}}>📁 选择文件
|
}}>📁 选择文件
|
||||||
</button> */}
|
</button>
|
||||||
<button className={classes.action_btn} onClick={handleSubmit}>✈️ 发送</button>
|
<button className={classes.action_btn} onClick={handleSubmit}>✈️ 发送</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -33,4 +33,6 @@ export interface ReceivedMessage {
|
|||||||
fileSize?: number;
|
fileSize?: number;
|
||||||
fileBlobUrl?: string;
|
fileBlobUrl?: string;
|
||||||
isFile?: boolean;
|
isFile?: boolean;
|
||||||
|
progress?: number; // 0-1
|
||||||
|
receiving?: boolean; // 是否正在接收
|
||||||
}
|
}
|
@ -55,14 +55,15 @@ const useClass = createUseStyles({
|
|||||||
|
|
||||||
export const LocalSharing: React.FC = () => {
|
export const LocalSharing: React.FC = () => {
|
||||||
const classes = useClass();
|
const classes = useClass();
|
||||||
const {id, name, set, setChannel} = useLocalStore();
|
const {id, name, set, } = useLocalStore();
|
||||||
const [rtc, setRTC] = useState<RTCPeerConnection>();
|
const [_rtc, setRTC] = useState<RTCPeerConnection>();
|
||||||
const rtcRef = useRef<RTCPeerConnection | null>(null);
|
const rtcRef = useRef<RTCPeerConnection | null>(null);
|
||||||
const [clients, setClients] = useState<Client[]>([]);
|
const [clients, setClients] = useState<Client[]>([]);
|
||||||
const {connect, close} = useWebsocket({});
|
const {connect, close} = useWebsocket({});
|
||||||
const [open, setOpen] = useState<{ send: boolean; receive: boolean }>({send: false, receive: false});
|
const [open, setOpen] = useState<{ send: boolean; receive: boolean }>({send: false, receive: false});
|
||||||
const [receivedMessage, setReceivedMessage] = useState<ReceivedMessage | null>(null);
|
const [receivedMessage, setReceivedMessage] = useState<ReceivedMessage | null>(null);
|
||||||
const [showMessageDialog, setShowMessageDialog] = useState(false);
|
const [showMessageDialog, setShowMessageDialog] = useState(false);
|
||||||
|
const [receivingFile, setReceivingFile] = useState<ReceivedMessage | null>(null);
|
||||||
|
|
||||||
// RTC处理器的回调函数 - 使用useCallback确保稳定性
|
// RTC处理器的回调函数 - 使用useCallback确保稳定性
|
||||||
const onChannelOpen = useCallback((type: 'sender' | 'receiver') => {
|
const onChannelOpen = useCallback((type: 'sender' | 'receiver') => {
|
||||||
@ -71,9 +72,16 @@ export const LocalSharing: React.FC = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onMessageReceived = useCallback((message: ReceivedMessage) => {
|
const onMessageReceived = useCallback((message: ReceivedMessage) => {
|
||||||
console.log('[D] Message received:', message);
|
if (message.isFile && message.receiving) {
|
||||||
|
setReceivingFile(message);
|
||||||
|
} else if (message.isFile && !message.receiving) {
|
||||||
|
setReceivingFile(null);
|
||||||
setReceivedMessage(message);
|
setReceivedMessage(message);
|
||||||
setShowMessageDialog(true);
|
setShowMessageDialog(true);
|
||||||
|
} else {
|
||||||
|
setReceivedMessage(message);
|
||||||
|
setShowMessageDialog(true);
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onChannelClose = useCallback(() => {
|
const onChannelClose = useCallback(() => {
|
||||||
@ -93,7 +101,6 @@ export const LocalSharing: React.FC = () => {
|
|||||||
// 更新RTC处理器的回调函数
|
// 更新RTC处理器的回调函数
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (rtcHandlerRef.current) {
|
if (rtcHandlerRef.current) {
|
||||||
console.log('[D] Updating RTC handler callbacks');
|
|
||||||
rtcHandlerRef.current.updateCallbacks(rtcCallbacks);
|
rtcHandlerRef.current.updateCallbacks(rtcCallbacks);
|
||||||
}
|
}
|
||||||
}, [rtcCallbacks]);
|
}, [rtcCallbacks]);
|
||||||
@ -108,7 +115,6 @@ export const LocalSharing: React.FC = () => {
|
|||||||
|
|
||||||
const handleWSEvent = async (e: MessageEvent) => {
|
const handleWSEvent = async (e: MessageEvent) => {
|
||||||
const msgData = JSON.parse(e.data);
|
const msgData = JSON.parse(e.data);
|
||||||
console.log('[D] ws event msg =', msgData);
|
|
||||||
|
|
||||||
if (msgData.type === "enter" || msgData.type === "leave") {
|
if (msgData.type === "enter" || msgData.type === "leave") {
|
||||||
await updateClients();
|
await updateClients();
|
||||||
@ -129,7 +135,6 @@ export const LocalSharing: React.FC = () => {
|
|||||||
|
|
||||||
const handleBubbleClick = async (bubble: any) => {
|
const handleBubbleClick = async (bubble: any) => {
|
||||||
setOpen({send: true, receive: false});
|
setOpen({send: true, receive: false});
|
||||||
console.log('[D] Bubble clicked:', bubble.id, 'Current RTC handler:', rtcHandlerRef.current);
|
|
||||||
if (rtcHandlerRef.current) {
|
if (rtcHandlerRef.current) {
|
||||||
try {
|
try {
|
||||||
await rtcHandlerRef.current.handleBubbleClick(bubble.id, id);
|
await rtcHandlerRef.current.handleBubbleClick(bubble.id, id);
|
||||||
@ -165,7 +170,6 @@ export const LocalSharing: React.FC = () => {
|
|||||||
const response = await fetch('/api/ulocal/register', {method: 'POST'});
|
const response = await fetch('/api/ulocal/register', {method: 'POST'});
|
||||||
const data = ((await response.json()) as Resp<{ id: string; name: string }>).data;
|
const data = ((await response.json()) as Resp<{ id: string; name: string }>).data;
|
||||||
set(data.id, data.name);
|
set(data.id, data.name);
|
||||||
console.log(`[D] register id = ${data.id}`);
|
|
||||||
connect(`/api/ulocal/ws?id=${data.id}`, {fn: handleWSEvent});
|
connect(`/api/ulocal/ws?id=${data.id}`, {fn: handleWSEvent});
|
||||||
await updateClients();
|
await updateClients();
|
||||||
|
|
||||||
@ -174,7 +178,6 @@ export const LocalSharing: React.FC = () => {
|
|||||||
setRTC(_rtc);
|
setRTC(_rtc);
|
||||||
|
|
||||||
// 在RTC连接创建后立即创建处理器实例
|
// 在RTC连接创建后立即创建处理器实例
|
||||||
console.log('[D] Creating RTC handler after connection setup');
|
|
||||||
rtcHandlerRef.current = new RTCHandler(rtcRef, rtcCallbacks);
|
rtcHandlerRef.current = new RTCHandler(rtcRef, rtcCallbacks);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@ -189,6 +192,14 @@ export const LocalSharing: React.FC = () => {
|
|||||||
|
|
||||||
const bubbles = generateBubbles(clients, id);
|
const bubbles = generateBubbles(clients, id);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (receivingFile && receivingFile.isFile && !receivingFile.receiving) {
|
||||||
|
setReceivingFile(null);
|
||||||
|
setReceivedMessage(receivingFile);
|
||||||
|
setShowMessageDialog(true);
|
||||||
|
}
|
||||||
|
}, [receivingFile]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.container}>
|
<div className={classes.container}>
|
||||||
<CloudBackground/>
|
<CloudBackground/>
|
||||||
@ -209,8 +220,17 @@ export const LocalSharing: React.FC = () => {
|
|||||||
open={open.send}
|
open={open.send}
|
||||||
onSend={handleSend}
|
onSend={handleSend}
|
||||||
onClose={() => setOpen({send: false, receive: false})}
|
onClose={() => setOpen({send: false, receive: false})}
|
||||||
|
name={name}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* 文件接收进度弹窗 */}
|
||||||
|
{receivingFile && (
|
||||||
|
<MessageDialog
|
||||||
|
open={true}
|
||||||
|
message={receivingFile}
|
||||||
|
onClose={() => setReceivingFile(null)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<MessageDialog
|
<MessageDialog
|
||||||
open={showMessageDialog}
|
open={showMessageDialog}
|
||||||
|
@ -136,7 +136,6 @@ const PanelLeftUpload: React.FC<{ set_code: (code:string) => void }> = ({set_cod
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onFileChange(e: React.ChangeEvent<HTMLInputElement>) {
|
function onFileChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||||
console.log('[D] onFileChange: e =', e)
|
|
||||||
setFile(e.currentTarget.files ? e.currentTarget.files[0] : null)
|
setFile(e.currentTarget.files ? e.currentTarget.files[0] : null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +42,6 @@ export const PanelRight = () => {
|
|||||||
|
|
||||||
async function onFetchFile() {
|
async function onFetchFile() {
|
||||||
const url = `/ushare/${code}`
|
const url = `/ushare/${code}`
|
||||||
console.log('[D] onFetchFile: url =', url)
|
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = url;
|
link.href = url;
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import {useState} from "react";
|
import {useState} from "react";
|
||||||
import {createUseStyles} from "react-jss";
|
import {createUseStyles} from "react-jss";
|
||||||
import {Dialog} from "../../component/dialog/dialog.tsx";
|
|
||||||
|
|
||||||
const useClass = createUseStyles({
|
const useClass = createUseStyles({
|
||||||
container: {}
|
container: {}
|
||||||
})
|
})
|
||||||
export const TestPage = () => {
|
export const TestPage = () => {
|
||||||
const classes = useClass()
|
const classes = useClass()
|
||||||
const [open, setOpen] = useState<boolean>(false)
|
const [_open, setOpen] = useState<boolean>(false)
|
||||||
|
|
||||||
const handleOpen = () => {
|
const handleOpen = () => {
|
||||||
setOpen(true)
|
setOpen(true)
|
||||||
@ -15,8 +14,5 @@ export const TestPage = () => {
|
|||||||
|
|
||||||
return <div className={classes.container}>
|
return <div className={classes.container}>
|
||||||
<button onClick={handleOpen}>open</button>
|
<button onClick={handleOpen}>open</button>
|
||||||
<Dialog open={open} title="hello world" >
|
|
||||||
<input />
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
@ -8,7 +8,7 @@ export interface LocalStore {
|
|||||||
setChannel: (chan?: RTCDataChannel) => void;
|
setChannel: (chan?: RTCDataChannel) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useLocalStore = create<LocalStore>()((_set, get) => ({
|
export const useLocalStore = create<LocalStore>()((_set, _get) => ({
|
||||||
id: '',
|
id: '',
|
||||||
name: '',
|
name: '',
|
||||||
set: (id: string, name: string) => {
|
set: (id: string, name: string) => {
|
||||||
|
33
go.mod
33
go.mod
@ -3,50 +3,65 @@ module github.com/loveuer/ushare
|
|||||||
go 1.24.2
|
go 1.24.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/glebarez/sqlite v1.11.0
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/gorilla/websocket v1.5.3
|
||||||
|
github.com/jedib0t/go-pretty/v6 v6.6.7
|
||||||
github.com/loveuer/nf v0.3.5
|
github.com/loveuer/nf v0.3.5
|
||||||
|
github.com/matoous/go-nanoid/v2 v2.1.0
|
||||||
|
github.com/mileusna/useragent v1.3.5
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
|
github.com/spf13/cast v1.7.1
|
||||||
github.com/spf13/viper v1.20.1
|
github.com/spf13/viper v1.20.1
|
||||||
|
golang.org/x/crypto v0.32.0
|
||||||
|
gorm.io/driver/mysql v1.6.0
|
||||||
|
gorm.io/driver/postgres v1.6.0
|
||||||
|
gorm.io/gorm v1.30.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
dario.cat/mergo v1.0.0 // indirect
|
dario.cat/mergo v1.0.0 // indirect
|
||||||
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||||
github.com/ProtonMail/go-crypto v1.0.0 // indirect
|
github.com/ProtonMail/go-crypto v1.0.0 // indirect
|
||||||
github.com/cloudflare/circl v1.3.7 // indirect
|
github.com/cloudflare/circl v1.3.7 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
github.com/fatih/color v1.17.0 // indirect
|
github.com/fatih/color v1.17.0 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||||
|
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||||
github.com/go-git/go-billy/v5 v5.5.0 // indirect
|
github.com/go-git/go-billy/v5 v5.5.0 // indirect
|
||||||
github.com/go-git/go-git/v5 v5.12.0 // indirect
|
github.com/go-git/go-git/v5 v5.12.0 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/gorilla/websocket v1.5.3 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
|
github.com/jackc/pgx/v5 v5.6.0 // indirect
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||||
github.com/jedib0t/go-pretty/v6 v6.6.7 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||||
github.com/matoous/go-nanoid/v2 v2.1.0 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
github.com/mileusna/useragent v1.3.5 // indirect
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.7.0 // indirect
|
github.com/sagikazarmark/locafero v0.7.0 // indirect
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||||
github.com/skeema/knownhosts v1.2.2 // indirect
|
github.com/skeema/knownhosts v1.2.2 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
github.com/spf13/afero v1.12.0 // indirect
|
github.com/spf13/afero v1.12.0 // indirect
|
||||||
github.com/spf13/cast v1.7.1 // indirect
|
|
||||||
github.com/spf13/pflag v1.0.6 // indirect
|
github.com/spf13/pflag v1.0.6 // indirect
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||||
go.uber.org/atomic v1.9.0 // indirect
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
go.uber.org/multierr v1.9.0 // indirect
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
golang.org/x/crypto v0.32.0 // indirect
|
|
||||||
golang.org/x/mod v0.17.0 // indirect
|
golang.org/x/mod v0.17.0 // indirect
|
||||||
golang.org/x/net v0.33.0 // indirect
|
golang.org/x/net v0.33.0 // indirect
|
||||||
golang.org/x/sync v0.11.0 // indirect
|
golang.org/x/sync v0.11.0 // indirect
|
||||||
@ -55,4 +70,8 @@ require (
|
|||||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
modernc.org/libc v1.22.5 // indirect
|
||||||
|
modernc.org/mathutil v1.5.0 // indirect
|
||||||
|
modernc.org/memory v1.5.0 // indirect
|
||||||
|
modernc.org/sqlite v1.23.1 // indirect
|
||||||
)
|
)
|
||||||
|
54
go.sum
54
go.sum
@ -1,5 +1,7 @@
|
|||||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||||
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||||
@ -18,6 +20,8 @@ github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxG
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
|
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
|
||||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
@ -28,6 +32,10 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
|
|||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
|
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||||
|
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||||
|
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||||
|
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||||
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
|
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
|
||||||
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||||
@ -38,20 +46,36 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj
|
|||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||||
github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
|
github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
|
||||||
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
|
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q=
|
||||||
|
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
|
||||||
|
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||||
github.com/jedib0t/go-pretty/v6 v6.6.7 h1:m+LbHpm0aIAPLzLbMfn8dc3Ht8MW7lsSO4MPItz/Uuo=
|
github.com/jedib0t/go-pretty/v6 v6.6.7 h1:m+LbHpm0aIAPLzLbMfn8dc3Ht8MW7lsSO4MPItz/Uuo=
|
||||||
github.com/jedib0t/go-pretty/v6 v6.6.7/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
|
github.com/jedib0t/go-pretty/v6 v6.6.7/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
|
||||||
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
@ -84,6 +108,9 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
@ -110,6 +137,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
@ -144,8 +172,7 @@ golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
|||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
|
||||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -161,8 +188,6 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
|
||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
@ -170,8 +195,8 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX
|
|||||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
@ -179,8 +204,6 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
|
||||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@ -198,5 +221,20 @@ gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
|||||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
|
||||||
|
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
|
||||||
|
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||||
|
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||||
|
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
||||||
|
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||||
|
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
||||||
|
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
|
||||||
|
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||||
|
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||||
|
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||||
|
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||||
|
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
|
||||||
|
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"au99999/internal/opt"
|
|
||||||
"au99999/pkg/tool"
|
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/loveuer/ushare/internal/opt"
|
||||||
|
"github.com/loveuer/ushare/internal/pkg/tool"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -167,8 +167,6 @@
|
|||||||
const text = inputBox.value;
|
const text = inputBox.value;
|
||||||
const files = fileInput.files;
|
const files = fileInput.files;
|
||||||
|
|
||||||
console.log('文本内容:', text);
|
|
||||||
console.log('发送文件:', Array.from(files));
|
|
||||||
|
|
||||||
// 清空内容
|
// 清空内容
|
||||||
inputBox.value = '';
|
inputBox.value = '';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user