[go学习笔记.第十六章.TCP编程] 3.项目-海量用户即时通讯系统-redis介入,用户登录,注册

1.实现功能-完成用户登录

在redis手动添加测试用户,并画出示意图以及说明注意事项(后续通过程序注册用户)

如:输入用户名和密码,如果在redis中存在并正确,则登录,否则退出系统,并给出相应提示:

提示信息:

        1.用户不存在或者密码错误

        2.重新注册并登录

 redis手动添加测试用户

 

server/model/user.go 

package model

//定义一个用户的结构体
type User struct {
    //确定字段信息
    //为了序列化和反序列化成功,要保证用户信息的json字符串的key和结构体对应的tag名字一致
    UserId int `json:"userId"`
    UserPwd string `json:"userPwd"`
    UserName string `json:"userName"`
}

server/model/userDao.go

package model

import(
    "fmt"
    "github.com/garyburd/redigo/redis" //引入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
}

//应该提供的方法有: 
//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 = ERROE_USER_NOTEXISTS
        }
        return
    }
    //这里需要把res反序列化成User实例
    user = &User{}
    err = json.Unmarshal([]byte(res), user)
    if err != nil {
        fmt.Println("json.UmMarshal fail, err=", err)
        return
    }
    return
}

//完成登录校验
//1.完成对用户的验证
//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 = ERROE_USER_PWD
        return
    }
    return
}

server/model/error.go

package model

import (
    "errors"
)
//根据业务逻辑的需要,自定义一些错误
var (
    ERROE_USER_NOTEXISTS = errors.New("用户不存在")
    ERROE_USER_EXISTS = errors.New("用户已存在")
    ERROE_USER_PWD = errors.New("密码不正确")
    ERROE_SERVER = errors.New("服务器内部错误")
)

server/main/redis.go

package main

import(
    "github.com/garyburd/redigo/redis" //引入redis包
    "time"
)

//定义一个全局变量
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
            return redis.Dial("tcp", address)
        },
    }
}

server/main/main.go修改

package main

import (
    "fmt"
    "net"
    "time"
    "go_code/chatroom/server/model"
)

//处理和客户端通讯
func process(conn net.Conn)  {
    //这里需要延时关闭
    defer conn.Close()
    //这里调用总控,创建一个总控实例
    processor := &Processor{
        Conn: conn,
    }
    err := processor.ProcessMain()
    if err != nil {
        fmt.Printf("客户端和服务器端的协程出问题了,err= %v\n", err)
        return
    }
}

func init() {
    //当服务器启动时,就初始化redis连接池
    initPool("127.0.0.1:6379", 16, 0, 300 * time.Second)
    initUserDao()
}

//编写一个函数,完成对UserDao的初始化任务
func initUserDao()  {
    //pool 就是一个全局的变量
    //这里需要注意初始化顺序问题: 先initPool(),再initUserDao()
    model.MyUserDao = model.NewUserDao(pool)
}

func main()  {
    //提示信息
    fmt.Println("服务器[新结构]正在监听8889端口...")
    listen, err := net.Listen("tcp", "127.0.0.1:8889")
    //这里需要延时关闭
    defer listen.Close()
    if err != nil {
        fmt.Printf("net listen err = %v\n", err)
        return
    }
    //一旦监听成功,就等待客户端来连接服务器
    for {
        fmt.Println("等待客户端来连接服务器...")
        conn, err := listen.Accept()
        if err != nil {
            fmt.Printf("listen accept err = %v\n", err)
            return
        }
        //一旦连接成功,则启动一个协程,保持和客户端通讯
        go process(conn)
    }
}

server/processBlock/userProcess.go修改

package processBlock

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

type UserProcess struct {
    Conn net.Conn
}

