├── .gitignore ├── LICENSE ├── README.md ├── app ├── apiCall.go ├── apnsmsg.go ├── app.go ├── appWeb.go ├── application.go ├── client.go ├── config.go ├── contact.go ├── device.go ├── fileLink.go ├── org.go ├── push.go ├── pushCnt.go ├── qun.go ├── resource.go ├── ret.go ├── session.go ├── shieldMsg.go └── token.go ├── appweb ├── conf │ └── app.conf ├── controllers │ ├── index.go │ ├── ret.go │ └── user.go ├── main.go ├── models │ ├── org.go │ ├── qun.go │ └── user.go ├── routers │ └── router.go ├── setting │ └── setting.go ├── static │ ├── css │ │ ├── bootstrap-theme.css │ │ ├── bootstrap-theme.css.map │ │ ├── bootstrap-theme.min.css │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.min.css │ │ └── login.css │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ └── glyphicons-halflings-regular.woff │ ├── images │ │ ├── bg-bottom.png │ │ ├── bg-top.png │ │ ├── loginForm.png │ │ ├── loginb.png │ │ ├── logo.png │ │ ├── one4.jpg │ │ ├── one5.jpg │ │ └── selectOrg.png │ └── js │ │ ├── bootstrap.js │ │ ├── bootstrap.min.js │ │ ├── jquery-2.1.1.min.js │ │ ├── msg-js.js │ │ └── npm.js └── views │ ├── base │ ├── base.html │ ├── base_common.html │ ├── foot.html │ └── head.html │ ├── index.html │ └── login.html ├── client └── js │ ├── README.md │ ├── demo │ ├── main.go │ ├── static │ │ ├── jquery-1.11.1.min.js │ │ ├── msg-js.js │ │ └── ws-flash │ │ │ ├── WebSocketMain.swf │ │ │ ├── swfobject.js │ │ │ └── web_socket.js │ └── template │ │ └── demo.html │ └── flash-security-policy-server │ ├── main.go │ └── socketpolicy.xml ├── comet.ini ├── comet ├── channel.go ├── comet.conf ├── config.go ├── db.conf ├── main.go ├── pubsub.go ├── pubsub_tcp.go ├── pubsub_websocket.go ├── rpc.go ├── seq_channel.go ├── signal.go ├── stat.go ├── token.go └── zk.go ├── db ├── config.go ├── db.conf └── db.go ├── dependencies.sh ├── hash ├── ketama.go ├── ketama_test.go ├── mmhash3.go └── mmhash3_test.go ├── hlist ├── hlist.go └── hlist_test.go ├── id ├── timeid.go └── timeid_test.go ├── ketama └── ketama.go ├── message.ini ├── message ├── config.go ├── main.go ├── message-example.conf ├── message.conf ├── mysql.go ├── redis.go ├── rpc.go ├── signal.go ├── storage.go └── zk.go ├── perf └── perf.go ├── process ├── process.go └── process_test.go ├── router └── cn.go ├── rpc ├── comet.go ├── message.go ├── rand_lb.go ├── ret.go └── zk.go ├── session └── session.go ├── sql ├── gopush-cluster.sql └── install.sql ├── start_all.sh ├── stop_all.sh ├── test ├── comet │ ├── comet-test-exmaple.conf │ ├── config.go │ └── main.go └── load │ ├── tcpclient.go │ └── websocket.go ├── ver └── ver.go ├── web.ini ├── web ├── admin.go ├── db.conf ├── handle.go ├── handle_1.0.go ├── http.go ├── main.go ├── router.go ├── signal.go ├── static │ ├── css │ │ └── user.css │ └── images │ │ ├── head-photo-big.png │ │ └── line-big.png ├── view │ └── erweima.html ├── web.conf └── zk.go ├── wiki ├── architecture │ └── architecture.jpg ├── comet │ ├── client_proto_zh.png │ ├── client_proto_zh.textile │ └── rpc_proto_zh.textile ├── message │ ├── rpc_proto_en.textile │ └── rpc_proto_zh.textile └── web │ ├── external_proto_en.textile │ ├── external_proto_zh.textile │ ├── internal_proto_en.textile │ └── internal_proto_zh.textile └── zk ├── zk.go └── zk_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | 24 | /web/web 25 | /web/main 26 | /message/message 27 | /message/main 28 | /comet/comet 29 | /comet/main 30 | 31 | /client/java/gopush-cli-java-master/target 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 应用消息服务 [![Build Status](https://drone.io/github.com/EPICPaaS/appmsgsrv/status.png)](https://drone.io/github.com/EPICPaaS/appmsgsrv/latest) 2 | 3 | 在 [gopush-cluster](https://github.com/Terry-Mao/gopush-cluster) 基础上做了扩展,加入了用户、群、组织机构等应用元素。 4 | 5 | ## 特性 6 | 7 | * 会话推送 8 | * 用户-用户、用户-群、应用-用户推送 9 | * iOS 通知 10 | * Web 推送 11 | * 图片、语音、链接推送 12 | * 配额与统计 13 | 14 | ## 实现概要 15 | 16 | * 在 gopush 中进行实现(web 进程) 17 | * 用户登录后返回 18 | * id:作为 gopush 的 key 19 | * token:用于身份验证 20 | * 用户/应用/群/组织机构都是使用 id 作为 key 21 | * 用户未登录时使用默认的 key 22 | * 用户发送群组消息时转为对该群组内所有用户的 id 批量发送 23 | * 对外提供对应用数据管理的 RESTful 接口,例如用户 CRUD 24 | 25 | ### 数据库表 26 | 27 | * 用户(user、user_user) 28 | * 组织机构(tenant、org、org_user) 29 | * 应用(application) 30 | * 群(qun、qun_user) 31 | * 客户端版本(client_version) 32 | 33 | ### 会话 34 | 35 | 一个会话对应一个推送连接,连接断开会话终止。 36 | 37 | 可以针对会话级别进行推送控制,请参考[会话推送](https://github.com/EPICPaaS/appmsgsrv/issues/1)。 38 | 39 | ### Name 40 | 41 | 使用 `Name` 作为 gopush-cluster 的 key。 42 | 43 | * 监听:`会话 id` + `@后缀` (监听客户端知道自己的会话) 44 | * 发送:`会话 id` + `@后缀` (推送时服务端根据参数 `sessions` 拼装会话 id) 45 | * 获取离线消息:`uid` + `@后缀` 46 | 47 | #### @后缀 48 | 49 | * 组织机构单位:@tenant 50 | * 组织机构部门:@org 51 | * 群:@qun 52 | * 用户:@user 53 | * 应用:@app 54 | 55 | 开发中可以使用定义在 `app/config.go` 中的常量。 56 | 57 | ## 开发 58 | 59 | * 熟悉 gopush-cluster 的[架构](https://camo.githubusercontent.com/3c2f6df17ff0bace9f88e657819160f0bcb14a8c/687474703a2f2f7261772e6769746875622e636f6d2f54657272792d4d616f2f676f707573682d636c75737465722f6d61737465722f77696b692f6172636869746563747572652f6172636869746563747572652e6a7067) 60 | * 项目只能在 Linux 下开发(通过虚拟机方式方便一些) 61 | 62 | ### web 模块 63 | 64 | 迁出项目后需要修改 web.conf,用于在当前目录下运行的默认配置文件。 65 | * base.app.bind:应用接口监听端口 66 | * zookeeper.addr:gopush-cluster 集群节点通知 67 | * user daniel:当前操作系统登录用户 68 | * 其他默认 IP 绑定 69 | 70 | 启动命令 `./web -v=3 -logtostderr=true` 71 | 72 | ### Postman 73 | 74 | Postman 是一个 Chrome 扩展,用于开发时调试 HTTP 接口。 75 | 76 | * 在线[安装](https://chrome.google.com/webstore/detail/postman-rest-client/fdmmgilgnpjigdojojpjoooidkmcomcm)(**需翻墙**) / 离线[安装](https://github.com/a85/POSTMan-Chrome-Extension) 77 | * 导入[测试用例](https://www.getpostman.com/collections/cba11454feb866c965c3) 78 | * 修改测试用例中的 IP,开始测试 79 | 80 | ### GitBook 81 | 82 | * [《使用 GitBook 写文档》](http://88250.b3log.org/write-doc-via-gitbook) 83 | * **开发的同时我们也需要同步完善文档** 84 | * https://github.com/EPICPaaS/youxin-dev-guide 85 | * [《有信开发指南》](http://epicpaas.gitbooks.io/youxin-dev-guide/) 86 | 87 | ## 部署 88 | 89 | * static、view 需要放到 bin 目录下 90 | -------------------------------------------------------------------------------- /app/apnsmsg.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/EPICPaaS/appmsgsrv/db" 7 | //"github.com/EPICPaaS/go-uuid/uuid" 8 | ) 9 | 10 | const ( 11 | 12 | // 租户资源插入 SQL. 13 | InsertApnsMsgSQL = "INSERT INTO `apns_msg` (`id`, `type`, `apns_token`, `cnt`, `tenant_id`, `pushed`, `created`, `updated`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)" 14 | // 根据资源id 获取资源 15 | SelectApnsMsgByIdSQL = "SELECT * FROM `apns_msg` where `id` = ?" 16 | // 根据租户 id 查询租户的资源. 17 | SelectApnsMsgByTenantIdSQL = "SELECT * FROM `apns_msg` where `tenant_id` = ?" 18 | // 根据apns_token 查询租户的资源. 19 | SelectApnsMsgByApnsTokenSQL = "SELECT * FROM `apns_msg` where `apns_token` = ?" 20 | //根据id修改资源 21 | UpdateApnsMsgByIdSQL = "UPDATE `apns_msg` SET `type` = ? ,`apns_token` = ? , `cnt` = ? , `tenant_id` = ? ,`pushed` = ? , `created` = ? , `updated` = ? WHERE `id` = ?" 22 | //根据id删除资源 23 | DelApnsMsgByIdSQL = "DELETE FROM `apns_msg` WHERE `id` =? " 24 | //根据tenant_id删除资源 25 | DelApnsMsgByTenantIdSQL = "DELETE FROM `apns_msg` where `tenant_id` = ?" 26 | ) 27 | 28 | /** 29 | APNS消息 30 | **/ 31 | type ApnsMsg struct { 32 | Id string `json:"id"` 33 | Type string `json:"type"` 34 | ApnsToken string `json:"apns_token"` 35 | Cnt int `json:"cnt"` 36 | TenantId string `json:"tenant_id"` 37 | Pushed string `json:"pushed"` 38 | Created time.Time `json:"created"` 39 | Updated time.Time `json:"updated"` 40 | } 41 | 42 | // 在数据库中查询资源. 43 | func GetApnsMsgById(id string) (*ApnsMsg, error) { 44 | row := db.MySQL.QueryRow(SelectApnsMsgByIdSQL, id) 45 | 46 | apnsmsg := ApnsMsg{} 47 | if err := row.Scan(&apnsmsg.Id, &apnsmsg.Type, &apnsmsg.ApnsToken, &apnsmsg.Cnt, &apnsmsg.TenantId, &apnsmsg.Pushed, &apnsmsg.Created, &apnsmsg.Updated); err != nil { 48 | logger.Error(err) 49 | 50 | return nil, err 51 | } 52 | 53 | return &apnsmsg, nil 54 | } 55 | 56 | // 在数据库中查询资源. 57 | func GetApnsMsgByTenantId(tenantId string) ([]*ApnsMsg, error) { 58 | 59 | rows, _ := db.MySQL.Query(SelectApnsMsgByTenantIdSQL, tenantId) 60 | 61 | ret := []*ApnsMsg{} 62 | for rows.Next() { 63 | apnsmsg := ApnsMsg{} 64 | if err := rows.Scan(&apnsmsg.Id, &apnsmsg.Type, &apnsmsg.ApnsToken, &apnsmsg.Cnt, &apnsmsg.TenantId, &apnsmsg.Pushed, &apnsmsg.Created, &apnsmsg.Updated); err != nil { 65 | logger.Error(err) 66 | 67 | return nil, err 68 | } 69 | ret = append(ret, &apnsmsg) 70 | } 71 | 72 | return ret, nil 73 | } 74 | 75 | // 在数据库中查询资源. 76 | func GetApnsMsgByApnsToken(apnsToken string) ([]*ApnsMsg, error) { 77 | 78 | rows, _ := db.MySQL.Query(SelectApnsMsgByApnsTokenSQL, apnsToken) 79 | 80 | ret := []*ApnsMsg{} 81 | for rows.Next() { 82 | apnsmsg := ApnsMsg{} 83 | if err := rows.Scan(&apnsmsg.Id, &apnsmsg.Type, &apnsmsg.ApnsToken, &apnsmsg.Cnt, &apnsmsg.TenantId, &apnsmsg.Pushed, &apnsmsg.Created, &apnsmsg.Updated); err != nil { 84 | logger.Error(err) 85 | 86 | return nil, err 87 | } 88 | ret = append(ret, &apnsmsg) 89 | } 90 | 91 | return ret, nil 92 | } 93 | 94 | // 数据库中插入资源 95 | func AddApnsMsg(apnsMsg *ApnsMsg) (*ApnsMsg, bool) { 96 | tx, err := db.MySQL.Begin() 97 | 98 | if err != nil { 99 | logger.Error(err) 100 | return nil, false 101 | } 102 | 103 | // 创建资源记录 104 | _, err = tx.Exec(InsertResourceSQL, apnsMsg.Id, apnsMsg.Type, apnsMsg.ApnsToken, apnsMsg.Cnt, apnsMsg.TenantId, apnsMsg.Pushed, apnsMsg.Created, apnsMsg.Updated) 105 | if err != nil { 106 | logger.Error(err) 107 | 108 | if err := tx.Rollback(); err != nil { 109 | logger.Error(err) 110 | } 111 | return nil, false 112 | } 113 | 114 | if err := tx.Commit(); err != nil { 115 | logger.Error(err) 116 | return nil, false 117 | } 118 | return apnsMsg, true 119 | } 120 | 121 | // 数据库中插入资源 122 | func UpdateApnsMsgById(apnsMsg *ApnsMsg) (*ApnsMsg, bool) { 123 | tx, err := db.MySQL.Begin() 124 | 125 | if err != nil { 126 | logger.Error(err) 127 | return nil, false 128 | } 129 | 130 | // 创建资源记录 131 | _, err = tx.Exec(UpdateApnsMsgByIdSQL, apnsMsg.Type, apnsMsg.ApnsToken, apnsMsg.Cnt, apnsMsg.TenantId, apnsMsg.Pushed, apnsMsg.Created, apnsMsg.Updated, apnsMsg.Id) 132 | if err != nil { 133 | logger.Error(err) 134 | 135 | if err := tx.Rollback(); err != nil { 136 | logger.Error(err) 137 | } 138 | return nil, false 139 | } 140 | 141 | if err := tx.Commit(); err != nil { 142 | logger.Error(err) 143 | return nil, false 144 | } 145 | return apnsMsg, true 146 | } 147 | 148 | func DeleteApnsMsgById(id string) bool { 149 | tx, err := db.MySQL.Begin() 150 | 151 | if err != nil { 152 | logger.Error(err) 153 | 154 | return false 155 | } 156 | 157 | _, err = tx.Exec(DelApnsMsgByIdSQL, id) 158 | if err != nil { 159 | logger.Error(err) 160 | 161 | if err := tx.Rollback(); err != nil { 162 | logger.Error(err) 163 | } 164 | 165 | return false 166 | } 167 | 168 | if err := tx.Commit(); err != nil { 169 | logger.Error(err) 170 | 171 | return false 172 | } 173 | 174 | return true 175 | } 176 | 177 | func DeleteApnsMsgByTenantId(tenantId string) bool { 178 | tx, err := db.MySQL.Begin() 179 | 180 | if err != nil { 181 | logger.Error(err) 182 | 183 | return false 184 | } 185 | 186 | _, err = tx.Exec(DelApnsMsgByTenantIdSQL, tenantId) 187 | if err != nil { 188 | logger.Error(err) 189 | 190 | if err := tx.Rollback(); err != nil { 191 | logger.Error(err) 192 | } 193 | 194 | return false 195 | } 196 | 197 | if err := tx.Commit(); err != nil { 198 | logger.Error(err) 199 | 200 | return false 201 | } 202 | 203 | return true 204 | } 205 | -------------------------------------------------------------------------------- /app/app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/b3log/wide/log" 5 | "os" 6 | ) 7 | 8 | // 定义应用端操作结构. 9 | type app struct{} 10 | 11 | // 声明应用端操作接口. 12 | var App = app{} 13 | 14 | var logger = log.NewLogger(os.Stdout) 15 | -------------------------------------------------------------------------------- /app/appWeb.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | // 定义网页端操作结构. 4 | type appWeb struct{} 5 | 6 | // 声明网页端操作接口. 7 | var AppWeb = appWeb{} 8 | 9 | const ( 10 | APPWEB_TYPE = "appWeb" 11 | ) 12 | -------------------------------------------------------------------------------- /app/config.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package app 18 | 19 | import ( 20 | "flag" 21 | "fmt" 22 | "github.com/Terry-Mao/goconf" 23 | "runtime" 24 | "time" 25 | ) 26 | 27 | var ( 28 | Conf *Config 29 | confFile string 30 | ) 31 | 32 | const ( 33 | /*后缀信息*/ 34 | TENANT_SUFFIX = "@tenant" 35 | ORG_SUFFIX = "@org" 36 | QUN_SUFFIX = "@qun" 37 | USER_SUFFIX = "@user" 38 | APP_SUFFIX = "@app" 39 | ) 40 | 41 | // InitConfig initialize config file path 42 | func init() { 43 | flag.StringVar(&confFile, "c", "./web.conf", " set web config file path") 44 | } 45 | 46 | type Config struct { 47 | HttpBind []string `goconf:"base:http.bind:,"` 48 | AdminBind []string `goconf:"base:admin.bind:,"` 49 | AppBind []string `goconf:"base:app.bind:,"` 50 | MaxProc int `goconf:"base:maxproc"` 51 | PprofBind []string `goconf:"base:pprof.bind:,"` 52 | User string `goconf:"base:user"` 53 | PidFile string `goconf:"base:pidfile"` 54 | Dir string `goconf:"base:dir"` 55 | Router string `goconf:"base:router"` 56 | QQWryPath string `goconf:"res:qqwry.path"` 57 | ZookeeperAddr []string `goconf:"zookeeper:addr:,"` 58 | ZookeeperTimeout time.Duration `goconf:"zookeeper:timeout:time"` 59 | ZookeeperCometPath string `goconf:"zookeeper:comet.path"` 60 | ZookeeperMessagePath string `goconf:"zookeeper:message.path"` 61 | RPCRetry time.Duration `goconf:"rpc:retry:time"` 62 | RPCPing time.Duration `goconf:"rpc:ping:time"` 63 | RedisSource map[string]string `goconf:"-"` 64 | RedisIdleTimeout time.Duration `goconf:"redis:timeout:time"` 65 | RedisMaxIdle int `goconf:"redis:idle"` 66 | RedisMaxActive int `goconf:"redis:active"` 67 | RedisMaxStore int `goconf:"redis:store"` 68 | RedisKetamaBase int `goconf:"redis:ketama.base"` 69 | TokenExpire int `goconf:"token:expire"` 70 | ApnsType string `goconf:"apns:type"` 71 | WeedfsAddr string `goconf:"weedfs:address:"` 72 | MsgExpire int `goconf:"msg:expire"` 73 | } 74 | 75 | // InitConfig init configuration file. 76 | func InitConfig() error { 77 | gconf := goconf.New() 78 | if err := gconf.Parse(confFile); err != nil { 79 | logger.Errorf("goconf.Parse(\"%s\") error(%v)", confFile, err) 80 | return err 81 | } 82 | // Default config 83 | Conf = &Config{ 84 | HttpBind: []string{"localhost:80"}, 85 | AdminBind: []string{"localhost:81"}, 86 | MaxProc: runtime.NumCPU(), 87 | PprofBind: []string{"localhost:8190"}, 88 | User: "nobody nobody", 89 | PidFile: "/tmp/gopush-cluster-web.pid", 90 | Dir: "./", 91 | Router: "", 92 | QQWryPath: "/tmp/QQWry.dat", 93 | ZookeeperAddr: []string{":2181"}, 94 | ZookeeperTimeout: 30 * time.Second, 95 | ZookeeperCometPath: "/gopush-cluster-comet", 96 | ZookeeperMessagePath: "/gopush-cluster-message", 97 | RPCRetry: 3 * time.Second, 98 | RPCPing: 1 * time.Second, 99 | RedisSource: make(map[string]string), 100 | } 101 | 102 | if err := gconf.Unmarshal(Conf); err != nil { 103 | logger.Errorf("goconf.Unmarshall() error(%v)", err) 104 | return err 105 | } 106 | 107 | redisAddrsSec := gconf.Get("redis.source") 108 | if redisAddrsSec != nil { 109 | for _, key := range redisAddrsSec.Keys() { 110 | addr, err := redisAddrsSec.String(key) 111 | if err != nil { 112 | return fmt.Errorf("config section: \"redis.addrs\" key: \"%s\" error(%v)", key, err) 113 | } 114 | Conf.RedisSource[key] = addr 115 | } 116 | } 117 | return nil 118 | } 119 | -------------------------------------------------------------------------------- /app/contact.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/EPICPaaS/appmsgsrv/db" 6 | "github.com/EPICPaaS/go-uuid/uuid" 7 | "io/ioutil" 8 | "net/http" 9 | "time" 10 | ) 11 | 12 | const ( 13 | // 添加联系人. 14 | InsertUserUserSQL = "INSERT INTO `user_user` (`id`, `from_user_id`, `to_user_id`, `remark_name`, `sort`,`created`, `updated`) VALUES " + 15 | "(?, ?, ?, ?, ?, ?, ?)" 16 | // 删除联系人. 17 | DeleteUserUserSQL = "DELETE FROM `user_user` WHERE `from_user_id` = ? AND `to_user_id` = ?" 18 | ) 19 | 20 | // 联系人结构. 21 | type UserUser struct { 22 | Id string `json:"id"` 23 | FromUserId string `json:"fromUserId"` 24 | ToUserId string `json:"toUserId"` 25 | RemarkName string `json:"remarkName"` 26 | Sort int `json:"sort"` 27 | Created time.Time `json:"created"` 28 | Updated time.Time `json:"updated"` 29 | } 30 | 31 | /*添加或删除联系人 , 当请求参数starFriend为1时添加联系人,否则删除*/ 32 | func (*device) AddOrRemoveContact(w http.ResponseWriter, r *http.Request) { 33 | if r.Method != "POST" { 34 | http.Error(w, "Method Not Allowed", 405) 35 | return 36 | } 37 | 38 | baseRes := baseResponse{OK, ""} 39 | body := "" 40 | res := map[string]interface{}{"baseResponse": &baseRes} 41 | defer RetPWriteJSON(w, r, res, &body, time.Now()) 42 | 43 | bodyBytes, err := ioutil.ReadAll(r.Body) 44 | if err != nil { 45 | baseRes.Ret = ParamErr 46 | logger.Errorf("ioutil.ReadAll() failed (%s)", err.Error()) 47 | 48 | return 49 | } 50 | body = string(bodyBytes) 51 | 52 | var args map[string]interface{} 53 | 54 | if err := json.Unmarshal(bodyBytes, &args); err != nil { 55 | baseRes.ErrMsg = err.Error() 56 | baseRes.Ret = ParamErr 57 | 58 | return 59 | } 60 | 61 | baseReq := args["baseRequest"].(map[string]interface{}) 62 | 63 | // Token 校验 64 | token := baseReq["token"].(string) 65 | user := getUserByToken(token) 66 | if nil == user { 67 | baseRes.Ret = AuthErr 68 | 69 | return 70 | } 71 | 72 | fromUserId := baseReq["uid"].(string) 73 | toUserId := args["uid"].(string) 74 | starFriend := int(args["starFriend"].(float64)) 75 | 76 | now := time.Now() 77 | 78 | if 1 == starFriend { // 添加联系人 79 | if isStar(fromUserId, toUserId) { 80 | return 81 | } 82 | 83 | userUser := UserUser{Id: uuid.New(), FromUserId: fromUserId, ToUserId: toUserId, RemarkName: "", Sort: 0, 84 | Created: now, Updated: now} 85 | 86 | if !createContact(&userUser) { 87 | baseRes.Ret = InternalErr 88 | 89 | return 90 | } 91 | 92 | logger.Tracef("Created a contact [from=%s, to=%s]", fromUserId, toUserId) 93 | } else { // 删除联系人 94 | if !deleteContact(fromUserId, toUserId) { 95 | baseRes.Ret = InternalErr 96 | 97 | return 98 | } 99 | 100 | logger.Tracef("Deleted a contact [from=%s, to=%s]", fromUserId, toUserId) 101 | } 102 | } 103 | 104 | // 数据库中插入联系人记录. 105 | func createContact(userUser *UserUser) bool { 106 | tx, err := db.MySQL.Begin() 107 | 108 | if err != nil { 109 | logger.Error(err) 110 | 111 | return false 112 | } 113 | 114 | _, err = tx.Exec(InsertUserUserSQL, userUser.Id, userUser.FromUserId, userUser.ToUserId, userUser.RemarkName, 115 | userUser.Sort, userUser.Created, userUser.Updated) 116 | if err != nil { 117 | logger.Error(err) 118 | 119 | if err := tx.Rollback(); err != nil { 120 | logger.Error(err) 121 | } 122 | 123 | return false 124 | } 125 | 126 | if err := tx.Commit(); err != nil { 127 | logger.Error(err) 128 | 129 | return false 130 | } 131 | 132 | return true 133 | } 134 | 135 | // 在数据库中删除联系人记录. 136 | func deleteContact(fromUserId, toUserId string) bool { 137 | tx, err := db.MySQL.Begin() 138 | 139 | if err != nil { 140 | logger.Error(err) 141 | 142 | return false 143 | } 144 | 145 | _, err = tx.Exec(DeleteUserUserSQL, fromUserId, toUserId) 146 | if err != nil { 147 | logger.Error(err) 148 | 149 | if err := tx.Rollback(); err != nil { 150 | logger.Error(err) 151 | } 152 | 153 | return false 154 | } 155 | 156 | if err := tx.Commit(); err != nil { 157 | logger.Error(err) 158 | 159 | return false 160 | } 161 | 162 | return true 163 | } 164 | -------------------------------------------------------------------------------- /app/device.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | // 定义移动端操作结构. 4 | type device struct{} 5 | 6 | // 声明移动端操作接口. 7 | var Device = device{} 8 | 9 | const ( 10 | DEVICE_TYPE_IOS = "ios" 11 | DEVICE_TYPE_ANDROID = "android" 12 | ) 13 | -------------------------------------------------------------------------------- /app/fileLink.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/EPICPaaS/appmsgsrv/db" 6 | "github.com/EPICPaaS/go-uuid/uuid" 7 | "io/ioutil" 8 | "net/http" 9 | "strconv" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | const ( 15 | INSERT_FILELINK = "insert into file_link (id , sender_id,file_id ,file_name,file_url,size,created,updated) values(?,?,?,?,?,?,?,?)" 16 | UPDATE_FILELINK_TIME = "update file_link set updated =? where sender_id =? and file_id =?" 17 | EXIST_FILELINK = "select id from file_link where sender_id =? and file_id =?" 18 | SELECT_EXPIRE_FILELINK = "select id, file_url from file_link where updated < ?" 19 | ) 20 | 21 | var ScanFileTime = time.NewTicker(24 * time.Hour) 22 | 23 | type FileLink struct { 24 | Id string 25 | SenderId string 26 | FileId string 27 | FileName string 28 | FileUrl string 29 | Size int 30 | Created time.Time 31 | Updated time.Time 32 | } 33 | 34 | /*保存文件链接信息*/ 35 | func SaveFileLinK(fileLink *FileLink) bool { 36 | tx, err := db.MySQL.Begin() 37 | if err != nil { 38 | logger.Error(err) 39 | return false 40 | } 41 | //更新 42 | if ExistFileLink(fileLink) { 43 | _, err = tx.Exec(UPDATE_FILELINK_TIME, time.Now().Local(), fileLink.SenderId, fileLink.FileId) 44 | } else { //新增 45 | _, err = tx.Exec(INSERT_FILELINK, uuid.New(), fileLink.SenderId, fileLink.FileId, fileLink.FileName, fileLink.FileUrl, fileLink.Size, time.Now().Local(), time.Now().Local()) 46 | } 47 | if err != nil { 48 | logger.Error(err) 49 | if err := tx.Rollback(); err != nil { 50 | logger.Error(err) 51 | } 52 | return false 53 | } 54 | 55 | if err := tx.Commit(); err != nil { 56 | return false 57 | } 58 | 59 | return true 60 | } 61 | 62 | /*判断是否存在文件链接记录*/ 63 | func ExistFileLink(fileLink *FileLink) bool { 64 | rows, err := db.MySQL.Query(EXIST_FILELINK, fileLink.SenderId, fileLink.FileId) 65 | if err != nil { 66 | logger.Error(err) 67 | return false 68 | } 69 | 70 | defer rows.Close() 71 | 72 | if err = rows.Err(); err != nil { 73 | logger.Error(err) 74 | return false 75 | } 76 | return rows.Next() 77 | } 78 | 79 | /*删除weedfs服务器文件*/ 80 | func DeleteFile(fileUrl string) bool { 81 | 82 | var url = "http://" + fileUrl 83 | client := &http.Client{} 84 | req, err := http.NewRequest("DELETE", url, nil) 85 | if err != nil { 86 | logger.Errorf("delete file fail [ERROR]-%s", err.Error()) 87 | return false 88 | } 89 | resp, err := client.Do(req) 90 | if err != nil { 91 | logger.Errorf("delete file fail [ERROR]-%s", err.Error()) 92 | return false 93 | } 94 | 95 | body, err := ioutil.ReadAll(resp.Body) 96 | if err != nil { 97 | logger.Errorf("ioutil.ReadAll() failed (%s)", err.Error()) 98 | return false 99 | } 100 | var respBody map[string]interface{} 101 | if err := json.Unmarshal(body, &respBody); err != nil { 102 | logger.Errorf("convert to json failed (%s)", err.Error()) 103 | return false 104 | } 105 | e, ok := respBody["error"].(string) 106 | if ok { 107 | logger.Errorf("delete file fail [ERROR]- %s", e) 108 | return false 109 | } 110 | return true 111 | } 112 | 113 | /*定时扫描过期的文件链接,如果过期侧删除该文件记录和文件服务器中的文件*/ 114 | func ScanExpireFileLink() { 115 | 116 | //构造时间差 117 | subTimeStr := strconv.Itoa(Conf.MsgExpire) 118 | subTime, _ := time.ParseDuration("-" + subTimeStr + "s") 119 | /*定时任务删除,过期聊天文件*/ 120 | for _ = range ScanFileTime.C { 121 | 122 | n := time.Now().Local() 123 | 124 | expire := time.Now().Local().Add(subTime) 125 | rows, err := db.MySQL.Query(SELECT_EXPIRE_FILELINK, expire) 126 | if err != nil { 127 | logger.Error(err) 128 | continue 129 | } 130 | 131 | if err := rows.Err(); err != nil { 132 | rows.Close() 133 | logger.Error(err) 134 | continue 135 | } 136 | 137 | var delIds []string 138 | for rows.Next() { 139 | var id, fileUrl string 140 | if err := rows.Scan(&id, &fileUrl); err != nil { 141 | logger.Error(err) 142 | rows.Close() 143 | continue 144 | } 145 | 146 | //删除文件 147 | if DeleteFile(fileUrl) { 148 | delIds = append(delIds, id) 149 | } 150 | } 151 | 152 | //查询完毕后关闭链接 153 | rows.Close() 154 | 155 | /*删除文件记录*/ 156 | if len(delIds) > 0 { 157 | tx, err := db.MySQL.Begin() 158 | if err != nil { 159 | logger.Error(err) 160 | continue 161 | } 162 | delSql := "delete from file_link where id in ('" + strings.Join(delIds, "','") + "')" 163 | _, err = tx.Exec(delSql) 164 | if err != nil { 165 | logger.Error(err) 166 | if err := tx.Rollback(); err != nil { 167 | logger.Error(err) 168 | } 169 | continue 170 | } 171 | //提交操作 172 | if err := tx.Commit(); err != nil { 173 | logger.Error(err) 174 | continue 175 | } 176 | } 177 | //为了查看扫描性能 178 | logger.Infof("scan file succeed --- %v \n", time.Since(n)) 179 | } 180 | logger.Error("scan expire file logout ") 181 | } 182 | -------------------------------------------------------------------------------- /app/resource.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/EPICPaaS/appmsgsrv/db" 7 | 8 | //"github.com/EPICPaaS/go-uuid/uuid" 9 | ) 10 | 11 | const ( 12 | 13 | // 租户资源插入 SQL. 14 | InsertResourceSQL = "INSERT INTO `resource` (`id`, `customer_id`, `name`, `description`, `type`, `content`, `created`, `updated`) VALUES " + 15 | "(?, ?, ?, ?, ?, ?, ?, ?)" 16 | 17 | // 根据资源id 获取资源 18 | SelectResourceByIdSQL = "SELECT * FROM `resource` where `id` = ?" 19 | 20 | // 根据租户 id 查询租户的资源. 21 | SelectResourceByTenantIdSQL = "SELECT * FROM `resource` where `customer_id` = (select customer_id from tenant where id = ?)" 22 | 23 | //根据id修改资源 24 | UpdateResourceByIdSQL = "UPDATE `resource` SET `customer_id` = ? ,`name` = ? , `description` = ? , `type` = ? ,`content` = ? , `created` = ? , `updated` = ? WHERE `id` = ?" 25 | 26 | //根据id删除资源 27 | DelResourceByIdSQL = "DELETE FROM `resource` WHERE `id` =? " 28 | ) 29 | 30 | //租户证书资源结构 31 | type Resource struct { 32 | Id string `json:"id"` 33 | CustomerId string `json:"customerId"` 34 | Name string `json:"name"` 35 | Description string `json:"description"` 36 | Type string `json:"type"` 37 | Content string `json:"content"` 38 | Created time.Time `json:"created"` 39 | Updated time.Time `json:"updated"` 40 | } 41 | 42 | // 在数据库中查询资源. 43 | func GetResourceById(resourceId string) (*Resource, error) { 44 | row := db.MySQL.QueryRow(SelectResourceByIdSQL, resourceId) 45 | 46 | resource := Resource{} 47 | if err := row.Scan(&resource.Id, &resource.CustomerId, &resource.Name, &resource.Description, &resource.Type, &resource.Content, &resource.Created, &resource.Updated); err != nil { 48 | logger.Error(err) 49 | 50 | return nil, err 51 | } 52 | 53 | return &resource, nil 54 | } 55 | 56 | // 在数据库中查询资源. 57 | func GetResourceByTenantId(tenantId string) ([]*Resource, error) { 58 | 59 | rows, _ := db.MySQL.Query(SelectResourceByTenantIdSQL, tenantId) 60 | if rows != nil { 61 | defer rows.Close() 62 | } 63 | ret := []*Resource{} 64 | for rows.Next() { 65 | resource := &Resource{} 66 | if err := rows.Scan(&resource.Id, &resource.CustomerId, &resource.Name, &resource.Description, &resource.Type, &resource.Content, &resource.Created, &resource.Updated); err != nil { 67 | logger.Error(err) 68 | 69 | return nil, err 70 | } 71 | ret = append(ret, resource) 72 | } 73 | 74 | return ret, nil 75 | } 76 | 77 | // 数据库中插入资源 78 | func AddResource(resource *Resource) (*Resource, bool) { 79 | tx, err := db.MySQL.Begin() 80 | 81 | if err != nil { 82 | logger.Error(err) 83 | return nil, false 84 | } 85 | 86 | // 创建资源记录 87 | _, err = tx.Exec(InsertResourceSQL, resource.Id, resource.CustomerId, resource.Name, resource.Description, resource.Type, resource.Content, resource.Created, resource.Updated) 88 | if err != nil { 89 | logger.Error(err) 90 | 91 | if err := tx.Rollback(); err != nil { 92 | logger.Error(err) 93 | } 94 | return nil, false 95 | } 96 | 97 | if err := tx.Commit(); err != nil { 98 | logger.Error(err) 99 | return nil, false 100 | } 101 | return resource, true 102 | } 103 | 104 | // 数据库中插入资源 105 | func UpdateResource(resource *Resource) (*Resource, bool) { 106 | tx, err := db.MySQL.Begin() 107 | 108 | if err != nil { 109 | logger.Error(err) 110 | return nil, false 111 | } 112 | 113 | // 创建资源记录 114 | _, err = tx.Exec(UpdateResourceByIdSQL, resource.CustomerId, resource.Name, resource.Description, resource.Type, resource.Content, resource.Created, resource.Updated, resource.Id) 115 | if err != nil { 116 | logger.Error(err) 117 | 118 | if err := tx.Rollback(); err != nil { 119 | logger.Error(err) 120 | } 121 | return nil, false 122 | } 123 | 124 | if err := tx.Commit(); err != nil { 125 | logger.Error(err) 126 | return nil, false 127 | } 128 | return resource, true 129 | } 130 | 131 | //删除资源 132 | func DeleteResourceById(resourceId string) bool { 133 | tx, err := db.MySQL.Begin() 134 | 135 | if err != nil { 136 | logger.Error(err) 137 | 138 | return false 139 | } 140 | 141 | _, err = tx.Exec(DelResourceByIdSQL, resourceId) 142 | if err != nil { 143 | logger.Error(err) 144 | 145 | if err := tx.Rollback(); err != nil { 146 | logger.Error(err) 147 | } 148 | 149 | return false 150 | } 151 | 152 | if err := tx.Commit(); err != nil { 153 | logger.Error(err) 154 | 155 | return false 156 | } 157 | 158 | return true 159 | } 160 | -------------------------------------------------------------------------------- /app/ret.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "time" 8 | ) 9 | 10 | const ( 11 | OK = 0 12 | NotFoundServer = 1001 13 | NotFound = 65531 14 | TooLong = 65532 15 | AuthErr = 65533 16 | ParamErr = 65534 17 | InternalErr = 65535 18 | OverQuotaApicall = 65536 19 | OverQuotaPush = 65537 20 | DeleteUser = 65538 21 | LoginErr = 65539 22 | ) 23 | 24 | // 响应基础结构. 25 | type baseResponse struct { 26 | Ret int `json:"ret"` 27 | ErrMsg string `json:"errMsg"` 28 | } 29 | 30 | // retWrite marshal the result and write to client(get). 31 | func RetWrite(w http.ResponseWriter, r *http.Request, res map[string]interface{}, callback string, start time.Time) { 32 | data, err := json.Marshal(res) 33 | if err != nil { 34 | logger.Errorf("json.Marshal(\"%v\") error(%v)", res, err) 35 | return 36 | } 37 | dataStr := "" 38 | if callback == "" { 39 | // Normal json 40 | dataStr = string(data) 41 | } else { 42 | // Jsonp 43 | dataStr = fmt.Sprintf("%s(%s)", callback, string(data)) 44 | } 45 | if n, err := w.Write([]byte(dataStr)); err != nil { 46 | logger.Errorf("w.Write(\"%s\") error(%v)", dataStr, err) 47 | } else { 48 | logger.Tracef("w.Write(\"%s\") write %d bytes", dataStr, n) 49 | } 50 | 51 | logger.Tracef("req: \"%s\", res:\"%s\", ip:\"%s\", time:\"%fs\"", r.URL.String(), dataStr, r.RemoteAddr, time.Now().Sub(start).Seconds()) 52 | } 53 | 54 | // retPWrite marshal the result and write to client(post). 55 | func RetPWrite(w http.ResponseWriter, r *http.Request, res map[string]interface{}, body *string, start time.Time) { 56 | data, err := json.Marshal(res) 57 | if err != nil { 58 | logger.Errorf("json.Marshal(\"%v\") error(%v)", res, err) 59 | return 60 | } 61 | dataStr := string(data) 62 | if n, err := w.Write([]byte(dataStr)); err != nil { 63 | logger.Errorf("w.Write(\"%s\") error(%v)", dataStr, err) 64 | } else { 65 | logger.Tracef("w.Write(\"%s\") write %d bytes", dataStr, n) 66 | } 67 | 68 | logger.Tracef("req: \"%s\", post: \"%s\", res:\"%s\", ip:\"%s\", time:\"%fs\"", r.URL.String(), *body, dataStr, r.RemoteAddr, time.Now().Sub(start).Seconds()) 69 | } 70 | 71 | // 带 Content-Type=application/json 头 写 JSON 数据,为了保持原有 gopush-cluster 的兼容性,所以新加了这个函数. 72 | func RetPWriteJSON(w http.ResponseWriter, r *http.Request, res map[string]interface{}, body *string, start time.Time) { 73 | w.Header().Set("Content-Type", "application/json") 74 | 75 | RetPWrite(w, r, res, body, start) 76 | } 77 | -------------------------------------------------------------------------------- /app/session.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/EPICPaaS/appmsgsrv/session" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | /*请求参数 13 | baseRequest: { 14 | "uid": "", 15 | "deviceID": "", 16 | "deviceType": "", // iOS / Android 17 | "token": "" 18 | } 19 | state:"active" 20 | sessionId:"1111" 21 | 设置会话状态(active/inactive) 22 | */ 23 | func SessionStat(w http.ResponseWriter, r *http.Request) { 24 | 25 | if r.Method != "POST" { 26 | http.Error(w, "Method Not Allowed", 405) 27 | return 28 | } 29 | 30 | baseRes := baseResponse{OK, ""} 31 | body := "" 32 | res := map[string]interface{}{"baseResponse": &baseRes} 33 | defer RetPWriteJSON(w, r, res, &body, time.Now()) 34 | 35 | bodyBytes, err := ioutil.ReadAll(r.Body) 36 | if err != nil { 37 | res["ret"] = ParamErr 38 | logger.Errorf("ioutil.ReadAll() failed (%s)", err.Error()) 39 | return 40 | } 41 | body = string(bodyBytes) 42 | 43 | var args map[string]interface{} 44 | 45 | if err := json.Unmarshal(bodyBytes, &args); err != nil { 46 | baseRes.ErrMsg = err.Error() 47 | baseRes.Ret = ParamErr 48 | return 49 | } 50 | 51 | baseReq := args["baseRequest"].(map[string]interface{}) 52 | deviceType, ok := baseReq["deviceType"].(string) 53 | if ok { 54 | deviceType = strings.ToLower(deviceType) 55 | } 56 | /* Token 校验,分为用户校验和应用校验*/ 57 | token := baseReq["token"].(string) 58 | //用户校验 59 | if deviceType == DEVICE_TYPE_IOS || deviceType == DEVICE_TYPE_ANDROID { 60 | user := getUserByToken(token) 61 | if nil == user { 62 | baseRes.Ret = AuthErr 63 | return 64 | } 65 | } else { //应用校验 66 | _, err := getApplicationByToken(token) 67 | if nil != err { 68 | baseRes.Ret = AuthErr 69 | return 70 | } 71 | } 72 | //修改会话状态 73 | state := args["state"].(string) 74 | sessionId := args["sessionId"].(string) 75 | if ("active" == state || "inactive" == state) && len(sessionId) > 0 { 76 | if !session.SetSessionStat(sessionId, state) { 77 | baseRes.Ret = NotFound 78 | baseRes.ErrMsg = "设置会话状态失败!" 79 | return 80 | } 81 | } else { 82 | baseRes.Ret = ParamErr 83 | baseRes.ErrMsg = "参数格式错误,state值只能是:active/inactive,会话id不能为空)" 84 | return 85 | } 86 | } 87 | 88 | /* 89 | baseRequest: { 90 | "uid": "", 91 | "deviceID": "", 92 | "deviceType": "", // iOS / Android 93 | "token": "" 94 | } 95 | 获取用户会话session 96 | */ 97 | func (*app) GetSession(w http.ResponseWriter, r *http.Request) { 98 | if r.Method != "POST" { 99 | http.Error(w, "Method Not Allowed", 405) 100 | return 101 | } 102 | 103 | baseRes := baseResponse{OK, ""} 104 | body := "" 105 | res := map[string]interface{}{"baseResponse": &baseRes} 106 | defer RetPWriteJSON(w, r, res, &body, time.Now()) 107 | 108 | bodyBytes, err := ioutil.ReadAll(r.Body) 109 | if err != nil { 110 | res["ret"] = ParamErr 111 | logger.Errorf("ioutil.ReadAll() failed (%s)", err.Error()) 112 | return 113 | } 114 | body = string(bodyBytes) 115 | 116 | var args map[string]interface{} 117 | 118 | if err := json.Unmarshal(bodyBytes, &args); err != nil { 119 | baseRes.ErrMsg = err.Error() 120 | baseRes.Ret = ParamErr 121 | return 122 | } 123 | 124 | baseReq := args["baseRequest"].(map[string]interface{}) 125 | token := baseReq["token"].(string) 126 | 127 | //应用校验 128 | _, err = getApplicationByToken(token) 129 | if nil != err { 130 | baseRes.Ret = AuthErr 131 | return 132 | } 133 | 134 | //获取用户会话列表 135 | userId := baseReq["uid"].(string) 136 | ret := session.GetSessions(userId, []string{"all"}) 137 | res["memberList"] = ret 138 | 139 | return 140 | } 141 | -------------------------------------------------------------------------------- /appweb/conf/app.conf: -------------------------------------------------------------------------------- 1 | appname = appweb 2 | httpport = 8080 3 | runmode = dev 4 | 5 | driverName= mysql 6 | dataSource= root:123456@tcp(10.180.120.63:3308)/appmsgsrv?charset=utf8&loc=UTC 7 | maxIdle= 10 8 | maxOpen= 50 -------------------------------------------------------------------------------- /appweb/controllers/index.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/EPICPaaS/appmsgsrv/appweb/models" 5 | "github.com/astaxie/beego" 6 | ) 7 | 8 | type IndexController struct { 9 | beego.Controller 10 | } 11 | 12 | func (this *IndexController) Get() { 13 | this.TplNames = "index.html" 14 | user := this.GetSession("user").(*models.User) 15 | orgs := models.GetRootOrg(user.TenantId) 16 | this.Data["orgs"] = orgs 17 | this.Data["User"] = user 18 | users := models.GetOrgUsersById("23622391649369951") 19 | this.Data["users"] = users 20 | } 21 | 22 | //获取下一单位 23 | func (this *IndexController) GetChildOrgs() { 24 | 25 | parentId := this.Input().Get("parentId") 26 | orgs := models.GetChildOrgs(parentId) 27 | this.Data["json"] = orgs 28 | this.ServeJson() 29 | this.StopRun() 30 | } 31 | 32 | //获取单位用户 33 | func (this *IndexController) GetOrgUserById() { 34 | orgId := this.Input().Get("orgId") 35 | users := models.GetOrgUsersById(orgId) 36 | this.Data["json"] = users 37 | this.ServeJson() 38 | this.StopRun() 39 | } 40 | 41 | //获取用户的群 42 | func (this *IndexController) GetMyQun() { 43 | 44 | user := this.GetSession("user").(*models.User) 45 | this.Data["json"] = models.GetMyQun(user.Id) 46 | this.ServeJson() 47 | this.StopRun() 48 | 49 | } 50 | -------------------------------------------------------------------------------- /appweb/controllers/ret.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | type Result struct { 4 | Success bool 5 | Msg string 6 | Data interface{} 7 | } 8 | -------------------------------------------------------------------------------- /appweb/controllers/user.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | //"fmt" 5 | "github.com/EPICPaaS/appmsgsrv/appweb/models" 6 | "github.com/astaxie/beego" 7 | ) 8 | 9 | /*user控制器*/ 10 | type UserController struct { 11 | beego.Controller 12 | } 13 | 14 | func (this *UserController) Get() { 15 | 16 | this.TplNames = "login.html" 17 | this.Data["Tenants"] = models.GetAll() 18 | } 19 | 20 | func (this *UserController) Post() { 21 | 22 | //返回结果 23 | ret := Result{ 24 | Success: true, 25 | } 26 | uname := this.Input().Get("userName") 27 | tenantId := this.Input().Get("tenantId") 28 | pwd := this.Input().Get("password") 29 | user := models.GetUserByNameTenantId(uname, tenantId) 30 | 31 | if user != nil && user.Password == pwd { 32 | ret.Data = user 33 | this.SetSession("user", user) 34 | 35 | } else { 36 | ret.Success = false 37 | ret.Msg = "用户名或密码错误" 38 | } 39 | 40 | this.Data["json"] = ret 41 | this.ServeJson() 42 | this.StopRun() 43 | } 44 | 45 | func (this *UserController) Logout() { 46 | //清除缓存 47 | this.DelSession("user") 48 | this.Redirect("/", 302) 49 | } 50 | -------------------------------------------------------------------------------- /appweb/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "github.com/EPICPaaS/appmsgsrv/appweb/routers" 5 | _ "github.com/EPICPaaS/appmsgsrv/appweb/setting" 6 | "github.com/astaxie/beego" 7 | "runtime" 8 | ) 9 | 10 | func main() { 11 | 12 | runtime.GOMAXPROCS(runtime.NumCPU()) 13 | beego.Run() 14 | beego.SetStaticPath("/static", "static") 15 | } 16 | -------------------------------------------------------------------------------- /appweb/models/org.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "bytes" 5 | "github.com/astaxie/beego" 6 | "github.com/astaxie/beego/orm" 7 | _ "github.com/go-sql-driver/mysql" 8 | "time" 9 | ) 10 | 11 | type Tenant struct { 12 | Id string `orm:"column(id);pk"` 13 | Code string 14 | Name string 15 | Status int 16 | CustomerId string 17 | Created time.Time 18 | Updated time.Time 19 | } 20 | 21 | type Org struct { 22 | Id string `orm:"column(id);pk"` 23 | Name string 24 | ShortName string 25 | ParentId string 26 | Location string 27 | TenantId string 28 | Sort int 29 | } 30 | 31 | func GetAll() *[]Tenant { 32 | 33 | o := orm.NewOrm() 34 | tenants := make([]Tenant, 0) 35 | qs := o.QueryTable("tenant") 36 | _, err := qs.All(&tenants) 37 | if err != nil { 38 | beego.Error(err) 39 | return nil 40 | } 41 | return &tenants 42 | } 43 | 44 | //获取一级单位 45 | func GetRootOrg(tenantId string) *[]Org { 46 | 47 | if len(tenantId) == 0 { 48 | return nil 49 | } 50 | o := orm.NewOrm() 51 | qs := o.QueryTable("org") 52 | orgs := &[]Org{} 53 | _, err := qs.Filter("tenant_id", tenantId).Filter("parent_id", "").All(orgs) 54 | if err != nil { 55 | beego.Error(err) 56 | return nil 57 | } 58 | return orgs 59 | } 60 | 61 | //获取指定单位的直属下级单位 62 | func GetChildOrgs(parentId string) *[]Org { 63 | if len(parentId) == 0 { 64 | return nil 65 | } 66 | o := orm.NewOrm() 67 | qs := o.QueryTable("org") 68 | orgs := &[]Org{} 69 | _, err := qs.Filter("parent_id", parentId).All(orgs) 70 | if err != nil { 71 | beego.Error(err) 72 | return nil 73 | } 74 | return orgs 75 | } 76 | 77 | //获取指定单位的所有下级单位 78 | func GetOrgsByParentId(parentId string) *[]Org { 79 | if len(parentId) == 0 { 80 | return nil 81 | } 82 | o := orm.NewOrm() 83 | orgs := &[]Org{} 84 | _, err := o.Raw("select * from (select location from org where id =?) t1 , org t2 where t2.location like CONCAT(t1.location,'%')", parentId).QueryRows(orgs) 85 | if err != nil { 86 | beego.Error(err) 87 | return nil 88 | } 89 | return orgs 90 | } 91 | 92 | //获取单位下的所有人员 93 | func GetOrgUsersById(orgId string) *[]User { 94 | if len(orgId) == 0 { 95 | return nil 96 | } 97 | o := orm.NewOrm() 98 | //查询该单位的所有下级单位 99 | orgs := GetOrgsByParentId(orgId) 100 | if orgs != nil { 101 | users := &[]User{} 102 | //拼接查询语句 103 | selectUser := bytes.Buffer{} 104 | selectUser.WriteString("select * from user where id in (select user_id from org_user where ") 105 | ls := len(*orgs) - 1 106 | for i, org := range *orgs { 107 | selectUser.WriteString(" org_id =" + org.Id) 108 | if i != ls { 109 | selectUser.WriteString(" or ") 110 | } else { 111 | selectUser.WriteString(" )") 112 | } 113 | } 114 | 115 | _, err := o.Raw(selectUser.String()).QueryRows(users) 116 | 117 | if err != nil { 118 | beego.Error(err) 119 | } 120 | return users 121 | } 122 | 123 | return nil 124 | } 125 | -------------------------------------------------------------------------------- /appweb/models/qun.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | "github.com/astaxie/beego" 6 | "github.com/astaxie/beego/orm" 7 | _ "github.com/go-sql-driver/mysql" 8 | "time" 9 | ) 10 | 11 | type Qun struct { 12 | Id string `orm:"column(id);pk"` 13 | CreatorId string 14 | Name string 15 | Description string 16 | MaxMember string 17 | Avatar string 18 | Created time.Time 19 | Updated time.Time 20 | } 21 | 22 | type QunUser struct { 23 | Id string `orm:"column(id);pk"` 24 | QunId string 25 | UserId string 26 | Sort int 27 | role int 28 | Created time.Time 29 | Updated time.Time 30 | } 31 | 32 | //获取用户所参与的群 33 | func GetMyQun(userId string) *[]Qun { 34 | if len(userId) == 0 { 35 | return nil 36 | } 37 | 38 | o := orm.NewOrm() 39 | quns := &[]Qun{} 40 | _, err := o.Raw("select * from qun where id in (SELECT qun_id from qun_user where user_id = ?)", userId).QueryRows(quns) 41 | if err != nil { 42 | beego.Error(err) 43 | return nil 44 | } 45 | fmt.Println(userId, "群:", quns) 46 | return quns 47 | } 48 | -------------------------------------------------------------------------------- /appweb/models/user.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | "github.com/astaxie/beego" 6 | "github.com/astaxie/beego/context" 7 | "github.com/astaxie/beego/orm" 8 | _ "github.com/go-sql-driver/mysql" 9 | "time" 10 | ) 11 | 12 | type User struct { 13 | Id string `orm:"column(id);pk"` 14 | Name string 15 | Nickname string 16 | Avatar string 17 | NamePy string 18 | NameQuanpin string 19 | Status int 20 | Rand int 21 | Password string 22 | TenantId string 23 | Level int 24 | Email string 25 | Mobile string 26 | Area string 27 | Created time.Time 28 | Updated time.Time 29 | } 30 | 31 | /*获取*/ 32 | func GetUserByNameTenantId(userName, tenantId string) *User { 33 | 34 | var users []User 35 | o := orm.NewOrm() 36 | num, err := o.Raw("SELECT * FROM user WHERE name = ? and tenant_id=?", userName, tenantId).QueryRows(&users) 37 | if err != nil { 38 | fmt.Println("查询出错") 39 | beego.Error(err) 40 | return nil 41 | } 42 | if num > 0 { 43 | return &users[0] 44 | } 45 | return nil 46 | } 47 | 48 | //验证用户是否登陆 49 | var IsUserLogin = func(ctx *context.Context) { 50 | user := ctx.Input.Session("user") 51 | if user == nil && ctx.Request.RequestURI != "/login" { 52 | ctx.Redirect(302, "/") 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /appweb/routers/router.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | "github.com/EPICPaaS/appmsgsrv/appweb/controllers" 5 | "github.com/astaxie/beego" 6 | ) 7 | 8 | func init() { 9 | beego.Router("/", &controllers.UserController{}) 10 | beego.Router("/login", &controllers.UserController{}) 11 | beego.Router("/logout", &controllers.UserController{}, "get:Logout") 12 | beego.Router("/index", &controllers.IndexController{}) 13 | beego.Router("/GetOrgUser", &controllers.IndexController{}, "post:GetOrgUserById") 14 | beego.Router("/GetMyQun", &controllers.IndexController{}, "post:GetMyQun") 15 | beego.Router("/GetChildOrgs", &controllers.IndexController{}, "post:GetChildOrgs") 16 | } 17 | -------------------------------------------------------------------------------- /appweb/setting/setting.go: -------------------------------------------------------------------------------- 1 | package setting 2 | 3 | import ( 4 | "github.com/EPICPaaS/appmsgsrv/appweb/models" 5 | "github.com/astaxie/beego" 6 | "github.com/astaxie/beego/orm" 7 | _ "github.com/go-sql-driver/mysql" 8 | ) 9 | 10 | func init() { 11 | // 注册模型 12 | orm.Debug = true 13 | orm.RegisterModel(new(models.User), new(models.Tenant), new(models.Org)) 14 | RegisterDB() 15 | InitSession() 16 | beego.InsertFilter("/index", beego.BeforeExec, models.IsUserLogin) 17 | 18 | } 19 | 20 | func RegisterDB() { 21 | 22 | driverName := beego.AppConfig.String("driverName") 23 | dataSource := beego.AppConfig.String("dataSource") 24 | maxIdle, _ := beego.AppConfig.Int("maxIdle") 25 | maxOpen, _ := beego.AppConfig.Int("maxOpen") 26 | // 注册驱动 27 | orm.RegisterDriver("mysql", orm.DR_MySQL) 28 | // 注册默认数据库 29 | // set default database 30 | err := orm.RegisterDataBase("default", driverName, dataSource, maxIdle, maxOpen) 31 | orm.RunCommand() 32 | //不自动建表 33 | err = orm.RunSyncdb("default", false, false) 34 | if err != nil { 35 | beego.Error(err) 36 | } 37 | } 38 | 39 | func InitSession() { 40 | beego.SessionOn = true 41 | beego.SessionProvider = "memory" 42 | beego.SessionGCMaxLifetime = 600 //60 seconds 43 | beego.SessionName = "yxUser" 44 | beego.SessionCookieLifeTime = 600 //60 seconds 45 | beego.SessionAutoSetCookie = true 46 | beego.SessionSavePath = "/" 47 | } 48 | -------------------------------------------------------------------------------- /appweb/static/css/login.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | /* CSS Document */ 3 | 4 | /** 5 | * Copyright 远信科技, All Rights Reserved. 6 | */ 7 | 8 | /** 9 | * @fileoverview 这是产品首页的css文件 10 | * 11 | * @author: Zhigang.li 12 | * @author Liyuan Li 13 | * @version 1.0.0.3, Feb 24, 2014 14 | */ 15 | body { 16 | background: #369dcc; 17 | margin: 0; 18 | font-family: '微软雅黑'; 19 | color: #FFF; 20 | background:url(/static/images/loginb.png) no-repeat; 21 | 22 | } 23 | div{ 24 | box-sizing: border-box; 25 | } 26 | 27 | .wrapper { 28 | margin: 0 auto; 29 | width: 620px; 30 | overflow: hidden; 31 | } 32 | .logo{ 33 | background: url(/static/images/logo.png) center center no-repeat; 34 | height:150px; 35 | width:600px; 36 | } 37 | .org{ 38 | height:24px; 39 | } 40 | .selectOrg{ 41 | float:left; 42 | margin-left:35px; 43 | width:110px; 44 | height:24px; 45 | text-align:center; 46 | font-size:14px; 47 | cursor:pointer; 48 | line-height:23px; 49 | background:url(/static/images/selectOrg.png) center bottom no-repeat; 50 | } 51 | .orgName{ 52 | float:left; 53 | height:24px; 54 | } 55 | .loginForm { 56 | height:51px; 57 | width:620px; 58 | background: url(/static/images/loginForm.png) no-repeat center center; 59 | overflow:hidden; 60 | } 61 | .parameter{ 62 | width:569px; 63 | height:51px; 64 | float:left; 65 | padding-top:8px; 66 | padding-left:15px; 67 | } 68 | .icon{ 69 | width:30px; 70 | height:35px; 71 | display:block; 72 | float:left; 73 | } 74 | .icon-user{ 75 | background:#FFF url(/static/images/one4.jpg) no-repeat center center; 76 | } 77 | .icon-pwd{ 78 | background:#FFF url(/static/images/one5.jpg) no-repeat center center; 79 | } 80 | .form-input{ 81 | display:block; 82 | float:left; 83 | width:236px; 84 | box-sizing: border-box; 85 | border:none; 86 | height:35px; 87 | padding: 5px ; 88 | outline: 0; 89 | } 90 | .login-btn{ 91 | height:51px; 92 | width:51px; 93 | float:left; 94 | cursor:pointer; 95 | text-align:center; 96 | line-height:45px; 97 | } 98 | .slide { 99 | width: 1440px; 100 | background:#633; 101 | } 102 | 103 | .bg-top, 104 | .bg-bottom { 105 | top: 0; 106 | width: 100%; 107 | position: fixed; 108 | z-index: -15; 109 | } 110 | .bg-bottom { 111 | bottom: 0; 112 | top: auto; 113 | } 114 | 115 | .org-panel { 116 | display:none; 117 | width:220px; 118 | height:270px; 119 | float:left; 120 | left:50%; 121 | margin-left:-130px; 122 | margin-top:-200px; 123 | position:absolute; 124 | z-index:10px; 125 | overflow-y:auto; 126 | background-color:#FFF; 127 | } 128 | .org-panel ol ,li { 129 | list-style:none; 130 | padding:0px; 131 | color:#666; 132 | } 133 | .org-panel ol li { 134 | height:25px; 135 | text-indent:20px; 136 | line-height:25px; 137 | cursor:pointer; 138 | } 139 | .org-panel ol li:hover{ 140 | color:#CC0033 ; 141 | } 142 | -------------------------------------------------------------------------------- /appweb/static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPICPaaS/appmsgsrv/537dd8c8fa6b4c4211a225ffd1b215553d58d876/appweb/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /appweb/static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPICPaaS/appmsgsrv/537dd8c8fa6b4c4211a225ffd1b215553d58d876/appweb/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /appweb/static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPICPaaS/appmsgsrv/537dd8c8fa6b4c4211a225ffd1b215553d58d876/appweb/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /appweb/static/images/bg-bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPICPaaS/appmsgsrv/537dd8c8fa6b4c4211a225ffd1b215553d58d876/appweb/static/images/bg-bottom.png -------------------------------------------------------------------------------- /appweb/static/images/bg-top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPICPaaS/appmsgsrv/537dd8c8fa6b4c4211a225ffd1b215553d58d876/appweb/static/images/bg-top.png -------------------------------------------------------------------------------- /appweb/static/images/loginForm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPICPaaS/appmsgsrv/537dd8c8fa6b4c4211a225ffd1b215553d58d876/appweb/static/images/loginForm.png -------------------------------------------------------------------------------- /appweb/static/images/loginb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPICPaaS/appmsgsrv/537dd8c8fa6b4c4211a225ffd1b215553d58d876/appweb/static/images/loginb.png -------------------------------------------------------------------------------- /appweb/static/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPICPaaS/appmsgsrv/537dd8c8fa6b4c4211a225ffd1b215553d58d876/appweb/static/images/logo.png -------------------------------------------------------------------------------- /appweb/static/images/one4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPICPaaS/appmsgsrv/537dd8c8fa6b4c4211a225ffd1b215553d58d876/appweb/static/images/one4.jpg -------------------------------------------------------------------------------- /appweb/static/images/one5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPICPaaS/appmsgsrv/537dd8c8fa6b4c4211a225ffd1b215553d58d876/appweb/static/images/one5.jpg -------------------------------------------------------------------------------- /appweb/static/images/selectOrg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPICPaaS/appmsgsrv/537dd8c8fa6b4c4211a225ffd1b215553d58d876/appweb/static/images/selectOrg.png -------------------------------------------------------------------------------- /appweb/static/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /appweb/views/base/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{template "base/head.html" .}} 5 | {{template "meta" .}} 6 | {{template "addCSS" .}} 7 | 8 | 9 |
10 | {{template "top" .}} 11 |
12 | {{template "body" .}} 13 |
14 |
15 | {{template "footer" .}} 16 | {{template "addJS" .}} 17 | 18 | -------------------------------------------------------------------------------- /appweb/views/base/base_common.html: -------------------------------------------------------------------------------- 1 | {{define "top"}} 2 | {{end}} 3 | {{define "footer"}} 4 | {{template "base/foot.html" .}} 5 | {{end}} -------------------------------------------------------------------------------- /appweb/views/base/foot.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPICPaaS/appmsgsrv/537dd8c8fa6b4c4211a225ffd1b215553d58d876/appweb/views/base/foot.html -------------------------------------------------------------------------------- /appweb/views/base/head.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /appweb/views/login.html: -------------------------------------------------------------------------------- 1 | {{template "base/base.html" .}} 2 | {{template "base/base_common.html" .}} 3 | {{define "meta"}} 4 | 有信网页版 5 | {{end}} 6 | 7 | {{define "addCSS"}} 8 | 9 | {{end}} 10 | 11 | {{define "addJS"}} 12 | 54 | {{end}} 55 | 56 | {{define "body"}} 57 |
58 | 59 |
60 |
61 | 选择单位 62 |
63 |
64 |
65 | 66 |
67 |
68 |
69 | 70 | 71 | 72 | 73 | 74 |
75 |
76 | 79 |
80 |
81 |
82 |
83 |
84 |
    85 | {{range .Tenants}} 86 |
  1. {{.Name}}
  2. 87 | {{end}} 88 |
89 |
90 | 91 | 92 | {{end}} -------------------------------------------------------------------------------- /client/js/README.md: -------------------------------------------------------------------------------- 1 | appmsgsrv-js 2 | ====== 3 | 4 | 应用消息服务 JavaScript 客户端 5 | -------------------------------------------------------------------------------- /client/js/demo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "html/template" 6 | "net/http" 7 | ) 8 | 9 | func index(w http.ResponseWriter, r *http.Request) { 10 | t, _ := template.ParseFiles("template/demo.html") 11 | 12 | t.Execute(w, nil) 13 | } 14 | 15 | func main() { 16 | fmt.Println("启动 Demo 应用") 17 | 18 | http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) 19 | http.HandleFunc("/", index) 20 | http.ListenAndServe(":8310", nil) 21 | } 22 | -------------------------------------------------------------------------------- /client/js/demo/static/ws-flash/WebSocketMain.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPICPaaS/appmsgsrv/537dd8c8fa6b4c4211a225ffd1b215553d58d876/client/js/demo/static/ws-flash/WebSocketMain.swf -------------------------------------------------------------------------------- /client/js/demo/template/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Go Push Cluster JavaScript Demo 6 | 7 | 32 | 33 | 34 | 35 | 38 | 39 | 40 |
41 |
42 | 43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /client/js/flash-security-policy-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net" 7 | "os" 8 | "os/signal" 9 | "strconv" 10 | "syscall" 11 | ) 12 | 13 | var ( 14 | socketpolicy []byte 15 | port int = 843 16 | socketpolicyfile string = "socketpolicy.xml" 17 | ) 18 | 19 | func signal_arm() { 20 | fmt.Println("signal armed") 21 | 22 | go func() { 23 | for { 24 | c := make(chan os.Signal) 25 | signal.Notify(c, syscall.SIGINT) 26 | sig := <-c 27 | 28 | fmt.Println("%s", sig.String()) 29 | 30 | switch sig { 31 | case syscall.SIGINT: 32 | os.Exit(0) 33 | default: 34 | //os.Exit(-1) 35 | } 36 | } 37 | }() 38 | } 39 | 40 | func loadPolicyFile() bool { 41 | file, err := os.Open(socketpolicyfile) 42 | if err == nil { 43 | stat, _ := file.Stat() 44 | socketpolicy = make([]byte, stat.Size()+1) 45 | file.Read(socketpolicy) 46 | socketpolicy[stat.Size()] = 0 47 | file.Close() 48 | file = nil 49 | } else { 50 | fmt.Println(">>>> socket policy file open error:", err.Error()) 51 | return false 52 | } 53 | return true 54 | } 55 | 56 | func main() { 57 | signal_arm() 58 | 59 | // Parse args 60 | flag.IntVar(&port, "port", port, "listen port number") 61 | flag.StringVar(&socketpolicyfile, "file", socketpolicyfile, "socket policy file name") 62 | flag.Parse() 63 | 64 | fmt.Println("config listen port =", port) 65 | fmt.Println("config socket policy =", socketpolicyfile) 66 | 67 | if loadPolicyFile() == false { 68 | return 69 | } 70 | 71 | accepts() 72 | } 73 | 74 | func accepts() { 75 | strPort := "0.0.0.0:" + strconv.Itoa(port) 76 | fmt.Println("open port", strPort) 77 | addr, err := net.ResolveTCPAddr("tcp4", strPort) 78 | l, err := net.ListenTCP("tcp4", addr) 79 | if err != nil { 80 | fmt.Println(">>>> listen failed: ", err.Error()) 81 | return 82 | } 83 | addr = nil 84 | for { 85 | fmt.Println("Accept ready") 86 | session, err := l.AcceptTCP() 87 | if err != nil { 88 | //return 89 | fmt.Println("Accept error:", err.Error()) 90 | continue 91 | } 92 | 93 | go session_process(session) 94 | session = nil 95 | } 96 | 97 | l = nil 98 | } 99 | 100 | func session_process(s *net.TCPConn) { 101 | fmt.Println("Accepted session start") 102 | if recieve_request(s) { 103 | send_response(s) 104 | } 105 | s.Close() 106 | s = nil 107 | fmt.Println("session done") 108 | } 109 | 110 | func recieve_request(s *net.TCPConn) bool { 111 | // 超適当なのでリクエストの受信完了は待たない 112 | /* 113 | _, err := ioutil.ReadAll(s) 114 | if err == io.EOF { 115 | // write request validation here! 116 | return true 117 | } 118 | return false 119 | */ 120 | 121 | return true 122 | } 123 | 124 | func send_response(s *net.TCPConn) { 125 | s.Write(socketpolicy) 126 | } 127 | -------------------------------------------------------------------------------- /client/js/flash-security-policy-server/socketpolicy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /comet.ini: -------------------------------------------------------------------------------- 1 | [program:gopush-comet] 2 | command=/home/paas/paas/appmsgsrv/bin/comet -c /home/paas/paas/appmsgsrv/bin/comet.conf -dbc /home/paas/paas/appmsgsrv/bin/db.conf -log_level=info 3 | 4 | ;process_name=%(program_name)s ; process_name expr (default %(program_name)s) 5 | numprocs=1 ; number of processes copies to start (def 1) 6 | directory=/home/paas/paas/appmsgsrv/bin ; directory to cwd to before exec (def no cwd) 7 | ;umask=022 ; umask for process (default None) 8 | ;priority=999 ; the relative start priority (default 999) 9 | autostart=true ; start at supervisord start (default: true) 10 | autorestart=true ; whether/when to restart (default: unexpected) 11 | startsecs=10 ; number of secs prog must stay running (def. 1) 12 | startretries=10 ; max # of serial start failures (default 3) 13 | ;exitcodes=0,2 ; 'expected' exit codes for process (default 0,2) 14 | ;stopsignal=QUIT ; signal used to kill process (default TERM) 15 | ;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10) 16 | ;stopasgroup=false ; send stop signal to the UNIX process group (default false) 17 | ;killasgroup=false ; SIGKILL the UNIX process group (def false) 18 | user=root ; setuid to this UNIX account to run the program 19 | redirect_stderr=true ; redirect proc stderr to stdout (default false) 20 | stdout_logfile=/home/paas/paas/appmsgsrv/logs/comet/comet.log ; stdout log path, NONE for none; default AUTO 21 | stdout_logfile_maxbytes=50MB ; max # logfile bytes b4 rotation (default 50MB) 22 | stdout_logfile_backups=10 ; # of stdout logfile backups (default 10) 23 | stdout_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0) 24 | ;stdout_events_enabled=false ; emit events on stdout writes (default false) 25 | ;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO 26 | ;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) 27 | ;stderr_logfile_backups=10 ; # of stderr logfile backups (default 10) 28 | ;stderr_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0) 29 | ;stderr_events_enabled=false ; emit events on stderr writes (default false) 30 | ;environment=A=1,B=2 ; process environment additions (def no adds) 31 | ;serverurl=AUTO ; override serverurl computation (childutils) 32 | -------------------------------------------------------------------------------- /comet/config.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "github.com/Terry-Mao/goconf" 22 | "runtime" 23 | "time" 24 | ) 25 | 26 | var ( 27 | Conf *Config 28 | confFile string 29 | ) 30 | 31 | func init() { 32 | flag.StringVar(&confFile, "c", "./comet.conf", " set gopush-cluster comet config file path") 33 | } 34 | 35 | type Config struct { 36 | // base 37 | User string `goconf:"base:user"` 38 | PidFile string `goconf:"base:pidfile"` 39 | Dir string `goconf:"base:dir"` 40 | MaxProc int `goconf:"base:maxproc"` 41 | TCPBind []string `goconf:"base:tcp.bind:,"` 42 | WebsocketBind []string `goconf:"base:websocket.bind:,"` 43 | RPCBind []string `goconf:"base:rpc.bind:,"` 44 | PprofBind []string `goconf:"base:pprof.bind:,"` 45 | StatBind []string `goconf:"base:stat.bind:,"` 46 | // zookeeper 47 | ZookeeperAddr []string `goconf:"zookeeper:addr:,"` 48 | ZookeeperTimeout time.Duration `goconf:"zookeeper:timeout:time"` 49 | ZookeeperCometPath string `goconf:"zookeeper:comet.path"` 50 | ZookeeperCometNode string `goconf:"zookeeper:comet.node"` 51 | ZookeeperMessagePath string `goconf:"zookeeper:message.path"` 52 | // rpc 53 | RPCPing time.Duration `goconf:"rpc:ping:time"` 54 | RPCRetry time.Duration `goconf:"rpc:retry:time"` 55 | // channel 56 | SndbufSize int `goconf:"channel:sndbuf.size:memory"` 57 | RcvbufSize int `goconf:"channel:rcvbuf.size:memory"` 58 | Proto []string `goconf:"channel:proto:,"` 59 | BufioInstance int `goconf:"channel:bufio.instance"` 60 | BufioNum int `goconf:"channel:bufio.num"` 61 | TCPKeepalive bool `goconf:"channel:tcp.keepalive"` 62 | MaxSubscriberPerChannel int `goconf:"channel:maxsubscriber"` 63 | ChannelBucket int `goconf:"channel:bucket"` 64 | Auth bool `goconf:"channel:auth"` 65 | TokenExpire time.Duration `goconf:"-"` 66 | MsgBufNum int `goconf:"channel:msgbuf.num"` 67 | } 68 | 69 | // InitConfig get a new Config struct. 70 | func InitConfig() error { 71 | Conf = &Config{ 72 | // base 73 | User: "nobody nobody", 74 | PidFile: "/tmp/gopush-cluster-comet.pid", 75 | Dir: "./", 76 | MaxProc: runtime.NumCPU(), 77 | WebsocketBind: []string{"localhost:6968"}, 78 | TCPBind: []string{"localhost:6969"}, 79 | RPCBind: []string{"localhost:6970"}, 80 | PprofBind: []string{"localhost:6971"}, 81 | StatBind: []string{"localhost:6972"}, 82 | // zookeeper 83 | ZookeeperAddr: []string{"localhost:2181"}, 84 | ZookeeperTimeout: 30 * time.Second, 85 | ZookeeperCometPath: "/gopush-cluster-comet", 86 | ZookeeperCometNode: "node1", 87 | ZookeeperMessagePath: "/gopush-cluster-message", 88 | // rpc 89 | RPCPing: 1 * time.Second, 90 | RPCRetry: 1 * time.Second, 91 | // channel 92 | SndbufSize: 2048, 93 | RcvbufSize: 256, 94 | Proto: []string{"tcp", "websocket"}, 95 | BufioInstance: runtime.NumCPU(), 96 | BufioNum: 128, 97 | TCPKeepalive: false, 98 | TokenExpire: 30 * 24 * time.Hour, 99 | MaxSubscriberPerChannel: 64, 100 | ChannelBucket: runtime.NumCPU(), 101 | Auth: false, 102 | MsgBufNum: 30, 103 | } 104 | c := goconf.New() 105 | if err := c.Parse(confFile); err != nil { 106 | logger.Errorf("goconf.Parse(\"%s\") error(%v)", confFile, err) 107 | return err 108 | } 109 | if err := c.Unmarshal(Conf); err != nil { 110 | logger.Errorf("goconf.Unmarshall() error(%v)", err) 111 | return err 112 | } 113 | return nil 114 | } 115 | -------------------------------------------------------------------------------- /comet/db.conf: -------------------------------------------------------------------------------- 1 | [base] 2 | #Mysql configuration 3 | app.dbURL root:123456@tcp(10.180.120.63:3308)/appmsgsrv?parseTime=true 4 | app.dbMaxIdleConns 10 5 | app.dbMaxOpenConns 100 -------------------------------------------------------------------------------- /comet/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "github.com/EPICPaaS/appmsgsrv/db" 22 | "github.com/EPICPaaS/appmsgsrv/perf" 23 | "github.com/EPICPaaS/appmsgsrv/process" 24 | "github.com/EPICPaaS/appmsgsrv/ver" 25 | "github.com/b3log/wide/log" 26 | "os" 27 | "runtime" 28 | "time" 29 | ) 30 | 31 | var logger = log.NewLogger(os.Stdout) 32 | 33 | func main() { 34 | 35 | logLevel := flag.String("log_level", "info", "logger level") 36 | // parse cmd-line arguments 37 | flag.Parse() 38 | log.SetLevel(*logLevel) 39 | logger.Infof("comet ver: \"%s\" start", ver.Version) 40 | 41 | // init config 42 | if err := InitConfig(); err != nil { 43 | logger.Errorf("InitConfig() error(%v)", err) 44 | return 45 | } 46 | //init db config 47 | if err := db.InitConfig(); err != nil { 48 | logger.Error("db-InitConfig() wrror(%v)", err) 49 | return 50 | } 51 | 52 | db.InitDB() 53 | defer db.CloseDB() 54 | 55 | // set max routine 56 | runtime.GOMAXPROCS(Conf.MaxProc) 57 | // start pprof 58 | perf.Init(Conf.PprofBind) 59 | // create channel 60 | // if process exit, close channel 61 | UserChannel = NewChannelList() 62 | defer UserChannel.Close() 63 | // start stats 64 | StartStats() 65 | // start rpc 66 | StartRPC() 67 | // start comet 68 | StartComet() 69 | // init zookeeper 70 | zkConn, err := InitZK() 71 | if err != nil { 72 | logger.Errorf("InitZookeeper() error(%v)", err) 73 | return 74 | } 75 | // if process exit, close zk 76 | defer zkConn.Close() 77 | // init process 78 | // sleep one second, let the listen start 79 | time.Sleep(time.Second) 80 | if err = process.Init(Conf.User, Conf.Dir, Conf.PidFile); err != nil { 81 | logger.Errorf("process.Init(\"%s\", \"%s\", \"%s\") error(%v)", Conf.User, Conf.Dir, Conf.PidFile, err) 82 | return 83 | } 84 | // init signals, block wait signals 85 | signalCH := InitSignal() 86 | HandleSignal(signalCH) 87 | // exit 88 | logger.Info("comet stop") 89 | } 90 | -------------------------------------------------------------------------------- /comet/pubsub.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package main 18 | 19 | import ( 20 | "errors" 21 | "time" 22 | ) 23 | 24 | const ( 25 | TCPProto = uint8(0) 26 | WebsocketProto = uint8(1) 27 | WebsocketProtoStr = "websocket" 28 | TCPProtoStr = "tcp" 29 | Heartbeat = "h" 30 | minHearbeatSec = 30 31 | delayHeartbeatSec = 5 32 | fitstPacketTimedoutSec = time.Second * 5 33 | Second = int64(time.Second) 34 | ) 35 | 36 | var ( 37 | // Exceed the max subscriber per key 38 | ErrMaxConn = errors.New("Exceed the max subscriber connection per key") 39 | // Assection type failed 40 | ErrAssertType = errors.New("Subscriber assert type failed") 41 | // Heartbeat 42 | // HeartbeatLen = len(Heartbeat) 43 | // hearbeat 44 | HeartbeatReply = []byte("+h\r\n") 45 | // auth failed reply 46 | AuthReply = []byte("-a\r\n") 47 | // channle not found reply 48 | ChannelReply = []byte("-c\r\n") 49 | // param error reply 50 | ParamReply = []byte("-p\r\n") 51 | ) 52 | 53 | // StartListen start accept client. 54 | func StartComet() { 55 | for _, proto := range Conf.Proto { 56 | if proto == WebsocketProtoStr { 57 | // Start http push service 58 | StartHttp() 59 | } else if proto == TCPProtoStr { 60 | // Start tcp push service 61 | StartTCP() 62 | } else { 63 | logger.Warnf("unknown gopush-cluster protocol %s, (\"websocket\" or \"tcp\")", proto) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /comet/signal.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package main 18 | 19 | import ( 20 | "os" 21 | "os/signal" 22 | "syscall" 23 | ) 24 | 25 | // InitSignal register signals handler. 26 | func InitSignal() chan os.Signal { 27 | c := make(chan os.Signal, 1) 28 | signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT, syscall.SIGSTOP) 29 | return c 30 | } 31 | 32 | // HandleSignal fetch signal from chan then do exit or reload. 33 | func HandleSignal(c chan os.Signal) { 34 | // Block until a signal is received. 35 | for { 36 | s := <-c 37 | logger.Tracef("comet get a signal %s", s.String()) 38 | switch s { 39 | case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGSTOP, syscall.SIGINT: 40 | logger.Error("Comet Exit: syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGSTOP, syscall.SIGINT") 41 | return 42 | case syscall.SIGHUP: 43 | // TODO reload 44 | //return 45 | logger.Error("Comet Exit: syscall.SIGHUP") 46 | default: 47 | logger.Error("Comet Exit: default") 48 | return 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /comet/token.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package main 18 | 19 | import ( 20 | "container/list" 21 | "errors" 22 | "time" 23 | ) 24 | 25 | var ( 26 | // Token exists 27 | ErrTokenExist = errors.New("token exist") 28 | // Token not exists 29 | ErrTokenNotExist = errors.New("token not exist") 30 | // Token expired 31 | ErrTokenExpired = errors.New("token expired") 32 | ) 33 | 34 | // Token struct 35 | type Token struct { 36 | token map[string]*list.Element // token map 37 | lru *list.List // lru double linked list 38 | } 39 | 40 | // Token Element 41 | type TokenData struct { 42 | Ticket string 43 | Expire time.Time 44 | } 45 | 46 | // NewToken create a token struct ptr 47 | func NewToken() *Token { 48 | return &Token{ 49 | token: map[string]*list.Element{}, 50 | lru: list.New(), 51 | } 52 | } 53 | 54 | // Add add a token 55 | func (t *Token) Add(ticket string) error { 56 | if e, ok := t.token[ticket]; !ok { 57 | // new element add to lru back 58 | e = t.lru.PushBack(&TokenData{Ticket: ticket, Expire: time.Now().Add(Conf.TokenExpire)}) 59 | t.token[ticket] = e 60 | } else { 61 | logger.Warnf("token \"%s\" exist", ticket) 62 | return ErrTokenExist 63 | } 64 | t.clean() 65 | return nil 66 | } 67 | 68 | // Auth auth a token is valid 69 | func (t *Token) Auth(ticket string) error { 70 | if e, ok := t.token[ticket]; !ok { 71 | logger.Warnf("token \"%s\" not exist", ticket) 72 | return ErrTokenNotExist 73 | } else { 74 | td, _ := e.Value.(*TokenData) 75 | if time.Now().After(td.Expire) { 76 | t.clean() 77 | logger.Warnf("token \"%s\" expired", ticket) 78 | return ErrTokenExpired 79 | } 80 | td.Expire = time.Now().Add(Conf.TokenExpire) 81 | t.lru.MoveToBack(e) 82 | } 83 | t.clean() 84 | return nil 85 | } 86 | 87 | // clean scan the lru list expire the element 88 | func (t *Token) clean() { 89 | now := time.Now() 90 | e := t.lru.Front() 91 | for { 92 | if e == nil { 93 | break 94 | } 95 | td, _ := e.Value.(*TokenData) 96 | if now.After(td.Expire) { 97 | logger.Warnf("token \"%s\" expired", td.Ticket) 98 | o := e.Next() 99 | delete(t.token, td.Ticket) 100 | t.lru.Remove(e) 101 | e = o 102 | continue 103 | } 104 | break 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /comet/zk.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | // github.com/samuel/go-zookeeper 18 | // Copyright (c) 2013, Samuel Stauffer 19 | // All rights reserved. 20 | 21 | package main 22 | 23 | import ( 24 | "fmt" 25 | "github.com/EPICPaaS/appmsgsrv/rpc" 26 | myzk "github.com/EPICPaaS/appmsgsrv/zk" 27 | "github.com/samuel/go-zookeeper/zk" 28 | "path" 29 | "strings" 30 | ) 31 | 32 | func InitZK() (*zk.Conn, error) { 33 | conn, err := myzk.Connect(Conf.ZookeeperAddr, Conf.ZookeeperTimeout) 34 | if err != nil { 35 | logger.Errorf("zk.Connect() error(%v)", err) 36 | return nil, err 37 | } 38 | fpath := path.Join(Conf.ZookeeperCometPath, Conf.ZookeeperCometNode) 39 | if err = myzk.Create(conn, fpath); err != nil { 40 | logger.Errorf("zk.Create() error(%v)", err) 41 | return conn, err 42 | } 43 | // tcp, websocket and rpc bind address store in the zk 44 | data := "" 45 | for _, addr := range Conf.TCPBind { 46 | data += fmt.Sprintf("tcp://%s,", addr) 47 | } 48 | for _, addr := range Conf.WebsocketBind { 49 | data += fmt.Sprintf("ws://%s,", addr) 50 | } 51 | for _, addr := range Conf.RPCBind { 52 | data += fmt.Sprintf("rpc://%s,", addr) 53 | } 54 | data = strings.TrimRight(data, ",") 55 | logger.Tracef("zk data: \"%s\"", data) 56 | if err = myzk.RegisterTemp(conn, fpath, data); err != nil { 57 | logger.Errorf("zk.RegisterTemp() error(%v)", err) 58 | return conn, err 59 | } 60 | rpc.InitMessage(conn, Conf.ZookeeperMessagePath, Conf.RPCRetry, Conf.RPCPing) 61 | return conn, nil 62 | } 63 | -------------------------------------------------------------------------------- /db/config.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package db 18 | 19 | import ( 20 | "flag" 21 | "fmt" 22 | "github.com/Terry-Mao/goconf" 23 | "github.com/b3log/wide/log" 24 | "os" 25 | ) 26 | 27 | var ( 28 | Conf *Config 29 | confFile string 30 | ) 31 | 32 | var logger = log.NewLogger(os.Stdout) 33 | 34 | // InitConfig initialize config file path 35 | func init() { 36 | flag.StringVar(&confFile, "dbc", "./db.conf", " set db config file path") 37 | } 38 | 39 | type Config struct { 40 | AppDBURL string `goconf:"base:app.dbURL"` 41 | AppDBMaxIdleConns int `goconf:"base:app.dbMaxIdleConns"` 42 | AppDBMaxOpenConns int `goconf:"base:app.dbMaxOpenConns"` 43 | } 44 | 45 | // InitConfig init configuration file. 46 | func InitConfig() error { 47 | 48 | gconf := goconf.New() 49 | if err := gconf.Parse(confFile); err != nil { 50 | fmt.Println("confFile", confFile) 51 | logger.Errorf("goconf.Parse(\"%s\") error(%v)", confFile, err) 52 | return err 53 | } 54 | Conf = &Config{} 55 | if err := gconf.Unmarshal(Conf); err != nil { 56 | logger.Errorf("goconf.Unmarshall() error(%v)", err) 57 | return err 58 | } 59 | 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /db/db.conf: -------------------------------------------------------------------------------- 1 | [base] 2 | #Mysql configuration 3 | app.dbURL root:123456@tcp(10.180.120.63:3308)/appmsgsrv?parseTime=true 4 | app.dbMaxIdleConns 10 5 | app.dbMaxOpenConns 100 -------------------------------------------------------------------------------- /db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "database/sql" 5 | 6 | _ "github.com/go-sql-driver/mysql" 7 | ) 8 | 9 | // 数据库操作句柄. 10 | var MySQL *sql.DB 11 | 12 | // 初始化数据库连接. 13 | func InitDB() { 14 | logger.Info("Connecting DB....") 15 | 16 | var err error 17 | MySQL, err = sql.Open("mysql", Conf.AppDBURL) 18 | 19 | if nil != err { 20 | logger.Error(err) 21 | } 22 | 23 | // 实际测试一次 24 | test := 0 25 | if err := MySQL.QueryRow("SELECT 1").Scan(&test); err != nil { 26 | logger.Error(err) 27 | } 28 | 29 | logger.Infof("DB max idle conns [%d]", Conf.AppDBMaxIdleConns) 30 | logger.Infof("DB max open conns [%d]", Conf.AppDBMaxOpenConns) 31 | 32 | MySQL.SetMaxIdleConns(Conf.AppDBMaxIdleConns) 33 | MySQL.SetMaxOpenConns(Conf.AppDBMaxOpenConns) 34 | 35 | logger.Info("DB connected") 36 | } 37 | 38 | // 关闭数据库连接. 39 | func CloseDB() { 40 | MySQL.Close() 41 | } 42 | -------------------------------------------------------------------------------- /dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Dependencies 3 | 4 | # go get -u github.com/Terry-Mao/gopush-cluster 5 | # go get -u github.com/Terry-Mao/goconf 6 | # go get -u github.com/garyburd/redigo/redis 7 | # go get -u code.google.com/p/go.net/websocket 8 | # go get -u github.com/samuel/go-zookeeper 9 | # go get -u github.com/golang/glog 10 | 11 | go get -u ./message/... 12 | go get -u ./comet/... 13 | go get -u ./web/... 14 | -------------------------------------------------------------------------------- /hash/ketama.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package hash 18 | 19 | import ( 20 | // "crypto/md5" 21 | "fmt" 22 | "sort" 23 | ) 24 | 25 | // Convenience types for common cases 26 | // UIntSlice attaches the methods of Interface to []uint, sorting in increasing order. 27 | type UIntSlice []uint 28 | 29 | func (p UIntSlice) Len() int { return len(p) } 30 | func (p UIntSlice) Less(i, j int) bool { return p[i] < p[j] } 31 | func (p UIntSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 32 | 33 | type Ketama struct { 34 | node int // phsical node number 35 | vnode int // virual node number 36 | nodes []uint // nodes 37 | nodesMapping map[uint]string // nodes maping 38 | } 39 | 40 | // New create a ketama consistent hashing struct 41 | func NewKetama(node, vnode int) *Ketama { 42 | ketama := &Ketama{} 43 | ketama.node = node 44 | ketama.vnode = vnode 45 | ketama.nodes = []uint{} 46 | ketama.nodesMapping = map[uint]string{} 47 | 48 | ketama.initCircle() 49 | 50 | return ketama 51 | } 52 | 53 | // New create a ketama consistent hashing struct use exist node slice 54 | func NewKetama2(node []string, vnode int) *Ketama { 55 | ketama := &Ketama{} 56 | ketama.node = len(node) 57 | ketama.vnode = vnode 58 | ketama.nodes = []uint{} 59 | ketama.nodesMapping = map[uint]string{} 60 | 61 | ketama.initCircle2(node) 62 | 63 | return ketama 64 | } 65 | 66 | // init consistent hashing circle 67 | func (k *Ketama) initCircle() { 68 | h := NewMurmur3C() 69 | for idx := 1; idx < k.node+1; idx++ { 70 | node := fmt.Sprintf("node%d", idx) 71 | for i := 0; i < k.vnode; i++ { 72 | vnode := fmt.Sprintf("%s#%d", node, i) 73 | h.Write([]byte(vnode)) 74 | vpos := uint(h.Sum32()) 75 | k.nodes = append(k.nodes, vpos) 76 | k.nodesMapping[vpos] = node 77 | h.Reset() 78 | } 79 | } 80 | 81 | sort.Sort(UIntSlice(k.nodes)) 82 | } 83 | 84 | // init consistent hashing circle 85 | func (k *Ketama) initCircle2(node []string) { 86 | h := NewMurmur3C() 87 | for _, str := range node { 88 | for i := 0; i < k.vnode; i++ { 89 | vnode := fmt.Sprintf("%s#%d", str, i) 90 | h.Write([]byte(vnode)) 91 | vpos := uint(h.Sum32()) 92 | k.nodes = append(k.nodes, vpos) 93 | k.nodesMapping[vpos] = str 94 | h.Reset() 95 | } 96 | } 97 | 98 | sort.Sort(UIntSlice(k.nodes)) 99 | } 100 | 101 | // Node get a consistent hashing node by key 102 | func (k *Ketama) Node(key string) string { 103 | if len(k.nodes) == 0 { 104 | return "" 105 | } 106 | h := NewMurmur3C() 107 | h.Write([]byte(key)) 108 | idx := searchLeft(k.nodes, uint(h.Sum32())) 109 | pos := k.nodes[0] 110 | if idx != len(k.nodes) { 111 | pos = k.nodes[idx] 112 | } 113 | 114 | return k.nodesMapping[pos] 115 | } 116 | 117 | // search the slice left-most value 118 | func searchLeft(a []uint, x uint) int { 119 | lo := 0 120 | hi := len(a) 121 | for lo < hi { 122 | mid := (lo + hi) / 2 123 | if a[mid] < x { 124 | lo = mid + 1 125 | } else { 126 | hi = mid 127 | } 128 | } 129 | 130 | return lo 131 | } 132 | -------------------------------------------------------------------------------- /hash/ketama_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package hash 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | func TestKetama(t *testing.T) { 24 | k := NewKetama(15, 255) 25 | n := k.Node("Terry-Mao332") 26 | if n != "node15" { 27 | t.Error("Terry-Mao332 must hit node13") 28 | } 29 | 30 | n = k.Node("Terry-Mao2") 31 | if n != "node13" { 32 | t.Error("Terry-Mao2 must hit node13") 33 | } 34 | 35 | n = k.Node("Terry-Mao3") 36 | if n != "node5" { 37 | t.Error("Terry-Mao3 must hit node13") 38 | } 39 | 40 | n = k.Node("Terry-Mao4") 41 | if n != "node2" { 42 | t.Error("Terry-Mao4 must hit node13") 43 | } 44 | 45 | n = k.Node("Terry-Mao5") 46 | if n != "node6" { 47 | t.Error("Terry-Mao5 must hit node13") 48 | } 49 | } 50 | 51 | func TestKetama2(t *testing.T) { 52 | k := NewKetama2([]string{"11", "22"}, 255) 53 | n := k.Node("Terry-Mao332") 54 | if n != "22" { 55 | t.Error("Terry-Mao332 must hit 22") 56 | } 57 | 58 | n = k.Node("Terry-Mao333") 59 | if n != "22" { 60 | t.Error("Terry-Mao333 must hit 22") 61 | } 62 | 63 | n = k.Node("Terry-Mao334") 64 | if n != "22" { 65 | t.Error("Terry-Mao333 must hit 22") 66 | } 67 | 68 | n = k.Node("Terry-Mao335") 69 | if n != "11" { 70 | t.Error("Terry-Mao335 must hit 11") 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /hash/mmhash3_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012 The Go Authors. All rights reserved. 2 | // refer https://github.com/gwenn/murmurhash3 3 | 4 | package hash 5 | 6 | import ( 7 | "hash" 8 | "testing" 9 | ) 10 | 11 | const testDataSize = 40 12 | 13 | func TestMurmur3A(t *testing.T) { 14 | expected := uint32(3127628307) 15 | hash := Murmur3A([]byte("test"), 0) 16 | if hash != expected { 17 | t.Errorf("Expected %d but was %d for Murmur3A\n", expected, hash) 18 | } 19 | } 20 | 21 | func TestMurmur3C(t *testing.T) { 22 | expected := []uint32{1862463280, 1426881896, 1426881896, 1426881896} 23 | hash := Murmur3C([]byte("test"), 0) 24 | for i, e := range expected { 25 | if hash[i] != e { 26 | t.Errorf("Expected %d but was %d for Murmur3C[%d]\n", e, hash[i], i) 27 | } 28 | } 29 | } 30 | 31 | func TestMurmur3F(t *testing.T) { 32 | expected := []uint64{12429135405209477533, 11102079182576635266} 33 | hash := Murmur3F([]byte("test"), 0) 34 | for i, e := range expected { 35 | if hash[i] != e { 36 | t.Errorf("Expected %d but was %d for Murmur3F[%d]\n", e, hash[i], i) 37 | } 38 | } 39 | } 40 | 41 | func Benchmark3A(b *testing.B) { 42 | benchmark(b, NewMurmur3A()) 43 | } 44 | func Benchmark3C(b *testing.B) { 45 | benchmark(b, NewMurmur3C()) 46 | } 47 | func Benchmark3F(b *testing.B) { 48 | benchmark(b, NewMurmur3F()) 49 | } 50 | 51 | func benchmark(b *testing.B, h hash.Hash) { 52 | b.ResetTimer() 53 | b.SetBytes(testDataSize) 54 | data := make([]byte, testDataSize) 55 | for i := range data { 56 | data[i] = byte(i + 'a') 57 | } 58 | 59 | b.StartTimer() 60 | for todo := b.N; todo != 0; todo-- { 61 | h.Reset() 62 | h.Write(data) 63 | h.Sum(nil) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /hlist/hlist.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package hlist 18 | 19 | // Head is an head of a linked hlist. 20 | type Head struct { 21 | first *Element 22 | } 23 | 24 | // Element is an element of a linked hlist. 25 | type Element struct { 26 | next *Element 27 | pprev **Element 28 | 29 | // The value stored with this element. 30 | Value interface{} 31 | } 32 | 33 | // Next returns the next hlist element or nil. 34 | func (e *Element) Next() *Element { 35 | return e.next 36 | } 37 | 38 | // Hlist represents a doubly linked hlist. 39 | // The zero value for Hlist is an empty Hlist ready to use. 40 | type Hlist struct { 41 | root Head // sentinel hlist head 42 | len int // current hlist length excluding (this) sentinel element 43 | } 44 | 45 | // Init initializes or clears hlist l. 46 | func (l *Hlist) Init() *Hlist { 47 | l.root.first = nil 48 | l.len = 0 49 | return l 50 | } 51 | 52 | // New returns an initialized hlist. 53 | func New() *Hlist { return new(Hlist).Init() } 54 | 55 | // Len returns the number of elements of hlist l. 56 | // The complexity is O(1). 57 | func (l *Hlist) Len() int { return l.len } 58 | 59 | // Front returns the first element of hlist l or nil 60 | func (l *Hlist) Front() *Element { 61 | return l.root.first 62 | } 63 | 64 | // PushFront inserts a new element e with value v at the front of hlist l and returns e. 65 | func (l *Hlist) PushFront(v interface{}) *Element { 66 | first := l.root.first 67 | n := &Element{Value: v} 68 | n.next = first 69 | if first != nil { 70 | first.pprev = &n.next 71 | } 72 | l.root.first = n 73 | n.pprev = &l.root.first 74 | l.len++ 75 | return n 76 | } 77 | 78 | // Remove removes e from l if e is an element of hlist l. 79 | // It returns the element value e.Value. 80 | func (l *Hlist) Remove(e *Element) interface{} { 81 | next := e.next 82 | pprev := e.pprev 83 | *pprev = next 84 | if next != nil { 85 | next.pprev = pprev 86 | } 87 | l.len-- 88 | e.next = nil // avoid memory leak 89 | e.pprev = nil // avoid memory leak 90 | return e.Value 91 | } 92 | -------------------------------------------------------------------------------- /hlist/hlist_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package hlist 18 | 19 | import ( 20 | "fmt" 21 | "testing" 22 | ) 23 | 24 | func TestHlist(t *testing.T) { 25 | l := New() 26 | first := l.Front() 27 | if first != nil { 28 | t.Error("first != nil") 29 | } 30 | l.PushFront(1) 31 | l.PushFront(2) 32 | first = l.Front() 33 | if first == nil { 34 | t.Error("first == nil") 35 | } 36 | if i, ok := first.Value.(int); !ok { 37 | t.Error("first.Value assection failed") 38 | } else { 39 | if i != 2 { 40 | t.Errorf("i value error: %d", i) 41 | } 42 | } 43 | if next := first.Next(); next == nil { 44 | t.Error("next == nil") 45 | } else { 46 | if i, ok := next.Value.(int); !ok { 47 | t.Error("next.Value assection failed") 48 | } else { 49 | if i != 1 { 50 | t.Errorf("i value error: %d", i) 51 | } 52 | } 53 | } 54 | if l.Len() != 2 { 55 | t.Errorf("length error") 56 | } 57 | l.PushFront(3) 58 | l.PushFront(4) 59 | l.PushFront(5) 60 | l.PushFront(6) 61 | first = l.Front() 62 | if l.Len() != 6 { 63 | t.Errorf("length error") 64 | } 65 | for e := l.Front(); e != nil; e = e.Next() { 66 | if i, ok := e.Value.(int); !ok { 67 | t.Error("e.Value assection failed") 68 | } else { 69 | fmt.Println(i) 70 | } 71 | } 72 | fmt.Println("------") 73 | if i, ok := l.Remove(first).(int); !ok { 74 | t.Error("first.Value assection failed") 75 | } else { 76 | if i != 6 { 77 | t.Errorf("i value error: %d", i) 78 | } 79 | } 80 | if l.Len() != 5 { 81 | t.Errorf("length error") 82 | } 83 | for e := l.Front(); e != nil; e = e.Next() { 84 | if i, ok := e.Value.(int); !ok { 85 | t.Error("e.Value assection failed") 86 | } else { 87 | fmt.Println(i) 88 | } 89 | } 90 | second := l.Front() // 5 91 | thrid := second.Next() // 4 92 | fourth := thrid.Next() // 3 93 | fifth := fourth.Next() // 2 94 | sixth := fifth.Next() // 1 95 | l.Remove(second) 96 | l.Remove(thrid) 97 | l.Remove(fourth) 98 | l.Remove(fifth) 99 | l.Remove(sixth) 100 | if l.Len() != 0 { 101 | t.Errorf("length error") 102 | } 103 | first = l.Front() 104 | if first != nil { 105 | t.Error("first != nil") 106 | } 107 | for e := l.Front(); e != nil; e = e.Next() { 108 | if i, ok := e.Value.(int); !ok { 109 | t.Error("e.Value assection failed") 110 | } else { 111 | fmt.Println(i) 112 | } 113 | } 114 | 115 | e := l.PushFront(7) 116 | if i, ok := e.Value.(int); !ok { 117 | t.Error("e.Value assection failed") 118 | } else { 119 | if i != 7 { 120 | t.Error("i value error: %d", i) 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /id/timeid.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package id 18 | 19 | import ( 20 | "time" 21 | ) 22 | 23 | type TimeID struct { 24 | lastID int64 25 | } 26 | 27 | // NewTimeID create a new TimeID struct 28 | func NewTimeID() *TimeID { 29 | return &TimeID{lastID: 0} 30 | } 31 | 32 | // ID generate a time ID 33 | func (t *TimeID) ID() int64 { 34 | for { 35 | s := time.Now().UnixNano() / 1000 36 | if t.lastID >= s { 37 | // if last time id > current time, may be who change the system id, 38 | // so sleep last time id minus current time 39 | time.Sleep(time.Duration((t.lastID-s+1)*1000) * time.Nanosecond) 40 | } else { 41 | // save the current time id 42 | t.lastID = s 43 | return s 44 | } 45 | } 46 | return 0 47 | } 48 | -------------------------------------------------------------------------------- /id/timeid_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package id 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | func TestTimeID(t *testing.T) { 24 | tid := NewTimeID() 25 | a := tid.ID() 26 | b := tid.ID() 27 | if a > b { 28 | t.Error("time a > b") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ketama/ketama.go: -------------------------------------------------------------------------------- 1 | /* 2 | refer https://github.com/mncaudill/ketama 3 | 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2013 Nolan Caudill 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy of 9 | this software and associated documentation files (the "Software"), to deal in 10 | the Software without restriction, including without limitation the rights to 11 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 12 | the Software, and to permit persons to whom the Software is furnished to do so, 13 | subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 20 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 21 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 22 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 23 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | package ketama 26 | 27 | import ( 28 | "crypto/sha1" 29 | "sort" 30 | "strconv" 31 | ) 32 | 33 | type node struct { 34 | node string 35 | hash uint 36 | } 37 | 38 | type tickArray []node 39 | 40 | func (p tickArray) Len() int { return len(p) } 41 | func (p tickArray) Less(i, j int) bool { return p[i].hash < p[j].hash } 42 | func (p tickArray) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 43 | func (p tickArray) Sort() { sort.Sort(p) } 44 | 45 | type HashRing struct { 46 | defaultSpots int 47 | ticks tickArray 48 | length int 49 | } 50 | 51 | func NewRing(n int) (h *HashRing) { 52 | h = new(HashRing) 53 | h.defaultSpots = n 54 | return 55 | } 56 | 57 | // Adds a new node to a hash ring 58 | // n: name of the server 59 | // s: multiplier for default number of ticks (useful when one cache node has more resources, like RAM, than another) 60 | func (h *HashRing) AddNode(n string, s int) { 61 | tSpots := h.defaultSpots * s 62 | hash := sha1.New() 63 | for i := 1; i <= tSpots; i++ { 64 | hash.Write([]byte(n + ":" + strconv.Itoa(i))) 65 | hashBytes := hash.Sum(nil) 66 | 67 | n := &node{ 68 | node: n, 69 | hash: uint(hashBytes[19]) | uint(hashBytes[18])<<8 | uint(hashBytes[17])<<16 | uint(hashBytes[16])<<24, 70 | } 71 | 72 | h.ticks = append(h.ticks, *n) 73 | hash.Reset() 74 | } 75 | } 76 | 77 | func (h *HashRing) Bake() { 78 | h.ticks.Sort() 79 | h.length = len(h.ticks) 80 | } 81 | 82 | func (h *HashRing) Hash(s string) string { 83 | hash := sha1.New() 84 | hash.Write([]byte(s)) 85 | hashBytes := hash.Sum(nil) 86 | v := uint(hashBytes[19]) | uint(hashBytes[18])<<8 | uint(hashBytes[17])<<16 | uint(hashBytes[16])<<24 87 | i := sort.Search(h.length, func(i int) bool { return h.ticks[i].hash >= v }) 88 | 89 | if i == h.length { 90 | i = 0 91 | } 92 | 93 | return h.ticks[i].node 94 | } 95 | -------------------------------------------------------------------------------- /message.ini: -------------------------------------------------------------------------------- 1 | [program:gopush-message] 2 | command=/home/paas/paas/appmsgsrv/bin/message -c /home/paas/paas/appmsgsrv/bin/message.conf -log_level=info 3 | ;process_name=%(program_name)s ; process_name expr (default %(program_name)s) 4 | numprocs=1 ; number of processes copies to start (def 1) 5 | directory=/home/paas/paas/appmsgsrv/bin ; directory to cwd to before exec (def no cwd) 6 | ;umask=022 ; umask for process (default None) 7 | ;priority=999 ; the relative start priority (default 999) 8 | autostart=true ; start at supervisord start (default: true) 9 | autorestart=true ; whether/when to restart (default: unexpected) 10 | startsecs=10 ; number of secs prog must stay running (def. 1) 11 | startretries=10 ; max # of serial start failures (default 3) 12 | ;exitcodes=0,2 ; 'expected' exit codes for process (default 0,2) 13 | ;stopsignal=QUIT ; signal used to kill process (default TERM) 14 | ;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10) 15 | ;stopasgroup=false ; send stop signal to the UNIX process group (default false) 16 | ;killasgroup=false ; SIGKILL the UNIX process group (def false) 17 | user=root ; setuid to this UNIX account to run the program 18 | redirect_stderr=true ; redirect proc stderr to stdout (default false) 19 | stdout_logfile=/home/paas/paas/appmsgsrv/logs/message/message.log ; stdout log path, NONE for none; default AUTO 20 | stdout_logfile_maxbytes=50MB ; max # logfile bytes b4 rotation (default 50MB) 21 | stdout_logfile_backups=10 ; # of stdout logfile backups (default 10) 22 | stdout_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0) 23 | ;stdout_events_enabled=false ; emit events on stdout writes (default false) 24 | ;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO 25 | ;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) 26 | ;stderr_logfile_backups=10 ; # of stderr logfile backups (default 10) 27 | ;stderr_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0) 28 | ;stderr_events_enabled=false ; emit events on stderr writes (default false) 29 | ;environment=A=1,B=2 ; process environment additions (def no adds) 30 | ;serverurl=AUTO ; override serverurl computation (childutils) 31 | -------------------------------------------------------------------------------- /message/config.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "fmt" 22 | "github.com/Terry-Mao/goconf" 23 | "runtime" 24 | "time" 25 | ) 26 | 27 | var ( 28 | Conf *Config 29 | confFile string 30 | ) 31 | 32 | func init() { 33 | flag.StringVar(&confFile, "c", "./message.conf", " set message config file path") 34 | } 35 | 36 | // Config struct 37 | type Config struct { 38 | RPCBind []string `goconf:"base:rpc.bind:,"` 39 | User string `goconf:"base:user"` 40 | PidFile string `goconf:"base:pidfile"` 41 | Dir string `goconf:"base:dir"` 42 | MaxProc int `goconf:"base:maxproc"` 43 | PprofBind []string `goconf:"base:pprof.bind:,"` 44 | StorageType string `goconf:"storage:type"` 45 | RedisIdleTimeout time.Duration `goconf:"redis:timeout:time"` 46 | RedisMaxIdle int `goconf:"redis:idle"` 47 | RedisMaxActive int `goconf:"redis:active"` 48 | RedisMaxStore int `goconf:"redis:store"` 49 | RedisKetamaBase int `goconf:"redis:ketama.base"` 50 | MySQLClean time.Duration `goconf:"mysql:clean:time"` 51 | MySQLKetamaBase int `goconf:"mysql:ketama.base"` 52 | RedisSource map[string]string `goconf:"-"` 53 | MySQLSource map[string]string `goconf:"-"` 54 | // zookeeper 55 | ZookeeperAddr []string `goconf:"zookeeper:addr:,"` 56 | ZookeeperTimeout time.Duration `goconf:"zookeeper:timeout:time"` 57 | ZookeeperPath string `goconf:"zookeeper:path"` 58 | } 59 | 60 | // NewConfig parse config file into Config. 61 | func InitConfig() error { 62 | gconf := goconf.New() 63 | if err := gconf.Parse(confFile); err != nil { 64 | logger.Errorf("goconf.Parse(\"%s\") error(%v)", confFile, err) 65 | return err 66 | } 67 | Conf = &Config{ 68 | // base 69 | RPCBind: []string{"localhost:8070"}, 70 | User: "nobody nobody", 71 | PidFile: "/tmp/gopush-cluster-message.pid", 72 | Dir: "./", 73 | MaxProc: runtime.NumCPU(), 74 | PprofBind: []string{"localhost:8170"}, 75 | // storage 76 | StorageType: "redis", 77 | // redis 78 | RedisIdleTimeout: 28800 * time.Second, 79 | RedisMaxIdle: 50, 80 | RedisMaxActive: 1000, 81 | RedisMaxStore: 20, 82 | RedisSource: make(map[string]string), 83 | // mysql 84 | MySQLSource: make(map[string]string), 85 | MySQLClean: 1 * time.Hour, 86 | // zookeeper 87 | ZookeeperAddr: []string{"localhost:2181"}, 88 | ZookeeperTimeout: 30 * time.Second, 89 | ZookeeperPath: "/gopush-cluster-message", 90 | } 91 | if err := gconf.Unmarshal(Conf); err != nil { 92 | logger.Errorf("goconf.Unmarshal() error(%v)", err) 93 | return err 94 | } 95 | // redis section 96 | redisAddrsSec := gconf.Get("redis.source") 97 | if redisAddrsSec != nil { 98 | for _, key := range redisAddrsSec.Keys() { 99 | addr, err := redisAddrsSec.String(key) 100 | if err != nil { 101 | return fmt.Errorf("config section: \"redis.addrs\" key: \"%s\" error(%v)", key, err) 102 | } 103 | Conf.RedisSource[key] = addr 104 | } 105 | } 106 | // mysql section 107 | dbSource := gconf.Get("mysql.source") 108 | if dbSource != nil { 109 | for _, key := range dbSource.Keys() { 110 | source, err := dbSource.String(key) 111 | if err != nil { 112 | return fmt.Errorf("config section: \"mysql.source\" key: \"%s\" error(%v)", key, err) 113 | } 114 | Conf.MySQLSource[key] = source 115 | } 116 | } 117 | return nil 118 | } 119 | -------------------------------------------------------------------------------- /message/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "github.com/EPICPaaS/appmsgsrv/perf" 22 | "github.com/EPICPaaS/appmsgsrv/process" 23 | "github.com/EPICPaaS/appmsgsrv/ver" 24 | "github.com/b3log/wide/log" 25 | "os" 26 | "runtime" 27 | "time" 28 | ) 29 | 30 | var logger = log.NewLogger(os.Stdout) 31 | 32 | func main() { 33 | logLevel := flag.String("log_level", "info", "logger level") 34 | flag.Parse() 35 | log.SetLevel(*logLevel) 36 | logger.Infof("message ver: \"%s\" start", ver.Version) 37 | 38 | if err := InitConfig(); err != nil { 39 | logger.Errorf("InitConfig() error(%v)", err) 40 | return 41 | } 42 | // Set max routine 43 | runtime.GOMAXPROCS(Conf.MaxProc) 44 | // start pprof http 45 | perf.Init(Conf.PprofBind) 46 | // Initialize redis 47 | if err := InitStorage(); err != nil { 48 | logger.Errorf("InitStorage() error(%v)", err) 49 | return 50 | } 51 | // init rpc service 52 | InitRPC() 53 | // init zookeeper 54 | zk, err := InitZK() 55 | if err != nil { 56 | logger.Errorf("InitZK() error(%v)", err) 57 | if zk != nil { 58 | zk.Close() 59 | } 60 | return 61 | } 62 | // if process exit, close zk 63 | defer zk.Close() 64 | // init process 65 | // sleep one second, let the listen start 66 | time.Sleep(time.Second) 67 | if err = process.Init(Conf.User, Conf.Dir, Conf.PidFile); err != nil { 68 | logger.Errorf("process.Init(\"%s\", \"%s\", \"%s\") error(%v)", Conf.User, Conf.Dir, Conf.PidFile, err) 69 | return 70 | } 71 | // init signals, block wait signals 72 | sig := InitSignal() 73 | HandleSignal(sig) 74 | // exit 75 | logger.Info("message stop") 76 | } 77 | -------------------------------------------------------------------------------- /message/message-example.conf: -------------------------------------------------------------------------------- 1 | # Message configuration file example 2 | 3 | # Note on units: when memory size is needed, it is possible to specify 4 | # it in the usual form of 1k 5GB 4M and so forth: 5 | # 6 | # 1kb => 1024 bytes 7 | # 1mb => 1024*1024 bytes 8 | # 1gb => 1024*1024*1024 bytes 9 | # 10 | # units are case insensitive so 1GB 1Gb 1gB are all the same. 11 | 12 | # Note on units: when time duration is needed, it is possible to specify 13 | # it in the usual form of 1s 5M 4h and so forth: 14 | # 15 | # 1s => 1000 * 1000 * 1000 nanoseconds 16 | # 1m => 60 seconds 17 | # 1h => 60 minutes 18 | # 19 | # units are case insensitive so 1h 1H are all the same. 20 | 21 | [base] 22 | # Message service tcp listen and server on this address, 23 | # default tcp listen localhost:8070 24 | # Examples: 25 | # 26 | # rpc.bind 192.168.1.100:8070,10.0.0.1:8070 27 | # rpc.bind 127.0.0.1:8070 28 | # rpc.bind 0.0.0.0:8070 29 | rpc.bind localhost:8070 30 | 31 | # If the master process is run as root, then message will setuid()/setgid() 32 | # to USER/GROUP. If GROUP is not specified, then message uses the same name as 33 | # USER. By default it's nobody user and nobody or nogroup group. 34 | user nobody 35 | 36 | # When running daemonized, Message writes a pid file in 37 | # /tmp/gopush-cluster-message.pid by default. You can specify a custom pid file 38 | # location here. 39 | pidfile /tmp/gopush-cluster-message.pid 40 | 41 | # Sets the maximum number of CPUs that can be executing simultaneously. 42 | # This call will go away when the scheduler improves. By default the number of 43 | # logical CPUs is set. 44 | # 45 | # maxproc 4 46 | 47 | # The working directory. 48 | # 49 | # Note that you must specify a directory here, not a file name. 50 | dir ./ 51 | 52 | # This is used by message service profiling (pprof). 53 | # By default message pprof listens for connections from local interfaces on 8170 54 | # port. It's not safty for listening internet IP addresses. 55 | # 56 | # Examples: 57 | # 58 | # pprof.bind 192.168.1.100:8170,10.0.0.1:8170 59 | # pprof.bind 127.0.0.1:8170 60 | # pprof.bind 0.0.0.0:8170 61 | pprof.bind localhost:8170 62 | 63 | [storage] 64 | # Storage type, Support: redis, mysql. We suggest use redis, cause mysql is low 65 | # efficency. Now, only support to run one of both in the same time 66 | type redis 67 | 68 | [redis] 69 | # Close connections after remaining idle for this duration. If the value 70 | # is zero, then idle connections are not closed. Applications should set 71 | # the timeout to a value less than the server's timeout. 72 | timeout 28800s 73 | 74 | # Maximum number of idle connections in the pool. 75 | idle 100 76 | 77 | # Maximum number of connections allocated by the pool at a given time. 78 | # When zero, there is no limit on the number of connections in the pool. 79 | active 300 80 | 81 | # Max quantity of stored message for each key, default 20 82 | store 20 83 | 84 | # ketama virtual node base number 85 | ketama.base 255 86 | 87 | [redis.source] 88 | # The format like "NodeName IP:Port", NodeName was specified by Comet service. 89 | # If there are multiple nodes, then configure following 90 | # nodeN:W, N is node name, W is node weight 91 | # node1:1 IP1:Port1 92 | # node2:2 IP2:Port2 93 | # node3:3 IP3:Port3 94 | node1:1 localhost:6379 95 | 96 | [mysql] 97 | # Delete all of expired message loop time interval 98 | clean 1h 99 | 100 | # ketama virtual node base number 101 | ketama.base 255 102 | 103 | [mysql.source] 104 | # Database source. 105 | # For example with the following 106 | # nodeN:W, N is node name, W is node weight 107 | # node1:1 test:test@(192.168.1.1:3306)/gopush?parseTime=true&loc=Local&charset=utf8 108 | # node2:1 test:test@(192.168.1.2:3306)/gopush?parseTime=true&loc=Local&charset=utf8 109 | node1:1 test:test@(192.168.1.2:3306)/gopush?parseTime=true&loc=Local&charset=utf8 110 | node2:2 test:test@(192.168.1.3:3306)/gopush?parseTime=true&loc=Local&charset=utf8 111 | node3:3 test:test@(192.168.1.4:3306)/gopush?parseTime=true&loc=Local&charset=utf8 112 | 113 | ################################## ZOOKEEPER ################################## 114 | 115 | # The zookeeper cluster section. When message start, it will register data in 116 | # the zookeeper cluster and create a ephemeral node. When message died, the 117 | # node will drop by zookeeper cluster. So we can use the feature implement node 118 | # failover. 119 | [zookeeper] 120 | # Zookeeper cluster addresses. Mutiple address split by a ",". 121 | # Examples: 122 | # 123 | # addr 192.168.1.100:2181,10.0.0.1:2181 124 | # addr 127.0.0.1:2181 125 | addr localhost:2181 126 | 127 | # Zookeeper cluster session idle timeout seconds. Zookeeper will close the 128 | # connection after a client is idle for N seconds, Zookeeper will send heartbeat 129 | # after timeout / 2 second. 130 | # Examples: 131 | # 132 | # timeout 30s 133 | # timeout 15s 134 | timeout 30s 135 | 136 | # message create ephemeral node at the specified root path. 137 | # 138 | # Note the path must start with "/". 139 | path /gopush-cluster-message 140 | 141 | ################################## INCLUDES ################################### 142 | 143 | # Include one or more other config files here. This is useful if you 144 | # have a standard template that goes to all comet server but also need 145 | # to customize a few per-server settings. Include files can include 146 | # other files, so use this wisely. 147 | # 148 | # include /path/to/local.conf 149 | # include /path/to/other.conf 150 | -------------------------------------------------------------------------------- /message/message.conf: -------------------------------------------------------------------------------- 1 | # Message configuration file example 2 | 3 | # Note on units: when memory size is needed, it is possible to specify 4 | # it in the usual form of 1k 5GB 4M and so forth: 5 | # 6 | # 1kb => 1024 bytes 7 | # 1mb => 1024*1024 bytes 8 | # 1gb => 1024*1024*1024 bytes 9 | # 10 | # units are case insensitive so 1GB 1Gb 1gB are all the same. 11 | 12 | # Note on units: when time duration is needed, it is possible to specify 13 | # it in the usual form of 1s 5M 4h and so forth: 14 | # 15 | # 1s => 1000 * 1000 * 1000 nanoseconds 16 | # 1m => 60 seconds 17 | # 1h => 60 minutes 18 | # 19 | # units are case insensitive so 1h 1H are all the same. 20 | 21 | [base] 22 | # Message service tcp listen and server on this address, 23 | # default tcp listen 192.168.1.122:8070 24 | # Examples: 25 | # 26 | # rpc.bind 192.168.1.100:8070,10.0.0.1:8070 27 | # rpc.bind 127.0.0.1:8070 28 | # rpc.bind 0.0.0.0:8070 29 | rpc.bind 192.168.1.122:8070 30 | 31 | # If the master process is run as root, then message will setuid()/setgid() 32 | # to USER/GROUP. If GROUP is not specified, then message uses the same name as 33 | # USER. By default it's nobody user and nobody or nogroup group. 34 | user ublxd 35 | 36 | # When running daemonized, Message writes a pid file in 37 | # /tmp/gopush-cluster-message.pid by default. You can specify a custom pid file 38 | # location here. 39 | pidfile /tmp/gopush-cluster-message.pid 40 | 41 | # Sets the maximum number of CPUs that can be executing simultaneously. 42 | # This call will go away when the scheduler improves. By default the number of 43 | # logical CPUs is set. 44 | # 45 | # maxproc 4 46 | 47 | # The working directory. 48 | # 49 | # Note that you must specify a directory here, not a file name. 50 | dir ./ 51 | 52 | # This is used by message service profiling (pprof). 53 | # By default message pprof listens for connections from local interfaces on 8170 54 | # port. It's not safty for listening internet IP addresses. 55 | # 56 | # Examples: 57 | # 58 | # pprof.bind 192.168.1.100:8170,10.0.0.1:8170 59 | # pprof.bind 127.0.0.1:8170 60 | # pprof.bind 0.0.0.0:8170 61 | pprof.bind 192.168.1.122:8170 62 | 63 | [storage] 64 | # Storage type, Support: redis, mysql. We suggest use redis, cause mysql is low 65 | # efficency. Now, only support to run one of both in the same time 66 | type mysql 67 | 68 | [redis] 69 | # Close connections after remaining idle for this duration. If the value 70 | # is zero, then idle connections are not closed. Applications should set 71 | # the timeout to a value less than the server's timeout. 72 | timeout 28800s 73 | 74 | # Maximum number of idle connections in the pool. 75 | idle 100 76 | 77 | # Maximum number of connections allocated by the pool at a given time. 78 | # When zero, there is no limit on the number of connections in the pool. 79 | active 300 80 | 81 | # Max quantity of stored message for each key, default 20 82 | store 20 83 | 84 | # ketama virtual node base number 85 | ketama.base 255 86 | 87 | [redis.source] 88 | # The format like "NodeName IP:Port", NodeName was specified by Comet service. 89 | # If there are multiple nodes, then configure following 90 | # nodeN:W, N is node name, W is node weight 91 | # node1:1 IP1:Port1 92 | # node2:2 IP2:Port2 93 | # node3:3 IP3:Port3 94 | node1:1 192.168.1.122:6379 95 | 96 | [mysql] 97 | # Delete all of expired message loop time interval 98 | clean 1h 99 | 100 | # ketama virtual node base number 101 | ketama.base 255 102 | 103 | [mysql.source] 104 | # Database source. 105 | # For example with the following 106 | # nodeN:W, N is node name, W is node weight 107 | # node1:1 test:test@(192.168.1.1:3306)/gopush?parseTime=true&loc=Local&charset=utf8 108 | # node2:1 test:test@(192.168.1.2:3306)/gopush?parseTime=true&loc=Local&charset=utf8 109 | node1:1 root:123456@(10.180.120.63:3308)/appmsgsrv?parseTime=true&loc=Local&charset=utf8 110 | #node2:2 test:test@(192.168.1.3:3306)/gopush?parseTime=true&loc=Local&charset=utf8 111 | #node3:3 test:test@(192.168.1.4:3306)/gopush?parseTime=true&loc=Local&charset=utf8 112 | 113 | ################################## ZOOKEEPER ################################## 114 | 115 | # The zookeeper cluster section. When message start, it will register data in 116 | # the zookeeper cluster and create a ephemeral node. When message died, the 117 | # node will drop by zookeeper cluster. So we can use the feature implement node 118 | # failover. 119 | [zookeeper] 120 | # Zookeeper cluster addresses. Mutiple address split by a ",". 121 | # Examples: 122 | # 123 | # addr 192.168.1.100:2181,10.0.0.1:2181 124 | # addr 127.0.0.1:2181 125 | addr 192.168.1.122:2181 126 | 127 | # Zookeeper cluster session idle timeout seconds. Zookeeper will close the 128 | # connection after a client is idle for N seconds, Zookeeper will send heartbeat 129 | # after timeout / 2 second. 130 | # Examples: 131 | # 132 | # timeout 30s 133 | # timeout 15s 134 | timeout 30s 135 | 136 | # message create ephemeral node at the specified root path. 137 | # 138 | # Note the path must start with "/". 139 | path /gopush-cluster-message 140 | 141 | ################################## INCLUDES ################################### 142 | 143 | # Include one or more other config files here. This is useful if you 144 | # have a standard template that goes to all comet server but also need 145 | # to customize a few per-server settings. Include files can include 146 | # other files, so use this wisely. 147 | # 148 | # include /path/to/local.conf 149 | # include /path/to/other.conf 150 | -------------------------------------------------------------------------------- /message/rpc.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package main 18 | 19 | import ( 20 | myrpc "github.com/EPICPaaS/appmsgsrv/rpc" 21 | "net" 22 | "net/rpc" 23 | ) 24 | 25 | // RPC For receive offline messages 26 | type MessageRPC struct { 27 | } 28 | 29 | // InitRPC start accept rpc call. 30 | func InitRPC() { 31 | msg := &MessageRPC{} 32 | rpc.Register(msg) 33 | for _, bind := range Conf.RPCBind { 34 | logger.Infof("start rpc listen addr: \"%s\"", bind) 35 | go rpcListen(bind) 36 | } 37 | } 38 | 39 | func rpcListen(bind string) { 40 | l, err := net.Listen("tcp", bind) 41 | if err != nil { 42 | logger.Errorf("net.Listen(\"tcp\", \"%s\") error(%v)", bind, err) 43 | panic(err) 44 | } 45 | defer func() { 46 | if err := l.Close(); err != nil { 47 | logger.Errorf("listener.Close() error(%v)", err) 48 | } 49 | }() 50 | rpc.Accept(l) 51 | } 52 | 53 | // SavePrivate rpc interface save user private message. 54 | func (r *MessageRPC) SavePrivate(m *myrpc.MessageSavePrivateArgs, ret *int) error { 55 | if m == nil || m.Msg == nil || m.MsgId < 0 { 56 | return myrpc.ErrParam 57 | } 58 | if err := UseStorage.SavePrivate(m.Key, m.Msg, m.MsgId, m.Expire); err != nil { 59 | logger.Errorf("UseStorage.SavePrivate(\"%s\", \"%s\", %d, %d) error(%v)", m.Key, string(m.Msg), m.MsgId, m.Expire, err) 60 | return err 61 | } 62 | logger.Tracef("UseStorage.SavePrivate(\"%s\", \"%s\", %d, %d) ok", m.Key, string(m.Msg), m.MsgId, m.Expire) 63 | return nil 64 | } 65 | 66 | // GetPrivate rpc interface get user private message. 67 | func (r *MessageRPC) GetPrivate(m *myrpc.MessageGetPrivateArgs, rw *myrpc.MessageGetResp) error { 68 | logger.Tracef("messageRPC.GetPrivate key:\"%s\" mid:\"%d\"", m.Key, m.MsgId) 69 | if m == nil || m.Key == "" || m.MsgId < 0 { 70 | return myrpc.ErrParam 71 | } 72 | msgs, err := UseStorage.GetPrivate(m.Key, m.MsgId) 73 | if err != nil { 74 | logger.Errorf("UseStorage.GetPrivate(\"%s\", %d) error(%v)", m.Key, m.MsgId, err) 75 | return err 76 | } 77 | rw.Msgs = msgs 78 | logger.Tracef("UserStorage.GetPrivate(\"%s\", %d) ok", m.Key, m.MsgId) 79 | return nil 80 | } 81 | 82 | // DelPrivate rpc interface delete user private message. 83 | func (r *MessageRPC) DelPrivate(key string, ret *int) error { 84 | if key == "" { 85 | return myrpc.ErrParam 86 | } 87 | if err := UseStorage.DelPrivate(key); err != nil { 88 | logger.Errorf("UserStorage.DelPrivate(\"%s\") error(%v)", key, err) 89 | return err 90 | } 91 | logger.Tracef("UserStorage.DelPrivate(\"%s\") ok", key) 92 | return nil 93 | } 94 | 95 | /* 96 | // SavePublish rpc interface save publish message. 97 | func (r *MessageRPC) SavePublish(m *myrpc.MessageSaveGroupArgs, ret *int) error { 98 | return nil 99 | } 100 | 101 | // GetPublish rpc interface get publish message. 102 | func (r *MessageRPC) GetPublish(m *myrpc.MessageGetGroupArgs, rw *myrpc.MessageGetResp) error { 103 | return nil 104 | } 105 | 106 | // DelPublish rpc interface delete publish message. 107 | func (r *MessageRPC) DelPublish(key string, ret *int) error { 108 | return nil 109 | } 110 | 111 | // SaveGroup rpc interface save publish message. 112 | func (r *MessageRPC) SaveGroup(m *myrpc.MessageSaveGroupArgs, ret *int) error { 113 | return nil 114 | } 115 | 116 | // GetPublish rpc interface get publish message. 117 | func (r *MessageRPC) GetGroup(m *myrpc.MessageGetGroupArgs, rw *myrpc.MessageGetResp) error { 118 | return nil 119 | } 120 | 121 | // DelPublish rpc interface delete publish message. 122 | func (r *MessageRPC) DelGroup(key string, ret *int) error { 123 | return nil 124 | } 125 | */ 126 | 127 | // Server Ping interface 128 | func (r *MessageRPC) Ping(p int, ret *int) error { 129 | logger.Trace("ping ok") 130 | return nil 131 | } 132 | -------------------------------------------------------------------------------- /message/signal.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package main 18 | 19 | import ( 20 | "os" 21 | "os/signal" 22 | "syscall" 23 | ) 24 | 25 | // InitSignal register signals handler. 26 | func InitSignal() chan os.Signal { 27 | c := make(chan os.Signal, 1) 28 | signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT, syscall.SIGSTOP) 29 | return c 30 | } 31 | 32 | // HandleSignal fetch signal from chan then do exit or reload. 33 | func HandleSignal(c chan os.Signal) { 34 | // Block until a signal is received. 35 | for { 36 | s := <-c 37 | logger.Infof("get a signal %s", s.String()) 38 | switch s { 39 | case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGSTOP, syscall.SIGINT: 40 | logger.Error("Comet Exit: syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGSTOP, syscall.SIGINT") 41 | return 42 | case syscall.SIGHUP: 43 | // TODO reload 44 | //return 45 | logger.Error("Comet Exit: syscall.SIGHUP") 46 | default: 47 | logger.Error("Comet Exit: default") 48 | return 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /message/storage.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package main 18 | 19 | import ( 20 | "encoding/json" 21 | "errors" 22 | "github.com/EPICPaaS/appmsgsrv/rpc" 23 | ) 24 | 25 | const ( 26 | RedisStorageType = "redis" 27 | MySQLStorageType = "mysql" 28 | ) 29 | 30 | var ( 31 | UseStorage Storage 32 | ErrStorageType = errors.New("unknown storage type") 33 | ) 34 | 35 | // Stored messages interface 36 | type Storage interface { 37 | // private message method 38 | GetPrivate(key string, mid int64) ([]*rpc.Message, error) 39 | SavePrivate(key string, msg json.RawMessage, mid int64, expire uint) error 40 | DelPrivate(key string) error 41 | } 42 | 43 | // InitStorage init the storage type(mysql or redis). 44 | func InitStorage() error { 45 | if Conf.StorageType == RedisStorageType { 46 | UseStorage = NewRedisStorage() 47 | } else if Conf.StorageType == MySQLStorageType { 48 | UseStorage = NewMySQLStorage() 49 | } else { 50 | logger.Errorf("unknown storage type: \"%s\"", Conf.StorageType) 51 | return ErrStorageType 52 | } 53 | return nil 54 | } 55 | -------------------------------------------------------------------------------- /message/zk.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | // github.com/samuel/go-zookeeper 18 | // Copyright (c) 2013, Samuel Stauffer 19 | // All rights reserved. 20 | 21 | package main 22 | 23 | import ( 24 | myzk "github.com/EPICPaaS/appmsgsrv/zk" 25 | "github.com/samuel/go-zookeeper/zk" 26 | "strings" 27 | ) 28 | 29 | // InitZK create zookeeper root path, and register a temp node. 30 | func InitZK() (*zk.Conn, error) { 31 | conn, err := myzk.Connect(Conf.ZookeeperAddr, Conf.ZookeeperTimeout) 32 | if err != nil { 33 | logger.Errorf("zk.Connect() error(%v)", err) 34 | return nil, err 35 | } 36 | if err = myzk.Create(conn, Conf.ZookeeperPath); err != nil { 37 | logger.Errorf("zk.Create() error(%v)", err) 38 | return conn, err 39 | } 40 | data := strings.Join(Conf.RPCBind, ",") 41 | logger.Tracef("zk data: \"%s\"", data) 42 | // tcp, websocket and rpc bind address store in the zk 43 | if err = myzk.RegisterTemp(conn, Conf.ZookeeperPath, data); err != nil { 44 | logger.Errorf("zk.RegisterTemp() error(%v)", err) 45 | return conn, err 46 | } 47 | return conn, nil 48 | } 49 | -------------------------------------------------------------------------------- /perf/perf.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package perf 18 | 19 | import ( 20 | "github.com/b3log/wide/log" 21 | "net/http" 22 | "net/http/pprof" 23 | "os" 24 | ) 25 | 26 | var logger = log.NewLogger(os.Stdout) 27 | 28 | // StartPprof start http pprof. 29 | func Init(pprofBind []string) { 30 | pprofServeMux := http.NewServeMux() 31 | pprofServeMux.HandleFunc("/debug/pprof/", pprof.Index) 32 | pprofServeMux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) 33 | pprofServeMux.HandleFunc("/debug/pprof/profile", pprof.Profile) 34 | pprofServeMux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 35 | for _, addr := range pprofBind { 36 | go func() { 37 | if err := http.ListenAndServe(addr, pprofServeMux); err != nil { 38 | logger.Errorf("http.ListenAndServe(\"%s\", pprofServeMux) error(%v)", addr, err) 39 | panic(err) 40 | } 41 | }() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /process/process.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package process 18 | 19 | import ( 20 | "fmt" 21 | "io/ioutil" 22 | "os" 23 | //"os/user" 24 | //"strconv" 25 | //"strings" 26 | //"syscall" 27 | ) 28 | 29 | const ( 30 | defaultUser = "nobody" 31 | defaultGroup = "nobody" 32 | ) 33 | 34 | // Init create pid file, set working dir, setgid and setuid. 35 | func Init(userGroup, dir, pidFile string) error { 36 | // change working dir 37 | if err := os.Chdir(dir); err != nil { 38 | return err 39 | } 40 | // create pid file 41 | if err := ioutil.WriteFile(pidFile, []byte(fmt.Sprintf("%d\n", os.Getpid())), 0644); err != nil { 42 | return err 43 | } 44 | /* 45 | // setuid and setgid 46 | ug := strings.SplitN(userGroup, " ", 2) 47 | usr := defaultUser 48 | grp := defaultGroup 49 | if len(ug) == 0 { 50 | // default user and group (nobody) 51 | } else if len(ug) == 1 { 52 | usr = ug[0] 53 | grp = "" 54 | } else if len(ug) == 2 { 55 | usr = ug[0] 56 | grp = ug[1] 57 | } 58 | uid := 0 59 | gid := 0 60 | ui, err := user.Lookup(usr) 61 | if err != nil { 62 | return err 63 | } 64 | uid, _ = strconv.Atoi(ui.Uid) 65 | // group no set 66 | if grp == "" { 67 | gid, _ = strconv.Atoi(ui.Gid) 68 | } else { 69 | // use user's group instread 70 | // TODO LookupGroup 71 | gid, _ = strconv.Atoi(ui.Gid) 72 | } 73 | if err := syscall.Setgid(gid); err != nil { 74 | fmt.Println(1) 75 | return err 76 | } 77 | if err := syscall.Setuid(uid); err != nil { 78 | fmt.Println(2) 79 | return err 80 | }*/ 81 | return nil 82 | } 83 | -------------------------------------------------------------------------------- /process/process_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package process 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | // mkdir -p /tmp/test && chown -R nobody:nobody /tmp/test 24 | // sudo go test 25 | func TestInit(t *testing.T) { 26 | if err := Init("nobody nobody", "./", "/tmp/test/process_test.pid"); err != nil { 27 | t.Error(err) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /router/cn.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package router 18 | 19 | import ( 20 | "fmt" 21 | "github.com/thinkboy/go-qqwry" 22 | "strings" 23 | ) 24 | 25 | const ( 26 | CTCC = "电信" 27 | CMCC = "移动" 28 | CUCC = "联通" 29 | ENET = "教育网" 30 | ) 31 | 32 | type RouterCN struct { 33 | qqWryReader *qqwry.QQWry 34 | } 35 | 36 | // Init china network router 37 | func InitCN(path string) (*RouterCN, error) { 38 | qqWryRead, err := qqwry.NewQQWry(path) 39 | if err != nil { 40 | return nil, fmt.Errorf("load QQWry.dat error : %v", err) 41 | } 42 | 43 | return &RouterCN{qqWryReader: qqWryRead}, nil 44 | } 45 | 46 | // Select the best ip 47 | func (r *RouterCN) SelectBest(remoteAddr string, ips []string) string { 48 | if len(ips) == 0 { 49 | return "" 50 | } 51 | 52 | ipRemote := strings.Split(remoteAddr, ":") 53 | cc := r.netName(ipRemote[0]) 54 | if cc == "" { 55 | return "" 56 | } 57 | 58 | for i := 0; i < len(ips); i++ { 59 | ip := strings.Split(ips[i], ":") 60 | if cc == r.netName(ip[0]) { 61 | return ips[i] 62 | } 63 | } 64 | 65 | return "" 66 | } 67 | 68 | func (r *RouterCN) netName(ip string) string { 69 | cc := "" 70 | _, area := r.qqWryReader.QueryIP(ip) 71 | if -1 != strings.Index(area, CTCC) { 72 | cc = CTCC 73 | } else if -1 != strings.Index(area, CMCC) { 74 | cc = CMCC 75 | } else if -1 != strings.Index(area, CUCC) { 76 | cc = CUCC 77 | } else if -1 != strings.Index(area, ENET) { 78 | cc = ENET 79 | } 80 | 81 | return cc 82 | } 83 | -------------------------------------------------------------------------------- /rpc/rand_lb.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package rpc 18 | 19 | import ( 20 | "errors" 21 | "fmt" 22 | "math/rand" 23 | "net/rpc" 24 | "time" 25 | ) 26 | 27 | const ( 28 | randLBRetryCHLength = 10 29 | ) 30 | 31 | var ( 32 | ErrRandLBLength = errors.New("clients and addrs length not match") 33 | ErrRandLBAddr = errors.New("clients map no addr key") 34 | ) 35 | 36 | // random load balancing object 37 | type RandLB struct { 38 | Clients map[string]*rpc.Client 39 | addrs []string 40 | length int 41 | exitCH chan int 42 | } 43 | 44 | // NewRandLB new a random load balancing object. 45 | func NewRandLB(clients map[string]*rpc.Client, addrs []string, service string, retry, ping time.Duration, check bool) (*RandLB, error) { 46 | length := len(clients) 47 | if length != len(addrs) { 48 | logger.Errorf("clients: %d, addrs: %d not equal", length, len(addrs)) 49 | return nil, ErrRandLBLength 50 | } 51 | for _, addr := range addrs { 52 | if _, ok := clients[addr]; !ok { 53 | logger.Errorf("addr: \"%s\" not exist in clients map", addr) 54 | return nil, ErrRandLBAddr 55 | } 56 | } 57 | r := &RandLB{Clients: clients, addrs: addrs, length: length} 58 | if check && length > 0 { 59 | logger.Info("rpc ping start") 60 | r.ping(service, retry, ping) 61 | } 62 | return r, nil 63 | } 64 | 65 | // Get get a rpc client randomly. 66 | func (r *RandLB) Get() *rpc.Client { 67 | if len(r.addrs) == 0 { 68 | return nil 69 | } 70 | addr := r.addrs[rand.Intn(r.length)] 71 | logger.Tracef("rand hit rpc node: \"%s\"", addr) 72 | client, _ := r.Clients[addr] 73 | return client 74 | } 75 | 76 | // Stop stop the retry connect goroutine and ping goroutines. 77 | func (r *RandLB) Stop() { 78 | if r.exitCH != nil { 79 | close(r.exitCH) 80 | } 81 | logger.Info("stop the randlb retry connect goroutine and ping goroutines") 82 | } 83 | 84 | // Destroy release the rpc.Client resource. 85 | func (r *RandLB) Destroy() { 86 | r.Stop() 87 | for _, client := range r.Clients { 88 | if client != nil { 89 | if err := client.Close(); err != nil { 90 | logger.Errorf("client.Close() error(%v)", err) 91 | } 92 | } 93 | } 94 | } 95 | 96 | // ping do a ping, if failed then retry. 97 | func (r *RandLB) ping(service string, retry, ping time.Duration) { 98 | method := fmt.Sprintf("%s.Ping", service) 99 | retryCH := make(chan string, randLBRetryCHLength) 100 | r.exitCH = make(chan int, 1) 101 | for _, addr := range r.addrs { 102 | // warn: closures problem 103 | go func(addr string) { 104 | logger.Infof("\"%s\" rpc ping goroutine start", addr) 105 | ret := 0 106 | for { 107 | select { 108 | case <-r.exitCH: 109 | logger.Infof("\"%s\" rpc ping goroutine exit", addr) 110 | return 111 | default: 112 | } 113 | // get client for ping 114 | client, _ := r.Clients[addr] 115 | if err := client.Call(method, 0, &ret); err != nil { 116 | // if failed send to chan reconnect, sleep 117 | client.Close() 118 | retryCH <- addr 119 | logger.Errorf("client.Call(\"%s\", 0, &ret) error(%v), retry", method, err) 120 | time.Sleep(retry) 121 | continue 122 | } 123 | // if ok, sleep 124 | logger.Tracef("\"%s\": rpc ping ok", addr) 125 | time.Sleep(ping) 126 | } 127 | }(addr) 128 | } 129 | // rpc retry connect 130 | go func() { 131 | var retryAddr string 132 | logger.Info("rpc retry connect goroutine start") 133 | for { 134 | select { 135 | case retryAddr = <-retryCH: 136 | case <-r.exitCH: 137 | logger.Info("rpc retry connect goroutine exit") 138 | return 139 | } 140 | rpcTmp, err := rpc.Dial("tcp", retryAddr) 141 | if err != nil { 142 | logger.Errorf("rpc.Dial(\"tcp\", %s) error(%s)", retryAddr, err) 143 | continue 144 | } 145 | logger.Infof("rpc.Dial(\"tcp\", %s) retry succeed", retryAddr) 146 | // copy-on-write 147 | tmpClients := make(map[string]*rpc.Client, r.length) 148 | for addr, client := range r.Clients { 149 | tmpClients[addr] = client 150 | } 151 | tmpClients[retryAddr] = rpcTmp 152 | // atomic update clients 153 | r.Clients = tmpClients 154 | } 155 | }() 156 | } 157 | -------------------------------------------------------------------------------- /rpc/ret.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package rpc 18 | 19 | import ( 20 | "errors" 21 | ) 22 | 23 | const ( 24 | // common 25 | // ok 26 | OK = 0 27 | // param error 28 | ParamErr = 65534 29 | // internal error 30 | InternalErr = 65535 31 | ) 32 | 33 | var ( 34 | ErrParam = errors.New("parameter error") 35 | ) 36 | -------------------------------------------------------------------------------- /rpc/zk.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package rpc 18 | 19 | import ( 20 | "time" 21 | ) 22 | 23 | const ( 24 | // node event 25 | eventNodeAdd = 1 26 | eventNodeDel = 2 27 | eventNodeUpdate = 3 28 | 29 | // wait node 30 | waitNodeDelay = 3 31 | waitNodeDelaySecond = waitNodeDelay * time.Second 32 | ) 33 | -------------------------------------------------------------------------------- /sql/gopush-cluster.sql: -------------------------------------------------------------------------------- 1 | # version 1.0 2 | # author Terry.Mao 3 | CREATE DATABASE IF NOT EXISTS gopush; 4 | USE gopush; 5 | 6 | # private message 7 | # DROP TABLE private_msg; 8 | CREATE TABLE IF NOT EXISTS private_msg ( 9 | id bigint unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY, # auto increment id 10 | skey varchar(64) NOT NULL, # subscriber key 11 | mid bigint unsigned NOT NULL, # message id 12 | ttl bigint NOT NULL, # message expire second 13 | msg blob NOT NULL, # message content 14 | ctime timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', # create time 15 | mtime timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', # modify time 16 | UNIQUE KEY ux_private_msg_1 (skey, mid), 17 | INDEX ix_private_msg_1 (ttl) 18 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 19 | 20 | # public message 21 | # DROP TABLE public_msg; 22 | CREATE TABLE IF NOT EXISTS public_msg ( 23 | id bigint unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY, # auto increment id 24 | mid bigint unsigned NOT NULL, # message id 25 | ttl bigint NOT NULL, # message expire second 26 | msg blob NOT NULL, # message content 27 | ctime timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', # create time 28 | mtime timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', # modify time 29 | INDEX ix_public_msg_1 (ttl) 30 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 31 | 32 | CREATE TABLE IF NOT EXISTS public_msg_log ( 33 | mid bigint unsigned NOT NULL, # message id 34 | stime timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', # create time 35 | ftime timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', # modify time 36 | UNIQUE KEY ux_public_msg_log_1 (mid) 37 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 38 | 39 | # group message 40 | # DROP TABLE group_msg; 41 | CREATE TABLE IF NOT EXISTS group_msg ( 42 | id bigint unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY, # auto increment id 43 | gid int unsigned NOT NULL, # group id 44 | mid bigint unsigned NOT NULL, # message id 45 | ttl bigint NOT NULL, # message expire second 46 | msg blob NOT NULL, # message content 47 | ctime timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', # create time 48 | mtime timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', # modify time 49 | UNIQUE KEY ux_group_msg_1 (gid, mid), 50 | INDEX ix_group_msg_1 (ttl) 51 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -------------------------------------------------------------------------------- /start_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cdir=`dirname $0` 4 | cd $cdir 5 | 6 | nohup ./web -v=3 -log_dir="../logs/web/" -stderrthreshold=INFO & 7 | nohup ./message -v=1 -log_dir="../logs/message/" -stderrthreshold=FATAL & 8 | nohup ./comet -v=1 -log_dir="../logs/comet/" -stderrthreshold=FATAL & 9 | 10 | cd - 11 | -------------------------------------------------------------------------------- /stop_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cdir=`dirname $0` 4 | cd $cdir 5 | 6 | kill `cat /tmp/gopush-cluster-comet.pid` 7 | kill `cat /tmp/gopush-cluster-message.pid` 8 | kill `cat /tmp/gopush-cluster-web.pid` 9 | 10 | cd - 11 | -------------------------------------------------------------------------------- /test/comet/comet-test-exmaple.conf: -------------------------------------------------------------------------------- 1 | # Comet test configuration file example 2 | 3 | # Note on units: when memory size is needed, it is possible to specify 4 | # it in the usual form of 1k 5GB 4M and so forth: 5 | # 6 | # 1kb => 1024 bytes 7 | # 1mb => 1024*1024 bytes 8 | # 1gb => 1024*1024*1024 bytes 9 | # 10 | # units are case insensitive so 1GB 1Gb 1gB are all the same. 11 | 12 | # Note on units: when time duration is needed, it is possible to specify 13 | # it in the usual form of 1s 5M 4h and so forth: 14 | # 15 | # 1s => 1000 * 1000 * 1000 nanoseconds 16 | # 1m => 60 seconds 17 | # 1h => 60 minutes 18 | # 19 | # units are case insensitive so 1h 1H are all the same. 20 | 21 | [base] 22 | # Set connect comet address. 23 | # 24 | # Note this directive is only support "tcp" protocol 25 | addr 42.96.176.106:6969 26 | 27 | # Set test subscriber key 28 | key Terry-Mao 29 | 30 | # Set test comet heartbeat second. 31 | heartbeat 30 32 | 33 | ################################## INCLUDES ################################### 34 | 35 | # Include one or more other config files here. This is useful if you 36 | # have a standard template that goes to all comet server but also need 37 | # to customize a few per-server settings. Include files can include 38 | # other files, so use this wisely. 39 | # 40 | # include /path/to/local.conf 41 | # include /path/to/other.conf 42 | -------------------------------------------------------------------------------- /test/comet/config.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package main 18 | 19 | import ( 20 | "errors" 21 | "flag" 22 | "github.com/Terry-Mao/goconf" 23 | "github.com/golang/glog" 24 | ) 25 | 26 | var ( 27 | Conf *Config 28 | ConfFile string 29 | ErrNoConfigSection = errors.New("no config section") 30 | ) 31 | 32 | func init() { 33 | flag.StringVar(&ConfFile, "c", "./comet-test.conf", " set gopush-cluster comet test config file path") 34 | } 35 | 36 | type Config struct { 37 | // base 38 | Addr string `goconf:"base:addr"` 39 | Key string `goconf:"base:key"` 40 | Heartbeat int `goconf:"base:heartbeat"` 41 | } 42 | 43 | // InitConfig get a new Config struct. 44 | func InitConfig(file string) (*Config, error) { 45 | cf := &Config{ 46 | // base 47 | Addr: "localhost:6969", 48 | Key: "EPICPaaS", 49 | Heartbeat: 30, 50 | } 51 | c := goconf.New() 52 | if err := c.Parse(file); err != nil { 53 | glog.Errorf("goconf.Parse(\"%s\") failed (%s)", file, err.Error()) 54 | return nil, err 55 | } 56 | if err := c.Unmarshal(cf); err != nil { 57 | glog.Errorf("goconf.Unmarshal() failed (%s)", err.Error()) 58 | return nil, err 59 | } 60 | return cf, nil 61 | } 62 | -------------------------------------------------------------------------------- /test/comet/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package main 18 | 19 | import ( 20 | "bufio" 21 | "flag" 22 | "fmt" 23 | "github.com/golang/glog" 24 | "net" 25 | "strconv" 26 | "time" 27 | ) 28 | 29 | func main() { 30 | var err error 31 | flag.Parse() 32 | defer glog.Flush() 33 | // init config 34 | Conf, err = InitConfig(ConfFile) 35 | if err != nil { 36 | glog.Errorf("NewConfig(\"%s\") failed (%s)", ConfFile, err.Error()) 37 | return 38 | } 39 | addr, err := net.ResolveTCPAddr("tcp", Conf.Addr) 40 | if err != nil { 41 | glog.Errorf("net.ResolveTCPAddr(\"tcp\", \"%s\") failed (%s)", Conf.Addr, err.Error()) 42 | return 43 | } 44 | glog.Info("connect to gopush-cluster comet") 45 | conn, err := net.DialTCP("tcp", nil, addr) 46 | if err != nil { 47 | glog.Errorf("net.DialTCP() failed (%s)", err.Error()) 48 | return 49 | } 50 | glog.Infof("send sub request") 51 | proto := []byte(fmt.Sprintf("*3\r\n$3\r\nsub\r\n$%d\r\n%s\r\n$%d\r\n%d\r\n", len(Conf.Key), Conf.Key, len(strconv.Itoa(int(Conf.Heartbeat))), Conf.Heartbeat)) 52 | glog.Infof("send protocol: %s", string(proto)) 53 | if _, err := conn.Write(proto); err != nil { 54 | glog.Errorf("conn.Write() failed (%s)", err.Error()) 55 | return 56 | } 57 | // get first heartbeat 58 | first := false 59 | rd := bufio.NewReader(conn) 60 | // block read reply from service 61 | glog.Info("wait message") 62 | for { 63 | if err := conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(Conf.Heartbeat) * 2)); err != nil { 64 | glog.Errorf("conn.SetReadDeadline() failed (%s)", err.Error()) 65 | return 66 | } 67 | line, err := rd.ReadBytes('\n') 68 | if err != nil { 69 | glog.Errorf("rd.ReadBytes() failed (%s)", err.Error()) 70 | return 71 | } 72 | if line[len(line)-2] != '\r' { 73 | glog.Errorf("protocol reply format error") 74 | return 75 | } 76 | glog.Infof("line: %s", line) 77 | switch line[0] { 78 | // reply 79 | case '$': 80 | cmdSize, err := strconv.Atoi(string(line[1 : len(line)-2])) 81 | if err != nil { 82 | glog.Errorf("protocol reply format error") 83 | return 84 | } 85 | data, err := rd.ReadBytes('\n') 86 | if err != nil { 87 | glog.Errorf("protocol reply format error") 88 | return 89 | } 90 | if len(data) != cmdSize+2 { 91 | glog.Errorf("protocol reply format error: %s", data) 92 | return 93 | } 94 | if data[cmdSize] != '\r' || data[cmdSize+1] != '\n' { 95 | glog.Errorf("protocol reply format error") 96 | return 97 | } 98 | reply := string(data[0:cmdSize]) 99 | glog.Infof("receive msg: %s", reply) 100 | break 101 | // heartbeat 102 | case '+': 103 | if !first { 104 | // send heartbeat 105 | go func() { 106 | for { 107 | glog.Info("send heartbeat") 108 | if _, err := conn.Write([]byte("h")); err != nil { 109 | glog.Errorf("conn.Write() failed (%s)", err.Error()) 110 | return 111 | } 112 | time.Sleep(time.Duration(Conf.Heartbeat) * time.Second) 113 | } 114 | }() 115 | first = true 116 | } 117 | glog.Info("receive heartbeat") 118 | break 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /test/load/tcpclient.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | //"strconv" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | func tcpClient() { 13 | 14 | tcpAddr, err := net.ResolveTCPAddr("tcp", "115.29.226.14:6969") 15 | checkError(err) 16 | conn, err := net.DialTCP("tcp", nil, tcpAddr) 17 | checkError(err) 18 | conn.SetKeepAlive(true) 19 | //链接添加成功 {userI_id}_{device_type}-{real_device_id}@{xx} 20 | key := "23370005043469383_Android-123@user" 21 | proto := []byte(fmt.Sprintf("*3\r\n$3\r\nsub\r\n$%d\r\n%s\r\n$2\r\n30\r\n", len(key), key)) 22 | var w sync.WaitGroup 23 | w.Add(1) 24 | go readMsg(conn, w) 25 | if i, err := conn.Write(proto); err != nil { 26 | fmt.Println(err) 27 | } else { 28 | fmt.Println("成功:", i) 29 | } 30 | ht := time.NewTicker(10 * time.Second) 31 | 32 | for _ = range ht.C { 33 | if _, err := conn.Write([]byte("h")); err != nil { 34 | w.Done() 35 | fmt.Println("发送信息失败:", err) 36 | } 37 | } 38 | w.Wait() 39 | fmt.Println("结束") 40 | } 41 | func readMsg(conn net.Conn, w sync.WaitGroup) { 42 | msg := make([]byte, 512) 43 | 44 | for { 45 | n, err := conn.Read(msg) 46 | if err == nil { 47 | fmt.Println("读取到消息:", string(msg[:n])) 48 | } else { 49 | fmt.Println("读取:", err) 50 | w.Done() 51 | break 52 | } 53 | } 54 | } 55 | func checkError(err error) { 56 | if err != nil { 57 | fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ver/ver.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package ver 18 | 19 | const ( 20 | Version = "1.0.4" 21 | ) 22 | -------------------------------------------------------------------------------- /web.ini: -------------------------------------------------------------------------------- 1 | [program:gopush-web] 2 | command=/home/paas/paas/appmsgsrv/bin/web -c /home/paas/paas/appmsgsrv/bin/web.conf -dbc /home/paas/paas/appmsgsrv/bin/db.conf -log_level=info 3 | ;process_name=%(program_name)s ; process_name expr (default %(program_name)s) 4 | numprocs=1 ; number of processes copies to start (def 1) 5 | directory=/home/paas/paas/appmsgsrv/bin ; directory to cwd to before exec (def no cwd) 6 | ;umask=022 ; umask for process (default None) 7 | ;priority=999 ; the relative start priority (default 999) 8 | autostart=true ; start at supervisord start (default: true) 9 | autorestart=true ; whether/when to restart (default: unexpected) 10 | startsecs=10 ; number of secs prog must stay running (def. 1) 11 | startretries=10 ; max # of serial start failures (default 3) 12 | ;exitcodes=0,2 ; 'expected' exit codes for process (default 0,2) 13 | ;stopsignal=QUIT ; signal used to kill process (default TERM) 14 | ;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10) 15 | ;stopasgroup=false ; send stop signal to the UNIX process group (default false) 16 | ;killasgroup=false ; SIGKILL the UNIX process group (def false) 17 | user=root ; setuid to this UNIX account to run the program 18 | redirect_stderr=true ; redirect proc stderr to stdout (default false) 19 | stdout_logfile=/home/paas/paas/appmsgsrv/logs/web/web.log ; stdout log path, NONE for none; default AUTO 20 | stdout_logfile_maxbytes=50MB ; max # logfile bytes b4 rotation (default 50MB) 21 | stdout_logfile_backups=10 ; # of stdout logfile backups (default 10) 22 | stdout_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0) 23 | ;stdout_events_enabled=false ; emit events on stdout writes (default false) 24 | ;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO 25 | ;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) 26 | ;stderr_logfile_backups=10 ; # of stderr logfile backups (default 10) 27 | ;stderr_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0) 28 | ;stderr_events_enabled=false ; emit events on stderr writes (default false) 29 | ;environment=A=1,B=2 ; process environment additions (def no adds) 30 | ;serverurl=AUTO ; override serverurl computation (childutils) 31 | -------------------------------------------------------------------------------- /web/admin.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package main 18 | 19 | import ( 20 | "encoding/json" 21 | "github.com/EPICPaaS/appmsgsrv/app" 22 | myrpc "github.com/EPICPaaS/appmsgsrv/rpc" 23 | "io/ioutil" 24 | "net/http" 25 | "net/url" 26 | "strconv" 27 | "time" 28 | ) 29 | 30 | // PushPrivate handle for push private message. 31 | func PushPrivate(w http.ResponseWriter, r *http.Request) { 32 | if r.Method != "POST" { 33 | http.Error(w, "Method Not Allowed", 405) 34 | return 35 | } 36 | body := "" 37 | 38 | res := map[string]interface{}{"ret": app.OK} 39 | 40 | defer app.RetPWrite(w, r, res, &body, time.Now()) 41 | // param 42 | bodyBytes, err := ioutil.ReadAll(r.Body) 43 | if err != nil { 44 | res["ret"] = app.ParamErr 45 | logger.Errorf("ioutil.ReadAll() failed (%s)", err.Error()) 46 | return 47 | } 48 | body = string(bodyBytes) 49 | params := r.URL.Query() 50 | key := params.Get("key") 51 | expireStr := params.Get("expire") 52 | if key == "" { 53 | res["ret"] = app.ParamErr 54 | return 55 | } 56 | expire, err := strconv.ParseUint(expireStr, 10, 32) 57 | if err != nil { 58 | res["ret"] = app.ParamErr 59 | logger.Errorf("strconv.ParseUint(\"%s\", 10, 32) error(%v)", expireStr, err) 60 | return 61 | } 62 | node := myrpc.GetComet(key) 63 | if node == nil || node.CometRPC == nil { 64 | res["ret"] = app.NotFoundServer 65 | return 66 | } 67 | client := node.CometRPC.Get() 68 | if client == nil { 69 | res["ret"] = app.NotFoundServer 70 | return 71 | } 72 | rm := json.RawMessage(bodyBytes) 73 | msg, err := rm.MarshalJSON() 74 | if err != nil { 75 | res["ret"] = app.ParamErr 76 | logger.Errorf("json.RawMessage(\"%s\").MarshalJSON() error(%v)", body, err) 77 | return 78 | } 79 | args := &myrpc.CometPushPrivateArgs{Msg: json.RawMessage(msg), Expire: uint(expire), Key: key} 80 | ret := 0 81 | if err := client.Call(myrpc.CometServicePushPrivate, args, &ret); err != nil { 82 | logger.Errorf("client.Call(\"%s\", \"%v\", &ret) error(%v)", myrpc.CometServicePushPrivate, args, err) 83 | res["ret"] = app.InternalErr 84 | return 85 | } 86 | return 87 | } 88 | 89 | // DelPrivate handle for push private message. 90 | func DelPrivate(w http.ResponseWriter, r *http.Request) { 91 | if r.Method != "POST" { 92 | http.Error(w, "Method Not Allowed", 405) 93 | return 94 | } 95 | body := "" 96 | res := map[string]interface{}{"ret": app.OK} 97 | defer app.RetPWrite(w, r, res, &body, time.Now()) 98 | // param 99 | bodyBytes, err := ioutil.ReadAll(r.Body) 100 | if err != nil { 101 | res["ret"] = app.ParamErr 102 | logger.Errorf("ioutil.ReadAll() failed (%s)", err.Error()) 103 | return 104 | } 105 | body = string(bodyBytes) 106 | params, err := url.ParseQuery(body) 107 | if err != nil { 108 | logger.Errorf("url.ParseQuery(\"%s\") error(%v)", body, err) 109 | res["ret"] = app.ParamErr 110 | return 111 | } 112 | key := params.Get("key") 113 | if key == "" { 114 | res["ret"] = app.ParamErr 115 | return 116 | } 117 | client := myrpc.MessageRPC.Get() 118 | if client == nil { 119 | logger.Warnf("user_key: \"%s\" can't not find message rpc node", key) 120 | res["ret"] = app.InternalErr 121 | return 122 | } 123 | ret := 0 124 | if err := client.Call(myrpc.MessageServiceDelPrivate, key, &ret); err != nil { 125 | logger.Errorf("client.Call(\"%s\", \"%s\", &ret) error(%v)", myrpc.MessageServiceDelPrivate, key, err) 126 | res["ret"] = app.InternalErr 127 | return 128 | } 129 | return 130 | } 131 | -------------------------------------------------------------------------------- /web/db.conf: -------------------------------------------------------------------------------- 1 | [base] 2 | #Mysql configuration 3 | app.dbURL root:123456@tcp(10.180.120.63:3308)/appmsgsrv?parseTime=true 4 | app.dbMaxIdleConns 10 5 | app.dbMaxOpenConns 100 -------------------------------------------------------------------------------- /web/handle.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package main 18 | 19 | import ( 20 | "github.com/EPICPaaS/appmsgsrv/app" 21 | myrpc "github.com/EPICPaaS/appmsgsrv/rpc" 22 | "net/http" 23 | "strconv" 24 | "time" 25 | ) 26 | 27 | // GetServer handle for server get 28 | func GetServer0(w http.ResponseWriter, r *http.Request) { 29 | if r.Method != "GET" { 30 | http.Error(w, "Method Not Allowed", 405) 31 | return 32 | } 33 | params := r.URL.Query() 34 | key := params.Get("key") 35 | callback := params.Get("callback") 36 | protoStr := params.Get("proto") 37 | res := map[string]interface{}{"ret": app.OK, "msg": "ok"} 38 | defer app.RetWrite(w, r, res, callback, time.Now()) 39 | if key == "" { 40 | res["ret"] = app.ParamErr 41 | return 42 | } 43 | proto, err := strconv.Atoi(protoStr) 44 | if err != nil { 45 | logger.Errorf("strconv.Atoi(\"%s\") error(%v)", protoStr, err) 46 | res["ret"] = app.ParamErr 47 | return 48 | } 49 | // Match a push-server with the value computed through ketama algorithm 50 | node := myrpc.GetComet(key) 51 | if node == nil { 52 | res["ret"] = app.NotFoundServer 53 | return 54 | } 55 | addr := node.Addr[proto] 56 | if addr == nil || len(addr) == 0 { 57 | res["ret"] = app.NotFoundServer 58 | return 59 | } 60 | server := "" 61 | // Select the best ip 62 | if app.Conf.Router != "" { 63 | server = routerCN.SelectBest(r.RemoteAddr, addr) 64 | logger.Tracef("select the best ip:\"%s\" match with remoteAddr:\"%s\" , from ip list:\"%v\"", server, r.RemoteAddr, addr) 65 | } 66 | if server == "" { 67 | logger.Tracef("remote addr: \"%s\" chose the ip: \"%s\"", r.RemoteAddr, addr[0]) 68 | server = addr[0] 69 | } 70 | res["data"] = map[string]interface{}{"server": server} 71 | return 72 | } 73 | 74 | // GetOfflineMsg get offline mesage http handler. 75 | func GetOfflineMsg0(w http.ResponseWriter, r *http.Request) { 76 | if r.Method != "GET" { 77 | http.Error(w, "Method Not Allowed", 405) 78 | return 79 | } 80 | params := r.URL.Query() 81 | key := params.Get("key") 82 | midStr := params.Get("mid") 83 | callback := params.Get("callback") 84 | res := map[string]interface{}{"ret": app.OK, "msg": "ok"} 85 | defer app.RetWrite(w, r, res, callback, time.Now()) 86 | if key == "" || midStr == "" { 87 | res["ret"] = app.ParamErr 88 | return 89 | } 90 | mid, err := strconv.ParseInt(midStr, 10, 64) 91 | if err != nil { 92 | res["ret"] = app.ParamErr 93 | logger.Errorf("strconv.ParseInt(\"%s\", 10, 64) error(%v)", midStr, err) 94 | return 95 | } 96 | // RPC get offline messages 97 | reply := &myrpc.MessageGetResp{} 98 | args := &myrpc.MessageGetPrivateArgs{MsgId: mid, Key: key} 99 | client := myrpc.MessageRPC.Get() 100 | if client == nil { 101 | res["ret"] = app.InternalErr 102 | return 103 | } 104 | if err := client.Call(myrpc.MessageServiceGetPrivate, args, reply); err != nil { 105 | logger.Errorf("myrpc.MessageRPC.Call(\"%s\", \"%v\", reply) error(%v)", myrpc.MessageServiceGetPrivate, args, err) 106 | res["ret"] = app.InternalErr 107 | return 108 | } 109 | omsgs := []string{} 110 | opmsgs := []string{} 111 | for _, msg := range reply.Msgs { 112 | omsg, err := msg.OldBytes() 113 | if err != nil { 114 | res["ret"] = app.InternalErr 115 | return 116 | } 117 | omsgs = append(omsgs, string(omsg)) 118 | } 119 | 120 | if len(omsgs) == 0 { 121 | return 122 | } 123 | 124 | res["data"] = map[string]interface{}{"msgs": omsgs, "pmsgs": opmsgs} 125 | return 126 | } 127 | 128 | // GetTime get server time http handler. 129 | func GetTime0(w http.ResponseWriter, r *http.Request) { 130 | if r.Method != "GET" { 131 | http.Error(w, "Method Not Allowed", 405) 132 | return 133 | } 134 | params := r.URL.Query() 135 | callback := params.Get("callback") 136 | res := map[string]interface{}{"ret": app.OK, "msg": "ok"} 137 | now := time.Now() 138 | defer app.RetWrite(w, r, res, callback, now) 139 | res["data"] = map[string]interface{}{"timeid": now.UnixNano() / 100} 140 | return 141 | } 142 | -------------------------------------------------------------------------------- /web/handle_1.0.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package main 18 | 19 | import ( 20 | "net/http" 21 | "strconv" 22 | "time" 23 | 24 | "github.com/EPICPaaS/appmsgsrv/app" 25 | myrpc "github.com/EPICPaaS/appmsgsrv/rpc" 26 | ) 27 | 28 | // GetServer handle for server get 29 | func GetServer(w http.ResponseWriter, r *http.Request) { 30 | if r.Method != "GET" { 31 | http.Error(w, "Method Not Allowed", 405) 32 | return 33 | } 34 | params := r.URL.Query() 35 | key := params.Get("k") 36 | callback := params.Get("cb") 37 | protoStr := params.Get("p") 38 | res := map[string]interface{}{"ret": app.OK} 39 | defer app.RetWrite(w, r, res, callback, time.Now()) 40 | if key == "" { 41 | res["ret"] = app.ParamErr 42 | return 43 | } 44 | proto, err := strconv.Atoi(protoStr) 45 | if err != nil { 46 | logger.Errorf("strconv.Atoi(\"%s\") error(%v)", protoStr, err) 47 | res["ret"] = app.ParamErr 48 | return 49 | } 50 | // Match a push-server with the value computed through ketama algorithm 51 | node := myrpc.GetComet(key) 52 | if node == nil { 53 | res["ret"] = app.NotFoundServer 54 | return 55 | } 56 | addr := node.Addr[proto] 57 | if addr == nil || len(addr) == 0 { 58 | res["ret"] = app.NotFoundServer 59 | return 60 | } 61 | server := "" 62 | // Select the best ip 63 | if app.Conf.Router != "" { 64 | server = routerCN.SelectBest(r.RemoteAddr, addr) 65 | logger.Tracef("select the best ip:\"%s\" match with remoteAddr:\"%s\" , from ip list:\"%v\"", server, r.RemoteAddr, addr) 66 | } 67 | if server == "" { 68 | logger.Tracef("remote addr: \"%s\" chose the ip: \"%s\"", r.RemoteAddr, addr[0]) 69 | server = addr[0] 70 | } 71 | res["data"] = map[string]interface{}{"server": server} 72 | return 73 | } 74 | 75 | // GetOfflineMsg get offline mesage http handler. 76 | func GetOfflineMsg(w http.ResponseWriter, r *http.Request) { 77 | if r.Method != "GET" { 78 | http.Error(w, "Method Not Allowed", 405) 79 | return 80 | } 81 | params := r.URL.Query() 82 | key := params.Get("k") 83 | midStr := params.Get("m") 84 | callback := params.Get("cb") 85 | res := map[string]interface{}{"ret": app.OK} 86 | defer app.RetWrite(w, r, res, callback, time.Now()) 87 | if key == "" || midStr == "" { 88 | res["ret"] = app.ParamErr 89 | return 90 | } 91 | mid, err := strconv.ParseInt(midStr, 10, 64) 92 | if err != nil { 93 | res["ret"] = app.ParamErr 94 | logger.Errorf("strconv.ParseInt(\"%s\", 10, 64) error(%v)", midStr, err) 95 | return 96 | } 97 | // RPC get offline messages 98 | reply := &myrpc.MessageGetResp{} 99 | args := &myrpc.MessageGetPrivateArgs{MsgId: mid, Key: key} 100 | client := myrpc.MessageRPC.Get() 101 | if client == nil { 102 | res["ret"] = app.InternalErr 103 | return 104 | } 105 | if err := client.Call(myrpc.MessageServiceGetPrivate, args, reply); err != nil { 106 | logger.Errorf("myrpc.MessageRPC.Call(\"%s\", \"%v\", reply) error(%v)", myrpc.MessageServiceGetPrivate, args, err) 107 | res["ret"] = app.InternalErr 108 | return 109 | } 110 | if len(reply.Msgs) == 0 { 111 | return 112 | } 113 | res["data"] = map[string]interface{}{"msgs": reply.Msgs} 114 | return 115 | } 116 | 117 | // GetTime get server time http handler. 118 | func GetTime(w http.ResponseWriter, r *http.Request) { 119 | if r.Method != "GET" { 120 | http.Error(w, "Method Not Allowed", 405) 121 | return 122 | } 123 | params := r.URL.Query() 124 | callback := params.Get("cb") 125 | res := map[string]interface{}{"ret": app.OK} 126 | now := time.Now() 127 | defer app.RetWrite(w, r, res, callback, now) 128 | res["data"] = map[string]interface{}{"timeid": now.UnixNano() / 1000} 129 | return 130 | } 131 | -------------------------------------------------------------------------------- /web/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "os" 22 | "runtime" 23 | "time" 24 | 25 | "github.com/EPICPaaS/appmsgsrv/app" 26 | "github.com/EPICPaaS/appmsgsrv/db" 27 | "github.com/EPICPaaS/appmsgsrv/perf" 28 | "github.com/EPICPaaS/appmsgsrv/process" 29 | "github.com/EPICPaaS/appmsgsrv/ver" 30 | 31 | "github.com/b3log/wide/log" 32 | ) 33 | 34 | var logger = log.NewLogger(os.Stdout) 35 | 36 | func main() { 37 | var err error 38 | // Parse cmd-line arguments 39 | logLevel := flag.String("log_level", "info", "logger level") 40 | flag.Parse() 41 | log.SetLevel(*logLevel) 42 | 43 | logger.Infof("web ver: \"%s\" start", ver.Version) 44 | 45 | if err = app.InitConfig(); err != nil { 46 | logger.Errorf("InitConfig() error(%v)", err) 47 | return 48 | } 49 | //init db config 50 | if err = db.InitConfig(); err != nil { 51 | logger.Error("db-InitConfig() error(%v)", err) 52 | return 53 | } 54 | // Set max routine 55 | runtime.GOMAXPROCS(app.Conf.MaxProc) 56 | // init zookeeper 57 | zkConn, err := InitZK() 58 | if err != nil { 59 | logger.Errorf("InitZookeeper() error(%v)", err) 60 | return 61 | } 62 | // if process exit, close zk 63 | defer zkConn.Close() 64 | // start pprof http 65 | perf.Init(app.Conf.PprofBind) 66 | // Init network router 67 | if app.Conf.Router != "" { 68 | if err := InitRouter(); err != nil { 69 | logger.Errorf("InitRouter() failed(%v)", err) 70 | return 71 | } 72 | } 73 | 74 | db.InitDB() 75 | defer db.CloseDB() 76 | 77 | app.InitRedisStorage() 78 | 79 | // start http listen. 80 | StartHTTP() 81 | // init process 82 | // sleep one second, let the listen start 83 | time.Sleep(time.Second) 84 | if err = process.Init(app.Conf.User, app.Conf.Dir, app.Conf.PidFile); err != nil { 85 | logger.Errorf("process.Init() error(%v)", err) 86 | return 87 | } 88 | 89 | defer db.MySQL.Close() 90 | 91 | //初始化配额配置 92 | app.InitQuotaAll() 93 | go app.LoadQuotaAll() 94 | 95 | //启动扫描过期文件 96 | go app.ScanExpireFileLink() 97 | 98 | // init signals, block wait signals 99 | signalCH := InitSignal() 100 | HandleSignal(signalCH) 101 | 102 | logger.Info("web stop") 103 | } 104 | -------------------------------------------------------------------------------- /web/router.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "github.com/EPICPaaS/appmsgsrv/app" 22 | "github.com/EPICPaaS/appmsgsrv/router" 23 | ) 24 | 25 | const ( 26 | NetworkRouterCN = "CN" // china 27 | ) 28 | 29 | var ( 30 | routerCN *router.RouterCN 31 | ) 32 | 33 | // Init network router 34 | func InitRouter() error { 35 | switch app.Conf.Router { 36 | case NetworkRouterCN: 37 | r, err := router.InitCN(app.Conf.QQWryPath) 38 | if err != nil { 39 | logger.Errorf("init china network router failed(%v)", err) 40 | return err 41 | } 42 | routerCN = r 43 | default: 44 | // TODO:support more countries` network routers 45 | return fmt.Errorf("unknown network router:\"%s\", please check your configuration", app.Conf.Router) 46 | } 47 | 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /web/signal.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package main 18 | 19 | import ( 20 | "os" 21 | "os/signal" 22 | "syscall" 23 | ) 24 | 25 | // InitSignal register signals handler. 26 | func InitSignal() chan os.Signal { 27 | c := make(chan os.Signal, 1) 28 | signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT, syscall.SIGSTOP) 29 | return c 30 | } 31 | 32 | // HandleSignal fetch signal from chan then do exit or reload. 33 | func HandleSignal(c chan os.Signal) { 34 | // Block until a signal is received. 35 | for { 36 | s := <-c 37 | logger.Infof("get a signal %s", s.String()) 38 | switch s { 39 | case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGSTOP, syscall.SIGINT: 40 | logger.Error("Comet Exit: syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGSTOP, syscall.SIGINT") 41 | return 42 | case syscall.SIGHUP: 43 | // TODO reload 44 | //return 45 | logger.Error("Comet Exit: syscall.SIGHUP") 46 | default: 47 | logger.Error("Comet Exit: default") 48 | return 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /web/static/css/user.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | body, div, p, ul, li, dl, dt, dd, h1, h2, h3, h4, h5, h6 { 4 | margin:0; 5 | padding:0; 6 | } 7 | html { 8 | font-size: 10px; 9 | } 10 | body { 11 | background:#eaeaea; 12 | font-family: 微软雅黑, "Helvetica Neue", Helvetica, STHeiTi, sans-serif; 13 | } 14 | /*html, body { 15 | -webkit-user-select: none; 16 | user-select: none; 17 | }*/ 18 | img { 19 | border:none; 20 | margin:0; 21 | padding:0; 22 | } 23 | a { 24 | text-decoration:none; 25 | } 26 | a, img { 27 | -webkit-touch-callout: none; /* 禁止长按链接与图片弹出菜单 */ 28 | } 29 | #warp { 30 | width:100%; 31 | max-width:1080px; 32 | margin:0 auto; 33 | font-size: 2.4rem; 34 | } 35 | #warp header { 36 | 37 | } 38 | header h1 { 39 | font-size: 2.8rem; 40 | font-weight: normal; 41 | padding:0px 0px 10px 10px; 42 | background: url(../images/line-big.png) left bottom no-repeat; 43 | background-size: 100% 2px; 44 | } 45 | .section { 46 | margin-top: 25px; 47 | padding: 10px; 48 | background: #fbfbfc; 49 | } 50 | #head-photo { 51 | margin: 35px 0px 25px 0px; 52 | display: box; 53 | display: -webkit-box; 54 | display: -moz-box; 55 | -webkit-box-pack:center; 56 | -moz-box-pack:center; 57 | box-pack:center; 58 | width: 100%; 59 | } 60 | #img-wrap { 61 | width: 13.5%; 62 | max-width: 144px; 63 | } 64 | #head-photo img { 65 | box-shadow: 0px 0px 5px 3px #dddcdc; 66 | border-radius: 65px; 67 | max-width: 100%; 68 | width: 100%; 69 | } 70 | #user-name { 71 | text-align: center; 72 | margin-bottom: 35px; 73 | } 74 | #user-info .section { 75 | padding: 20px; 76 | } 77 | footer { 78 | margin: 55px 0px 20px 0px; 79 | color: #8b8b8b; 80 | font-size: 14px; 81 | text-align: center; 82 | } 83 | 84 | @media screen and (max-width: 480px) { 85 | html { 86 | font-size: 7.5px; 87 | } 88 | } -------------------------------------------------------------------------------- /web/static/images/head-photo-big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPICPaaS/appmsgsrv/537dd8c8fa6b4c4211a225ffd1b215553d58d876/web/static/images/head-photo-big.png -------------------------------------------------------------------------------- /web/static/images/line-big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPICPaaS/appmsgsrv/537dd8c8fa6b4c4211a225ffd1b215553d58d876/web/static/images/line-big.png -------------------------------------------------------------------------------- /web/view/erweima.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 用户信息 7 | 8 | 9 | 10 | 11 |
12 |
13 |

