自学内容网 自学内容网

go聊天系统项目-2 redis 验证用户id和密码

一、前言

敬告:本文不讲解代码,只是把代码展示出来。
该代码之前的代码见
go 聊天系统项目-1
注意:go 聊天系统项目-1 未使用 go mod 管理代码,本文改成使用 go mod 管理代码。详情见go 包相关知识

二、redis 端操作

1、安装redis
2、手动在redis添加数据

127.0.0.1:6379> hset user 100 "{\"UserId\":100,\"UserPwd\":\"123456\",\"UserName\":\"scott\"}"

三、使用mod管理项目

go mod init redis
go get github.com/gomodule/redigo/redis

四、代码

4.1、代码目录结构

pwd
/Users/Go-project/src
tree
.
├── client
├── day8
│   └── chatroom
│       ├── client
│       │   ├── main
│       │   │   └── main.go
│       │   ├── model
│       │   ├── process
│       │   │   ├── server.go
│       │   │   ├── smsProcess.go
│       │   │   └── userProcess.go
│       │   └── utils
│       │       └── utils.go
│       ├── common
│       │   └── message
│       │       └── message.go
│       └── server
│           ├── model
│           │   ├── error.go
│           │   ├── user.go
│           │   └── userDao.go
│           ├── process
│           │   ├── smsProcess.go
│           │   └── userProcess.go
│           ├── processor
│           │   ├── processor.go
│           │   └── redis.go
│           └── utils
│               └── utils.go
├── go.mod
├── go.sum
├── main
└── main.go

15 directories, 19 files

4.2、代码

4.2.1、服务端代码
4.2.1.1、day8/chatroom/server/model/error.go
package model

import (
"errors"
)

// 根据业务逻辑的需要自定义一些错误.
var (
ERROR_USER_NOTEXISTS = errors.New("用户不存在...")
ERROR_USER_EXISTS    = errors.New("用户已经存在...")
ERROR_USER_PWD       = errors.New("密码不正确")
)
4.2.1.2、day8/chatroom/server/model/user.go
package model

//先定义一个用户的结构体

type User struct {
UserId   int    `json:"userId"`
UserPwd  string `json:"userPwd"`
UserName string `json:"userName"`
}
4.2.1.3、day8/chatroom/server/model/userDao.go
package model

import (
"encoding/json"
"fmt"
"github.com/gomodule/redigo/redis"
//"github.com/garyburd/redisgo/redis"
//"go"
//"github.com/go-redis/redis/v8"
)

// 我们在服务器启动后,就初始化一个UserDao实例,
// 把它做成全局的变量,在需要和redis操作时,就直接使用即可
var (
MyUserDao *UserDao
)

// 定义一个UserDaoo 结构体
// 完成对 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", "user", 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
}

// 完成登录的校验
// 1.Login 完成对用户的验证
// 2.如果用户的id和密码都正确,则返回一个user实例
// 3.如果用户的id和密码有错误,则返回对应的错误信息
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.2.1.4、day8/chatroom/server/process/smsProcess.go
package process2
4.2.1.5、day8/chatroom/server/process/userProcess.go
package process2

import (
"encoding/json"
"fmt"
"net"
"redis/day8/chatroom/common/message"
"redis/day8/chatroom/server/model"
"redis/day8/chatroom/server/utils"
)

type UserProcess struct {
//
Conn net.Conn
}

func (this *UserProcess) ServerProcessLogin(mes *message.Message) (err error) {
//核心代码
//先从mes 中取出 mes.Data,并直接反序列化成 LoginMes
var loginMes message.LoginMes
err = json.Unmarshal([]byte(mes.Data), &loginMes)
if err != nil {
fmt.Println("json.Unmarshal fail err=", err)
return
}

// 先声明一个 resMes
var resMes message.Message
resMes.Type = message.LoginResMesType

//再声明一个 LoginResMes
var loginResMes message.LoginResMes
//我们需要到redis数据库去完成验证
//1.使用model.MyUserDao 到redis 去验证
user, err := model.MyUserDao.Login(loginMes.UserId, loginMes.UserPwd)
if err != nil {
loginResMes.Code = 500
loginResMes.Error = "该用户不存在,请注册使用..."
//这里我们先测试成功,然后我们在可以根据返回具体错误信息

} else {
loginResMes.Code = 200
fmt.Println(user, "登录成功")

}
//如果用户id=100,密码=123456,认为合法,否则不合法
// if loginMes.UserId == 100 && loginMes.UserPwd == "123456" {
// //合法
// loginResMes.Code = 200
// } else {
// //不合法
// loginResMes.Code = 500
// loginResMes.Error = "该用户不存在,请注册再使用..."
// }
//将 loginResMes 序列化
data, err := json.Marshal(loginResMes)
if err != nil {
fmt.Println("json.Marshal fail", err)
return
}
//将data赋值给resMes
resMes.Data = string(data)

//对 resMes 进行序列化,准备发送
data, err = json.Marshal(resMes)
if err != nil {
fmt.Println("json.Marshal fail", err)
return
}
//发送 data,将其封装到函数中
//因为使用分层模式(mvc),我们先创建一个 Transfer 实例,然后读取
tf := &utils.Transfer{
Conn: this.Conn,
}
err = tf.WritePkg(data)
return
}
4.2.1.6、day8/chatroom/server/processor/processor.go
package processor