//编写一个函数serverProcessLogin函数,专门处理登录请求
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.Printf("json.Unmarshal fail, err = %v\n", err)
        return
    }
    //1.声明一个resMes
    var resMes message.Message
    resMes.Type = message.LoginResMesType
    //2.再声明一个loginResMes,并完成赋值
    var loginResMes message.LoginResMes
    // //如果用户id=100,密码=123456,则合法,不然,则不合法
    // if loginMes.UserId == 100 && loginMes.UserPwd == "123456" {
    //  //合法
    //  loginResMes.Code = 200
    // } else {
    //  //不合法
    //  loginResMes.Code = 500  //500 表示不存在
    //  loginResMes.Error = "该用户不存在,请注册后使用"
    // }
    //需要到redis数据去验证
    //1.使用mode.MyUserDao到redis去验证
    user, err := model.MyUserDao.Login(loginMes.UserId, loginMes.UserPwd)
    
    if err != nil {
        if err == model.ERROE_USER_NOTEXISTS {
            loginResMes.Code = 500
        } else if err == model.ERROE_USER_PWD {
            loginResMes.Code = 403
        } else {
            loginResMes.Code = 505
        }
        loginResMes.Error = err.Error()
    } else {
        loginResMes.Code = 200
        fmt.Println("user = ", user)
    }

    //3.将loginResMes序列化
    data, err := json.Marshal(loginResMes)
    if err != nil {
        fmt.Printf("json.Marshal fail, err = %v\n", err)
        return
    }
    //4.将data赋值给resMes
    resMes.Data = string(data)
    //5.对resMes序列化,准备发送
    data, err = json.Marshal(resMes)
    if err != nil {
        fmt.Printf("json.Marshal fail, err = %v\n", err)
        return
    }
    //6.发送data,将其封装到writePkg函数中
    //因为使用了分层模式(mvc),先创建Transfer实例,然后读取
    tf := &utils.Transfer{
        Conn: this.Conn,
    }
    err = tf.WritePkg(data)
    return
}   

2.实现功能-完成用户注册

(1).完成注册功能,将用户信息录入到redis中

(2).思路分析,代码展示

(1).新增 common/message/user.go 

package message

//定义一个用户的结构体
type User struct {
    //确定字段信息
    //为了序列化和反序列化成功,要保证用户信息的json字符串的key和结构体对应的tag名字一致
    UserId int `json:"userId"`
    UserPwd string `json:"userPwd"`
    UserName string `json:"userName"`
}

(2). common/message/message.go新增了方法

package message

//定义消息类型
const (
    LoginMesType = "LoginMes"
    LoginResMesType = "LoginResMes"
    RegisterMesType = "RegisterMes"
    RegisterResMesType = "RegisterResMes"
)

type Message struct {
    Type string `json:"type"` // 消息类型
    Data string `json:"data"` //消息内容
}

//定义需要的消息

type LoginMes struct {
    UserId int `json:"userId"`//用户id
    UserPwd string `json:"userPwd"` //用户密码
    UserName string `json:"userName"` //用户名
}

type LoginResMes struct {
    Code int `json:"code"` //返回状态码: 200 登录成功, 500 用户未注册
    Error string `json:"error"` //返回错误信息
}

type RegisterMes struct {
    User User `json:"user"` //类型就是User结构体
}

type RegisterResMes struct {
    Code int `json:"code"` //返回状态码: 200 注册成功, 40 用户已被占用
    Error string `json:"error"` //返回错误信息
}

(3).client/processBlock/userProcess.go新增方法

func (this *UserProcess) Register(userId int, userPwd, userName string) (err error){
    //1.连接到服务器端
    conn, err := net.Dial("tcp", "127.0.0.1:8889")
    if err != nil {
        fmt.Printf("net dial err = %v\n", err)
        return
    }
    //延时关闭
    defer conn.Close()  
    //2.准备通过conn发送消息给服务端
    var mes message.Message
    mes.Type = message.RegisterMesType
    //3.创建一个RegisterMes结构体
    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.Printf("json marshal err = %v\n", err)
        return
    }
    //5.将序列化后的registerMes byte切片赋给mes.Data
    mes.Data = string(data)
    //6.将mes序列化
    data, err = json.Marshal(mes)
    if err != nil {
        fmt.Printf("json marshal err = %v\n", err)
        return
    }

    //创建一个Transfer实例
    tf := &utils.Transfer{
        Conn: conn,
    }
    //发送data给服务器
    err = tf.WritePkg(data)
    if err != nil {
        fmt.Println("WritePkg(data) err = ", err)
        return
    }
    //读取返回的消息
    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), &registerResMes)
    if err != nil {
        fmt.Println("json.Unmarshal err = ", err)
        return
    }
    if registerResMes.Code == 200 {
        fmt.Println("注册成功,请重新登录") 
        os.Exit(0)
    } else {
        fmt.Println(registerResMes.Error)
        os.Exit(0)
    }
    return
}

(4).client/main/main.go完善了注册请求