用户信息

14 | 15 |
16 |
17 |
18 |

{{.nickname}}

19 |
20 |
21 |

Name: {{.nickname}}

22 |

Email:{{.email}}

23 |

Phone:{{.phone}}

24 |
25 | 26 |
27 |

云南远信数通科技有限公司  版权所有

28 |

Copyright  ©  2011-2014

29 |

All Rights Reserved

30 |
31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /web/web.conf: -------------------------------------------------------------------------------- 1 | # Web configuration file example 2 | 3 | # Note on units: when memory size is needed, it is possible to specify 4 | # it in the usual form of 1k 5GB 4M and so forth: 5 | # 6 | # 1kb => 1024 bytes 7 | # 1mb => 1024*1024 bytes 8 | # 1gb => 1024*1024*1024 bytes 9 | # 10 | # units are case insensitive so 1GB 1Gb 1gB are all the same. 11 | 12 | # Note on units: when time duration is needed, it is possible to specify 13 | # it in the usual form of 1s 5M 4h and so forth: 14 | # 15 | # 1s => 1000 * 1000 * 1000 nanoseconds 16 | # 1m => 60 seconds 17 | # 1h => 60 minutes 18 | # 19 | # units are case insensitive so 1h 1H are all the same. 20 | 21 | [base] 22 | # Web service http listen and server on this address, default localhost:8090 23 | # 24 | # Examples 25 | # http.bind localhost:8090 26 | # http.bind 192.168.1.100:8090,192.168.1.101:8090 27 | # http.bind 0.0.0.0:8090 28 | http.bind 192.168.1.122:8090 29 | 30 | # Web service http listen and server on this address, default localhost:8091 31 | # mainly servers for internal admin 32 | # Examples 33 | # tcp.bind localhost:8091 34 | # tcp.bind 192.168.1.100:8091,192.168.1.101:8091 35 | # tcp.bind 0.0.0.0:8091 36 | admin.bind 192.168.1.122:8091 37 | 38 | app.bind 192.168.1.122:8093 39 | 40 | 41 | # Sets the maximum number of CPUs that can be executing simultaneously. 42 | # This call will go away when the scheduler improves. By default the number of 43 | # logical CPUs is set. 44 | # 45 | # maxproc 4 46 | 47 | # This is used by web service profiling (pprof). 48 | # By default web pprof listens for connections from local interfaces on 8190 49 | # port. It's not safty for listening internet IP addresses. 50 | # 51 | # Examples: 52 | # 53 | # pprof.bind 192.168.1.100:8190,10.0.0.1:8190 54 | # pprof.bind 127.0.0.1:8190 55 | # pprof.bind 0.0.0.0:8190 56 | pprof.bind 192.168.1.122:8190 57 | 58 | # If the master process is run as root, then web will setuid()/setgid() 59 | # to USER/GROUP. If GROUP is not specified, then web uses the same name as 60 | # USER. By default it's nobody user and nobody or nogroup group. 61 | user ublxd 62 | 63 | # When running daemonized, Web writes a pid file in 64 | # /tmp/gopush-cluster-web.pid by default. You can specify a custom pid file 65 | # location here. 66 | pidfile /tmp/gopush-cluster-web.pid 67 | 68 | # The working directory. 69 | # 70 | # The log will be written inside this directory, with the filename specified 71 | # above using the 'logfile' configuration directive. 72 | # 73 | # Note that you must specify a directory here, not a file name. 74 | # dir ./ 75 | 76 | # Network router, now only support CN, if need not router then can annotate 77 | # router CN 78 | 79 | [res] 80 | # QQWry.dat ip library resource path. 81 | # You could get QQWry.dat from github.com/thinkboy/go-qqwry 82 | # qqwry.path /tmp/QQWry.dat 83 | 84 | [zookeeper] 85 | # The provided servers parameter may include multiple server addresses, separated 86 | # by commas, so that the client will automatically attempt to connect 87 | # to another server if one of them stops working for whatever reason. 88 | # Used for exmple following 89 | # addr IP1:Port1,IP2:Port2,IP3:Port3 90 | #addr 192.168.1.122:2181 91 | addr 192.168.1.122:2181 92 | 93 | # The timeout parameter, given in nanoseconds, allows controlling 94 | # the amount of time the zookeeper connection can stay unresponsive before the 95 | # zookeeper server will be considered problematic. 96 | timeout 30s 97 | 98 | # The root path of all nodes that Comet mounted in zookeeper,default /gopush-cluster-comet 99 | comet.path /gopush-cluster-comet 100 | 101 | # The root path of all nodes that message mounted in zookeeper,default /gopush-cluster 102 | message.path /gopush-cluster-message 103 | 104 | [rpc] 105 | # It will ping rpc service per ping time to confirm connecting is alive 106 | # ping 1s 107 | 108 | # Interval time of every reconnection 109 | # retry 3s 110 | 111 | [redis] 112 | # Close connections after remaining idle for this duration. If the value 113 | # is zero, then idle connections are not closed. Applications should set 114 | # the timeout to a value less than the server's timeout. 115 | timeout 28800s 116 | 117 | # Maximum number of idle connections in the pool. 118 | idle 100 119 | 120 | # Maximum number of connections allocated by the pool at a given time. 121 | # When zero, there is no limit on the number of connections in the pool. 122 | active 300 123 | 124 | # Max quantity of stored message for each key, default 20 125 | store 20 126 | 127 | # ketama virtual node base number 128 | ketama.base 255 129 | 130 | [redis.source] 131 | # The format like "NodeName IP:Port", NodeName was specified by Comet service. 132 | # If there are multiple nodes, then configure following 133 | # nodeN:W, N is node name, W is node weight 134 | # node1:1 IP1:Port1 135 | # node2:2 IP2:Port2 136 | # node3:3 IP3:Port3 137 | node1:1 192.168.1.122:6379 138 | 139 | [token] 140 | expire 3600 141 | 142 | [msg] 143 | expire 604800 144 | 145 | #type support [product ,develop ] 146 | [apns] 147 | type develop 148 | 149 | #weedfs address 150 | [weedfs] 151 | address 115.29.107.77:5083 152 | 153 | ################################## INCLUDES ################################### 154 | 155 | # Include one or more other config files here. This is useful if you 156 | # have a standard template that goes to all comet server but also need 157 | # to customize a few per-server settings. Include files can include 158 | # other files, so use this wisely. 159 | # 160 | # include /path/to/local.conf 161 | # include /path/to/other.conf 162 | -------------------------------------------------------------------------------- /web/zk.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package main 18 | 19 | import ( 20 | "github.com/EPICPaaS/appmsgsrv/app" 21 | myrpc "github.com/EPICPaaS/appmsgsrv/rpc" 22 | myzk "github.com/EPICPaaS/appmsgsrv/zk" 23 | "github.com/samuel/go-zookeeper/zk" 24 | ) 25 | 26 | func InitZK() (*zk.Conn, error) { 27 | conn, err := myzk.Connect(app.Conf.ZookeeperAddr, app.Conf.ZookeeperTimeout) 28 | if err != nil { 29 | logger.Errorf("zk.Connect() error(%v)", err) 30 | return nil, err 31 | } 32 | myrpc.InitComet(conn, app.Conf.ZookeeperCometPath, app.Conf.RPCRetry, app.Conf.RPCPing) 33 | myrpc.InitMessage(conn, app.Conf.ZookeeperMessagePath, app.Conf.RPCRetry, app.Conf.RPCPing) 34 | return conn, nil 35 | } 36 | -------------------------------------------------------------------------------- /wiki/architecture/architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPICPaaS/appmsgsrv/537dd8c8fa6b4c4211a225ffd1b215553d58d876/wiki/architecture/architecture.jpg -------------------------------------------------------------------------------- /wiki/comet/client_proto_zh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EPICPaaS/appmsgsrv/537dd8c8fa6b4c4211a225ffd1b215553d58d876/wiki/comet/client_proto_zh.png -------------------------------------------------------------------------------- /wiki/comet/client_proto_zh.textile: -------------------------------------------------------------------------------- 1 |

