├── .gitignore ├── LICENSE ├── README.md ├── account.go ├── cmq_client.go ├── cmq_http.go ├── cmq_tool.go ├── message.go ├── queue.go ├── queue_meta.go ├── sign.go ├── subscription.go ├── subscription_meta.go ├── test ├── cmq_queue_test.go └── cmq_topic_test.go ├── topic.go └── topic_meta.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 netkiddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cmq-go 2 | Tencent CMQ Golang SDK 3 | 4 | 与腾讯云官方SDK接口保持一致 5 | 6 | ## Getting Started 7 | 8 | 所有的API中都有[test case](https://github.com/NETkiddy/cmq-go/tree/master/test),这里使用[CreateQueue](https://github.com/NETkiddy/cmq-go/blob/master/test/cmq_queue_test.go#L15)举例。 9 | ``` 10 | var secretId = "YourTencentSecretId" 11 | var secretKey = "YourTencentSecretKey" 12 | var endpointQueue = "https://cmq-queue-sh.api.qcloud.com" 13 | var endpointQueueInner = "http://cmq-queue-sh.api.tencentyun.com" 14 | 15 | // 创建队列 16 | func Test_CreateQueue(t *testing.T) { 17 | //创建账户 18 | account := cmq_go.NewAccount(endpointQueue, secretId, secretKey) 19 | 20 | //设置队列metadata 21 | meta := cmq_go.QueueMeta{} 22 | meta.PollingWaitSeconds = 10 23 | meta.VisibilityTimeout = 10 24 | meta.MaxMsgSize = 1048576 25 | meta.MsgRetentionSeconds = 345600 26 | 27 | //创建队列queue-test-001 28 | err := account.CreateQueue("queue-test-001", meta) 29 | if err != nil { 30 | t.Errorf("queue-test-001 created failed, %v", err.Error()) 31 | return 32 | } 33 | t.Log("queue-test-001 created") 34 | 35 | //创建队列queue-test-002 36 | err = account.CreateQueue("queue-test-002", meta) 37 | if err != nil { 38 | t.Errorf("queue-test-002 created failed, %v", err.Error()) 39 | return 40 | } 41 | t.Log("queue-test-002 created") 42 | } 43 | ``` 44 | 45 | ## Test Case 46 | 47 | ``` 48 | 测试单个方法 49 | go test -v -test.run Test_CreateQueue 50 | ``` 51 | 52 | 53 | ## API Status 54 | ### 队列模型 55 | #### 队列相关接口 56 | - [x] CreateQueue 57 | - [x] ListQueue 58 | - [x] GetQueueAttributes 59 | - [x] SetQueueAttributes 60 | - [x] DeleteQueue 61 | 62 | #### 消息相关接口 63 | - [x] SendMessage 64 | - [x] BatchSendMessage 65 | - [x] ReceiveMessage 66 | - [x] BatchReceiveMessage 67 | - [x] DeleteMessage 68 | - [x] BatchDeleteMessage 69 | 70 | ### 主题模型 71 | #### 主题相关接口 72 | - [x] CreateTopic 73 | - [x] SetTopicAttributes 74 | - [x] ListTopic 75 | - [x] GetTopicAttributes 76 | - [x] DeleteTopic 77 | 78 | #### 消息相关接口 79 | - [x] PublishMessage 80 | - [x] BatchPublishMessage 81 | 82 | #### 订阅相关接口 83 | - [x] Subscribe 84 | - [x] ListSubscriptionByTopic 85 | - [x] SetSubscriptionAttributes 86 | - [x] GetSubscriptionAttributes 87 | - [x] Unsubscribe 88 | 89 | -------------------------------------------------------------------------------- /account.go: -------------------------------------------------------------------------------- 1 | package cmq_go 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "encoding/json" 7 | "net/http" 8 | ) 9 | 10 | const ( 11 | DEFAULT_ERROR_CODE = -1 12 | ) 13 | 14 | type Account struct { 15 | client *CMQClient 16 | } 17 | 18 | func NewAccount(endpoint, secretId, secretKey string) *Account { 19 | return &Account{ 20 | client: NewCMQClient(endpoint, "/v2/index.php", secretId, secretKey, "POST"), 21 | } 22 | } 23 | 24 | func (this *Account) SetProxy(proxyUrl string) *Account{ 25 | this.client.setProxy(proxyUrl) 26 | return this 27 | } 28 | 29 | func (this *Account) UnsetProxy() *Account{ 30 | this.client.unsetProxy() 31 | return this 32 | } 33 | 34 | func (this *Account) SetTransport(transport http.RoundTripper) *Account{ 35 | this.client.CmqHttp.SetTransport(transport) 36 | return this 37 | } 38 | 39 | func (this *Account) setSignMethod(method string) (err error) { 40 | return this.client.setSignMethod(method) 41 | } 42 | 43 | func (this *Account) CreateQueue(queueName string, queueMeta QueueMeta) (err error, code int) { 44 | code = DEFAULT_ERROR_CODE 45 | param := make(map[string]string) 46 | if queueName == "" { 47 | err = fmt.Errorf("createQueue failed: queueName is empty") 48 | //log.Printf("%v", err.Error()) 49 | return 50 | } 51 | param["queueName"] = queueName 52 | if queueMeta.MaxMsgHeapNum > 0 { 53 | param["maxMsgHeapNum"] = strconv.Itoa(queueMeta.MaxMsgHeapNum) 54 | } 55 | if queueMeta.PollingWaitSeconds > 0 { 56 | param["pollingWaitSeconds"] = strconv.Itoa(queueMeta.PollingWaitSeconds) 57 | } 58 | if queueMeta.VisibilityTimeout > 0 { 59 | param["visibilityTimeout"] = strconv.Itoa(queueMeta.VisibilityTimeout) 60 | } 61 | if queueMeta.MaxMsgSize > 0 { 62 | param["maxMsgSize"] = strconv.Itoa(queueMeta.MaxMsgSize) 63 | } 64 | if queueMeta.MsgRetentionSeconds > 0 { 65 | param["msgRetentionSeconds"] = strconv.Itoa(queueMeta.MsgRetentionSeconds) 66 | } 67 | if queueMeta.RewindSeconds > 0 { 68 | param["rewindSeconds"] = strconv.Itoa(queueMeta.RewindSeconds) 69 | } 70 | 71 | _, err, code = doCall(this.client, param, "CreateQueue") 72 | if err != nil { 73 | //log.Printf("client.call CreateQueue failed: %v\n", err.Error()) 74 | return 75 | } 76 | return 77 | } 78 | 79 | func (this *Account) DeleteQueue(queueName string) (err error, code int) { 80 | code = DEFAULT_ERROR_CODE 81 | param := make(map[string]string) 82 | if queueName == "" { 83 | err = fmt.Errorf("deleteQueue failed: queueName is empty") 84 | //log.Printf("%v", err.Error()) 85 | return 86 | } 87 | param["queueName"] = queueName 88 | 89 | _, err, code = doCall(this.client, param, "DeleteQueue") 90 | if err != nil { 91 | //log.Printf("client.call DeleteQueue failed: %v\n", err.Error()) 92 | return 93 | } 94 | return 95 | } 96 | 97 | func (this *Account) ListQueue(searchWord string, offset, limit int) ( 98 | totalCount int, queueList []string, err error, code int) { 99 | code = DEFAULT_ERROR_CODE 100 | queueList = make([]string, 0) 101 | param := make(map[string]string) 102 | if searchWord != "" { 103 | param["searchWord"] = searchWord 104 | } 105 | if offset >= 0 { 106 | param["offset"] = strconv.Itoa(offset) 107 | } 108 | if limit > 0 { 109 | param["limit"] = strconv.Itoa(limit) 110 | } 111 | 112 | resMap, err, code := doCall(this.client, param, "ListQueue") 113 | if err != nil { 114 | //log.Printf("client.call ListQueue failed: %v\n", err.Error()) 115 | return 116 | } 117 | totalCount = int(resMap["totalCount"].(float64)) 118 | resQueueList := resMap["queueList"].([]interface{}) 119 | for _, v := range resQueueList { 120 | queue := v.(map[string]interface{}) 121 | queueList = append(queueList, queue["queueName"].(string)) 122 | } 123 | return 124 | } 125 | 126 | func (this *Account) GetQueue(queueName string) (queue *Queue) { 127 | return NewQueue(queueName, this.client) 128 | } 129 | 130 | func (this *Account) GetTopic(topicName string) (topic *Topic) { 131 | return NewTopic(topicName, this.client) 132 | } 133 | 134 | func (this *Account) CreateTopic(topicName string, maxMsgSize int) (err error, code int) { 135 | err, code = _createTopic(this.client, topicName, maxMsgSize, 1) 136 | return 137 | } 138 | 139 | func _createTopic(client *CMQClient, topicName string, maxMsgSize, filterType int) (err error, code int) { 140 | code = DEFAULT_ERROR_CODE 141 | param := make(map[string]string) 142 | if topicName == "" { 143 | err = fmt.Errorf("createTopic failed: topicName is empty") 144 | //log.Printf("%v", err.Error()) 145 | return 146 | } 147 | param["topicName"] = topicName 148 | param["filterType"] = strconv.Itoa(filterType) 149 | if maxMsgSize < 1024 || maxMsgSize > 1048576 { 150 | err = fmt.Errorf("createTopic failed: Invalid parameter: maxMsgSize > 1024KB or maxMsgSize < 1KB") 151 | //log.Printf("%v", err.Error()) 152 | return 153 | } 154 | param["maxMsgSize"] = strconv.Itoa(maxMsgSize) 155 | 156 | _, err, code = doCall(client, param, "CreateTopic") 157 | if err != nil { 158 | //log.Printf("client.call CreateTopic failed: %v\n", err.Error()) 159 | return 160 | } 161 | return 162 | } 163 | 164 | func (this *Account) DeleteTopic(topicName string) (err error, code int) { 165 | code = DEFAULT_ERROR_CODE 166 | param := make(map[string]string) 167 | if topicName == "" { 168 | err = fmt.Errorf("deleteTopic failed: topicName is empty") 169 | //log.Printf("%v", err.Error()) 170 | return 171 | } 172 | param["topicName"] = topicName 173 | 174 | _, err, code = doCall(this.client, param, "DeleteTopic") 175 | if err != nil { 176 | //log.Printf("client.call DeleteTopic failed: %v\n", err.Error()) 177 | return 178 | } 179 | return 180 | } 181 | 182 | func (this *Account) ListTopic(searchWord string, offset, limit int) ( 183 | totalCount int, topicList []string, err error, code int) { 184 | code = DEFAULT_ERROR_CODE 185 | topicList = make([]string, 0) 186 | param := make(map[string]string) 187 | if searchWord != "" { 188 | param["searchWord"] = searchWord 189 | } 190 | if offset > 0 { 191 | param["offset"] = strconv.Itoa(offset) 192 | } 193 | if limit > 0 { 194 | param["limit"] = strconv.Itoa(limit) 195 | } 196 | 197 | resMap, err, code := doCall(this.client, param, "ListTopic") 198 | if err != nil { 199 | //log.Printf("client.call ListTopic failed: %v\n", err.Error()) 200 | return 201 | } 202 | totalCount = int(resMap["totalCount"].(float64)) 203 | resTopicList := resMap["topicList"].([]interface{}) 204 | for _, v := range resTopicList { 205 | topic := v.(map[string]interface{}) 206 | topicList = append(topicList, topic["topicName"].(string)) 207 | } 208 | return 209 | } 210 | 211 | func (this *Account) CreateSubscribe(topicName, subscriptionName, endpoint, protocol, notifyContentFormat string) ( 212 | err error, code int) { 213 | err, code = _createSubscribe( 214 | this.client, topicName, subscriptionName, endpoint, protocol, nil, nil, 215 | NotifyStrategyDefault, notifyContentFormat) 216 | return 217 | } 218 | 219 | func _createSubscribe(client *CMQClient, topicName, subscriptionName, endpoint, protocol string, filterTag []string, 220 | bindingKey []string, notifyStrategy, notifyContentFormat string) (err error, code int) { 221 | code = DEFAULT_ERROR_CODE 222 | param := make(map[string]string) 223 | if topicName == "" { 224 | err = fmt.Errorf("createSubscribe failed: topicName is empty") 225 | //log.Printf("%v", err.Error()) 226 | return 227 | } 228 | param["topicName"] = topicName 229 | 230 | if subscriptionName == "" { 231 | err = fmt.Errorf("createSubscribe failed: subscriptionName is empty") 232 | //log.Printf("%v", err.Error()) 233 | return 234 | } 235 | param["subscriptionName"] = subscriptionName 236 | 237 | if endpoint == "" { 238 | err = fmt.Errorf("createSubscribe failed: endpoint is empty") 239 | //log.Printf("%v", err.Error()) 240 | return 241 | } 242 | param["endpoint"] = endpoint 243 | 244 | if protocol == "" { 245 | err = fmt.Errorf("createSubscribe failed: protocal is empty") 246 | //log.Printf("%v", err.Error()) 247 | return 248 | } 249 | param["protocol"] = protocol 250 | 251 | if notifyStrategy == "" { 252 | err = fmt.Errorf("createSubscribe failed: notifyStrategy is empty") 253 | //log.Printf("%v", err.Error()) 254 | return 255 | } 256 | param["notifyStrategy"] = notifyStrategy 257 | 258 | if notifyContentFormat == "" { 259 | err = fmt.Errorf("createSubscribe failed: notifyContentFormat is empty") 260 | //log.Printf("%v", err.Error()) 261 | return 262 | } 263 | param["notifyContentFormat"] = notifyContentFormat 264 | 265 | if filterTag != nil { 266 | for i, tag := range filterTag { 267 | param["filterTag."+strconv.Itoa(i+1)] = tag 268 | } 269 | } 270 | 271 | if bindingKey != nil { 272 | for i, key := range bindingKey { 273 | param["bindingKey."+strconv.Itoa(i+1)] = key 274 | } 275 | } 276 | 277 | _, err, code = doCall(client, param, "Subscribe") 278 | if err != nil { 279 | //log.Printf("client.call Subscribe failed: %v\n", err.Error()) 280 | return 281 | } 282 | return 283 | } 284 | 285 | func (this *Account) DeleteSubscribe(topicName, subscriptionName string) (err error, code int) { 286 | code = DEFAULT_ERROR_CODE 287 | param := make(map[string]string) 288 | if topicName == "" { 289 | err = fmt.Errorf("createSubscribe failed: topicName is empty") 290 | //log.Printf("%v", err.Error()) 291 | return 292 | } 293 | param["topicName"] = topicName 294 | 295 | if subscriptionName == "" { 296 | err = fmt.Errorf("createSubscribe failed: subscriptionName is empty") 297 | //log.Printf("%v", err.Error()) 298 | return 299 | } 300 | param["subscriptionName"] = subscriptionName 301 | 302 | _, err, code = doCall(this.client, param, "Unsubscribe") 303 | if err != nil { 304 | //log.Printf("client.call Unsubscribe failed: %v\n", err.Error()) 305 | return 306 | } 307 | return 308 | } 309 | 310 | func (this *Account) GetSubscription(topicName, subscriptionName string) *Subscription { 311 | return NewSubscription(topicName, subscriptionName, this.client) 312 | } 313 | 314 | func doCall(client *CMQClient, param map[string]string, opration string) (resMap map[string]interface{}, err error, code int) { 315 | code = DEFAULT_ERROR_CODE 316 | res, err := client.call(opration, param) 317 | if err != nil { 318 | //log.Printf("client.call %v failed: %v\n", opration, err.Error()) 319 | return 320 | } 321 | //log.Printf("res: %v", res) 322 | 323 | resMap = make(map[string]interface{}, 0) 324 | err = json.Unmarshal([]byte(res), &resMap) 325 | if err != nil { 326 | //log.Printf(err.Error()) 327 | return 328 | } 329 | code = int(resMap["code"].(float64)) 330 | if code != 0 { 331 | err = fmt.Errorf("%v failed: code: %v, message: %v, requestId: %v", opration, code, resMap["message"], resMap["requestId"]) 332 | //log.Printf(err.Error()) 333 | return 334 | } 335 | 336 | return 337 | } 338 | -------------------------------------------------------------------------------- /cmq_client.go: -------------------------------------------------------------------------------- 1 | package cmq_go 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | "strings" 8 | "net/url" 9 | "strconv" 10 | "sort" 11 | ) 12 | 13 | const ( 14 | CURRENT_VERSION = "SDK_GO_1.3" 15 | ) 16 | 17 | type CMQClient struct { 18 | Endpoint string 19 | Path string 20 | SecretId string 21 | SecretKey string 22 | Method string 23 | SignMethod string 24 | CmqHttp *CMQHttp 25 | } 26 | 27 | func NewCMQClient(endpoint, path, secretId, secretKey, method string) *CMQClient { 28 | return &CMQClient{ 29 | Endpoint: endpoint, 30 | Path: path, 31 | SecretId: secretId, 32 | SecretKey: secretKey, 33 | Method: method, 34 | SignMethod: "sha1", 35 | CmqHttp: NewCMQHttp(), 36 | } 37 | } 38 | 39 | func (this *CMQClient) setSignMethod(signMethod string) (err error) { 40 | if signMethod != "sha1" && signMethod != "sha256" { 41 | err = fmt.Errorf("Only support sha1 or sha256 now") 42 | return 43 | } else { 44 | this.SignMethod = signMethod 45 | } 46 | return 47 | } 48 | 49 | func (this *CMQClient) setProxy(proxyUrl string) { 50 | this.CmqHttp.setProxy(proxyUrl) 51 | return 52 | } 53 | 54 | func (this *CMQClient) unsetProxy() { 55 | this.CmqHttp.unsetProxy() 56 | return 57 | } 58 | 59 | func (this *CMQClient) call(action string, param map[string]string) (resp string, err error) { 60 | param["Action"] = action 61 | param["Nonce"] = strconv.Itoa(rand.Int()) 62 | param["SecretId"] = this.SecretId 63 | param["Timestamp"] = strconv.FormatInt(time.Now().Unix(), 10) 64 | param["RequestClient"] = CURRENT_VERSION 65 | if this.SignMethod == "sha256" { 66 | param["SignatureMethod"] = "HmacSHA256" 67 | } else { 68 | param["SignatureMethod"] = "HmacSHA1" 69 | } 70 | sortedParamKeys := make([]string, 0) 71 | for k, _ := range param { 72 | sortedParamKeys = append(sortedParamKeys, k) 73 | } 74 | sort.Strings(sortedParamKeys) 75 | 76 | host := "" 77 | if strings.HasPrefix(this.Endpoint, "https") { 78 | host = this.Endpoint[8:] 79 | } else { 80 | host = this.Endpoint[7:] 81 | } 82 | 83 | src := this.Method + host + this.Path + "?" 84 | flag := false 85 | for _, key := range sortedParamKeys { 86 | if flag { 87 | src += "&" 88 | } 89 | src += key + "=" + param[key] 90 | flag = true 91 | } 92 | param["Signature"] = Sign(src, this.SecretKey, this.SignMethod) 93 | urlStr := "" 94 | reqStr := "" 95 | if this.Method == "GET" { 96 | urlStr = this.Endpoint + this.Path + "?" 97 | flag = false 98 | for k, v := range param { 99 | if flag { 100 | urlStr += "&" 101 | } 102 | urlStr += k + "=" + url.QueryEscape(v) 103 | flag = true 104 | } 105 | if len(urlStr) > 2048 { 106 | err = fmt.Errorf("url string length is large than 2048") 107 | return 108 | } 109 | } else { 110 | urlStr = this.Endpoint + this.Path 111 | flag := false 112 | for k, v := range param { 113 | if flag { 114 | reqStr += "&" 115 | } 116 | reqStr += k + "=" + url.QueryEscape(v) 117 | flag = true 118 | } 119 | } 120 | //log.Printf("urlStr :%v", urlStr) 121 | //log.Printf("reqStr :%v", reqStr) 122 | 123 | userTimeout := 0 124 | if UserpollingWaitSeconds, found := param["UserpollingWaitSeconds"]; found { 125 | userTimeout, err = strconv.Atoi(UserpollingWaitSeconds) 126 | if err != nil { 127 | return "", fmt.Errorf("strconv failed: %v", err.Error()) 128 | } 129 | } 130 | 131 | resp, err = this.CmqHttp.request(this.Method, urlStr, reqStr, userTimeout) 132 | if err != nil { 133 | return resp, fmt.Errorf("CmqHttp.request failed: %v", err.Error()) 134 | } 135 | return 136 | } 137 | -------------------------------------------------------------------------------- /cmq_http.go: -------------------------------------------------------------------------------- 1 | package cmq_go 2 | 3 | import ( 4 | "time" 5 | "net/http" 6 | "fmt" 7 | "io/ioutil" 8 | "net/url" 9 | "bytes" 10 | "errors" 11 | "net" 12 | ) 13 | 14 | const ( 15 | DEFAULT_HTTP_TIMEOUT = 3000 //ms 16 | ) 17 | 18 | type CMQHttp struct { 19 | isKeepAlive bool 20 | conn *http.Client 21 | } 22 | 23 | var DefaultTransport = &http.Transport{ 24 | Proxy: http.ProxyFromEnvironment, 25 | DialContext: (&net.Dialer{ 26 | Timeout: 30 * time.Second, 27 | KeepAlive: 30 * time.Second, 28 | DualStack: true, 29 | }).DialContext, 30 | MaxIdleConns: 500, 31 | MaxIdleConnsPerHost: 100, 32 | IdleConnTimeout: 90 * time.Second, 33 | TLSHandshakeTimeout: 10 * time.Second, 34 | ExpectContinueTimeout: 1 * time.Second, 35 | } 36 | 37 | func NewCMQHttp() *CMQHttp { 38 | return &CMQHttp{ 39 | isKeepAlive: true, 40 | conn: &http.Client{ 41 | Timeout: DEFAULT_HTTP_TIMEOUT * time.Millisecond, 42 | Transport: DefaultTransport, 43 | }, 44 | } 45 | 46 | } 47 | 48 | func (this *CMQHttp) setProxy(proxyUrlStr string) (err error) { 49 | if proxyUrlStr == "" { 50 | return 51 | } 52 | proxyUrl, err := url.Parse(proxyUrlStr) 53 | if err != nil { 54 | return 55 | } 56 | transport, err := this.getTransport() 57 | if err != nil { 58 | return 59 | } 60 | transport.Proxy = http.ProxyURL(proxyUrl) 61 | return 62 | } 63 | 64 | func (this *CMQHttp) unsetProxy() (err error) { 65 | transport, err := this.getTransport() 66 | if err != nil { 67 | return 68 | } 69 | transport.Proxy = nil 70 | return 71 | } 72 | 73 | func (this *CMQHttp) getTransport() (*http.Transport, error) { 74 | if this.conn.Transport == nil { 75 | this.SetTransport(DefaultTransport) 76 | } 77 | 78 | if transport, ok := this.conn.Transport.(*http.Transport); ok { 79 | return transport, nil 80 | } 81 | return nil, errors.New("transport is not an *http.Transport instance") 82 | } 83 | 84 | func (this *CMQHttp) SetTransport(transport http.RoundTripper) { 85 | this.conn.Transport = transport 86 | } 87 | 88 | func (this *CMQHttp) request(method, urlStr, reqStr string, userTimeout int) (result string, err error) { 89 | timeout := DEFAULT_HTTP_TIMEOUT 90 | if userTimeout >= 0 { 91 | timeout += userTimeout 92 | } 93 | this.conn.Timeout = time.Duration(timeout) * time.Millisecond 94 | 95 | req, err := http.NewRequest(method, urlStr, bytes.NewReader([]byte(reqStr))) 96 | if err != nil { 97 | return "", fmt.Errorf("make http req error %v", err) 98 | } 99 | resp, err := this.conn.Do(req) 100 | if err != nil { 101 | return "", fmt.Errorf("http error %v", err) 102 | } 103 | defer resp.Body.Close() 104 | if resp.StatusCode != http.StatusOK { 105 | return "", fmt.Errorf("http error code %d", resp.StatusCode) 106 | } 107 | body, err := ioutil.ReadAll(resp.Body) 108 | if err != nil { 109 | return "", fmt.Errorf("read http resp body error %v", err) 110 | } 111 | result = string(body) 112 | return 113 | } 114 | -------------------------------------------------------------------------------- /cmq_tool.go: -------------------------------------------------------------------------------- 1 | package cmq_go 2 | 3 | type CMQTool struct { 4 | } 5 | 6 | -------------------------------------------------------------------------------- /message.go: -------------------------------------------------------------------------------- 1 | package cmq_go 2 | 3 | type Message struct { 4 | /** 服务器返回的消息ID */ 5 | MsgId string 6 | /** 每次消费唯一的消息句柄,用于删除等操作 */ 7 | ReceiptHandle string 8 | /** 消息体 */ 9 | MsgBody string 10 | /** 消息发送到队列的时间,从 1970年1月1日 00:00:00 000 开始的毫秒数 */ 11 | EnqueueTime int64 12 | /** 消息下次可见的时间,从 1970年1月1日 00:00:00 000 开始的毫秒数 */ 13 | NextVisibleTime int64 14 | /** 消息第一次出队列的时间,从 1970年1月1日 00:00:00 000 开始的毫秒数 */ 15 | FirstDequeueTime int64 16 | /** 出队列次数 */ 17 | DequeueCount int 18 | MsgTag []string 19 | } 20 | -------------------------------------------------------------------------------- /queue.go: -------------------------------------------------------------------------------- 1 | package cmq_go 2 | 3 | import ( 4 | "strconv" 5 | "fmt" 6 | ) 7 | 8 | type Queue struct { 9 | queueName string 10 | client *CMQClient 11 | } 12 | 13 | func NewQueue(queueName string, client *CMQClient) (queue *Queue) { 14 | return &Queue{ 15 | queueName: queueName, 16 | client: client, 17 | } 18 | } 19 | 20 | func (this *Queue) SetQueueAttributes(queueMeta QueueMeta) (err error, code int) { 21 | code = DEFAULT_ERROR_CODE 22 | param := make(map[string]string) 23 | param["queueName"] = this.queueName 24 | 25 | if queueMeta.MaxMsgHeapNum > 0 { 26 | param["maxMsgHeapNum"] = strconv.Itoa(queueMeta.MaxMsgHeapNum) 27 | } 28 | if queueMeta.PollingWaitSeconds > 0 { 29 | param["pollingWaitSeconds"] = strconv.Itoa(queueMeta.PollingWaitSeconds) 30 | } 31 | if queueMeta.VisibilityTimeout > 0 { 32 | param["visibilityTimeout"] = strconv.Itoa(queueMeta.VisibilityTimeout) 33 | } 34 | if queueMeta.MaxMsgSize > 0 { 35 | param["maxMsgSize"] = strconv.Itoa(queueMeta.MaxMsgSize) 36 | } 37 | if queueMeta.MsgRetentionSeconds > 0 { 38 | param["msgRetentionSeconds"] = strconv.Itoa(queueMeta.MsgRetentionSeconds) 39 | } 40 | if queueMeta.RewindSeconds > 0 { 41 | param["rewindSeconds"] = strconv.Itoa(queueMeta.RewindSeconds) 42 | } 43 | 44 | _, err, code = doCall(this.client, param, "SetQueueAttributes") 45 | if err != nil { 46 | //log.Printf("client.call SetQueueAttributes failed: %v\n", err.Error()) 47 | return 48 | } 49 | return 50 | } 51 | 52 | func (this *Queue) GetQueueAttributes() (queueMeta QueueMeta, err error, code int) { 53 | code = DEFAULT_ERROR_CODE 54 | param := make(map[string]string) 55 | param["queueName"] = this.queueName 56 | 57 | resMap, err, code := doCall(this.client, param, "GetQueueAttributes") 58 | if err != nil { 59 | //log.Printf("client.call GetQueueAttributes failed: %v\n", err.Error()) 60 | return 61 | } 62 | 63 | queueMeta.MaxMsgHeapNum = int(resMap["maxMsgHeapNum"].(float64)) 64 | queueMeta.PollingWaitSeconds = int(resMap["pollingWaitSeconds"].(float64)) 65 | queueMeta.VisibilityTimeout = int(resMap["visibilityTimeout"].(float64)) 66 | queueMeta.MaxMsgSize = int(resMap["maxMsgSize"].(float64)) 67 | queueMeta.MsgRetentionSeconds = int(resMap["msgRetentionSeconds"].(float64)) 68 | queueMeta.CreateTime = int(resMap["createTime"].(float64)) 69 | queueMeta.LastModifyTime = int(resMap["lastModifyTime"].(float64)) 70 | queueMeta.ActiveMsgNum = int(resMap["activeMsgNum"].(float64)) 71 | queueMeta.InactiveMsgNum = int(resMap["inactiveMsgNum"].(float64)) 72 | queueMeta.RewindMsgNum = int(resMap["rewindMsgNum"].(float64)) 73 | queueMeta.MinMsgTime = int(resMap["minMsgTime"].(float64)) 74 | queueMeta.DelayMsgNum = int(resMap["delayMsgNum"].(float64)) 75 | queueMeta.RewindSeconds = int(resMap["rewindSeconds"].(float64)) 76 | 77 | return 78 | } 79 | 80 | func (this *Queue) SendMessage(msgBody string) (messageId string, err error, code int) { 81 | messageId, err, code = _sendMessage(this.client, msgBody, this.queueName, 0) 82 | return 83 | } 84 | 85 | func (this *Queue) SendDelayMessage(msgBody string, delaySeconds int) (messageId string, err error, code int) { 86 | messageId, err, code = _sendMessage(this.client, msgBody, this.queueName, delaySeconds) 87 | return 88 | } 89 | 90 | func _sendMessage(client *CMQClient, msgBody, queueName string, delaySeconds int) (messageId string, err error, code int) { 91 | code = DEFAULT_ERROR_CODE 92 | param := make(map[string]string) 93 | param["queueName"] = queueName 94 | param["msgBody"] = msgBody 95 | param["delaySeconds"] = strconv.Itoa(delaySeconds) 96 | 97 | resMap, err, code := doCall(client, param, "SendMessage") 98 | if err != nil { 99 | //log.Printf("client.call GetQueueAttributes failed: %v\n", err.Error()) 100 | return 101 | } 102 | 103 | messageId = resMap["msgId"].(string) 104 | return 105 | } 106 | 107 | func (this *Queue) BatchSendMessage(msgBodys []string) (messageIds []string, err error, code int) { 108 | messageIds, err, code = _batchSendMessage(this.client, msgBodys, this.queueName, 0) 109 | return 110 | } 111 | 112 | func (this *Queue) BatchSendDelayMessage(msgBodys []string, delaySeconds int) (messageIds []string, err error, code int) { 113 | messageIds, err, code = _batchSendMessage(this.client, msgBodys, this.queueName, delaySeconds) 114 | return 115 | } 116 | 117 | func _batchSendMessage(client *CMQClient, msgBodys []string, queueName string, delaySeconds int) (messageIds []string, err error, code int) { 118 | code = DEFAULT_ERROR_CODE 119 | messageIds = make([]string, 0) 120 | 121 | if len(msgBodys) == 0 || len(msgBodys) > 16 { 122 | err = fmt.Errorf("message size is 0 or more than 16") 123 | //log.Printf("%v", err.Error()) 124 | return 125 | } 126 | 127 | param := make(map[string]string) 128 | param["queueName"] = queueName 129 | for i, msgBody := range msgBodys { 130 | param["msgBody."+strconv.Itoa(i+1)] = msgBody 131 | } 132 | param["delaySeconds"] = strconv.Itoa(delaySeconds) 133 | 134 | resMap, err, code := doCall(client, param, "BatchSendMessage") 135 | if err != nil { 136 | //log.Printf("client.call BatchSendMessage failed: %v\n", err.Error()) 137 | return 138 | } 139 | 140 | msgList := resMap["msgList"].([]interface{}) 141 | for _, v := range msgList { 142 | msg := v.(map[string]interface{}) 143 | messageIds = append(messageIds, msg["msgId"].(string)) 144 | } 145 | return 146 | } 147 | 148 | func (this *Queue) ReceiveMessage(pollingWaitSeconds int) (msg Message, err error, code int) { 149 | code = DEFAULT_ERROR_CODE 150 | param := make(map[string]string) 151 | param["queueName"] = this.queueName 152 | if pollingWaitSeconds >= 0 { 153 | param["UserpollingWaitSeconds"] = strconv.Itoa(pollingWaitSeconds * 1000) 154 | param["pollingWaitSeconds"] = strconv.Itoa(pollingWaitSeconds) 155 | } else { 156 | param["UserpollingWaitSeconds"] = strconv.Itoa(30000) 157 | } 158 | 159 | resMap, err, code := doCall(this.client, param, "ReceiveMessage") 160 | if err != nil { 161 | //log.Printf("client.call ReceiveMessage failed: %v\n", err.Error()) 162 | return 163 | } 164 | 165 | msg.MsgId = resMap["msgId"].(string) 166 | msg.ReceiptHandle = resMap["receiptHandle"].(string) 167 | msg.MsgBody = resMap["msgBody"].(string) 168 | msg.EnqueueTime = int64(resMap["enqueueTime"].(float64)) 169 | msg.NextVisibleTime = int64(resMap["nextVisibleTime"].(float64)) 170 | msg.FirstDequeueTime = int64(resMap["firstDequeueTime"].(float64)) 171 | msg.DequeueCount = int(resMap["dequeueCount"].(float64)) 172 | 173 | return 174 | } 175 | 176 | func (this *Queue) BatchReceiveMessage(numOfMsg, pollingWaitSeconds int) (msgs []Message, err error, code int) { 177 | code = DEFAULT_ERROR_CODE 178 | msgs = make([]Message, 0) 179 | param := make(map[string]string) 180 | param["queueName"] = this.queueName 181 | param["numOfMsg"] = strconv.Itoa(numOfMsg) 182 | if pollingWaitSeconds >= 0 { 183 | param["UserpollingWaitSeconds"] = strconv.Itoa(pollingWaitSeconds * 1000) 184 | param["pollingWaitSeconds"] = strconv.Itoa(pollingWaitSeconds) 185 | } else { 186 | param["UserpollingWaitSeconds"] = strconv.Itoa(30000) 187 | } 188 | 189 | resMap, err, code := doCall(this.client, param, "BatchReceiveMessage") 190 | if err != nil { 191 | //log.Printf("client.call BatchReceiveMessage failed: %v\n", err.Error()) 192 | return 193 | } 194 | msgInfoList := resMap["msgInfoList"].([]interface{}) 195 | for _, v := range msgInfoList { 196 | msgInfo := v.(map[string]interface{}) 197 | msg := Message{} 198 | msg.MsgId = msgInfo["msgId"].(string) 199 | msg.ReceiptHandle = msgInfo["receiptHandle"].(string) 200 | msg.MsgBody = msgInfo["msgBody"].(string) 201 | msg.EnqueueTime = int64(msgInfo["enqueueTime"].(float64)) 202 | msg.NextVisibleTime = int64(msgInfo["nextVisibleTime"].(float64)) 203 | msg.FirstDequeueTime = int64(msgInfo["firstDequeueTime"].(float64)) 204 | msg.DequeueCount = int(msgInfo["dequeueCount"].(float64)) 205 | 206 | msgs = append(msgs, msg) 207 | } 208 | 209 | return 210 | } 211 | 212 | func (this *Queue) DeleteMessage(receiptHandle string) (err error, code int) { 213 | code = DEFAULT_ERROR_CODE 214 | param := make(map[string]string) 215 | param["queueName"] = this.queueName 216 | param["receiptHandle"] = receiptHandle 217 | 218 | _, err, code = doCall(this.client, param, "DeleteMessage") 219 | if err != nil { 220 | //log.Printf("client.call DeleteMessage failed: %v\n", err.Error()) 221 | return 222 | } 223 | return 224 | } 225 | 226 | func (this *Queue) BatchDeleteMessage(receiptHandles []string) (err error, code int) { 227 | code = DEFAULT_ERROR_CODE 228 | if len(receiptHandles) == 0 { 229 | return 230 | } 231 | param := make(map[string]string) 232 | param["queueName"] = this.queueName 233 | for i, receiptHandle := range receiptHandles { 234 | param["receiptHandle."+strconv.Itoa(i+1)] = receiptHandle 235 | } 236 | 237 | _, err, code = doCall(this.client, param, "BatchDeleteMessage") 238 | if err != nil { 239 | //log.Printf("client.call BatchDeleteMessage failed: %v\n", err.Error()) 240 | return 241 | } 242 | return 243 | } 244 | 245 | func (this *Queue) RewindQueue(backTrackingTime int) (err error, code int) { 246 | code = DEFAULT_ERROR_CODE 247 | if backTrackingTime <= 0 { 248 | return 249 | } 250 | param := make(map[string]string) 251 | param["queueName"] = this.queueName 252 | param["startConsumeTime"] = strconv.Itoa(backTrackingTime) 253 | 254 | _, err, code = doCall(this.client, param, "RewindQueue") 255 | if err != nil { 256 | //log.Printf("client.call RewindQueue failed: %v\n", err.Error()) 257 | return 258 | } 259 | return 260 | } 261 | -------------------------------------------------------------------------------- /queue_meta.go: -------------------------------------------------------------------------------- 1 | package cmq_go 2 | 3 | const ( 4 | /** 缺省消息接收长轮询等待时间 */ 5 | DEFAULT_POLLING_WAIT_SECONDS = 0 6 | /** 缺省消息可见性超时 */ 7 | DEFAULT_VISIBILITY_TIMEOUT = 30 8 | /** 缺省消息最大长度,单位字节 */ 9 | DEFAULT_MAX_MSG_SIZE = 1048576 10 | /** 缺省消息保留周期,单位秒 */ 11 | DEFAULT_MSG_RETENTION_SECONDS = 345600 12 | ) 13 | 14 | type QueueMeta struct { 15 | /** 最大堆积消息数 */ 16 | MaxMsgHeapNum int 17 | /** 消息接收长轮询等待时间 */ 18 | PollingWaitSeconds int 19 | /** 消息可见性超时 */ 20 | VisibilityTimeout int 21 | /** 消息最大长度 */ 22 | MaxMsgSize int 23 | /** 消息保留周期 */ 24 | MsgRetentionSeconds int 25 | /** 队列创建时间 */ 26 | CreateTime int 27 | /** 队列属性最后修改时间 */ 28 | LastModifyTime int 29 | /** 队列处于Active状态的消息总数 */ 30 | ActiveMsgNum int 31 | /** 队列处于Inactive状态的消息总数 */ 32 | InactiveMsgNum int 33 | /** 已删除的消息,但还在回溯保留时间内的消息数量 */ 34 | RewindMsgNum int 35 | /** 消息最小未消费时间 */ 36 | MinMsgTime int 37 | /** 延时消息数量 */ 38 | DelayMsgNum int 39 | /** 回溯时间 */ 40 | RewindSeconds int 41 | } 42 | -------------------------------------------------------------------------------- /sign.go: -------------------------------------------------------------------------------- 1 | package cmq_go 2 | 3 | import ( 4 | "crypto/sha1" 5 | "crypto/sha256" 6 | "hash" 7 | "crypto/hmac" 8 | "encoding/base64" 9 | ) 10 | 11 | const ( 12 | SIGN_ALGORITHM_SHA1 = "sha1" 13 | ) 14 | 15 | func Sign(src, key, method string) string{ 16 | var mac hash.Hash 17 | if method == SIGN_ALGORITHM_SHA1{ 18 | mac = hmac.New(sha1.New, []byte(key)) 19 | }else{ 20 | mac = hmac.New(sha256.New, []byte(key)) 21 | } 22 | 23 | mac.Write([]byte(src)) 24 | return base64.StdEncoding.EncodeToString(mac.Sum(nil)) 25 | } -------------------------------------------------------------------------------- /subscription.go: -------------------------------------------------------------------------------- 1 | package cmq_go 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | type Subscription struct { 8 | topicName string 9 | subscriptionName string 10 | client *CMQClient 11 | } 12 | 13 | func NewSubscription(topicName, subscriptionName string, client *CMQClient) *Subscription { 14 | return &Subscription{ 15 | topicName: topicName, 16 | subscriptionName: subscriptionName, 17 | client: client, 18 | } 19 | } 20 | 21 | func (this *Subscription) ClearFilterTags() (err error, code int) { 22 | code = DEFAULT_ERROR_CODE 23 | param := make(map[string]string) 24 | param["topicName"] = this.topicName 25 | param["subscriptionName "] = this.subscriptionName 26 | 27 | _, err, code = doCall(this.client, param, "ClearSubscriptionFilterTags") 28 | if err != nil { 29 | //log.Printf("client.call ClearSubscriptionFilterTags failed: %v\n", err.Error()) 30 | return 31 | } 32 | 33 | return 34 | } 35 | 36 | func (this *Subscription) SetSubscriptionAttributes(meta SubscriptionMeta) (err error, code int) { 37 | code = DEFAULT_ERROR_CODE 38 | param := make(map[string]string) 39 | param["topicName"] = this.topicName 40 | param["subscriptionName "] = this.subscriptionName 41 | if meta.NotifyStrategy != "" { 42 | param["notifyStrategy"] = meta.NotifyStrategy 43 | } 44 | if meta.NotifyContentFormat != "" { 45 | param["notifyContentFormat"] = meta.NotifyContentFormat 46 | } 47 | if meta.FilterTag != nil { 48 | for i, flag := range meta.FilterTag { 49 | param["filterTag."+strconv.Itoa(i+1)] = flag 50 | } 51 | } 52 | if meta.BindingKey != nil { 53 | for i, binding := range meta.BindingKey { 54 | param["bindingKey."+strconv.Itoa(i+1)] = binding 55 | } 56 | } 57 | 58 | _, err, code = doCall(this.client, param, "SetSubscriptionAttributes") 59 | if err != nil { 60 | //log.Printf("client.call SetSubscriptionAttributes failed: %v\n", err.Error()) 61 | return 62 | } 63 | 64 | return 65 | } 66 | 67 | func (this *Subscription) GetSubscriptionAttributes() (meta *SubscriptionMeta, err error, code int) { 68 | code = DEFAULT_ERROR_CODE 69 | param := make(map[string]string) 70 | param["topicName"] = this.topicName 71 | param["subscriptionName"] = this.subscriptionName 72 | 73 | resMap, err, code := doCall(this.client, param, "GetSubscriptionAttributes") 74 | if err != nil { 75 | //log.Printf("client.call GetSubscriptionAttributes failed: %v\n", err.Error()) 76 | return 77 | } 78 | 79 | meta = NewSubscriptionMeta() 80 | meta.FilterTag = make([]string, 0) 81 | meta.BindingKey = make([]string, 0) 82 | meta.TopicOwner = resMap["topicOwner"].(string) 83 | meta.Endpoint = resMap["endpoint"].(string) 84 | meta.Protocal = resMap["protocol"].(string) 85 | meta.NotifyStrategy = resMap["notifyStrategy"].(string) 86 | meta.NotifyContentFormat = resMap["notifyContentFormat"].(string) 87 | meta.CreateTime = int(resMap["createTime"].(float64)) 88 | meta.LastModifyTime = int(resMap["lastModifyTime"].(float64)) 89 | meta.MsgCount = int(resMap["msgCount"].(float64)) 90 | if filterTag, found := resMap["filterTag"]; found { 91 | for _, v := range filterTag.([]interface{}) { 92 | filter := v.(string) 93 | meta.FilterTag = append(meta.FilterTag, filter) 94 | } 95 | } 96 | if bindingKey, found := resMap["bindingKey"]; found { 97 | for _, v := range bindingKey.([]interface{}) { 98 | binding := v.(string) 99 | meta.BindingKey = append(meta.BindingKey, binding) 100 | } 101 | } 102 | 103 | return 104 | } 105 | -------------------------------------------------------------------------------- /subscription_meta.go: -------------------------------------------------------------------------------- 1 | package cmq_go 2 | 3 | const ( 4 | NotifyStrategyDefault = "BACKOFF_RETRY" 5 | NotifyContentFormatDefault = "JSON" 6 | NotifyContentFormatSimplified = "SIMPLIFIED" 7 | ) 8 | 9 | type SubscriptionMeta struct { 10 | //Subscription 订阅的主题所有者的appId 11 | TopicOwner string 12 | //订阅的终端地址 13 | Endpoint string 14 | //订阅的协议 15 | Protocal string 16 | //推送消息出现错误时的重试策略 17 | NotifyStrategy string 18 | //向 Endpoint 推送的消息内容格式 19 | NotifyContentFormat string 20 | //描述了该订阅中消息过滤的标签列表(仅标签一致的消息才会被推送) 21 | FilterTag []string 22 | //Subscription 的创建时间,从 1970-1-1 00:00:00 到现在的秒值 23 | CreateTime int 24 | //修改 Subscription 属性信息最近时间,从 1970-1-1 00:00:00 到现在的秒值 25 | LastModifyTime int 26 | //该订阅待投递的消息数 27 | MsgCount int 28 | BindingKey []string 29 | } 30 | 31 | func NewSubscriptionMeta() *SubscriptionMeta { 32 | return &SubscriptionMeta{ 33 | TopicOwner: "", 34 | Endpoint: "", 35 | Protocal: "", 36 | NotifyStrategy: NotifyStrategyDefault, 37 | NotifyContentFormat: NotifyContentFormatDefault, 38 | FilterTag: nil, 39 | CreateTime: 0, 40 | LastModifyTime: 0, 41 | MsgCount: 0, 42 | BindingKey: nil, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/cmq_queue_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | "github.com/NETkiddy/cmq-go" 6 | ) 7 | 8 | var secretId = "YourTencentSecretId" 9 | var secretKey = "YourTencentSecretKey" 10 | var endpointQueue = "https://cmq-queue-sh.api.qcloud.com" 11 | var endpointQueueInner = "http://cmq-queue-sh.api.tencentyun.com" 12 | 13 | //----------------------------------------------------------------- 14 | // 创建队列 15 | func Test_CreateQueue(t *testing.T) { 16 | account := cmq_go.NewAccount(endpointQueue, secretId, secretKey) 17 | meta := cmq_go.QueueMeta{} 18 | meta.PollingWaitSeconds = 10 19 | meta.VisibilityTimeout = 10 20 | meta.MaxMsgSize = 1048576 21 | meta.MsgRetentionSeconds = 345600 22 | 23 | err, _ := account.CreateQueue("queue-test-001", meta) 24 | if err != nil { 25 | t.Errorf("queue-test-001 created failed, %v", err.Error()) 26 | return 27 | } 28 | t.Log("queue-test-001 created") 29 | 30 | err, _ = account.CreateQueue("queue-test-002", meta) 31 | if err != nil { 32 | t.Errorf("queue-test-002 created failed, %v", err.Error()) 33 | return 34 | } 35 | t.Log("queue-test-002 created") 36 | } 37 | 38 | // 列出当前帐号下所有队列名字 39 | func Test_ListQueue(t *testing.T) { 40 | account := cmq_go.NewAccount(endpointQueue, secretId, secretKey) 41 | totalCount, queueList, err, _ := account.ListQueue("", -1, -1) 42 | if err != nil { 43 | t.Error(err) 44 | return 45 | } 46 | t.Logf("totalCount: %v", totalCount) 47 | t.Logf("queueList: %v", queueList) 48 | } 49 | 50 | // 删除队列 51 | func Test_DeleteQueue(t *testing.T) { 52 | account := cmq_go.NewAccount(endpointQueue, secretId, secretKey) 53 | err, _ := account.DeleteQueue("queue-test-002") 54 | if err != nil { 55 | t.Error(err) 56 | return 57 | } 58 | } 59 | 60 | // 获得队列实例 61 | func Test_GetQueue(t *testing.T) { 62 | account := cmq_go.NewAccount(endpointQueue, secretId, secretKey) 63 | queue := account.GetQueue("queue-test-001") 64 | t.Logf("GetQueue: %v", queue) 65 | } 66 | 67 | //----------------------------------------------------------------- 68 | // 设置队列属性 69 | func Test_SetQueueAttributes(t *testing.T) { 70 | account := cmq_go.NewAccount(endpointQueue, secretId, secretKey) 71 | meta := cmq_go.QueueMeta{} 72 | meta.PollingWaitSeconds = 10 73 | meta.VisibilityTimeout = 10 74 | meta.MaxMsgSize = 1048576 75 | meta.MsgRetentionSeconds = 345600 76 | 77 | queue := account.GetQueue("queue-test-001") 78 | 79 | err, _ := queue.SetQueueAttributes(meta) 80 | if err != nil { 81 | t.Error(err) 82 | return 83 | } 84 | } 85 | 86 | //----------------------------------------------------------------- 87 | // 获取队列属性 88 | func Test_GetQueueAttributes(t *testing.T) { 89 | account := cmq_go.NewAccount(endpointQueue, secretId, secretKey) 90 | queue := account.GetQueue("queue-test-001") 91 | newMeta, err, _ := queue.GetQueueAttributes() 92 | if err != nil { 93 | t.Error(err) 94 | return 95 | } 96 | t.Logf("GetQueueAttributes: %v", newMeta) 97 | } 98 | 99 | // 发送,接受,删除消息 100 | func Test_SendReceiveDeleteMessage(t *testing.T) { 101 | account := cmq_go.NewAccount(endpointQueue, secretId, secretKey) 102 | queue := account.GetQueue("queue-test-001") 103 | // send 104 | msgId, err, _ := queue.SendMessage("hello world") 105 | if err != nil { 106 | t.Error(err) 107 | return 108 | } 109 | t.Logf("SendMessage msgId: %v", msgId) 110 | // receive 111 | msg, err, code := queue.ReceiveMessage(10) 112 | if err != nil { 113 | if code == 7000{ 114 | t.Logf("no message") 115 | }else if code == 6070{ 116 | t.Logf("too many unacked(inactive messages or delayed messages)") 117 | }else { 118 | t.Error(err) 119 | return 120 | } 121 | } 122 | t.Logf("ReceiveMessage msgId: %v", msg.MsgId) 123 | // delete 124 | err, _ = queue.DeleteMessage(msg.ReceiptHandle) 125 | if err != nil { 126 | t.Error(err) 127 | return 128 | } 129 | t.Logf("DeleteMessage msgId: %v", msg.MsgId) 130 | } 131 | 132 | // 批量发送,接收,删除消息 133 | func Test_BatchSendReceiveDeleteMessage(t *testing.T) { 134 | account := cmq_go.NewAccount(endpointQueue, secretId, secretKey) 135 | queue := account.GetQueue("queue-test-001") 136 | // batch send 137 | msgBodys := []string{"hello world", "foo", "bar"} 138 | msgIds, err, _ := queue.BatchSendMessage(msgBodys) 139 | if err != nil { 140 | t.Error(err) 141 | return 142 | } 143 | t.Logf("BatchSendMessage msgId: %v", msgIds) 144 | // batch receive 145 | msgs, err, _ := queue.BatchReceiveMessage(10, 10) 146 | if err != nil { 147 | t.Error(err) 148 | return 149 | } 150 | handlers := make([]string, 0) 151 | msgIds = msgIds[0:0] 152 | for _, msg := range msgs { 153 | handlers = append(handlers, msg.ReceiptHandle) 154 | msgIds = append(msgIds, msg.MsgId) 155 | } 156 | t.Logf("BatchReceiveMessage msgIds: %v", msgIds) 157 | // batch delete 158 | err, _ = queue.BatchDeleteMessage(handlers) 159 | if err != nil { 160 | t.Error(err) 161 | return 162 | } 163 | t.Logf("BatchDeleteMessage msgId: %v", msgIds) 164 | } 165 | -------------------------------------------------------------------------------- /test/cmq_topic_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | "github.com/NETkiddy/cmq-go" 6 | ) 7 | 8 | var endpointTopic = "https://cmq-topic-sh.api.qcloud.com" 9 | var endpointTopicInner = "http://cmq-topic-sh.api.tencentyun.com" 10 | //----------------------------------------------------------------- 11 | // 创建主题 12 | func Test_CreateTopic(t *testing.T) { 13 | account := cmq_go.NewAccount(endpointTopic, secretId, secretKey) 14 | err, _ := account.CreateTopic("topic-test-001", 2048) 15 | if err != nil { 16 | t.Errorf("topic-test-001 created failed, %v", err.Error()) 17 | return 18 | } 19 | t.Log("topic-test-001 created") 20 | err, _ = account.CreateTopic("topic-test-002", 4096) 21 | if err != nil { 22 | t.Errorf("topic-test-002 created failed, %v", err.Error()) 23 | return 24 | } 25 | t.Log("topic-test-002 created") 26 | } 27 | 28 | // 删除主题 29 | func Test_ListTopic(t *testing.T) { 30 | account := cmq_go.NewAccount(endpointTopic, secretId, secretKey) 31 | totalCount, topicList, err, _ := account.ListTopic("", -1, -1) 32 | if err != nil { 33 | t.Errorf("ListTopic failed, %v", err.Error()) 34 | return 35 | } 36 | t.Logf("totalCount, %v", totalCount) 37 | t.Logf("topicList, %v", topicList) 38 | } 39 | 40 | // 获取主题 41 | func Test_GetTopic(t *testing.T) { 42 | account := cmq_go.NewAccount(endpointTopic, secretId, secretKey) 43 | topic := account.GetTopic("topic-test-001") 44 | t.Logf("GetTopic, %v", *topic) 45 | } 46 | 47 | // 删除主题 48 | func Test_DeleteTopic(t *testing.T) { 49 | account := cmq_go.NewAccount(endpointTopic, secretId, secretKey) 50 | err, _ := account.DeleteTopic("topic-test-001") 51 | if err != nil { 52 | t.Errorf("DeleteTopic failed, %v", err.Error()) 53 | return 54 | } 55 | t.Logf("DeleteTopic, %v", "topic-test-001") 56 | } 57 | 58 | //----------------------------------------------------------------- 59 | // 设置,获取主题属性 60 | func Test_GetSetTopicAttributes(t *testing.T) { 61 | account := cmq_go.NewAccount(endpointTopic, secretId, secretKey) 62 | topic := account.GetTopic("topic-test-001") 63 | t.Logf("GetTopic, %v", *topic) 64 | // get meta 65 | meta, err, _ := topic.GetTopicAttributes() 66 | if err != nil { 67 | t.Errorf("GetTopicAttributes failed, %v", err.Error()) 68 | return 69 | } 70 | t.Logf("GetTopicAttributes, before set, %v", meta) 71 | // set meta 72 | meta.MaxMsgSize = 32768 73 | topic.SetTopicAttributes(meta.MaxMsgSize) 74 | // get meta 75 | newMeta, err, _ := topic.GetTopicAttributes() 76 | if err != nil { 77 | t.Errorf("GetTopicAttributes failed, %v", err.Error()) 78 | return 79 | } 80 | t.Logf("GetTopicAttributes, after set, %v", newMeta) 81 | } 82 | 83 | // 创建订阅者 84 | func Test_CreateSubscribe(t *testing.T) { 85 | account := cmq_go.NewAccount(endpointTopic, secretId, secretKey) 86 | err, _ := account.CreateSubscribe("topic-test-001", "sub-test", "queue-test-001", "queue", "SIMPLIFIED") 87 | if err != nil { 88 | t.Errorf("CreateSubscribe failed, %v", err.Error()) 89 | return 90 | } 91 | t.Logf("CreateSubscribe succeed") 92 | } 93 | 94 | // 获取订阅者 95 | func Test_GetSubscribe(t *testing.T) { 96 | account := cmq_go.NewAccount(endpointTopic, secretId, secretKey) 97 | // get 98 | sub := account.GetSubscription("topic-test-001", "sub-test") 99 | t.Logf("GetSubscription succeed: %v", sub) 100 | 101 | // set meta 102 | meta, err, _ := sub.GetSubscriptionAttributes() 103 | if err != nil { 104 | t.Errorf("CreateSubscribe failed, %v", err.Error()) 105 | return 106 | } 107 | t.Logf("GetSubscriptionAttributes succeed: %v", meta) 108 | } 109 | 110 | // 获取所有主题订阅者 111 | func Test_ListSubscription(t *testing.T) { 112 | account := cmq_go.NewAccount(endpointTopic, secretId, secretKey) 113 | topic := account.GetTopic("topic-test-001") 114 | t.Logf("GetTopic, %v", topic) 115 | 116 | totalCount, subscriptionList, err, _ := topic.ListSubscription(-1, -1, "") 117 | if err != nil { 118 | t.Errorf("ListSubscription failed, %v", err.Error()) 119 | return 120 | } 121 | t.Logf("ListSubscription totalCount, %v", totalCount) 122 | t.Logf("ListSubscription subscriptionList, %v", subscriptionList) 123 | } 124 | 125 | // 发布消息 126 | func Test_PublishMessage(t *testing.T) { 127 | account := cmq_go.NewAccount(endpointTopic, secretId, secretKey) 128 | topic := account.GetTopic("topic-test-001") 129 | t.Logf("GetTopic, %v", topic) 130 | 131 | msgId, err, _ := topic.PublishMessage("hello world") 132 | if err != nil { 133 | t.Errorf("PublishMessage failed, %v", err.Error()) 134 | return 135 | } 136 | t.Logf("PublishMessage msgId, %v", msgId) 137 | } 138 | 139 | // 批量发布消息 140 | func Test_BatchPublishMessage(t *testing.T) { 141 | account := cmq_go.NewAccount(endpointTopic, secretId, secretKey) 142 | topic := account.GetTopic("topic-test-001") 143 | t.Logf("GetTopic, %v", topic) 144 | 145 | msgs := []string{"hello world", "foo", "bar"} 146 | msgIds, err, _ := topic.BatchPublishMessage(msgs) 147 | if err != nil { 148 | t.Errorf("BatchPublishMessage failed, %v", err.Error()) 149 | return 150 | } 151 | t.Logf("BatchPublishMessage msgIds, %v", msgIds) 152 | } 153 | 154 | // 删除主题订阅者 155 | func Test_DeleteSubscription(t *testing.T) { 156 | account := cmq_go.NewAccount(endpointTopic, secretId, secretKey) 157 | err, _ := account.DeleteSubscribe("topic-test-001", "sub-test") 158 | if err != nil { 159 | t.Errorf("DeleteSubscribe failed, %v", err.Error()) 160 | return 161 | } 162 | t.Logf("DeleteSubscribe succeed") 163 | } 164 | -------------------------------------------------------------------------------- /topic.go: -------------------------------------------------------------------------------- 1 | package cmq_go 2 | 3 | import ( 4 | "strconv" 5 | "fmt" 6 | ) 7 | 8 | type Topic struct { 9 | topicName string 10 | client *CMQClient 11 | } 12 | 13 | func NewTopic(topicName string, client *CMQClient) (queue *Topic) { 14 | return &Topic{ 15 | topicName: topicName, 16 | client: client, 17 | } 18 | } 19 | 20 | func (this *Topic) SetTopicAttributes(maxMsgSize int) (err error, code int) { 21 | code = DEFAULT_ERROR_CODE 22 | if maxMsgSize < 1024 || maxMsgSize > 1048576 { 23 | err = fmt.Errorf("Invalid parameter maxMsgSize < 1KB or maxMsgSize > 1024KB") 24 | //log.Printf("%v", err.Error()) 25 | return 26 | } 27 | param := make(map[string]string) 28 | param["topicName"] = this.topicName 29 | if maxMsgSize > 0 { 30 | param["maxMsgSize"] = strconv.Itoa(maxMsgSize) 31 | } 32 | 33 | _, err, code = doCall(this.client, param, "SetTopicAttributes") 34 | if err != nil { 35 | //log.Printf("client.call SetTopicAttributes failed: %v\n", err.Error()) 36 | return 37 | } 38 | return 39 | } 40 | 41 | func (this *Topic) GetTopicAttributes() (meta TopicMeta, err error, code int) { 42 | code = DEFAULT_ERROR_CODE 43 | param := make(map[string]string) 44 | param["topicName"] = this.topicName 45 | 46 | resMap, err, code := doCall(this.client, param, "GetTopicAttributes") 47 | if err != nil { 48 | //log.Printf("client.call GetTopicAttributes failed: %v\n", err.Error()) 49 | return 50 | } 51 | pmeta := NewTopicMeta() 52 | pmeta.MsgCount = int(resMap["msgCount"].(float64)) 53 | pmeta.MaxMsgSize = int(resMap["maxMsgSize"].(float64)) 54 | pmeta.MsgRetentionSeconds = int(resMap["msgRetentionSeconds"].(float64)) 55 | pmeta.CreateTime = int(resMap["createTime"].(float64)) 56 | pmeta.LastModifyTime = int(resMap["lastModifyTime"].(float64)) 57 | 58 | meta = *pmeta 59 | return 60 | } 61 | 62 | func (this *Topic) PublishMessage(message string) (msgId string, err error, code int) { 63 | msgId, err, code = _publishMessage(this.client, this.topicName, message, nil, "") 64 | return 65 | } 66 | 67 | func _publishMessage(client *CMQClient, topicName, msg string, tagList []string, routingKey string) ( 68 | msgId string, err error, code int) { 69 | code = DEFAULT_ERROR_CODE 70 | param := make(map[string]string) 71 | param["topicName"] = topicName 72 | param["msgBody"] = msg 73 | if routingKey != "" { 74 | param["routingKey"] = routingKey 75 | } 76 | if tagList != nil { 77 | for i, tag := range tagList { 78 | param["msgTag."+strconv.Itoa(i+1)] = tag 79 | } 80 | } 81 | resMap, err, code := doCall(client, param, "PublishMessage") 82 | if err != nil { 83 | //log.Printf("client.call GetTopicAttributes failed: %v\n", err.Error()) 84 | return 85 | } 86 | msgId = resMap["msgId"].(string) 87 | 88 | return 89 | } 90 | 91 | func (this *Topic) BatchPublishMessage(msgList []string) (msgIds []string, err error, code int) { 92 | msgIds, err, code = _batchPublishMessage(this.client, this.topicName, msgList, nil, "") 93 | return 94 | } 95 | 96 | func _batchPublishMessage(client *CMQClient, topicName string, msgList, tagList []string, routingKey string) ( 97 | msgIds []string, err error, code int) { 98 | code = DEFAULT_ERROR_CODE 99 | param := make(map[string]string) 100 | param["topicName"] = topicName 101 | if routingKey != "" { 102 | param["routingKey"] = routingKey 103 | } 104 | if msgList != nil { 105 | for i, msg := range msgList { 106 | param["msgBody."+strconv.Itoa(i+1)] = msg 107 | } 108 | } 109 | if tagList != nil { 110 | for i, tag := range tagList { 111 | param["msgTag."+strconv.Itoa(i+1)] = tag 112 | } 113 | } 114 | 115 | resMap, err, code := doCall(client, param, "BatchPublishMessage") 116 | if err != nil { 117 | //log.Printf("client.call BatchPublishMessage failed: %v\n", err.Error()) 118 | return 119 | } 120 | resMsgList := resMap["msgList"].([]interface{}) 121 | for _, v := range resMsgList { 122 | msg := v.(map[string]interface{}) 123 | msgIds = append(msgIds, msg["msgId"].(string)) 124 | } 125 | 126 | return 127 | } 128 | 129 | func (this *Topic) ListSubscription(offset, limit int, searchWord string) (totalCount int, subscriptionList []string, err error, code int) { 130 | code = DEFAULT_ERROR_CODE 131 | param := make(map[string]string) 132 | param["topicName"] = this.topicName 133 | if searchWord != "" { 134 | param["searchWord "] = searchWord 135 | } 136 | if offset >= 0 { 137 | param["offset "] = strconv.Itoa(offset) 138 | } 139 | if limit > 0 { 140 | param["limit "] = strconv.Itoa(limit) 141 | } 142 | 143 | resMap, err, code := doCall(this.client, param, "ListSubscriptionByTopic") 144 | if err != nil { 145 | //log.Printf("client.call ListSubscriptionByTopic failed: %v\n", err.Error()) 146 | return 147 | } 148 | 149 | totalCount = int(resMap["totalCount"].(float64)) 150 | resSubscriptionList := resMap["subscriptionList"].([]interface{}) 151 | for _, v := range resSubscriptionList { 152 | subscribe := v.(map[string]interface{}) 153 | subscriptionList = append(subscriptionList, subscribe["subscriptionName"].(string)) 154 | } 155 | 156 | return 157 | } 158 | -------------------------------------------------------------------------------- /topic_meta.go: -------------------------------------------------------------------------------- 1 | package cmq_go 2 | 3 | type TopicMeta struct { 4 | // 当前该主题的消息堆积数 5 | MsgCount int 6 | // 消息最大长度,取值范围1024-1048576 Byte(即1-1024K),默认1048576 7 | MaxMsgSize int 8 | //消息在主题中最长存活时间,从发送到该主题开始经过此参数指定的时间后, 9 | //不论消息是否被成功推送给用户都将被删除,单位为秒。固定为一天,该属性不能修改。 10 | MsgRetentionSeconds int 11 | //创建时间 12 | CreateTime int 13 | //修改属性信息最近时间 14 | LastModifyTime int 15 | LoggingEnabled int 16 | FilterType int 17 | } 18 | 19 | func NewTopicMeta() *TopicMeta { 20 | return &TopicMeta{ 21 | MsgCount: 0, 22 | MaxMsgSize: 1048576, 23 | MsgRetentionSeconds: 86400, 24 | CreateTime: 0, 25 | LastModifyTime: 0, 26 | LoggingEnabled: 0, 27 | FilterType: 1, 28 | } 29 | } 30 | --------------------------------------------------------------------------------