├── .gitignore ├── README.md ├── apiService ├── api │ ├── api.go │ ├── auth.go │ ├── dto │ │ ├── auth.go │ │ ├── base.go │ │ ├── group.go │ │ └── user.go │ ├── group.go │ └── user.go └── apiServer.go ├── common ├── config │ └── config.go ├── consts │ └── consts.go ├── log │ └── log.go ├── protoc │ ├── message.pb.go │ └── message.proto └── util │ ├── arith.go │ └── util.go ├── config.cfg ├── docs └── pics │ └── archi.png ├── graphService ├── graph │ ├── clustering.go │ └── db.go └── socialNetwork.go ├── idService ├── generator │ ├── generator.go │ ├── generator.pb.go │ ├── generator.proto │ └── generator_test.go └── idServer.go ├── messageService ├── message │ ├── conn.go │ ├── hub.go │ ├── server.go │ └── wokerPool.go └── messageServer.go └── storageService └── storage ├── auth.go ├── group.go ├── schema ├── contacts.go ├── group.go ├── message.go ├── schema.go └── user.go ├── storage.go ├── user.go └── userMessage.go /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | ssl/ 3 | apiService/apiService 4 | idService/idService 5 | messageService/messageService 6 | messageService/messageServer 7 | routerService/routerService 8 | storageService/storageService 9 | graphService/graphService 10 | logs/ 11 | child.cfg 12 | child2.cfg 13 | parent.cfg 14 | longChat-Server 15 | pprofile 16 | apiService/nohup.out 17 | idService/nohup.out 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | longChat 2 | ====== 3 | 基于websocket的聊天系统 4 | 5 | Features 6 | ====== 7 | 1.树形架构,扩展伸缩方便,可在任意节点上添加或删除子服务器,也可以将子树整体移除或添加 8 | 2.高可用性,聊天集群上每一个节点运行的都是同一份代码、同一种数据结构,单一节点down了时Moniter会选举出其子节点顶替 9 | 3.基于社交网络的聚类(Clustering),将联系频繁的用户划分到同一节点服务器,减少父节点服务器转发压力 10 | 4.使用分布式mysql和redis clusters负责核心数据存储,mongodb负责聊天数据存储 11 | 12 | Key Services 13 | ====== 14 | ### messageService 15 | 消息的主体,维护着子节点和父节点的keepalive长连接,对于messageService而言子节点是messageService服务器还是用户客户端都是一视同仁的,它只负责转发;同时messageService还会连接到父节点messageService,将自己无法处理的消息发送给父节点处理 16 | 17 | ### apiService 18 | 负责用户登录注册、获取聊天群信息等基本的restful服务,同时负责获取用户cluster以分配messageService的地址 19 | 20 | ### idService 21 | 负责给其他服务配分全局唯一19位纯数字id,id组成:1-13位为unix timestamp,14-17位为每个idService的自增计数器(步长为总idservice数,这样可以保证生成的id不重复),18-19为id类型Type 22 | 23 | ### storageService 24 | 负责与redis cluster、mongodb建立连接并传输数据;同时根据object所在组以及环型hash算法连接分布式mysql获取数据,并将数据聚类整合后返回调用者 25 | 26 | ### graphService 27 | 根据用户聊天频率、聊天对象在neo4j中建立图,根据聚类算法离线计算出每一个用户所属的群(cluster) 28 | 29 | 30 | Architecture 31 | ====== 32 | ![](./docs/pics/archi.png) 33 | 34 | 35 | Todo 36 | ====== 37 | - [ ] 聊天集群监控Moniter,单个聊天节点down掉后选举替代节点,自动负载均衡 38 | - [ ] 私聊转发服务器和群聊转发服务器分离,单独设立群聊转发服务器 39 | 40 | Dependency 41 | ====== 42 | longChat-Server采用了redis和MongoDB、Neo4j、mysql分别做cache和持久化存储,需要安装这四者的驱动 43 | -------------------------------------------------------------------------------- /apiService/api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "log" 5 | "strings" 6 | "time" 7 | 8 | "github.com/iris-contrib/sessiondb/redis" 9 | "github.com/iris-contrib/sessiondb/redis/service" 10 | "github.com/kataras/iris" 11 | iconfig "github.com/kataras/iris/config" 12 | "github.com/longchat/longChat-Server/common/config" 13 | "github.com/longchat/longChat-Server/common/consts" 14 | "github.com/longchat/longChat-Server/idService/generator" 15 | "github.com/longchat/longChat-Server/storageService/storage" 16 | ) 17 | 18 | func Iint(framework *iris.Framework, idGen *generator.IdGenerator, store *storage.Storage) { 19 | framework.Config.Gzip = true 20 | 21 | redisAddr, err := config.GetConfigString(consts.RedisAddress) 22 | if err != nil { 23 | log.Fatalf(consts.ErrGetConfigFailed(consts.RedisAddress, err)) 24 | } 25 | redisPsw, err := config.GetConfigString(consts.RedisPassword) 26 | if err != nil { 27 | log.Fatalf(consts.ErrGetConfigFailed(consts.RedisPassword, err)) 28 | } 29 | redisPrefix, err := config.GetConfigString(consts.SessionPrefix) 30 | if err != nil { 31 | log.Fatalf(consts.ErrGetConfigFailed(consts.SessionPrefix, err)) 32 | } 33 | cookie, err := config.GetConfigString(consts.SessionCookieName) 34 | if err != nil { 35 | log.Fatalf(consts.ErrGetConfigFailed(consts.SessionCookieName, err)) 36 | } 37 | framework.Config.Sessions = iconfig.Sessions{ 38 | Cookie: cookie, 39 | GcDuration: time.Duration(2) * time.Hour, 40 | } 41 | 42 | db := redis.New(service.Config{Network: service.DefaultRedisNetwork, 43 | Addr: redisAddr, 44 | Password: redisPsw, 45 | Database: "0", 46 | MaxIdle: 4, 47 | MaxActive: 4, 48 | IdleTimeout: service.DefaultRedisIdleTimeout, 49 | Prefix: redisPrefix, 50 | MaxAgeSeconds: service.DefaultRedisMaxAgeSeconds}) // optionally configure the bridge between your redis server 51 | 52 | framework.UseSessionDB(db) 53 | 54 | addrStr, err := config.GetConfigString(consts.LeafMsgServiceAddress) 55 | if err != nil { 56 | log.Fatalf(consts.ErrGetConfigFailed(consts.SessionCookieName, err)) 57 | } 58 | addrs := strings.Split(addrStr, ",") 59 | ua := UserApi{idGen: idGen, store: store, serverAddrs: addrs} 60 | ua.RegisterRoute(framework) 61 | au := AuthApi{store: store} 62 | au.RegisterRoute(framework) 63 | ga := GroupApi{idGen: idGen, store: store} 64 | ga.RegisterRoute(framework) 65 | 66 | staicPath, err := config.GetConfigString(consts.ApiServiceStaticPath) 67 | if err != nil { 68 | log.Fatalf(consts.ErrGetConfigFailed(consts.ApiServiceAddress, err)) 69 | } 70 | framework.Static("/static", staicPath, 1) 71 | } 72 | -------------------------------------------------------------------------------- /apiService/api/auth.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/hex" 6 | "fmt" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/kataras/iris" 11 | "github.com/longchat/longChat-Server/apiService/api/dto" 12 | "github.com/longchat/longChat-Server/common/config" 13 | "github.com/longchat/longChat-Server/common/consts" 14 | "github.com/longchat/longChat-Server/common/log" 15 | "github.com/longchat/longChat-Server/common/util" 16 | "github.com/longchat/longChat-Server/storageService/storage" 17 | ) 18 | 19 | type AuthApi struct { 20 | store *storage.Storage 21 | } 22 | 23 | func (au *AuthApi) RegisterRoute(framework *iris.Framework) { 24 | framework.Post("/login", au.login) 25 | framework.Post("/logout", au.login) 26 | } 27 | 28 | func getHashedPassword(raw string, salt string) string { 29 | sha := sha256.Sum256([]byte(raw + salt)) 30 | return hex.EncodeToString(sha[:]) 31 | } 32 | 33 | func newToken(id int64) (string, error) { 34 | privateToken, err := config.GetConfigString(consts.PrivateToken) 35 | if err != nil { 36 | log.ERROR.Printf(consts.ErrGetConfigFailed(consts.PrivateToken, err)) 37 | return "", err 38 | } 39 | return util.NewToken(id, privateToken, time.Hour*12), nil 40 | } 41 | 42 | func (au *AuthApi) login(c *iris.Context) { 43 | var loginReq dto.LoginReq 44 | err := c.ReadJSON(&loginReq) 45 | if err != nil { 46 | c.JSON(http.StatusBadRequest, dto.PostDataErrRsp("LoginReq")) 47 | return 48 | } 49 | user, err := au.store.GetUserByUserName(loginReq.UserName) 50 | if err != nil { 51 | log.ERROR.Printf("GetUserByUserName(%s) from storage failed!err:=%v\n", loginReq.UserName, err) 52 | c.JSON(http.StatusInternalServerError, dto.InternalErrRsp()) 53 | return 54 | } 55 | if getHashedPassword(loginReq.Password, user.Salt) != user.Password { 56 | c.JSON(http.StatusUnauthorized, dto.PasswordNotMatchErrRsp()) 57 | return 58 | } 59 | c.Session().Set("Id", user.Id) 60 | 61 | var userDto dto.UserInfo 62 | userDto.Id = fmt.Sprintf("%d", user.Id) 63 | userDto.Avatar = user.Avatar 64 | userDto.Introduce = user.Introduce 65 | userDto.NickName = user.NickName 66 | 67 | var rsp dto.LoginRsp 68 | rsp.Data.User = userDto 69 | c.JSON(http.StatusOK, &rsp) 70 | } 71 | 72 | func (au *AuthApi) logout(c *iris.Context) { 73 | 74 | } 75 | -------------------------------------------------------------------------------- /apiService/api/dto/auth.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type LoginReq struct { 4 | UserName string 5 | Password string 6 | } 7 | 8 | type LoginRsp struct { 9 | BaseRsp 10 | Data LoginData 11 | } 12 | 13 | type LoginData struct { 14 | User UserInfo 15 | } 16 | -------------------------------------------------------------------------------- /apiService/api/dto/base.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type BaseRsp struct { 8 | //StatusCode=0时表示请求成功 9 | StatusCode int 10 | //StatusCode !=0时,Error会被详细错误信息填充 11 | Error string 12 | } 13 | 14 | func PasswordNotMatchErrRsp() *BaseRsp { 15 | return &BaseRsp{ 16 | StatusCode: 1, 17 | Error: fmt.Sprintf("password or username not matched!"), 18 | } 19 | } 20 | func SessionNotFoudErrRsp() *BaseRsp { 21 | return &BaseRsp{ 22 | StatusCode: 2, 23 | Error: fmt.Sprintf("session not found!"), 24 | } 25 | } 26 | func PostDataErrRsp(dataName string) *BaseRsp { 27 | return &BaseRsp{ 28 | StatusCode: 1000, 29 | Error: fmt.Sprintf("invalid POST data(%s)", dataName), 30 | } 31 | } 32 | 33 | func ParameterErrRsp(params ...string) *BaseRsp { 34 | s := "" 35 | if len(params) > 0 { 36 | for i := range params { 37 | s += params[i] + "," 38 | } 39 | s = s[:len(s)-1] 40 | } 41 | 42 | return &BaseRsp{ 43 | StatusCode: 1001, 44 | Error: fmt.Sprintf("invalid url parameters(%s)", s), 45 | } 46 | } 47 | 48 | func InternalErrRsp() *BaseRsp { 49 | return &BaseRsp{StatusCode: 1002, Error: "internal server error"} 50 | } 51 | 52 | func SuccessRsp() *BaseRsp { 53 | var succRsp BaseRsp = BaseRsp{StatusCode: 0, Error: ""} 54 | return &succRsp 55 | } 56 | -------------------------------------------------------------------------------- /apiService/api/dto/group.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type GetGroupListRsp struct { 4 | BaseRsp 5 | Data GetGroupListData 6 | } 7 | 8 | type GetGroupListData struct { 9 | Groups []Group 10 | } 11 | 12 | type Group struct { 13 | Id string 14 | Title string 15 | Logo string 16 | Introduce string 17 | OrderIdx string 18 | } 19 | 20 | type GroupDetail struct { 21 | Id string 22 | Title string 23 | Logo string 24 | Introduce string 25 | OrderIdx string 26 | Members []UserInfo 27 | } 28 | 29 | type GetGroupDetailRsp struct { 30 | BaseRsp 31 | Data GetGroupDetailData 32 | } 33 | 34 | type GetGroupDetailData struct { 35 | Group GroupDetail 36 | } 37 | -------------------------------------------------------------------------------- /apiService/api/dto/user.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type UserInfo struct { 4 | Id string 5 | NickName string 6 | Avatar string 7 | Introduce string 8 | } 9 | type CreateUserReq struct { 10 | UserName string 11 | PassWord string 12 | Captcha string 13 | } 14 | 15 | type UpdateInfoReq struct { 16 | UserInfo 17 | } 18 | 19 | type GetUserInfoRsp struct { 20 | BaseRsp 21 | Data GetUserInfoData 22 | } 23 | 24 | type GetUserInfoData struct { 25 | User UserInfo 26 | } 27 | 28 | type GetUserServerAddrRsp struct { 29 | BaseRsp 30 | Data GetUserServerAddrData 31 | } 32 | type GetUserServerAddrData struct { 33 | Addr string 34 | } 35 | -------------------------------------------------------------------------------- /apiService/api/group.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/kataras/iris" 8 | "github.com/longchat/longChat-Server/apiService/api/dto" 9 | "github.com/longchat/longChat-Server/common/log" 10 | "github.com/longchat/longChat-Server/idService/generator" 11 | "github.com/longchat/longChat-Server/storageService/storage" 12 | ) 13 | 14 | type GroupApi struct { 15 | idGen *generator.IdGenerator 16 | store *storage.Storage 17 | } 18 | 19 | func (ga *GroupApi) RegisterRoute(framework *iris.Framework) { 20 | users := framework.Party("/groups") 21 | users.Get("", ga.getGroupList) 22 | users.Get("/:id", ga.getGroupDetail) 23 | users.Post("/:id/members/:uid", ga.joinGroup) 24 | 25 | } 26 | 27 | func (ga *GroupApi) joinGroup(c *iris.Context) { 28 | gId, err := c.ParamInt64("id") 29 | if err != nil { 30 | c.JSON(http.StatusBadRequest, dto.ParameterErrRsp("id")) 31 | return 32 | } 33 | uId, err := c.ParamInt64("uid") 34 | if err != nil { 35 | c.JSON(http.StatusBadRequest, dto.ParameterErrRsp("uid")) 36 | return 37 | } 38 | 39 | err = ga.store.AddUserGroup(uId, gId) 40 | if err != nil { 41 | c.JSON(http.StatusInternalServerError, dto.InternalErrRsp()) 42 | return 43 | } 44 | rsp := dto.SuccessRsp() 45 | c.JSON(200, &rsp) 46 | } 47 | 48 | func (ga *GroupApi) getGroupDetail(c *iris.Context) { 49 | gId, err := c.ParamInt64("id") 50 | if err != nil { 51 | c.JSON(http.StatusBadRequest, dto.ParameterErrRsp("id")) 52 | return 53 | } 54 | group, err := ga.store.GetGroupById(gId) 55 | if err != nil { 56 | log.ERROR.Printf("getGroupById(%d) from storage failed!err:=%v\n", gId, err) 57 | c.JSON(http.StatusInternalServerError, dto.InternalErrRsp()) 58 | return 59 | } 60 | users, err := ga.store.GetUsersByIds(group.Members) 61 | rsp := dto.GetGroupDetailRsp{BaseRsp: *dto.SuccessRsp()} 62 | rsp.Data.Group = dto.GroupDetail{ 63 | Id: fmt.Sprintf("%d", group.Id), 64 | Title: group.Title, 65 | Logo: group.Logo, 66 | Introduce: group.Introduce, 67 | OrderIdx: fmt.Sprintf("%d", group.Id), 68 | } 69 | var usersDto []dto.UserInfo 70 | for i := range users { 71 | data := &users[i] 72 | userDto := dto.UserInfo{ 73 | Id: fmt.Sprintf("%d", data.Id), 74 | NickName: data.NickName, 75 | Avatar: data.Avatar, 76 | Introduce: data.Introduce, 77 | } 78 | usersDto = append(usersDto, userDto) 79 | } 80 | rsp.Data.Group.Members = usersDto 81 | c.JSON(200, &rsp) 82 | } 83 | 84 | func (ga *GroupApi) getGroupList(c *iris.Context) { 85 | orderIdx, err := c.URLParamInt64("orderidx") 86 | if err != nil { 87 | orderIdx = 0 88 | } 89 | limit, err := c.URLParamInt("limit") 90 | if err != nil { 91 | limit = 15 92 | } 93 | groups, err := ga.store.GetGroupsByOrderId(orderIdx, limit) 94 | if err != nil { 95 | log.ERROR.Printf("GetGroupsByOrderIdx from storage failed!err:=%v\n", err) 96 | c.JSON(http.StatusInternalServerError, dto.InternalErrRsp()) 97 | return 98 | } 99 | groupsDto := make([]dto.Group, limit) 100 | for i := range groups { 101 | data := &groups[i] 102 | groupDto := dto.Group{ 103 | Id: fmt.Sprintf("%d", data.Id), 104 | Title: data.Title, 105 | Logo: data.Logo, 106 | Introduce: data.Introduce, 107 | OrderIdx: fmt.Sprintf("%d", data.Id), 108 | } 109 | groupsDto[i] = groupDto 110 | } 111 | rsp := dto.GetGroupListRsp{ 112 | BaseRsp: *dto.SuccessRsp(), 113 | } 114 | rsp.Data.Groups = groupsDto[:len(groups)] 115 | c.JSON(200, &rsp) 116 | } 117 | -------------------------------------------------------------------------------- /apiService/api/user.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/kataras/iris" 8 | "github.com/longchat/longChat-Server/apiService/api/dto" 9 | "github.com/longchat/longChat-Server/common/log" 10 | "github.com/longchat/longChat-Server/common/util" 11 | "github.com/longchat/longChat-Server/graphService/graph" 12 | "github.com/longchat/longChat-Server/idService/generator" 13 | "github.com/longchat/longChat-Server/storageService/storage" 14 | ) 15 | 16 | type UserApi struct { 17 | idGen *generator.IdGenerator 18 | store *storage.Storage 19 | serverAddrs []string 20 | } 21 | 22 | func (ua *UserApi) RegisterRoute(framework *iris.Framework) { 23 | users := framework.Party("/users") 24 | users.Post("", ua.createUser) 25 | users.Put("/:id", ua.updateInfo) 26 | users.Get("/:id", ua.getInfo) 27 | users.Get("/:id/serveraddr", ua.getserverAddr) 28 | } 29 | 30 | func (ua *UserApi) getserverAddr(c *iris.Context) { 31 | uid, err := c.ParamInt64("id") 32 | if err != nil { 33 | c.JSON(http.StatusBadRequest, dto.ParameterErrRsp("id")) 34 | return 35 | } 36 | clusterId, err := graph.GetClusterByUserId(uid) 37 | if err != nil { 38 | log.ERROR.Printf("get user cluster id from graph failed!err:=%v\n", uid, err) 39 | c.JSON(http.StatusInternalServerError, dto.InternalErrRsp()) 40 | return 41 | } 42 | 43 | id := clusterId % len(ua.serverAddrs) 44 | userRsp := dto.GetUserServerAddrRsp{BaseRsp: *dto.SuccessRsp()} 45 | userRsp.Data.Addr = ua.serverAddrs[id] 46 | c.JSON(http.StatusOK, &userRsp) 47 | } 48 | 49 | func (ua *UserApi) getInfo(c *iris.Context) { 50 | uid, err := c.ParamInt64("id") 51 | if err != nil { 52 | c.JSON(http.StatusBadRequest, dto.ParameterErrRsp("id")) 53 | return 54 | } 55 | user, err := ua.store.GetUserById(uid) 56 | if err != nil { 57 | log.ERROR.Printf("get usser(%d) from storage failed!err:=%v\n", uid, err) 58 | c.JSON(http.StatusInternalServerError, dto.InternalErrRsp()) 59 | return 60 | } 61 | userRsp := dto.GetUserInfoRsp{BaseRsp: *dto.SuccessRsp()} 62 | userRsp.Data.User = dto.UserInfo{ 63 | Id: fmt.Sprintf("%d", user.Id), 64 | NickName: user.NickName, 65 | Avatar: user.Avatar, 66 | Introduce: user.Introduce, 67 | } 68 | c.JSON(http.StatusOK, &userRsp) 69 | } 70 | 71 | func (ua *UserApi) updateInfo(c *iris.Context) { 72 | var infoReq dto.UpdateInfoReq 73 | err := c.ReadJSON(&infoReq) 74 | if err != nil { 75 | c.JSON(http.StatusBadRequest, dto.PostDataErrRsp("UpdateInfoReq")) 76 | return 77 | } 78 | uid, err := c.ParamInt64("id") 79 | if err != nil { 80 | c.JSON(http.StatusBadRequest, dto.ParameterErrRsp("id")) 81 | return 82 | } 83 | err = ua.store.UpdateUserInfo(uid, infoReq.NickName, infoReq.Avatar, infoReq.Introduce) 84 | if err != nil { 85 | log.ERROR.Printf("UpdateUserInfo from storage failed!err:=%v\n", err) 86 | c.JSON(http.StatusInternalServerError, dto.InternalErrRsp()) 87 | return 88 | } 89 | c.JSON(http.StatusOK, dto.SuccessRsp()) 90 | } 91 | 92 | func (ua *UserApi) createUser(c *iris.Context) { 93 | var userReq dto.CreateUserReq 94 | err := c.ReadJSON(&userReq) 95 | if err != nil { 96 | c.JSON(http.StatusBadRequest, dto.PostDataErrRsp("CreateUserReq")) 97 | return 98 | } 99 | id, err := ua.idGen.Generate(generator.GenerateReq_User) 100 | if err != nil { 101 | c.JSON(http.StatusInternalServerError, dto.InternalErrRsp()) 102 | return 103 | } 104 | salt := util.RandomString(8) 105 | hashedPassword := getHashedPassword(userReq.PassWord, salt) 106 | err = ua.store.CreateUser(id, userReq.UserName, hashedPassword, salt, c.RemoteAddr()) 107 | if err != nil { 108 | log.ERROR.Printf("CreateUser from storage failed!err:=%v\n", err) 109 | c.JSON(http.StatusInternalServerError, dto.InternalErrRsp()) 110 | return 111 | } 112 | c.JSON(http.StatusOK, dto.SuccessRsp()) 113 | } 114 | -------------------------------------------------------------------------------- /apiService/apiServer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | slog "log" 6 | 7 | "github.com/kataras/iris" 8 | "github.com/longchat/longChat-Server/apiService/api" 9 | "github.com/longchat/longChat-Server/common/config" 10 | "github.com/longchat/longChat-Server/common/consts" 11 | "github.com/longchat/longChat-Server/common/log" 12 | "github.com/longchat/longChat-Server/graphService/graph" 13 | "github.com/longchat/longChat-Server/idService/generator" 14 | "github.com/longchat/longChat-Server/storageService/storage" 15 | ) 16 | 17 | func main() { 18 | pconfig := flag.String("config", "../config.cfg", "config file") 19 | psection := flag.String("section", "dev", "section of config file to apply") 20 | flag.Parse() 21 | config.InitConfig(pconfig, psection) 22 | accPath, err := config.GetConfigString(consts.AccessLogPath) 23 | if err != nil { 24 | slog.Fatalf(consts.ErrGetConfigFailed(consts.AccessLogPath, err)) 25 | } 26 | errPath, err := config.GetConfigString(consts.ErrorLogPath) 27 | if err != nil { 28 | slog.Fatalf(consts.ErrGetConfigFailed(consts.ErrorLogPath, err)) 29 | } 30 | err = log.InitLogger(errPath, accPath, 1024, 5*1024) 31 | if err != nil { 32 | slog.Fatalf("init log failed!err:=%v\n", err) 33 | } 34 | defer log.FiniLogger() 35 | 36 | addr, err := config.GetConfigString(consts.ApiServiceAddress) 37 | if err != nil { 38 | slog.Fatalln(consts.ErrGetConfigFailed(consts.ApiServiceAddress, err)) 39 | } 40 | idGen := generator.IdGenerator{} 41 | err = idGen.Init(true) 42 | defer idGen.Close() 43 | if err != nil { 44 | slog.Fatalf("init IdGenerator failed!err:=%v\n", err) 45 | } 46 | store, err := storage.NewStorage() 47 | if err != nil { 48 | slog.Fatalf("init DB failed!err:=%v\n", err) 49 | } 50 | defer store.Close() 51 | 52 | neoUrl, err := config.GetConfigString(consts.Neo4JDbUrl) 53 | if err != nil { 54 | slog.Fatalln(consts.ErrGetConfigFailed(consts.Neo4JDbUrl, err)) 55 | } 56 | err = graph.NewDb(neoUrl) 57 | if err != nil { 58 | slog.Fatalf("init graph service failed!", err) 59 | } 60 | defer graph.FinDb() 61 | framework := iris.New() 62 | api.Iint(framework, &idGen, store) 63 | framework.Listen(addr) 64 | 65 | } 66 | -------------------------------------------------------------------------------- /common/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | slog "log" 5 | "strconv" 6 | "strings" 7 | 8 | conf "github.com/robfig/config" 9 | ) 10 | 11 | var defaultConfig *conf.Config 12 | var env map[string]string 13 | 14 | func GetConfigString(key string) (v string, err error) { 15 | v, err = defaultConfig.String(env["section"], key) 16 | return 17 | } 18 | 19 | func GetConfigIntSlice(key string) (v []int, err error) { 20 | var vs string 21 | vs, err = defaultConfig.String(env["section"], key) 22 | if err != nil { 23 | return 24 | } 25 | vsslice := strings.Split(vs, ",") 26 | var itm int64 27 | for _, vi := range vsslice { 28 | itm, err = strconv.ParseInt(vi, 10, 32) 29 | if err != nil { 30 | return 31 | } 32 | v = append(v, int(itm)) 33 | } 34 | return 35 | } 36 | 37 | func GetConfigInt(key string) (v int, err error) { 38 | v, err = defaultConfig.Int(env["section"], key) 39 | return 40 | } 41 | 42 | func GetConfigInt64(key string) (v int64, err error) { 43 | v32, er := defaultConfig.Int(env["section"], key) 44 | v = int64(v32) 45 | err = er 46 | return 47 | } 48 | 49 | func GetConfigBool(key string) (v bool, err error) { 50 | v, err = defaultConfig.Bool(env["section"], key) 51 | return 52 | } 53 | 54 | func GetConfigFloat(key string) (v float64, err error) { 55 | v, err = defaultConfig.Float(env["section"], key) 56 | return 57 | } 58 | 59 | func loadConfigFile() error { 60 | configpath, _ := env["config"] 61 | c, err := conf.ReadDefault(configpath) 62 | if err != nil { 63 | return err 64 | } 65 | defaultConfig.Merge(c) 66 | return nil 67 | } 68 | 69 | func InitConfig(pconfig *string, psection *string) { 70 | var err error 71 | if *pconfig == "" || *psection == "" { 72 | slog.Fatalf("config and section not found in Env\n") 73 | } 74 | env = map[string]string{ 75 | "config": *pconfig, 76 | "section": *psection, 77 | } 78 | configpath, _ := env["config"] 79 | defaultConfig, err = conf.ReadDefault(configpath) 80 | if err != nil { 81 | slog.Fatalf("ReadDefault failed. err=%v\n", err) 82 | } 83 | 84 | err = loadConfigFile() 85 | if err != nil { 86 | slog.Fatalf("LoadConfigFile failed!err:=%v", err) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /common/consts/consts.go: -------------------------------------------------------------------------------- 1 | package consts 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | const ( 8 | IdServiceAddress = "service.id.address" 9 | IdServiceStartIdx = "service.id.startidx" 10 | IdServiceStep = "service.id.step" 11 | 12 | MongoDbName = "mongodb.dbname" 13 | MongoDbAddr = "mongodb.addr" 14 | MongoDbUser = "mongodb.user" 15 | MongoDbPsw = "mongodb.psw" 16 | RedisAddress = "redis.address" 17 | RedisPassword = "redis.password" 18 | RedisDb = "redis.db" 19 | Neo4JDbUrl = "neo4j.url" 20 | 21 | ApiServiceAddress = "service.api.address" 22 | ApiServiceStaticPath = "service.api.staticpath" 23 | 24 | LeafMsgServiceAddress = "service.message.leaf.addrs" 25 | MsgServiceAddress = "service.message.address" 26 | ParentServiceAddress = "service.parent.address" 27 | IsLeafServer = "service.message.isleaf" 28 | ErrorLogPath = "log.error.path" 29 | AccessLogPath = "log.access.path" 30 | 31 | TlsEnable = "security.tls.enable" 32 | 33 | SessionCookieName = "session.cookie" 34 | SessionPrefix = "session.prefix" 35 | 36 | PrivateToken = "security.token" 37 | ) 38 | 39 | func ErrGetConfigFailed(configName string, err error) string { 40 | return fmt.Sprintf("get config(%s) failed!err:=%s\n", configName, err.Error()) 41 | } 42 | 43 | func ErrDialRemoteServiceFailed(addr string, err error) string { 44 | return fmt.Sprintf("dial to server(%s) failed!err:=%s\n", addr, err.Error()) 45 | } 46 | 47 | func ErrRPCCallFailed(service string, function string, err error) string { 48 | return fmt.Sprintf("rpc call(%s,%s) failed!err:=%s\n", service, function, err.Error()) 49 | } 50 | -------------------------------------------------------------------------------- /common/log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "errors" 5 | slog "log" 6 | "sync" 7 | 8 | "github.com/YueHonghui/rfw" 9 | ) 10 | 11 | var ERROR *slog.Logger 12 | var WARN *slog.Logger 13 | var INFO *slog.Logger 14 | var ACCESS *slog.Logger 15 | 16 | type bufLogger struct { 17 | writer *rfw.Rfw 18 | writerClosed bool 19 | writerlock sync.RWMutex 20 | channel chan []byte 21 | } 22 | 23 | var lwriter *bufLogger 24 | var awriter *bufLogger 25 | 26 | func InitLogger(logpath string, alogpath string, lchansize int, achansize int) error { 27 | var err error 28 | lwriter = &bufLogger{ 29 | channel: make(chan []byte, lchansize), 30 | writerClosed: false, 31 | } 32 | lwriter.writer, err = rfw.New(logpath) 33 | if err != nil { 34 | return err 35 | } 36 | awriter = &bufLogger{ 37 | channel: make(chan []byte, achansize), 38 | writerClosed: false, 39 | } 40 | awriter.writer, err = rfw.New(alogpath) 41 | if err != nil { 42 | return err 43 | } 44 | ERROR = slog.New(lwriter, "[ERR] ", slog.Ldate|slog.Ltime|slog.Lshortfile) 45 | WARN = slog.New(lwriter, "[WRN] ", slog.Ldate|slog.Ltime|slog.Lshortfile) 46 | INFO = slog.New(lwriter, "[INF] ", slog.Ldate|slog.Ltime|slog.Lshortfile) 47 | ACCESS = slog.New(awriter, "", slog.Ldate|slog.Ltime) 48 | lwriter.Writing(1) 49 | awriter.Writing(1) 50 | return nil 51 | } 52 | 53 | func (w *bufLogger) Write(p []byte) (int, error) { 54 | if !w.writerClosed { 55 | w.writerlock.RLock() 56 | if w.writerClosed { 57 | w.writerlock.RUnlock() 58 | return 0, errors.New("logger is closed") 59 | } 60 | line := make([]byte, len(p)) 61 | copy(line, p) 62 | w.channel <- line 63 | w.writerlock.RUnlock() 64 | return len(line), nil 65 | } 66 | return 0, errors.New("logger is closed") 67 | } 68 | 69 | func (w *bufLogger) Writing(nworker int) { 70 | for i := 0; i < nworker; i++ { 71 | go func() { 72 | for !w.writerClosed { 73 | l, more := <-w.channel 74 | if !more || w.writerClosed { 75 | break 76 | } else { 77 | w.writerlock.RLock() 78 | if w.writerClosed { 79 | w.writerlock.RUnlock() 80 | break 81 | } 82 | w.writer.Write(l) 83 | w.writerlock.RUnlock() 84 | } 85 | } 86 | }() 87 | } 88 | } 89 | 90 | func (w *bufLogger) Close() { 91 | w.writerlock.Lock() 92 | defer w.writerlock.Unlock() 93 | w.writer.Close() 94 | w.writerClosed = true 95 | close(w.channel) 96 | } 97 | 98 | func FiniLogger() { 99 | awriter.Close() 100 | lwriter.Close() 101 | } 102 | -------------------------------------------------------------------------------- /common/protoc/message.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. 2 | // source: message.proto 3 | // DO NOT EDIT! 4 | 5 | /* 6 | Package protoc is a generated protocol buffer package. 7 | 8 | It is generated from these files: 9 | message.proto 10 | 11 | It has these top-level messages: 12 | MessageReq 13 | OnlineReq 14 | GroupReq 15 | */ 16 | package protoc 17 | 18 | import proto "github.com/golang/protobuf/proto" 19 | import fmt "fmt" 20 | import math "math" 21 | 22 | // Reference imports to suppress errors if they are not otherwise used. 23 | var _ = proto.Marshal 24 | var _ = fmt.Errorf 25 | var _ = math.Inf 26 | 27 | // This is a compile-time assertion to ensure that this generated file 28 | // is compatible with the proto package it is being compiled against. 29 | // A compilation error at this line likely means your copy of the 30 | // proto package needs to be updated. 31 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 32 | 33 | type MessageReq_Message_ContentType int32 34 | 35 | const ( 36 | MessageReq_Message_Text MessageReq_Message_ContentType = 0 37 | MessageReq_Message_Pic MessageReq_Message_ContentType = 1 38 | MessageReq_Message_Emotion MessageReq_Message_ContentType = 2 39 | MessageReq_Message_Voice MessageReq_Message_ContentType = 3 40 | ) 41 | 42 | var MessageReq_Message_ContentType_name = map[int32]string{ 43 | 0: "Text", 44 | 1: "Pic", 45 | 2: "Emotion", 46 | 3: "Voice", 47 | } 48 | var MessageReq_Message_ContentType_value = map[string]int32{ 49 | "Text": 0, 50 | "Pic": 1, 51 | "Emotion": 2, 52 | "Voice": 3, 53 | } 54 | 55 | func (x MessageReq_Message_ContentType) String() string { 56 | return proto.EnumName(MessageReq_Message_ContentType_name, int32(x)) 57 | } 58 | func (MessageReq_Message_ContentType) EnumDescriptor() ([]byte, []int) { 59 | return fileDescriptor0, []int{0, 0, 0} 60 | } 61 | 62 | type MessageReq struct { 63 | Messages []*MessageReq_Message `protobuf:"bytes,1,rep,name=Messages,json=messages" json:"Messages,omitempty"` 64 | } 65 | 66 | func (m *MessageReq) Reset() { *m = MessageReq{} } 67 | func (m *MessageReq) String() string { return proto.CompactTextString(m) } 68 | func (*MessageReq) ProtoMessage() {} 69 | func (*MessageReq) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } 70 | 71 | func (m *MessageReq) GetMessages() []*MessageReq_Message { 72 | if m != nil { 73 | return m.Messages 74 | } 75 | return nil 76 | } 77 | 78 | type MessageReq_Message struct { 79 | Id []byte `protobuf:"bytes,1,opt,name=Id,json=id,proto3" json:"Id,omitempty"` 80 | From []byte `protobuf:"bytes,2,opt,name=From,json=from,proto3" json:"From,omitempty"` 81 | To []byte `protobuf:"bytes,3,opt,name=To,json=to,proto3" json:"To,omitempty"` 82 | Content string `protobuf:"bytes,4,opt,name=Content,json=content" json:"Content,omitempty"` 83 | Type MessageReq_Message_ContentType `protobuf:"varint,5,opt,name=Type,json=type,enum=protoc.MessageReq_Message_ContentType" json:"Type,omitempty"` 84 | IsGroupMessage bool `protobuf:"varint,6,opt,name=IsGroupMessage,json=isGroupMessage" json:"IsGroupMessage,omitempty"` 85 | } 86 | 87 | func (m *MessageReq_Message) Reset() { *m = MessageReq_Message{} } 88 | func (m *MessageReq_Message) String() string { return proto.CompactTextString(m) } 89 | func (*MessageReq_Message) ProtoMessage() {} 90 | func (*MessageReq_Message) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0, 0} } 91 | 92 | type OnlineReq struct { 93 | Items []*OnlineReq_Item `protobuf:"bytes,1,rep,name=Items,json=items" json:"Items,omitempty"` 94 | } 95 | 96 | func (m *OnlineReq) Reset() { *m = OnlineReq{} } 97 | func (m *OnlineReq) String() string { return proto.CompactTextString(m) } 98 | func (*OnlineReq) ProtoMessage() {} 99 | func (*OnlineReq) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } 100 | 101 | func (m *OnlineReq) GetItems() []*OnlineReq_Item { 102 | if m != nil { 103 | return m.Items 104 | } 105 | return nil 106 | } 107 | 108 | type OnlineReq_Item struct { 109 | Id []byte `protobuf:"bytes,1,opt,name=Id,json=id,proto3" json:"Id,omitempty"` 110 | IsOnline bool `protobuf:"varint,2,opt,name=IsOnline,json=isOnline" json:"IsOnline,omitempty"` 111 | IsGroup bool `protobuf:"varint,3,opt,name=IsGroup,json=isGroup" json:"IsGroup,omitempty"` 112 | } 113 | 114 | func (m *OnlineReq_Item) Reset() { *m = OnlineReq_Item{} } 115 | func (m *OnlineReq_Item) String() string { return proto.CompactTextString(m) } 116 | func (*OnlineReq_Item) ProtoMessage() {} 117 | func (*OnlineReq_Item) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1, 0} } 118 | 119 | type GroupReq struct { 120 | Groups []*GroupReq_Group `protobuf:"bytes,1,rep,name=Groups,json=groups" json:"Groups,omitempty"` 121 | } 122 | 123 | func (m *GroupReq) Reset() { *m = GroupReq{} } 124 | func (m *GroupReq) String() string { return proto.CompactTextString(m) } 125 | func (*GroupReq) ProtoMessage() {} 126 | func (*GroupReq) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } 127 | 128 | func (m *GroupReq) GetGroups() []*GroupReq_Group { 129 | if m != nil { 130 | return m.Groups 131 | } 132 | return nil 133 | } 134 | 135 | type GroupReq_Group struct { 136 | Id []byte `protobuf:"bytes,1,opt,name=Id,json=id,proto3" json:"Id,omitempty"` 137 | Title string `protobuf:"bytes,2,opt,name=Title,json=title" json:"Title,omitempty"` 138 | Logo string `protobuf:"bytes,3,opt,name=Logo,json=logo" json:"Logo,omitempty"` 139 | Members [][]byte `protobuf:"bytes,4,rep,name=Members,json=members,proto3" json:"Members,omitempty"` 140 | Introduce string `protobuf:"bytes,5,opt,name=Introduce,json=introduce" json:"Introduce,omitempty"` 141 | } 142 | 143 | func (m *GroupReq_Group) Reset() { *m = GroupReq_Group{} } 144 | func (m *GroupReq_Group) String() string { return proto.CompactTextString(m) } 145 | func (*GroupReq_Group) ProtoMessage() {} 146 | func (*GroupReq_Group) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2, 0} } 147 | 148 | func init() { 149 | proto.RegisterType((*MessageReq)(nil), "protoc.MessageReq") 150 | proto.RegisterType((*MessageReq_Message)(nil), "protoc.MessageReq.Message") 151 | proto.RegisterType((*OnlineReq)(nil), "protoc.OnlineReq") 152 | proto.RegisterType((*OnlineReq_Item)(nil), "protoc.OnlineReq.Item") 153 | proto.RegisterType((*GroupReq)(nil), "protoc.GroupReq") 154 | proto.RegisterType((*GroupReq_Group)(nil), "protoc.GroupReq.Group") 155 | proto.RegisterEnum("protoc.MessageReq_Message_ContentType", MessageReq_Message_ContentType_name, MessageReq_Message_ContentType_value) 156 | } 157 | 158 | func init() { proto.RegisterFile("message.proto", fileDescriptor0) } 159 | 160 | var fileDescriptor0 = []byte{ 161 | // 393 bytes of a gzipped FileDescriptorProto 162 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x74, 0x52, 0x4d, 0x6b, 0xdb, 0x40, 163 | 0x10, 0xad, 0xbe, 0x77, 0xc7, 0xad, 0x10, 0x4b, 0x29, 0x42, 0xf4, 0x50, 0x74, 0x30, 0x3e, 0x14, 164 | 0x1d, 0x5c, 0x28, 0xa5, 0xd7, 0xd2, 0x16, 0x81, 0x4d, 0xc2, 0x62, 0x72, 0x8f, 0xe5, 0x8d, 0x59, 165 | 0xb0, 0xb4, 0x8e, 0xb4, 0x86, 0xf8, 0x17, 0xe4, 0x17, 0xe5, 0x96, 0xbf, 0x95, 0x7b, 0xf6, 0x4b, 166 | 0x4e, 0x82, 0xc9, 0x45, 0x9a, 0xf7, 0xf6, 0xcd, 0xe8, 0xed, 0x1b, 0xc1, 0xa7, 0x96, 0x0d, 0xc3, 167 | 0xf5, 0x96, 0x55, 0xfb, 0x5e, 0x48, 0x41, 0x62, 0xf3, 0x6a, 0xca, 0x47, 0x1f, 0x60, 0x69, 0x4f, 168 | 0x28, 0xbb, 0x25, 0x3f, 0x01, 0x39, 0x34, 0xe4, 0xde, 0xb7, 0x60, 0x36, 0x99, 0x17, 0xb6, 0xa1, 169 | 0xa9, 0x5e, 0x54, 0xa7, 0x12, 0xb9, 0x99, 0x43, 0xf1, 0xe4, 0x41, 0xe2, 0x58, 0x92, 0x82, 0x5f, 170 | 0x6f, 0x54, 0xb7, 0x37, 0xfb, 0x48, 0x7d, 0xbe, 0x21, 0x04, 0xc2, 0x7f, 0xbd, 0x68, 0x73, 0xdf, 171 | 0x30, 0xe1, 0x8d, 0xaa, 0xb5, 0x66, 0x25, 0xf2, 0xc0, 0x6a, 0x94, 0x9d, 0x1c, 0x92, 0x3f, 0xa2, 172 | 0x93, 0xac, 0x93, 0x79, 0xa8, 0x48, 0x4c, 0x93, 0xc6, 0x42, 0xf2, 0x1b, 0xc2, 0xd5, 0x71, 0xcf, 173 | 0xf2, 0x48, 0xd1, 0xe9, 0x7c, 0xfa, 0xbe, 0x9b, 0xca, 0x0d, 0xd0, 0x6a, 0x1a, 0x4a, 0xf5, 0x24, 174 | 0x53, 0x48, 0xeb, 0xe1, 0x7f, 0x2f, 0x0e, 0x7b, 0xa7, 0xc9, 0x63, 0x35, 0x05, 0xd1, 0x94, 0xbf, 175 | 0x61, 0xcb, 0x5f, 0x30, 0x79, 0xd5, 0x4c, 0x90, 0xfa, 0x24, 0xbb, 0x93, 0xd9, 0x07, 0x92, 0x40, 176 | 0x70, 0xc9, 0x9b, 0xcc, 0x23, 0x13, 0x48, 0xfe, 0xb6, 0x42, 0x72, 0xd1, 0x65, 0x3e, 0xc1, 0x10, 177 | 0x5d, 0x09, 0xde, 0xb0, 0x2c, 0x28, 0xef, 0x3d, 0xc0, 0x17, 0xdd, 0x8e, 0x77, 0x26, 0xbd, 0xef, 178 | 0x10, 0xd5, 0x92, 0xb5, 0x63, 0x74, 0x5f, 0x46, 0xb3, 0x27, 0x45, 0xa5, 0x8f, 0x69, 0xc4, 0xb5, 179 | 0xa8, 0x58, 0x40, 0xa8, 0xe1, 0x59, 0x5e, 0x05, 0xa0, 0x7a, 0xb0, 0x2d, 0x26, 0x33, 0x44, 0x11, 180 | 0x77, 0x58, 0xe7, 0xe4, 0x6e, 0x64, 0xc2, 0x43, 0x34, 0x71, 0x57, 0x29, 0x1f, 0x3c, 0x40, 0xa6, 181 | 0xd2, 0x46, 0x2a, 0x88, 0x4d, 0x7d, 0xe6, 0x64, 0x54, 0xb8, 0x22, 0xde, 0x1a, 0x55, 0x71, 0x84, 182 | 0xc8, 0x10, 0x67, 0x5e, 0x3e, 0x43, 0xb4, 0xe2, 0x72, 0x67, 0x8d, 0x60, 0x1a, 0x49, 0x0d, 0xf4, 183 | 0x46, 0x17, 0x62, 0x6b, 0xf7, 0x87, 0x69, 0xb8, 0x53, 0xb5, 0x76, 0xb6, 0x64, 0xed, 0x9a, 0xf5, 184 | 0x83, 0xda, 0x60, 0xa0, 0xda, 0x93, 0xd6, 0x42, 0xf2, 0x15, 0x70, 0xdd, 0xc9, 0x5e, 0x6c, 0x0e, 185 | 0x8d, 0x5d, 0x23, 0xa6, 0x98, 0x8f, 0xc4, 0xda, 0xfe, 0x88, 0x3f, 0x9e, 0x03, 0x00, 0x00, 0xff, 186 | 0xff, 0xf2, 0x63, 0x9f, 0xd3, 0xa0, 0x02, 0x00, 0x00, 187 | } 188 | -------------------------------------------------------------------------------- /common/protoc/message.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package protoc; 4 | 5 | message MessageReq { 6 | message Message { 7 | bytes Id=1; 8 | bytes From=2; 9 | bytes To=3; 10 | string Content=4; 11 | enum ContentType { 12 | Text = 0; 13 | Pic = 1; 14 | Emotion = 2; 15 | Voice = 3; 16 | } 17 | ContentType Type=5; 18 | bool IsGroupMessage=6; 19 | } 20 | repeated Message Messages = 1; 21 | } 22 | 23 | message OnlineReq{ 24 | message Item { 25 | bytes Id=1; 26 | bool IsOnline=2; 27 | bool IsGroup=3; 28 | } 29 | repeated Item Items = 1; 30 | } 31 | 32 | 33 | message GroupReq{ 34 | message Group { 35 | bytes Id=1; 36 | string Title=2; 37 | string Logo=3; 38 | repeated bytes Members=4; 39 | string Introduce=5; 40 | } 41 | repeated Group Groups = 1; 42 | } 43 | -------------------------------------------------------------------------------- /common/util/arith.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "unsafe" 5 | ) 6 | 7 | func Int2Bytes(i int64) []byte { 8 | b := (*[8]byte)(unsafe.Pointer(&i)) 9 | return (*b)[:] 10 | } 11 | 12 | func Bytes2Int(b []byte) int64 { 13 | if len(b) != 8 { 14 | return 0 15 | } 16 | i := (*int64)(unsafe.Pointer(&b[0])) 17 | return *i 18 | } 19 | -------------------------------------------------------------------------------- /common/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/base64" 6 | "fmt" 7 | "math/rand" 8 | "strconv" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | func RandomString(num int) string { 14 | rand.Seed(time.Now().UTC().UnixNano()) 15 | var temp []byte = make([]byte, num) 16 | for i := 0; i < num; i++ { 17 | temp[i] = byte(33 + rand.Intn(93)) 18 | } 19 | return string(temp[:]) 20 | } 21 | 22 | func RandomInt(min int, max int) int { 23 | rand.Seed(time.Now().UTC().UnixNano()) 24 | return min + rand.Intn(max-min) 25 | } 26 | 27 | func NewToken(id int64, privateToken string, expireIn time.Duration) string { 28 | idBytes := []byte(fmt.Sprintf("%d", id)) 29 | idEn := base64.URLEncoding.EncodeToString(idBytes) 30 | expireBytes := []byte(fmt.Sprintf("%d", time.Now().UnixNano()+int64(expireIn))) 31 | expireEn := base64.URLEncoding.EncodeToString(expireBytes) 32 | sha := sha256.Sum256([]byte(idEn + expireEn + privateToken)) 33 | return idEn + ":" + expireEn + ":" + base64.URLEncoding.EncodeToString(sha[:]) 34 | } 35 | 36 | func DecodeToken(accessToken string, privateToken string) (id int64, expireAt int64, isValid bool) { 37 | tokens := strings.Split(accessToken, ":") 38 | if len(tokens) < 3 { 39 | return 40 | } 41 | cksum := sha256.Sum256([]byte(tokens[0] + tokens[1] + privateToken)) 42 | if base64.URLEncoding.EncodeToString(cksum[:]) != tokens[2] { 43 | return 44 | } 45 | ids, err := base64.URLEncoding.DecodeString(tokens[0]) 46 | if err != nil { 47 | return 48 | } 49 | expireAts, err := base64.URLEncoding.DecodeString(tokens[1]) 50 | if err != nil { 51 | return 52 | } 53 | id, err = strconv.ParseInt(string(ids), 10, 64) 54 | if err != nil { 55 | return 56 | } 57 | expireAt, err = strconv.ParseInt(string(expireAts), 10, 64) 58 | if err != nil { 59 | return 60 | } 61 | isValid = true 62 | return 63 | } 64 | -------------------------------------------------------------------------------- /config.cfg: -------------------------------------------------------------------------------- 1 | [dev] 2 | service.api.address=0.0.0.0:9090 3 | service.api.staticpath=/home/longxboy/go/mkProject/src/github.com/longchat/longChat-Client/ 4 | 5 | service.id.address=127.0.0.1:9091 6 | service.id.startidx=0 7 | service.id.step=1 8 | 9 | service.parent.address=127.0.0.1:9092 10 | service.message.address=0.0.0.0:9094 11 | service.message.isleaf=true 12 | service.message.leaf.addrs=127.0.0.1:9093,127.0.0.1:9094 13 | 14 | mongodb.dbname=longchat 15 | mongodb.psw=123456 16 | mongodb.user=longchat 17 | mongodb.addr=127.0.0.1:27017 18 | 19 | mysql.db.user=root 20 | mysql.db.password=123456 21 | mysql.db.name=longchat 22 | mysql.group.0.cluster.0=127.0.0.1:3306 23 | mysql.group.0.cluster.1=127.0.0.1:3307 24 | mysql.group.1.cluster.0=127.0.0.1:3308 25 | 26 | 27 | redis.address=127.0.0.1:7000,127.0.0.1:7001,127.0.0.1:7002 28 | redis.password=123456 29 | redis.db=0 30 | 31 | neo4j.url=http://localhost:7474 32 | 33 | log.error.path=/home/longxboy/go/mkProject/src/github.com/longchat/longChat-Server/logs/err/e 34 | log.access.path=/home/longxboy/go/mkProject/src/github.com/longchat/longChat-Server/logs/acc/a 35 | 36 | security.tls.enable=false 37 | security.tls.certfile="" 38 | security.tls.keyfile="" 39 | security.token="1234567890" 40 | 41 | session.cookie=lcSess 42 | session.prefix=lc.sess. 43 | -------------------------------------------------------------------------------- /docs/pics/archi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/longchat/longChat-Server/145a72eb30e759e36615f02ac69750e5c466bb10/docs/pics/archi.png -------------------------------------------------------------------------------- /graphService/graph/clustering.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | import ( 4 | slog "log" 5 | 6 | _ "github.com/go-cq/cq" 7 | ) 8 | 9 | type Relation struct { 10 | id int 11 | score int 12 | } 13 | type Cluster struct { 14 | id int 15 | SubCluster []*Cluster 16 | ParaRelation []Relation 17 | FatherCluster *Cluster 18 | IsClosed bool 19 | } 20 | 21 | var clusters []Cluster 22 | var oldClusters []Cluster 23 | var newClusters []Cluster 24 | 25 | func GetClusterByUserId(userid int64) (int, error) { 26 | cypher := "match (p) where u.UserId={0} return p.GroupId" 27 | rows, err := graphDb.Query(cypher, userid) 28 | if err != nil { 29 | return 0, err 30 | } 31 | rows.Next() 32 | var groupId int 33 | err = rows.Scan(&groupId) 34 | if err != nil { 35 | return 0, err 36 | } 37 | return groupId, err 38 | } 39 | 40 | func Clustering() { 41 | cypher := `match (p)-[l:Like]-(p2) return p.id,collect(p2.id) as nodes,collect(l.counts) as nodeScore order by p.id asc` 42 | rows, err := graphDb.Query(cypher) 43 | if err != nil { 44 | slog.Fatalln("error querying movie:", err) 45 | } 46 | defer rows.Close() 47 | clusters = make([]Cluster, 0, 1500) 48 | baseId := -1 49 | cmap := make(map[int]bool) 50 | for rows.Next() { 51 | var cid int 52 | var pids []int 53 | var pscores []int 54 | err := rows.Scan(&cid, &pids, &pscores) 55 | if err != nil { 56 | slog.Fatalf("scan error", err) 57 | } 58 | _, isok := cmap[cid] 59 | if isok || cid > 1144 { 60 | slog.Fatalf("duplicate:%d %v \n", cid, pids) 61 | } else { 62 | cmap[cid] = true 63 | } 64 | if baseId == -1 { 65 | baseId = cid 66 | } 67 | var relations []Relation 68 | for i := range pids { 69 | relations = append(relations, Relation{id: pids[i] - baseId, score: pscores[i]}) 70 | } 71 | oneCluster := Cluster{id: len(clusters), ParaRelation: relations} 72 | clusters = append(clusters, oneCluster) 73 | } 74 | oldClusters = make([]Cluster, len(clusters)) 75 | newClusters = make([]Cluster, 0, 700) 76 | idx := 0 77 | for idx < 6 { 78 | idx++ 79 | for i := range clusters { 80 | if clusters[i].FatherCluster != nil { 81 | continue 82 | } 83 | if clusters[i].IsClosed { 84 | put(&clusters[i]) 85 | } 86 | if clusters[i].SubCluster == nil { 87 | candidate := Relation{score: -1, id: -1} 88 | for j := range clusters[i].ParaRelation { 89 | p := clusters[i].ParaRelation[j] 90 | if p.score > candidate.score && clusters[p.id].FatherCluster == nil { 91 | candidate = p 92 | } 93 | } 94 | if candidate.id == -1 { 95 | for j := range clusters[i].ParaRelation { 96 | p := clusters[i].ParaRelation[j] 97 | if p.score > candidate.score { 98 | candidate = p 99 | } 100 | } 101 | join(clusters[candidate.id].FatherCluster, &clusters[i]) 102 | } else { 103 | merge(&clusters[i], &clusters[candidate.id]) 104 | } 105 | } else { 106 | candidate := Relation{score: -1, id: -1} 107 | candidate2 := Relation{score: -1, id: -1} 108 | calculateParaWithCandicate(&clusters[i], &candidate, &candidate2) 109 | if candidate.id != -1 { 110 | calculatePara(&clusters[candidate.id]) 111 | merge(&clusters[i], &clusters[candidate.id]) 112 | } else if candidate2.id != -1 { 113 | join(clusters[candidate2.id].FatherCluster, &clusters[i]) 114 | } else { 115 | clusters[i].IsClosed = true 116 | put(&clusters[i]) 117 | } 118 | } 119 | } 120 | oldClusters = make([]Cluster, len(clusters)) 121 | copy(oldClusters, clusters) 122 | clusters = make([]Cluster, len(newClusters)) 123 | copy(clusters, newClusters) 124 | newClusters = make([]Cluster, 0, len(newClusters)/2) 125 | } 126 | 127 | for i, data := range clusters { 128 | setCluster(i, &data) 129 | } 130 | } 131 | 132 | var sum int 133 | 134 | func setCluster(i int, c *Cluster) { 135 | if c.SubCluster == nil { 136 | graphDb.Query("match (p) where p.id={0} set p.GroupId={1}", c.id, i) 137 | //fmt.Printf("%d ", c.id) 138 | sum++ 139 | } else { 140 | for _, data := range c.SubCluster { 141 | setCluster(i, data) 142 | } 143 | } 144 | return 145 | } 146 | 147 | func calculateParaWithCandicate(c *Cluster, candidate *Relation, candidate2 *Relation) { 148 | paraRelation := make(map[int]Relation) 149 | for j := range c.SubCluster { 150 | sub := c.SubCluster[j] 151 | 152 | for k := range sub.ParaRelation { 153 | fId := oldClusters[sub.ParaRelation[k].id].FatherCluster.id 154 | if fId != c.id { 155 | r, isok := paraRelation[fId] 156 | if !isok { 157 | r = Relation{id: fId, score: sub.ParaRelation[k].score} 158 | } else { 159 | r.score = sub.ParaRelation[k].score + r.score 160 | } 161 | paraRelation[fId] = r 162 | if r.score > candidate.score { 163 | if clusters[fId].FatherCluster == nil { 164 | *candidate = r 165 | } 166 | *candidate2 = r 167 | } 168 | } 169 | } 170 | } 171 | for _, v := range paraRelation { 172 | c.ParaRelation = append(c.ParaRelation, v) 173 | } 174 | } 175 | 176 | func calculatePara(c *Cluster) { 177 | paraRelation := make(map[int]Relation) 178 | for j := range c.SubCluster { 179 | sub := c.SubCluster[j] 180 | for k := range sub.ParaRelation { 181 | fId := oldClusters[sub.ParaRelation[k].id].FatherCluster.id 182 | if fId != c.id { 183 | r, isok := paraRelation[fId] 184 | if !isok { 185 | r = Relation{id: fId, score: sub.ParaRelation[k].score} 186 | } else { 187 | r.score = sub.ParaRelation[k].score + r.score 188 | } 189 | paraRelation[fId] = r 190 | } 191 | } 192 | } 193 | for _, v := range paraRelation { 194 | c.ParaRelation = append(c.ParaRelation, v) 195 | } 196 | } 197 | 198 | //append one cluster to another big cluster 199 | func join(father *Cluster, child *Cluster) { 200 | father.SubCluster = append(father.SubCluster, child) 201 | child.FatherCluster = father 202 | } 203 | 204 | //merge two clusters into one big cluster 205 | func merge(ca *Cluster, cb *Cluster) { 206 | father := Cluster{ 207 | id: len(newClusters), 208 | SubCluster: []*Cluster{ca, cb}, 209 | } 210 | newClusters = append(newClusters, father) 211 | ca.FatherCluster = &newClusters[father.id] 212 | cb.FatherCluster = &newClusters[father.id] 213 | } 214 | 215 | func put(c *Cluster) { 216 | father := Cluster{ 217 | id: len(newClusters), 218 | SubCluster: []*Cluster{c}, 219 | ParaRelation: c.ParaRelation, 220 | } 221 | newClusters = append(newClusters, father) 222 | c.FatherCluster = &newClusters[father.id] 223 | } 224 | -------------------------------------------------------------------------------- /graphService/graph/db.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | import ( 4 | _ "github.com/go-cq/cq" 5 | "github.com/jmoiron/sqlx" 6 | ) 7 | 8 | var graphDb *sqlx.DB 9 | 10 | func NewDb(neo4jURL string) error { 11 | var err error 12 | graphDb, err = sqlx.Connect("neo4j-cypher", neo4jURL) 13 | if err != nil { 14 | return err 15 | } 16 | return nil 17 | } 18 | 19 | func FinDb() { 20 | graphDb.Close() 21 | } 22 | -------------------------------------------------------------------------------- /graphService/socialNetwork.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | slog "log" 6 | 7 | "github.com/longchat/longChat-Server/common/config" 8 | "github.com/longchat/longChat-Server/common/consts" 9 | "github.com/longchat/longChat-Server/graphService/graph" 10 | ) 11 | 12 | func main() { 13 | pconfig := flag.String("config", "../config.cfg", "config file") 14 | psection := flag.String("section", "dev", "section of config file to apply") 15 | flag.Parse() 16 | config.InitConfig(pconfig, psection) 17 | url, err := config.GetConfigString(consts.Neo4JDbUrl) 18 | if err != nil { 19 | slog.Fatalln("get GetConfigString failed!:", consts.Neo4JDbUrl) 20 | } 21 | graph.NewDb(url) 22 | defer graph.FinDb() 23 | graph.Clustering() 24 | } 25 | -------------------------------------------------------------------------------- /idService/generator/generator.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "errors" 5 | "sync/atomic" 6 | "time" 7 | 8 | "github.com/longchat/longChat-Server/common/config" 9 | "github.com/longchat/longChat-Server/common/consts" 10 | "github.com/longchat/longChat-Server/common/log" 11 | 12 | "golang.org/x/net/context" 13 | "google.golang.org/grpc" 14 | ) 15 | 16 | type IdGenerator struct { 17 | conn *grpc.ClientConn 18 | rpcEnabled bool 19 | client IdGeneratorClient 20 | 21 | counters [100]int64 22 | step int64 23 | max int64 24 | } 25 | 26 | func (is *IdGenerator) Init(rpcEnabled bool) error { 27 | is.rpcEnabled = rpcEnabled 28 | if rpcEnabled { 29 | address, err := config.GetConfigString(consts.IdServiceAddress) 30 | if err != nil { 31 | return errors.New(consts.ErrGetConfigFailed(consts.IdServiceAddress, err)) 32 | } 33 | is.conn, err = grpc.Dial(address, grpc.WithInsecure()) 34 | if err != nil { 35 | return errors.New(consts.ErrGetConfigFailed(consts.IdServiceAddress, err)) 36 | } 37 | is.client = NewIdGeneratorClient(is.conn) 38 | } 39 | 40 | startIdx, err := config.GetConfigInt64(consts.IdServiceStartIdx) 41 | if err != nil { 42 | return errors.New(consts.ErrGetConfigFailed(consts.IdServiceStartIdx, err)) 43 | } 44 | for i := range is.counters { 45 | is.counters[i] = startIdx 46 | } 47 | 48 | step, err := config.GetConfigInt64(consts.IdServiceStep) 49 | if err != nil { 50 | return errors.New(consts.ErrGetConfigFailed(consts.IdServiceStep, err)) 51 | } 52 | is.step = step 53 | is.max = 10000 - 10000%step 54 | return nil 55 | } 56 | 57 | func (is *IdGenerator) Close() { 58 | if is.conn != nil { 59 | is.conn.Close() 60 | } 61 | } 62 | 63 | func (is *IdGenerator) Generate(idType GenerateReq_IdType) (int64, error) { 64 | if is.rpcEnabled { 65 | reply, err := is.client.Generate(context.Background(), &GenerateReq{idType}) 66 | if err != nil { 67 | log.ERROR.Printf(consts.ErrRPCCallFailed("idService", "Generate", err)) 68 | return 0, err 69 | } 70 | return reply.Id, nil 71 | } else { 72 | return is.generate(idType, &(is.counters[idType]), is.step), nil 73 | } 74 | } 75 | 76 | func (is *IdGenerator) generate(idType GenerateReq_IdType, idx *int64, step int64) int64 { 77 | //共19位,前13位是时间戳,中间4位是计数器,后2位是类型Id 78 | return (int64(time.Now().UnixNano())/1000000)*1000000 + (atomic.AddInt64(idx, is.step)%is.max)*100 + int64(idType) 79 | } 80 | -------------------------------------------------------------------------------- /idService/generator/generator.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. 2 | // source: generator.proto 3 | // DO NOT EDIT! 4 | 5 | /* 6 | Package generator is a generated protocol buffer package. 7 | 8 | It is generated from these files: 9 | generator.proto 10 | 11 | It has these top-level messages: 12 | GenerateReq 13 | GenerateRsp 14 | */ 15 | package generator 16 | 17 | import proto "github.com/golang/protobuf/proto" 18 | import fmt "fmt" 19 | import math "math" 20 | 21 | import ( 22 | context "golang.org/x/net/context" 23 | grpc "google.golang.org/grpc" 24 | ) 25 | 26 | // Reference imports to suppress errors if they are not otherwise used. 27 | var _ = proto.Marshal 28 | var _ = fmt.Errorf 29 | var _ = math.Inf 30 | 31 | // This is a compile-time assertion to ensure that this generated file 32 | // is compatible with the proto package it is being compiled against. 33 | // A compilation error at this line likely means your copy of the 34 | // proto package needs to be updated. 35 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 36 | 37 | type GenerateReq_IdType int32 38 | 39 | const ( 40 | GenerateReq_User GenerateReq_IdType = 0 41 | GenerateReq_Group GenerateReq_IdType = 1 42 | GenerateReq_UserMessage GenerateReq_IdType = 2 43 | GenerateReq_GroupMessage GenerateReq_IdType = 3 44 | ) 45 | 46 | var GenerateReq_IdType_name = map[int32]string{ 47 | 0: "User", 48 | 1: "Group", 49 | 2: "UserMessage", 50 | 3: "GroupMessage", 51 | } 52 | var GenerateReq_IdType_value = map[string]int32{ 53 | "User": 0, 54 | "Group": 1, 55 | "UserMessage": 2, 56 | "GroupMessage": 3, 57 | } 58 | 59 | func (x GenerateReq_IdType) String() string { 60 | return proto.EnumName(GenerateReq_IdType_name, int32(x)) 61 | } 62 | func (GenerateReq_IdType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0, 0} } 63 | 64 | type GenerateReq struct { 65 | Type GenerateReq_IdType `protobuf:"varint,1,opt,name=Type,json=type,enum=generator.GenerateReq_IdType" json:"Type,omitempty"` 66 | } 67 | 68 | func (m *GenerateReq) Reset() { *m = GenerateReq{} } 69 | func (m *GenerateReq) String() string { return proto.CompactTextString(m) } 70 | func (*GenerateReq) ProtoMessage() {} 71 | func (*GenerateReq) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } 72 | 73 | type GenerateRsp struct { 74 | Id int64 `protobuf:"varint,1,opt,name=Id,json=id" json:"Id,omitempty"` 75 | } 76 | 77 | func (m *GenerateRsp) Reset() { *m = GenerateRsp{} } 78 | func (m *GenerateRsp) String() string { return proto.CompactTextString(m) } 79 | func (*GenerateRsp) ProtoMessage() {} 80 | func (*GenerateRsp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } 81 | 82 | func init() { 83 | proto.RegisterType((*GenerateReq)(nil), "generator.GenerateReq") 84 | proto.RegisterType((*GenerateRsp)(nil), "generator.GenerateRsp") 85 | proto.RegisterEnum("generator.GenerateReq_IdType", GenerateReq_IdType_name, GenerateReq_IdType_value) 86 | } 87 | 88 | // Reference imports to suppress errors if they are not otherwise used. 89 | var _ context.Context 90 | var _ grpc.ClientConn 91 | 92 | // This is a compile-time assertion to ensure that this generated file 93 | // is compatible with the grpc package it is being compiled against. 94 | const _ = grpc.SupportPackageIsVersion3 95 | 96 | // Client API for IdGenerator service 97 | 98 | type IdGeneratorClient interface { 99 | Generate(ctx context.Context, in *GenerateReq, opts ...grpc.CallOption) (*GenerateRsp, error) 100 | } 101 | 102 | type idGeneratorClient struct { 103 | cc *grpc.ClientConn 104 | } 105 | 106 | func NewIdGeneratorClient(cc *grpc.ClientConn) IdGeneratorClient { 107 | return &idGeneratorClient{cc} 108 | } 109 | 110 | func (c *idGeneratorClient) Generate(ctx context.Context, in *GenerateReq, opts ...grpc.CallOption) (*GenerateRsp, error) { 111 | out := new(GenerateRsp) 112 | err := grpc.Invoke(ctx, "/generator.IdGenerator/Generate", in, out, c.cc, opts...) 113 | if err != nil { 114 | return nil, err 115 | } 116 | return out, nil 117 | } 118 | 119 | // Server API for IdGenerator service 120 | 121 | type IdGeneratorServer interface { 122 | Generate(context.Context, *GenerateReq) (*GenerateRsp, error) 123 | } 124 | 125 | func RegisterIdGeneratorServer(s *grpc.Server, srv IdGeneratorServer) { 126 | s.RegisterService(&_IdGenerator_serviceDesc, srv) 127 | } 128 | 129 | func _IdGenerator_Generate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 130 | in := new(GenerateReq) 131 | if err := dec(in); err != nil { 132 | return nil, err 133 | } 134 | if interceptor == nil { 135 | return srv.(IdGeneratorServer).Generate(ctx, in) 136 | } 137 | info := &grpc.UnaryServerInfo{ 138 | Server: srv, 139 | FullMethod: "/generator.IdGenerator/Generate", 140 | } 141 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 142 | return srv.(IdGeneratorServer).Generate(ctx, req.(*GenerateReq)) 143 | } 144 | return interceptor(ctx, in, info, handler) 145 | } 146 | 147 | var _IdGenerator_serviceDesc = grpc.ServiceDesc{ 148 | ServiceName: "generator.IdGenerator", 149 | HandlerType: (*IdGeneratorServer)(nil), 150 | Methods: []grpc.MethodDesc{ 151 | { 152 | MethodName: "Generate", 153 | Handler: _IdGenerator_Generate_Handler, 154 | }, 155 | }, 156 | Streams: []grpc.StreamDesc{}, 157 | Metadata: fileDescriptor0, 158 | } 159 | 160 | func init() { proto.RegisterFile("generator.proto", fileDescriptor0) } 161 | 162 | var fileDescriptor0 = []byte{ 163 | // 190 bytes of a gzipped FileDescriptorProto 164 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x4f, 0x4f, 0xcd, 0x4b, 165 | 0x2d, 0x4a, 0x2c, 0xc9, 0x2f, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x84, 0x0b, 0x28, 166 | 0x35, 0x31, 0x72, 0x71, 0xbb, 0x43, 0x78, 0xa9, 0x41, 0xa9, 0x85, 0x42, 0x86, 0x5c, 0x2c, 0x21, 167 | 0x95, 0x05, 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x7c, 0x46, 0xb2, 0x7a, 0x08, 0xad, 0x48, 0xaa, 168 | 0xf4, 0x3c, 0x53, 0x40, 0x8a, 0x82, 0x58, 0x4a, 0x80, 0xa4, 0x92, 0x03, 0x17, 0x1b, 0x84, 0x2f, 169 | 0xc4, 0xc1, 0xc5, 0x12, 0x5a, 0x9c, 0x5a, 0x24, 0xc0, 0x20, 0xc4, 0xc9, 0xc5, 0xea, 0x5e, 0x94, 170 | 0x5f, 0x5a, 0x20, 0xc0, 0x28, 0xc4, 0xcf, 0xc5, 0x0d, 0x12, 0xf4, 0x4d, 0x2d, 0x2e, 0x4e, 0x4c, 171 | 0x4f, 0x15, 0x60, 0x12, 0x12, 0xe0, 0xe2, 0x01, 0xcb, 0xc1, 0x44, 0x98, 0x95, 0x64, 0x91, 0xdc, 172 | 0x50, 0x5c, 0x20, 0xc4, 0xc7, 0xc5, 0xe4, 0x99, 0x02, 0x76, 0x01, 0x73, 0x10, 0x53, 0x66, 0x8a, 173 | 0x91, 0x37, 0x17, 0xb7, 0x67, 0x8a, 0x3b, 0xcc, 0x21, 0x42, 0x36, 0x5c, 0x1c, 0x30, 0xd5, 0x42, 174 | 0x62, 0xd8, 0x1d, 0x28, 0x85, 0x55, 0xbc, 0xb8, 0x40, 0x89, 0x21, 0x89, 0x0d, 0x1c, 0x04, 0xc6, 175 | 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x18, 0x65, 0x81, 0xef, 0x15, 0x01, 0x00, 0x00, 176 | } 177 | -------------------------------------------------------------------------------- /idService/generator/generator.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package generator; 4 | 5 | service IdGenerator { 6 | rpc Generate (GenerateReq) returns (GenerateRsp) {} 7 | } 8 | 9 | message GenerateReq { 10 | enum IdType { 11 | User = 0; 12 | Group = 1; 13 | UserMessage = 2; 14 | GroupMessage = 3; 15 | } 16 | IdType Type =1; 17 | } 18 | 19 | message GenerateRsp { 20 | int64 Id = 1; 21 | } 22 | 23 | -------------------------------------------------------------------------------- /idService/generator/generator_test.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | ) 7 | 8 | func BenchmarkGenerate(b *testing.B) { 9 | for i := 0; i < b.N; i++ { 10 | b.StopTimer() 11 | var a uint64 = uint64(rand.Intn(1000)) 12 | b.StartTimer() 13 | generate(a) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /idService/idServer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | slog "log" 5 | "net" 6 | 7 | "github.com/longchat/longChat-Server/common/config" 8 | "github.com/longchat/longChat-Server/common/consts" 9 | "github.com/longchat/longChat-Server/idService/generator" 10 | "golang.org/x/net/context" 11 | "google.golang.org/grpc" 12 | ) 13 | 14 | // server is used to implement helloworld.GreeterServer. 15 | type generatorServer struct { 16 | idGenerator *generator.IdGenerator 17 | } 18 | 19 | // SayHello implements helloworld.GreeterServer 20 | func (s *generatorServer) Generate(ctx context.Context, in *generator.GenerateReq) (*generator.GenerateRsp, error) { 21 | id, _ := s.idGenerator.Generate(in.Type) 22 | return &generator.GenerateRsp{Id: id}, nil 23 | } 24 | 25 | func main() { 26 | config.InitConfig() 27 | 28 | server := generatorServer{} 29 | server.idGenerator = &generator.IdGenerator{} 30 | err := server.idGenerator.Init(false) 31 | defer server.idGenerator.Close() 32 | if err != nil { 33 | slog.Fatalf("init idGenerator failed!err:=%v", err) 34 | } 35 | 36 | idAddress, err := config.GetConfigString(consts.IdServiceAddress) 37 | if err != nil { 38 | slog.Fatalln(consts.ErrGetConfigFailed(consts.IdServiceAddress, err)) 39 | } 40 | lis, err := net.Listen("tcp", idAddress) 41 | if err != nil { 42 | slog.Fatalf("failed to listen on %s!err:=%v", idAddress, err) 43 | } 44 | s := grpc.NewServer() 45 | generator.RegisterIdGeneratorServer(s, &server) 46 | s.Serve(lis) 47 | } 48 | -------------------------------------------------------------------------------- /messageService/message/conn.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | 8 | "github.com/golang/protobuf/proto" 9 | "github.com/gorilla/websocket" 10 | messagepb "github.com/longchat/longChat-Server/common/protoc" 11 | "github.com/longchat/longChat-Server/common/util" 12 | ) 13 | 14 | const ( 15 | MessageTypeMessage int = 1 16 | MessageTypeOnline int = 2 17 | MessageTypeGroup int = 3 18 | ) 19 | 20 | type connState uint8 21 | 22 | const ( 23 | ConnStateIdle connState = 0 24 | ConnStateWorking connState = 1 25 | ) 26 | 27 | type conn struct { 28 | Id uint32 29 | ws *websocket.Conn 30 | wLock sync.Mutex 31 | state connState 32 | } 33 | 34 | func (wsConn *conn) readPump(userId int64) { 35 | for { 36 | _, msg, err := wsConn.ws.ReadMessage() 37 | if err != nil { 38 | break 39 | } 40 | if int(msg[0]) == MessageTypeMessage { 41 | msg = msg[1:] 42 | var messageReq messagepb.MessageReq 43 | err := proto.Unmarshal(msg, &messageReq) 44 | if err != nil || len(messageReq.Messages) == 0 { 45 | continue 46 | } 47 | if userId != 0 { 48 | for i := range messageReq.Messages { 49 | ts := time.Now().UnixNano() 50 | messageReq.Messages[i].Id = util.Int2Bytes(ts) 51 | messageReq.Messages[i].From = util.Int2Bytes(userId) 52 | var err error 53 | if messageReq.Messages[i].IsGroupMessage { 54 | err = store.CreateGroupMessage(ts, userId, 55 | util.Bytes2Int(messageReq.Messages[i].To), 56 | messageReq.Messages[i].Content, 57 | int(messageReq.Messages[i].Type)) 58 | } else { 59 | err = store.CreateUserMessage(ts, userId, 60 | util.Bytes2Int(messageReq.Messages[i].To), 61 | messageReq.Messages[i].Content, 62 | int(messageReq.Messages[i].Type)) 63 | } 64 | if err != nil { 65 | fmt.Println("sotre message failed!", err) 66 | } 67 | } 68 | } 69 | msgCh <- message{wsConn: wsConn, messageReq: messageReq} 70 | } else if int(msg[0]) == MessageTypeOnline { 71 | msg = msg[1:] 72 | var onlineReq messagepb.OnlineReq 73 | err := proto.Unmarshal(msg, &onlineReq) 74 | if err != nil { 75 | continue 76 | } 77 | onlineCh <- online{wsConn: wsConn, onlineReq: onlineReq} 78 | } 79 | } 80 | } 81 | 82 | func (wsConn *conn) writeAndFlush(messageType int, pb proto.Message) error { 83 | bytes, err := proto.Marshal(pb) 84 | if err != nil { 85 | return err 86 | } 87 | rb := make([]byte, len(bytes)+1) 88 | rb[0] = byte(messageType) 89 | copy(rb[1:], bytes) 90 | id := wsConn.Id 91 | wsConn.wLock.Lock() 92 | defer wsConn.wLock.Unlock() 93 | if wsConn.state == ConnStateIdle || wsConn.Id != id { 94 | return err 95 | } 96 | wsConn.ws.SetWriteDeadline(time.Now().Add(time.Second * 2)) 97 | writer, err := wsConn.ws.NextWriter(websocket.BinaryMessage) 98 | if err != nil { 99 | wsConn.state = ConnStateIdle 100 | return err 101 | } 102 | _, err = writer.Write(rb) 103 | if err != nil { 104 | wsConn.state = ConnStateIdle 105 | return err 106 | } 107 | err = writer.Close() 108 | if err != nil { 109 | wsConn.state = ConnStateIdle 110 | return err 111 | } 112 | return nil 113 | } 114 | -------------------------------------------------------------------------------- /messageService/message/hub.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | messagepb "github.com/longchat/longChat-Server/common/protoc" 8 | "github.com/longchat/longChat-Server/common/util" 9 | ) 10 | 11 | const ( 12 | ForceFlushMessageCount uint32 = 256 13 | JobFlushInterval time.Duration = time.Millisecond * 5 14 | ) 15 | 16 | type hubCenter struct { 17 | parentJob job 18 | wp *workerPool 19 | workers []*worker 20 | 21 | userMap map[int64]*conn 22 | groupMap map[int64]map[uint32]*conn 23 | 24 | jobs map[uint32]job 25 | messageCount uint32 26 | } 27 | 28 | type message struct { 29 | messageReq messagepb.MessageReq 30 | wsConn *conn 31 | } 32 | 33 | type online struct { 34 | wsConn *conn 35 | onlineReq messagepb.OnlineReq 36 | } 37 | 38 | type removeConn struct { 39 | wsConn *conn 40 | } 41 | 42 | var ( 43 | msgCh chan message 44 | onlineCh chan online 45 | rmConnCh chan removeConn 46 | ) 47 | 48 | func startHub(parentConn *conn) { 49 | hub := hubCenter{ 50 | parentJob: job{ 51 | wsConn: parentConn, 52 | }, 53 | userMap: make(map[int64]*conn, 1024), 54 | groupMap: make(map[int64]map[uint32]*conn, 128), 55 | wp: newWorkerPool(), 56 | jobs: make(map[uint32]job, 128), 57 | workers: make([]*worker, 8), 58 | } 59 | go hub.hub() 60 | go hub.wp.idleCleaner() 61 | } 62 | 63 | func (hub *hubCenter) hub() { 64 | tickCh := time.Tick(JobFlushInterval) 65 | for { 66 | select { 67 | case msg := <-msgCh: 68 | hub.processMessage(msg) 69 | case online := <-onlineCh: 70 | hub.handleOnline(online) 71 | case rm := <-rmConnCh: 72 | hub.removeConn(rm) 73 | case _ = <-tickCh: 74 | hub.dispatchJobs() 75 | } 76 | } 77 | } 78 | 79 | func (hub *hubCenter) removeConn(rmConn removeConn) { 80 | for k, v := range hub.userMap { 81 | if v.Id == rmConn.wsConn.Id { 82 | delete(hub.userMap, k) 83 | if hasParentServer { 84 | hub.parentJob.onlineReq.Items = append(hub.parentJob.onlineReq.Items, &messagepb.OnlineReq_Item{ 85 | Id: util.Int2Bytes(k), 86 | IsGroup: false, 87 | IsOnline: false, 88 | }) 89 | hub.messageCount++ 90 | } 91 | } 92 | } 93 | for k, v := range hub.groupMap { 94 | for k2, v2 := range v { 95 | if v2.Id == rmConn.wsConn.Id { 96 | delete(v, k2) 97 | if len(v) == 0 { 98 | delete(hub.groupMap, k) 99 | if hasParentServer { 100 | hub.parentJob.onlineReq.Items = append(hub.parentJob.onlineReq.Items, &messagepb.OnlineReq_Item{ 101 | Id: util.Int2Bytes(k), 102 | IsGroup: true, 103 | IsOnline: false, 104 | }) 105 | hub.messageCount++ 106 | } 107 | } else { 108 | hub.groupMap[k] = v 109 | } 110 | break 111 | } 112 | } 113 | } 114 | if hub.messageCount >= ForceFlushMessageCount { 115 | hub.dispatchJobs() 116 | } 117 | } 118 | 119 | func (hub *hubCenter) handleOnline(req online) { 120 | for i := range req.onlineReq.Items { 121 | data := req.onlineReq.Items[i] 122 | if data.IsGroup { 123 | group, isok := hub.groupMap[util.Bytes2Int(data.Id)] 124 | if isok { 125 | conns, isok := group[req.wsConn.Id] 126 | if data.IsOnline { 127 | conns = req.wsConn 128 | group[req.wsConn.Id] = conns 129 | } else if isok { 130 | delete(group, req.wsConn.Id) 131 | if len(group) == 0 { 132 | delete(hub.groupMap, util.Bytes2Int(data.Id)) 133 | if hasParentServer { 134 | if req.wsConn.Id == hub.parentJob.wsConn.Id { 135 | panic(fmt.Sprintf("onlineReq can't come from parent server")) 136 | } 137 | hub.parentJob.onlineReq.Items = append(hub.parentJob.onlineReq.Items, data) 138 | hub.messageCount++ 139 | } 140 | } 141 | } 142 | } else if data.IsOnline { 143 | group = make(map[uint32]*conn, 10) 144 | group[req.wsConn.Id] = req.wsConn 145 | if hasParentServer { 146 | if req.wsConn.Id == hub.parentJob.wsConn.Id { 147 | panic(fmt.Sprintf("onlineReq can't come from parent server")) 148 | } 149 | hub.parentJob.onlineReq.Items = append(hub.parentJob.onlineReq.Items, data) 150 | hub.messageCount++ 151 | } 152 | } 153 | if len(group) > 0 { 154 | hub.groupMap[util.Bytes2Int(data.Id)] = group 155 | } 156 | } else { 157 | user, isok := hub.userMap[util.Bytes2Int(data.Id)] 158 | if data.IsOnline { 159 | user = req.wsConn 160 | hub.userMap[util.Bytes2Int(data.Id)] = user 161 | } else if isok { 162 | delete(hub.userMap, util.Bytes2Int(data.Id)) 163 | } 164 | if hasParentServer { 165 | if req.wsConn.Id == hub.parentJob.wsConn.Id { 166 | panic(fmt.Sprintf("onlineReq can't come from parent server")) 167 | } 168 | hub.parentJob.onlineReq.Items = append(hub.parentJob.onlineReq.Items, data) 169 | hub.messageCount++ 170 | } 171 | } 172 | } 173 | if hub.messageCount >= ForceFlushMessageCount { 174 | hub.dispatchJobs() 175 | } 176 | } 177 | 178 | func (hub *hubCenter) processMessage(msg message) { 179 | for i := range msg.messageReq.Messages { 180 | data := msg.messageReq.Messages[i] 181 | if data.IsGroupMessage { 182 | var exceptConnId uint32 183 | if !isLeafServer { 184 | userFromConn, isok := hub.userMap[util.Bytes2Int(data.From)] 185 | if isok { 186 | exceptConnId = userFromConn.Id 187 | } 188 | } 189 | group, isok := hub.groupMap[util.Bytes2Int(data.To)] 190 | if isok { 191 | for k, v := range group { 192 | if !isLeafServer { 193 | if v.Id == exceptConnId { 194 | continue 195 | } 196 | } 197 | ajob, isok := hub.jobs[k] 198 | if isok { 199 | ajob.message.Messages = append(ajob.message.Messages, data) 200 | } else { 201 | msgReq := messagepb.MessageReq{Messages: []*messagepb.MessageReq_Message{data}} 202 | ajob = job{wsConn: v, message: msgReq} 203 | } 204 | hub.jobs[k] = ajob 205 | hub.messageCount++ 206 | } 207 | } 208 | if hasParentServer && msg.wsConn.Id != hub.parentJob.wsConn.Id { 209 | hub.parentJob.message.Messages = append(hub.parentJob.message.Messages, data) 210 | hub.messageCount++ 211 | } 212 | } else { 213 | userConn, isok := hub.userMap[util.Bytes2Int(data.To)] 214 | if isok { 215 | ajob, isok := hub.jobs[userConn.Id] 216 | if isok { 217 | ajob.message.Messages = append(ajob.message.Messages, data) 218 | 219 | } else { 220 | msgReq := messagepb.MessageReq{Messages: []*messagepb.MessageReq_Message{data}} 221 | ajob = job{wsConn: userConn, message: msgReq} 222 | } 223 | hub.jobs[userConn.Id] = ajob 224 | hub.messageCount++ 225 | } else if hasParentServer && msg.wsConn.Id != hub.parentJob.wsConn.Id { 226 | hub.parentJob.message.Messages = append(hub.parentJob.message.Messages, data) 227 | hub.messageCount++ 228 | } 229 | } 230 | } 231 | if hub.messageCount >= ForceFlushMessageCount { 232 | hub.dispatchJobs() 233 | } 234 | } 235 | 236 | func (hub *hubCenter) dispatchJobs() { 237 | needJobCount := len(hub.jobs) 238 | parentJobCount := 0 239 | if hasParentServer && (len(hub.parentJob.message.Messages) > 0 || len(hub.parentJob.onlineReq.Items) > 0) { 240 | parentJobCount++ 241 | } 242 | if needJobCount+parentJobCount == 0 { 243 | return 244 | } 245 | if cap(hub.workers) < (needJobCount + parentJobCount) { 246 | hub.workers = make([]*worker, needJobCount+parentJobCount) 247 | } else { 248 | hub.workers = hub.workers[:needJobCount+parentJobCount] 249 | } 250 | hub.wp.getWorkers(&hub.workers, needJobCount+parentJobCount) 251 | var i int 252 | for _, v := range hub.jobs { 253 | hub.workers[i].ch <- v 254 | i++ 255 | } 256 | if parentJobCount > 0 { 257 | hub.workers[i].ch <- hub.parentJob 258 | hub.parentJob.message.Messages = make([]*messagepb.MessageReq_Message, 0, 50) 259 | hub.parentJob.onlineReq.Items = make([]*messagepb.OnlineReq_Item, 0, 10) 260 | } 261 | 262 | hub.jobs = make(map[uint32]job, 16) 263 | } 264 | -------------------------------------------------------------------------------- /messageService/message/server.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "fmt" 5 | slog "log" 6 | "net" 7 | "net/http" 8 | "net/url" 9 | "sync" 10 | "sync/atomic" 11 | 12 | "github.com/gorilla/websocket" 13 | messagepb "github.com/longchat/longChat-Server/common/protoc" 14 | "github.com/longchat/longChat-Server/common/util" 15 | "github.com/longchat/longChat-Server/storageService/storage" 16 | ) 17 | 18 | var store *storage.Storage 19 | 20 | type Server struct { 21 | connPool sync.Pool 22 | maxConnId uint32 23 | } 24 | 25 | var ( 26 | hasParentServer bool 27 | isLeafServer bool 28 | cookieName string 29 | redisPrefix string 30 | ) 31 | 32 | var upgrader = websocket.Upgrader{ 33 | CheckOrigin: func(r *http.Request) bool { return true }, 34 | } 35 | 36 | func StartServer(storage *storage.Storage, addr string, parentAddr string, isLeaf bool) { 37 | if parentAddr != "" { 38 | hasParentServer = true 39 | } 40 | isLeafServer = isLeaf 41 | msgCh = make(chan message, 256) 42 | if isLeafServer { 43 | onlineCh = make(chan online, 32) 44 | } else { 45 | rmConnCh = make(chan removeConn) 46 | onlineCh = make(chan online, 8) 47 | } 48 | s := Server{ 49 | connPool: sync.Pool{ 50 | New: func() interface{} { 51 | return &conn{} 52 | }, 53 | }, 54 | } 55 | store = storage 56 | if hasParentServer { 57 | go s.connectParentAndStartHub(parentAddr) 58 | } else { 59 | go startHub(nil) 60 | } 61 | http.HandleFunc("/websocket", s.serveWebSocket) 62 | fmt.Println("message server running on:", addr) 63 | http.ListenAndServe(addr, nil) 64 | } 65 | 66 | func (s *Server) connectParentAndStartHub(addr string) { 67 | rawC, err := net.Dial("tcp4", addr) 68 | if err != nil { 69 | slog.Fatalln("tcp dial to parent server failed", err) 70 | } 71 | urlA := url.URL{ 72 | Scheme: "ws", 73 | Host: addr, 74 | Path: "websocket", 75 | } 76 | header := http.Header(make(map[string][]string)) 77 | ws, _, err := websocket.NewClient(rawC, &urlA, header, 4096, 4096) 78 | if err != nil { 79 | slog.Fatalln("can't create websocket connect to parent server!", err) 80 | } 81 | wsConn := s.getWsConn(ws) 82 | defer s.releaseWsConn(wsConn) 83 | go startHub(wsConn) 84 | wsConn.readPump(0) 85 | } 86 | 87 | func (s *Server) serveWebSocket(w http.ResponseWriter, r *http.Request) { 88 | if isLeafServer { 89 | s.serveLeaf(w, r) 90 | } else { 91 | s.serveNode(w, r) 92 | } 93 | } 94 | 95 | func (s *Server) serveNode(w http.ResponseWriter, r *http.Request) { 96 | ws, err := upgrader.Upgrade(w, r, nil) 97 | if err != nil { 98 | w.WriteHeader(http.StatusInternalServerError) 99 | w.Write([]byte("internal server error")) 100 | return 101 | } 102 | wsConn := s.getWsConn(ws) 103 | defer s.releaseWsConn(wsConn) 104 | wsConn.readPump(0) 105 | rmConnCh <- removeConn{wsConn} 106 | } 107 | 108 | func (s *Server) serveLeaf(w http.ResponseWriter, r *http.Request) { 109 | cookieRaw, err := r.Cookie(cookieName) 110 | if err != nil { 111 | w.WriteHeader(http.StatusUnauthorized) 112 | w.Write([]byte("no cookie")) 113 | return 114 | } 115 | c1, err := url.QueryUnescape(cookieRaw.Value) 116 | if err != nil { 117 | w.WriteHeader(http.StatusUnauthorized) 118 | w.Write([]byte("invalid session cookie")) 119 | return 120 | } 121 | sId, err := url.QueryUnescape(c1) 122 | if err != nil { 123 | w.WriteHeader(http.StatusUnauthorized) 124 | w.Write([]byte("invalid session cookie")) 125 | return 126 | } 127 | rmap, err := store.GetSessionValue(sId) 128 | if err != nil { 129 | w.WriteHeader(http.StatusInternalServerError) 130 | w.Write([]byte("internal server error")) 131 | return 132 | } 133 | uid, isok := rmap["Id"] 134 | if !isok { 135 | w.WriteHeader(http.StatusUnauthorized) 136 | w.Write([]byte("invalid session")) 137 | return 138 | } 139 | userId := uid.(int64) 140 | user, err := store.GetUserById(userId) 141 | if err != nil { 142 | w.WriteHeader(http.StatusInternalServerError) 143 | w.Write([]byte("internal server error")) 144 | return 145 | } 146 | olItems := make([]*messagepb.OnlineReq_Item, 1, 4) 147 | olItems[0] = &messagepb.OnlineReq_Item{ 148 | Id: util.Int2Bytes(user.Id), 149 | IsOnline: true, 150 | IsGroup: false, 151 | } 152 | var groups messagepb.GroupReq 153 | for i := range user.JoinedGroups { 154 | olItems = append(olItems, &messagepb.OnlineReq_Item{ 155 | Id: util.Int2Bytes(user.JoinedGroups[i].Id), 156 | IsOnline: true, 157 | IsGroup: true, 158 | }) 159 | group, err := store.GetGroupById(user.JoinedGroups[i].Id) 160 | if err != nil { 161 | return 162 | } 163 | members := make([][]byte, 0, 10) 164 | for _, data := range group.Members { 165 | members = append(members, util.Int2Bytes(data)) 166 | } 167 | groups.Groups = append(groups.Groups, &messagepb.GroupReq_Group{Id: util.Int2Bytes(group.Id), Title: group.Title, Logo: group.Logo, Introduce: group.Introduce, Members: members}) 168 | } 169 | req := messagepb.OnlineReq{olItems} 170 | ws, err := upgrader.Upgrade(w, r, nil) 171 | if err != nil { 172 | fmt.Println("upgrade websocket failed!", err) 173 | w.WriteHeader(http.StatusInternalServerError) 174 | w.Write([]byte("internal server error")) 175 | return 176 | } 177 | wsConn := s.getWsConn(ws) 178 | defer s.releaseWsConn(wsConn) 179 | err = wsConn.writeAndFlush(MessageTypeGroup, &groups) 180 | if err != nil { 181 | return 182 | } 183 | onlineCh <- online{wsConn, req} 184 | wsConn.readPump(userId) 185 | for i := range req.Items { 186 | req.Items[i].IsOnline = false 187 | } 188 | onlineCh <- online{wsConn, req} 189 | } 190 | 191 | func (s *Server) getWsConn(ws *websocket.Conn) *conn { 192 | wsConn := s.connPool.Get().(*conn) 193 | wsConn.ws = ws 194 | wsConn.Id = atomic.AddUint32(&s.maxConnId, 1) 195 | wsConn.wLock.Lock() 196 | wsConn.state = ConnStateWorking 197 | wsConn.wLock.Unlock() 198 | return wsConn 199 | } 200 | 201 | func (s *Server) releaseWsConn(wsConn *conn) { 202 | wsConn.wLock.Lock() 203 | wsConn.state = ConnStateIdle 204 | wsConn.wLock.Unlock() 205 | wsConn.ws.Close() 206 | s.connPool.Put(wsConn) 207 | } 208 | -------------------------------------------------------------------------------- /messageService/message/wokerPool.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | messagepb "github.com/longchat/longChat-Server/common/protoc" 8 | "github.com/longchat/longChat-Server/common/util" 9 | ) 10 | 11 | const ( 12 | MaxWorkerCount = 64 * 1024 13 | MaxIdleTime time.Duration = time.Second * 5 14 | ) 15 | 16 | type job struct { 17 | wsConn *conn 18 | message messagepb.MessageReq 19 | onlineReq messagepb.OnlineReq 20 | } 21 | 22 | type worker struct { 23 | lastUseTs int64 24 | ch chan job 25 | } 26 | 27 | type workerPool struct { 28 | idle []*worker 29 | idleLock sync.Mutex 30 | pool sync.Pool 31 | workerCount int32 32 | } 33 | 34 | func newWorkerPool() *workerPool { 35 | wp := &workerPool{ 36 | idle: make([]*worker, 0, 128), 37 | } 38 | wp.pool = sync.Pool{ 39 | New: func() interface{} { 40 | return nil 41 | }, 42 | } 43 | return wp 44 | } 45 | 46 | func (wp *workerPool) idleCleaner() { 47 | for { 48 | time.Sleep(MaxIdleTime) 49 | var i int 50 | var releases []*worker 51 | nowTs := time.Now().UnixNano() 52 | wp.idleLock.Lock() 53 | for i = range wp.idle { 54 | if wp.idle[i].lastUseTs >= nowTs-int64(MaxIdleTime) { 55 | break 56 | } 57 | } 58 | releases = wp.idle[:i] 59 | wp.idle = wp.idle[i:] 60 | wp.idleLock.Unlock() 61 | for j := range releases { 62 | //pass an empty job{} to stop the goroutine 63 | releases[j].ch <- job{} 64 | wp.pool.Put(releases[j]) 65 | } 66 | } 67 | } 68 | 69 | func (wp *workerPool) getWorkers(workers *[]*worker, jobLen int) { 70 | var workerSelf []*worker 71 | wp.idleLock.Lock() 72 | remain := len(wp.idle) - jobLen 73 | if remain >= 0 { 74 | workerSelf = wp.idle[remain:] 75 | wp.idle = wp.idle[:remain] 76 | } else { 77 | workerSelf = wp.idle[:] 78 | wp.idle = wp.idle[:0] 79 | } 80 | copy(*workers, workerSelf) 81 | wp.idleLock.Unlock() 82 | if remain < 0 { 83 | for i := 0; i < (-remain); i++ { 84 | aworker := wp.pool.Get() 85 | if aworker == nil { 86 | w := new(worker) 87 | w.ch = make(chan job) 88 | aworker = w 89 | } 90 | go wp.worker(aworker.(*worker)) 91 | (*workers)[jobLen+remain+i] = aworker.(*worker) 92 | } 93 | } 94 | } 95 | 96 | func (wp *workerPool) releaseWorker(worker *worker) { 97 | wp.idleLock.Lock() 98 | worker.lastUseTs = time.Now().UnixNano() 99 | wp.idle = append(wp.idle, worker) 100 | wp.idleLock.Unlock() 101 | } 102 | 103 | func (wp *workerPool) worker(worker *worker) { 104 | for info := range worker.ch { 105 | if info.wsConn == nil { 106 | break 107 | } 108 | var err error 109 | if len(info.message.Messages) > 0 { 110 | err = info.wsConn.writeAndFlush(MessageTypeMessage, &info.message) 111 | } else if len(info.onlineReq.Items) > 0 { 112 | err = info.wsConn.writeAndFlush(MessageTypeOnline, &info.onlineReq) 113 | } 114 | if isLeafServer { 115 | if !hasParentServer || info.wsConn.Id > 1 { 116 | if err == nil { 117 | for _, data := range info.message.Messages { 118 | if !data.IsGroupMessage { 119 | store.MarkUserMessageRead(util.Bytes2Int(data.Id)) 120 | } 121 | } 122 | } 123 | } 124 | } 125 | 126 | if len(worker.ch) != 0 { 127 | panic("worker.ch's length must be zero") 128 | } 129 | wp.releaseWorker(worker) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /messageService/messageServer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | slog "log" 6 | "os" 7 | "os/signal" 8 | "runtime/pprof" 9 | "syscall" 10 | 11 | "github.com/longchat/longChat-Server/common/config" 12 | "github.com/longchat/longChat-Server/common/consts" 13 | "github.com/longchat/longChat-Server/common/log" 14 | "github.com/longchat/longChat-Server/messageService/message" 15 | "github.com/longchat/longChat-Server/storageService/storage" 16 | ) 17 | 18 | var cpuf *os.File 19 | 20 | func main() { 21 | pconfig := flag.String("config", "../config.cfg", "config file") 22 | psection := flag.String("section", "dev", "section of config file to apply") 23 | cpuProfile := flag.String("cpuprofile", "", "write cpu profile to file") 24 | memProfile := flag.String("memprofile", "", "write mem profile to file") 25 | 26 | flag.Parse() 27 | if *cpuProfile != "" { 28 | var err error 29 | cpuf, err = os.Create(*cpuProfile) 30 | if err != nil { 31 | slog.Fatal(err) 32 | } 33 | pprof.StartCPUProfile(cpuf) 34 | } 35 | if *memProfile != "" || *cpuProfile != "" { 36 | go func() { 37 | c := make(chan os.Signal) 38 | signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) 39 | for { 40 | s := <-c 41 | if s.(syscall.Signal) == syscall.SIGINT || s.(syscall.Signal) == syscall.SIGTERM { 42 | break 43 | } 44 | } 45 | 46 | if *memProfile != "" { 47 | memf, err := os.Create(*memProfile) 48 | if err != nil { 49 | slog.Fatal(err) 50 | } 51 | pprof.WriteHeapProfile(memf) 52 | memf.Close() 53 | } 54 | if *cpuProfile != "" { 55 | pprof.StopCPUProfile() 56 | cpuf.Close() 57 | } 58 | os.Exit(0) 59 | }() 60 | } 61 | config.InitConfig(pconfig, psection) 62 | 63 | accPath, err := config.GetConfigString(consts.AccessLogPath) 64 | if err != nil { 65 | slog.Fatalf(consts.ErrGetConfigFailed(consts.AccessLogPath, err)) 66 | } 67 | errPath, err := config.GetConfigString(consts.ErrorLogPath) 68 | if err != nil { 69 | slog.Fatalf(consts.ErrGetConfigFailed(consts.ErrorLogPath, err)) 70 | } 71 | err = log.InitLogger(errPath, accPath, 1024, 5*1024) 72 | if err != nil { 73 | slog.Fatalf("init log failed!err:=%v\n", err) 74 | } 75 | defer log.FiniLogger() 76 | addr, err := config.GetConfigString(consts.MsgServiceAddress) 77 | if err != nil { 78 | slog.Fatalln(consts.ErrGetConfigFailed(consts.MsgServiceAddress, err)) 79 | } 80 | parentAddr, err := config.GetConfigString(consts.ParentServiceAddress) 81 | if err != nil { 82 | slog.Fatalln(consts.ErrGetConfigFailed(consts.ParentServiceAddress, err)) 83 | } 84 | isLeaf, err := config.GetConfigBool(consts.IsLeafServer) 85 | if err != nil { 86 | slog.Fatalln(consts.ErrGetConfigFailed(consts.IsLeafServer, err)) 87 | } 88 | store, err := storage.NewStorage() 89 | if err != nil { 90 | slog.Fatalln("init store failed!", err) 91 | } 92 | defer store.Close() 93 | if err != nil { 94 | slog.Fatalf("init DB failed!err:=%v\n", err) 95 | } 96 | message.StartServer(store, addr, parentAddr, isLeaf) 97 | } 98 | -------------------------------------------------------------------------------- /storageService/storage/auth.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "github.com/kataras/iris/utils" 5 | "gopkg.in/redis.v4" 6 | ) 7 | 8 | func getSessionValue(redisCli *redis.ClusterClient, key string) (map[string]interface{}, error) { 9 | b, err := redisCli.Get(key).Bytes() 10 | if err != nil { 11 | return nil, err 12 | } 13 | r := make(map[string]interface{}) 14 | err = utils.DeserializeBytes(b, &r) 15 | if err != nil { 16 | return nil, err 17 | } 18 | return r, nil 19 | } 20 | -------------------------------------------------------------------------------- /storageService/storage/group.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "github.com/go-xorm/xorm" 5 | "github.com/longchat/longChat-Server/common/log" 6 | "github.com/longchat/longChat-Server/storageService/storage/schema" 7 | ) 8 | 9 | func getGroupById(db *xorm.Engine, id int64) (*schema.Group, error) { 10 | var group schema.Group 11 | _, err := db.Where("Id=?", id).Get(&group) 12 | if err != nil { 13 | log.ERROR.Printf("find group by id(%d) from db failed!err:=%v\n", id, err) 14 | return nil, err 15 | } 16 | return &group, nil 17 | } 18 | 19 | func getGroupsByIds(db *xorm.Engine, id []int64) ([]schema.Group, error) { 20 | var groups []schema.Group 21 | _, err := db.In("Id", id).Get(&groups) 22 | if err != nil { 23 | log.ERROR.Printf("find group by id(%d) from db failed!err:=%v\n", id, err) 24 | return nil, err 25 | } 26 | return groups, nil 27 | } 28 | func getGroupsByOrderIdx(db *xorm.Engine, id int64, limit int) ([]schema.Group, error) { 29 | var groups []schema.Group 30 | err := db.Where("Id 0 { 236 | return user, nil 237 | } 238 | } 239 | return nil, nil 240 | } 241 | 242 | func (s *Storage) GetUserById(id int64) (*schema.User, error) { 243 | return getUserById(s.getDb(0, id), id) 244 | } 245 | 246 | func (s *Storage) GetGroupsByOrderId(id int64, limit int) ([]schema.Group, error) { 247 | var groups []schema.Group 248 | for i := 0; i <= 1; i++ { 249 | gs, err := getGroupsByOrderIdx(s.mysqlDb[0][i], id, limit) 250 | if err != nil { 251 | return nil, err 252 | } 253 | groups = append(groups, gs...) 254 | } 255 | return groups, nil 256 | } 257 | func (s *Storage) GetUsersByIds(ids []int64) ([]schema.User, error) { 258 | dbs := s.getDbsByIds(0, ids) 259 | var users []schema.User 260 | for _, data := range dbs { 261 | g, err := getUsersByIds(data.db, data.ids) 262 | if err != nil { 263 | return nil, err 264 | } 265 | users = append(users, g...) 266 | } 267 | return users, nil 268 | } 269 | func (s *Storage) GetGroupById(id int64) (*schema.Group, error) { 270 | return getGroupById(s.getDb(0, id), id) 271 | } 272 | 273 | func (s *Storage) GetSessionValue(key string) (map[string]interface{}, error) { 274 | return getSessionValue(s.redis, s.sessionPrefix+key) 275 | } 276 | 277 | func (s *Storage) CreateUserMessage(id int64, from int64, to int64, content string, msgType int) error { 278 | return createUserMessage(s.db, id, from, to, content, msgType) 279 | } 280 | 281 | func (s *Storage) CreateGroupMessage(id int64, from int64, groupId int64, content string, msgType int) error { 282 | return createGroupMessage(s.db, id, groupId, from, content, msgType) 283 | } 284 | 285 | func (s *Storage) MarkUserMessageRead(id int64) error { 286 | return markUserMessageRead(s.db, id) 287 | } 288 | -------------------------------------------------------------------------------- /storageService/storage/user.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/go-xorm/xorm" 7 | "github.com/longchat/longChat-Server/common/log" 8 | "github.com/longchat/longChat-Server/storageService/storage/schema" 9 | ) 10 | 11 | func createUser(db *xorm.Engine, id int64, userName string, password string, salt string, lastLoginIp string) error { 12 | user := schema.User{Id: id, UserName: userName, Password: password, Salt: salt, LastLoginIp: lastLoginIp} 13 | _, err := db.InsertOne(&user) 14 | if err != nil { 15 | log.ERROR.Printf("insert user(%v) into db failed!err:=%v\n", user, err) 16 | return err 17 | } 18 | return nil 19 | } 20 | 21 | func updateUserInfo(db *xorm.Engine, id int64, nickName string, avatar string, intro string) error { 22 | user := schema.User{Id: id, NickName: nickName, Avatar: avatar, Introduce: intro} 23 | _, err := db.Where("Id=?", id).Update(&user) 24 | if err != nil { 25 | log.ERROR.Printf("update user(%d) from db failed!err:=%v\n", id, err) 26 | return err 27 | } 28 | return nil 29 | } 30 | 31 | func getUserByUserName(db *xorm.Engine, userName string) (*schema.User, error) { 32 | var user schema.User 33 | _, err := db.Where("UserName=?", userName).Get(&user) 34 | if err != nil { 35 | log.ERROR.Printf("findone user(%s) from db failed!err:=%v\n", userName, err) 36 | return nil, err 37 | } 38 | return &user, nil 39 | } 40 | 41 | func getUserById(db *xorm.Engine, id int64) (*schema.User, error) { 42 | var user schema.User 43 | _, err := db.Where("Id=?", id).Get(&user) 44 | if err != nil { 45 | log.ERROR.Printf("findone user(%s) from db failed!err:=%v\n", id, err) 46 | return nil, err 47 | } 48 | return &user, nil 49 | } 50 | 51 | func addUserGroup(db *xorm.Engine, groupId int64, userId int64) error { 52 | userGroup := schema.GroupMembers{ 53 | UserId: userId, 54 | GroupId: groupId, 55 | JoinTs: time.Now().Unix(), 56 | } 57 | _, err := db.InsertOne(&userGroup) 58 | if err != nil { 59 | log.ERROR.Printf("add a member to a group failed!err:=%v\n", err) 60 | return err 61 | } 62 | return nil 63 | } 64 | func getUsersByIds(db *xorm.Engine, ids []int64) ([]schema.User, error) { 65 | var users []schema.User 66 | _, err := db.In("Id", ids).Get(&users) 67 | if err != nil { 68 | log.ERROR.Printf("find group by id(%d) from db failed!err:=%v\n", ids, err) 69 | return nil, err 70 | } 71 | return users, nil 72 | } 73 | -------------------------------------------------------------------------------- /storageService/storage/userMessage.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "github.com/longchat/longChat-Server/common/log" 5 | "github.com/longchat/longChat-Server/storageService/storage/schema" 6 | "gopkg.in/mgo.v2" 7 | "gopkg.in/mgo.v2/bson" 8 | ) 9 | 10 | func createGroupMessage(db *mgo.Database, id int64, groupId int64, from int64, content string, msgType int) error { 11 | msg := schema.GroupMessage{Id: id, From: from, GroupId: groupId, Content: content, Type: msgType} 12 | err := db.C("GroupMessage").Insert(&msg) 13 | if err != nil { 14 | log.ERROR.Printf("insert groupmessage(%v) into db failed!err:=%v\n", msg, err) 15 | return err 16 | } 17 | return nil 18 | } 19 | 20 | func createUserMessage(db *mgo.Database, id int64, from int64, to int64, content string, msgType int) error { 21 | msg := schema.UserMessage{Id: id, From: from, To: to, Content: content, Type: msgType, IsRead: false} 22 | err := db.C("UserMessage").Insert(&msg) 23 | if err != nil { 24 | log.ERROR.Printf("insert usermessage(%v) into db failed!err:=%v\n", msg, err) 25 | return err 26 | } 27 | return nil 28 | } 29 | 30 | func markUserMessageRead(db *mgo.Database, id int64) error { 31 | err := db.C("UserMessage").UpdateId(id, bson.M{"$set": bson.M{"isread": true}}) 32 | if err != nil && err != mgo.ErrNotFound { 33 | log.ERROR.Printf("update user(%d) from db failed!err:=%v\n", id, err) 34 | return err 35 | } 36 | return nil 37 | } 38 | --------------------------------------------------------------------------------