Terry-Mao/gopush-cluster Comet TCP客户端通讯协议文档

2 | 直接使用Redis的协议格式,方便解析和使用,参考 "Redis 协议":redis_ref 。 3 | 4 |

流程图

5 | !http://raw.github.com/Terry-Mao/gopush-cluster/master/wiki/comet/client_proto_zh.png(comet protocol)! 6 | 7 |

网络层

8 | 协议命令总是以 \r\n(CRLF)结尾。 9 | 10 |

请求

11 | 当comet接受订阅命令,如果存在任何错误会返回状态包或主动断开连接,否则会返回响应心跳或者是响应包。 12 |
13 | ==*==参数个数 CR LF
14 | $第一个参数的字符串占用字节数 CR LF
15 | 参数数据 CR LF
16 | ...
17 | $第N个参数的字符串占用字节数字 CR LF
18 | 参数数据 CR LF
19 | 
20 | 请求订阅参数列表: 21 | 22 | (head). | 字段 | 类型 | 是否必选 | 顺序 | 描述 | 23 | | cmd | string | 是 | 0 | 指令,发起订阅指令为“sub” | 24 | | key | string | 是 | 1 | 用户发起订阅的Key | 25 | | heartbeat | int | 是 | 2 | 长连接的心跳周期(单位:秒)| 26 | | token | string | 否 | 3 | 验证连接的token | 27 | | version | string | 否 | 4 | 客户端版本号 | 28 | 29 | 例如: 30 |
==*==4\r\n$3\r\nsub\r\n$9\r\nTerry-Mao\r\n$2\r\n30\r\n$5\r\n1.0.4\r\n
31 | 表示一共有 *4* 个参数的指令,第一个参数表示指令是 *sub* ;第二个参数表示Key是 *Terry-Mao* ;第三个参数表示心跳周期是 *30* 秒;第四个参数表示客户端版本,一般写为gopush-cluster版本号即可;其中指令前面的$num表示指令的字符字节长度,如$3表示sub的订阅指令长度为 *3* 。 32 | 33 |