for true {
        fmt.Println("-------------------欢迎登录多人聊天系统-------------------")
        fmt.Println("\t\t\t\t 1 登录聊天室")
        fmt.Println("\t\t\t\t 2 注册用户")
        fmt.Println("\t\t\t\t 3 退出系统")
        fmt.Println("\t\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)
                //先把登录的函数写到另一个文件,比如:login.go
                //因为使用了分层模式(mvc),故调用分层模式中processBlock.UserProcess.Login()处理
                up := &processBlock.UserProcess{}
                up.Login(userId, userPwd)
            case 2 : 
                fmt.Println("注册用户")
                fmt.Println("请输入用户的id:")
                fmt.Scanf("%d\n", &userId)
                fmt.Println("请输入用户的密码:")
                fmt.Scanf("%s\n", &userPwd)
                fmt.Println("请输入用户名:")
                fmt.Scanf("%s\n", &userName)
                //因为使用了分层模式(mvc),故调用分层模式中processBlock.UserProcess.Register()处理注册相关逻辑
                up := &processBlock.UserProcess{}
                up.Register(userId, userPwd, userName)
            case 3 :  
                fmt.Println("退出系统")
                os.Exit(0)
            default:
                fmt.Println("输入有误,请重新输入")
        }
    }

(5).server/model/userDao.go增加了方法

//完成注册
func (this *UserDao) Register(user *message.User) (err error) {
    //先从UserDao的连接池中取出一条连接
    conn := this.pool.Get()
    defer conn.Close()
    _, err = this.getUserById(conn, user.UserId)
    if err == nil {
        err = ERROE_USER_EXISTS
        return
    }
    //说明id在redis中还没有,则可以完成注册
    data, err := json.Marshal(user) //序列化
    if err != nil {
        return
    }
    //入库
    _, err = conn.Do("HSet", "users", user.UserId, string(data))
    if err != nil {
        fmt.Println("保存注册用户错误,err=", err)
        return
    }
    return
}

(6).server/processBlock/userProcess.go增加注册方法

//编写一个函数ServerProcessRegister函数,专门处理注册请求
func (this * UserProcess) ServerProcessRegister (mes *message.Message) (err error)  {
    //先从mes中取出mes.Data,并直接反序列化成RegisterMes
    var registerMes message.RegisterMes
    err = json.Unmarshal([]byte(mes.Data), &registerMes)
    if err != nil {
        fmt.Printf("json.Unmarshal fail, err = %v\n", err)
        return
    }
    //1.声明一个resMes
    var resMes message.Message
    resMes.Type = message.RegisterResMesType
    //2.再声明一个registerResMes,并完成赋值
    var registerResMes message.RegisterResMes
    //需要到redis数据去验证,完成注册
    //1.使用mode.MyUserDao到redis去验证
    err = model.MyUserDao.Register(&registerMes.User)
    if err != nil {
        if err == model.ERROE_USER_EXISTS {
            registerResMes.Code = 505
        } else {
            registerResMes.Code = 404
        }
        registerResMes.Error = err.Error()
    }
    //3.将registerResMes列化
    data, err := json.Marshal(registerResMes)
    if err != nil {
        fmt.Printf("json.Marshal fail, err = %v\n", err)
        return
    }
    //4.将data赋值给resMes
    resMes.Data = string(data)
    //5.对resMes序列化,准备发送
    data, err = json.Marshal(resMes)
    if err != nil {
        fmt.Printf("json.Marshal fail, err = %v\n", err)
        return
    }
    //6.发送data,将其封装到writePkg函数中
    //因为使用了分层模式(mvc),先创建Transfer实例,然后读取
    tf := &utils.Transfer{
        Conn: this.Conn,
    }
    err = tf.WritePkg(data)
    return
}

(7).server/main/processor.go修改了调用方法

//编写一个ServerProcessMes函数
//功能:根据客户端发送消息类型不同,决定调用哪个函数来处理
func (this *Processor) serverProcessMes(mes *message.Message) (err error)  {
    switch mes.Type {
        case message.LoginMesType : 
            //处理登录消息
            //创建一个UserProcess
            up := &processBlock.UserProcess{
                Conn: this.Conn,
            }
            err = up.ServerProcessLogin(mes)
        case message.RegisterMesType : 
            //处理注册
            up := &processBlock.UserProcess{
                Conn: this.Conn,
            }
            err = up.ServerProcessRegister(mes)
        default :
            fmt.Println("消息类型不存在, 无法处理...")
    }
    return
}

[上一节][go学习笔记.第十六章.TCP编程] 2.项目-海量用户即时通讯系统

[下一节] [go学习笔记.第十六章.TCP编程] 4.项目-海量用户即时通讯系统-显示在线用户列表,群聊

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值