import (
"fmt"
"io"
"net"
"redis/day8/chatroom/common/message"
"redis/day8/chatroom/server/process"
"redis/day8/chatroom/server/utils"
)

// 先创建一个processor的结构体
type Processor struct {
Conn net.Conn
}

func (this *Processor) serverProcessMes(mes *message.Message) (err error) {
switch mes.Type {
case message.LoginMesType:
//处理登录
up := &process2.UserProcess{
Conn: this.Conn,
}
err = up.ServerProcessLogin(mes)
case message.RegisterMesType:
//处理注册
default:
fmt.Printf("消息类型不存在,无法处理")
}
return
}

func (this *Processor) Process2() (err error) {
//循环客户端发送信息
for {
//创建一个 Transfer 实例完成读包的任务
tf := &utils.Transfer{
Conn: this.Conn,
}
mes, err := tf.ReadPkg()

if err != nil {
if err == io.EOF {
fmt.Println("客户端退出,服务器端也退出..")
return err
} else {
fmt.Println("readPkg err=", err)
return err
}
}
//fmt.Println("mes=", mes)
err = this.serverProcessMes(&mes)
if err != nil {
return err
}
}
}
4.2.1.7、day8/chatroom/server/processor/redis.go
package processor

import (
//"github.com/garyburd/redisgo/redis"
//"github.com/go-redis/redis/v8"
"github.com/gomodule/redigo/redis"
"time"
)

// 定义一个全局的pool
var Pool *redis.Pool

func InitPool(address string, maxIdle, maxActive int, idleTimeout time.Duration) {
Pool = &redis.Pool{
MaxIdle:     maxIdle,     //最大空闲连接数
MaxActive:   maxActive,   //表示和数据库的最大连接数,0 表示没有限制
IdleTimeout: idleTimeout, //最大空闲时间
Dial: func() (redis.Conn, error) { //初始化连接的代码,连接哪个ip的redis
return redis.Dial("tcp", address)
},
}
}
4.2.1.8、day8/chatroom/server/utils/utils.go
package utils

import (
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"net"
"redis/day8/chatroom/common/message"
)

// 这里将这些方法关联到结构体中
type Transfer struct {
//分析它应该有哪些字段
Conn net.Conn
Buf  [8096]byte // 这是传输时,使用缓冲
}

func (this *Transfer) ReadPkg() (mes message.Message, err error) {
//buf := make([]byte, 8096)
// conn.Read 在 conn 没有被关闭的情况下,才会阻塞
//如果客户端关闭了conn 就不会阻塞
_, err = this.Conn.Read(this.Buf[:4])
if err != nil {
//err = errors.New("read pkg header error")
return
}
//根据读到的  buf[:4] 转成一个 unit32 类型
var pkgLen uint32
pkgLen = binary.BigEndian.Uint32(this.Buf[0:4])
n, err := this.Conn.Read(this.Buf[:pkgLen])
if n != int(pkgLen) || err != nil {
//err = errors.New("read pkg body error")
return
}
//把 pkgLen 反序列化成  -> message.Message
err = json.Unmarshal(this.Buf[:pkgLen], &mes)
if err != nil {
err = errors.New("json.Unmarshal error")
return
}
return

}

func (this *Transfer) WritePkg(data []byte) (err error) {
//先发送一个长度给对方
//data是 我们要发送的消息,先发送 data 长度
//由于 conn 接口的 Write 方法参数要求是 bytes 切片
var pkgLen uint32
pkgLen = uint32(len(data))
//var buf [4]byte
binary.BigEndian.PutUint32(this.Buf[0:4], pkgLen)
//发送长度
n, err := this.Conn.Write(this.Buf[:4])
if n != 4 || err != nil {
fmt.Println("conn.Write(bytes) fail", err)
return
}
//发送 data本身
n, err = this.Conn.Write(data)
if n != int(pkgLen) || err != nil {
fmt.Println("conn.Write(bytes) fail", err)
return
}
return
}
4.2.2、common 代码
4.2.2.1、day8/chatroom/common/message/message.go
package message