状态

34 | 错误状态的协议首字符都是“-”,例如参数错误、未授权的Channel、Token验证失败等。 35 |
-p\r\n
36 | -a\r\n
37 | -c\r\n
38 | 
39 | 其中p表示参数错误、a表示token验证失败、c表示channel未授权或找不到。 40 | 41 |

请求心跳

42 | 心跳包:
h
43 | 客户端定期发送请求心跳给服务端,服务端接受以后,返回响应心跳包。 44 | 45 |

响应心跳

46 | 心跳包:
+h\r\n
47 | 服务端接受请求命令包成功以后,返回一个初始响应心跳给客户端,这时候客户端才开始定期发送请求心跳以及接受返回数据。 48 | 49 |

响应

50 | 格式参照上面提到的Redis协议来返回reply, 51 | 例如: 52 |
$5\r\nTerry\r\n
53 | 其中Terry就是接受到推送的消息内容。 54 | 在comet返回的数据定义为标准json: 55 |
{msg:"your data", mid:100, gid:0}
56 | 客户端需要最终拿到的是json字符串,然后解析获取其中的msg为推送数据,mid为 *int64* 消息ID(客户端保存这个ID,用于获取下次离线消息用,注意区分私信和公共信息的MID要分开存储),gid为消息分组ID(0:表示私信,1:表示公共信息)。 57 | 58 | [redis_ref]http://redis.io/topics/protocol 59 | -------------------------------------------------------------------------------- /wiki/comet/rpc_proto_zh.textile: -------------------------------------------------------------------------------- 1 |

