Go学习笔记(18)
经典项目-海量用户即时通讯系统
项目开发流程
需求分析--> 设计阶段---> 编码实现 --> 测试阶段-->实施
需求分析
- 用户注册
- 用户登录
- 显示在线用户列表
- 群聊(广播)
- 点对点聊天
- 离线留言
界面设计
项目开发前技术准备
项目要保存用户信息和消息数据,因此我们需要学习数据库(Redis 或者 Mysql) , 这里我们选择Redis , 所以先给同学们讲解如何在 Golang 中使用 Redis.
实现功能-显示客户端登录菜单
功能:能够正确的显示客户端的菜单。
界面:
思路分析:这个非常简单,直接写.
代码实现:
client/main.go
client/login.go
实现功能-完成用户登录
要求:先完成指定用户的验证,用户 id=100, 密码 pwd=123456 可以登录,其它用户不能登录
这里需要先说明一个 Message 的组成(示意图),并发送一个 Message 的流程
- 完成客户端可以发送消息长度,服务器端可以正常收到该长度值 分析思路
(1) 先确定消息 Message 的格式和结构
(2) 然后根据上图的分析完成代码
(3) 示意图
代码实现:
server/main.go
common/message/message.go
client/main.go
和前面的代码一样,没有修改
client/login.go
2.完成客户端可以发送消息本身,服务器端可以正常接收到消息,并根据客户 端发 送的消 息(LoginMes), 判断用户的合法性,并返回相应的 LoginResMes
思路分析:
(1) 让客户端发送消息本身
(2) 服务器端接受到消息, 然后反序列化成对应的消息结构体.
(3) 服务器端根据反序列化成对应的消息, 判断是否登录用户是合法, 返回 LoginResMes
(4) 客户端解析返回的 LoginResMes,显示对应界面
(5) 这里我们需要做函数的封装
代码实现:
client/login.go 做了修改
server/main.go 修改
将读取包的任务封装到了一个函数中.readPkg()
能够完成登录,并提示相应信息
server/main.go 修改
client/utils.go 新增
client/login.go 增加代码
程序结构的改进, 前面的程序虽然完成了功能,但是没有结构,系统的可读性、扩展性和维护性都不好,因此需要对程序的结构进行改进。
- 先改进服务端, 先画出程序的框架图[思路],再写代码.
- 步骤
[1] . 先把分析出来的文件,创建好,然后放到相应的文件夹[包]
[2] 现在根据各个文件,完成的任务不同,将 main.go 的代码剥离到对应的文件中即可。
[3] 先修改了 utils/utils.go
[4] 修改了 process2/userProcess.go
[5] 修改了 main/processor.go
[6] 修改 main/main.go
修改客户端, 先画出程序的框架图[思路],再写代码
[1] 步骤 1-画出示意图
[2] 先把各个文件放到对应的文件夹[包]
[3] 将 server/utils.go 拷贝到 client/utils/utils.go
[4] 创建了 server/process/userProcess.go
说明:该文件就是在原来的 login.go 做了一个改进,即封装到 UserProcess 结构体
[5] 创建了 server/process/server.go
[6] server/main/main.go 修改
在 Redis 手动添加测试用户,并画图+说明注意. (后面通过程序注册用户)
手动直接在 redis 增加一个用户信息:
如输入的用户名密码在 Redis 中存在则登录,否则退出系统,并给出相应的提示信息:
- 用户不存在,你也可以重新注册,再登录
- 你密码不正确。。
代码实现:
[1] 编写 model/user.go
[2] 编写 model/error.go
[3] 编写 model/userDao.go
package model
import (
"fmt"
"github.com/garyburd/redigo/redis"
"encoding/json"
)
//我们在服务器启动后,就初始化一个 userDao 实例,
//把它做成全局的变量,在需要和 redis 操作时,就直接使用即可
var (
MyUserDao *UserDao
)
//定义一个 UserDao 结构体体
//完成对 User 结构体的各种操作.
type UserDao struct {
pool *redis.Pool
}
//使用工厂模式,创建一个 UserDao 实例
func NewUserDao(pool *redis.Pool) (userDao *UserDao) {
userDao = &UserDao{
pool: pool,
}
return
}
//思考一下在 UserDao 应该提供哪些方法给我们
//1. 根据用户 id 返回 一个 User 实例+err
func (this *UserDao) getUserById(conn redis.Conn, id int) (user *User, err error) {
//通过给定 id 去 redis 查询这个用户
res, err := redis.String(conn.Do("HGet", "users", id))
if err != nil {
//错误!
if err == redis.ErrNil { //表示在 users 哈希中,没有找到对应 id
err = ERROR_USER_NOTEXISTS
}
return
}
user = &User{}
//这里我们需要把 res 反序列化成 User 实例
err = json.Unmarshal([]byte(res), user)
if err != nil {
fmt.Println("json.Unmarshal err=", err)
return
}
return
}
//完成登录的校验 Login
//1. Login 完成对用户的验证
//2. 如果用户的 id 和 pwd 都正确,则返回一个 user 实例
//3. 如果用户的 id 或 pwd 有错误,则返回对应的错误信息
func (this *UserDao) Login(userId int, userPwd string) (user *User, err error) {
//先从 UserDao 的连接池中取出一根连接
conn := this.pool.Get()
defer conn.Close()
user, err = this.getUserById(conn, userId)
if err != nil {
return
}
//这时证明这个用户是获取到.
if user.UserPwd != userPwd {
err = ERROR_USER_PWD
return
}
return
}
[4] main/redis.go
[5] main/main.go
[6] 在 process/userProcess.go 使用到 redis 验证的功能
实现功能-完成注册用户
- 完成注册功能,将用户信息录入到 Redis 中
- 思路分析,并完成代码
- 思路分析的示意图
实现功能-完成注册用户
[1] common/message/user.go
[2] common/message/message.go
[3] client/process/userProcess.go
func (this *UserProcess) Register(userId int,
userPwd string, userName string) (err error) {
//1. 链接到服务器
conn, err := net.Dial("tcp", "localhost:8889")
if err != nil {
fmt.Println("net.Dial err=", err)
return
}
//延时关闭
defer conn.Close()
//2. 准备通过 conn 发送消息给服务
var mes message.Message
mes.Type = message.RegisterMesType
//3. 创建一个 LoginMes 结构体
var registerMes message.RegisterMes
registerMes.User.UserId = userId
registerMes.User.UserPwd = userPwd
registerMes.User.UserName = userName
//4.将 registerMes 序列化
data, err := json.Marshal(registerMes)
if err != nil {
fmt.Println("json.Marshal err=", err)
return
}
// 5. 把 data 赋给 mes.Data 字段
mes.Data = string(data)
// 6. 将 mes 进行序列化化
data, err = json.Marshal(mes)
if err != nil {
fmt.Println("json.Marshal err=", err)
return
}
//创建一个 Transfer 实例
tf := &utils.Transfer{
Conn : conn,
}
//发送 data 给服务器端
err = tf.WritePkg(data)
if err != nil {
fmt.Println("注册发送信息错误 err=", err)
}
mes, err = tf.ReadPkg() // mes 就是 RegisterResMes
if err != nil {
fmt.Println("readPkg(conn) err=", err)
return
}
//将 mes 的 Data 部分反序列化成 RegisterResMes
var registerResMes message.RegisterResMes
err = json.Unmarshal([]byte(mes.Data), ®isterResMes)
if registerResMes.Code == 200 {
fmt.Println("注册成功, 你重新登录一把")
os.Exit(0)
} else {
fmt.Println(registerResMes.Error)
os.Exit(0)
}
return
}
[4] 在 client/main/main.go 增加了代码
[5] 在 server/model/userDao.go 增加方法
[6] 在 server/process/userProcess.go 增加了方法,处理注册
func (this *UserProcess) ServerProcessRegister(mes *message.Message) (err error) {
//1.先从 mes 中取出 mes.Data ,并直接反序列化成 RegisterMes
var registerMes message.RegisterMes
err = json.Unmarshal([]byte(mes.Data), ®isterMes)
if err != nil {
fmt.Println("json.Unmarshal fail err=", err)
return
}
//1 先声明一个 resMes
var resMes message.Message
resMes.Type = message.RegisterResMesType
var registerResMes message.RegisterResMes
//我们需要到 redis 数据库去完成注册.
//1.使用 model.MyUserDao 到 redis 去验证
err = model.MyUserDao.Register(®isterMes.User)
if err != nil {
if err == model.ERROR_USER_EXISTS {
registerResMes.Code = 505
registerResMes.Error = model.ERROR_USER_EXISTS.Error()
} else {
registerResMes.Code = 506
registerResMes.Error = "注册发生未知错误..."
}
} else {
registerResMes.Code = 200
}
data, err := json.Marshal(registerResMes)
if err != nil {
fmt.Println("json.Marshal fail", err)
return
}
//4. 将 data 赋值给 resMes
resMes.Data = string(data)
//5. 对 resMes 进行序列化,准备发送
data, err = json.Marshal(resMes)
if err != nil {
fmt.Println("json.Marshal fail", err)
return
}
//6. 发送 data, 我们将其封装到 writePkg 函数
//因为使用分层模式(mvc), 我们先创建一个 Transfer 实例,然后读取
tf := &utils.Transfer{
Conn : this.Conn,
}
err = tf.WritePkg(data)
return
}
[7] server/main/processor.go 调用了
实现功能-完成登录时能返回当前在线用户
用户登录后,可以得到当前在线用户列表思路分析、示意图、代码实现
思路分析:
代码实现:
[1] 编写了 server/process/userMgr.go
[2] server/process/userProcess.go
[3] common/message/message.go
[4] client/process/userProcess.go
当一个新的用户上线后,其它已经登录的用户也能获取最新在线用户列表,思路分析、示意图、代码实现
[1] server/process/userProcess.go
[2] sever/proces/userProcess.go [的 Login]
[3] common/mesage/message.go
[4] client/process/userMgr.go
[5] client/process/server.go
[6] client/process/server.go
实现功能-完成登录用可以群聊
步骤 1:步骤 1:当一个用户上线后,可以将群聊消息发给服务器,服务器可以接收到
思路分析:
代码实现:
[1] common/message/messag.go
[2] client/model/curUser.go
[3] client/process/smsProcess.go 增加了发送群聊消息
[4] 测试
步骤 2:服务器可以将接收到的消息,群发给所有在线用户(发送者除外)
思路分析:
代码实现:
[1] server/process/smsProcess.go
[2] server/main/processor.go
[3] client/process/smsMgr.go
[4] client/process/server.go
聊天的项目的扩展功能要求
- 实现私聊.[点对点聊天]
- 如果一个登录用户离线,就把这个人从在线列表去掉【】
- 实现离线留言,在群聊时,如果某个用户没有在线,当登录后,可以接受离线的消息
- 发送一个文件
评论