const (
LoginMesType    = "LoginMes"
LoginResMesType = "LoginResMes"
RegisterMesType = "RegisterMes"
)

type Message struct {
Type string `josn: "type"`
Data string `json: "Data"`
}
type LoginMes struct {
UserId   int    `json: "userId"`
UserPwd  string `json: "userPwd"`
UserName string `json: "userName"`
}
type LoginResMes struct {
Code  int    `json: "code"`
Error string `json: "error"`
}

type RegisterMes struct{
//
}
4.2.3、客户端代码
4.2.3.1、day8/chatroom/client/main/main.go
package main

import (
"fmt"
"redis/day8/chatroom/client/process"
)

var userId int
var userPwd string

func main() {
var key int
var loop = true
for loop {
fmt.Println("---------------欢迎登录多人聊天系统-------------------")
fmt.Println("\t\t\t 1、登录聊天室")
fmt.Println("\t\t\t 2、注册用户")
fmt.Println("\t\t\t 3、退出系统")
fmt.Println("\t\t\t 请选择(1-3):")

fmt.Scanf("%d\n", &key)
switch key {
case 1:
fmt.Println("登录聊天室")
fmt.Println("请输入用户的id")
fmt.Scanf("%d\n", &userId)
fmt.Println("请输入用户密码")
fmt.Scanf("%s\n", &userPwd)
//完成登录
//1.创建一个UserProcess的实例
up := &process.UserProcess{}
up.Login(userId, userPwd)

//loop = false
case 2:
fmt.Println("注册用户")
loop = false
case 3:
fmt.Println("退出系统")
loop = false
default:
fmt.Println("你的输入有误,请重新输入")
}
}
}
4.2.3.2、day8/chatroom/client/process/server.go
package process

import (
"fmt"
"net"
"os"
"redis/day8/chatroom/client/utils"
)

func ShowMenu() {
fmt.Println("----------恭喜xxx登录成功--------")
fmt.Println("--------1、显示在线用户列表--------")
fmt.Println("--------2、发送消息--------")
fmt.Println("--------3、信息列表--------")
fmt.Println("--------4、退出系统--------")
var key int
fmt.Scanf("%d\n", &key)
switch key {
case 1:
fmt.Println("显示在线用户列表")
case 2:
fmt.Println("发送消息")
case 3:
fmt.Println("信息列表")
case 4:
fmt.Println("你选择退出了系统...")
os.Exit(0)
default:
fmt.Println("你输入的选项不正确..")
}
}

// 和服务器保持通讯
func serverProcessMes(conn net.Conn) {
//创建一个transfer实例,不停的读取服务器发送的消息
tf := &utils.Transfer{
Conn: conn,
}
for {
fmt.Println("客户端%s正在等待读取服务器发送的消息")
mes, err := tf.ReadPkg()
if err != nil {
fmt.Println("tf.ReadPkg err=", err)
return
}
//如果读取到消息,又是下一步处理逻辑
fmt.Printf("mes=%v", mes)
}
}
4.2.3.3、day8/chatroom/client/process/smsProcess.go
package process
4.2.3.4、day8/chatroom/client/process/userProcess.go
package process

import (
"encoding/binary"
"encoding/json"
"fmt"
"net"
"redis/day8/chatroom/client/utils"
"redis/day8/chatroom/common/message"
)

type UserProcess struct {
}