Terry-Mao/gopush-cluster Comet RPC协议文档

2 | Comet内部RPC接口文档,用于Channel的创建,关闭,推送消息以及节点迁移接口等。 3 | 4 |

接口汇总

5 | (head). | 接口名 | 描述 | 访问方式 | 6 | | "ChannelRPC.New":ChannelRPC_New | 创建用户Channel | tcp RPC | 7 | | "ChannelRPC.Close":ChannelRPC_Close | 关闭用户Channel | tcp RPC | 8 | | "ChannelRPC.PushPrivate":ChannelRPC_PushPrivate | 向Channel推送私有信息 | tcp RPC | 9 | | "ChannelRPC.Migrate":ChannelRPC_Migrate | 新增或删除节点调用迁移接口,关闭非本节点的Channel | tcp RPC | 10 | 11 |

公共返回码

12 | 所有接口均返回整型int 13 | 14 | (head). | 错误码 | 描述 | 15 | | 0 | 成功 | 16 | | 65534 | 参数错误 | 17 | | 65535 | 内部错误 | 18 | 19 |

ChannelRPC.New

20 | * 请求参数 21 | 22 | (head). | 参数 | 类型 | 是否必选 | 描述 | 23 | | args | rpc.ChannelNewArgs | 是 | new channel结构体,其中Expire和Token是可选 | 24 | 25 |
26 | package rpc
27 | 
28 | // Channel New Args
29 | type ChannelNewArgs struct {
30 |     Expire int64  // message expire second (not required)
31 |     Token  string // auth token (not required)
32 |     Key    string // subscriber key (required)
33 | }
34 | 
35 | * 返回码 36 | 37 |

