diff --git a/common/crypto.go b/common/crypto.go new file mode 100644 index 0000000..ab41ad1 --- /dev/null +++ b/common/crypto.go @@ -0,0 +1,22 @@ +package common + +import ( + "errors" + + "golang.org/x/crypto/bcrypt" +) + +func EncodePassword(password string) (passwordHash string, err error) { + b, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost) + if err != nil { + return "", errors.New("密码hash失败") + } + return string(b), nil +} + +func ComparePassword(passwordInDB string, password string) (match bool) { + if err := bcrypt.CompareHashAndPassword([]byte(passwordInDB), []byte(password)); err != nil { + return false + } + return true +} diff --git a/controller/api.go b/controller/api.go index 9b75b3d..7ecfd83 100644 --- a/controller/api.go +++ b/controller/api.go @@ -1,9 +1,23 @@ package controller -import "github.com/gin-gonic/gin" +import ( + "github.com/gin-gonic/gin" +) var Engine *gin.Engine func Router(r *gin.Engine) { - r.GET("/login", WsHandler) + api := r.Group("/api") + { + user := api.Group("/user") + { + user.GET("/login", WsHandler) + user.POST("/register", Register) + } + group := api.Group("/group") + { + group.POST("/create", CreateGroup) + group.POST("/join", JoinGroup) + } + } } diff --git a/controller/group.go b/controller/group.go new file mode 100644 index 0000000..a8e4f4e --- /dev/null +++ b/controller/group.go @@ -0,0 +1,50 @@ +package controller + +import ( + "chat/model" + "chat/service" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" +) + +func CreateGroup(c *gin.Context) { + p := &model.CreateGroupParam{} + err := c.ShouldBindJSON(p) + if err != nil || p.GroupName == "" { + msg := "参数不合法,创建群组失败" + logrus.Errorf("[controller.group.CreatGroup] %v", err) + Response(c, http.StatusBadRequest, msg, nil) + } + + err = service.CreateGroup(p) + if err != nil { + msg := "创建群组失败" + logrus.Errorf("[controller.group.CreateGroup] %v", err) + Response(c, http.StatusBadRequest, msg, nil) + } + + logrus.Infof("[controller.group.CreatGroup] 创建群组成功") + Response(c, http.StatusOK, "", nil) +} + +func JoinGroup(c *gin.Context) { + p := &model.JoinGroupParam{} + err := c.ShouldBindJSON(p) + if err != nil { + msg := "参数不合法,加入群组失败" + logrus.Errorf("[controller.group.JoinGroup] %v", err) + Response(c, http.StatusBadRequest, msg, nil) + } + + err = service.JoinGroup(p) + if err != nil { + msg := "加入群组失败" + logrus.Errorf("[controller.group.JoinGroup] %v", err) + Response(c, http.StatusBadRequest, msg, nil) + } + + logrus.Infof("[controller.group.JoinGroup] 加入群组成功") + Response(c, http.StatusOK, "", nil) +} diff --git a/controller/user.go b/controller/user.go new file mode 100644 index 0000000..84efdb2 --- /dev/null +++ b/controller/user.go @@ -0,0 +1,44 @@ +package controller + +import ( + "chat/common" + "chat/model" + "chat/service" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" +) + +func Register(c *gin.Context) { + param := &model.RegisterParam{} + err := c.ShouldBind(param) + if err != nil { + msg := "参数解析失败" + logrus.Errorf("[controller.user.Register] %v", err) + Response(c, http.StatusBadRequest, msg, nil) + return + } + logrus.Infof("[controller.user.Register] %+v:注册请求", param) + + //todo:用户名,密码,邮箱,邀请码格式要求 + + //密码加密 + passwordHash, err := common.EncodePassword(param.Password) + param.Password = passwordHash + if err != nil { + logrus.Errorf("[controller.user.Register] %v", err) + Response(c, http.StatusBadRequest, "密码Encode失败,注册失败", nil) + return + } + + err = service.Register(param) + if err != nil { + logrus.Errorf("[controller.user.Register] %v", err) + Response(c, http.StatusBadRequest, "注册失败", nil) + return + } + + logrus.Infof("[controller.user.Register] %+v:注册成功", param) + Response(c, http.StatusOK, "注册成功", nil) +} diff --git a/dao/group.go b/dao/group.go index 7fbc0df..d2dafee 100644 --- a/dao/group.go +++ b/dao/group.go @@ -1,13 +1,53 @@ package dao -import "chat/model" +import ( + "chat/model" + "gorm.io/gorm" +) -func GetMemberGroupID(UserID uint) (GroupID []uint, err error) { +func (db *DBService) GetMemberGroupID(UserID uint) (GroupID []uint, err error) { // todo: 用redis缓存 - - err = Mysql.Model(&model.GroupMember{}). + err = db.mysql.Model(&model.GroupMember{}). Select("group_id"). Where("user_id = ?", UserID). Find(&GroupID).Error return } + +func (db *DBService) CreateGroup(group *model.Group) error { + return db.mysql.Table("groups").Create(group).Error +} + +func (db DBService) GetGroupByID(id uint) (group *model.Group, err error) { + err = db.mysql.Table("groups"). + Where("id = ?", id). + First(group).Error + return +} + +func (db DBService) AddNewUserToGroup(user *model.User, group *model.Group, role int) error { + groupMember := &model.GroupMember{ + GroupID: group.ID, + UserID: user.ID, + Role: role, + } + err := db.mysql. + Table("GroupMembers"). + Create(groupMember).Error + return err +} + +func (db DBService) GetGroupUsers(groupID uint) (members *[]model.GroupMember, err error) { + err = db.mysql. + Table("GroupMembers"). + Where("group_id = ?", groupID). + Find(members).Error + return +} + +func (db DBService) IncrGroupUserNum(groupID uint) error { + err := db.mysql.Table("groups"). + Where("group_id = ?", groupID). + Update("mem_num", gorm.Expr("mem_num + 1")).Error + return err +} diff --git a/dao/init.go b/dao/init.go index 0cef467..a3567a4 100644 --- a/dao/init.go +++ b/dao/init.go @@ -7,7 +7,13 @@ import ( "gorm.io/gorm" ) -var Mysql *gorm.DB +var DB *DBService + +type DBService struct { + mysql *gorm.DB +} + +var db *gorm.DB func init() { // init mysql @@ -22,9 +28,11 @@ func init() { "%v:%v@tcp(%v:%v)/%v?charset=utf8mb4&parseTime=True&loc=Local", MysqlUsername, MysqlPassword, MysqlHost, MysqlPort, MysqlDatabase) - Mysql, err = gorm.Open(mysql.Open(dsn), &gorm.Config{ + DB.mysql, err = gorm.Open(mysql.Open(dsn), &gorm.Config{ SkipDefaultTransaction: true, // 禁用默认事务 }) + db = DB.mysql + if err != nil { panic(err) } diff --git a/dao/user.go b/dao/user.go index 07a0cc0..06371cf 100644 --- a/dao/user.go +++ b/dao/user.go @@ -1 +1,19 @@ package dao + +import "chat/model" + +func (db *DBService) CreateUser(user *model.User) error { + return db.mysql.Create(user).Error +} + +/* +func CreateUser(user *model.User) error { + err := db.Table("users").Create(user).Error + return err +} +*/ + +func (db *DBService) GetUserByName(username string) (user *model.User, err error) { + err = db.mysql.Table("users").Where("username = ?", username).First(user).Error + return +} diff --git a/go.mod b/go.mod index dc922c3..403ad9d 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/gorilla/websocket v1.5.0 github.com/sirupsen/logrus v1.9.0 github.com/spf13/viper v1.15.0 + golang.org/x/crypto v0.5.0 gorm.io/driver/mysql v1.4.7 gorm.io/gorm v1.24.6 ) @@ -41,7 +42,6 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.9 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect - golang.org/x/crypto v0.5.0 // indirect golang.org/x/net v0.7.0 // indirect golang.org/x/sys v0.5.0 // indirect golang.org/x/text v0.7.0 // indirect diff --git a/main.go b/main.go index 373b43b..ddc80bd 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "chat/controller" "fmt" + "github.com/gin-gonic/gin" "github.com/spf13/viper" ) diff --git a/model/group.go b/model/group.go index 6901a09..bb8a873 100644 --- a/model/group.go +++ b/model/group.go @@ -2,12 +2,23 @@ package model import "gorm.io/gorm" +type CreateGroupParam struct { + UserName string `json:"user_name"` + GroupName string `json:"group_name"` + Introduction string `json:"introduction"` +} + // Group 群组 type Group struct { gorm.Model - OwnerID uint `gorm:"group_id"` + OwnerID uint `gorm:"owner_id"` Name string Avatar string Introduction string - MemNum uint + MemNum uint `gorm:"mem_num"` +} + +type JoinGroupParam struct { + UserName string `json:"user_name"` + GroupID uint `json:"group_id" binding:"required"` } diff --git a/model/group_member.go b/model/group_member.go index 8bacc8b..657d7b1 100644 --- a/model/group_member.go +++ b/model/group_member.go @@ -8,6 +8,11 @@ type GroupMember struct { GroupID uint `gorm:"group_id, not null, index"` UserID uint `gorm:"user_id, not null, index"` Nickname string // 用户在当前群组的昵称 - - Role int // 用户在当前群组的role + Role int // 用户在当前群组的role } + +const ( + OWNER = 1 + ADMAIN = 2 + SPEAKER = 3 +) diff --git a/model/user.go b/model/user.go index bd6d319..460c431 100644 --- a/model/user.go +++ b/model/user.go @@ -11,3 +11,10 @@ type User struct { Email string `gorm:"uniqueIndex"` InvitationCode string } + +type RegisterParam struct { + Username string `form:"username" binding:"required"` + Password string `form:"password" binding:"required"` + Email string `form:"email" binding:"required,email"` + InvitationCode string `form:"invitation_code"` +} diff --git a/service/client/ClientManager.go b/service/client/client_manager.go similarity index 97% rename from service/client/ClientManager.go rename to service/client/client_manager.go index 3ad03ad..3d22685 100644 --- a/service/client/ClientManager.go +++ b/service/client/client_manager.go @@ -89,7 +89,7 @@ func LoginEvent(client *Client) { CM.ClientLock.Unlock() // 查库得到用户所在的roomId - rooms, err := dao.GetMemberGroupID(client.UserID) + rooms, err := dao.DB.GetMemberGroupID(client.UserID) if err != nil { return } @@ -122,7 +122,7 @@ func LoginEvent(client *Client) { // LogoutEvent 退出事件 func LogoutEvent(client *Client) { // 退出rooms - roomIDs, err := dao.GetMemberGroupID(client.UserID) + roomIDs, err := dao.DB.GetMemberGroupID(client.UserID) if err != nil { logrus.Errorf("LogoutEvent:GetMemberGroupID ClientID:%v_%v, error: %v", client.UserID, client.AppID, err) diff --git a/service/client/group.go b/service/client/group.go new file mode 100644 index 0000000..da13c8e --- /dev/null +++ b/service/client/group.go @@ -0,0 +1 @@ +package client diff --git a/service/group.go b/service/group.go new file mode 100644 index 0000000..17308d0 --- /dev/null +++ b/service/group.go @@ -0,0 +1,74 @@ +package service + +import ( + "chat/dao" + "chat/model" + "github.com/sirupsen/logrus" +) + +func CreateGroup(param *model.CreateGroupParam) error { + user, err := dao.DB.GetUserByName(param.UserName) + if err != nil { + logrus.Errorf("[service.client.CreateGroup] %v", err) + return err + } + + group := &model.Group{ + OwnerID: user.ID, + Name: param.GroupName, + Introduction: param.Introduction, + MemNum: 1, + } + //创建新群 + err = dao.DB.CreateGroup(group) + if err != nil { + logrus.Errorf("[service.group.CreateGroup] %v", err) + return err + } + //写入G-U表 + err = dao.DB.AddNewUserToGroup(user, group, model.OWNER) + if err != nil { + logrus.Errorf("[service.group.CreateGroup] %v", err) + return err + } + + return nil +} + +func JoinGroup(param *model.JoinGroupParam) error { + user, err := dao.DB.GetUserByName(param.UserName) + if err != nil { + logrus.Errorf("[service.client.JoinGroup] %v", err) + return err + } + + g, err := GroupOp.GetGroup(param.GroupID) + if err != nil { + logrus.Errorf("[service.group.JoinGroup] %v", err) + return err + } + + g.Lock() + defer g.Unlock() + + err = dao.DB.AddNewUserToGroup(user, g.Group, model.SPEAKER) + if err != nil { + logrus.Errorf("[service.group.JoinGroup] %v", err) + return err + } + err = dao.DB.IncrGroupUserNum(g.Group.ID) + if err != nil { + logrus.Errorf("[service.group.JoinGroup] %v", err) + return err + } + + groupMember := model.GroupMember{ + GroupID: g.Group.ID, + UserID: user.ID, + Role: model.SPEAKER, + } + *g.Members = append(*g.Members, groupMember) + g.Group.MemNum += 1 + + return nil +} diff --git a/service/group_operator.go b/service/group_operator.go new file mode 100644 index 0000000..3d46c4a --- /dev/null +++ b/service/group_operator.go @@ -0,0 +1,57 @@ +package service + +import ( + "chat/dao" + "chat/model" + "github.com/sirupsen/logrus" + "sync" +) + +type Group_ struct { + Group *model.Group + Members *[]model.GroupMember + sync.RWMutex +} + +type GroupOperator struct { + GroupsMap sync.Map + lock sync.Mutex +} + +var GroupOp GroupOperator + +func (gpo GroupOperator) StoreGroup(groupID uint, group *Group_) { + GroupOp.GroupsMap.Store(groupID, group) +} + +func (gpo GroupOperator) GetGroup(groupID uint) (*Group_, error) { + gpo.lock.Lock() + defer gpo.lock.Unlock() + + v, ok := gpo.GroupsMap.Load(groupID) + if !ok { + g, err := dao.DB.GetGroupByID(groupID) + if err != nil { + logrus.Errorf("[service.group_operator.GetGroup] %v", err) + return nil, err + } + v = &Group_{ + Group: g, + } + gpo.StoreGroup(groupID, v.(*Group_)) + } + + g := v.(*Group_) + g.Lock() + if g.Group.MemNum != uint(len(*g.Members)) { + var err error + g.Members, err = dao.DB.GetGroupUsers(groupID) + if err != nil { + logrus.Errorf("[service.group_operator.GetGroup] %v", err) + g.Unlock() + return nil, err + } + } + g.Unlock() + return g, nil +} diff --git a/service/user.go b/service/user.go new file mode 100644 index 0000000..c175796 --- /dev/null +++ b/service/user.go @@ -0,0 +1,23 @@ +package service + +import ( + "chat/dao" + "chat/model" + "github.com/sirupsen/logrus" +) + +func Register(param *model.RegisterParam) error { + user := &model.User{ + Username: param.Username, + Email: param.Email, + Avatar: "", + InvitationCode: param.InvitationCode, + Password: param.Password, + } + err := dao.DB.CreateUser(user) + if err != nil { + logrus.Errorf("[service.user.Register] %v", err) + return err + } + return nil +}