func (this *UserProcess) Login(userId int, userPwd string) (err error) {
//fmt.Printf("userId = %d userPwd = %s\n", userId, userPwd)
//return nil
//连接到服务器
conn, err := net.Dial("tcp", "localhost:8889")
if err != nil {
fmt.Println("net.Dial err=", err)
return
}
//延时关闭
defer conn.Close()
//准备通过 conn 发送消息给服务器
var mes message.Message
mes.Type = message.LoginMesType
var loginMes message.LoginMes
loginMes.UserId = userId
loginMes.UserPwd = userPwd
//将 loginMes 序列化
data, err := json.Marshal(loginMes)
if err != nil {
fmt.Println("json.Marshal err=", err)
return
}
//将data赋值给 message 结构体 Data 字段
mes.Data = string(data)
//将 mes 进行序列化
data, err = json.Marshal(mes)
if err != nil {
fmt.Println("json.Marshal err=", err)
return
}
//data是 我们要发送的消息,先发送 data 长度
//由于 conn 接口的 Write 方法参数要求是 bytes 切片
var pkgLen uint32
pkgLen = uint32(len(data))
var buf [4]byte
binary.BigEndian.PutUint32(buf[0:4], pkgLen)
//发送长度
n, err := conn.Write(buf[0:4])
if n != 4 || err != nil {
fmt.Println("conn.Write(bytes) fail", err)
return
}
fmt.Printf("客户端,发送消息的长度=%d,消息内容为: %s\n", len(data), string(data))
_, err = conn.Write(data)
if err != nil {
fmt.Printf("conn.Write(data) fail", err)
return
}
//time.sleep(20*time.Second)
//fmt.Println("休眠了20S")
//这里还需要处理服务器返回的消息
tf := &utils.Transfer{
Conn: conn,
}
mes, err = tf.ReadPkg() //mes 就是

if err != nil {
fmt.Println("readPkg(conn) err=", err)
return
}

//将 mes 的 data 部分反序列化成 LoginResMes
var loginResMes message.LoginResMes
err = json.Unmarshal([]byte(mes.Data), &loginResMes)
if loginResMes.Code == 200 {
//fmt.Println("登录成功")
//这里我们还需要在客户端启动一个协程
//该协程保持和服务器端的通讯,如果服务器有数据推送给客户端
//则接收并显示在客户端的终端
go serverProcessMes(conn)
//1.显示登录成功后的菜单
for {
ShowMenu()
}
} else if loginResMes.Code == 500 {
fmt.Println(loginResMes.Error)
}

return
}
4.2.3.5、day8/chatroom/client/utils/utils.go
package utils

import (
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"net"
"redis/day8/chatroom/common/message"
)

// 这里将这些方法关联到结构体中
type Transfer struct {
//分析它应该有哪些字段
Conn net.Conn
Buf  [8096]byte // 这是传输时,使用缓冲
}

func (this *Transfer) ReadPkg() (mes message.Message, err error) {
//buf := make([]byte, 8096)
// conn.Read 在 conn 没有被关闭的情况下,才会阻塞
//如果客户端关闭了conn 就不会阻塞
_, err = this.Conn.Read(this.Buf[:4])
if err != nil {
//err = errors.New("read pkg header error")
return
}
//根据读到的  buf[:4] 转成一个 unit32 类型
var pkgLen uint32
pkgLen = binary.BigEndian.Uint32(this.Buf[0:4])
n, err := this.Conn.Read(this.Buf[:pkgLen])
if n != int(pkgLen) || err != nil {
//err = errors.New("read pkg body error")
return
}
//把 pkgLen 反序列化成  -> message.Message
err = json.Unmarshal(this.Buf[:pkgLen], &mes)
if err != nil {
err = errors.New("json.Unmarshal error")
return
}
return

}

func (this *Transfer) WritePkg(data []byte) (err error) {
//先发送一个长度给对方
//data是 我们要发送的消息,先发送 data 长度
//由于 conn 接口的 Write 方法参数要求是 bytes 切片
var pkgLen uint32
pkgLen = uint32(len(data))
//var buf [4]byte
binary.BigEndian.PutUint32(this.Buf[0:4], pkgLen)
//发送长度
n, err := this.Conn.Write(this.Buf[:4])
if n != 4 || err != nil {
fmt.Println("conn.Write(bytes) fail", err)
return
}
//发送 data本身
n, err = this.Conn.Write(data)
if n != int(pkgLen) || err != nil {
fmt.Println("conn.Write(bytes) fail", err)
return
}
return
}
4.2.4、main代码

main 代码跟 go.mod 在同级目录下

cat main.go
package main

import (
"fmt"
"net"
//"redis/day8/chatroom/server"
"redis/day8/chatroom/server/model"
"redis/day8/chatroom/server/processor"
"time"
)

// 处理和客户端的通信
func process(conn net.Conn) {
//这里需要延时关闭conn
defer conn.Close()
//这里调用总控,创建一个
processor := &processor.Processor{
Conn: conn,
}
err := processor.Process2()
if err != nil {
fmt.Println("客户端和服务器端通讯的协程错误=err", err)
return
}
}

