添加聊天工具项目的基本结构和功能实现,包括用户认证、消息处理、文件传输及数据库操作
This commit is contained in:
parent
64686a2de0
commit
f74b59749d
39
chat_project/README.md
Normal file
39
chat_project/README.md
Normal file
@ -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. 编写文档与交付
|
45
chat_project/auth.go
Normal file
45
chat_project/auth.go
Normal file
@ -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
|
||||
}
|
BIN
chat_project/client/client
Normal file
BIN
chat_project/client/client
Normal file
Binary file not shown.
59
chat_project/client/file_transfer.go
Normal file
59
chat_project/client/file_transfer.go
Normal file
@ -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
|
||||
}
|
50
chat_project/client/main.go
Normal file
50
chat_project/client/main.go
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
}
|
8
chat_project/go.mod
Normal file
8
chat_project/go.mod
Normal file
@ -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
|
||||
)
|
4
chat_project/go.sum
Normal file
4
chat_project/go.sum
Normal file
@ -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=
|
72
chat_project/server/db.go
Normal file
72
chat_project/server/db.go
Normal file
@ -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
|
||||
}
|
59
chat_project/server/file_transfer.go
Normal file
59
chat_project/server/file_transfer.go
Normal file
@ -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
|
||||
}
|
75
chat_project/server/main.go
Normal file
75
chat_project/server/main.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
BIN
chat_project/server/server
Normal file
BIN
chat_project/server/server
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user