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

news2024/10/7 2:30:09

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.项目-海量用户即时通讯系统-显示在线用户列表,群聊

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/25403.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GitLab的使用

目录 一、腾讯云 / 阿里云 购买服务器 二、服务器上安装GitLab-ce 三、使用git 1、首先把服务器上的代码克隆下来 2、将所有有改动的全部添加到要提交的本地库中 3、将修改提交到本地库 4、将本地库的commit推送到远程服务器 5、拉取服务器上最新资源: 6、…

智创未来 · 引领5G价值,广和通携5G AIoT创新应用亮相2022德国慕尼黑电子展

11月15-18日,两年一届的全球电子行业盛会——德国慕尼黑电子展(Electronica 2022)于慕尼黑展览中心顺利举行。作为全球领先的物联网无线通信解决方案和无线模组供应商,广和通精彩亮相B5馆139展台,向全球物联网企业分享…

机器学习分类模型评价指标之ROC 曲线、 ROC 的 AUC 、 ROI 和 KS

前文回顾: 机器学习模型评价指标之混淆矩阵机器学习模型评价指标之Accuracy、Precision、Recall、F-Score、P-R Curve、AUC、AP 和 mAP 1. 基本指标 1.1 True Positive Rate(TPR) TPRTPTPFNTPR \frac{TP}{TPFN}TPRTPFNTP​ 中文:真正率、灵敏度、召…

一天快速掌握Mybaits[一]

一、搭环境 Spring Initializr的搭建 创建完毕后的项目结构 此时application的后缀更名为yml,因为这样,看起来更简洁明了,而作用上,无差别 数据库环境的搭建 新建数据库 执行SQL语句 use mybatis-demo; CREATE TABLE user (id…

珠海航展有图扑 | 数字孪生方案助力智慧航天

2022 年 11 月 8 日~11 月 15 日,在第十四届中国国际航空航天博览会(简称中国航展)中,图扑先进的数字孪生解决方案,支撑合作伙伴实现人机交互场景。 图扑助力航展数字化 在本届国际航空航天博览会中,图扑…

Word控件Spire.Doc 【图像形状】教程(2) ;在 C#、VB.NET 中从 Word 中提取图像

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下,轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具,专注于创建、编辑、转…

代码演示GC回收

JVM配置 -XX:NewSize5m 初始新生代大小 -XX:MaxNewSize5m 最大新生代大小 -XX:InitialHeapSize10m 初始堆大小 等同于Xms -XX:MaxHeapSize10m 最大堆大小 等同于Xmx -XX:SurvivorRatio8 Eden区占80% -XX:PretenureSizeThreshold10m 大对象阈值 -XX:UseParNewGC 新生代使用ParN…

最新最全面的Spring详解(五)——事务管理

前言 本文为 【Spring】事务管理 相关知识,下边将对Spring框架事务支持模型的优点,Spring框架的事务抽象的理解(包含TransactionManager、TransactionDefinition、TransactionStatus,编程式事务管理(包含使用 Transact…

(七)笔记.net core学习之反射、加载dll、读取moudle、类、方法、特性

1.反射加载dll、读取moudle、类、方法、特性 (1)模块信息获取 (2)方法调用 2.反射创建对象、反射简单工厂配置文件(破坏单例,创建泛型) (1)程序的可配置可扩展&#xf…

【MEIF:ℓ1-ℓ0混合分解】

Multimodal Medical Image Fusion Using Hybrid Layer Decomposition With CNN-Based Feature Mapping and Structural Clustering (基于CNN的特征映射和结构聚类的混合层分解的多模态医学图像融合) 本文提出了一种特征级多模态医学图像融合&#xff0c…

springboot获取不到客户端ip问题排查

一、现象 springboot从2.0.2升级到 2.5.7后线上环境无法通过request.getHeader("x-forwarded-for")获取客户端ip地址,测试环境正常,开发环境也异常 二、结论 springboot 2.5.7版本中CloudPlatform多了Kubernetes platform的类型识别&#x…

SpringCloud整合Nacos最全教程(简介及安装部署整合)

目录 一、Nacos简介 Nacos与eureka的共同点 Nacos与Eureka的区别 二、Nacos安装配置 在windows中的安装教程 1.首先将windows安装版本的zip解压: 2.如果8848端口被占用,可以修改端口 3.进入到bin目录下,在cmd中运行以下命令启动 4.启…

Webpack 5 超详细解读(二)

11.importLoaders 属性 问题: test.css的内容如下: .title {transition: all .5s;user-select: none; }login.css的内容如下: /* 导入test.css */ import ./test.css; .title {color: #12345678; }再次npm run build发现运行之后的test.c…

外汇天眼:想通过外汇交易在几个月内成为亿万富翁吗?你必须知道的七大交易法则

WikiFX 策略 -这里有七个交易规则,将在不同程度上让您受益。 1.交易不是儿戏 这是一项业务,如果没有适当的计划、战略和有效的运营,就不可能取得长期的成功。 2.损失不可避免 由于市场始终存在风险,因此在您的交易中从多头转为…

C++:项目相互依赖调用解决方法两种方法

Bmodel依赖于Amodel,但是Amodel又需要BModel的信息。这样就会导致相互依赖。 方法一:采用静态变量static 链接:C开发中一个解决方案里,两个项目的相互引用,相互依赖的实现方法(解决方法)_Capri…

P物质肽[DArg1, DTrp5, 7, 9, Leu11]

这种物质P类似物是一种非常有效的小细胞肺癌(SCLC)细胞体外生长的广谱神经肽抑制剂(IC₅₀ 5M)。此外,它在体外有效地抑制信号转导通路,并在体内显著延缓SCLC异种移植物的生长。因此,它可能对SCLC有治疗价值。 编号: 139994中文名称: P物质肽…

数据结构学习笔记(Ⅰ):绪论

课程链接:【旧版】1.0_开篇_数据结构在学什么_哔哩哔哩_bilibili 目录 1 数据结构的基本概念 2 算法 2.1 算法的基本概念 1.算法概念 2.算法的特性 3.好算法特质 2.2 算法的时间复杂度 2.3 算法的空间复杂度 1 数据结构的基本概念 数据:能输入到计算机中并…

Android StudioJNI开发之NDK环境的搭建以及添加JNI支持(图文解释 简单易懂)

有问题可以评论区留言讨论~~~ 一、NDK环境搭建 Android系统的所谓原生开发是在App中调用C/C代码,鉴于这两个语言具有跨平台的特性,如果某项功能使用C/C实现,就很容易在不同平台之间移植。 完整的Android环境包括三个开发工具。分别是JDK SD…

如何缩减layout电路面积?减少晶体管的数量——以全加器为例【VLSI】

如何缩减layout电路面积?减少晶体管的数量——以全加器为例【VLSI】What is Full adder ?全加器的设计方法1. 32T 原始表达式不经过化简的电路图2. 28个晶体管 最基本的静态互补CMOS电路的全加器静态互补CMOS静态互补CMOS的优势与劣势28T 电路图28T的棒状图Stick D…

彻底搞明白概率论:随机事件,样本空间,必然事件,不可能事件

文章目录样本空间样本点随机事件,必然事件,不可能事件参考视频样本空间 随机试验E的一切可能基本结果(或实验过程如取法或分配法)组成的集合称为E的样本空间,记为S 注意,对于不同的实验,样本空间…