// 这里我们编写一个函数,完成对 UserDao 的初始化任务
func initUserDao() {
model.MyUserDao = model.NewUserDao(processor.Pool)
}
func main() {
//当服务器启动时就初始化redis的连接池
processor.InitPool("127.0.0.1:6379", 16, 0, 300*time.Second)
initUserDao()
//提示信息
fmt.Println("服务器[新的结构]在 8889 端口监听......")
listen, err := net.Listen("tcp", "0.0.0.0:8889")
if err != nil {
fmt.Println("net.Listen err=", err)
return
}
for {
fmt.Println("等待客户端连接服务器......")
conn, err := listen.Accept()
if err != nil {
fmt.Println("listen.Accept() err=", err)
}
//一旦连接成功,则启动一个协程和客户端保持通讯
go process(conn)
}
}
4.2.5、编译项目代码
4.2.5.1、编译服务端代码
go build main main.go
4.2.5.2、编译客户端代码
go build -o client day8/chatroom/client/main/main.go
4.2.6、演示代码

说明:本文代码是去redis数据库验证,redis数据库中存放的就一个用户id 100,密码 123456 的用户信息。所以只有这个才是正确的登录输入。另外如果登录失败,代码暂时没做详细的错误处理,统一输出 该用户不存在,请注册使用...

4.2.6.1、验证登录成功的情况
go run main.go 
服务器[新的结构]8889 端口监听......
等待客户端连接服务器......
go run day8/chatroom/client/main/main.go 
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):

client

1
登录聊天室
请输入用户的id
100
请输入用户密码
123456
客户端,发送消息的长度=86,消息内容为: {"Type":"LoginMes","Data":"{\"UserId\":100,\"UserPwd\":\"123456\",\"UserName\":\"\"}"}
----------恭喜xxx登录成功--------
--------1、显示在线用户列表--------
--------2、发送消息--------
--------3、信息列表--------
--------4、退出系统--------
客户端%s正在等待读取服务器发送的消息

server

等待客户端连接服务器......
&{100 123456 scott} 登录成功
客户端退出,服务器端也退出..
客户端和服务器端通讯的协程错误=err EOF
4.2.6.2、验证密码错误的情况

说明:由于没对登录错误做详细处理,统一输出 该用户不存在,请注册使用... 所以,这里只验证密码错误的情况,不再验证用户 id 错误的情况。

服务端不退出
重新执行客户端代码

./client 
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):

client

1
登录聊天室
请输入用户的id
100
请输入用户密码
123
客户端,发送消息的长度=83,消息内容为: {"Type":"LoginMes","Data":"{\"UserId\":100,\"UserPwd\":\"123\",\"UserName\":\"\"}"}
该用户不存在,请注册使用...
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):

server

等待客户端连接服务器......
&{100 123456 scott} 登录失败
客户端退出,服务器端也退出..
客户端和服务器端通讯的协程错误=err EOF

4.3、错误处理

4.3.1、处理详细的错误输出

修改 day8/chatroom/server/process/userProcess.go 文件的代码

if err != nil {
if err == model.ERROR_USER_NOTEXISTS {
loginResMes.Code = 500
loginResMes.Error = err.Error()
} else if err == model.ERROR_USER_PWD {
loginResMes.Code = 403
loginResMes.Error = err.Error()
} else {
loginResMes.Code = 505
loginResMes.Error = "服务器内部错误"
}
} else {
loginResMes.Code = 200
fmt.Println(user, "登录成功")

}

修改day8/chatroom/client/process/userProcess.go文件的代码

if loginResMes.Code == 200 {
//fmt.Println("登录成功")
//这里我们还需要在客户端启动一个协程
//该协程保持和服务器端的通讯,如果服务器有数据推送给客户端
//则接收并显示在客户端的终端
go serverProcessMes(conn)
//1.显示登录成功后的菜单
for {
ShowMenu()
}
} else {
fmt.Println(loginResMes.Error)
}
4.3.2、演示代码
./main 
服务器[新的结构]8889 端口监听......
等待客户端连接服务器......
等待客户端连接服务器......
客户端退出,服务器端也退出..
客户端和服务器端通讯的协程错误=err EOF
等待客户端连接服务器......
客户端退出,服务器端也退出..
客户端和服务器端通讯的协程错误=err EOF
./client 
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):
1
登录聊天室
请输入用户的id
100
请输入用户密码
123
客户端,发送消息的长度=83,消息内容为: {"Type":"LoginMes","Data":"{\"UserId\":100,\"UserPwd\":\"123\",\"UserName\":\"\"}"}
密码不正确
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):
1
登录聊天室
请输入用户的id
900
请输入用户密码
123
客户端,发送消息的长度=83,消息内容为: {"Type":"LoginMes","Data":"{\"UserId\":900,\"UserPwd\":\"123\",\"UserName\":\"\"}"}
用户不存在...
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):

原文地址:https://blog.csdn.net/weixin_40548182/article/details/143406490

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!