ChannelRPC.Close

38 | * 请求参数 39 | 40 | (head). | 参数 | 类型 | 是否必选 | 描述 | 41 | | key | string | 是 | 用户key | 42 | * 返回码 43 | 44 |

ChannelRPC.PushPrivate

45 | * 请求参数 46 | 47 | (head). | 参数 | 类型 | 是否必选 | 描述 | 48 | | args | rpc.ChannelPushPrivateArgs | 是 | 推送私有消息结构体 | 49 |
50 | type ChannelPushPrivateArgs struct {
51 | 	GroupID int    // message group id
52 | 	Msg     string // message content
53 | 	Expire  int64  // message expire second
54 | 	Key     string // subscriber key
55 | }
56 | 
57 | * 返回码 58 | 59 |

ChannelRPC.Migrate

60 | * 请求参数 61 | 62 | (head). | 参数 | 类型 | 是否必选 |描述 | 63 | | args | rpc.ChannelMigrateArgs | 是 | migrate接口推送消息结构体 | 64 |
65 | package rpc
66 | 
67 | // Channel Migrate Args
68 | type ChannelMigrateArgs struct {
69 |     Nodes []string // current comet nodes (required)
70 |     Vnode int      // ketama virtual node number (required)
71 | }
72 | 
73 | 74 | [ChannelRPC_New]#channelrpcnew 75 | [ChannelRPC_Close]#channelrpcclose 76 | [ChannelRPC_PushPrivate]#channelrpcpushprivate 77 | [ChannelRPC_Migrate]#channelrpcmigrate 78 | -------------------------------------------------------------------------------- /wiki/message/rpc_proto_en.textile: -------------------------------------------------------------------------------- 1 |

