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 |
|||
|
|||
import ( |
|||
"encoding/json" |
|||
"fmt" |
|||
"godo/libs" |
|||
"io" |
|||
"log" |
|||
"net" |
|||
"net/http" |
|||
"os" |
|||
"path/filepath" |
|||
"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() |
|||
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 |
|||
} |
|||
} |
|||
modelDir := filepath.Join(baseDir, "C", "Users", "Reciv", time.Now().Format("2006-01-02")) |
|||
if !libs.PathExists(modelDir) { |
|||
os.MkdirAll(modelDir, 0755) |
|||
|
|||
// 确定文件路径
|
|||
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) |
|||
} |
|||
func calculateChecksum(data []byte) uint32 { |
|||
checksum := uint32(0) |
|||
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