在redis手动添加测试用户,并画出示意图以及说明注意事项(后续通过程序注册用户)
如:输入用户名和密码,如果在redis中存在并正确,则登录,否则退出系统,并给出相应提示:
提示信息:
1.用户不存在或者密码错误
2.重新注册并登录
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
}
(1).完成注册功能,将用户信息录入到redis中
(2).思路分析,代码展示
package message
//定义一个用户的结构体
type User struct {
//确定字段信息
//为了序列化和反序列化成功,要保证用户信息的json字符串的key和结构体对应的tag名字一致
UserId int `json:"userId"`
UserPwd string `json:"userPwd"`
UserName string `json:"userName"`
}
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"` //返回错误信息
}
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), ®isterResMes)
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
}
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("输入有误,请重新输入")
}
}
//完成注册
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
}
//编写一个函数ServerProcessRegister函数,专门处理注册请求
func (this * UserProcess) ServerProcessRegister (mes *message.Message) (err error) {
//先从mes中取出mes.Data,并直接反序列化成RegisterMes
var registerMes message.RegisterMes
err = json.Unmarshal([]byte(mes.Data), ®isterMes)
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(®isterMes.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
}
//编写一个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.项目-海量用户即时通讯系统-显示在线用户列表,群聊