添加聊天工具项目的基本结构和功能实现,包括用户认证、消息处理、文件传输及数据库操作

This commit is contained in:
deastern 2025-06-02 19:15:41 +08:00
parent 64686a2de0
commit f74b59749d
11 changed files with 411 additions and 0 deletions

39
chat_project/README.md Normal file
View 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
View 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

Binary file not shown.

View 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
}

View 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
View 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
View 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
View 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
}

View 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
}

View 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

Binary file not shown.