diff --git a/chat_project/README.md b/chat_project/README.md new file mode 100644 index 0000000..b0e5354 --- /dev/null +++ b/chat_project/README.md @@ -0,0 +1,39 @@ +# 简易聊天工具项目 + +## 项目结构 + +``` +chat_project/ +├── server/ +│ ├── main.go # 服务器入口 +│ ├── auth.go # 用户认证模块 +│ ├── message.go # 消息处理模块 +│ ├── file_transfer.go # 文件传输模块 +│ ├── db.go # 数据库操作模块 +├── client/ +│ ├── main.go # 客户端入口 +│ ├── auth.go # 客户端认证模块 +│ ├── message.go # 客户端消息处理 +│ ├── file_transfer.go # 客户端文件传输 +├── go.mod +└── go.sum +``` + +## 技术选型 + +- Go 1.24 +- TCP协议自定义消息格式 +- SQLite数据库用于聊天记录持久化 +- bcrypt加密用于密码存储 +- 命令行客户端实现 + +## 开发步骤 + +1. 搭建TCP服务器和客户端基础连接框架 +2. 实现用户身份认证 +3. 实现聊天消息传输 +4. 集成聊天记录持久化 +5. 实现文件和图片传输 +6. 完善客户端命令行交互 +7. 测试与优化 +8. 编写文档与交付 \ No newline at end of file diff --git a/chat_project/auth.go b/chat_project/auth.go new file mode 100644 index 0000000..1975e25 --- /dev/null +++ b/chat_project/auth.go @@ -0,0 +1,45 @@ +package main + +import ( + "errors" + + "golang.org/x/crypto/bcrypt" +) + +// 用户结构体 +type User struct { + Username string + PasswordHash []byte +} + +// 模拟用户存储(实际应存入数据库) +var users = map[string]User{} + +// 注册用户 +func RegisterUser(username, password string) error { + if _, exists := users[username]; exists { + return errors.New("用户已存在") + } + hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return err + } + users[username] = User{ + Username: username, + PasswordHash: hash, + } + return nil +} + +// 用户登录验证 +func AuthenticateUser(username, password string) error { + user, exists := users[username] + if !exists { + return errors.New("用户不存在") + } + err := bcrypt.CompareHashAndPassword(user.PasswordHash, []byte(password)) + if err != nil { + return errors.New("密码错误") + } + return nil +} diff --git a/chat_project/client/client b/chat_project/client/client new file mode 100644 index 0000000..48800c3 Binary files /dev/null and b/chat_project/client/client differ diff --git a/chat_project/client/file_transfer.go b/chat_project/client/file_transfer.go new file mode 100644 index 0000000..469ac8f --- /dev/null +++ b/chat_project/client/file_transfer.go @@ -0,0 +1,59 @@ +package main + +import ( + "fmt" + "io" + "net" + "os" +) + +// 客户端接收文件的示例函数 +func ReceiveFile(conn net.Conn, filename string, filesize int64) error { + file, err := os.Create(filename) + if err != nil { + return err + } + defer file.Close() + + var received int64 = 0 + buf := make([]byte, 4096) + for received < filesize { + n, err := conn.Read(buf) + if err != nil { + if err == io.EOF { + break + } + return err + } + file.Write(buf[:n]) + received += int64(n) + } + fmt.Printf("文件 %s 接收完成,共 %d 字节\n", filename, received) + return nil +} + +// 客户端发送文件的示例函数 +func SendFile(conn net.Conn, filepath string) error { + file, err := os.Open(filepath) + if err != nil { + return err + } + defer file.Close() + + buf := make([]byte, 4096) + for { + n, err := file.Read(buf) + if err != nil { + if err == io.EOF { + break + } + return err + } + _, err = conn.Write(buf[:n]) + if err != nil { + return err + } + } + fmt.Printf("文件 %s 发送完成\n", filepath) + return nil +} diff --git a/chat_project/client/main.go b/chat_project/client/main.go new file mode 100644 index 0000000..8f67052 --- /dev/null +++ b/chat_project/client/main.go @@ -0,0 +1,50 @@ +package main + +import ( + "bufio" + "fmt" + "log" + "net" + "os" +) + +func main() { + conn, err := net.Dial("tcp", "127.0.0.1:8888") + if err != nil { + log.Fatalf("无法连接服务器: %v", err) + } + defer conn.Close() + fmt.Println("已连接到服务器 127.0.0.1:8888") + + // 启动协程接收服务器消息 + go func() { + buf := make([]byte, 1024) + for { + n, err := conn.Read(buf) + if err != nil { + log.Printf("读取服务器消息错误: %v", err) + os.Exit(0) + } + fmt.Printf("服务器消息: %s\n", string(buf[:n])) + } + }() + + // 从命令行读取输入并发送给服务器 + scanner := bufio.NewScanner(os.Stdin) + for { + fmt.Print("请输入消息: ") + if !scanner.Scan() { + break + } + text := scanner.Text() + if text == "exit" { + fmt.Println("退出客户端") + break + } + _, err := conn.Write([]byte(text)) + if err != nil { + log.Printf("发送消息失败: %v", err) + break + } + } +} diff --git a/chat_project/go.mod b/chat_project/go.mod new file mode 100644 index 0000000..dc045df --- /dev/null +++ b/chat_project/go.mod @@ -0,0 +1,8 @@ +module chatv1 + +go 1.24.0 + +require ( + github.com/mattn/go-sqlite3 v1.14.28 // indirect + golang.org/x/crypto v0.38.0 // indirect +) diff --git a/chat_project/go.sum b/chat_project/go.sum new file mode 100644 index 0000000..3014632 --- /dev/null +++ b/chat_project/go.sum @@ -0,0 +1,4 @@ +github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= +github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= diff --git a/chat_project/server/db.go b/chat_project/server/db.go new file mode 100644 index 0000000..b30a68b --- /dev/null +++ b/chat_project/server/db.go @@ -0,0 +1,72 @@ +package main + +import ( + "database/sql" + "log" + "time" + + _ "github.com/mattn/go-sqlite3" +) + +var db *sql.DB + +func InitDB() { + var err error + db, err = sql.Open("sqlite3", "./chat.db") + if err != nil { + log.Fatalf("打开数据库失败: %v", err) + } + + createTableSQL := ` + CREATE TABLE IF NOT EXISTS messages ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + sender TEXT NOT NULL, + receiver TEXT NOT NULL, + content TEXT NOT NULL, + timestamp DATETIME NOT NULL + ); + ` + _, err = db.Exec(createTableSQL) + if err != nil { + log.Fatalf("创建表失败: %v", err) + } +} + +func SaveMessage(sender, receiver, content string) error { + stmt, err := db.Prepare("INSERT INTO messages(sender, receiver, content, timestamp) VALUES (?, ?, ?, ?)") + if err != nil { + return err + } + defer stmt.Close() + + _, err = stmt.Exec(sender, receiver, content, time.Now()) + return err +} + +func GetMessages(sender, receiver string) ([]Message, error) { + rows, err := db.Query("SELECT sender, receiver, content, timestamp FROM messages WHERE (sender = ? AND receiver = ?) OR (sender = ? AND receiver = ?) ORDER BY timestamp ASC", sender, receiver, receiver, sender) + if err != nil { + return nil, err + } + defer rows.Close() + + var messages []Message + for rows.Next() { + var msg Message + var ts string + err = rows.Scan(&msg.Sender, &msg.Receiver, &msg.Content, &ts) + if err != nil { + return nil, err + } + msg.Timestamp, _ = time.Parse(time.RFC3339, ts) + messages = append(messages, msg) + } + return messages, nil +} + +type Message struct { + Sender string + Receiver string + Content string + Timestamp time.Time +} diff --git a/chat_project/server/file_transfer.go b/chat_project/server/file_transfer.go new file mode 100644 index 0000000..00c89ec --- /dev/null +++ b/chat_project/server/file_transfer.go @@ -0,0 +1,59 @@ +package main + +import ( + "fmt" + "io" + "net" + "os" +) + +// 服务器端接收文件的示例函数 +func ReceiveFile(conn net.Conn, filename string, filesize int64) error { + file, err := os.Create(filename) + if err != nil { + return err + } + defer file.Close() + + var received int64 = 0 + buf := make([]byte, 4096) + for received < filesize { + n, err := conn.Read(buf) + if err != nil { + if err == io.EOF { + break + } + return err + } + file.Write(buf[:n]) + received += int64(n) + } + fmt.Printf("文件 %s 接收完成,共 %d 字节\n", filename, received) + return nil +} + +// 服务器端发送文件的示例函数 +func SendFile(conn net.Conn, filepath string) error { + file, err := os.Open(filepath) + if err != nil { + return err + } + defer file.Close() + + buf := make([]byte, 4096) + for { + n, err := file.Read(buf) + if err != nil { + if err == io.EOF { + break + } + return err + } + _, err = conn.Write(buf[:n]) + if err != nil { + return err + } + } + fmt.Printf("文件 %s 发送完成\n", filepath) + return nil +} diff --git a/chat_project/server/main.go b/chat_project/server/main.go new file mode 100644 index 0000000..3bc6d0d --- /dev/null +++ b/chat_project/server/main.go @@ -0,0 +1,75 @@ +package main + +import ( + "bufio" + "fmt" + "log" + "net" + "sync" +) + +var ( + clients = make(map[net.Conn]string) // 连接映射:连接对象到用户名 + clientsMu sync.Mutex // 保护clients的互斥锁 +) + +func main() { + listener, err := net.Listen("tcp", ":8888") + if err != nil { + log.Fatalf("监听端口失败: %v", err) + } + defer listener.Close() + fmt.Println("服务器已启动,监听端口 8888") + + for { + conn, err := listener.Accept() + if err != nil { + log.Printf("接受连接失败: %v", err) + continue + } + go handleConnection(conn) + } +} + +func handleConnection(conn net.Conn) { + defer func() { + // 连接关闭时,移除客户端 + clientsMu.Lock() + delete(clients, conn) + clientsMu.Unlock() + conn.Close() + broadcast(fmt.Sprintf("用户退出:%s", conn.RemoteAddr().String()), "系统") + fmt.Printf("客户端断开:%s\n", conn.RemoteAddr().String()) + }() + + // 添加新连接 + clientsMu.Lock() + clients[conn] = conn.RemoteAddr().String() + clientsMu.Unlock() + + // 通知所有客户端有新用户加入 + broadcast(fmt.Sprintf("新用户加入:%s", conn.RemoteAddr().String()), "系统") + + scanner := bufio.NewScanner(conn) + for scanner.Scan() { + msg := scanner.Text() + sender := clients[conn] + broadcast(msg, sender) + } + if err := scanner.Err(); err != nil { + log.Printf("读取消息错误: %v", err) + } +} + +func broadcast(message, sender string) { + clientsMu.Lock() + defer clientsMu.Unlock() + for c := range clients { + if c != nil { + _, err := fmt.Fprintf(c, "[%s] %s\n", sender, message) + if err != nil { + log.Printf("广播失败: %v", err) + } + } + } +} diff --git a/chat_project/server/server b/chat_project/server/server new file mode 100644 index 0000000..a7e0902 Binary files /dev/null and b/chat_project/server/server differ