第九步:
在redis中手动添加测试用户并画图分析后续通过程序添加用户
手动添加用户信息
127.0.0.1:6379> hset users 100 "{\"userId\":100,\"userPwd\":\"123456\",\"userName\":\"showmaker\"}"
(integer) 0
server包结构
做出修改的地方:
main.go/redis.go
model/*.go
process2/userProcess.go
server/main/main.go
package main
import (
"fmt"
"net"
"time"
"gotcp/chatroom/server/model"
)
func init(){
// 当服务器开启后就初始化连接池
initPool("localhost:6379",8,0,180 * time.Second)
initUserDao()
}
// 编写一个函数完成对UserDao的初始化
func initUserDao(){
// 这里要注意一个初始化顺序问题
// 先调用initPool 再调用 initUserDao
model.MyUserDao = model.NewUserDao(pool) // 这里的pool来源于redis中的pool
}
func process(conn net.Conn){
defer conn.Close()
// 循环读取
processall := &Processall{
Conn : conn,
}
processall.processalltwo()
}
func main(){
listener,err := net.Listen("tcp","0.0.0.0:8889")
defer listener.Close()
if err != nil{
fmt.Println("net.Listener err = ",err)
return
}
fmt.Printf("服务器网络类型为:%v 地址为:%v\n",listener.Addr().Network(),listener.Addr().String())
for{
// Accept返回listen服务端的连接并等待下一个连接
conn,err := listener.Accept()
defer conn.Close()
if err != nil{
fmt.Println("listener.Accept err = ",err)
return
}
fmt.Println("客户端连接成功!ip地址为:",conn.RemoteAddr())
go process(conn)
}
}
server/main/processall.go
package main
import (
"io"
"fmt"
"net"
"gotcp/chatroom/common/message"
"gotcp/chatroom/server/process2"
"gotcp/chatroom/server/utils"
)
type Processall struct{
Conn net.Conn
}
// 编写一个ServerProcessMsg函数判断消息请求类型进行相应处理
func (this *Processall)serverProcessMsg(msg *message.Message)(err error){
switch msg.Type{
case message.LoginMsgType:
up := &process2.Userprocess{
Conn : this.Conn,
}
err = up.ServerProcessLogin(msg)
// err = serverProcessLogin(conn,msg)
case message.LoginResMsgType:
// 处理注册请求
default :
fmt.Println("消息类型不存在")
}
return
}
func (this *Processall)processalltwo(){
for {
tf := &utils.Transfer{
Conn : this.Conn,
}
msg,err := tf.ReadMsg()
if err != nil{
// fmt.Println("readMsg(conn) err = ",err)
if err == io.EOF{
fmt.Println("客户端退出服务端也退出...")
return
}else{
fmt.Println("tf.ReadMsg() err = ",err)
return
}
}
fmt.Println("msg反序列化后 = ",msg)
err = this.serverProcessMsg(&msg)
if err != nil{
fmt.Println("ServerProcessMsg(conn,msg) err:",err)
}
}
}
server/main/redis.go
// 初始化pool连接池
package main
import (
"time"
"github.com/garyburd/redigo/redis"
)
// 声明一个redis.Pool结构体类型的pool
// 定义一个全局的pool
var pool *redis.Pool
// 程序启动初始化连接池
func initPool(address string,maxIdle int,maxActive int,idleTimeout time.Duration){
pool = &redis.Pool{
// 最大空闲连接数
MaxIdle : maxIdle,
// 表示和数据库的最大连接数,0表示不限制
MaxActive : maxActive,
// 最大空闲时间
IdleTimeout : idleTimeout,
// 初始化连接,表示连接到哪个地址
Dial : func() (redis.Conn, error){
return redis.Dial("tcp",address)
},
}
}
server/model/err.go
// 用户自定义错误
package model
import (
"errors"
)
var (
ERROR_USER_EXISTENCE = errors.New("用户不存在")
ERROR_USER_EXISTS = errors.New("用户存在")
ERROR_USER_PWD = errors.New("密码错误")
)
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/userDo.go
// 此文件用来操作user结构体
package model
import (
"fmt"
"github.com/garyburd/redigo/redis"
"encoding/json"
)
// 在服务器启动后就初始化一个UserDao实例
// 把它做成全局变量方便使用
// 声明一个UserDao结构体,实例化一个pool连接池
type rootUserDao struct{
pool *redis.Pool
}
// 实例化一个 rootUserDao 指针类型
var (
MyUserDao *rootUserDao
)
// 使用工厂模式创建UserDao实例
// 工厂模式主要是为了程序的隔离性,在资源和使用者之间提供一个中介服务
// 通过工厂模式函数返回一个 pool实例再赋值给 MyUserDao 进行使用
func NewUserDao(pool *redis.Pool)(userDao *rootUserDao){
userDao = &rootUserDao{
pool:pool,
}
// 返回userDao
return
}
// 根据id返回User
// 第一层对信息校验,校验是否能查到对应的ID
func (this *rootUserDao)getUserById(conn redis.Conn,id int)(user *User,err error){
res,err := redis.String(conn.Do("hget","users",id))
if err != nil{
if err == redis.ErrNil{
err = ERROR_USER_EXISTENCE
}
return
}
user = &User{}
err = json.Unmarshal([]byte(res),user)
if err != nil{
fmt.Println("json.Unmarshal([]byte(res),user) err=",err)
}
return
}
// 根据返回的User进行校验
// 如果id和pwd都正确返回User实例
// 如果id和pwd有误返回对应错误信息
func (this *rootUserDao)Login(userId int,userPwd string)(user *User,err error){
// 从rootUserDao中取出一根连接
conn := this.pool.Get()
defer conn.Close()
user,err = this.getUserById(conn,userId)
if err != nil{
// fmt.Println("this.getUserById(conn,UserId) err",err)
// return
// 进行信息校验
if err == ERROR_USER_EXISTENCE{
return
}else{
fmt.Println("this.getUserById err",err)
}
}
// 第二层校验校验查出来的用户密码是否正确
if userPwd != user.UserPwd{
err = ERROR_USER_PWD
return
}
return
}
server/process2/smsProcess.go
package process2
// 处理和短消息相关的请求
// 群聊,点对点聊天
server/process2/userProcess.go
// 处理和用户相关的请求,登录,注册,注销,用户列表管理
package process2
import (
"fmt"
"net"
"gotcp/chatroom/common/message"
"gotcp/chatroom/server/utils"
"encoding/json"
"gotcp/chatroom/server/model"
)
type Userprocess struct{
Conn net.Conn
}
// 编写一个serverProcessLogin处理登录请求
func (this *Userprocess)ServerProcessLogin(msg *message.Message)(err error){
// 核心代码 先从msg中取出msg.Data并直接反序列化为loginMsg
// 用作对消息进行判断进而返回client消息
var loginMsg message.LoginMsg
err = json.Unmarshal([]byte(msg.Data),&loginMsg)
if err != nil{
fmt.Println("serverProcessLogin json.Marshal err:",err)
return
}
// 声明一个Message标识消息类型
var resMsg message.Message
resMsg.Type = message.LoginResMsgType
// 声明一个LoginResMsg完成赋值
var loginResMsg message.LoginResMsg
// 使用model.MyUserDao去redis验证
user,err := model.MyUserDao.Login(loginMsg.UserId,loginMsg.UserPwd)
if err != nil{
if err == model.ERROR_USER_EXISTENCE{
// 用户不存在返回500
loginResMsg.Code = 500
loginResMsg.Error = err.Error()
}else if err == model.ERROR_USER_PWD{
// 密码不正确返回300
loginResMsg.Code = 300
loginResMsg.Error = err.Error()
}else{
loginResMsg.Code = 500
loginResMsg.Error = "内部信息错误"
}
}else{
loginResMsg.Code = 200
fmt.Println(user,"登录成功")
}
// if loginMsg.UserId == 100 && loginMsg.UserPwd == "123456"{
// loginResMsg.Code = 200
// }else{
// loginResMsg.Code = 500
// fmt.Println("user notfund fpx.no1")
// }
data,err := json.Marshal(loginResMsg)
if err != nil{
fmt.Println("serverProcessLogin json.Marshal(loginResMsg) err:",err)
return
}
resMsg.Data = string(data)
data,err = json.Marshal(resMsg)
if err != nil{
fmt.Println("serverProcessLogin json.Marshal(resMsg) err:",err)
return
}
// 消息序列化完毕准备发送
tf := &utils.Transfer{
Conn : this.Conn, // 调用本方法(this *Userprocess) this绑定struct里的Conn
}
err = tf.WritePkg(data)
return
}
server/utils/utils.go
package utils
import (
"fmt"
"net"
"gotcp/chatroom/common/message"
"encoding/binary"
"encoding/json"
)
type Transfer struct{
Conn net.Conn
Buf [9600]byte // 传输时使用缓冲
}
func (this *Transfer)ReadMsg()(msg message.Message,err error){
// buf := make([]byte,9600)
fmt.Println("准备读取客户端传输数据...")
// 从conn中读取4个字节放入bug中
_,err = this.Conn.Read(this.Buf[:4])
if err != nil{
// fmt.Println("conn.Read err = ",err)
return
}
// Uint32传入一个切片返回一个uint32类型长度
relay := binary.BigEndian.Uint32(this.Buf[:4])
// 根据relay读取消息内容
n,err := this.Conn.Read(this.Buf[:relay])
if n != int(relay) || err != nil{
// fmt.Println("conn.Read file err = ",err)
return
}
// 这里序列化buf[:relay]并存入msg中一定要加&符号,地址否则不会返回给函数返回值
err = json.Unmarshal(this.Buf[:relay],&msg)
if err != nil{
fmt.Println("json.Unmarshal err = ",err)
return
}
return
}
// 编写一个writePkg函数用来发送请求
func (this *Transfer)WritePkg(data []byte)(err error){
// 先把data转为uint32再转换并放入切片中把切片发送给对方
var relay uint32
relay = uint32(len(data))
// var buf [4]byte
binary.BigEndian.PutUint32(this.Buf[:4],relay)
n,err := this.Conn.Write(this.Buf[0:4])
if err != nil || n != 4{
fmt.Println("conn.Write err = ",err)
return
}
// 发送data本身
n,err = this.Conn.Write(data)
if n != int(relay) || err != nil{
fmt.Println("writePkg conn.Write err",err)
return
}
return
}
2019-11-23 今天刚好把这个项目做完,也恰好被裁掉,一切都是那么的巧合,祝自己未来一帆风顺,