Terry-Mao/gopush-cluster Message RPC protocol document

2 | Message internal communication RPC interface document, used for storing and getting message and such messages management. 3 | 4 |

Interface Summary

5 | (head). | Name | Description | Method | 6 | | "MessageRPC.Ping":MessageRPC_Ping | Service Ping | tcp RPC | 7 | | "MessageRPC.SavePrivate":MessageRPC_SavePrivate | Stored Message | tcp RPC | 8 | | "MessageRPC.GetPrivate":MessageRPC_GetPrivate | Get Message | tcp RPC | 9 | | "MessageRPC.DelPrivate":MessageRPC_DelPrivate | Clean Key | tcp RPC | 10 | 11 |

Public ErrorCode

12 | 13 | (head). | ErrorCode | Description | 14 | | 0 | Success | 15 | | 65534 | Parameter Error | 16 | | 65535 | Internal Error | 17 | 18 | 19 | 20 |

MessageRPC.Ping

21 | * Request Parameter 22 | 23 | (head). | Parameter | Type | Description | 24 | | m | int | Write 0 | 25 | 26 | * ErrorCode 27 | Only Public ErrorCode 28 | 29 | 30 | 31 |

MessageRPC.SavePrivate

32 | * Request Parameter 33 | 34 | (head). | Parameter | Type | Description | 35 | | m | rpc.MessageSavePrivateArgs | Request parameter struct of Save interface | 36 |
37 | package rpc
38 | 
39 | // Message SavePrivate Args
40 | type MessageSavePrivateArgs struct {
41 | 	Key    string          // subscriber key
42 | 	Msg    json.RawMessage // message content
43 | 	MsgId  int64           // message id
44 | 	Expire uint            // message expire second
45 | }
46 | 
47 | 48 | * ErrorCode 49 | Only Public ErrorCode 50 | 51 | 52 | 53 |

