diff --git a/frontend/components.d.ts b/frontend/components.d.ts
index 8b1a284..fd69015 100644
--- a/frontend/components.d.ts
+++ b/frontend/components.d.ts
@@ -40,8 +40,10 @@ declare module 'vue' {
DialogTemp: typeof import('./src/components/window/DialogTemp.vue')['default']
EditFileName: typeof import('./src/components/builtin/EditFileName.vue')['default']
EditType: typeof import('./src/components/builtin/EditType.vue')['default']
+ ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElBadge: typeof import('element-plus/es')['ElBadge']
ElButton: typeof import('element-plus/es')['ElButton']
+ ElCard: typeof import('element-plus/es')['ElCard']
ElCarousel: typeof import('element-plus/es')['ElCarousel']
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
diff --git a/frontend/src/components/builtin/FileList.vue b/frontend/src/components/builtin/FileList.vue
index c0a5a6e..db34348 100644
--- a/frontend/src/components/builtin/FileList.vue
+++ b/frontend/src/components/builtin/FileList.vue
@@ -124,15 +124,8 @@ function getName(item: any) {
}
}
function handleOnOpen(item: OsFileWithoutContent) {
- chosenIndexs.value = [];
- if (choose.ifShow && !item.isDirectory) {
- choose.path.push(item.path)
- choose.close()
- } else {
props.onOpen(item);
emitEvent('desktop.app.open');
- }
-
}
function hadnleDrop(mouse: DragEvent, path: string) {
hoverIndex.value = -1;
@@ -325,22 +318,10 @@ function handleRightClick(mouse: MouseEvent, item: OsFileWithoutContent, index:
}
if (choose.ifShow) {
menuArr.push({
- label: t('selected'),
+ label: "选中发送",
click: () => {
- const paths: any = []
- chosenIndexs.value.forEach((index) => {
- const item = props.fileList[index];
- if (!item.isDirectory) {
- paths.push(item.path)
- }
- })
- if (paths.length > 0) {
- choose.path = paths
- choose.close()
- }
- chosenIndexs.value = [];
-
-
+ choose.path = item.path
+ choose.close()
},
})
}
diff --git a/frontend/src/components/desktop/LockDesktop.vue b/frontend/src/components/desktop/LockDesktop.vue
index a9c74a3..7a4e49b 100644
--- a/frontend/src/components/desktop/LockDesktop.vue
+++ b/frontend/src/components/desktop/LockDesktop.vue
@@ -1,12 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 登录
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 注册
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ alertMsg }}
-
-
-
-
+
\ No newline at end of file
diff --git a/frontend/src/components/localchat/Chat.vue b/frontend/src/components/localchat/Chat.vue
index ef2eea1..49375af 100644
--- a/frontend/src/components/localchat/Chat.vue
+++ b/frontend/src/components/localchat/Chat.vue
@@ -13,59 +13,59 @@
diff --git a/frontend/src/components/taskbar/StartOption.vue b/frontend/src/components/taskbar/StartOption.vue
index 02698eb..d7b218c 100644
--- a/frontend/src/components/taskbar/StartOption.vue
+++ b/frontend/src/components/taskbar/StartOption.vue
@@ -28,12 +28,14 @@
import { emitEvent } from "@/system/event";
import { useSystem, Dialog, t, Menu, BrowserWindow } from "@/system";
import { vGlowing } from "@/util/glowingBorder";
+import { fetchGet } from "@/system/config";
+import { RestartApp } from "@/util/goutil";
const sys = useSystem();
function handleClick(key: number, ev: MouseEvent) {
switch (key) {
case 0:
- Menu.buildFromTemplate([
+ const actions = [
{
label: t("startMenu.shutdown"),
click: () => {
@@ -55,7 +57,19 @@ function handleClick(key: number, ev: MouseEvent) {
});
},
},
- ]).popup(ev);
+ ]
+ if(sys.getConfig('userType') == 'member'){
+ actions.push({
+ label: '退出',
+ click: () => {
+ const userInfo:any = sys.getConfig('userInfo')
+ fetchGet(userInfo.url + "/member/loginout").then(() => {
+ RestartApp()
+ })
+ },
+ })
+ }
+ Menu.buildFromTemplate(actions).popup(ev);
break;
case 1: {
diff --git a/frontend/src/stores/choose.ts b/frontend/src/stores/choose.ts
index 81284ad..44d0a19 100644
--- a/frontend/src/stores/choose.ts
+++ b/frontend/src/stores/choose.ts
@@ -3,7 +3,7 @@ import { BrowserWindow } from "@/system";
import { ref } from 'vue';
export const useChooseStore = defineStore('chooseStore', () => {
const win:any = ref()
- const path:any = ref([])
+ const path:any = ref("")
const ifShow = ref(false)
const select = (title = '选择文件', fileExt:any) => {
win.value = new BrowserWindow({
diff --git a/frontend/src/stores/localchat.ts b/frontend/src/stores/localchat.ts
index 5001bda..93b6ac5 100644
--- a/frontend/src/stores/localchat.ts
+++ b/frontend/src/stores/localchat.ts
@@ -27,6 +27,9 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
const chatTargetId = ref(0)
const chatTargetIp = ref("")
const showAddUser = ref(false)
+ const handlerMessage = (message : any) => {
+ console.log(message)
+ }
const handleSelect = (key: number) => {
navId.value = key;
};
@@ -344,10 +347,15 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
const targetUser = userList.value.find((d: any) => d.ip === chatTargetIp.value)
//console.log(targetUser)
if (targetUser.isOnline) {
- const postUrl = `http://${targetUser.ip}:56780/localchat/message`
+ const postUrl = `${config.apiUrl}/localchat/message`
+ const messages = {
+ type: 'text',
+ message: saveMsg.content,
+ ip: saveMsg.targetIp
+ }
const completion = await fetch(postUrl, {
method: "POST",
- body: JSON.stringify(saveMsg),
+ body: JSON.stringify(messages),
})
if (!completion.ok) {
console.log(completion)
@@ -507,7 +515,8 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
moreMsgList,
refreshUserList,
clearMsg,
- addUser
+ addUser,
+ handlerMessage
}
}, {
persist: {
diff --git a/frontend/src/stores/upgrade.ts b/frontend/src/stores/upgrade.ts
index 59d8811..0bc571a 100644
--- a/frontend/src/stores/upgrade.ts
+++ b/frontend/src/stores/upgrade.ts
@@ -4,6 +4,7 @@ import { getSystemKey, setSystemKey, parseJson, getSystemConfig } from '@/system
import { RestartApp } from '@/util/goutil';
import { ElMessage } from 'element-plus'
import { t } from '@/i18n';
+import { useLocalChatStore } from "./localchat";
export const useUpgradeStore = defineStore('upgradeStore', () => {
const hasUpgrade = ref(false);
const hasNotice = ref(false);
@@ -15,6 +16,7 @@ export const useUpgradeStore = defineStore('upgradeStore', () => {
const progress = ref(0)
const noticeList:any = ref([])
const adList:any = ref([])
+ const localChatStore = useLocalChatStore()
function compareVersions(version1:string, version2:string) {
// 将版本号字符串按"."分割成数组
const parts1 = version1.split('.').map(Number);
@@ -53,6 +55,9 @@ export const useUpgradeStore = defineStore('upgradeStore', () => {
case 'update':
checkUpdate(message.data.data)
break;
+ case 'localchat':
+ localChatStore.handlerMessage(message)
+ break;
default:
console.warn('Unknown message type:', message.type);
}
diff --git a/godo/cmd/main.go b/godo/cmd/main.go
index a3b1171..ed44c3a 100644
--- a/godo/cmd/main.go
+++ b/godo/cmd/main.go
@@ -79,10 +79,10 @@ func OsStart() {
fileRouter.HandleFunc("/watch", files.WatchHandler).Methods(http.MethodGet)
localchatRouter := router.PathPrefix("/localchat").Subrouter()
- localchatRouter.HandleFunc("/sse", localchat.SseHandler).Methods(http.MethodGet)
+ // localchatRouter.HandleFunc("/sse", localchat.SseHandler).Methods(http.MethodGet)
localchatRouter.HandleFunc("/message", localchat.HandleMessage).Methods(http.MethodPost)
- localchatRouter.HandleFunc("/upload", localchat.MultiUploadHandler).Methods(http.MethodPost)
- localchatRouter.HandleFunc("/check", localchat.CheckUserHanlder).Methods(http.MethodGet)
+ // localchatRouter.HandleFunc("/upload", localchat.MultiUploadHandler).Methods(http.MethodPost)
+ // localchatRouter.HandleFunc("/check", localchat.CheckUserHanlder).Methods(http.MethodGet)
// 注册 WebDAV 路由
webdavRouter := router.PathPrefix("/webdav").Subrouter()
diff --git a/godo/go.mod b/godo/go.mod
index 96f5a2b..75cbf11 100644
--- a/godo/go.mod
+++ b/godo/go.mod
@@ -8,6 +8,7 @@ require (
github.com/gorilla/mux v1.8.1
github.com/minio/selfupdate v0.6.0
github.com/shirou/gopsutil v3.21.11+incompatible
+ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
)
require (
diff --git a/godo/go.sum b/godo/go.sum
index be86de1..11d6708 100644
--- a/godo/go.sum
+++ b/godo/go.sum
@@ -29,6 +29,7 @@ golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWP
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b h1:QAqMVf3pSa6eeTsuklijukjXBlj7Es2QQplab+/RbQ4=
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
diff --git a/godo/localchat/client.go b/godo/localchat/client.go
deleted file mode 100644
index 380adcb..0000000
--- a/godo/localchat/client.go
+++ /dev/null
@@ -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
-}
diff --git a/godo/localchat/file.go b/godo/localchat/file.go
index 2c4b8b9..2adaf03 100644
--- a/godo/localchat/file.go
+++ b/godo/localchat/file.go
@@ -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
}
diff --git a/godo/localchat/info.go b/godo/localchat/info.go
new file mode 100644
index 0000000..29bb6d6
--- /dev/null
+++ b/godo/localchat/info.go
@@ -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
+}
diff --git a/godo/localchat/server.go b/godo/localchat/server.go
deleted file mode 100644
index 5e9cc33..0000000
--- a/godo/localchat/server.go
+++ /dev/null
@@ -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
- }
-}
diff --git a/godo/localchat/sse.go b/godo/localchat/sse.go
deleted file mode 100644
index bf00a66..0000000
--- a/godo/localchat/sse.go
+++ /dev/null
@@ -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)
-}
diff --git a/godo/localchat/type.go b/godo/localchat/type.go
deleted file mode 100644
index d50e0b6..0000000
--- a/godo/localchat/type.go
+++ /dev/null
@@ -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为主机名
diff --git a/godo/localchat/udp.go b/godo/localchat/udp.go
new file mode 100644
index 0000000..75687aa
--- /dev/null
+++ b/godo/localchat/udp.go
@@ -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")
+}
diff --git a/godo/localchat/upload.go b/godo/localchat/upload.go
deleted file mode 100644
index 9ac4e56..0000000
--- a/godo/localchat/upload.go
+++ /dev/null
@@ -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)
-}
diff --git a/godo/sys/msg.go b/godo/sys/msg.go
index 28d8a66..9daabfe 100644
--- a/godo/sys/msg.go
+++ b/godo/sys/msg.go
@@ -3,6 +3,7 @@ package sys
import (
"encoding/json"
"fmt"
+ "godo/localchat"
"net/http"
"sync"
"time"
@@ -52,12 +53,20 @@ func HandleSystemEvents(w http.ResponseWriter, r *http.Request) {
mutex.Unlock()
}()
- // 读取客户端请求直到关闭
- for {
- select {
- case <-r.Context().Done():
+ // 使用定时器轮询客户端请求
+ ticker := time.NewTicker(1 * time.Second)
+ defer ticker.Stop()
+ for range ticker.C {
+ // 检查客户端是否已断开连接
+ if r.Context().Err() != nil {
return
}
+ onlineUsers := localchat.GetOnlineUsers()
+ msg := Message{
+ Type: "localchat",
+ Data: onlineUsers,
+ }
+ Broadcast(msg)
}
}