├── .gitignore ├── README.md ├── docs ├── IM接口协议V1.0.1.docx ├── im.sql ├── images │ ├── client_chat.png │ ├── client_login.png │ ├── client_main.png │ ├── login.png │ ├── server_5k.png │ ├── server_idle.png │ └── start_server.png ├── postman │ ├── im-go.json.postman_collection │ └── im-go.postman_environment ├── test └── test_data.sql ├── im ├── common │ ├── client.go │ ├── constants.go │ ├── errors.go │ ├── request.go │ └── response.go ├── config.json ├── model │ ├── buddyrequest.go │ ├── category.go │ ├── conn.go │ ├── context.go │ ├── errors.go │ ├── login.go │ ├── message.go │ ├── session.go │ ├── user.go │ └── userrelation.go ├── server │ ├── httpserver.go │ └── tcpserver.go ├── startclient.go ├── startserver.go └── util │ ├── azDGUtil.go │ ├── config.go │ └── util.go └── test ├── aes.go ├── client.go ├── config.go ├── config.json ├── config_test.go ├── pressuretest.go └── test.ini /.gitignore: -------------------------------------------------------------------------------- 1 | # System 2 | .DS_Store 3 | 4 | # Build artifacts 5 | artifacts 6 | target 7 | classes 8 | 9 | # IDE & module files 10 | .idea 11 | *.iml 12 | .project 13 | .settings 14 | .classpath 15 | workspace.xml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IM-GO 2 | 3 | ## 概要 4 | 这是一个使用go语言开发的高性能IM服务端。单台服务器可以承受近百万并发(经测试单台服务器可开启90W+协程,30W+TCP长连接),实现具体IM基本功能,用户能够达到一般IM所拥有的使用功能。 5 | ## 服务端设计 6 | ![image](https://raw.githubusercontent.com/im-qq/imgo/master/docs/images/login.png) 7 | ## 服务端特点 8 | 具有稳定性,可拓展性,高效性,高并发等特点 9 | ## 客户端运行截图 10 | 登录界面 11 | 12 | ![image](https://raw.githubusercontent.com/im-qq/imgo/master/docs/images/client_login.png) 13 | 14 | 主界面 15 | 16 | ![image](https://raw.githubusercontent.com/im-qq/imgo/master/docs/images/client_main.png) 17 | 18 | 聊天界面 19 | 20 | ![image](https://raw.githubusercontent.com/im-qq/imgo/master/docs/images/client_chat.png) 21 | 22 | ## 客户端设计 23 | 客户端是新版本的IQQ项目。使用Swing开发。可以在win,osx,linux上良好运行。具有良好的界面交互效果 24 | ## 测试数据 25 | 服务器配置: 26 | 操作系统:CentOS 6.5 27 | CPU: 2核心 28 | 内存: 4G 29 | 系统盘: 12G 30 | SSD:40G 31 | 带宽:5M 32 | 33 | 测试结果: 34 | 35 | ![image](https://raw.githubusercontent.com/im-qq/imgo/master/docs/images/start_server.png) 36 | 37 | 空闲时服务器信息: 38 | 39 | ![image](https://raw.githubusercontent.com/im-qq/imgo/master/docs/images/server_idle.png) 40 | 41 | 建立5000TCP连接之后的服务器信息: 42 | 43 | ![image](https://raw.githubusercontent.com/im-qq/imgo/master/docs/images/server_5k.png) 44 | 45 | ## 未来 46 | #### 开源版本: 47 | 1. 发送表情 48 | 2. 发送文件 49 | 3. 好友分组 50 | 4. 离线重连机制处理 51 | 5. 系统重构提高性能 52 | #### 商业版本: 53 | 1. 集成微信 54 | 2. 开发App 55 | 3. 开发webchat 56 | 4. 添加管理后台 57 | 5. 系统重构为分布式设计 58 | 6. 添加缓存机制 59 | 7. 简单的数据采集和BI 60 | 61 | ## 版权声明 62 | 开发者:Tony、Itnik 63 | 64 | 该系统遵守GPL3.0开源协议。 65 | 商业使用请联系作者。 66 | 67 | ## Link 68 | 69 | + [IM协议文档](https://github.com/im-qq/imgo/blob/master/docs/IM%E6%8E%A5%E5%8F%A3%E5%8D%8F%E8%AE%AEV1.0.1.docx) 70 | + [客户端](https://github.com/im-qq/italk) 71 | -------------------------------------------------------------------------------- /docs/IM接口协议V1.0.1.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonybase/imgo/20ebe1450e4d1e65b336aab3936bc4a073d3f357/docs/IM接口协议V1.0.1.docx -------------------------------------------------------------------------------- /docs/im.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat Premium Data Transfer 3 | 4 | Source Server : localhost 5 | Source Server Type : MySQL 6 | Source Server Version : 50538 7 | Source Host : localhost 8 | Source Database : im 9 | 10 | Target Server Type : MySQL 11 | Target Server Version : 50538 12 | File Encoding : utf-8 13 | 14 | Date: 05/27/2015 10:40:11 AM 15 | */ 16 | 17 | SET NAMES utf8; 18 | SET FOREIGN_KEY_CHECKS = 0; 19 | 20 | -- ---------------------------- 21 | -- Table structure for `im_buddy_request` 22 | -- ---------------------------- 23 | DROP TABLE IF EXISTS `im_buddy_request`; 24 | CREATE TABLE `im_buddy_request` ( 25 | `id` varchar(255) NOT NULL COMMENT 'ID', 26 | `sender` varchar(255) NOT NULL COMMENT '发送者', 27 | `sender_category_id` varchar(255) NOT NULL COMMENT '发送者好友分类ID', 28 | `receiver` varchar(255) NOT NULL COMMENT '接收者', 29 | `receiver_category_id` varchar(255) DEFAULT NULL, 30 | `send_at` datetime NOT NULL COMMENT '发送请求日期', 31 | `status` char(1) NOT NULL DEFAULT '0' COMMENT '状态 0未读、1同意、2拒绝', 32 | PRIMARY KEY (`id`) 33 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 34 | 35 | -- ---------------------------- 36 | -- Records of `im_buddy_request` 37 | -- ---------------------------- 38 | BEGIN; 39 | INSERT INTO `im_buddy_request` VALUES ('66708492-6201-4c03-a7ef-6e7bbc6f589c', '22', '44', '11', null, '2015-05-26 10:59:38', '2'), ('d44369b7-27c1-4de4-bcfc-44385094f7e1', '22', '44', '11', '33', '2015-05-26 11:00:01', '1'); 40 | COMMIT; 41 | 42 | -- ---------------------------- 43 | -- Table structure for `im_category` 44 | -- ---------------------------- 45 | DROP TABLE IF EXISTS `im_category`; 46 | CREATE TABLE `im_category` ( 47 | `id` varchar(255) NOT NULL COMMENT '唯一标识', 48 | `name` varchar(255) NOT NULL COMMENT '分类名', 49 | `creator` varchar(255) DEFAULT NULL COMMENT '创建人 user_id', 50 | `create_at` datetime DEFAULT NULL COMMENT '创建日期', 51 | PRIMARY KEY (`id`) 52 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 53 | 54 | -- ---------------------------- 55 | -- Records of `im_category` 56 | -- ---------------------------- 57 | BEGIN; 58 | INSERT INTO `im_category` VALUES ('33', '我的好友', '11', '2015-05-04 21:55:31'), ('44', '我的好友', '22', '2015-05-04 21:57:38'); 59 | COMMIT; 60 | 61 | -- ---------------------------- 62 | -- Table structure for `im_conn` 63 | -- ---------------------------- 64 | DROP TABLE IF EXISTS `im_conn`; 65 | CREATE TABLE `im_conn` ( 66 | `id` varchar(255) NOT NULL COMMENT '连接唯一标识', 67 | `user_id` varchar(255) NOT NULL COMMENT '用户ID', 68 | `token` varchar(255) NOT NULL COMMENT '连接TOKEN', 69 | `create_at` datetime NOT NULL COMMENT '创建日期', 70 | `update_at` datetime NOT NULL COMMENT '更新日期', 71 | PRIMARY KEY (`id`) 72 | ) ENGINE=MyISAM DEFAULT CHARSET=latin1; 73 | 74 | -- ---------------------------- 75 | -- Records of `im_conn` 76 | -- ---------------------------- 77 | BEGIN; 78 | INSERT INTO `im_conn` VALUES ('d7e8aab3-2546-418a-a66b-4f4d7ac7dd6d', '11', '9b8c7bea-369d-4d8a-8145-748ac54748fa', '2015-05-27 10:19:08', '2015-05-27 10:19:08'); 79 | COMMIT; 80 | 81 | -- ---------------------------- 82 | -- Table structure for `im_login` 83 | -- ---------------------------- 84 | DROP TABLE IF EXISTS `im_login`; 85 | CREATE TABLE `im_login` ( 86 | `id` varchar(255) NOT NULL COMMENT '登录记录唯一标识', 87 | `user_id` varchar(255) NOT NULL COMMENT '用户ID', 88 | `token` varchar(255) NOT NULL COMMENT '用户token', 89 | `login_at` datetime NOT NULL COMMENT '登录日期', 90 | `login_ip` varchar(32) NOT NULL COMMENT '用户登录IP', 91 | PRIMARY KEY (`id`) 92 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 93 | 94 | -- ---------------------------- 95 | -- Records of `im_login` 96 | -- ---------------------------- 97 | BEGIN; 98 | INSERT INTO `im_login` VALUES ('3b1901d1-5788-46e7-97f3-0b796cdf0e6d', '11', 'a09932a6-0654-4cfe-8199-d877cd01ae5e', '2015-05-26 11:03:54', '127.0.0.1'), ('0e5cc251-b881-47ce-9da3-d746f56d9597', '11', 'd0d6a544-bc54-4b5f-a824-b744800b70de', '2015-05-26 11:08:21', '127.0.0.1'), ('d933314c-89f9-46ef-b175-503e1649a6c0', '11', 'c84c4962-fe94-451f-9926-57cd80393f54', '2015-05-26 11:11:36', '127.0.0.1'), ('2f231cd5-5687-4e1f-9683-75c3da3c3209', '11', '45c1c7cf-a4c1-4825-8459-9ce5d1869462', '2015-05-26 11:14:10', '127.0.0.1'), ('9a18e073-6e26-4808-b081-0ddc1245f505', '11', '889a96ff-9f8c-420c-9389-cc3ed5cb56e6', '2015-05-26 11:19:46', '127.0.0.1'), ('bda8ae16-9b71-436b-8071-cbb1881b15e0', '11', '2c2bc2e8-9d71-41e6-a374-d552544327de', '2015-05-26 11:20:26', '127.0.0.1'), ('73a71b3f-64bc-45e3-abd1-cca4ca13fa6f', '11', '534fba51-5687-4546-9b04-97f8c7936426', '2015-05-26 11:28:33', '127.0.0.1'), ('51bda13c-fcd9-4c88-981e-13262d5b9915', '11', '90b7a455-8cc3-407b-adde-081b7534fa83', '2015-05-26 11:31:27', '127.0.0.1'), ('079f1ad1-a655-44ce-b0d4-516f7042f91a', '11', '22ee84c3-3978-4ea8-b199-13f448763c5c', '2015-05-27 08:56:16', '127.0.0.1'), ('0fcd0b70-da2a-493c-8f0e-d4252d2a0166', '22', '952a2ace-e434-4236-b04e-0381add5773f', '2015-05-27 08:56:55', '127.0.0.1'), ('6cd10533-7364-4be7-8ca8-f2318c19b16d', '11', 'd05afbb0-48dc-4e49-95cd-b09e07f72159', '2015-05-27 08:59:15', '127.0.0.1'), ('bfd8d7ec-6b31-418e-9022-1f669504de62', '11', '6316e29a-eed0-4a9c-b4ef-8abae5421056', '2015-05-27 09:00:01', '127.0.0.1'), ('fcc4a579-a164-4c0b-8c8c-e8981a4be173', '11', '9b8c7bea-369d-4d8a-8145-748ac54748fa', '2015-05-27 10:19:08', '127.0.0.1'), ('cbaa5779-7338-4532-9f80-ce6324e5f2f9', '11', '95f82794-5b25-48e2-8af9-ae81024c7b44', '2015-05-27 10:24:05', '127.0.0.1'), ('383ed174-f6e3-4f7f-98cb-9435dfd5e6d0', '11', 'b172a467-03dd-4d3f-902a-647e60ed558d', '2015-05-27 10:27:08', '127.0.0.1'), ('abbe3dde-0f26-4ca5-8eae-c503f3aac1b0', '11', 'ee4155aa-b38f-4d1a-bfdb-3294f0b1df1b', '2015-05-27 10:36:05', '127.0.0.1'); 99 | COMMIT; 100 | 101 | -- ---------------------------- 102 | -- Table structure for `im_message` 103 | -- ---------------------------- 104 | DROP TABLE IF EXISTS `im_message`; 105 | CREATE TABLE `im_message` ( 106 | `id` varchar(255) NOT NULL COMMENT '消息唯一标识', 107 | `sender` varchar(255) NOT NULL COMMENT '发送人(用户ID)', 108 | `contents` varchar(255) NOT NULL COMMENT '内容(支持富文本)', 109 | `send_at` datetime NOT NULL COMMENT '发送日期', 110 | `state` char(1) NOT NULL DEFAULT '0' COMMENT '消息状态 0未发送,1送达,2已读,3取消,4删除', 111 | `direction` char(1) NOT NULL DEFAULT '0' COMMENT '方向 0发送,1接收', 112 | `type` char(1) NOT NULL DEFAULT '0' COMMENT '消息类型 0聊天信息,1系统提示信息', 113 | `font` varchar(255) DEFAULT NULL COMMENT '字体', 114 | `receiver` varchar(255) NOT NULL COMMENT '接收人(可以是用户,群,讨论组)', 115 | PRIMARY KEY (`id`) 116 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 117 | 118 | -- ---------------------------- 119 | -- Table structure for `im_relation_user_category` 120 | -- ---------------------------- 121 | DROP TABLE IF EXISTS `im_relation_user_category`; 122 | CREATE TABLE `im_relation_user_category` ( 123 | `user_id` varchar(255) NOT NULL COMMENT '用户ID', 124 | `category_id` varchar(255) NOT NULL COMMENT '分类ID', 125 | `create_at` datetime NOT NULL COMMENT '建立好友关系时间' 126 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 127 | 128 | -- ---------------------------- 129 | -- Records of `im_relation_user_category` 130 | -- ---------------------------- 131 | BEGIN; 132 | INSERT INTO `im_relation_user_category` VALUES ('22', '33', '2015-05-04 21:55:44'), ('11', '44', '0000-00-00 00:00:00'); 133 | COMMIT; 134 | 135 | -- ---------------------------- 136 | -- Table structure for `im_relation_user_room` 137 | -- ---------------------------- 138 | DROP TABLE IF EXISTS `im_relation_user_room`; 139 | CREATE TABLE `im_relation_user_room` ( 140 | `user_id` varchar(255) NOT NULL COMMENT '用户ID', 141 | `room_id` varchar(255) NOT NULL COMMENT '聊天室ID', 142 | `create_at` datetime NOT NULL COMMENT '加入聊天室时间' 143 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 144 | 145 | -- ---------------------------- 146 | -- Table structure for `im_room` 147 | -- ---------------------------- 148 | DROP TABLE IF EXISTS `im_room`; 149 | CREATE TABLE `im_room` ( 150 | `id` varchar(255) NOT NULL COMMENT '群的唯一标识', 151 | `name` varchar(255) NOT NULL COMMENT '群名称', 152 | `creator` varchar(255) NOT NULL COMMENT '创建者 user_id', 153 | `create_at` datetime NOT NULL COMMENT '创建日期', 154 | `user_num` int(11) NOT NULL DEFAULT '100' COMMENT '群允许的用户数量', 155 | PRIMARY KEY (`id`) 156 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 157 | 158 | -- ---------------------------- 159 | -- Table structure for `im_session` 160 | -- ---------------------------- 161 | DROP TABLE IF EXISTS `im_session`; 162 | CREATE TABLE `im_session` ( 163 | `id` varchar(255) NOT NULL COMMENT '会话的唯一标识', 164 | `creator` varchar(255) NOT NULL COMMENT '创建者 user_id', 165 | `receiver` varchar(255) NOT NULL COMMENT '接收人(可以是用户,群,讨论组)', 166 | `type` char(1) NOT NULL DEFAULT '0' COMMENT '会话类型 0用户会话,1群会话,2讨论组会话', 167 | `create_at` datetime NOT NULL COMMENT '创建日期', 168 | PRIMARY KEY (`id`) 169 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 170 | 171 | -- ---------------------------- 172 | -- Records of `im_session` 173 | -- ---------------------------- 174 | BEGIN; 175 | INSERT INTO `im_session` VALUES ('ff668c19-6ae1-4369-b1c2-0ad1a4fb6b21', '11', '22', '0', '2015-05-04 22:29:47'), ('44be7aa6-9f8e-4226-85ef-6d0148112526', '22', '11', '0', '2015-05-04 22:29:57'); 176 | COMMIT; 177 | 178 | -- ---------------------------- 179 | -- Table structure for `im_user` 180 | -- ---------------------------- 181 | DROP TABLE IF EXISTS `im_user`; 182 | CREATE TABLE `im_user` ( 183 | `id` varchar(255) NOT NULL COMMENT '唯一标识', 184 | `account` varchar(255) NOT NULL COMMENT '账号', 185 | `password` varchar(255) NOT NULL COMMENT '密码', 186 | `nick` varchar(255) NOT NULL COMMENT '用户昵称', 187 | `sign` varchar(255) DEFAULT '' COMMENT '个人前民', 188 | `avatar` varchar(255) DEFAULT NULL COMMENT '头像', 189 | `status` char(1) NOT NULL DEFAULT '0' COMMENT '状态 0离线,1在线,2离开,3请勿打扰,4忙碌,5Q我吧,6隐身', 190 | `create_at` datetime NOT NULL COMMENT '注册日期', 191 | `update_at` datetime NOT NULL COMMENT '更新日期', 192 | `remark` varchar(255) DEFAULT NULL COMMENT '好友备注', 193 | PRIMARY KEY (`id`) 194 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 195 | 196 | -- ---------------------------- 197 | -- Records of `im_user` 198 | -- ---------------------------- 199 | BEGIN; 200 | INSERT INTO `im_user` VALUES ('22', '22', '22', 'Itnik', '我是要成为海贼王的男人', 'http://att2.citysbs.com/hangzhou/sns01/11_2011/14/middle_20461634334ec10b53d15d53.34164698.jpg', '0', '2015-05-04 21:55:31', '2015-05-04 21:55:31', null), ('11', '11', '11', 'toy', '马上回来', 'http://img.qqbody.com/uploads/allimg/201409/02-173237_949.jpg', '0', '2015-05-04 21:57:38', '2015-05-04 21:57:38', null); 201 | COMMIT; 202 | 203 | SET FOREIGN_KEY_CHECKS = 1; 204 | -------------------------------------------------------------------------------- /docs/images/client_chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonybase/imgo/20ebe1450e4d1e65b336aab3936bc4a073d3f357/docs/images/client_chat.png -------------------------------------------------------------------------------- /docs/images/client_login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonybase/imgo/20ebe1450e4d1e65b336aab3936bc4a073d3f357/docs/images/client_login.png -------------------------------------------------------------------------------- /docs/images/client_main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonybase/imgo/20ebe1450e4d1e65b336aab3936bc4a073d3f357/docs/images/client_main.png -------------------------------------------------------------------------------- /docs/images/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonybase/imgo/20ebe1450e4d1e65b336aab3936bc4a073d3f357/docs/images/login.png -------------------------------------------------------------------------------- /docs/images/server_5k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonybase/imgo/20ebe1450e4d1e65b336aab3936bc4a073d3f357/docs/images/server_5k.png -------------------------------------------------------------------------------- /docs/images/server_idle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonybase/imgo/20ebe1450e4d1e65b336aab3936bc4a073d3f357/docs/images/server_idle.png -------------------------------------------------------------------------------- /docs/images/start_server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonybase/imgo/20ebe1450e4d1e65b336aab3936bc4a073d3f357/docs/images/start_server.png -------------------------------------------------------------------------------- /docs/postman/im-go.json.postman_collection: -------------------------------------------------------------------------------- 1 | { 2 | "id": "1524f763-645a-febb-b1f5-bb5a4da194b9", 3 | "name": "IM-GO", 4 | "description": "", 5 | "order": [ 6 | "0fd53eb4-2db3-a62a-ee9e-e99abdd1f612", 7 | "094e7b8b-1a49-635b-9a55-a9db088ff62d", 8 | "f3eda7cc-24c3-4bf4-64f5-742bc840ab75", 9 | "b37409d1-8f2d-d609-9541-22d0fe70fdb3" 10 | ], 11 | "folders": [], 12 | "timestamp": 1429336346111, 13 | "synced": false, 14 | "owner": 0, 15 | "sharedWithTeam": false, 16 | "subscribed": false, 17 | "remoteLink": "", 18 | "public": false, 19 | "write": true, 20 | "requests": [ 21 | { 22 | "id": "094e7b8b-1a49-635b-9a55-a9db088ff62d", 23 | "headers": "", 24 | "url": "{{host}}/register", 25 | "preRequestScript": "", 26 | "pathVariables": {}, 27 | "method": "POST", 28 | "data": [ 29 | { 30 | "key": "account", 31 | "value": "22", 32 | "type": "text", 33 | "enabled": true 34 | }, 35 | { 36 | "key": "password", 37 | "value": "22", 38 | "type": "text", 39 | "enabled": true 40 | }, 41 | { 42 | "key": "nick", 43 | "value": "22", 44 | "type": "text", 45 | "enabled": true 46 | } 47 | ], 48 | "dataMode": "params", 49 | "version": 2, 50 | "tests": "", 51 | "currentHelper": "normal", 52 | "helperAttributes": {}, 53 | "time": 1430557757208, 54 | "name": "register", 55 | "description": "", 56 | "collectionId": "1524f763-645a-febb-b1f5-bb5a4da194b9", 57 | "responses": [], 58 | "synced": false 59 | }, 60 | { 61 | "id": "0fd53eb4-2db3-a62a-ee9e-e99abdd1f612", 62 | "headers": "", 63 | "url": "{{host}}/login", 64 | "pathVariables": {}, 65 | "preRequestScript": "", 66 | "method": "POST", 67 | "collectionId": "1524f763-645a-febb-b1f5-bb5a4da194b9", 68 | "data": [], 69 | "dataMode": "params", 70 | "name": "login", 71 | "description": "", 72 | "descriptionFormat": "html", 73 | "time": 1429336510880, 74 | "version": 2, 75 | "responses": [], 76 | "tests": "", 77 | "currentHelper": "normal", 78 | "helperAttributes": {}, 79 | "collectionOwner": 0, 80 | "synced": false 81 | }, 82 | { 83 | "id": "b37409d1-8f2d-d609-9541-22d0fe70fdb3", 84 | "headers": "", 85 | "url": "{{host}}/user_category", 86 | "preRequestScript": "", 87 | "pathVariables": {}, 88 | "method": "POST", 89 | "data": [ 90 | { 91 | "key": "user_id", 92 | "value": "22", 93 | "type": "text", 94 | "enabled": true 95 | }, 96 | { 97 | "key": "name", 98 | "value": "22", 99 | "type": "text", 100 | "enabled": true 101 | } 102 | ], 103 | "dataMode": "params", 104 | "version": 2, 105 | "tests": "", 106 | "currentHelper": "normal", 107 | "helperAttributes": {}, 108 | "time": 1430558725241, 109 | "name": "user_category", 110 | "description": "", 111 | "collectionId": "1524f763-645a-febb-b1f5-bb5a4da194b9", 112 | "responses": [], 113 | "synced": false 114 | }, 115 | { 116 | "id": "f3eda7cc-24c3-4bf4-64f5-742bc840ab75", 117 | "headers": "", 118 | "url": "{{host}}/user_relation", 119 | "preRequestScript": "", 120 | "pathVariables": {}, 121 | "method": "POST", 122 | "data": [ 123 | { 124 | "key": "user_id", 125 | "value": "22", 126 | "type": "text", 127 | "enabled": true 128 | }, 129 | { 130 | "key": "category_id", 131 | "value": "22", 132 | "type": "text", 133 | "enabled": true 134 | } 135 | ], 136 | "dataMode": "params", 137 | "version": 2, 138 | "tests": "", 139 | "currentHelper": "normal", 140 | "helperAttributes": {}, 141 | "time": 1430558880756, 142 | "name": "user_relation", 143 | "description": "", 144 | "collectionId": "1524f763-645a-febb-b1f5-bb5a4da194b9", 145 | "responses": [], 146 | "synced": false 147 | } 148 | ] 149 | } -------------------------------------------------------------------------------- /docs/postman/im-go.postman_environment: -------------------------------------------------------------------------------- 1 | { 2 | "id": "92d99828-7996-6c50-8bd3-e476952b1954", 3 | "name": "im-go", 4 | "values": [ 5 | { 6 | "key": "host", 7 | "value": "localhost:8080", 8 | "type": "text", 9 | "name": "host", 10 | "enabled": true 11 | } 12 | ], 13 | "timestamp": 1429344692017, 14 | "synced": false, 15 | "syncedFilename": "" 16 | } -------------------------------------------------------------------------------- /docs/test: -------------------------------------------------------------------------------- 1 | -- ---------------------------- 2 | -- Records of im_user 3 | -- ---------------------------- 4 | INSERT INTO `im_user` VALUES ('1', '11', '11', '11', '11', '11', '0', '2006-01-02 15:04:05', '2006-01-02 15:04:05', null); 5 | INSERT INTO `im_user` VALUES ('2', '22', '22', '22', '22', '22', '0', '2006-01-02 15:04:05', '2006-01-02 15:04:05', null); 6 | 7 | -- ---------------------------- 8 | -- Records of im_relation_user_category 9 | -- ---------------------------- 10 | INSERT INTO `im_relation_user_group` VALUES ('2', '1'); 11 | INSERT INTO `im_relation_user_group` VALUES ('1', '2'); 12 | 13 | {"command":"GET_CONN","data":{"user":{"id":"1","token":"11","key":"b4ac46bc-544e-4970-a75a-d007e9aff9b1"}}} 14 | 15 | {"command":"GET_CONN","data":{"user":{"id":"2","token":"22","key":"0fee4bca-a303-4e53-86cd-c4d9c3aa9232"}}} 16 | 17 | {"command":"CREATE_SESSION","data":{"session":{"sender":"1","receiver":"2","token":"11"}}} 18 | 19 | {"command":"SEND_MSG","data":{"message":{"content":"xxx","ticket":"a865b423-a148-4464-b427-35f22f3b6811","token":"11"}}} 20 | 21 | select c1.`id`,c2.`creater` from im_conn c1 left join im_session c2 on c1.user_id=c2.receiver where c2.id='a865b423-a148-4464-b427-35f22f3b6811' -------------------------------------------------------------------------------- /docs/test_data.sql: -------------------------------------------------------------------------------- 1 | /* 2 | -- Query: SELECT * FROM im.im_user 3 | LIMIT 0, 1000 4 | 5 | -- Date: 2015-05-04 22:17 6 | */ 7 | INSERT INTO `im_user` (`id`,`account`,`password`,`nick`,`sign`,`avatar`,`status`,`create_at`,`update_at`,`remark`) VALUES ('c70a9fb3-5eaf-432a-81df-010d1318335d','22','22','22','','','0','2015-05-03 15:17:25','2015-05-03 15:17:25',NULL); 8 | INSERT INTO `im_user` (`id`,`account`,`password`,`nick`,`sign`,`avatar`,`status`,`create_at`,`update_at`,`remark`) VALUES ('b26f11c4-a49e-47a3-8d88-f827e708a7a8','11','11','11','','','0','2015-05-03 15:17:30','2015-05-03 15:17:30',NULL); 9 | 10 | /* 11 | -- Query: SELECT * FROM im.im_category 12 | LIMIT 0, 1000 13 | 14 | -- Date: 2015-05-04 22:18 15 | */ 16 | INSERT INTO `im_category` (`id`,`name`,`creator`,`create_at`) VALUES ('59d7ec9a-752f-41e5-a63f-e025efc9a4e5','我的好友','c70a9fb3-5eaf-432a-81df-010d1318335d','2015-05-03 15:17:25'); 17 | INSERT INTO `im_category` (`id`,`name`,`creator`,`create_at`) VALUES ('1330d92b-f20e-4171-a2ca-26dde50199f5','我的好友','b26f11c4-a49e-47a3-8d88-f827e708a7a8','2015-05-03 15:17:30'); 18 | 19 | /* 20 | -- Query: SELECT * FROM im.im_relation_user_category 21 | LIMIT 0, 1000 22 | 23 | -- Date: 2015-05-04 22:18 24 | */ 25 | INSERT INTO `im_relation_user_category` (`user_id`,`category_id`,`create_at`) VALUES ('b26f11c4-a49e-47a3-8d88-f827e708a7a8','59d7ec9a-752f-41e5-a63f-e025efc9a4e5','2015-05-03 15:17:58'); 26 | INSERT INTO `im_relation_user_category` (`user_id`,`category_id`,`create_at`) VALUES ('c70a9fb3-5eaf-432a-81df-010d1318335d','1330d92b-f20e-4171-a2ca-26dde50199f5','2015-05-03 15:18:14'); 27 | -------------------------------------------------------------------------------- /im/common/client.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "bufio" 5 | "log" 6 | "net" 7 | "imgo/im/model" 8 | ) 9 | 10 | /* 11 | 客户端结构体 12 | */ 13 | type Client struct { 14 | // 连接信息 15 | Key string //客户端连接的唯标志 16 | Conn net.Conn //连接 17 | In InMessage //输入消息 18 | Out OutMessage //输出消息 19 | Quit chan *Client //退出 20 | reader *bufio.Reader //读取 21 | writer *bufio.Writer //输出 22 | // 登录信息 23 | Login *model.Login //客户端用户ID 24 | } 25 | 26 | /* 27 | 客户端列表 28 | */ 29 | type ClientTable map[string]*Client 30 | 31 | /* 32 | 获取输入消息 33 | */ 34 | func (this *Client) GetIn() IMRequest { 35 | return <-this.In 36 | } 37 | 38 | /* 39 | 设置输出消息 40 | */ 41 | func (this *Client) PutOut(resp *IMResponse) { 42 | this.Out <- *resp 43 | } 44 | 45 | /* 46 | 创建客户端 47 | */ 48 | func CreateClient(key string, conn net.Conn) *Client { 49 | reader := bufio.NewReader(conn) 50 | writer := bufio.NewWriter(conn) 51 | client := &Client{ 52 | Key: key, 53 | Conn: conn, 54 | In: make(InMessage), 55 | Out: make(OutMessage), 56 | Quit: make(chan *Client), 57 | reader: reader, 58 | writer: writer, 59 | } 60 | client.Listen() 61 | return client 62 | } 63 | 64 | /* 65 | 自动读入或者写出消息 66 | */ 67 | func (this *Client) Listen() { 68 | go this.read() 69 | go this.write() 70 | } 71 | 72 | /* 73 | 退出了一个连接 74 | */ 75 | func (this *Client) Quiting() { 76 | this.Quit <- this 77 | } 78 | 79 | /* 80 | 关闭连接通道 81 | */ 82 | func (this *Client) Close() { 83 | this.Conn.Close() 84 | } 85 | 86 | /* 87 | 读取消息 88 | */ 89 | func (this *Client) read() { 90 | for { 91 | if line, _, err := this.reader.ReadLine(); err == nil { 92 | req, err := DecodeIMRequest(line) 93 | if err == nil { 94 | req.Client = this 95 | this.In <- *req 96 | } else { 97 | // 忽略消息,连命令都不知道,没办法处理 98 | log.Printf("解析JSON错误: %s", line) 99 | } 100 | } else { 101 | // log.Printf("Read error: %s\n", err) 102 | this.Quiting() 103 | return 104 | } 105 | } 106 | } 107 | 108 | /* 109 | 输出消息 110 | */ 111 | func (this *Client) write() { 112 | for resp := range this.Out { 113 | if _, err := this.writer.WriteString(string(resp.Encode()) + "\n"); err != nil { 114 | this.Quiting() 115 | return 116 | } 117 | if err := this.writer.Flush(); err != nil { 118 | log.Printf("Write error: %s\n", err) 119 | this.Quiting() 120 | return 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /im/common/constants.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | /* 4 | 全局常量 5 | */ 6 | const ( 7 | GET_KEY_RETURN = "GET_KEY_RETURN" // 请求TCP获取连接KEY 8 | GET_CONN = "GET_CONN" // 建立TCP长连接 9 | GET_CONN_RETURN = "GET_CONN_RETURN" // 获取连接返回 10 | GET_BUDDY_LIST = "GET_BUDDY_LIST" // 获取好友列表 11 | GET_BUDDY_LIST_RETURN = "GET_BUDDY_LIST_RETURN" // 获取好友列表返回 12 | CREATE_SESSION = "CREATE_SESSION" // 创建会话 13 | CREATE_SESSION_RETURN = "CREATE_SESSION_RETURN" // 创建会话返回 14 | SEND_MSG = "SEND_MSG" // 发送消息 15 | SEND_MSG_RETURN = "SEND_MSG_RETURN" // 发送消息返回 16 | PUSH_MSG = "PUSH_MSG" // 接收消息 17 | SEND_STATUS_CHANGE = "SEND_STATUS_CHANGE" // 发送状态 18 | PUSH_STATUS_CHANGE = "PUSH_STATUS_CHANGE" // 接收状态 19 | LOGOUT_REQUEST = "LOGOUT_REQUEST" // 退出 20 | UNAUTHORIZED = "UNAUTHORIZED" // 未授权 21 | SEND_BUDDY_REQUEST = "SEND_BUDDY_REQUEST" // 发送好友请求 22 | PUSH_BUDDY_REQUEST = "PUSH_BUDDY_REQUEST" // 接收好友请求 23 | ADD_BUDDY = "ADD_BUDDY" 24 | FORMAT_DATE = "2006-01-02" 25 | FORMAT_DATETIME = "2006-01-02 15:04:05" 26 | FORMAT_DATETIME_ZONE = "2006-01-02T15:04:05+08:00" 27 | ) 28 | -------------------------------------------------------------------------------- /im/common/errors.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "errors" 4 | 5 | var ( 6 | InvalidMessageError = errors.New("Invalid message") 7 | ) 8 | 9 | // Server error 10 | type ServerError struct { 11 | err string 12 | } 13 | 14 | // Error string 15 | func (this *ServerError) Error() string { 16 | return this.err 17 | } 18 | 19 | // Protocol error 20 | type ProtocolError struct { 21 | err string 22 | } 23 | 24 | // Error string 25 | func (this *ProtocolError) Error() string { 26 | return this.err 27 | } 28 | 29 | // Configuration error 30 | type ConfigurationError struct { 31 | err string 32 | } 33 | 34 | // Error string 35 | func (this *ConfigurationError) Error() string { 36 | return this.err 37 | } 38 | -------------------------------------------------------------------------------- /im/common/request.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | /* 8 | 输入对象 9 | */ 10 | type IMRequest struct { 11 | Client *Client `json:"-"` //客户端 12 | Command string `json:"command"` //命令 13 | Data map[string]map[string]string `json:"data"` //数据 14 | } 15 | 16 | /* 17 | 输入消息通道 18 | */ 19 | type InMessage chan IMRequest 20 | 21 | /* 22 | 转成JSON数据 23 | */ 24 | func (this *IMRequest) Encode() []byte { 25 | s, _ := json.Marshal(*this) 26 | return s 27 | } 28 | 29 | /* 30 | 解析JSON数据 31 | */ 32 | func (this *IMRequest) Decode(data []byte) error { 33 | err := json.Unmarshal(data, this) 34 | return err 35 | } 36 | 37 | /* 38 | 解析JSON数据 39 | */ 40 | func DecodeIMRequest(data []byte) (*IMRequest, error) { 41 | req := new(IMRequest) 42 | err := req.Decode(data) 43 | if err != nil { 44 | return nil, err 45 | } 46 | return req, nil 47 | } 48 | -------------------------------------------------------------------------------- /im/common/response.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | /* 8 | 返回消息结构体 9 | */ 10 | type IMResponse struct { 11 | Status int `json:"status"` //状态 0成功,非0错误 12 | Msg string `json:"msg"` //消息 13 | Data interface{} `json:"data"` //数据 14 | Refer string `json:"refer"` //来源 15 | } 16 | 17 | /* 18 | 输出消息通道 19 | */ 20 | type OutMessage chan IMResponse 21 | 22 | /* 23 | 错误消息构造方法 24 | */ 25 | func NewIMResponseSimple(status int, msg string, refer string) *IMResponse { 26 | return &IMResponse{status, msg, nil, refer} 27 | } 28 | 29 | /* 30 | 成功消息构造方法 31 | */ 32 | func NewIMResponseData(data interface{}, refer string) *IMResponse { 33 | return &IMResponse{0, "", data, refer} 34 | } 35 | 36 | /* 37 | 将返回消息转成JSON 38 | */ 39 | func (this *IMResponse) Encode() []byte { 40 | s, _ := json.Marshal(*this) 41 | return s 42 | } 43 | 44 | /* 45 | 将JSON转成返回消息 46 | */ 47 | func (this *IMResponse) Decode(data []byte) error { 48 | err := json.Unmarshal(data, this) 49 | return err 50 | } 51 | 52 | -------------------------------------------------------------------------------- /im/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "http_port": 8080, 3 | "im_port": 9090, 4 | "max_clients": 10, 5 | "db_config": { 6 | "host": "127.0.0.1:3306", 7 | "username": "root", 8 | "password": "root", 9 | "name": "im", 10 | "max_idle_conns": 10, 11 | "max_open_conns": 50 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /im/model/buddyrequest.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "code.google.com/p/go-uuid/uuid" 5 | "database/sql" 6 | "log" 7 | "time" 8 | ) 9 | 10 | type BuddyRequest struct { 11 | Id string `json:"id"` // ID 12 | Sender string `json:"sender"` // 请求者 13 | SenderCategoryId string `json:"senderCategoryId"` // 请求者分组ID 14 | Receiver string `json:"receiver"` // 接收者 15 | ReceiverCategoryId string `json:"receiverCategoryId"` 16 | SendAt time.Time `json:"sendAt"` // 请求时间 17 | Status string `json:"status"` // 状态 18 | } 19 | 20 | /* 21 | 添加好友请求数据(好友未在线所以存好友请求表) 22 | */ 23 | func AddBuddyRequest(sender string, sender_cate_id string, receiver string) (*string, error) { 24 | insStmt, err := Database.Prepare("insert into im_buddy_request (id,sender,sender_category_id,receiver,send_at,status) VALUES (?,?,?,?,?,?)") 25 | if err != nil { 26 | log.Println(err) 27 | return nil, &DatabaseError{"保存好友请求错误"} 28 | } 29 | defer insStmt.Close() 30 | id := uuid.New() 31 | _, err = insStmt.Exec(id, sender, sender_cate_id, receiver, time.Now().Format("2006-01-02 15:04:05"), "0") 32 | if err != nil { 33 | log.Println(err) 34 | return nil, &DatabaseError{"保存好友请求错误"} 35 | } 36 | return &id, nil 37 | } 38 | 39 | /* 40 | 根据receiver获取未读的好友请求 41 | */ 42 | func GetBuddyRequestsByReceiver(receiver string) ([]BuddyRequest, error) { 43 | var buddyRequests []BuddyRequest 44 | rows, err := Database.Query("select * from im_buddy_request where status='0' and receiver=?", receiver) 45 | if err != nil { 46 | return nil, &DatabaseError{"根据receiver获取未读的好友请求错误"} 47 | } 48 | defer rows.Close() 49 | for rows.Next() { 50 | var buddyRequest BuddyRequest 51 | rows.Scan(&buddyRequest.Id, &buddyRequest.Sender, &buddyRequest.SenderCategoryId, &buddyRequest.Receiver, &buddyRequest.ReceiverCategoryId, &buddyRequest.SendAt, &buddyRequest.Status) 52 | buddyRequests = append(buddyRequests, buddyRequest) 53 | } 54 | return buddyRequests, nil 55 | } 56 | 57 | /* 58 | 根据ID获取未读的好友请求 59 | */ 60 | func GetBuddyRequestById(id string) (*BuddyRequest, error) { 61 | var buddyRequest BuddyRequest 62 | row := Database.QueryRow("select id,sender,sender_category_id,receiver from im_buddy_request where status='0' and id=?", id) 63 | err := row.Scan(&buddyRequest.Id, &buddyRequest.Sender, &buddyRequest.SenderCategoryId, &buddyRequest.Receiver) 64 | if err != nil { 65 | return nil, &DatabaseError{"根据ID查询好友请求-将结果映射至对象错误"} 66 | } 67 | return &buddyRequest, nil 68 | } 69 | 70 | /* 71 | 根据ID修改好友请求状态 72 | */ 73 | func UpdateBuddyRequestStatus(tx *sql.Tx, id string, status string) (int64, error) { 74 | var num int64 75 | updateStmt, err := tx.Prepare("update im_buddy_request SET `status` = ? WHERE id =?") 76 | if err != nil { 77 | return -1, &DatabaseError{"修改好友请求数据库处理错误"} 78 | } 79 | defer updateStmt.Close() 80 | res, err := updateStmt.Exec(status, id) 81 | if err != nil { 82 | return -1, &DatabaseError{"更新好友请求错误"} 83 | } 84 | num, err = res.RowsAffected() 85 | if err != nil { 86 | return -1, &DatabaseError{"读取修改好友请求影响行数错误"} 87 | } 88 | return num, nil 89 | } 90 | func UpdateBuddyRequestReceiverCategoryId(tx *sql.Tx, id string, receiver_category_id string) (int64, error) { 91 | var num int64 92 | updateStmt, err := tx.Prepare("update im_buddy_request SET `receiver_category_id` = ? WHERE id =?") 93 | if err != nil { 94 | return -1, &DatabaseError{"修改好友请求数据库处理错误"} 95 | } 96 | defer updateStmt.Close() 97 | res, err := updateStmt.Exec(receiver_category_id, id) 98 | if err != nil { 99 | return -1, &DatabaseError{"更新好友请求错误"} 100 | } 101 | num, err = res.RowsAffected() 102 | if err != nil { 103 | return -1, &DatabaseError{"读取修改好友请求影响行数错误"} 104 | } 105 | return num, nil 106 | } 107 | -------------------------------------------------------------------------------- /im/model/category.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "code.google.com/p/go-uuid/uuid" 5 | "encoding/json" 6 | "time" 7 | ) 8 | 9 | /* 10 | 分组对象 11 | */ 12 | type Category struct { 13 | Id string `json:"id"` // 分组ID 14 | Name string `json:"name"` // 分组名称 15 | Creator string `json:"creator"` // 分组名称 16 | CreateAt time.Time `json:"create_at"` // 创建时间 17 | Buddies []User `json:"buddies"` // 好友列表 18 | } 19 | 20 | /* 21 | 转JSON数据 22 | */ 23 | func (this *Category) Encode() []byte { 24 | s, _ := json.Marshal(*this) 25 | return s 26 | } 27 | 28 | /* 29 | 解析JSON数据 30 | */ 31 | func (this *Category) Decode(data []byte) error { 32 | err := json.Unmarshal(data, this) 33 | return err 34 | } 35 | 36 | /* 37 | 分组PUSH用户(仅传递数据 非操作数据库方法) 38 | */ 39 | func (this *Category) AddUser(u User) { 40 | this.Buddies = append(this.Buddies, u) 41 | } 42 | 43 | /* 44 | 根据token获取分组数据 45 | */ 46 | func GetCategoriesByToken(token string) ([]Category, error) { 47 | var categories []Category 48 | rows, err := Database.Query("select g.id, g.name from im_category g left join im_login l on l.user_id=g.creator where token=?", token) 49 | if err != nil { 50 | return nil, &DatabaseError{"根据Token获取好友分类错误"} 51 | } 52 | defer rows.Close() 53 | for rows.Next() { 54 | var category Category 55 | rows.Scan(&category.Id, &category.Name) 56 | categories = append(categories, category) 57 | } 58 | return categories, nil 59 | } 60 | 61 | /* 62 | 根据ID删除好友分类 63 | */ 64 | func DelCategoryById(categoryId string) (int64, error) { 65 | delStmt, err := Database.Prepare("delete from im_category where id=? ") 66 | if err != nil { 67 | return -1, &DatabaseError{"删除好友分类数据库处理错误"} 68 | } 69 | defer delStmt.Close() 70 | res, err := delStmt.Exec(categoryId) 71 | if err != nil { 72 | return -1, &DatabaseError{"删除好友分类记录错误"} 73 | } 74 | num, err := res.RowsAffected() 75 | if err != nil { 76 | return -1, &DatabaseError{"读取删除好友分类记录影响行数错误"} 77 | } 78 | return num, nil 79 | } 80 | 81 | /* 82 | 根据ID修改好友分类名称 83 | */ 84 | func EditCategoryById(categoryId string, categoryName string) (int64, error) { 85 | var num int64 86 | updateStmt, err := Database.Prepare("UPDATE im_category SET `name` = ? WHERE id =?") 87 | if err != nil { 88 | return -1, &DatabaseError{"修改好友分类数据库处理错误"} 89 | } 90 | defer updateStmt.Close() 91 | res, err := updateStmt.Exec(categoryName, categoryId) 92 | if err != nil { 93 | return -1, &DatabaseError{"更新好友分类错误"} 94 | } 95 | num, err = res.RowsAffected() 96 | if err != nil { 97 | return -1, &DatabaseError{"读取修改好友分类影响行数错误"} 98 | } 99 | return num, nil 100 | } 101 | 102 | /* 103 | 根据UserId获取分组数据 104 | */ 105 | func GetCategoriesByUserId(id string) ([]Category, error) { 106 | var categories []Category 107 | rows, err := Database.Query("select * from im_category where creator=?", id) 108 | if err != nil { 109 | return nil, &DatabaseError{"根据用户ID获取好友分类错误"} 110 | } 111 | defer rows.Close() 112 | for rows.Next() { 113 | var category Category 114 | rows.Scan(&category.Id, &category.Name, &category.Creator, &category.CreateAt) 115 | categories = append(categories, category) 116 | } 117 | return categories, nil 118 | } 119 | 120 | /* 121 | 添加好友分类 122 | */ 123 | func AddCategory(userId string, name string) (*string, error) { 124 | insStmt, err := Database.Prepare("insert into im_category (id, name, creator, create_at) VALUES (?, ?, ?, ?)") 125 | if err != nil { 126 | return nil, &DatabaseError{"保存好友分类记录错误"} 127 | } 128 | defer insStmt.Close() 129 | id := uuid.New() 130 | _, err = insStmt.Exec(id, name, userId, time.Now().Format("2006-01-02 15:04:05")) 131 | if err != nil { 132 | return nil, &DatabaseError{"保存好友分类记录错误"} 133 | } 134 | return &id, nil 135 | } 136 | -------------------------------------------------------------------------------- /im/model/conn.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "database/sql" 5 | "time" 6 | ) 7 | 8 | /* 9 | 连接对象 10 | */ 11 | type Conn struct { 12 | Key string `json:"key"` // 连接key 唯一标识符 13 | UserId string `json:"id"` // 用户ID 14 | Token string `json:"token"` // token 15 | CreateAt time.Time `json:"create_at"` // 时间 16 | UpdateAt time.Time `json:"update_at"` // 时间 17 | } 18 | 19 | /* 20 | 根据Key获取连接的数量 21 | */ 22 | func CountConnByKey(key string) (int64, error) { 23 | var num int64 24 | err := Database.QueryRow("select count(*) from im_conn where id=?", key).Scan(&num) 25 | if err != nil { 26 | return -1, &DatabaseError{"根据Token获取连接数量错误"} 27 | } 28 | return num, nil 29 | } 30 | 31 | /* 32 | 根据Token获取连接的数量 33 | */ 34 | func CountConnByToken(token string) (int64, error) { 35 | var num int64 36 | err := Database.QueryRow("select count(*) from im_conn where token=?", token).Scan(&num) 37 | if err != nil { 38 | return -1, &DatabaseError{"根据Token获取连接数量错误"} 39 | } 40 | return num, nil 41 | } 42 | 43 | /* 44 | 根据Token获取连接的数量 45 | */ 46 | func CountConnByUserId(userId string) (int64, error) { 47 | var num int64 48 | err := Database.QueryRow("select count(*) from im_conn where user_id=?", userId).Scan(&num) 49 | if err != nil { 50 | return -1, &DatabaseError{"根据Token获取连接数量错误"} 51 | } 52 | return num, nil 53 | } 54 | 55 | /* 56 | 根据用户ID修改连接 57 | */ 58 | func UpdateConnByToken(key string, userId string, token string) (int64, error) { 59 | updateStmt, err := Database.Prepare("UPDATE im_conn SET `id`=?, `user_id` = ?, update_at=? WHERE token =?") 60 | if err != nil { 61 | return -1, &DatabaseError{"读取修改用户连接影响行数错误"} 62 | } 63 | defer updateStmt.Close() 64 | res, err := updateStmt.Exec(key, userId, time.Now().Format("2006-01-02 15:04:05"), token) 65 | if err != nil { 66 | return -1, &DatabaseError{"更新用户连接错误"} 67 | } 68 | num, err := res.RowsAffected() 69 | if err != nil { 70 | return -1, &DatabaseError{"读取更新用户连接影响行数错误"} 71 | } 72 | return num, nil 73 | } 74 | 75 | /* 76 | 根据token获取token 77 | */ 78 | func GetConnByToken(token string) (*Conn, error) { 79 | var conn Conn 80 | row := Database.QueryRow("select * from im_conn where token=?", token) 81 | err := row.Scan(&conn.Key, &conn.UserId, &conn.Token, &conn.CreateAt, &conn.UpdateAt) 82 | if err != nil { 83 | // log.Println("根据Token获取用户连接记录错误", err) 84 | return nil, &DatabaseError{"根据Token获取用户连接记录错误"} 85 | } 86 | return &conn, nil 87 | } 88 | 89 | /* 90 | 根据用户ID获取连接 91 | */ 92 | func GetConnByUserId(userId string) (*Conn, error) { 93 | var conn Conn 94 | row := Database.QueryRow("select * from im_conn where user_id=?", userId) 95 | err := row.Scan(&conn.Key, &conn.UserId, &conn.Token, &conn.CreateAt, &conn.UpdateAt) 96 | if err != nil { 97 | return nil, &DatabaseError{"根据用户ID获取用户连接记录错误"} 98 | } 99 | return &conn, nil 100 | } 101 | 102 | /* 103 | 根据token删除连接 104 | */ 105 | func DeleteConnByKey(key string) error { 106 | //删除连接该token的连接 107 | delStmt, err := Database.Prepare("delete from im_conn where id=?") 108 | if err != nil { 109 | return &DatabaseError{"删除用户连接错误"} 110 | } 111 | defer delStmt.Close() 112 | _, err = delStmt.Exec(key) 113 | if err != nil { 114 | return &DatabaseError{"删除用户连接错误"} 115 | } 116 | return nil 117 | } 118 | 119 | /* 120 | 根据token删除连接 121 | */ 122 | func DeleteConnByToken(tx *sql.Tx, token string) error { 123 | //删除连接该token的连接 124 | delStmt, err := tx.Prepare("delete from im_conn where token=?") 125 | if err != nil { 126 | return &DatabaseError{"删除用户连接错误"} 127 | } 128 | defer delStmt.Close() 129 | _, err = delStmt.Exec(token) 130 | if err != nil { 131 | tx.Rollback() 132 | if err != nil { 133 | return &DatabaseError{"删除用户连接错误"} 134 | } 135 | } 136 | return nil 137 | } 138 | 139 | /* 140 | 添加连接 141 | */ 142 | func AddConn(key string, userId string, token string) (*string, error) { 143 | insertStmt, err := Database.Prepare("insert into im_conn VALUES (?, ?, ?, ?, ?)") 144 | if err != nil { 145 | return nil, &DatabaseError{"保存用户连接错误"} 146 | } 147 | defer insertStmt.Close() 148 | now := time.Now().Format("2006-01-02 15:04:05") 149 | _, err = insertStmt.Exec(key, userId, token, now, now) 150 | if err != nil { 151 | return nil, &DatabaseError{"保存用户连接错误"} 152 | } 153 | return &key, nil 154 | } 155 | -------------------------------------------------------------------------------- /im/model/context.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "database/sql" 5 | "imgo/im/util" 6 | ) 7 | 8 | /* 9 | 包内上下文变量 10 | */ 11 | var ( 12 | Database *sql.DB = nil //数据库操作对象 13 | Config *util.IMConfig //配置对象 14 | ) 15 | -------------------------------------------------------------------------------- /im/model/errors.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "errors" 4 | 5 | var ( 6 | InvalidMessageError = errors.New("Invalid message") 7 | ) 8 | 9 | // Database error 10 | type DatabaseError struct { 11 | err string 12 | } 13 | 14 | // Error string 15 | func (this *DatabaseError) Error() string { 16 | return this.err 17 | } -------------------------------------------------------------------------------- /im/model/login.go: -------------------------------------------------------------------------------- 1 | package model 2 | import ( 3 | "code.google.com/p/go-uuid/uuid" 4 | "time" 5 | ) 6 | 7 | type Login struct { 8 | Id string `json:"id"` // id 9 | UserId string `json:"user_id"` // 用户ID 10 | Token string `json:"token"` // 用户TOKEN 11 | LoginAt time.Time `json:"login_at"` // 登录日期 12 | LoginIp string `json:"login_ip"` // 登录IP 13 | } 14 | 15 | /* 16 | 根据token获取用户登录 17 | */ 18 | func GetLoginByToken(token string) (*Login, error) { 19 | var login Login 20 | row := Database.QueryRow("select id, user_id, token, login_at, login_ip from im_login where token=?", token) 21 | err := row.Scan(&login.Id, &login.UserId, &login.Token, &login.LoginAt, &login.LoginIp) 22 | if err != nil { 23 | return nil, &DatabaseError{"根据Token获取用户登录错误"} 24 | } 25 | return &login, nil 26 | } 27 | 28 | /* 29 | 保存登录状态 30 | */ 31 | func SaveLogin(userId string, token string, ip string) (*string, error) { 32 | insStmt, errStmt := Database.Prepare("insert into im_login (id, user_id, token, login_at, login_ip) VALUES (?, ?, ?, ?,?)") 33 | if errStmt != nil { 34 | return nil, &DatabaseError{"保存用户登录记录错误,数据库语句错误"} 35 | } 36 | defer insStmt.Close() 37 | id := uuid.New(); 38 | _, err := insStmt.Exec(id, userId, token, time.Now().Format("2006-01-02 15:04:05"), ip) 39 | if err != nil { 40 | return nil, &DatabaseError{"保存用户登录记录错误"} 41 | } 42 | return &id, nil 43 | } -------------------------------------------------------------------------------- /im/model/message.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | ) 7 | 8 | /* 9 | 消息对象 10 | */ 11 | type Message struct { 12 | Id string //id 13 | Sender string //发送人 14 | To string //接收人 15 | Ticket string //ticket 16 | Token string //发送人登录token 17 | Content string //内容 18 | Create_at time.Time //时间 19 | } 20 | 21 | /* 22 | 转JSON数据 23 | */ 24 | func (this *Message) Encode() []byte { 25 | s, _ := json.Marshal(*this) 26 | return s 27 | } 28 | 29 | /* 30 | 解析JSON数据 31 | */ 32 | func (this *Message) Decode(data []byte) error { 33 | err := json.Unmarshal(data, this) 34 | return err 35 | } 36 | -------------------------------------------------------------------------------- /im/model/session.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "code.google.com/p/go-uuid/uuid" 5 | "log" 6 | "time" 7 | ) 8 | 9 | type Conversation struct { 10 | Id string `json:"id"` 11 | Creator string `json:"creator"` 12 | Receiver string `json:"receiver"` 13 | Type string `json:"type"` 14 | Create_at time.Time `json:"create_at"` 15 | } 16 | 17 | /* 18 | 创建会话 19 | */ 20 | func AddSession(sender string, receiver string) string { 21 | insertStmt, _ := Database.Prepare("insert into `im_session` VALUES (?, ?, ?, ?, ?)") 22 | defer insertStmt.Close() 23 | id := uuid.New() 24 | res, err := insertStmt.Exec(id, sender, receiver, "0", time.Now().Format("2006-01-02 15:04:05")) 25 | if err != nil { 26 | log.Println("创建会话错误:", err) 27 | } 28 | num, err := res.RowsAffected() 29 | if err != nil { 30 | num = 0 31 | log.Println("读取保存用户连接影响行数错误:", err) 32 | } 33 | if num == 0 { 34 | return "" 35 | } 36 | return id 37 | } 38 | 39 | func GetSession(sender string, receiver string) Conversation { 40 | var conv Conversation 41 | rows, err := Database.Query("select * from im_session where creator=? and receiver=? ", sender, receiver) 42 | if err != nil { 43 | log.Printf("根据账号及密码查询用户错误: ", err) 44 | } 45 | r, _ := rows.Columns(); 46 | log.Println(r) 47 | 48 | defer rows.Close() 49 | for rows.Next() { 50 | err := rows.Scan(&conv.Id, &conv.Creator, &conv.Receiver, &conv.Type, &conv.Create_at) 51 | if err != nil { 52 | log.Printf("根据账号及密码查询结果映射至对象错误:", err) 53 | } 54 | } 55 | return conv 56 | } 57 | 58 | /* 59 | 根据ID获取会话 60 | */ 61 | func GetSessionById(id string) (*Conversation, error) { 62 | var conv Conversation 63 | row := Database.QueryRow("select * from im_session where id=?", id) 64 | err := row.Scan(&conv.Id, &conv.Creator, &conv.Receiver, &conv.Type, &conv.Create_at) 65 | if err != nil { 66 | return nil, &DatabaseError{"根据ID获会话错误"} 67 | } 68 | return &conv, nil 69 | 70 | } 71 | 72 | /* 73 | 根据ticket获取会话 74 | */ 75 | func GetReceiverKeyByTicket(ticket string) ([]string, error) { 76 | var keys []string 77 | rows, err := Database.Query("select c1.`id` from im_conn c1 left join im_session c2 on c1.user_id=c2.receiver where c2.id=?", ticket) 78 | if err != nil { 79 | return nil, &DatabaseError{"根据Ticket获取接收者Key和发送者ID错误"} 80 | } 81 | for rows.Next() { 82 | var key string 83 | rows.Scan(&key) 84 | 85 | keys = append(keys, key) 86 | } 87 | return keys, nil 88 | } 89 | -------------------------------------------------------------------------------- /im/model/user.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "code.google.com/p/go-uuid/uuid" 5 | "database/sql" 6 | "encoding/json" 7 | "fmt" 8 | "time" 9 | ) 10 | 11 | /* 12 | 用户对象 13 | */ 14 | type User struct { 15 | Id string `json:"id"` //id 16 | Nick string `json:"nick"` //昵称 17 | Status string `json:"status"` //状态 0离线,1在线 18 | Sign string `json:"sign"` //个性签名 19 | Avatar string `json:"avatar"` //头像 20 | CreateAt time.Time `json:"create_at"` //注册日期 21 | UpdateAt time.Time `json:"update_at"` //更新日期 22 | Token string `json:"token"` 23 | } 24 | 25 | /* 26 | 转JSON数据 27 | */ 28 | func (this *User) Encode() []byte { 29 | s, _ := json.Marshal(*this) 30 | return s 31 | } 32 | 33 | /* 34 | 解析JSON 35 | */ 36 | func (this *User) Decode(data []byte) error { 37 | err := json.Unmarshal(data, this) 38 | return err 39 | } 40 | 41 | /* 42 | 检查账号是否存在 43 | */ 44 | func CheckAccount(account string) (int, error) { 45 | var num int 46 | rows, err := Database.Query("select count(*) from im_user where account=? ", account) 47 | if err != nil { 48 | return -1, &DatabaseError{"根据账号查询用户错误"} 49 | } 50 | defer rows.Close() 51 | for rows.Next() { 52 | rows.Scan(&num) 53 | } 54 | return num, nil 55 | 56 | } 57 | 58 | /* 59 | 根据ID获取用户 60 | */ 61 | func GetUserById(id string) (*User, error) { 62 | var user User 63 | row := Database.QueryRow("select id, nick, status, sign, avatar, create_at, update_at from im_user where id=?", id) 64 | err := row.Scan(&user.Id, &user.Nick, &user.Status, &user.Sign, &user.Avatar, &user.CreateAt, &user.UpdateAt) 65 | if err != nil { 66 | return nil, &DatabaseError{"根据ID查询用户-将结果映射至对象错误"} 67 | } 68 | return &user, err 69 | } 70 | 71 | /* 72 | 根据token获取用户 73 | */ 74 | func GetUserByToken(token string) (*User, error) { 75 | var user User 76 | row := Database.QueryRow("select u.id, u.nick, u.status, u.sign, u.avatar, u.create_at, u.update_at from im_user u left join im_login l on u.id=l.user_id where l.token=?", token) 77 | err := row.Scan(&user.Id, &user.Nick, &user.Status, &user.Sign, &user.Avatar, &user.CreateAt, &user.UpdateAt) 78 | if err != nil { 79 | return nil, &DatabaseError{"根据Token查询用户-将结果映射至对象错误"} 80 | } 81 | return &user, nil 82 | } 83 | 84 | /* 85 | 根据分组获取好友列表 86 | */ 87 | func GetBuddiesByCategories(categories []Category) ([]Category, error) { 88 | for k, v := range categories { 89 | rows, err := Database.Query("select u.id, u.nick, u.status, u.sign, u.avatar, u.create_at, u.update_at from im_user u left join im_relation_user_category ug on u.id=ug.user_id where ug.category_id=?", v.Id) 90 | if err != nil { 91 | return categories, &DatabaseError{"根据分类查询好友列表错误"} 92 | } 93 | for rows.Next() { 94 | var user User 95 | rows.Scan(&user.Id, &user.Nick, &user.Status, &user.Sign, &user.Avatar, &user.CreateAt, &user.UpdateAt) 96 | categories[k].AddUser(user) 97 | } 98 | } 99 | return categories, nil 100 | } 101 | 102 | /* 103 | 登录账号 104 | */ 105 | func LoginUser(account string, password string) (*User, error) { 106 | var user User 107 | rows, err := Database.Query("select id, nick, status, sign, avatar, create_at, update_at from im_user where account=? and password=? ", account, password) 108 | if err != nil { 109 | return nil, &DatabaseError{"根据账号及密码查询用户错误"} 110 | } 111 | defer rows.Close() 112 | for rows.Next() { 113 | err := rows.Scan(&user.Id, &user.Nick, &user.Status, &user.Sign, &user.Avatar, &user.CreateAt, &user.UpdateAt) 114 | if err != nil { 115 | return nil, &DatabaseError{"根据账号及密码查询结果映射至对象错误"} 116 | } 117 | } 118 | return &user, nil 119 | } 120 | 121 | /* 122 | 保存用户 123 | */ 124 | func SaveUser(account string, password string, nick string, avatar string) (*string, error) { 125 | insStmt, err := Database.Prepare("insert into im_user (id, account, password, nick, avatar, create_at, update_at) VALUES (?, ?, ?, ?, ?, ?, ?)") 126 | if err != nil { 127 | return nil, &DatabaseError{"保存用户数据库处理错误"} 128 | } 129 | defer insStmt.Close() 130 | now := time.Now().Format("2006-01-02 15:04:05") 131 | uid := uuid.New() 132 | _, err = insStmt.Exec(uid, account, password, nick, avatar, now, now) 133 | if err != nil { 134 | return nil, &DatabaseError{"保存用户记录错误"} 135 | } 136 | // 添加默认分类 137 | AddCategory(uid, "我的好友") 138 | return &uid, nil 139 | } 140 | 141 | /* 142 | 修改用户状态 143 | */ 144 | func UpdateUserStatus(userId string, status string) (int64, error) { 145 | updateStmt, _ := Database.Prepare("UPDATE im_user SET `status` = ? WHERE id =?") 146 | defer updateStmt.Close() 147 | res, err := updateStmt.Exec(status, userId) 148 | if err != nil { 149 | return -1, &DatabaseError{"更新用户状态错误"} 150 | } 151 | num, err := res.RowsAffected() 152 | if err != nil { 153 | return -1, &DatabaseError{"读取修改用户状态影响行数错误"} 154 | } 155 | return num, nil 156 | } 157 | 158 | /* 159 | 修改用户状态(事务) 160 | */ 161 | func UpdateUserStatusTx(tx *sql.Tx, userId string, status string) (int64, error) { 162 | var num int64 163 | updateStmt, err := tx.Prepare("UPDATE im_user SET `status` = ? WHERE id =?") 164 | if err != nil { 165 | return -1, &DatabaseError{"修改用户状态数据库处理错误"} 166 | } 167 | defer updateStmt.Close() 168 | res, err := updateStmt.Exec(status, userId) 169 | if err != nil { 170 | tx.Rollback() 171 | return -1, &DatabaseError{"更新用户状态错误"} 172 | } 173 | num, err = res.RowsAffected() 174 | if err != nil { 175 | tx.Rollback() 176 | return -1, &DatabaseError{"读取修改用户状态影响行数错误"} 177 | } 178 | return num, nil 179 | } 180 | 181 | /* 182 | 根据用户ID获取在线好友的连接KEY列表 183 | */ 184 | func GetBuddiesKeyById(id string) ([]string, error) { 185 | var keys []string 186 | rows, err := Database.Query("select co.`id` from im_conn co where co.user_id in (select ug.user_id from im_relation_user_category ug where ug.category_id in (select g.id from im_category g where g.creator=?))", id) 187 | if err != nil { 188 | return keys, &DatabaseError{"根据用户ID获取在线好友的连接KEY列表错误"} 189 | } 190 | for rows.Next() { 191 | var key string 192 | rows.Scan(&key) 193 | keys = append(keys, key) 194 | } 195 | return keys, nil 196 | } 197 | 198 | /* 199 | 根据条件查询获取好友列表 200 | */ 201 | func QueryUser(clumn string, reg string, data string) ([]User, error) { 202 | var users []User 203 | sql := "select u.id, u.nick, u.status, u.sign, u.avatar, u.create_at, u.update_at from im_user u where %s %s %s " 204 | 205 | if reg == "like" { 206 | data = "'%" + data + "%'" 207 | } 208 | sql = fmt.Sprintf(sql, clumn, reg, data) 209 | rows, err := Database.Query(sql) 210 | if err != nil { 211 | return users, &DatabaseError{"根据" + clumn + "查询用户错误"} 212 | } 213 | for rows.Next() { 214 | var user User 215 | rows.Scan(&user.Id, &user.Nick, &user.Status, &user.Sign, &user.Avatar, &user.CreateAt, &user.UpdateAt) 216 | users = append(users, user) 217 | } 218 | return users, nil 219 | } 220 | -------------------------------------------------------------------------------- /im/model/userrelation.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "database/sql" 5 | "time" 6 | ) 7 | 8 | type UserRelation struct { 9 | UserId string `json:"user_id"` 10 | CategoryId string `json:"category_id"` 11 | CreateAt time.Time `json:"create_at"` 12 | } 13 | 14 | /** 15 | 添加好友关系数据库 16 | */ 17 | func AddFriendRelation(tx *sql.Tx, userId string, categoryId string) (int64, error) { 18 | insStmt, err := tx.Prepare("insert into im_relation_user_category (user_id, category_id, create_at) VALUES (?, ?, ?)") 19 | if err != nil { 20 | return -1, &DatabaseError{"添加好友关系数据库处理错误"} 21 | } 22 | defer insStmt.Close() 23 | res, err := insStmt.Exec(userId, categoryId, time.Now().Format("2006-01-02 15:04:05")) 24 | if err != nil { 25 | return -1, &DatabaseError{"保存好友分类记录错误"} 26 | } 27 | num, err := res.RowsAffected() 28 | if err != nil { 29 | return -1, &DatabaseError{"读取保存好友分类记录影响行数错误"} 30 | } 31 | return num, nil 32 | } 33 | 34 | /** 35 | 删除好友关系数据库 36 | */ 37 | func DelFriendRelation(userId string, categoryId string) (int64, error) { 38 | delStmt, err := Database.Prepare("delete from im_relation_user_category where user_id=? and category_id=? ") 39 | if err != nil { 40 | return -1, &DatabaseError{"删除好友关系数据库处理错误"} 41 | } 42 | defer delStmt.Close() 43 | res, err := delStmt.Exec(userId, categoryId) 44 | if err != nil { 45 | return -1, &DatabaseError{"删除好友关系记录错误"} 46 | } 47 | num, err := res.RowsAffected() 48 | if err != nil { 49 | return -1, &DatabaseError{"读取删除好友关系记录影响行数错误"} 50 | } 51 | return num, nil 52 | } 53 | -------------------------------------------------------------------------------- /im/server/httpserver.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "code.google.com/p/go-uuid/uuid" 5 | "github.com/shirou/gopsutil/mem" 6 | "github.com/shirou/gopsutil/cpu" 7 | "fmt" 8 | "imgo/im/common" 9 | "imgo/im/model" 10 | "imgo/im/util" 11 | "log" 12 | "net/http" 13 | "strings" 14 | "time" 15 | "strconv" 16 | ) 17 | 18 | // 启动HTTP服务 19 | func StartHttpServer(config util.IMConfig) error { 20 | log.Printf("Http服务器启动中...") 21 | 22 | // 设置请求映射地址及对应处理方法 23 | http.HandleFunc("/system", handleSystem) 24 | http.HandleFunc("/register", handleRegister) 25 | http.HandleFunc("/login", handleLogin) 26 | http.HandleFunc("/query", handleQuery) 27 | http.HandleFunc("/users/relation/add", handleUserRelationAdd) 28 | http.HandleFunc("/users/relation/del", handleUserRelationDel) 29 | http.HandleFunc("/users/relation/push", handleUserRelationPush) 30 | http.HandleFunc("/users/relation/refuse", handleUserRelationRefuse) 31 | http.HandleFunc("/users/category/add", handleUserCategoryAdd) 32 | http.HandleFunc("/users/category/del", handleUserCategoryDel) 33 | http.HandleFunc("/users/category/edit", handleUserCategoryEdit) 34 | http.HandleFunc("/users/category/query", handleUserCategoryQuery) 35 | //打印监听端口 36 | log.Printf("Http服务器开始监听[%d]端口", config.HttpPort) 37 | log.Println("*********************************************") 38 | // 设置监听地址及端口 39 | addr := fmt.Sprintf("0.0.0.0:%d", config.HttpPort) 40 | if err := http.ListenAndServe(addr, nil); err != nil { 41 | return fmt.Errorf("监听Http失败: %s", err) 42 | } 43 | return nil 44 | } 45 | 46 | // 系统状态信息 47 | func handleSystem(resp http.ResponseWriter, req *http.Request) { 48 | mem, _ := mem.VirtualMemory() 49 | cpuNum, _ := cpu.CPUCounts(true); 50 | cpuInfo, _ := cpu.CPUPercent(10 * time.Microsecond, true); 51 | 52 | data := make(map[string]interface{}) 53 | data["im.conn"] = len(ClientMaps) 54 | data["mem.total"] = fmt.Sprintf("%vMB", mem.Total/1024/1024) 55 | data["mem.free"] = fmt.Sprintf("%vMB", mem.Free/1024/1024) 56 | data["mem.used_percent"] = fmt.Sprintf("%s%%", strconv.FormatFloat(mem.UsedPercent, 'f', 2, 64)) 57 | data["cpu.num"] = cpuNum 58 | data["cpu.info"] = cpuInfo 59 | 60 | resp.Write(common.NewIMResponseData(data, "").Encode()) 61 | } 62 | 63 | // 注册请求 64 | func handleRegister(resp http.ResponseWriter, req *http.Request) { 65 | if req.Method == "POST" { 66 | account := req.FormValue("account") 67 | password := req.FormValue("password") 68 | nick := req.FormValue("nick") 69 | avatar := req.FormValue("avatar") 70 | register(resp, account, password, nick, avatar) 71 | } else { 72 | resp.Write(common.NewIMResponseSimple(404, "Not Found: "+req.Method, "").Encode()) 73 | } 74 | } 75 | 76 | /** 77 | 登录请求处理方法 78 | */ 79 | func handleLogin(resp http.ResponseWriter, req *http.Request) { 80 | // POST登录请求 81 | if req.Method == "POST" { 82 | ip := util.GetIp(req) 83 | account := req.FormValue("account") 84 | password := req.FormValue("password") 85 | login(resp, account, password, ip) 86 | } else { 87 | resp.Write(common.NewIMResponseSimple(404, "Not Found: "+req.Method, "").Encode()) 88 | } 89 | } 90 | 91 | /** 92 | 查询请求处理方法 93 | */ 94 | func handleQuery(resp http.ResponseWriter, req *http.Request) { 95 | nick := req.FormValue("nick") 96 | users, err := model.QueryUser("nick", "like", nick) 97 | if err == nil { 98 | resp.Write(common.NewIMResponseData(util.SetData("users", users), "").Encode()) 99 | } 100 | } 101 | 102 | // 添加好友分类 103 | func handleUserCategoryAdd(resp http.ResponseWriter, req *http.Request) { 104 | switch req.Method { 105 | case "GET": 106 | //获取好友列表 107 | userId := req.FormValue("user_id") 108 | 109 | categories, err := model.GetCategoriesByUserId(userId) 110 | if err != nil { 111 | resp.Write(common.NewIMResponseSimple(100, err.Error(), "").Encode()) 112 | return 113 | } 114 | categories, err = model.GetBuddiesByCategories(categories) 115 | if err != nil { 116 | resp.Write(common.NewIMResponseSimple(100, err.Error(), "").Encode()) 117 | return 118 | } 119 | resp.Write(common.NewIMResponseData(util.SetData("categories", categories), "").Encode()) 120 | case "POST": 121 | // 添加好友列表 122 | userId := req.FormValue("user_id") 123 | name := req.FormValue("name") 124 | 125 | if userId == "" { 126 | resp.Write(common.NewIMResponseSimple(101, "用户ID不能为空", "").Encode()) 127 | } else if name == "" { 128 | resp.Write(common.NewIMResponseSimple(102, "类别名称不能为空", "").Encode()) 129 | } else { 130 | _, err := model.AddCategory(userId, name) 131 | if err != nil { 132 | resp.Write(common.NewIMResponseSimple(103, err.Error(), "").Encode()) 133 | } else { 134 | resp.Write(common.NewIMResponseSimple(0, "添加分类成功", "").Encode()) 135 | } 136 | } 137 | default: 138 | resp.Write(common.NewIMResponseSimple(404, "Not Found: "+req.Method, "").Encode()) 139 | 140 | } 141 | } 142 | 143 | // 删除好友分类 144 | func handleUserCategoryDel(resp http.ResponseWriter, req *http.Request) { 145 | categoryId := req.FormValue("category_id") 146 | switch req.Method { 147 | case "GET": 148 | if categoryId == "" { 149 | resp.Write(common.NewIMResponseSimple(102, "类别ID不能为空", "").Encode()) 150 | } else { 151 | num, err := model.DelCategoryById(categoryId) 152 | if err != nil { 153 | resp.Write(common.NewIMResponseSimple(100, err.Error(), "").Encode()) 154 | return 155 | } 156 | if num > 0 { 157 | resp.Write(common.NewIMResponseSimple(0, "已删除好友分类", "").Encode()) 158 | } else { 159 | resp.Write(common.NewIMResponseSimple(103, "删除好友分类失败", "").Encode()) 160 | } 161 | } 162 | case "POST": 163 | if categoryId == "" { 164 | resp.Write(common.NewIMResponseSimple(102, "类别ID不能为空", "").Encode()) 165 | } else { 166 | num, err := model.DelCategoryById(categoryId) 167 | if err != nil { 168 | resp.Write(common.NewIMResponseSimple(100, err.Error(), "").Encode()) 169 | return 170 | } 171 | if num > 0 { 172 | resp.Write(common.NewIMResponseSimple(0, "已删除好友分类", "").Encode()) 173 | } else { 174 | resp.Write(common.NewIMResponseSimple(103, "删除好友关系分类", "").Encode()) 175 | } 176 | } 177 | default: 178 | resp.Write(common.NewIMResponseSimple(404, "Not Found: "+req.Method, "").Encode()) 179 | } 180 | } 181 | 182 | // 编辑好友分类 183 | func handleUserCategoryEdit(resp http.ResponseWriter, req *http.Request) { 184 | categoryId := req.FormValue("category_id") 185 | categoryName := req.FormValue("category_name") 186 | switch req.Method { 187 | case "GET": 188 | if categoryId == "" { 189 | resp.Write(common.NewIMResponseSimple(101, "类别ID不能为空", "").Encode()) 190 | } else if categoryName == "" { 191 | resp.Write(common.NewIMResponseSimple(102, "类别名称不能为空", "").Encode()) 192 | } else { 193 | num, err := model.EditCategoryById(categoryId, categoryName) 194 | if err != nil { 195 | resp.Write(common.NewIMResponseSimple(100, err.Error(), "").Encode()) 196 | return 197 | } 198 | if num > 0 { 199 | resp.Write(common.NewIMResponseSimple(0, "修改用户好友类别成功", "").Encode()) 200 | } else { 201 | resp.Write(common.NewIMResponseSimple(103, "修改用户好友类别失败", "").Encode()) 202 | } 203 | } 204 | case "POST": 205 | if categoryId == "" { 206 | resp.Write(common.NewIMResponseSimple(101, "类别ID不能为空", "").Encode()) 207 | } else if categoryName == "" { 208 | resp.Write(common.NewIMResponseSimple(102, "类别名称不能为空", "").Encode()) 209 | } else { 210 | num, err := model.EditCategoryById(categoryId, categoryName) 211 | if err != nil { 212 | resp.Write(common.NewIMResponseSimple(100, err.Error(), "").Encode()) 213 | return 214 | } 215 | if num > 0 { 216 | resp.Write(common.NewIMResponseSimple(0, "修改用户好友类别成功", "").Encode()) 217 | } else { 218 | resp.Write(common.NewIMResponseSimple(103, "修改用户好友类别失败", "").Encode()) 219 | } 220 | } 221 | default: 222 | resp.Write(common.NewIMResponseSimple(404, "Not Found: "+req.Method, "").Encode()) 223 | 224 | } 225 | } 226 | func handleUserCategoryQuery(resp http.ResponseWriter, req *http.Request) { 227 | id := req.FormValue("id") 228 | categories, err := model.GetCategoriesByUserId(id) 229 | if err != nil { 230 | resp.Write(common.NewIMResponseSimple(100, err.Error(), "").Encode()) 231 | } else { 232 | resp.Write(common.NewIMResponseData(util.SetData("categories", categories), "").Encode()) 233 | } 234 | } 235 | 236 | // 添加好友关系 237 | func handleUserRelationAdd(resp http.ResponseWriter, req *http.Request) { 238 | if req.Method == "POST" { 239 | receiver_category_id := req.FormValue("receiver_category_id") 240 | buddy_request_id := req.FormValue("buddy_request_id") 241 | buddyrequest, _ := model.GetBuddyRequestById(buddy_request_id) 242 | if buddyrequest != nil { 243 | receiver := buddyrequest.Receiver 244 | sender := buddyrequest.Sender 245 | sender_category_id := buddyrequest.SenderCategoryId 246 | //开启事务 247 | tx, _ := model.Database.Begin() 248 | //修改好友请求记录中接受人的好友分组ID 249 | _, err := model.UpdateBuddyRequestReceiverCategoryId(tx, buddy_request_id, receiver_category_id) 250 | //添加请求人好友关系数据 251 | _, err = model.AddFriendRelation(tx, receiver, sender_category_id) 252 | //添加接收人好友关系数据 253 | _, err = model.AddFriendRelation(tx, sender, receiver_category_id) 254 | //修改好友请求记录中状态 255 | _, err = model.UpdateBuddyRequestStatus(tx, buddy_request_id, "1") 256 | 257 | if err != nil { 258 | tx.Rollback() 259 | resp.Write(common.NewIMResponseSimple(100, err.Error(), "").Encode()) 260 | return 261 | } else { 262 | tx.Commit() 263 | //判断请求者是不是在线 在线就把接受者推送给请求者 264 | conn, _ := model.GetConnByUserId(sender) 265 | if conn != nil { //在线 266 | user, _ := model.GetUserById(receiver) 267 | data := make(map[string]interface{}) 268 | data["category_id"] = sender_category_id 269 | data["user"] = user 270 | ClientMaps[conn.Key].PutOut(common.NewIMResponseData(util.SetData("user", data), common.ADD_BUDDY)) 271 | } 272 | conn, _ = model.GetConnByUserId(receiver) 273 | if conn != nil { 274 | user, _ := model.GetUserById(sender) 275 | data := make(map[string]interface{}) 276 | data["category_id"] = receiver_category_id 277 | data["user"] = user 278 | ClientMaps[conn.Key].PutOut(common.NewIMResponseData(util.SetData("user", data), common.ADD_BUDDY)) 279 | } 280 | resp.Write(common.NewIMResponseSimple(0, "好友关系建立成功", "").Encode()) 281 | return 282 | } 283 | 284 | } else { 285 | resp.Write(common.NewIMResponseSimple(104, "该好友请求不存在", "").Encode()) 286 | } 287 | 288 | } else { 289 | resp.Write(common.NewIMResponseSimple(404, "Not Found: "+req.Method, "").Encode()) 290 | } 291 | } 292 | 293 | // 删除好友关系 294 | func handleUserRelationDel(resp http.ResponseWriter, req *http.Request) { 295 | if req.Method == "POST" { 296 | userId := req.FormValue("user_id") 297 | categoryId := req.FormValue("category_id") 298 | if userId == "" { 299 | resp.Write(common.NewIMResponseSimple(101, "用户ID不能为空", "").Encode()) 300 | } else if categoryId == "" { 301 | resp.Write(common.NewIMResponseSimple(102, "类别ID不能为空", "").Encode()) 302 | } else { 303 | num, err := model.DelFriendRelation(userId, categoryId) 304 | if err != nil { 305 | resp.Write(common.NewIMResponseSimple(100, err.Error(), "").Encode()) 306 | return 307 | } 308 | if num > 0 { 309 | resp.Write(common.NewIMResponseSimple(0, "已删除好友关系", "").Encode()) 310 | } else { 311 | resp.Write(common.NewIMResponseSimple(103, "删除好友关系失败", "").Encode()) 312 | } 313 | } 314 | } else { 315 | resp.Write(common.NewIMResponseSimple(404, "Not Found: "+req.Method, "").Encode()) 316 | } 317 | } 318 | func handleUserRelationPush(resp http.ResponseWriter, req *http.Request) { 319 | if req.Method == "POST" { 320 | sender_category_id := req.FormValue("sender_category_id") 321 | sender := req.FormValue("sender") 322 | receiver := req.FormValue("receiver") 323 | if sender_category_id == "" { 324 | resp.Write(common.NewIMResponseSimple(101, "请选择分组", "").Encode()) 325 | } else if sender == "" { 326 | resp.Write(common.NewIMResponseSimple(102, "请重新登录", "").Encode()) 327 | } else { 328 | //判断接收人是不是在线 在线直接推送,不在线记录至请求表中 329 | conn, _ := model.GetConnByUserId(receiver) 330 | user, _ := model.GetUserById(sender) 331 | buddyRequestId, err := model.AddBuddyRequest(sender, sender_category_id, receiver) 332 | if err != nil { 333 | resp.Write(common.NewIMResponseSimple(100, err.Error(), "").Encode()) 334 | } else { 335 | if conn != nil { //在线 直接推送 不在线 客户登录时候会激活请求通知 336 | data := make(map[string]interface{}) 337 | data["id"] = user.Id 338 | data["nick"] = user.Nick 339 | data["status"] = user.Status 340 | data["sign"] = user.Sign 341 | data["avatar"] = user.Avatar 342 | data["buddyRequestId"] = buddyRequestId 343 | ClientMaps[conn.Key].PutOut(common.NewIMResponseData(util.SetData("user", data), common.PUSH_BUDDY_REQUEST)) 344 | resp.Write(common.NewIMResponseSimple(0, "发送好友请求成功", "").Encode()) 345 | } 346 | resp.Write(common.NewIMResponseSimple(1, "发送好友请求成功", "").Encode()) 347 | return 348 | } 349 | } 350 | } else { 351 | resp.Write(common.NewIMResponseSimple(404, "Not Found: "+req.Method, "").Encode()) 352 | } 353 | } 354 | 355 | func handleUserRelationRefuse(resp http.ResponseWriter, req *http.Request) { 356 | if req.Method == "POST" { 357 | buddy_request_id := req.FormValue("buddy_request_id") 358 | if buddy_request_id != "" { 359 | tx, _ := model.Database.Begin() 360 | //修改好友请求记录中状态 361 | _, err := model.UpdateBuddyRequestStatus(tx, buddy_request_id, "2") 362 | if err != nil { 363 | tx.Rollback() 364 | resp.Write(common.NewIMResponseSimple(100, err.Error(), "").Encode()) 365 | return 366 | } else { 367 | tx.Commit() 368 | resp.Write(common.NewIMResponseSimple(0, "已经拒绝该好友请求成功", "").Encode()) 369 | return 370 | } 371 | } else { 372 | resp.Write(common.NewIMResponseSimple(109, "该好友请求不合法", "").Encode()) 373 | } 374 | 375 | } else { 376 | resp.Write(common.NewIMResponseSimple(404, "Not Found: "+req.Method, "").Encode()) 377 | } 378 | } 379 | 380 | // 登录主方法 381 | func login(resp http.ResponseWriter, account string, password string, ip string) { 382 | if account == "" { 383 | resp.Write(common.NewIMResponseSimple(101, "账号不能为空", "").Encode()) 384 | } else if password == "" { 385 | resp.Write(common.NewIMResponseSimple(102, "密码不能为空", "").Encode()) 386 | } else { 387 | num, err := model.CheckAccount(account) 388 | if err != nil { 389 | resp.Write(common.NewIMResponseSimple(100, err.Error(), "").Encode()) 390 | return 391 | } 392 | if num > 0 { 393 | user, err := model.LoginUser(account, password) 394 | if err != nil { 395 | resp.Write(common.NewIMResponseSimple(100, err.Error(), "").Encode()) 396 | return 397 | } 398 | if !strings.EqualFold(user.Id, "") { 399 | token := uuid.New() 400 | if _, err := model.SaveLogin(user.Id, token, ip); err != nil { 401 | resp.Write(common.NewIMResponseSimple(100, err.Error(), "").Encode()) 402 | } else { 403 | // returnData := make(map[string]string) 404 | // returnData["id"] = user.Id 405 | // returnData["nick"] = user.Nick 406 | // returnData["avatar"] = user.Avatar 407 | // returnData["status"] = user.Status 408 | // returnData["token"] = token //token uuid 带 横杠 409 | // returnData["sign"]=user.Sign 410 | user.Token = token 411 | resp.Write(common.NewIMResponseData(util.SetData("user", user), "LOGIN_RETURN").Encode()) 412 | } 413 | } else { 414 | resp.Write(common.NewIMResponseSimple(104, "密码错误", "").Encode()) 415 | } 416 | } else { 417 | resp.Write(common.NewIMResponseSimple(103, "账号不存在", "").Encode()) 418 | } 419 | } 420 | } 421 | 422 | /* 423 | 用户注册 424 | 101 账号不能为空 425 | 102 密码不能为空 426 | 103 用户名已存在 427 | 104 昵称不能为空 428 | 105 注册失败 429 | */ 430 | func register(resp http.ResponseWriter, account string, password string, nick string, avatar string) { 431 | if account == "" { 432 | resp.Write(common.NewIMResponseSimple(101, "账号不能为空", "").Encode()) 433 | } else if password == "" { 434 | resp.Write(common.NewIMResponseSimple(102, "密码不能为空", "").Encode()) 435 | } else if nick == "" { 436 | resp.Write(common.NewIMResponseSimple(103, "昵称不能为空", "").Encode()) 437 | } else { 438 | num, err := model.CheckAccount(account) 439 | if err != nil { 440 | resp.Write(common.NewIMResponseSimple(103, err.Error(), "").Encode()) 441 | return 442 | } 443 | if num > 0 { 444 | resp.Write(common.NewIMResponseSimple(104, "用户名已存在", "").Encode()) 445 | } else { 446 | _, err := model.SaveUser(account, password, nick, avatar) 447 | if err != nil { 448 | resp.Write(common.NewIMResponseSimple(104, err.Error(), "").Encode()) 449 | return 450 | } 451 | if num > 0 { 452 | resp.Write(common.NewIMResponseSimple(0, "注册成功", "").Encode()) 453 | } else { 454 | resp.Write(common.NewIMResponseSimple(105, "注册失败", "").Encode()) 455 | } 456 | } 457 | } 458 | } 459 | -------------------------------------------------------------------------------- /im/server/tcpserver.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "code.google.com/p/go-uuid/uuid" 5 | "fmt" 6 | "imgo/im/common" 7 | "imgo/im/model" 8 | "imgo/im/util" 9 | "log" 10 | "net" 11 | "os" 12 | "os/signal" 13 | "strings" 14 | "syscall" 15 | ) 16 | 17 | /* 18 | 服务端结构体 19 | */ 20 | type Server struct { 21 | listener net.Listener // 服务端监听器 监听xx端口 22 | clients common.ClientTable // 客户端列表 抽象出来单独维护和入参 更方便管理连接 23 | joinsniffer chan net.Conn // 访问连接嗅探器 触发创建客户端连接处理方法 24 | quitsniffer chan *common.Client // 连接退出嗅探器 触发连接退出处理方法 25 | insniffer common.InMessage // 接收消息嗅探器 触发接收消息处理方法 对应客户端中in属性 26 | } 27 | 28 | var ClientMaps common.ClientTable 29 | 30 | /* 31 | IM服务启动方法 32 | */ 33 | func StartIMServer(config util.IMConfig) { 34 | log.Println("服务端启动中...") 35 | //初始化服务端 36 | server := &Server{ 37 | clients: make(common.ClientTable, model.Config.MaxClients), 38 | joinsniffer: make(chan net.Conn), 39 | quitsniffer: make(chan *common.Client), 40 | insniffer: make(common.InMessage), 41 | } 42 | ClientMaps = server.clients 43 | // 添加关闭勾子,当关闭服务器时执行 44 | server.interruptHandler() 45 | // 启动监听方法(包含各类嗅探器) 46 | server.listen() 47 | // 启动服务端端口监听(等待连接) 48 | server.start() 49 | } 50 | 51 | /* 52 | 监听方法 53 | */ 54 | func (this *Server) listen() { 55 | go func() { 56 | for { 57 | select { 58 | // 接收到了消息 59 | case message := <-this.insniffer: 60 | this.receivedHandler(message) 61 | // 新来了一个连接 62 | case conn := <-this.joinsniffer: 63 | this.joinHandler(conn) 64 | // 退出了一个连接 65 | case client := <-this.quitsniffer: 66 | this.quitHandler(client) 67 | } 68 | } 69 | }() 70 | } 71 | 72 | /* 73 | 新客户端请求处理方法 74 | */ 75 | func (this *Server) joinHandler(conn net.Conn) { 76 | //获取UUID作为客户端的key 77 | key := uuid.New() 78 | // 创建一个客户端 79 | client := common.CreateClient(key, conn) 80 | //给客户端指定key 81 | this.clients[key] = client 82 | // log.Printf("新客户端Key:[%s] online:%d", client.Key, len(ClientMaps)) 83 | // 开启协程不断地接收消息 84 | go func() { 85 | for { 86 | // 客户端读取消息 87 | msg := <-client.In 88 | // 消息交给嗅探器 触发对应的处理方法 89 | this.insniffer <- msg 90 | } 91 | }() 92 | // 开启协程一直等待断开 93 | go func() { 94 | for { 95 | //客户端接收断开请求 96 | conn := <-client.Quit 97 | // log.Printf("客户端:[%s]退出", client.Key) 98 | //请求交给嗅探器 触发对应的处理方法 99 | this.quitsniffer <- conn 100 | } 101 | }() 102 | // 返回客户端的唯一标识 103 | data := make(map[string]interface{}) 104 | data["key"] = key 105 | client.PutOut(common.NewIMResponseData(util.SetData("conn", data), common.GET_KEY_RETURN)) 106 | } 107 | 108 | /* 109 | 客户端退出处理方法 110 | */ 111 | func (this *Server) quitHandler(client *common.Client) { 112 | if client != nil { 113 | // 通知在线的好友,我离线了 114 | if client.Login != nil { 115 | // 判断要求改变的状态和当前该用户的状态是否一致 116 | model.DeleteConnByKey(client.Key) 117 | count, _ := model.CountConnByUserId(client.Login.UserId) 118 | // 如果没有这用户的连接,同时更新用户状态为离线 119 | if count == 0 { 120 | model.UpdateUserStatus(client.Login.UserId, "0") 121 | } 122 | keys, err := model.GetBuddiesKeyById(client.Login.UserId) 123 | if err == nil { 124 | for i := 0; i < len(keys); i++ { 125 | //给对应的连接推送好友状态变化的通知 126 | data := make(map[string]string) 127 | data["id"] = client.Login.UserId 128 | data["state"] = "0" 129 | if (this.clients[keys[i]] != nil) { 130 | this.clients[keys[i]].PutOut(common.NewIMResponseData(util.SetData("user", data), common.PUSH_STATUS_CHANGE)) 131 | } 132 | } 133 | } 134 | } 135 | 136 | // 调用客户端关闭方法 137 | client.Close() 138 | delete(this.clients, client.Key) 139 | 140 | // log.Printf("客户端退出: %s online:%d", client.Key, len(ClientMaps)) 141 | } 142 | } 143 | 144 | /* 145 | 接收消息处理方法 146 | */ 147 | func (this *Server) receivedHandler(request common.IMRequest) { 148 | // log.Println("开始读取数据") 149 | // log.Println("读取的数据为", request) 150 | 151 | // 获取请求的客户端 152 | client := request.Client 153 | // 获取请求数据 154 | reqData := request.Data 155 | // log.Printf("客户端:[%s]发送命令:[%s]消息内容:[%s]", client.Key, request.Command, request.Data) 156 | 157 | // 未登录业务处理部分 158 | switch request.Command { 159 | case common.GET_CONN: 160 | token := reqData["user"]["token"] 161 | if token == "" { 162 | client.PutOut(common.NewIMResponseSimple(301, "用户令牌不能为空!", common.GET_CONN_RETURN)) 163 | return 164 | } 165 | // 校验用户是否登录,把Login数据放在client当中 166 | login, err := model.GetLoginByToken(token) 167 | if err != nil { 168 | client.PutOut(common.NewIMResponseSimple(300, err.Error(), common.GET_CONN_RETURN)) 169 | return 170 | } 171 | client.Login = login 172 | // log.Printf("登录比较:token=%s Login=%s", token, client.Login) 173 | if !strings.EqualFold(client.Login.Token, token) { 174 | client.PutOut(common.NewIMResponseSimple(302, "该用户令牌无效!", common.GET_CONN_RETURN)) 175 | return 176 | } 177 | if client.Login.Id != "" { 178 | // 更新在线状态,如果现在已经是在线,然后再设置在线,影响行还是为0 179 | _, err := model.UpdateUserStatus(client.Login.UserId, "1") 180 | if err != nil { 181 | client.PutOut(common.NewIMResponseSimple(304, "设置用户状态失败!", common.GET_CONN_RETURN)) 182 | return 183 | } 184 | // 创建或者更新连接信息 这个error不能handler,当没有数据时会为 sql: no rows in result set 185 | conn, _ := model.GetConnByToken(token) 186 | if conn != nil { 187 | _, err := model.UpdateConnByToken(client.Key, client.Login.UserId, token) 188 | if err != nil { 189 | client.PutOut(common.NewIMResponseSimple(300, err.Error(), common.GET_CONN_RETURN)) 190 | return 191 | } 192 | } else { 193 | _, err := model.AddConn(client.Key, client.Login.UserId, token) 194 | if err != nil { 195 | client.PutOut(common.NewIMResponseSimple(300, err.Error(), common.GET_CONN_RETURN)) 196 | return 197 | } 198 | } 199 | data := make(map[string]interface{}) 200 | data["status"] = 1 201 | client.PutOut(common.NewIMResponseData(util.SetData("conn", data), common.GET_CONN_RETURN)) 202 | // 通知在线的好友,我上线了 203 | keys, err := model.GetBuddiesKeyById(client.Login.UserId) 204 | if err != nil { 205 | client.PutOut(common.NewIMResponseSimple(300, err.Error(), common.SEND_STATUS_CHANGE)) 206 | return 207 | } 208 | for i := 0; i < len(keys); i++ { 209 | //给对应的连接推送好友状态变化的通知 210 | data := make(map[string]string) 211 | data["id"] = client.Login.UserId 212 | data["state"] = "1" 213 | if (this.clients[keys[i]] != nil) { 214 | this.clients[keys[i]].PutOut(common.NewIMResponseData(util.SetData("user", data), common.PUSH_STATUS_CHANGE)) 215 | } 216 | } 217 | return 218 | } else { 219 | client.PutOut(common.NewIMResponseSimple(303, "用户未登录!", common.GET_CONN_RETURN)) 220 | return 221 | } 222 | } 223 | // 校验连接是已经授权 224 | if client.Login == nil { 225 | client.PutOut(common.NewIMResponseSimple(401, "用户未登录!", common.UNAUTHORIZED)) 226 | return 227 | } 228 | // 已经登录业务逻辑部分 229 | switch request.Command { 230 | case common.GET_BUDDY_LIST: 231 | // 获取好友分组列表 232 | categories, err := model.GetCategoriesByUserId(client.Login.UserId) 233 | if err != nil { 234 | client.PutOut(common.NewIMResponseSimple(301, "获取好友分类错误!", common.GET_BUDDY_LIST_RETURN)) 235 | return 236 | } 237 | categories, err = model.GetBuddiesByCategories(categories) 238 | if err != nil { 239 | client.PutOut(common.NewIMResponseSimple(300, err.Error(), common.GET_BUDDY_LIST_RETURN)) 240 | return 241 | } 242 | client.PutOut(common.NewIMResponseData(util.SetData("categories", categories), common.GET_BUDDY_LIST_RETURN)) 243 | //初始化好友列表之后 检查该用户有没有未读的好友请求 并推送给用户 244 | buddyRequests, err := model.GetBuddyRequestsByReceiver(client.Login.UserId) 245 | if(err != nil) { 246 | client.PutOut(common.NewIMResponseSimple(300, err.Error(), common.GET_BUDDY_LIST_RETURN)) 247 | } 248 | if len(buddyRequests) > 0 { 249 | for _, buddyRequest := range buddyRequests { 250 | user, _ := model.GetUserById(buddyRequest.Sender) 251 | data := make(map[string]interface{}) 252 | data["id"] = user.Id 253 | data["nick"] = user.Nick 254 | data["status"] = user.Status 255 | data["sign"] = user.Sign 256 | data["avatar"] = user.Avatar 257 | data["buddyRequestId"] = buddyRequest.Id 258 | client.PutOut(common.NewIMResponseData(util.SetData("user", data), common.PUSH_BUDDY_REQUEST)) 259 | } 260 | } 261 | case common.CREATE_SESSION: 262 | // 创建会话 //{"command":"CREATE_SESSION","data":{"session":{"sender":"xxx","receiver":"xxx","token":"xxxx"}}} 263 | sender := reqData["session"]["sender"] 264 | receiver := reqData["session"]["receiver"] 265 | 266 | if sender == "" { 267 | client.PutOut(common.NewIMResponseSimple(301, "发送者不能为空!", common.CREATE_SESSION_RETURN)) 268 | return 269 | } 270 | if receiver == "" { 271 | client.PutOut(common.NewIMResponseSimple(302, "接收者不能为空!", common.CREATE_SESSION_RETURN)) 272 | return 273 | } 274 | conversationId := model.GetSession(sender, receiver).Id 275 | if conversationId == "" { 276 | conversationId = model.AddSession(sender, receiver) 277 | } 278 | if conversationId == "" { 279 | client.PutOut(common.NewIMResponseSimple(303, "创建会话失败", common.GET_CONN_RETURN)) 280 | return 281 | } else { 282 | data := make(map[string]string) 283 | data["ticket"] = conversationId 284 | data["receiver"] = receiver 285 | client.PutOut(common.NewIMResponseData(util.SetData("session", data), common.CREATE_SESSION_RETURN)) 286 | } 287 | 288 | case common.SEND_MSG: 289 | ticket := reqData["message"]["ticket"] 290 | content := reqData["message"]["content"] 291 | 292 | if ticket == "" { 293 | client.PutOut(common.NewIMResponseSimple(301, "Ticket不能为空!", common.SEND_MSG_RETURN)) 294 | return 295 | } 296 | if content == "" { 297 | client.PutOut(common.NewIMResponseSimple(302, "消息内容不能为空!", common.SEND_MSG_RETURN)) 298 | return 299 | } 300 | conversion, err := model.GetSessionById(ticket) 301 | if err != nil { 302 | client.PutOut(common.NewIMResponseSimple(300, err.Error(), common.SEND_MSG_RETURN)) 303 | return 304 | } 305 | if conversion.Id != "" { 306 | isSent := false 307 | keys, err := model.GetReceiverKeyByTicket(ticket) 308 | if err != nil { 309 | client.PutOut(common.NewIMResponseSimple(300, err.Error(), common.SEND_MSG_RETURN)) 310 | return 311 | } 312 | for _, key := range keys { 313 | if this.clients[key] == nil { 314 | // client.PutOut(common.NewIMResponseSimple(402, "对方还未登录!", common.SEND_MSG_RETURN)) 315 | continue 316 | } 317 | // 把消息转发给接收者 318 | data := make(map[string]string) 319 | data["sender"] = client.Login.UserId 320 | data["ticket"] = ticket 321 | data["content"] = content 322 | log.Println("开始转发给:", key) 323 | this.clients[key].PutOut(common.NewIMResponseData(util.SetData("message", data), common.PUSH_MSG)) 324 | isSent = true 325 | } 326 | if !isSent { 327 | client.PutOut(common.NewIMResponseSimple(304, "对方不在线!", common.GET_CONN_RETURN)) 328 | return 329 | } 330 | } else { 331 | client.PutOut(common.NewIMResponseSimple(303, "会话已关闭!", common.SEND_MSG_RETURN)) 332 | return 333 | } 334 | 335 | case common.SEND_STATUS_CHANGE: 336 | status := reqData["user"]["status"] 337 | if status == "" { 338 | client.PutOut(common.NewIMResponseSimple(301, "状态不能为空!", common.SEND_STATUS_CHANGE)) 339 | return 340 | } 341 | user, err := model.GetUserByToken(client.Login.Token) 342 | if err != nil { 343 | client.PutOut(common.NewIMResponseSimple(300, err.Error(), common.SEND_STATUS_CHANGE)) 344 | return 345 | } 346 | //判断用户的合法性 347 | if user.Id == "" { 348 | //判断要求改变的状态和当前该用户的状态是否一致 349 | if strings.EqualFold(user.Status, status) { 350 | //FIXME 此处不做如果状态是离线就删除用户连接的操作,状态改变认为是客户端手动操作或者网络异常 351 | _, err := model.UpdateUserStatus(user.Id, status) 352 | if err != nil { 353 | client.PutOut(common.NewIMResponseSimple(304, err.Error(), common.SEND_STATUS_CHANGE)) 354 | return 355 | } 356 | keys, err := model.GetBuddiesKeyById(user.Id) 357 | if err != nil { 358 | client.PutOut(common.NewIMResponseSimple(300, err.Error(), common.SEND_STATUS_CHANGE)) 359 | return 360 | } 361 | for i := 0; i < len(keys); i++ { 362 | //给对应的连接推送好友状态变化的通知 363 | data := make(map[string]string) 364 | data["id"] = user.Id 365 | data["state"] = reqData["user"]["status"] 366 | this.clients[keys[i]].PutOut(common.NewIMResponseData(util.SetData("user", data), common.PUSH_STATUS_CHANGE)) 367 | } 368 | } else { 369 | client.PutOut(common.NewIMResponseSimple(303, "请退出重新登录!", common.SEND_STATUS_CHANGE)) 370 | return 371 | } 372 | 373 | } else { 374 | client.PutOut(common.NewIMResponseSimple(302, "Token不合法!", common.SEND_STATUS_CHANGE)) 375 | return 376 | } 377 | case common.LOGOUT_REQUEST: 378 | client.Quiting() 379 | case common.SEND_BUDDY_REQUEST: 380 | receiver := reqData["buddyRequest"]["receiver"] 381 | //判断接收者是不是在线 382 | user, _ := model.GetUserById(receiver) 383 | if user == nil || user.Status == "0" { //不在线 记录到好友请求表中 384 | id, _ := model.AddBuddyRequest(reqData["buddyRequest"]["sender"], reqData["buddyRequest"]["senderCateId"], receiver) 385 | if id != nil { 386 | 387 | } 388 | } else { //在线直接推送给接收者 389 | conn, _ := model.GetConnByUserId(receiver) 390 | data := make(map[string]string) 391 | data["sender"] = reqData["buddyRequest"]["sender"] 392 | data["senderCateId"] = reqData["buddyRequest"]["senderCateId"] 393 | data["receiver"] = reqData["buddyRequest"]["receiver"] 394 | this.clients[conn.Key].PutOut(common.NewIMResponseData(util.SetData("buddyRequest", data), common.PUSH_BUDDY_REQUEST)) 395 | } 396 | 397 | } 398 | } 399 | 400 | func (this *Server) start() { 401 | // 设置监听地址及端口 402 | addr := fmt.Sprintf("0.0.0.0:%d", model.Config.IMPort) 403 | this.listener, _ = net.Listen("tcp", addr) 404 | log.Printf("开始监听服务器[%d]端口", model.Config.IMPort) 405 | for { 406 | conn, err := this.listener.Accept() 407 | if err != nil { 408 | log.Fatalln(err) 409 | return 410 | } 411 | // log.Printf("新连接地址为:[%s]", conn.RemoteAddr()) 412 | this.joinsniffer <- conn 413 | } 414 | } 415 | 416 | // FIXME: need to figure out if this is the correct approach to gracefully 417 | // terminate a server. 418 | func (this *Server) Stop() { 419 | this.listener.Close() 420 | } 421 | 422 | // 服务端关闭时执行 423 | func (this *Server) interruptHandler() { 424 | c := make(chan os.Signal, 1) 425 | signal.Notify(c, os.Interrupt) 426 | signal.Notify(c, syscall.SIGTERM) 427 | go func() { 428 | sig := <-c 429 | log.Printf("captured %v, stopping profiler and exiting..", sig) 430 | // 清除客户端连接 431 | for _, v := range this.clients { 432 | this.quitHandler(v) 433 | } 434 | // 退出 435 | os.Exit(1) 436 | }() 437 | } 438 | -------------------------------------------------------------------------------- /im/startclient.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "log" 6 | "net" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | 12 | conn, err := net.Dial("tcp", "127.0.0.1:9090") 13 | 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | defer conn.Close() 18 | reader := bufio.NewReader(conn) 19 | writer := bufio.NewWriter(conn) 20 | 21 | in := bufio.NewReader(os.Stdin) 22 | 23 | go func() { 24 | for { 25 | if line, _, err := reader.ReadLine(); err == nil { 26 | log.Println(string(line)) 27 | } 28 | } 29 | }() 30 | 31 | for { 32 | line, _, _ := in.ReadLine() 33 | // 模拟一个请求 34 | // {"command":"GET_CONN","data":null} 35 | // {"command":"GET_BUDDY_LIST","data":null} 36 | writer.WriteString(string(line) + "\n") 37 | writer.Flush() 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /im/startserver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "imgo/im/util" 6 | "log" 7 | "imgo/im/model" 8 | "imgo/im/server" 9 | ) 10 | 11 | const ( 12 | Name string = "Go-IM" 13 | Version string = "1.0" 14 | ) 15 | 16 | /* 17 | 启动服务方法 18 | */ 19 | func Start(config *util.IMConfig) { 20 | var err error 21 | //初始化model包下全局变量值 22 | model.Config = config 23 | model.Database, err = config.DBConfig.Connect() 24 | if err != nil { 25 | log.Fatalf(err.Error()) 26 | } 27 | defer model.Database.Close() 28 | go func() { 29 | err := server.StartHttpServer(*config) 30 | log.Fatalf("HttpServer", err) 31 | }() 32 | // 启动IM服务 33 | server.StartIMServer(*config) 34 | } 35 | 36 | func main() { 37 | log.Println("*********************************************") 38 | log.Printf(" 系统:[%s]版本:[%s]", Name, Version) 39 | log.Println("*********************************************") 40 | configPath := flag.String("config", "config.json", "Configuration file to use") 41 | flag.Parse() 42 | // 读取配置信息 43 | config, err := util.ReadConfig(*configPath) 44 | if err != nil { 45 | log.Fatalf("读取配置文件错误: %s", err) 46 | } 47 | 48 | Start(config) 49 | } -------------------------------------------------------------------------------- /im/util/azDGUtil.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/base64" 6 | "fmt" 7 | "time" 8 | ) 9 | 10 | var h = md5.New() 11 | 12 | func cipherEncode(key string, sourceText string) string { 13 | h.Write([]byte(key)) 14 | cipherHash := fmt.Sprintf("%x", h.Sum(nil)) 15 | h.Reset() 16 | inputData := []byte(sourceText) 17 | loopCount := len(inputData) 18 | outData := make([]byte, loopCount) 19 | for i := 0; i < loopCount; i++ { 20 | outData[i] = inputData[i] ^ cipherHash[i%32] 21 | } 22 | return fmt.Sprintf("%s", outData) 23 | } 24 | 25 | func encode(key string, sourceText string) string { 26 | h.Write([]byte(time.Now().Format("2006-01-02 15:04:05"))) 27 | noise := fmt.Sprintf("%x", h.Sum(nil)) 28 | h.Reset() 29 | inputData := []byte(sourceText) 30 | loopCount := len(inputData) 31 | outData := make([]byte, loopCount*2) 32 | 33 | for i, j := 0, 0; i < loopCount; i, j = i+1, j+1 { 34 | outData[j] = noise[i%32] 35 | j++ 36 | outData[j] = inputData[i] ^ noise[i%32] 37 | } 38 | 39 | return base64.StdEncoding.EncodeToString([]byte(cipherEncode(key, fmt.Sprintf("%s", outData)))) 40 | } 41 | 42 | func decode(key string, sourceText string) string { 43 | buf, err := base64.StdEncoding.DecodeString(sourceText) 44 | if err != nil { 45 | fmt.Println("Decode(%q) failed: %v", sourceText, err) 46 | return "" 47 | } 48 | inputData := []byte(cipherEncode(key, fmt.Sprintf("%s", buf))) 49 | loopCount := len(inputData) 50 | outData := make([]byte, loopCount) 51 | for i, j := 0, 0; i < loopCount; i, j = i+2, j+1 { 52 | outData[j] = inputData[i] ^ inputData[i+1] 53 | } 54 | return fmt.Sprintf("%s", outData) 55 | } 56 | 57 | func main() { 58 | s := encode("kk", "fuck you") 59 | fmt.Println(s) 60 | fmt.Println(decode("kk", s)) 61 | } 62 | -------------------------------------------------------------------------------- /im/util/config.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "database/sql" 5 | "encoding/json" 6 | "fmt" 7 | _ "github.com/go-sql-driver/mysql" 8 | "io/ioutil" 9 | "log" 10 | ) 11 | 12 | /* 13 | IM配置结构体 14 | */ 15 | type IMConfig struct { 16 | IMPort int `json:"im_port"` //服务端长连接监听端口 17 | HttpPort int `json:"http_port"` //服务端短连接监听端口(登录接口) 18 | MaxClients int `json:"max_clients"` //服务端长连接最大连接数 19 | DBConfig DBConfig `json:"db_config"` //数据库配置 20 | } 21 | 22 | /* 23 | 数据库配置结构体 24 | */ 25 | type DBConfig struct { 26 | Host string `json:"host"` //连接地址 27 | Username string `json:"username"` //用户名 28 | Password string `json:"password"` //用户密码 29 | Name string `json:"name"` //数据库名 30 | MaxIdleConns int `json:"max_idle_conns"` //连接池最大空闲连接数 31 | MaxOpenConns int `json:"max_open_conns"` //连接池最大连接数 32 | } 33 | 34 | /* 35 | 读取配置文件 36 | */ 37 | func ReadConfig(path string) (*IMConfig, error) { 38 | config := new(IMConfig) 39 | err := config.Parse(path) 40 | if err != nil { 41 | return nil, err 42 | } 43 | return config, nil 44 | } 45 | 46 | /* 47 | 解析配置文件 48 | */ 49 | func (this *IMConfig) Parse(path string) error { 50 | file, err := ioutil.ReadFile(path) 51 | if err != nil { 52 | return err 53 | } 54 | err = json.Unmarshal(file, &this) 55 | if err != nil { 56 | return err 57 | } 58 | return nil 59 | } 60 | 61 | /* 62 | 连接数据库 63 | */ 64 | func (this *DBConfig) Connect() (*sql.DB, error) { 65 | // 从配置文件中读取配置信息并初始化连接池(go中含有连接池处理机制) 66 | db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?parseTime=true&charset=utf8", this.Username, this.Password, this.Host, this.Name)) 67 | db.SetMaxIdleConns(this.MaxIdleConns) // 最大空闲连接 68 | db.SetMaxOpenConns(this.MaxOpenConns) // 最大连接数 69 | if err != nil { 70 | return nil, err 71 | } 72 | if err := db.Ping(); err != nil { 73 | return nil, err 74 | } 75 | log.Println("连接数据库成功") 76 | return db, nil 77 | } 78 | -------------------------------------------------------------------------------- /im/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "database/sql" 5 | "log" 6 | "net" 7 | "net/http" 8 | "strings" 9 | ) 10 | 11 | /* 12 | 获取IP 13 | */ 14 | func GetIp(r *http.Request) string { 15 | ip := net.ParseIP(strings.Split(r.RemoteAddr, ":")[0]).String() 16 | if ip == "" { 17 | ip = "127.0.0.1" 18 | } 19 | return ip 20 | } 21 | 22 | /* 23 | 组合数据 原转JSON(已修正不需要转JSON) 24 | */ 25 | //FIXME 此处方法名需要重新命名 否则会产生干扰 26 | func SetData(key string, data interface{}) interface{} { 27 | dataMap := make(map[string]interface{}) 28 | dataMap[key] = data 29 | return dataMap 30 | } 31 | 32 | /* 33 | 把查询数据库的结果集转换成map 34 | */ 35 | func ResToMap(rows *sql.Rows) map[string]string { 36 | data := make(map[string]string) 37 | columns, err := rows.Columns() 38 | if err != nil { 39 | log.Println("获取结果集中列名数组错误:", err) 40 | } 41 | values := make([]sql.RawBytes, len(columns)) 42 | scanArgs := make([]interface{}, len(values)) 43 | for i := range values { 44 | scanArgs[i] = &values[i] 45 | } 46 | for rows.Next() { 47 | err = rows.Scan(scanArgs...) 48 | if err != nil { 49 | log.Println("扫描结果集中参数值错误:", err) 50 | } 51 | var value string 52 | for i, col := range values { 53 | if col == nil { 54 | value = "NULL" 55 | } else { 56 | value = string(col) 57 | } 58 | data[columns[i]] = value 59 | } 60 | 61 | } 62 | return data 63 | } 64 | -------------------------------------------------------------------------------- /test/aes.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/base64" 6 | "fmt" 7 | "time" 8 | ) 9 | 10 | var cipher = "xx" 11 | var h = md5.New() 12 | 13 | func cipherEncode(sourceText string) string { 14 | h.Write([]byte(cipher)) 15 | cipherHash := fmt.Sprintf("%x", h.Sum(nil)) 16 | h.Reset() 17 | inputData := []byte(sourceText) 18 | loopCount := len(inputData) 19 | outData := make([]byte, loopCount) 20 | for i := 0; i < loopCount; i++ { 21 | outData[i] = inputData[i] ^ cipherHash[i%32] 22 | } 23 | return fmt.Sprintf("%s", outData) 24 | } 25 | 26 | func encode(sourceText string) string { 27 | h.Write([]byte(time.Now().Format("2006-01-02 15:04:05"))) 28 | noise := fmt.Sprintf("%x", h.Sum(nil)) 29 | h.Reset() 30 | inputData := []byte(sourceText) 31 | loopCount := len(inputData) 32 | outData := make([]byte, loopCount*2) 33 | 34 | for i, j := 0, 0; i < loopCount; i, j = i+1, j+1 { 35 | outData[j] = noise[i%32] 36 | j++ 37 | outData[j] = inputData[i] ^ noise[i%32] 38 | } 39 | 40 | return base64.StdEncoding.EncodeToString([]byte(cipherEncode(fmt.Sprintf("%s", outData)))) 41 | } 42 | 43 | func decode(sourceText string) string { 44 | buf, err := base64.StdEncoding.DecodeString(sourceText) 45 | if err != nil { 46 | fmt.Println("Decode(%q) failed: %v", sourceText, err) 47 | return "" 48 | } 49 | inputData := []byte(cipherEncode(fmt.Sprintf("%s", buf))) 50 | loopCount := len(inputData) 51 | outData := make([]byte, loopCount) 52 | for i, j := 0, 0; i < loopCount; i, j = i+2, j+1 { 53 | outData[j] = inputData[i] ^ inputData[i+1] 54 | } 55 | return fmt.Sprintf("%s", outData) 56 | } 57 | 58 | func main() { 59 | s := encode("dasdasddfsdfdsfsdfsdfrfrgtetewwerwvwerwerewrwerwerwefwfwerewrwerwerwwewerewrewrwerewr") 60 | fmt.Println(s) 61 | fmt.Println(decode(s)) 62 | } 63 | -------------------------------------------------------------------------------- /test/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net" 7 | ) 8 | 9 | type Clienter struct { 10 | client net.Conn 11 | isAlive bool 12 | SendStr chan string 13 | RecvStr chan string 14 | } 15 | type IMResponse struct { 16 | Status int `json:"status"` //状态 0成功,非0错误 17 | Msg string `json:"msg"` //消息 18 | Data map[string]map[string]interface{} `json:"data"` //数据 19 | Refer string `json:"refer"` //来源 20 | } 21 | 22 | func (c *Clienter) Connect() bool { 23 | if c.isAlive { 24 | return true 25 | } else { 26 | var err error 27 | c.client, err = net.Dial("tcp", "127.0.0.1:9090") 28 | if err != nil { 29 | fmt.Printf("Failure to connet:%s\n", err.Error()) 30 | return false 31 | } 32 | c.isAlive = true 33 | } 34 | return true 35 | } 36 | 37 | func (c *Clienter) Echo() { 38 | line := <-c.SendStr 39 | c.client.Write([]byte(line)) 40 | buf := make([]byte, 1024) 41 | n, err := c.client.Read(buf) 42 | if err != nil { 43 | c.RecvStr <- string("Server close...") 44 | c.client.Close() 45 | c.isAlive = false 46 | return 47 | } 48 | c.RecvStr <- string(buf[0:n]) 49 | } 50 | 51 | func Work(tc *Clienter) { 52 | if !tc.isAlive { 53 | if tc.Connect() { 54 | tc.Echo() 55 | } else { 56 | <-tc.SendStr 57 | tc.RecvStr <- string("Server close...") 58 | } 59 | } else { 60 | tc.Echo() 61 | } 62 | } 63 | func main() { 64 | var tc Clienter 65 | tc.SendStr = make(chan string) 66 | tc.RecvStr = make(chan string) 67 | if !tc.Connect() { 68 | return 69 | } 70 | var line string 71 | var res IMResponse 72 | for { 73 | 74 | go Work(&tc) 75 | fmt.Printf("发送:%s\n", line) 76 | tc.SendStr <- line 77 | s := <-tc.RecvStr 78 | fmt.Printf("返回:%s\n", s) 79 | if tc.Connect() && s != "" { 80 | json.Unmarshal([]byte(s), &res) 81 | switch res.Refer { 82 | case "GET_KEY_RETURN": 83 | //建立连接 84 | line = "{\"command\":\"GET_CONN\",\"data\":{\"user\":{\"id\":\"11\",\"token\":\"11\",\"key\":\"" + res.Data["conn"]["key"].(string) + "\"}}}" 85 | case "GET_CONN_RETURN": 86 | //创建会话 87 | line = "{\"command\":\"CREATE_SESSION\",\"data\":{\"session\":{\"sender\":\"11\",\"receiver\":\"22\",\"token\":\"11\"}}}" 88 | 89 | case "CREATE_SESSION_RETURN": 90 | //发送消息session 91 | line = "{\"command\":\"SEND_MSG\",\"data\":{\"message\":{\"content\":\"Hello World\",\"ticket\":\"" + res.Data["session"]["ticket"].(string) + "\",\"token\":\"11\"}}}" 92 | 93 | } 94 | 95 | } 96 | 97 | line += "\n" 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /test/config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | //申明一个map到时候存取配置文件 12 | var per map[string]interface{} 13 | 14 | func main() { 15 | //实例化这个map 16 | per = make(map[string]interface{}) 17 | //打开这个ini文件 18 | f, _ := os.Open("test.ini") 19 | //读取文件到buffer里边 20 | buf := bufio.NewReader(f) 21 | for { 22 | //按照换行读取每一行 23 | l, err := buf.ReadString('\n') 24 | //相当于PHP的trim 25 | line := strings.TrimSpace(l) 26 | //判断退出循环 27 | if err != nil { 28 | if err != io.EOF { 29 | //return err 30 | panic(err) 31 | } 32 | if len(line) == 0 { 33 | break 34 | } 35 | } 36 | switch { 37 | case len(line) == 0: 38 | //匹配[db]然后存储 39 | case line[0] == '[' && line[len(line)-1] == ']': 40 | section := strings.TrimSpace(line[1 : len(line)-1]) 41 | fmt.Println(section) 42 | default: 43 | //dnusername = xiaowei 这种的可以匹配存储 44 | i := strings.IndexAny(line, "=") 45 | per[strings.TrimSpace(line[0:i])] = strings.TrimSpace(line[i+1:]) 46 | 47 | } 48 | } 49 | //循环输出结果 50 | for k, v := range per { 51 | fmt.Println(k, v) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "im_port": 9090, 3 | "http_port": 8080, 4 | "max_clients": 10, 5 | "db_config": { 6 | "host": "127.0.0.1:3306", 7 | "username": "root", 8 | "password": "", 9 | "name": "im", 10 | "max_idle_conns": 10, 11 | "max_open_conns": 50 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/config_test.go: -------------------------------------------------------------------------------- 1 | package im 2 | 3 | import ( 4 | //"flag" 5 | ///"im-go2" 6 | "fmt" 7 | "im-go/im/model" 8 | "testing" 9 | ) 10 | 11 | func TestConfig(t *testing.T) { 12 | /** 13 | var tt []string; 14 | tt=append(tt,"444"); 15 | fmt.Println(tt[0]) 16 | */ 17 | var buddies []model.User 18 | group := model.Category{"id", "tt", buddies} 19 | 20 | user := model.User{"userid", "", "", "", ""} 21 | users := []model.User{user} 22 | 23 | group.Buddies = users 24 | fmt.Println(string(group.Encode())) 25 | 26 | } 27 | -------------------------------------------------------------------------------- /test/pressuretest.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "fmt" 7 | "net" 8 | "os" 9 | "time" 10 | ) 11 | 12 | type IMResponse struct { 13 | Status int `json:"status"` //状态 0成功,非0错误 14 | Msg string `json:"msg"` //消息 15 | Data map[string]map[string]interface{} `json:"data"` //数据 16 | Refer string `json:"refer"` //来源 17 | } 18 | 19 | var Host = "123.59.15.125:9090" 20 | 21 | //var Host = "127.0.0.1:9090" 22 | var conut = 0 23 | var waiter = make(chan string) 24 | 25 | func main() { 26 | 27 | for i := 0; i < 400000; i++ { 28 | go testConn() 29 | 30 | time.Sleep(50 * time.Millisecond) 31 | } 32 | 33 | <-waiter 34 | } 35 | 36 | // 测试长连接数量 37 | func testConn() { 38 | conn, err := net.Dial("tcp", Host) 39 | 40 | if err != nil { 41 | fmt.Println(err) 42 | return 43 | } 44 | conut++ 45 | fmt.Printf("connected: %d\n", conut) 46 | reader := bufio.NewReader(conn) 47 | for { 48 | if line, _, err := reader.ReadLine(); err == nil { 49 | fmt.Println(string(line)) 50 | } 51 | } 52 | } 53 | 54 | // 测试tcp发送和接收 55 | func testTcp() { 56 | conn, err := net.Dial("tcp", Host) 57 | 58 | if err != nil { 59 | fmt.Println(err) 60 | return 61 | } 62 | defer conn.Close() 63 | reader := bufio.NewReader(conn) 64 | writer := bufio.NewWriter(conn) 65 | 66 | recv := make(chan string) 67 | 68 | go func() { 69 | for { 70 | if line, _, err := reader.ReadLine(); err == nil { 71 | var str string 72 | str = string(line) 73 | fmt.Println(str) 74 | recv <- str 75 | } else { 76 | os.Exit(0) 77 | } 78 | } 79 | }() 80 | 81 | for { 82 | // 收到消息,然后再回复 83 | line := <-recv 84 | if line != "" { 85 | line = "{\"command\":\"TEST_TCP\",\"data\":null}" 86 | } 87 | time.Sleep(120 * time.Second) 88 | 89 | writer.WriteString(string(line) + "\n") 90 | err := writer.Flush() 91 | if err != nil { 92 | os.Exit(0) 93 | } 94 | } 95 | } 96 | 97 | // 测试转发,以及数据库能力 98 | func test(sender string, token string, receiver string) { 99 | conn, err := net.Dial("tcp", Host) 100 | 101 | if err != nil { 102 | fmt.Println(err) 103 | return 104 | } 105 | defer conn.Close() 106 | reader := bufio.NewReader(conn) 107 | writer := bufio.NewWriter(conn) 108 | 109 | recv := make(chan string) 110 | 111 | go func() { 112 | for { 113 | if line, _, err := reader.ReadLine(); err == nil { 114 | var str string 115 | str = string(line) 116 | fmt.Println(str) 117 | recv <- str 118 | } else { 119 | os.Exit(0) 120 | } 121 | } 122 | }() 123 | 124 | var res IMResponse 125 | var ticket string 126 | for { 127 | // 收到消息,然后再回复 128 | line := <-recv 129 | if line != "" { 130 | json.Unmarshal([]byte(line), &res) 131 | switch res.Refer { 132 | case "GET_KEY_RETURN": 133 | // 建立连接 134 | line = "{\"command\":\"GET_CONN\",\"data\":{\"user\":{\"id\":\"" + sender + "\",\"token\":\"" + token + "\",\"key\":\"" + res.Data["conn"]["key"].(string) + "\"}}}" 135 | case "GET_CONN_RETURN": 136 | // 创建会话 137 | line = "{\"command\":\"CREATE_SESSION\",\"data\":{\"session\":{\"sender\":\"" + sender + "\",\"receiver\":\"" + receiver + "\",\"token\":\"" + token + "\"}}}" 138 | 139 | case "CREATE_SESSION_RETURN": 140 | // 发送消息 141 | ticket = res.Data["session"]["ticket"].(string) 142 | line = "{\"command\":\"SEND_MSG\",\"data\":{\"message\":{\"content\":\"Hello World\",\"ticket\":\"" + ticket + "\",\"token\":\"" + token + "\"}}}" 143 | 144 | case "PUSH_MSG": 145 | // 发送消息 146 | line = "{\"command\":\"SEND_MSG\",\"data\":{\"message\":{\"content\":\"Hello World\",\"ticket\":\"" + res.Data["session"]["ticket"].(string) + "\",\"token\":\"" + token + "\"}}}" 147 | 148 | time.Sleep(30 * time.Second) 149 | } 150 | } 151 | writer.WriteString(string(line) + "\n") 152 | err := writer.Flush() 153 | if err != nil { 154 | os.Exit(0) 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /test/test.ini: -------------------------------------------------------------------------------- 1 | [user] 2 | name=fuck 3 | age=xx 4 | [db] 5 | host=127.0.0.1 --------------------------------------------------------------------------------