MessageRPC.GetPrivate

54 | * Request Parameter 55 | 56 | (head). | Parameter | Type | Description | 57 | | m | rpc.MessageGetPrivateArgs | Request parameter struct of Get interface | 58 |
59 | // Message Get Args
60 | type MessageGetPrivateArgs struct {
61 | 	MsgId int64  // message id
62 | 	Key   string // subscriber key
63 | }
64 | 
65 | 66 | * ErrorCode 67 | 68 | (head). | ErrorCode | Description | 69 | | rpc. MessageGetResp | Struct of response | 70 |
71 | // Message Get Response
72 | type MessageGetResp struct {
73 | 	Msgs []*Message // messages
74 | }
75 | 
76 | 77 | 78 | 79 |

MessageRPC.DelPrivate

80 | * Request Parameter 81 | 82 | (head). | Parameter | Type | Description | 83 | | key | string | Subscription key or Public key | 84 | 85 | * ErrorCode 86 | Only Public ErrorCode 87 | -------------------------------------------------------------------------------- /wiki/message/rpc_proto_zh.textile: -------------------------------------------------------------------------------- 1 |

Terry-Mao/gopush-cluster Message RPC协议文档

2 | Message内部RPC接口文档,用于Message离线消息内部存储等。 3 | 4 |

接口汇总

5 | (head). | 接口名 | 描述 | 访问方式 | 6 | | "MessageRPC.Ping":MessageRPC_Ping | Service Ping | tcp RPC | 7 | | "MessageRPC.SavePrivate":MessageRPC_SavePrivate | 存储Message | tcp RPC | 8 | | "MessageRPC.GetPrivate":MessageRPC_GetPrivate | 获取Message | tcp RPC | 9 | | "MessageRPC.DelPrivate":MessageRPC_DelPrivate | 清理Key | tcp RPC | 10 | 11 |

公共返回码

12 | 13 | (head). | 错误码 | 描述 | 14 | | 0 | 成功 | 15 | | 65534 | 参数错误 | 16 | | 65535 | 内部错误 | 17 | 18 |

MessageRPC.Ping

19 | * 请求参数 20 | 21 | (head). | 参数 | 类型 | 描述 | 22 | | m | int | 填0即可 | 23 | 24 | * 返回码 25 | 仅返回公共参数 26 | 27 |

MessageRPC.SavePrivate

28 | * 请求参数 29 | 30 | (head). | 参数 | 类型 | 描述 | 31 | | m | rpc.MessageSavePrivateArgs | 存储消息结构体 | 32 |
33 | package rpc
34 | 
35 | // Message SavePrivate Args
36 | type MessageSavePrivateArgs struct {
37 | 	Key    string          // subscriber key
38 | 	Msg    json.RawMessage // message content
39 | 	MsgId  int64           // message id
40 | 	Expire uint            // message expire second
41 | }
42 | 
43 | 44 | * 返回码 45 | 仅返回公共参数 46 | 47 |

MessageRPC.GetPrivate

48 | * 请求参数 49 | 50 | (head). | 参数 | 类型 | 描述 | 51 | | m | rpc.MessageGetPrivateArgs | 获取离线消息结构体 | 52 |
53 | // Message Get Args
54 | type MessageGetPrivateArgs struct {
55 | 	MsgId int64  // message id
56 | 	Key   string // subscriber key
57 | }
58 | 
59 | 60 | * 返回码 61 | 62 | (head). | 错误码 | 描述 | 63 | | rpc. MessageGetResp | 返回的消息结构体 | 64 |
65 | // Message Get Response
66 | type MessageGetResp struct {
67 | 	Msgs []*Message // messages
68 | }
69 | 
70 | 71 |

MessageRPC.DelPrivate

72 | * 请求参数 73 | 74 | (head). | 参数 | 类型 | 描述 | 75 | | key | string | 客户端的订阅key或公共key | 76 | 77 | * 返回码 78 | 仅返回公共参数 79 | 80 | 81 | [MessageRPC_Ping]#messagerpcping 82 | [MessageRPC_SavePrivate]#messagerpcsaveprivate 83 | [MessageRPC_GetPrivate]#messagerpcgetprivate 84 | [MessageRPC_DelPrivate]#messagerpcdelprivate 85 | -------------------------------------------------------------------------------- /wiki/web/external_proto_en.textile: -------------------------------------------------------------------------------- 1 |

Terry-Mao/gopush-cluster Web http protocol document

2 | Web external interface document, used for getting subscription node, getting offline messages and so on 3 | 4 |

Interface Summary

5 | (head). | Name | URL | Method | 6 | | "Get Subscription Node":ServerGet | /server/get | GET | 7 | | "Get Offline Messages":MsgGet | /msg/get | GET | 8 | 9 |

Public ErrorCode

10 | 11 | (head). | ErrorCode | Description | 12 | | 0 | Success | 13 | | 65534 | Parameter Error | 14 | | 65535 | Internal Error | 15 | 16 |

Basic Response Json

17 |
18 | {
19 |     "ret": 0,  //ErrorCode
20 |     "data": {….}//Data does not appear if data is null or ret != 0
21 | }
22 | 
23 | If you are working on JavaScript, you can specify a callback to URL Parameter "callback" eg: callback=test. 24 | Respond data like following: 25 |
26 | test({
27 |     "ret": 0,
28 |     "data": {….}
29 | })
30 | 
31 | 32 | 33 | 34 |

Get Subscription Node

35 | * Request Parameter 36 | 37 | (head). | Parameter | Type | Description | 38 | | key | string | Subscription Key | 39 | | proto | int | Subscription Protocol (1:websocket 2:tcp) | 40 | | callback | string | Callback Name(Optional) | 41 | 42 | * Response Parameter Description 43 | 44 | (head). | Parameter | Type | Description | 45 | | server | string | Address of Subscription | 46 | 47 | * ErrorCode 48 | 49 | (head). | ErrorCode | Description | 50 | | 1001 | no node corresponding with subscription key | 51 | | 1003 | Unknown Subscription Protocol | 52 |
53 | {
54 |     "ret": 0,
55 |     "data": {
56 | 	    "server": "127.0.0.1:8080"  //IP:Port
57 |     }
58 | }
59 | 
60 | 61 | 62 | 63 |

Get Offline Messages

64 | * Request Parameter 65 | 66 | (head). | Parameter | Type | Description | 67 | | key | string | Subscription Key | 68 | | mid | int64 | Latest Private Message ID | 69 | | pmid | int64 | Latest Public Message ID | 70 | | callback | string | Callback Name(Optional) | 71 | 72 | * Response Parameter Description 73 | 74 | (head). | Parameter | Type | Description | 75 | | msgs | string Array | Private Offline Message | 76 | | pmsgs | string Array | Public Offline Message | 77 | Note: 78 | 1.Every message is a string rather than struct, so you need to Unmarshal message one by one 79 | 2.The type of parameter "mid" is int64. 80 | 81 | * Response result 82 | 83 |
84 | {
85 |     "data": {
86 |         "msgs": [
87 |             {"msg":"{\"test\":1}","mid":13999084541846408,"gid":0},
88 |             {"msg":"{\"test\":2}","mid":13999084579056605,"gid":0}
89 |         ],
90 |         "pmsgs": []
91 |     },
92 |     "ret": 0
93 | }
94 | 
95 | -------------------------------------------------------------------------------- /wiki/web/external_proto_zh.textile: -------------------------------------------------------------------------------- 1 |

Terry-Mao/gopush-cluster Web http协议文档

2 | Web外部接口文档,用于获取Subcribe节点,获取离线消息等 3 | 4 |

接口汇总

5 | (head). | 接口名 | URL | 访问方式 | 6 | | "获取订阅节点":ServerGet | /server/get | GET | 7 | | "获取离线消息":MsgGet | /msg/get | GET | 8 | 9 |

公共返回码

10 | 11 | (head). | 错误码 | 描述 | 12 | | 0 | 成功 | 13 | | 65534 | 参数错误 | 14 | | 65535 | 内部错误 | 15 | 16 |

基本返回结构

17 |
18 | {
19 |     "ret": 0,  //错误码
20 |     "data": {….}//如果有数据返回,则有data,无数据返回就只有前面的ret和msg
21 | }
22 | 
23 | 如何接口需要返回jsonp结构(例如客户端使用js解析json),则指定callback参数,例如:callback=test,则返回结构如下: 24 |
25 | test({
26 |     "ret": 0,  //错误码
27 |     "data": {….}//如果有数据返回,则有data,无数据返回就只有前面的ret和msg
28 | })
29 | 
30 | 31 |

获取订阅节点

32 | * 请求参数 33 | 34 | (head). | 参数 | 类型 | 描述 | 35 | | key | string | 订阅key | 36 | | proto | int | 订阅协议 1:websocket 2:tcp | 37 | | callback | string | 返回jsonp结构函数名(可选) | 38 | 39 | * 返回参数说明 40 | 41 | (head). | 参数 | 类型 | 描述 | 42 | | server | string | 返回的可用于订阅的地址 | 43 | 44 | * 返回码 45 | 46 | (head). | 错误码 | 描述 | 47 | | 1001 | 没有找到key对应的节点 | 48 | | 1003 | 未知的订阅协议 | 49 |
50 | {
51 |     "ret": 0,
52 |     "data": {
53 | 	    "server": "127.0.0.1:8080"  //IP:Port
54 |     }
55 | }
56 | 
57 | 58 |

获取离线消息

59 | * 请求参数 60 | 61 | (head). | 参数 | 类型 | 描述 | 62 | | key | string | 订阅key | 63 | | mid | int64 | 最新接收的私有消息ID | 64 | | pmid | int64 | 最新接收收的公共消息ID | 65 | | callback | string | jsonp函数名(可选) | 66 | 67 | * 返回参数说明 68 | 69 | (head). | 参数 | 类型 | 描述 | 70 | | msgs | string数组 | 私有离线消息 | 71 | | pmsgs | string数组 | 公共离线消息 | 72 | 注: 73 | 1.解析每条message的时候注意,每条信息是string数据,而不是结构体 74 | 2.返回msgs、pmsgs消息中的参数mid类型为int64. 75 | 76 | * 返回结果 77 | 78 |
79 | {
80 |     "data": {
81 |         "msgs": [
82 |             {"msg":"{\"test\":1}","mid":13999084541846408,"gid":0},
83 |             {"msg":"{\"test\":2}","mid":13999084579056605,"gid":0}
84 |         ],
85 |         "pmsgs": []
86 |     },
87 |     "ret": 0
88 | }
89 | 
90 | 91 | 92 | [ServerGet]#获取订阅节点 93 | [MsgGet]#获取离线消息 94 | -------------------------------------------------------------------------------- /wiki/web/internal_proto_en.textile: -------------------------------------------------------------------------------- 1 |

Terry-Mao/gopush-cluster Web http protocol document

2 | Web internal interface document, used for push message, node management and so on 3 | 4 |

Interface Summary

5 | (head). | Name | URL | Method | 6 | | "Push Private Message":AdminPush | /admin/push | POST | 7 | | "Clean Message":AdminMsgClean | /admin/msg/clean | POST | 8 | 9 |

Public ErrorCode

10 | 11 | (head). | ErrorCode | Description | 12 | | 0 | Success | 13 | | 65534 | Parameter Error | 14 | | 65535 | Internal Error | 15 | 16 |

Basic Response Json

17 |
18 | {
19 |     "ret": 0,  //ErrorCode
20 |     "data": {….}//Data does not appear if data is null or ret != 0
21 | }
22 | 
23 | 24 | 25 | 26 |

Push Private Message

27 | * Request Parameter 28 | 29 | (head). | Parameter | Type | Description | 30 | | key | string | Subscription key | 31 | | gid | string | Group ID (0)| 32 | | expire | int64 | Message Expire Time, Unit:second| 33 | 34 | Note: Messages stored in body, and service will wholly intact return to client. Above just as URL Parameter. 35 | 36 | * ErrorCode 37 | 38 | (head). | ErrorCode | Description | 39 | | 1001 | no node corresponding with subscription key | 40 |
41 | {
42 |     "ret": 0
43 | }
44 | 
45 | 46 | 47 | 48 |

Clean Message

49 | Node: It will delete all of messages what belong to specify key and it will delete the Channel in the Comet-Service, for release memory 50 | * Request Parameter 51 | 52 | (head). | Parameter | Type | Description | 53 | | key | string | Subscription Key | 54 | 55 | * ErrorCode 56 | 57 |
58 | {
59 |     "ret": 0
60 | }
61 | 
62 | -------------------------------------------------------------------------------- /wiki/web/internal_proto_zh.textile: -------------------------------------------------------------------------------- 1 |

Terry-Mao/gopush-cluster Web http协议文档

2 | Web内部接口文档,用于推送消息、节点管理等 3 | 4 |

接口汇总

5 | (head). | 接口名 | URL | 访问方式 | 6 | | "推送私有消息":AdminPush | /admin/push | POST | 7 | | "清理消息":AdminMsgClean | /admin/msg/clean | POST | 8 | 9 |

公共返回码

10 | 11 | (head). | 错误码 | 描述 | 12 | | 0 | 成功 | 13 | | 65534 | 参数错误 | 14 | | 65535 | 内部错误 | 15 | 16 |

基本返回结构

17 |
18 | {
19 |     "ret": 0,  //错误码
20 |     "data": {….}//如果有数据返回,则有data,无数据返回就只有前面的ret和msg
21 | }
22 | 
23 | 24 |

推送私有消息

25 | * 请求参数 26 | 27 | (head). | 参数 | 类型 | 描述 | 28 | | key | string | 订阅key | 29 | | gid | string | Group ID (写死0即可)| 30 | | expire | int64 | 消息过期时间,单位:秒(s)| 31 | 注: 消息体存放到body中,以上参数为URL参数 32 | 33 | * 返回码 34 | 35 | (head). | 错误码 | 描述 | 36 | | 1001 | 没有找到key对应的节点 | 37 |
38 | {
39 |     "ret": 0
40 | }
41 | 
42 | 43 |

清理消息

44 | 注:清理单个订阅(key)下的所有消息,并从Comet模块中清理掉Key对应的Channel 45 | * 请求参数 46 | 47 | (head). | 参数 | 类型 | 描述 | 48 | | key | string | 客户端订阅时的key | 49 | 50 | * 返回码 51 | 52 |
53 | {
54 |     "ret": 0
55 | }
56 | 
57 | 58 | 59 | [AdminPush]#推送私有消息 60 | [AdminMsgClean]#清理消息 61 | -------------------------------------------------------------------------------- /zk/zk.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | // github.com/samuel/go-zookeeper 18 | // Copyright (c) 2013, Samuel Stauffer 19 | // All rights reserved. 20 | 21 | package zk 22 | 23 | import ( 24 | "errors" 25 | "github.com/b3log/wide/log" 26 | "github.com/samuel/go-zookeeper/zk" 27 | "os" 28 | "path" 29 | "strings" 30 | "syscall" 31 | "time" 32 | ) 33 | 34 | var ( 35 | // error 36 | ErrNoChild = errors.New("zk: children is nil") 37 | ErrNodeNotExist = errors.New("zk: node not exist") 38 | logger = log.NewLogger(os.Stdout) 39 | ) 40 | 41 | // Connect connect to zookeeper, and start a goroutine log the event. 42 | func Connect(addr []string, timeout time.Duration) (*zk.Conn, error) { 43 | conn, session, err := zk.Connect(addr, timeout) 44 | if err != nil { 45 | logger.Errorf("zk.Connect(\"%v\", %d) error(%v)", addr, timeout, err) 46 | return nil, err 47 | } 48 | go func() { 49 | for { 50 | event := <-session 51 | logger.Tracef("zookeeper get a event: %s", event.State.String()) 52 | } 53 | }() 54 | return conn, nil 55 | } 56 | 57 | // Create create zookeeper path, if path exists ignore error 58 | func Create(conn *zk.Conn, fpath string) error { 59 | // create zk root path 60 | tpath := "" 61 | for _, str := range strings.Split(fpath, "/")[1:] { 62 | tpath = path.Join(tpath, "/", str) 63 | logger.Tracef("create zookeeper path: \"%s\"", tpath) 64 | _, err := conn.Create(tpath, []byte(""), 0, zk.WorldACL(zk.PermAll)) 65 | if err != nil { 66 | if err == zk.ErrNodeExists { 67 | logger.Warnf("zk.create(\"%s\") exists", tpath) 68 | } else { 69 | logger.Errorf("zk.create(\"%s\") error(%v)", tpath, err) 70 | return err 71 | } 72 | } 73 | } 74 | return nil 75 | } 76 | 77 | // RegisterTmp create a ephemeral node, and watch it, if node droped then send a SIGQUIT to self. 78 | func RegisterTemp(conn *zk.Conn, fpath, data string) error { 79 | tpath, err := conn.Create(path.Join(fpath)+"/", []byte(data), zk.FlagEphemeral|zk.FlagSequence, zk.WorldACL(zk.PermAll)) 80 | if err != nil { 81 | logger.Errorf("conn.Create(\"%s\", \"%s\", zk.FlagEphemeral|zk.FlagSequence) error(%v)", fpath, data, err) 82 | return err 83 | } 84 | logger.Tracef("create a zookeeper node:%s", tpath) 85 | // watch self 86 | go func() { 87 | for { 88 | logger.Infof("zk path: \"%s\" set a watch", tpath) 89 | exist, _, watch, err := conn.ExistsW(tpath) 90 | if err != nil { 91 | logger.Errorf("zk.ExistsW(\"%s\") error(%v)", tpath, err) 92 | logger.Warnf("zk path: \"%s\" set watch failed, kill itself", tpath) 93 | killSelf() 94 | return 95 | } 96 | if !exist { 97 | logger.Warnf("zk path: \"%s\" not exist, kill itself", tpath) 98 | killSelf() 99 | return 100 | } 101 | event := <-watch 102 | logger.Infof("zk path: \"%s\" receive a event %v", tpath, event) 103 | } 104 | }() 105 | return nil 106 | } 107 | 108 | // GetNodesW get all child from zk path with a watch. 109 | func GetNodesW(conn *zk.Conn, path string) ([]string, <-chan zk.Event, error) { 110 | nodes, stat, watch, err := conn.ChildrenW(path) 111 | if err != nil { 112 | if err == zk.ErrNoNode { 113 | return nil, nil, ErrNodeNotExist 114 | } 115 | logger.Errorf("zk.ChildrenW(\"%s\") error(%v)", path, err) 116 | return nil, nil, err 117 | } 118 | if stat == nil { 119 | return nil, nil, ErrNodeNotExist 120 | } 121 | if len(nodes) == 0 { 122 | return nil, nil, ErrNoChild 123 | } 124 | return nodes, watch, nil 125 | } 126 | 127 | // GetNodes get all child from zk path. 128 | func GetNodes(conn *zk.Conn, path string) ([]string, error) { 129 | nodes, stat, err := conn.Children(path) 130 | if err != nil { 131 | if err == zk.ErrNoNode { 132 | return nil, ErrNodeNotExist 133 | } 134 | logger.Errorf("zk.Children(\"%s\") error(%v)", path, err) 135 | return nil, err 136 | } 137 | if stat == nil { 138 | return nil, ErrNodeNotExist 139 | } 140 | if len(nodes) == 0 { 141 | return nil, ErrNoChild 142 | } 143 | return nodes, nil 144 | } 145 | 146 | // killSelf send a SIGQUIT to self. 147 | func killSelf() { 148 | if err := syscall.Kill(os.Getpid(), syscall.SIGQUIT); err != nil { 149 | logger.Errorf("syscall.Kill(%d, SIGQUIT) error(%v)", os.Getpid(), err) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /zk/zk_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | // github.com/samuel/go-zookeeper 18 | // Copyright (c) 2013, Samuel Stauffer 19 | // All rights reserved. 20 | 21 | package zk 22 | 23 | import ( 24 | "testing" 25 | "time" 26 | ) 27 | 28 | func TestZK(t *testing.T) { 29 | conn, err := Connect([]string{"10.33.21.152:2181"}, time.Second*30) 30 | if err != nil { 31 | t.Error(err) 32 | } 33 | defer conn.Close() 34 | err = Create(conn, "/test/test") 35 | if err != nil { 36 | t.Error(err) 37 | } 38 | // registertmp 39 | err = RegisterTemp(conn, "/test/test", "1") 40 | if err != nil { 41 | t.Error(err) 42 | } 43 | } 44 | --------------------------------------------------------------------------------