mirror of https://gitee.com/godoos/godoos.git
20 changed files with 774 additions and 808 deletions
@ -1,111 +0,0 @@ |
|||||
package localchat |
|
||||
|
|
||||
import ( |
|
||||
"encoding/json" |
|
||||
"fmt" |
|
||||
"log" |
|
||||
"net" |
|
||||
"os" |
|
||||
"time" |
|
||||
) |
|
||||
|
|
||||
func DiscoverServers() { |
|
||||
broadcastTicker := time.NewTicker(broadcartTime) |
|
||||
done := make(chan struct{}) // New channel to signal when to stop
|
|
||||
|
|
||||
go func() { |
|
||||
for { |
|
||||
select { |
|
||||
case <-broadcastTicker.C: |
|
||||
conn, err := net.Dial("udp", broadcastAddr) |
|
||||
if err != nil { |
|
||||
fmt.Println(err) |
|
||||
continue |
|
||||
} |
|
||||
|
|
||||
myIP, myHostname, err := getMyIPAndHostname() |
|
||||
if err != nil { |
|
||||
log.Printf("Failed to get my IP and hostname: %v", err) |
|
||||
conn.Close() |
|
||||
continue |
|
||||
} |
|
||||
|
|
||||
msg := UdpMessage{ |
|
||||
Type: "online", |
|
||||
Hostname: myHostname, |
|
||||
IP: myIP, |
|
||||
Message: time.Now().Format("2006-01-02 15:04:05"), |
|
||||
} |
|
||||
jsonData, err := json.Marshal(msg) |
|
||||
if err != nil { |
|
||||
log.Printf("Failed to marshal message to JSON: %v", err) |
|
||||
conn.Close() |
|
||||
continue |
|
||||
} |
|
||||
//log.Printf("Sending message: %+v", msg)
|
|
||||
_, err = conn.Write(jsonData) |
|
||||
if err != nil { |
|
||||
fmt.Println(err) |
|
||||
} |
|
||||
conn.Close() // Close the connection after use
|
|
||||
case <-done: // Signal to stop
|
|
||||
broadcastTicker.Stop() |
|
||||
return |
|
||||
} |
|
||||
} |
|
||||
}() |
|
||||
} |
|
||||
|
|
||||
// 获取自己的IP地址和主机名
|
|
||||
func getMyIPAndHostname() (string, string, error) { |
|
||||
hostname, err := os.Hostname() |
|
||||
if err != nil { |
|
||||
return "", "", fmt.Errorf("failed to get hostname: %w", err) |
|
||||
} |
|
||||
|
|
||||
addrs, err := net.Interfaces() |
|
||||
if err != nil { |
|
||||
return "", "", fmt.Errorf("failed to get network interfaces: %w", err) |
|
||||
} |
|
||||
|
|
||||
var preferredIP net.IP |
|
||||
for _, iface := range addrs { |
|
||||
if iface.Flags&net.FlagUp == 0 { |
|
||||
// Skip interfaces that are not up
|
|
||||
continue |
|
||||
} |
|
||||
ifAddrs, err := iface.Addrs() |
|
||||
if err != nil { |
|
||||
continue // Ignore this interface if we can't get its addresses
|
|
||||
} |
|
||||
for _, addr := range ifAddrs { |
|
||||
var ip net.IP |
|
||||
switch v := addr.(type) { |
|
||||
case *net.IPNet: |
|
||||
ip = v.IP |
|
||||
case *net.IPAddr: |
|
||||
ip = v.IP |
|
||||
default: |
|
||||
continue |
|
||||
} |
|
||||
if ip.IsLoopback() { |
|
||||
continue // Skip loopback addresses
|
|
||||
} |
|
||||
if ip.To4() != nil && (ip.IsPrivate() || ip.IsGlobalUnicast()) { |
|
||||
// Prefer global unicast or private addresses over link-local
|
|
||||
preferredIP = ip |
|
||||
break |
|
||||
} |
|
||||
} |
|
||||
if preferredIP != nil { |
|
||||
// Found a preferred IP, break out of the loop
|
|
||||
break |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if preferredIP == nil { |
|
||||
return "", "", fmt.Errorf("no preferred non-loopback IPv4 address found") |
|
||||
} |
|
||||
|
|
||||
return preferredIP.String(), hostname, nil |
|
||||
} |
|
@ -1,20 +1,206 @@ |
|||||
package localchat |
package localchat |
||||
|
|
||||
import ( |
import ( |
||||
|
"encoding/json" |
||||
|
"fmt" |
||||
"godo/libs" |
"godo/libs" |
||||
|
"io" |
||||
|
"log" |
||||
|
"net" |
||||
|
"net/http" |
||||
"os" |
"os" |
||||
"path/filepath" |
"path/filepath" |
||||
"time" |
"time" |
||||
) |
) |
||||
|
|
||||
func GetChatPath() (string, error) { |
const ( |
||||
|
fileSize = 1024 // 每个数据包的大小
|
||||
|
) |
||||
|
|
||||
|
type FileChunk struct { |
||||
|
ChunkIndex int `json:"chunk_index"` |
||||
|
Data []byte `json:"data"` |
||||
|
Checksum uint32 `json:"checksum"` |
||||
|
Timestamp time.Time `json:"timestamp"` |
||||
|
Filename string `json:"filename"` |
||||
|
} |
||||
|
|
||||
|
func FileHandler(w http.ResponseWriter, r *http.Request) { |
||||
|
// 初始化多播地址
|
||||
|
var msg UdpMessage |
||||
|
decoder := json.NewDecoder(r.Body) |
||||
|
if err := decoder.Decode(&msg); err != nil { |
||||
|
http.Error(w, "Invalid request body", http.StatusBadRequest) |
||||
|
return |
||||
|
} |
||||
|
defer r.Body.Close() |
||||
|
toIp := msg.IP |
||||
|
preferredIP, err := GetMyIp() |
||||
|
if err != nil { |
||||
|
http.Error(w, "Failed to get preferred IP", http.StatusInternalServerError) |
||||
|
return |
||||
|
} |
||||
|
msg.IP = preferredIP |
||||
|
msg.Type = "file" |
||||
|
basePath, err := libs.GetOsDir() |
||||
|
if err != nil { |
||||
|
libs.HTTPError(w, http.StatusInternalServerError, err.Error()) |
||||
|
return |
||||
|
} |
||||
|
filePath := filepath.Join(basePath, msg.Message.(string)) |
||||
|
// 处理单个文件或整个文件夹
|
||||
|
if fileInfo, err := os.Stat(filePath); err == nil { |
||||
|
if fileInfo.IsDir() { |
||||
|
handleDirectory(filePath, toIp, msg) |
||||
|
} else { |
||||
|
handleFile(filePath, toIp, msg) |
||||
|
} |
||||
|
} else { |
||||
|
http.Error(w, "Failed to stat path", http.StatusInternalServerError) |
||||
|
return |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func handleFile(filePath string, toIp string, message UdpMessage) { |
||||
|
// 打开文件
|
||||
|
file, err := os.Open(filePath) |
||||
|
if err != nil { |
||||
|
log.Fatalf("Failed to open file: %v", err) |
||||
|
} |
||||
|
defer file.Close() |
||||
|
|
||||
|
// 获取文件大小
|
||||
|
fileInfo, err := file.Stat() |
||||
|
if err != nil { |
||||
|
log.Fatalf("Failed to get file info: %v", err) |
||||
|
} |
||||
|
fileSize := fileInfo.Size() |
||||
|
|
||||
|
// 计算需要发送的数据包数量
|
||||
|
numChunks := (fileSize + fileSize - 1) / fileSize |
||||
|
|
||||
|
// 发送文件
|
||||
|
SendFile(file, int(numChunks), toIp, message) |
||||
|
} |
||||
|
|
||||
|
func handleDirectory(dirPath string, toIp string, message UdpMessage) { |
||||
|
err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error { |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
if !info.IsDir() { |
||||
|
handleFile(path, toIp, message) |
||||
|
} |
||||
|
return nil |
||||
|
}) |
||||
|
if err != nil { |
||||
|
log.Fatalf("Failed to walk directory: %v", err) |
||||
|
} |
||||
|
} |
||||
|
func SendFile(file *os.File, numChunks int, toIp string, message UdpMessage) { |
||||
|
// 逐块读取文件并发送
|
||||
|
for i := 0; i < numChunks; i++ { |
||||
|
var chunkData [fileSize]byte |
||||
|
n, err := file.Read(chunkData[:]) |
||||
|
if err != nil && err != io.EOF { |
||||
|
log.Fatalf("Failed to read file chunk: %v", err) |
||||
|
} |
||||
|
|
||||
|
// 创建文件块
|
||||
|
chunk := FileChunk{ |
||||
|
ChunkIndex: i, |
||||
|
Data: chunkData[:n], |
||||
|
Checksum: calculateChecksum(chunkData[:n]), |
||||
|
Timestamp: time.Now(), |
||||
|
Filename: filepath.Base(file.Name()), |
||||
|
} |
||||
|
message.Message = chunk |
||||
|
// 将文件块转换为 JSON 格式
|
||||
|
data, err := json.Marshal(message) |
||||
|
if err != nil { |
||||
|
log.Fatalf("Failed to marshal chunk: %v", err) |
||||
|
} |
||||
|
|
||||
|
// 发送文件块
|
||||
|
addr, err := net.ResolveUDPAddr("udp4", toIp) |
||||
|
if err != nil { |
||||
|
log.Fatalf("Failed to resolve UDP address: %v", err) |
||||
|
} |
||||
|
|
||||
|
conn, err := net.DialUDP("udp4", nil, addr) |
||||
|
if err != nil { |
||||
|
log.Fatalf("Failed to dial UDP address: %v", err) |
||||
|
} |
||||
|
defer conn.Close() |
||||
|
|
||||
|
_, err = conn.Write(data) |
||||
|
if err != nil { |
||||
|
log.Printf("Failed to write data: %v", err) |
||||
|
} |
||||
|
|
||||
|
fmt.Printf("发送文件块 %d 到 %s 成功\n", i, toIp) |
||||
|
} |
||||
|
} |
||||
|
func RecieveFile(msg UdpMessage) { |
||||
|
chunk := msg.Message.(FileChunk) |
||||
|
|
||||
|
// 验证校验和
|
||||
|
calculatedChecksum := calculateChecksum(chunk.Data) |
||||
|
if calculatedChecksum != chunk.Checksum { |
||||
|
fmt.Printf("Checksum mismatch for chunk %d from %s\n", chunk.ChunkIndex, msg.IP) |
||||
|
return |
||||
|
} |
||||
|
|
||||
baseDir, err := libs.GetOsDir() |
baseDir, err := libs.GetOsDir() |
||||
if err != nil { |
if err != nil { |
||||
return "", err |
log.Printf("Failed to get OS directory: %v", err) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// 创建接收文件的目录
|
||||
|
receiveDir := filepath.Join(baseDir, "C", "Users", "Reciv", time.Now().Format("2006-01-02")) |
||||
|
if !libs.PathExists(receiveDir) { |
||||
|
err := os.MkdirAll(receiveDir, 0755) |
||||
|
if err != nil { |
||||
|
log.Printf("Failed to create receive directory: %v", err) |
||||
|
return |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 确定文件路径
|
||||
|
filePath := filepath.Join(receiveDir, chunk.Filename) |
||||
|
|
||||
|
// 如果文件不存在,则创建新文件
|
||||
|
if _, err := os.Stat(filePath); os.IsNotExist(err) { |
||||
|
file, err := os.Create(filePath) |
||||
|
if err != nil { |
||||
|
log.Printf("Failed to create file: %v", err) |
||||
|
return |
||||
|
} |
||||
|
defer file.Close() |
||||
|
} |
||||
|
|
||||
|
// 打开或追加到现有文件
|
||||
|
file, err := os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY, 0644) |
||||
|
if err != nil { |
||||
|
log.Printf("Failed to open file: %v", err) |
||||
|
return |
||||
|
} |
||||
|
defer file.Close() |
||||
|
|
||||
|
// 写入数据
|
||||
|
_, err = file.Write(chunk.Data) |
||||
|
if err != nil { |
||||
|
log.Printf("Failed to write data to file: %v", err) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
fmt.Printf("接收到文件块 %d 从 %s 成功\n", chunk.ChunkIndex, msg.IP) |
||||
} |
} |
||||
modelDir := filepath.Join(baseDir, "C", "Users", "Reciv", time.Now().Format("2006-01-02")) |
func calculateChecksum(data []byte) uint32 { |
||||
if !libs.PathExists(modelDir) { |
checksum := uint32(0) |
||||
os.MkdirAll(modelDir, 0755) |
for _, b := range data { |
||||
|
checksum += uint32(b) |
||||
} |
} |
||||
return modelDir, nil |
return checksum |
||||
} |
} |
||||
|
@ -0,0 +1,67 @@ |
|||||
|
package localchat |
||||
|
|
||||
|
import ( |
||||
|
"fmt" |
||||
|
"net" |
||||
|
"os" |
||||
|
) |
||||
|
|
||||
|
// 获取自己的IP地址和主机名
|
||||
|
func GetMyIPAndHostname() (string, string, error) { |
||||
|
hostname, err := os.Hostname() |
||||
|
if err != nil { |
||||
|
return "", "", fmt.Errorf("failed to get hostname: %w", err) |
||||
|
} |
||||
|
preferredIP, err := GetMyIp() |
||||
|
if err != nil { |
||||
|
return "", "", fmt.Errorf("failed to get IP address: %w", err) |
||||
|
} |
||||
|
|
||||
|
return preferredIP, hostname, nil |
||||
|
} |
||||
|
func GetMyIp() (string, error) { |
||||
|
addrs, err := net.Interfaces() |
||||
|
if err != nil { |
||||
|
return "", fmt.Errorf("failed to get network interfaces: %w", err) |
||||
|
} |
||||
|
|
||||
|
var preferredIP net.IP |
||||
|
for _, iface := range addrs { |
||||
|
if iface.Flags&net.FlagUp == 0 { |
||||
|
// Skip interfaces that are not up
|
||||
|
continue |
||||
|
} |
||||
|
ifAddrs, err := iface.Addrs() |
||||
|
if err != nil { |
||||
|
continue // Ignore this interface if we can't get its addresses
|
||||
|
} |
||||
|
for _, addr := range ifAddrs { |
||||
|
var ip net.IP |
||||
|
switch v := addr.(type) { |
||||
|
case *net.IPNet: |
||||
|
ip = v.IP |
||||
|
case *net.IPAddr: |
||||
|
ip = v.IP |
||||
|
default: |
||||
|
continue |
||||
|
} |
||||
|
if ip.IsLoopback() { |
||||
|
continue // Skip loopback addresses
|
||||
|
} |
||||
|
if ip.To4() != nil && (ip.IsPrivate() || ip.IsGlobalUnicast()) { |
||||
|
// Prefer global unicast or private addresses over link-local
|
||||
|
preferredIP = ip |
||||
|
break |
||||
|
} |
||||
|
} |
||||
|
if preferredIP != nil { |
||||
|
// Found a preferred IP, break out of the loop
|
||||
|
break |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if preferredIP == nil { |
||||
|
return "", fmt.Errorf("no preferred non-loopback IPv4 address found") |
||||
|
} |
||||
|
return preferredIP.String(), nil |
||||
|
} |
@ -1,44 +0,0 @@ |
|||||
package localchat |
|
||||
|
|
||||
import ( |
|
||||
"encoding/json" |
|
||||
"fmt" |
|
||||
"log" |
|
||||
"net" |
|
||||
) |
|
||||
|
|
||||
func StartServiceDiscovery() { |
|
||||
// 解析多播地址
|
|
||||
addr, err := net.ResolveUDPAddr("udp4", broadcastAddr) |
|
||||
if err != nil { |
|
||||
fmt.Println("Error resolving multicast address:", err) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// 监听本地网络接口上的多播地址
|
|
||||
conn, err := net.ListenMulticastUDP("udp4", nil, addr) |
|
||||
if err != nil { |
|
||||
fmt.Println("Error listening on multicast address:", err) |
|
||||
return |
|
||||
} |
|
||||
defer conn.Close() |
|
||||
|
|
||||
buffer := make([]byte, 1024) |
|
||||
for { |
|
||||
n, addr, err := conn.ReadFromUDP(buffer) |
|
||||
if err != nil { |
|
||||
log.Printf("Error reading from UDP: %v,addr:%v", err, addr) |
|
||||
continue |
|
||||
} |
|
||||
//fmt.Printf("Received message: %s from %s\n", buffer[:n], addr)
|
|
||||
|
|
||||
var udpMsg UdpMessage |
|
||||
err = json.Unmarshal(buffer[:n], &udpMsg) |
|
||||
if err != nil { |
|
||||
fmt.Printf("Error unmarshalling JSON: %v\n", err) |
|
||||
continue |
|
||||
} |
|
||||
//log.Printf("Get message: %+v", udpMsg)
|
|
||||
OnlineUsers[udpMsg.IP] = udpMsg |
|
||||
} |
|
||||
} |
|
@ -1,133 +0,0 @@ |
|||||
package localchat |
|
||||
|
|
||||
import ( |
|
||||
"encoding/json" |
|
||||
"fmt" |
|
||||
"godo/libs" |
|
||||
"log" |
|
||||
"net/http" |
|
||||
"os" |
|
||||
"time" |
|
||||
) |
|
||||
|
|
||||
func init() { |
|
||||
go StartServiceDiscovery() |
|
||||
go DiscoverServers() |
|
||||
} |
|
||||
|
|
||||
func SseHandler(w http.ResponseWriter, r *http.Request) { |
|
||||
flusher, ok := w.(http.Flusher) |
|
||||
if !ok { |
|
||||
http.Error(w, "Streaming unsupported!", http.StatusInternalServerError) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
w.Header().Set("Content-Type", "text/event-stream") |
|
||||
w.Header().Set("Cache-Control", "no-cache") |
|
||||
w.Header().Set("Connection", "keep-alive") |
|
||||
w.WriteHeader(http.StatusOK) |
|
||||
ticker := time.NewTicker(broadcartTime) // 每3秒检查一次在线用户
|
|
||||
defer ticker.Stop() |
|
||||
// 处理新消息
|
|
||||
ctx := r.Context() |
|
||||
// 使用Context来监听请求的取消
|
|
||||
go func() { |
|
||||
defer func() { |
|
||||
if r := recover(); r != nil { |
|
||||
log.Printf("Recovered in SSE goroutine: %v", r) |
|
||||
} |
|
||||
}() |
|
||||
|
|
||||
for { |
|
||||
select { |
|
||||
case <-ctx.Done(): // 当请求被取消时,退出循环
|
|
||||
return |
|
||||
case msg := <-messageChan: |
|
||||
// 构造JSON数据
|
|
||||
jsonData, err := json.Marshal(msg) |
|
||||
if err != nil { |
|
||||
log.Printf("Failed to marshal message to JSON: %v", err) |
|
||||
continue |
|
||||
} |
|
||||
|
|
||||
// 通过SSE发送JSON数据
|
|
||||
fmt.Fprintf(w, "data: %s\n\n", string(jsonData)) |
|
||||
flusher.Flush() |
|
||||
} |
|
||||
} |
|
||||
}() |
|
||||
myIP, myHostname, err := getMyIPAndHostname() |
|
||||
if err != nil { |
|
||||
log.Printf("Failed to get my IP and hostname: %v", err) |
|
||||
return |
|
||||
} |
|
||||
for { |
|
||||
select { |
|
||||
case <-ticker.C: // 每隔一段时间检查并广播在线用户
|
|
||||
var userList []UdpMessage |
|
||||
// 首先将自己的IP和主机名放入列表
|
|
||||
myMsg := UdpMessage{ |
|
||||
IP: myIP, |
|
||||
Hostname: myHostname, |
|
||||
Type: "online", |
|
||||
Message: time.Now().Format("2006-01-02 15:04:05"), |
|
||||
} |
|
||||
userList = append(userList, myMsg) |
|
||||
//log.Printf("Online users: %v", OnlineUsers)
|
|
||||
for ip, info := range OnlineUsers { |
|
||||
if ip != myIP { // 确保不重复添加自己
|
|
||||
userList = append(userList, info) |
|
||||
} |
|
||||
} |
|
||||
res := UserList{Type: "user_list", Content: userList} |
|
||||
// 将用户列表转换为JSON字符串
|
|
||||
jsonData, err := json.Marshal(res) |
|
||||
if err != nil { |
|
||||
log.Printf("Failed to marshal online users to JSON: %v", err) |
|
||||
continue |
|
||||
} |
|
||||
// 通过SSE发送JSON数据
|
|
||||
fmt.Fprintf(w, "data: %s\n\n", string(jsonData)) |
|
||||
flusher.Flush() |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
func HandleMessage(w http.ResponseWriter, r *http.Request) { |
|
||||
if r.Method != http.MethodPost { |
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
var msg Message |
|
||||
decoder := json.NewDecoder(r.Body) |
|
||||
if err := decoder.Decode(&msg); err != nil { |
|
||||
http.Error(w, "Invalid request body", http.StatusBadRequest) |
|
||||
return |
|
||||
} |
|
||||
defer r.Body.Close() |
|
||||
// 将消息放入messageChan
|
|
||||
messageChan <- msg |
|
||||
//log.Printf("Received text message from %s: %s", msg.SenderInfo.IP, msg.Content)
|
|
||||
// 这里可以添加存储文本消息到数据库或其他处理逻辑
|
|
||||
w.WriteHeader(http.StatusOK) |
|
||||
fmt.Fprintln(w, "Text message send successfully") |
|
||||
} |
|
||||
func CheckUserHanlder(w http.ResponseWriter, r *http.Request) { |
|
||||
res := map[string]any{} |
|
||||
res["code"] = 0 |
|
||||
res["message"] = "ok" |
|
||||
// 获取主机名
|
|
||||
hostname, err := os.Hostname() |
|
||||
if err == nil { |
|
||||
hostname = "Unknown" |
|
||||
} |
|
||||
ip, _ := libs.GetIPAddress() |
|
||||
|
|
||||
res["data"] = map[string]any{ |
|
||||
"ip": ip, |
|
||||
"hostname": hostname, |
|
||||
} |
|
||||
w.Header().Set("Content-Type", "application/json") |
|
||||
w.WriteHeader(http.StatusOK) |
|
||||
json.NewEncoder(w).Encode(res) |
|
||||
} |
|
@ -1,63 +0,0 @@ |
|||||
package localchat |
|
||||
|
|
||||
import ( |
|
||||
"sync" |
|
||||
"time" |
|
||||
) |
|
||||
|
|
||||
// type SenderInfo struct {
|
|
||||
// SenderIP string `json:"sender_ip"`
|
|
||||
// Username string `json:"username"`
|
|
||||
// }
|
|
||||
type Message struct { |
|
||||
Type string `json:"type"` // 消息类型,如"text"、"image"等
|
|
||||
Content string `json:"content"` // 消息内容
|
|
||||
SenderInfo UserInfo `json:"senderInfo"` // 发送者的IP地址
|
|
||||
FileInfo FilePartInfo `json:"fileInfo"` |
|
||||
FileList []UploadInfo `json:"fileList"` |
|
||||
} |
|
||||
type UserInfo struct { |
|
||||
IP string `json:"ip"` |
|
||||
Hostname string `json:"hostname"` |
|
||||
} |
|
||||
type UdpMessage struct { |
|
||||
Type string `json:"type"` |
|
||||
IP string `json:"ip"` |
|
||||
Hostname string `json:"hostname"` |
|
||||
Message string `json:"message"` |
|
||||
} |
|
||||
type UserList struct { |
|
||||
Type string `json:"type"` |
|
||||
Content []UdpMessage `json:"content"` |
|
||||
} |
|
||||
type UploadInfo struct { |
|
||||
Name string `json:"name"` |
|
||||
SavePath string `json:"save_path"` |
|
||||
Content string `json:"content"` |
|
||||
CreatedAt time.Time `json:"created_at"` |
|
||||
} |
|
||||
|
|
||||
// 文件分片信息
|
|
||||
type FilePartInfo struct { |
|
||||
FileName string `json:"filename"` |
|
||||
PartNumber int `json:"part_number"` |
|
||||
TotalParts int `json:"total_parts"` |
|
||||
} |
|
||||
|
|
||||
// 分片上传状态跟踪
|
|
||||
type UploadStatus struct { |
|
||||
sync.Mutex |
|
||||
Status map[string]int // key: fileName, value: 已上传分片数
|
|
||||
} |
|
||||
|
|
||||
var ( |
|
||||
messageChan = make(chan Message, 100) // 缓存大小根据实际情况设定
|
|
||||
) |
|
||||
var uploadStatus = UploadStatus{Status: make(map[string]int)} |
|
||||
var broadcartTime = 3 * time.Second |
|
||||
|
|
||||
// var broadcastAddr = "224.0.0.1:1679" // 多播地址
|
|
||||
var broadcastAddr = "224.0.0.251:1234" |
|
||||
|
|
||||
// var broadcastAddr = "255.255.255.255:1769" // 广播地址
|
|
||||
var OnlineUsers = make(map[string]UdpMessage) // 全局map,key为IP,value为主机名
|
|
@ -0,0 +1,168 @@ |
|||||
|
package localchat |
||||
|
|
||||
|
import ( |
||||
|
"encoding/json" |
||||
|
"fmt" |
||||
|
"log" |
||||
|
"net" |
||||
|
"net/http" |
||||
|
|
||||
|
"golang.org/x/net/ipv4" |
||||
|
) |
||||
|
|
||||
|
type UdpMessage struct { |
||||
|
Type string `json:"type"` |
||||
|
IP string `json:"ip"` |
||||
|
Hostname string `json:"hostname"` |
||||
|
Message any `json:"message"` |
||||
|
} |
||||
|
|
||||
|
// 多播地址列表
|
||||
|
var multicastAddrs = []string{"239.255.255.250:2024", "239.255.255.251:2024", "224.0.0.251:1234", "224.0.0.1:1679"} |
||||
|
var OnlineUsers = make(map[string]UdpMessage) // 全局map,key为IP,value为主机名
|
||||
|
// SendMulticast 发送多播消息
|
||||
|
func init() { |
||||
|
go InitMulticast() |
||||
|
go ListenForMulticast() |
||||
|
} |
||||
|
func InitMulticast() { |
||||
|
myIP, myHostname, err := GetMyIPAndHostname() |
||||
|
if err != nil { |
||||
|
return |
||||
|
} |
||||
|
message := UdpMessage{ |
||||
|
Type: "online", |
||||
|
IP: myIP, |
||||
|
Hostname: myHostname, |
||||
|
Message: "online", |
||||
|
} |
||||
|
SendMulticast(message) |
||||
|
} |
||||
|
func SendMulticast(message UdpMessage) error { |
||||
|
for _, addrStr := range multicastAddrs { |
||||
|
addr, err := net.ResolveUDPAddr("udp4", addrStr) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
conn, err := net.DialUDP("udp4", nil, addr) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
defer conn.Close() |
||||
|
|
||||
|
data, err := json.Marshal(message) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
_, err = conn.WriteToUDP(data, addr) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
fmt.Printf("发送多播消息到 %s 成功\n", addrStr) |
||||
|
} |
||||
|
|
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// ListenForMulticast 监听多播消息
|
||||
|
func ListenForMulticast() { |
||||
|
multicastGroup, err := net.ResolveUDPAddr("udp4", multicastAddrs[0]) |
||||
|
if err != nil { |
||||
|
fmt.Println("Error resolving UDP address:", err) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
conn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 2024}) |
||||
|
if err != nil { |
||||
|
fmt.Println("Error listening on UDP address:", err) |
||||
|
return |
||||
|
} |
||||
|
defer conn.Close() |
||||
|
udpConn := ipv4.NewPacketConn(conn) |
||||
|
if err := udpConn.JoinGroup(nil, multicastGroup); err != nil { |
||||
|
log.Fatalf("Failed to join multicast group: %v", err) |
||||
|
} |
||||
|
|
||||
|
buffer := make([]byte, 1024) |
||||
|
for { |
||||
|
n, _, src, err := udpConn.ReadFrom(buffer) |
||||
|
if err != nil { |
||||
|
log.Printf("Error reading from UDP: %v, addr: %v", err, src) |
||||
|
continue |
||||
|
} |
||||
|
|
||||
|
var udpMsg UdpMessage |
||||
|
err = json.Unmarshal(buffer[:n], &udpMsg) |
||||
|
if err != nil { |
||||
|
fmt.Printf("Error unmarshalling JSON: %v\n", err) |
||||
|
continue |
||||
|
} |
||||
|
|
||||
|
OnlineUsers[udpMsg.IP] = udpMsg |
||||
|
if udpMsg.Type == "file" { |
||||
|
RecieveFile(udpMsg) |
||||
|
} |
||||
|
fmt.Printf("Received message from %s: %s\n", udpMsg.IP, udpMsg.Hostname) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// SendToIP 向指定的 IP 地址发送 UDP 消息
|
||||
|
func SendToIP(ip string, message UdpMessage) error { |
||||
|
addr, err := net.ResolveUDPAddr("udp4", fmt.Sprintf("%s:2024", ip)) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
conn, err := net.DialUDP("udp4", nil, addr) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
defer conn.Close() |
||||
|
|
||||
|
data, err := json.Marshal(message) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
_, err = conn.WriteToUDP(data, addr) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
fmt.Printf("发送 UDP 消息到 %s 成功\n", ip) |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
// 获取 OnlineUsers 的最新状态
|
||||
|
func GetOnlineUsers() map[string]UdpMessage { |
||||
|
return OnlineUsers |
||||
|
} |
||||
|
func HandleMessage(w http.ResponseWriter, r *http.Request) { |
||||
|
var msg UdpMessage |
||||
|
decoder := json.NewDecoder(r.Body) |
||||
|
if err := decoder.Decode(&msg); err != nil { |
||||
|
http.Error(w, "Invalid request body", http.StatusBadRequest) |
||||
|
return |
||||
|
} |
||||
|
defer r.Body.Close() |
||||
|
ip := msg.IP |
||||
|
preferredIP, err := GetMyIp() |
||||
|
if err != nil { |
||||
|
http.Error(w, "Failed to get preferred IP", http.StatusInternalServerError) |
||||
|
return |
||||
|
} |
||||
|
msg.IP = preferredIP |
||||
|
//msg.Type = "text"
|
||||
|
err = SendToIP(ip, msg) |
||||
|
if err != nil { |
||||
|
http.Error(w, "Failed to send message", http.StatusInternalServerError) |
||||
|
return |
||||
|
} |
||||
|
//log.Printf("Received text message from %s: %s", msg.SenderInfo.IP, msg.Content)
|
||||
|
// 这里可以添加存储文本消息到数据库或其他处理逻辑
|
||||
|
w.WriteHeader(http.StatusOK) |
||||
|
fmt.Fprintln(w, "Text message send successfully") |
||||
|
} |
@ -1,205 +0,0 @@ |
|||||
package localchat |
|
||||
|
|
||||
import ( |
|
||||
"encoding/base64" |
|
||||
"fmt" |
|
||||
"godo/libs" |
|
||||
"io" |
|
||||
"log" |
|
||||
"net/http" |
|
||||
"os" |
|
||||
"path/filepath" |
|
||||
"strconv" |
|
||||
"strings" |
|
||||
"time" |
|
||||
) |
|
||||
|
|
||||
// 合并文件分片
|
|
||||
func mergeFiles(fileName string, totalParts int, chatDir string) error { |
|
||||
var parts []io.Reader |
|
||||
for i := 1; i <= totalParts; i++ { |
|
||||
filePath := fmt.Sprintf("%v%v_%v.part", chatDir, fileName, i) |
|
||||
file, err := os.Open(filePath) |
|
||||
if err != nil { |
|
||||
return fmt.Errorf("failed to open part %d: %w", i, err) |
|
||||
} |
|
||||
defer file.Close() |
|
||||
parts = append(parts, file) |
|
||||
} |
|
||||
|
|
||||
mergedFilePath := fmt.Sprintf("%vmerged_%v", chatDir, fileName) |
|
||||
mergedFile, err := os.Create(mergedFilePath) |
|
||||
if err != nil { |
|
||||
return fmt.Errorf("failed to create merged file: %w", err) |
|
||||
} |
|
||||
defer mergedFile.Close() |
|
||||
|
|
||||
_, err = io.Copy(mergedFile, io.MultiReader(parts...)) |
|
||||
if err != nil { |
|
||||
return fmt.Errorf("failed to merge files: %w", err) |
|
||||
} |
|
||||
|
|
||||
// 合并后清理分片文件
|
|
||||
for i := 1; i <= totalParts; i++ { |
|
||||
os.Remove(fmt.Sprintf("%v%v_%v.part", chatDir, fileName, i)) |
|
||||
} |
|
||||
|
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
func UploadBigFileHandler(w http.ResponseWriter, r *http.Request, msg Message) { |
|
||||
|
|
||||
chatDir, err := GetChatPath() |
|
||||
if err != nil { |
|
||||
http.Error(w, "Failed to get chat path", http.StatusInternalServerError) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// 创建或打开临时文件以写入分片
|
|
||||
tempFilePath := fmt.Sprintf("%v%v_%v.part", chatDir, msg.FileInfo.FileName, msg.FileInfo.PartNumber) |
|
||||
out, err := os.Create(tempFilePath) |
|
||||
if err != nil { |
|
||||
log.Printf("Failed to create temp file: %v", err) |
|
||||
http.Error(w, "Failed to create temp file", http.StatusInternalServerError) |
|
||||
return |
|
||||
} |
|
||||
defer out.Close() |
|
||||
|
|
||||
// 将请求体的内容写入临时文件
|
|
||||
_, err = io.Copy(out, r.Body) |
|
||||
if err != nil { |
|
||||
log.Printf("Failed to write file part: %v", err) |
|
||||
http.Error(w, "Failed to write file part", http.StatusInternalServerError) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// 更新上传状态
|
|
||||
uploadStatus.Lock() |
|
||||
uploadStatus.Status[msg.FileInfo.FileName]++ |
|
||||
if uploadStatus.Status[msg.FileInfo.FileName] == msg.FileInfo.TotalParts { |
|
||||
// 所有分片上传完成,触发合并
|
|
||||
go func() { |
|
||||
err := mergeFiles(msg.FileInfo.FileName, msg.FileInfo.TotalParts, chatDir) |
|
||||
if err != nil { |
|
||||
log.Printf("Failed to merge files for %v: %v", msg.FileInfo.FileName, err) |
|
||||
} else { |
|
||||
log.Printf("Merged file %v successfully", msg.FileInfo.FileName) |
|
||||
} |
|
||||
// 清理状态记录
|
|
||||
delete(uploadStatus.Status, msg.FileInfo.FileName) |
|
||||
msg.Content = "uploaded" |
|
||||
messageChan <- msg |
|
||||
}() |
|
||||
} |
|
||||
uploadStatus.Unlock() |
|
||||
|
|
||||
// 返回成功响应
|
|
||||
w.WriteHeader(http.StatusCreated) |
|
||||
fmt.Fprintln(w, "File part uploaded successfully") |
|
||||
} |
|
||||
|
|
||||
// SaveContentToFile 保存内容到文件并返回UploadInfo结构体
|
|
||||
func SaveContentToFile(content, fileName string) (UploadInfo, error) { |
|
||||
uploadBaseDir, err := GetChatPath() |
|
||||
if err != nil { |
|
||||
return UploadInfo{}, err |
|
||||
} |
|
||||
appDir, err := libs.GetOsDir() |
|
||||
if err != nil { |
|
||||
return UploadInfo{}, err |
|
||||
} |
|
||||
|
|
||||
// 去除文件名中的空格
|
|
||||
fileNameWithoutSpaces := strings.ReplaceAll(fileName, " ", "_") |
|
||||
fileNameWithoutSpaces = strings.ReplaceAll(fileNameWithoutSpaces, "/", "") |
|
||||
fileNameWithoutSpaces = strings.ReplaceAll(fileNameWithoutSpaces, `\`, "") |
|
||||
// 提取文件名和扩展名
|
|
||||
// 查找最后一个点的位置
|
|
||||
lastDotIndex := strings.LastIndexByte(fileNameWithoutSpaces, '.') |
|
||||
|
|
||||
// 如果找到点,则提取扩展名,否则视为没有扩展名
|
|
||||
ext := "" |
|
||||
if lastDotIndex != -1 { |
|
||||
ext = fileNameWithoutSpaces[lastDotIndex:] |
|
||||
fileNameWithoutSpaces = fileNameWithoutSpaces[:lastDotIndex] |
|
||||
} else { |
|
||||
ext = "" |
|
||||
} |
|
||||
randFileName := fmt.Sprintf("%s_%s%s", fileNameWithoutSpaces, strconv.FormatInt(time.Now().UnixNano(), 10), ext) |
|
||||
savePath := filepath.Join(uploadBaseDir, randFileName) |
|
||||
|
|
||||
if err := os.MkdirAll(filepath.Dir(savePath), 0755); err != nil { |
|
||||
return UploadInfo{}, err |
|
||||
} |
|
||||
|
|
||||
if err := os.WriteFile(savePath, []byte(content), 0644); err != nil { |
|
||||
return UploadInfo{}, err |
|
||||
} |
|
||||
content = string(content) |
|
||||
// 检查文件内容是否以"link::"开头
|
|
||||
if !strings.HasPrefix(content, "link::") { |
|
||||
content = base64.StdEncoding.EncodeToString([]byte(content)) |
|
||||
} |
|
||||
return UploadInfo{ |
|
||||
Name: fileNameWithoutSpaces, |
|
||||
SavePath: strings.TrimPrefix(savePath, appDir), |
|
||||
Content: content, |
|
||||
CreatedAt: time.Now(), |
|
||||
}, nil |
|
||||
} |
|
||||
|
|
||||
// MultiUploadHandler 处理多文件上传请求
|
|
||||
func MultiUploadHandler(w http.ResponseWriter, r *http.Request) { |
|
||||
if err := r.ParseMultipartForm(10000 << 20); err != nil { |
|
||||
http.Error(w, "Failed to parse multipart form", http.StatusBadRequest) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
files := r.MultipartForm.File["files"] |
|
||||
if len(files) == 0 { |
|
||||
http.Error(w, "No file parts in the request", http.StatusBadRequest) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
fileInfoList := make([]UploadInfo, 0, len(files)) |
|
||||
|
|
||||
for _, fileHeader := range files { |
|
||||
file, err := fileHeader.Open() |
|
||||
if err != nil { |
|
||||
http.Error(w, "Failed to open uploaded file", http.StatusBadRequest) |
|
||||
continue |
|
||||
} |
|
||||
defer file.Close() |
|
||||
|
|
||||
content, err := io.ReadAll(file) |
|
||||
if err != nil { |
|
||||
http.Error(w, "Failed to read uploaded file", http.StatusBadRequest) |
|
||||
continue |
|
||||
} |
|
||||
//log.Printf(string(content))
|
|
||||
// 保存上传的文件内容
|
|
||||
info, err := SaveContentToFile(string(content), fileHeader.Filename) |
|
||||
if err != nil { |
|
||||
http.Error(w, "Failed to save uploaded file", http.StatusBadRequest) |
|
||||
continue |
|
||||
} |
|
||||
log.Println(info.SavePath) |
|
||||
|
|
||||
//info.SavePath = savePath
|
|
||||
fileInfoList = append(fileInfoList, info) |
|
||||
} |
|
||||
user := UserInfo{ |
|
||||
IP: r.FormValue("ip"), |
|
||||
Hostname: r.FormValue("hostname"), |
|
||||
} |
|
||||
msg := Message{ |
|
||||
Type: "file", |
|
||||
Content: "file recieved", |
|
||||
SenderInfo: user, |
|
||||
FileList: fileInfoList, |
|
||||
} |
|
||||
messageChan <- msg |
|
||||
w.WriteHeader(http.StatusOK) |
|
||||
fmt.Fprintln(w, "File send successfully") |
|
||||
//serv.Res(serv.Response{Code: 0, Data: fileInfoList}, w)
|
|
||||
} |
|
Loading…
Reference in new issue